diff --git a/.bandit b/.bandit index 17c7fbe6c78a..ac77d7fc63cc 100644 --- a/.bandit +++ b/.bandit @@ -6,3 +6,4 @@ # B406 : import_xml_sax # B410 : import_lxml skips: B101,B102,B320,B404,B406,B410 +exclude: **/tests/**,tests diff --git a/.coveragerc b/.coveragerc index c669baf71266..1190ab96c730 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,7 +4,8 @@ branch = true source = cvat/apps/ - utils/cli/ + cvat-sdk/ + cvat-cli/ utils/dataset_manifest omit = diff --git a/.dockerignore b/.dockerignore index afe7b64b36d8..21b715a688b9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,4 +7,4 @@ /db.sqlite3 /keys **/node_modules - +/static diff --git a/.eslintrc.js b/.eslintrc.js index 9487f423f9a3..152821a7d190 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,27 +7,27 @@ module.exports = { env: { node: true, browser: true, - es6: true, + es2020: true, }, parserOptions: { sourceType: 'module', - ecmaVersion: 2018, + parser: '@typescript-eslint/parser', }, ignorePatterns: [ '.eslintrc.js', 'lint-staged.config.js', ], - plugins: ['security', 'no-unsanitized', 'eslint-plugin-header', 'import'], + plugins: ['@typescript-eslint', 'security', 'no-unsanitized', 'import'], extends: [ 'eslint:recommended', 'plugin:security/recommended', 'plugin:no-unsanitized/DOM', 'airbnb-base', 'plugin:import/errors', 'plugin:import/warnings', - 'plugin:import/typescript', + 'plugin:import/typescript', 'plugin:@typescript-eslint/recommended', 'airbnb-typescript/base', ], rules: { - 'header/header': [2, 'line', [{ - pattern: ' {1}Copyright \\(C\\) (?:20\\d{2}-)?2022 Intel Corporation', - template: ' Copyright (C) 2022 Intel Corporation' - }, '', ' SPDX-License-Identifier: MIT']], + // 'header/header': [2, 'line', [{ + // pattern: ' {1}Copyright \\(C\\) (?:20\\d{2}-)?2022 Intel Corporation', + // template: ' Copyright (C) 2022 Intel Corporation' + // }, '', ' SPDX-License-Identifier: MIT']], 'no-plusplus': 0, 'no-continue': 0, 'no-console': 0, @@ -51,5 +51,22 @@ module.exports = { 'security/detect-object-injection': 0, // the rule is relevant for user input data on the node.js environment 'import/order': ['error', {'groups': ['builtin', 'external', 'internal']}], 'import/prefer-default-export': 0, // works incorrect with interfaces + + '@typescript-eslint/ban-ts-comment': 0, + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/indent': ['error', 4], + '@typescript-eslint/lines-between-class-members': 0, + '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }], + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/ban-types': [ + 'error', + { + types: { + '{}': false, // TODO: try to fix with Record + object: false, // TODO: try to fix with Record + Function: false, // TODO: try to fix somehow + }, + }, + ], }, }; diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index b5088a5bfabb..680d3c4caea4 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,11 +1,5 @@ - - ### My actions before raising this issue -- [ ] Read/searched [the docs](https://github.com/opencv/cvat/tree/master#documentation) +- [ ] Read/searched [the docs](https://github.com/cvat-ai/cvat/tree/master#documentation) - [ ] Searched [past issues](/issues) @@ -49,5 +43,3 @@ the bug in --> Logs from `cvat` container -### Next steps -You may [join our Gitter](https://gitter.im/opencv-cvat/public) channel for community support. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f2425456570a..87332b9bbeb9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,13 +1,7 @@ - - - @@ -28,24 +22,18 @@ If an item isn't applicable by a reason then ~~explicitly strikethrough~~ the wh line. If you don't do that github will show an incorrect process for the pull request. If you're unsure about any of these, don't hesitate to ask. We're here to help! --> - [ ] I submit my changes into the `develop` branch -- [ ] I have added a description of my changes into [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file +- [ ] I have added a description of my changes into [CHANGELOG](https://github.com/cvat-ai/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the [documentation]( - https://github.com/opencv/cvat/blob/develop/README.md#documentation) accordingly + https://github.com/cvat-ai/cvat/blob/develop/README.md#documentation) accordingly - [ ] I have added tests to cover my changes - [ ] I have linked related issues ([read github docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) -- [ ] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), -[cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) +- [ ] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/cvat-ai/cvat/tree/develop/cvat-canvas#versioning), +[cvat-core](https://github.com/cvat-ai/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/cvat-ai/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/cvat-ai/cvat/tree/develop/cvat-ui#versioning)) ### License - [ ] I submit _my code changes_ under the same [MIT License]( - https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. + https://github.com/cvat-ai/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. -- [ ] I have updated the license header for each file (see an example below) - -```python -# Copyright (C) 2022 Intel Corporation -# -# SPDX-License-Identifier: MIT -``` + diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml index aec22944cb87..ee383fda7558 100644 --- a/.github/workflows/bandit.yml +++ b/.github/workflows/bandit.yml @@ -1,10 +1,10 @@ -name: Linter +name: Bandit on: pull_request jobs: - Bandit: + Linter: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - id: files uses: jitterbit/get-changed-files@v1 continue-on-error: true @@ -17,7 +17,8 @@ jobs: PR_FILES="$PR_FILES_AM $PR_FILES_RENAMED" for FILE in $PR_FILES; do EXTENSION="${FILE##*.}" - if [[ $EXTENSION == 'py' ]]; then + DIRECTORY="${FILE%%/*}" + if [[ "$EXTENSION" == 'py' && "$DIRECTORY" != 'cvat-sdk' ]]; then CHANGED_FILES+=" $FILE" fi done @@ -32,7 +33,7 @@ jobs: echo "Bandit version: "$(bandit --version | head -1) echo "The files will be checked: "$(echo $CHANGED_FILES) - bandit $CHANGED_FILES --exclude '**/tests/**' -a file --ini ./.bandit -f html -o ./bandit_report/bandit_checks.html + bandit -a file --ini .bandit -f html -o ./bandit_report/bandit_checks.html $CHANGED_FILES deactivate else echo "No files with the \"py\" extension found" @@ -40,7 +41,7 @@ jobs: - name: Upload artifacts if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.1 with: name: bandit_report path: bandit_report diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml new file mode 100644 index 000000000000..b0793bfb6eb4 --- /dev/null +++ b/.github/workflows/black.yml @@ -0,0 +1,82 @@ +name: Black +on: pull_request +jobs: + Linter: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - id: files + uses: jitterbit/get-changed-files@v1 + continue-on-error: true + + - name: Run checks + env: + PR_FILES_AM: ${{ steps.files.outputs.added_modified }} + PR_FILES_RENAMED: ${{ steps.files.outputs.renamed }} + run: | + # If different modules use different Black configs, + # we need to run Black for each python component group separately. + # Otherwise, they all will use the same config. + ENABLED_DIRS=("cvat-sdk" "cvat-cli" "tests/python") + + isValueIn () { + # Checks if a value is in an array + # https://stackoverflow.com/a/8574392 + # args: value, array + local e match="$1" + shift + for e; do + [[ "$e" == "$match" ]] && return 0; + done + return 1 + } + + startswith () { + # Inspired by https://stackoverflow.com/a/2172367 + # Checks if the first arg starts with the second one + local value="$1" + local beginning="$2" + return $([[ $value == ${beginning}* ]]) + } + + PR_FILES="$PR_FILES_AM $PR_FILES_RENAMED" + UPDATED_DIRS="" + for FILE in $PR_FILES; do + EXTENSION="${FILE##*.}" + DIRECTORY="$(dirname $FILE)" + if [[ "$EXTENSION" == "py" ]]; then + for EDIR in ${ENABLED_DIRS[@]}; do + if startswith "${DIRECTORY}/" "${EDIR}/" && ! isValueIn "${EDIR}" ${UPDATED_DIRS[@]}; + then + UPDATED_DIRS+=" ${EDIR}" + fi + done + fi + done + + if [[ ! -z $UPDATED_DIRS ]]; then + sudo apt-get --no-install-recommends install -y build-essential curl python3-dev python3-pip python3-venv + python3 -m venv .env + . .env/bin/activate + pip install -U pip wheel setuptools + pip install $(egrep "black.*" ./cvat-cli/requirements/development.txt) + mkdir -p black_report + + echo "Black version: "$(black --version) + echo "The dirs will be checked: $UPDATED_DIRS" + EXIT_CODE=0 + for DIR in $UPDATED_DIRS; do + black --check --diff $DIR >> ./black_report/black_checks.txt || EXIT_CODE=$(($? | $EXIT_CODE)) || true + done + deactivate + exit $EXIT_CODE + else + echo "No files with the \"py\" extension found" + fi + + - name: Upload artifacts + if: failure() + uses: actions/upload-artifact@v3.1.1 + with: + name: black_report + path: black_report diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml index 7401b3d5de60..226a7d04ebef 100644 --- a/.github/workflows/cache.yml +++ b/.github/workflows/cache.yml @@ -6,27 +6,74 @@ on: jobs: Caching_CVAT: + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v2 + - uses: actions/checkout@v3 + + - name: Getting SHA with cache from the default branch + id: get-sha + run: | + DEFAULT_BRANCH=$(gh api /repos/$REPO | jq -r '.default_branch') + for sha in $(gh api "/repos/$REPO/commits?per_page=100&sha=${DEFAULT_BRANCH}" | jq -r '.[].sha'); + do + RUN_status=$(gh api /repos/${REPO}/actions/workflows/cache.yml/runs | \ + jq -r ".workflow_runs[]? | select((.head_sha == \"${sha}\") and (.conclusion == \"success\")) | .status") + + if [[ ${RUN_status} == "completed" ]]; then + SHA=$sha + break + fi + done + + echo Default branch is ${DEFAULT_BRANCH} + echo Workflow will try to get cache from commit: ${SHA} + + echo "default_branch=${DEFAULT_BRANCH}" >> $GITHUB_OUTPUT + echo "sha=${SHA}" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + id: server-cache-action with: path: /tmp/cvat_cache_server key: ${{ runner.os }}-build-server-${{ github.sha }} restore-keys: | + ${{ runner.os }}-build-server-${{ steps.get-sha.outputs.sha }} ${{ runner.os }}-build-server- - - uses: actions/cache@v2 + - uses: actions/cache@v3 + id: ui-cache-action with: path: /tmp/cvat_cache_ui key: ${{ runner.os }}-build-ui-${{ github.sha }} restore-keys: | + ${{ runner.os }}-build-ui-${{ steps.get-sha.outputs.sha }} ${{ runner.os }}-build-ui- + - uses: actions/cache@v3 + id: elasticsearch-cache-action + with: + path: /tmp/cvat_cache_elasticsearch + key: ${{ runner.os }}-build-elasticsearch-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-build-elasticsearch-${{ steps.get-sha.outputs.sha }} + ${{ runner.os }}-build-elasticsearch- + + - uses: actions/cache@v3 + id: logstash-cache-action + with: + path: /tmp/cvat_cache_logstash + key: ${{ runner.os }}-build-logstash-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-build-logstash-${{ steps.get-sha.outputs.sha }} + ${{ runner.os }}-build-logstash- + - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1.1.2 + uses: docker/setup-buildx-action@v2 - - name: Caching CVAT server + - name: Caching CVAT Server uses: docker/build-push-action@v2 with: context: . @@ -42,9 +89,34 @@ jobs: cache-from: type=local,src=/tmp/cvat_cache_ui cache-to: type=local,dest=/tmp/cvat_cache_ui-new + - name: Caching CVAT Elasticsearch + uses: docker/build-push-action@v2 + with: + context: ./components/analytics/elasticsearch/ + file: ./components/analytics/elasticsearch/Dockerfile + cache-from: type=local,src=/tmp/cvat_cache_elasticsearch + cache-to: type=local,dest=/tmp/cvat_cache_elasticsearch-new + build-args: ELK_VERSION=6.8.23 + + - name: Caching CVAT Logstash + uses: docker/build-push-action@v2 + with: + context: ./components/analytics/logstash/ + file: ./components/analytics/logstash/Dockerfile + cache-from: type=local,src=/tmp/cvat_cache_logstash + cache-to: type=local,dest=/tmp/cvat_cache_logstash-new + build-args: ELK_VERSION=6.8.23 + - name: Moving cache run: | rm -rf /tmp/cvat_cache_server mv /tmp/cvat_cache_server-new /tmp/cvat_cache_server + rm -rf /tmp/cvat_cache_ui mv /tmp/cvat_cache_ui-new /tmp/cvat_cache_ui + + rm -rf /tmp/cvat_cache_elasticsearch + mv /tmp/cvat_cache_elasticsearch-new /tmp/cvat_cache_elasticsearch + + rm -rf /tmp/cvat_cache_logstash + mv /tmp/cvat_cache_logstash-new /tmp/cvat_cache_logstash diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml new file mode 100644 index 000000000000..0b0c8544d178 --- /dev/null +++ b/.github/workflows/cleanup.yml @@ -0,0 +1,21 @@ +# Workflow deletes image artifacts that created by CI workflow +name: Delete image artifacts +on: + workflow_run: + workflows: [CI, Comment, Full] + types: + - completed + +jobs: + cleanup: + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Clean up + run: | + wri=${{ github.event.workflow_run.id }} + for ai in $(gh api /repos/${{ github.repository }}/actions/runs/$wri/artifacts | jq '.artifacts[] | select( .name | startswith("cvat")) | .id'); + do + gh api --method DELETE /repos/${{ github.repository }}/actions/artifacts/$ai + done diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 749dcbbe484d..054a77ab02b0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,12 +13,12 @@ name: "CodeQL" on: push: - branches: [ develop, hotfix-*, master, release-* ] + branches: [ "develop", master, release-* ] pull_request: # The branches below must be a subset of the branches above - branches: [ develop ] + branches: [ "develop" ] schedule: - - cron: '25 19 * * 6' + - cron: '27 19 * * 4' jobs: analyze: @@ -33,39 +33,40 @@ jobs: fail-fast: false matrix: language: [ 'javascript', 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + # 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@v1 + uses: github/codeql-action/autobuild@v2 # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - #- run: | - # make bootstrap - # make release + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/comment.yml b/.github/workflows/comment.yml new file mode 100644 index 000000000000..e2a478c9d660 --- /dev/null +++ b/.github/workflows/comment.yml @@ -0,0 +1,89 @@ +name: Comment +on: + issue_comment: + types: [created] + +env: + WORKFLOW_RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + +jobs: + verify_author: + if: contains(github.event.issue.html_url, '/pull') && + contains(github.event.comment.body, '/check') + runs-on: ubuntu-latest + outputs: + ref: ${{ steps.get-ref.outputs.ref }} + cid: ${{ steps.send-status.outputs.cid }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Check author of comment + id: check-author + run: | + PERM=$(gh api repos/${{ github.repository }}/collaborators/${{ github.event.comment.user.login }}/permission | jq -r '.permission') + if [[ $PERM == "write" || $PERM == "maintain" || $PERM == "admin" ]]; + then + ALLOW="true" + fi + echo "allow=${ALLOW}" >> $GITHUB_OUTPUT + + - name: Verify that author of comment is collaborator + if: steps.check-author.outputs.allow == '' + uses: actions/github-script@v3 + with: + script: | + core.setFailed('User that send comment with /check command is not collaborator') + + - name: Get branch name + id: get-ref + run: | + SHA=$(gh api /repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} | jq -r '.head.sha') + echo "ref=${SHA}" >> $GITHUB_OUTPUT + + - name: Send comment. Test are executing + id: send-status + run: | + BODY=":hourglass: Tests are executing, see more information [here](${{ env.WORKFLOW_RUN_URL }})" + BODY=$BODY"\n :warning: Cancel [this](${{ env.WORKFLOW_RUN_URL }}) workflow manually first, if you want to restart full check" + BODY=$(echo -e $BODY) + + COMMENT_ID=$(gh api --method POST \ + /repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments \ + -f body="${BODY}" | jq '.id') + + echo "cid=${COMMENT_ID}" >> $GITHUB_OUTPUT + + run-full: + needs: verify_author + uses: ./.github/workflows/full.yml + with: + ref: ${{ needs.verify_author.outputs.ref }} + + send_status: + runs-on: ubuntu-latest + needs: [run-full, verify_author] + if: needs.run-full.result != 'skipped' && always() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Send status in comments + run: | + BODY="" + + if [[ "${{ needs.run-full.result }}" == "failure" ]] + then + BODY=":x: Some checks failed" + elif [[ "${{ needs.run-full.result }}" == "success" ]] + then + BODY=":heavy_check_mark: All checks completed successfully" + elif [[ "${{ needs.run-full.result }}" == "cancelled" ]] + then + BODY=":no_entry_sign: Workflows has been canceled" + fi + + BODY=$BODY"\n :page_facing_up: See logs [here](${WORKFLOW_RUN_URL})" + BODY=$(echo -e $BODY) + + gh api --method PATCH \ + /repos/${{ github.repository }}/issues/comments/${{ needs.verify_author.outputs.cid }} \ + -f body="${BODY}" \ No newline at end of file diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 00849dd0f8a3..a443931a2e2b 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -1,11 +1,11 @@ -name: Linter +name: ESLint on: pull_request jobs: - ESLint: + Linter: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: '16.x' - id: files @@ -26,9 +26,8 @@ jobs: done if [[ ! -z $CHANGED_FILES ]]; then - npm ci - cd tests && npm ci && cd .. - npm install eslint-detailed-reporter --save-dev --legacy-peer-deps + yarn install --frozen-lockfile && cd tests && yarn install --frozen-lockfile && cd .. + yarn add eslint-detailed-reporter -D -W mkdir -p eslint_report echo "ESLint version: "$(npx eslint --version) @@ -40,7 +39,7 @@ jobs: - name: Upload artifacts if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.1 with: name: eslint_report path: eslint_report diff --git a/.github/workflows/full.yml b/.github/workflows/full.yml new file mode 100644 index 000000000000..7772637f6c4c --- /dev/null +++ b/.github/workflows/full.yml @@ -0,0 +1,392 @@ +name: Full +on: + workflow_call: + inputs: + ref: + type: string + required: true + workflow_dispatch: + inputs: + ref: + type: string + required: true + +env: + WORKFLOW_RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + CYPRESS_VERIFY_TIMEOUT: 180000 # https://docs.cypress.io/guides/guides/command-line#cypress-verify + +jobs: + search_cache: + runs-on: ubuntu-latest + outputs: + sha: ${{ steps.get-sha.outputs.sha}} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + steps: + - name: Getting SHA with cache from the default branch + id: get-sha + run: | + DEFAULT_BRANCH=$(gh api /repos/$REPO | jq -r '.default_branch') + for sha in $(gh api "/repos/$REPO/commits?per_page=100&sha=$DEFAULT_BRANCH" | jq -r '.[].sha'); + do + RUN_status=$(gh api /repos/${REPO}/actions/workflows/cache.yml/runs | \ + jq -r ".workflow_runs[]? | select((.head_sha == \"${sha}\") and (.conclusion == \"success\")) | .status") + if [[ ${RUN_status} == "completed" ]]; then + SHA=$sha + break + fi + done + echo Default branch is ${DEFAULT_BRANCH} + echo Workflow will try to get cache from commit: ${SHA} + + echo "default_branch=${DEFAULT_BRANCH}" >> $GITHUB_OUTPUT + echo "sha=${SHA}" >> $GITHUB_OUTPUT + + build: + needs: search_cache + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - name: CVAT server. Getting cache from the default branch + uses: actions/cache@v3 + with: + path: /tmp/cvat_cache_server + key: ${{ runner.os }}-build-server-${{ needs.search_cache.outputs.sha }} + + - name: CVAT UI. Getting cache from the default branch + uses: actions/cache@v3 + with: + path: /tmp/cvat_cache_ui + key: ${{ runner.os }}-build-ui-${{ needs.search_cache.outputs.sha }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Create artifact directories + run: | + mkdir /tmp/cvat_server + mkdir /tmp/cvat_ui + mkdir /tmp/cvat_sdk + + - name: CVAT server. Build and push + uses: docker/build-push-action@v3 + with: + cache-from: type=local,src=/tmp/cvat_cache_server + context: . + file: Dockerfile + tags: cvat/server + outputs: type=docker,dest=/tmp/cvat_server/image.tar + + - name: CVAT UI. Build and push + uses: docker/build-push-action@v3 + with: + cache-from: type=local,src=/tmp/cvat_cache_ui + context: . + file: Dockerfile.ui + tags: cvat/ui + outputs: type=docker,dest=/tmp/cvat_ui/image.tar + + - name: CVAT SDK. Build + run: | + docker load --input /tmp/cvat_server/image.tar + docker run --rm -v ${PWD}/cvat-sdk/schema/:/transfer \ + --entrypoint /bin/bash -u root cvat/server \ + -c 'python manage.py spectacular --file /transfer/schema.yml' + pip3 install --user -r cvat-sdk/gen/requirements.txt + cd cvat-sdk/ + gen/generate.sh + cd .. + + cp -r cvat-sdk/* /tmp/cvat_sdk/ + + - name: Upload CVAT server artifact + uses: actions/upload-artifact@v3 + with: + name: cvat_server + path: /tmp/cvat_server/image.tar + + - name: Upload CVAT UI artifact + uses: actions/upload-artifact@v3 + with: + name: cvat_ui + path: /tmp/cvat_ui/image.tar + + - name: Upload CVAT SDK artifact + uses: actions/upload-artifact@v3 + with: + name: cvat_sdk + path: /tmp/cvat_sdk/ + + rest_api: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - uses: actions/setup-python@v3 + with: + python-version: '3.8' + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@master + + - name: Getting CVAT Elasticsearch cache from the default branch + uses: actions/cache@v3 + with: + path: /tmp/cvat_cache_elasticsearch + key: ${{ runner.os }}-build-elasticsearch-${{ needs.search_cache.outputs.sha }} + + - name: Getting CVAT Logstash cache from the default branch + uses: actions/cache@v3 + with: + path: /tmp/cvat_cache_logstash + key: ${{ runner.os }}-build-logstash-${{ needs.search_cache.outputs.sha }} + + - name: Building CVAT Elasticsearch + uses: docker/build-push-action@v2 + with: + context: ./components/analytics/elasticsearch/ + file: ./components/analytics/elasticsearch/Dockerfile + cache-from: type=local,src=/tmp/cvat_cache_elasticsearch + tags: cvat_elasticsearch:latest + load: true + build-args: ELK_VERSION=6.8.23 + + - name: Building CVAT Logstash + uses: docker/build-push-action@v2 + with: + context: ./components/analytics/logstash/ + file: ./components/analytics/logstash/Dockerfile + cache-from: type=local,src=/tmp/cvat_cache_logstash + tags: cvat_logstash:latest + load: true + build-args: ELK_VERSION=6.8.23 + + - name: Download CVAT server image + uses: actions/download-artifact@v3 + with: + name: cvat_server + path: /tmp/cvat_server/ + + - name: Download CVAT UI images + uses: actions/download-artifact@v3 + with: + name: cvat_ui + path: /tmp/cvat_ui/ + + - name: Download CVAT SDK package + uses: actions/download-artifact@v3 + with: + name: cvat_sdk + path: /tmp/cvat_sdk/ + + - name: Load Docker images + run: | + docker load --input /tmp/cvat_server/image.tar + docker load --input /tmp/cvat_ui/image.tar + docker tag cvat/server:latest cvat/server:dev + docker tag cvat/ui:latest cvat/ui:dev + docker image ls -a + + - name: Running REST API and SDK tests + run: | + pip3 install --user '/tmp/cvat_sdk/[pytorch]' + pip3 install --user cvat-cli/ + pip3 install --user -r tests/python/requirements.txt + pytest tests/python -s -v + + - name: Creating a log file from cvat containers + if: failure() + env: + LOGS_DIR: "${{ github.workspace }}/rest_api" + run: | + mkdir $LOGS_DIR + docker logs test_cvat_server_1 > $LOGS_DIR/cvat_server.log + docker logs test_cvat_worker_default_1 > $LOGS_DIR/cvat_worker_default.log + docker logs test_cvat_opa_1 2> $LOGS_DIR/cvat_opa.log + + - name: Uploading "cvat" container logs as an artifact + if: failure() + uses: actions/upload-artifact@v3.1.1 + with: + name: rest_api_container_logs + path: "${{ github.workspace }}/rest_api" + + unit_testing: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@master + + - name: Download CVAT server image + uses: actions/download-artifact@v3 + with: + name: cvat_server + path: /tmp/cvat_server/ + + - name: Load Docker images + run: | + docker load --input /tmp/cvat_server/image.tar + docker tag cvat/server:latest cvat/server:dev + docker image ls -a + + - name: Running OPA tests + run: | + python cvat/apps/iam/rules/tests/generate_tests.py \ + --output-dir cvat/apps/iam/rules/ + + curl -L -o opa https://openpolicyagent.org/downloads/v0.34.2/opa_linux_amd64_static + chmod +x ./opa + ./opa test cvat/apps/iam/rules + + - name: Running unit tests + env: + HOST_COVERAGE_DATA_DIR: ${{ github.workspace }} + CONTAINER_COVERAGE_DATA_DIR: "/coverage_data" + run: | + docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d cvat_opa cvat_server + max_tries=12 + while [[ $(curl -s -o /dev/null -w "%{http_code}" localhost:8181/health?bundles) != "200" && max_tries -gt 0 ]]; do (( max_tries-- )); sleep 5; done + + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash \ + -c 'python manage.py test cvat/apps -v 2' + + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash \ + -c 'yarn --frozen-lockfile --ignore-scripts && yarn workspace cvat-core run test' + + - name: Creating a log file from cvat containers + if: failure() + env: + LOGS_DIR: "${{ github.workspace }}/unit_testing" + run: | + mkdir $LOGS_DIR + docker logs cvat_server > $LOGS_DIR/cvat_server.log + docker logs cvat_opa 2> $LOGS_DIR/cvat_opa.log + + - name: Uploading "cvat" container logs as an artifact + if: failure() + uses: actions/upload-artifact@v3.1.1 + with: + name: unit_tests_container_logs + path: "${{ github.workspace }}/unit_testing" + + e2e_testing: + needs: build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + specs: ['actions_tasks', 'actions_tasks2', 'actions_tasks3', + 'actions_objects', 'actions_objects2', 'actions_users', + 'actions_projects_models', 'actions_organizations', 'canvas3d_functionality', + 'canvas3d_functionality_2', 'issues_prs', 'issues_prs2', 'masks', 'skeletons'] + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - uses: actions/setup-node@v3 + with: + node-version: '16.x' + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@master + + - name: Download CVAT server image + uses: actions/download-artifact@v3 + with: + name: cvat_server + path: /tmp/cvat_server/ + + - name: Download CVAT UI image + uses: actions/download-artifact@v3 + with: + name: cvat_ui + path: /tmp/cvat_ui/ + + - name: Load Docker images + run: | + docker load --input /tmp/cvat_server/image.tar + docker load --input /tmp/cvat_ui/image.tar + docker tag cvat/server:latest cvat/server:dev + docker tag cvat/ui:latest cvat/ui:dev + docker image ls -a + + - name: Run CVAT instance + run: | + docker-compose \ + -f docker-compose.yml \ + -f docker-compose.dev.yml \ + -f components/serverless/docker-compose.serverless.yml \ + -f tests/docker-compose.minio.yml \ + -f tests/docker-compose.file_share.yml up -d + + - name: Waiting for server + env: + API_ABOUT_PAGE: "localhost:8080/api/server/about" + run: | + max_tries=60 + status_code=$(curl -s -o /tmp/server_response -w "%{http_code}" ${API_ABOUT_PAGE}) + while [[ $status_code != "200" && max_tries -gt 0 ]] + do + echo Number of attempts left: $max_tries + echo Status code of response: $status_code + sleep 5 + status_code=$(curl -s -o /tmp/server_response -w "%{http_code}" ${API_ABOUT_PAGE}) + (( max_tries-- )) + done + + - name: Run E2E tests + env: + DJANGO_SU_NAME: 'admin' + DJANGO_SU_EMAIL: 'admin@localhost.company' + DJANGO_SU_PASSWORD: '12qwaszx' + run: | + docker exec -i cvat_server /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" + cd ./tests + yarn --frozen-lockfile + + shopt -s extglob + if [[ ${{ matrix.specs }} == canvas3d_* ]]; then + npx cypress run \ + --headed \ + --browser chrome \ + --env coverage=false \ + --config-file cypress_canvas3d.json \ + --spec 'cypress/integration/${{ matrix.specs }}/**/*.js,cypress/integration/remove_users_tasks_projects_organizations.js' + else + npx cypress run \ + --browser chrome \ + --env coverage=false \ + --spec 'cypress/integration/${{ matrix.specs }}/**/*.js,cypress/integration/remove_users_tasks_projects_organizations.js' + fi + + - name: Creating a log file from "cvat" container logs + if: failure() + run: | + docker logs cvat_server > ${{ github.workspace }}/tests/cvat_${{ matrix.specs }}.log + + - name: Uploading "cvat" container logs as an artifact + if: failure() + uses: actions/upload-artifact@v3.1.1 + with: + name: e2e_container_logs + path: ${{ github.workspace }}/tests/cvat_${{ matrix.specs }}.log + + - name: Uploading cypress screenshots as an artifact + if: failure() + uses: actions/upload-artifact@v3.1.1 + with: + name: cypress_screenshots_${{ matrix.specs }} + path: ${{ github.workspace }}/tests/cypress/screenshots diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml deleted file mode 100644 index 17fb8880f11a..000000000000 --- a/.github/workflows/github_pages.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Github pages - -on: - push: - branches: - - develop - -jobs: - deploy: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - fetch-depth: 0 - - - name: Setup Hugo - uses: peaceiris/actions-hugo@v2 - with: - hugo-version: '0.83.1' - extended: true - - - name: Setup Node - uses: actions/setup-node@v2 - with: - node-version: '16.x' - - - name: Install npm packages - working-directory: ./site - run: | - npm ci - - - name: Build docs - run: | - pip install gitpython packaging toml - python site/build_docs.py - - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./public - force_orphan: true diff --git a/.github/workflows/hadolint.yml b/.github/workflows/hadolint.yml index 85e327d9d772..808e8cacd243 100644 --- a/.github/workflows/hadolint.yml +++ b/.github/workflows/hadolint.yml @@ -1,10 +1,10 @@ -name: Linter +name: HadoLint on: pull_request jobs: - HadoLint: + Linter: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - id: files uses: jitterbit/get-changed-files@v1 continue-on-error: true @@ -47,7 +47,7 @@ jobs: - name: Upload artifacts if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.1 with: name: hadolint_report path: hadolint_report diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml new file mode 100644 index 000000000000..c541bfac9549 --- /dev/null +++ b/.github/workflows/helm.yml @@ -0,0 +1,109 @@ +name: Helm +on: + pull_request: + types: [edited, ready_for_review, opened, synchronize, reopened] + workflow_dispatch: + +jobs: + check_changes: + runs-on: ubuntu-latest + outputs: + helm_dir_changed: ${{ steps.check_updates.outputs.helm_dir_changed }} + steps: + - uses: jitterbit/get-changed-files@v1 + id: files + continue-on-error: true + + - name: Run check + id: check_updates + env: + PR_FILES_AM: ${{ steps.files.outputs.added_modified }} + PR_FILES_RENAMED: ${{ steps.files.outputs.renamed }} + run: | + PR_FILES="$PR_FILES_AM $PR_FILES_RENAMED" + for FILE in $PR_FILES; do + if [[ $FILE == helm-chart/* ]] ; then + echo "helm_dir_changed=true" >> $GITHUB_OUTPUT + break + fi + done + + testing: + needs: check_changes + if: needs.check_changes.outputs.helm_dir_changed == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Start minikube + uses: medyagh/setup-minikube@master + with: + cpus: max + memory: max + + - name: Try the cluster! + run: kubectl get pods -A + + - name: Build images + run: | + export SHELL=/bin/bash + eval $(minikube -p minikube docker-env) + docker compose -f docker-compose.yml -f docker-compose.dev.yml build + echo -n "verifying images:" + docker images + + - uses: azure/setup-helm@v3 + with: + version: 'v3.9.4' + + - name: Deploy to minikube + run: | + printf "traefik:\n service:\n externalIPs:\n - $(minikube ip)\n" >> tests/values.test.yaml + find cvat/apps/iam/rules -name "*.rego" -and ! -name '*test*' -exec basename {} \; | tar -czf helm-chart/rules.tar.gz -C cvat/apps/iam/rules/ -T - + cd helm-chart + helm dependency update + cd .. + helm upgrade -n default release-${{ github.run_id }}-${{ github.run_attempt }} -i --create-namespace helm-chart -f helm-chart/values.yaml -f tests/values.test.yaml + + - name: Update test config + run: | + sed -i -e 's$http://localhost:8080$http://cvat.local:80$g' tests/python/shared/utils/config.py + find tests/python/shared/assets/ -type f -name '*.json' | xargs sed -i -e 's$http://localhost:8080$http://cvat.local$g' + echo "$(minikube ip) cvat.local" | sudo tee -a /etc/hosts + + - name: Wait for CVAT to be ready + run: | + max_tries=60 + while [[ $(kubectl get pods -l component=server -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" && max_tries -gt 0 ]]; do echo "waiting for CVAT pod" && (( max_tries-- )) && sleep 5; done + while [[ $(kubectl get pods -l app.kubernetes.io/name=postgresql -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" && max_tries -gt 0 ]]; do echo "waiting for DB pod" && (( max_tries-- )) && sleep 5; done + while [[ $(curl -s -o /tmp/server_response -w "%{http_code}" cvat.local/api/server/about) != "200" && max_tries -gt 0 ]]; do echo "waiting for CVAT" && (( max_tries-- )) && sleep 5; done + kubectl get pods + kubectl logs $(kubectl get pods -l component=server -o jsonpath='{.items[0].metadata.name}') + + + - name: Generate schema + run: | + mkdir cvat-sdk/schema + kubectl exec $(kubectl get pods -l component=server -o jsonpath='{.items[0].metadata.name}') -- /bin/bash -c "python manage.py spectacular --file /tmp/schema.yml" + kubectl cp $(kubectl get pods -l component=server -o jsonpath='{.items[0].metadata.name}'):/tmp/schema.yml cvat-sdk/schema/schema.yml + pip3 install --user -r cvat-sdk/gen/requirements.txt + cd cvat-sdk/ + gen/generate.sh + cd .. + + - name: Install test requrements + run: | + pip3 install --user cvat-sdk/ + pip3 install --user cvat-cli/ + pip3 install --user -r tests/python/requirements.txt + + - name: REST API and SDK tests + run: | + kubectl cp tests/mounted_file_share/images $(kubectl get pods -l component=server -o jsonpath='{.items[0].metadata.name}'):/home/django/share + pytest --platform=kube \ + --ignore=tests/python/rest_api/test_cloud_storages.py \ + --ignore=tests/python/rest_api/test_analytics.py \ + --ignore=tests/python/rest_api/test_resource_import_export.py \ + --ignore=tests/python/rest_api/test_webhooks_sender.py \ + -k 'not create_task_with_cloud_storage_files' \ + tests/python diff --git a/.github/workflows/isort.yml b/.github/workflows/isort.yml new file mode 100644 index 000000000000..007976893202 --- /dev/null +++ b/.github/workflows/isort.yml @@ -0,0 +1,82 @@ +name: isort +on: pull_request +jobs: + Linter: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - id: files + uses: jitterbit/get-changed-files@v1 + continue-on-error: true + + - name: Run checks + env: + PR_FILES_AM: ${{ steps.files.outputs.added_modified }} + PR_FILES_RENAMED: ${{ steps.files.outputs.renamed }} + run: | + # If different modules use different isort configs, + # we need to run isort for each python component group separately. + # Otherwise, they all will use the same config. + ENABLED_DIRS=("cvat-sdk" "cvat-cli" "tests/python") + + isValueIn () { + # Checks if a value is in an array + # https://stackoverflow.com/a/8574392 + # args: value, array + local e match="$1" + shift + for e; do + [[ "$e" == "$match" ]] && return 0; + done + return 1 + } + + startswith () { + # Inspired by https://stackoverflow.com/a/2172367 + # Checks if the first arg starts with the second one + local value="$1" + local beginning="$2" + return $([[ $value == ${beginning}* ]]) + } + + PR_FILES="$PR_FILES_AM $PR_FILES_RENAMED" + UPDATED_DIRS="" + for FILE in $PR_FILES; do + EXTENSION="${FILE##*.}" + DIRECTORY="$(dirname $FILE)" + if [[ "$EXTENSION" == "py" ]]; then + for EDIR in ${ENABLED_DIRS[@]}; do + if startswith "${DIRECTORY}/" "${EDIR}/" && ! isValueIn "${EDIR}" ${UPDATED_DIRS[@]}; + then + UPDATED_DIRS+=" ${EDIR}" + fi + done + fi + done + + if [[ ! -z $UPDATED_DIRS ]]; then + sudo apt-get --no-install-recommends install -y build-essential curl python3-dev python3-pip python3-venv + python3 -m venv .env + . .env/bin/activate + pip install -U pip wheel setuptools + pip install $(egrep "isort.*" ./cvat-cli/requirements/development.txt) + mkdir -p isort_report + + echo "isort version: $(isort --version-number)" + echo "The dirs will be checked: $UPDATED_DIRS" + EXIT_CODE=0 + for DIR in $UPDATED_DIRS; do + isort --check $DIR >> ./isort_report/isort_checks.txt || EXIT_CODE=$(($? | $EXIT_CODE)) || true + done + deactivate + exit $EXIT_CODE + else + echo "No files with the \"py\" extension found" + fi + + - name: Upload artifacts + if: failure() + uses: actions/upload-artifact@v3.1.1 + with: + name: isort_report + path: isort_report diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5b6023df1a49..1e59492d8bc0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,276 +6,450 @@ on: - 'develop' pull_request: types: [edited, ready_for_review, opened, synchronize, reopened] + paths-ignore: + - 'site/**' + - '**/*.md' + +env: + CYPRESS_VERIFY_TIMEOUT: 180000 # https://docs.cypress.io/guides/guides/command-line#cypress-verify + CVAT_VERSION: "local" jobs: - Unit_testing: - if: | - github.event.pull_request.draft == false && - !startsWith(github.event.pull_request.title, '[WIP]') && - !startsWith(github.event.pull_request.title, '[Dependent]') - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: '3.8' - - name: Getting SHA from the default branch + search_cache: + if: | + github.event.pull_request.draft == false && + !startsWith(github.event.pull_request.title, '[WIP]') && + !startsWith(github.event.pull_request.title, '[Dependent]') + runs-on: ubuntu-latest + outputs: + sha: ${{ steps.get-sha.outputs.sha}} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + steps: + - name: Getting SHA with cache from the default branch id: get-sha run: | - URL_get_default_branch="https://api.github.com/repos/${{ github.repository }}" - DEFAULT_BRANCH=$(curl -s -X GET -G ${URL_get_default_branch} | jq -r '.default_branch') - URL_get_sha_default_branch="https://api.github.com/repos/${{ github.repository }}/git/ref/heads/${DEFAULT_BRANCH}" - SHA=$(curl -s -X GET -G ${URL_get_sha_default_branch} | jq .object.sha | tr -d '"') - echo ::set-output name=default_branch::${DEFAULT_BRANCH} - echo ::set-output name=sha::${SHA} - - name: Waiting a cache creation in the default branch - run: | - URL_runs="https://api.github.com/repos/${{ github.repository }}/actions/workflows/cache.yml/runs" - SLEEP=45 - NUMBER_ATTEMPTS=10 - while [[ ${NUMBER_ATTEMPTS} -gt 0 ]]; do - RUN_status=$(curl -s -X GET -G ${URL_runs} | jq -r '.workflow_runs[]? | select((.head_sha == "${{ steps.get-sha.outputs.sha }}") and (.event == "push") and (.name == "Cache") and (.head_branch == "${{ steps.get-sha.outputs.default_branch }}")) | .status') + DEFAULT_BRANCH=$(gh api /repos/$REPO | jq -r '.default_branch') + for sha in $(gh api "/repos/$REPO/commits?per_page=100&sha=$DEFAULT_BRANCH" | jq -r '.[].sha'); + do + RUN_status=$(gh api /repos/${REPO}/actions/workflows/cache.yml/runs | \ + jq -r ".workflow_runs[]? | select((.head_sha == \"${sha}\") and (.conclusion == \"success\")) | .status") + if [[ ${RUN_status} == "completed" ]]; then - echo "The cache creation on the '${{ steps.get-sha.outputs.default_branch }}' branch has finished. Status: ${RUN_status}" + SHA=$sha break - else - echo "The creation of the cache is not yet complete." - echo "There are still attempts to check the cache: ${NUMBER_ATTEMPTS}" - echo "Status of caching in the '${{ steps.get-sha.outputs.default_branch }}' branch: ${RUN_status}" - echo "sleep ${SLEEP}" - sleep ${SLEEP} - ((NUMBER_ATTEMPTS--)) fi done - if [[ ${NUMBER_ATTEMPTS} -eq 0 ]]; then - echo "Number of attempts expired!" - echo "Probably the creation of the cache is not yet complete. Will continue working without the cache." - fi - - name: Getting CVAT server cache from the default branch - uses: actions/cache@v2 + + echo Default branch is ${DEFAULT_BRANCH} + echo Workflow will try to get cache from commit: ${SHA} + + echo "default_branch=${DEFAULT_BRANCH}" >> $GITHUB_OUTPUT + echo "sha=${SHA}" >> $GITHUB_OUTPUT + + build: + needs: search_cache + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: CVAT server. Getting cache from the default branch + uses: actions/cache@v3 with: path: /tmp/cvat_cache_server - key: ${{ runner.os }}-build-server-${{ steps.get-sha.outputs.sha }} + key: ${{ runner.os }}-build-server-${{ needs.search_cache.outputs.sha }} + + - name: CVAT UI. Getting cache from the default branch + uses: actions/cache@v3 + with: + path: /tmp/cvat_cache_ui + key: ${{ runner.os }}-build-ui-${{ needs.search_cache.outputs.sha }} + - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1.1.2 - - name: Building CVAT server image - uses: docker/build-push-action@v2 + uses: docker/setup-buildx-action@v2 + + - name: Create artifact directories + run: | + mkdir /tmp/cvat_server + mkdir /tmp/cvat_ui + mkdir /tmp/cvat_sdk + + - name: CVAT server. Build and push + uses: docker/build-push-action@v3 with: - context: . - file: ./Dockerfile cache-from: type=local,src=/tmp/cvat_cache_server - tags: openvino/cvat_server:latest - load: true + context: . + file: Dockerfile + tags: cvat/server + outputs: type=docker,dest=/tmp/cvat_server/image.tar + + - name: CVAT UI. Build and push + uses: docker/build-push-action@v3 + with: + cache-from: type=local,src=/tmp/cvat_cache_ui + context: . + file: Dockerfile.ui + tags: cvat/ui + outputs: type=docker,dest=/tmp/cvat_ui/image.tar + + - name: CVAT SDK. Build + run: | + docker load --input /tmp/cvat_server/image.tar + docker run --rm -v ${PWD}/cvat-sdk/schema/:/transfer \ + --entrypoint /bin/bash -u root cvat/server \ + -c 'python manage.py spectacular --file /transfer/schema.yml' + pip3 install --user -r cvat-sdk/gen/requirements.txt + cd cvat-sdk/ + gen/generate.sh + cd .. + + cp -r cvat-sdk/* /tmp/cvat_sdk/ + + - name: Upload CVAT server artifact + uses: actions/upload-artifact@v3 + with: + name: cvat_server + path: /tmp/cvat_server/image.tar + + - name: Upload CVAT UI artifact + uses: actions/upload-artifact@v3 + with: + name: cvat_ui + path: /tmp/cvat_ui/image.tar + + - name: Upload CVAT SDK artifact + uses: actions/upload-artifact@v3 + with: + name: cvat_sdk + path: /tmp/cvat_sdk/ + + rest_api_testing: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v3 + with: + python-version: '3.8' + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Download CVAT server image + uses: actions/download-artifact@v3 + with: + name: cvat_server + path: /tmp/cvat_server/ + + - name: Download CVAT UI images + uses: actions/download-artifact@v3 + with: + name: cvat_ui + path: /tmp/cvat_ui/ + + - name: Download CVAT SDK package + uses: actions/download-artifact@v3 + with: + name: cvat_sdk + path: /tmp/cvat_sdk/ + + - name: Load Docker images + run: | + docker load --input /tmp/cvat_server/image.tar + docker load --input /tmp/cvat_ui/image.tar + docker tag cvat/server:latest cvat/server:${CVAT_VERSION} + docker tag cvat/ui:latest cvat/ui:${CVAT_VERSION} + docker image ls -a + + - name: Running REST API and SDK tests + run: | + pip3 install --user '/tmp/cvat_sdk/[pytorch]' + pip3 install --user cvat-cli/ + pip3 install --user -r tests/python/requirements.txt + pytest tests/python/ -s -v + + - name: Creating a log file from cvat containers + if: failure() + env: + LOGS_DIR: "${{ github.workspace }}/rest_api_testing" + run: | + mkdir $LOGS_DIR + docker logs test_cvat_server_1 > $LOGS_DIR/cvat_server.log + docker logs test_cvat_worker_default_1 > $LOGS_DIR/cvat_worker_default.log + docker logs test_cvat_opa_1 2> $LOGS_DIR/cvat_opa.log + + - name: Uploading "cvat" container logs as an artifact + if: failure() + uses: actions/upload-artifact@v3.1.1 + with: + name: rest_api_container_logs + path: "${{ github.workspace }}/rest_api_testing" + + unit_testing: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Download CVAT server image + uses: actions/download-artifact@v3 + with: + name: cvat_server + path: /tmp/cvat_server/ + + - name: Load Docker server image + run: | + docker load --input /tmp/cvat_server/image.tar + docker tag cvat/server:latest cvat/server:${CVAT_VERSION} + docker image ls -a + - name: Running OPA tests run: | + python cvat/apps/iam/rules/tests/generate_tests.py \ + --output-dir cvat/apps/iam/rules/ + curl -L -o opa https://openpolicyagent.org/downloads/v0.34.2/opa_linux_amd64_static chmod +x ./opa ./opa test cvat/apps/iam/rules - - name: Running REST API tests - env: - API_ABOUT_PAGE: "localhost:8080/api/server/about" - # Access key length should be at least 3, and secret key length at least 8 characters - MINIO_ACCESS_KEY: "minio_access_key" - MINIO_SECRET_KEY: "minio_secret_key" - run: | - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml -f components/analytics/docker-compose.analytics.yml -f tests/rest_api/docker-compose.minio.yml up -d - /bin/bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${API_ABOUT_PAGE})" != "401" ]]; do sleep 5; done' - pip3 install --user -r tests/rest_api/requirements.txt - pytest tests/rest_api/ - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml -f components/analytics/docker-compose.analytics.yml -f tests/rest_api/docker-compose.minio.yml down -v + - name: Running unit tests env: HOST_COVERAGE_DATA_DIR: ${{ github.workspace }} CONTAINER_COVERAGE_DATA_DIR: "/coverage_data" run: | - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'coverage run -a manage.py test cvat/apps utils/cli && mv .coverage ${CONTAINER_COVERAGE_DATA_DIR}' - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd cvat-data && npm ci && cd ../cvat-core && npm ci && npm run test && mv ./reports/coverage/lcov.info ${CONTAINER_COVERAGE_DATA_DIR} && chmod a+rwx ${CONTAINER_COVERAGE_DATA_DIR}/lcov.info' - - name: Uploading code coverage results as an artifact - if: github.ref == 'refs/heads/develop' - uses: actions/upload-artifact@v2 + docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d cvat_opa cvat_server + + max_tries=12 + while [[ $(curl -s -o /dev/null -w "%{http_code}" localhost:8181/health?bundles) != "200" && max_tries -gt 0 ]]; do (( max_tries-- )); sleep 5; done + + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash \ + -c 'python manage.py test cvat/apps -v 2' + + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash \ + -c 'yarn --frozen-lockfile --ignore-scripts && yarn workspace cvat-core run test' + + - name: Creating a log file from cvat containers + if: failure() + env: + LOGS_DIR: "${{ github.workspace }}/unit_testing" + run: | + mkdir $LOGS_DIR + docker logs cvat_server > $LOGS_DIR/cvat_server.log + docker logs cvat_opa 2> $LOGS_DIR/cvat_opa.log + + - name: Uploading "cvat" container logs as an artifact + if: failure() + uses: actions/upload-artifact@v3.1.1 with: - name: coverage_results - path: | - ${{ github.workspace }}/.coverage - ${{ github.workspace }}/lcov.info + name: unit_tests_container_logs + path: "${{ github.workspace }}/unit_testing" - E2E_testing: - if: | - github.event.pull_request.draft == false && - !startsWith(github.event.pull_request.title, '[WIP]') && - !startsWith(github.event.pull_request.title, '[Dependent]') + + e2e_testing: + needs: build runs-on: ubuntu-latest strategy: fail-fast: false matrix: - specs: ['actions_tasks', 'actions_tasks2', 'actions_tasks3', 'actions_objects', 'actions_objects2', 'actions_users', 'actions_projects_models', 'actions_organizations', 'canvas3d_functionality', 'canvas3d_functionality_2', 'issues_prs', 'issues_prs2'] + specs: ['actions_tasks', 'actions_tasks2', 'actions_tasks3', + 'actions_objects', 'actions_objects2', 'actions_users', + 'actions_projects_models', 'actions_organizations', 'canvas3d_functionality', + 'canvas3d_functionality_2', 'issues_prs', 'issues_prs2', 'masks', 'skeletons'] steps: - - uses: actions/checkout@v2 - - name: Getting SHA from the default branch - id: get-sha - run: | - URL_get_default_branch="https://api.github.com/repos/${{ github.repository }}" - DEFAULT_BRANCH=$(curl -s -X GET -G ${URL_get_default_branch} | jq -r '.default_branch') - URL_get_sha_default_branch="https://api.github.com/repos/${{ github.repository }}/git/ref/heads/${DEFAULT_BRANCH}" - SHA=$(curl -s -X GET -G ${URL_get_sha_default_branch} | jq .object.sha | tr -d '"') - echo ::set-output name=default_branch::${DEFAULT_BRANCH} - echo ::set-output name=sha::${SHA} - - name: Waiting a cache creation in the default branch - run: | - URL_runs="https://api.github.com/repos/${{ github.repository }}/actions/workflows/cache.yml/runs" - SLEEP=45 - NUMBER_ATTEMPTS=10 - while [[ ${NUMBER_ATTEMPTS} -gt 0 ]]; do - RUN_status=$(curl -s -X GET -G ${URL_runs} | jq -r '.workflow_runs[]? | select((.head_sha == "${{ steps.get-sha.outputs.sha }}") and (.event == "push") and (.name == "Cache") and (.head_branch == "${{ steps.get-sha.outputs.default_branch }}")) | .status') - if [[ ${RUN_status} == "completed" ]]; then - echo "The cache creation on the '${{ steps.get-sha.outputs.default_branch }}' branch has finished. Status: ${RUN_status}" - break - else - echo "The creation of the cache is not yet complete." - echo "There are still attempts to check the cache: ${NUMBER_ATTEMPTS}" - echo "Status of caching in the '${{ steps.get-sha.outputs.default_branch }}' branch: ${RUN_status}" - echo "sleep ${SLEEP}" - sleep ${SLEEP} - ((NUMBER_ATTEMPTS--)) - fi - done - if [[ ${NUMBER_ATTEMPTS} -eq 0 ]]; then - echo "Number of attempts expired!" - echo "Probably the creation of the cache is not yet complete. Will continue working without the cache." - fi - - name: Getting CVAT server cache from the default branch - uses: actions/cache@v2 - with: - path: /tmp/cvat_cache_server - key: ${{ runner.os }}-build-server-${{ steps.get-sha.outputs.sha }} - - name: Getting cache CVAT UI from the default branch - uses: actions/cache@v2 - with: - path: /tmp/cvat_cache_ui - key: ${{ runner.os }}-build-ui-${{ steps.get-sha.outputs.sha }} - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - uses: actions/setup-node@v3 with: node-version: '16.x' - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1.1.2 - - name: Building CVAT server image - uses: docker/build-push-action@v2 + + - name: Download CVAT server images + uses: actions/download-artifact@v3 with: - context: . - file: ./Dockerfile - cache-from: type=local,src=/tmp/cvat_cache_server - tags: openvino/cvat_server:latest - load: true - - name: Building CVAT UI image - uses: docker/build-push-action@v2 + name: cvat_server + path: /tmp/cvat_server/ + + - name: Download CVAT UI image + uses: actions/download-artifact@v3 with: - context: . - file: ./Dockerfile.ui - cache-from: type=local,src=/tmp/cvat_cache_ui - tags: openvino/cvat_ui:latest - load: true - - name: Instrumentation of the code then rebuilding the CVAT UI - if: github.ref == 'refs/heads/develop' + name: cvat_ui + path: /tmp/cvat_ui/ + + - name: Load Docker images run: | - npm ci - npm run coverage - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml build cvat_ui - - name: Running e2e tests + docker load --input /tmp/cvat_server/image.tar + docker load --input /tmp/cvat_ui/image.tar + docker tag cvat/server:latest cvat/server:${CVAT_VERSION} + docker tag cvat/ui:latest cvat/ui:${CVAT_VERSION} + docker image ls -a + + - name: Run CVAT instance + run: | + docker-compose \ + -f docker-compose.yml \ + -f docker-compose.dev.yml \ + -f components/serverless/docker-compose.serverless.yml \ + -f tests/docker-compose.minio.yml \ + -f tests/docker-compose.file_share.yml up -d + + - name: Waiting for server + env: + API_ABOUT_PAGE: "localhost:8080/api/server/about" + run: | + max_tries=60 + status_code=$(curl -s -o /tmp/server_response -w "%{http_code}" ${API_ABOUT_PAGE}) + while [[ $status_code != "200" && max_tries -gt 0 ]] + do + echo Number of attempts left: $max_tries + echo Status code of response: $status_code + + sleep 5 + status_code=$(curl -s -o /tmp/server_response -w "%{http_code}" ${API_ABOUT_PAGE}) + (( max_tries-- )) + done + + - name: Run E2E tests env: DJANGO_SU_NAME: 'admin' DJANGO_SU_EMAIL: 'admin@localhost.company' DJANGO_SU_PASSWORD: '12qwaszx' - API_ABOUT_PAGE: "localhost:8080/api/server/about" run: | - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml -f tests/docker-compose.file_share.yml up -d - /bin/bash -c 'while [[ $(curl -s -o /dev/null -w "%{http_code}" ${API_ABOUT_PAGE}) != "401" ]]; do sleep 5; done' - docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" + docker exec -i cvat_server /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" cd ./tests - npm ci - if [[ ${{ github.ref }} == 'refs/heads/develop' ]]; then - if [ ${{ matrix.specs }} == 'canvas3d_functionality' ] || [ ${{ matrix.specs }} == 'canvas3d_functionality_2' ]; then - npx cypress run --headed --browser chrome --config-file cypress_canvas3d.json --spec 'cypress/integration/${{ matrix.specs }}/**/*.js,cypress/integration/remove_users_tasks_projects_organizations.js' - else - npx cypress run --browser chrome --spec 'cypress/integration/${{ matrix.specs }}/**/*.js,cypress/integration/remove_users_tasks_projects_organizations.js' - fi - mv ./.nyc_output/out.json ./.nyc_output/out_${{ matrix.specs }}.json + yarn --frozen-lockfile + + if [[ ${{ matrix.specs }} == canvas3d_* ]]; then + npx cypress run \ + --headed \ + --browser chrome \ + --env coverage=false \ + --config-file cypress_canvas3d.json \ + --spec 'cypress/integration/${{ matrix.specs }}/**/*.js,cypress/integration/remove_users_tasks_projects_organizations.js' else - if [ ${{ matrix.specs }} == 'canvas3d_functionality' ] || [ ${{ matrix.specs }} == 'canvas3d_functionality_2' ]; then - npx cypress run --headed --browser chrome --env coverage=false --config-file cypress_canvas3d.json --spec 'cypress/integration/${{ matrix.specs }}/**/*.js,cypress/integration/remove_users_tasks_projects_organizations.js' - else - npx cypress run --browser chrome --env coverage=false --spec 'cypress/integration/${{ matrix.specs }}/**/*.js,cypress/integration/remove_users_tasks_projects_organizations.js' - fi + npx cypress run \ + --browser chrome \ + --env coverage=false \ + --spec 'cypress/integration/${{ matrix.specs }}/**/*.js,cypress/integration/remove_users_tasks_projects_organizations.js' fi + - name: Creating a log file from "cvat" container logs if: failure() run: | - docker logs cvat > ${{ github.workspace }}/tests/cvat_${{ matrix.specs }}.log - - name: Uploading cypress screenshots as an artifact - if: failure() - uses: actions/upload-artifact@v2 - with: - name: cypress_screenshots_${{ matrix.specs }} - path: ${{ github.workspace }}/tests/cypress/screenshots + docker logs cvat_server > ${{ github.workspace }}/tests/cvat_${{ matrix.specs }}.log + - name: Uploading "cvat" container logs as an artifact if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.1 with: - name: cvat_container_logs + name: e2e_container_logs path: ${{ github.workspace }}/tests/cvat_${{ matrix.specs }}.log - - name: Uploading code coverage results as an artifact - if: github.ref == 'refs/heads/develop' - uses: actions/upload-artifact@v2 + + - name: Uploading cypress screenshots as an artifact + if: failure() + uses: actions/upload-artifact@v3.1.1 with: - name: coverage_results - path: ${{ github.workspace }}/tests/.nyc_output + name: cypress_screenshots_${{ matrix.specs }} + path: ${{ github.workspace }}/tests/cypress/screenshots - Coveralls: + generate_github_pages: if: github.ref == 'refs/heads/develop' + needs: [rest_api_testing, unit_testing, e2e_testing] runs-on: ubuntu-latest - needs: [Unit_testing, E2E_testing] steps: - - uses: actions/checkout@v2 - - name: Getting SHA from the default branch - id: get-sha - run: | - URL_get_default_branch="https://api.github.com/repos/${{ github.repository }}" - DEFAULT_BRANCH=$(curl -s -X GET -G ${URL_get_default_branch} | jq -r '.default_branch') - URL_get_sha_default_branch="https://api.github.com/repos/${{ github.repository }}/git/ref/heads/${DEFAULT_BRANCH}" - SHA=$(curl -s -X GET -G ${URL_get_sha_default_branch} | jq .object.sha | tr -d '"') - echo ::set-output name=sha::${SHA} - - name: Getting CVAT server cache from the default branch - uses: actions/cache@v2 + - uses: actions/checkout@v3 with: - path: /tmp/cvat_cache_server - key: ${{ runner.os }}-build-server-${{ steps.get-sha.outputs.sha }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1.1.2 - - name: Building CVAT server image - uses: docker/build-push-action@v2 + submodules: recursive + fetch-depth: 0 + + - name: Download CVAT server images + uses: actions/download-artifact@v3 with: - context: . - file: ./Dockerfile - cache-from: type=local,src=/tmp/cvat_cache_server - tags: openvino/cvat_server:latest - load: true - - name: Downloading coverage results - uses: actions/download-artifact@v2 + name: cvat_server + path: /tmp/cvat_server/ + + - name: Download CVAT server images + uses: actions/download-artifact@v3 with: - name: coverage_results - - name: Combining coverage results + name: cvat_sdk + path: /tmp/cvat_sdk/ + + - name: Load Docker images + run: | + docker load --input /tmp/cvat_server/image.tar + + - name: Setup Hugo + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: '0.83.1' + extended: true + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: '16.x' + + - name: Install npm packages + working-directory: ./site run: | - mkdir -p ./nyc_output_tmp - mv ./out_*.json ./nyc_output_tmp - mkdir -p ./.nyc_output npm ci - npx nyc merge ./nyc_output_tmp ./.nyc_output/out.json - - name: Sending results to Coveralls + + - name: Build docs + run: | + pip install -r site/requirements.txt + python site/process_sdk_docs.py --input-dir /tmp/cvat_sdk/docs/ --site-root site/ + python site/build_docs.py + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + force_orphan: true + + publish_dev_images: + if: github.ref == 'refs/heads/develop' + needs: [rest_api_testing, unit_testing, e2e_testing, generate_github_pages] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Download CVAT server images + uses: actions/download-artifact@v3 + with: + name: cvat_server + path: /tmp/cvat_server/ + + - name: Download CVAT UI images + uses: actions/download-artifact@v3 + with: + name: cvat_ui + path: /tmp/cvat_ui/ + + - name: Load Docker images + run: | + docker load --input /tmp/cvat_server/image.tar + docker load --input /tmp/cvat_ui/image.tar + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Push to Docker Hub env: - HOST_COVERAGE_DATA_DIR: ${{ github.workspace }} - CONTAINER_COVERAGE_DATA_DIR: "/coverage_data" - COVERALLS_SERVICE_NAME: github - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SERVER_IMAGE_REPO: ${{ secrets.DOCKERHUB_WORKSPACE }}/server + UI_IMAGE_REPO: ${{ secrets.DOCKERHUB_WORKSPACE }}/ui run: | - npx nyc report --reporter=text-lcov >> ${HOST_COVERAGE_DATA_DIR}/lcov.info - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd ${CONTAINER_COVERAGE_DATA_DIR} && coveralls-lcov -v -n lcov.info > ${CONTAINER_COVERAGE_DATA_DIR}/coverage.json' - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'ln -s ${CONTAINER_COVERAGE_DATA_DIR}/.git . && ln -s ${CONTAINER_COVERAGE_DATA_DIR}/.coverage . && ln -s ${CONTAINER_COVERAGE_DATA_DIR}/coverage.json . && coveralls --merge=coverage.json' + docker tag cvat/server:latest "${SERVER_IMAGE_REPO}:dev" + docker push "${SERVER_IMAGE_REPO}:dev" + + docker tag cvat/ui:latest "${UI_IMAGE_REPO}:dev" + docker push "${UI_IMAGE_REPO}:dev" diff --git a/.github/workflows/publish_docker_images.yml b/.github/workflows/publish_docker_images.yml index 67cd1d9a724e..7be307139c80 100644 --- a/.github/workflows/publish_docker_images.yml +++ b/.github/workflows/publish_docker_images.yml @@ -1,58 +1,16 @@ name: Publish Docker images on: release: - types: [published] + types: [released] jobs: - Unit_testing: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Run unit tests - env: - HOST_COVERAGE_DATA_DIR: ${{ github.workspace }} - CONTAINER_COVERAGE_DATA_DIR: '/coverage_data' - run: | - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'coverage run -a manage.py test cvat/apps utils/cli' - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd cvat-data && npm ci && cd ../cvat-core && npm ci && npm run test' - - E2E_testing: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: '16.x' - - name: Run end-to-end tests - env: - DJANGO_SU_NAME: 'admin' - DJANGO_SU_EMAIL: 'admin@localhost.company' - DJANGO_SU_PASSWORD: '12qwaszx' - API_ABOUT_PAGE: "localhost:8080/api/server/about" - run: | - docker-compose -f docker-compose.yml -f docker-compose.dev.yml build - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f components/serverless/docker-compose.serverless.yml -f tests/docker-compose.file_share.yml up -d - /bin/bash -c 'while [[ $(curl -s -o /dev/null -w "%{http_code}" ${API_ABOUT_PAGE}) != "401" ]]; do sleep 5; done' - docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" - cd ./tests - npm ci - npm run cypress:run:chrome - npm run cypress:run:chrome:canvas3d - - name: Uploading cypress screenshots as an artifact - if: failure() - uses: actions/upload-artifact@v2 - with: - name: cypress_screenshots - path: ${{ github.workspace }}/tests/cypress/screenshots - Push_to_registry: runs-on: ubuntu-latest - needs: [Unit_testing, E2E_testing] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build images run: | - CLAM_AV=yes INSTALL_SOURCES=yes docker-compose -f docker-compose.yml -f docker-compose.dev.yml build + CVAT_VERSION=latest CLAM_AV=yes INSTALL_SOURCES=yes docker-compose -f docker-compose.yml -f docker-compose.dev.yml build - name: Login to Docker Hub uses: docker/login-action@v1 with: @@ -60,9 +18,9 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Push to Docker Hub env: - DOCKERHUB_WORKSPACE: 'openvino' - SERVER_IMAGE_REPO: 'cvat_server' - UI_IMAGE_REPO: 'cvat_ui' + DOCKERHUB_WORKSPACE: ${{ secrets.DOCKERHUB_WORKSPACE }} + SERVER_IMAGE_REPO: 'server' + UI_IMAGE_REPO: 'ui' run: | docker tag "${DOCKERHUB_WORKSPACE}/${SERVER_IMAGE_REPO}:latest" "${DOCKERHUB_WORKSPACE}/${SERVER_IMAGE_REPO}:${{ github.event.release.tag_name }}" docker push "${DOCKERHUB_WORKSPACE}/${SERVER_IMAGE_REPO}:${{ github.event.release.tag_name }}" diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 0d222b6c4b4f..8d68c5a94e87 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -1,10 +1,10 @@ -name: Linter +name: Pylint on: pull_request jobs: - PyLint: + Linter: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - id: files uses: jitterbit/get-changed-files@v1 continue-on-error: true @@ -17,7 +17,7 @@ jobs: PR_FILES="$PR_FILES_AM $PR_FILES_RENAMED" for FILE in $PR_FILES; do EXTENSION="${FILE##*.}" - if [[ $EXTENSION == 'py' ]]; then + if [[ "$EXTENSION" == 'py' ]]; then CHANGED_FILES+=" $FILE" fi done @@ -44,7 +44,7 @@ jobs: - name: Upload artifacts if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.1 with: name: pylint_report path: pylint_report diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index 61fd229438b5..8524364a5746 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -1,21 +1,21 @@ -name: Linter +name: Remark on: pull_request jobs: - Remark: + Linter: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: '16.x' - name: Run checks run: | - npm ci + yarn install --frozen-lockfile mkdir -p remark_report echo "Remark version: "`npx remark --version` - npx remark --quiet --report json --no-stdout . 2> ./remark_report/remark_report.json + npx remark --quiet --report json --no-stdout -i .remarkignore . 2> ./remark_report/remark_report.json get_report=`cat ./remark_report/remark_report.json | jq -r '.[] | select(.messages | length > 0)'` if [[ ! -z ${get_report} ]]; then pip install json2html @@ -25,7 +25,7 @@ jobs: - name: Upload artifacts if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.1 with: name: remark_report path: remark_report diff --git a/.github/workflows/schedule.yml b/.github/workflows/schedule.yml index 7f6d7005f926..61bfc638cba2 100644 --- a/.github/workflows/schedule.yml +++ b/.github/workflows/schedule.yml @@ -3,32 +3,473 @@ on: schedule: - cron: '0 22 * * *' workflow_dispatch: + +env: + SERVER_IMAGE_TEST_REPO: cvat_server + UI_IMAGE_TEST_REPO: instrumentation_cvat_ui + CYPRESS_VERIFY_TIMEOUT: 180000 # https://docs.cypress.io/guides/guides/command-line#cypress-verify + jobs: + check_updates: + runs-on: ubuntu-latest + env: + REPO: ${{ github.repository }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + outputs: + last_commit_time: ${{ steps.check_updates.outputs.last_commit_time }} + last_night_time: ${{ steps.check_updates.outputs.last_night_time }} + steps: + - id: check_updates + run: | + default_branch=$(gh api /repos/$REPO | jq -r '.default_branch') + + last_commit_date=$(gh api /repos/${REPO}/branches/${default_branch} | jq -r '.commit.commit.author.date') + + last_night_date=$(gh api /repos/${REPO}/actions/workflows/schedule.yml/runs | \ + jq -r '.workflow_runs[]? | select((.status == "completed")) | .updated_at' \ + | sort | tail -1) + + last_night_time=$(date +%s -d $last_night_date) + last_commit_time=$(date +%s -d $last_commit_date) + + echo Last CI-nightly workflow run time: $last_night_date + echo Last commit time in develop branch: $last_commit_date + + echo "last_commit_time=${last_commit_time}" >> $GITHUB_OUTPUT + echo "last_night_time=${last_night_time}" >> $GITHUB_OUTPUT + + search_cache: + needs: check_updates + if: + needs.check_updates.outputs.last_commit_time > needs.check_updates.outputs.last_night_time + runs-on: ubuntu-latest + outputs: + sha: ${{ steps.get-sha.outputs.sha}} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + steps: + - name: Getting SHA with cache from the default branch + id: get-sha + run: | + DEFAULT_BRANCH=$(gh api /repos/$REPO | jq -r '.default_branch') + for sha in $(gh api "/repos/$REPO/commits?per_page=100&sha=$DEFAULT_BRANCH" | jq -r '.[].sha'); + do + RUN_status=$(gh api /repos/${REPO}/actions/workflows/cache.yml/runs | \ + jq -r ".workflow_runs[]? | select((.head_sha == \"${sha}\") and (.conclusion == \"success\")) | .status") + + if [[ ${RUN_status} == "completed" ]]; then + SHA=$sha + break + fi + done + + echo Default branch is ${DEFAULT_BRANCH} + echo Workflow will try to get cache from commit: ${SHA} + + echo "default_branch=${DEFAULT_BRANCH}" >> $GITHUB_OUTPUT + echo "sha=${SHA}" >> $GITHUB_OUTPUT + build: + needs: search_cache runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_CI_USERNAME }} + password: ${{ secrets.DOCKERHUB_CI_TOKEN }} + + - name: CVAT server. Getting cache from the default branch + uses: actions/cache@v3 + with: + path: /tmp/cvat_cache_server + key: ${{ runner.os }}-build-server-${{ needs.search_cache.outputs.sha }} + + - name: CVAT UI. Getting cache from the default branch + uses: actions/cache@v3 + with: + path: /tmp/cvat_cache_ui + key: ${{ runner.os }}-build-ui-${{ needs.search_cache.outputs.sha }} + + - name: CVAT server. Extract metadata (tags, labels) for Docker + id: meta-server + uses: docker/metadata-action@master + with: + images: ${{ secrets.DOCKERHUB_CI_WORKSPACE }}/${{ env.SERVER_IMAGE_TEST_REPO }} + tags: + type=raw,value=nightly + + - name: CVAT UI. Extract metadata (tags, labels) for Docker + id: meta-ui + uses: docker/metadata-action@master + with: + images: ${{ secrets.DOCKERHUB_CI_WORKSPACE }}/${{ env.UI_IMAGE_TEST_REPO }} + tags: + type=raw,value=nightly + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: CVAT server. Build and push + uses: docker/build-push-action@v3 + with: + cache-from: type=local,src=/tmp/cvat_cache_server + context: . + file: Dockerfile + push: true + tags: ${{ steps.meta-server.outputs.tags }} + labels: ${{ steps.meta-server.outputs.labels }} + + - name: Instrumentation of the code then rebuilding the CVAT UI + run: | + yarn --frozen-lockfile + yarn run coverage + + - name: CVAT UI. Build and push + uses: docker/build-push-action@v3 + with: + cache-from: type=local,src=/tmp/cvat_cache_ui + context: . + file: Dockerfile.ui + push: true + tags: ${{ steps.meta-ui.outputs.tags }} + labels: ${{ steps.meta-ui.outputs.labels }} + + unit_testing: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v3 + with: + python-version: '3.8' + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Getting CVAT UI cache from the default branch + uses: actions/cache@v3 + with: + path: /tmp/cvat_cache_ui + key: ${{ runner.os }}-build-ui-${{ needs.search_cache.outputs.sha }} + + - name: Getting CVAT Logstash cache from the default branch + uses: actions/cache@v3 + with: + path: /tmp/cvat_cache_logstash + key: ${{ runner.os }}-build-logstash-${{ needs.search_cache.outputs.sha }} + + - name: Getting CVAT Elasticsearch cache from the default branch + uses: actions/cache@v3 + with: + path: /tmp/cvat_cache_elasticsearch + key: ${{ runner.os }}-build-elasticsearch-${{ needs.search_cache.outputs.sha }} + + - name: Building CVAT UI image + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile.ui + cache-from: type=local,src=/tmp/cvat_cache_ui + tags: cvat/ui:latest + load: true + + - name: Building CVAT Logstash image + uses: docker/build-push-action@v2 + with: + context: ./components/analytics/logstash/ + file: ./components/analytics/logstash/Dockerfile + cache-from: type=local,src=/tmp/cvat_cache_logstash + build-args: ELK_VERSION=6.8.23 + tags: cvat_logstash + load: true + + - name: Building CVAT Elasticsearch image + uses: docker/build-push-action@v2 + with: + context: ./components/analytics/elasticsearch/ + file: ./components/analytics/elasticsearch/Dockerfile + cache-from: type=local,src=/tmp/cvat_cache_elasticsearch + build-args: ELK_VERSION=6.8.23 + tags: cvat_elasticsearch + load: true + + - name: CVAT server. Extract metadata (tags, labels) for Docker + id: meta-server + uses: docker/metadata-action@master + with: + images: ${{ secrets.DOCKERHUB_CI_WORKSPACE }}/${{ env.SERVER_IMAGE_TEST_REPO }} + tags: + type=raw,value=nightly + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_CI_USERNAME }} + password: ${{ secrets.DOCKERHUB_CI_TOKEN }} + + - name: Pull CVAT server image + run: | + docker pull ${{ steps.meta-server.outputs.tags }} + docker tag ${{ steps.meta-server.outputs.tags }} cvat/server:dev + docker tag ${{ steps.meta-server.outputs.tags }} cvat/server:latest + docker tag cvat/ui:latest cvat/ui:dev + + - name: OPA tests + run: | + curl -L -o opa https://openpolicyagent.org/downloads/v0.34.2/opa_linux_amd64_static + chmod +x ./opa + ./opa test cvat/apps/iam/rules + + - name: REST API and SDK tests + run: | + docker run --rm -v ${PWD}/cvat-sdk/schema/:/transfer \ + --entrypoint /bin/bash -u root cvat/server \ + -c 'python manage.py spectacular --file /transfer/schema.yml' + pip3 install --user -r cvat-sdk/gen/requirements.txt + cd cvat-sdk/ + gen/generate.sh + cd .. + + pip3 install --user 'cvat-sdk/[pytorch]' + pip3 install --user cvat-cli/ + pip3 install --user -r tests/python/requirements.txt + pytest tests/python/ + pytest tests/python/ --stop-services + + - name: Unit tests + env: + HOST_COVERAGE_DATA_DIR: ${{ github.workspace }} + CONTAINER_COVERAGE_DATA_DIR: "/coverage_data" + run: | + docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d cvat_opa cvat_server + max_tries=12 + while [[ $(curl -s -o /dev/null -w "%{http_code}" localhost:8181/health?bundles) != "200" && max_tries -gt 0 ]]; do (( max_tries-- )); sleep 5; done + + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash \ + -c 'coverage run -a manage.py test cvat/apps && mv .coverage ${CONTAINER_COVERAGE_DATA_DIR}' + + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash \ + -c 'yarn --frozen-lockfile --ignore-scripts && yarn workspace cvat-core run test' + + docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml down -v + + - name: Uploading code coverage results as an artifact + uses: actions/upload-artifact@v3.1.1 + with: + name: coverage_results + path: | + ${{ github.workspace }}/lcov.info + ${{ github.workspace }}/.coverage + + e2e_testing: + needs: build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + specs: ['actions_tasks', 'actions_tasks2', 'actions_tasks3', + 'actions_objects', 'actions_objects2', 'actions_users', + 'actions_projects_models', 'actions_organizations', 'canvas3d_functionality', + 'canvas3d_functionality_2', 'issues_prs', 'issues_prs2', 'masks', 'skeletons'] + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 with: node-version: '16.x' - - name: Build CVAT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_CI_USERNAME }} + password: ${{ secrets.DOCKERHUB_CI_TOKEN }} + + - name: CVAT server. Extract metadata (tags, labels) for Docker + id: meta-server + uses: docker/metadata-action@master + with: + images: ${{ secrets.DOCKERHUB_CI_WORKSPACE }}/${{ env.SERVER_IMAGE_TEST_REPO }} + tags: + type=raw,value=nightly + + - name: CVAT UI. Extract metadata (tags, labels) for Docker + id: meta-ui + uses: docker/metadata-action@master + with: + images: ${{ secrets.DOCKERHUB_CI_USERNAME }}/${{ env.UI_IMAGE_TEST_REPO }} + tags: + type=raw,value=nightly + + - name: Pull CVAT UI image + run: | + docker pull ${{ steps.meta-server.outputs.tags }} + docker tag ${{ steps.meta-server.outputs.tags }} cvat/server:dev + + docker pull ${{ steps.meta-ui.outputs.tags }} + docker tag ${{ steps.meta-ui.outputs.tags }} cvat/ui:dev + + - name: Run CVAT instance + run: | + docker-compose \ + -f docker-compose.yml \ + -f docker-compose.dev.yml \ + -f tests/docker-compose.file_share.yml \ + -f tests/docker-compose.minio.yml \ + -f components/serverless/docker-compose.serverless.yml up -d + + - name: Waiting for server + id: wait-server + env: + API_ABOUT_PAGE: "localhost:8080/api/server/about" + run: | + max_tries=60 + status_code=$(curl -s -o /tmp/server_response -w "%{http_code}" ${API_ABOUT_PAGE}) + while [[ $status_code != "200" && max_tries -gt 0 ]] + do + echo Number of attempts left: $max_tries + echo Status code of response: $status_code + + sleep 5 + status_code=$(curl -s -o /tmp/server_response -w "%{http_code}" ${API_ABOUT_PAGE}) + (( max_tries-- )) + done + + if [[ $status_code != "200" ]]; then + echo Response from server is incorrect, output: + cat /tmp/server_response + fi + echo "status_code=${status_code}" >> $GITHUB_OUTPUT + + - name: Fail on bad response from server + if: steps.wait-server.outputs.status_code != '200' + uses: actions/github-script@v3 + with: + script: | + core.setFailed('Workflow failed: incorrect response from server. See logs artifact to get more info') + + - name: Add user for tests env: DJANGO_SU_NAME: "admin" DJANGO_SU_EMAIL: "admin@localhost.company" DJANGO_SU_PASSWORD: "12qwaszx" - API_ABOUT_PAGE: "localhost:8080/api/server/about" run: | - docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f ./tests/docker-compose.email.yml -f tests/docker-compose.file_share.yml -f components/serverless/docker-compose.serverless.yml up -d --build - /bin/bash -c 'while [[ $(curl -s -o /dev/null -w "%{http_code}" ${API_ABOUT_PAGE}) != "401" ]]; do sleep 5; done' - docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" - - name: End-to-end testing + docker exec -i cvat_server /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell" + + - name: Run tests run: | cd ./tests - npm ci - npm run cypress:run:firefox + yarn --frozen-lockfile + + shopt -s extglob + if [[ ${{ matrix.specs }} == canvas3d_* ]]; then + npx cypress run \ + --headed \ + --browser chrome \ + --config-file cypress_canvas3d.json \ + --spec 'cypress/integration/${{ matrix.specs }}/**/*.js,cypress/integration/remove_users_tasks_projects_organizations.js' + mv ./.nyc_output/out.json ./.nyc_output/out_${{ matrix.specs }}.json + else + npx cypress run \ + --browser chrome \ + --spec 'cypress/integration/${{ matrix.specs }}/**/*.js,cypress/integration/remove_users_tasks_projects_organizations.js' + mv ./.nyc_output/out.json ./.nyc_output/out_${{ matrix.specs }}.json + fi + + - name: Creating a log file from "cvat" container logs + if: failure() + run: | + docker logs cvat_server > ${{ github.workspace }}/tests/cvat.log + - name: Uploading cypress screenshots as an artifact if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.1 with: name: cypress_screenshots path: ${{ github.workspace }}/tests/cypress/screenshots + + - name: Uploading "cvat" container logs as an artifact + if: failure() + uses: actions/upload-artifact@v3.1.1 + with: + name: cvat_container_logs + path: ${{ github.workspace }}/tests/cvat.log + + - name: Uploading code coverage results as an artifact + uses: actions/upload-artifact@v3.1.1 + with: + name: coverage_results + path: ${{ github.workspace }}/tests/.nyc_output + + coveralls: + runs-on: ubuntu-latest + needs: [unit_testing, e2e_testing] + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: CVAT server. Extract metadata (tags, labels) for Docker + id: meta-server + uses: docker/metadata-action@master + with: + images: ${{ secrets.DOCKERHUB_CI_WORKSPACE }}/${{ env.SERVER_IMAGE_TEST_REPO }} + tags: + type=raw,value=nightly + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_CI_USERNAME }} + password: ${{ secrets.DOCKERHUB_CI_TOKEN }} + + - name: Pull CVAT server image + run: | + docker pull ${{ steps.meta-server.outputs.tags }} + docker tag ${{ steps.meta-server.outputs.tags }} cvat/server:dev + + - name: Downloading coverage results + uses: actions/download-artifact@v2 + with: + name: coverage_results + + - name: Combining coverage results + run: | + mkdir -p ./nyc_output_tmp + mv ./out_*.json ./nyc_output_tmp + mkdir -p ./.nyc_output + yarn --frozen-lockfile + npx nyc merge ./nyc_output_tmp ./.nyc_output/out.json + + - name: Sending results to Coveralls + env: + HOST_COVERAGE_DATA_DIR: ${{ github.workspace }} + CONTAINER_COVERAGE_DATA_DIR: "/coverage_data" + COVERALLS_SERVICE_NAME: github + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + npx nyc report --reporter=text-lcov >> ${HOST_COVERAGE_DATA_DIR}/lcov.info + + docker-compose \ + -f docker-compose.yml \ + -f docker-compose.dev.yml \ + -f docker-compose.ci.yml \ + run cvat_ci /bin/bash -c 'cd ${CONTAINER_COVERAGE_DATA_DIR} && coveralls-lcov -v -n lcov.info > ${CONTAINER_COVERAGE_DATA_DIR}/coverage.json' + + docker-compose \ + -f docker-compose.yml \ + -f docker-compose.dev.yml \ + -f docker-compose.ci.yml \ + run cvat_ci /bin/bash -c '\ + ln -s ${CONTAINER_COVERAGE_DATA_DIR}/.git . \ + && ln -s ${CONTAINER_COVERAGE_DATA_DIR}/.coverage . \ + && ln -s ${CONTAINER_COVERAGE_DATA_DIR}/coverage.json . \ + && coveralls --merge=coverage.json' diff --git a/.github/workflows/stylelint.yml b/.github/workflows/stylelint.yml index 0c07eaea0060..4c1deebaf5fa 100644 --- a/.github/workflows/stylelint.yml +++ b/.github/workflows/stylelint.yml @@ -1,11 +1,11 @@ -name: Linter +name: StyleLint on: pull_request jobs: - StyleLint: + Linter: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: '16.x' - id: files @@ -26,7 +26,7 @@ jobs: done if [[ ! -z $CHANGED_FILES ]]; then - npm ci + yarn install --frozen-lockfile mkdir -p stylelint_report echo "StyleLint version: "$(npx stylelint --version) @@ -41,7 +41,7 @@ jobs: - name: Upload artifacts if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.1 with: name: stylelint_report path: stylelint_report diff --git a/.github/workflows/update-yarn-lock.yml b/.github/workflows/update-yarn-lock.yml new file mode 100644 index 000000000000..dda63c12ac4a --- /dev/null +++ b/.github/workflows/update-yarn-lock.yml @@ -0,0 +1,29 @@ +# The purpose of this workflow: update yarn.lock file for PRs that come from Snyk +name: Update yarn.lock file +on: + pull_request: + types: ['opened', 'reopened'] + paths: + - '**/package.json' + - 'package.json' + branches: + - 'develop' + +jobs: + update: + if: startsWith(github.event.pull_request.head.ref, 'snyk-') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: '16.x' + + - name: Update yarn.lock file + run: yarn + + - uses: stefanzweifel/git-auto-commit-action@v4.15.2 + with: + commit_message: Update yarn.lock file + file_pattern: yarn.lock diff --git a/.gitignore b/.gitignore index 8e22339a1769..cf085d567c1b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ /share/ /static/ /db.sqlite3 -/.env +/.*env* /keys /logs /profiles @@ -44,6 +44,7 @@ yarn-error.log* /site/resources/ /site/node_modules/ /site/tech-doc-hugo +/site/.hugo_build.lock # Ignore all the installed packages node_modules diff --git a/.pylintrc b/.pylintrc index 677a395622ab..63b2524e61f5 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,420 +1,973 @@ -[MASTER] +[MAIN] -# Specify a configuration file. -#rcfile= +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the ignore-list. The +# regex matches against paths and can be in Posix or Windows format. +ignore-paths= + cvat-sdk\\cvat_sdk\\api_client|cvat-sdk/cvat_sdk/api_client + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. The default value ignores Emacs file +# locks +ignore-patterns= + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=0 -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins=pylint_django # Pickle collected data for later comparisons. persistent=yes -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins=pylint_django +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.7 -# Use multiple processes to speed up Pylint. -jobs=1 +# Discover python modules and packages in the file system subtree. +recursive=no + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. This option is deprecated -# and it will be removed in Pylint 2.0. -optimize-ast=no + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + c-extension-no-member, + invalid-name, + disallowed-name, + typevar-name-incorrect-variance, + typevar-double-variance, + typevar-name-mismatch, + empty-docstring, + missing-module-docstring, + missing-class-docstring, + missing-function-docstring, + non-ascii-name, + non-ascii-module-import, + line-too-long, + too-many-lines, + trailing-newlines, + multiple-statements, + mixed-line-endings, + unexpected-line-ending-format, + unnecessary-lambda-assignment, + unnecessary-direct-lambda-call, + unneeded-not, + consider-iterating-dictionary, + consider-using-dict-items, + use-maxsplit-arg, + use-sequence-for-iteration, + consider-using-f-string, + use-implicit-booleaness-not-len, + use-implicit-booleaness-not-comparison, + wrong-spelling-in-comment, + wrong-spelling-in-docstring, + invalid-characters-in-docstring, + bad-file-encoding, + multiple-imports, + wrong-import-order, + ungrouped-imports, + wrong-import-position, + useless-import-alias, + import-outside-toplevel, + bad-classmethod-argument, + bad-mcs-method-argument, + bad-mcs-classmethod-argument, + single-string-used-for-slots, + unnecessary-dunder-call, + useless-option-value, + literal-comparison, + comparison-with-itself, + comparison-of-constants, + too-many-ancestors, + too-many-instance-attributes, + too-few-public-methods, + too-many-public-methods, + too-many-return-statements, + too-many-branches, + too-many-arguments, + too-many-locals, + too-many-statements, + too-many-boolean-expressions, + consider-merging-isinstance, + too-many-nested-blocks, + redefined-argument-from-local, + no-else-return, + consider-using-ternary, + trailing-comma-tuple, + stop-iteration-return, + simplify-boolean-expression, + inconsistent-return-statements, + useless-return, + consider-swap-variables, + consider-using-join, + consider-using-in, + consider-using-get, + chained-comparison, + consider-using-dict-comprehension, + consider-using-set-comprehension, + simplifiable-if-expression, + no-else-raise, + unnecessary-comprehension, + consider-using-sys-exit, + no-else-break, + no-else-continue, + super-with-arguments, + simplifiable-condition, + condition-evals-to-constant, + consider-using-generator, + use-a-generator, + consider-using-min-builtin, + consider-using-max-builtin, + consider-using-with, + unnecessary-dict-index-lookup, + use-list-literal, + use-dict-literal, + unnecessary-list-index-lookup, + duplicate-code, + cyclic-import, + consider-using-from-import, + property-with-parameters, + http-response-with-json-dumps, + http-response-with-content-type-json, + redundant-content-type-for-json-response, + unknown-option-value, + eval-used, + using-constant-test, + missing-parentheses-for-call-in-test, + self-assigning-variable, + redeclared-assigned-name, + assert-on-string-literal, + duplicate-value, + comparison-with-callable, + nan-comparison, + non-ascii-file-name, + global-statement, + unused-argument, + unused-wildcard-import, + redefined-outer-name, + undefined-loop-variable, + unbalanced-tuple-unpacking, + cell-var-from-loop, + possibly-unused-variable, + self-cls-assignment, + using-f-string-in-unsupported-version, + using-final-decorator-in-unsupported-version, + unused-format-string-argument, + duplicate-string-formatting-argument, + f-string-without-interpolation, + format-string-without-interpolation, + implicit-str-concat, + inconsistent-quotes, + redundant-u-string-prefix, + fixme, + broad-except, + try-except-raise, + raise-missing-from, + raising-format-tuple, + wrong-exception-operation, + wildcard-import, + deprecated-module, + import-self, + preferred-module, + attribute-defined-outside-init, + bad-staticmethod-argument, + protected-access, + abstract-method, + super-init-not-called, + useless-super-delegation, + invalid-overridden-method, + arguments-renamed, + unused-private-member, + overridden-final-method, + subclassed-final-class, + redefined-slots-in-subclass, + super-without-brackets, + modified-iterating-list, + unnecessary-ellipsis, + bad-open-mode, + boolean-datetime, + redundant-unittest-assert, + deprecated-method, + bad-thread-instantiation, + shallow-copy-environ, + invalid-envvar-default, + subprocess-popen-preexec-fn, + subprocess-run-check, + deprecated-argument, + deprecated-class, + deprecated-decorator, + unspecified-encoding, + forgotten-debug-statement, + method-cache-max-size-none, + useless-with-lock, + keyword-arg-before-vararg, + arguments-out-of-order, + non-str-assignment-to-dunder-name, + isinstance-second-argument-not-valid-type, + logging-not-lazy, + logging-format-interpolation, + logging-fstring-interpolation, + model-missing-unicode, + model-has-unicode, + model-no-explicit-unicode, + django-not-available-placeholder, + modelform-uses-exclude, + unrecognized-inline-option, + bad-plugin-value, + bad-configuration-section, + unrecognized-option, + return-arg-in-generator, + used-prior-global-declaration, + bad-reversed-sequence, + misplaced-format-function, + invalid-all-format, + no-name-in-module, + unpacking-non-sequence, + potential-index-error, + bad-string-format-type, + bad-str-strip-call, + yield-inside-async-function, + not-async-context-manager, + invalid-unicode-codec, + bidirectional-unicode, + invalid-character-backspace, + invalid-character-carriage-return, + invalid-character-sub, + invalid-character-esc, + invalid-character-nul, + invalid-character-zero-width-space, + import-error, + relative-beyond-top-level, + no-self-argument, + assigning-non-slot, + class-variable-slots-conflict, + invalid-class-object, + invalid-enum-extension, + invalid-length-returned, + invalid-bool-returned, + invalid-index-returned, + invalid-repr-returned, + invalid-str-returned, + invalid-bytes-returned, + invalid-hash-returned, + invalid-length-hint-returned, + invalid-format-returned, + invalid-getnewargs-returned, + invalid-getnewargs-ex-returned, + modified-iterating-dict, + modified-iterating-set, + invalid-envvar-value, + no-member, + assignment-from-none, + not-context-manager, + invalid-unary-operand-type, + unsupported-binary-operation, + unsupported-membership-test, + unsubscriptable-object, + unsupported-assignment-operation, + unsupported-delete-operation, + invalid-metaclass, + unhashable-dict-key, + dict-iter-missing-items, + await-outside-async, + not-an-iterable, + not-a-mapping, + model-unicode-not-callable, + hard-coded-auth-user, + imported-auth-user, + django-not-configured, + fatal, + astroid-error, + parse-error, + config-parse-error, + method-check-failed, + django-not-available, + django-settings-module-not-found # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. -disable=all -enable= E0001,E0100,E0101,E0102,E0103,E0104,E0105,E0106,E0107,E0110, - E0113,E0114,E0115,E0116,E0117,E0108,E0202,E0203,E0211,E0236, - E0238,E0239,E0240,E0241,E0301,E0302,E0601,E0603,E0604,E0701, - E0702,E0703,E0704,E0710,E0711,E0712,E1003,E1102,E1111,E0112, - E1120,E1121,E1123,E1124,E1125,E1126,E1127,E1132,E1200,E1201, - E1205,E1206,E1300,E1301,E1302,E1303,E1304,E1305,E1306, - C0123,C0200,C0303,C1001, - W0101,W0102,W0104,W0105,W0106,W0107,W0108,W0109,W0110,W0120, - W0122,W0124,W0150,W0199,W0221,W0222,W0233,W0404,W0410,W0601, - W0602,W0604,W0611,W0612,W0622,W0623,W0702,W0705,W0711,W1300, - W1301,W1302,W1303,,W1305,W1306,W1307 - R0102,R0202,R0203 +enable=singleton-comparison, + unidiomatic-typecheck, + trailing-whitespace, + missing-final-newline, + superfluous-parens, + consider-using-enumerate, + simplifiable-if-statement, + no-classmethod-decorator, + no-staticmethod-decorator, + useless-object-inheritance, + useless-else-on-loop, + unreachable, + dangerous-default-value, + pointless-statement, + pointless-string-statement, + expression-not-assigned, + unnecessary-lambda, + duplicate-key, + exec-used, + confusing-with-statement, + lost-exception, + assert-on-tuple, + unnecessary-pass, + global-variable-undefined, + global-variable-not-assigned, + global-at-module-level, + unused-import, + unused-variable, + redefined-builtin, + unnecessary-semicolon, + bad-indentation, + bad-format-string-key, + unused-format-string-key, + bad-format-string, + missing-format-argument-key, + format-combined-specification, + missing-format-attribute, + invalid-format-index, + anomalous-backslash-in-string, + anomalous-unicode-escape-in-string, + bare-except, + duplicate-except, + binary-op-exception, + reimported, + misplaced-future, + arguments-differ, + signature-differs, + non-parent-init-called, + syntax-error, + init-is-generator, + return-in-init, + function-redefined, + not-in-loop, + return-outside-function, + yield-outside-function, + nonexistent-operator, + duplicate-argument-name, + abstract-class-instantiated, + too-many-star-expressions, + invalid-star-assignment-target, + star-needs-assignment-target, + nonlocal-and-global, + continue-in-finally, + nonlocal-without-binding, + used-before-assignment, + undefined-variable, + undefined-all-variable, + invalid-all-object, + bad-format-character, + truncated-format-string, + mixed-format-string, + format-needs-mapping, + missing-format-string-key, + too-many-format-args, + too-few-format-args, + bad-except-order, + raising-bad-type, + bad-exception-context, + misplaced-bare-raise, + raising-non-exception, + notimplemented-raised, + catching-non-exception, + method-hidden, + access-member-before-definition, + no-method-argument, + invalid-slots-object, + invalid-slots, + inherit-non-class, + inconsistent-mro, + duplicate-bases, + non-iterator-returned, + unexpected-special-method-signature, + bad-super-call, + not-callable, + assignment-from-no-return, + no-value-for-parameter, + too-many-function-args, + unexpected-keyword-arg, + redundant-keyword-arg, + missing-kwoa, + invalid-sequence-index, + invalid-slice-index, + repeated-keyword, + logging-unsupported-format, + logging-format-truncated, + logging-too-many-args, + logging-too-few-args -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -#disable=old-octal-literal,basestring-builtin,no-absolute-import,old-division,coerce-method,long-suffix,reload-builtin,unichr-builtin,indexing-exception,raising-string,dict-iter-method,metaclass-assignment,filter-builtin-not-iterating,import-star-module-level,next-method-called,cmp-method,raw_input-builtin,old-raise-syntax,cmp-builtin,apply-builtin,getslice-method,input-builtin,backtick,coerce-builtin,range-builtin-not-iterating,xrange-builtin,using-cmp-argument,buffer-builtin,hex-method,execfile-builtin,unpacking-in-except,standarderror-builtin,round-builtin,nonzero-method,unicode-builtin,reduce-builtin,file-builtin,dict-view-method,old-ne-operator,print-statement,suppressed-message,oct-method,useless-suppression,delslice-method,long-builtin,setslice-method,zip-builtin-not-iterating,map-builtin-not-iterating,intern-builtin,parameter-unpacking +[BASIC] +# Naming style matching correct argument names. +argument-naming-style=snake_case -[REPORTS] +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +argument-rgx=[a-z_][a-z0-9_]{2,30}$ -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". This option is deprecated -# and it will be removed in Pylint 2.0. -files-output=no - -# Tells whether to display a full report or only the messages -reports=yes - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) +# Naming style matching correct attribute names. +attr-naming-style=snake_case -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +attr-rgx=[a-z_][a-z0-9_]{2,30}$ +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata -[BASIC] +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +class-rgx=[A-Z_][a-zA-Z0-9]+$ -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. property-classes=abc.abstractproperty -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ +# Naming style matching correct variable names. +variable-naming-style=snake_case -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +variable-rgx=[a-z_][a-z0-9_]{2,30}$ -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ +[VARIABLES] -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ +# List of names allowed to shadow builtins +allowed-redefined-builtins= -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.* -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Tells whether we should check for unused import in __init__ files. +init-import=no -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ +[DESIGN] -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Maximum number of arguments for function / method. +max-args=5 -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ +# Maximum number of attributes for a class (see R0902). +max-attributes=7 -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ +# Maximum number of branch for function / method body. +max-branches=12 -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 +# Maximum number of return / yield for function / method body. +max-returns=6 -[ELIF] +# Maximum number of statements in function / method body. +max-statements=50 -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 [FORMAT] -# Maximum number of characters on a single line. -max-line-length=100 +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Maximum number of lines in a module -max-module-lines=1000 +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 +# Maximum number of characters on a single line. +max-line-length=100 -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= +# Maximum number of lines in a module. +max-module-lines=1000 +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no -[LOGGING] +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO +notes=FIXME, + XXX, + TODO +# Regular expression of note tags to take in consideration. +notes-rgx= -[SIMILARITIES] -# Minimum lines number of a similarity. -min-similarity-lines=4 +[REFACTORING] -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 -# Ignore imports when computing similarities. -ignore-imports=no +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error [SPELLING] -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. spelling-dict= +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + # List of comma separated words that should not be checked. spelling-ignore-words= -# A path to a file that contains private dictionary; one word per line. +# A path to a file that contains the private dictionary; one word per line. spelling-private-dict-file= -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. spelling-store-unknown-words=no -[TYPECHECK] +[SIMILARITIES] -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes +# Comments are removed from the similarity computation +ignore-comments=yes -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= +# Docstrings are removed from the similarity computation +ignore-docstrings=yes -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local +# Imports are removed from the similarity computation +ignore-imports=no -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= +# Signatures are removed from the similarity computation +ignore-signatures=yes -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager +# Minimum lines number of a similarity. +min-similarity-lines=4 -[VARIABLES] +[EXCEPTIONS] -# Tells whether we should check for unused import in __init__ files. -init-import=no +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=Exception -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= +[IMPORTS] -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse -[CLASSES] +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= -[DESIGN] -# Maximum number of arguments for function / method -max-args=5 +[CLASSES] -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no -# Maximum number of locals for function / method body -max-locals=15 +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp -# Maximum number of return / yield for function / method body -max-returns=6 +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make -# Maximum number of branch for function / method body -max-branches=12 +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls -# Maximum number of statements in function / method body -max-statements=50 +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs -# Maximum number of parents for a class (see R0901). -max-parents=7 -# Maximum number of attributes for a class (see R0902). -max-attributes=7 +[TYPECHECK] -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes -[IMPORTS] +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant +# List of decorators that change the signature of a decorated function. +signature-mutators= -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no +[LOGGING] -[EXCEPTIONS] +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[DJANGO FOREIGN KEYS REFERENCED BY STRINGS] + +# A module containing Django settings to be used while linting. +#django-settings-module= diff --git a/.remarkignore b/.remarkignore new file mode 100644 index 000000000000..4f1b9bbf8425 --- /dev/null +++ b/.remarkignore @@ -0,0 +1,2 @@ +cvat-sdk/docs/ +cvat-sdk/README.md diff --git a/.vscode/launch.json b/.vscode/launch.json index 903ed4138971..19e05d371091 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,66 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "REST API tests: Attach to server", + "type": "python", + "request": "attach", + "connect": { + "host": "127.0.0.1", + "port": 9090 + }, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "/home/django/" + }, + { + "localRoot": "${workspaceFolder}/.env", + "remoteRoot": "/opt/venv", + } + ], + "justMyCode": false, + }, + { + "name": "REST API tests: Attach to RQ low", + "type": "python", + "request": "attach", + "connect": { + "host": "127.0.0.1", + "port": 9091 + }, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "/home/django/" + }, + { + "localRoot": "${workspaceFolder}/.env", + "remoteRoot": "/opt/venv", + } + ], + "justMyCode": false, + }, + { + "name": "REST API tests: Attach to RQ default", + "type": "python", + "request": "attach", + "connect": { + "host": "127.0.0.1", + "port": 9092 + }, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "/home/django/" + }, + { + "localRoot": "${workspaceFolder}/.env", + "remoteRoot": "/opt/venv", + } + ], + "justMyCode": false, + }, { "type": "pwa-chrome", "request": "launch", @@ -45,7 +105,9 @@ "python": "${command:python.interpreterPath}", "program": "${workspaceRoot}/manage.py", "env": { - "CVAT_SERVERLESS": "1" + "CVAT_SERVERLESS": "1", + "ALLOWED_HOSTS": "*", + "IAM_OPA_BUNDLE": "1" }, "args": [ "runserver", @@ -82,7 +144,7 @@ "rqworker", "default", "--worker-class", - "cvat.simpleworker.SimpleWorker", + "cvat.rqworker.SimpleWorker", ], "django": true, "cwd": "${workspaceFolder}", @@ -117,7 +179,26 @@ "rqworker", "low", "--worker-class", - "cvat.simpleworker.SimpleWorker", + "cvat.rqworker.SimpleWorker", + ], + "django": true, + "cwd": "${workspaceFolder}", + "env": {}, + "console": "internalConsole" + }, + { + "name": "server: RQ - webhooks", + "type": "python", + "request": "launch", + "justMyCode": false, + "stopOnEntry": false, + "python": "${command:python.interpreterPath}", + "program": "${workspaceRoot}/manage.py", + "args": [ + "rqworker", + "webhooks", + "--worker-class", + "cvat.rqworker.SimpleWorker", ], "django": true, "cwd": "${workspaceFolder}", @@ -169,7 +250,97 @@ "--settings", "cvat.settings.testing", "cvat/apps", - "utils/cli" + "cvat-cli/" + ], + "django": true, + "cwd": "${workspaceFolder}", + "env": {}, + "console": "internalConsole" + }, + { + "name": "server: REST API tests", + "type": "python", + "request": "launch", + "justMyCode": false, + "stopOnEntry": false, + "python": "${command:python.interpreterPath}", + "module": "pytest", + "args": [ + "tests/python/rest_api/" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal" + }, + { + "name": "sdk: tests", + "type": "python", + "request": "launch", + "justMyCode": false, + "stopOnEntry": false, + "python": "${command:python.interpreterPath}", + "module": "pytest", + "args": [ + "tests/python/sdk/" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal" + }, + { + "name": "cli: tests", + "type": "python", + "request": "launch", + "justMyCode": false, + "stopOnEntry": false, + "python": "${command:python.interpreterPath}", + "module": "pytest", + "args": [ + "tests/python/cli/" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal" + }, + { + "name": "api client: Postprocess generator output", + "type": "python", + "request": "launch", + "justMyCode": false, + "stopOnEntry": false, + "python": "${command:python.interpreterPath}", + "program": "${workspaceFolder}/cvat-sdk/gen/postprocess.py", + "args": [ + "--schema", "${workspaceFolder}/cvat-sdk/schema/schema.yml", + "--input-path", "${workspaceFolder}/cvat-sdk/cvat_sdk/" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal" + }, + { + "name": "sdk docs: Postprocess generated docs", + "type": "python", + "request": "launch", + "justMyCode": false, + "stopOnEntry": false, + "python": "${command:python.interpreterPath}", + "program": "${workspaceFolder}/site/process_sdk_docs.py", + "args": [ + "--input-dir", "${workspaceFolder}/cvat-sdk/docs/", + "--site-root", "${workspaceFolder}/site/", + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal" + }, + { + "name": "server: Generate REST API Schema", + "type": "python", + "request": "launch", + "justMyCode": false, + "stopOnEntry": false, + "python": "${command:python.interpreterPath}", + "program": "${workspaceRoot}/manage.py", + "args": [ + "spectacular", + "--file", + "schema.yml" ], "django": true, "cwd": "${workspaceFolder}", @@ -210,9 +381,10 @@ "server: django", "server: RQ - default", "server: RQ - low", + "server: RQ - webhooks", "server: RQ - scheduler", "server: git", ] } ] -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 10bb3ed27b4c..5d766603e3f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,160 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## \[2.3.0] - 2022-12-22 +### Added +- SDK section in docs () +- An option to enable or disable host certificate checking in CLI () +- REST API tests with skeletons () +- Host schema auto-detection in SDK () +- Server compatibility checks in SDK () +- Objects sorting option in the sidebar, by z order. Additional visualization when the sorting is applied +() +- Added YOLOv5 serverless function NVIDIA GPU support () +- Mask tools are supported now (brush, eraser, polygon-plus, polygon-minus, returning masks +from online detectors & interactors) () +- Added Webhooks () +- Authentication with social accounts google & github (, , ) +- REST API tests to export job datasets & annotations and validate their structure () +- Propagation backward on UI () +- A PyTorch dataset adapter layer in the SDK + () +- A way to debug the server deployed with Docker () + +### Changed +- `api/docs`, `api/swagger`, `api/schema`, `server/about` endpoints now allow unauthorized access (, ) +- 3D canvas now can be dragged in IDLE mode () +- Datumaro version is upgraded to 0.3 (dev) () +- Allowed trailing slashes in the SDK host address () +- Adjusted initial camera position, enabled 'Reset zoom' option for 3D canvas () +- Enabled authentication via email () +- Unify error handling with the cloud storage () +- In the SDK, functions taking paths as strings now also accept path-like objects + () + +### Removed +- The `--https` option of CLI () + +### Fixed +- Significantly optimized access to DB for api/jobs, api/tasks, and api/projects. +- Removed a possibly duplicated encodeURI() calls in `server-proxy.ts` to prevent doubly encoding +non-ascii paths while adding files from "Connected file share" (issue #4428) +- Removed unnecessary volumes defined in docker-compose.serverless.yml +() +- Project import/export with skeletons (, + ) +- Shape color is not changed on canvas after changing a label () +- Unstable e2e restore tests () +- IOG and f-BRS serverless function () +- Invisible label item in label constructor when label color background is white, + or close to it () +- Fixed cvat-core ESlint problems () +- Fixed task creation with non-local files via the SDK/CLI + () +- HRNET serverless function () +- Invalid export of segmentation masks when the `background` label gets nonzero id () +- A trailing slash in hostname does't allow SDK to send some requests + () +- Double modal export/backup a task/project () +- Fixed bug of computing Job's unsolved/resolved issues numbers () +- Dataset export for job () +- Angle is not propagated when use ``propagate`` feature () +- Could not fetch task in a corner case () +- Restoring CVAT in case of React-renderning fail () +- Deleted frames become restored if a user deletes frames from another job of the same task +() +- Wrong issue position when create a quick issue on a rotated shape () +- Extra rerenders of different pages with each click () +- Skeleton points exported out of order in the COCO Keypoints format + () +- PASCAL VOC 1.1 can't import dataset () +- Changing an object causes current z layer to be set to the maximum () +- Job assignee can not resolve an issue () +- Create manifest with cvat/server docker container command () +- Cannot assign a resource to a user who has an organization () +- Logs and annotations are not saved when logout from a job page () +- Added "type" field for all the labels, allows to reduce number of controls on annotation view () +- Occluded not applied on canvas instantly for a skeleton elements () +- Oriented bounding boxes broken with COCO format ss() +- Can't dump annotations with objects type is track from several jobs () +- Fixed upload resumption in production environments + () +- Fixed job exporting () +- Visibility and ignored information fail to be loaded (MOT dataset format) () +- Added force logout on CVAT app start if token is missing () +- Drawing issues on 3D canvas () +- Missed token with using social account authentication () +- The same object on 3D scene or `null` selected each click (PERFORMANCE) () +- An exception when run export for an empty task () +- Fixed FBRS serverless function runtime error on images with alpha channel () +- Attaching manifest with custom name () +- Uploading non-zip annotaion files () +- Loss of rotation in CVAT format () +- A permission problem with interactive model launches for workers in orgs () +- Fix chart not being upgradable () +- Broken helm chart - if using custom release name () +- Missing source tag in project annotations () +- Creating a task with a Git repository via the SDK + () +- `Project.import_dataset` not waiting for completion correctly + () + +## \[2.2.0] - 2022-09-12 +### Added +- Added ability to delete frames from a job based on () +- Support of attributes returned by serverless functions based on () +- Project/task backups uploading via chunk uploads +- Fixed UX bug when jobs pagination is reset after changing a job +- Progressbars in CLI for file uploading and downloading +- `utils/cli` changed to `cvat-cli` package +- Support custom file name for backup +- Possibility to display tags on frame +- Support source and target storages (server part) +- Tests for import/export annotation, dataset, backup from/to cloud storage +- Added Python SDK package (`cvat-sdk`) () +- Previews for jobs +- Documentation for LDAP authentication () +- OpenCV.js caching and autoload () +- Publishing dev version of CVAT docker images () +- Support of Human Pose Estimation, Facial Landmarks (and similar) use-cases, new shape type: +Skeleton (), () +- Added helm chart support for serverless functions and analytics () +- Added confirmation when remove a track () +- [COCO Keypoints](https://cocodataset.org/#keypoints-2020) format support (, + ) +- Support for Oracle OCI Buckets () +- `cvat-sdk` and `cvat-cli` packages on PyPI () +- UI part for source and target storages () +- Backup import/export modals () +- Annotations import modal () + +### Changed +- Bumped nuclio version to 1.8.14 +- Simplified running REST API tests. Extended CI-nightly workflow +- REST API tests are partially moved to Python SDK (`users`, `projects`, `tasks`, `issues`) +- cvat-ui: Improve UI/UX on label, create task and create project forms () +- Removed link to OpenVINO documentation () +- Clarified meaning of chunking for videos + +### Fixed +- Task creation progressbar bug +- Removed Python dependency ``open3d`` which brought different issues to the building process +- Analytics not accessible when https is enabled +- Dataset import in an organization +- Updated minimist npm package to v1.2.6 +- Request Status Code 500 "StopIteration" when exporting dataset +- Generated OpenAPI schema for several endpoints +- Annotation window might have top offset if try to move a locked object +- Image search in cloud storage () +- Reset password functionality () +- Creating task with cloud storage data () +- Show empty tasks () +- Fixed project filtration () +- Maximum callstack exceed when create task with 100000+ files from cloud storage () +- Fixed invocation of serverless functions () +- Removing label attributes () +- Notification with a required manifest file () + ## \[2.1.0] - 2022-04-08 ### Added - Task annotations importing via chunk uploads () @@ -17,12 +171,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved helm chart readme () - Added helm chart support for CVAT 2.X and made ingress compatible with Kubernetes >=1.22 () -### Deprecated -- TDB - -### Removed -- TDB - ### Fixed - Permission error occured when accessing the JobCommits () - job assignee can remove or update any issue created by the task owner () @@ -31,9 +179,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Unable to upload annotations () - Fix build dependencies for Siammask () - Bug: Exif orientation information handled incorrectly () - -### Security -- TDB +- Fixed build of retinanet function image () +- Dataset import for Datumaro, KITTI and VGGFace2 formats () +- Bug: Import dataset of Imagenet format fail () ## \[2.0.0] - 2022-03-04 ### Added diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000000..b9410e0c974d --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,38 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: Computer Vision Annotation Tool (CVAT) +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software +authors: + - email: support+github@cvat.ai + name: CVAT.ai Corporation +identifiers: + - type: doi + value: 10.5281/zenodo.4009388 +repository-code: 'https://github.com/opencv/cvat' +url: 'http://cvat.ai/' +abstract: >- + Annotate better with CVAT, the industry-leading + data engine for machine learning. Used and trusted + by teams at any scale, for data of any scale. +keywords: + - image-labeling-tool + - computer-vision-annotation + - labeling-tool + - image-labeling + - semantic-segmentation + - annotation-tool + - object-detection + - image-classification + - video-annotation + - computer-vision + - deep-learning + - annotation +license: MIT +version: 2.2.0 +date-released: '2022-09-12' + diff --git a/Dockerfile b/Dockerfile index 1f6410f55dfb..121778ac3504 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,8 +47,14 @@ RUN python3 -m venv /opt/venv ENV PATH="/opt/venv/bin:${PATH}" RUN python3 -m pip install --no-cache-dir -U pip==22.0.2 setuptools==60.6.0 wheel==0.37.1 COPY cvat/requirements/ /tmp/requirements/ -RUN DATUMARO_HEADLESS=1 python3 -m pip install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt +COPY utils/dataset_manifest/ /tmp/dataset_manifest/ +# The server implementation depends on the dataset_manifest utility +# so we need to install its dependencies too +# https://github.com/opencv/cvat/issues/5096 +RUN DATUMARO_HEADLESS=1 python3 -m pip install --no-cache-dir \ + -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt \ + -r /tmp/dataset_manifest/requirements.txt FROM ubuntu:20.04 @@ -139,21 +145,30 @@ COPY --from=build-image /tmp/openh264/openh264*.tar.gz /tmp/ffmpeg/ffmpeg*.tar.g # Copy python virtual environment and FFmpeg binaries from build-image COPY --from=build-image /opt/venv /opt/venv ENV PATH="/opt/venv/bin:${PATH}" +ENV NUMPROCS=1 COPY --from=build-image /opt/ffmpeg /usr +# These variables are required for supervisord substitutions in files +# This library allows remote python debugging with VS Code +ARG CVAT_DEBUG_ENABLED +RUN if [ "${CVAT_DEBUG_ENABLED}" = 'yes' ]; then \ + python3 -m pip install --no-cache-dir debugpy; \ + fi + # Install and initialize CVAT, copy all necessary files COPY --chown=${USER} components /tmp/components +COPY --chown=${USER} supervisord/ ${HOME}/supervisord COPY --chown=${USER} ssh ${HOME}/.ssh -COPY --chown=${USER} supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/ -COPY --chown=${USER} cvat/ ${HOME}/cvat +COPY --chown=${USER} mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/ COPY --chown=${USER} utils/ ${HOME}/utils -COPY --chown=${USER} tests/ ${HOME}/tests +COPY --chown=${USER} cvat/ ${HOME}/cvat # RUN all commands below as 'django' user USER ${USER} WORKDIR ${HOME} -RUN mkdir data share media keys logs /tmp/supervisord +RUN mkdir -p data share keys logs /tmp/supervisord static EXPOSE 8080 ENTRYPOINT ["/usr/bin/supervisord"] +CMD ["-c", "supervisord/all.conf"] diff --git a/Dockerfile.ci b/Dockerfile.ci index 6d55179b3afa..ab01eb72da5f 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -1,4 +1,4 @@ -FROM openvino/cvat_server +FROM cvat/server ENV DJANGO_CONFIGURATION=testing USER root @@ -19,17 +19,19 @@ RUN apt-get update && \ google-chrome-stable \ nodejs \ && \ + npm install --global yarn && \ rm -rf /var/lib/apt/lists/*; -COPY cvat/requirements/ /tmp/requirements/ +COPY cvat/requirements/ /tmp/cvat/requirements/ -RUN DATUMARO_HEADLESS=1 python3 -m pip install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt && \ +RUN DATUMARO_HEADLESS=1 python3 -m pip install --no-cache-dir -r /tmp/cvat/requirements/${DJANGO_CONFIGURATION}.txt && \ python3 -m pip install --no-cache-dir coveralls RUN gem install coveralls-lcov -COPY utils ${HOME}/utils COPY cvat-core ${HOME}/cvat-core COPY cvat-data ${HOME}/cvat-data +COPY package.json ${HOME}/ +COPY yarn.lock ${HOME}/ COPY tests ${HOME}/tests COPY .coveragerc . diff --git a/Dockerfile.ui b/Dockerfile.ui index a6dd25992ecd..4d0cadae2165 100644 --- a/Dockerfile.ui +++ b/Dockerfile.ui @@ -16,16 +16,17 @@ ENV TERM=xterm \ LC_ALL='C.UTF-8' # Install dependencies -COPY package*.json /tmp/ -COPY cvat-core/package*.json /tmp/cvat-core/ -COPY cvat-canvas/package*.json /tmp/cvat-canvas/ -COPY cvat-canvas3d/package*.json /tmp/cvat-canvas3d/ -COPY cvat-ui/package*.json /tmp/cvat-ui/ -COPY cvat-data/package*.json /tmp/cvat-data/ +COPY package.json /tmp/ +COPY yarn.lock /tmp/ +COPY cvat-core/package.json /tmp/cvat-core/ +COPY cvat-canvas/package.json /tmp/cvat-canvas/ +COPY cvat-canvas3d/package.json /tmp/cvat-canvas3d/ +COPY cvat-ui/package.json /tmp/cvat-ui/ +COPY cvat-data/package.json /tmp/cvat-data/ # Install common dependencies WORKDIR /tmp/ -RUN npm ci --ignore-scripts +RUN yarn install --ignore-scripts --frozen-lockfile # Build source code COPY cvat-data/ /tmp/cvat-data/ @@ -33,7 +34,7 @@ COPY cvat-core/ /tmp/cvat-core/ COPY cvat-canvas3d/ /tmp/cvat-canvas3d/ COPY cvat-canvas/ /tmp/cvat-canvas/ COPY cvat-ui/ /tmp/cvat-ui/ -RUN npm run build:cvat-ui +RUN yarn run build:cvat-ui FROM nginx:mainline-alpine # Replace default.conf configuration to remove unnecessary rules diff --git a/LICENSE b/LICENSE index 10dca5629a58..9d2e6dc9fb75 100644 --- a/LICENSE +++ b/LICENSE @@ -1,31 +1,22 @@ MIT License -Copyright (C) 2018-2022 Intel Corporation -  +Copyright (c) 2018-2022 Intel Corporation +Copyright (c) 2022 CVAT.ai Corporation + Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom -the Software is furnished to do so, subject to the following conditions: -  -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. -  -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES -OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -OR OTHER DEALINGS IN THE SOFTWARE. -  -This software uses LGPL licensed libraries from the [FFmpeg](https://www.ffmpeg.org) project. -The exact steps on how FFmpeg was configured and compiled can be found in the [Dockerfile](Dockerfile). +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -FFmpeg is an open source framework licensed under LGPL and GPL. -See https://www.ffmpeg.org/legal.html. You are solely responsible -for determining if your use of FFmpeg requires any -additional licenses. Intel is not responsible for obtaining any -such licenses, nor liable for any licensing fees due in -connection with your use of FFmpeg. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 34791d46ca12..b642d5f25c02 100644 --- a/README.md +++ b/README.md @@ -1,186 +1,231 @@ +![CVAT logo](site/content/en/images/cvat_poster_with_name.png) + # Computer Vision Annotation Tool (CVAT) +CVAT – Computer Vision Annotation Tool - The open data annotation platform for AI | Product Hunt + [![CI][ci-img]][ci-url] [![Gitter chat][gitter-img]][gitter-url] +[![Discord][discord-img]][discord-url] [![Coverage Status][coverage-img]][coverage-url] [![server pulls][docker-server-pulls-img]][docker-server-image-url] [![ui pulls][docker-ui-pulls-img]][docker-ui-image-url] [![DOI][doi-img]][doi-url] -CVAT is free, online, interactive video and image annotation -tool for computer vision. It is being used by our team to -annotate million of objects with different properties. Many UI -and UX decisions are based on feedbacks from professional data -annotation team. Try it online [cvat.org](https://cvat.org). - -![CVAT screenshot](site/content/en/images/cvat.jpg) - -## Documentation - -- [Contributing](https://openvinotoolkit.github.io/cvat/docs/contributing/) -- [Installation guide](https://openvinotoolkit.github.io/cvat/docs/administration/basics/installation/) -- [Manual](https://openvinotoolkit.github.io/cvat/docs/manual/) -- [Django REST API documentation](https://openvinotoolkit.github.io/cvat/docs/administration/basics/rest_api_guide/) -- [Datumaro dataset framework](https://github.com/openvinotoolkit/datumaro/blob/develop/README.md) -- [Command line interface](https://openvinotoolkit.github.io/cvat/docs/manual/advanced/cli/) -- [XML annotation format](https://openvinotoolkit.github.io/cvat/docs/manual/advanced/xml_format/) -- [AWS Deployment Guide](https://openvinotoolkit.github.io/cvat/docs/administration/basics/aws-deployment-guide/) -- [Frequently asked questions](https://openvinotoolkit.github.io/cvat/docs/faq/) -- [Questions](#questions) - -## Screencasts - -- [Introduction](https://youtu.be/JERohTFp-NI) -- [Annotation mode](https://youtu.be/vH_639N67HI) -- [Interpolation of bounding boxes](https://youtu.be/Hc3oudNuDsY) -- [Interpolation of polygons](https://youtu.be/K4nis9lk92s) -- [Tag annotation video](https://youtu.be/62bI4mF-Xfk) -- [Attribute mode](https://youtu.be/iIkJsOkDzVA) -- [Segmentation mode](https://youtu.be/9Fe_GzMLo3E) -- [Tutorial for polygons](https://youtu.be/C7-r9lZbjBw) -- [Semi-automatic segmentation](https://youtu.be/9HszWP_qsRQ) +CVAT is an interactive video and image annotation +tool for computer vision. It is used by tens of thousands of users and +companies around the world. Our mission is to help developers, companies, and +organizations around the world to solve real problems using the Data-centric +AI approach. -## Supported annotation formats +CVAT is free and open-source. -Format selection is possible after clicking on the Upload annotation and Dump -annotation buttons. [Datumaro](https://github.com/openvinotoolkit/datumaro) -dataset framework allows additional dataset transformations via its command -line tool and Python library. +**A new repo**: CVAT core team moved the active development of the tool +to this new repository. -For more information about supported formats look at the -[documentation](https://openvinotoolkit.github.io/cvat/docs/manual/advanced/formats/). +Start using CVAT online for free: [cvat.ai](https://cvat.ai). +Or set it up as a self-hosted solution: +[Self-hosted Installation Guide](https://opencv.github.io/cvat/docs/administration/basics/installation/). - +![CVAT screencast](site/content/en/images/cvat-ai-screencast.gif) -| Annotation format | Import | Export | -| --------------------------------------------------------------------------------------------------------- | ------ | ------ | -| [CVAT for images](https://openvinotoolkit.github.io/cvat/docs/manual/advanced/xml_format/#annotation) | X | X | -| [CVAT for a video](https://openvinotoolkit.github.io/cvat/docs/manual/advanced/xml_format/#interpolation) | X | X | -| [Datumaro](https://github.com/openvinotoolkit/datumaro) | | X | -| [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | -| Segmentation masks from [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | X | X | -| [YOLO](https://pjreddie.com/darknet/yolo/) | X | X | -| [MS COCO Object Detection](http://cocodataset.org/#format-data) | X | X | -| [TFrecord](https://www.tensorflow.org/tutorials/load_data/tfrecord) | X | X | -| [MOT](https://motchallenge.net/) | X | X | -| [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0) | X | X | -| [ImageNet](http://www.image-net.org) | X | X | -| [CamVid](http://mi.eng.cam.ac.uk/research/projects/VideoRec/CamVid/) | X | X | -| [WIDER Face](http://shuoyang1213.me/WIDERFACE/) | X | X | -| [VGGFace2](https://github.com/ox-vgg/vgg_face2) | X | X | -| [Market-1501](https://www.aitribune.com/dataset/2018051063) | X | X | -| [ICDAR13/15](https://rrc.cvc.uab.es/?ch=2) | X | X | -| [Open Images V6](https://storage.googleapis.com/openimages/web/index.html) | X | X | -| [Cityscapes](https://www.cityscapes-dataset.com/login/) | X | X | -| [KITTI](http://www.cvlibs.net/datasets/kitti/) | X | X | -| [LFW](http://vis-www.cs.umass.edu/lfw/) | X | X | +## Quick start ⚡ - +- [Installation guide](https://opencv.github.io/cvat/docs/administration/basics/installation/) +- [Manual](https://opencv.github.io/cvat/docs/manual/) +- [Contributing](https://opencv.github.io/cvat/docs/contributing/) +- [Datumaro dataset framework](https://github.com/cvat-ai/datumaro/blob/develop/README.md) +- [Server API](#api) +- [Python SDK](#sdk) +- [Command line tool](#cli) +- [XML annotation format](https://opencv.github.io/cvat/docs/manual/advanced/xml_format/) +- [AWS Deployment Guide](https://opencv.github.io/cvat/docs/administration/basics/aws-deployment-guide/) +- [Frequently asked questions](https://opencv.github.io/cvat/docs/faq/) +- [Where to ask questions](#where-to-ask-questions) -## Deep learning serverless functions for automatic labeling +## Partners â¤ī¸ + +CVAT is used by teams all over the world. In the list, you can find key companies which +help us support the product or an essential part of our ecosystem. If you use us, +please drop us a line at [contact@cvat.ai](mailto:contact+github@cvat.ai). + +- [Human Protocol](https://hmt.ai) uses CVAT as a way of adding annotation service to the Human Protocol. +- [FiftyOne](https://fiftyone.ai) is an open-source dataset curation and model analysis + tool for visualizing, exploring, and improving computer vision datasets and models that are + [tightly integrated](https://voxel51.com/docs/fiftyone/integrations/cvat.html) with CVAT + for annotation and label refinement. + +## Public datasets + +[ATLANTIS](https://github.com/smhassanerfani/atlantis), an open-source dataset for semantic segmentation +of waterbody images, developed by [iWERS](http://ce.sc.edu/iwers/) group in the +Department of Civil and Environmental Engineering at the University of South Carolina is using CVAT. + +For developing a semantic segmentation dataset using CVAT, see: + +- [ATLANTIS published article](https://www.sciencedirect.com/science/article/pii/S1364815222000391) +- [ATLANTIS Development Kit](https://github.com/smhassanerfani/atlantis/tree/master/adk) +- [ATLANTIS annotation tutorial videos](https://www.youtube.com/playlist?list=PLIfLGY-zZChS5trt7Lc3MfNhab7OWl2BR). + +## CVAT online: [cvat.ai](https://cvat.ai) + +This is an online version of CVAT. It's free, efficient, and easy to use. + +[cvat.ai](https://cvat.ai) runs the latest version of the tool. You can create up +to 10 tasks there and upload up to 500Mb of data to annotate. It will only be +visible to you or the people you assign to it. + +For now, it does not have [analytics features](https://opencv.github.io/cvat/docs/administration/advanced/analytics/) +like management and monitoring the data annotation team. + +We plan to enhance [cvat.ai](https://cvat.ai) with new powerful features. Stay tuned! + +## Prebuilt Docker images đŸŗ + +Prebuilt docker images are the easiest way to start using CVAT locally. They are available on Docker Hub: + +- [cvat/server](https://hub.docker.com/r/cvat/server) +- [cvat/ui](https://hub.docker.com/r/cvat/ui) + +The images have been downloaded more than 1M times so far. + +## Screencasts đŸŽĻ + +Here are some screencasts showing how to use CVAT. -| Name | Type | Framework | CPU | GPU | -| ------------------------------------------------------------------------------------------------------- | ---------- | ---------- | --- | --- | -| [Deep Extreme Cut](/serverless/openvino/dextr/nuclio) | interactor | OpenVINO | X | | -| [Faster RCNN](/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio) | detector | OpenVINO | X | | -| [Mask RCNN](/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio) | detector | OpenVINO | X | | -| [YOLO v3](/serverless/openvino/omz/public/yolo-v3-tf/nuclio) | detector | OpenVINO | X | | -| [Object reidentification](/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio) | reid | OpenVINO | X | | -| [Semantic segmentation for ADAS](/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio) | detector | OpenVINO | X | | -| [Text detection v4](/serverless/openvino/omz/intel/text-detection-0004/nuclio) | detector | OpenVINO | X | | -| [YOLO v5](/serverless/pytorch/ultralytics/yolov5/nuclio) | detector | PyTorch | X | | -| [SiamMask](/serverless/pytorch/foolwood/siammask/nuclio) | tracker | PyTorch | X | X | -| [f-BRS](/serverless/pytorch/saic-vul/fbrs/nuclio) | interactor | PyTorch | X | | -| [HRNet](/serverless/pytorch/saic-vul/hrnet/nuclio) | interactor | PyTorch | | X | -| [Inside-Outside Guidance](/serverless/pytorch/shiyinzhang/iog/nuclio) | interactor | PyTorch | X | | -| [Faster RCNN](/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio) | detector | TensorFlow | X | X | -| [Mask RCNN](/serverless/tensorflow/matterport/mask_rcnn/nuclio) | detector | TensorFlow | X | X | -| [RetinaNet](serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio) | detector | PyTorch | X | X | -| [Face Detection](/serverless/openvino/omz/intel/face-detection-0205/nuclio) | detector | OpenVINO | X | | +[Computer Vision Annotation Course](https://www.youtube.com/playlist?list=PL0to7Ng4PuuYQT4eXlHb_oIlq_RPeuasN): we introduce our course series designed to help you annotate data faster and better using CVAT. This course is about CVAT deployment and integrations, it includes presentations and covers the following topics: + +- **Speeding up your data annotation process: introduction to CVAT and Datumaro**. What problems do CVAT and Datumaro solve, and how they can speed up your model training process. Some resources you can use to learn more about how to use them. +- **Deployment and use CVAT**. Use the app online at [app.cvat.ai](app.cvat.ai). A local deployment. A containerized local deployment with docker-compose (for regular use), and a local cluster deployment with Kubernetes (for enterprise users). A 2-minute tour of the interface, a breakdown of CVAT’s internals, and a demonstration of how to deploy CVAT using docker-compose. + +[Product tour](https://www.youtube.com/playlist?list=PL0to7Ng4Puua37NJVMIShl_pzqJTigFzg): in this course, we show how to use CVAT, and help to get familiar with CVAT functionality and interfaces. This course does not cover integrations and is dedicated solely to CVAT. It covers the following topics: + +- **Pipeline**. In this video, we show how to use [app.cvat.ai](app.cvat.ai): how to sign up, upload your data, annotate it, and download it. -## Online demo: [cvat.org](https://cvat.org) +For feedback, please see [Contact us](#contact-us) + +## API -This is an online demo with the latest version of the annotation tool. -Try it online without local installation. Only own or assigned tasks -are visible to users. +- [Documentation](https://opencv.github.io/cvat/docs/api_sdk/api/) -Disabled features: +## SDK -- [Analytics: management and monitoring of data annotation team](https://openvinotoolkit.github.io/cvat/docs/administration/advanced/analytics/) +- Install with `pip install cvat-sdk` +- [PyPI package homepage](https://pypi.org/project/cvat-sdk/) +- [Documentation](https://opencv.github.io/cvat/docs/api_sdk/sdk/) -Limitations: +## CLI -- No more than 10 tasks per user -- Uploaded data is limited to 500Mb +- Install with `pip install cvat-cli` +- [PyPI package homepage](https://pypi.org/project/cvat-cli/) +- [Documentation](https://opencv.github.io/cvat/docs/api_sdk/cli/) -## Prebuilt Docker images +## Supported annotation formats + +CVAT supports multiple annotation formats. You can select the format +after clicking the **Upload annotation** and **Dump annotation** buttons. +[Datumaro](https://github.com/cvat-ai/datumaro) dataset framework allows +additional dataset transformations with its command line tool and Python library. + +For more information about the supported formats, see: +[Annotation Formats](https://opencv.github.io/cvat/docs/manual/advanced/formats/). -Prebuilt docker images for CVAT releases are available on Docker Hub: + -- [cvat_server](https://hub.docker.com/r/openvino/cvat_server) -- [cvat_ui](https://hub.docker.com/r/openvino/cvat_ui) +| Annotation format | Import | Export | +| ------------------------------------------------------------------------------------------------ | ------ | ------ | +| [CVAT for images](https://opencv.github.io/cvat/docs/manual/advanced/xml_format/#annotation) | âœ”ī¸ | âœ”ī¸ | +| [CVAT for a video](https://opencv.github.io/cvat/docs/manual/advanced/xml_format/#interpolation) | âœ”ī¸ | âœ”ī¸ | +| [Datumaro](https://github.com/cvat-ai/datumaro) | âœ”ī¸ | âœ”ī¸ | +| [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | âœ”ī¸ | âœ”ī¸ | +| Segmentation masks from [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) | âœ”ī¸ | âœ”ī¸ | +| [YOLO](https://pjreddie.com/darknet/yolo/) | âœ”ī¸ | âœ”ī¸ | +| [MS COCO Object Detection](http://cocodataset.org/#format-data) | âœ”ī¸ | âœ”ī¸ | +| [MS COCO Keypoints Detection](http://cocodataset.org/#format-data) | âœ”ī¸ | âœ”ī¸ | +| [TFrecord](https://www.tensorflow.org/tutorials/load_data/tfrecord) | âœ”ī¸ | âœ”ī¸ | +| [MOT](https://motchallenge.net/) | âœ”ī¸ | âœ”ī¸ | +| [MOTS PNG](https://www.vision.rwth-aachen.de/page/mots) | âœ”ī¸ | âœ”ī¸ | +| [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0) | âœ”ī¸ | âœ”ī¸ | +| [ImageNet](http://www.image-net.org) | âœ”ī¸ | âœ”ī¸ | +| [CamVid](http://mi.eng.cam.ac.uk/research/projects/VideoRec/CamVid/) | âœ”ī¸ | âœ”ī¸ | +| [WIDER Face](http://shuoyang1213.me/WIDERFACE/) | âœ”ī¸ | âœ”ī¸ | +| [VGGFace2](https://github.com/ox-vgg/vgg_face2) | âœ”ī¸ | âœ”ī¸ | +| [Market-1501](https://www.aitribune.com/dataset/2018051063) | âœ”ī¸ | âœ”ī¸ | +| [ICDAR13/15](https://rrc.cvc.uab.es/?ch=2) | âœ”ī¸ | âœ”ī¸ | +| [Open Images V6](https://storage.googleapis.com/openimages/web/index.html) | âœ”ī¸ | âœ”ī¸ | +| [Cityscapes](https://www.cityscapes-dataset.com/login/) | âœ”ī¸ | âœ”ī¸ | +| [KITTI](http://www.cvlibs.net/datasets/kitti/) | âœ”ī¸ | âœ”ī¸ | +| [Kitti Raw Format](https://www.cvlibs.net/datasets/kitti/raw_data.php) | âœ”ī¸ | âœ”ī¸ | +| [LFW](http://vis-www.cs.umass.edu/lfw/) | âœ”ī¸ | âœ”ī¸ | +| [Supervisely Point Cloud Format](https://docs.supervise.ly/data-organization/00_ann_format_navi) | âœ”ī¸ | âœ”ī¸ | -## REST API -The current REST API version is `2.0-alpha`. We focus on its improvement and therefore -REST API may be changed in the next release. + -## LICENSE +## Deep learning serverless functions for automatic labeling -Code released under the [MIT License](https://opensource.org/licenses/MIT). +CVAT supports automatic labeling. It can speed up the annotation process +up to 10x. Here is a list of the algorithms we support, and the platforms they can be run on: -This software uses LGPL licensed libraries from the [FFmpeg](https://www.ffmpeg.org) project. + + +| Name | Type | Framework | CPU | GPU | +| ------------------------------------------------------------------------------------------------------- | ---------- | ---------- | --- | --- | +| [Deep Extreme Cut](/serverless/openvino/dextr/nuclio) | interactor | OpenVINO | âœ”ī¸ | | +| [Faster RCNN](/serverless/openvino/omz/public/faster_rcnn_inception_v2_coco/nuclio) | detector | OpenVINO | âœ”ī¸ | | +| [Mask RCNN](/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio) | detector | OpenVINO | âœ”ī¸ | | +| [YOLO v3](/serverless/openvino/omz/public/yolo-v3-tf/nuclio) | detector | OpenVINO | âœ”ī¸ | | +| [Object reidentification](/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio) | reid | OpenVINO | âœ”ī¸ | | +| [Semantic segmentation for ADAS](/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio) | detector | OpenVINO | âœ”ī¸ | | +| [Text detection v4](/serverless/openvino/omz/intel/text-detection-0004/nuclio) | detector | OpenVINO | âœ”ī¸ | | +| [YOLO v5](/serverless/pytorch/ultralytics/yolov5/nuclio) | detector | PyTorch | âœ”ī¸ | | +| [SiamMask](/serverless/pytorch/foolwood/siammask/nuclio) | tracker | PyTorch | âœ”ī¸ | âœ”ī¸ | +| [TransT](/serverless/pytorch/dschoerk/transt/nuclio) | tracker | PyTorch | âœ”ī¸ | âœ”ī¸ | +| [f-BRS](/serverless/pytorch/saic-vul/fbrs/nuclio) | interactor | PyTorch | âœ”ī¸ | | +| [HRNet](/serverless/pytorch/saic-vul/hrnet/nuclio) | interactor | PyTorch | | âœ”ī¸ | +| [Inside-Outside Guidance](/serverless/pytorch/shiyinzhang/iog/nuclio) | interactor | PyTorch | âœ”ī¸ | | +| [Faster RCNN](/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio) | detector | TensorFlow | âœ”ī¸ | âœ”ī¸ | +| [Mask RCNN](/serverless/tensorflow/matterport/mask_rcnn/nuclio) | detector | TensorFlow | âœ”ī¸ | âœ”ī¸ | +| [RetinaNet](serverless/pytorch/facebookresearch/detectron2/retinanet/nuclio) | detector | PyTorch | âœ”ī¸ | âœ”ī¸ | +| [Face Detection](/serverless/openvino/omz/intel/face-detection-0205/nuclio) | detector | OpenVINO | âœ”ī¸ | | + + + +## License + +The code is released under the [MIT License](https://opensource.org/licenses/MIT). + +This software uses LGPL-licensed libraries from the [FFmpeg](https://www.ffmpeg.org) project. The exact steps on how FFmpeg was configured and compiled can be found in the [Dockerfile](Dockerfile). -FFmpeg is an open source framework licensed under LGPL and GPL. +FFmpeg is an open-source framework licensed under LGPL and GPL. See [https://www.ffmpeg.org/legal.html](https://www.ffmpeg.org/legal.html). You are solely responsible for determining if your use of FFmpeg requires any -additional licenses. Intel is not responsible for obtaining any +additional licenses. CVAT.ai Corporation is not responsible for obtaining any such licenses, nor liable for any licensing fees due in connection with your use of FFmpeg. -## Partners - -- [ATLANTIS](https://github.com/smhassanerfani/atlantis) is an open-source dataset for semantic segmentation - of waterbody images, depevoped by [iWERS](http://ce.sc.edu/iwers/) group in the - Department of Civil and Environmental Engineering at University of South Carolina, using CVAT. - For developing a semantic segmentation dataset using CVAT, please check - [ATLANTIS published article](https://www.sciencedirect.com/science/article/pii/S1364815222000391), - [ATLANTIS Development Kit](https://github.com/smhassanerfani/atlantis/tree/master/adk) - and [annotation tutorial videos](https://www.youtube.com/playlist?list=PLIfLGY-zZChS5trt7Lc3MfNhab7OWl2BR). -- [Onepanel](https://github.com/onepanelio/core) is an open source - vision AI platform that fully integrates CVAT with scalable data processing - and parallelized training pipelines. -- [DataIsKey](https://dataiskey.eu/annotation-tool/) uses CVAT as their prime data labeling tool - to offer annotation services for projects of any size. -- [Human Protocol](https://hmt.ai) uses CVAT as a way of adding annotation service to the human protocol. -- [Cogito Tech LLC](https://bit.ly/3klT0h6), a Human-in-the-Loop Workforce Solutions Provider, used CVAT - in annotation of about 5,000 images for a brand operating in the fashion segment. -- [FiftyOne](https://fiftyone.ai) is an open-source dataset curation and model analysis -tool for visualizing, exploring, and improving computer vision datasets and models that is -[tightly integrated](https://voxel51.com/docs/fiftyone/integrations/cvat.html) with CVAT -for annotation and label refinement. +## Contact us + +[Gitter](https://gitter.im/opencv-cvat/public) to ask CVAT usage-related questions. +Typically questions get answered fast by the core team or community. There you can also browse other common questions. -## Questions +[Discord](https://discord.gg/S6sRHhuQ7K) is the place to also ask questions or discuss any other stuff related to CVAT. -CVAT usage related questions or unclear concepts can be posted in our -[Gitter chat](https://gitter.im/opencv-cvat) for **quick replies** from -contributors and other users. +[LinkedIn](https://www.linkedin.com/company/cvat-ai/) for the company and work-related questions. -However, if you have a feature request or a bug report that can reproduced, -feel free to open an issue (with steps to reproduce the bug if it's a bug -report) on [GitHub\* issues](https://github.com/opencv/cvat/issues). +[YouTube](https://www.youtube.com/@cvat-ai) to see screencast and tutorials about the CVAT. -If you are not sure or just want to browse other users common questions, -[Gitter chat](https://gitter.im/opencv-cvat) is the way to go. +[GitHub issues](https://github.com/cvat-ai/cvat/issues) for feature requests or bug reports. +If it's a bug, please add the steps to reproduce it. -Other ways to ask questions and get our support: +[#cvat](https://stackoverflow.com/search?q=%23cvat) tag on StackOverflow is one more way to ask +questions and get our support. -- [\#cvat](https://stackoverflow.com/search?q=%23cvat) tag on StackOverflow\* -- [Forum on Intel Developer Zone](https://software.intel.com/en-us/forums/computer-vision) +[contact@cvat.ai](mailto:contact+github@cvat.ai) to reach out to us if you need commercial support. ## Links @@ -191,15 +236,17 @@ Other ways to ask questions and get our support: -[docker-server-pulls-img]: https://img.shields.io/docker/pulls/openvino/cvat_server.svg?style=flat-square&label=server%20pulls -[docker-server-image-url]: https://hub.docker.com/r/openvino/cvat_server -[docker-ui-pulls-img]: https://img.shields.io/docker/pulls/openvino/cvat_ui.svg?style=flat-square&label=UI%20pulls -[docker-ui-image-url]: https://hub.docker.com/r/openvino/cvat_ui -[ci-img]: https://github.com/openvinotoolkit/cvat/workflows/CI/badge.svg?branch=develop -[ci-url]: https://github.com/openvinotoolkit/cvat/actions -[gitter-img]: https://badges.gitter.im/opencv-cvat/gitter.png +[docker-server-pulls-img]: https://img.shields.io/docker/pulls/cvat/server.svg?style=flat-square&label=server%20pulls +[docker-server-image-url]: https://hub.docker.com/r/cvat/server +[docker-ui-pulls-img]: https://img.shields.io/docker/pulls/cvat/ui.svg?style=flat-square&label=UI%20pulls +[docker-ui-image-url]: https://hub.docker.com/r/cvat/ui +[ci-img]: https://github.com/opencv/cvat/workflows/CI/badge.svg?branch=develop +[ci-url]: https://github.com/opencv/cvat/actions +[gitter-img]: https://img.shields.io/gitter/room/opencv-cvat/public?style=flat [gitter-url]: https://gitter.im/opencv-cvat -[coverage-img]: https://coveralls.io/repos/github/openvinotoolkit/cvat/badge.svg?branch=develop -[coverage-url]: https://coveralls.io/github/openvinotoolkit/cvat?branch=develop +[coverage-img]: https://coveralls.io/repos/github/cvat-ai/cvat/badge.svg?branch=develop +[coverage-url]: https://coveralls.io/github/cvat-ai/cvat?branch=develop [doi-img]: https://zenodo.org/badge/139156354.svg [doi-url]: https://zenodo.org/badge/latestdoi/139156354 +[discord-img]: https://img.shields.io/discord/1000789942802337834?label=discord +[discord-url]: https://discord.gg/fNR3eXfk6C diff --git a/SECURITY.md b/SECURITY.md index 3f9e48cc83d7..9aa8bdccdefe 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,36 +13,12 @@ be sure it can be reproduced in the supported version. ## Reporting a Vulnerability If you have information about a security issue or vulnerability in the product, please -send an e-mail to [secure@intel.com](mailto:secure@intel.com). Encrypt sensitive information -using our PGP public key. +send an e-mail to [secure@cvat.ai](mailto:secure+github@cvat.ai). Please provide as much information as possible, including: - The products and versions affected - Detailed description of the vulnerability - Information on known exploits -- A member of the Intel Product Security Team will review your e-mail and contact you to +- A member of the CVAT.ai Product Security Team will review your e-mail and contact you to collaborate on resolving the issue. - -For more information on how Intel works to resolve security issues, see: -[Vulnerability handling guidelines]() - -## IntelÂŽ Bug Bounty Program - -Intel Corporation believes that working with skilled security researchers across the globe -is a crucial part of identifying and mitigating security vulnerabilities in Intel products. - -Like other major technology companies, Intel incentivizes security researchers to report -security vulnerabilities in Intel products to us to enable a coordinated response. To -encourage closer collaboration with the security research community on these kinds of issues, -Intel created its Bug Bounty Program. - -If you believe you've found a security vulnerability in an Intel product or technology, we -encourage you to notify us through our program and work with us to mitigate and to coordinate -disclosure of the vulnerability. - -[IntelÂŽ Bug Bounty Program Terms]() - -Watch this video, [So You Found a Vulnerability](), -to find out what you can expect when participating in the IntelÂŽ Bug Bounty Program. - diff --git a/components/analytics/docker-compose.analytics.yml b/components/analytics/docker-compose.analytics.yml index 4a8c769c42a7..ceae95219980 100644 --- a/components/analytics/docker-compose.analytics.yml +++ b/components/analytics/docker-compose.analytics.yml @@ -23,13 +23,15 @@ services: args: ELK_VERSION: 6.8.23 depends_on: ['elasticsearch'] + environment: + - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 restart: always cvat_kibana_setup: container_name: cvat_kibana_setup - image: openvino/cvat_server + image: cvat/server:${CVAT_VERSION:-dev} volumes: ['./components/analytics/kibana:/home/django/kibana:ro'] - depends_on: ['cvat'] + depends_on: ['cvat_server'] working_dir: '/home/django' networks: - cvat @@ -72,7 +74,7 @@ services: depends_on: ['elasticsearch'] restart: always - cvat: + cvat_server: environment: DJANGO_LOG_SERVER_HOST: logstash DJANGO_LOG_SERVER_PORT: 8080 diff --git a/components/analytics/kibana/kibana.yml b/components/analytics/kibana/kibana.yml index 4fba9171112f..0459f3441cbb 100644 --- a/components/analytics/kibana/kibana.yml +++ b/components/analytics/kibana/kibana.yml @@ -1,5 +1,4 @@ server.host: 0.0.0.0 -elasticsearch.url: http://elasticsearch:9200 elasticsearch.requestHeadersWhitelist: ['cookie', 'authorization', 'x-forwarded-user'] kibana.defaultAppId: 'discover' server.basePath: /analytics diff --git a/components/analytics/kibana/setup.py b/components/analytics/kibana/setup.py index 198edb2cbe85..2335fcb7da19 100644 --- a/components/analytics/kibana/setup.py +++ b/components/analytics/kibana/setup.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 Intel Corporation +# Copyright (C) 2021-2022 Intel Corporation # # SPDX-License-Identifier: MIT diff --git a/components/analytics/kibana_conf.yml b/components/analytics/kibana_conf.yml index 347d561105f1..e5ccc75b3239 100644 --- a/components/analytics/kibana_conf.yml +++ b/components/analytics/kibana_conf.yml @@ -8,11 +8,20 @@ http: - strip-prefix service: kibana rule: Host(`{{ env "CVAT_HOST" }}`) && PathPrefix(`/analytics`) + kibana_https: + entryPoints: + - websecure + middlewares: + - analytics-auth + - strip-prefix + service: kibana + tls: {} + rule: Host(`{{ env "CVAT_HOST" }}`) && PathPrefix(`/analytics`) middlewares: analytics-auth: forwardauth: - address: http://cvat:8080/analytics + address: http://cvat_server:8080/analytics authRequestHeaders: - "Cookie" - "Authorization" diff --git a/components/analytics/logstash/logstash.yml b/components/analytics/logstash/logstash.yml index 73f412c139ed..01d2873030cc 100644 --- a/components/analytics/logstash/logstash.yml +++ b/components/analytics/logstash/logstash.yml @@ -1,3 +1,4 @@ queue.type: persisted queue.max_bytes: 1gb queue.checkpoint.writes: 20 +http.host: 0.0.0.0 diff --git a/components/serverless/docker-compose.serverless.yml b/components/serverless/docker-compose.serverless.yml index 34414dfc7331..bfd829b8de5c 100644 --- a/components/serverless/docker-compose.serverless.yml +++ b/components/serverless/docker-compose.serverless.yml @@ -2,7 +2,7 @@ version: '3.3' services: nuclio: container_name: nuclio - image: quay.io/nuclio/dashboard:1.5.16-amd64 + image: quay.io/nuclio/dashboard:1.8.14-amd64 restart: always networks: - cvat @@ -17,10 +17,18 @@ services: NUCLIO_DASHBOARD_DEFAULT_FUNCTION_MOUNT_MODE: 'volume' ports: - '8070:8070' + logging: + driver: "json-file" + options: + max-size: 100m + max-file: "3" - cvat: + cvat_server: environment: CVAT_SERVERLESS: 1 + extra_hosts: + - "host.docker.internal:host-gateway" -volumes: - cvat_events: + cvat_worker_low: + extra_hosts: + - "host.docker.internal:host-gateway" diff --git a/cvat-canvas/.eslintrc.js b/cvat-canvas/.eslintrc.js index 8971d6ab584b..ab68a0338f42 100644 --- a/cvat-canvas/.eslintrc.js +++ b/cvat-canvas/.eslintrc.js @@ -1,13 +1,8 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT -const globalConfig = require('../.eslintrc.js'); - module.exports = { - env: { - node: true, - }, ignorePatterns: [ '.eslintrc.js', 'webpack.config.js', @@ -15,31 +10,7 @@ module.exports = { 'dist/**', ], parserOptions: { - parser: '@typescript-eslint/parser', - ecmaVersion: 6, project: './tsconfig.json', tsconfigRootDir: __dirname, }, - plugins: ['@typescript-eslint'], - extends: ['plugin:@typescript-eslint/recommended', 'airbnb-typescript/base'], - rules: { - ...globalConfig.rules, - - '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/indent': ['error', 4], - '@typescript-eslint/lines-between-class-members': 0, - '@typescript-eslint/no-explicit-any': [0], - '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }], - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/ban-types': [ - 'error', - { - types: { - '{}': false, // TODO: try to fix with Record - object: false, // TODO: try to fix with Record - Function: false, // TODO: try to fix somehow - }, - }, - ], - }, }; diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index 68939171a625..ca1ae15c3c53 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -9,17 +9,17 @@ It presents a canvas to viewing, drawing and editing of annotations. If you make changes in this package, please do following: -- After not important changes (typos, backward compatible bug fixes, refactoring) do: `npm version patch` -- After changing API (backward compatible new features) do: `npm version minor` -- After changing API (changes that break backward compatibility) do: `npm version major` +- After not important changes (typos, backward compatible bug fixes, refactoring) do: `yarn version --patch` +- After changing API (backward compatible new features) do: `yarn version --minor` +- After changing API (changes that break backward compatibility) do: `yarn version --major` ## Commands - Building of the module from sources in the `dist` directory: ```bash -npm run build -npm run build -- --mode=development # without a minification +yarn run build +yarn run build --mode=development # without a minification ``` ## Using @@ -62,8 +62,19 @@ Canvas itself handles: } interface Configuration { - displayAllText?: boolean; - undefinedAttrValue?: string; + smoothImage?: boolean; + autoborders?: boolean; + displayAllText?: boolean; + textFontSize?: number; + textPosition?: 'auto' | 'center'; + textContent?: string; + undefinedAttrValue?: string; + showProjections?: boolean; + forceDisableEditing?: boolean; + intelligentPolygonCrop?: boolean; + forceFrameUpdate?: boolean; + creationOpacity?: number; + CSSImageFilter?: string; } interface DrawData { diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json deleted file mode 100644 index 4c915d045a79..000000000000 --- a/cvat-canvas/package-lock.json +++ /dev/null @@ -1,169 +0,0 @@ -{ - "name": "cvat-canvas", - "version": "2.13.2", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "cvat-canvas", - "version": "2.13.2", - "license": "MIT", - "dependencies": { - "@types/polylabel": "^1.0.5", - "polylabel": "^1.1.0", - "svg.draggable.js": "2.2.2", - "svg.draw.js": "^2.0.4", - "svg.js": "2.7.1", - "svg.resize.js": "1.4.3", - "svg.select.js": "3.0.1" - } - }, - "node_modules/@types/polylabel": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/polylabel/-/polylabel-1.0.5.tgz", - "integrity": "sha512-gnaNmo1OJiYNBFAZMZdqLZ3hKx2ee4ksAzqhKWBxuQ61PmhINHMcvIqsGmyCD1WFKCkwRt9NFhMSmKE6AgYY+w==" - }, - "node_modules/polylabel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/polylabel/-/polylabel-1.1.0.tgz", - "integrity": "sha512-bxaGcA40sL3d6M4hH72Z4NdLqxpXRsCFk8AITYg6x1rn1Ei3izf00UMLklerBZTO49aPA3CYrIwVulx2Bce2pA==", - "dependencies": { - "tinyqueue": "^2.0.3" - } - }, - "node_modules/svg.draggable.js": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", - "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", - "dependencies": { - "svg.js": "^2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.draw.js": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg.draw.js/-/svg.draw.js-2.0.4.tgz", - "integrity": "sha512-NMbecB0vg11AP76B0aLfI2cX7g9WurPM8x3yKxuJ9feM1vkI1GVjWZZjWpo3mkEzB1UJ8pKngaPaUCIOGi8uUA==", - "dependencies": { - "svg.js": "2.x.x" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.js": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", - "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" - }, - "node_modules/svg.resize.js": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", - "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", - "dependencies": { - "svg.js": "^2.6.5", - "svg.select.js": "^2.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.resize.js/node_modules/svg.select.js": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", - "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", - "dependencies": { - "svg.js": "^2.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.select.js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", - "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", - "dependencies": { - "svg.js": "^2.6.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/tinyqueue": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", - "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" - } - }, - "dependencies": { - "@types/polylabel": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/polylabel/-/polylabel-1.0.5.tgz", - "integrity": "sha512-gnaNmo1OJiYNBFAZMZdqLZ3hKx2ee4ksAzqhKWBxuQ61PmhINHMcvIqsGmyCD1WFKCkwRt9NFhMSmKE6AgYY+w==" - }, - "polylabel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/polylabel/-/polylabel-1.1.0.tgz", - "integrity": "sha512-bxaGcA40sL3d6M4hH72Z4NdLqxpXRsCFk8AITYg6x1rn1Ei3izf00UMLklerBZTO49aPA3CYrIwVulx2Bce2pA==", - "requires": { - "tinyqueue": "^2.0.3" - } - }, - "svg.draggable.js": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", - "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", - "requires": { - "svg.js": "^2.0.1" - } - }, - "svg.draw.js": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg.draw.js/-/svg.draw.js-2.0.4.tgz", - "integrity": "sha512-NMbecB0vg11AP76B0aLfI2cX7g9WurPM8x3yKxuJ9feM1vkI1GVjWZZjWpo3mkEzB1UJ8pKngaPaUCIOGi8uUA==", - "requires": { - "svg.js": "2.x.x" - } - }, - "svg.js": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", - "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" - }, - "svg.resize.js": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", - "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", - "requires": { - "svg.js": "^2.6.5", - "svg.select.js": "^2.1.2" - }, - "dependencies": { - "svg.select.js": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", - "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", - "requires": { - "svg.js": "^2.2.5" - } - } - } - }, - "svg.select.js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", - "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", - "requires": { - "svg.js": "^2.6.5" - } - }, - "tinyqueue": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", - "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" - } - } -} diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index 4c5d7da82d56..b97d416139f0 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -1,13 +1,13 @@ { "name": "cvat-canvas", - "version": "2.13.2", + "version": "2.16.1", "description": "Part of Computer Vision Annotation Tool which presents its canvas library", "main": "src/canvas.ts", "scripts": { "build": "tsc && webpack --config ./webpack.config.js", "server": "nodemon --watch config --exec 'webpack-dev-server --config ./webpack.config.js --mode=development --open'" }, - "author": "Intel", + "author": "CVAT.ai", "license": "MIT", "browserslist": [ "Chrome >= 63", @@ -17,6 +17,8 @@ ], "dependencies": { "@types/polylabel": "^1.0.5", + "@types/fabric": "^4.5.7", + "fabric": "^5.2.1", "polylabel": "^1.1.0", "svg.draggable.js": "2.2.2", "svg.draw.js": "^2.0.4", diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 273010568143..556ba4a337de 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -1,4 +1,5 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -10,6 +11,12 @@ stroke-opacity: 1; } +g.cvat_canvas_shape { + > circle { + fill-opacity: 1; + } +} + polyline.cvat_canvas_shape { fill-opacity: 0; } @@ -65,6 +72,10 @@ polyline.cvat_shape_drawing_opacity { fill: darkmagenta; } +image.cvat_canvas_shape_grouping { + visibility: hidden; +} + .cvat_canvas_shape_region_selection { @extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_opacity; @@ -93,6 +104,10 @@ polyline.cvat_canvas_shape_grouping { @extend .cvat_shape_action_opacity; fill: blue; + + > circle[data-node-id] { + fill: blue; + } } polyline.cvat_canvas_shape_merging { @@ -120,7 +135,6 @@ polyline.cvat_canvas_shape_splitting { @extend .cvat_shape_drawing_opacity; fill: white; - stroke: black; } .cvat_canvas_zoom_selection { @@ -134,6 +148,25 @@ polyline.cvat_canvas_shape_splitting { stroke-dasharray: 5; } +.cvat_canvas_shape_occluded_point { + stroke-dasharray: 1 !important; + stroke: white; +} + +circle.cvat_canvas_shape_occluded { + @extend .cvat_canvas_shape_occluded_point; +} + +g.cvat_canvas_shape_occluded { + > rect { + stroke-dasharray: 5; + } + + > circle { + @extend .cvat_canvas_shape_occluded_point; + } +} + .svg_select_points_rot { fill: white; } @@ -226,6 +259,12 @@ polyline.cvat_canvas_shape_splitting { } } +.cvat_canvas_skeleton_wrapping_rect { + // wrapping rect must not apply transform attribute from selectize.js + // otherwise it rotated twice, because we apply the same rotation value to parent element (skeleton itself) + transform: none !important; +} + .cvat_canvas_pixelized { image-rendering: optimizeSpeed; /* Legal fallback */ image-rendering: -moz-crisp-edges; /* Firefox */ @@ -237,6 +276,10 @@ polyline.cvat_canvas_shape_splitting { -ms-interpolation-mode: nearest-neighbor; /* IE8+ */ } +.cvat_canvas_removed_image { + filter: saturate(0) brightness(1.2) contrast(0.75) !important; +} + #cvat_canvas_wrapper { width: calc(100% - 10px); height: calc(100% - 10px); @@ -314,6 +357,11 @@ polyline.cvat_canvas_shape_splitting { height: 100%; } +.cvat_masks_canvas_wrapper { + z-index: 3; + display: none; +} + #cvat_canvas_attachment_board { position: absolute; z-index: 4; diff --git a/cvat-canvas/src/typescript/autoborderHandler.ts b/cvat-canvas/src/typescript/autoborderHandler.ts index 625317cf3cce..aaf119d04f92 100644 --- a/cvat-canvas/src/typescript/autoborderHandler.ts +++ b/cvat-canvas/src/typescript/autoborderHandler.ts @@ -1,11 +1,11 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT import * as SVG from 'svg.js'; import consts from './consts'; -import { Geometry } from './canvasModel'; +import { Configuration, Geometry } from './canvasModel'; interface TransformedShape { points: string; @@ -14,6 +14,7 @@ interface TransformedShape { export interface AutoborderHandler { autoborder(enabled: boolean, currentShape?: SVG.Shape, currentID?: number): void; + configurate(configuration: Configuration): void; transform(geometry: Geometry): void; updateObjects(): void; } @@ -24,19 +25,14 @@ export class AutoborderHandlerImpl implements AutoborderHandler { private frameContent: SVGSVGElement; private enabled: boolean; private scale: number; + private controlPointsSize: number; private groups: SVGGElement[]; private auxiliaryGroupID: number | null; private auxiliaryClicks: number[]; - private listeners: Record< - number, - Record< - number, - { + private listeners: Record void; dblclick: (event: MouseEvent) => void; - } - > - >; + }>>; public constructor(frameContent: SVGSVGElement) { this.frameContent = frameContent; @@ -45,6 +41,7 @@ export class AutoborderHandlerImpl implements AutoborderHandler { this.enabled = false; this.scale = 1; this.groups = []; + this.controlPointsSize = consts.BASE_POINT_SIZE; this.auxiliaryGroupID = null; this.auxiliaryClicks = []; this.listeners = {}; @@ -126,7 +123,7 @@ export class AutoborderHandlerImpl implements AutoborderHandler { circle.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / this.scale}`); circle.setAttribute('cx', x); circle.setAttribute('cy', y); - circle.setAttribute('r', `${consts.BASE_POINT_SIZE / this.scale}`); + circle.setAttribute('r', `${this.controlPointsSize / this.scale}`); const click = (event: MouseEvent): void => { event.stopPropagation(); @@ -303,9 +300,13 @@ export class AutoborderHandlerImpl implements AutoborderHandler { this.scale = geometry.scale; this.groups.forEach((group: SVGGElement): void => { Array.from(group.children).forEach((circle: SVGCircleElement): void => { - circle.setAttribute('r', `${consts.BASE_POINT_SIZE / this.scale}`); + circle.setAttribute('r', `${this.controlPointsSize / this.scale}`); circle.setAttribute('stroke-width', `${consts.BASE_STROKE_WIDTH / this.scale}`); }); }); } + + public configurate(configuration: Configuration): void { + this.controlPointsSize = configuration.controlPointsSize || consts.BASE_POINT_SIZE; + } } diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index aec02854a446..9b4c6c60d11a 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -1,4 +1,5 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -8,6 +9,7 @@ import { MergeData, SplitData, GroupData, + MasksEditData, InteractionData as _InteractionData, InteractionResult as _InteractionResult, CanvasModel, @@ -38,6 +40,7 @@ interface Canvas { interact(interactionData: InteractionData): void; draw(drawData: DrawData): void; + edit(editData: MasksEditData): void; group(groupData: GroupData): void; split(splitData: SplitData): void; merge(mergeData: MergeData): void; @@ -129,6 +132,10 @@ class CanvasImpl implements Canvas { this.model.draw(drawData); } + public edit(editData: MasksEditData): void { + this.model.edit(editData); + } + public split(splitData: SplitData): void { this.model.split(splitData); } diff --git a/cvat-canvas/src/typescript/canvasController.ts b/cvat-canvas/src/typescript/canvasController.ts index 7ec577f57808..d8b50fc05db2 100644 --- a/cvat-canvas/src/typescript/canvasController.ts +++ b/cvat-canvas/src/typescript/canvasController.ts @@ -1,4 +1,5 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -15,6 +16,7 @@ import { Mode, InteractionData, Configuration, + MasksEditData, } from './canvasModel'; export interface CanvasController { @@ -24,6 +26,7 @@ export interface CanvasController { readonly focusData: FocusData; readonly activeElement: ActiveElement; readonly drawData: DrawData; + readonly editData: MasksEditData; readonly interactionData: InteractionData; readonly mergeData: MergeData; readonly splitData: SplitData; @@ -35,6 +38,7 @@ export interface CanvasController { zoom(x: number, y: number, direction: number): void; draw(drawData: DrawData): void; + edit(editData: MasksEditData): void; interact(interactionData: InteractionData): void; merge(mergeData: MergeData): void; split(splitData: SplitData): void; @@ -91,6 +95,10 @@ export class CanvasControllerImpl implements CanvasController { this.model.draw(drawData); } + public edit(editData: MasksEditData): void { + this.model.edit(editData); + } + public interact(interactionData: InteractionData): void { this.model.interact(interactionData); } @@ -143,6 +151,10 @@ export class CanvasControllerImpl implements CanvasController { return this.model.drawData; } + public get editData(): MasksEditData { + return this.model.editData; + } + public get interactionData(): InteractionData { return this.model.interactionData; } diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index bc43af8fec42..b7591f02277b 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -1,4 +1,5 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -52,6 +53,12 @@ export enum CuboidDrawingMethod { CORNER_POINTS = 'By 4 points', } +export enum ColorBy { + INSTANCE = 'Instance', + GROUP = 'Group', + LABEL = 'Label', +} + export interface Configuration { smoothImage?: boolean; autoborders?: boolean; @@ -64,18 +71,35 @@ export interface Configuration { forceDisableEditing?: boolean; intelligentPolygonCrop?: boolean; forceFrameUpdate?: boolean; - creationOpacity?: number; + CSSImageFilter?: string; + colorBy?: ColorBy; + selectedShapeOpacity?: number; + shapeOpacity?: number; + controlPointsSize?: number; + outlinedBorders?: string | false; +} + +export interface BrushTool { + type: 'brush' | 'eraser' | 'polygon-plus' | 'polygon-minus'; + color: string; + form: 'circle' | 'square'; + size: number; } export interface DrawData { enabled: boolean; + continue?: boolean; shapeType?: string; rectDrawingMethod?: RectDrawingMethod; cuboidDrawingMethod?: CuboidDrawingMethod; + skeletonSVG?: string; numberOfPoints?: number; initialState?: any; crosshair?: boolean; + brushTool?: BrushTool; redraw?: number; + onDrawDone?: (data: object) => void; + onUpdateConfiguration?: (configuration: { brushTool?: Pick }) => void; } export interface InteractionData { @@ -101,12 +125,19 @@ export interface InteractionResult { button: number; } -export interface EditData { +export interface PolyEditData { enabled: boolean; state: any; pointID: number; } +export interface MasksEditData { + enabled: boolean; + state?: any; + brushTool?: BrushTool; + onUpdateConfiguration?: (configuration: { brushTool?: Pick }) => void; +} + export interface GroupData { enabled: boolean; } @@ -140,6 +171,7 @@ export enum UpdateReasons { INTERACT = 'interact', DRAW = 'draw', + EDIT = 'edit', MERGE = 'merge', SPLIT = 'split', GROUP = 'group', @@ -171,6 +203,7 @@ export enum Mode { export interface CanvasModel { readonly imageBitmap: boolean; + readonly imageIsDeleted: boolean; readonly image: Image | null; readonly issueRegions: Record; readonly objects: any[]; @@ -179,6 +212,7 @@ export interface CanvasModel { readonly focusData: FocusData; readonly activeElement: ActiveElement; readonly drawData: DrawData; + readonly editData: MasksEditData; readonly interactionData: InteractionData; readonly mergeData: MergeData; readonly splitData: SplitData; @@ -201,6 +235,7 @@ export interface CanvasModel { grid(stepX: number, stepY: number): void; draw(drawData: DrawData): void; + edit(editData: MasksEditData): void; group(groupData: GroupData): void; split(splitData: SplitData): void; merge(mergeData: MergeData): void; @@ -219,6 +254,50 @@ export interface CanvasModel { destroy(): void; } +const defaultData = { + drawData: { + enabled: false, + }, + editData: { + enabled: false, + }, + interactionData: { + enabled: false, + }, + mergeData: { + enabled: false, + }, + groupData: { + enabled: false, + }, + splitData: { + enabled: false, + }, +}; + +function hasShapeIsBeingDrawn(): boolean { + const [element] = window.document.getElementsByClassName('cvat_canvas_shape_drawing'); + if (element) { + return !!(element as any).instance.remember('_paintHandler'); + } + + return false; +} + +function disableInternalSVGDrawing(data: DrawData | MasksEditData, currentData: DrawData | MasksEditData): boolean { + // P.S. spaghetti code, but probably significant refactoring needed to find a better solution + // when it is a mask drawing/editing using polygon fill + // a user needs to close drawing/editing twice + // first close stops internal drawing/editing with svg.js + // the second one stops drawing/editing mask itself + + return !data.enabled && currentData.enabled && + (('shapeType' in currentData && currentData.shapeType === 'mask') || + ('state' in currentData && currentData.state.shapeType === 'mask')) && + currentData.brushTool?.type?.startsWith('polygon-') && + hasShapeIsBeingDrawn(); +} + export class CanvasModelImpl extends MasterImpl implements CanvasModel { private data: { activeElement: ActiveElement; @@ -230,6 +309,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { imageID: number | null; imageOffset: number; imageSize: Size; + imageIsDeleted: boolean; focusData: FocusData; gridSize: Size; left: number; @@ -239,6 +319,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { top: number; zLayer: number | null; drawData: DrawData; + editData: MasksEditData; interactionData: InteractionData; mergeData: MergeData; groupData: GroupData; @@ -262,12 +343,23 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { width: 0, }, configuration: { - displayAllText: false, + smoothImage: true, autoborders: false, - undefinedAttrValue: '', - textContent: 'id,label,attributes,source,descriptions', - textPosition: 'auto', + displayAllText: false, + showProjections: false, + forceDisableEditing: false, + intelligentPolygonCrop: false, + forceFrameUpdate: false, + CSSImageFilter: '', + colorBy: ColorBy.LABEL, + selectedShapeOpacity: 0.5, + shapeOpacity: 0.2, + outlinedBorders: false, textFontSize: consts.DEFAULT_SHAPE_TEXT_SIZE, + controlPointsSize: consts.BASE_POINT_SIZE, + textPosition: consts.DEFAULT_SHAPE_TEXT_POSITION, + textContent: consts.DEFAULT_SHAPE_TEXT_CONTENT, + undefinedAttrValue: consts.DEFAULT_UNDEFINED_ATTR_VALUE, }, imageBitmap: false, image: null, @@ -277,6 +369,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { height: 0, width: 0, }, + imageIsDeleted: false, focusData: { clientID: 0, padding: 0, @@ -291,25 +384,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { scale: 1, top: 0, zLayer: null, - drawData: { - enabled: false, - initialState: null, - }, - interactionData: { - enabled: false, - }, - mergeData: { - enabled: false, - }, - groupData: { - enabled: false, - }, - splitData: { - enabled: false, - }, selected: null, mode: Mode.IDLE, exception: null, + ...defaultData, }; } @@ -406,7 +484,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { throw Error(`Canvas is busy. Action: ${this.data.mode}`); } } - if (frameData.number === this.data.imageID && !this.data.configuration.forceFrameUpdate) { + if (frameData.number === this.data.imageID && + frameData.deleted === this.data.imageIsDeleted && + !this.data.configuration.forceFrameUpdate + ) { this.data.zLayer = zLayer; this.data.objects = objectStates; this.notify(UpdateReasons.OBJECTS_UPDATED); @@ -431,6 +512,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { }; this.data.image = data; + this.data.imageIsDeleted = frameData.deleted; + if (this.data.imageIsDeleted) { + this.data.angle = 0; + } this.notify(UpdateReasons.IMAGE_CHANGED); this.data.zLayer = zLayer; this.data.objects = objectStates; @@ -476,7 +561,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { } public rotate(rotationAngle: number): void { - if (this.data.angle !== rotationAngle) { + if (this.data.angle !== rotationAngle && !this.data.imageIsDeleted) { this.data.angle = (360 + Math.floor(rotationAngle / 90) * 90) % 360; this.fit(); } @@ -530,9 +615,11 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { } if (drawData.enabled) { - if (this.data.drawData.enabled) { - throw new Error('Drawing has been already started'); - } else if (!drawData.shapeType && !drawData.initialState) { + if (drawData.shapeType === 'skeleton' && !drawData.skeletonSVG) { + throw new Error('Skeleton template must be specified when drawing a skeleton'); + } + + if (!drawData.shapeType && !drawData.initialState) { throw new Error('A shape type is not specified'); } else if (typeof drawData.numberOfPoints !== 'undefined') { if (drawData.shapeType === 'polygon' && drawData.numberOfPoints < 3) { @@ -554,6 +641,11 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { return; } } else { + if (disableInternalSVGDrawing(drawData, this.data.drawData)) { + this.notify(UpdateReasons.DRAW); + return; + } + this.data.drawData = { ...drawData }; if (this.data.drawData.initialState) { this.data.drawData.shapeType = this.data.drawData.initialState.shapeType; @@ -573,6 +665,33 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.notify(UpdateReasons.DRAW); } + public edit(editData: MasksEditData): void { + if (![Mode.IDLE, Mode.EDIT].includes(this.data.mode)) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + + if (editData.enabled && !editData.state) { + throw Error('State must be specified when call edit() editing process'); + } + + if (this.data.editData.enabled && editData.enabled && + editData.state.clientID !== this.data.editData.state.clientID + ) { + throw Error('State cannot be updated during editing, need to finish current editing first'); + } + + if (editData.enabled) { + this.data.editData = { ...editData }; + } else if (disableInternalSVGDrawing(editData, this.data.editData)) { + this.notify(UpdateReasons.EDIT); + return; + } else { + this.data.editData = { enabled: false }; + } + + this.notify(UpdateReasons.EDIT); + } + public interact(interactionData: InteractionData): void { if (![Mode.IDLE, Mode.INTERACT].includes(this.data.mode)) { throw Error(`Canvas is busy. Action: ${this.data.mode}`); @@ -659,6 +778,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.configuration.textFontSize = configuration.textFontSize; } + if (typeof configuration.controlPointsSize === 'number') { + this.data.configuration.controlPointsSize = configuration.controlPointsSize; + } + if (['auto', 'center'].includes(configuration.textPosition)) { this.data.configuration.textPosition = configuration.textPosition; } @@ -691,8 +814,21 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { if (typeof configuration.forceFrameUpdate === 'boolean') { this.data.configuration.forceFrameUpdate = configuration.forceFrameUpdate; } - if (typeof configuration.creationOpacity === 'number') { - this.data.configuration.creationOpacity = configuration.creationOpacity; + if (typeof configuration.selectedShapeOpacity === 'number') { + this.data.configuration.selectedShapeOpacity = configuration.selectedShapeOpacity; + } + if (typeof configuration.shapeOpacity === 'number') { + this.data.configuration.shapeOpacity = configuration.shapeOpacity; + } + if (['string', 'boolean'].includes(typeof configuration.outlinedBorders)) { + this.data.configuration.outlinedBorders = configuration.outlinedBorders; + } + if (Object.values(ColorBy).includes(configuration.colorBy)) { + this.data.configuration.colorBy = configuration.colorBy; + } + + if (typeof configuration.CSSImageFilter === 'string') { + this.data.configuration.CSSImageFilter = configuration.CSSImageFilter; } this.notify(UpdateReasons.CONFIG_UPDATED); @@ -706,6 +842,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { } public cancel(): void { + this.data = { + ...this.data, + ...defaultData, + }; this.notify(UpdateReasons.CANCEL); } @@ -753,6 +893,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { return this.data.imageBitmap; } + public get imageIsDeleted(): boolean { + return this.data.imageIsDeleted; + } + public get image(): Image | null { return this.data.image; } @@ -785,6 +929,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { return { ...this.data.drawData }; } + public get editData(): MasksEditData { + return { ...this.data.editData }; + } + public get interactionData(): InteractionData { return { ...this.data.interactionData }; } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 4cc1377f54b0..1eeafbf1ee59 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1,8 +1,10 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import polylabel from 'polylabel'; +import { fabric } from 'fabric'; import * as SVG from 'svg.js'; import 'svg.draggable.js'; @@ -12,6 +14,7 @@ import 'svg.select.js'; import { CanvasController } from './canvasController'; import { Listener, Master } from './master'; import { DrawHandler, DrawHandlerImpl } from './drawHandler'; +import { MasksHandler, MasksHandlerImpl } from './masksHandler'; import { EditHandler, EditHandlerImpl } from './editHandler'; import { MergeHandler, MergeHandlerImpl } from './mergeHandler'; import { SplitHandler, SplitHandlerImpl } from './splitHandler'; @@ -35,6 +38,10 @@ import { DrawnState, rotate2DPoints, readPointsFromShape, + setupSkeletonEdges, + makeSVGFromTemplate, + imageDataToDataURL, + expandChannels, } from './shared'; import { CanvasModel, @@ -51,6 +58,7 @@ import { Configuration, InteractionResult, InteractionData, + ColorBy, } from './canvasModel'; export interface CanvasView { @@ -62,6 +70,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private text: SVGSVGElement; private adoptedText: SVG.Container; private background: HTMLCanvasElement; + private masksContent: HTMLCanvasElement; private bitmap: HTMLCanvasElement; private grid: SVGSVGElement; private content: SVGSVGElement; @@ -79,6 +88,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private drawnIssueRegions: Record; private geometry: Geometry; private drawHandler: DrawHandler; + private masksHandler: MasksHandler; private editHandler: EditHandler; private mergeHandler: MergeHandler; private splitHandler: SplitHandler; @@ -92,6 +102,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private snapToAngleResize: number; private innerObjectsFlags: { drawHidden: Record; + editHidden: Record; }; private set mode(value: Mode) { @@ -117,20 +128,29 @@ export class CanvasViewImpl implements CanvasView, Listener { return translateFromCanvas(offset, points); } - private translatePointsFromRotatedShape(shape: SVG.Shape, points: number[]): number[] { + private translatePointsFromRotatedShape( + shape: SVG.Shape, points: number[], cx: number = null, cy: number = null, + ): number[] { const { rotation } = shape.transform(); - // currently shape is rotated and shifted somehow additionally (css transform property) + // currently shape is rotated and SHIFTED somehow additionally (css transform property) // let's remove rotation to get correct transformation matrix (element -> screen) // correct means that we do not consider points to be rotated // because rotation property is stored separately and already saved - shape.rotate(0); + if (cx !== null && cy !== null) { + shape.rotate(0, cx, cy); + } else { + shape.rotate(0); + } + const result = []; try { // get each point and apply a couple of matrix transformation to it const point = this.content.createSVGPoint(); - // matrix to convert from ELEMENT file system to CLIENT coordinate system - const ctm = ((shape.node as any) as SVGRectElement | SVGPolygonElement | SVGPolylineElement).getScreenCTM(); + // matrix to convert from ELEMENT coordinate system to CLIENT coordinate system + const ctm = ( + (shape.node as any) as SVGRectElement | SVGPolygonElement | SVGPolylineElement | SVGGElement + ).getScreenCTM(); // matrix to convert from CLIENT coordinate system to CANVAS coordinate system const ctm1 = this.content.getScreenCTM().inverse(); // NOTE: I tried to use element.getCTM(), but this way does not work on firefox @@ -144,7 +164,11 @@ export class CanvasViewImpl implements CanvasView, Listener { result.push(transformedPoint.x, transformedPoint.y); } } finally { - shape.rotate(rotation); + if (cx !== null && cy !== null) { + shape.rotate(rotation, cx, cy); + } else { + shape.rotate(rotation); + } } return result; @@ -164,7 +188,7 @@ export class CanvasViewImpl implements CanvasView, Listener { return this.innerObjectsFlags.drawHidden[clientID] || false; } - private setupInnerFlags(clientID: number, path: 'drawHidden', value: boolean): void { + private setupInnerFlags(clientID: number, path: 'drawHidden' | 'editHidden', value: boolean): void { this.innerObjectsFlags[path][clientID] = value; const shape = this.svgShapes[clientID]; const text = this.svgTexts[clientID]; @@ -193,7 +217,7 @@ export class CanvasViewImpl implements CanvasView, Listener { if (text) { text.removeClass('cvat_canvas_hidden'); - this.updateTextPosition(text, shape); + this.updateTextPosition(text); } } } @@ -237,7 +261,7 @@ export class CanvasViewImpl implements CanvasView, Listener { } } - private onDrawDone(data: any | null, duration: number, continueDraw?: boolean): void { + private onDrawDone(data: any | null, duration: number, continueDraw?: boolean, prevDrawData?: DrawData): void { const hiddenBecauseOfDraw = Object.keys(this.innerObjectsFlags.drawHidden) .map((_clientID): number => +_clientID); if (hiddenBecauseOfDraw.length) { @@ -247,18 +271,19 @@ export class CanvasViewImpl implements CanvasView, Listener { } if (data) { - const { clientID, points } = data as any; + const { clientID, elements } = data as any; + const points = data.points || elements.map((el: any) => el.points).flat(); if (typeof clientID === 'number') { + const [state] = this.controller.objects + .filter((_state: any): boolean => _state.clientID === clientID); + this.onEditDone(state, points); + const event: CustomEvent = new CustomEvent('canvas.canceled', { bubbles: false, cancelable: true, }); this.canvas.dispatchEvent(event); - - const [state] = this.controller.objects.filter((_state: any): boolean => _state.clientID === clientID); - - this.onEditDone(state, points); return; } @@ -279,23 +304,50 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas.dispatchEvent(event); } else if (!continueDraw) { - const event: CustomEvent = new CustomEvent('canvas.canceled', { + this.canvas.dispatchEvent(new CustomEvent('canvas.canceled', { bubbles: false, cancelable: true, - }); - - this.canvas.dispatchEvent(event); + })); } - if (!continueDraw) { - this.mode = Mode.IDLE; - this.controller.draw({ - enabled: false, - }); + if (continueDraw) { + this.canvas.dispatchEvent( + new CustomEvent('canvas.drawstart', { + bubbles: false, + cancelable: true, + detail: { + drawData: prevDrawData, + }, + }), + ); + } else { + // when draw stops from inside canvas (for example if use predefined number of points) + this.controller.draw({ enabled: false }); } } - private onEditDone(state: any, points: number[], rotation?: number): void { + private onEditStart = (state?: any): void => { + this.canvas.style.cursor = 'crosshair'; + this.deactivate(); + this.canvas.dispatchEvent( + new CustomEvent('canvas.editstart', { + bubbles: false, + cancelable: true, + detail: { + state, + }, + }), + ); + + if (state && state.shapeType === 'mask') { + this.setupInnerFlags(state.clientID, 'editHidden', true); + } + + this.mode = Mode.EDIT; + }; + + private onEditDone = (state: any, points: number[], rotation?: number): void => { + this.canvas.style.cursor = ''; if (state && points) { const event: CustomEvent = new CustomEvent('canvas.edited', { bubbles: false, @@ -317,8 +369,11 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas.dispatchEvent(event); } + for (const clientID of Object.keys(this.innerObjectsFlags.editHidden)) { + this.setupInnerFlags(+clientID, 'editHidden', false); + } this.mode = Mode.IDLE; - } + }; private onMergeDone(objects: any[] | null, duration?: number): void { if (objects) { @@ -341,10 +396,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas.dispatchEvent(event); } - this.controller.merge({ - enabled: false, - }); - + this.controller.merge({ enabled: false }); this.mode = Mode.IDLE; } @@ -369,10 +421,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas.dispatchEvent(event); } - this.controller.split({ - enabled: false, - }); - + this.controller.split({ enabled: false }); this.mode = Mode.IDLE; } @@ -396,10 +445,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas.dispatchEvent(event); } - this.controller.group({ - enabled: false, - }); - + this.controller.group({ enabled: false }); this.mode = Mode.IDLE; } @@ -442,7 +488,6 @@ export class CanvasViewImpl implements CanvasView, Listener { }); this.canvas.dispatchEvent(event); - e.preventDefault(); } } @@ -506,6 +551,7 @@ export class CanvasViewImpl implements CanvasView, Listener { // Transform handlers this.drawHandler.transform(this.geometry); + this.masksHandler.transform(this.geometry); this.editHandler.transform(this.geometry); this.zoomHandler.transform(this.geometry); this.autoborderHandler.transform(this.geometry); @@ -515,7 +561,13 @@ export class CanvasViewImpl implements CanvasView, Listener { private transformCanvas(): void { // Transform canvas - for (const obj of [this.background, this.grid, this.content, this.bitmap, this.attachmentBoard]) { + for (const obj of [ + this.background, + this.grid, + this.content, + this.bitmap, + this.attachmentBoard, + ]) { obj.style.transform = `scale(${this.geometry.scale}) rotate(${this.geometry.angle}deg)`; } @@ -526,9 +578,10 @@ export class CanvasViewImpl implements CanvasView, Listener { for (const element of [ ...window.document.getElementsByClassName('svg_select_points'), ...window.document.getElementsByClassName('svg_select_points_rot'), + ...window.document.getElementsByClassName('svg_select_boundingRect'), ]) { element.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / this.geometry.scale}`); - element.setAttribute('r', `${consts.BASE_POINT_SIZE / this.geometry.scale}`); + element.setAttribute('r', `${this.configuration.controlPointsSize / this.geometry.scale}`); } for (const element of window.document.getElementsByClassName('cvat_canvas_poly_direction')) { @@ -544,24 +597,24 @@ export class CanvasViewImpl implements CanvasView, Listener { element.setAttribute('stroke-width', `${+previousWidth * 2}`); } - // Transform all drawn shapes - for (const key in this.svgShapes) { - if (Object.prototype.hasOwnProperty.call(this.svgShapes, key)) { - const object = this.svgShapes[key]; - object.attr({ - 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - }); + // Transform all drawn shapes and text + for (const key of Object.keys(this.svgShapes)) { + const clientID = +key; + const object = this.svgShapes[clientID]; + object.attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + }); + if (object.type === 'circle') { + object.attr('r', `${this.configuration.controlPointsSize / this.geometry.scale}`); + } + if (clientID in this.svgTexts) { + this.updateTextPosition(this.svgTexts[clientID]); } } - // Transform all text - for (const key in this.svgShapes) { - if ( - Object.prototype.hasOwnProperty.call(this.svgShapes, key) && - Object.prototype.hasOwnProperty.call(this.svgTexts, key) - ) { - this.updateTextPosition(this.svgTexts[key], this.svgShapes[key]); - } + // Transform skeleton edges + for (const skeletonEdge of window.document.getElementsByClassName('cvat_canvas_skeleton_edge')) { + skeletonEdge.setAttribute('stroke-width', `${consts.BASE_STROKE_WIDTH / this.geometry.scale}`); } // Transform all drawn issues region @@ -587,6 +640,7 @@ export class CanvasViewImpl implements CanvasView, Listener { // Transform handlers this.drawHandler.transform(this.geometry); + this.masksHandler.transform(this.geometry); this.editHandler.transform(this.geometry); this.zoomHandler.transform(this.geometry); this.autoborderHandler.transform(this.geometry); @@ -595,7 +649,7 @@ export class CanvasViewImpl implements CanvasView, Listener { } private resizeCanvas(): void { - for (const obj of [this.background, this.grid, this.bitmap]) { + for (const obj of [this.background, this.masksContent, this.grid, this.bitmap]) { obj.style.width = `${this.geometry.image.width}px`; obj.style.height = `${this.geometry.image.height}px`; } @@ -666,6 +720,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private setupObjects(states: any[]): void { const created = []; const updated = []; + for (const state of states) { if (!(state.clientID in this.drawnStates)) { created.push(state); @@ -690,7 +745,16 @@ export class CanvasViewImpl implements CanvasView, Listener { this.deleteObjects(deleted); this.addObjects(created); - this.updateObjects(updated); + + const updatedSkeletons = updated.filter((state: any): boolean => state.shapeType === 'skeleton'); + const updatedNotSkeletons = updated.filter((state: any): boolean => state.shapeType !== 'skeleton'); + // todo: implement updateObjects for skeletons, add group and color to updateObjects function + // change colors if necessary (for example when instance color is changed) + this.updateObjects(updatedNotSkeletons); + + this.deleteObjects(updatedSkeletons); + this.addObjects(updatedSkeletons); + this.sortObjects(); if (this.controller.activeElement.clientID !== null) { @@ -803,15 +867,7 @@ export class CanvasViewImpl implements CanvasView, Listener { const { points } = state; this.onEditDone(state, points.slice(0, pointID * 2).concat(points.slice(pointID * 2 + 2))); } else if (e.shiftKey) { - this.canvas.dispatchEvent( - new CustomEvent('canvas.editstart', { - bubbles: false, - cancelable: true, - }), - ); - - this.mode = Mode.EDIT; - this.deactivate(); + this.onEditStart(state); this.editHandler.edit({ enabled: true, state, @@ -871,8 +927,9 @@ export class CanvasViewImpl implements CanvasView, Listener { const getActiveElement = (): ActiveElement => this.activeElement; (shape as any).selectize(value, { deepSelect: true, - pointSize: (2 * consts.BASE_POINT_SIZE) / this.geometry.scale, + pointSize: (2 * this.configuration.controlPointsSize) / this.geometry.scale, rotationPoint: shape.type === 'rect' || shape.type === 'ellipse', + pointsExclude: shape.type === 'image' ? ['lt', 'rt', 'rb', 'lb', 't', 'r', 'b', 'l'] : [], pointType(cx: number, cy: number): SVG.Circle { const circle: SVG.Circle = this.nested .circle(this.options.pointSize) @@ -934,11 +991,31 @@ export class CanvasViewImpl implements CanvasView, Listener { } const [rotationPoint] = window.document.getElementsByClassName('svg_select_points_rot'); + const [topPoint] = window.document.getElementsByClassName('svg_select_points_t'); if (rotationPoint && !rotationPoint.children.length) { + if (topPoint) { + const rotY = +(rotationPoint as SVGEllipseElement).getAttribute('cy'); + const topY = +(topPoint as SVGEllipseElement).getAttribute('cy'); + (rotationPoint as SVGCircleElement).style.transform = `translate(0px, -${rotY - topY + 20}px)`; + } + const title = document.createElementNS('http://www.w3.org/2000/svg', 'title'); title.textContent = 'Hold Shift to snap angle'; rotationPoint.appendChild(title); } + + if (value && shape.type === 'image') { + const [boundingRect] = window.document.getElementsByClassName('svg_select_boundingRect'); + if (boundingRect) { + (boundingRect as SVGRectElement).style.opacity = '1'; + boundingRect.setAttribute('fill', 'none'); + boundingRect.setAttribute('stroke', shape.attr('stroke')); + boundingRect.setAttribute('stroke-width', `${consts.BASE_STROKE_WIDTH / this.geometry.scale}px`); + if (shape.hasClass('cvat_canvas_shape_occluded')) { + boundingRect.setAttribute('stroke-dasharray', '5'); + } + } + } } private onShiftKeyDown = (e: KeyboardEvent): void => { @@ -946,8 +1023,15 @@ export class CanvasViewImpl implements CanvasView, Listener { this.snapToAngleResize = consts.SNAP_TO_ANGLE_RESIZE_SHIFT; if (this.activeElement) { const shape = this.svgShapes[this.activeElement.clientID]; - if (shape && shape.hasClass('cvat_canvas_shape_activated')) { - (shape as any).resize({ snapToAngle: this.snapToAngleResize }); + if (shape && shape?.remember('_selectHandler')?.options?.rotationPoint) { + if (this.drawnStates[this.activeElement.clientID]?.shapeType === 'skeleton') { + const wrappingRect = (shape as any).children().find((child: SVG.Element) => child.type === 'rect'); + if (wrappingRect) { + (wrappingRect as any).resize({ snapToAngle: this.snapToAngleResize }); + } + } else { + (shape as any).resize({ snapToAngle: this.snapToAngleResize }); + } } } } @@ -958,8 +1042,15 @@ export class CanvasViewImpl implements CanvasView, Listener { this.snapToAngleResize = consts.SNAP_TO_ANGLE_RESIZE_DEFAULT; if (this.activeElement) { const shape = this.svgShapes[this.activeElement.clientID]; - if (shape && shape.hasClass('cvat_canvas_shape_activated')) { - (shape as any).resize({ snapToAngle: this.snapToAngleResize }); + if (shape && shape?.remember('_selectHandler')?.options?.rotationPoint) { + if (this.drawnStates[this.activeElement.clientID]?.shapeType === 'skeleton') { + const wrappingRect = (shape as any).children().find((child: SVG.Element) => child.type === 'rect'); + if (wrappingRect) { + (wrappingRect as any).resize({ snapToAngle: this.snapToAngleResize }); + } + } else { + (shape as any).resize({ snapToAngle: this.snapToAngleResize }); + } } } } @@ -987,6 +1078,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.snapToAngleResize = consts.SNAP_TO_ANGLE_RESIZE_DEFAULT; this.innerObjectsFlags = { drawHidden: {}, + editHidden: {}, }; // Create HTML elements @@ -994,6 +1086,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.text = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.adoptedText = SVG.adopt((this.text as any) as HTMLElement) as SVG.Container; this.background = window.document.createElement('canvas'); + this.masksContent = window.document.createElement('canvas'); this.bitmap = window.document.createElement('canvas'); // window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); @@ -1009,11 +1102,13 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas = window.document.createElement('div'); const loadingCircle: SVGCircleElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + const gridDefs: SVGDefsElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'defs'); const gridRect: SVGRectElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'rect'); // Setup defs const contentDefs = this.adoptedContent.defs(); + this.issueRegionPattern_1 = contentDefs .pattern(consts.BASE_PATTERN_SIZE, consts.BASE_PATTERN_SIZE, (add): void => { add.line(0, 0, 0, 10).stroke('red'); @@ -1059,6 +1154,7 @@ export class CanvasViewImpl implements CanvasView, Listener { // Setup content this.text.setAttribute('id', 'cvat_canvas_text_content'); this.background.setAttribute('id', 'cvat_canvas_background'); + this.masksContent.setAttribute('id', 'cvat_canvas_masks_content'); this.content.setAttribute('id', 'cvat_canvas_content'); this.bitmap.setAttribute('id', 'cvat_canvas_bitmap'); this.bitmap.style.display = 'none'; @@ -1080,6 +1176,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.canvas.appendChild(this.loadingAnimation); this.canvas.appendChild(this.text); this.canvas.appendChild(this.background); + this.canvas.appendChild(this.masksContent); this.canvas.appendChild(this.bitmap); this.canvas.appendChild(this.grid); this.canvas.appendChild(this.content); @@ -1095,7 +1192,15 @@ export class CanvasViewImpl implements CanvasView, Listener { this.geometry, this.configuration, ); - this.editHandler = new EditHandlerImpl(this.onEditDone.bind(this), this.adoptedContent, this.autoborderHandler); + this.masksHandler = new MasksHandlerImpl( + this.onDrawDone.bind(this), + this.controller.draw.bind(this.controller), + this.onEditStart, + this.onEditDone, + this.drawHandler, + this.masksContent, + ); + this.editHandler = new EditHandlerImpl(this.onEditDone, this.adoptedContent, this.autoborderHandler); this.mergeHandler = new MergeHandlerImpl( this.onMergeDone.bind(this), this.onFindObject.bind(this), @@ -1126,12 +1231,12 @@ export class CanvasViewImpl implements CanvasView, Listener { ); // Setup event handlers - this.content.addEventListener('dblclick', (e: MouseEvent): void => { + this.canvas.addEventListener('dblclick', (e: MouseEvent): void => { this.controller.fit(); e.preventDefault(); }); - this.content.addEventListener('mousedown', (event): void => { + this.canvas.addEventListener('mousedown', (event): void => { if ([0, 1].includes(event.button)) { if ( [Mode.IDLE, Mode.DRAG_CANVAS, Mode.MERGE, Mode.SPLIT] @@ -1146,7 +1251,7 @@ export class CanvasViewImpl implements CanvasView, Listener { window.document.addEventListener('keydown', this.onShiftKeyDown); window.document.addEventListener('keyup', this.onShiftKeyUp); - this.content.addEventListener('wheel', (event): void => { + this.canvas.addEventListener('wheel', (event): void => { if (event.ctrlKey) return; const { offset } = this.controller.geometry; const point = translateToSVG(this.content, [event.clientX, event.clientY]); @@ -1160,7 +1265,7 @@ export class CanvasViewImpl implements CanvasView, Listener { event.preventDefault(); }); - this.content.addEventListener('mousemove', (e): void => { + this.canvas.addEventListener('mousemove', (e): void => { this.controller.drag(e.clientX, e.clientY); if (this.mode !== Mode.IDLE) return; @@ -1192,6 +1297,43 @@ export class CanvasViewImpl implements CanvasView, Listener { this.deactivate(); const { configuration } = model; + const updateShapeViews = (states: DrawnState[], parentState?: DrawnState): void => { + for (const drawnState of states) { + const { + fill, stroke, 'fill-opacity': fillOpacity, + } = this.getShapeColorization(drawnState, { parentState }); + const shapeView = window.document.getElementById(`cvat_canvas_shape_${drawnState.clientID}`); + const [objectState] = this.controller.objects + .filter((_state: any) => _state.clientID === drawnState.clientID); + if (shapeView) { + const handler = (shapeView as any).instance.remember('_selectHandler'); + if (handler && handler.nested) { + handler.nested.fill({ color: fill }); + } + + if (drawnState.shapeType === 'mask') { + // if there are masks, we need to redraw them + this.deleteObjects([drawnState]); + this.addObjects([objectState]); + continue; + } + + (shapeView as any).instance + .fill({ color: fill, opacity: fillOpacity }) + .stroke({ color: stroke }); + } + + if (drawnState.elements) { + updateShapeViews(drawnState.elements, drawnState); + } + } + }; + + const withUpdatingShapeViews = configuration.shapeOpacity !== this.configuration.shapeOpacity || + configuration.selectedShapeOpacity !== this.configuration.selectedShapeOpacity || + configuration.outlinedBorders !== this.configuration.outlinedBorders || + configuration.colorBy !== this.configuration.colorBy; + if (configuration.displayAllText && !this.configuration.displayAllText) { for (const i in this.drawnStates) { if (!(i in this.svgTexts)) { @@ -1199,10 +1341,9 @@ export class CanvasViewImpl implements CanvasView, Listener { } } } else if (configuration.displayAllText === false && this.configuration.displayAllText) { - for (const i in this.drawnStates) { - if (i in this.svgTexts && Number.parseInt(i, 10) !== activeElement.clientID) { - this.svgTexts[i].remove(); - delete this.svgTexts[i]; + for (const clientID in this.drawnStates) { + if (+clientID !== activeElement.clientID) { + this.deleteText(+clientID); } } } @@ -1220,15 +1361,20 @@ export class CanvasViewImpl implements CanvasView, Listener { } this.configuration = configuration; + if (withUpdatingShapeViews) { + updateShapeViews(Object.values(this.drawnStates)); + } + if (recreateText) { const states = this.controller.objects; for (const key of Object.keys(this.drawnStates)) { const clientID = +key; const [state] = states.filter((_state: any) => _state.clientID === clientID); if (clientID in this.svgTexts) { - this.svgTexts[clientID].remove(); - delete this.svgTexts[clientID]; - if (state) this.svgTexts[clientID] = this.addText(state); + this.deleteText(+clientID); + if (state) { + this.svgTexts[clientID] = this.addText(state); + } } } } @@ -1236,15 +1382,22 @@ export class CanvasViewImpl implements CanvasView, Listener { if (updateTextPosition) { for (const i in this.drawnStates) { if (i in this.svgTexts) { - this.updateTextPosition(this.svgTexts[i], this.svgShapes[i]); + this.updateTextPosition(this.svgTexts[i]); } } } + if (typeof configuration.CSSImageFilter === 'string') { + this.background.style.filter = configuration.CSSImageFilter; + } + this.activate(activeElement); this.editHandler.configurate(this.configuration); this.drawHandler.configurate(this.configuration); + this.masksHandler.configurate(this.configuration); + this.autoborderHandler.configurate(this.configuration); this.interactionHandler.configurate(this.configuration); + this.transformCanvas(); // remove if exist and not enabled // this.setupObjects([]); @@ -1282,6 +1435,28 @@ export class CanvasViewImpl implements CanvasView, Listener { ctx.drawImage(image.imageData, 0, 0); } } + + if (model.imageIsDeleted) { + let { width, height } = this.background; + if (image.imageData instanceof ImageData) { + width = image.imageData.width; + height = image.imageData.height; + } + + this.background.classList.add('cvat_canvas_removed_image'); + const canvasContext = this.background.getContext('2d'); + const fontSize = width / 10; + canvasContext.font = `bold ${fontSize}px serif`; + canvasContext.textAlign = 'center'; + canvasContext.lineWidth = fontSize / 20; + canvasContext.strokeStyle = 'white'; + canvasContext.strokeText('IMAGE REMOVED', width / 2, height / 2); + canvasContext.fillStyle = 'black'; + canvasContext.fillText('IMAGE REMOVED', width / 2, height / 2); + } else if (this.background.classList.contains('cvat_canvas_removed_image')) { + this.background.classList.remove('cvat_canvas_removed_image'); + } + this.moveCanvas(); this.resizeCanvas(); this.transformCanvas(); @@ -1388,19 +1563,46 @@ export class CanvasViewImpl implements CanvasView, Listener { } } else if (reason === UpdateReasons.DRAW) { const data: DrawData = this.controller.drawData; - if (data.enabled && this.mode === Mode.IDLE) { - this.canvas.style.cursor = 'crosshair'; - this.mode = Mode.DRAW; - if (typeof data.redraw === 'number') { - this.setupInnerFlags(data.redraw, 'drawHidden', true); + if (data.enabled && [Mode.IDLE, Mode.DRAW].includes(this.mode)) { + if (data.shapeType !== 'mask') { + this.drawHandler.draw(data, this.geometry); + } else { + this.masksHandler.draw(data); } - this.drawHandler.draw(data, this.geometry); - } else { + + if (this.mode === Mode.IDLE) { + this.canvas.style.cursor = 'crosshair'; + this.mode = Mode.DRAW; + this.canvas.dispatchEvent( + new CustomEvent('canvas.drawstart', { + bubbles: false, + cancelable: true, + detail: { + drawData: data, + }, + }), + ); + + if (typeof data.redraw === 'number') { + this.setupInnerFlags(data.redraw, 'drawHidden', true); + } + } + } else if (this.mode !== Mode.IDLE) { this.canvas.style.cursor = ''; - if (this.mode !== Mode.IDLE) { + this.mode = Mode.IDLE; + if (this.masksHandler.enabled) { + this.masksHandler.draw(data); + } else { this.drawHandler.draw(data, this.geometry); } } + } else if (reason === UpdateReasons.EDIT) { + const data = this.controller.editData; + if (data.enabled && data.state.shapeType === 'mask') { + this.masksHandler.edit(data); + } else if (this.masksHandler.enabled) { + this.masksHandler.edit(data); + } } else if (reason === UpdateReasons.INTERACT) { const data: InteractionData = this.controller.interactionData; if (data.enabled && (this.mode === Mode.IDLE || data.intermediateShape)) { @@ -1454,7 +1656,11 @@ export class CanvasViewImpl implements CanvasView, Listener { } } else if (reason === UpdateReasons.CANCEL) { if (this.mode === Mode.DRAW) { - this.drawHandler.cancel(); + if (this.masksHandler.enabled) { + this.masksHandler.cancel(); + } else { + this.drawHandler.cancel(); + } } else if (this.mode === Mode.INTERACT) { this.interactionHandler.cancel(); } else if (this.mode === Mode.MERGE) { @@ -1466,7 +1672,11 @@ export class CanvasViewImpl implements CanvasView, Listener { } else if (this.mode === Mode.SELECT_REGION) { this.regionSelector.cancel(); } else if (this.mode === Mode.EDIT) { - this.editHandler.cancel(); + if (this.masksHandler.enabled) { + this.masksHandler.cancel(); + } else { + this.editHandler.cancel(); + } } else if (this.mode === Mode.DRAG_CANVAS) { this.canvas.dispatchEvent( new CustomEvent('canvas.dragstop', { @@ -1577,6 +1787,22 @@ export class CanvasViewImpl implements CanvasView, Listener { ctx.fill(); } + if (state.shapeType === 'mask') { + const { points } = state; + const [left, top, right, bottom] = points.slice(-4); + const imageBitmap = expandChannels(255, 255, 255, points, 4); + imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1, + (dataURL: string) => new Promise((resolve) => { + const img = document.createElement('img'); + img.addEventListener('load', () => { + ctx.drawImage(img, left, top); + URL.revokeObjectURL(dataURL); + resolve(); + }); + img.src = dataURL; + })); + } + if (state.shapeType === 'cuboid') { for (let i = 0; i < 5; i++) { const points = [ @@ -1602,8 +1828,8 @@ export class CanvasViewImpl implements CanvasView, Listener { } } - private saveState(state: any): void { - this.drawnStates[state.clientID] = { + private saveState(state: any): DrawnState { + const result = { clientID: state.clientID, outside: state.outside, occluded: state.occluded, @@ -1620,6 +1846,37 @@ export class CanvasViewImpl implements CanvasView, Listener { updated: state.updated, frame: state.frame, label: state.label, + group: { id: state.group.id, color: state.group.color }, + color: state.color, + elements: state.shapeType === 'skeleton' ? + state.elements.map((element: any) => this.saveState(element)) : null, + }; + + return result; + } + + private getShapeColorization(state: any, opts: { + parentState?: any, + } = {}): { fill: string; stroke: string, 'fill-opacity': number } { + const { shapeType } = state; + const parentShapeType = opts.parentState?.shapeType; + const { configuration } = this; + const { colorBy, shapeOpacity, outlinedBorders } = configuration; + let shapeColor = ''; + + if (colorBy === ColorBy.INSTANCE) { + shapeColor = state.color; + } else if (colorBy === ColorBy.GROUP) { + shapeColor = state.group.color; + } else if (colorBy === ColorBy.LABEL) { + shapeColor = state.label.color; + } + const outlinedColor = parentShapeType === 'skeleton' ? 'black' : outlinedBorders || shapeColor; + + return { + fill: shapeColor, + stroke: outlinedColor, + 'fill-opacity': !['polyline', 'points', 'skeleton'].includes(shapeType) || parentShapeType === 'skeleton' ? shapeOpacity : 0, }; } @@ -1645,7 +1902,7 @@ export class CanvasViewImpl implements CanvasView, Listener { ); if (text) { text.removeClass('cvat_canvas_hidden'); - this.updateTextPosition(text, shape); + this.updateTextPosition(text); } } } @@ -1659,10 +1916,11 @@ export class CanvasViewImpl implements CanvasView, Listener { } if (drawnState.occluded !== state.occluded) { + const instance = state.shapeType === 'points' ? this.svgShapes[clientID].remember('_selectHandler').nested : shape; if (state.occluded) { - shape.addClass('cvat_canvas_shape_occluded'); + instance.addClass('cvat_canvas_shape_occluded'); } else { - shape.removeClass('cvat_canvas_shape_occluded'); + instance.removeClass('cvat_canvas_shape_occluded'); } } @@ -1681,6 +1939,13 @@ export class CanvasViewImpl implements CanvasView, Listener { state.points.length !== drawnState.points.length || state.points.some((p: number, id: number): boolean => p !== drawnState.points[id]) ) { + if (state.shapeType === 'mask') { + // if masks points were updated, draw from scratch + this.deleteObjects([this.drawnStates[+clientID]]); + this.addObjects([state]); + continue; + } + const translatedPoints: number[] = this.translateToCanvas(state.points); if (state.shapeType === 'rectangle') { @@ -1725,7 +1990,7 @@ export class CanvasViewImpl implements CanvasView, Listener { drawnStateDescriptions.length !== stateDescriptions.length || drawnStateDescriptions.some((desc: string, id: number): boolean => desc !== stateDescriptions[id]) ) { - // need to remove created text and create it again + // remove created text and create it again if (text) { text.remove(); this.svgTexts[state.clientID] = this.addText(state); @@ -1745,23 +2010,50 @@ export class CanvasViewImpl implements CanvasView, Listener { } } - this.saveState(state); + if ( + drawnState.label.id !== state.label.id || + drawnState.group.id !== state.group.id || + drawnState.group.color !== state.group.color || + drawnState.color !== state.color + ) { + // update shape color if necessary + if (shape) { + if (state.shapeType === 'mask') { + // if masks points were updated, draw from scratch + this.deleteObjects([this.drawnStates[+clientID]]); + this.addObjects([state]); + continue; + } else { + shape.attr({ ...this.getShapeColorization(state) }); + } + } + } + + this.drawnStates[state.clientID] = this.saveState(state); } } private deleteObjects(states: any[]): void { for (const state of states) { if (state.clientID in this.svgTexts) { - this.svgTexts[state.clientID].remove(); - delete this.svgTexts[state.clientID]; + this.deleteText(state.clientID); } - this.svgShapes[state.clientID].fire('remove'); - this.svgShapes[state.clientID].off('click'); - this.svgShapes[state.clientID].off('remove'); - this.svgShapes[state.clientID].remove(); - delete this.drawnStates[state.clientID]; - delete this.svgShapes[state.clientID]; + if (state.shapeType === 'skeleton') { + this.deleteObjects(state.elements); + } + + if (state.clientID in this.svgShapes) { + this.svgShapes[state.clientID].fire('remove'); + this.svgShapes[state.clientID].off('click'); + this.svgShapes[state.clientID].off('remove'); + this.svgShapes[state.clientID].remove(); + delete this.svgShapes[state.clientID]; + } + + if (state.clientID in this.drawnStates) { + delete this.drawnStates[state.clientID]; + } } } @@ -1769,26 +2061,32 @@ export class CanvasViewImpl implements CanvasView, Listener { const { displayAllText } = this.configuration; for (const state of states) { const points: number[] = state.points as number[]; - const translatedPoints: number[] = this.translateToCanvas(points); // TODO: Use enums after typification cvat-core - if (state.shapeType === 'rectangle') { - this.svgShapes[state.clientID] = this.addRect(translatedPoints, state); + if (state.shapeType === 'mask') { + this.svgShapes[state.clientID] = this.addMask(points, state); + } else if (state.shapeType === 'skeleton') { + this.svgShapes[state.clientID] = this.addSkeleton(state); } else { - const stringified = this.stringifyToCanvas(translatedPoints); - - if (state.shapeType === 'polygon') { - this.svgShapes[state.clientID] = this.addPolygon(stringified, state); - } else if (state.shapeType === 'polyline') { - this.svgShapes[state.clientID] = this.addPolyline(stringified, state); - } else if (state.shapeType === 'points') { - this.svgShapes[state.clientID] = this.addPoints(stringified, state); - } else if (state.shapeType === 'ellipse') { - this.svgShapes[state.clientID] = this.addEllipse(stringified, state); - } else if (state.shapeType === 'cuboid') { - this.svgShapes[state.clientID] = this.addCuboid(stringified, state); + const translatedPoints: number[] = this.translateToCanvas(points); + if (state.shapeType === 'rectangle') { + this.svgShapes[state.clientID] = this.addRect(translatedPoints, state); } else { - continue; + const stringified = this.stringifyToCanvas(translatedPoints); + + if (state.shapeType === 'polygon') { + this.svgShapes[state.clientID] = this.addPolygon(stringified, state); + } else if (state.shapeType === 'polyline') { + this.svgShapes[state.clientID] = this.addPolyline(stringified, state); + } else if (state.shapeType === 'points') { + this.svgShapes[state.clientID] = this.addPoints(stringified, state); + } else if (state.shapeType === 'ellipse') { + this.svgShapes[state.clientID] = this.addEllipse(stringified, state); + } else if (state.shapeType === 'cuboid') { + this.svgShapes[state.clientID] = this.addCuboid(stringified, state); + } else { + continue; + } } } @@ -1806,10 +2104,10 @@ export class CanvasViewImpl implements CanvasView, Listener { if (displayAllText) { this.svgTexts[state.clientID] = this.addText(state); - this.updateTextPosition(this.svgTexts[state.clientID], this.svgShapes[state.clientID]); + this.updateTextPosition(this.svgTexts[state.clientID]); } - this.saveState(state); + this.drawnStates[state.clientID] = this.saveState(state); } } @@ -1863,8 +2161,19 @@ export class CanvasViewImpl implements CanvasView, Listener { const drawnState = this.drawnStates[clientID]; const shape = this.svgShapes[clientID]; - shape.removeClass('cvat_canvas_shape_activated'); + if (drawnState.shapeType === 'points') { + this.svgShapes[clientID] + .remember('_selectHandler').nested + .removeClass('cvat_canvas_shape_activated'); + } else { + shape.removeClass('cvat_canvas_shape_activated'); + } shape.removeClass('cvat_canvas_shape_draggable'); + if (drawnState.shapeType === 'mask') { + shape.attr('opacity', `${this.configuration.shapeOpacity}`); + } else { + shape.attr('fill-opacity', `${this.configuration.shapeOpacity}`); + } if (!drawnState.pinned) { (shape as any).off('dragstart'); @@ -1880,6 +2189,10 @@ export class CanvasViewImpl implements CanvasView, Listener { (shape as any).attr('projections', false); } + if (drawnState.shapeType === 'mask') { + (shape as any).off('mousedown'); + } + (shape as any).off('resizestart'); (shape as any).off('resizing'); (shape as any).off('resizedone'); @@ -1888,8 +2201,7 @@ export class CanvasViewImpl implements CanvasView, Listener { // TODO: Hide text only if it is hidden by settings const text = this.svgTexts[clientID]; if (text && !displayAllText) { - text.remove(); - delete this.svgTexts[clientID]; + this.deleteText(clientID); } this.sortObjects(); @@ -1940,13 +2252,26 @@ export class CanvasViewImpl implements CanvasView, Listener { text = this.addText(state); this.svgTexts[state.clientID] = text; } - this.updateTextPosition(text, shape); + this.updateTextPosition(text); if (this.stateIsLocked(state)) { return; } - shape.addClass('cvat_canvas_shape_activated'); + if (state.shapeType === 'points') { + this.svgShapes[clientID] + .remember('_selectHandler').nested + .addClass('cvat_canvas_shape_activated'); + } else { + shape.addClass('cvat_canvas_shape_activated'); + } + + if (state.shapeType === 'mask') { + shape.attr('opacity', `${this.configuration.selectedShapeOpacity}`); + } else { + shape.attr('fill-opacity', `${this.configuration.selectedShapeOpacity}`); + } + if (state.shapeType === 'points') { this.content.append(this.svgShapes[clientID].remember('_selectHandler').nested.node); } else { @@ -1967,14 +2292,16 @@ export class CanvasViewImpl implements CanvasView, Listener { const showText = (): void => { if (text) { text.removeClass('cvat_canvas_hidden'); - this.updateTextPosition(text, shape); + this.updateTextPosition(text); } }; if (!state.pinned) { shape.addClass('cvat_canvas_shape_draggable'); (shape as any) - .draggable() + .draggable({ + ...(state.shapeType === 'mask' ? { snapToGrid: 1 } : {}), + }) .on('dragstart', (): void => { this.mode = Mode.DRAG; hideText(); @@ -1990,10 +2317,29 @@ export class CanvasViewImpl implements CanvasView, Listener { showText(); const p1 = e.detail.handler.startPoints.point; const p2 = e.detail.p; - const delta = 1; const dx2 = (p1.x - p2.x) ** 2; const dy2 = (p1.y - p2.y) ** 2; - if (Math.sqrt(dx2 + dy2) >= delta) { + if (Math.sqrt(dx2 + dy2) > 0) { + if (shape.type === 'image') { + const { points } = state; + const x = Math.trunc(shape.x()) - this.geometry.offset; + const y = Math.trunc(shape.y()) - this.geometry.offset; + points.splice(-4); + points.push(x, y, x + shape.width() - 1, y + shape.height() - 1); + this.drawnStates[state.clientID].points = points; + this.onEditDone(state, points); + this.canvas.dispatchEvent( + new CustomEvent('canvas.dragshape', { + bubbles: false, + cancelable: true, + detail: { + id: state.clientID, + }, + }), + ); + + return; + } // these points does not take into account possible transformations, applied on the element // so, if any (like rotation) we need to map them to canvas coordinate space let points = readPointsFromShape(shape); @@ -2007,6 +2353,7 @@ export class CanvasViewImpl implements CanvasView, Listener { } points = this.translateFromCanvas(points); + this.onEditDone(state, points); this.canvas.dispatchEvent( new CustomEvent('canvas.dragshape', { bubbles: false, @@ -2016,7 +2363,6 @@ export class CanvasViewImpl implements CanvasView, Listener { }, }), ); - this.onEditDone(state, points); } }); } @@ -2050,67 +2396,75 @@ export class CanvasViewImpl implements CanvasView, Listener { this.mode = Mode.IDLE; }; - (shape as any) - .resize({ - snapToGrid: 0.1, - snapToAngle: this.snapToAngleResize, - }) - .on('resizestart', (): void => { - this.mode = Mode.RESIZE; - resized = false; - hideDirection(); - hideText(); - if (state.shapeType === 'rectangle' || state.shapeType === 'ellipse') { - shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); - } - (shape as any).on('remove.resize', () => { - // disable internal resize events of SVG.js - window.dispatchEvent(new MouseEvent('mouseup')); - resizeFinally(); - }); - }) - .on('resizing', (): void => { - resized = true; - if (shapeSizeElement) { - shapeSizeElement.update(shape); - } - }) - .on('resizedone', (): void => { - (shape as any).off('remove.resize'); - resizeFinally(); - showDirection(); - showText(); - if (resized) { - let rotation = shape.transform().rotation || 0; - - // be sure, that rotation in range [0; 360] - while (rotation < 0) rotation += 360; - rotation %= 360; - - // these points does not take into account possible transformations, applied on the element - // so, if any (like rotation) we need to map them to canvas coordinate space - let points = readPointsFromShape(shape); - - // let's keep current points, but they could be rewritten in updateObjects - this.drawnStates[clientID].points = this.translateFromCanvas(points); - this.drawnStates[clientID].rotation = rotation; - if (rotation) { - points = this.translatePointsFromRotatedShape(shape, points); + if (state.shapeType !== 'mask') { + (shape as any) + .resize({ + snapToGrid: 0.1, + snapToAngle: this.snapToAngleResize, + }) + .on('resizestart', (): void => { + this.mode = Mode.RESIZE; + resized = false; + hideDirection(); + hideText(); + if (state.shapeType === 'rectangle' || state.shapeType === 'ellipse') { + shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); } + (shape as any).on('remove.resize', () => { + // disable internal resize events of SVG.js + window.dispatchEvent(new MouseEvent('mouseup')); + resizeFinally(); + }); + }) + .on('resizing', (): void => { + resized = true; + if (shapeSizeElement) { + shapeSizeElement.update(shape); + } + }) + .on('resizedone', (): void => { + (shape as any).off('remove.resize'); + resizeFinally(); + showDirection(); + showText(); + if (resized) { + let rotation = shape.transform().rotation || 0; - // points = this.translateFromCanvas(points); - this.canvas.dispatchEvent( - new CustomEvent('canvas.resizeshape', { - bubbles: false, - cancelable: true, - detail: { - id: state.clientID, - }, - }), - ); - this.onEditDone(state, this.translateFromCanvas(points), rotation); + // be sure, that rotation in range [0; 360] + while (rotation < 0) rotation += 360; + rotation %= 360; + + // these points does not take into account possible transformations, applied on the element + // so, if any (like rotation) we need to map them to canvas coordinate space + let points = readPointsFromShape(shape); + + // let's keep current points, but they could be rewritten in updateObjects + this.drawnStates[clientID].points = this.translateFromCanvas(points); + this.drawnStates[clientID].rotation = rotation; + if (rotation) { + points = this.translatePointsFromRotatedShape(shape, points); + } + + this.onEditDone(state, this.translateFromCanvas(points), rotation); + this.canvas.dispatchEvent( + new CustomEvent('canvas.resizeshape', { + bubbles: false, + cancelable: true, + detail: { + id: state.clientID, + }, + }), + ); + } + }); + } else { + (shape as any).on('dblclick', (e: MouseEvent) => { + if (e.shiftKey) { + this.controller.edit({ enabled: true, state }); + e.stopPropagation(); } }); + } this.canvas.dispatchEvent( new CustomEvent('canvas.activated', { @@ -2149,13 +2503,21 @@ export class CanvasViewImpl implements CanvasView, Listener { } // Update text position after corresponding box has been moved, resized, etc. - private updateTextPosition(text: SVG.Text, shape: SVG.Shape): void { + private updateTextPosition( + text: SVG.Text, + options: { rotation?: { angle: number, cx: number, cy: number } } = {}, + ): void { + const clientID = text.attr('data-client-id'); + if (!Number.isInteger(clientID)) return; + const shape = this.svgShapes[clientID]; + if (!shape) return; + if (text.node.style.display === 'none') return; // wrong transformation matrix const { textFontSize, textPosition } = this.configuration; text.untransform(); text.style({ 'font-size': `${textFontSize}px` }); - const { rotation } = shape.transform(); + const rotation = options.rotation?.angle || shape.transform().rotation; // Find the best place for a text let [clientX, clientY, clientCX, clientCY]: number[] = [0, 0, 0, 0]; @@ -2216,8 +2578,8 @@ export class CanvasViewImpl implements CanvasView, Listener { const [x, y, rotX, rotY]: number[] = translateToSVG(this.text, [ clientX + (textPosition === 'auto' ? consts.TEXT_MARGIN : 0), clientY + (textPosition === 'auto' ? consts.TEXT_MARGIN : 0), - clientCX, - clientCY, + options.rotation?.cx || clientCX, + options.rotation?.cy || clientCY, ]); const textBBox = ((text.node as any) as SVGTextElement).getBBox(); @@ -2228,8 +2590,24 @@ export class CanvasViewImpl implements CanvasView, Listener { text.move(x, y); } + let childOptions = {}; if (rotation) { text.rotate(rotation, rotX, rotY); + childOptions = { + rotation: { + angle: rotation, + cx: clientCX, + cy: clientCY, + }, + }; + } + + if (clientID in this.drawnStates && this.drawnStates[clientID].shapeType === 'skeleton') { + this.drawnStates[clientID].elements.forEach((element: DrawnState) => { + if (element.clientID in this.svgTexts) { + this.updateTextPosition(this.svgTexts[element.clientID], childOptions); + } + }); } for (const tspan of (text.lines() as any).members) { @@ -2237,15 +2615,27 @@ export class CanvasViewImpl implements CanvasView, Listener { } } - private addText(state: any): SVG.Text { + private deleteText(clientID: number): void { + if (clientID in this.svgTexts) { + this.svgTexts[clientID].remove(); + delete this.svgTexts[clientID]; + } + + if (clientID in this.drawnStates && this.drawnStates[clientID].shapeType === 'skeleton') { + this.drawnStates[clientID].elements.forEach((element) => { + this.deleteText(element.clientID); + }); + } + } + + private addText(state: any, options: { textContent?: string } = {}): SVG.Text { const { undefinedAttrValue } = this.configuration; - const content = this.configuration.textContent; + const content = options.textContent || this.configuration.textContent; const withID = content.includes('id'); const withAttr = content.includes('attributes'); const withLabel = content.includes('label'); const withSource = content.includes('source'); const withDescriptions = content.includes('descriptions'); - const textFontSize = this.configuration.textFontSize || 12; const { label, clientID, attributes, source, descriptions, @@ -2255,6 +2645,19 @@ export class CanvasViewImpl implements CanvasView, Listener { return acc; }, {}); + if (state.shapeType === 'skeleton') { + state.elements.forEach((element: any) => { + if (!(element.clientID in this.svgTexts)) { + this.svgTexts[element.clientID] = this.addText(element, { + textContent: [ + ...(withLabel ? ['label'] : []), + ...(withAttr ? ['attributes'] : []), + ].join(',') || ' ', + }); + } + }); + } + return this.adoptedText .text((block): void => { block.tspan(`${withLabel ? label.name : ''} ${withID ? clientID : ''} ${withSource ? `(${source})` : ''}`).style({ @@ -2286,6 +2689,7 @@ export class CanvasViewImpl implements CanvasView, Listener { } }) .move(0, 0) + .attr({ 'data-client-id': state.clientID }) .style({ 'font-size': textFontSize }) .addClass('cvat_canvas_text'); } @@ -2299,14 +2703,11 @@ export class CanvasViewImpl implements CanvasView, Listener { clientID: state.clientID, 'color-rendering': 'optimizeQuality', id: `cvat_canvas_shape_${state.clientID}`, - fill: state.color, 'shape-rendering': 'geometricprecision', - stroke: state.color, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'data-z-order': state.zOrder, - }) - .move(xtl, ytl) - .addClass('cvat_canvas_shape'); + ...this.getShapeColorization(state), + }).move(xtl, ytl).addClass('cvat_canvas_shape'); if (state.rotation) { rect.rotate(state.rotation); @@ -2330,13 +2731,11 @@ export class CanvasViewImpl implements CanvasView, Listener { clientID: state.clientID, 'color-rendering': 'optimizeQuality', id: `cvat_canvas_shape_${state.clientID}`, - fill: state.color, 'shape-rendering': 'geometricprecision', - stroke: state.color, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'data-z-order': state.zOrder, - }) - .addClass('cvat_canvas_shape'); + ...this.getShapeColorization(state), + }).addClass('cvat_canvas_shape'); if (state.occluded) { polygon.addClass('cvat_canvas_shape_occluded'); @@ -2356,13 +2755,11 @@ export class CanvasViewImpl implements CanvasView, Listener { clientID: state.clientID, 'color-rendering': 'optimizeQuality', id: `cvat_canvas_shape_${state.clientID}`, - fill: state.color, 'shape-rendering': 'geometricprecision', - stroke: state.color, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'data-z-order': state.zOrder, - }) - .addClass('cvat_canvas_shape'); + ...this.getShapeColorization(state), + }).addClass('cvat_canvas_shape'); if (state.occluded) { polyline.addClass('cvat_canvas_shape_occluded'); @@ -2383,13 +2780,11 @@ export class CanvasViewImpl implements CanvasView, Listener { clientID: state.clientID, 'color-rendering': 'optimizeQuality', id: `cvat_canvas_shape_${state.clientID}`, - fill: state.color, 'shape-rendering': 'geometricprecision', - stroke: state.color, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'data-z-order': state.zOrder, - }) - .addClass('cvat_canvas_shape'); + ...this.getShapeColorization(state), + }).addClass('cvat_canvas_shape'); if (state.occluded) { cube.addClass('cvat_canvas_shape_occluded'); @@ -2402,6 +2797,521 @@ export class CanvasViewImpl implements CanvasView, Listener { return cube; } + private addMask(points: number[], state: any): SVG.Image { + const colorization = this.getShapeColorization(state); + const color = fabric.Color.fromHex(colorization.fill).getSource(); + const [left, top, right, bottom] = points.slice(-4); + const imageBitmap = expandChannels(color[0], color[1], color[2], points, 4); + + const image = this.adoptedContent.image().attr({ + clientID: state.clientID, + 'color-rendering': 'optimizeQuality', + id: `cvat_canvas_shape_${state.clientID}`, + 'shape-rendering': 'geometricprecision', + 'data-z-order': state.zOrder, + opacity: colorization['fill-opacity'], + stroke: colorization.stroke, + }).addClass('cvat_canvas_shape'); + image.move(this.geometry.offset + left, this.geometry.offset + top); + + imageDataToDataURL( + imageBitmap, + right - left + 1, + bottom - top + 1, + (dataURL: string) => new Promise((resolve, reject) => { + image.loaded(() => { + resolve(); + }); + image.error(() => { + reject(); + }); + image.load(dataURL); + }), + ); + + return image; + } + + private addSkeleton(state: any): any { + const skeleton = (this.adoptedContent as any) + .group() + .attr({ + clientID: state.clientID, + 'color-rendering': 'optimizeQuality', + id: `cvat_canvas_shape_${state.clientID}`, + 'shape-rendering': 'geometricprecision', + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'data-z-order': state.zOrder, + ...this.getShapeColorization(state), + }).addClass('cvat_canvas_shape') as SVG.G; + + const SVGElement = makeSVGFromTemplate(state.label.structure.svg); + + let xtl = Number.MAX_SAFE_INTEGER; + let ytl = Number.MAX_SAFE_INTEGER; + let xbr = Number.MIN_SAFE_INTEGER; + let ybr = Number.MIN_SAFE_INTEGER; + + const svgElements: Record = {}; + const templateElements = Array.from(SVGElement.children()).filter((el: SVG.Element) => el.type === 'circle'); + for (let i = 0; i < state.elements.length; i++) { + const element = state.elements[i]; + if (element.shapeType === 'points') { + const points: number[] = element.points as number[]; + const [cx, cy] = this.translateToCanvas(points); + + if (!element.outside) { + xtl = Math.min(xtl, cx); + ytl = Math.min(ytl, cy); + xbr = Math.max(xbr, cx); + ybr = Math.max(ybr, cy); + } + + const templateElement = templateElements.find((el: SVG.Circle) => el.attr('data-label-id') === element.label.id); + const circle = skeleton.circle() + .center(cx, cy) + .attr({ + id: `cvat_canvas_shape_${element.clientID}`, + r: this.configuration.controlPointsSize / this.geometry.scale, + 'color-rendering': 'optimizeQuality', + 'shape-rendering': 'geometricprecision', + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'data-node-id': templateElement.attr('data-node-id'), + 'data-element-id': templateElement.attr('data-element-id'), + 'data-label-id': templateElement.attr('data-label-id'), + 'data-client-id': element.clientID, + ...this.getShapeColorization(element, { parentState: state }), + }).style({ + cursor: 'default', + }); + this.svgShapes[element.clientID] = circle; + if (element.occluded) { + circle.addClass('cvat_canvas_shape_occluded'); + } + + if (element.hidden || element.outside || this.isInnerHidden(element.clientID)) { + circle.addClass('cvat_canvas_hidden'); + } + + const mouseover = (e: MouseEvent): void => { + const locked = this.drawnStates[state.clientID].lock; + if (!locked && !e.ctrlKey && this.mode === Mode.IDLE) { + circle.attr({ + 'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale, + }); + + const [x, y] = translateToSVG(this.content, [e.clientX, e.clientY]); + const event: CustomEvent = new CustomEvent('canvas.moved', { + bubbles: false, + cancelable: true, + detail: { + x: x - this.geometry.offset, + y: y - this.geometry.offset, + activatedElementID: element.clientID, + states: this.controller.objects, + }, + }); + + this.canvas.dispatchEvent(event); + } + }; + + const mousemove = (e: MouseEvent): void => { + if (this.mode === Mode.IDLE) { + // stop propagation to canvas where it calls another canvas.moved + // and does not allow to activate an element + e.stopPropagation(); + } + }; + + const mouseleave = (): void => { + circle.attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + }); + }; + + const click = (e: MouseEvent): void => { + e.stopPropagation(); + this.canvas.dispatchEvent( + new CustomEvent('canvas.clicked', { + bubbles: false, + cancelable: true, + detail: { + state: element, + }, + }), + ); + }; + + circle.on('mouseover', mouseover); + circle.on('mouseleave', mouseleave); + circle.on('mousemove', mousemove); + circle.on('click', click); + circle.on('remove', () => { + circle.off('remove'); + circle.off('mouseover', mouseover); + circle.off('mouseleave', mouseleave); + circle.off('mousemove', mousemove); + circle.off('click', click); + }); + + svgElements[element.clientID] = circle; + } + } + + xtl -= consts.SKELETON_RECT_MARGIN; + ytl -= consts.SKELETON_RECT_MARGIN; + xbr += consts.SKELETON_RECT_MARGIN; + ybr += consts.SKELETON_RECT_MARGIN; + + skeleton.on('remove', () => { + Object.values(svgElements).forEach((element) => element.fire('remove')); + skeleton.off('remove'); + }); + + const wrappingRect = skeleton.rect(xbr - xtl, ybr - ytl).move(xtl, ytl).attr({ + fill: 'inherit', + 'fill-opacity': 0, + 'color-rendering': 'optimizeQuality', + 'shape-rendering': 'geometricprecision', + stroke: 'inherit', + 'stroke-width': 'inherit', + 'data-xtl': xtl, + 'data-ytl': ytl, + 'data-xbr': xbr, + 'data-ybr': ybr, + }).addClass('cvat_canvas_skeleton_wrapping_rect'); + + skeleton.node.prepend(wrappingRect.node); + setupSkeletonEdges(skeleton, SVGElement); + + if (state.occluded) { + skeleton.addClass('cvat_canvas_shape_occluded'); + } + + if (state.hidden || state.outside || this.isInnerHidden(state.clientID)) { + skeleton.addClass('cvat_canvas_hidden'); + } + + (skeleton as any).selectize = (enabled: boolean) => { + this.selectize(enabled, wrappingRect); + const handler = wrappingRect.remember('_selectHandler'); + if (enabled && handler) { + this.adoptedContent.node.append(handler.nested.node); + handler.nested.attr('fill', skeleton.attr('fill')); + } + + return skeleton; + }; + + (skeleton as any).draggable = (enabled = true) => { + const textList = [ + state.clientID, ...state.elements.map((element: any): number => element.clientID), + ].map((clientID: number) => this.svgTexts[clientID]).filter((text: SVG.Text | undefined) => ( + typeof text !== 'undefined' + )); + + const hideText = (): void => { + textList.forEach((text: SVG.Text) => { + text.addClass('cvat_canvas_hidden'); + }); + }; + + const showText = (): void => { + textList.forEach((text: SVG.Text) => { + text.removeClass('cvat_canvas_hidden'); + this.updateTextPosition(text); + }); + }; + + if (enabled) { + (wrappingRect as any).draggable() + .on('dragstart', (): void => { + this.mode = Mode.DRAG; + hideText(); + skeleton.on('remove.drag', (): void => { + this.mode = Mode.IDLE; + showText(); + // disable internal drag events of SVG.js + window.dispatchEvent(new MouseEvent('mouseup')); + skeleton.off('remove.drag'); + }); + }) + .on('dragmove', (e: CustomEvent): void => { + // skeleton elements itself are not updated yet, need to run as macrotask + setTimeout(() => { + const { instance } = e.target as any; + const [x, y] = [instance.x(), instance.y()]; + const prevXtl = +wrappingRect.attr('data-xtl'); + const prevYtl = +wrappingRect.attr('data-ytl'); + + for (const child of skeleton.children()) { + if (child.type === 'circle') { + const childClientID = child.attr('data-client-id'); + if (state.elements.find((el: any) => el.clientID === childClientID).lock || false) { + continue; + } + child.center( + child.cx() - prevXtl + x, + child.cy() - prevYtl + y, + ); + } + } + + wrappingRect.attr('data-xtl', x); + wrappingRect.attr('data-ytl', y); + wrappingRect.attr('data-xbr', x + instance.width()); + wrappingRect.attr('data-ybr', y + instance.height()); + + setupSkeletonEdges(skeleton, SVGElement); + }); + }) + .on('dragend', (e: CustomEvent): void => { + setTimeout(() => { + skeleton.off('remove.drag'); + this.mode = Mode.IDLE; + showText(); + const p1 = e.detail.handler.startPoints.point; + const p2 = e.detail.p; + const delta = 1; + const dx2 = (p1.x - p2.x) ** 2; + const dy2 = (p1.y - p2.y) ** 2; + if (Math.sqrt(dx2 + dy2) >= delta) { + state.elements.forEach((element: any) => { + const elementShape = skeleton.children() + .find((child: SVG.Shape) => ( + child.id() === `cvat_canvas_shape_${element.clientID}` + )); + + if (elementShape) { + const points = readPointsFromShape(elementShape); + element.points = this.translateFromCanvas(points); + } + }); + + this.canvas.dispatchEvent( + new CustomEvent('canvas.dragshape', { + bubbles: false, + cancelable: true, + detail: { + id: state.clientID, + }, + }), + ); + this.onEditDone(state, state.points); + } + }); + }); + } else { + (wrappingRect as any).off('dragstart'); + (wrappingRect as any).off('dragend'); + (wrappingRect as any).draggable(false); + } + + return skeleton; + }; + + (skeleton as any).resize = (action: any) => { + const textList = [ + state.clientID, ...state.elements.map((element: any): number => element.clientID), + ].map((clientID: number) => this.svgTexts[clientID]).filter((text: SVG.Text | undefined) => ( + typeof text !== 'undefined' + )); + + const hideText = (): void => { + textList.forEach((text: SVG.Text) => { + text.addClass('cvat_canvas_hidden'); + }); + }; + + const showText = (): void => { + textList.forEach((text: SVG.Text) => { + text.removeClass('cvat_canvas_hidden'); + this.updateTextPosition(text); + }); + }; + + Object.entries(svgElements).forEach(([key, element]) => { + const clientID = +key; + const elementState = state.elements + .find((_element: any) => _element.clientID === clientID); + const text = this.svgTexts[clientID]; + const hideElementText = (): void => { + if (text) { + text.addClass('cvat_canvas_hidden'); + } + }; + + const showElementText = (): void => { + if (text) { + text.removeClass('cvat_canvas_hidden'); + this.updateTextPosition(text); + } + }; + + if (action !== 'stop' && !elementState.lock) { + (element as any).draggable() + .on('dragstart', (): void => { + this.mode = Mode.RESIZE; + hideElementText(); + element.on('remove.drag', (): void => { + this.mode = Mode.IDLE; + // disable internal drag events of SVG.js + window.dispatchEvent(new MouseEvent('mouseup')); + element.off('remove.drag'); + }); + }) + .on('dragmove', (): void => { + // element itself is not updated yet, need to run as macrotask + setTimeout(() => { + setupSkeletonEdges(skeleton, SVGElement); + }); + }) + .on('dragend', (e: CustomEvent): void => { + setTimeout(() => { + element.off('remove.drag'); + this.mode = Mode.IDLE; + const p1 = e.detail.handler.startPoints.point; + const p2 = e.detail.p; + const delta = 1; + const dx2 = (p1.x - p2.x) ** 2; + const dy2 = (p1.y - p2.y) ** 2; + if (Math.sqrt(dx2 + dy2) >= delta) { + const elementShape = skeleton.children() + .find((child: SVG.Shape) => child.id() === `cvat_canvas_shape_${clientID}`); + + if (elementShape) { + const points = readPointsFromShape(elementShape); + this.canvas.dispatchEvent( + new CustomEvent('canvas.resizeshape', { + bubbles: false, + cancelable: true, + detail: { + id: elementState.clientID, + }, + }), + ); + this.onEditDone(elementState, this.translateFromCanvas(points)); + } + } + + showElementText(); + }); + }); + } else { + (element as any).off('dragstart'); + (element as any).off('dragend'); + (element as any).draggable(false); + } + }); + + let resized = false; + if (action !== 'stop') { + (wrappingRect as any).resize(action).on('resizestart', (): void => { + this.mode = Mode.RESIZE; + resized = false; + hideText(); + (wrappingRect as any).on('remove.resize', () => { + this.mode = Mode.IDLE; + showText(); + + // disable internal resize events of SVG.js + window.dispatchEvent(new MouseEvent('mouseup')); + this.mode = Mode.IDLE; + }); + }).on('resizing', (e: CustomEvent): void => { + setTimeout(() => { + const { instance } = e.target as any; + + // rotate skeleton instead of wrapping bounding box + const { rotation } = wrappingRect.transform(); + skeleton.rotate(rotation); + + const [x, y] = [instance.x(), instance.y()]; + const prevXtl = +wrappingRect.attr('data-xtl'); + const prevYtl = +wrappingRect.attr('data-ytl'); + const prevXbr = +wrappingRect.attr('data-xbr'); + const prevYbr = +wrappingRect.attr('data-ybr'); + + if (prevXbr - prevXtl < 0.1) return; + if (prevYbr - prevYtl < 0.1) return; + + for (const child of skeleton.children()) { + if (child.type === 'circle') { + const childClientID = child.attr('data-client-id'); + if (state.elements.find((el: any) => el.clientID === childClientID).lock || false) { + continue; + } + const offsetX = (child.cx() - prevXtl) / (prevXbr - prevXtl); + const offsetY = (child.cy() - prevYtl) / (prevYbr - prevYtl); + child.center(offsetX * instance.width() + x, offsetY * instance.height() + y); + } + } + + wrappingRect.attr('data-xtl', x); + wrappingRect.attr('data-ytl', y); + wrappingRect.attr('data-xbr', x + instance.width()); + wrappingRect.attr('data-ybr', y + instance.height()); + + resized = true; + setupSkeletonEdges(skeleton, SVGElement); + }); + }).on('resizedone', (): void => { + setTimeout(() => { + let { rotation } = skeleton.transform(); + // be sure, that rotation in range [0; 360] + while (rotation < 0) rotation += 360; + rotation %= 360; + showText(); + this.mode = Mode.IDLE; + (wrappingRect as any).off('remove.resize'); + if (resized) { + if (rotation) { + this.onEditDone(state, state.points, rotation); + } else { + const points: number[] = []; + + state.elements.forEach((element: any) => { + const elementShape = skeleton.children() + .find((child: SVG.Shape) => ( + child.id() === `cvat_canvas_shape_${element.clientID}` + )); + + if (elementShape) { + points.push(...this.translateFromCanvas( + readPointsFromShape(elementShape), + )); + } + }); + + this.onEditDone(state, points, rotation); + } + + this.canvas.dispatchEvent( + new CustomEvent('canvas.resizeshape', { + bubbles: false, + cancelable: true, + detail: { + id: state.clientID, + }, + }), + ); + } + }); + }); + } else if (action === 'stop') { + (wrappingRect as any).off('resizestart'); + (wrappingRect as any).off('resizing'); + (wrappingRect as any).off('resizedone'); + (wrappingRect as any).resize('stop'); + } + + return skeleton; + }; + + return skeleton; + } + private setupPoints(basicPolyline: SVG.PolyLine, state: any): any { this.selectize(true, basicPolyline); @@ -2437,11 +3347,10 @@ export class CanvasViewImpl implements CanvasView, Listener { clientID: state.clientID, 'color-rendering': 'optimizeQuality', id: `cvat_canvas_shape_${state.clientID}`, - fill: state.color, 'shape-rendering': 'geometricprecision', - stroke: state.color, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'data-z-order': state.zOrder, + ...this.getShapeColorization(state), }) .center(cx, cy) .addClass('cvat_canvas_shape'); @@ -2469,9 +3378,8 @@ export class CanvasViewImpl implements CanvasView, Listener { 'pointer-events': 'none', 'shape-rendering': 'geometricprecision', 'stroke-width': 0, - fill: state.color, // to right fill property when call SVG.Shape::clone() - }) - .style({ + ...this.getShapeColorization(state), + }).style({ opacity: 0, }); @@ -2481,6 +3389,10 @@ export class CanvasViewImpl implements CanvasView, Listener { group.addClass('cvat_canvas_hidden'); } + if (state.occluded) { + group.addClass('cvat_canvas_shape_occluded'); + } + shape.remove = (): SVG.PolyLine => { this.selectize(false, shape); shape.constructor.prototype.remove.call(shape); diff --git a/cvat-canvas/src/typescript/consts.ts b/cvat-canvas/src/typescript/consts.ts index e382496f5a44..96ff980512a4 100644 --- a/cvat-canvas/src/typescript/consts.ts +++ b/cvat-canvas/src/typescript/consts.ts @@ -1,10 +1,10 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT const BASE_STROKE_WIDTH = 1.25; const BASE_GRID_WIDTH = 2; -const BASE_POINT_SIZE = 5; +const BASE_POINT_SIZE = 4; const TEXT_MARGIN = 10; const AREA_THRESHOLD = 9; const SIZE_THRESHOLD = 3; @@ -19,8 +19,13 @@ const ARROW_PATH = 'M13.162 6.284L.682.524a.483.483 0 0 0-.574.134.477.477 0 ' + const BASE_PATTERN_SIZE = 5; const SNAP_TO_ANGLE_RESIZE_DEFAULT = 0.1; const SNAP_TO_ANGLE_RESIZE_SHIFT = 15; -const DEFAULT_SHAPE_TEXT_SIZE = 12; const MINIMUM_TEXT_FONT_SIZE = 8; +const SKELETON_RECT_MARGIN = 20; + +const DEFAULT_SHAPE_TEXT_SIZE = 12; +const DEFAULT_SHAPE_TEXT_CONTENT = 'id,label,attributes,source,descriptions'; +const DEFAULT_SHAPE_TEXT_POSITION: 'auto' | 'center' = 'auto'; +const DEFAULT_UNDEFINED_ATTR_VALUE = '__undefined__'; export default { BASE_STROKE_WIDTH, @@ -40,5 +45,9 @@ export default { SNAP_TO_ANGLE_RESIZE_DEFAULT, SNAP_TO_ANGLE_RESIZE_SHIFT, DEFAULT_SHAPE_TEXT_SIZE, + DEFAULT_SHAPE_TEXT_CONTENT, + DEFAULT_SHAPE_TEXT_POSITION, + DEFAULT_UNDEFINED_ATTR_VALUE, MINIMUM_TEXT_FONT_SIZE, + SKELETON_RECT_MARGIN, }; diff --git a/cvat-canvas/src/typescript/crosshair.ts b/cvat-canvas/src/typescript/crosshair.ts index 24175c59af79..5c4d3098cd5b 100644 --- a/cvat-canvas/src/typescript/crosshair.ts +++ b/cvat-canvas/src/typescript/crosshair.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index fce04b810ad3..19addc0ef6d3 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index b57976f9e3ad..60ca0c486913 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -17,6 +17,11 @@ import { Point, readPointsFromShape, clamp, + translateToCanvas, + computeWrappingBox, + makeSVGFromTemplate, + setupSkeletonEdges, + translateFromCanvas, } from './shared'; import Crosshair from './crosshair'; import consts from './consts'; @@ -72,7 +77,12 @@ function checkConstraint(shapeType: string, points: number[], box: Box | null = export class DrawHandlerImpl implements DrawHandler { // callback is used to notify about creating new shape - private onDrawDone: (data: object | null, duration?: number, continueDraw?: boolean) => void; + private onDrawDoneDefault: ( + data: object | null, + duration?: number, + continueDraw?: boolean, + prevDrawData?: DrawData, + ) => void; private startTimestamp: number; private canvas: SVG.Container; private text: SVG.Container; @@ -81,11 +91,13 @@ export class DrawHandlerImpl implements DrawHandler { y: number; }; private crosshair: Crosshair; - private drawData: DrawData; + private drawData: DrawData | null; private geometry: Geometry; - private configuration: Configuration; private autoborderHandler: AutoborderHandler; private autobordersEnabled: boolean; + private controlPointsSize: number; + private selectedShapeOpacity: number; + private outlinedBorders: string; // we should use any instead of SVG.Shape because svg plugins cannot change declared interface // so, methods like draw() just undefined for SVG.Shape, but nevertheless they exist @@ -93,7 +105,7 @@ export class DrawHandlerImpl implements DrawHandler { private initialized: boolean; private canceled: boolean; private pointsGroup: SVG.G | null; - private shapeSizeElement: ShapeSizeElement; + private shapeSizeElement: ShapeSizeElement | null; private getFinalEllipseCoordinates(points: number[], fitIntoFrame: boolean): number[] { const { offset } = this.geometry; @@ -337,6 +349,15 @@ export class DrawHandlerImpl implements DrawHandler { this.crosshair.hide(); } + private onDrawDone(...args: any[]): void { + if (this.drawData.onDrawDone) { + this.drawData.onDrawDone.call(this, ...args); + return; + } + + this.onDrawDoneDefault.call(this, ...args); + } + private release(): void { if (!this.initialized) { // prevents recursive calls @@ -348,26 +369,26 @@ export class DrawHandlerImpl implements DrawHandler { this.canvas.off('mousedown.draw'); this.canvas.off('mousemove.draw'); - if (this.pointsGroup) { - this.pointsGroup.remove(); - this.pointsGroup = null; - } - // Draw plugin in some cases isn't activated // For example when draw from initialState // Or when no drawn points, but we call cancel() drawing // We check if it is activated with remember function if (this.drawInstance.remember('_paintHandler')) { - if ( - ['polygon', 'polyline', 'points'].includes(this.drawData.shapeType) || + if (['polygon', 'polyline', 'points'].includes(this.drawData.shapeType) || (this.drawData.shapeType === 'cuboid' && - this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CORNER_POINTS) - ) { + this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CORNER_POINTS)) { // Check for unsaved drawn shapes this.drawInstance.draw('done'); } // Clear drawing this.drawInstance.draw('stop'); + } else if (this.drawInstance && this.drawData.shapeType === 'ellipse' && !this.drawData.initialState) { + this.drawInstance.fire('drawstop'); + } + + if (this.pointsGroup) { + this.pointsGroup.remove(); + this.pointsGroup = null; } this.drawInstance.off(); @@ -417,7 +438,8 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - 'fill-opacity': this.configuration.creationOpacity, + 'fill-opacity': this.selectedShapeOpacity, + stroke: this.outlinedBorders, }); } @@ -426,7 +448,8 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - 'fill-opacity': this.configuration.creationOpacity, + 'fill-opacity': this.selectedShapeOpacity, + stroke: this.outlinedBorders, }); const initialPoint: { @@ -442,21 +465,7 @@ export class DrawHandlerImpl implements DrawHandler { const translated = translateToSVG(this.canvas.node as any as SVGSVGElement, [e.clientX, e.clientY]); [initialPoint.x, initialPoint.y] = translated; } else { - const points = this.getFinalEllipseCoordinates(readPointsFromShape(this.drawInstance), false); - const { shapeType, redraw: clientID } = this.drawData; - this.release(); - - if (this.canceled) return; - if (checkConstraint('ellipse', points)) { - this.onDrawDone( - { - clientID, - shapeType, - points, - }, - Date.now() - this.startTimestamp, - ); - } + this.drawInstance.fire('drawstop'); } }); @@ -472,6 +481,25 @@ export class DrawHandlerImpl implements DrawHandler { this.shapeSizeElement.update(this.drawInstance); } }); + + this.drawInstance.on('drawstop', () => { + this.drawInstance.off('drawstop'); + const points = this.getFinalEllipseCoordinates(readPointsFromShape(this.drawInstance), false); + const { shapeType, redraw: clientID } = this.drawData; + this.release(); + + if (this.canceled) return; + if (checkConstraint('ellipse', points)) { + this.onDrawDone( + { + clientID, + shapeType, + points, + }, + Date.now() - this.startTimestamp, + ); + } + }); } private drawBoxBy4Points(): void { @@ -612,7 +640,8 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - 'fill-opacity': this.configuration.creationOpacity, + 'fill-opacity': this.selectedShapeOpacity, + stroke: this.outlinedBorders, }); this.drawPolyshape(); @@ -628,6 +657,7 @@ export class DrawHandlerImpl implements DrawHandler { .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'fill-opacity': 0, + stroke: this.outlinedBorders, }); this.drawPolyshape(); @@ -651,6 +681,7 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + stroke: this.outlinedBorders, }); this.drawPolyshape(); } @@ -681,7 +712,131 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - 'fill-opacity': this.configuration.creationOpacity, + 'fill-opacity': this.selectedShapeOpacity, + stroke: this.outlinedBorders, + }); + } + + private drawSkeleton(): void { + this.drawInstance = this.canvas.rect().attr({ + stroke: this.outlinedBorders, + }); + this.pointsGroup = makeSVGFromTemplate(this.drawData.skeletonSVG); + this.canvas.add(this.pointsGroup); + this.pointsGroup.attr('stroke-width', consts.BASE_STROKE_WIDTH / this.geometry.scale); + this.pointsGroup.attr('stroke', this.outlinedBorders); + + let minX = Number.MAX_SAFE_INTEGER; + let minY = Number.MAX_SAFE_INTEGER; + let maxX = 0; + let maxY = 0; + + this.pointsGroup.children().forEach((child: SVG.Element): void => { + const cx = child.cx(); + const cy = child.cy(); + minX = Math.min(cx, minX); + minY = Math.min(cy, minY); + maxX = Math.max(cx, maxX); + maxY = Math.max(cy, maxY); + }); + + this.drawInstance + .on('drawstop', (e: Event): void => { + const points = readPointsFromShape((e.target as any as { instance: SVG.Rect }).instance); + const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(points, true); + const elements: any[] = []; + Array.from(this.pointsGroup.node.children).forEach((child: Element) => { + if (child.tagName === 'circle') { + const cx = +(child.getAttribute('cx') as string) + xtl; + const cy = +(child.getAttribute('cy') as string) + ytl; + const label = +child.getAttribute('data-label-id'); + elements.push({ + shapeType: 'points', + points: [cx, cy], + labelID: label, + }); + } + }); + + const { shapeType, redraw: clientID } = this.drawData; + this.release(); + + if (this.canceled) return; + if (checkConstraint('rectangle', [xtl, ytl, xbr, ybr])) { + this.onDrawDone({ + clientID, + shapeType, + elements, + }, + Date.now() - this.startTimestamp); + } + }) + .on('drawupdate', (): void => { + const x = this.drawInstance.x(); + const y = this.drawInstance.y(); + const width = this.drawInstance.width(); + const height = this.drawInstance.height(); + this.pointsGroup.style({ + transform: `translate(${x}px, ${y}px)`, + }); + + /* eslint-disable-next-line no-unsanitized/property */ + this.pointsGroup.node.innerHTML = this.drawData.skeletonSVG; + Array.from(this.pointsGroup.node.children).forEach((child: Element) => { + const dataType = child.getAttribute('data-type'); + if (child.tagName === 'circle' && dataType && dataType.includes('element')) { + child.setAttribute('r', `${this.controlPointsSize / this.geometry.scale}`); + let cx = +(child.getAttribute('cx') as string); + let cy = +(child.getAttribute('cy') as string); + const cxOffset = (cx - minX) / (maxX - minX); + const cyOffset = (cy - minY) / (maxY - minY); + cx = Number.isNaN(cxOffset) ? 0.5 * width : cxOffset * width; + cy = Number.isNaN(cyOffset) ? 0.5 * height : cyOffset * height; + child.setAttribute('cx', `${cx}`); + child.setAttribute('cy', `${cy}`); + } + }); + + Array.from(this.pointsGroup.node.children).forEach((child: Element) => { + const dataType = child.getAttribute('data-type'); + if (child.tagName === 'line' && dataType && dataType.includes('edge')) { + child.setAttribute('stroke-width', 'inherit'); + child.setAttribute('stroke', 'inherit'); + const dataNodeFrom = child.getAttribute('data-node-from'); + const dataNodeTo = child.getAttribute('data-node-to'); + if (dataNodeFrom && dataNodeTo) { + const from = this.pointsGroup.node.querySelector(`[data-node-id="${dataNodeFrom}"]`); + const to = this.pointsGroup.node.querySelector(`[data-node-id="${dataNodeTo}"]`); + + if (from && to) { + const x1 = from.getAttribute('cx'); + const y1 = from.getAttribute('cy'); + const x2 = to.getAttribute('cx'); + const y2 = to.getAttribute('cy'); + + if (x1 && y1 && x2 && y2) { + child.setAttribute('x1', x1); + child.setAttribute('y1', y1); + child.setAttribute('x2', x2); + child.setAttribute('y2', y2); + } + } + } + let cx = +(child.getAttribute('cx') as string); + let cy = +(child.getAttribute('cy') as string); + const cxOffset = cx / 100; + const cyOffset = cy / 100; + cx = cxOffset * width; + cy = cyOffset * height; + child.setAttribute('cx', `${cx}`); + child.setAttribute('cy', `${cy}`); + } + }); + }) + .addClass('cvat_canvas_shape_drawing') + .attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'fill-opacity': this.selectedShapeOpacity, }); } @@ -697,10 +852,6 @@ export class DrawHandlerImpl implements DrawHandler { this.getFinalCuboidCoordinates(targetPoints) : this.getFinalPolyshapeCoordinates(targetPoints, true); - if (!e.detail.originalEvent.ctrlKey) { - this.release(); - } - if (checkConstraint(shapeType, points, box)) { this.onDrawDone( { @@ -714,26 +865,30 @@ export class DrawHandlerImpl implements DrawHandler { }, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey, + this.drawData, ); } + + if (!e.detail.originalEvent.ctrlKey) { + this.release(); + } }); } // Common settings for rectangle and polyshapes private pasteShape(): void { - function moveShape(shape: SVG.Shape, x: number, y: number): void { - const bbox = shape.bbox(); + const moveShape = (shape: SVG.Shape, x: number, y: number): void => { const { rotation } = shape.transform(); shape.untransform(); - shape.move(x - bbox.width / 2, y - bbox.height / 2); + shape.center(x, y); shape.rotate(rotation); - } + }; const { x: initialX, y: initialY } = this.cursorPosition; moveShape(this.drawInstance, initialX, initialY); this.canvas.on('mousemove.draw', (): void => { - const { x, y } = this.cursorPosition; // was computer in another callback + const { x, y } = this.cursorPosition; // was computed in another callback moveShape(this.drawInstance, x, y); }); } @@ -741,21 +896,18 @@ export class DrawHandlerImpl implements DrawHandler { private pasteBox(box: BBox, rotation: number): void { this.drawInstance = (this.canvas as any) .rect(box.width, box.height) - .move(box.x, box.y) + .center(box.x, box.y) .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - 'fill-opacity': this.configuration.creationOpacity, + 'fill-opacity': this.selectedShapeOpacity, + stroke: this.outlinedBorders, }).rotate(rotation); this.pasteShape(); this.drawInstance.on('done', (e: CustomEvent): void => { const points = readPointsFromShape((e.target as any as { instance: SVG.Rect }).instance); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(points, !this.drawData.initialState.rotation); - if (!e.detail.originalEvent.ctrlKey) { - this.release(); - } - if (checkConstraint('rectangle', [xtl, ytl, xbr, ybr])) { this.onDrawDone( { @@ -770,8 +922,13 @@ export class DrawHandlerImpl implements DrawHandler { }, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey, + this.drawData, ); } + + if (!e.detail.originalEvent.ctrlKey) { + this.release(); + } }); } @@ -782,7 +939,8 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - 'fill-opacity': this.configuration.creationOpacity, + 'fill-opacity': this.selectedShapeOpacity, + stroke: this.outlinedBorders, }).rotate(rotation); this.pasteShape(); @@ -790,11 +948,6 @@ export class DrawHandlerImpl implements DrawHandler { const points = this.getFinalEllipseCoordinates( readPointsFromShape((e.target as any as { instance: SVG.Ellipse }).instance), false, ); - - if (!e.detail.originalEvent.ctrlKey) { - this.release(); - } - if (checkConstraint('ellipse', points)) { this.onDrawDone( { @@ -809,8 +962,13 @@ export class DrawHandlerImpl implements DrawHandler { }, Date.now() - this.startTimestamp, e.detail.originalEvent.ctrlKey, + this.drawData, ); } + + if (!e.detail.originalEvent.ctrlKey) { + this.release(); + } }); } @@ -820,7 +978,8 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - 'fill-opacity': this.configuration.creationOpacity, + 'fill-opacity': this.selectedShapeOpacity, + stroke: this.outlinedBorders, }); this.pasteShape(); this.pastePolyshape(); @@ -832,6 +991,7 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + stroke: this.outlinedBorders, }); this.pasteShape(); this.pastePolyshape(); @@ -843,26 +1003,108 @@ export class DrawHandlerImpl implements DrawHandler { .addClass('cvat_canvas_shape_drawing') .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, - 'face-stroke': 'black', - 'fill-opacity': this.configuration.creationOpacity, + 'face-stroke': this.outlinedBorders, + 'fill-opacity': this.selectedShapeOpacity, + stroke: this.outlinedBorders, }); this.pasteShape(); this.pastePolyshape(); } + private pasteSkeleton(box: BBox, elements: any[]): void { + const { offset } = this.geometry; + let [xtl, ytl] = [box.x, box.y]; + + this.pasteBox(box, 0); + this.pointsGroup = makeSVGFromTemplate(this.drawData.skeletonSVG); + this.pointsGroup.attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + stroke: this.outlinedBorders, + }); + this.canvas.add(this.pointsGroup); + + this.pointsGroup.children().forEach((child: SVG.Element): void => { + const dataType = child.attr('data-type'); + if (child.node.tagName === 'circle' && dataType && dataType.includes('element')) { + child.attr('r', `${this.controlPointsSize / this.geometry.scale}`); + const labelID = +child.attr('data-label-id'); + const element = elements.find((_element: any): boolean => _element.label.id === labelID); + if (element) { + const points = translateToCanvas(offset, element.points); + child.center(points[0], points[1]); + } + } + }); + + this.drawInstance.off('done').on('done', (e: CustomEvent) => { + const result = { + shapeType: this.drawData.initialState.shapeType, + objectType: this.drawData.initialState.objectType, + elements: this.drawData.initialState.elements.map((element: any) => ({ + shapeType: element.shapeType, + outside: element.outside, + occluded: element.occluded, + label: element.label, + attributes: element.attributes, + points: (() => { + const circle = this.pointsGroup.children() + .find((child: SVG.Element) => child.attr('data-label-id') === element.label.id); + const points = translateFromCanvas(this.geometry.offset, [circle.cx(), circle.cy()]); + return points; + })(), + })), + occluded: this.drawData.initialState.occluded, + attributes: { ...this.drawData.initialState.attributes }, + label: this.drawData.initialState.label, + color: this.drawData.initialState.color, + rotation: this.drawData.initialState.rotation, + }; + + this.onDrawDone( + result, + Date.now() - this.startTimestamp, + e.detail.originalEvent.ctrlKey, + this.drawData, + ); + + if (!e.detail.originalEvent.ctrlKey) { + this.release(); + } + }); + + this.canvas.on('mousemove.draw', (): void => { + const [newXtl, newYtl] = [ + this.drawInstance.x(), this.drawInstance.y(), + this.drawInstance.width(), this.drawInstance.height(), + ]; + const [xDiff, yDiff] = [newXtl - xtl, newYtl - ytl]; + xtl = newXtl; + ytl = newYtl; + this.pointsGroup.children().forEach((child: SVG.Element): void => { + const dataType = child.attr('data-type'); + if (child.node.tagName === 'circle' && dataType && dataType.includes('element')) { + const [cx, cy] = [child.cx(), child.cy()]; + child.center(cx + xDiff, cy + yDiff); + } + }); + this.pointsGroup.untransform(); + setupSkeletonEdges(this.pointsGroup, this.pointsGroup); + }); + } + private pastePoints(initialPoints: string): void { - function moveShape(shape: SVG.PolyLine, group: SVG.G, x: number, y: number, scale: number): void { + const moveShape = (shape: SVG.PolyLine, group: SVG.G, x: number, y: number, scale: number): void => { const bbox = shape.bbox(); shape.move(x - bbox.width / 2, y - bbox.height / 2); const points = shape.attr('points').split(' '); - const radius = consts.BASE_POINT_SIZE / scale; + const radius = this.controlPointsSize / scale; group.children().forEach((child: SVG.Element, idx: number): void => { const [px, py] = points[idx].split(','); child.move(px - radius / 2, py - radius / 2); }); - } + }; const { x: initialX, y: initialY } = this.cursorPosition; this.pointsGroup = this.canvas.group(); @@ -873,7 +1115,7 @@ export class DrawHandlerImpl implements DrawHandler { let numOfPoints = initialPoints.split(' ').length; while (numOfPoints) { numOfPoints--; - const radius = consts.BASE_POINT_SIZE / this.geometry.scale; + const radius = this.controlPointsSize / this.geometry.scale; const stroke = consts.POINTS_STROKE_WIDTH / this.geometry.scale; this.pointsGroup.circle().fill('white').stroke('black').attr({ r: radius, @@ -919,10 +1161,7 @@ export class DrawHandlerImpl implements DrawHandler { if (this.drawData.initialState) { const { offset } = this.geometry; if (this.drawData.shapeType === 'rectangle') { - const [xtl, ytl, xbr, ybr] = this.drawData.initialState.points.map( - (coord: number): number => coord + offset, - ); - + const [xtl, ytl, xbr, ybr] = translateToCanvas(offset, this.drawData.initialState.points); this.pasteBox({ x: xtl, y: ytl, @@ -930,13 +1169,15 @@ export class DrawHandlerImpl implements DrawHandler { height: ybr - ytl, }, this.drawData.initialState.rotation); } else if (this.drawData.shapeType === 'ellipse') { - const [cx, cy, rightX, topY] = this.drawData.initialState.points.map( - (coord: number): number => coord + offset, - ); - + const [cx, cy, rightX, topY] = translateToCanvas(offset, this.drawData.initialState.points); this.pasteEllipse([cx, cy, rightX - cx, cy - topY], this.drawData.initialState.rotation); + } else if (this.drawData.shapeType === 'skeleton') { + const box = computeWrappingBox( + translateToCanvas(offset, this.drawData.initialState.points), consts.SKELETON_RECT_MARGIN, + ); + this.pasteSkeleton(box, this.drawData.initialState.elements); } else { - const points = this.drawData.initialState.points.map((coord: number): number => coord + offset); + const points = translateToCanvas(offset, this.drawData.initialState.points); const stringifiedPoints = stringifyPoints(points); if (this.drawData.shapeType === 'polygon') { @@ -975,6 +1216,8 @@ export class DrawHandlerImpl implements DrawHandler { this.drawCuboid(); this.shapeSizeElement = displayShapeSize(this.canvas, this.text); } + } else if (this.drawData.shapeType === 'skeleton') { + this.drawSkeleton(); } if (this.drawData.shapeType !== 'ellipse') { @@ -987,7 +1230,7 @@ export class DrawHandlerImpl implements DrawHandler { } public constructor( - onDrawDone: (data: object | null, duration?: number, continueDraw?: boolean) => void, + onDrawDone: (data: object | null, duration?: number, continueDraw?: boolean, prevDrawData?: DrawData) => void, canvas: SVG.Container, text: SVG.Container, autoborderHandler: AutoborderHandler, @@ -995,16 +1238,18 @@ export class DrawHandlerImpl implements DrawHandler { configuration: Configuration, ) { this.autoborderHandler = autoborderHandler; + this.controlPointsSize = configuration.controlPointsSize; + this.selectedShapeOpacity = configuration.selectedShapeOpacity; + this.outlinedBorders = configuration.outlinedBorders || 'black'; this.autobordersEnabled = false; this.startTimestamp = Date.now(); - this.onDrawDone = onDrawDone; + this.onDrawDoneDefault = onDrawDone; this.canvas = canvas; this.text = text; this.initialized = false; this.canceled = false; this.drawData = null; this.geometry = geometry; - this.configuration = configuration; this.crosshair = new Crosshair(); this.drawInstance = null; this.pointsGroup = null; @@ -1023,7 +1268,9 @@ export class DrawHandlerImpl implements DrawHandler { } public configurate(configuration: Configuration): void { - this.configuration = configuration; + this.controlPointsSize = configuration.controlPointsSize; + this.selectedShapeOpacity = configuration.selectedShapeOpacity; + this.outlinedBorders = configuration.outlinedBorders || 'black'; const isFillableRect = this.drawData && this.drawData.shapeType === 'rectangle' && @@ -1034,17 +1281,23 @@ export class DrawHandlerImpl implements DrawHandler { const isFilalblePolygon = this.drawData && this.drawData.shapeType === 'polygon'; if (this.drawInstance && (isFillableRect || isFillableCuboid || isFilalblePolygon)) { - this.drawInstance.fill({ opacity: configuration.creationOpacity }); + this.drawInstance.fill({ opacity: configuration.selectedShapeOpacity }); } - if (typeof configuration.autoborders === 'boolean') { - this.autobordersEnabled = configuration.autoborders; - if (this.drawInstance && !this.drawData.initialState) { - if (this.autobordersEnabled) { - this.autoborderHandler.autoborder(true, this.drawInstance, this.drawData.redraw); - } else { - this.autoborderHandler.autoborder(false); - } + if (this.drawInstance && this.drawInstance.attr('stroke')) { + this.drawInstance.attr('stroke', this.outlinedBorders); + } + + if (this.pointsGroup && this.pointsGroup.attr('stroke')) { + this.pointsGroup.attr('stroke', this.outlinedBorders); + } + + this.autobordersEnabled = configuration.autoborders; + if (this.drawInstance && !this.drawData.initialState) { + if (this.autobordersEnabled) { + this.autoborderHandler.autoborder(true, this.drawInstance, this.drawData.redraw); + } else { + this.autoborderHandler.autoborder(false); } } } @@ -1061,10 +1314,14 @@ export class DrawHandlerImpl implements DrawHandler { } if (this.pointsGroup) { + this.pointsGroup.attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + }); + for (const point of this.pointsGroup.children()) { point.attr({ 'stroke-width': consts.POINTS_STROKE_WIDTH / geometry.scale, - r: consts.BASE_POINT_SIZE / geometry.scale, + r: this.controlPointsSize / geometry.scale, }); } } @@ -1079,7 +1336,7 @@ export class DrawHandlerImpl implements DrawHandler { for (const point of (paintHandler as any).set.members) { point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`); - point.attr('r', `${consts.BASE_POINT_SIZE / geometry.scale}`); + point.attr('r', `${this.controlPointsSize / geometry.scale}`); } } } diff --git a/cvat-canvas/src/typescript/editHandler.ts b/cvat-canvas/src/typescript/editHandler.ts index 50db5e5c5f80..1e907ba1c3a4 100644 --- a/cvat-canvas/src/typescript/editHandler.ts +++ b/cvat-canvas/src/typescript/editHandler.ts @@ -1,4 +1,5 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -7,11 +8,11 @@ import 'svg.select.js'; import consts from './consts'; import { translateFromSVG, pointsToNumberArray } from './shared'; -import { EditData, Geometry, Configuration } from './canvasModel'; +import { PolyEditData, Geometry, Configuration } from './canvasModel'; import { AutoborderHandler } from './autoborderHandler'; export interface EditHandler { - edit(editData: EditData): void; + edit(editData: PolyEditData): void; transform(geometry: Geometry): void; configurate(configuration: Configuration): void; cancel(): void; @@ -20,14 +21,16 @@ export interface EditHandler { export class EditHandlerImpl implements EditHandler { private onEditDone: (state: any, points: number[]) => void; private autoborderHandler: AutoborderHandler; - private geometry: Geometry; + private geometry: Geometry | null; private canvas: SVG.Container; - private editData: EditData; - private editedShape: SVG.Shape; - private editLine: SVG.PolyLine; + private editData: PolyEditData | null; + private editedShape: SVG.Shape | null; + private editLine: SVG.PolyLine | null; private clones: SVG.Polygon[]; + private controlPointsSize: number; private autobordersEnabled: boolean; private intelligentCutEnabled: boolean; + private outlinedBorders: string; private setupTrailingPoint(circle: SVG.Circle): void { const head = this.editedShape.attr('points').split(' ').slice(0, this.editData.pointID).join(' '); @@ -112,16 +115,15 @@ export class EditHandlerImpl implements EditHandler { }); } - const strokeColor = this.editedShape.attr('stroke'); (this.editLine as any) .addClass('cvat_canvas_shape_drawing') .style({ 'pointer-events': 'none', 'fill-opacity': 0, - stroke: strokeColor, }) .attr({ 'data-origin-client-id': this.editData.state.clientID, + stroke: this.editedShape.attr('stroke'), }) .on('drawstart drawpoint', (e: CustomEvent): void => { this.transform(this.geometry); @@ -299,7 +301,7 @@ export class EditHandlerImpl implements EditHandler { if (enabled) { (this.editedShape as any).selectize(true, { deepSelect: true, - pointSize: (2 * consts.BASE_POINT_SIZE) / getGeometry().scale, + pointSize: (2 * this.controlPointsSize) / getGeometry().scale, rotationPoint: false, pointType(cx: number, cy: number): SVG.Circle { const circle: SVG.Circle = this.nested @@ -365,7 +367,9 @@ export class EditHandlerImpl implements EditHandler { } private initEditing(): void { - this.editedShape = this.canvas.select(`#cvat_canvas_shape_${this.editData.state.clientID}`).first().clone(); + this.editedShape = this.canvas + .select(`#cvat_canvas_shape_${this.editData.state.clientID}`).first() + .clone().attr('stroke', this.outlinedBorders); this.setupPoints(true); this.startEdit(); // draw points for this with selected and start editing till another point is clicked @@ -387,6 +391,8 @@ export class EditHandlerImpl implements EditHandler { this.autoborderHandler = autoborderHandler; this.autobordersEnabled = false; this.intelligentCutEnabled = false; + this.controlPointsSize = consts.BASE_POINT_SIZE; + this.outlinedBorders = 'black'; this.onEditDone = onEditDone; this.canvas = canvas; this.editData = null; @@ -416,20 +422,23 @@ export class EditHandlerImpl implements EditHandler { } public configurate(configuration: Configuration): void { - if (typeof configuration.autoborders === 'boolean') { - this.autobordersEnabled = configuration.autoborders; - if (this.editLine) { - if (this.autobordersEnabled) { - this.autoborderHandler.autoborder(true, this.editLine, this.editData.state.clientID); - } else { - this.autoborderHandler.autoborder(false); - } - } + this.autobordersEnabled = configuration.autoborders; + this.outlinedBorders = configuration.outlinedBorders || 'black'; + + if (this.editedShape) { + this.editedShape.attr('stroke', this.outlinedBorders); } - if (typeof configuration.intelligentPolygonCrop === 'boolean') { - this.intelligentCutEnabled = configuration.intelligentPolygonCrop; + if (this.editLine) { + this.editLine.attr('stroke', this.outlinedBorders); + if (this.autobordersEnabled) { + this.autoborderHandler.autoborder(true, this.editLine, this.editData.state.clientID); + } else { + this.autoborderHandler.autoborder(false); + } } + this.controlPointsSize = configuration.controlPointsSize || consts.BASE_POINT_SIZE; + this.intelligentCutEnabled = configuration.intelligentPolygonCrop; } public transform(geometry: Geometry): void { @@ -453,7 +462,7 @@ export class EditHandlerImpl implements EditHandler { for (const point of (paintHandler as any).set.members) { point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`); - point.attr('r', `${consts.BASE_POINT_SIZE / geometry.scale}`); + point.attr('r', `${this.controlPointsSize / geometry.scale}`); } } } diff --git a/cvat-canvas/src/typescript/groupHandler.ts b/cvat-canvas/src/typescript/groupHandler.ts index 29b0736ab5ed..f97a54c48991 100644 --- a/cvat-canvas/src/typescript/groupHandler.ts +++ b/cvat-canvas/src/typescript/groupHandler.ts @@ -1,11 +1,11 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import * as SVG from 'svg.js'; import { GroupData } from './canvasModel'; - -import { translateToSVG } from './shared'; +import { expandChannels, imageDataToDataURL, translateToSVG } from './shared'; export interface GroupHandler { group(groupData: GroupData): void; @@ -22,7 +22,7 @@ export class GroupHandlerImpl implements GroupHandler { private bindedOnSelectStart: (event: MouseEvent) => void; private bindedOnSelectUpdate: (event: MouseEvent) => void; private bindedOnSelectStop: (event: MouseEvent) => void; - private selectionRect: SVG.Rect; + private selectionRect: SVG.Rect | null; private startSelectionPoint: { x: number; y: number; @@ -31,6 +31,7 @@ export class GroupHandlerImpl implements GroupHandler { private initialized: boolean; private statesToBeGroupped: any[]; private highlightedShapes: Record; + private groupingCopies: Record; private getSelectionBox( event: MouseEvent, @@ -106,11 +107,7 @@ export class GroupHandlerImpl implements GroupHandler { (state: any): boolean => state.clientID === clientID, )[0]; - if (objectState) { - this.statesToBeGroupped.push(objectState); - this.highlightedShapes[clientID] = shape; - (shape as any).addClass('cvat_canvas_shape_grouping'); - } + this.appendToSelection(objectState); } } } @@ -164,6 +161,7 @@ export class GroupHandlerImpl implements GroupHandler { this.canvas = canvas; this.statesToBeGroupped = []; this.highlightedShapes = {}; + this.groupingCopies = {}; this.selectionRect = null; this.initialized = false; this.startSelectionPoint = { @@ -185,23 +183,67 @@ export class GroupHandlerImpl implements GroupHandler { } } + private appendToSelection(objectState: any): void { + const { clientID } = objectState; + + const shape = this.canvas.select(`#cvat_canvas_shape_${clientID}`).first(); + if (shape) { + if (objectState.shapeType === 'mask') { + const { points } = objectState; + const colorRGB = [139, 0, 139]; + const [left, top, right, bottom] = points.slice(-4); + const imageBitmap = expandChannels(colorRGB[0], colorRGB[1], colorRGB[2], points, 4); + + const bbox = shape.bbox(); + const image = this.canvas.image().attr({ + 'color-rendering': 'optimizeQuality', + 'shape-rendering': 'geometricprecision', + 'data-z-order': Number.MAX_SAFE_INTEGER, + 'grouping-copy-for': clientID, + }).move(bbox.x, bbox.y); + this.groupingCopies[clientID] = image; + + imageDataToDataURL( + imageBitmap, + right - left + 1, + bottom - top + 1, + (dataURL: string) => new Promise((resolve, reject) => { + image.loaded(() => { + resolve(); + }); + image.error(() => { + reject(); + }); + image.load(dataURL); + }), + ); + } + + this.statesToBeGroupped.push(objectState); + this.highlightedShapes[clientID] = shape; + shape.addClass('cvat_canvas_shape_grouping'); + } + } + public select(objectState: any): void { const stateIndexes = this.statesToBeGroupped.map((state): number => state.clientID); - const includes = stateIndexes.indexOf(objectState.clientID); + const { clientID } = objectState; + const includes = stateIndexes.indexOf(clientID); if (includes !== -1) { - const shape = this.highlightedShapes[objectState.clientID]; + const shape = this.highlightedShapes[clientID]; this.statesToBeGroupped.splice(includes, 1); if (shape) { - delete this.highlightedShapes[objectState.clientID]; + if (this.groupingCopies[clientID]) { + // remove clones for masks + this.groupingCopies[clientID].remove(); + delete this.groupingCopies[clientID]; + } + + delete this.highlightedShapes[clientID]; shape.removeClass('cvat_canvas_shape_grouping'); } } else { - const shape = this.canvas.select(`#cvat_canvas_shape_${objectState.clientID}`).first(); - if (shape) { - this.statesToBeGroupped.push(objectState); - this.highlightedShapes[objectState.clientID] = shape; - shape.addClass('cvat_canvas_shape_grouping'); - } + this.appendToSelection(objectState); } } @@ -210,8 +252,14 @@ export class GroupHandlerImpl implements GroupHandler { const shape = this.highlightedShapes[state.clientID]; shape.removeClass('cvat_canvas_shape_grouping'); } + + for (const shape of Object.values(this.groupingCopies)) { + shape.remove(); + } + this.statesToBeGroupped = []; this.highlightedShapes = {}; + this.groupingCopies = {}; if (this.selectionRect) { this.selectionRect.remove(); this.selectionRect = null; diff --git a/cvat-canvas/src/typescript/interactionHandler.ts b/cvat-canvas/src/typescript/interactionHandler.ts index 10d6b729e483..42370d186e6d 100644 --- a/cvat-canvas/src/typescript/interactionHandler.ts +++ b/cvat-canvas/src/typescript/interactionHandler.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ import * as SVG from 'svg.js'; import consts from './consts'; import Crosshair from './crosshair'; import { - translateToSVG, PropType, stringifyPoints, translateToCanvas, + translateToSVG, PropType, stringifyPoints, translateToCanvas, expandChannels, imageDataToDataURL, } from './shared'; import { @@ -23,7 +23,6 @@ export interface InteractionHandler { export class InteractionHandlerImpl implements InteractionHandler { private onInteraction: (shapes: InteractionResult[] | null, shapesUpdated?: boolean, isDone?: boolean) => void; - private configuration: Configuration; private geometry: Geometry; private canvas: SVG.Container; private interactionData: InteractionData; @@ -37,6 +36,8 @@ export class InteractionHandlerImpl implements InteractionHandler { private intermediateShape: PropType; private drawnIntermediateShape: SVG.Shape; private thresholdWasModified: boolean; + private controlPointsSize: number; + private selectedShapeOpacity: number; private prepareResult(): InteractionResult[] { return this.interactionShapes.map( @@ -111,7 +112,7 @@ export class InteractionHandlerImpl implements InteractionHandler { if (!this.isWithinThreshold(cx, cy)) return; this.currentInteractionShape = this.canvas - .circle((consts.BASE_POINT_SIZE * 2) / this.geometry.scale) + .circle((this.controlPointsSize * 2) / this.geometry.scale) .center(cx, cy) .fill('white') .stroke(e.button === 0 ? 'green' : 'red') @@ -137,7 +138,7 @@ export class InteractionHandlerImpl implements InteractionHandler { self.addClass('cvat_canvas_removable_interaction_point'); self.attr({ 'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale, - r: (consts.BASE_POINT_SIZE * 1.5) / this.geometry.scale, + r: (this.controlPointsSize * 1.5) / this.geometry.scale, }); self.on('mousedown', (_e: MouseEvent): void => { @@ -162,7 +163,7 @@ export class InteractionHandlerImpl implements InteractionHandler { self.removeClass('cvat_canvas_removable_interaction_point'); self.attr({ 'stroke-width': consts.POINTS_STROKE_WIDTH / this.geometry.scale, - r: consts.BASE_POINT_SIZE / this.geometry.scale, + r: this.controlPointsSize / this.geometry.scale, }); self.off('mousedown'); @@ -205,7 +206,7 @@ export class InteractionHandlerImpl implements InteractionHandler { .attr({ 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, }) - .fill({ opacity: this.configuration.creationOpacity, color: 'white' }); + .fill({ opacity: this.selectedShapeOpacity, color: 'white' }); } private initInteraction(): void { @@ -300,9 +301,36 @@ export class InteractionHandlerImpl implements InteractionHandler { 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, stroke: erroredShape ? 'red' : 'black', }) - .fill({ opacity: this.configuration.creationOpacity, color: 'white' }) + .fill({ opacity: this.selectedShapeOpacity, color: 'white' }) .addClass('cvat_canvas_interact_intermediate_shape'); this.selectize(true, this.drawnIntermediateShape, erroredShape); + } else if (shapeType === 'mask') { + const [left, top, right, bottom] = points.slice(-4); + const imageBitmap = expandChannels(255, 255, 255, points, 4); + + const image = this.canvas.image().attr({ + 'color-rendering': 'optimizeQuality', + 'shape-rendering': 'geometricprecision', + 'pointer-events': 'none', + opacity: 0.5, + }).addClass('cvat_canvas_interact_intermediate_shape'); + image.move(this.geometry.offset, this.geometry.offset); + this.drawnIntermediateShape = image; + + imageDataToDataURL( + imageBitmap, + right - left + 1, + bottom - top + 1, + (dataURL: string) => new Promise((resolve, reject) => { + image.loaded(() => { + resolve(); + }); + image.error(() => { + reject(); + }); + image.load(dataURL); + }), + ); } else { throw new Error( `Shape type "${shapeType}" was not implemented at interactionHandler::updateIntermediateShape`, @@ -317,7 +345,7 @@ export class InteractionHandlerImpl implements InteractionHandler { if (value) { (shape as any).selectize(value, { deepSelect: true, - pointSize: consts.BASE_POINT_SIZE / self.geometry.scale, + pointSize: this.controlPointsSize / self.geometry.scale, rotationPoint: false, classPoints: 'cvat_canvas_interact_intermediate_shape_point', pointType(cx: number, cy: number): SVG.Circle { @@ -399,7 +427,6 @@ export class InteractionHandlerImpl implements InteractionHandler { onInteraction(shapes, shapesUpdated, isDone, this.threshold ? this.thresholdRectSize / 2 : null); }; this.canvas = canvas; - this.configuration = configuration; this.geometry = geometry; this.shapesWereUpdated = false; this.interactionShapes = []; @@ -410,6 +437,8 @@ export class InteractionHandlerImpl implements InteractionHandler { this.thresholdRectSize = 300; this.intermediateShape = null; this.drawnIntermediateShape = null; + this.controlPointsSize = configuration.controlPointsSize; + this.selectedShapeOpacity = configuration.selectedShapeOpacity; this.cursorPosition = { x: 0, y: 0, @@ -477,10 +506,10 @@ export class InteractionHandlerImpl implements InteractionHandler { for (const shape of shapesToBeScaled) { if (shape.type === 'circle') { if (shape.hasClass('cvat_canvas_removable_interaction_point')) { - (shape as SVG.Circle).radius((consts.BASE_POINT_SIZE * 1.5) / this.geometry.scale); + (shape as SVG.Circle).radius((this.controlPointsSize * 1.5) / this.geometry.scale); shape.attr('stroke-width', consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale); } else { - (shape as SVG.Circle).radius(consts.BASE_POINT_SIZE / this.geometry.scale); + (shape as SVG.Circle).radius(this.controlPointsSize / this.geometry.scale); shape.attr('stroke-width', consts.POINTS_STROKE_WIDTH / this.geometry.scale); } } else { @@ -490,7 +519,7 @@ export class InteractionHandlerImpl implements InteractionHandler { for (const element of window.document.getElementsByClassName('cvat_canvas_interact_intermediate_shape_point')) { element.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / (2 * this.geometry.scale)}`); - element.setAttribute('r', `${consts.BASE_POINT_SIZE / this.geometry.scale}`); + element.setAttribute('r', `${this.controlPointsSize / this.geometry.scale}`); } if (this.drawnIntermediateShape) { @@ -520,21 +549,23 @@ export class InteractionHandlerImpl implements InteractionHandler { } public configurate(configuration: Configuration): void { - this.configuration = configuration; + this.controlPointsSize = configuration.controlPointsSize; + this.selectedShapeOpacity = configuration.selectedShapeOpacity; + if (this.drawnIntermediateShape) { this.drawnIntermediateShape.fill({ - opacity: configuration.creationOpacity, + opacity: configuration.selectedShapeOpacity, }); } // when interactRectangle if (this.currentInteractionShape && this.currentInteractionShape.type === 'rect') { - this.currentInteractionShape.fill({ opacity: configuration.creationOpacity }); + this.currentInteractionShape.fill({ opacity: configuration.selectedShapeOpacity }); } // when interactPoints with startwithbbox if (this.interactionShapes[0] && this.interactionShapes[0].type === 'rect') { - this.interactionShapes[0].fill({ opacity: configuration.creationOpacity }); + this.interactionShapes[0].fill({ opacity: configuration.selectedShapeOpacity }); } } diff --git a/cvat-canvas/src/typescript/masksHandler.ts b/cvat-canvas/src/typescript/masksHandler.ts new file mode 100644 index 000000000000..e0512c066640 --- /dev/null +++ b/cvat-canvas/src/typescript/masksHandler.ts @@ -0,0 +1,655 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { fabric } from 'fabric'; + +import { + DrawData, MasksEditData, Geometry, Configuration, BrushTool, ColorBy, +} from './canvasModel'; +import consts from './consts'; +import { DrawHandler } from './drawHandler'; +import { + PropType, computeWrappingBox, alphaChannelOnly, expandChannels, imageDataToDataURL, +} from './shared'; + +interface WrappingBBox { + left: number; + top: number; + right: number; + bottom: number; +} + +export interface MasksHandler { + draw(drawData: DrawData): void; + edit(state: MasksEditData): void; + configurate(configuration: Configuration): void; + transform(geometry: Geometry): void; + cancel(): void; + enabled: boolean; +} + +export class MasksHandlerImpl implements MasksHandler { + private onDrawDone: ( + data: object | null, + duration?: number, + continueDraw?: boolean, + prevDrawData?: DrawData, + ) => void; + private onDrawRepeat: (data: DrawData) => void; + private onEditStart: (state: any) => void; + private onEditDone: (state: any, points: number[]) => void; + private vectorDrawHandler: DrawHandler; + + private redraw: number | null; + private isDrawing: boolean; + private isEditing: boolean; + private isInsertion: boolean; + private isMouseDown: boolean; + private isBrushSizeChanging: boolean; + private resizeBrushToolLatestX: number; + private brushMarker: fabric.Rect | fabric.Circle | null; + private drawablePolygon: null | fabric.Polygon; + private isPolygonDrawing: boolean; + private drawnObjects: (fabric.Polygon | fabric.Circle | fabric.Rect | fabric.Line | fabric.Image)[]; + + private tool: DrawData['brushTool'] | null; + private drawData: DrawData | null; + private canvas: fabric.Canvas; + + private editData: MasksEditData | null; + + private colorBy: ColorBy; + private latestMousePos: { x: number; y: number; }; + private startTimestamp: number; + private geometry: Geometry; + private drawingOpacity: number; + + private keepDrawnPolygon(): void { + const canvasWrapper = this.canvas.getElement().parentElement; + canvasWrapper.style.pointerEvents = ''; + canvasWrapper.style.zIndex = ''; + this.isPolygonDrawing = false; + this.vectorDrawHandler.draw({ enabled: false }, this.geometry); + } + + private removeBrushMarker(): void { + if (this.brushMarker) { + this.canvas.remove(this.brushMarker); + this.brushMarker = null; + this.canvas.renderAll(); + } + } + + private setupBrushMarker(): void { + if (['brush', 'eraser'].includes(this.tool.type)) { + const common = { + evented: false, + selectable: false, + opacity: 0.75, + left: this.latestMousePos.x - this.tool.size / 2, + top: this.latestMousePos.y - this.tool.size / 2, + stroke: 'white', + strokeWidth: 1, + }; + this.brushMarker = this.tool.form === 'circle' ? new fabric.Circle({ + ...common, + radius: this.tool.size / 2, + }) : new fabric.Rect({ + ...common, + width: this.tool.size, + height: this.tool.size, + }); + + this.canvas.defaultCursor = 'none'; + this.canvas.add(this.brushMarker); + } else { + this.canvas.defaultCursor = 'inherit'; + } + } + + private releaseCanvasWrapperCSS(): void { + const canvasWrapper = this.canvas.getElement().parentElement; + canvasWrapper.style.pointerEvents = ''; + canvasWrapper.style.zIndex = ''; + canvasWrapper.style.display = ''; + } + + private releasePaste(): void { + this.releaseCanvasWrapperCSS(); + this.canvas.clear(); + this.canvas.renderAll(); + this.isInsertion = false; + this.drawnObjects = []; + this.onDrawDone(null); + } + + private releaseDraw(): void { + this.removeBrushMarker(); + this.releaseCanvasWrapperCSS(); + if (this.isPolygonDrawing) { + this.isPolygonDrawing = false; + this.vectorDrawHandler.cancel(); + } + this.canvas.clear(); + this.canvas.renderAll(); + this.isDrawing = false; + this.isInsertion = false; + this.redraw = null; + this.drawnObjects = []; + this.onDrawDone(null); + } + + private releaseEdit(): void { + this.removeBrushMarker(); + this.releaseCanvasWrapperCSS(); + if (this.isPolygonDrawing) { + this.isPolygonDrawing = false; + this.vectorDrawHandler.cancel(); + } + this.canvas.clear(); + this.canvas.renderAll(); + this.isEditing = false; + this.drawnObjects = []; + this.onEditDone(null, null); + } + + private getStateColor(state: any): string { + if (this.colorBy === ColorBy.INSTANCE) { + return state.color; + } + + if (this.colorBy === ColorBy.LABEL) { + return state.label.color; + } + + return state.group.color; + } + + private getDrawnObjectsWrappingBox(): WrappingBBox { + type BoundingRect = ReturnType>; + type TwoCornerBox = Pick & { right: number; bottom: number }; + const { width, height } = this.geometry.image; + const wrappingBbox = this.drawnObjects + .map((obj) => { + if (obj instanceof fabric.Polygon) { + const bbox = computeWrappingBox(obj.points + .reduce(((acc, val) => { + acc.push(val.x, val.y); + return acc; + }), [])); + + return { + left: bbox.xtl, + top: bbox.ytl, + width: bbox.width, + height: bbox.height, + }; + } + + if (obj instanceof fabric.Image) { + return { + left: obj.left, + top: obj.top, + width: obj.width, + height: obj.height, + }; + } + + return obj.getBoundingRect(); + }) + .reduce((acc: TwoCornerBox, rect: BoundingRect) => { + acc.top = Math.floor(Math.max(0, Math.min(rect.top, acc.top))); + acc.left = Math.floor(Math.max(0, Math.min(rect.left, acc.left))); + acc.bottom = Math.floor(Math.min(height, Math.max(rect.top + rect.height, acc.bottom))); + acc.right = Math.floor(Math.min(width, Math.max(rect.left + rect.width, acc.right))); + return acc; + }, { + left: Number.MAX_SAFE_INTEGER, + top: Number.MAX_SAFE_INTEGER, + right: Number.MIN_SAFE_INTEGER, + bottom: Number.MIN_SAFE_INTEGER, + }); + + return wrappingBbox; + } + + private imageDataFromCanvas(wrappingBBox: WrappingBBox): Uint8ClampedArray { + const imageData = this.canvas.toCanvasElement() + .getContext('2d').getImageData( + wrappingBBox.left, wrappingBBox.top, + wrappingBBox.right - wrappingBBox.left + 1, wrappingBBox.bottom - wrappingBBox.top + 1, + ).data; + return imageData; + } + + private updateBrushTools(brushTool?: BrushTool, opts: Partial = {}): void { + if (this.isPolygonDrawing) { + // tool was switched from polygon to brush for example + this.keepDrawnPolygon(); + } + + this.removeBrushMarker(); + if (brushTool) { + if (brushTool.color && this.tool?.color !== brushTool.color) { + const color = fabric.Color.fromHex(brushTool.color); + for (const object of this.drawnObjects) { + if (object instanceof fabric.Line) { + const alpha = +object.stroke.split(',')[3].slice(0, -1); + color.setAlpha(alpha); + object.set({ stroke: color.toRgba() }); + } else if (!(object instanceof fabric.Image)) { + const alpha = +(object.fill as string).split(',')[3].slice(0, -1); + color.setAlpha(alpha); + object.set({ fill: color.toRgba() }); + } + } + this.canvas.renderAll(); + } + + this.tool = { ...brushTool, ...opts }; + if (this.isDrawing || this.isEditing) { + this.setupBrushMarker(); + } + } + + if (this.tool?.type?.startsWith('polygon-')) { + this.isPolygonDrawing = true; + this.vectorDrawHandler.draw({ + enabled: true, + shapeType: 'polygon', + onDrawDone: (data: { points: number[] } | null) => { + if (!data) return; + const points = data.points.reduce((acc: fabric.Point[], _: number, idx: number) => { + if (idx % 2) { + acc.push(new fabric.Point(data.points[idx - 1], data.points[idx])); + } + + return acc; + }, []); + + const color = fabric.Color.fromHex(this.tool.color); + color.setAlpha(this.tool.type === 'polygon-minus' ? 1 : this.drawingOpacity); + const polygon = new fabric.Polygon(points, { + fill: color.toRgba(), + selectable: false, + objectCaching: false, + absolutePositioned: true, + globalCompositeOperation: this.tool.type === 'polygon-minus' ? 'destination-out' : 'xor', + }); + + this.canvas.add(polygon); + this.drawnObjects.push(polygon); + this.canvas.renderAll(); + }, + }, this.geometry); + + const canvasWrapper = this.canvas.getElement().parentElement as HTMLDivElement; + canvasWrapper.style.pointerEvents = 'none'; + canvasWrapper.style.zIndex = '0'; + } + } + + public constructor( + onDrawDone: ( + data: object | null, + duration?: number, + continueDraw?: boolean, + prevDrawData?: DrawData, + ) => void, + onDrawRepeat: (data: DrawData) => void, + onEditStart: (state: any) => void, + onEditDone: (state: any, points: number[]) => void, + vectorDrawHandler: DrawHandler, + canvas: HTMLCanvasElement, + ) { + this.redraw = null; + this.isDrawing = false; + this.isEditing = false; + this.isMouseDown = false; + this.isBrushSizeChanging = false; + this.isPolygonDrawing = false; + this.drawData = null; + this.editData = null; + this.drawnObjects = []; + this.drawingOpacity = 0.5; + this.brushMarker = null; + this.colorBy = ColorBy.LABEL; + this.onDrawDone = onDrawDone; + this.onDrawRepeat = onDrawRepeat; + this.onEditDone = onEditDone; + this.onEditStart = onEditStart; + this.vectorDrawHandler = vectorDrawHandler; + this.canvas = new fabric.Canvas(canvas, { + containerClass: 'cvat_masks_canvas_wrapper', + fireRightClick: true, + selection: false, + defaultCursor: 'inherit', + }); + this.canvas.imageSmoothingEnabled = false; + + this.canvas.getElement().parentElement.addEventListener('contextmenu', (e: MouseEvent) => e.preventDefault()); + this.latestMousePos = { x: -1, y: -1 }; + window.document.addEventListener('mouseup', () => { + this.isMouseDown = false; + this.isBrushSizeChanging = false; + }); + + this.canvas.on('mouse:down', (options: fabric.IEvent) => { + const { isDrawing, isEditing, isInsertion } = this; + this.isMouseDown = (isDrawing || isEditing) && options.e.button === 0 && !options.e.altKey; + this.isBrushSizeChanging = (isDrawing || isEditing) && options.e.button === 2 && options.e.altKey; + + if (isInsertion) { + const continueInserting = options.e.ctrlKey; + const wrappingBbox = this.getDrawnObjectsWrappingBox(); + const imageData = this.imageDataFromCanvas(wrappingBbox); + const alpha = alphaChannelOnly(imageData); + alpha.push(wrappingBbox.left, wrappingBbox.top, wrappingBbox.right, wrappingBbox.bottom); + + this.onDrawDone({ + shapeType: this.drawData.shapeType, + points: alpha, + }, Date.now() - this.startTimestamp, continueInserting, this.drawData); + + if (!continueInserting) { + this.releasePaste(); + } + } + }); + + this.canvas.on('mouse:move', (e: fabric.IEvent) => { + const { image: { width: imageWidth, height: imageHeight } } = this.geometry; + const { angle } = this.geometry; + let [x, y] = [e.pointer.x, e.pointer.y]; + if (angle === 180) { + [x, y] = [imageWidth - x, imageHeight - y]; + } else if (angle === 270) { + [x, y] = [imageWidth - (y / imageHeight) * imageWidth, (x / imageWidth) * imageHeight]; + } else if (angle === 90) { + [x, y] = [(y / imageHeight) * imageWidth, imageHeight - (x / imageWidth) * imageHeight]; + } + + const position = { x, y }; + const { + tool, isMouseDown, isInsertion, isBrushSizeChanging, + } = this; + + if (isInsertion) { + const [object] = this.drawnObjects; + if (object && object instanceof fabric.Image) { + object.left = position.x - object.width / 2; + object.top = position.y - object.height / 2; + this.canvas.renderAll(); + } + } + + if (isBrushSizeChanging && ['brush', 'eraser'].includes(tool?.type)) { + const xDiff = e.pointer.x - this.resizeBrushToolLatestX; + let onUpdateConfiguration = null; + if (this.isDrawing) { + onUpdateConfiguration = this.drawData.onUpdateConfiguration; + } else if (this.isEditing) { + onUpdateConfiguration = this.editData.onUpdateConfiguration; + } + if (onUpdateConfiguration) { + onUpdateConfiguration({ + brushTool: { + size: Math.trunc(Math.max(1, this.tool.size + xDiff)), + }, + }); + } + + this.resizeBrushToolLatestX = e.pointer.x; + e.e.stopPropagation(); + return; + } + + if (this.brushMarker) { + this.brushMarker.left = position.x - tool.size / 2; + this.brushMarker.top = position.y - tool.size / 2; + this.canvas.bringToFront(this.brushMarker); + this.canvas.renderAll(); + } + + if (isMouseDown && !isBrushSizeChanging && ['brush', 'eraser'].includes(tool?.type)) { + const color = fabric.Color.fromHex(tool.color); + color.setAlpha(tool.type === 'eraser' ? 1 : 0.5); + + const commonProperties = { + selectable: false, + evented: false, + globalCompositeOperation: tool.type === 'eraser' ? 'destination-out' : 'xor', + }; + + const shapeProperties = { + ...commonProperties, + fill: color.toRgba(), + left: position.x - tool.size / 2, + top: position.y - tool.size / 2, + }; + + let shape: fabric.Circle | fabric.Rect | null = null; + if (tool.form === 'circle') { + shape = new fabric.Circle({ + ...shapeProperties, + radius: tool.size / 2, + }); + } else if (tool.form === 'square') { + shape = new fabric.Rect({ + ...shapeProperties, + width: tool.size, + height: tool.size, + }); + } + + this.canvas.add(shape); + if (tool.type === 'brush') { + this.drawnObjects.push(shape); + } + + // add line to smooth the mask + if (this.latestMousePos.x !== -1 && this.latestMousePos.y !== -1) { + const dx = position.x - this.latestMousePos.x; + const dy = position.y - this.latestMousePos.y; + if (Math.sqrt(dx ** 2 + dy ** 2) > tool.size / 2) { + const line = new fabric.Line([ + this.latestMousePos.x - tool.size / 2, + this.latestMousePos.y - tool.size / 2, + position.x - tool.size / 2, + position.y - tool.size / 2, + ], { + ...commonProperties, + stroke: color.toRgba(), + strokeWidth: tool.size, + strokeLineCap: tool.form === 'circle' ? 'round' : 'square', + }); + + this.canvas.add(line); + if (tool.type === 'brush') { + this.drawnObjects.push(line); + } + } + } + this.canvas.renderAll(); + } else if (tool?.type.startsWith('polygon-') && this.drawablePolygon) { + // update the polygon position + const points = this.drawablePolygon.get('points'); + if (points.length) { + points[points.length - 1].setX(e.e.offsetX); + points[points.length - 1].setY(e.e.offsetY); + } + this.canvas.renderAll(); + } + + this.latestMousePos.x = position.x; + this.latestMousePos.y = position.y; + this.resizeBrushToolLatestX = position.x; + }); + } + + public configurate(configuration: Configuration): void { + this.colorBy = configuration.colorBy; + } + + public transform(geometry: Geometry): void { + this.geometry = geometry; + const { + scale, angle, image: { width, height }, top, left, + } = geometry; + + const topCanvas = this.canvas.getElement().parentElement as HTMLDivElement; + if (this.canvas.width !== width || this.canvas.height !== height) { + this.canvas.setHeight(height); + this.canvas.setWidth(width); + this.canvas.setDimensions({ width, height }); + } + + topCanvas.style.top = `${top}px`; + topCanvas.style.left = `${left}px`; + topCanvas.style.transform = `scale(${scale}) rotate(${angle}deg)`; + + if (this.drawablePolygon) { + this.drawablePolygon.set('strokeWidth', consts.BASE_STROKE_WIDTH / scale); + this.canvas.renderAll(); + } + } + + public draw(drawData: DrawData): void { + if (drawData.enabled && drawData.shapeType === 'mask') { + if (!this.isInsertion && drawData.initialState?.shapeType === 'mask') { + // initialize inserting pipeline if not started + const { points } = drawData.initialState; + const color = fabric.Color.fromHex(this.getStateColor(drawData.initialState)).getSource(); + const [left, top, right, bottom] = points.slice(-4); + const imageBitmap = expandChannels(color[0], color[1], color[2], points, 4); + imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1, + (dataURL: string) => new Promise((resolve) => { + fabric.Image.fromURL(dataURL, (image: fabric.Image) => { + try { + image.selectable = false; + image.evented = false; + image.globalCompositeOperation = 'xor'; + image.opacity = 0.5; + this.canvas.add(image); + this.drawnObjects.push(image); + this.canvas.renderAll(); + } finally { + resolve(); + } + }, { left, top }); + })); + + this.isInsertion = true; + } else if (!this.isDrawing) { + // initialize drawing pipeline if not started + this.isDrawing = true; + this.redraw = drawData.redraw || null; + } + + this.canvas.getElement().parentElement.style.display = 'block'; + this.startTimestamp = Date.now(); + } + + this.updateBrushTools(drawData.brushTool); + + if (!drawData.enabled && this.isDrawing) { + try { + if (this.drawnObjects.length) { + const wrappingBbox = this.getDrawnObjectsWrappingBox(); + const imageData = this.imageDataFromCanvas(wrappingBbox); + const alpha = alphaChannelOnly(imageData); + alpha.push(wrappingBbox.left, wrappingBbox.top, wrappingBbox.right, wrappingBbox.bottom); + this.onDrawDone({ + shapeType: this.drawData.shapeType, + points: alpha, + ...(Number.isInteger(this.redraw) ? { clientID: this.redraw } : {}), + }, Date.now() - this.startTimestamp, drawData.continue, this.drawData); + } + } finally { + this.releaseDraw(); + } + + if (drawData.continue) { + const newDrawData = { + ...this.drawData, + brushTool: { ...this.tool }, + ...drawData, + enabled: true, + shapeType: 'mask', + }; + this.onDrawRepeat(newDrawData); + return; + } + } + + this.drawData = drawData; + } + + public edit(editData: MasksEditData): void { + if (editData.enabled && editData.state.shapeType === 'mask') { + if (!this.isEditing) { + // start editing pipeline if not started yet + this.canvas.getElement().parentElement.style.display = 'block'; + const { points } = editData.state; + const color = fabric.Color.fromHex(this.getStateColor(editData.state)).getSource(); + const [left, top, right, bottom] = points.slice(-4); + const imageBitmap = expandChannels(color[0], color[1], color[2], points, 4); + imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1, + (dataURL: string) => new Promise((resolve) => { + fabric.Image.fromURL(dataURL, (image: fabric.Image) => { + try { + image.selectable = false; + image.evented = false; + image.globalCompositeOperation = 'xor'; + image.opacity = 0.5; + this.canvas.add(image); + this.drawnObjects.push(image); + this.canvas.renderAll(); + } finally { + resolve(); + } + }, { left, top }); + })); + + this.isEditing = true; + this.startTimestamp = Date.now(); + this.onEditStart(editData.state); + } + } + + this.updateBrushTools( + editData.brushTool, + editData.state ? { color: this.getStateColor(editData.state) } : {}, + ); + + if (!editData.enabled && this.isEditing) { + try { + if (this.drawnObjects.length) { + const wrappingBbox = this.getDrawnObjectsWrappingBox(); + const imageData = this.imageDataFromCanvas(wrappingBbox); + const alpha = alphaChannelOnly(imageData); + alpha.push(wrappingBbox.left, wrappingBbox.top, wrappingBbox.right, wrappingBbox.bottom); + this.onEditDone(this.editData.state, alpha); + } + } finally { + this.releaseEdit(); + } + } + this.editData = editData; + } + + get enabled(): boolean { + return this.isDrawing || this.isEditing || this.isInsertion; + } + + public cancel(): void { + if (this.isDrawing || this.isInsertion) { + this.releaseDraw(); + } + + if (this.isEditing) { + this.releaseEdit(); + } + } +} diff --git a/cvat-canvas/src/typescript/master.ts b/cvat-canvas/src/typescript/master.ts index 2bdf4cf298a5..92fa053f35f6 100644 --- a/cvat-canvas/src/typescript/master.ts +++ b/cvat-canvas/src/typescript/master.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-canvas/src/typescript/mergeHandler.ts b/cvat-canvas/src/typescript/mergeHandler.ts index 530f363c7be0..c7c7005e8f08 100644 --- a/cvat-canvas/src/typescript/mergeHandler.ts +++ b/cvat-canvas/src/typescript/mergeHandler.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -24,7 +24,7 @@ export class MergeHandlerImpl implements MergeHandler { private constraints: { labelID: number; shapeType: string; - }; + } | null; private addConstraints(): void { const shape = this.statesToBeMerged[0]; @@ -103,6 +103,10 @@ export class MergeHandlerImpl implements MergeHandler { } public select(objectState: any): void { + if (objectState.shapeType === 'mask') { + // masks can not be merged + return; + } const stateIndexes = this.statesToBeMerged.map((state): number => state.clientID); const stateFrames = this.statesToBeMerged.map((state): number => state.frame); const includes = stateIndexes.indexOf(objectState.clientID); diff --git a/cvat-canvas/src/typescript/regionSelector.ts b/cvat-canvas/src/typescript/regionSelector.ts index 79bfb4d86bda..066539edd731 100644 --- a/cvat-canvas/src/typescript/regionSelector.ts +++ b/cvat-canvas/src/typescript/regionSelector.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index 3d1ea0d2a9fa..b288d56d2a7a 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -52,6 +52,9 @@ export interface DrawnState { updated: number; frame: number; label: any; + group: any; + color: string; + elements: DrawnState[] | null; } // Translate point array from the canvas coordinate system @@ -192,11 +195,13 @@ export function readPointsFromShape(shape: SVG.Shape): number[] { let points = null; if (shape.type === 'ellipse') { const [rx, ry] = [+shape.attr('rx'), +shape.attr('ry')]; - const [cx, cy] = [+shape.attr('cx'), +shape.attr('cy')]; + const [cx, cy] = [shape.cx(), shape.cy()]; points = `${cx},${cy} ${cx + rx},${cy - ry}`; } else if (shape.type === 'rect') { points = `${shape.attr('x')},${shape.attr('y')} ` + `${shape.attr('x') + shape.attr('width')},${shape.attr('y') + shape.attr('height')}`; + } else if (shape.type === 'circle') { + points = `${shape.cx()},${shape.cy()}`; } else { points = shape.attr('points'); } @@ -239,4 +244,163 @@ export function translateFromCanvas(offset: number, points: number[]): number[] return points.map((coord: number): number => coord - offset); } +export function computeWrappingBox(points: number[], margin = 0): Box & BBox { + let xtl = Number.MAX_SAFE_INTEGER; + let ytl = Number.MAX_SAFE_INTEGER; + let xbr = Number.MIN_SAFE_INTEGER; + let ybr = Number.MIN_SAFE_INTEGER; + + for (let i = 0; i < points.length; i += 2) { + const [x, y] = [points[i], points[i + 1]]; + xtl = Math.min(xtl, x); + ytl = Math.min(ytl, y); + xbr = Math.max(xbr, x); + ybr = Math.max(ybr, y); + } + + const box = { + xtl: xtl - margin, + ytl: ytl - margin, + xbr: xbr + margin, + ybr: ybr + margin, + }; + + return { + ...box, + x: box.xtl, + y: box.ytl, + width: box.xbr - box.xtl, + height: box.ybr - box.ytl, + }; +} + +export function getSkeletonEdgeCoordinates(edge: SVG.Line): { + x1: number, y1: number, x2: number, y2: number +} { + let x1 = 0; + let y1 = 0; + let x2 = 0; + let y2 = 0; + + const parent = edge.parent() as any as SVG.G; + if (parent.type !== 'g') { + throw new Error('Edge parent must be a group'); + } + + const dataNodeFrom = edge.attr('data-node-from'); + const dataNodeTo = edge.attr('data-node-to'); + const nodeFrom = parent.children() + .find((element: SVG.Element): boolean => element.attr('data-node-id') === dataNodeFrom); + const nodeTo = parent.children() + .find((element: SVG.Element): boolean => element.attr('data-node-id') === dataNodeTo); + + if (!nodeFrom || !nodeTo) { + throw new Error(`Edge's nodeFrom ${dataNodeFrom} or nodeTo ${dataNodeTo} do not to refer to any node`); + } + + x1 = nodeFrom.cx(); + y1 = nodeFrom.cy(); + x2 = nodeTo.cx(); + y2 = nodeTo.cy(); + + if (nodeFrom.hasClass('cvat_canvas_hidden') || nodeTo.hasClass('cvat_canvas_hidden')) { + edge.addClass('cvat_canvas_hidden'); + } else { + edge.removeClass('cvat_canvas_hidden'); + } + + if (nodeFrom.hasClass('cvat_canvas_shape_occluded') || nodeTo.hasClass('cvat_canvas_shape_occluded')) { + edge.addClass('cvat_canvas_shape_occluded'); + } + + if ([x1, y1, x2, y2].some((coord: number): boolean => typeof coord !== 'number')) { + throw new Error(`Edge coordinates must be numbers, got [${x1}, ${y1}, ${x2}, ${y2}]`); + } + + return { + x1, y1, x2, y2, + }; +} + +export function makeSVGFromTemplate(template: string): SVG.G { + const SVGElement = new SVG.G(); + /* eslint-disable-next-line no-unsanitized/property */ + SVGElement.node.innerHTML = template; + return SVGElement; +} + +export function setupSkeletonEdges(skeleton: SVG.G, referenceSVG: SVG.G): void { + for (const child of referenceSVG.children()) { + // search for all edges on template + const dataType = child.attr('data-type'); + if (child.type === 'line' && dataType === 'edge') { + const dataNodeFrom = child.attr('data-node-from'); + const dataNodeTo = child.attr('data-node-to'); + if (!Number.isInteger(dataNodeFrom) || !Number.isInteger(dataNodeTo)) { + throw new Error(`Edge nodeFrom and nodeTo must be numbers, got ${dataNodeFrom}, ${dataNodeTo}`); + } + + // try to find the same edge on the skeleton + let edge = skeleton.children().find((_child: SVG.Element) => ( + _child.attr('data-node-from') === dataNodeFrom && _child.attr('data-node-to') === dataNodeTo + )) as SVG.Line; + + // if not found, lets create it + if (!edge) { + edge = skeleton.line(0, 0, 0, 0).attr({ + 'data-node-from': dataNodeFrom, + 'data-node-to': dataNodeTo, + 'stroke-width': 'inherit', + }).addClass('cvat_canvas_skeleton_edge') as SVG.Line; + } + + skeleton.node.prepend(edge.node); + const points = getSkeletonEdgeCoordinates(edge); + edge.attr({ ...points, 'stroke-width': 'inherit' }); + } + } +} + +export function imageDataToDataURL( + imageBitmap: Uint8ClampedArray, + width: number, + height: number, + handleResult: (dataURL: string) => Promise, +): void { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + canvas.getContext('2d').putImageData( + new ImageData(imageBitmap, width, height), 0, 0, + ); + + canvas.toBlob((blob) => { + const dataURL = URL.createObjectURL(blob); + handleResult(dataURL).finally(() => { + URL.revokeObjectURL(dataURL); + }); + }, 'image/png'); +} + +export function alphaChannelOnly(imageData: Uint8ClampedArray): number[] { + const alpha = new Array(imageData.length / 4); + for (let i = 3; i < imageData.length; i += 4) { + alpha[Math.floor(i / 4)] = imageData[i] > 0 ? 1 : 0; + } + return alpha; +} + +export function expandChannels(r: number, g: number, b: number, alpha: number[], endOffset = 0): Uint8ClampedArray { + const imageBitmap = new Uint8ClampedArray((alpha.length - endOffset) * 4); + for (let i = 0; i < alpha.length - endOffset; i++) { + const val = alpha[i] ? 1 : 0; + imageBitmap[i * 4] = r; + imageBitmap[i * 4 + 1] = g; + imageBitmap[i * 4 + 2] = b; + imageBitmap[i * 4 + 3] = val * 255; + } + return imageBitmap; +} + export type PropType = T[Prop]; diff --git a/cvat-canvas/src/typescript/splitHandler.ts b/cvat-canvas/src/typescript/splitHandler.ts index 69b602ab3ae6..e2110ca15290 100644 --- a/cvat-canvas/src/typescript/splitHandler.ts +++ b/cvat-canvas/src/typescript/splitHandler.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -16,7 +16,7 @@ export class SplitHandlerImpl implements SplitHandler { private onSplitDone: (object: any) => void; private onFindObject: (event: MouseEvent) => void; private canvas: SVG.Container; - private highlightedShape: SVG.Shape; + private highlightedShape: SVG.Shape | null; private initialized: boolean; private splitDone: boolean; diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 4fa591d7bbf3..40af155a956f 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-canvas/src/typescript/zoomHandler.ts b/cvat-canvas/src/typescript/zoomHandler.ts index b11eb9739bc5..2e298de83f1f 100644 --- a/cvat-canvas/src/typescript/zoomHandler.ts +++ b/cvat-canvas/src/typescript/zoomHandler.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-canvas/tsconfig.json b/cvat-canvas/tsconfig.json index 700dfe36889c..eed9cbafe0d4 100644 --- a/cvat-canvas/tsconfig.json +++ b/cvat-canvas/tsconfig.json @@ -3,7 +3,7 @@ "baseUrl": ".", "emitDeclarationOnly": true, "module": "es6", - "target": "es2016", + "target": "es2019", "noImplicitAny": true, "preserveConstEnums": true, "declaration": true, diff --git a/cvat-canvas/webpack.config.js b/cvat-canvas/webpack.config.js index fe972099b498..d2e35b739bfb 100644 --- a/cvat-canvas/webpack.config.js +++ b/cvat-canvas/webpack.config.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ const path = require('path'); // eslint-disable-next-line @typescript-eslint/no-var-requires -const DtsBundleWebpack = require('dts-bundle-webpack'); +const BundleDeclarationsWebpackPlugin = require('bundle-declarations-webpack-plugin'); const styleLoaders = [ 'style-loader', @@ -64,10 +64,8 @@ const nodeConfig = { ], }, plugins: [ - new DtsBundleWebpack({ - name: 'cvat-canvas.node', - main: 'dist/declaration/src/typescript/canvas.d.ts', - out: '../cvat-canvas.node.d.ts', + new BundleDeclarationsWebpackPlugin({ + outFile: "declaration/src/cvat-canvas.d.ts", }), ], }; @@ -116,10 +114,8 @@ const webConfig = { ], }, plugins: [ - new DtsBundleWebpack({ - name: 'cvat-canvas', - main: 'dist/declaration/src/typescript/canvas.d.ts', - out: '../cvat-canvas.d.ts', + new BundleDeclarationsWebpackPlugin({ + outFile: "declaration/src/cvat-canvas.d.ts", }), ], }; diff --git a/cvat-canvas3d/.eslintrc.js b/cvat-canvas3d/.eslintrc.js index 350aa3df3f41..144b2c3032a8 100644 --- a/cvat-canvas3d/.eslintrc.js +++ b/cvat-canvas3d/.eslintrc.js @@ -1,16 +1,9 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT -const globalConfig = require('../.eslintrc.js'); - module.exports = { - env: { - node: true, - }, parserOptions: { - parser: '@typescript-eslint/parser', - ecmaVersion: 6, project: './tsconfig.json', tsconfigRootDir: __dirname, }, @@ -20,26 +13,4 @@ module.exports = { 'node_modules/**', 'dist/**', ], - plugins: ['@typescript-eslint'], - extends: ['plugin:@typescript-eslint/recommended', 'airbnb-typescript/base'], - rules: { - ...globalConfig.rules, - - '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/indent': ['error', 4], - '@typescript-eslint/lines-between-class-members': 0, - '@typescript-eslint/no-explicit-any': [0], - '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }], - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/ban-types': [ - 'error', - { - types: { - '{}': false, // TODO: try to fix with Record - object: false, // TODO: try to fix with Record - Function: false, // TODO: try to fix somehow - }, - }, - ], - }, }; diff --git a/cvat-canvas3d/README.md b/cvat-canvas3d/README.md index 17566e61635d..eea73bfed8d7 100644 --- a/cvat-canvas3d/README.md +++ b/cvat-canvas3d/README.md @@ -9,17 +9,17 @@ It presents a canvas to viewing, drawing and editing of 3D annotations. If you make changes in this package, please do following: -- After not important changes (typos, backward compatible bug fixes, refactoring) do: `npm version patch` -- After changing API (backward compatible new features) do: `npm version minor` -- After changing API (changes that break backward compatibility) do: `npm version major` +- After not important changes (typos, backward compatible bug fixes, refactoring) do: `yarn version --patch` +- After changing API (backward compatible new features) do: `yarn version --minor` +- After changing API (changes that break backward compatibility) do: `yarn version --major` ## Commands - Building of the module from sources in the `dist` directory: ```bash -npm run build -npm run build -- --mode=development # without a minification +yarn run build +yarn run build --mode=development # without a minification ``` ### API Methods diff --git a/cvat-canvas3d/package-lock.json b/cvat-canvas3d/package-lock.json deleted file mode 100644 index 3edab8dc3b69..000000000000 --- a/cvat-canvas3d/package-lock.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "cvat-canvas3d", - "version": "0.0.1", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "cvat-canvas3d", - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "@types/three": "^0.125.3", - "camera-controls": "^1.25.3", - "three": "^0.126.1" - }, - "devDependencies": {} - }, - "node_modules/@types/three": { - "version": "0.125.3", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.125.3.tgz", - "integrity": "sha512-tUPMzKooKDvMOhqcNVUPwkt+JNnF8ASgWSsrLgleVd0SjLj4boJhteSsF9f6YDjye0mmUjO+BDMWW83F97ehXA==" - }, - "node_modules/camera-controls": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-1.33.0.tgz", - "integrity": "sha512-QTXwz/XbLCPGf7l6u9cWKfR3WwKulnNAahfg+RE+dFOAQ40KKvwTIvBs3Q29kqntJlKvY79ZVsmPUEUA6LoF2A==", - "peerDependencies": { - "three": ">=0.126.1" - } - }, - "node_modules/three": { - "version": "0.126.1", - "resolved": "https://registry.npmjs.org/three/-/three-0.126.1.tgz", - "integrity": "sha512-eOEXnZeE1FDV0XgL1u08auIP13jxdN9LQBAEmlErYzMxtIIfuGIAZbijOyookALUhqVzVOx0Tywj6n192VM+nQ==" - } - }, - "dependencies": { - "@types/three": { - "version": "0.125.3", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.125.3.tgz", - "integrity": "sha512-tUPMzKooKDvMOhqcNVUPwkt+JNnF8ASgWSsrLgleVd0SjLj4boJhteSsF9f6YDjye0mmUjO+BDMWW83F97ehXA==" - }, - "camera-controls": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-1.33.0.tgz", - "integrity": "sha512-QTXwz/XbLCPGf7l6u9cWKfR3WwKulnNAahfg+RE+dFOAQ40KKvwTIvBs3Q29kqntJlKvY79ZVsmPUEUA6LoF2A==", - "requires": {} - }, - "three": { - "version": "0.126.1", - "resolved": "https://registry.npmjs.org/three/-/three-0.126.1.tgz", - "integrity": "sha512-eOEXnZeE1FDV0XgL1u08auIP13jxdN9LQBAEmlErYzMxtIIfuGIAZbijOyookALUhqVzVOx0Tywj6n192VM+nQ==" - } - } -} diff --git a/cvat-canvas3d/package.json b/cvat-canvas3d/package.json index e3ab9102ee92..f8c1ea524157 100644 --- a/cvat-canvas3d/package.json +++ b/cvat-canvas3d/package.json @@ -1,13 +1,13 @@ { "name": "cvat-canvas3d", - "version": "0.0.1", + "version": "0.0.6", "description": "Part of Computer Vision Annotation Tool which presents its canvas3D library", "main": "src/canvas3d.ts", "scripts": { "build": "tsc && webpack --config ./webpack.config.js", "server": "nodemon --watch config --exec 'webpack-dev-server --config ./webpack.config.js --mode=development --open'" }, - "author": "Intel", + "author": "CVAT.ai", "license": "MIT", "browserslist": [ "Chrome >= 63", @@ -17,6 +17,7 @@ ], "devDependencies": {}, "dependencies": { + "cvat-core": "link:./../cvat-core", "@types/three": "^0.125.3", "camera-controls": "^1.25.3", "three": "^0.126.1" diff --git a/cvat-canvas3d/src/typescript/canvas3d.ts b/cvat-canvas3d/src/typescript/canvas3d.ts index ef6e1ff131bc..6333e23db754 100644 --- a/cvat-canvas3d/src/typescript/canvas3d.ts +++ b/cvat-canvas3d/src/typescript/canvas3d.ts @@ -1,4 +1,5 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -13,6 +14,7 @@ import { MouseInteraction, ShapeProperties, GroupData, + Configuration, } from './canvas3dModel'; import { Canvas3dView, Canvas3dViewImpl, ViewsDOM, CameraAction, @@ -94,8 +96,12 @@ class Canvas3dImpl implements Canvas3d { this.model.configureShapes(shapeProperties); } + public configure(configuration: Configuration): void { + this.model.configure(configuration); + } + public activate(clientID: number | null, attributeID: number | null = null): void { - this.model.activate(String(clientID), attributeID); + this.model.activate(typeof clientID === 'number' ? String(clientID) : null, attributeID); } public fit(): void { @@ -112,5 +118,7 @@ class Canvas3dImpl implements Canvas3d { } export { - Canvas3dImpl as Canvas3d, Canvas3dVersion, ViewType, MouseInteraction, CameraAction, ViewsDOM, + Canvas3dImpl as Canvas3d, Canvas3dVersion, ViewType, MouseInteraction, CameraAction, Mode as CanvasMode, }; + +export type { ViewsDOM }; diff --git a/cvat-canvas3d/src/typescript/canvas3dController.ts b/cvat-canvas3d/src/typescript/canvas3dController.ts index 00b08f6c3a87..404b8baaa518 100644 --- a/cvat-canvas3d/src/typescript/canvas3dController.ts +++ b/cvat-canvas3d/src/typescript/canvas3dController.ts @@ -1,17 +1,20 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT +import { ObjectState } from '.'; import { - Canvas3dModel, Mode, DrawData, ActiveElement, FocusData, GroupData, + Canvas3dModel, Mode, DrawData, ActiveElement, GroupData, Configuration, } from './canvas3dModel'; export interface Canvas3dController { readonly drawData: DrawData; readonly activeElement: ActiveElement; - readonly selected: any; - readonly focused: FocusData; readonly groupData: GroupData; + readonly configuration: Configuration; + readonly imageIsDeleted: boolean; + readonly objects: ObjectState[]; mode: Mode; group(groupData: GroupData): void; } @@ -39,18 +42,22 @@ export class Canvas3dControllerImpl implements Canvas3dController { return this.model.data.activeElement; } - public get selected(): any { - return this.model.data.selected; - } - - public get focused(): any { - return this.model.data.focusData; + public get imageIsDeleted(): any { + return this.model.imageIsDeleted; } public get groupData(): GroupData { return this.model.groupData; } + public get configuration(): Configuration { + return this.model.configuration; + } + + public get objects(): ObjectState[] { + return this.model.objects; + } + public group(groupData: GroupData): void { this.model.group(groupData); } diff --git a/cvat-canvas3d/src/typescript/canvas3dModel.ts b/cvat-canvas3d/src/typescript/canvas3dModel.ts index 928e16c4d4e3..c87feb6bf1ef 100644 --- a/cvat-canvas3d/src/typescript/canvas3dModel.ts +++ b/cvat-canvas3d/src/typescript/canvas3dModel.ts @@ -1,7 +1,9 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT +import { ObjectState } from '.'; import { MasterImpl } from './master'; export interface Size { @@ -16,7 +18,11 @@ export interface ActiveElement { export interface GroupData { enabled: boolean; - grouped?: []; + grouped: ObjectState[]; +} + +export interface Configuration { + resetZoom?: boolean; } export interface Image { @@ -57,10 +63,6 @@ export enum MouseInteraction { HOVER = 'hover', } -export interface FocusData { - clientID: string | null; -} - export interface ShapeProperties { opacity: number; outlined: boolean; @@ -75,23 +77,20 @@ export enum UpdateReasons { DRAW = 'draw', SELECT = 'select', CANCEL = 'cancel', - DATA_FAILED = 'data_failed', DRAG_CANVAS = 'drag_canvas', SHAPE_ACTIVATED = 'shape_activated', GROUP = 'group', FITTED_CANVAS = 'fitted_canvas', + CONFIG_UPDATED = 'config_updated', + SHAPES_CONFIG_UPDATED = 'shapes_config_updated', } export enum Mode { IDLE = 'idle', - DRAG = 'drag', - RESIZE = 'resize', DRAW = 'draw', EDIT = 'edit', - INTERACT = 'interact', DRAG_CANVAS = 'drag_canvas', GROUP = 'group', - BUSY = 'busy', } export interface Canvas3dDataModel { @@ -101,32 +100,40 @@ export interface Canvas3dDataModel { imageID: number | null; imageOffset: number; imageSize: Size; + imageIsDeleted: boolean; drawData: DrawData; mode: Mode; - objectUpdating: boolean; - exception: Error | null; - objects: any[]; - groupedObjects: any[]; - focusData: FocusData; - selected: any; + objects: ObjectState[]; shapeProperties: ShapeProperties; groupData: GroupData; + configuration: Configuration; + isFrameUpdating: boolean; + nextSetupRequest: { + frameData: any; + objectStates: ObjectState[]; + } | null; } export interface Canvas3dModel { mode: Mode; data: Canvas3dDataModel; + readonly imageIsDeleted: boolean; readonly groupData: GroupData; - setup(frameData: any, objectStates: any[]): void; + readonly configuration: Configuration; + readonly objects: ObjectState[]; + setup(frameData: any, objectStates: ObjectState[]): void; isAbleToChangeFrame(): boolean; draw(drawData: DrawData): void; cancel(): void; dragCanvas(enable: boolean): void; activate(clientID: string | null, attributeID: number | null): void; - configureShapes(shapeProperties: any): void; + configureShapes(shapeProperties: ShapeProperties): void; + configure(configuration: Configuration): void; fit(): void; group(groupData: GroupData): void; destroy(): void; + updateCanvasObjects(): void; + unlockFrameUpdating(): void; } export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { @@ -143,9 +150,7 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { height: 0, width: 0, }, - objectUpdating: false, objects: [], - groupedObjects: [], image: null, imageID: null, imageOffset: 0, @@ -153,20 +158,16 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { height: 0, width: 0, }, + imageIsDeleted: false, drawData: { enabled: false, initialState: null, }, mode: Mode.IDLE, - exception: null, - focusData: { - clientID: null, - }, groupData: { enabled: false, grouped: [], }, - selected: null, shapeProperties: { opacity: 40, outlined: false, @@ -174,29 +175,51 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { selectedOpacity: 60, colorBy: 'Label', }, + configuration: { + resetZoom: false, + }, + isFrameUpdating: false, + nextSetupRequest: null, }; } - public setup(frameData: any, objectStates: any[]): void { + public updateCanvasObjects(): void { + this.notify(UpdateReasons.OBJECTS_UPDATED); + } + + public unlockFrameUpdating(): void { + this.data.isFrameUpdating = false; + if (this.data.nextSetupRequest) { + try { + const { frameData, objectStates } = this.data.nextSetupRequest; + this.setup(frameData, objectStates); + } finally { + this.data.nextSetupRequest = null; + } + } + } + + public setup(frameData: any, objectStates: ObjectState[]): void { if (this.data.imageID !== frameData.number) { - if ([Mode.EDIT, Mode.DRAG, Mode.RESIZE].includes(this.data.mode)) { + if ([Mode.EDIT].includes(this.data.mode)) { throw Error(`Canvas is busy. Action: ${this.data.mode}`); } } - if ([Mode.EDIT, Mode.BUSY].includes(this.data.mode)) { + + if (this.data.isFrameUpdating) { + this.data.nextSetupRequest = { + frameData, objectStates, + }; return; } - if (frameData.number === this.data.imageID) { - if (this.data.objectUpdating) { - return; - } + if (frameData.number === this.data.imageID && frameData.deleted === this.data.imageIsDeleted) { this.data.objects = objectStates; - this.data.objectUpdating = true; this.notify(UpdateReasons.OBJECTS_UPDATED); return; } + this.data.isFrameUpdating = true; this.data.imageID = frameData.number; frameData .data((): void => { @@ -204,24 +227,17 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { this.notify(UpdateReasons.IMAGE_CHANGED); }) .then((data: Image): void => { - if (frameData.number !== this.data.imageID) { - // already another image - return; - } - this.data.imageSize = { height: frameData.height as number, width: frameData.width as number, }; - + this.data.imageIsDeleted = frameData.deleted; this.data.image = data; - this.notify(UpdateReasons.IMAGE_CHANGED); this.data.objects = objectStates; - this.notify(UpdateReasons.OBJECTS_UPDATED); + this.notify(UpdateReasons.IMAGE_CHANGED); }) .catch((exception: any): void => { - this.data.exception = exception; - this.notify(UpdateReasons.DATA_FAILED); + this.data.isFrameUpdating = false; throw exception; }); } @@ -234,9 +250,13 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { return this.data.mode; } + public get objects(): ObjectState[] { + return [...this.data.objects]; + } + public isAbleToChangeFrame(): boolean { - const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT, Mode.BUSY].includes(this.data.mode) || - (this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number'); + const isUnable = [Mode.EDIT].includes(this.data.mode) || + this.data.isFrameUpdating || (this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number'); return !isUnable; } @@ -287,11 +307,11 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { this.notify(UpdateReasons.DRAG_CANVAS); } - public activate(clientID: string, attributeID: number | null): void { + public activate(clientID: string | null, attributeID: number | null): void { if (this.data.activeElement.clientID === clientID && this.data.activeElement.attributeID === attributeID) { return; } - if (this.data.mode !== Mode.IDLE) { + if (this.data.mode !== Mode.IDLE && clientID !== null) { throw Error(`Canvas is busy. Action: ${this.data.mode}`); } if (typeof clientID === 'number') { @@ -324,23 +344,53 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { this.notify(UpdateReasons.GROUP); } + public configure(configuration: Configuration): void { + if (typeof configuration.resetZoom === 'boolean') { + this.data.configuration.resetZoom = configuration.resetZoom; + } + + this.notify(UpdateReasons.CONFIG_UPDATED); + } + public configureShapes(shapeProperties: ShapeProperties): void { - this.data.drawData.enabled = false; - this.data.mode = Mode.IDLE; - this.cancel(); - this.data.shapeProperties = { - ...shapeProperties, - }; - this.notify(UpdateReasons.OBJECTS_UPDATED); + if (typeof shapeProperties.opacity === 'number') { + this.data.shapeProperties.opacity = Math.max(0, Math.min(shapeProperties.opacity, 100)); + } + + if (typeof shapeProperties.selectedOpacity === 'number') { + this.data.shapeProperties.selectedOpacity = Math.max(0, Math.min(shapeProperties.selectedOpacity, 100)); + } + + if (['Label', 'Instance', 'Group'].includes(shapeProperties.colorBy)) { + this.data.shapeProperties.colorBy = shapeProperties.colorBy; + } + + if (typeof shapeProperties.outlined === 'boolean') { + this.data.shapeProperties.outlined = shapeProperties.outlined; + } + + if (typeof shapeProperties.outlineColor === 'string') { + this.data.shapeProperties.outlineColor = shapeProperties.outlineColor; + } + + this.notify(UpdateReasons.SHAPES_CONFIG_UPDATED); } public fit(): void { this.notify(UpdateReasons.FITTED_CANVAS); } + public get configuration(): Configuration { + return { ...this.data.configuration }; + } + public get groupData(): GroupData { return { ...this.data.groupData }; } + public get imageIsDeleted(): boolean { + return this.data.imageIsDeleted; + } + public destroy(): void {} } diff --git a/cvat-canvas3d/src/typescript/canvas3dView.ts b/cvat-canvas3d/src/typescript/canvas3dView.ts index 229d22c3de22..8310bc7bd94a 100644 --- a/cvat-canvas3d/src/typescript/canvas3dView.ts +++ b/cvat-canvas3d/src/typescript/canvas3dView.ts @@ -1,4 +1,5 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -12,8 +13,11 @@ import { Canvas3dModel, DrawData, Mode, Planes, UpdateReasons, ViewType, } from './canvas3dModel'; import { - createRotationHelper, CuboidModel, setEdges, setTranslationHelper, + createRotationHelper, removeRotationHelper, + createResizeHelper, removeResizeHelper, + createCuboidEdges, removeCuboidEdges, CuboidModel, makeCornerPointsMatrix, } from './cuboid'; +import { ObjectState } from '.'; export interface Canvas3dView { html(): ViewsDOM; @@ -34,38 +38,65 @@ export enum CameraAction { ROTATE_LEFT = 'ArrowLeft', } -export interface RayCast { - renderer: THREE.Raycaster; - mouseVector: THREE.Vector2; -} - -export interface Views { - perspective: RenderView; - top: RenderView; - side: RenderView; - front: RenderView; -} +export type Views = { + [key in ViewType]: RenderView; +}; -export interface CubeObject { - perspective: THREE.Mesh; - top: THREE.Mesh; - side: THREE.Mesh; - front: THREE.Mesh; -} +export type ViewsDOM = { + [key in ViewType]: HTMLCanvasElement; +}; export interface RenderView { renderer: THREE.WebGLRenderer; scene: THREE.Scene; camera?: THREE.PerspectiveCamera | THREE.OrthographicCamera; controls?: CameraControls; - rayCaster?: RayCast; + rayCaster?: { + renderer: THREE.Raycaster; + mouseVector: THREE.Vector2; + }; } -export interface ViewsDOM { - perspective: HTMLCanvasElement; - top: HTMLCanvasElement; - side: HTMLCanvasElement; - front: HTMLCanvasElement; +interface DrawnObjectData { + clientID: number; + labelID: number; + labelColor: string; + points: number[]; + groupID: number | null; + groupColor: string; + color: string; + occluded: boolean; + outside: boolean; + hidden: boolean; + pinned: boolean; + lock: boolean; + updated: number; +} + +const BOTTOM_VIEWS = [ + ViewType.TOP, + ViewType.SIDE, + ViewType.FRONT, +]; + +const ALL_VIEWS = [...BOTTOM_VIEWS, ViewType.PERSPECTIVE]; + +function drawnDataFromState(state: ObjectState): DrawnObjectData { + return { + clientID: state.clientID, + labelID: state.label.id, + labelColor: state.label.color, + groupID: state.group?.id || null, + groupColor: state.group?.color || '#ffffff', + points: [...state.points], + color: state.color, + hidden: state.hidden, + lock: state.lock, + occluded: state.occluded, + outside: state.outside, + pinned: state.pinned, + updated: state.updated, + }; } export class Canvas3dViewImpl implements Canvas3dView, Listener { @@ -74,11 +105,44 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { private clock: THREE.Clock; private speed: number; private cube: CuboidModel; - private highlighted: boolean; - private selected: CubeObject; + private isPerspectiveBeingDragged: boolean; + private activatedElementID: number | null; + private drawnObjects: Record; private model: Canvas3dModel & Master; - private action: any; - private globalHelpers: any; + private action: { + translation: any; + resize: { + status: boolean; + previousPosition: null | THREE.Vector3; + helperElement: THREE.Object3D; + }; + scan: any; + rotation: any; + frameCoordinates: any; + detected: any; + initialMouseVector: any; + detachCam: any; + detachCamRef: any; + }; + private cameraSettings: { + [key in ViewType]: { + position: [number, number, number], + lookAt: [number, number, number], + up: [number, number, number], + } + }; + + private get selectedCuboid(): CuboidModel | null { + const { clientID } = this.model.data.activeElement; + if (clientID !== null) { + return this.drawnObjects[+clientID].cuboid || null; + } + + return null; + } private set mode(value: Mode) { this.controller.mode = value; @@ -93,28 +157,35 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.clock = new THREE.Clock(); this.speed = CONST.MOVEMENT_FACTOR; this.cube = new CuboidModel('line', '#ffffff'); - this.highlighted = false; - this.selected = this.cube; + this.isPerspectiveBeingDragged = false; + this.activatedElementID = null; + this.drawnObjects = {}; this.model = model; - this.globalHelpers = { + this.cameraSettings = { + perspective: { + position: [-15, 0, 8], + lookAt: [10, 0, 0], + up: [0, 0, 1], + }, top: { - resize: [], - rotate: [], + position: [0, 0, 8], + lookAt: [0, 0, 0], + up: [0, 0, 1], }, side: { - resize: [], - rotate: [], + position: [0, -8, 0], + lookAt: [0, 0, 0], + up: [0, 0, 1], }, front: { - resize: [], - rotate: [], + position: [8, 0, 0], + lookAt: [0, 0, 0], + up: [0, 0, 1], }, }; + this.action = { - loading: false, - oldState: '', scan: null, - selectable: true, frameCoordinates: { x: 0, y: 0, @@ -146,20 +217,8 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { }, resize: { status: false, - helper: null, - recentMouseVector: new THREE.Vector2(0, 0), - initScales: { - x: 1, - y: 1, - z: 1, - }, - memScales: { - x: 1, - y: 1, - z: 1, - }, - resizeVector: new THREE.Vector3(0, 0, 0), - frontBool: false, + helperElement: null, + previousPosition: null, }, }; @@ -205,13 +264,13 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { const canvasFrontView = this.views.front.renderer.domElement; canvasPerspectiveView.addEventListener('contextmenu', (e: MouseEvent): void => { - if (this.controller.focused.clientID !== null) { + if (this.model.data.activeElement.clientID !== null) { this.dispatchEvent( new CustomEvent('canvas.contextmenu', { bubbles: false, cancelable: true, detail: { - clientID: Number(this.controller.focused.clientID), + clientID: Number(this.model.data.activeElement.clientID), clientX: e.clientX, clientY: e.clientY, }, @@ -224,28 +283,33 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { const { x: rotationX, y: rotationY, z: rotationZ } = this.cube.perspective.rotation; const points = [x, y, z, rotationX, rotationY, rotationZ, width, height, depth, 0, 0, 0, 0, 0, 0, 0]; const initState = this.model.data.drawData.initialState; - let label; - if (initState) { - ({ label } = initState); - } this.dispatchEvent( new CustomEvent('canvas.drawn', { bubbles: false, cancelable: true, detail: { state: { - ...initState, shapeType: 'cuboid', frame: this.model.data.imageID, points, - label, + attributes: { ...initState.attributes }, + group: initState.group?.id || null, + label: initState.label, }, continue: true, duration: 0, }, }), ); - this.action.oldState = Mode.DRAW; + } + }); + + canvasPerspectiveView.addEventListener('mousedown', this.onPerspectiveDrag); + window.document.addEventListener('mouseup', () => { + this.disablePerspectiveDragging(); + if (this.isPerspectiveBeingDragged && this.mode !== Mode.DRAG_CANVAS) { + // call this body only of drag was activated inside the canvas, but not globally + this.isPerspectiveBeingDragged = false; } }); @@ -276,43 +340,43 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { canvasPerspectiveView.addEventListener('click', (e: MouseEvent): void => { e.preventDefault(); - if (e.detail !== 1) return; - if (![Mode.GROUP, Mode.IDLE].includes(this.mode) || !this.views.perspective.rayCaster) return; - const intersects = this.views.perspective.rayCaster.renderer.intersectObjects( - this.views.perspective.scene.children[0].children, - false, - ); - if (intersects.length !== 0 && this.mode === Mode.GROUP && this.model.data.groupData.grouped) { - const item = this.model.data.groupData.grouped.filter( - (_state: any): boolean => _state.clientID === Number(intersects[0].object.name), - ); - if (item.length !== 0) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this.model.data.groupData.grouped = this.model.data.groupData.grouped.filter( - (_state: any): boolean => _state.clientID !== Number(intersects[0].object.name), - ); - intersects[0].object.material.color.set(intersects[0].object.originalColor); + const selectionIsBlocked = ![Mode.GROUP, Mode.IDLE].includes(this.mode) || + !this.views.perspective.rayCaster || + this.isPerspectiveBeingDragged; + + if (e.detail !== 1 || selectionIsBlocked) return; + const intersects = this.views.perspective.rayCaster.renderer + .intersectObjects(this.getAllVisibleCuboids(), false); + const intersectionClientID = +(intersects[0]?.object?.name) || null; + const objectState = Number.isInteger(intersectionClientID) ? this.model.objects + .find((state: ObjectState) => state.clientID === intersectionClientID) : null; + if ( + objectState && + this.mode === Mode.GROUP && + this.model.data.groupData.grouped + ) { + const objectStateIdx = this.model.data.groupData.grouped + .findIndex((state: ObjectState) => state.clientID === intersectionClientID); + if (objectStateIdx !== -1) { + this.model.data.groupData.grouped.splice(objectStateIdx, 1); } else { - const [state] = this.model.data.objects.filter( - (_state: any): boolean => _state.clientID === Number(intersects[0].object.name), - ); - this.model.data.groupData.grouped.push(state); - intersects[0].object.material.color.set('#ffffff'); + this.model.data.groupData.grouped.push(objectState); } + + this.drawnObjects[intersectionClientID].cuboid.setColor(this.receiveShapeColor(objectState)); } else if (this.mode === Mode.IDLE) { - if (intersects.length === 0) { - this.setHelperVisibility(false); + const intersectedClientID = intersects[0]?.object?.name || null; + if (this.model.data.activeElement.clientID !== intersectedClientID) { + this.dispatchEvent( + new CustomEvent('canvas.selected', { + bubbles: false, + cancelable: true, + detail: { + clientID: typeof intersectedClientID === 'string' ? +intersectedClientID : null, + }, + }), + ); } - this.dispatchEvent( - new CustomEvent('canvas.selected', { - bubbles: false, - cancelable: true, - detail: { - clientID: intersects.length !== 0 ? Number(intersects[0].object.name) : null, - }, - }), - ); } }); @@ -321,18 +385,14 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { if (this.mode !== Mode.DRAW) { const { perspective: viewType } = this.views; viewType.rayCaster.renderer.setFromCamera(viewType.rayCaster.mouseVector, viewType.camera); - const intersects = viewType.rayCaster.renderer.intersectObjects( - this.views.perspective.scene.children[0].children, - false, - ); - if (intersects.length !== 0 || this.controller.focused.clientID !== null) { - this.setDefaultZoom(); - } else { + const intersects = viewType.rayCaster.renderer.intersectObjects(this.getAllVisibleCuboids(), false); + if (!intersects.length) { const { x, y, z } = this.action.frameCoordinates; this.positionAllViews(x, y, z, true); } return; } + this.controller.drawData.enabled = false; this.mode = Mode.IDLE; const { x, y, z } = this.cube.perspective.position; @@ -340,15 +400,13 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { const { x: rotationX, y: rotationY, z: rotationZ } = this.cube.perspective.rotation; const points = [x, y, z, rotationX, rotationY, rotationZ, width, height, depth, 0, 0, 0, 0, 0, 0, 0]; const initState = this.model.data.drawData.initialState; - let label; - if (initState) { - ({ label } = initState); - } + const { redraw } = this.model.data.drawData; + if (typeof redraw === 'number') { + const state = this.model.objects + .find((object: ObjectState): boolean => object.clientID === redraw); + const { cuboid } = this.drawnObjects[redraw]; + cuboid.perspective.visible = true; - if (typeof this.model.data.drawData.redraw === 'number') { - const [state] = this.model.data.objects.filter( - (_state: any): boolean => _state.clientID === Number(this.model.data.selected.perspective.name), - ); this.dispatchEvent( new CustomEvent('canvas.edited', { bubbles: false, @@ -366,18 +424,23 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { cancelable: true, detail: { state: { - ...initState, shapeType: 'cuboid', frame: this.model.data.imageID, points, - label, + ...(initState ? { + attributes: { ...initState.attributes }, + group: initState.group?.id || null, + label: initState.label, + shapeType: initState.shapeType, + } : {}), }, - continue: undefined, duration: 0, }, }), ); } + + this.views[ViewType.PERSPECTIVE].scene.children[0].remove(this.cube.perspective); this.dispatchEvent(new CustomEvent('canvas.canceled')); }); @@ -394,11 +457,6 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { // setting up the camera and adding it in the scene this.views.perspective.camera = new THREE.PerspectiveCamera(50, aspectRatio, 1, 500); - this.views.perspective.camera.position.set(-15, 0, 4); - this.views.perspective.camera.up.set(0, 0, 1); - this.views.perspective.camera.lookAt(10, 0, 0); - this.views.perspective.camera.name = 'cameraPerspective'; - this.views.top.camera = new THREE.OrthographicCamera( (-aspectRatio * viewSize) / 2 - 2, (aspectRatio * viewSize) / 2 + 2, @@ -407,12 +465,6 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { -50, 50, ); - - this.views.top.camera.position.set(0, 0, 5); - this.views.top.camera.lookAt(0, 0, 0); - this.views.top.camera.up.set(0, 0, 1); - this.views.top.camera.name = 'cameraTop'; - this.views.side.camera = new THREE.OrthographicCamera( (-aspectRatio * viewSize) / 2, (aspectRatio * viewSize) / 2, @@ -421,11 +473,6 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { -50, 50, ); - this.views.side.camera.position.set(0, 5, 0); - this.views.side.camera.lookAt(0, 0, 0); - this.views.side.camera.up.set(0, 0, 1); - this.views.side.camera.name = 'cameraSide'; - this.views.front.camera = new THREE.OrthographicCamera( (-aspectRatio * viewSize) / 2, (aspectRatio * viewSize) / 2, @@ -434,10 +481,13 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { -50, 50, ); - this.views.front.camera.position.set(3, 0, 0); - this.views.front.camera.up.set(0, 0, 1); - this.views.front.camera.lookAt(0, 0, 0); - this.views.front.camera.name = 'cameraFront'; + + for (const cameraType of ALL_VIEWS) { + this.views[cameraType].camera.position.set(...this.cameraSettings[cameraType].position); + this.views[cameraType].camera.up.set(...this.cameraSettings[cameraType].up); + this.views[cameraType].camera.lookAt(...this.cameraSettings[cameraType].lookAt); + this.views[cameraType].camera.name = `camera${cameraType[0].toUpperCase()}${cameraType.slice(1)}`; + } Object.keys(this.views).forEach((view: string): void => { const viewType = this.views[view as keyof Views]; @@ -451,7 +501,6 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { viewType.controls = new CameraControls(viewType.camera, viewType.renderer.domElement); viewType.controls.mouseButtons.left = CameraControls.ACTION.NONE; viewType.controls.mouseButtons.right = CameraControls.ACTION.NONE; - viewType.controls.mouseButtons.wheel = CameraControls.ACTION.NONE; viewType.controls.touches.one = CameraControls.ACTION.NONE; viewType.controls.touches.two = CameraControls.ACTION.NONE; viewType.controls.touches.three = CameraControls.ACTION.NONE; @@ -464,7 +513,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.views.side.controls.enabled = false; this.views.front.controls.enabled = false; - [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view: ViewType): void => { + BOTTOM_VIEWS.forEach((view: ViewType): void => { this.views[view].renderer.domElement.addEventListener( 'wheel', (event: WheelEvent): void => { @@ -475,7 +524,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { } else if (event.deltaY > CONST.FOV_MIN && camera.zoom > CONST.FOV_MIN + 0.1) { camera.zoom -= CONST.FOV_INC; } - this.setHelperSize(view); + this.updateHelperPointsSize(view); }, { passive: false }, ); @@ -484,8 +533,13 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { model.subscribe(this); } + private getAllVisibleCuboids(view: ViewType = ViewType.PERSPECTIVE): THREE.Mesh[] { + return Object.values(this.drawnObjects) + .map(({ cuboid }) => cuboid[view]).filter((mesh: THREE.Mesh) => mesh.visible); + } + private setDefaultZoom(): void { - if (this.model.data.activeElement === 'null') { + if (this.model.data.activeElement === null) { Object.keys(this.views).forEach((view: string): void => { const viewType = this.views[view as keyof Views]; if (view !== ViewType.PERSPECTIVE) { @@ -495,7 +549,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { }); } else { const canvasTop = this.views.top.renderer.domElement; - const bboxtop = new THREE.Box3().setFromObject(this.model.data.selected.top); + const bboxtop = new THREE.Box3().setFromObject(this.selectedCuboid.top); const x1 = Math.min( canvasTop.offsetWidth / (bboxtop.max.x - bboxtop.min.x), canvasTop.offsetHeight / (bboxtop.max.y - bboxtop.min.y), @@ -503,10 +557,10 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.views.top.camera.zoom = x1 / 100; this.views.top.camera.updateProjectionMatrix(); this.views.top.camera.updateMatrix(); - this.setHelperSize(ViewType.TOP); + this.updateHelperPointsSize(ViewType.TOP); const canvasFront = this.views.top.renderer.domElement; - const bboxfront = new THREE.Box3().setFromObject(this.model.data.selected.front); + const bboxfront = new THREE.Box3().setFromObject(this.selectedCuboid.front); const x2 = Math.min( canvasFront.offsetWidth / (bboxfront.max.y - bboxfront.min.y), canvasFront.offsetHeight / (bboxfront.max.z - bboxfront.min.z), @@ -514,10 +568,10 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.views.front.camera.zoom = x2 / 100; this.views.front.camera.updateProjectionMatrix(); this.views.front.camera.updateMatrix(); - this.setHelperSize(ViewType.FRONT); + this.updateHelperPointsSize(ViewType.FRONT); const canvasSide = this.views.side.renderer.domElement; - const bboxside = new THREE.Box3().setFromObject(this.model.data.selected.side); + const bboxside = new THREE.Box3().setFromObject(this.selectedCuboid.side); const x3 = Math.min( canvasSide.offsetWidth / (bboxside.max.x - bboxside.min.x), canvasSide.offsetHeight / (bboxside.max.z - bboxside.min.z), @@ -525,15 +579,40 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.views.side.camera.zoom = x3 / 100; this.views.side.camera.updateProjectionMatrix(); this.views.side.camera.updateMatrix(); - this.setHelperSize(ViewType.SIDE); + this.updateHelperPointsSize(ViewType.SIDE); } } + private enablePerspectiveDragging(): void { + const { controls } = this.views.perspective; + controls.mouseButtons.left = CameraControls.ACTION.ROTATE; + controls.mouseButtons.right = CameraControls.ACTION.TRUCK; + controls.touches.one = CameraControls.ACTION.TOUCH_ROTATE; + controls.touches.two = CameraControls.ACTION.TOUCH_DOLLY_TRUCK; + controls.touches.three = CameraControls.ACTION.TOUCH_TRUCK; + } + + private disablePerspectiveDragging(): void { + const { controls } = this.views.perspective; + controls.mouseButtons.left = CameraControls.ACTION.NONE; + controls.mouseButtons.right = CameraControls.ACTION.NONE; + controls.touches.one = CameraControls.ACTION.NONE; + controls.touches.two = CameraControls.ACTION.NONE; + controls.touches.three = CameraControls.ACTION.NONE; + } + + private onPerspectiveDrag = (): void => { + if (![Mode.DRAG_CANVAS, Mode.IDLE].includes(this.mode)) return; + this.isPerspectiveBeingDragged = true; + this.enablePerspectiveDragging(); + } + private startAction(view: any, event: MouseEvent): void { - if (event.detail !== 1) return; - if (this.model.mode === Mode.DRAG_CANVAS) return; const { clientID } = this.model.data.activeElement; - if (clientID === 'null') return; + if (event.detail !== 1 || this.mode !== Mode.IDLE || clientID === null || !(clientID in this.drawnObjects)) { + return; + } + const canvas = this.views[view as keyof Views].renderer.domElement; const rect = canvas.getBoundingClientRect(); const { mouseVector } = this.views[view as keyof Views].rayCaster as { mouseVector: THREE.Vector2 }; @@ -543,22 +622,21 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { mouseVector.y = -(diffY / canvas.clientHeight) * 2 + 1; this.action.rotation.screenInit = { x: diffX, y: diffY }; this.action.rotation.screenMove = { x: diffX, y: diffY }; - if ( - this.model.data.selected && - !this.model.data.selected.perspective.userData.lock && - !this.model.data.selected.perspective.userData.hidden - ) { + const { data } = this.drawnObjects[+clientID]; + + if (!data.lock) { this.action.scan = view; this.model.mode = Mode.EDIT; - this.action.selectable = false; } } private moveAction(view: any, event: MouseEvent): void { event.preventDefault(); - if (this.model.mode === Mode.DRAG_CANVAS) return; const { clientID } = this.model.data.activeElement; - if (clientID === 'null') return; + if (this.model.mode === Mode.DRAG_CANVAS || clientID === null) { + return; + } + const canvas = this.views[view as keyof Views].renderer.domElement; const rect = canvas.getBoundingClientRect(); const { mouseVector } = this.views[view as keyof Views].rayCaster as { mouseVector: THREE.Vector2 }; @@ -607,28 +685,27 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { resize: { ...this.action.resize, status: false, - helper: null, - recentMouseVector: new THREE.Vector2(0, 0), + helperElement: null, + previousPosition: null, }, }; this.model.mode = Mode.IDLE; - this.action.selectable = true; } private completeActions(): void { const { scan, detected } = this.action; - if (this.model.mode === Mode.DRAG_CANVAS) return; + if (this.model.data.activeElement.clientID === null) return; if (!detected) { this.resetActions(); return; } - const { x, y, z } = this.model.data.selected[scan].position; - const { x: width, y: height, z: depth } = this.model.data.selected[scan].scale; - const { x: rotationX, y: rotationY, z: rotationZ } = this.model.data.selected[scan].rotation; + const { x, y, z } = this.selectedCuboid[scan].position; + const { x: width, y: height, z: depth } = this.selectedCuboid[scan].scale; + const { x: rotationX, y: rotationY, z: rotationZ } = this.selectedCuboid[scan].rotation; const points = [x, y, z, rotationX, rotationY, rotationZ, width, height, depth, 0, 0, 0, 0, 0, 0, 0]; - const [state] = this.model.data.objects.filter( - (_state: any): boolean => _state.clientID === Number(this.model.data.selected[scan].name), + const [state] = this.model.objects.filter( + (_state: any): boolean => _state.clientID === Number(this.selectedCuboid[scan].name), ); this.dispatchEvent( new CustomEvent('canvas.edited', { @@ -640,11 +717,8 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { }, }), ); - if (this.action.rotation.status) { - this.detachCamera(scan); - } - this.adjustPerspectiveCameras(); + // this.adjustPerspectiveCameras(); this.translateReferencePlane(new THREE.Vector3(x, y, z)); this.resetActions(); } @@ -677,92 +751,185 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.mode = Mode.IDLE; } - private setupObject(object: any, addToScene: boolean): CuboidModel { - const { - opacity, outlined, outlineColor, selectedOpacity, colorBy, - } = this.model.data.shapeProperties; - const clientID = String(object.clientID); - const cuboid = new CuboidModel(object.occluded ? 'dashed' : 'line', outlined ? outlineColor : '#ffffff'); + private receiveShapeColor(state: ObjectState | DrawnObjectData): string { + const { colorBy } = this.model.data.shapeProperties; - cuboid.setName(clientID); - cuboid.perspective.userData = object; - let color = ''; - if (colorBy === 'Label') { - ({ color } = object.label); - } else if (colorBy === 'Instance') { - ({ color } = object); - } else { - ({ color } = object.group); + if (this.mode === Mode.GROUP) { + const { grouped } = this.model.data.groupData; + if (grouped.some((_state: ObjectState): boolean => _state.clientID === state.clientID)) { + return CONST.GROUPING_COLOR; + } } - cuboid.setOriginalColor(color); - cuboid.setColor(color); - cuboid.setOpacity(opacity); - if ( - this.model.data.activeElement.clientID === clientID && - ![Mode.DRAG_CANVAS, Mode.GROUP].includes(this.mode) - ) { - cuboid.setOpacity(selectedOpacity); - if (!object.lock) { - createRotationHelper(cuboid.top, ViewType.TOP); - createRotationHelper(cuboid.side, ViewType.SIDE); - createRotationHelper(cuboid.front, ViewType.FRONT); - setTranslationHelper(cuboid.top); - setTranslationHelper(cuboid.side); - setTranslationHelper(cuboid.front); + if (state instanceof ObjectState) { + if (colorBy === 'Label') { + return state.label.color; } - setEdges(cuboid.top); - setEdges(cuboid.side); - setEdges(cuboid.front); - this.translateReferencePlane(new THREE.Vector3(object.points[0], object.points[1], object.points[2])); - this.model.data.selected = cuboid; - if (object.hidden) { - this.setHelperVisibility(false); - return cuboid; + + if (colorBy === 'Group') { + return state.group?.color || CONST.DEFAULT_GROUP_COLOR; } - } else { - cuboid.top.visible = false; - cuboid.side.visible = false; - cuboid.front.visible = false; + + return state.color; } - if (object.hidden) { - return cuboid; + + if (colorBy === 'Label') { + return state.labelColor; } - cuboid.setPosition(object.points[0], object.points[1], object.points[2]); - cuboid.setScale(object.points[6], object.points[7], object.points[8]); - cuboid.setRotation(object.points[3], object.points[4], object.points[5]); - if (addToScene) { - this.addSceneChildren(cuboid); + + if (colorBy === 'Group') { + return state.groupColor; } - if (this.model.data.activeElement.clientID === clientID) { - cuboid.attachCameraReference(); - this.rotatePlane(null, null); - this.action.detachCam = true; - this.action.detachCamRef = this.model.data.activeElement.clientID; - if (!object.lock) { - this.setSelectedChildScale(1 / cuboid.top.scale.x, 1 / cuboid.top.scale.y, 1 / cuboid.top.scale.z); - this.setHelperVisibility(true); - this.updateRotationHelperPos(); - this.updateResizeHelperPos(); - } else { - this.setHelperVisibility(false); - } + + return state.color; + } + + private addCuboid(state: ObjectState): CuboidModel { + const { + opacity, outlined, outlineColor, + } = this.model.data.shapeProperties; + const clientID = String(state.clientID); + const cuboid = new CuboidModel(state.occluded ? 'dashed' : 'line', outlined ? outlineColor : '#ffffff'); + const color = this.receiveShapeColor(state); + + cuboid.setName(clientID); + cuboid.setColor(color); + cuboid.setOpacity(opacity); + cuboid.setPosition(state.points[0], state.points[1], state.points[2]); + cuboid.setScale(state.points[6], state.points[7], state.points[8]); + cuboid.setRotation(state.points[3], state.points[4], state.points[5]); + cuboid.attachCameraReference(); + + cuboid[ViewType.PERSPECTIVE].visible = !(state.hidden || state.outside); + for (const view of BOTTOM_VIEWS) { + cuboid[view].visible = false; } + return cuboid; } - private setupObjects(): void { - if (this.views.perspective.scene.children[0]) { - this.clearSceneObjects(); - this.setHelperVisibility(false); - for (let i = 0; i < this.model.data.objects.length; i++) { - const object = this.model.data.objects[i]; - this.setupObject(object, true); + private deactivateObject(): void { + const { opacity } = this.model.data.shapeProperties; + if (this.activatedElementID !== null) { + const { cuboid } = this.drawnObjects[this.activatedElementID]; + cuboid.setOpacity(opacity); + for (const view of BOTTOM_VIEWS) { + cuboid[view].visible = false; + removeCuboidEdges(cuboid[view]); + removeResizeHelper(cuboid[view]); + removeRotationHelper(cuboid[view]); } - this.action.loading = false; + this.activatedElementID = null; } } + private activateObject(): void { + const { selectedOpacity } = this.model.data.shapeProperties; + const { clientID } = this.model.data.activeElement; + if (clientID !== null && this.drawnObjects[+clientID]?.cuboid?.perspective?.visible) { + const { cuboid, data } = this.drawnObjects[+clientID]; + cuboid.setOpacity(selectedOpacity); + for (const view of BOTTOM_VIEWS) { + cuboid[view].visible = true; + createCuboidEdges(cuboid[view]); + + if (!data.lock) { + createResizeHelper(cuboid[view]); + createRotationHelper(cuboid[view], view); + } + } + + this.activatedElementID = +clientID; + this.rotatePlane(null, null); + this.detachCamera(null); + this.setDefaultZoom(); + } + } + + private createObjects(states: ObjectState[]): void { + states.forEach((state: ObjectState) => { + const cuboid = this.addCuboid(state); + this.addSceneChildren(cuboid); + this.drawnObjects[state.clientID] = { + cuboid, + data: drawnDataFromState(state), + }; + }); + } + + private updateObjects(states: ObjectState[]): void { + const { outlined, outlineColor } = this.model.data.shapeProperties; + states.forEach((state: ObjectState) => { + const { + clientID, points, color, label, group, occluded, outside, hidden, + } = state; + const { cuboid, data } = this.drawnObjects[clientID]; + + if (points.length !== data.points.length || + points.some((point: number, idx: number) => point !== data.points[idx])) { + cuboid.setPosition(state.points[0], state.points[1], state.points[2]); + cuboid.setScale(state.points[6], state.points[7], state.points[8]); + cuboid.setRotation(state.points[3], state.points[4], state.points[5]); + } + + if ( + color !== data.color || + label.id !== data.labelID || + group.id !== data.groupID || + group.color !== data.groupColor + ) { + const newColor = this.receiveShapeColor(state); + cuboid.setColor(newColor); + if (outlined) { + cuboid.setOutlineColor(outlineColor); + } + } + + if (outside !== data.outside || hidden !== data.hidden) { + cuboid.perspective.visible = !(outside || hidden); + cuboid.top.visible = !(outside || hidden); + cuboid.side.visible = !(outside || hidden); + cuboid.front.visible = !(outside || hidden); + } + + if (occluded !== data.occluded) { + this.deleteObjects([clientID]); + this.createObjects([state]); + return; + } + + this.drawnObjects[clientID].data = drawnDataFromState(state); + }); + } + + private deleteObjects(clientIDs: number[]): void { + clientIDs.forEach((clientID: number): void => { + const { cuboid } = this.drawnObjects[clientID]; + Object.keys(this.views).forEach((view: string): void => { + this.views[view as keyof Views].scene.children[0].remove(cuboid[view as keyof Views]); + }); + + delete this.drawnObjects[clientID]; + }); + } + + private setupObjectsIncremental(states: ObjectState[]): void { + const created = states.filter((state: ObjectState): boolean => !(state.clientID in this.drawnObjects)); + const updated = states.filter((state: ObjectState): boolean => ( + state.clientID in this.drawnObjects && this.drawnObjects[state.clientID].data.updated !== state.updated + )); + const deleted = Object.keys(this.drawnObjects).map((key: string): number => +key) + .filter((clientID: number): boolean => ( + states.findIndex((state: ObjectState) => state.clientID === clientID) === -1 + )); + + this.deactivateObject(); + this.createObjects(created); + this.updateObjects(updated); + this.deleteObjects(deleted); + this.activateObject(); + } + private addSceneChildren(shapeObject: CuboidModel): void { this.views.perspective.scene.children[0].add(shapeObject.perspective); this.views.top.scene.children[0].add(shapeObject.top); @@ -776,224 +943,312 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { public notify(model: Canvas3dModel & Master, reason: UpdateReasons): void { if (reason === UpdateReasons.IMAGE_CHANGED) { - if (!model.data.image) return; - this.dispatchEvent(new CustomEvent('canvas.canceled')); - if (this.model.mode === Mode.DRAW) { - this.model.data.drawData.enabled = false; - } - this.views.perspective.renderer.dispose(); - this.model.mode = Mode.BUSY; - this.action.loading = true; - const loader = new PCDLoader(); - const objectURL = URL.createObjectURL(model.data.image.imageData); + model.data.groupData.grouped = []; this.clearScene(); - loader.load(objectURL, this.addScene.bind(this)); - URL.revokeObjectURL(objectURL); - this.dispatchEvent(new CustomEvent('canvas.setup')); - } else if (reason === UpdateReasons.SHAPE_ACTIVATED) { - const { clientID } = this.model.data.activeElement; - this.setupObjects(); - if (clientID !== 'null') { - this.setDefaultZoom(); + + const onPCDLoadFailed = (): void => { + model.unlockFrameUpdating(); + }; + + const onPCDLoadSuccess = (points: any): void => { + try { + this.onSceneImageLoaded(points); + model.updateCanvasObjects(); + } finally { + model.unlockFrameUpdating(); + } + }; + + try { + if (!model.data.image) { + throw new Error('No image data found'); + } + + const loader = new PCDLoader(); + const objectURL = URL.createObjectURL(model.data.image.imageData); + + try { + this.views.perspective.renderer.dispose(); + if (this.controller.imageIsDeleted) { + try { + this.render(); + const [container] = window.document.getElementsByClassName('cvat-canvas-container'); + const overlay = window.document.createElement('canvas'); + overlay.classList.add('cvat_3d_canvas_deleted_overlay'); + overlay.style.width = '100%'; + overlay.style.height = '100%'; + overlay.style.position = 'absolute'; + overlay.style.top = '0px'; + overlay.style.left = '0px'; + container.appendChild(overlay); + const { clientWidth: width, clientHeight: height } = overlay; + overlay.width = width; + overlay.height = height; + const canvasContext = overlay.getContext('2d'); + const fontSize = width / 10; + canvasContext.font = `bold ${fontSize}px serif`; + canvasContext.textAlign = 'center'; + canvasContext.lineWidth = fontSize / 20; + canvasContext.strokeStyle = 'white'; + canvasContext.strokeText('IMAGE REMOVED', width / 2, height / 2); + canvasContext.fillStyle = 'black'; + canvasContext.fillText('IMAGE REMOVED', width / 2, height / 2); + } finally { + model.unlockFrameUpdating(); + } + } else { + loader.load(objectURL, onPCDLoadSuccess, () => {}, onPCDLoadFailed); + const [overlay] = window.document.getElementsByClassName('cvat_3d_canvas_deleted_overlay'); + if (overlay) { + overlay.remove(); + } + } + + this.dispatchEvent(new CustomEvent('canvas.setup')); + } finally { + URL.revokeObjectURL(objectURL); + } + } catch (error: any) { + model.unlockFrameUpdating(); + throw error; } + } else if (reason === UpdateReasons.SHAPES_CONFIG_UPDATED) { + const config = { ...model.data.shapeProperties }; + for (const key of Object.keys(this.drawnObjects)) { + const clientID = +key; + const { cuboid, data } = this.drawnObjects[clientID]; + const newColor = this.receiveShapeColor(data); + cuboid.setColor(newColor); + cuboid.setOpacity( + ((clientID === this.activatedElementID) ? config.selectedOpacity : config.opacity), + ); + + if (config.outlined) { + cuboid.setOutlineColor(config.outlineColor || CONST.DEFAULT_OUTLINE_COLOR); + } + } + } else if (reason === UpdateReasons.SHAPE_ACTIVATED) { + this.deactivateObject(); + this.activateObject(); } else if (reason === UpdateReasons.DRAW) { const data: DrawData = this.controller.drawData; - this.cube = new CuboidModel('line', '#ffffff'); - if (data.redraw) { - const object = this.views.perspective.scene.getObjectByName(String(data.redraw)); - if (object) { - this.cube.perspective = object.clone() as THREE.Mesh; - object.visible = false; + if (Number.isInteger(data.redraw)) { + if (this.drawnObjects[data.redraw]?.cuboid?.perspective?.visible) { + const { cuboid } = this.drawnObjects[data.redraw]; + this.cube.perspective = cuboid.perspective.clone() as THREE.Mesh; + cuboid.perspective.visible = false; + } else { + // an object must be drawn and visible to be redrawn + model.cancel(); + return; } } else if (data.initialState) { - this.model.data.activeElement.clientID = 'null'; - this.setupObjects(); - this.cube = this.setupObject(data.initialState, false); + if (!data.initialState.outside && !data.initialState.hidden) { + this.cube = this.addCuboid(data.initialState); + } else { + // an object must visible to paste it + model.cancel(); + return; + } + } else { + this.cube = new CuboidModel('line', '#ffffff'); } - this.setHelperVisibility(false); + + this.cube.setName('drawTemplate'); + this.deactivateObject(); + this.views[ViewType.PERSPECTIVE].scene.children[0].add(this.cube.perspective); } else if (reason === UpdateReasons.OBJECTS_UPDATED) { - this.setupObjects(); + this.setupObjectsIncremental(model.objects); } else if (reason === UpdateReasons.DRAG_CANVAS) { + this.isPerspectiveBeingDragged = true; this.dispatchEvent( - new CustomEvent(this.model.mode === Mode.DRAG_CANVAS ? 'canvas.dragstart' : 'canvas.dragstop', { + new CustomEvent('canvas.dragstart', { bubbles: false, cancelable: true, }), ); - this.model.data.activeElement.clientID = 'null'; - if (this.model.mode === Mode.DRAG_CANVAS) { - const { controls } = this.views.perspective; - controls.mouseButtons.left = CameraControls.ACTION.ROTATE; - controls.mouseButtons.right = CameraControls.ACTION.TRUCK; - controls.mouseButtons.wheel = CameraControls.ACTION.DOLLY; - controls.touches.one = CameraControls.ACTION.TOUCH_ROTATE; - controls.touches.two = CameraControls.ACTION.TOUCH_DOLLY_TRUCK; - controls.touches.three = CameraControls.ACTION.TOUCH_TRUCK; - } - this.setupObjects(); + model.data.activeElement.clientID = null; + this.deactivateObject(); } else if (reason === UpdateReasons.CANCEL) { + if (this.mode === Mode.DRAG_CANVAS) { + this.isPerspectiveBeingDragged = false; + this.dispatchEvent( + new CustomEvent('canvas.dragstop', { + bubbles: false, + cancelable: true, + }), + ); + } + if (this.mode === Mode.DRAW) { this.controller.drawData.enabled = false; - this.controller.drawData.redraw = undefined; - Object.keys(this.views).forEach((view: string): void => { - this.views[view as keyof Views].scene.children[0].remove(this.cube[view as keyof Views]); + const { redraw } = this.controller.drawData; + if (Number.isInteger(redraw)) { + this.drawnObjects[redraw].cuboid.perspective.visible = true; + this.controller.drawData.redraw = undefined; + } + const scene = this.views[ViewType.PERSPECTIVE].scene.children[0]; + const template = scene.getObjectByName('drawTemplate'); + if (template) { + scene.remove(template); + } + } + + if (this.mode === Mode.GROUP) { + const { grouped } = this.model.groupData; + this.model.group({ enabled: false, grouped: [] }); + grouped.forEach((state: ObjectState) => { + const { clientID } = state; + const { cuboid } = this.drawnObjects[clientID] || {}; + if (cuboid) { + cuboid.setColor(this.receiveShapeColor(state)); + } }); } - this.model.data.groupData.grouped = []; - this.setHelperVisibility(false); - this.model.mode = Mode.IDLE; - const { controls } = this.views.perspective; - controls.mouseButtons.left = CameraControls.ACTION.NONE; - controls.mouseButtons.right = CameraControls.ACTION.NONE; - controls.mouseButtons.wheel = CameraControls.ACTION.NONE; - controls.touches.one = CameraControls.ACTION.NONE; - controls.touches.two = CameraControls.ACTION.NONE; - controls.touches.three = CameraControls.ACTION.NONE; + + this.mode = Mode.IDLE; + model.mode = Mode.IDLE; + this.dispatchEvent(new CustomEvent('canvas.canceled')); } else if (reason === UpdateReasons.FITTED_CANVAS) { this.dispatchEvent(new CustomEvent('canvas.fit')); } else if (reason === UpdateReasons.GROUP) { - if (!this.model.groupData.enabled) { - this.onGroupDone(this.model.data.groupData.grouped); + if (!model.groupData.enabled) { + this.onGroupDone(model.data.groupData.grouped); } else { - this.model.data.groupData.grouped = []; - this.model.data.activeElement.clientID = 'null'; - this.setupObjects(); + this.deactivateObject(); + model.data.groupData.grouped = []; + model.data.activeElement.clientID = null; } } } private clearScene(): void { + this.drawnObjects = {}; + this.activatedElementID = null; Object.keys(this.views).forEach((view: string): void => { this.views[view as keyof Views].scene.children = []; }); } - private clearSceneObjects(): void { - Object.keys(this.views).forEach((view: string): void => { - this.views[view as keyof Views].scene.children[0].children = []; - }); - } + private updateRotationHelperPos(): void { + const cuboid = this.selectedCuboid; + if (!cuboid) { + return; + } - private setHelperVisibility(visibility: boolean): void { - [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((viewType: ViewType): void => { - const globalRotationObject = this.views[viewType].scene.getObjectByName('globalRotationHelper'); - if (globalRotationObject) { - globalRotationObject.visible = visibility; - } - for (let i = 0; i < 8; i++) { - const resizeObject = this.views[viewType].scene.getObjectByName(`globalResizeHelper${i}`); - if (resizeObject) { - resizeObject.visible = visibility; + BOTTOM_VIEWS.forEach((view: ViewType): void => { + const rotationHelper = cuboid[view].parent.getObjectByName(CONST.ROTATION_HELPER_NAME); + if (rotationHelper) { + const sphere = new THREE.Mesh(new THREE.SphereGeometry(1)); + cuboid[view].add(sphere); + sphere.position.set(0, 0, 0); + if (view === ViewType.TOP) { + sphere.translateY(CONST.ROTATION_HELPER_OFFSET); + } else { + sphere.translateZ(CONST.ROTATION_HELPER_OFFSET); } + + const worldPosition = sphere.getWorldPosition(new THREE.Vector3()); + rotationHelper.position.copy(worldPosition); + cuboid[view].remove(sphere); } }); } - private static setupRotationHelper(): THREE.Mesh { - const sphereGeometry = new THREE.SphereGeometry(0.15); - const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1, visible: true }); - const rotationHelper = new THREE.Mesh(sphereGeometry, sphereMaterial); - rotationHelper.name = 'globalRotationHelper'; - return rotationHelper; - } + private updateResizeHelperPos(): void { + const cuboid = this.selectedCuboid; + if (cuboid === null) { + return; + } - private updateRotationHelperPos(): void { - [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view: ViewType): void => { - const point = new THREE.Vector3(0, 0, 0); - this.model.data.selected[view].getObjectByName('rotationHelper').getWorldPosition(point); - const globalRotationObject = this.views[view].scene.getObjectByName('globalRotationHelper'); - if (globalRotationObject) { - globalRotationObject.position.set(point.x, point.y, point.z); + BOTTOM_VIEWS.forEach((view: ViewType): void => { + const pointsToBeUpdated = cuboid[view].parent.children + .filter((child: THREE.Object3D) => child.name.startsWith(CONST.RESIZE_HELPER_NAME)) + .sort((child1: THREE.Object3D, child2: THREE.Object3D) => { + const order1 = +child1.name.split('_')[1]; + const order2 = +child2.name.split('_')[1]; + return order1 - order2; + }); + + const cornerPoints = makeCornerPointsMatrix(0.5, 0.5, 0.5); + for (let i = 0; i < cornerPoints.length; i++) { + const [x, y, z] = cornerPoints[i]; + const vector = new THREE.Vector3(x, y, z); + const sphere = new THREE.Mesh(new THREE.SphereGeometry(1)); + cuboid[view].add(sphere); + sphere.position.set(vector.x, vector.y, vector.z); + const worldPosition = sphere.getWorldPosition(new THREE.Vector3()); + pointsToBeUpdated[i].position.copy(worldPosition); + cuboid[view].remove(sphere); } }); } - private setHelperSize(viewType: ViewType): void { - if ([ViewType.TOP, ViewType.SIDE, ViewType.FRONT].includes(viewType)) { - const { camera } = this.views[viewType]; - if (!camera || camera instanceof THREE.PerspectiveCamera) return; - const factor = (camera.top - camera.bottom) / camera.zoom; - const rotationObject = this.views[viewType].scene.getObjectByName('globalRotationHelper'); + private updateHelperPointsSize(viewType: ViewType): void { + if (BOTTOM_VIEWS.includes(viewType)) { + const camera = this.views[viewType].camera as THREE.OrthographicCamera; + if (!camera) { return; } + + const rotationObject = this.views[viewType].scene.children[0].getObjectByName(CONST.ROTATION_HELPER_NAME); if (rotationObject) { - rotationObject.scale.set(1, 1, 1).multiplyScalar(factor / 10); - } - for (let i = 0; i < 8; i++) { - const resizeObject = this.views[viewType].scene.getObjectByName(`globalResizeHelper${i}`); - if (resizeObject) { - resizeObject.scale.set(1, 1, 1).multiplyScalar(factor / 10); - } + rotationObject.scale.set(1 / camera.zoom, 1 / camera.zoom, 1 / camera.zoom); } - } - } - private setupResizeHelper(viewType: ViewType): void { - const sphereGeometry = new THREE.SphereGeometry(0.15); - const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1, visible: true }); - const helpers = []; - for (let i = 0; i < 8; i++) { - helpers[i] = new THREE.Mesh(sphereGeometry.clone(), sphereMaterial.clone()); - helpers[i].name = `globalResizeHelper${i}`; - this.globalHelpers[viewType].resize.push(helpers[i]); - this.views[viewType].scene.add(helpers[i]); + this.views[viewType].scene.children[0].children + .filter((child: THREE.Object3D) => child.name.startsWith(CONST.RESIZE_HELPER_NAME)) + .forEach((child: THREE.Object3D) => { + child.scale.set(1 / camera.zoom, 1 / camera.zoom, 1 / camera.zoom); + }); } } - private updateResizeHelperPos(): void { - [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view: ViewType): void => { - let i = 0; - this.model.data.selected[view].children.forEach((element: any): void => { - if (element.name === 'resizeHelper') { - const p = new THREE.Vector3(0, 0, 0); - element.getWorldPosition(p); - const name = `globalResizeHelper${i}`; - const object = this.views[view].scene.getObjectByName(name); - if (object) { - object.position.set(p.x, p.y, p.z); - } - i++; - } - }); - }); - } + private onSceneImageLoaded(points: any): void { + const getCameraSettingsToFitScene = ( + camera: THREE.PerspectiveCamera, + boundingBox: THREE.Box3, + ): [number, number, number] => { + const offset = 5; + const width = boundingBox.max.x - boundingBox.min.x; + const height = boundingBox.max.y - boundingBox.min.y; + + // find the maximum width or height, compute z to approximately fit the scene + const maxDim = Math.max(width, height); + const fov = camera.fov * (Math.PI / 180); + const cameraZ = Math.abs((maxDim / 8) * Math.tan(fov * 2)); + + return [ + boundingBox.min.x + offset, + boundingBox.max.y + offset, + cameraZ + offset, + ]; + }; - private addScene(points: any): void { // eslint-disable-next-line no-param-reassign points.material.size = 0.05; points.material.color.set(new THREE.Color(0xffffff)); + + const { controls } = this.views.perspective; + controls.mouseButtons.wheel = CameraControls.ACTION.DOLLY; + const material = points.material.clone(); - const sphereCenter = points.geometry.boundingSphere.center; - const { radius } = points.geometry.boundingSphere; if (!this.views.perspective.camera) return; - const xRange = -radius / 2 < this.views.perspective.camera.position.x - sphereCenter.x && - radius / 2 > this.views.perspective.camera.position.x - sphereCenter.x; - const yRange = -radius / 2 < this.views.perspective.camera.position.y - sphereCenter.y && - radius / 2 > this.views.perspective.camera.position.y - sphereCenter.y; - const zRange = -radius / 2 < this.views.perspective.camera.position.z - sphereCenter.z && - radius / 2 > this.views.perspective.camera.position.z - sphereCenter.z; - let newX = 0; - let newY = 0; - let newZ = 0; - if (!xRange) { - newX = sphereCenter.x; - } - if (!yRange) { - newY = sphereCenter.y; - } - if (!zRange) { - newZ = sphereCenter.z; - } - if (newX || newY || newZ) { - this.action.frameCoordinates = { x: newX, y: newY, z: newZ }; - this.positionAllViews(newX, newY, newZ, false); - } - [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view: ViewType): void => { - this.globalHelpers[view].resize = []; - this.globalHelpers[view].rotation = []; - }); + if (this.model.configuration.resetZoom) { + points.geometry.computeBoundingBox(); + this.cameraSettings.perspective.position = getCameraSettingsToFitScene( + this.views.perspective.camera as THREE.PerspectiveCamera, points.geometry.boundingBox, + ); + this.positionAllViews( + this.action.frameCoordinates.x, + this.action.frameCoordinates.y, + this.action.frameCoordinates.z, + false, + ); + } this.views.perspective.scene.add(points.clone()); + this.views.perspective.scene.add(new THREE.AxesHelper(5)); // Setup TopView const canvasTopView = this.views.top.renderer.domElement; const topScenePlane = new THREE.Mesh( @@ -1005,10 +1260,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { ), new THREE.MeshBasicMaterial({ color: 0xffffff, - alphaTest: 0, visible: false, - transparent: true, - opacity: 0, }), ); topScenePlane.position.set(0, 0, 0); @@ -1020,10 +1272,6 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { material.size = 0.5; this.views.top.scene.add(points.clone()); this.views.top.scene.add(topScenePlane); - const topRotationHelper = Canvas3dViewImpl.setupRotationHelper(); - this.globalHelpers.top.rotation.push(topRotationHelper); - this.views.top.scene.add(topRotationHelper); - this.setupResizeHelper(ViewType.TOP); // Setup Side View const canvasSideView = this.views.side.renderer.domElement; const sideScenePlane = new THREE.Mesh( @@ -1034,24 +1282,18 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { canvasSideView.offsetWidth, ), new THREE.MeshBasicMaterial({ - color: 0xffffff, - alphaTest: 0, + color: 0x00ff00, visible: false, - transparent: true, - opacity: 0, + opacity: 0.5, }), ); sideScenePlane.position.set(0, 0, 0); - sideScenePlane.rotation.set(-Math.PI / 2, Math.PI / 2000, Math.PI); + sideScenePlane.rotation.set(0, 0, 0); sideScenePlane.name = Planes.SIDE; (sideScenePlane.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; (sideScenePlane as any).verticesNeedUpdate = true; this.views.side.scene.add(points.clone()); this.views.side.scene.add(sideScenePlane); - const sideRotationHelper = Canvas3dViewImpl.setupRotationHelper(); - this.globalHelpers.side.rotation.push(sideRotationHelper); - this.views.side.scene.add(sideRotationHelper); - this.setupResizeHelper(ViewType.SIDE); // Setup front View const canvasFrontView = this.views.front.renderer.domElement; const frontScenePlane = new THREE.Mesh( @@ -1063,25 +1305,20 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { ), new THREE.MeshBasicMaterial({ color: 0xffffff, - alphaTest: 0, visible: false, - transparent: true, - opacity: 0, }), ); frontScenePlane.position.set(0, 0, 0); - frontScenePlane.rotation.set(0, Math.PI / 2, 0); + frontScenePlane.rotation.set(0, 0, 0); frontScenePlane.name = Planes.FRONT; (frontScenePlane.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; (frontScenePlane as any).verticesNeedUpdate = true; this.views.front.scene.add(points.clone()); this.views.front.scene.add(frontScenePlane); - const frontRotationHelper = Canvas3dViewImpl.setupRotationHelper(); - this.globalHelpers.front.rotation.push(frontRotationHelper); - this.views.front.scene.add(frontRotationHelper); - this.setupResizeHelper(ViewType.FRONT); - this.setHelperVisibility(false); - this.setupObjects(); + + if (this.mode === Mode.DRAW) { + this.views[ViewType.PERSPECTIVE].scene.children[0].add(this.cube.perspective); + } } private positionAllViews(x: number, y: number, z: number, animation: boolean): void { @@ -1091,16 +1328,23 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.views.side.controls && this.views.front.controls ) { - this.views.perspective.controls.setLookAt(x - 8, y - 8, z + 3, x, y, z, animation); - this.views.top.camera.position.set(x, y, z + 8); - this.views.top.camera.lookAt(x, y, z); - this.views.top.camera.zoom = CONST.FOV_DEFAULT; - this.views.side.camera.position.set(x, y + 8, z); - this.views.side.camera.lookAt(x, y, z); - this.views.side.camera.zoom = CONST.FOV_DEFAULT; - this.views.front.camera.position.set(x + 8, y, z); - this.views.front.camera.lookAt(x, y, z); - this.views.front.camera.zoom = CONST.FOV_DEFAULT; + this.views.perspective.controls.setLookAt( + x + this.cameraSettings.perspective.position[0], + y - this.cameraSettings.perspective.position[1], + z + this.cameraSettings.perspective.position[2], + x, y, z, animation, + ); + + for (const cameraType of BOTTOM_VIEWS) { + const { camera } = this.views[cameraType]; + camera.position.set( + x + this.cameraSettings[cameraType].position[0], + y + this.cameraSettings[cameraType].position[1], + z + this.cameraSettings[cameraType].position[2], + ); + camera.lookAt(x, y, z); + camera.zoom = CONST.FOV_DEFAULT; + } } } @@ -1135,56 +1379,34 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { private renderRayCaster = (viewType: RenderView): void => { viewType.rayCaster.renderer.setFromCamera(viewType.rayCaster.mouseVector, viewType.camera); if (this.mode === Mode.DRAW) { - const intersects = this.views.perspective.rayCaster.renderer.intersectObjects( - this.views.perspective.scene.children, - false, - ); - if (intersects.length > 0) { - this.views.perspective.scene.children[0].add(this.cube.perspective); - const newPoints = intersects[0].point; - this.cube.perspective.position.copy(newPoints); - this.views.perspective.renderer.domElement.style.cursor = 'default'; + const [intersection] = viewType.rayCaster.renderer.intersectObjects(this.views.perspective.scene.children); + if (intersection) { + const object = this.views.perspective.scene.getObjectByName('drawTemplate'); + const { x, y, z } = intersection.point; + object.position.set(x, y, z); } - } else if (this.mode === Mode.IDLE) { - const { children } = this.views.perspective.scene.children[0]; + } else if (this.mode === Mode.IDLE && !this.isPerspectiveBeingDragged) { const { renderer } = this.views.perspective.rayCaster; - const intersects = renderer.intersectObjects(children, false); + const intersects = renderer.intersectObjects(this.getAllVisibleCuboids(), false); if (intersects.length !== 0) { const clientID = intersects[0].object.name; - if (clientID === undefined || clientID === '' || this.model.data.focusData.clientID === clientID) { - return; + if (this.model.data.activeElement.clientID !== clientID) { + const object = this.views.perspective.scene.getObjectByName(clientID); + if (object === undefined) return; + this.dispatchEvent( + new CustomEvent('canvas.selected', { + bubbles: false, + cancelable: true, + detail: { + clientID: Number(intersects[0].object.name), + }, + }), + ); } - if (!this.action.selectable) return; - this.resetColor(); - const object = this.views.perspective.scene.getObjectByName(clientID); - if (object === undefined) return; - this.model.data.focusData.clientID = clientID; - this.dispatchEvent( - new CustomEvent('canvas.selected', { - bubbles: false, - cancelable: true, - detail: { - clientID: Number(intersects[0].object.name), - }, - }), - ); - } else if (this.model.data.focusData.clientID !== null) { - this.resetColor(); - this.model.data.focusData.clientID = null; } } }; - private resetColor(): void { - this.model.data.objects.forEach((object: any): void => { - const { clientID } = object; - const target = this.views.perspective.scene.getObjectByName(String(clientID)); - if (target) { - ((target as THREE.Mesh).material as THREE.MeshBasicMaterial).color.set((target as any).originalColor); - } - }); - } - public render(): void { Object.keys(this.views).forEach((view: string): void => { const viewType = this.views[view as keyof Views]; @@ -1200,12 +1422,12 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.renderRayCaster(viewType); } const { clientID } = this.model.data.activeElement; - if (clientID !== 'null' && view !== ViewType.PERSPECTIVE) { + if (clientID !== null && view !== ViewType.PERSPECTIVE) { viewType.rayCaster.renderer.setFromCamera(viewType.rayCaster.mouseVector, viewType.camera); // First Scan if (this.action.scan === view) { if (!(this.action.translation.status || this.action.resize.status || this.action.rotation.status)) { - this.initiateAction(view, viewType); + this.initiateAction(view as ViewType, viewType); } // Action Operations if (this.action.detected) { @@ -1216,49 +1438,36 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { } else { this.renderRotateAction(view as ViewType, viewType); } - this.updateRotationHelperPos(); - this.updateResizeHelperPos(); } else { this.resetActions(); } } } }); + if (this.action.detachCam && this.action.detachCamRef === this.model.data.activeElement.clientID) { try { this.detachCamera(null); - // eslint-disable-next-line no-empty - } catch (e) { } finally { this.action.detachCam = false; } } - if (this.model.mode === Mode.BUSY && !this.action.loading) { - if (this.action.oldState !== '') { - this.model.mode = this.action.oldState; - this.action.oldState = ''; - } else { - this.model.mode = Mode.IDLE; - } - } else if (this.model.data.objectUpdating && !this.action.loading) { - this.model.data.objectUpdating = false; - } } private adjustPerspectiveCameras(): void { - const coordinatesTop = this.model.data.selected.getReferenceCoordinates(ViewType.TOP); + const coordinatesTop = this.selectedCuboid.getReferenceCoordinates(ViewType.TOP); const sphericalTop = new THREE.Spherical(); sphericalTop.setFromVector3(coordinatesTop); this.views.top.camera.position.setFromSpherical(sphericalTop); this.views.top.camera.updateProjectionMatrix(); - const coordinatesSide = this.model.data.selected.getReferenceCoordinates(ViewType.SIDE); + const coordinatesSide = this.selectedCuboid.getReferenceCoordinates(ViewType.SIDE); const sphericalSide = new THREE.Spherical(); sphericalSide.setFromVector3(coordinatesSide); this.views.side.camera.position.setFromSpherical(sphericalSide); this.views.side.camera.updateProjectionMatrix(); - const coordinatesFront = this.model.data.selected.getReferenceCoordinates(ViewType.FRONT); + const coordinatesFront = this.selectedCuboid.getReferenceCoordinates(ViewType.FRONT); const sphericalFront = new THREE.Spherical(); sphericalFront.setFromVector3(coordinatesFront); this.views.front.camera.position.setFromSpherical(sphericalFront); @@ -1287,7 +1496,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { private moveObject(coordinates: THREE.Vector3): void { const { perspective, top, side, front, - } = this.model.data.selected; + } = this.selectedCuboid; let localCoordinates = coordinates; if (this.action.translation.status) { localCoordinates = coordinates @@ -1299,144 +1508,113 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { top.position.copy(localCoordinates.clone()); side.position.copy(localCoordinates.clone()); front.position.copy(localCoordinates.clone()); + + this.updateResizeHelperPos(); + this.updateRotationHelperPos(); } private setSelectedChildScale(x: number, y: number, z: number): void { - [ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view: ViewType): void => { - this.model.data.selected[view].children.forEach((element: any): void => { - if (element.name !== CONST.CUBOID_EDGE_NAME) { - element.scale.set( - x == null ? element.scale.x : x, - y == null ? element.scale.y : y, - z == null ? element.scale.z : z, - ); - } + const cuboid = this.selectedCuboid; + if (cuboid) { + BOTTOM_VIEWS.forEach((view: ViewType): void => { + cuboid[view].children.forEach((element: any): void => { + if (element.name !== CONST.CUBOID_EDGE_NAME) { + element.scale.set( + x == null ? element.scale.x : x, + y == null ? element.scale.y : y, + z == null ? element.scale.z : z, + ); + } + }); }); - }); + } } private renderResizeAction(view: ViewType, viewType: any): void { - const intersects = viewType.rayCaster.renderer.intersectObjects( - [viewType.scene.getObjectByName(`${view}Plane`)], - true, - ); - // Return if no intersection with the reference plane - if (intersects.length === 0) return; - const { x: scaleInitX, y: scaleInitY, z: scaleInitZ } = this.action.resize.initScales; - const { x: scaleMemX, y: scaleMemY, z: scaleMemZ } = this.action.resize.memScales; - const { x: initPosX, y: initPosY } = this.action.resize.helper; - const { x: currentPosX, y: currentPosY } = viewType.rayCaster.mouseVector; - const { resizeVector } = this.action.resize; - - if (this.action.resize.helper.x === currentPosX && this.action.resize.helper.y === currentPosY) { + const cuboid = this.selectedCuboid; + const intersects = viewType.rayCaster.renderer + .intersectObjects([viewType.scene.getObjectByName(`${view}Plane`)], true); + + if ( + cuboid === null || intersects.length === 0) { return; } - if ( - this.action.resize.recentMouseVector.x === currentPosX && - this.action.resize.recentMouseVector.y === currentPosY - ) { + if (!this.action.resize.previousPosition) { + this.action.resize.previousPosition = intersects[0].object.worldToLocal(intersects[0].point.clone()); return; } - this.action.resize.recentMouseVector = viewType.rayCaster.mouseVector.clone(); - switch (view) { - case ViewType.TOP: { - let y = scaleInitX * (currentPosX / initPosX); - let x = scaleInitY * (currentPosY / initPosY); - if (x < 0) x = 0.2; - if (y < 0) y = 0.2; - this.model.data.selected.setScale(y, x, this.model.data.selected.top.scale.z); - this.setSelectedChildScale(1 / y, 1 / x, null); - const differenceX = y / 2 - scaleMemX / 2; - const differenceY = x / 2 - scaleMemY / 2; - - if (currentPosX > 0 && currentPosY < 0) { - resizeVector.x += differenceX; - resizeVector.y -= differenceY; - } else if (currentPosX > 0 && currentPosY > 0) { - resizeVector.x += differenceX; - resizeVector.y += differenceY; - } else if (currentPosX < 0 && currentPosY < 0) { - resizeVector.x -= differenceX; - resizeVector.y -= differenceY; - } else if (currentPosX < 0 && currentPosY > 0) { - resizeVector.x -= differenceX; - resizeVector.y += differenceY; - } - this.action.resize.memScales.x = y; - this.action.resize.memScales.y = x; - break; - } - case ViewType.SIDE: { - let x = scaleInitX * (currentPosX / initPosX); - let z = scaleInitZ * (currentPosY / initPosY); - if (x < 0) x = 0.2; - if (z < 0) z = 0.2; - this.model.data.selected.setScale(x, this.model.data.selected.top.scale.y, z); - this.setSelectedChildScale(1 / x, null, 1 / z); - const differenceX = x / 2 - scaleMemX / 2; - const differenceY = z / 2 - scaleMemZ / 2; - - if (currentPosX > 0 && currentPosY < 0) { - resizeVector.x += differenceX; - resizeVector.y -= differenceY; - } else if (currentPosX > 0 && currentPosY > 0) { - resizeVector.x += differenceX; - resizeVector.y += differenceY; - } else if (currentPosX < 0 && currentPosY < 0) { - resizeVector.x -= differenceX; - resizeVector.y -= differenceY; - } else if (currentPosX < 0 && currentPosY > 0) { - resizeVector.x -= differenceX; - resizeVector.y += differenceY; - } + if (Math.abs(this.action.resize.previousPosition.x - intersects[0].point.x) < Number.EPSILON || + Math.abs(this.action.resize.previousPosition.y - intersects[0].point.y) < Number.EPSILON) { + return; + } - this.action.resize.memScales = { ...this.action.resize.memScales, x, z }; - break; - } - case ViewType.FRONT: { - let y = scaleInitY * (currentPosX / initPosX); - let z = scaleInitZ * (currentPosY / initPosY); - if (y < 0) y = 0.2; - if (z < 0) z = 0.2; - this.model.data.selected.setScale(this.model.data.selected.top.scale.x, y, z); - this.setSelectedChildScale(null, 1 / y, 1 / z); - let differenceX; - let differenceY; - - if (!this.action.resize.frontBool) { - differenceX = z / 2 - scaleMemZ / 2; - differenceY = y / 2 - scaleMemY / 2; - this.action.resize.frontBool = true; - } else { - differenceX = z / 2 - scaleMemY / 2; - differenceY = y / 2 - scaleMemZ / 2; - } - if (currentPosX > 0 && currentPosY < 0) { - resizeVector.x += differenceX; - resizeVector.y += differenceY; - } else if (currentPosX > 0 && currentPosY > 0) { - resizeVector.x -= differenceX; - resizeVector.y += differenceY; - } else if (currentPosX < 0 && currentPosY < 0) { - resizeVector.x += differenceX; - resizeVector.y -= differenceY; - } else if (currentPosX < 0 && currentPosY > 0) { - resizeVector.x -= differenceX; - resizeVector.y -= differenceY; - } + // first let's find the point that is used to resize + // and the opposite point in another corner + const currentPointNumber = +this.action.resize.helperElement.name.split('_')[1]; + const cuboidNodes = makeCornerPointsMatrix(0.5, 0.5, 0.5); + const crosslyingPointInternalCoordonates = (new THREE.Vector3()) + .fromArray(cuboidNodes[+currentPointNumber]).multiply(new THREE.Vector3(-1, -1, -1)); + const crosslyingHelperIndex = cuboidNodes + .findIndex(([x, y, z]): boolean => ( + Math.sign(crosslyingPointInternalCoordonates.x) === Math.sign(x) && + Math.sign(crosslyingPointInternalCoordonates.y) === Math.sign(y) && + Math.sign(crosslyingPointInternalCoordonates.z) === Math.sign(z) + )); + const crosslyingHelper = cuboid.perspective.getObjectByName(`cuboidNodeHelper_${crosslyingHelperIndex}`); + const crosslyingPointCoordinates = crosslyingHelper.getWorldPosition(new THREE.Vector3()); + + // after we've found two points + // we can get all the information from them (scale and center) + // but first we need to move the current point + // we will move point in "internal" cuboid coordinates + // and then using localToWorld we will receive world coordinates + const currentPointCoordOnPlane = intersects[0].object.worldToLocal(intersects[0].point.clone()); + const scale = cuboid.perspective.scale.clone(); + const currentPointInternalCoordinates = new THREE.Vector3(); + if (view === ViewType.FRONT) { + const diffX = currentPointCoordOnPlane.x - this.action.resize.previousPosition.x; + const diffY = currentPointCoordOnPlane.y - this.action.resize.previousPosition.y; + currentPointInternalCoordinates + .fromArray(cuboidNodes[currentPointNumber]).add(new THREE.Vector3(0, diffY, -diffX).divide(scale)); + } else if (view === ViewType.SIDE) { + const diffX = currentPointCoordOnPlane.x - this.action.resize.previousPosition.x; + const diffY = currentPointCoordOnPlane.y - this.action.resize.previousPosition.y; + currentPointInternalCoordinates + .fromArray(cuboidNodes[currentPointNumber]).add(new THREE.Vector3(-diffX, 0, diffY).divide(scale)); + } else if (view === ViewType.TOP) { + const diffX = currentPointCoordOnPlane.x - this.action.resize.previousPosition.x; + const diffY = currentPointCoordOnPlane.y - this.action.resize.previousPosition.y; + currentPointInternalCoordinates + .fromArray(cuboidNodes[currentPointNumber]).add(new THREE.Vector3(diffX, diffY, 0).divide(scale)); + } + const perspectivePosition = cuboid.perspective.localToWorld(currentPointInternalCoordinates.clone()); - this.action.resize.memScales.y = z; - this.action.resize.memScales.z = y; - break; - } - default: + // small check to avoid case when points change their relative orientation + if ( + Math.sign(crosslyingPointInternalCoordonates.x - cuboidNodes[currentPointNumber][0]) !== + Math.sign(crosslyingPointInternalCoordonates.x - currentPointInternalCoordinates.x) || + Math.sign(crosslyingPointInternalCoordonates.y - cuboidNodes[currentPointNumber][1]) !== + Math.sign(crosslyingPointInternalCoordonates.y - currentPointInternalCoordinates.y) || + Math.sign(crosslyingPointInternalCoordonates.z - cuboidNodes[currentPointNumber][2]) !== + Math.sign(crosslyingPointInternalCoordonates.z - currentPointInternalCoordinates.z) + ) { + return; } - const coordinates = resizeVector.clone(); - intersects[0].object.localToWorld(coordinates); - this.moveObject(coordinates); + + // finally let's compute new center and scale + scale.x *= Math.abs(crosslyingPointInternalCoordonates.x - currentPointInternalCoordinates.x); + scale.y *= Math.abs(crosslyingPointInternalCoordonates.y - currentPointInternalCoordinates.y); + scale.z *= Math.abs(crosslyingPointInternalCoordonates.z - currentPointInternalCoordinates.z); + const newPosition = crosslyingPointCoordinates.clone().add(perspectivePosition).divideScalar(2); + + // and apply them + this.moveObject(newPosition); + cuboid.setScale(scale.x, scale.y, scale.z); this.adjustPerspectiveCameras(); + + this.action.resize.previousPosition = currentPointCoordOnPlane; } private static isLeft(a: any, b: any, c: any): boolean { @@ -1491,38 +1669,20 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { } } - private attachCamera(view: ViewType): void { - switch (view) { - case ViewType.TOP: - this.model.data.selected.side.attach(this.views.side.camera); - this.model.data.selected.front.attach(this.views.front.camera); - break; - case ViewType.SIDE: - this.model.data.selected.front.attach(this.views.front.camera); - this.model.data.selected.top.attach(this.views.top.camera); - break; - case ViewType.FRONT: - this.model.data.selected.side.attach(this.views.side.camera); - this.model.data.selected.top.attach(this.views.top.camera); - break; - default: - } - } - private detachCamera(view: ViewType): void { - const coordTop = this.model.data.selected.getReferenceCoordinates(ViewType.TOP); + const coordTop = this.selectedCuboid.getReferenceCoordinates(ViewType.TOP); const sphericaltop = new THREE.Spherical(); sphericaltop.setFromVector3(coordTop); - const coordSide = this.model.data.selected.getReferenceCoordinates(ViewType.SIDE); + const coordSide = this.selectedCuboid.getReferenceCoordinates(ViewType.SIDE); const sphericalside = new THREE.Spherical(); sphericalside.setFromVector3(coordSide); - const coordFront = this.model.data.selected.getReferenceCoordinates(ViewType.FRONT); + const coordFront = this.selectedCuboid.getReferenceCoordinates(ViewType.FRONT); const sphericalfront = new THREE.Spherical(); sphericalfront.setFromVector3(coordFront); - const { side: objectSideView, front: objectFrontView, top: objectTopView } = this.model.data.selected; + const { side: objectSideView, front: objectFrontView, top: objectTopView } = this.selectedCuboid; const { camera: sideCamera } = this.views.side; const { camera: frontCamera } = this.views.front; const { camera: topCamera } = this.views.top; @@ -1625,89 +1785,19 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { sceneFrontPlane.rotateZ(direction); break; default: { - const { top: objectTopView, side: objectSideView, front: objectFrontView } = this.model.data.selected; - objectTopView.add(sceneTopPlane); - objectSideView.add(sceneSidePlane); - objectFrontView.add(sceneFrontPlane); - objectTopView.getObjectByName(Planes.TOP).rotation.set(0, 0, 0); - objectSideView.getObjectByName(Planes.SIDE).rotation.set(-Math.PI / 2, Math.PI / 2000, Math.PI); - objectFrontView.getObjectByName(Planes.FRONT).rotation.set(0, Math.PI / 2, 0); - - const quaternionSide = new THREE.Quaternion(); - objectSideView.getObjectByName(Planes.SIDE).getWorldQuaternion(quaternionSide); - const rotationSide = new THREE.Euler(); - rotationSide.setFromQuaternion(quaternionSide); - - const quaternionFront = new THREE.Quaternion(); - objectFrontView.getObjectByName(Planes.FRONT).getWorldQuaternion(quaternionFront); - const rotationFront = new THREE.Euler(); - rotationFront.setFromQuaternion(quaternionFront); - - const quaternionTop = new THREE.Quaternion(); - objectTopView.getObjectByName(Planes.TOP).getWorldQuaternion(quaternionTop); - const rotationTop = new THREE.Euler(); - rotationTop.setFromQuaternion(quaternionTop); - - objectTopView.remove(sceneTopPlane); - objectSideView.remove(sceneSidePlane); - objectFrontView.remove(sceneFrontPlane); - - const canvasTopView = this.views.top.renderer.domElement; - const planeTop = new THREE.Mesh( - new THREE.PlaneBufferGeometry( - canvasTopView.offsetHeight, - canvasTopView.offsetWidth, - canvasTopView.offsetHeight, - canvasTopView.offsetWidth, - ), - new THREE.MeshBasicMaterial({ - color: 0xff0000, - alphaTest: 0, - visible: false, - transparent: true, - opacity: 0.1, - }), - ); - planeTop.name = Planes.TOP; - (planeTop.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; - - const canvasSideView = this.views.side.renderer.domElement; - const planeSide = new THREE.Mesh( - new THREE.PlaneBufferGeometry( - canvasSideView.offsetHeight, - canvasSideView.offsetWidth, - canvasSideView.offsetHeight, - canvasSideView.offsetWidth, - ), - new THREE.MeshBasicMaterial({ - color: 0x00ff00, - alphaTest: 0, - visible: false, - transparent: true, - opacity: 0.1, - }), - ); - planeSide.name = Planes.SIDE; - (planeSide.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; - - const canvasFrontView = this.views.front.renderer.domElement; - const planeFront = new THREE.Mesh( - new THREE.PlaneBufferGeometry( - canvasFrontView.offsetHeight, - canvasFrontView.offsetWidth, - canvasFrontView.offsetHeight, - canvasFrontView.offsetWidth, - ), - new THREE.MeshBasicMaterial({ - color: 0x0000ff, - alphaTest: 0, - visible: false, - transparent: true, - opacity: 0.5, - }), - ); - planeFront.name = Planes.FRONT; - (planeFront.material as THREE.MeshBasicMaterial).side = THREE.DoubleSide; + const { top: objectTopView, side: objectSideView, front: objectFrontView } = this.selectedCuboid; + + const quaternionSide = objectSideView.getObjectByName(CONST.PLANE_ROTATION_HELPER) + .getWorldQuaternion(new THREE.Quaternion()); + const rotationSide = new THREE.Euler().setFromQuaternion(quaternionSide); + + const quaternionFront = objectFrontView.getObjectByName(CONST.PLANE_ROTATION_HELPER) + .getWorldQuaternion(new THREE.Quaternion()); + const rotationFront = new THREE.Euler().setFromQuaternion(quaternionFront); + + const quaternionTop = objectTopView.getObjectByName(CONST.PLANE_ROTATION_HELPER) + .getWorldQuaternion(new THREE.Quaternion()); + const rotationTop = new THREE.Euler().setFromQuaternion(quaternionTop); const coordinates = { x: objectTopView.position.x, @@ -1715,12 +1805,9 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { z: objectTopView.position.z, }; - planeTop.rotation.set(rotationTop.x, rotationTop.y, rotationTop.z); - planeSide.rotation.set(rotationSide.x, rotationSide.y, rotationSide.z); - planeFront.rotation.set(rotationFront.x, rotationFront.y, rotationFront.z); - this.views.top.scene.add(planeTop); - this.views.side.scene.add(planeSide); - this.views.front.scene.add(planeFront); + sceneTopPlane.rotation.set(rotationTop.x, rotationTop.y, rotationTop.z); + sceneSidePlane.rotation.set(rotationSide.x, rotationSide.y, rotationSide.z); + sceneFrontPlane.rotation.set(rotationFront.x, rotationFront.y, rotationFront.z); this.translateReferencePlane(coordinates); } @@ -1736,6 +1823,7 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { x: canvas.offsetLeft + canvas.offsetWidth / 2, y: canvas.offsetTop + canvas.offsetHeight / 2, }; + if ( this.action.rotation.screenInit.x === this.action.rotation.screenMove.x && this.action.rotation.screenInit.y === this.action.rotation.screenMove.y @@ -1751,42 +1839,50 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { } this.action.rotation.recentMouseVector = this.views[view].rayCaster.mouseVector.clone(); if (Canvas3dViewImpl.isLeft(canvasCentre, this.action.rotation.screenInit, this.action.rotation.screenMove)) { - this.rotateCube(this.model.data.selected, -rotationSpeed, view); + this.rotateCube(this.selectedCuboid, -rotationSpeed, view); this.rotatePlane(-rotationSpeed, view); } else { - this.rotateCube(this.model.data.selected, rotationSpeed, view); + this.rotateCube(this.selectedCuboid, rotationSpeed, view); this.rotatePlane(rotationSpeed, view); } + + this.updateResizeHelperPos(); + this.updateRotationHelperPos(); + this.detachCamera(null); this.action.rotation.screenInit.x = this.action.rotation.screenMove.x; this.action.rotation.screenInit.y = this.action.rotation.screenMove.y; } - private initiateAction(view: string, viewType: any): void { + private initiateAction(view: ViewType, viewType: any): void { + const { clientID } = this.model.data.activeElement; + const { cuboid, data } = this.drawnObjects[+clientID] || {}; + if (!data || !cuboid || data.lock) return; + const intersectsHelperResize = viewType.rayCaster.renderer.intersectObjects( - this.globalHelpers[view].resize, + cuboid[view].parent.children + .filter((child: THREE.Object3D) => child.name.startsWith(CONST.RESIZE_HELPER_NAME)), false, ); - const [state] = this.model.data.objects.filter( - (_state: any): boolean => _state.clientID === Number(this.model.data.selected[view].name), + + const intersectsPlane = viewType.rayCaster.renderer.intersectObjects( + [viewType.scene.getObjectByName(`${view}Plane`)], + false, ); - if (state.lock) return; - if (intersectsHelperResize.length !== 0) { - this.action.resize.helper = viewType.rayCaster.mouseVector.clone(); - this.action.resize.status = true; + if (intersectsHelperResize.length !== 0 && intersectsPlane.length !== 0) { this.action.detected = true; this.views.top.controls.enabled = false; this.views.side.controls.enabled = false; this.views.front.controls.enabled = false; - const { x, y, z } = this.model.data.selected[view].scale; - this.action.resize.initScales = { x, y, z }; - this.action.resize.memScales = { x, y, z }; - this.action.resize.frontBool = false; - this.action.resize.resizeVector = new THREE.Vector3(0, 0, 0); + this.action.resize.status = true; + this.action.resize.helperElement = intersectsHelperResize[0].object; + this.action.resize.previousPosition = null; return; } + const intersectsHelperRotation = viewType.rayCaster.renderer.intersectObjects( - this.globalHelpers[view].rotation, + cuboid[view].parent.children + .filter((child: THREE.Object3D) => child.name.startsWith(CONST.ROTATION_HELPER_NAME)), false, ); if (intersectsHelperRotation.length !== 0) { @@ -1796,17 +1892,15 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { this.views.top.controls.enabled = false; this.views.side.controls.enabled = false; this.views.front.controls.enabled = false; - this.attachCamera(view as ViewType); return; } - const intersectsBox = viewType.rayCaster.renderer.intersectObjects([this.model.data.selected[view]], false); + const intersectsBox = viewType.rayCaster.renderer.intersectObjects([cuboid[view]], false); const intersectsPointCloud = viewType.rayCaster.renderer.intersectObjects( [viewType.scene.getObjectByName(`${view}Plane`)], true, ); - if (intersectsBox.length !== 0) { - if (state.pinned) return; + if (intersectsBox.length !== 0 && !data.pinned) { this.action.translation.helper = viewType.rayCaster.mouseVector.clone(); this.action.translation.inverseMatrix = intersectsBox[0].object.parent.matrixWorld.invert(); this.action.translation.offset = intersectsPointCloud[0].point.sub( @@ -1863,8 +1957,6 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener { default: break; } - } else if (key.code === 'ControlLeft') { - this.action.selectable = !key.ctrlKey; } } diff --git a/cvat-canvas3d/src/typescript/consts.ts b/cvat-canvas3d/src/typescript/consts.ts index f4bda8fc969d..20df990dd212 100644 --- a/cvat-canvas3d/src/typescript/consts.ts +++ b/cvat-canvas3d/src/typescript/consts.ts @@ -1,4 +1,5 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -8,15 +9,20 @@ const DOLLY_FACTOR = 5; const MAX_DISTANCE = 100; const MIN_DISTANCE = 0.3; const ZOOM_FACTOR = 7; -const ROTATION_HELPER_OFFSET = 0.1; +const ROTATION_HELPER_OFFSET = 0.75; const CAMERA_REFERENCE = 'camRef'; const CUBOID_EDGE_NAME = 'edges'; -const ROTATION_HELPER = 'rotationHelper'; +const ROTATION_HELPER_NAME = '2DRotationHelper'; +const PLANE_ROTATION_HELPER = 'planeRotationHelper'; +const RESIZE_HELPER_NAME = '2DResizeHelper'; const ROTATION_SPEED = 80; const FOV_DEFAULT = 1; const FOV_MAX = 2; const FOV_MIN = 0; const FOV_INC = 0.08; +const DEFAULT_GROUP_COLOR = '#e0e0e0'; +const DEFAULT_OUTLINE_COLOR = '#000000'; +const GROUPING_COLOR = '#8b008b'; export default { BASE_GRID_WIDTH, @@ -28,10 +34,15 @@ export default { ROTATION_HELPER_OFFSET, CAMERA_REFERENCE, CUBOID_EDGE_NAME, - ROTATION_HELPER, + ROTATION_HELPER_NAME, + PLANE_ROTATION_HELPER, + RESIZE_HELPER_NAME, ROTATION_SPEED, FOV_DEFAULT, FOV_MAX, FOV_MIN, FOV_INC, + DEFAULT_GROUP_COLOR, + DEFAULT_OUTLINE_COLOR, + GROUPING_COLOR, }; diff --git a/cvat-canvas3d/src/typescript/cuboid.ts b/cvat-canvas3d/src/typescript/cuboid.ts index d293e40e5582..5f9abd387d1b 100644 --- a/cvat-canvas3d/src/typescript/cuboid.ts +++ b/cvat-canvas3d/src/typescript/cuboid.ts @@ -1,8 +1,9 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT + import * as THREE from 'three'; -import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils'; import { ViewType } from './canvas3dModel'; import constants from './consts'; @@ -10,11 +11,25 @@ export interface Indexable { [key: string]: any; } +export function makeCornerPointsMatrix(x: number, y: number, z: number): number[][] { + return ([ + [1 * x, 1 * y, 1 * z], + [1 * x, 1 * y, -1 * z], + [1 * x, -1 * y, 1 * z], + [1 * x, -1 * y, -1 * z], + [-1 * x, 1 * y, 1 * z], + [-1 * x, 1 * y, -1 * z], + [-1 * x, -1 * y, 1 * z], + [-1 * x, -1 * y, -1 * z], + ]); +} + export class CuboidModel { public perspective: THREE.Mesh; public top: THREE.Mesh; public side: THREE.Mesh; public front: THREE.Mesh; + public wireframe: THREE.LineSegments; public constructor(outline: string, outlineColor: string) { const geometry = new THREE.BoxGeometry(1, 1, 1); @@ -26,24 +41,72 @@ export class CuboidModel { }); this.perspective = new THREE.Mesh(geometry, material); const geo = new THREE.EdgesGeometry(this.perspective.geometry); - const wireframe = new THREE.LineSegments( + this.wireframe = new THREE.LineSegments( geo, - outline === 'line' - ? new THREE.LineBasicMaterial({ color: outlineColor, linewidth: 4 }) - : new THREE.LineDashedMaterial({ + outline === 'line' ? new THREE.LineBasicMaterial({ color: outlineColor, linewidth: 4 }) : + new THREE.LineDashedMaterial({ color: outlineColor, dashSize: 0.05, gapSize: 0.05, }), ); - wireframe.computeLineDistances(); - wireframe.renderOrder = 1; - this.perspective.add(wireframe); + this.wireframe.computeLineDistances(); + this.wireframe.renderOrder = 1; + this.perspective.add(this.wireframe); this.top = new THREE.Mesh(geometry, material); this.side = new THREE.Mesh(geometry, material); this.front = new THREE.Mesh(geometry, material); + const planeTop = new THREE.Mesh( + new THREE.PlaneBufferGeometry(1, 1, 1, 1), + new THREE.MeshBasicMaterial({ + color: 0xff0000, + visible: false, + }), + ); + + const planeSide = new THREE.Mesh( + new THREE.PlaneBufferGeometry(1, 1, 1, 1), + new THREE.MeshBasicMaterial({ + color: 0xff0000, + visible: false, + }), + ); + + const planeFront = new THREE.Mesh( + new THREE.PlaneBufferGeometry(1, 1, 1, 1), + new THREE.MeshBasicMaterial({ + color: 0xff0000, + visible: false, + }), + ); + + this.top.add(planeTop); + planeTop.rotation.set(0, 0, 0); + planeTop.position.set(0, 0, 0.5); + planeTop.name = constants.PLANE_ROTATION_HELPER; + + this.side.add(planeSide); + planeSide.rotation.set(-Math.PI / 2, 0, Math.PI); + planeTop.position.set(0, 0.5, 0); + planeSide.name = constants.PLANE_ROTATION_HELPER; + + this.front.add(planeFront); + planeFront.rotation.set(0, Math.PI / 2, 0); + planeTop.position.set(0.5, 0, 0); + planeFront.name = constants.PLANE_ROTATION_HELPER; + + const cornerPoints = makeCornerPointsMatrix(0.5, 0.5, 0.5); + for (let i = 0; i < cornerPoints.length; i++) { + const point = new THREE.Vector3().fromArray(cornerPoints[i]); + const helper = new THREE.Mesh(new THREE.SphereGeometry(0.1)); + helper.visible = false; + helper.name = `cuboidNodeHelper_${i}`; + this.perspective.add(helper); + helper.position.copy(point); + } + const camRotateHelper = new THREE.Object3D(); camRotateHelper.translateX(-2); camRotateHelper.name = 'camRefRot'; @@ -71,29 +134,25 @@ export class CuboidModel { } public attachCameraReference(): void { - // Attach Cam Reference const topCameraReference = new THREE.Object3D(); topCameraReference.translateZ(2); topCameraReference.name = constants.CAMERA_REFERENCE; this.top.add(topCameraReference); - this.top.userData = { ...this.top.userData, camReference: topCameraReference }; const sideCameraReference = new THREE.Object3D(); sideCameraReference.translateY(2); sideCameraReference.name = constants.CAMERA_REFERENCE; this.side.add(sideCameraReference); - this.side.userData = { ...this.side.userData, camReference: sideCameraReference }; const frontCameraReference = new THREE.Object3D(); frontCameraReference.translateX(2); frontCameraReference.name = constants.CAMERA_REFERENCE; this.front.add(frontCameraReference); - this.front.userData = { ...this.front.userData, camReference: frontCameraReference }; } public getReferenceCoordinates(viewType: string): THREE.Vector3 { - const { elements } = (this as Indexable)[viewType].getObjectByName(constants.CAMERA_REFERENCE).matrixWorld; - return new THREE.Vector3(elements[12], elements[13], elements[14]); + const camRef = (this as Indexable)[viewType].getObjectByName(constants.CAMERA_REFERENCE); + return camRef.getWorldPosition(new THREE.Vector3()); } public setName(clientId: any): void { @@ -102,18 +161,17 @@ export class CuboidModel { }); } - public setOriginalColor(color: string): void { - [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { - ((this as Indexable)[view] as any).originalColor = color; - }); - } - public setColor(color: string): void { + this.setOutlineColor(color); [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { ((this as Indexable)[view].material as THREE.MeshBasicMaterial).color.set(color); }); } + public setOutlineColor(color: string): void { + (this.wireframe.material as THREE.MeshBasicMaterial).color.set(color); + } + public setOpacity(opacity: number): void { [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { ((this as Indexable)[view].material as THREE.MeshBasicMaterial).opacity = opacity / 100; @@ -121,69 +179,72 @@ export class CuboidModel { } } -export function setEdges(instance: THREE.Mesh): THREE.LineSegments { - const edges = new THREE.EdgesGeometry(instance.geometry); - const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: '#ffffff', linewidth: 3 })); - line.name = constants.CUBOID_EDGE_NAME; - instance.add(line); - return line; +export function createCuboidEdges(instance: THREE.Mesh): THREE.LineSegments { + const geometry = new THREE.EdgesGeometry(instance.geometry); + const edges = new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({ color: '#ffffff', linewidth: 3 })); + edges.name = constants.CUBOID_EDGE_NAME; + instance.add(edges); + return edges; } -export function setTranslationHelper(instance: THREE.Mesh): void { +export function removeCuboidEdges(instance: THREE.Mesh): void { + const edges = instance.getObjectByName(constants.CUBOID_EDGE_NAME); + instance.remove(edges); +} + +export function createResizeHelper(instance: THREE.Mesh): void { const sphereGeometry = new THREE.SphereGeometry(0.1); - const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1 }); - instance.geometry.deleteAttribute('normal'); - instance.geometry.deleteAttribute('uv'); - // eslint-disable-next-line no-param-reassign - instance.geometry = BufferGeometryUtils.mergeVertices(instance.geometry); - const vertices = []; - const positionAttribute = instance.geometry.getAttribute('position'); - for (let i = 0; i < positionAttribute.count; i++) { - const vertex = new THREE.Vector3(); - vertex.fromBufferAttribute(positionAttribute, i); - vertices.push(vertex); - } - const helpers = []; - for (let i = 0; i < vertices.length; i++) { - helpers[i] = new THREE.Mesh(sphereGeometry.clone(), sphereMaterial.clone()); - helpers[i].position.set(vertices[i].x, vertices[i].y, vertices[i].z); - helpers[i].up.set(0, 0, 1); - helpers[i].name = 'resizeHelper'; - instance.add(helpers[i]); - helpers[i].scale.set(1 / instance.scale.x, 1 / instance.scale.y, 1 / instance.scale.z); + const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ff0000', opacity: 1 }); + const cornerPoints = makeCornerPointsMatrix(0.5, 0.5, 0.5); + + for (let i = 0; i < cornerPoints.length; i++) { + const point = new THREE.Vector3().fromArray(cornerPoints[i]); + const tmpSphere = new THREE.Mesh(new THREE.SphereGeometry(0.1)); + instance.add(tmpSphere); + tmpSphere.position.copy(point); + const globalPosition = tmpSphere.getWorldPosition(new THREE.Vector3()); + instance.remove(tmpSphere); + + const helper = new THREE.Mesh(sphereGeometry.clone(), sphereMaterial.clone()); + helper.position.copy(globalPosition); + helper.name = `${constants.RESIZE_HELPER_NAME}_${i}`; + instance.parent.add(helper); } - // eslint-disable-next-line no-param-reassign - instance.userData = { ...instance.userData, resizeHelpers: helpers }; +} + +export function removeResizeHelper(instance: THREE.Mesh): void { + instance.parent.children.filter((child: THREE.Object3D) => child.name.startsWith(constants.RESIZE_HELPER_NAME)) + .forEach((helper) => { + instance.parent.remove(helper); + }); } export function createRotationHelper(instance: THREE.Mesh, viewType: ViewType): void { - const sphereGeometry = new THREE.SphereGeometry(0.1); - const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1 }); - const rotationHelper = new THREE.Mesh(sphereGeometry, sphereMaterial); - rotationHelper.name = constants.ROTATION_HELPER; - switch (viewType) { - case ViewType.TOP: - rotationHelper.position.set( - (instance.geometry as THREE.BoxGeometry).parameters.height / 2 + constants.ROTATION_HELPER_OFFSET, - instance.position.y, - instance.position.z, - ); - instance.add(rotationHelper.clone()); - // eslint-disable-next-line no-param-reassign - instance.userData = { ...instance.userData, rotationHelpers: rotationHelper.clone() }; - break; - case ViewType.SIDE: - case ViewType.FRONT: - rotationHelper.position.set( - instance.position.x, - instance.position.y, - (instance.geometry as THREE.BoxGeometry).parameters.depth / 2 + constants.ROTATION_HELPER_OFFSET, - ); - instance.add(rotationHelper.clone()); - // eslint-disable-next-line no-param-reassign - instance.userData = { ...instance.userData, rotationHelpers: rotationHelper.clone() }; - break; - default: - break; + if ([ViewType.TOP, ViewType.SIDE, ViewType.FRONT].includes(viewType)) { + // Create a temporary element to get correct position + const tmpSphere = new THREE.Mesh(new THREE.SphereGeometry(0.1)); + instance.add(tmpSphere); + if (viewType === ViewType.TOP) { + tmpSphere.translateY(constants.ROTATION_HELPER_OFFSET); + } else { + tmpSphere.translateZ(constants.ROTATION_HELPER_OFFSET); + } + const globalPosition = tmpSphere.getWorldPosition(new THREE.Vector3()); + instance.remove(tmpSphere); + + // Create rotation helper itself first + const sphereGeometry = new THREE.SphereGeometry(0.1); + const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#33b864', opacity: 1 }); + const rotationHelper = new THREE.Mesh(sphereGeometry, sphereMaterial); + rotationHelper.name = constants.ROTATION_HELPER_NAME; + instance.parent.add(rotationHelper); + rotationHelper.position.copy(globalPosition); + } +} + +export function removeRotationHelper(instance: THREE.Mesh): void { + const helper = instance.parent.getObjectByName(constants.ROTATION_HELPER_NAME); + if (helper) { + instance.parent.remove(helper); } } diff --git a/cvat-canvas3d/src/typescript/index.ts b/cvat-canvas3d/src/typescript/index.ts new file mode 100644 index 000000000000..f9b3c572c00b --- /dev/null +++ b/cvat-canvas3d/src/typescript/index.ts @@ -0,0 +1,9 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import ObjectState from 'cvat-core/src/object-state'; +import { Label } from 'cvat-core/src/labels'; +import { ShapeType } from 'cvat-core/src/enums'; + +export { ObjectState, Label, ShapeType }; diff --git a/cvat-canvas3d/src/typescript/master.ts b/cvat-canvas3d/src/typescript/master.ts index 4831fce7fa1b..e3f9d58b3b16 100644 --- a/cvat-canvas3d/src/typescript/master.ts +++ b/cvat-canvas3d/src/typescript/master.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-canvas3d/webpack.config.js b/cvat-canvas3d/webpack.config.js index bfdfc248c4c5..8185308c332a 100644 --- a/cvat-canvas3d/webpack.config.js +++ b/cvat-canvas3d/webpack.config.js @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ const path = require('path'); // eslint-disable-next-line @typescript-eslint/no-var-requires -const DtsBundleWebpack = require('dts-bundle-webpack'); +const BundleDeclarationsWebpackPlugin = require('bundle-declarations-webpack-plugin'); const styleLoaders = [ 'style-loader', @@ -64,10 +64,8 @@ const nodeConfig = { ], }, plugins: [ - new DtsBundleWebpack({ - name: 'cvat-canvas3d.node', - main: 'dist/declaration/src/typescript/canvas3d.d.ts', - out: '../cvat-canvas3d.node.d.ts', + new BundleDeclarationsWebpackPlugin({ + outFile: "declaration/src/cvat-canvas.d.ts", }), ], }; @@ -116,10 +114,8 @@ const webConfig = { ], }, plugins: [ - new DtsBundleWebpack({ - name: 'cvat-canvas3d', - main: 'dist/declaration/src/typescript/canvas3d.d.ts', - out: '../cvat-canvas3d.d.ts', + new BundleDeclarationsWebpackPlugin({ + outFile: "declaration/src/cvat-canvas.d.ts", }), ], }; diff --git a/cvat-cli/.gitignore b/cvat-cli/.gitignore new file mode 100644 index 000000000000..43995bd42fa2 --- /dev/null +++ b/cvat-cli/.gitignore @@ -0,0 +1,66 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.venv/ +.python-version +.pytest_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/cvat-cli/MANIFEST.in b/cvat-cli/MANIFEST.in new file mode 100644 index 000000000000..e56c92e0be78 --- /dev/null +++ b/cvat-cli/MANIFEST.in @@ -0,0 +1,2 @@ +include README.md +include requirements/base.txt \ No newline at end of file diff --git a/cvat-cli/README.md b/cvat-cli/README.md new file mode 100644 index 000000000000..71c19b79d908 --- /dev/null +++ b/cvat-cli/README.md @@ -0,0 +1,67 @@ +# Command-line client for CVAT + +A simple command line interface for working with CVAT tasks. At the moment it +implements a basic feature set but may serve as the starting point for a more +comprehensive CVAT administration tool in the future. + +Overview of functionality: + +- Create a new task (supports name, bug tracker, project, labels JSON, local/share/remote files) +- Delete tasks (supports deleting a list of task IDs) +- List all tasks (supports basic CSV or JSON output) +- Download JPEG frames (supports a list of frame IDs) +- Dump annotations (supports all formats via format string) +- Upload annotations for a task in the specified format (e.g. 'YOLO ZIP 1.0') +- Export and download a whole task +- Import a task + +## Installation + +`pip install cvat-cli` + +## Usage + +```bash +$ cvat-cli --help + +usage: cvat-cli [-h] [--version] [--auth USER:[PASS]] + [--server-host SERVER_HOST] [--server-port SERVER_PORT] [--debug] + {create,delete,ls,frames,dump,upload,export,import} ... + +Perform common operations related to CVAT tasks. + +positional arguments: + {create,delete,ls,frames,dump,upload,export,import} + +optional arguments: + -h, --help show this help message and exit + --version show program's version number and exit + --auth USER:[PASS] defaults to the current user and supports the PASS + environment variable or password prompt + (default: current user) + --server-host SERVER_HOST + host (default: localhost) + --server-port SERVER_PORT + port (default: 8080) + --debug show debug output +``` + +## Examples + +Create a task with local images: + +```bash +cvat-cli --auth user create + --labels '[{"name": "car"}, {"name": "person"}]' + "test_task" + "local" + "image1.jpg" "image2.jpg" +``` + +List tasks on a custom server with auth: + +```bash +cvat-cli --auth admin:password \ + --server-host cvat.my.server.com --server-port 30123 \ + ls +``` diff --git a/cvat-cli/developer_guide.md b/cvat-cli/developer_guide.md new file mode 100644 index 000000000000..0bd47bd1a31f --- /dev/null +++ b/cvat-cli/developer_guide.md @@ -0,0 +1,19 @@ +# Developer guide + +Install testing requirements: + +```bash +pip install -r requirements/testing.txt +``` + +Run unit tests: +``` +cd cvat/ +python manage.py test --settings cvat.settings.testing cvat-cli/ +``` + +Install package in the editable mode: + +```bash +pip install -e . +``` diff --git a/cvat-cli/pyproject.toml b/cvat-cli/pyproject.toml new file mode 100644 index 000000000000..67280a49cac1 --- /dev/null +++ b/cvat-cli/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.isort] +profile = "black" +forced_separate = ["tests"] +line_length = 100 +skip_gitignore = true # align tool behavior with Black + +# Can't just use a pyproject in the root dir, so duplicate +# https://github.com/psf/black/issues/2863 +[tool.black] +line-length = 100 +target-version = ['py38'] diff --git a/cvat-cli/requirements/base.txt b/cvat-cli/requirements/base.txt new file mode 100644 index 000000000000..28637e6d1103 --- /dev/null +++ b/cvat-cli/requirements/base.txt @@ -0,0 +1,2 @@ +cvat-sdk~=2.1.0 +Pillow>=6.2.0 diff --git a/cvat-cli/requirements/development.txt b/cvat-cli/requirements/development.txt new file mode 100644 index 000000000000..1b3f6ae04b26 --- /dev/null +++ b/cvat-cli/requirements/development.txt @@ -0,0 +1,5 @@ +-r base.txt + +black>=22.1.0 +isort>=5.10.1 +pylint>=2.7.0 \ No newline at end of file diff --git a/cvat-cli/setup.py b/cvat-cli/setup.py new file mode 100644 index 000000000000..58567b65e3d8 --- /dev/null +++ b/cvat-cli/setup.py @@ -0,0 +1,67 @@ +# Copyright (C) 2022 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os.path as osp +import re + +from setuptools import find_packages, setup + + +def find_version(project_dir=None): + if not project_dir: + project_dir = osp.dirname(osp.abspath(__file__)) + + file_path = osp.join(project_dir, "version.py") + + with open(file_path, "r") as version_file: + version_text = version_file.read() + + # PEP440: + # https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions + pep_regex = r"([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*((a|b|rc)(0|[1-9]\d*))?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*))?" + version_regex = r"VERSION\s*=\s*.(" + pep_regex + ")." + match = re.match(version_regex, version_text) + if not match: + raise RuntimeError("Failed to find version string in '%s'" % file_path) + + version = version_text[match.start(1) : match.end(1)] + return version + + +BASE_REQUIREMENTS_FILE = "requirements/base.txt" + + +def parse_requirements(filename=BASE_REQUIREMENTS_FILE): + with open(filename) as fh: + return fh.readlines() + + +BASE_REQUIREMENTS = parse_requirements(BASE_REQUIREMENTS_FILE) + +with open("README.md", "r") as fh: + long_description = fh.read() + +setup( + name="cvat-cli", + version=find_version(project_dir="src/cvat_cli"), + description="Command-line client for CVAT", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/cvat-ai/cvat/", + package_dir={"": "src"}, + packages=find_packages(where="src"), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires=">=3.7", + install_requires=BASE_REQUIREMENTS, + entry_points={ + "console_scripts": [ + "cvat-cli=cvat_cli.__main__:main", + ], + }, + include_package_data=True, +) diff --git a/utils/cli/tests/__init__.py b/cvat-cli/src/cvat_cli/__init__.py similarity index 100% rename from utils/cli/tests/__init__.py rename to cvat-cli/src/cvat_cli/__init__.py diff --git a/cvat-cli/src/cvat_cli/__main__.py b/cvat-cli/src/cvat_cli/__main__.py new file mode 100755 index 000000000000..ce32b832fdde --- /dev/null +++ b/cvat-cli/src/cvat_cli/__main__.py @@ -0,0 +1,76 @@ +# Copyright (C) 2020-2022 Intel Corporation +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +import logging +import sys +from http.client import HTTPConnection +from types import SimpleNamespace +from typing import List + +from cvat_sdk import exceptions +from cvat_sdk.core.client import Client, Config + +from cvat_cli.cli import CLI +from cvat_cli.parser import get_action_args, make_cmdline_parser + +logger = logging.getLogger(__name__) + + +def configure_logger(level): + formatter = logging.Formatter( + "[%(asctime)s] %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", style="%" + ) + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(level) + if level <= logging.DEBUG: + HTTPConnection.debuglevel = 1 + + +def build_client(parsed_args: SimpleNamespace, logger: logging.Logger) -> Client: + config = Config(verify_ssl=not parsed_args.insecure) + + url = parsed_args.server_host + if parsed_args.server_port: + url += f":{parsed_args.server_port}" + + return Client( + url=url, + logger=logger, + config=config, + check_server_version=False, # version is checked after auth to support versions < 2.3 + ) + + +def main(args: List[str] = None): + actions = { + "create": CLI.tasks_create, + "delete": CLI.tasks_delete, + "ls": CLI.tasks_list, + "frames": CLI.tasks_frames, + "dump": CLI.tasks_dump, + "upload": CLI.tasks_upload, + "export": CLI.tasks_export, + "import": CLI.tasks_import, + } + parser = make_cmdline_parser() + parsed_args = parser.parse_args(args) + configure_logger(parsed_args.loglevel) + + with build_client(parsed_args, logger=logger) as client: + action_args = get_action_args(parser, parsed_args) + try: + cli = CLI(client=client, credentials=parsed_args.auth) + actions[parsed_args.action](cli, **vars(action_args)) + except exceptions.ApiException as e: + logger.critical(e) + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/cvat-cli/src/cvat_cli/cli.py b/cvat-cli/src/cvat_cli/cli.py new file mode 100644 index 000000000000..00122341cf58 --- /dev/null +++ b/cvat-cli/src/cvat_cli/cli.py @@ -0,0 +1,138 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import json +from typing import Dict, List, Sequence, Tuple + +import tqdm +from cvat_sdk import Client, models +from cvat_sdk.core.helpers import TqdmProgressReporter +from cvat_sdk.core.proxies.tasks import ResourceType + + +class CLI: + def __init__(self, client: Client, credentials: Tuple[str, str]): + self.client = client + + # allow arbitrary kwargs in models + # TODO: will silently ignore invalid args, so remove this ASAP + self.client.api_client.configuration.discard_unknown_keys = True + + self.client.login(credentials) + + self.client.check_server_version(fail_if_unsupported=False) + + def tasks_list(self, *, use_json_output: bool = False, **kwargs): + """List all tasks in either basic or JSON format.""" + results = self.client.tasks.list(return_json=use_json_output, **kwargs) + if use_json_output: + print(json.dumps(json.loads(results), indent=2)) + else: + for r in results: + print(r.id) + + def tasks_create( + self, + name: str, + labels: List[Dict[str, str]], + resource_type: ResourceType, + resources: Sequence[str], + *, + annotation_path: str = "", + annotation_format: str = "CVAT XML 1.1", + status_check_period: int = 2, + dataset_repository_url: str = "", + lfs: bool = False, + **kwargs, + ) -> None: + """ + Create a new task with the given name and labels JSON and add the files to it. + """ + task = self.client.tasks.create_from_data( + spec=models.TaskWriteRequest(name=name, labels=labels, **kwargs), + resource_type=resource_type, + resources=resources, + data_params=kwargs, + annotation_path=annotation_path, + annotation_format=annotation_format, + status_check_period=status_check_period, + dataset_repository_url=dataset_repository_url, + use_lfs=lfs, + pbar=self._make_pbar(), + ) + print("Created task id", task.id) + + def tasks_delete(self, task_ids: Sequence[int]) -> None: + """Delete a list of tasks, ignoring those which don't exist.""" + self.client.tasks.remove_by_ids(task_ids=task_ids) + + def tasks_frames( + self, + task_id: int, + frame_ids: Sequence[int], + *, + outdir: str = "", + quality: str = "original", + ) -> None: + """ + Download the requested frame numbers for a task and save images as + task__frame_.jpg. + """ + self.client.tasks.retrieve(obj_id=task_id).download_frames( + frame_ids=frame_ids, + outdir=outdir, + quality=quality, + filename_pattern=f"task_{task_id}" + "_frame_{frame_id:06d}{frame_ext}", + ) + + def tasks_dump( + self, + task_id: int, + fileformat: str, + filename: str, + *, + status_check_period: int = 2, + include_images: bool = False, + ) -> None: + """ + Download annotations for a task in the specified format (e.g. 'YOLO ZIP 1.0'). + """ + self.client.tasks.retrieve(obj_id=task_id).export_dataset( + format_name=fileformat, + filename=filename, + pbar=self._make_pbar(), + status_check_period=status_check_period, + include_images=include_images, + ) + + def tasks_upload( + self, task_id: str, fileformat: str, filename: str, *, status_check_period: int = 2 + ) -> None: + """Upload annotations for a task in the specified format + (e.g. 'YOLO ZIP 1.0').""" + self.client.tasks.retrieve(obj_id=task_id).import_annotations( + format_name=fileformat, + filename=filename, + status_check_period=status_check_period, + pbar=self._make_pbar(), + ) + + def tasks_export(self, task_id: str, filename: str, *, status_check_period: int = 2) -> None: + """Download a task backup""" + self.client.tasks.retrieve(obj_id=task_id).download_backup( + filename=filename, status_check_period=status_check_period, pbar=self._make_pbar() + ) + + def tasks_import(self, filename: str, *, status_check_period: int = 2) -> None: + """Import a task from a backup file""" + self.client.tasks.create_from_backup( + filename=filename, status_check_period=status_check_period, pbar=self._make_pbar() + ) + + def _make_pbar(self, title: str = None) -> TqdmProgressReporter: + return TqdmProgressReporter( + tqdm.tqdm(unit_scale=True, unit="B", unit_divisor=1024, desc=title) + ) diff --git a/cvat-cli/src/cvat_cli/parser.py b/cvat-cli/src/cvat_cli/parser.py new file mode 100644 index 000000000000..5a5410a4a5c9 --- /dev/null +++ b/cvat-cli/src/cvat_cli/parser.py @@ -0,0 +1,336 @@ +# Copyright (C) 2021-2022 Intel Corporation +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +import getpass +import json +import logging +import os +from distutils.util import strtobool + +from cvat_sdk.core.proxies.tasks import ResourceType + +from .version import VERSION + + +def get_auth(s): + """Parse USER[:PASS] strings and prompt for password if none was + supplied.""" + user, _, password = s.partition(":") + password = password or os.environ.get("PASS") or getpass.getpass() + return user, password + + +def parse_label_arg(s): + """If s is a file load it as JSON, otherwise parse s as JSON.""" + if os.path.exists(s): + with open(s, "r") as fp: + return json.load(fp) + else: + return json.loads(s) + + +def parse_resource_type(s: str) -> ResourceType: + try: + return ResourceType[s.upper()] + except KeyError: + return s + + +def make_cmdline_parser() -> argparse.ArgumentParser: + ####################################################################### + # Command line interface definition + ####################################################################### + parser = argparse.ArgumentParser( + description="Perform common operations related to CVAT tasks.\n\n" + ) + parser.add_argument("--version", action="version", version=VERSION) + parser.add_argument( + "--insecure", + action="store_true", + help="Allows to disable SSL certificate check", + ) + + task_subparser = parser.add_subparsers(dest="action") + + ####################################################################### + # Positional arguments + ####################################################################### + parser.add_argument( + "--auth", + type=get_auth, + metavar="USER:[PASS]", + default=getpass.getuser(), + help="""defaults to the current user and supports the PASS + environment variable or password prompt + (default user: %(default)s).""", + ) + parser.add_argument( + "--server-host", type=str, default="localhost", help="host (default: %(default)s)" + ) + parser.add_argument( + "--server-port", + type=int, + default=None, + help="port (default: 80 for http and 443 for https connections)", + ) + parser.add_argument( + "--debug", + action="store_const", + dest="loglevel", + const=logging.DEBUG, + default=logging.INFO, + help="show debug output", + ) + + ####################################################################### + # Create + ####################################################################### + task_create_parser = task_subparser.add_parser( + "create", + description="""Create a new CVAT task. To create a task, you need + to specify labels using the --labels argument or + attach the task to an existing project using the + --project_id argument.""", + ) + task_create_parser.add_argument("name", type=str, help="name of the task") + task_create_parser.add_argument( + "resource_type", + default="local", + choices=list(ResourceType), + type=parse_resource_type, + help="type of files specified", + ) + task_create_parser.add_argument("resources", type=str, help="list of paths or URLs", nargs="+") + task_create_parser.add_argument( + "--annotation_path", default="", type=str, help="path to annotation file" + ) + task_create_parser.add_argument( + "--annotation_format", + default="CVAT 1.1", + type=str, + help="format of the annotation file being uploaded, e.g. CVAT 1.1", + ) + task_create_parser.add_argument( + "--bug_tracker", "--bug", default=None, type=str, help="bug tracker URL" + ) + task_create_parser.add_argument( + "--chunk_size", default=None, type=int, help="the number of frames per chunk" + ) + task_create_parser.add_argument( + "--completion_verification_period", + dest="status_check_period", + default=2, + type=float, + help="""number of seconds to wait until checking + if data compression finished (necessary before uploading annotations)""", + ) + task_create_parser.add_argument( + "--copy_data", + default=False, + action="store_true", + help="""set the option to copy the data, only used when resource type is + share (default: %(default)s)""", + ) + task_create_parser.add_argument( + "--dataset_repository_url", + default="", + type=str, + help=( + "git repository to store annotations e.g." + " https://github.com/user/repos [annotation/]" + ), + ) + task_create_parser.add_argument( + "--frame_step", + default=None, + type=int, + help="""set the frame step option in the advanced configuration + when uploading image series or videos (default: %(default)s)""", + ) + task_create_parser.add_argument( + "--image_quality", + default=70, + type=int, + help="""set the image quality option in the advanced configuration + when creating tasks.(default: %(default)s)""", + ) + task_create_parser.add_argument( + "--labels", + default="[]", + type=parse_label_arg, + help="string or file containing JSON labels specification", + ) + task_create_parser.add_argument( + "--lfs", + default=False, + action="store_true", + help="using lfs for dataset repository (default: %(default)s)", + ) + task_create_parser.add_argument( + "--project_id", default=None, type=int, help="project ID if project exists" + ) + task_create_parser.add_argument( + "--overlap", + default=None, + type=int, + help="the number of intersected frames between different segments", + ) + task_create_parser.add_argument( + "--segment_size", default=None, type=int, help="the number of frames in a segment" + ) + task_create_parser.add_argument( + "--sorting-method", + default="lexicographical", + choices=["lexicographical", "natural", "predefined", "random"], + help="""data soring method (default: %(default)s)""", + ) + task_create_parser.add_argument( + "--start_frame", default=None, type=int, help="the start frame of the video" + ) + task_create_parser.add_argument( + "--stop_frame", default=None, type=int, help="the stop frame of the video" + ) + task_create_parser.add_argument( + "--use_cache", action="store_true", help="""use cache""" # automatically sets default=False + ) + task_create_parser.add_argument( + "--use_zip_chunks", + action="store_true", # automatically sets default=False + help="""zip chunks before sending them to the server""", + ) + + ####################################################################### + # Delete + ####################################################################### + delete_parser = task_subparser.add_parser("delete", description="Delete a CVAT task.") + delete_parser.add_argument("task_ids", type=int, help="list of task IDs", nargs="+") + + ####################################################################### + # List + ####################################################################### + ls_parser = task_subparser.add_parser( + "ls", description="List all CVAT tasks in simple or JSON format." + ) + ls_parser.add_argument( + "--json", + dest="use_json_output", + default=False, + action="store_true", + help="output JSON data", + ) + + ####################################################################### + # Frames + ####################################################################### + frames_parser = task_subparser.add_parser( + "frames", description="Download all frame images for a CVAT task." + ) + frames_parser.add_argument("task_id", type=int, help="task ID") + frames_parser.add_argument( + "frame_ids", type=int, help="list of frame IDs to download", nargs="+" + ) + frames_parser.add_argument( + "--outdir", type=str, default="", help="directory to save images (default: CWD)" + ) + frames_parser.add_argument( + "--quality", + type=str, + choices=("original", "compressed"), + default="original", + help="choose quality of images (default: %(default)s)", + ) + + ####################################################################### + # Dump + ####################################################################### + dump_parser = task_subparser.add_parser( + "dump", description="Download annotations for a CVAT task." + ) + dump_parser.add_argument("task_id", type=int, help="task ID") + dump_parser.add_argument("filename", type=str, help="output file") + dump_parser.add_argument( + "--format", + dest="fileformat", + type=str, + default="CVAT for images 1.1", + help="annotation format (default: %(default)s)", + ) + dump_parser.add_argument( + "--completion_verification_period", + dest="status_check_period", + default=2, + type=float, + help="number of seconds to wait until checking if dataset building finished", + ) + dump_parser.add_argument( + "--with-images", + type=strtobool, + default=False, + dest="include_images", + help="Whether to include images or not (default: %(default)s)", + ) + + ####################################################################### + # Upload Annotations + ####################################################################### + upload_parser = task_subparser.add_parser( + "upload", description="Upload annotations for a CVAT task." + ) + upload_parser.add_argument("task_id", type=int, help="task ID") + upload_parser.add_argument("filename", type=str, help="upload file") + upload_parser.add_argument( + "--format", + dest="fileformat", + type=str, + default="CVAT 1.1", + help="annotation format (default: %(default)s)", + ) + + ####################################################################### + # Export task + ####################################################################### + export_task_parser = task_subparser.add_parser("export", description="Export a CVAT task.") + export_task_parser.add_argument("task_id", type=int, help="task ID") + export_task_parser.add_argument("filename", type=str, help="output file") + export_task_parser.add_argument( + "--completion_verification_period", + dest="status_check_period", + default=2, + type=float, + help="time interval between checks if archive building has been finished, in seconds", + ) + + ####################################################################### + # Import task + ####################################################################### + import_task_parser = task_subparser.add_parser("import", description="Import a CVAT task.") + import_task_parser.add_argument("filename", type=str, help="upload file") + import_task_parser.add_argument( + "--completion_verification_period", + dest="status_check_period", + default=2, + type=float, + help="time interval between checks if archive processing was finished, in seconds", + ) + + return parser + + +def get_action_args( + parser: argparse.ArgumentParser, parsed_args: argparse.Namespace +) -> argparse.Namespace: + # FIXME: a hacky way to remove unnecessary args + action_args = dict(vars(parsed_args)) + + for action in parser._actions: + action_args.pop(action.dest, None) + + # remove default args + for k, v in dict(action_args).items(): + if v is None: + action_args.pop(k, None) + + return argparse.Namespace(**action_args) diff --git a/cvat-cli/src/cvat_cli/version.py b/cvat-cli/src/cvat_cli/version.py new file mode 100644 index 000000000000..4fc4e1115449 --- /dev/null +++ b/cvat-cli/src/cvat_cli/version.py @@ -0,0 +1 @@ +VERSION = "2.1.0.post1" diff --git a/cvat-core/.eslintrc.js b/cvat-core/.eslintrc.js index 48f3895fa51a..a28a5cc1b80a 100644 --- a/cvat-core/.eslintrc.js +++ b/cvat-core/.eslintrc.js @@ -1,14 +1,8 @@ -// Copyright (C) 2018-2021 Intel Corporation +// Copyright (C) 2018-2022 Intel Corporation // // SPDX-License-Identifier: MIT module.exports = { - env: { - node: true, - browser: true, - es6: true, - 'jest/globals': true, - }, ignorePatterns: [ '.eslintrc.js', 'webpack.config.js', @@ -17,17 +11,10 @@ module.exports = { 'src/3rdparty/**', 'node_modules/**', 'dist/**', + 'tests/**/*.js', ], parserOptions: { - parser: 'babel-eslint', - sourceType: 'module', - ecmaVersion: 2018, + project: './tsconfig.json', + tsconfigRootDir: __dirname, }, - plugins: ['jest'], - rules: { - 'jest/no-disabled-tests': 'warn', - 'jest/no-focused-tests': 'error', - 'jest/no-identical-title': 'error', - 'jest/prefer-to-have-length': 'warn', - } }; diff --git a/cvat-core/README.md b/cvat-core/README.md index e1deebe9a497..fd0f23e8e6e8 100644 --- a/cvat-core/README.md +++ b/cvat-core/README.md @@ -9,43 +9,43 @@ It contains the core logic of the Computer Vision Annotation Tool. If you make changes in this package, please do following: -- After not important changes (typos, backward compatible bug fixes, refactoring) do: `npm version patch` -- After changing API (backward compatible new features) do: `npm version minor` -- After changing API (changes that break backward compatibility) do: `npm version major` +- After not important changes (typos, backward compatible bug fixes, refactoring) do: `yarn version --patch` +- After changing API (backward compatible new features) do: `yarn version --minor` +- After changing API (changes that break backward compatibility) do: `yarn version --major` ### Commands - Dependencies installation ```bash -npm ci +yarn ci --frozen-lockfile ``` - Building the module from sources in the `dist` directory: ```bash -npm run build -npm run build -- --mode=development # without a minification +yarn run build +yarn run build --mode=development # without a minification ``` - Building the documentation in the `docs` directory: ```bash -npm run-script docs +yarn run docs ``` - Running of tests: ```bash -npm run-script test +yarn run test ``` - Updating of a module version: ```bash -npm version patch # updated after minor fixes -npm version minor # updated after major changes which don't affect API compatibility with previous versions -npm version major # updated after major changes which affect API compatibility with previous versions +yarn version --patch # updated after minor fixes +yarn version --minor # updated after major changes which don't affect API compatibility with previous versions +yarn version --major # updated after major changes which affect API compatibility with previous versions ``` Visual studio code configurations: diff --git a/cvat-core/jest.config.js b/cvat-core/jest.config.js index f356b2dfa5f8..cc38eb8f9a34 100644 --- a/cvat-core/jest.config.js +++ b/cvat-core/jest.config.js @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT const { defaults } = require('jest-config'); module.exports = { + preset: 'ts-jest', coverageDirectory: 'reports/coverage', coverageReporters: ['json', ['lcov', { projectRoot: '../' }]], moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'], @@ -12,4 +13,9 @@ module.exports = { testMatch: ['**/tests/**/*.js'], testPathIgnorePatterns: ['/node_modules/', '/tests/mocks/*'], automock: false, + globals: { + 'ts-jest': { + diagnostics: false, + }, + }, }; diff --git a/cvat-core/jsdoc.config.js b/cvat-core/jsdoc.config.js index a85e5eccbc07..f5c515cb7a98 100644 --- a/cvat-core/jsdoc.config.js +++ b/cvat-core/jsdoc.config.js @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-core/package-lock.json b/cvat-core/package-lock.json deleted file mode 100644 index 54bd177dc553..000000000000 --- a/cvat-core/package-lock.json +++ /dev/null @@ -1,11506 +0,0 @@ -{ - "name": "cvat-core", - "version": "5.0.1", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "cvat-core", - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "axios": "^0.21.4", - "browser-or-node": "^1.2.1", - "cvat-data": "../cvat-data", - "detect-browser": "^5.2.1", - "error-stack-parser": "^2.0.2", - "form-data": "^2.5.0", - "jest-config": "^26.6.3", - "js-cookie": "^2.2.0", - "json-logic-js": "^2.0.1", - "platform": "^1.3.5", - "quickhull": "^1.0.3", - "store": "^2.0.12", - "tus-js-client": "^2.3.0" - }, - "devDependencies": { - "coveralls": "^3.0.5", - "jest": "^26.6.3", - "jest-junit": "^6.4.0", - "jsdoc": "^3.6.6" - } - }, - "../cvat-data": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "async-mutex": "^0.3.2", - "jszip": "3.7.1" - }, - "devDependencies": {} - }, - "detect-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.1.tgz", - "integrity": "sha512-eAcRiEPTs7utXWPaAgu/OX1HRJpxW7xSHpw4LTDrGFaeWnJ37HRlqpUkKsDm0AoTbtrvHQhH+5U2Cd87EGhJTg==", - "extraneous": true - }, - "node_modules/@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz", - "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", - "dependencies": { - "@babel/code-frame": "^7.15.8", - "@babel/generator": "^7.15.8", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.8", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.8", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", - "dependencies": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", - "dependencies": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dependencies": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", - "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", - "dependencies": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", - "dependencies": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dependencies": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - }, - "bin": { - "watch": "cli.js" - }, - "engines": { - "node": ">=0.1.95" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "dependencies": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "dependencies": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "node-notifier": "^8.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "dependencies": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/node": { - "version": "16.10.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz", - "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==" - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "node_modules/@types/prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==" - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" - }, - "node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" - }, - "node_modules/acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "dependencies": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "dependencies": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-or-node": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz", - "integrity": "sha512-0F2z/VSnLbmEeBcUrSuDH5l0HxTXdQQzLjkmBR4cYfvg1zJrKSlmIZFqyFR8oX0NrwPhy3c3HQ6i3OxMbew4Tg==" - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" - }, - "node_modules/browserslist": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.3.tgz", - "integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==", - "dependencies": { - "caniuse-lite": "^1.0.30001264", - "electron-to-chromium": "^1.3.857", - "escalade": "^3.1.1", - "node-releases": "^1.1.77", - "picocolors": "^0.2.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001265", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz", - "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dependencies": { - "rsvp": "^4.8.4" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "node_modules/catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "node_modules/cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==" - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/combine-errors": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", - "integrity": "sha1-9N9nQAg+VwOjGBEQwrEFUfAD2oY=", - "dependencies": { - "custom-error-instance": "2.1.1", - "lodash.uniqby": "4.5.0" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "node_modules/coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, - "dependencies": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - }, - "bin": { - "coveralls": "bin/coveralls.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" - }, - "node_modules/custom-error-instance": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", - "integrity": "sha1-PPY5FIemYppiR+sMoM4ACBt+Nho=" - }, - "node_modules/cvat-data": { - "resolved": "../cvat-data", - "link": true - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.1.tgz", - "integrity": "sha512-eAcRiEPTs7utXWPaAgu/OX1HRJpxW7xSHpw4LTDrGFaeWnJ37HRlqpUkKsDm0AoTbtrvHQhH+5U2Cd87EGhJTg==" - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.3.861", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.861.tgz", - "integrity": "sha512-GZyflmpMnZRdZ1e2yAyvuFwz1MPSVQelwHX4TJZyXypB8NcxdPvPNwy5lOTxnlkrK13EiQzyTPugRSnj6cBgKg==" - }, - "node_modules/emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", - "dev": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", - "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", - "dependencies": { - "stackframe": "^1.1.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/exec-sh": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", - "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==" - }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" - }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/import-local": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", - "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", - "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "optional": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-reports": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.3.tgz", - "integrity": "sha512-0i77ZFLsb9U3DHi22WzmIngVzfoyxxbQcZRqlF3KoKmCJGq9nhFHoGi8FqBztN2rE8w6hURnZghetn0xpkVb6A==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "dependencies": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "fsevents": "^2.1.2" - } - }, - "node_modules/jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-junit": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-6.4.0.tgz", - "integrity": "sha512-GXEZA5WBeUich94BARoEUccJumhCgCerg7mXDFLxWwI2P7wL3Z7sGWk+53x343YdBLjiMR9aD/gYMVKO+0pE4Q==", - "dev": true, - "dependencies": { - "jest-validate": "^24.0.0", - "mkdirp": "^0.5.1", - "strip-ansi": "^4.0.0", - "xml": "^1.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/jest-junit/node_modules/@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jest-junit/node_modules/@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-junit/node_modules/@types/yargs": { - "version": "13.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.12.tgz", - "integrity": "sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-junit/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/jest-junit/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/jest-junit/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/jest-junit/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/jest-junit/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/jest-junit/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/jest-junit/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/jest-junit/node_modules/jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jest-junit/node_modules/jest-validate": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", - "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==", - "dev": true, - "dependencies": { - "@jest/types": "^24.9.0", - "camelcase": "^5.3.1", - "chalk": "^2.0.1", - "jest-get-type": "^24.9.0", - "leven": "^3.1.0", - "pretty-format": "^24.9.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jest-junit/node_modules/pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", - "dev": true, - "dependencies": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jest-junit/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "node_modules/jest-junit/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/jest-junit/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/jest-junit/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dependencies": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - }, - "bin": { - "jest-runtime": "bin/jest-runtime.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "dependencies": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dev": true, - "dependencies": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" - }, - "node_modules/js-cookie": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", - "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js2xmlparser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", - "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", - "dev": true, - "dependencies": { - "xmlcreate": "^2.0.3" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "node_modules/jsdoc": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz", - "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.9.4", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.1", - "klaw": "^3.0.0", - "markdown-it": "^10.0.0", - "markdown-it-anchor": "^5.2.7", - "marked": "^2.0.3", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.1" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=8.15.0" - } - }, - "node_modules/jsdoc/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-logic-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/json-logic-js/-/json-logic-js-2.0.1.tgz", - "integrity": "sha512-J3hhqM4IY66sL8qyzU7cwLmTAt3kA6ZsYxyuZBEwhcc+OYPTmAHc64fBTXHT6K5RwFeUqJUX1tfO7wpKsUx+9A==" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", - "dev": true, - "bin": { - "lcov-parse": "bin/cli.js" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "node_modules/linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash._baseiteratee": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", - "integrity": "sha1-NKm1VDVycnw9sueO2uPA6eZr0QI=", - "dependencies": { - "lodash._stringtopath": "~4.8.0" - } - }, - "node_modules/lodash._basetostring": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", - "integrity": "sha1-kyfJ3FFYhmt/pLnUL0Y45XZt2d8=" - }, - "node_modules/lodash._baseuniq": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", - "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", - "dependencies": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" - } - }, - "node_modules/lodash._createset": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", - "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=" - }, - "node_modules/lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" - }, - "node_modules/lodash._stringtopath": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", - "integrity": "sha1-lBvPDmQmbl/B1m/tCmlZVExXaCQ=", - "dependencies": { - "lodash._basetostring": "~4.12.0" - } - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" - }, - "node_modules/lodash.uniqby": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", - "integrity": "sha1-o6F7v2LutiQPSRhG6XwcTipeHiE=", - "dependencies": { - "lodash._baseiteratee": "~4.7.0", - "lodash._baseuniq": "~4.6.0" - } - }, - "node_modules/log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true, - "engines": { - "node": ">=0.8.6" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dependencies": { - "tmpl": "1.0.x" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/markdown-it-anchor": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", - "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", - "dev": true, - "peerDependencies": { - "markdown-it": "*" - } - }, - "node_modules/marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", - "dev": true, - "bin": { - "marked": "bin/marked" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", - "dependencies": { - "mime-db": "1.50.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" - }, - "node_modules/node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/node-notifier": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", - "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", - "dev": true, - "optional": true, - "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - } - }, - "node_modules/node-notifier/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "optional": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-releases": { - "version": "1.1.77", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", - "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==" - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dependencies": { - "node-modules-regexp": "^1.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/platform": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", - "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==" - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/proper-lockfile": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-2.0.1.tgz", - "integrity": "sha1-FZ+wYZPTIAP0s2kd0uwaY0qoDR0=", - "dependencies": { - "graceful-fs": "^4.1.2", - "retry": "^0.10.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/quickhull": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/quickhull/-/quickhull-1.0.3.tgz", - "integrity": "sha512-AQbLaXdzGDJdO9Mu3qY/NY5JWlDqIutCLW8vJbsQTq+/bydIZeltnMVRKCElp81Y5/uRm4Yw/RsMdcltFYsS6w==" - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/request/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, - "node_modules/requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated" - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", - "engines": { - "node": "*" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "engines": { - "node": "6.* || >= 7.*" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "deprecated": "some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added", - "dependencies": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "bin": { - "sane": "src/cli.js" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/sane/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/sane/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/sane/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sane/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sane/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sane/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "engines": { - "node": ">=4" - } - }, - "node_modules/sane/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/sane/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "node_modules/signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==" - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "node_modules/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stackframe": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", - "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/store": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/store/-/store-2.0.12.tgz", - "integrity": "sha1-jFNOKguDH3K3X8XxEZhXxE711ZM=", - "engines": { - "node": "*" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", - "dev": true - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tus-js-client": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-2.3.0.tgz", - "integrity": "sha512-I4cSwm6N5qxqCmBqenvutwSHe9ntf81lLrtf6BmLpG2v4wTl89atCQKqGgqvkodE6Lx+iKIjMbaXmfvStTg01g==", - "dependencies": { - "buffer-from": "^0.1.1", - "combine-errors": "^3.0.3", - "is-stream": "^2.0.0", - "js-base64": "^2.6.1", - "lodash.throttle": "^4.1.1", - "proper-lockfile": "^2.0.1", - "url-parse": "^1.4.3" - } - }, - "node_modules/tus-js-client/node_modules/buffer-from": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", - "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==" - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "node_modules/underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", - "dev": true - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated" - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", - "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dependencies": { - "makeerror": "1.0.x" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "engines": { - "node": ">=10.4" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", - "dev": true - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" - }, - "node_modules/xmlcreate": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", - "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", - "dev": true - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==" - }, - "@babel/core": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz", - "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", - "requires": { - "@babel/code-frame": "^7.15.8", - "@babel/generator": "^7.15.8", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.8", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.8", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", - "requires": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", - "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", - "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", - "requires": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==" - }, - "@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==" - }, - "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==" - }, - "@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", - "requires": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==" - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - } - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==" - }, - "@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "requires": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - } - }, - "@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "requires": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - } - }, - "@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "requires": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - } - }, - "@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "node-notifier": "^8.0.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - } - }, - "@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - } - }, - "@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "requires": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "requires": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - } - }, - "@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - } - }, - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" - }, - "@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/node": { - "version": "16.10.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz", - "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==" - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "@types/prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==" - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" - }, - "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" - }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" - }, - "acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==" - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" - } - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - }, - "babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "requires": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "requires": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-or-node": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz", - "integrity": "sha512-0F2z/VSnLbmEeBcUrSuDH5l0HxTXdQQzLjkmBR4cYfvg1zJrKSlmIZFqyFR8oX0NrwPhy3c3HQ6i3OxMbew4Tg==" - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" - }, - "browserslist": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.3.tgz", - "integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==", - "requires": { - "caniuse-lite": "^1.0.30001264", - "electron-to-chromium": "^1.3.857", - "escalade": "^3.1.1", - "node-releases": "^1.1.77", - "picocolors": "^0.2.1" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "caniuse-lite": { - "version": "1.0.30001265", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz", - "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==" - }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "requires": { - "rsvp": "^4.8.4" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==" - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "combine-errors": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", - "integrity": "sha1-9N9nQAg+VwOjGBEQwrEFUfAD2oY=", - "requires": { - "custom-error-instance": "2.1.1", - "lodash.uniqby": "4.5.0" - } - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, - "requires": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" - } - } - }, - "custom-error-instance": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", - "integrity": "sha1-PPY5FIemYppiR+sMoM4ACBt+Nho=" - }, - "cvat-data": { - "version": "file:../cvat-data", - "requires": { - "async-mutex": "^0.3.2", - "jszip": "3.7.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "detect-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.1.tgz", - "integrity": "sha512-eAcRiEPTs7utXWPaAgu/OX1HRJpxW7xSHpw4LTDrGFaeWnJ37HRlqpUkKsDm0AoTbtrvHQhH+5U2Cd87EGhJTg==" - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==" - }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==" - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" - } - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "electron-to-chromium": { - "version": "1.3.861", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.861.tgz", - "integrity": "sha512-GZyflmpMnZRdZ1e2yAyvuFwz1MPSVQelwHX4TJZyXypB8NcxdPvPNwy5lOTxnlkrK13EiQzyTPugRSnj6cBgKg==" - }, - "emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "error-stack-parser": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", - "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", - "requires": { - "stackframe": "^1.1.1" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "exec-sh": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", - "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==" - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "requires": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "requires": { - "bser": "2.1.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "import-local": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", - "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", - "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "optional": true - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==" - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.3.tgz", - "integrity": "sha512-0i77ZFLsb9U3DHi22WzmIngVzfoyxxbQcZRqlF3KoKmCJGq9nhFHoGi8FqBztN2rE8w6hURnZghetn0xpkVb6A==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" - } - }, - "jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - } - }, - "jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - } - }, - "jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - } - }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - } - }, - "jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - } - }, - "jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - } - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" - }, - "jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "requires": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - } - }, - "jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - } - }, - "jest-junit": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-6.4.0.tgz", - "integrity": "sha512-GXEZA5WBeUich94BARoEUccJumhCgCerg7mXDFLxWwI2P7wL3Z7sGWk+53x343YdBLjiMR9aD/gYMVKO+0pE4Q==", - "dev": true, - "requires": { - "jest-validate": "^24.0.0", - "mkdirp": "^0.5.1", - "strip-ansi": "^4.0.0", - "xml": "^1.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "@types/yargs": { - "version": "13.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.12.tgz", - "integrity": "sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", - "dev": true - }, - "jest-validate": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", - "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "camelcase": "^5.3.1", - "chalk": "^2.0.1", - "jest-get-type": "^24.9.0", - "leven": "^3.1.0", - "pretty-format": "^24.9.0" - } - }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "requires": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - } - }, - "jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*" - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "requires": {} - }, - "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==" - }, - "jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - } - }, - "jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - } - }, - "jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - } - }, - "jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, - "jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, - "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" - } - } - }, - "jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dev": true, - "requires": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - } - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - } - }, - "js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" - }, - "js-cookie": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", - "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "js2xmlparser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", - "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", - "dev": true, - "requires": { - "xmlcreate": "^2.0.3" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdoc": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz", - "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==", - "dev": true, - "requires": { - "@babel/parser": "^7.9.4", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.1", - "klaw": "^3.0.0", - "markdown-it": "^10.0.0", - "markdown-it-anchor": "^5.2.7", - "marked": "^2.0.3", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.1" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, - "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-logic-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/json-logic-js/-/json-logic-js-2.0.1.tgz", - "integrity": "sha512-J3hhqM4IY66sL8qyzU7cwLmTAt3kA6ZsYxyuZBEwhcc+OYPTmAHc64fBTXHT6K5RwFeUqJUX1tfO7wpKsUx+9A==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, - "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash._baseiteratee": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", - "integrity": "sha1-NKm1VDVycnw9sueO2uPA6eZr0QI=", - "requires": { - "lodash._stringtopath": "~4.8.0" - } - }, - "lodash._basetostring": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", - "integrity": "sha1-kyfJ3FFYhmt/pLnUL0Y45XZt2d8=" - }, - "lodash._baseuniq": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", - "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", - "requires": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" - } - }, - "lodash._createset": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", - "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=" - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" - }, - "lodash._stringtopath": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", - "integrity": "sha1-lBvPDmQmbl/B1m/tCmlZVExXaCQ=", - "requires": { - "lodash._basetostring": "~4.12.0" - } - }, - "lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" - }, - "lodash.uniqby": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", - "integrity": "sha1-o6F7v2LutiQPSRhG6XwcTipeHiE=", - "requires": { - "lodash._baseiteratee": "~4.7.0", - "lodash._baseuniq": "~4.6.0" - } - }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "requires": { - "tmpl": "1.0.x" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "requires": { - "object-visit": "^1.0.0" - } - }, - "markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "markdown-it-anchor": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", - "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", - "dev": true, - "requires": {} - }, - "marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", - "dev": true - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" - }, - "mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", - "requires": { - "mime-db": "1.50.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" - }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" - }, - "node-notifier": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", - "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", - "dev": true, - "optional": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "optional": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "node-releases": { - "version": "1.1.77", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", - "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==" - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "requires": { - "isobject": "^3.0.0" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "isobject": "^3.0.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "platform": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", - "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==" - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "proper-lockfile": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-2.0.1.tgz", - "integrity": "sha1-FZ+wYZPTIAP0s2kd0uwaY0qoDR0=", - "requires": { - "graceful-fs": "^4.1.2", - "retry": "^0.10.0" - } - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "quickhull": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/quickhull/-/quickhull-1.0.3.tgz", - "integrity": "sha512-AQbLaXdzGDJdO9Mu3qY/NY5JWlDqIutCLW8vJbsQTq+/bydIZeltnMVRKCElp81Y5/uRm4Yw/RsMdcltFYsS6w==" - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" - } - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, - "requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "requires": { - "xmlchars": "^2.2.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - } - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==" - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "requires": { - "escape-string-regexp": "^2.0.0" - } - }, - "stackframe": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", - "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "store": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/store/-/store-2.0.12.tgz", - "integrity": "sha1-jFNOKguDH3K3X8XxEZhXxE711ZM=" - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", - "dev": true - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - } - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "requires": { - "punycode": "^2.1.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tus-js-client": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-2.3.0.tgz", - "integrity": "sha512-I4cSwm6N5qxqCmBqenvutwSHe9ntf81lLrtf6BmLpG2v4wTl89atCQKqGgqvkodE6Lx+iKIjMbaXmfvStTg01g==", - "requires": { - "buffer-from": "^0.1.1", - "combine-errors": "^3.0.3", - "is-stream": "^2.0.0", - "js-base64": "^2.6.1", - "lodash.throttle": "^4.1.1", - "proper-lockfile": "^2.0.1", - "url-parse": "^1.4.3" - }, - "dependencies": { - "buffer-from": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", - "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==" - } - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", - "dev": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - } - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - } - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true - }, - "v8-to-istanbul": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", - "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "requires": { - "makeerror": "1.0.x" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" - }, - "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "requires": {} - }, - "xml": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", - "dev": true - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" - }, - "xmlcreate": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", - "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", - "dev": true - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } -} diff --git a/cvat-core/package.json b/cvat-core/package.json index 55f6408e92ca..1afc44f64364 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,15 +1,17 @@ { "name": "cvat-core", - "version": "5.0.1", + "version": "7.3.0", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", - "main": "babel.config.js", + "main": "src/api.ts", "scripts": { "build": "webpack", "test": "jest --config=jest.config.js --coverage", "docs": "jsdoc --readme README.md src/*.js -p -c jsdoc.config.js -d docs", - "coveralls": "cat ./reports/coverage/lcov.info | coveralls" + "coveralls": "cat ./reports/coverage/lcov.info | coveralls", + "type-check": "tsc --noEmit", + "type-check:watch": "yarn run type-check --watch" }, - "author": "Intel", + "author": "CVAT.ai", "license": "MIT", "browserslist": [ "Chrome >= 63", @@ -21,21 +23,22 @@ "coveralls": "^3.0.5", "jest": "^26.6.3", "jest-junit": "^6.4.0", - "jsdoc": "^3.6.6" + "jsdoc": "^3.6.6", + "ts-jest": "26" }, "dependencies": { - "axios": "^0.21.4", - "browser-or-node": "^1.2.1", - "cvat-data": "../cvat-data", + "axios": "^0.27.2", + "browser-or-node": "^2.0.0", + "cvat-data": "link:./../cvat-data", "detect-browser": "^5.2.1", "error-stack-parser": "^2.0.2", - "form-data": "^2.5.0", - "jest-config": "^26.6.3", - "js-cookie": "^2.2.0", + "form-data": "^4.0.0", + "jest-config": "^29.0.3", + "js-cookie": "^3.0.1", "json-logic-js": "^2.0.1", "platform": "^1.3.5", "quickhull": "^1.0.3", "store": "^2.0.12", - "tus-js-client": "^2.3.0" + "tus-js-client": "^3.0.1" } } diff --git a/cvat-core/src/annotation-formats.js b/cvat-core/src/annotation-formats.js deleted file mode 100644 index 0bad3b7fb8a2..000000000000 --- a/cvat-core/src/annotation-formats.js +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (C) 2019-2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - /** - * Class representing an annotation loader - * @memberof module:API.cvat.classes - * @hideconstructor - */ - class Loader { - constructor(initialData) { - const data = { - name: initialData.name, - format: initialData.ext, - version: initialData.version, - enabled: initialData.enabled, - dimension: initialData.dimension, - }; - - Object.defineProperties(this, { - name: { - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Loader - * @readonly - * @instance - */ - get: () => data.name, - }, - format: { - /** - * @name format - * @type {string} - * @memberof module:API.cvat.classes.Loader - * @readonly - * @instance - */ - get: () => data.format, - }, - version: { - /** - * @name version - * @type {string} - * @memberof module:API.cvat.classes.Loader - * @readonly - * @instance - */ - get: () => data.version, - }, - enabled: { - /** - * @name enabled - * @type {string} - * @memberof module:API.cvat.classes.Loader - * @readonly - * @instance - */ - get: () => data.enabled, - }, - dimension: { - /** - * @name dimension - * @type {string} - * @memberof module:API.cvat.enums.DimensionType - * @readonly - * @instance - */ - get: () => data.dimension, - }, - }); - } - } - - /** - * Class representing an annotation dumper - * @memberof module:API.cvat.classes - * @hideconstructor - */ - class Dumper { - constructor(initialData) { - const data = { - name: initialData.name, - format: initialData.ext, - version: initialData.version, - enabled: initialData.enabled, - dimension: initialData.dimension, - }; - - Object.defineProperties(this, { - name: { - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Dumper - * @readonly - * @instance - */ - get: () => data.name, - }, - format: { - /** - * @name format - * @type {string} - * @memberof module:API.cvat.classes.Dumper - * @readonly - * @instance - */ - get: () => data.format, - }, - version: { - /** - * @name version - * @type {string} - * @memberof module:API.cvat.classes.Dumper - * @readonly - * @instance - */ - get: () => data.version, - }, - enabled: { - /** - * @name enabled - * @type {string} - * @memberof module:API.cvat.classes.Loader - * @readonly - * @instance - */ - get: () => data.enabled, - }, - dimension: { - /** - * @name dimension - * @type {string} - * @memberof module:API.cvat.enums.DimensionType - * @readonly - * @instance - */ - get: () => data.dimension, - }, - }); - } - } - - /** - * Class representing an annotation format - * @memberof module:API.cvat.classes - * @hideconstructor - */ - class AnnotationFormats { - constructor(initialData) { - const data = { - exporters: initialData.exporters.map((el) => new Dumper(el)), - importers: initialData.importers.map((el) => new Loader(el)), - }; - - // Now all fields are readonly - Object.defineProperties(this, { - loaders: { - /** - * @name loaders - * @type {module:API.cvat.classes.Loader[]} - * @memberof module:API.cvat.classes.AnnotationFormats - * @readonly - * @instance - */ - get: () => [...data.importers], - }, - dumpers: { - /** - * @name dumpers - * @type {module:API.cvat.classes.Dumper[]} - * @memberof module:API.cvat.classes.AnnotationFormats - * @readonly - * @instance - */ - get: () => [...data.exporters], - }, - }); - } - } - - module.exports = { - AnnotationFormats, - Loader, - Dumper, - }; -})(); diff --git a/cvat-core/src/annotation-formats.ts b/cvat-core/src/annotation-formats.ts new file mode 100644 index 000000000000..f8af7f3ca6f1 --- /dev/null +++ b/cvat-core/src/annotation-formats.ts @@ -0,0 +1,210 @@ +// Copyright (C) 2019-2022 Intel Corporation +// +// SPDX-License-Identifier: MIT + +interface RawLoaderData { + name: string; + ext: string; + version: string; + enabled: boolean; + dimension: '2d' | '3d'; +} + +/** + * Class representing an annotation loader + * @memberof module:API.cvat.classes + * @hideconstructor +*/ +export class Loader { + public name: string; + public format: string; + public version: string; + public enabled: boolean; + public dimension: '2d' | '3d'; + + constructor(initialData: RawLoaderData) { + const data = { + name: initialData.name, + format: initialData.ext, + version: initialData.version, + enabled: initialData.enabled, + dimension: initialData.dimension, + }; + + Object.defineProperties(this, { + name: { + /** + * @name name + * @type {string} + * @memberof module:API.cvat.classes.Loader + * @readonly + * @instance + */ + get: () => data.name, + }, + format: { + /** + * @name format + * @type {string} + * @memberof module:API.cvat.classes.Loader + * @readonly + * @instance + */ + get: () => data.format, + }, + version: { + /** + * @name version + * @type {string} + * @memberof module:API.cvat.classes.Loader + * @readonly + * @instance + */ + get: () => data.version, + }, + enabled: { + /** + * @name enabled + * @type {boolean} + * @memberof module:API.cvat.classes.Loader + * @readonly + * @instance + */ + get: () => data.enabled, + }, + dimension: { + /** + * @name dimension + * @type {module:API.cvat.enums.DimensionType} + * @memberof module:API.cvat.classes.Loader + * @readonly + * @instance + */ + get: () => data.dimension, + }, + }); + } +} + +type RawDumperData = RawLoaderData; + +/** + * Class representing an annotation dumper + * @memberof module:API.cvat.classes + * @hideconstructor + */ +export class Dumper { + public name: string; + public format: string; + public version: string; + public enabled: boolean; + public dimension: '2d' | '3d'; + + constructor(initialData: RawDumperData) { + const data = { + name: initialData.name, + format: initialData.ext, + version: initialData.version, + enabled: initialData.enabled, + dimension: initialData.dimension, + }; + + Object.defineProperties(this, { + name: { + /** + * @name name + * @type {string} + * @memberof module:API.cvat.classes.Dumper + * @readonly + * @instance + */ + get: () => data.name, + }, + format: { + /** + * @name format + * @type {string} + * @memberof module:API.cvat.classes.Dumper + * @readonly + * @instance + */ + get: () => data.format, + }, + version: { + /** + * @name version + * @type {string} + * @memberof module:API.cvat.classes.Dumper + * @readonly + * @instance + */ + get: () => data.version, + }, + enabled: { + /** + * @name enabled + * @type {boolean} + * @memberof module:API.cvat.classes.Dumper + * @readonly + * @instance + */ + get: () => data.enabled, + }, + dimension: { + /** + * @name dimension + * @type {module:API.cvat.enums.DimensionType} + * @memberof module:API.cvat.classes.Dumper + * @readonly + * @instance + */ + get: () => data.dimension, + }, + }); + } +} + +interface AnnotationFormatRawData { + importers: RawLoaderData[]; + exporters: RawDumperData[]; +} + +/** + * Class representing an annotation format + * @memberof module:API.cvat.classes + * @hideconstructor + */ +export class AnnotationFormats { + public loaders: Loader[]; + public dumpers: Dumper[]; + + constructor(initialData: AnnotationFormatRawData) { + const data = { + exporters: initialData.exporters.map((el) => new Dumper(el)), + importers: initialData.importers.map((el) => new Loader(el)), + }; + + Object.defineProperties(this, { + loaders: { + /** + * @name loaders + * @type {module:API.cvat.classes.Loader[]} + * @memberof module:API.cvat.classes.AnnotationFormats + * @readonly + * @instance + */ + get: () => [...data.importers], + }, + dumpers: { + /** + * @name dumpers + * @type {module:API.cvat.classes.Dumper[]} + * @memberof module:API.cvat.classes.AnnotationFormats + * @readonly + * @instance + */ + get: () => [...data.exporters], + }, + }); + } +} diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.ts similarity index 65% rename from cvat-core/src/annotations-collection.js rename to cvat-core/src/annotations-collection.ts index 31892dc1fb0f..118b8b0fc321 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.ts @@ -1,103 +1,28 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT (() => { const { - RectangleShape, - PolygonShape, - PolylineShape, - PointsShape, - EllipseShape, - CuboidShape, - RectangleTrack, - PolygonTrack, - PolylineTrack, - PointsTrack, - EllipseTrack, - CuboidTrack, + shapeFactory, + trackFactory, Track, Shape, Tag, - objectStateFactory, } = require('./annotations-objects'); - const AnnotationsFilter = require('./annotations-filter'); + const AnnotationsFilter = require('./annotations-filter').default; const { checkObjectType } = require('./common'); - const Statistics = require('./statistics'); + const Statistics = require('./statistics').default; const { Label } = require('./labels'); - const { DataError, ArgumentError, ScriptingError } = require('./exceptions'); + const { ArgumentError, ScriptingError } = require('./exceptions'); + const ObjectState = require('./object-state').default; + const { mask2Rle, truncateMask } = require('./object-utils'); + const config = require('./config').default; const { - HistoryActions, ObjectShape, ObjectType, colors, + HistoryActions, ShapeType, ObjectType, colors, Source, } = require('./enums'); - const ObjectState = require('./object-state'); - - function shapeFactory(shapeData, clientID, injection) { - const { type } = shapeData; - const color = colors[clientID % colors.length]; - - let shapeModel = null; - switch (type) { - case 'rectangle': - shapeModel = new RectangleShape(shapeData, clientID, color, injection); - break; - case 'polygon': - shapeModel = new PolygonShape(shapeData, clientID, color, injection); - break; - case 'polyline': - shapeModel = new PolylineShape(shapeData, clientID, color, injection); - break; - case 'points': - shapeModel = new PointsShape(shapeData, clientID, color, injection); - break; - case 'ellipse': - shapeModel = new EllipseShape(shapeData, clientID, color, injection); - break; - case 'cuboid': - shapeModel = new CuboidShape(shapeData, clientID, color, injection); - break; - default: - throw new DataError(`An unexpected type of shape "${type}"`); - } - - return shapeModel; - } - - function trackFactory(trackData, clientID, injection) { - if (trackData.shapes.length) { - const { type } = trackData.shapes[0]; - const color = colors[clientID % colors.length]; - - let trackModel = null; - switch (type) { - case 'rectangle': - trackModel = new RectangleTrack(trackData, clientID, color, injection); - break; - case 'polygon': - trackModel = new PolygonTrack(trackData, clientID, color, injection); - break; - case 'polyline': - trackModel = new PolylineTrack(trackData, clientID, color, injection); - break; - case 'points': - trackModel = new PointsTrack(trackData, clientID, color, injection); - break; - case 'ellipse': - trackModel = new EllipseTrack(trackData, clientID, color, injection); - break; - case 'cuboid': - trackModel = new CuboidTrack(trackData, clientID, color, injection); - break; - default: - throw new DataError(`An unexpected type of track "${type}"`); - } - - return trackModel; - } - - console.warn('The track without any shapes had been found. It was ignored.'); - return null; - } class Collection { constructor(data) { @@ -107,6 +32,10 @@ this.labels = data.labels.reduce((labelAccumulator, label) => { labelAccumulator[label.id] = label; + (label?.structure?.sublabels || []).forEach((sublabel) => { + labelAccumulator[sublabel.id] = sublabel; + }); + return labelAccumulator; }, {}); @@ -126,7 +55,10 @@ groups: this.groups, frameMeta: this.frameMeta, history: this.history, + nextClientID: () => ++this.count, groupColors: {}, + getMasksOnFrame: (frame: number) => this.shapes[frame] + .filter((object) => object.objectShape === ObjectType.MASK), }; } @@ -165,9 +97,8 @@ // In this case a corresponded message will be sent to the console if (trackModel) { this.tracks.push(trackModel); - this.objects[clientID] = trackModel; - result.tracks.push(trackModel); + this.objects[clientID] = trackModel; } } @@ -178,15 +109,15 @@ const data = { tracks: this.tracks.filter((track) => !track.removed).map((track) => track.toJSON()), shapes: Object.values(this.shapes) - .reduce((accumulator, value) => { - accumulator.push(...value); + .reduce((accumulator, frameShapes) => { + accumulator.push(...frameShapes); return accumulator; }, []) .filter((shape) => !shape.removed) .map((shape) => shape.toJSON()), tags: Object.values(this.tags) - .reduce((accumulator, value) => { - accumulator.push(...value); + .reduce((accumulator, frameTags) => { + accumulator.push(...frameTags); return accumulator; }, []) .filter((tag) => !tag.removed) @@ -202,10 +133,7 @@ const tags = this.tags[frame] || []; const objects = [].concat(tracks, shapes, tags); - const visible = { - models: [], - data: [], - }; + const visible = []; for (const object of objects) { if (object.removed) { @@ -213,21 +141,19 @@ } const stateData = object.get(frame); - if (!allTracks && stateData.outside && !stateData.keyframe) { + if (stateData.outside && !stateData.keyframe && !allTracks && object instanceof Track) { continue; } - visible.models.push(object); - visible.data.push(stateData); + visible.push(stateData); } const objectStates = []; - const filtered = this.annotationsFilter.filter(visible.data, filters); + const filtered = this.annotationsFilter.filter(visible, filters); - visible.data.forEach((stateData, idx) => { + visible.forEach((stateData, idx) => { if (!filters.length || filtered.includes(stateData.clientID)) { - const model = visible.models[idx]; - const objectState = objectStateFactory.call(model, frame, stateData); + const objectState = new ObjectState(stateData); objectStates.push(objectState); } }); @@ -235,27 +161,11 @@ return objectStates; } - merge(objectStates) { - checkObjectType('shapes for merge', objectStates, null, Array); - if (!objectStates.length) return; - const objectsForMerge = objectStates.map((state) => { - checkObjectType('object state', state, null, ObjectState); - const object = this.objects[state.clientID]; - if (typeof object === 'undefined') { - throw new ArgumentError( - 'The object is not in collection yet. Call ObjectState.put([state]) before you can merge it', - ); - } - return object; - }); - + _mergeInternal(objectsForMerge, shapeType, label): any { const keyframes = {}; // frame: position - const { label, shapeType } = objectStates[0]; - if (!(label.id in this.labels)) { - throw new ArgumentError(`Unknown label for the task: ${label.id}`); - } + const elements = {}; // element_sublabel_id: [element], each sublabel will be merged recursively - if (!Object.values(ObjectShape).includes(shapeType)) { + if (!Object.values(ShapeType).includes(shapeType)) { throw new ArgumentError(`Got unknown shapeType "${shapeType}"`); } @@ -267,15 +177,16 @@ for (let i = 0; i < objectsForMerge.length; i++) { // For each state get corresponding object const object = objectsForMerge[i]; - const state = objectStates[i]; - if (state.label.id !== label.id) { + if (object.label.id !== label.id) { throw new ArgumentError( - `All shape labels are expected to be ${label.name}, but got ${state.label.name}`, + `All object labels are expected to be "${label.name}", but got "${object.label.name}"`, ); } - if (state.shapeType !== shapeType) { - throw new ArgumentError(`All shapes are expected to be ${shapeType}, but got ${state.shapeType}`); + if (object.shapeType !== shapeType) { + throw new ArgumentError( + `All shapes are expected to be "${shapeType}", but got "${object.shapeType}"`, + ); } // If this object is shape, get it position and save as a keyframe @@ -288,10 +199,10 @@ keyframes[object.frame] = { type: shapeType, frame: object.frame, - points: [...object.points], + points: object.shapeType === ShapeType.SKELETON ? undefined : [...object.points], occluded: object.occluded, rotation: object.rotation, - zOrder: object.zOrder, + z_order: object.zOrder, outside: false, attributes: Object.keys(object.attributes).reduce((accumulator, attrID) => { // We save only mutable attributes inside a keyframe @@ -306,18 +217,24 @@ }; // Push outside shape after each annotation shape - // Any not outside shape rewrites it + // Any not outside shape will rewrite it later if (!(object.frame + 1 in keyframes) && object.frame + 1 <= this.stopFrame) { keyframes[object.frame + 1] = JSON.parse(JSON.stringify(keyframes[object.frame])); keyframes[object.frame + 1].outside = true; keyframes[object.frame + 1].frame++; + keyframes[object.frame + 1].attributes = []; + (keyframes[object.frame + 1].elements || []).forEach((el) => { + el.outside = keyframes[object.frame + 1].outside; + el.frame = keyframes[object.frame + 1].frame; + }); } } else if (object instanceof Track) { - // If this object is track, iterate through all its + // If this object is a track, iterate through all its // keyframes and push copies to new keyframes const attributes = {}; // id:value - for (const keyframe of Object.keys(object.shapes)) { - const shape = object.shapes[keyframe]; + const trackShapes = object.shapes; + for (const keyframe of Object.keys(trackShapes)) { + const shape = trackShapes[keyframe]; // Frame already saved and it is not outside if (keyframe in keyframes && !keyframes[keyframe].outside) { // This shape is outside and non-outside shape already exists @@ -341,11 +258,11 @@ keyframes[keyframe] = { type: shapeType, frame: +keyframe, - points: [...shape.points], + points: object.shapeType === ShapeType.SKELETON ? undefined : [...shape.points], rotation: shape.rotation, occluded: shape.occluded, outside: shape.outside, - zOrder: shape.zOrder, + z_order: shape.zOrder, attributes: updatedAttributes ? Object.keys(attributes).reduce((accumulator, attrID) => { accumulator.push({ spec_id: +attrID, @@ -362,6 +279,37 @@ 'Only shapes and tracks are expected.', ); } + + if (object.shapeType === ShapeType.SKELETON) { + for (const element of object.elements) { + // for each track/shape element get its first objectState and keep it + elements[element.label.id] = [ + ...(elements[element.label.id] || []), element, + ]; + } + } + } + + const mergedElements = []; + if (shapeType === ShapeType.SKELETON) { + for (const sublabel of label.structure.sublabels) { + if (!(sublabel.id in elements)) { + throw new ArgumentError( + `Merged skeleton is absent some of its elements (sublabel id: ${sublabel.id})`, + ); + } + + try { + mergedElements.push(this._mergeInternal( + elements[sublabel.id], elements[sublabel.id][0].shapeType, sublabel, + )); + } catch (error) { + throw new ArgumentError( + `Could not merge some skeleton parts (sublabel id: ${sublabel.id}). + Original error is ${error.toString()}`, + ); + } + } } let firstNonOutside = false; @@ -375,21 +323,21 @@ } } - const clientID = ++this.count; const track = { frame: Math.min.apply( null, Object.keys(keyframes).map((frame) => +frame), ), shapes: Object.values(keyframes), + elements: shapeType === ShapeType.SKELETON ? mergedElements : undefined, group: 0, - source: objectStates[0].source, + source: Source.MANUAL, label_id: label.id, - attributes: Object.keys(objectStates[0].attributes).reduce((accumulator, attrID) => { + attributes: Object.keys(objectsForMerge[0].attributes).reduce((accumulator, attrID) => { if (!labelAttributes[attrID].mutable) { accumulator.push({ spec_id: +attrID, - value: objectStates[0].attributes[attrID], + value: objectsForMerge[0].attributes[attrID], }); } @@ -397,67 +345,102 @@ }, []), }; - const trackModel = trackFactory(track, clientID, this.injection); - this.tracks.push(trackModel); - this.objects[clientID] = trackModel; + return track; + } + + merge(objectStates) { + checkObjectType('shapes for merge', objectStates, null, Array); + if (!objectStates.length) return; + const objectsForMerge = objectStates.map((state) => { + checkObjectType('object state', state, null, ObjectState); + const object = this.objects[state.clientID]; + if (typeof object === 'undefined') { + throw new ArgumentError( + 'The object is not in collection yet. Call ObjectState.put([state]) before you can merge it', + ); + } + + if (state.shapeType === ShapeType.MASK) { + throw new ArgumentError( + 'Merging for masks is not supported', + ); + } + return object; + }); + + const { label, shapeType } = objectStates[0]; + if (!(label.id in this.labels)) { + throw new ArgumentError(`Unknown label for the task: ${label.id}`); + } + + const track = this._mergeInternal(objectsForMerge, shapeType, label); + const imported = this.import({ + tracks: [track], + tags: [], + shapes: [], + }); // Remove other shapes for (const object of objectsForMerge) { object.removed = true; } + const [importedTrack] = imported.tracks; this.history.do( HistoryActions.MERGED_OBJECTS, () => { - trackModel.removed = true; + importedTrack.removed = true; for (const object of objectsForMerge) { object.removed = false; } }, () => { - trackModel.removed = false; + importedTrack.removed = false; for (const object of objectsForMerge) { object.removed = true; } }, - [...objectsForMerge.map((object) => object.clientID), trackModel.clientID], + [...objectsForMerge.map((object) => object.clientID), importedTrack.clientID], objectStates[0].frame, ); } - split(objectState, frame) { - checkObjectType('object state', objectState, null, ObjectState); - checkObjectType('frame', frame, 'integer', null); - - const object = this.objects[objectState.clientID]; - if (typeof object === 'undefined') { - throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before'); - } - - if (objectState.objectType !== ObjectType.TRACK) { - return; - } - - const keyframes = Object.keys(object.shapes).sort((a, b) => +a - +b); - if (frame <= +keyframes[0]) { - return; - } - + _splitInternal(objectState, object, frame): ObjectState[] { const labelAttributes = object.label.attributes.reduce((accumulator, attribute) => { accumulator[attribute.id] = attribute; return accumulator; }, {}); - const exported = object.toJSON(); + // first clear all server ids which may exist in the object being splitted + const copy = trackFactory(object.toJSON(), -1, this.injection); + copy.clearServerID(); + const exported = copy.toJSON(); + + // then create two copies, before this frame and after this frame + const prev = { + frame: exported.frame, + group: 0, + label_id: exported.label_id, + attributes: exported.attributes, + shapes: [], + source: Source.MANUAL, + elements: [], + }; + + // after this frame copy is almost the same, except of starting frame + const next = JSON.parse(JSON.stringify(prev)); + next.frame = frame; + + // get position of the object on a frame where user does split and push it to next shape const position = { type: objectState.shapeType, - points: [...objectState.points], + points: objectState.shapeType === ShapeType.SKELETON ? undefined : [...objectState.points], rotation: objectState.rotation, occluded: objectState.occluded, outside: objectState.outside, - zOrder: objectState.zOrder, + z_order: objectState.zOrder, attributes: Object.keys(objectState.attributes).reduce((accumulator, attrID) => { - if (!labelAttributes[attrID].mutable) { + if (labelAttributes[attrID].mutable) { accumulator.push({ spec_id: +attrID, value: objectState.attributes[attrID], @@ -468,64 +451,66 @@ }, []), frame, }; - - const prev = { - frame: exported.frame, - group: 0, - label_id: exported.label_id, - attributes: exported.attributes, - shapes: [], - }; - - const next = JSON.parse(JSON.stringify(prev)); - next.frame = frame; - next.shapes.push(JSON.parse(JSON.stringify(position))); - exported.shapes.map((shape) => { - delete shape.id; + // split all shapes of an initial object into two groups (before/after the frame) + exported.shapes.forEach((shape) => { if (shape.frame < frame) { prev.shapes.push(JSON.parse(JSON.stringify(shape))); } else if (shape.frame > frame) { next.shapes.push(JSON.parse(JSON.stringify(shape))); } + }); + prev.shapes.push(JSON.parse(JSON.stringify(position))); + prev.shapes[prev.shapes.length - 1].outside = true; - return shape; + // do the same recursively for all objet elements if there are any + objectState.elements.forEach((elementState, idx) => { + const elementObject = object.elements[idx]; + const [prevEl, nextEl] = this._splitInternal(elementState, elementObject, frame); + prev.elements.push(prevEl); + next.elements.push(nextEl); }); - prev.shapes.push(position); - // add extra keyframe if no other keyframes before outside - if (!prev.shapes.some((shape) => shape.frame === frame - 1)) { - prev.shapes.push(JSON.parse(JSON.stringify(position))); - prev.shapes[prev.shapes.length - 2].frame -= 1; + return [prev, next]; + } + + split(objectState, frame) { + checkObjectType('object state', objectState, null, ObjectState); + checkObjectType('frame', frame, 'integer', null); + + const object = this.objects[objectState.clientID]; + if (typeof object === 'undefined') { + throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before'); } - prev.shapes[prev.shapes.length - 1].outside = true; - let clientID = ++this.count; - const prevTrack = trackFactory(prev, clientID, this.injection); - this.tracks.push(prevTrack); - this.objects[clientID] = prevTrack; + if (objectState.objectType !== ObjectType.TRACK) return; + const keyframes = Object.keys(object.shapes).sort((a, b) => +a - +b); + if (frame <= +keyframes[0]) return; - clientID = ++this.count; - const nextTrack = trackFactory(next, clientID, this.injection); - this.tracks.push(nextTrack); - this.objects[clientID] = nextTrack; + const [prev, next] = this._splitInternal(objectState, object, frame); + const imported = this.import({ + tracks: [prev, next], + tags: [], + shapes: [], + }); // Remove source object object.removed = true; + const [prevImported, nextImported] = imported.tracks; this.history.do( HistoryActions.SPLITTED_TRACK, () => { object.removed = false; - prevTrack.removed = true; - nextTrack.removed = true; + prevImported.removed = true; + nextImported.removed = true; }, () => { object.removed = true; - prevTrack.removed = false; - nextTrack.removed = false; + prevImported.removed = false; + nextImported.removed = false; }, - [object.clientID, prevTrack.clientID, nextTrack.clientID], + [object.clientID, prevImported.clientID, nextImported.clientID], frame, ); } @@ -546,6 +531,7 @@ const undoGroups = objectsForGroup.map((object) => object.group); for (const object of objectsForGroup) { object.group = groupIdx; + object.updated = Date.now(); } const redoGroups = objectsForGroup.map((object) => object.group); @@ -554,11 +540,13 @@ () => { objectsForGroup.forEach((object, idx) => { object.group = undoGroups[idx]; + object.updated = Date.now(); }); }, () => { objectsForGroup.forEach((object, idx) => { object.group = redoGroups[idx]; + object.updated = Date.now(); }); }, objectsForGroup.map((object) => object.clientID), @@ -579,8 +567,17 @@ tracks.forEach((track) => { if (track.frame <= endframe) { if (delTrackKeyframesOnly) { - for (const keyframe in track.shapes) { - if (keyframe >= startframe && keyframe <= endframe) { delete track.shapes[keyframe]; } + for (const keyframe of Object.keys(track.shapes)) { + if (+keyframe >= startframe && +keyframe <= endframe) { + delete track.shapes[keyframe]; + (track.elements || []).forEach((element) => { + if (keyframe in element.shapes) { + delete element.shapes[keyframe]; + element.updated = Date.now(); + } + }); + track.updated = Date.now(); + } } } else if (track.frame >= startframe) { const index = tracks.indexOf(track); @@ -593,7 +590,7 @@ this.shapes = {}; this.tags = {}; this.tracks = []; - this.objects = {}; // by id + this.objects = {}; this.count = 0; this.flush = true; @@ -606,42 +603,75 @@ statistics() { const labels = {}; - const skeleton = { - rectangle: { - shape: 0, - track: 0, - }, - polygon: { - shape: 0, - track: 0, - }, - polyline: { - shape: 0, - track: 0, - }, - points: { - shape: 0, - track: 0, - }, - ellipse: { - shape: 0, - track: 0, - }, - cuboid: { - shape: 0, - track: 0, - }, - tags: 0, + const shapes = ['rectangle', 'polygon', 'polyline', 'points', 'ellipse', 'cuboid', 'skeleton']; + const body = { + ...(shapes.reduce((acc, val) => ({ + ...acc, + [val]: { shape: 0, track: 0 }, + }), {})), + + mask: { shape: 0 }, + tag: 0, manually: 0, interpolated: 0, total: 0, }; - const total = JSON.parse(JSON.stringify(skeleton)); - for (const label of Object.values(this.labels)) { - const { name } = label; - labels[name] = JSON.parse(JSON.stringify(skeleton)); - } + const sep = '{{cvat.skeleton.lbl.sep}}'; + const fillBody = (spec, prefix = ''): void => { + const pref = prefix ? `${prefix}${sep}` : ''; + for (const label of spec) { + const { name } = label; + labels[`${pref}${name}`] = JSON.parse(JSON.stringify(body)); + + if (label?.structure?.sublabels) { + fillBody(label.structure.sublabels, `${pref}${name}`); + } + } + }; + + const total = JSON.parse(JSON.stringify(body)); + fillBody(Object.values(this.labels).filter((label) => !label.hasParent)); + + const scanTrack = (track, prefix = ''): void => { + const pref = prefix ? `${prefix}${sep}` : ''; + const label = `${pref}${track.label.name}`; + labels[label][track.shapeType].track++; + const keyframes = Object.keys(track.shapes) + .sort((a, b) => +a - +b) + .map((el) => +el); + + let prevKeyframe = keyframes[0]; + let visible = false; + for (const keyframe of keyframes) { + if (visible) { + const interpolated = keyframe - prevKeyframe - 1; + labels[label].interpolated += interpolated; + labels[label].total += interpolated; + } + visible = !track.shapes[keyframe].outside; + prevKeyframe = keyframe; + + if (visible) { + labels[label].manually++; + labels[label].total++; + } + } + + let lastKey = keyframes[keyframes.length - 1]; + if (track.shapeType === ShapeType.SKELETON) { + track.elements.forEach((element) => { + scanTrack(element, label); + lastKey = Math.max(lastKey, ...Object.keys(element.shapes).map((key) => +key)); + }); + } + + if (lastKey !== this.stopFrame && !track.get(lastKey).outside) { + const interpolated = this.stopFrame - lastKey; + labels[label].interpolated += interpolated; + labels[label].total += interpolated; + } + }; for (const object of Object.values(this.objects)) { if (object.removed) { @@ -659,59 +689,37 @@ throw new ScriptingError(`Unexpected object type: "${objectType}"`); } - const label = object.label.name; + const { name: label } = object.label; if (objectType === 'tag') { - labels[label].tags++; + labels[label].tag++; labels[label].manually++; labels[label].total++; + } else if (objectType === 'track') { + scanTrack(object); } else { const { shapeType } = object; - labels[label][shapeType][objectType]++; - - if (objectType === 'track') { - const keyframes = Object.keys(object.shapes) - .sort((a, b) => +a - +b) - .map((el) => +el); - - let prevKeyframe = keyframes[0]; - let visible = false; - - for (const keyframe of keyframes) { - if (visible) { - const interpolated = keyframe - prevKeyframe - 1; - labels[label].interpolated += interpolated; - labels[label].total += interpolated; - } - visible = !object.shapes[keyframe].outside; - prevKeyframe = keyframe; - - if (visible) { - labels[label].manually++; - labels[label].total++; - } - } - - const lastKey = keyframes[keyframes.length - 1]; - if (lastKey !== this.stopFrame && !object.shapes[lastKey].outside) { - const interpolated = this.stopFrame - lastKey; - labels[label].interpolated += interpolated; - labels[label].total += interpolated; - } - } else { - labels[label].manually++; - labels[label].total++; + labels[label][shapeType].shape++; + labels[label].manually++; + labels[label].total++; + if (shapeType === ShapeType.SKELETON) { + object.elements.forEach((element) => { + const combinedName = [label, element.label.name].join(sep); + labels[combinedName][element.shapeType].shape++; + labels[combinedName].manually++; + labels[combinedName].total++; + }); } } } for (const label of Object.keys(labels)) { - for (const key of Object.keys(labels[label])) { - if (typeof labels[label][key] === 'object') { - for (const objectType of Object.keys(labels[label][key])) { - total[key][objectType] += labels[label][key][objectType]; + for (const shapeType of Object.keys(labels[label])) { + if (typeof labels[label][shapeType] === 'object') { + for (const objectType of Object.keys(labels[label][shapeType])) { + total[shapeType][objectType] += labels[label][shapeType][objectType]; } } else { - total[key] += labels[label][key]; + total[shapeType] += labels[label][shapeType]; } } } @@ -744,7 +752,7 @@ for (const state of objectStates) { checkObjectType('object state', state, null, ObjectState); - checkObjectType('state client ID', state.clientID, 'undefined', null); + checkObjectType('state client ID', state.clientID, null, null); checkObjectType('state frame', state.frame, 'integer', null); checkObjectType('state rotation', state.rotation || 0, 'number', null); checkObjectType('state attributes', state.attributes, null, Object); @@ -775,9 +783,9 @@ checkObjectType('point coordinate', coord, 'number', null); } - if (!Object.values(ObjectShape).includes(state.shapeType)) { + if (!Object.values(ShapeType).includes(state.shapeType)) { throw new ArgumentError( - `Object shape must be one of: ${JSON.stringify(Object.values(ObjectShape))}`, + `Object shape must be one of: ${JSON.stringify(Object.values(ShapeType))}`, ); } @@ -789,11 +797,30 @@ group: 0, label_id: state.label.id, occluded: state.occluded || false, - points: [...state.points], + points: state.shapeType === 'mask' ? (() => { + const { width, height } = this.frameMeta[state.frame]; + const points = truncateMask(state.points, 0, width, height); + const [left, top, right, bottom] = points.splice(-4); + const rlePoints = mask2Rle(points); + rlePoints.push(left, top, right, bottom); + return rlePoints; + })() : state.points, rotation: state.rotation || 0, type: state.shapeType, z_order: state.zOrder, source: state.source, + elements: state.shapeType === 'skeleton' ? state.elements.map((element) => ({ + attributes: [], + frame: element.frame, + group: 0, + label_id: element.label.id, + points: [...element.points], + rotation: 0, + type: element.shapeType, + z_order: 0, + outside: element.outside || false, + occluded: element.occluded || false, + })) : undefined, }); } else if (state.objectType === 'track') { constructed.tracks.push({ @@ -807,7 +834,7 @@ { attributes: attributes.filter((attr) => labelAttributes[attr.spec_id].mutable), frame: state.frame, - occluded: state.occluded || false, + occluded: false, outside: false, points: [...state.points], rotation: state.rotation || 0, @@ -815,6 +842,33 @@ z_order: state.zOrder, }, ], + elements: state.shapeType === 'skeleton' ? state.elements.map((element) => { + const elementAttrValues = Object.keys(state.attributes) + .reduce(convertAttributes.bind(state), []); + const elementAttributes = element.label.attributes.reduce((accumulator, attribute) => { + accumulator[attribute.id] = attribute; + return accumulator; + }, {}); + + return ({ + attributes: elementAttrValues + .filter((attr) => !elementAttributes[attr.spec_id].mutable), + frame: state.frame, + group: 0, + label_id: element.label.id, + shapes: [{ + frame: state.frame, + type: element.shapeType, + points: [...element.points], + zOrder: state.zOrder, + outside: element.outside || false, + occluded: element.occluded || false, + rotation: element.rotation || 0, + attributes: elementAttrValues + .filter((attr) => !elementAttributes[attr.spec_id].mutable), + }], + }); + }) : undefined, }); } else { throw new ArgumentError( @@ -828,6 +882,11 @@ // eslint-disable-next-line no-unsanitized/method const imported = this.import(constructed); const importedArray = imported.tags.concat(imported.tracks).concat(imported.shapes); + for (const object of importedArray) { + if (object.shapeType === ShapeType.MASK && config.removeUnderlyingMaskPixels) { + object.removeUnderlyingPixels(object.frame); + } + } if (objectStates.length) { this.history.do( diff --git a/cvat-core/src/annotations-filter.js b/cvat-core/src/annotations-filter.ts similarity index 55% rename from cvat-core/src/annotations-filter.js rename to cvat-core/src/annotations-filter.ts index 9842dc068f0a..63c748cdd0eb 100644 --- a/cvat-core/src/annotations-filter.js +++ b/cvat-core/src/annotations-filter.ts @@ -1,15 +1,15 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT -const jsonLogic = require('json-logic-js'); -const { AttributeType, ObjectType } = require('./enums'); +import jsonLogic from 'json-logic-js'; +import { AttributeType, ObjectType, ShapeType } from './enums'; -function adjustName(name) { +function adjustName(name): string { return name.replace(/\./g, '\u2219'); } -class AnnotationsFilter { +export default class AnnotationsFilter { _convertObjects(statesData) { const objects = statesData.map((state) => { const labelAttributes = state.label.attributes.reduce((acc, attr) => { @@ -17,25 +17,35 @@ class AnnotationsFilter { return acc; }, {}); - let xtl = Number.MAX_SAFE_INTEGER; - let xbr = Number.MIN_SAFE_INTEGER; - let ytl = Number.MAX_SAFE_INTEGER; - let ybr = Number.MIN_SAFE_INTEGER; let [width, height] = [null, null]; if (state.objectType !== ObjectType.TAG) { - state.points.forEach((coord, idx) => { - if (idx % 2) { - // y - ytl = Math.min(ytl, coord); - ybr = Math.max(ybr, coord); - } else { - // x - xtl = Math.min(xtl, coord); - xbr = Math.max(xbr, coord); - } - }); - [width, height] = [xbr - xtl, ybr - ytl]; + if (state.shapeType === ShapeType.MASK) { + const [xtl, ytl, xbr, ybr] = state.points.slice(-4); + [width, height] = [xbr - xtl + 1, ybr - ytl + 1]; + } else { + let xtl = Number.MAX_SAFE_INTEGER; + let xbr = Number.MIN_SAFE_INTEGER; + let ytl = Number.MAX_SAFE_INTEGER; + let ybr = Number.MIN_SAFE_INTEGER; + + const points = state.points || state.elements.reduce((acc, val) => { + acc.push(val.points); + return acc; + }, []).flat(); + points.forEach((coord, idx) => { + if (idx % 2) { + // y + ytl = Math.min(ytl, coord); + ybr = Math.max(ybr, coord); + } else { + // x + xtl = Math.min(xtl, coord); + xbr = Math.max(xbr, coord); + } + }); + [width, height] = [xbr - xtl, ybr - ytl]; + } } const attributes = {}; @@ -75,5 +85,3 @@ class AnnotationsFilter { .filter((_, index) => jsonLogic.apply(filters[0], converted[index])); } } - -module.exports = AnnotationsFilter; diff --git a/cvat-core/src/annotations-history.js b/cvat-core/src/annotations-history.ts similarity index 63% rename from cvat-core/src/annotations-history.js rename to cvat-core/src/annotations-history.ts index 5cb5ffb80f8c..5d7fecf26be7 100644 --- a/cvat-core/src/annotations-history.js +++ b/cvat-core/src/annotations-history.ts @@ -1,27 +1,41 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation // // SPDX-License-Identifier: MIT +import { HistoryActions } from './enums'; + const MAX_HISTORY_LENGTH = 128; -class AnnotationHistory { +interface ActionItem { + action: HistoryActions; + undo: Function; + redo: Function; + clientIDs: number[]; + frame: number; +} + +export default class AnnotationHistory { + private frozen: boolean; + private _undo: ActionItem[]; + private _redo: ActionItem[]; + constructor() { this.frozen = false; this.clear(); } - freeze(frozen) { + public freeze(frozen: boolean): void { this.frozen = frozen; } - get() { + public get(): { undo: [HistoryActions, number][], redo: [HistoryActions, number][] } { return { undo: this._undo.map((undo) => [undo.action, undo.frame]), redo: this._redo.map((redo) => [redo.action, redo.frame]), }; } - do(action, undo, redo, clientIDs, frame) { + public do(action: HistoryActions, undo: Function, redo: Function, clientIDs: number[], frame: number): void { if (this.frozen) return; const actionItem = { clientIDs, @@ -36,12 +50,12 @@ class AnnotationHistory { this._redo = []; } - undo(count) { + public async undo(count: number): Promise { const affectedObjects = []; for (let i = 0; i < count; i++) { const action = this._undo.pop(); if (action) { - action.undo(); + await action.undo(); this._redo.push(action); affectedObjects.push(...action.clientIDs); } else { @@ -52,12 +66,12 @@ class AnnotationHistory { return affectedObjects; } - redo(count) { + public async redo(count: number): Promise { const affectedObjects = []; for (let i = 0; i < count; i++) { const action = this._redo.pop(); if (action) { - action.redo(); + await action.redo(); this._undo.push(action); affectedObjects.push(...action.clientIDs); } else { @@ -68,10 +82,8 @@ class AnnotationHistory { return affectedObjects; } - clear() { + public clear(): void { this._undo = []; this._redo = []; } } - -module.exports = AnnotationHistory; diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js deleted file mode 100644 index e4392810a332..000000000000 --- a/cvat-core/src/annotations-objects.js +++ /dev/null @@ -1,2175 +0,0 @@ -// Copyright (C) 2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const ObjectState = require('./object-state'); - const { checkObjectType } = require('./common'); - const { - colors, Source, ObjectShape, ObjectType, AttributeType, HistoryActions, - } = require('./enums'); - - const { DataError, ArgumentError, ScriptingError } = require('./exceptions'); - - const { Label } = require('./labels'); - - const defaultGroupColor = '#E0E0E0'; - - // Called with the Annotation context - function objectStateFactory(frame, data) { - const objectState = new ObjectState(data); - - // eslint-disable-next-line no-underscore-dangle - objectState.__internal = { - save: this.save.bind(this, frame, objectState), - delete: this.delete.bind(this), - }; - - return objectState; - } - - function checkNumberOfPoints(shapeType, points) { - if (shapeType === ObjectShape.RECTANGLE) { - if (points.length / 2 !== 2) { - throw new DataError(`Rectangle must have 2 points, but got ${points.length / 2}`); - } - } else if (shapeType === ObjectShape.POLYGON) { - if (points.length / 2 < 3) { - throw new DataError(`Polygon must have at least 3 points, but got ${points.length / 2}`); - } - } else if (shapeType === ObjectShape.POLYLINE) { - if (points.length / 2 < 2) { - throw new DataError(`Polyline must have at least 2 points, but got ${points.length / 2}`); - } - } else if (shapeType === ObjectShape.POINTS) { - if (points.length / 2 < 1) { - throw new DataError(`Points must have at least 1 points, but got ${points.length / 2}`); - } - } else if (shapeType === ObjectShape.CUBOID) { - if (points.length / 2 !== 8) { - throw new DataError(`Cuboid must have 8 points, but got ${points.length / 2}`); - } - } else if (shapeType === ObjectShape.ELLIPSE) { - if (points.length / 2 !== 2) { - throw new DataError(`Ellipse must have 1 point, rx and ry but got ${points.toString()}`); - } - } else { - throw new ArgumentError(`Unknown value of shapeType has been received ${shapeType}`); - } - } - - function findAngleDiff(rightAngle, leftAngle) { - let angleDiff = rightAngle - leftAngle; - angleDiff = ((angleDiff + 180) % 360) - 180; - if (Math.abs(angleDiff) >= 180) { - // if the main arc is bigger than 180, go another arc - // to find it, just substract absolute value from 360 and inverse sign - angleDiff = 360 - Math.abs(angleDiff) * Math.sign(angleDiff) * -1; - } - return angleDiff; - } - - function checkShapeArea(shapeType, points) { - const MIN_SHAPE_LENGTH = 3; - const MIN_SHAPE_AREA = 9; - - if (shapeType === ObjectShape.POINTS) { - return true; - } - - if (shapeType === ObjectShape.ELLIPSE) { - const [cx, cy, rightX, topY] = points; - const [rx, ry] = [rightX - cx, cy - topY]; - return rx * ry * Math.PI > MIN_SHAPE_AREA; - } - - let xmin = Number.MAX_SAFE_INTEGER; - let xmax = Number.MIN_SAFE_INTEGER; - let ymin = Number.MAX_SAFE_INTEGER; - let ymax = Number.MIN_SAFE_INTEGER; - - for (let i = 0; i < points.length - 1; i += 2) { - xmin = Math.min(xmin, points[i]); - xmax = Math.max(xmax, points[i]); - ymin = Math.min(ymin, points[i + 1]); - ymax = Math.max(ymax, points[i + 1]); - } - - if (shapeType === ObjectShape.POLYLINE) { - const length = Math.max(xmax - xmin, ymax - ymin); - return length >= MIN_SHAPE_LENGTH; - } - - const area = (xmax - xmin) * (ymax - ymin); - return area >= MIN_SHAPE_AREA; - } - - function rotatePoint(x, y, angle, cx = 0, cy = 0) { - const sin = Math.sin((angle * Math.PI) / 180); - const cos = Math.cos((angle * Math.PI) / 180); - const rotX = (x - cx) * cos - (y - cy) * sin + cx; - const rotY = (y - cy) * cos + (x - cx) * sin + cy; - return [rotX, rotY]; - } - - function fitPoints(shapeType, points, rotation, maxX, maxY) { - checkObjectType('rotation', rotation, 'number', null); - points.forEach((coordinate) => checkObjectType('coordinate', coordinate, 'number', null)); - - if (shapeType === ObjectShape.CUBOID || shapeType === ObjectShape.ELLIPSE || !!rotation) { - // cuboids and rotated bounding boxes cannot be fitted - return points; - } - - const fittedPoints = []; - - for (let i = 0; i < points.length - 1; i += 2) { - const x = points[i]; - const y = points[i + 1]; - const clampedX = Math.clamp(x, 0, maxX); - const clampedY = Math.clamp(y, 0, maxY); - fittedPoints.push(clampedX, clampedY); - } - - return fittedPoints; - } - - function validateAttributeValue(value, attr) { - const { values } = attr; - const type = attr.inputType; - - if (typeof value !== 'string') { - throw new ArgumentError(`Attribute value is expected to be string, but got ${typeof value}`); - } - - if (type === AttributeType.NUMBER) { - return +value >= +values[0] && +value <= +values[1]; - } - - if (type === AttributeType.CHECKBOX) { - return ['true', 'false'].includes(value.toLowerCase()); - } - - if (type === AttributeType.TEXT) { - return true; - } - - return values.includes(value); - } - - class Annotation { - constructor(data, clientID, color, injection) { - this.taskLabels = injection.labels; - this.history = injection.history; - this.groupColors = injection.groupColors; - this.clientID = clientID; - this.serverID = data.id; - this.group = data.group; - this.label = this.taskLabels[data.label_id]; - this.frame = data.frame; - this.removed = false; - this.lock = false; - this.color = color; - this.source = data.source; - this.updated = Date.now(); - this.attributes = data.attributes.reduce((attributeAccumulator, attr) => { - attributeAccumulator[attr.spec_id] = attr.value; - return attributeAccumulator; - }, {}); - this.groupObject = Object.defineProperties( - {}, - { - color: { - get: () => { - if (this.group) { - return this.groupColors[this.group] || colors[this.group % colors.length]; - } - return defaultGroupColor; - }, - set: (newColor) => { - if (this.group && typeof newColor === 'string' && /^#[0-9A-F]{6}$/i.test(newColor)) { - this.groupColors[this.group] = newColor; - } - }, - }, - id: { - get: () => this.group, - }, - }, - ); - this.appendDefaultAttributes(this.label); - - injection.groups.max = Math.max(injection.groups.max, this.group); - } - - _saveLock(lock, frame) { - const undoLock = this.lock; - const redoLock = lock; - - this.history.do( - HistoryActions.CHANGED_LOCK, - () => { - this.lock = undoLock; - this.updated = Date.now(); - }, - () => { - this.lock = redoLock; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - - this.lock = lock; - } - - _saveColor(color, frame) { - const undoColor = this.color; - const redoColor = color; - - this.history.do( - HistoryActions.CHANGED_COLOR, - () => { - this.color = undoColor; - this.updated = Date.now(); - }, - () => { - this.color = redoColor; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - - this.color = color; - } - - _saveHidden(hidden, frame) { - const undoHidden = this.hidden; - const redoHidden = hidden; - - this.history.do( - HistoryActions.CHANGED_HIDDEN, - () => { - this.hidden = undoHidden; - this.updated = Date.now(); - }, - () => { - this.hidden = redoHidden; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - - this.hidden = hidden; - } - - _saveLabel(label, frame) { - const undoLabel = this.label; - const redoLabel = label; - const undoAttributes = { ...this.attributes }; - this.label = label; - this.attributes = {}; - this.appendDefaultAttributes(label); - - // Try to keep old attributes if name matches and old value is still valid - for (const attribute of redoLabel.attributes) { - for (const oldAttribute of undoLabel.attributes) { - if ( - attribute.name === oldAttribute.name && - validateAttributeValue(undoAttributes[oldAttribute.id], attribute) - ) { - this.attributes[attribute.id] = undoAttributes[oldAttribute.id]; - } - } - } - const redoAttributes = { ...this.attributes }; - - this.history.do( - HistoryActions.CHANGED_LABEL, - () => { - this.label = undoLabel; - this.attributes = undoAttributes; - this.updated = Date.now(); - }, - () => { - this.label = redoLabel; - this.attributes = redoAttributes; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - } - - _saveAttributes(attributes, frame) { - const undoAttributes = { ...this.attributes }; - - for (const attrID of Object.keys(attributes)) { - this.attributes[attrID] = attributes[attrID]; - } - - const redoAttributes = { ...this.attributes }; - - this.history.do( - HistoryActions.CHANGED_ATTRIBUTES, - () => { - this.attributes = undoAttributes; - this.updated = Date.now(); - }, - () => { - this.attributes = redoAttributes; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - } - - _validateStateBeforeSave(frame, data, updated) { - let fittedPoints = []; - - if (updated.label) { - checkObjectType('label', data.label, null, Label); - } - - const labelAttributes = data.label.attributes.reduce((accumulator, value) => { - accumulator[value.id] = value; - return accumulator; - }, {}); - - if (updated.attributes) { - for (const attrID of Object.keys(data.attributes)) { - const value = data.attributes[attrID]; - if (attrID in labelAttributes) { - if (!validateAttributeValue(value, labelAttributes[attrID])) { - throw new ArgumentError( - `Trying to save an attribute attribute with id ${attrID} and invalid value ${value}`, - ); - } - } else { - throw new ArgumentError( - `The label of the shape doesn't have the attribute with id ${attrID} and value ${value}`, - ); - } - } - } - - if (updated.descriptions) { - if (!Array.isArray(data.descriptions) || data.descriptions.some((desc) => typeof desc !== 'string')) { - throw new ArgumentError( - `Descriptions are expected to be an array of strings but got ${data.descriptions}`, - ); - } - } - - if (updated.points) { - checkObjectType('points', data.points, null, Array); - checkNumberOfPoints(this.shapeType, data.points); - // cut points - const { width, height, filename } = this.frameMeta[frame]; - fittedPoints = fitPoints(this.shapeType, data.points, data.rotation, width, height); - let check = true; - if (filename && filename.slice(filename.length - 3) === 'pcd') { - check = false; - } - if (check) { - if (!checkShapeArea(this.shapeType, fittedPoints)) { - fittedPoints = []; - } - } - } - - if (updated.occluded) { - checkObjectType('occluded', data.occluded, 'boolean', null); - } - - if (updated.outside) { - checkObjectType('outside', data.outside, 'boolean', null); - } - - if (updated.zOrder) { - checkObjectType('zOrder', data.zOrder, 'integer', null); - } - - if (updated.lock) { - checkObjectType('lock', data.lock, 'boolean', null); - } - - if (updated.pinned) { - checkObjectType('pinned', data.pinned, 'boolean', null); - } - - if (updated.color) { - checkObjectType('color', data.color, 'string', null); - if (!/^#[0-9A-F]{6}$/i.test(data.color)) { - throw new ArgumentError(`Got invalid color value: "${data.color}"`); - } - } - - if (updated.hidden) { - checkObjectType('hidden', data.hidden, 'boolean', null); - } - - if (updated.keyframe) { - checkObjectType('keyframe', data.keyframe, 'boolean', null); - if (!this.shapes || (Object.keys(this.shapes).length === 1 && !data.keyframe)) { - throw new ArgumentError( - 'Can not remove the latest keyframe of an object. Consider removing the object instead', - ); - } - } - - return fittedPoints; - } - - appendDefaultAttributes(label) { - const labelAttributes = label.attributes; - for (const attribute of labelAttributes) { - if (!(attribute.id in this.attributes)) { - this.attributes[attribute.id] = attribute.defaultValue; - } - } - } - - updateTimestamp(updated) { - const anyChanges = Object.keys(updated).some((key) => !!updated[key]); - if (anyChanges) { - this.updated = Date.now(); - } - } - - delete(frame, force) { - if (!this.lock || force) { - this.removed = true; - - this.history.do( - HistoryActions.REMOVED_OBJECT, - () => { - this.serverID = undefined; - this.removed = false; - this.updated = Date.now(); - }, - () => { - this.removed = true; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - } - - return this.removed; - } - } - - class Drawn extends Annotation { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.frameMeta = injection.frameMeta; - this.descriptions = data.descriptions || []; - this.hidden = false; - this.pinned = true; - this.shapeType = null; - } - - _saveDescriptions(descriptions) { - this.descriptions = [...descriptions]; - } - - _savePinned(pinned, frame) { - const undoPinned = this.pinned; - const redoPinned = pinned; - - this.history.do( - HistoryActions.CHANGED_PINNED, - () => { - this.pinned = undoPinned; - this.updated = Date.now(); - }, - () => { - this.pinned = redoPinned; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - - this.pinned = pinned; - } - - save() { - throw new ScriptingError('Is not implemented'); - } - - get() { - throw new ScriptingError('Is not implemented'); - } - - toJSON() { - throw new ScriptingError('Is not implemented'); - } - } - - class Shape extends Drawn { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.points = data.points; - this.rotation = data.rotation || 0; - this.occluded = data.occluded; - this.zOrder = data.z_order; - } - - // Method is used to export data to the server - toJSON() { - return { - type: this.shapeType, - clientID: this.clientID, - occluded: this.occluded, - z_order: this.zOrder, - points: [...this.points], - rotation: this.rotation, - attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { - attributeAccumulator.push({ - spec_id: attrId, - value: this.attributes[attrId], - }); - - return attributeAccumulator; - }, []), - id: this.serverID, - frame: this.frame, - label_id: this.label.id, - group: this.group, - source: this.source, - }; - } - - // Method is used to construct ObjectState objects - get(frame) { - if (frame !== this.frame) { - throw new ScriptingError('Got frame is not equal to the frame of the shape'); - } - - return { - objectType: ObjectType.SHAPE, - shapeType: this.shapeType, - clientID: this.clientID, - serverID: this.serverID, - occluded: this.occluded, - lock: this.lock, - zOrder: this.zOrder, - points: [...this.points], - rotation: this.rotation, - attributes: { ...this.attributes }, - descriptions: [...this.descriptions], - label: this.label, - group: this.groupObject, - color: this.color, - hidden: this.hidden, - updated: this.updated, - pinned: this.pinned, - frame, - source: this.source, - }; - } - - _savePoints(points, rotation, frame) { - const undoPoints = this.points; - const undoRotation = this.rotation; - const redoPoints = points; - const redoRotation = rotation; - const undoSource = this.source; - const redoSource = Source.MANUAL; - - this.history.do( - HistoryActions.CHANGED_POINTS, - () => { - this.points = undoPoints; - this.source = undoSource; - this.rotation = undoRotation; - this.updated = Date.now(); - }, - () => { - this.points = redoPoints; - this.source = redoSource; - this.rotation = redoRotation; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - - this.source = Source.MANUAL; - this.points = points; - this.rotation = rotation; - } - - _saveOccluded(occluded, frame) { - const undoOccluded = this.occluded; - const redoOccluded = occluded; - const undoSource = this.source; - const redoSource = Source.MANUAL; - - this.history.do( - HistoryActions.CHANGED_OCCLUDED, - () => { - this.occluded = undoOccluded; - this.source = undoSource; - this.updated = Date.now(); - }, - () => { - this.occluded = redoOccluded; - this.source = redoSource; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - - this.source = Source.MANUAL; - this.occluded = occluded; - } - - _saveZOrder(zOrder, frame) { - const undoZOrder = this.zOrder; - const redoZOrder = zOrder; - const undoSource = this.source; - const redoSource = Source.MANUAL; - - this.history.do( - HistoryActions.CHANGED_ZORDER, - () => { - this.zOrder = undoZOrder; - this.source = undoSource; - this.updated = Date.now(); - }, - () => { - this.zOrder = redoZOrder; - this.source = redoSource; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - - this.source = Source.MANUAL; - this.zOrder = zOrder; - } - - save(frame, data) { - if (frame !== this.frame) { - throw new ScriptingError('Got frame is not equal to the frame of the shape'); - } - - if (this.lock && data.lock) { - return objectStateFactory.call(this, frame, this.get(frame)); - } - - const updated = data.updateFlags; - const fittedPoints = this._validateStateBeforeSave(frame, data, updated); - const { rotation } = data; - - // Now when all fields are validated, we can apply them - if (updated.label) { - this._saveLabel(data.label, frame); - } - - if (updated.attributes) { - this._saveAttributes(data.attributes, frame); - } - - if (updated.descriptions) { - this._saveDescriptions(data.descriptions); - } - - if (updated.points && fittedPoints.length) { - this._savePoints(fittedPoints, rotation, frame); - } - - if (updated.occluded) { - this._saveOccluded(data.occluded, frame); - } - - if (updated.zOrder) { - this._saveZOrder(data.zOrder, frame); - } - - if (updated.lock) { - this._saveLock(data.lock, frame); - } - - if (updated.pinned) { - this._savePinned(data.pinned, frame); - } - - if (updated.color) { - this._saveColor(data.color, frame); - } - - if (updated.hidden) { - this._saveHidden(data.hidden, frame); - } - - this.updateTimestamp(updated); - updated.reset(); - - return objectStateFactory.call(this, frame, this.get(frame)); - } - } - - class Track extends Drawn { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapes = data.shapes.reduce((shapeAccumulator, value) => { - shapeAccumulator[value.frame] = { - serverID: value.id, - occluded: value.occluded, - zOrder: value.z_order, - points: value.points, - outside: value.outside, - rotation: value.rotation || 0, - attributes: value.attributes.reduce((attributeAccumulator, attr) => { - attributeAccumulator[attr.spec_id] = attr.value; - return attributeAccumulator; - }, {}), - }; - - return shapeAccumulator; - }, {}); - } - - // Method is used to export data to the server - toJSON() { - const labelAttributes = this.label.attributes.reduce((accumulator, attribute) => { - accumulator[attribute.id] = attribute; - return accumulator; - }, {}); - - return { - clientID: this.clientID, - id: this.serverID, - frame: this.frame, - label_id: this.label.id, - group: this.group, - source: this.source, - attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { - if (!labelAttributes[attrId].mutable) { - attributeAccumulator.push({ - spec_id: attrId, - value: this.attributes[attrId], - }); - } - - return attributeAccumulator; - }, []), - shapes: Object.keys(this.shapes).reduce((shapesAccumulator, frame) => { - shapesAccumulator.push({ - type: this.shapeType, - occluded: this.shapes[frame].occluded, - z_order: this.shapes[frame].zOrder, - points: [...this.shapes[frame].points], - rotation: this.shapes[frame].rotation, - outside: this.shapes[frame].outside, - attributes: Object.keys(this.shapes[frame].attributes).reduce( - (attributeAccumulator, attrId) => { - if (labelAttributes[attrId].mutable) { - attributeAccumulator.push({ - spec_id: attrId, - value: this.shapes[frame].attributes[attrId], - }); - } - - return attributeAccumulator; - }, - [], - ), - id: this.shapes[frame].serverID, - frame: +frame, - }); - - return shapesAccumulator; - }, []), - }; - } - - // Method is used to construct ObjectState objects - get(frame) { - const { - prev, next, first, last, - } = this.boundedKeyframes(frame); - - return { - ...this.getPosition(frame, prev, next), - attributes: this.getAttributes(frame), - descriptions: [...this.descriptions], - group: this.groupObject, - objectType: ObjectType.TRACK, - shapeType: this.shapeType, - clientID: this.clientID, - serverID: this.serverID, - lock: this.lock, - color: this.color, - hidden: this.hidden, - updated: this.updated, - label: this.label, - pinned: this.pinned, - keyframes: { - prev, - next, - first, - last, - }, - frame, - source: this.source, - }; - } - - boundedKeyframes(targetFrame) { - const frames = Object.keys(this.shapes).map((frame) => +frame); - let lDiff = Number.MAX_SAFE_INTEGER; - let rDiff = Number.MAX_SAFE_INTEGER; - let first = Number.MAX_SAFE_INTEGER; - let last = Number.MIN_SAFE_INTEGER; - - for (const frame of frames) { - if (frame < first) { - first = frame; - } - if (frame > last) { - last = frame; - } - - const diff = Math.abs(targetFrame - frame); - - if (frame < targetFrame && diff < lDiff) { - lDiff = diff; - } else if (frame > targetFrame && diff < rDiff) { - rDiff = diff; - } - } - - const prev = lDiff === Number.MAX_SAFE_INTEGER ? null : targetFrame - lDiff; - const next = rDiff === Number.MAX_SAFE_INTEGER ? null : targetFrame + rDiff; - - return { - prev, - next, - first, - last, - }; - } - - getAttributes(targetFrame) { - const result = {}; - - // First of all copy all unmutable attributes - for (const attrID in this.attributes) { - if (Object.prototype.hasOwnProperty.call(this.attributes, attrID)) { - result[attrID] = this.attributes[attrID]; - } - } - - // Secondly get latest mutable attributes up to target frame - const frames = Object.keys(this.shapes).sort((a, b) => +a - +b); - for (const frame of frames) { - if (frame <= targetFrame) { - const { attributes } = this.shapes[frame]; - - for (const attrID in attributes) { - if (Object.prototype.hasOwnProperty.call(attributes, attrID)) { - result[attrID] = attributes[attrID]; - } - } - } - } - - return result; - } - - _saveLabel(label, frame) { - const undoLabel = this.label; - const redoLabel = label; - const undoAttributes = { - unmutable: { ...this.attributes }, - mutable: Object.keys(this.shapes).map((key) => ({ - frame: +key, - attributes: { ...this.shapes[key].attributes }, - })), - }; - - this.label = label; - this.attributes = {}; - for (const shape of Object.values(this.shapes)) { - shape.attributes = {}; - } - this.appendDefaultAttributes(label); - - const redoAttributes = { - unmutable: { ...this.attributes }, - mutable: Object.keys(this.shapes).map((key) => ({ - frame: +key, - attributes: { ...this.shapes[key].attributes }, - })), - }; - - this.history.do( - HistoryActions.CHANGED_LABEL, - () => { - this.label = undoLabel; - this.attributes = undoAttributes.unmutable; - for (const mutable of undoAttributes.mutable) { - this.shapes[mutable.frame].attributes = mutable.attributes; - } - this.updated = Date.now(); - }, - () => { - this.label = redoLabel; - this.attributes = redoAttributes.unmutable; - for (const mutable of redoAttributes.mutable) { - this.shapes[mutable.frame].attributes = mutable.attributes; - } - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - } - - _saveAttributes(attributes, frame) { - const current = this.get(frame); - const labelAttributes = this.label.attributes.reduce((accumulator, value) => { - accumulator[value.id] = value; - return accumulator; - }, {}); - - const wasKeyframe = frame in this.shapes; - const undoAttributes = this.attributes; - const undoShape = wasKeyframe ? this.shapes[frame] : undefined; - - let mutableAttributesUpdated = false; - const redoAttributes = { ...this.attributes }; - for (const attrID of Object.keys(attributes)) { - if (!labelAttributes[attrID].mutable) { - redoAttributes[attrID] = attributes[attrID]; - } else if (attributes[attrID] !== current.attributes[attrID]) { - mutableAttributesUpdated = mutableAttributesUpdated || - // not keyframe yet - !(frame in this.shapes) || - // keyframe, but without this attrID - !(attrID in this.shapes[frame].attributes) || - // keyframe with attrID, but with another value - this.shapes[frame].attributes[attrID] !== attributes[attrID]; - } - } - let redoShape; - if (mutableAttributesUpdated) { - if (wasKeyframe) { - redoShape = { - ...this.shapes[frame], - attributes: { - ...this.shapes[frame].attributes, - }, - }; - } else { - redoShape = { - frame, - zOrder: current.zOrder, - points: current.points, - outside: current.outside, - occluded: current.occluded, - attributes: {}, - }; - } - } - - for (const attrID of Object.keys(attributes)) { - if (labelAttributes[attrID].mutable && attributes[attrID] !== current.attributes[attrID]) { - redoShape.attributes[attrID] = attributes[attrID]; - } - } - - this.attributes = redoAttributes; - if (redoShape) { - this.shapes[frame] = redoShape; - } - - this.history.do( - HistoryActions.CHANGED_ATTRIBUTES, - () => { - this.attributes = undoAttributes; - if (undoShape) { - this.shapes[frame] = undoShape; - } else if (redoShape) { - delete this.shapes[frame]; - } - this.updated = Date.now(); - }, - () => { - this.attributes = redoAttributes; - if (redoShape) { - this.shapes[frame] = redoShape; - } - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - } - - _appendShapeActionToHistory(actionType, frame, undoShape, redoShape, undoSource, redoSource) { - this.history.do( - actionType, - () => { - if (!undoShape) { - delete this.shapes[frame]; - } else { - this.shapes[frame] = undoShape; - } - this.source = undoSource; - this.updated = Date.now(); - }, - () => { - if (!redoShape) { - delete this.shapes[frame]; - } else { - this.shapes[frame] = redoShape; - } - this.source = redoSource; - this.updated = Date.now(); - }, - [this.clientID], - frame, - ); - } - - _savePoints(points, rotation, frame) { - const current = this.get(frame); - const wasKeyframe = frame in this.shapes; - const undoSource = this.source; - const redoSource = Source.MANUAL; - const undoShape = wasKeyframe ? this.shapes[frame] : undefined; - const redoShape = wasKeyframe ? { ...this.shapes[frame], points, rotation } : { - frame, - points, - rotation, - zOrder: current.zOrder, - outside: current.outside, - occluded: current.occluded, - attributes: {}, - }; - - this.shapes[frame] = redoShape; - this.source = Source.MANUAL; - this._appendShapeActionToHistory( - HistoryActions.CHANGED_POINTS, - frame, - undoShape, - redoShape, - undoSource, - redoSource, - ); - } - - _saveOutside(frame, outside) { - const current = this.get(frame); - const wasKeyframe = frame in this.shapes; - const undoSource = this.source; - const redoSource = Source.MANUAL; - const undoShape = wasKeyframe ? this.shapes[frame] : undefined; - const redoShape = wasKeyframe ? - { ...this.shapes[frame], outside } : - { - frame, - outside, - rotation: current.rotation, - zOrder: current.zOrder, - points: current.points, - occluded: current.occluded, - attributes: {}, - }; - - this.shapes[frame] = redoShape; - this.source = Source.MANUAL; - this._appendShapeActionToHistory( - HistoryActions.CHANGED_OUTSIDE, - frame, - undoShape, - redoShape, - undoSource, - redoSource, - ); - } - - _saveOccluded(occluded, frame) { - const current = this.get(frame); - const wasKeyframe = frame in this.shapes; - const undoSource = this.source; - const redoSource = Source.MANUAL; - const undoShape = wasKeyframe ? this.shapes[frame] : undefined; - const redoShape = wasKeyframe ? - { ...this.shapes[frame], occluded } : - { - frame, - occluded, - rotation: current.rotation, - zOrder: current.zOrder, - points: current.points, - outside: current.outside, - attributes: {}, - }; - - this.shapes[frame] = redoShape; - this.source = Source.MANUAL; - this._appendShapeActionToHistory( - HistoryActions.CHANGED_OCCLUDED, - frame, - undoShape, - redoShape, - undoSource, - redoSource, - ); - } - - _saveZOrder(zOrder, frame) { - const current = this.get(frame); - const wasKeyframe = frame in this.shapes; - const undoSource = this.source; - const redoSource = Source.MANUAL; - const undoShape = wasKeyframe ? this.shapes[frame] : undefined; - const redoShape = wasKeyframe ? - { ...this.shapes[frame], zOrder } : - { - frame, - zOrder, - rotation: current.rotation, - occluded: current.occluded, - points: current.points, - outside: current.outside, - attributes: {}, - }; - - this.shapes[frame] = redoShape; - this.source = Source.MANUAL; - this._appendShapeActionToHistory( - HistoryActions.CHANGED_ZORDER, - frame, - undoShape, - redoShape, - undoSource, - redoSource, - ); - } - - _saveKeyframe(frame, keyframe) { - const current = this.get(frame); - const wasKeyframe = frame in this.shapes; - - if ((keyframe && wasKeyframe) || (!keyframe && !wasKeyframe)) { - return; - } - - const undoSource = this.source; - const redoSource = Source.MANUAL; - const undoShape = wasKeyframe ? this.shapes[frame] : undefined; - const redoShape = keyframe ? - { - frame, - rotation: current.rotation, - zOrder: current.zOrder, - points: current.points, - outside: current.outside, - occluded: current.occluded, - attributes: {}, - source: current.source, - } : - undefined; - - this.source = Source.MANUAL; - if (redoShape) { - this.shapes[frame] = redoShape; - } else { - delete this.shapes[frame]; - } - - this._appendShapeActionToHistory( - HistoryActions.CHANGED_KEYFRAME, - frame, - undoShape, - redoShape, - undoSource, - redoSource, - ); - } - - save(frame, data) { - if (this.lock && data.lock) { - return objectStateFactory.call(this, frame, this.get(frame)); - } - - const updated = data.updateFlags; - const fittedPoints = this._validateStateBeforeSave(frame, data, updated); - const { rotation } = data; - - if (updated.label) { - this._saveLabel(data.label, frame); - } - - if (updated.lock) { - this._saveLock(data.lock, frame); - } - - if (updated.pinned) { - this._savePinned(data.pinned, frame); - } - - if (updated.color) { - this._saveColor(data.color, frame); - } - - if (updated.hidden) { - this._saveHidden(data.hidden, frame); - } - - if (updated.points && fittedPoints.length) { - this._savePoints(fittedPoints, rotation, frame); - } - - if (updated.outside) { - this._saveOutside(frame, data.outside); - } - - if (updated.occluded) { - this._saveOccluded(data.occluded, frame); - } - - if (updated.zOrder) { - this._saveZOrder(data.zOrder, frame); - } - - if (updated.attributes) { - this._saveAttributes(data.attributes, frame); - } - - if (updated.descriptions) { - this._saveDescriptions(data.descriptions); - } - - if (updated.keyframe) { - this._saveKeyframe(frame, data.keyframe); - } - - this.updateTimestamp(updated); - updated.reset(); - - return objectStateFactory.call(this, frame, this.get(frame)); - } - - getPosition(targetFrame, leftKeyframe, rightFrame) { - const leftFrame = targetFrame in this.shapes ? targetFrame : leftKeyframe; - const rightPosition = Number.isInteger(rightFrame) ? this.shapes[rightFrame] : null; - const leftPosition = Number.isInteger(leftFrame) ? this.shapes[leftFrame] : null; - - if (leftPosition && rightPosition) { - return { - ...this.interpolatePosition( - leftPosition, - rightPosition, - (targetFrame - leftFrame) / (rightFrame - leftFrame), - ), - keyframe: targetFrame in this.shapes, - }; - } - - if (leftPosition) { - return { - points: [...leftPosition.points], - rotation: leftPosition.rotation, - occluded: leftPosition.occluded, - outside: leftPosition.outside, - zOrder: leftPosition.zOrder, - keyframe: targetFrame in this.shapes, - }; - } - - if (rightPosition) { - return { - points: [...rightPosition.points], - rotation: rightPosition.rotation, - occluded: rightPosition.occluded, - zOrder: rightPosition.zOrder, - keyframe: targetFrame in this.shapes, - outside: true, - }; - } - - throw new DataError( - 'No one left position or right position was found. ' + - `Interpolation impossible. Client ID: ${this.clientID}`, - ); - } - } - - class Tag extends Annotation { - // Method is used to export data to the server - toJSON() { - return { - clientID: this.clientID, - id: this.serverID, - frame: this.frame, - label_id: this.label.id, - group: this.group, - source: this.source, - attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { - attributeAccumulator.push({ - spec_id: attrId, - value: this.attributes[attrId], - }); - - return attributeAccumulator; - }, []), - }; - } - - // Method is used to construct ObjectState objects - get(frame) { - if (frame !== this.frame) { - throw new ScriptingError('Got frame is not equal to the frame of the shape'); - } - - return { - objectType: ObjectType.TAG, - clientID: this.clientID, - serverID: this.serverID, - lock: this.lock, - attributes: { ...this.attributes }, - label: this.label, - group: this.groupObject, - color: this.color, - updated: this.updated, - frame, - source: this.source, - }; - } - - save(frame, data) { - if (frame !== this.frame) { - throw new ScriptingError('Got frame is not equal to the frame of the tag'); - } - - if (this.lock && data.lock) { - return objectStateFactory.call(this, frame, this.get(frame)); - } - - const updated = data.updateFlags; - this._validateStateBeforeSave(frame, data, updated); - - // Now when all fields are validated, we can apply them - if (updated.label) { - this._saveLabel(data.label, frame); - } - - if (updated.attributes) { - this._saveAttributes(data.attributes, frame); - } - - if (updated.lock) { - this._saveLock(data.lock, frame); - } - - if (updated.color) { - this._saveColor(data.color, frame); - } - - this.updateTimestamp(updated); - updated.reset(); - - return objectStateFactory.call(this, frame, this.get(frame)); - } - } - - class RectangleShape extends Shape { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.RECTANGLE; - this.pinned = false; - checkNumberOfPoints(this.shapeType, this.points); - } - - static distance(points, x, y, angle) { - const [xtl, ytl, xbr, ybr] = points; - const cx = xtl + (xbr - xtl) / 2; - const cy = ytl + (ybr - ytl) / 2; - const [rotX, rotY] = rotatePoint(x, y, -angle, cx, cy); - - if (!(rotX >= xtl && rotX <= xbr && rotY >= ytl && rotY <= ybr)) { - // Cursor is outside of a box - return null; - } - - // The shortest distance from point to an edge - return Math.min.apply(null, [rotX - xtl, rotY - ytl, xbr - rotX, ybr - rotY]); - } - } - - class EllipseShape extends Shape { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.ELLIPSE; - this.pinned = false; - checkNumberOfPoints(this.shapeType, this.points); - } - - static distance(points, x, y, angle) { - const [cx, cy, rightX, topY] = points; - const [rx, ry] = [rightX - cx, cy - topY]; - const [rotX, rotY] = rotatePoint(x, y, -angle, cx, cy); - // https://math.stackexchange.com/questions/76457/check-if-a-point-is-within-an-ellipse - const pointWithinEllipse = (_x, _y) => ( - ((_x - cx) ** 2) / rx ** 2) + (((_y - cy) ** 2) / ry ** 2 - ) <= 1; - - if (!pointWithinEllipse(rotX, rotY)) { - // Cursor is outside of an ellipse - return null; - } - - if (Math.abs(x - cx) < Number.EPSILON && Math.abs(y - cy) < Number.EPSILON) { - // cursor is near to the center, just return minimum of height, width - return Math.min(rx, ry); - } - - // ellipse equation is x^2/rx^2 + y^2/ry^2 = 1 - // from this equation: - // x^2 = ((rx * ry)^2 - (y * rx)^2) / ry^2 - // y^2 = ((rx * ry)^2 - (x * ry)^2) / rx^2 - - // we have one point inside the ellipse, let's build two lines (horizontal and vertical) through the point - // and find their interception with ellipse - const x2Equation = (_y) => (((rx * ry) ** 2) - ((_y * rx) ** 2)) / (ry ** 2); - const y2Equation = (_x) => (((rx * ry) ** 2) - ((_x * ry) ** 2)) / (rx ** 2); - - // shift x,y to the ellipse coordinate system to compute equation correctly - // y axis is inverted - const [shiftedX, shiftedY] = [x - cx, cy - y]; - const [x1, x2] = [Math.sqrt(x2Equation(shiftedY)), -Math.sqrt(x2Equation(shiftedY))]; - const [y1, y2] = [Math.sqrt(y2Equation(shiftedX)), -Math.sqrt(y2Equation(shiftedX))]; - - // found two points on ellipse edge - const ellipseP1X = shiftedX >= 0 ? x1 : x2; // ellipseP1Y is shiftedY - const ellipseP2Y = shiftedY >= 0 ? y1 : y2; // ellipseP1X is shiftedX - - // found diffs between two points on edges and target point - const diff1X = ellipseP1X - shiftedX; - const diff2Y = ellipseP2Y - shiftedY; - - // return minimum, get absolute value because we need distance, not diff - return Math.min(Math.abs(diff1X), Math.abs(diff2Y)); - } - } - - class PolyShape extends Shape { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.rotation = 0; // is not supported - } - } - - class PolygonShape extends PolyShape { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.POLYGON; - checkNumberOfPoints(this.shapeType, this.points); - } - - static distance(points, x, y) { - function position(x1, y1, x2, y2) { - return (x2 - x1) * (y - y1) - (x - x1) * (y2 - y1); - } - - let wn = 0; - const distances = []; - - for (let i = 0, j = points.length - 2; i < points.length - 1; j = i, i += 2) { - // Current point - const x1 = points[j]; - const y1 = points[j + 1]; - - // Next point - const x2 = points[i]; - const y2 = points[i + 1]; - - // Check if a point is inside a polygon - // with a winding numbers algorithm - // https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm - if (y1 <= y) { - if (y2 > y) { - if (position(x1, y1, x2, y2) > 0) { - wn++; - } - } - } else if (y2 <= y) { - if (position(x1, y1, x2, y2) < 0) { - wn--; - } - } - - // Find the shortest distance from point to an edge - // Get an equation of a line in general - const aCoef = y1 - y2; - const bCoef = x2 - x1; - - // Vector (aCoef, bCoef) is a perpendicular to line - // Now find the point where two lines - // (edge and its perpendicular through the point (x,y)) are cross - const xCross = x - aCoef; - const yCross = y - bCoef; - - if ((xCross - x1) * (x2 - xCross) >= 0 && (yCross - y1) * (y2 - yCross) >= 0) { - // Cross point is on segment between p1(x1,y1) and p2(x2,y2) - distances.push(Math.sqrt((x - xCross) ** 2 + (y - yCross) ** 2)); - } else { - distances.push( - Math.min( - Math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2), - Math.sqrt((x2 - x) ** 2 + (y2 - y) ** 2), - ), - ); - } - } - - if (wn !== 0) { - return Math.min.apply(null, distances); - } - - return null; - } - } - - class PolylineShape extends PolyShape { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.POLYLINE; - checkNumberOfPoints(this.shapeType, this.points); - } - - static distance(points, x, y) { - const distances = []; - for (let i = 0; i < points.length - 2; i += 2) { - // Current point - const x1 = points[i]; - const y1 = points[i + 1]; - - // Next point - const x2 = points[i + 2]; - const y2 = points[i + 3]; - - // Find the shortest distance from point to an edge - if ((x - x1) * (x2 - x) >= 0 && (y - y1) * (y2 - y) >= 0) { - // Find the length of a perpendicular - // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line - distances.push( - Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / - Math.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2), - ); - } else { - // The link below works for lines (which have infinite length) - // There is a case when perpendicular doesn't cross the edge - // In this case we don't use the computed distance - // Instead we use just distance to the nearest point - distances.push( - Math.min( - Math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2), - Math.sqrt((x2 - x) ** 2 + (y2 - y) ** 2), - ), - ); - } - } - - return Math.min.apply(null, distances); - } - } - - class PointsShape extends PolyShape { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.POINTS; - checkNumberOfPoints(this.shapeType, this.points); - } - - static distance(points, x, y) { - const distances = []; - for (let i = 0; i < points.length; i += 2) { - const x1 = points[i]; - const y1 = points[i + 1]; - - distances.push(Math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2)); - } - - return Math.min.apply(null, distances); - } - } - - class CuboidShape extends Shape { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.rotation = 0; - this.shapeType = ObjectShape.CUBOID; - this.pinned = false; - checkNumberOfPoints(this.shapeType, this.points); - } - - static makeHull(geoPoints) { - // Returns the convex hull, assuming that each points[i] <= points[i + 1]. - function makeHullPresorted(points) { - if (points.length <= 1) return points.slice(); - - // Andrew's monotone chain algorithm. Positive y coordinates correspond to 'up' - // as per the mathematical convention, instead of 'down' as per the computer - // graphics convention. This doesn't affect the correctness of the result. - - const upperHull = []; - for (let i = 0; i < points.length; i += 1) { - const p = points[`${i}`]; - while (upperHull.length >= 2) { - const q = upperHull[upperHull.length - 1]; - const r = upperHull[upperHull.length - 2]; - if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) upperHull.pop(); - else break; - } - upperHull.push(p); - } - upperHull.pop(); - - const lowerHull = []; - for (let i = points.length - 1; i >= 0; i -= 1) { - const p = points[`${i}`]; - while (lowerHull.length >= 2) { - const q = lowerHull[lowerHull.length - 1]; - const r = lowerHull[lowerHull.length - 2]; - if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) lowerHull.pop(); - else break; - } - lowerHull.push(p); - } - lowerHull.pop(); - - if ( - upperHull.length === 1 && - lowerHull.length === 1 && - upperHull[0].x === lowerHull[0].x && - upperHull[0].y === lowerHull[0].y - ) return upperHull; - return upperHull.concat(lowerHull); - } - - function POINT_COMPARATOR(a, b) { - if (a.x < b.x) return -1; - if (a.x > b.x) return +1; - if (a.y < b.y) return -1; - if (a.y > b.y) return +1; - return 0; - } - - const newPoints = geoPoints.slice(); - newPoints.sort(POINT_COMPARATOR); - return makeHullPresorted(newPoints); - } - - static contain(shapePoints, x, y) { - function isLeft(P0, P1, P2) { - return (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y); - } - const points = CuboidShape.makeHull(shapePoints); - let wn = 0; - for (let i = 0; i < points.length; i += 1) { - const p1 = points[`${i}`]; - const p2 = points[i + 1] || points[0]; - - if (p1.y <= y) { - if (p2.y > y) { - if (isLeft(p1, p2, { x, y }) > 0) { - wn += 1; - } - } - } else if (p2.y < y) { - if (isLeft(p1, p2, { x, y }) < 0) { - wn -= 1; - } - } - } - - return wn !== 0; - } - - static distance(actualPoints, x, y) { - const points = []; - - for (let i = 0; i < 16; i += 2) { - points.push({ x: actualPoints[i], y: actualPoints[i + 1] }); - } - - if (!CuboidShape.contain(points, x, y)) return null; - - let minDistance = Number.MAX_SAFE_INTEGER; - for (let i = 0; i < points.length; i += 1) { - const p1 = points[`${i}`]; - const p2 = points[i + 1] || points[0]; - - // perpendicular from point to straight length - const distance = Math.abs((p2.y - p1.y) * x - (p2.x - p1.x) * y + p2.x * p1.y - p2.y * p1.x) / - Math.sqrt((p2.y - p1.y) ** 2 + (p2.x - p1.x) ** 2); - - // check if perpendicular belongs to the straight segment - const a = (p1.x - x) ** 2 + (p1.y - y) ** 2; - const b = (p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2; - const c = (p2.x - x) ** 2 + (p2.y - y) ** 2; - if (distance < minDistance && a + b - c >= 0 && c + b - a >= 0) { - minDistance = distance; - } - } - return minDistance; - } - } - - class RectangleTrack extends Track { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.RECTANGLE; - this.pinned = false; - for (const shape of Object.values(this.shapes)) { - checkNumberOfPoints(this.shapeType, shape.points); - } - } - - interpolatePosition(leftPosition, rightPosition, offset) { - const positionOffset = leftPosition.points.map((point, index) => rightPosition.points[index] - point); - return { - points: leftPosition.points.map((point, index) => point + positionOffset[index] * offset), - rotation: - (leftPosition.rotation + findAngleDiff( - rightPosition.rotation, leftPosition.rotation, - ) * offset + 360) % 360, - occluded: leftPosition.occluded, - outside: leftPosition.outside, - zOrder: leftPosition.zOrder, - }; - } - } - - class EllipseTrack extends Track { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.ELLIPSE; - this.pinned = false; - for (const shape of Object.values(this.shapes)) { - checkNumberOfPoints(this.shapeType, shape.points); - } - } - - interpolatePosition(leftPosition, rightPosition, offset) { - const positionOffset = leftPosition.points.map((point, index) => rightPosition.points[index] - point); - - return { - points: leftPosition.points.map((point, index) => point + positionOffset[index] * offset), - rotation: - (leftPosition.rotation + findAngleDiff( - rightPosition.rotation, leftPosition.rotation, - ) * offset + 360) % 360, - occluded: leftPosition.occluded, - outside: leftPosition.outside, - zOrder: leftPosition.zOrder, - }; - } - } - - class PolyTrack extends Track { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - for (const shape of Object.values(this.shapes)) { - shape.rotation = 0; // is not supported - } - } - - interpolatePosition(leftPosition, rightPosition, offset) { - if (offset === 0) { - return { - points: [...leftPosition.points], - rotation: leftPosition.rotation, - occluded: leftPosition.occluded, - outside: leftPosition.outside, - zOrder: leftPosition.zOrder, - }; - } - - function toArray(points) { - return points.reduce((acc, val) => { - acc.push(val.x, val.y); - return acc; - }, []); - } - - function toPoints(array) { - return array.reduce((acc, _, index) => { - if (index % 2) { - acc.push({ - x: array[index - 1], - y: array[index], - }); - } - - return acc; - }, []); - } - - function curveLength(points) { - return points.slice(1).reduce((acc, _, index) => { - const dx = points[index + 1].x - points[index].x; - const dy = points[index + 1].y - points[index].y; - return acc + Math.sqrt(dx ** 2 + dy ** 2); - }, 0); - } - - function curveToOffsetVec(points, length) { - const offsetVector = [0]; // with initial value - let accumulatedLength = 0; - - points.slice(1).forEach((_, index) => { - const dx = points[index + 1].x - points[index].x; - const dy = points[index + 1].y - points[index].y; - accumulatedLength += Math.sqrt(dx ** 2 + dy ** 2); - offsetVector.push(accumulatedLength / length); - }); - - return offsetVector; - } - - function findNearestPair(value, curve) { - let minimum = [0, Math.abs(value - curve[0])]; - for (let i = 1; i < curve.length; i++) { - const distance = Math.abs(value - curve[i]); - if (distance < minimum[1]) { - minimum = [i, distance]; - } - } - - return minimum[0]; - } - - function matchLeftRight(leftCurve, rightCurve) { - const matching = {}; - for (let i = 0; i < leftCurve.length; i++) { - matching[i] = [findNearestPair(leftCurve[i], rightCurve)]; - } - - return matching; - } - - function matchRightLeft(leftCurve, rightCurve, leftRightMatching) { - const matchedRightPoints = Object.values(leftRightMatching).flat(); - const unmatchedRightPoints = rightCurve - .map((_, index) => index) - .filter((index) => !matchedRightPoints.includes(index)); - const updatedMatching = { ...leftRightMatching }; - - for (const rightPoint of unmatchedRightPoints) { - const leftPoint = findNearestPair(rightCurve[rightPoint], leftCurve); - updatedMatching[leftPoint].push(rightPoint); - } - - for (const key of Object.keys(updatedMatching)) { - const sortedRightIndexes = updatedMatching[key].sort((a, b) => a - b); - updatedMatching[key] = sortedRightIndexes; - } - - return updatedMatching; - } - - function reduceInterpolation(interpolatedPoints, matching, leftPoints, rightPoints) { - function averagePoint(points) { - let sumX = 0; - let sumY = 0; - for (const point of points) { - sumX += point.x; - sumY += point.y; - } - - return { - x: sumX / points.length, - y: sumY / points.length, - }; - } - - function computeDistance(point1, point2) { - return Math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2); - } - - function minimizeSegment(baseLength, N, startInterpolated, stopInterpolated) { - const threshold = baseLength / (2 * N); - const minimized = [interpolatedPoints[startInterpolated]]; - let latestPushed = startInterpolated; - for (let i = startInterpolated + 1; i < stopInterpolated; i++) { - const distance = computeDistance(interpolatedPoints[latestPushed], interpolatedPoints[i]); - - if (distance >= threshold) { - minimized.push(interpolatedPoints[i]); - latestPushed = i; - } - } - - minimized.push(interpolatedPoints[stopInterpolated]); - - if (minimized.length === 2) { - const distance = computeDistance( - interpolatedPoints[startInterpolated], - interpolatedPoints[stopInterpolated], - ); - - if (distance < threshold) { - return [averagePoint(minimized)]; - } - } - - return minimized; - } - - const reduced = []; - const interpolatedIndexes = {}; - let accumulated = 0; - for (let i = 0; i < leftPoints.length; i++) { - // eslint-disable-next-line - interpolatedIndexes[i] = matching[i].map(() => accumulated++); - } - - function leftSegment(start, stop) { - const startInterpolated = interpolatedIndexes[start][0]; - const stopInterpolated = interpolatedIndexes[stop][0]; - - if (startInterpolated === stopInterpolated) { - reduced.push(interpolatedPoints[startInterpolated]); - return; - } - - const baseLength = curveLength(leftPoints.slice(start, stop + 1)); - const N = stop - start + 1; - - reduced.push(...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated)); - } - - function rightSegment(leftPoint) { - const start = matching[leftPoint][0]; - const [stop] = matching[leftPoint].slice(-1); - const startInterpolated = interpolatedIndexes[leftPoint][0]; - const [stopInterpolated] = interpolatedIndexes[leftPoint].slice(-1); - const baseLength = curveLength(rightPoints.slice(start, stop + 1)); - const N = stop - start + 1; - - reduced.push(...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated)); - } - - let previousOpened = null; - for (let i = 0; i < leftPoints.length; i++) { - if (matching[i].length === 1) { - // check if left segment is opened - if (previousOpened !== null) { - // check if we should continue the left segment - if (matching[i][0] === matching[previousOpened][0]) { - continue; - } else { - // left segment found - const start = previousOpened; - const stop = i - 1; - leftSegment(start, stop); - - // start next left segment - previousOpened = i; - } - } else { - // start next left segment - previousOpened = i; - } - } else { - // check if left segment is opened - if (previousOpened !== null) { - // left segment found - const start = previousOpened; - const stop = i - 1; - leftSegment(start, stop); - - previousOpened = null; - } - - // right segment found - rightSegment(i); - } - } - - // check if there is an opened segment - if (previousOpened !== null) { - leftSegment(previousOpened, leftPoints.length - 1); - } - - return reduced; - } - - // the algorithm below is based on fact that both left and right - // polyshapes have the same start point and the same draw direction - const leftPoints = toPoints(leftPosition.points); - const rightPoints = toPoints(rightPosition.points); - const leftOffsetVec = curveToOffsetVec(leftPoints, curveLength(leftPoints)); - const rightOffsetVec = curveToOffsetVec(rightPoints, curveLength(rightPoints)); - - const matching = matchLeftRight(leftOffsetVec, rightOffsetVec); - const completedMatching = matchRightLeft(leftOffsetVec, rightOffsetVec, matching); - - const interpolatedPoints = Object.keys(completedMatching) - .map((leftPointIdx) => +leftPointIdx) - .sort((a, b) => a - b) - .reduce((acc, leftPointIdx) => { - const leftPoint = leftPoints[leftPointIdx]; - for (const rightPointIdx of completedMatching[leftPointIdx]) { - const rightPoint = rightPoints[rightPointIdx]; - acc.push({ - x: leftPoint.x + (rightPoint.x - leftPoint.x) * offset, - y: leftPoint.y + (rightPoint.y - leftPoint.y) * offset, - }); - } - - return acc; - }, []); - - const reducedPoints = reduceInterpolation(interpolatedPoints, completedMatching, leftPoints, rightPoints); - - return { - points: toArray(reducedPoints), - rotation: leftPosition.rotation, - occluded: leftPosition.occluded, - outside: leftPosition.outside, - zOrder: leftPosition.zOrder, - }; - } - } - - class PolygonTrack extends PolyTrack { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.POLYGON; - for (const shape of Object.values(this.shapes)) { - checkNumberOfPoints(this.shapeType, shape.points); - } - } - - interpolatePosition(leftPosition, rightPosition, offset) { - const copyLeft = { - ...leftPosition, - points: [...leftPosition.points, leftPosition.points[0], leftPosition.points[1]], - }; - - const copyRight = { - ...rightPosition, - points: [...rightPosition.points, rightPosition.points[0], rightPosition.points[1]], - }; - - const result = PolyTrack.prototype.interpolatePosition.call(this, copyLeft, copyRight, offset); - - return { - ...result, - points: result.points.slice(0, -2), - }; - } - } - - class PolylineTrack extends PolyTrack { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.POLYLINE; - for (const shape of Object.values(this.shapes)) { - checkNumberOfPoints(this.shapeType, shape.points); - } - } - } - - class PointsTrack extends PolyTrack { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.POINTS; - for (const shape of Object.values(this.shapes)) { - checkNumberOfPoints(this.shapeType, shape.points); - } - } - - interpolatePosition(leftPosition, rightPosition, offset) { - // interpolate only when one point in both left and right positions - if (leftPosition.points.length === 2 && rightPosition.points.length === 2) { - return { - points: leftPosition.points.map( - (value, index) => value + (rightPosition.points[index] - value) * offset, - ), - rotation: leftPosition.rotation, - occluded: leftPosition.occluded, - outside: leftPosition.outside, - zOrder: leftPosition.zOrder, - }; - } - - return { - points: [...leftPosition.points], - rotation: leftPosition.rotation, - occluded: leftPosition.occluded, - outside: leftPosition.outside, - zOrder: leftPosition.zOrder, - }; - } - } - - class CuboidTrack extends Track { - constructor(data, clientID, color, injection) { - super(data, clientID, color, injection); - this.shapeType = ObjectShape.CUBOID; - this.pinned = false; - for (const shape of Object.values(this.shapes)) { - checkNumberOfPoints(this.shapeType, shape.points); - shape.rotation = 0; // is not supported - } - } - - interpolatePosition(leftPosition, rightPosition, offset) { - const positionOffset = leftPosition.points.map((point, index) => rightPosition.points[index] - point); - - return { - points: leftPosition.points.map((point, index) => point + positionOffset[index] * offset), - rotation: leftPosition.rotation, - occluded: leftPosition.occluded, - outside: leftPosition.outside, - zOrder: leftPosition.zOrder, - }; - } - } - - RectangleTrack.distance = RectangleShape.distance; - PolygonTrack.distance = PolygonShape.distance; - PolylineTrack.distance = PolylineShape.distance; - PointsTrack.distance = PointsShape.distance; - EllipseTrack.distance = EllipseShape.distance; - CuboidTrack.distance = CuboidShape.distance; - - module.exports = { - RectangleShape, - PolygonShape, - PolylineShape, - PointsShape, - EllipseShape, - CuboidShape, - RectangleTrack, - PolygonTrack, - PolylineTrack, - PointsTrack, - EllipseTrack, - CuboidTrack, - Track, - Shape, - Tag, - objectStateFactory, - }; -})(); diff --git a/cvat-core/src/annotations-objects.ts b/cvat-core/src/annotations-objects.ts new file mode 100644 index 000000000000..bca6a83d1929 --- /dev/null +++ b/cvat-core/src/annotations-objects.ts @@ -0,0 +1,3146 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import config from './config'; +import ObjectState, { SerializedData } from './object-state'; +import { checkObjectType, clamp } from './common'; +import { DataError, ArgumentError, ScriptingError } from './exceptions'; +import { Label } from './labels'; +import { + colors, Source, ShapeType, ObjectType, HistoryActions, +} from './enums'; +import AnnotationHistory from './annotations-history'; +import { + checkNumberOfPoints, attrsAsAnObject, checkShapeArea, mask2Rle, rle2Mask, + computeWrappingBox, findAngleDiff, rotatePoint, validateAttributeValue, truncateMask, +} from './object-utils'; + +const defaultGroupColor = '#E0E0E0'; + +function copyShape(state: TrackedShape, data: Partial = {}): TrackedShape { + return { + rotation: state.rotation, + zOrder: state.zOrder, + points: state.points, + occluded: state.occluded, + outside: state.outside, + attributes: {}, + ...data, + }; +} + +interface AnnotationInjection { + labels: Label[]; + groups: { max: number }; + frameMeta: { + deleted_frames: Record; + }; + history: AnnotationHistory; + groupColors: Record; + parentID?: number; + readOnlyFields?: string[]; + nextClientID: () => number; + getMasksOnFrame: (frame: number) => MaskShape[]; +} + +class Annotation { + public clientID: number; + protected taskLabels: Label[]; + protected history: any; + protected groupColors: Record; + protected serverID: number | null; + protected parentID: number | null; + protected group: number; + public label: Label; + protected frame: number; + private _removed: boolean; + public lock: boolean; + protected readOnlyFields: string[]; + protected color: string; + protected source: Source; + public updated: number; + protected attributes: Record; + protected groupObject: { + color: string; + readonly id: number; + }; + + constructor(data, clientID: number, color: string, injection: AnnotationInjection) { + this.taskLabels = injection.labels; + this.history = injection.history; + this.groupColors = injection.groupColors; + this.clientID = clientID; + this.serverID = data.id || null; + this.parentID = injection.parentID || null; + this.group = data.group; + this.label = this.taskLabels[data.label_id]; + this.frame = data.frame; + this._removed = false; + this.lock = false; + this.readOnlyFields = injection.readOnlyFields || []; + this.color = color; + this.source = data.source; + this.updated = Date.now(); + this.attributes = data.attributes.reduce((attributeAccumulator, attr) => { + attributeAccumulator[attr.spec_id] = attr.value; + return attributeAccumulator; + }, {}); + this.groupObject = Object.defineProperties( + {}, { + color: { + get: () => { + if (this.group) { + return this.groupColors[this.group] || colors[this.group % colors.length]; + } + return defaultGroupColor; + }, + set: (newColor) => { + if (this.group && typeof newColor === 'string' && /^#[0-9A-F]{6}$/i.test(newColor)) { + this.groupColors[this.group] = newColor; + this.updated = Date.now(); + } + }, + }, + id: { + get: () => this.group, + }, + }, + ) as Annotation['groupObject']; + + this.appendDefaultAttributes(this.label); + injection.groups.max = Math.max(injection.groups.max, this.group); + } + + protected withContext(frame: number): { + __internal: { + save: (data: ObjectState) => ObjectState; + delete: Annotation['delete']; + }; + } { + return { + __internal: { + save: (this as any).save.bind(this, frame), + delete: this.delete.bind(this), + }, + }; + } + + protected saveLock(lock: boolean, frame: number): void { + const undoLock = this.lock; + const redoLock = lock; + + this.history.do( + HistoryActions.CHANGED_LOCK, + () => { + this.lock = undoLock; + this.updated = Date.now(); + }, + () => { + this.lock = redoLock; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + + this.lock = lock; + } + + protected saveColor(color: string, frame: number): void { + const undoColor = this.color; + const redoColor = color; + + this.history.do( + HistoryActions.CHANGED_COLOR, + () => { + this.color = undoColor; + this.updated = Date.now(); + }, + () => { + this.color = redoColor; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + + this.color = color; + } + + protected saveLabel(label: Label, frame: number): void { + const undoLabel = this.label; + const redoLabel = label; + const undoAttributes = { ...this.attributes }; + this.label = label; + this.attributes = {}; + this.appendDefaultAttributes(label); + + // Try to keep old attributes if name matches and old value is still valid + for (const attribute of redoLabel.attributes) { + for (const oldAttribute of undoLabel.attributes) { + if ( + attribute.name === oldAttribute.name && + validateAttributeValue(undoAttributes[oldAttribute.id], attribute) + ) { + this.attributes[attribute.id] = undoAttributes[oldAttribute.id]; + } + } + } + const redoAttributes = { ...this.attributes }; + + this.history.do( + HistoryActions.CHANGED_LABEL, + () => { + this.label = undoLabel; + this.attributes = undoAttributes; + this.updated = Date.now(); + }, + () => { + this.label = redoLabel; + this.attributes = redoAttributes; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + } + + protected saveAttributes(attributes: Record, frame: number): void { + const undoAttributes = { ...this.attributes }; + + for (const attrID of Object.keys(attributes)) { + this.attributes[attrID] = attributes[attrID]; + } + + const redoAttributes = { ...this.attributes }; + + this.history.do( + HistoryActions.CHANGED_ATTRIBUTES, + () => { + this.attributes = undoAttributes; + this.updated = Date.now(); + }, + () => { + this.attributes = redoAttributes; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + } + + protected validateStateBeforeSave(data: ObjectState, updated: ObjectState['updateFlags']): void { + if (updated.label) { + checkObjectType('label', data.label, null, Label); + } + + const labelAttributes = attrsAsAnObject(data.label.attributes); + if (updated.attributes) { + for (const attrID of Object.keys(data.attributes)) { + const value = data.attributes[attrID]; + if (attrID in labelAttributes) { + if (!validateAttributeValue(value, labelAttributes[attrID])) { + throw new ArgumentError( + `Trying to save an attribute attribute with id ${attrID} and invalid value ${value}`, + ); + } + } else { + throw new ArgumentError( + `The label of the shape doesn't have the attribute with id ${attrID} and value ${value}`, + ); + } + } + } + + if (updated.descriptions) { + if (!Array.isArray(data.descriptions) || data.descriptions.some((desc) => typeof desc !== 'string')) { + throw new ArgumentError( + `Descriptions are expected to be an array of strings but got ${data.descriptions}`, + ); + } + } + + if (updated.occluded) { + checkObjectType('occluded', data.occluded, 'boolean', null); + } + + if (updated.outside) { + checkObjectType('outside', data.outside, 'boolean', null); + } + + if (updated.zOrder) { + checkObjectType('zOrder', data.zOrder, 'integer', null); + } + + if (updated.lock) { + checkObjectType('lock', data.lock, 'boolean', null); + } + + if (updated.pinned) { + checkObjectType('pinned', data.pinned, 'boolean', null); + } + + if (updated.color) { + checkObjectType('color', data.color, 'string', null); + if (!/^#[0-9A-F]{6}$/i.test(data.color)) { + throw new ArgumentError(`Got invalid color value: "${data.color}"`); + } + } + + if (updated.hidden) { + checkObjectType('hidden', data.hidden, 'boolean', null); + } + + if (updated.keyframe) { + checkObjectType('keyframe', data.keyframe, 'boolean', null); + if (Object.keys(this.shapes).length === 1 && data.frame in this.shapes && !data.keyframe) { + throw new ArgumentError( + `Can not remove the latest keyframe of an object "${data.label.name}".` + + 'Consider removing the object instead', + ); + } + } + } + + public clearServerID(): void { + this.serverID = undefined; + } + + public updateServerID(body: any): void { + this.serverID = body.id; + } + + protected appendDefaultAttributes(label: Label): void { + const labelAttributes = label.attributes; + for (const attribute of labelAttributes) { + if (!(attribute.id in this.attributes)) { + this.attributes[attribute.id] = attribute.defaultValue; + } + } + } + + protected updateTimestamp(updated: ObjectState['updateFlags']): void { + const anyChanges = Object.keys(updated).some((key) => !!updated[key]); + if (anyChanges) { + this.updated = Date.now(); + } + } + + public delete(frame: number, force: boolean): boolean { + if (!this.lock || force) { + this.removed = true; + this.history.do( + HistoryActions.REMOVED_OBJECT, + () => { + this.removed = false; + this.updated = Date.now(); + }, + () => { + this.removed = true; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + } + + return this.removed; + } + + public get removed(): boolean { + return this._removed; + } + + public set removed(value: boolean) { + if (value) { + this.clearServerID(); + } + this._removed = value; + } +} + +class Drawn extends Annotation { + protected frameMeta: AnnotationInjection['frameMeta']; + protected descriptions: string[]; + public hidden: boolean; + protected pinned: boolean; + protected shapeType: ShapeType; + + constructor(data, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.frameMeta = injection.frameMeta; + this.descriptions = data.descriptions || []; + this.hidden = false; + this.pinned = true; + this.shapeType = null; + } + + protected saveDescriptions(descriptions: string[]): void { + this.descriptions = [...descriptions]; + } + + protected savePinned(pinned: boolean, frame: number): void { + const undoPinned = this.pinned; + const redoPinned = pinned; + + this.history.do( + HistoryActions.CHANGED_PINNED, + () => { + this.pinned = undoPinned; + this.updated = Date.now(); + }, + () => { + this.pinned = redoPinned; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + + this.pinned = pinned; + } + + protected saveHidden(hidden: boolean, frame: number): void { + const undoHidden = this.hidden; + const redoHidden = hidden; + + this.history.do( + HistoryActions.CHANGED_HIDDEN, + () => { + this.hidden = undoHidden; + this.updated = Date.now(); + }, + () => { + this.hidden = redoHidden; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + + this.hidden = hidden; + } + + private fitPoints(points: number[], rotation: number, maxX: number, maxY: number): number[] { + const { shapeType, parentID } = this; + checkObjectType('rotation', rotation, 'number', null); + points.forEach((coordinate) => checkObjectType('coordinate', coordinate, 'number', null)); + + if (parentID !== null || shapeType === ShapeType.CUBOID || + shapeType === ShapeType.ELLIPSE || !!rotation) { + // cuboids and rotated bounding boxes cannot be fitted + return points; + } + + const fittedPoints = []; + + for (let i = 0; i < points.length - 1; i += 2) { + const x = points[i]; + const y = points[i + 1]; + const clampedX = clamp(x, 0, maxX); + const clampedY = clamp(y, 0, maxY); + fittedPoints.push(clampedX, clampedY); + } + + return fittedPoints; + } + + protected validateStateBeforeSave(data: ObjectState, updated: ObjectState['updateFlags'], frame?: number): number[] { + Annotation.prototype.validateStateBeforeSave.call(this, data, updated); + + let fittedPoints = []; + if (updated.points && Number.isInteger(frame)) { + checkObjectType('points', data.points, null, Array); + checkNumberOfPoints(this.shapeType, data.points); + // cut points + const { width, height, filename } = this.frameMeta[frame]; + fittedPoints = this.fitPoints(data.points, data.rotation, width, height); + let check = true; + if (filename && filename.slice(filename.length - 3) === 'pcd') { + check = false; + } + if (check) { + if (!checkShapeArea(this.shapeType, fittedPoints)) { + fittedPoints = []; + } + } + } + + return fittedPoints; + } +} + +interface RawShapeData { + id?: number; + clientID?: number; + label_id: number; + group: number; + frame: number; + source: Source; + attributes: { spec_id: number; value: string }[]; + elements: { + id?: number; + attributes: RawTrackData['attributes']; + label_id: number; + occluded: boolean; + outside: boolean; + points: number[]; + type: ShapeType; + }[]; + occluded: boolean; + outside?: boolean; // only for skeleton elements + points?: number[]; + rotation: number; + z_order: number; + type: ShapeType; +} + +export class Shape extends Drawn { + public points: number[]; + public occluded: boolean; + public outside: boolean; + protected rotation: number; + protected zOrder: number; + + constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.points = data.points; + this.rotation = data.rotation || 0; + this.occluded = data.occluded; + this.outside = data.outside; + this.zOrder = data.z_order; + } + + // Method is used to export data to the server + public toJSON(): RawShapeData { + const result: RawShapeData = { + type: this.shapeType, + clientID: this.clientID, + occluded: this.occluded, + z_order: this.zOrder, + points: this.points.slice(), + rotation: this.rotation, + attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { + attributeAccumulator.push({ + spec_id: +attrId, + value: this.attributes[attrId], + }); + + return attributeAccumulator; + }, []), + elements: [], + frame: this.frame, + label_id: this.label.id, + group: this.group, + source: this.source, + }; + + if (this.serverID !== null) { + result.id = this.serverID; + } + + if (typeof this.outside !== 'undefined') { + result.outside = this.outside; + } + + return result; + } + + public get(frame): { outside?: boolean } & Omit, 'keyframe' | 'keyframes' | 'elements' | 'outside'> { + if (frame !== this.frame) { + throw new ScriptingError('Received frame is not equal to the frame of the shape'); + } + + const result: ReturnType = { + objectType: ObjectType.SHAPE, + shapeType: this.shapeType, + clientID: this.clientID, + serverID: this.serverID, + parentID: this.parentID, + occluded: this.occluded, + lock: this.lock, + zOrder: this.zOrder, + points: this.points.slice(), + rotation: this.rotation, + attributes: { ...this.attributes }, + descriptions: [...this.descriptions], + label: this.label, + group: this.groupObject, + color: this.color, + hidden: this.hidden, + updated: this.updated, + pinned: this.pinned, + frame, + source: this.source, + ...this.withContext(frame), + }; + + if (typeof this.outside !== 'undefined') { + result.outside = this.outside; + } + + return result; + } + + protected saveRotation(rotation: number, frame: number): void { + const undoRotation = this.rotation; + const redoRotation = rotation; + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + + this.history.do( + HistoryActions.CHANGED_ROTATION, + () => { + this.source = undoSource; + this.rotation = undoRotation; + this.updated = Date.now(); + }, + () => { + this.source = redoSource; + this.rotation = redoRotation; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + + this.source = redoSource; + this.rotation = redoRotation; + } + + protected savePoints(points: number[], frame: number): void { + const undoPoints = this.points; + const redoPoints = points; + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + + this.history.do( + HistoryActions.CHANGED_POINTS, + () => { + this.points = undoPoints; + this.source = undoSource; + this.updated = Date.now(); + }, + () => { + this.points = redoPoints; + this.source = redoSource; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + + this.source = redoSource; + this.points = redoPoints; + } + + protected saveOccluded(occluded: boolean, frame: number): void { + const undoOccluded = this.occluded; + const redoOccluded = occluded; + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + + this.history.do( + HistoryActions.CHANGED_OCCLUDED, + () => { + this.occluded = undoOccluded; + this.source = undoSource; + this.updated = Date.now(); + }, + () => { + this.occluded = redoOccluded; + this.source = redoSource; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + + this.source = redoSource; + this.occluded = redoOccluded; + } + + protected saveOutside(outside: boolean, frame: number): void { + const undoOutside = this.outside; + const redoOutside = outside; + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + + this.history.do( + HistoryActions.CHANGED_OCCLUDED, + () => { + this.outside = undoOutside; + this.source = undoSource; + this.updated = Date.now(); + }, + () => { + this.occluded = redoOutside; + this.source = redoSource; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + + this.source = redoSource; + this.outside = redoOutside; + } + + protected saveZOrder(zOrder: number, frame: number): void { + const undoZOrder = this.zOrder; + const redoZOrder = zOrder; + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + + this.history.do( + HistoryActions.CHANGED_ZORDER, + () => { + this.zOrder = undoZOrder; + this.source = undoSource; + this.updated = Date.now(); + }, + () => { + this.zOrder = redoZOrder; + this.source = redoSource; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + + this.source = redoSource; + this.zOrder = redoZOrder; + } + + public save(frame: number, data: ObjectState): ObjectState { + if (frame !== this.frame) { + throw new ScriptingError('Received frame is not equal to the frame of the shape'); + } + + if (this.lock && data.lock) { + return new ObjectState(this.get(frame)); + } + + const updated = data.updateFlags; + for (const readOnlyField of this.readOnlyFields) { + updated[readOnlyField] = false; + } + + const fittedPoints = this.validateStateBeforeSave(data, updated, frame); + const { rotation } = data; + + // Now when all fields are validated, we can apply them + if (updated.label) { + this.saveLabel(data.label, frame); + } + + if (updated.attributes) { + this.saveAttributes(data.attributes, frame); + } + + if (updated.descriptions) { + this.saveDescriptions(data.descriptions); + } + + if (updated.rotation) { + this.saveRotation(rotation, frame); + } + + if (updated.points && fittedPoints.length) { + this.savePoints(fittedPoints, frame); + } + + if (updated.occluded) { + this.saveOccluded(data.occluded, frame); + } + + if (updated.outside) { + this.saveOutside(data.outside, frame); + } + + if (updated.zOrder) { + this.saveZOrder(data.zOrder, frame); + } + + if (updated.lock) { + this.saveLock(data.lock, frame); + } + + if (updated.pinned) { + this.savePinned(data.pinned, frame); + } + + if (updated.color) { + this.saveColor(data.color, frame); + } + + if (updated.hidden) { + this.saveHidden(data.hidden, frame); + } + + this.updateTimestamp(updated); + updated.reset(); + + return new ObjectState(this.get(frame)); + } +} + +interface RawTrackData { + id?: number; + clientID?: number; + label_id: number; + group: number; + frame: number; + source: Source; + attributes: { spec_id: number; value: string }[]; + shapes: { + attributes: RawTrackData['attributes']; + id?: number; + points?: number[]; + frame: number; + occluded: boolean; + outside: boolean; + rotation: number; + type: ShapeType; + z_order: number; + }[]; + elements?: RawTrackData[]; +} + +interface TrackedShape { + serverID?: number; + occluded: boolean; + outside: boolean; + rotation: number; + zOrder: number; + points?: number[]; + attributes: Record; +} + +export interface InterpolatedPosition { + points: number[]; + rotation: number; + occluded: boolean; + outside: boolean; + zOrder: number; +} + +export class Track extends Drawn { + public shapes: Record; + constructor(data: RawTrackData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapes = data.shapes.reduce((shapeAccumulator, value) => { + shapeAccumulator[value.frame] = { + serverID: value.id, + occluded: value.occluded, + zOrder: value.z_order, + points: value.points, + outside: value.outside, + rotation: value.rotation || 0, + attributes: value.attributes.reduce((attributeAccumulator, attr) => { + attributeAccumulator[attr.spec_id] = attr.value; + return attributeAccumulator; + }, {}), + }; + + return shapeAccumulator; + }, {}); + } + + // Method is used to export data to the server + public toJSON(): RawTrackData { + const labelAttributes = attrsAsAnObject(this.label.attributes); + + const result: RawTrackData = { + clientID: this.clientID, + label_id: this.label.id, + frame: this.frame, + group: this.group, + source: this.source, + elements: [], + attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { + if (!labelAttributes[attrId].mutable) { + attributeAccumulator.push({ + spec_id: +attrId, + value: this.attributes[attrId], + }); + } + + return attributeAccumulator; + }, []), + shapes: Object.keys(this.shapes).reduce((shapesAccumulator, frame) => { + shapesAccumulator.push({ + type: this.shapeType, + occluded: this.shapes[frame].occluded, + z_order: this.shapes[frame].zOrder, + rotation: this.shapes[frame].rotation, + outside: this.shapes[frame].outside, + attributes: Object.keys(this.shapes[frame].attributes).reduce( + (attributeAccumulator, attrId) => { + if (labelAttributes[attrId].mutable) { + attributeAccumulator.push({ + spec_id: +attrId, + value: this.shapes[frame].attributes[attrId], + }); + } + + return attributeAccumulator; + }, + [], + ), + id: this.shapes[frame].serverID, + frame: +frame, + }); + + if (this.shapes[frame].points) { + shapesAccumulator[shapesAccumulator.length - 1].points = [...this.shapes[frame].points]; + } + + return shapesAccumulator; + }, []), + }; + + if (this.serverID !== null) { + result.id = this.serverID; + } + + return result; + } + + public get(frame: number): Omit, 'elements'> { + const { + prev, next, first, last, + } = this.boundedKeyframes(frame); + + return { + ...this.getPosition(frame, prev, next), + attributes: this.getAttributes(frame), + descriptions: [...this.descriptions], + group: this.groupObject, + objectType: ObjectType.TRACK, + shapeType: this.shapeType, + clientID: this.clientID, + serverID: this.serverID, + parentID: this.parentID, + lock: this.lock, + color: this.color, + hidden: this.hidden, + updated: this.updated, + label: this.label, + pinned: this.pinned, + keyframes: { + prev, + next, + first, + last, + }, + frame, + source: this.source, + ...this.withContext(frame), + }; + } + + public boundedKeyframes(targetFrame: number): ObjectState['keyframes'] { + const frames = Object.keys(this.shapes).map((frame) => +frame); + let lDiff = Number.MAX_SAFE_INTEGER; + let rDiff = Number.MAX_SAFE_INTEGER; + let first = Number.MAX_SAFE_INTEGER; + let last = Number.MIN_SAFE_INTEGER; + + for (const frame of frames) { + if (frame in this.frameMeta.deleted_frames) { + continue; + } + + if (frame < first) { + first = frame; + } + if (frame > last) { + last = frame; + } + + const diff = Math.abs(targetFrame - frame); + + if (frame < targetFrame && diff < lDiff) { + lDiff = diff; + } else if (frame > targetFrame && diff < rDiff) { + rDiff = diff; + } + } + + const prev = lDiff === Number.MAX_SAFE_INTEGER ? null : targetFrame - lDiff; + const next = rDiff === Number.MAX_SAFE_INTEGER ? null : targetFrame + rDiff; + + return { + prev, + next, + first, + last, + }; + } + + protected getAttributes(targetFrame: number): Record { + const result = {}; + + // First of all copy all unmutable attributes + for (const attrID in this.attributes) { + if (Object.prototype.hasOwnProperty.call(this.attributes, attrID)) { + result[attrID] = this.attributes[attrID]; + } + } + + // Secondly get latest mutable attributes up to target frame + const frames = Object.keys(this.shapes).sort((a, b) => +a - +b); + for (const frame of frames) { + if (+frame <= targetFrame) { + const { attributes } = this.shapes[frame]; + + for (const attrID in attributes) { + if (Object.prototype.hasOwnProperty.call(attributes, attrID)) { + result[attrID] = attributes[attrID]; + } + } + } + } + + return result; + } + + public updateServerID(body: RawTrackData): void { + this.serverID = body.id; + for (const shape of body.shapes) { + this.shapes[shape.frame].serverID = shape.id; + } + } + + public clearServerID(): void { + Drawn.prototype.clearServerID.call(this); + for (const keyframe of Object.keys(this.shapes)) { + this.shapes[keyframe].serverID = undefined; + } + } + + protected saveLabel(label: Label, frame: number): void { + const undoLabel = this.label; + const redoLabel = label; + const undoAttributes = { + unmutable: { ...this.attributes }, + mutable: Object.keys(this.shapes).map((key) => ({ + frame: +key, + attributes: { ...this.shapes[key].attributes }, + })), + }; + + this.label = label; + this.attributes = {}; + for (const shape of Object.values(this.shapes)) { + shape.attributes = {}; + } + this.appendDefaultAttributes(label); + + const redoAttributes = { + unmutable: { ...this.attributes }, + mutable: Object.keys(this.shapes).map((key) => ({ + frame: +key, + attributes: { ...this.shapes[key].attributes }, + })), + }; + + this.history.do( + HistoryActions.CHANGED_LABEL, + () => { + this.label = undoLabel; + this.attributes = undoAttributes.unmutable; + for (const mutable of undoAttributes.mutable) { + this.shapes[mutable.frame].attributes = mutable.attributes; + } + this.updated = Date.now(); + }, + () => { + this.label = redoLabel; + this.attributes = redoAttributes.unmutable; + for (const mutable of redoAttributes.mutable) { + this.shapes[mutable.frame].attributes = mutable.attributes; + } + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + } + + protected saveAttributes(attributes: Record, frame: number): void { + const current = this.get(frame); + const labelAttributes = attrsAsAnObject(this.label.attributes); + + const wasKeyframe = frame in this.shapes; + const undoAttributes = this.attributes; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + + let mutableAttributesUpdated = false; + const redoAttributes = { ...this.attributes }; + for (const attrID of Object.keys(attributes)) { + if (!labelAttributes[attrID].mutable) { + redoAttributes[attrID] = attributes[attrID]; + } else if (attributes[attrID] !== current.attributes[attrID]) { + mutableAttributesUpdated = mutableAttributesUpdated || + // not keyframe yet + !(frame in this.shapes) || + // keyframe, but without this attrID + !(attrID in this.shapes[frame].attributes) || + // keyframe with attrID, but with another value + this.shapes[frame].attributes[attrID] !== attributes[attrID]; + } + } + let redoShape; + if (mutableAttributesUpdated) { + if (wasKeyframe) { + redoShape = { + ...this.shapes[frame], + attributes: { + ...this.shapes[frame].attributes, + }, + }; + } else { + redoShape = { + frame, + zOrder: current.zOrder, + points: current.points, + outside: current.outside, + occluded: current.occluded, + attributes: {}, + }; + } + } + + for (const attrID of Object.keys(attributes)) { + if (labelAttributes[attrID].mutable && attributes[attrID] !== current.attributes[attrID]) { + redoShape.attributes[attrID] = attributes[attrID]; + } + } + + this.attributes = redoAttributes; + if (redoShape) { + this.shapes[frame] = redoShape; + } + + this.history.do( + HistoryActions.CHANGED_ATTRIBUTES, + () => { + this.attributes = undoAttributes; + if (undoShape) { + this.shapes[frame] = undoShape; + } else if (redoShape) { + delete this.shapes[frame]; + } + this.updated = Date.now(); + }, + () => { + this.attributes = redoAttributes; + if (redoShape) { + this.shapes[frame] = redoShape; + } + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + } + + protected appendShapeActionToHistory(actionType, frame, undoShape, redoShape, undoSource, redoSource): void { + this.history.do( + actionType, + () => { + if (!undoShape) { + delete this.shapes[frame]; + } else { + this.shapes[frame] = undoShape; + } + this.source = undoSource; + this.updated = Date.now(); + }, + () => { + if (!redoShape) { + delete this.shapes[frame]; + } else { + this.shapes[frame] = redoShape; + } + this.source = redoSource; + this.updated = Date.now(); + }, + [this.clientID], + frame, + ); + } + + protected saveRotation(rotation: number, frame: number): void { + const wasKeyframe = frame in this.shapes; + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = wasKeyframe ? + { ...this.shapes[frame], rotation } : copyShape(this.get(frame), { rotation }); + + this.shapes[frame] = redoShape; + this.source = redoSource; + this.appendShapeActionToHistory( + HistoryActions.CHANGED_ROTATION, + frame, + undoShape, + redoShape, + undoSource, + redoSource, + ); + } + + protected savePoints(points: number[], frame: number): void { + const wasKeyframe = frame in this.shapes; + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = wasKeyframe ? + { ...this.shapes[frame], points } : copyShape(this.get(frame), { points }); + + this.shapes[frame] = redoShape; + this.source = redoSource; + this.appendShapeActionToHistory( + HistoryActions.CHANGED_POINTS, + frame, + undoShape, + redoShape, + undoSource, + redoSource, + ); + } + + protected saveOutside(frame: number, outside: boolean): void { + const wasKeyframe = frame in this.shapes; + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = wasKeyframe ? + { ...this.shapes[frame], outside } : + copyShape(this.get(frame), { outside }); + + this.shapes[frame] = redoShape; + this.source = redoSource; + this.appendShapeActionToHistory( + HistoryActions.CHANGED_OUTSIDE, + frame, + undoShape, + redoShape, + undoSource, + redoSource, + ); + } + + protected saveOccluded(occluded: boolean, frame: number): void { + const wasKeyframe = frame in this.shapes; + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = wasKeyframe ? + { ...this.shapes[frame], occluded } : + copyShape(this.get(frame), { occluded }); + + this.shapes[frame] = redoShape; + this.source = redoSource; + this.appendShapeActionToHistory( + HistoryActions.CHANGED_OCCLUDED, + frame, + undoShape, + redoShape, + undoSource, + redoSource, + ); + } + + protected saveZOrder(zOrder: number, frame: number): void { + const wasKeyframe = frame in this.shapes; + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = wasKeyframe ? + { ...this.shapes[frame], zOrder } : + copyShape(this.get(frame), { zOrder }); + + this.shapes[frame] = redoShape; + this.source = redoSource; + this.appendShapeActionToHistory( + HistoryActions.CHANGED_ZORDER, + frame, + undoShape, + redoShape, + undoSource, + redoSource, + ); + } + + protected saveKeyframe(frame: number, keyframe: boolean): void { + const wasKeyframe = frame in this.shapes; + + if ((keyframe && wasKeyframe) || (!keyframe && !wasKeyframe)) { + return; + } + + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + const undoShape = wasKeyframe ? this.shapes[frame] : undefined; + const redoShape = keyframe ? copyShape(this.get(frame)) : undefined; + + this.source = redoSource; + if (redoShape) { + this.shapes[frame] = redoShape; + } else { + delete this.shapes[frame]; + } + + this.appendShapeActionToHistory( + HistoryActions.CHANGED_KEYFRAME, + frame, + undoShape, + redoShape, + undoSource, + redoSource, + ); + } + + public save(frame: number, data: ObjectState): ObjectState { + if (this.lock && data.lock) { + return new ObjectState(this.get(frame)); + } + + const updated = data.updateFlags; + for (const readOnlyField of this.readOnlyFields) { + updated[readOnlyField] = false; + } + + const fittedPoints = this.validateStateBeforeSave(data, updated, frame); + const { rotation } = data; + + if (updated.label) { + this.saveLabel(data.label, frame); + } + + if (updated.lock) { + this.saveLock(data.lock, frame); + } + + if (updated.pinned) { + this.savePinned(data.pinned, frame); + } + + if (updated.color) { + this.saveColor(data.color, frame); + } + + if (updated.hidden) { + this.saveHidden(data.hidden, frame); + } + + if (updated.points && fittedPoints.length) { + this.savePoints(fittedPoints, frame); + } + + if (updated.rotation) { + this.saveRotation(rotation, frame); + } + + if (updated.outside) { + this.saveOutside(frame, data.outside); + } + + if (updated.occluded) { + this.saveOccluded(data.occluded, frame); + } + + if (updated.zOrder) { + this.saveZOrder(data.zOrder, frame); + } + + if (updated.attributes) { + this.saveAttributes(data.attributes, frame); + } + + if (updated.descriptions) { + this.saveDescriptions(data.descriptions); + } + + if (updated.keyframe) { + this.saveKeyframe(frame, data.keyframe); + } + + this.updateTimestamp(updated); + updated.reset(); + + return new ObjectState(this.get(frame)); + } + + protected getPosition( + targetFrame: number, leftKeyframe: number | null, rightFrame: number | null, + ): InterpolatedPosition & { keyframe: boolean } { + const leftFrame = targetFrame in this.shapes ? targetFrame : leftKeyframe; + const rightPosition = Number.isInteger(rightFrame) ? this.shapes[rightFrame] : null; + const leftPosition = Number.isInteger(leftFrame) ? this.shapes[leftFrame] : null; + + if (leftPosition && rightPosition) { + return { + ...(this as any).interpolatePosition( + leftPosition, + rightPosition, + (targetFrame - leftFrame) / (rightFrame - leftFrame), + ), + keyframe: targetFrame in this.shapes, + }; + } + + const singlePosition = leftPosition || rightPosition; + if (singlePosition) { + return { + points: [...singlePosition.points], + rotation: singlePosition.rotation, + occluded: singlePosition.occluded, + zOrder: singlePosition.zOrder, + keyframe: targetFrame in this.shapes, + outside: singlePosition === rightPosition ? true : singlePosition.outside, + }; + } + + throw new DataError( + 'No one left position or right position was found. ' + + `Interpolation impossible. Client ID: ${this.clientID}`, + ); + } +} + +interface RawTagData { + id?: number; + clientID?: number; + label_id: number; + frame: number; + group: number; + source: Source; + attributes: { spec_id: number; value: string }[]; +} + +export class Tag extends Annotation { + // Method is used to export data to the server + public toJSON(): RawTagData { + const result: RawTagData = { + clientID: this.clientID, + frame: this.frame, + label_id: this.label.id, + source: this.source, + group: 0, // TODO: why server requires group for tags? + attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { + attributeAccumulator.push({ + spec_id: +attrId, + value: this.attributes[attrId], + }); + + return attributeAccumulator; + }, []), + }; + + if (this.serverID !== null) { + result.id = this.serverID; + } + + return result; + } + + public get(frame: number): Omit, + 'elements' | 'occluded' | 'outside' | 'rotation' | 'zOrder' | + 'points' | 'hidden' | 'pinned' | 'keyframe' | 'shapeType' | + 'parentID' | 'descriptions' | 'keyframes' + > { + if (frame !== this.frame) { + throw new ScriptingError('Received frame is not equal to the frame of the shape'); + } + + return { + objectType: ObjectType.TAG, + clientID: this.clientID, + serverID: this.serverID, + lock: this.lock, + attributes: { ...this.attributes }, + label: this.label, + group: this.groupObject, + color: this.color, + updated: this.updated, + frame, + source: this.source, + ...this.withContext(frame), + }; + } + + public save(frame: number, data: ObjectState): ObjectState { + if (frame !== this.frame) { + throw new ScriptingError('Received frame is not equal to the frame of the tag'); + } + + if (this.lock && data.lock) { + return new ObjectState(this.get(frame)); + } + + const updated = data.updateFlags; + for (const readOnlyField of this.readOnlyFields) { + updated[readOnlyField] = false; + } + + this.validateStateBeforeSave(data, updated); + + // Now when all fields are validated, we can apply them + if (updated.label) { + this.saveLabel(data.label, frame); + } + + if (updated.attributes) { + this.saveAttributes(data.attributes, frame); + } + + if (updated.lock) { + this.saveLock(data.lock, frame); + } + + if (updated.color) { + this.saveColor(data.color, frame); + } + + this.updateTimestamp(updated); + updated.reset(); + + return new ObjectState(this.get(frame)); + } +} + +export class RectangleShape extends Shape { + constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.RECTANGLE; + this.pinned = false; + checkNumberOfPoints(this.shapeType, this.points); + } + + static distance(points: number[], x: number, y: number, angle: number): number { + const [xtl, ytl, xbr, ybr] = points; + const cx = xtl + (xbr - xtl) / 2; + const cy = ytl + (ybr - ytl) / 2; + const [rotX, rotY] = rotatePoint(x, y, -angle, cx, cy); + + if (!(rotX >= xtl && rotX <= xbr && rotY >= ytl && rotY <= ybr)) { + // Cursor is outside of a box + return null; + } + + // The shortest distance from point to an edge + return Math.min.apply(null, [rotX - xtl, rotY - ytl, xbr - rotX, ybr - rotY]); + } +} + +export class EllipseShape extends Shape { + constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.ELLIPSE; + this.pinned = false; + checkNumberOfPoints(this.shapeType, this.points); + } + + static distance(points: number[], x: number, y: number, angle: number): number { + const [cx, cy, rightX, topY] = points; + const [rx, ry] = [rightX - cx, cy - topY]; + const [rotX, rotY] = rotatePoint(x, y, -angle, cx, cy); + // https://math.stackexchange.com/questions/76457/check-if-a-point-is-within-an-ellipse + const pointWithinEllipse = (_x: number, _y: number): boolean => ( + ((_x - cx) ** 2) / rx ** 2) + (((_y - cy) ** 2) / ry ** 2 + ) <= 1; + + if (!pointWithinEllipse(rotX, rotY)) { + // Cursor is outside of an ellipse + return null; + } + + if (Math.abs(x - cx) < Number.EPSILON && Math.abs(y - cy) < Number.EPSILON) { + // cursor is near to the center, just return minimum of height, width + return Math.min(rx, ry); + } + + // ellipse equation is x^2/rx^2 + y^2/ry^2 = 1 + // from this equation: + // x^2 = ((rx * ry)^2 - (y * rx)^2) / ry^2 + // y^2 = ((rx * ry)^2 - (x * ry)^2) / rx^2 + + // we have one point inside the ellipse, let's build two lines (horizontal and vertical) through the point + // and find their interception with ellipse + const x2Equation = (_y: number): number => (((rx * ry) ** 2) - ((_y * rx) ** 2)) / (ry ** 2); + const y2Equation = (_x: number): number => (((rx * ry) ** 2) - ((_x * ry) ** 2)) / (rx ** 2); + + // shift x,y to the ellipse coordinate system to compute equation correctly + // y axis is inverted + const [shiftedX, shiftedY] = [x - cx, cy - y]; + const [x1, x2] = [Math.sqrt(x2Equation(shiftedY)), -Math.sqrt(x2Equation(shiftedY))]; + const [y1, y2] = [Math.sqrt(y2Equation(shiftedX)), -Math.sqrt(y2Equation(shiftedX))]; + + // found two points on ellipse edge + const ellipseP1X = shiftedX >= 0 ? x1 : x2; // ellipseP1Y is shiftedY + const ellipseP2Y = shiftedY >= 0 ? y1 : y2; // ellipseP1X is shiftedX + + // found diffs between two points on edges and target point + const diff1X = ellipseP1X - shiftedX; + const diff2Y = ellipseP2Y - shiftedY; + + // return minimum, get absolute value because we need distance, not diff + return Math.min(Math.abs(diff1X), Math.abs(diff2Y)); + } +} + +class PolyShape extends Shape { + constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.rotation = 0; // is not supported + } +} + +export class PolygonShape extends PolyShape { + constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.POLYGON; + checkNumberOfPoints(this.shapeType, this.points); + } + + static distance(points: number[], x: number, y: number): number { + function position(x1, y1, x2, y2): number { + return (x2 - x1) * (y - y1) - (x - x1) * (y2 - y1); + } + + let wn = 0; + const distances = []; + + for (let i = 0, j = points.length - 2; i < points.length - 1; j = i, i += 2) { + // Current point + const x1 = points[j]; + const y1 = points[j + 1]; + + // Next point + const x2 = points[i]; + const y2 = points[i + 1]; + + // Check if a point is inside a polygon + // with a winding numbers algorithm + // https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm + if (y1 <= y) { + if (y2 > y) { + if (position(x1, y1, x2, y2) > 0) { + wn++; + } + } + } else if (y2 <= y) { + if (position(x1, y1, x2, y2) < 0) { + wn--; + } + } + + // Find the shortest distance from point to an edge + // Get an equation of a line in general + const aCoef = y1 - y2; + const bCoef = x2 - x1; + + // Vector (aCoef, bCoef) is a perpendicular to line + // Now find the point where two lines + // (edge and its perpendicular through the point (x,y)) are cross + const xCross = x - aCoef; + const yCross = y - bCoef; + + if ((xCross - x1) * (x2 - xCross) >= 0 && (yCross - y1) * (y2 - yCross) >= 0) { + // Cross point is on segment between p1(x1,y1) and p2(x2,y2) + distances.push(Math.sqrt((x - xCross) ** 2 + (y - yCross) ** 2)); + } else { + distances.push( + Math.min( + Math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2), + Math.sqrt((x2 - x) ** 2 + (y2 - y) ** 2), + ), + ); + } + } + + if (wn !== 0) { + return Math.min.apply(null, distances); + } + + return null; + } +} + +export class PolylineShape extends PolyShape { + constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.POLYLINE; + checkNumberOfPoints(this.shapeType, this.points); + } + + static distance(points: number[], x: number, y: number): number { + const distances = []; + for (let i = 0; i < points.length - 2; i += 2) { + // Current point + const x1 = points[i]; + const y1 = points[i + 1]; + + // Next point + const x2 = points[i + 2]; + const y2 = points[i + 3]; + + // Find the shortest distance from point to an edge + if ((x - x1) * (x2 - x) >= 0 && (y - y1) * (y2 - y) >= 0) { + // Find the length of a perpendicular + // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line + distances.push( + Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / + Math.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2), + ); + } else { + // The link below works for lines (which have infinite length) + // There is a case when perpendicular doesn't cross the edge + // In this case we don't use the computed distance + // Instead we use just distance to the nearest point + distances.push( + Math.min( + Math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2), + Math.sqrt((x2 - x) ** 2 + (y2 - y) ** 2), + ), + ); + } + } + + return Math.min.apply(null, distances); + } +} + +export class PointsShape extends PolyShape { + constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.POINTS; + checkNumberOfPoints(this.shapeType, this.points); + } + + static distance(points: number[], x: number, y: number): number { + const distances = []; + for (let i = 0; i < points.length; i += 2) { + const x1 = points[i]; + const y1 = points[i + 1]; + + distances.push(Math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2)); + } + + return Math.min.apply(null, distances); + } +} + +interface Point2D { + x: number; + y: number; +} + +export class CuboidShape extends Shape { + constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.rotation = 0; + this.shapeType = ShapeType.CUBOID; + this.pinned = false; + checkNumberOfPoints(this.shapeType, this.points); + } + + static makeHull(geoPoints: Point2D[]): Point2D[] { + // Returns the convex hull, assuming that each points[i] <= points[i + 1]. + function makeHullPresorted(points: Point2D[]): Point2D[] { + if (points.length <= 1) return points.slice(); + + // Andrew's monotone chain algorithm. Positive y coordinates correspond to 'up' + // as per the mathematical convention, instead of 'down' as per the computer + // graphics convention. This doesn't affect the correctness of the result. + + const upperHull = []; + for (let i = 0; i < points.length; i += 1) { + const p = points[`${i}`]; + while (upperHull.length >= 2) { + const q = upperHull[upperHull.length - 1]; + const r = upperHull[upperHull.length - 2]; + if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) upperHull.pop(); + else break; + } + upperHull.push(p); + } + upperHull.pop(); + + const lowerHull = []; + for (let i = points.length - 1; i >= 0; i -= 1) { + const p = points[`${i}`]; + while (lowerHull.length >= 2) { + const q = lowerHull[lowerHull.length - 1]; + const r = lowerHull[lowerHull.length - 2]; + if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) lowerHull.pop(); + else break; + } + lowerHull.push(p); + } + lowerHull.pop(); + + if ( + upperHull.length === 1 && + lowerHull.length === 1 && + upperHull[0].x === lowerHull[0].x && + upperHull[0].y === lowerHull[0].y + ) return upperHull; + return upperHull.concat(lowerHull); + } + + function pointsComparator(a, b): number { + if (a.x < b.x) return -1; + if (a.x > b.x) return +1; + if (a.y < b.y) return -1; + if (a.y > b.y) return +1; + return 0; + } + + const newPoints = geoPoints.slice(); + newPoints.sort(pointsComparator); + return makeHullPresorted(newPoints); + } + + static contain(shapePoints, x, y): boolean { + function isLeft(P0, P1, P2): number { + return (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y); + } + + const points = CuboidShape.makeHull(shapePoints); + let wn = 0; + for (let i = 0; i < points.length; i += 1) { + const p1 = points[`${i}`]; + const p2 = points[i + 1] || points[0]; + + if (p1.y <= y) { + if (p2.y > y) { + if (isLeft(p1, p2, { x, y }) > 0) { + wn += 1; + } + } + } else if (p2.y < y) { + if (isLeft(p1, p2, { x, y }) < 0) { + wn -= 1; + } + } + } + + return wn !== 0; + } + + static distance(actualPoints: number[], x: number, y: number): number { + const points = []; + + for (let i = 0; i < 16; i += 2) { + points.push({ x: actualPoints[i], y: actualPoints[i + 1] }); + } + + if (!CuboidShape.contain(points, x, y)) return null; + + let minDistance = Number.MAX_SAFE_INTEGER; + for (let i = 0; i < points.length; i += 1) { + const p1 = points[`${i}`]; + const p2 = points[i + 1] || points[0]; + + // perpendicular from point to straight length + const distance = Math.abs((p2.y - p1.y) * x - (p2.x - p1.x) * y + p2.x * p1.y - p2.y * p1.x) / + Math.sqrt((p2.y - p1.y) ** 2 + (p2.x - p1.x) ** 2); + + // check if perpendicular belongs to the straight segment + const a = (p1.x - x) ** 2 + (p1.y - y) ** 2; + const b = (p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2; + const c = (p2.x - x) ** 2 + (p2.y - y) ** 2; + if (distance < minDistance && a + b - c >= 0 && c + b - a >= 0) { + minDistance = distance; + } + } + return minDistance; + } +} + +export class SkeletonShape extends Shape { + private elements: Shape[]; + + constructor(data: RawShapeData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.SKELETON; + this.pinned = false; + this.rotation = 0; + this.occluded = false; + this.points = undefined; + this.readOnlyFields = ['points', 'label', 'occluded']; + + /* eslint-disable-next-line @typescript-eslint/no-use-before-define */ + this.elements = data.elements.map((element) => shapeFactory({ + ...element, + group: this.group, + z_order: this.zOrder, + source: this.source, + rotation: 0, + frame: data.frame, + elements: [], + }, injection.nextClientID(), { + ...injection, + parentID: this.clientID, + readOnlyFields: ['group', 'zOrder', 'source', 'rotation'], + })) as any as Shape[]; + } + + static distance(points: number[], x: number, y: number): number { + const distances = []; + let xtl = Number.MAX_SAFE_INTEGER; + let ytl = Number.MAX_SAFE_INTEGER; + let xbr = 0; + let ybr = 0; + + const MARGIN = 20; + for (let i = 0; i < points.length; i += 2) { + const x1 = points[i]; + const y1 = points[i + 1]; + xtl = Math.min(x1, xtl); + ytl = Math.min(y1, ytl); + xbr = Math.max(x1, xbr); + ybr = Math.max(y1, ybr); + + distances.push(Math.sqrt((x1 - x) ** 2 + (y1 - y) ** 2)); + } + + xtl -= MARGIN; + xbr += MARGIN; + ytl -= MARGIN; + ybr += MARGIN; + + if (!(x >= xtl && x <= xbr && y >= ytl && y <= ybr)) { + // Cursor is outside of a box + return null; + } + + // The shortest distance from point to an edge + return Math.min.apply(null, [x - xtl, y - ytl, xbr - x, ybr - y]); + } + + // Method is used to export data to the server + public toJSON(): RawShapeData { + const elements = this.elements.map((element) => ({ + ...element.toJSON(), + outside: element.outside, + points: [...element.points], + source: this.source, + group: this.group, + z_order: this.zOrder, + rotation: 0, + })); + + const result: RawShapeData = { + type: this.shapeType, + clientID: this.clientID, + occluded: elements.every((el) => el.occluded), + outside: elements.every((el) => el.outside), + z_order: this.zOrder, + points: this.points, + rotation: 0, + attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { + attributeAccumulator.push({ + spec_id: +attrId, + value: this.attributes[attrId], + }); + + return attributeAccumulator; + }, []), + elements, + frame: this.frame, + label_id: this.label.id, + group: this.group, + source: this.source, + }; + + if (this.serverID !== null) { + result.id = this.serverID; + } + + return result; + } + + public get(frame): Omit, 'parentID' | 'keyframe' | 'keyframes'> { + if (frame !== this.frame) { + throw new ScriptingError('Received frame is not equal to the frame of the shape'); + } + + const elements = this.elements.map((element) => ({ + ...element.get(frame), + source: this.source, + group: this.groupObject, + zOrder: this.zOrder, + rotation: 0, + })); + + return { + objectType: ObjectType.SHAPE, + shapeType: this.shapeType, + clientID: this.clientID, + serverID: this.serverID, + points: this.points, + zOrder: this.zOrder, + rotation: 0, + attributes: { ...this.attributes }, + descriptions: [...this.descriptions], + elements, + label: this.label, + group: this.groupObject, + color: this.color, + updated: Math.max(this.updated, ...this.elements.map((element) => element.updated)), + pinned: this.pinned, + outside: elements.every((el) => el.outside), + occluded: elements.every((el) => el.occluded), + lock: elements.every((el) => el.lock), + hidden: elements.every((el) => el.hidden), + frame, + source: this.source, + ...this.withContext(frame), + }; + } + + public updateServerID(body: RawShapeData): void { + Shape.prototype.updateServerID.call(this, body); + for (const element of body.elements) { + const thisElement = this.elements.find((_element: Shape) => _element.label.id === element.label_id); + thisElement.updateServerID(element); + } + } + + public clearServerID(): void { + Shape.prototype.clearServerID.call(this); + for (const element of this.elements) { + element.clearServerID(); + } + } + + protected saveRotation(rotation, frame): void { + const undoSkeletonPoints = this.elements.map((element) => element.points); + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + + const bbox = computeWrappingBox(undoSkeletonPoints.flat()); + const [cx, cy] = [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2]; + for (const element of this.elements) { + const { points } = element; + const rotatedPoints = []; + for (let i = 0; i < points.length; i += 2) { + const [x, y] = [points[i], points[i + 1]]; + rotatedPoints.push(...rotatePoint(x, y, rotation, cx, cy)); + } + + element.points = rotatedPoints; + } + this.source = redoSource; + + const redoSkeletonPoints = this.elements.map((element) => element.points); + this.history.do( + HistoryActions.CHANGED_ROTATION, + () => { + for (let i = 0; i < this.elements.length; i++) { + this.elements[i].points = undoSkeletonPoints[i]; + this.elements[i].updated = Date.now(); + } + this.source = undoSource; + this.updated = Date.now(); + }, + () => { + for (let i = 0; i < this.elements.length; i++) { + this.elements[i].points = redoSkeletonPoints[i]; + this.elements[i].updated = Date.now(); + } + this.source = redoSource; + this.updated = Date.now(); + }, + [this.clientID, ...this.elements.map((element) => element.clientID)], + frame, + ); + } + + public save(frame: number, data: ObjectState): ObjectState { + if (this.lock && data.lock) { + return new ObjectState(this.get(frame)); + } + + const updateElements = (affectedElements, action, property: 'points' | 'occluded' | 'hidden' | 'lock') => { + const undoSkeletonProperties = this.elements.map((element) => element[property]); + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + + try { + this.history.freeze(true); + affectedElements.forEach((element, idx) => { + const annotationContext = this.elements[idx]; + annotationContext.save(frame, element); + }); + } finally { + this.history.freeze(false); + } + + const redoSkeletonProperties = this.elements.map((element) => element[property]); + + this.history.do( + action, + () => { + for (let i = 0; i < this.elements.length; i++) { + this.elements[i][property] = undoSkeletonProperties[i]; + this.elements[i].updated = Date.now(); + } + this.source = undoSource; + this.updated = Date.now(); + }, + () => { + for (let i = 0; i < this.elements.length; i++) { + this.elements[i][property] = redoSkeletonProperties[i]; + this.elements[i].updated = Date.now(); + } + this.source = redoSource; + this.updated = Date.now(); + }, + [this.clientID, ...affectedElements.map((element) => element.clientID)], + frame, + ); + }; + + const updatedPoints = data.elements.filter((el) => el.updateFlags.points); + const updatedOccluded = data.elements.filter((el) => el.updateFlags.occluded); + const updatedHidden = data.elements.filter((el) => el.updateFlags.hidden); + const updatedLock = data.elements.filter((el) => el.updateFlags.lock); + + updatedOccluded.forEach((el) => { el.updateFlags.occluded = false; }); + updatedHidden.forEach((el) => { el.updateFlags.hidden = false; }); + updatedLock.forEach((el) => { el.updateFlags.lock = false; }); + + if (updatedPoints.length) { + updateElements(updatedPoints, HistoryActions.CHANGED_POINTS, 'points'); + } + + if (updatedOccluded.length) { + updatedOccluded.forEach((el) => { el.updateFlags.occluded = true; }); + updateElements(updatedOccluded, HistoryActions.CHANGED_OCCLUDED, 'occluded'); + } + + if (updatedHidden.length) { + updatedHidden.forEach((el) => { el.updateFlags.hidden = true; }); + updateElements(updatedHidden, HistoryActions.CHANGED_OUTSIDE, 'hidden'); + } + + if (updatedLock.length) { + updatedLock.forEach((el) => { el.updateFlags.lock = true; }); + updateElements(updatedLock, HistoryActions.CHANGED_LOCK, 'lock'); + } + + const result = Shape.prototype.save.call(this, frame, data); + return result; + } + + get occluded(): boolean { + return this.elements.every((element) => element.occluded); + } + + set occluded(_) { + // stub + } + + get lock(): boolean { + return this.elements.every((element) => element.lock); + } + + set lock(_) { + // stub + } +} + +export class MaskShape extends Shape { + private left: number; + private top: number; + private right: number; + private bottom: number; + private getMasksOnFrame: AnnotationInjection['getMasksOnFrame']; + + constructor(data, clientID, color, injection) { + super(data, clientID, color, injection); + [this.left, this.top, this.right, this.bottom] = this.points.splice(-4, 4); + this.getMasksOnFrame = injection.getMasksOnFrame; + this.pinned = true; + this.shapeType = ShapeType.MASK; + } + + protected validateStateBeforeSave(data: ObjectState, updated: ObjectState['updateFlags'], frame?: number): number[] { + Annotation.prototype.validateStateBeforeSave.call(this, data, updated); + if (updated.points) { + const { width, height } = this.frameMeta[frame]; + const fittedPoints = truncateMask(data.points, 0, width, height); + return fittedPoints; + } + + return []; + } + + protected removeUnderlyingPixels(frame: number): void { + if (frame !== this.frame) { + throw new ArgumentError( + `Wrong "frame" attribute: is not equal to the shape frame (${frame} vs ${this.frame})`, + ); + } + + const others = this.getMasksOnFrame(frame) + .filter((mask: MaskShape) => mask.clientID !== this.clientID && !mask.removed); + const width = this.right - this.left + 1; + const height = this.bottom - this.top + 1; + const updatedObjects: Record = {}; + + const masks = {}; + const currentMask = rle2Mask(this.points, width, height); + for (let i = 0; i < currentMask.length; i++) { + if (currentMask[i]) { + const imageX = (i % width) + this.left; + const imageY = Math.trunc(i / width) + this.top; + for (const other of others) { + const box = { + left: other.left, + top: other.top, + right: other.right, + bottom: other.bottom, + }; + const translatedX = imageX - box.left; + const translatedY = imageY - box.top; + const [otherWidth, otherHeight] = [box.right - box.left + 1, box.bottom - box.top + 1]; + if (translatedX >= 0 && translatedX < otherWidth && + translatedY >= 0 && translatedY < otherHeight) { + masks[other.clientID] = masks[other.clientID] || + rle2Mask(other.points, otherWidth, otherHeight); + const j = translatedY * otherWidth + translatedX; + masks[other.clientID][j] = 0; + updatedObjects[other.clientID] = other; + } + } + } + } + + for (const object of Object.values(updatedObjects)) { + object.points = mask2Rle(masks[object.clientID]); + object.updated = Date.now(); + } + } + + protected savePoints(maskPoints: number[], frame: number): void { + const undoPoints = this.points; + const undoLeft = this.left; + const undoRight = this.right; + const undoTop = this.top; + const undoBottom = this.bottom; + const undoSource = this.source; + + const [redoLeft, redoTop, redoRight, redoBottom] = maskPoints.splice(-4); + const points = mask2Rle(maskPoints); + + const redoPoints = points; + const redoSource = Source.MANUAL; + + const undo = (): void => { + this.points = undoPoints; + this.source = undoSource; + this.left = undoLeft; + this.top = undoTop; + this.right = undoRight; + this.bottom = undoBottom; + this.updated = Date.now(); + }; + + const redo = (): void => { + this.points = redoPoints; + this.source = redoSource; + this.left = redoLeft; + this.top = redoTop; + this.right = redoRight; + this.bottom = redoBottom; + this.updated = Date.now(); + }; + + this.history.do( + HistoryActions.CHANGED_POINTS, + undo, redo, [this.clientID], frame, + ); + + redo(); + + if (config.removeUnderlyingMaskPixels) { + this.removeUnderlyingPixels(frame); + } + } + + static distance(points: number[], x: number, y: number): null | number { + const [left, top, right, bottom] = points.slice(-4); + const [width, height] = [right - left + 1, bottom - top + 1]; + const [translatedX, translatedY] = [x - left, y - top]; + if (translatedX < 0 || translatedX >= width || translatedY < 0 || translatedY >= height) { + return null; + } + const offset = Math.floor(translatedY) * width + Math.floor(translatedX); + + if (points[offset]) return 1; + return null; + } +} + +MaskShape.prototype.toJSON = function () { + const result = Shape.prototype.toJSON.call(this); + result.points = this.points.slice(); + result.points.push(this.left, this.top, this.right, this.bottom); + return result; +}; + +MaskShape.prototype.get = function (frame) { + const result = Shape.prototype.get.call(this, frame); + result.points = rle2Mask(this.points, this.right - this.left + 1, this.bottom - this.top + 1); + result.points.push(this.left, this.top, this.right, this.bottom); + return result; +}; + +export class RectangleTrack extends Track { + constructor(data: RawTrackData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.RECTANGLE; + this.pinned = false; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + } + } + + protected interpolatePosition(leftPosition, rightPosition, offset): InterpolatedPosition { + const positionOffset = leftPosition.points.map((point, index) => rightPosition.points[index] - point); + return { + points: leftPosition.points.map((point, index) => point + positionOffset[index] * offset), + rotation: + (leftPosition.rotation + findAngleDiff( + rightPosition.rotation, leftPosition.rotation, + ) * offset + 360) % 360, + occluded: leftPosition.occluded, + outside: leftPosition.outside, + zOrder: leftPosition.zOrder, + }; + } +} + +export class EllipseTrack extends Track { + constructor(data: RawTrackData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.ELLIPSE; + this.pinned = false; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + } + } + + interpolatePosition(leftPosition, rightPosition, offset): InterpolatedPosition { + const positionOffset = leftPosition.points.map((point, index) => rightPosition.points[index] - point); + + return { + points: leftPosition.points.map((point, index) => point + positionOffset[index] * offset), + rotation: + (leftPosition.rotation + findAngleDiff( + rightPosition.rotation, leftPosition.rotation, + ) * offset + 360) % 360, + occluded: leftPosition.occluded, + outside: leftPosition.outside, + zOrder: leftPosition.zOrder, + }; + } +} + +class PolyTrack extends Track { + constructor(data: RawTrackData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + for (const shape of Object.values(this.shapes)) { + shape.rotation = 0; // is not supported + } + } + + protected interpolatePosition(leftPosition, rightPosition, offset): InterpolatedPosition { + if (offset === 0) { + return { + points: [...leftPosition.points], + rotation: leftPosition.rotation, + occluded: leftPosition.occluded, + outside: leftPosition.outside, + zOrder: leftPosition.zOrder, + }; + } + + function toArray(points: { x: number; y: number; }[]): number[] { + return points.reduce((acc, val) => { + acc.push(val.x, val.y); + return acc; + }, []); + } + + function toPoints(array: number[]): { x: number; y: number; }[] { + return array.reduce((acc, _, index) => { + if (index % 2) { + acc.push({ + x: array[index - 1], + y: array[index], + }); + } + + return acc; + }, []); + } + + function curveLength(points: { x: number; y: number; }[]): number { + return points.slice(1).reduce((acc, _, index) => { + const dx = points[index + 1].x - points[index].x; + const dy = points[index + 1].y - points[index].y; + return acc + Math.sqrt(dx ** 2 + dy ** 2); + }, 0); + } + + function curveToOffsetVec(points: { x: number; y: number; }[], length: number): number[] { + const offsetVector = [0]; // with initial value + let accumulatedLength = 0; + + points.slice(1).forEach((_, index) => { + const dx = points[index + 1].x - points[index].x; + const dy = points[index + 1].y - points[index].y; + accumulatedLength += Math.sqrt(dx ** 2 + dy ** 2); + offsetVector.push(accumulatedLength / length); + }); + + return offsetVector; + } + + function findNearestPair(value: number, curve: number[]): number { + let minimum = [0, Math.abs(value - curve[0])]; + for (let i = 1; i < curve.length; i++) { + const distance = Math.abs(value - curve[i]); + if (distance < minimum[1]) { + minimum = [i, distance]; + } + } + + return minimum[0]; + } + + function matchLeftRight(leftCurve: number[], rightCurve: number[]): Record { + const matching = {}; + for (let i = 0; i < leftCurve.length; i++) { + matching[i] = [findNearestPair(leftCurve[i], rightCurve)]; + } + + return matching; + } + + function matchRightLeft( + leftCurve: number[], + rightCurve: number[], + leftRightMatching: Record, + ): Record { + const matchedRightPoints = Object.values(leftRightMatching).flat(); + const unmatchedRightPoints = rightCurve + .map((_, index) => index) + .filter((index) => !matchedRightPoints.includes(index)); + const updatedMatching = { ...leftRightMatching }; + + for (const rightPoint of unmatchedRightPoints) { + const leftPoint = findNearestPair(rightCurve[rightPoint], leftCurve); + updatedMatching[leftPoint].push(rightPoint); + } + + for (const key of Object.keys(updatedMatching)) { + const sortedRightIndexes = updatedMatching[key].sort((a, b) => a - b); + updatedMatching[key] = sortedRightIndexes; + } + + return updatedMatching; + } + + function reduceInterpolation(interpolatedPoints, matching, leftPoints, rightPoints) { + function averagePoint(points: Point2D[]): Point2D { + let sumX = 0; + let sumY = 0; + for (const point of points) { + sumX += point.x; + sumY += point.y; + } + + return { + x: sumX / points.length, + y: sumY / points.length, + }; + } + + function computeDistance(point1: Point2D, point2: Point2D): number { + return Math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2); + } + + function minimizeSegment(baseLength: number, N: number, startInterpolated, stopInterpolated) { + const threshold = baseLength / (2 * N); + const minimized = [interpolatedPoints[startInterpolated]]; + let latestPushed = startInterpolated; + for (let i = startInterpolated + 1; i < stopInterpolated; i++) { + const distance = computeDistance(interpolatedPoints[latestPushed], interpolatedPoints[i]); + + if (distance >= threshold) { + minimized.push(interpolatedPoints[i]); + latestPushed = i; + } + } + + minimized.push(interpolatedPoints[stopInterpolated]); + + if (minimized.length === 2) { + const distance = computeDistance( + interpolatedPoints[startInterpolated], + interpolatedPoints[stopInterpolated], + ); + + if (distance < threshold) { + return [averagePoint(minimized)]; + } + } + + return minimized; + } + + const reduced = []; + const interpolatedIndexes = {}; + let accumulated = 0; + for (let i = 0; i < leftPoints.length; i++) { + // eslint-disable-next-line + interpolatedIndexes[i] = matching[i].map(() => accumulated++); + } + + function leftSegment(start, stop) { + const startInterpolated = interpolatedIndexes[start][0]; + const stopInterpolated = interpolatedIndexes[stop][0]; + + if (startInterpolated === stopInterpolated) { + reduced.push(interpolatedPoints[startInterpolated]); + return; + } + + const baseLength = curveLength(leftPoints.slice(start, stop + 1)); + const N = stop - start + 1; + + reduced.push(...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated)); + } + + function rightSegment(leftPoint) { + const start = matching[leftPoint][0]; + const [stop] = matching[leftPoint].slice(-1); + const startInterpolated = interpolatedIndexes[leftPoint][0]; + const [stopInterpolated] = interpolatedIndexes[leftPoint].slice(-1); + const baseLength = curveLength(rightPoints.slice(start, stop + 1)); + const N = stop - start + 1; + + reduced.push(...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated)); + } + + let previousOpened = null; + for (let i = 0; i < leftPoints.length; i++) { + if (matching[i].length === 1) { + // check if left segment is opened + if (previousOpened !== null) { + // check if we should continue the left segment + if (matching[i][0] === matching[previousOpened][0]) { + continue; + } else { + // left segment found + const start = previousOpened; + const stop = i - 1; + leftSegment(start, stop); + + // start next left segment + previousOpened = i; + } + } else { + // start next left segment + previousOpened = i; + } + } else { + // check if left segment is opened + if (previousOpened !== null) { + // left segment found + const start = previousOpened; + const stop = i - 1; + leftSegment(start, stop); + + previousOpened = null; + } + + // right segment found + rightSegment(i); + } + } + + // check if there is an opened segment + if (previousOpened !== null) { + leftSegment(previousOpened, leftPoints.length - 1); + } + + return reduced; + } + + // the algorithm below is based on fact that both left and right + // polyshapes have the same start point and the same draw direction + const leftPoints = toPoints(leftPosition.points); + const rightPoints = toPoints(rightPosition.points); + const leftOffsetVec = curveToOffsetVec(leftPoints, curveLength(leftPoints)); + const rightOffsetVec = curveToOffsetVec(rightPoints, curveLength(rightPoints)); + + const matching = matchLeftRight(leftOffsetVec, rightOffsetVec); + const completedMatching = matchRightLeft(leftOffsetVec, rightOffsetVec, matching); + + const interpolatedPoints = Object.keys(completedMatching) + .map((leftPointIdx) => +leftPointIdx) + .sort((a, b) => a - b) + .reduce((acc, leftPointIdx) => { + const leftPoint = leftPoints[leftPointIdx]; + for (const rightPointIdx of completedMatching[leftPointIdx]) { + const rightPoint = rightPoints[rightPointIdx]; + acc.push({ + x: leftPoint.x + (rightPoint.x - leftPoint.x) * offset, + y: leftPoint.y + (rightPoint.y - leftPoint.y) * offset, + }); + } + + return acc; + }, []); + + const reducedPoints = reduceInterpolation(interpolatedPoints, completedMatching, leftPoints, rightPoints); + + return { + points: toArray(reducedPoints), + rotation: leftPosition.rotation, + occluded: leftPosition.occluded, + outside: leftPosition.outside, + zOrder: leftPosition.zOrder, + }; + } +} + +export class PolygonTrack extends PolyTrack { + constructor(data: RawTrackData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.POLYGON; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + } + } + + protected interpolatePosition(leftPosition, rightPosition, offset): InterpolatedPosition { + const copyLeft = { + ...leftPosition, + points: [...leftPosition.points, leftPosition.points[0], leftPosition.points[1]], + }; + + const copyRight = { + ...rightPosition, + points: [...rightPosition.points, rightPosition.points[0], rightPosition.points[1]], + }; + + const result = PolyTrack.prototype.interpolatePosition.call(this, copyLeft, copyRight, offset); + + return { + ...result, + points: result.points.slice(0, -2), + }; + } +} + +export class PolylineTrack extends PolyTrack { + constructor(data: RawTrackData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.POLYLINE; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + } + } +} + +export class PointsTrack extends PolyTrack { + constructor(data: RawTrackData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.POINTS; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + } + } + + protected interpolatePosition(leftPosition, rightPosition, offset): InterpolatedPosition { + // interpolate only when one point in both left and right positions + if (leftPosition.points.length === 2 && rightPosition.points.length === 2) { + return { + points: leftPosition.points.map( + (value, index) => value + (rightPosition.points[index] - value) * offset, + ), + rotation: leftPosition.rotation, + occluded: leftPosition.occluded, + outside: leftPosition.outside, + zOrder: leftPosition.zOrder, + }; + } + + return { + points: [...leftPosition.points], + rotation: leftPosition.rotation, + occluded: leftPosition.occluded, + outside: leftPosition.outside, + zOrder: leftPosition.zOrder, + }; + } +} + +export class CuboidTrack extends Track { + constructor(data: RawTrackData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.CUBOID; + this.pinned = false; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + shape.rotation = 0; // is not supported + } + } + + protected interpolatePosition(leftPosition, rightPosition, offset): InterpolatedPosition { + const positionOffset = leftPosition.points.map((point, index) => rightPosition.points[index] - point); + + return { + points: leftPosition.points.map((point, index) => point + positionOffset[index] * offset), + rotation: leftPosition.rotation, + occluded: leftPosition.occluded, + outside: leftPosition.outside, + zOrder: leftPosition.zOrder, + }; + } +} + +export class SkeletonTrack extends Track { + private elements: Track[]; + + constructor(data: RawTrackData, clientID: number, color: string, injection: AnnotationInjection) { + super(data, clientID, color, injection); + this.shapeType = ShapeType.SKELETON; + + for (const shape of Object.values(this.shapes)) { + delete shape.points; + } + + this.readOnlyFields = ['points', 'label', 'occluded', 'outside']; + this.pinned = false; + this.elements = data.elements.map((element: RawTrackData['elements'][0]) => ( + /* eslint-disable-next-line @typescript-eslint/no-use-before-define */ + trackFactory({ + ...element, + group: this.group, + source: this.source, + }, injection.nextClientID(), { + ...injection, + parentID: this.clientID, + readOnlyFields: ['group', 'zOrder', 'source', 'rotation'], + }) + + // todo z_order: this.zOrder, + )).sort((a: Annotation, b: Annotation) => a.label.id - b.label.id) as any as Track[]; + } + + public updateServerID(body: RawTrackData): void { + Track.prototype.updateServerID.call(this, body); + for (const element of body.elements) { + const thisElement = this.elements.find((_element: Track) => _element.label.id === element.label_id); + thisElement.updateServerID(element); + } + } + + public clearServerID(): void { + Track.prototype.clearServerID.call(this); + for (const element of this.elements) { + element.clearServerID(); + } + } + + protected saveRotation(rotation: number, frame: number): void { + const undoSkeletonShapes = this.elements.map((element) => element.shapes[frame]); + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + + const elementsData = this.elements.map((element) => element.get(frame)); + const skeletonPoints = elementsData.map((data) => data.points); + const bbox = computeWrappingBox(skeletonPoints.flat()); + const [cx, cy] = [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2]; + + for (let i = 0; i < this.elements.length; i++) { + const element = this.elements[i]; + const { points } = elementsData[i]; + + const rotatedPoints = []; + for (let j = 0; j < points.length; j += 2) { + const [x, y] = [points[j], points[j + 1]]; + rotatedPoints.push(...rotatePoint(x, y, rotation, cx, cy)); + } + + if (undoSkeletonShapes[i]) { + element.shapes[frame] = { + ...undoSkeletonShapes[i], + points: rotatedPoints, + }; + } else { + element.shapes[frame] = { + ...copyShape(elementsData[i]), + points: rotatedPoints, + }; + } + } + this.source = redoSource; + + const redoSkeletonShapes = this.elements.map((element) => element.shapes[frame]); + this.history.do( + HistoryActions.CHANGED_ROTATION, + () => { + for (let i = 0; i < this.elements.length; i++) { + const element = this.elements[i]; + if (undoSkeletonShapes[i]) { + element.shapes[frame] = undoSkeletonShapes[i]; + } else { + delete element.shapes[frame]; + } + this.updated = Date.now(); + } + this.source = undoSource; + this.updated = Date.now(); + }, + () => { + for (let i = 0; i < this.elements.length; i++) { + const element = this.elements[i]; + element.shapes[frame] = redoSkeletonShapes[i]; + this.updated = Date.now(); + } + this.source = redoSource; + this.updated = Date.now(); + }, + [this.clientID, ...this.elements.map((element) => element.clientID)], + frame, + ); + } + + // Method is used to export data to the server + public toJSON(): RawTrackData { + const result: RawTrackData = Track.prototype.toJSON.call(this); + result.elements = this.elements.map((el) => el.toJSON()); + return result; + } + + public get(frame: number): Omit, 'parentID'> { + const { prev, next } = this.boundedKeyframes(frame); + const position = this.getPosition(frame, prev, next); + const elements = this.elements.map((element) => ({ + ...element.get(frame), + source: this.source, + group: this.groupObject, + zOrder: position.zOrder, + rotation: 0, + })); + + return { + ...position, + keyframe: position.keyframe || elements.some((el) => el.keyframe), + attributes: this.getAttributes(frame), + descriptions: [...this.descriptions], + group: this.groupObject, + objectType: ObjectType.TRACK, + shapeType: this.shapeType, + clientID: this.clientID, + serverID: this.serverID, + color: this.color, + updated: Math.max(this.updated, ...this.elements.map((element) => element.updated)), + label: this.label, + pinned: this.pinned, + keyframes: this.deepBoundedKeyframes(frame), + elements, + frame, + source: this.source, + outside: elements.every((el) => el.outside), + occluded: elements.every((el) => el.occluded), + lock: elements.every((el) => el.lock), + hidden: elements.every((el) => el.hidden), + ...this.withContext(frame), + }; + } + + // finds keyframes considering keyframes of nested elements + private deepBoundedKeyframes(targetFrame: number): ObjectState['keyframes'] { + const boundedKeyframes = Track.prototype.boundedKeyframes.call(this, targetFrame); + + for (const element of this.elements) { + const keyframes = element.boundedKeyframes(targetFrame); + if (keyframes.prev !== null) { + boundedKeyframes.prev = boundedKeyframes.prev === null || keyframes.prev > boundedKeyframes.prev ? + keyframes.prev : boundedKeyframes.prev; + } + + if (keyframes.next !== null) { + boundedKeyframes.next = boundedKeyframes.next === null || keyframes.next < boundedKeyframes.next ? + keyframes.next : boundedKeyframes.next; + } + + if (keyframes.first !== null) { + boundedKeyframes.first = + boundedKeyframes.first === null || keyframes.first < boundedKeyframes.first ? + keyframes.first : boundedKeyframes.first; + } + + if (keyframes.last !== null) { + boundedKeyframes.last = boundedKeyframes.last === null || keyframes.last > boundedKeyframes.last ? + keyframes.last : boundedKeyframes.last; + } + } + + return boundedKeyframes; + } + + public save(frame: number, data: ObjectState): ObjectState { + if (this.lock && data.lock) { + return new ObjectState(this.get(frame)); + } + + const updateElements = (affectedElements, action, property: 'hidden' | 'lock' | null = null): void => { + const undoSkeletonProperties = this.elements.map((element) => element[property] || null); + const undoSkeletonShapes = this.elements.map((element) => element.shapes[frame]); + const undoSource = this.source; + const redoSource = this.readOnlyFields.includes('source') ? this.source : Source.MANUAL; + + const errors = []; + try { + this.history.freeze(true); + affectedElements.forEach((element, idx) => { + try { + const annotationContext = this.elements[idx]; + annotationContext.save(frame, element); + } catch (error: any) { + errors.push(error); + } + }); + } finally { + this.history.freeze(false); + } + + const redoSkeletonProperties = this.elements.map((element) => element[property] || null); + const redoSkeletonShapes = this.elements.map((element) => element.shapes[frame]); + + this.history.do( + action, + () => { + for (let i = 0; i < this.elements.length; i++) { + if (property) { + this.elements[i][property] = undoSkeletonProperties[i]; + } if (undoSkeletonShapes[i]) { + this.elements[i].shapes[frame] = undoSkeletonShapes[i]; + } else if (redoSkeletonShapes[i]) { + delete this.elements[i].shapes[frame]; + } + this.elements[i].updated = Date.now(); + } + this.source = undoSource; + this.updated = Date.now(); + }, + () => { + for (let i = 0; i < this.elements.length; i++) { + if (property) { + this.elements[i][property] = redoSkeletonProperties[i]; + } else if (redoSkeletonShapes[i]) { + this.elements[i].shapes[frame] = redoSkeletonShapes[i]; + } else if (undoSkeletonShapes[i]) { + delete this.elements[i].shapes[frame]; + } + this.elements[i].updated = Date.now(); + } + this.source = redoSource; + this.updated = Date.now(); + }, + [this.clientID, ...affectedElements.map((element) => element.clientID)], + frame, + ); + + if (errors.length) { + throw new Error(`Several errors occured during saving skeleton:\n ${errors.join(';\n')}`); + } + }; + + const updatedPoints = data.elements.filter((el) => el.updateFlags.points); + const updatedOccluded = data.elements.filter((el) => el.updateFlags.occluded); + const updatedOutside = data.elements.filter((el) => el.updateFlags.outside); + const updatedKeyframe = data.elements.filter((el) => el.updateFlags.keyframe); + const updatedHidden = data.elements.filter((el) => el.updateFlags.hidden); + const updatedLock = data.elements.filter((el) => el.updateFlags.lock); + + updatedOccluded.forEach((el) => { el.updateFlags.occluded = false; }); + updatedOutside.forEach((el) => { el.updateFlags.outside = false; }); + updatedKeyframe.forEach((el) => { el.updateFlags.keyframe = false; }); + updatedHidden.forEach((el) => { el.updateFlags.hidden = false; }); + updatedLock.forEach((el) => { el.updateFlags.lock = false; }); + + if (updatedPoints.length) { + updateElements(updatedPoints, HistoryActions.CHANGED_POINTS); + } + + if (updatedOccluded.length) { + updatedOccluded.forEach((el) => { el.updateFlags.occluded = true; }); + updateElements(updatedOccluded, HistoryActions.CHANGED_OCCLUDED); + } + + if (updatedOutside.length) { + updatedOutside.forEach((el) => { el.updateFlags.outside = true; }); + updateElements(updatedOutside, HistoryActions.CHANGED_OUTSIDE); + } + + if (updatedKeyframe.length) { + updatedKeyframe.forEach((el) => { el.updateFlags.keyframe = true; }); + // todo: fix extra undo/redo change + this.validateStateBeforeSave(data, data.updateFlags, frame); + this.saveKeyframe(frame, data.keyframe); + data.updateFlags.keyframe = false; + updateElements(updatedKeyframe, HistoryActions.CHANGED_KEYFRAME); + } + + if (updatedHidden.length) { + updatedHidden.forEach((el) => { el.updateFlags.hidden = true; }); + updateElements(updatedHidden, HistoryActions.CHANGED_HIDDEN, 'hidden'); + } + + if (updatedLock.length) { + updatedLock.forEach((el) => { el.updateFlags.lock = true; }); + updateElements(updatedLock, HistoryActions.CHANGED_LOCK, 'lock'); + } + + const result = Track.prototype.save.call(this, frame, data); + return result; + } + + protected getPosition( + targetFrame: number, leftKeyframe: number | null, rightKeyframe: number | null, + ): Omit & { keyframe: boolean } { + const leftFrame = targetFrame in this.shapes ? targetFrame : leftKeyframe; + const rightPosition = Number.isInteger(rightKeyframe) ? this.shapes[rightKeyframe] : null; + const leftPosition = Number.isInteger(leftFrame) ? this.shapes[leftFrame] : null; + + if (leftPosition && rightPosition) { + return { + rotation: 0, + occluded: leftPosition.occluded, + outside: leftPosition.outside, + zOrder: leftPosition.zOrder, + keyframe: targetFrame in this.shapes, + }; + } + + const singlePosition = leftPosition || rightPosition; + if (singlePosition) { + return { + rotation: 0, + occluded: singlePosition.occluded, + zOrder: singlePosition.zOrder, + keyframe: targetFrame in this.shapes, + outside: singlePosition === rightPosition ? true : singlePosition.outside, + }; + } + + throw new DataError( + 'No one left position or right position was found. ' + + `Interpolation impossible. Client ID: ${this.clientID}`, + ); + } +} + +Object.defineProperty(RectangleTrack, 'distance', { value: RectangleShape.distance }); +Object.defineProperty(PolygonTrack, 'distance', { value: PolygonShape.distance }); +Object.defineProperty(PolylineTrack, 'distance', { value: PolylineShape.distance }); +Object.defineProperty(PointsTrack, 'distance', { value: PointsShape.distance }); +Object.defineProperty(EllipseTrack, 'distance', { value: EllipseShape.distance }); +Object.defineProperty(CuboidTrack, 'distance', { value: CuboidShape.distance }); +Object.defineProperty(SkeletonTrack, 'distance', { value: SkeletonShape.distance }); + +export function shapeFactory(data: RawShapeData, clientID: number, injection: AnnotationInjection): Annotation { + const { type } = data; + const color = colors[clientID % colors.length]; + + let shapeModel = null; + switch (type) { + case ShapeType.RECTANGLE: + shapeModel = new RectangleShape(data, clientID, color, injection); + break; + case ShapeType.POLYGON: + shapeModel = new PolygonShape(data, clientID, color, injection); + break; + case ShapeType.POLYLINE: + shapeModel = new PolylineShape(data, clientID, color, injection); + break; + case ShapeType.POINTS: + shapeModel = new PointsShape(data, clientID, color, injection); + break; + case ShapeType.ELLIPSE: + shapeModel = new EllipseShape(data, clientID, color, injection); + break; + case ShapeType.CUBOID: + shapeModel = new CuboidShape(data, clientID, color, injection); + break; + case ShapeType.MASK: + shapeModel = new MaskShape(data, clientID, color, injection); + break; + case ShapeType.SKELETON: + shapeModel = new SkeletonShape(data, clientID, color, injection); + break; + default: + throw new DataError(`An unexpected type of shape "${type}"`); + } + + return shapeModel; +} + +export function trackFactory(trackData: RawTrackData, clientID: number, injection: AnnotationInjection): Annotation { + if (trackData.shapes.length) { + const { type } = trackData.shapes[0]; + const color = colors[clientID % colors.length]; + + let trackModel = null; + switch (type) { + case ShapeType.RECTANGLE: + trackModel = new RectangleTrack(trackData, clientID, color, injection); + break; + case ShapeType.POLYGON: + trackModel = new PolygonTrack(trackData, clientID, color, injection); + break; + case ShapeType.POLYLINE: + trackModel = new PolylineTrack(trackData, clientID, color, injection); + break; + case ShapeType.POINTS: + trackModel = new PointsTrack(trackData, clientID, color, injection); + break; + case ShapeType.ELLIPSE: + trackModel = new EllipseTrack(trackData, clientID, color, injection); + break; + case ShapeType.CUBOID: + trackModel = new CuboidTrack(trackData, clientID, color, injection); + break; + case ShapeType.SKELETON: + trackModel = new SkeletonTrack(trackData, clientID, color, injection); + break; + default: + throw new DataError(`An unexpected type of track "${type}"`); + } + + return trackModel; + } + + console.warn('The track without any shapes had been found. It was ignored.'); + return null; +} diff --git a/cvat-core/src/annotations-saver.js b/cvat-core/src/annotations-saver.ts similarity index 97% rename from cvat-core/src/annotations-saver.js rename to cvat-core/src/annotations-saver.ts index d70b51a6e0b6..41800d6a00bd 100644 --- a/cvat-core/src/annotations-saver.js +++ b/cvat-core/src/annotations-saver.ts @@ -1,9 +1,10 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corp // // SPDX-License-Identifier: MIT (() => { - const serverProxy = require('./server-proxy'); + const serverProxy = require('./server-proxy').default; const { Task } = require('./session'); const { ScriptingError } = require('./exceptions'); @@ -103,6 +104,7 @@ 'rotation', 'type', 'shapes', + 'elements', 'attributes', 'value', 'spec_id', @@ -150,9 +152,7 @@ _updateCreatedObjects(saved, indexes) { const savedLength = saved.tracks.length + saved.shapes.length + saved.tags.length; - const indexesLength = indexes.tracks.length + indexes.shapes.length + indexes.tags.length; - if (indexesLength !== savedLength) { throw new ScriptingError( `Number of indexes is differed by number of saved objects ${indexesLength} vs ${savedLength}`, @@ -163,7 +163,7 @@ for (const type of Object.keys(indexes)) { for (let i = 0; i < indexes[type].length; i++) { const clientID = indexes[type][i]; - this.collection.objects[clientID].serverID = saved[type][i].id; + this.collection.objects[clientID].updateServerID(saved[type][i]); } } } diff --git a/cvat-core/src/annotations.js b/cvat-core/src/annotations.js deleted file mode 100644 index a511f0f8a013..000000000000 --- a/cvat-core/src/annotations.js +++ /dev/null @@ -1,393 +0,0 @@ -// Copyright (C) 2019-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const serverProxy = require('./server-proxy'); - const Collection = require('./annotations-collection'); - const AnnotationsSaver = require('./annotations-saver'); - const AnnotationsHistory = require('./annotations-history'); - const { checkObjectType } = require('./common'); - const { Project } = require('./project'); - const { Task, Job } = require('./session'); - const { Loader } = require('./annotation-formats'); - const { ScriptingError, DataError, ArgumentError } = require('./exceptions'); - - const jobCache = new WeakMap(); - const taskCache = new WeakMap(); - - function getCache(sessionType) { - if (sessionType === 'task') { - return taskCache; - } - - if (sessionType === 'job') { - return jobCache; - } - - throw new ScriptingError(`Unknown session type was received ${sessionType}`); - } - - async function getAnnotationsFromServer(session) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (!cache.has(session)) { - const rawAnnotations = await serverProxy.annotations.getAnnotations(sessionType, session.id); - - // Get meta information about frames - const startFrame = sessionType === 'job' ? session.startFrame : 0; - const stopFrame = sessionType === 'job' ? session.stopFrame : session.size - 1; - const frameMeta = {}; - for (let i = startFrame; i <= stopFrame; i++) { - frameMeta[i] = await session.frames.get(i); - } - - const history = new AnnotationsHistory(); - const collection = new Collection({ - labels: session.labels || session.task.labels, - history, - startFrame, - stopFrame, - frameMeta, - }); - // eslint-disable-next-line no-unsanitized/method - collection.import(rawAnnotations); - - const saver = new AnnotationsSaver(rawAnnotations.version, collection, session); - - cache.set(session, { - collection, - saver, - history, - }); - } - } - - async function closeSession(session) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - cache.delete(session); - } - } - - async function getAnnotations(session, frame, allTracks, filters) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).collection.get(frame, allTracks, filters); - } - - await getAnnotationsFromServer(session); - return cache.get(session).collection.get(frame, allTracks, filters); - } - - async function saveAnnotations(session, onUpdate) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - await cache.get(session).saver.save(onUpdate); - } - - // If a collection wasn't uploaded, than it wasn't changed, finally we shouldn't save it - } - - function searchAnnotations(session, filters, frameFrom, frameTo) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).collection.search(filters, frameFrom, frameTo); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function searchEmptyFrame(session, frameFrom, frameTo) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).collection.searchEmpty(frameFrom, frameTo); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function mergeAnnotations(session, objectStates) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).collection.merge(objectStates); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function splitAnnotations(session, objectState, frame) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).collection.split(objectState, frame); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function groupAnnotations(session, objectStates, reset) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).collection.group(objectStates, reset); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function hasUnsavedChanges(session) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).saver.hasUnsavedChanges(); - } - - return false; - } - - async function clearAnnotations(session, reload, startframe, endframe, delTrackKeyframesOnly) { - checkObjectType('reload', reload, 'boolean', null); - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - cache.get(session).collection.clear(startframe, endframe, delTrackKeyframesOnly); - } - - if (reload) { - cache.delete(session); - await getAnnotationsFromServer(session); - } - } - - function annotationsStatistics(session) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).collection.statistics(); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function putAnnotations(session, objectStates) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).collection.put(objectStates); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function selectObject(session, objectStates, x, y) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).collection.select(objectStates, x, y); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - async function uploadAnnotations(session, file, loader) { - const sessionType = session instanceof Task ? 'task' : 'job'; - if (!(loader instanceof Loader)) { - throw new ArgumentError('A loader must be instance of Loader class'); - } - await serverProxy.annotations.uploadAnnotations(sessionType, session.id, file, loader.name); - } - - function importAnnotations(session, data) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - // eslint-disable-next-line no-unsanitized/method - return cache.get(session).collection.import(data); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function exportAnnotations(session) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).collection.export(); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - async function exportDataset(instance, format, name, saveImages = false) { - if (!(format instanceof String || typeof format === 'string')) { - throw new ArgumentError('Format must be a string'); - } - if (!(instance instanceof Task || instance instanceof Project || instance instanceof Job)) { - throw new ArgumentError('A dataset can only be created from a job, task or project'); - } - if (typeof saveImages !== 'boolean') { - throw new ArgumentError('Save images parameter must be a boolean'); - } - - let result = null; - if (instance instanceof Task) { - result = await serverProxy.tasks.exportDataset(instance.id, format, name, saveImages); - } else if (instance instanceof Job) { - result = await serverProxy.tasks.exportDataset(instance.taskId, format, name, saveImages); - } else { - result = await serverProxy.projects.exportDataset(instance.id, format, name, saveImages); - } - - return result; - } - - function importDataset(instance, format, file, updateStatusCallback = () => {}) { - if (!(typeof format === 'string')) { - throw new ArgumentError('Format must be a string'); - } - if (!(instance instanceof Project)) { - throw new ArgumentError('Instance should be a Project instance'); - } - if (!(typeof updateStatusCallback === 'function')) { - throw new ArgumentError('Callback should be a function'); - } - if (!(['application/zip', 'application/x-zip-compressed'].includes(file.type))) { - throw new ArgumentError('File should be file instance with ZIP extension'); - } - return serverProxy.projects.importDataset(instance.id, format, file, updateStatusCallback); - } - - function undoActions(session, count) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).history.undo(count); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function redoActions(session, count) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).history.redo(count); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function freezeHistory(session, frozen) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).history.freeze(frozen); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function clearActions(session) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).history.clear(); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - function getActions(session) { - const sessionType = session instanceof Task ? 'task' : 'job'; - const cache = getCache(sessionType); - - if (cache.has(session)) { - return cache.get(session).history.get(); - } - - throw new DataError( - 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', - ); - } - - module.exports = { - getAnnotations, - putAnnotations, - saveAnnotations, - hasUnsavedChanges, - mergeAnnotations, - searchAnnotations, - searchEmptyFrame, - splitAnnotations, - groupAnnotations, - clearAnnotations, - annotationsStatistics, - selectObject, - uploadAnnotations, - importAnnotations, - exportAnnotations, - exportDataset, - importDataset, - undoActions, - redoActions, - freezeHistory, - clearActions, - getActions, - closeSession, - }; -})(); diff --git a/cvat-core/src/annotations.ts b/cvat-core/src/annotations.ts new file mode 100644 index 000000000000..de14ca99b4fc --- /dev/null +++ b/cvat-core/src/annotations.ts @@ -0,0 +1,429 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { Storage } from './storage'; + +const serverProxy = require('./server-proxy').default; +const Collection = require('./annotations-collection'); +const AnnotationsSaver = require('./annotations-saver'); +const AnnotationsHistory = require('./annotations-history').default; +const { checkObjectType } = require('./common'); +const Project = require('./project').default; +const { Task, Job } = require('./session'); +const { ScriptingError, DataError, ArgumentError } = require('./exceptions'); +const { getDeletedFrames } = require('./frames'); + +const jobCache = new WeakMap(); +const taskCache = new WeakMap(); + +function getCache(sessionType) { + if (sessionType === 'task') { + return taskCache; + } + + if (sessionType === 'job') { + return jobCache; + } + + throw new ScriptingError(`Unknown session type was received ${sessionType}`); +} + +async function getAnnotationsFromServer(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (!cache.has(session)) { + const rawAnnotations = await serverProxy.annotations.getAnnotations(sessionType, session.id); + + // Get meta information about frames + const startFrame = sessionType === 'job' ? session.startFrame : 0; + const stopFrame = sessionType === 'job' ? session.stopFrame : session.size - 1; + const frameMeta = {}; + for (let i = startFrame; i <= stopFrame; i++) { + frameMeta[i] = await session.frames.get(i); + } + frameMeta.deleted_frames = await getDeletedFrames(sessionType, session.id); + + const history = new AnnotationsHistory(); + const collection = new Collection({ + labels: session.labels || session.task.labels, + history, + startFrame, + stopFrame, + frameMeta, + }); + + // eslint-disable-next-line no-unsanitized/method + collection.import(rawAnnotations); + const saver = new AnnotationsSaver(rawAnnotations.version, collection, session); + cache.set(session, { collection, saver, history }); + } +} + +export async function clearCache(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + cache.delete(session); + } +} + +export async function getAnnotations(session, frame, allTracks, filters) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.get(frame, allTracks, filters); + } + + await getAnnotationsFromServer(session); + return cache.get(session).collection.get(frame, allTracks, filters); +} + +export async function saveAnnotations(session, onUpdate) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + await cache.get(session).saver.save(onUpdate); + } + + // If a collection wasn't uploaded, than it wasn't changed, finally we shouldn't save it +} + +export function searchAnnotations(session, filters, frameFrom, frameTo) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.search(filters, frameFrom, frameTo); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function searchEmptyFrame(session, frameFrom, frameTo) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.searchEmpty(frameFrom, frameTo); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function mergeAnnotations(session, objectStates) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.merge(objectStates); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function splitAnnotations(session, objectState, frame) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.split(objectState, frame); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function groupAnnotations(session, objectStates, reset) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.group(objectStates, reset); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function hasUnsavedChanges(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).saver.hasUnsavedChanges(); + } + + return false; +} + +export async function clearAnnotations(session, reload, startframe, endframe, delTrackKeyframesOnly) { + checkObjectType('reload', reload, 'boolean', null); + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + cache.get(session).collection.clear(startframe, endframe, delTrackKeyframesOnly); + } + + if (reload) { + cache.delete(session); + await getAnnotationsFromServer(session); + } +} + +export function annotationsStatistics(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.statistics(); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function putAnnotations(session, objectStates) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.put(objectStates); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function selectObject(session, objectStates, x, y) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.select(objectStates, x, y); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function importCollection(session, data) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + // eslint-disable-next-line no-unsanitized/method + return cache.get(session).collection.import(data); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function exportCollection(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).collection.export(); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export async function exportDataset( + instance, + format: string, + saveImages: boolean, + useDefaultSettings: boolean, + targetStorage: Storage, + name?: string, +) { + if (!(instance instanceof Task || instance instanceof Project || instance instanceof Job)) { + throw new ArgumentError('A dataset can only be created from a job, task or project'); + } + + let result = null; + if (instance instanceof Task) { + result = await serverProxy.tasks + .exportDataset(instance.id, format, saveImages, useDefaultSettings, targetStorage, name); + } else if (instance instanceof Job) { + result = await serverProxy.jobs + .exportDataset(instance.id, format, saveImages, useDefaultSettings, targetStorage, name); + } else { + result = await serverProxy.projects + .exportDataset(instance.id, format, saveImages, useDefaultSettings, targetStorage, name); + } + + return result; +} + +export function importDataset( + instance: any, + format: string, + useDefaultSettings: boolean, + sourceStorage: Storage, + file: File | string, + options: { + convMaskToPoly?: boolean, + updateStatusCallback?: (s: string, n: number) => void, + } = {}, +): Promise { + const updateStatusCallback = options.updateStatusCallback || (() => {}); + const convMaskToPoly = 'convMaskToPoly' in options ? options.convMaskToPoly : true; + const adjustedOptions = { + updateStatusCallback, + convMaskToPoly, + }; + + if (!(instance instanceof Project || instance instanceof Task || instance instanceof Job)) { + throw new ArgumentError('Instance must be a Project || Task || Job instance'); + } + if (!(typeof updateStatusCallback === 'function')) { + throw new ArgumentError('Callback must be a function'); + } + if (!(typeof convMaskToPoly === 'boolean')) { + throw new ArgumentError('Option "convMaskToPoly" must be a boolean'); + } + const allowedFileExtensions = [ + '.zip', '.xml', '.json', + ]; + const allowedFileExtensionsList = allowedFileExtensions.join(', '); + if (typeof file === 'string' && !(allowedFileExtensions.some((ext) => file.toLowerCase().endsWith(ext)))) { + throw new ArgumentError( + `File must be file instance with one of the following extensions: ${allowedFileExtensionsList}`, + ); + } + const allowedMimeTypes = [ + 'application/zip', 'application/x-zip-compressed', + 'application/xml', 'text/xml', + 'application/json', + ]; + if (file instanceof File && !(allowedMimeTypes.includes(file.type))) { + throw new ArgumentError( + `File must be file instance with one of the following extensions: ${allowedFileExtensionsList}`, + ); + } + + if (instance instanceof Project) { + return serverProxy.projects + .importDataset( + instance.id, + format, + useDefaultSettings, + sourceStorage, + file, + adjustedOptions, + ); + } + + const instanceType = instance instanceof Task ? 'task' : 'job'; + return serverProxy.annotations + .uploadAnnotations( + instanceType, + instance.id, + format, + useDefaultSettings, + sourceStorage, + file, + adjustedOptions, + ); +} + +export function getHistory(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history; + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export async function undoActions(session, count) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.undo(count); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export async function redoActions(session, count) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.redo(count); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function freezeHistory(session, frozen) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.freeze(frozen); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function clearActions(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.clear(); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} + +export function getActions(session) { + const sessionType = session instanceof Task ? 'task' : 'job'; + const cache = getCache(sessionType); + + if (cache.has(session)) { + return cache.get(session).history.get(); + } + + throw new DataError( + 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', + ); +} diff --git a/cvat-core/src/api-implementation.js b/cvat-core/src/api-implementation.js deleted file mode 100644 index ecbd57d6d8ab..000000000000 --- a/cvat-core/src/api-implementation.js +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright (C) 2019-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -const config = require('./config'); - -(() => { - const PluginRegistry = require('./plugins'); - const serverProxy = require('./server-proxy'); - const lambdaManager = require('./lambda-manager'); - const { - isBoolean, - isInteger, - isString, - checkFilter, - checkExclusiveFields, - checkObjectType, - } = require('./common'); - - const User = require('./user'); - const { AnnotationFormats } = require('./annotation-formats'); - const { ArgumentError } = require('./exceptions'); - const { Task, Job } = require('./session'); - const { Project } = require('./project'); - const { CloudStorage } = require('./cloud-storage'); - const Organization = require('./organization'); - - function implementAPI(cvat) { - cvat.plugins.list.implementation = PluginRegistry.list; - cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat); - - cvat.lambda.list.implementation = lambdaManager.list.bind(lambdaManager); - cvat.lambda.run.implementation = lambdaManager.run.bind(lambdaManager); - cvat.lambda.call.implementation = lambdaManager.call.bind(lambdaManager); - cvat.lambda.cancel.implementation = lambdaManager.cancel.bind(lambdaManager); - cvat.lambda.listen.implementation = lambdaManager.listen.bind(lambdaManager); - cvat.lambda.requests.implementation = lambdaManager.requests.bind(lambdaManager); - - cvat.server.about.implementation = async () => { - const result = await serverProxy.server.about(); - return result; - }; - - cvat.server.share.implementation = async (directory) => { - const result = await serverProxy.server.share(directory); - return result; - }; - - cvat.server.formats.implementation = async () => { - const result = await serverProxy.server.formats(); - return new AnnotationFormats(result); - }; - - cvat.server.userAgreements.implementation = async () => { - const result = await serverProxy.server.userAgreements(); - return result; - }; - - cvat.server.register.implementation = async ( - username, - firstName, - lastName, - email, - password1, - password2, - userConfirmations, - ) => { - const user = await serverProxy.server.register( - username, - firstName, - lastName, - email, - password1, - password2, - userConfirmations, - ); - - return new User(user); - }; - - cvat.server.login.implementation = async (username, password) => { - await serverProxy.server.login(username, password); - }; - - cvat.server.logout.implementation = async () => { - await serverProxy.server.logout(); - }; - - cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => { - await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2); - }; - - cvat.server.requestPasswordReset.implementation = async (email) => { - await serverProxy.server.requestPasswordReset(email); - }; - - cvat.server.resetPassword.implementation = async (newPassword1, newPassword2, uid, token) => { - await serverProxy.server.resetPassword(newPassword1, newPassword2, uid, token); - }; - - cvat.server.authorized.implementation = async () => { - const result = await serverProxy.server.authorized(); - return result; - }; - - cvat.server.request.implementation = async (url, data) => { - const result = await serverProxy.server.request(url, data); - return result; - }; - - cvat.server.installedApps.implementation = async () => { - const result = await serverProxy.server.installedApps(); - return result; - }; - - cvat.users.get.implementation = async (filter) => { - checkFilter(filter, { - id: isInteger, - is_active: isBoolean, - self: isBoolean, - search: isString, - limit: isInteger, - }); - - let users = null; - if ('self' in filter && filter.self) { - users = await serverProxy.users.self(); - users = [users]; - } else { - const searchParams = {}; - for (const key in filter) { - if (filter[key] && key !== 'self') { - searchParams[key] = filter[key]; - } - } - users = await serverProxy.users.get(searchParams); - } - - users = users.map((user) => new User(user)); - return users; - }; - - cvat.jobs.get.implementation = async (filter) => { - checkFilter(filter, { - page: isInteger, - filter: isString, - sort: isString, - search: isString, - taskID: isInteger, - jobID: isInteger, - }); - - if ('taskID' in filter && 'jobID' in filter) { - throw new ArgumentError('Filter fields "taskID" and "jobID" are not permitted to be used at the same time'); - } - - if ('taskID' in filter) { - const [task] = await serverProxy.tasks.get({ id: filter.taskID }); - if (task) { - return new Task(task).jobs; - } - - return []; - } - - if ('jobID' in filter) { - const job = await serverProxy.jobs.get({ id: filter.jobID }); - if (job) { - return [new Job(job)]; - } - } - - const searchParams = {}; - for (const key of Object.keys(filter)) { - if (['page', 'sort', 'search', 'filter'].includes(key)) { - searchParams[key] = filter[key]; - } - } - - const jobsData = await serverProxy.jobs.get(searchParams); - const jobs = jobsData.results.map((jobData) => new Job(jobData)); - jobs.count = jobsData.count; - return jobs; - }; - - cvat.tasks.get.implementation = async (filter) => { - checkFilter(filter, { - page: isInteger, - projectId: isInteger, - id: isInteger, - sort: isString, - search: isString, - filter: isString, - ordering: isString, - }); - - checkExclusiveFields(filter, ['id', 'projectId'], ['page']); - const searchParams = {}; - for (const key of Object.keys(filter)) { - if (['page', 'id', 'sort', 'search', 'filter', 'ordering'].includes(key)) { - searchParams[key] = filter[key]; - } - } - - let tasksData = null; - if (filter.projectId) { - if (searchParams.filter) { - const parsed = JSON.parse(searchParams.filter); - searchParams.filter = JSON.stringify({ and: [parsed, { '==': [{ var: 'project_id' }, filter.projectId] }] }); - } else { - searchParams.filter = JSON.stringify({ and: [{ '==': [{ var: 'project_id' }, filter.projectId] }] }); - } - } - - tasksData = await serverProxy.tasks.get(searchParams); - const tasks = tasksData.map((task) => new Task(task)); - tasks.count = tasksData.count; - return tasks; - }; - - cvat.projects.get.implementation = async (filter) => { - checkFilter(filter, { - id: isInteger, - page: isInteger, - search: isString, - sort: isString, - filter: isString, - }); - - checkExclusiveFields(filter, ['id'], ['page']); - const searchParams = {}; - for (const key of Object.keys(filter)) { - if (['id', 'page', 'search', 'sort', 'page'].includes(key)) { - searchParams[key] = filter[key]; - } - } - - const projectsData = await serverProxy.projects.get(searchParams); - const projects = projectsData.map((project) => { - project.task_ids = project.tasks; - return project; - }).map((project) => new Project(project)); - - projects.count = projectsData.count; - - return projects; - }; - - cvat.projects.searchNames - .implementation = async (search, limit) => serverProxy.projects.searchNames(search, limit); - - cvat.cloudStorages.get.implementation = async (filter) => { - checkFilter(filter, { - page: isInteger, - filter: isString, - sort: isString, - id: isInteger, - search: isString, - }); - - checkExclusiveFields(filter, ['id', 'search'], ['page']); - const searchParams = {}; - for (const key of Object.keys(filter)) { - if (['page', 'filter', 'sort', 'id', 'search'].includes(key)) { - searchParams[key] = filter[key]; - } - } - const cloudStoragesData = await serverProxy.cloudStorages.get(searchParams); - const cloudStorages = cloudStoragesData.map((cloudStorage) => new CloudStorage(cloudStorage)); - cloudStorages.count = cloudStoragesData.count; - return cloudStorages; - }; - - cvat.organizations.get.implementation = async () => { - const organizationsData = await serverProxy.organizations.get(); - const organizations = organizationsData.map((organizationData) => new Organization(organizationData)); - return organizations; - }; - - cvat.organizations.activate.implementation = (organization) => { - checkObjectType('organization', organization, null, Organization); - config.organizationID = organization.slug; - }; - - cvat.organizations.deactivate.implementation = async () => { - config.organizationID = null; - }; - - return cvat; - } - - module.exports = implementAPI; -})(); diff --git a/cvat-core/src/api-implementation.ts b/cvat-core/src/api-implementation.ts new file mode 100644 index 000000000000..7835e140a978 --- /dev/null +++ b/cvat-core/src/api-implementation.ts @@ -0,0 +1,348 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import config from './config'; + +import PluginRegistry from './plugins'; +import serverProxy from './server-proxy'; +import lambdaManager from './lambda-manager'; +import { + isBoolean, + isInteger, + isString, + checkFilter, + checkExclusiveFields, + checkObjectType, +} from './common'; + +import User from './user'; +import { AnnotationFormats } from './annotation-formats'; +import { ArgumentError } from './exceptions'; +import { Task, Job } from './session'; +import Project from './project'; +import CloudStorage from './cloud-storage'; +import Organization from './organization'; +import Webhook from './webhook'; + +export default function implementAPI(cvat) { + cvat.plugins.list.implementation = PluginRegistry.list; + cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat); + + cvat.lambda.list.implementation = lambdaManager.list.bind(lambdaManager); + cvat.lambda.run.implementation = lambdaManager.run.bind(lambdaManager); + cvat.lambda.call.implementation = lambdaManager.call.bind(lambdaManager); + cvat.lambda.cancel.implementation = lambdaManager.cancel.bind(lambdaManager); + cvat.lambda.listen.implementation = lambdaManager.listen.bind(lambdaManager); + cvat.lambda.requests.implementation = lambdaManager.requests.bind(lambdaManager); + + cvat.server.about.implementation = async () => { + const result = await serverProxy.server.about(); + return result; + }; + + cvat.server.share.implementation = async (directory) => { + const result = await serverProxy.server.share(directory); + return result; + }; + + cvat.server.formats.implementation = async () => { + const result = await serverProxy.server.formats(); + return new AnnotationFormats(result); + }; + + cvat.server.userAgreements.implementation = async () => { + const result = await serverProxy.server.userAgreements(); + return result; + }; + + cvat.server.register.implementation = async ( + username, + firstName, + lastName, + email, + password, + userConfirmations, + ) => { + const user = await serverProxy.server.register( + username, + firstName, + lastName, + email, + password, + userConfirmations, + ); + + return new User(user); + }; + + cvat.server.login.implementation = async (username, email, password) => { + await serverProxy.server.login(username, email, password); + }; + + cvat.server.logout.implementation = async () => { + await serverProxy.server.logout(); + }; + + cvat.server.advancedAuthentication.implementation = async () => { + const result = await serverProxy.server.advancedAuthentication(); + return result; + }; + + cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => { + await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2); + }; + + cvat.server.requestPasswordReset.implementation = async (email) => { + await serverProxy.server.requestPasswordReset(email); + }; + + cvat.server.resetPassword.implementation = async (newPassword1, newPassword2, uid, token) => { + await serverProxy.server.resetPassword(newPassword1, newPassword2, uid, token); + }; + + cvat.server.authorized.implementation = async () => { + const result = await serverProxy.server.authorized(); + return result; + }; + + cvat.server.healthCheck.implementation = async ( + maxRetries = 1, + checkPeriod = 3000, + requestTimeout = 5000, + progressCallback = undefined, + ) => { + const result = await serverProxy.server.healthCheck(maxRetries, checkPeriod, requestTimeout, progressCallback); + return result; + }; + + cvat.server.request.implementation = async (url, data) => { + const result = await serverProxy.server.request(url, data); + return result; + }; + + cvat.server.installedApps.implementation = async () => { + const result = await serverProxy.server.installedApps(); + return result; + }; + + cvat.server.loginWithSocialAccount.implementation = async ( + provider: string, + code: string, + authParams?: string, + process?: string, + scope?: string, + ) => { + const result = await serverProxy.server.loginWithSocialAccount(provider, code, authParams, process, scope); + return result; + }; + + cvat.users.get.implementation = async (filter) => { + checkFilter(filter, { + id: isInteger, + is_active: isBoolean, + self: isBoolean, + search: isString, + limit: isInteger, + }); + + let users = null; + if ('self' in filter && filter.self) { + users = await serverProxy.users.self(); + users = [users]; + } else { + const searchParams = {}; + for (const key in filter) { + if (filter[key] && key !== 'self') { + searchParams[key] = filter[key]; + } + } + users = await serverProxy.users.get(searchParams); + } + + users = users.map((user) => new User(user)); + return users; + }; + + cvat.jobs.get.implementation = async (filter) => { + checkFilter(filter, { + page: isInteger, + filter: isString, + sort: isString, + search: isString, + taskID: isInteger, + jobID: isInteger, + }); + + if ('taskID' in filter && 'jobID' in filter) { + throw new ArgumentError('Filter fields "taskID" and "jobID" are not permitted to be used at the same time'); + } + + if ('taskID' in filter) { + const [task] = await serverProxy.tasks.get({ id: filter.taskID }); + if (task) { + return new Task(task).jobs; + } + + return []; + } + + if ('jobID' in filter) { + const job = await serverProxy.jobs.get({ id: filter.jobID }); + if (job) { + return [new Job(job)]; + } + } + + const searchParams = {}; + for (const key of Object.keys(filter)) { + if (['page', 'sort', 'search', 'filter'].includes(key)) { + searchParams[key] = filter[key]; + } + } + + const jobsData = await serverProxy.jobs.get(searchParams); + const jobs = jobsData.results.map((jobData) => new Job(jobData)); + jobs.count = jobsData.count; + return jobs; + }; + + cvat.tasks.get.implementation = async (filter) => { + checkFilter(filter, { + page: isInteger, + projectId: isInteger, + id: isInteger, + sort: isString, + search: isString, + filter: isString, + ordering: isString, + }); + + checkExclusiveFields(filter, ['id', 'projectId'], ['page']); + const searchParams = {}; + for (const key of Object.keys(filter)) { + if (['page', 'id', 'sort', 'search', 'filter', 'ordering'].includes(key)) { + searchParams[key] = filter[key]; + } + } + + let tasksData = null; + if (filter.projectId) { + if (searchParams.filter) { + const parsed = JSON.parse(searchParams.filter); + searchParams.filter = JSON.stringify({ and: [parsed, { '==': [{ var: 'project_id' }, filter.projectId] }] }); + } else { + searchParams.filter = JSON.stringify({ and: [{ '==': [{ var: 'project_id' }, filter.projectId] }] }); + } + } + + tasksData = await serverProxy.tasks.get(searchParams); + const tasks = tasksData.map((task) => new Task(task)); + tasks.count = tasksData.count; + return tasks; + }; + + cvat.projects.get.implementation = async (filter) => { + checkFilter(filter, { + id: isInteger, + page: isInteger, + search: isString, + sort: isString, + filter: isString, + }); + + checkExclusiveFields(filter, ['id'], ['page']); + const searchParams = {}; + for (const key of Object.keys(filter)) { + if (['id', 'page', 'search', 'sort', 'page', 'filter'].includes(key)) { + searchParams[key] = filter[key]; + } + } + + const projectsData = await serverProxy.projects.get(searchParams); + const projects = projectsData.map((project) => { + project.task_ids = project.tasks; + return project; + }).map((project) => new Project(project)); + + projects.count = projectsData.count; + + return projects; + }; + + cvat.projects.searchNames + .implementation = async (search, limit) => serverProxy.projects.searchNames(search, limit); + + cvat.cloudStorages.get.implementation = async (filter) => { + checkFilter(filter, { + page: isInteger, + filter: isString, + sort: isString, + id: isInteger, + search: isString, + }); + + checkExclusiveFields(filter, ['id', 'search'], ['page']); + const searchParams = {}; + for (const key of Object.keys(filter)) { + if (['page', 'filter', 'sort', 'id', 'search'].includes(key)) { + searchParams[key] = filter[key]; + } + } + const cloudStoragesData = await serverProxy.cloudStorages.get(searchParams); + const cloudStorages = cloudStoragesData.map((cloudStorage) => new CloudStorage(cloudStorage)); + cloudStorages.count = cloudStoragesData.count; + return cloudStorages; + }; + + cvat.organizations.get.implementation = async () => { + const organizationsData = await serverProxy.organizations.get(); + const organizations = organizationsData.map((organizationData) => new Organization(organizationData)); + return organizations; + }; + + cvat.organizations.activate.implementation = (organization) => { + checkObjectType('organization', organization, null, Organization); + config.organizationID = organization.slug; + }; + + cvat.organizations.deactivate.implementation = async () => { + config.organizationID = null; + }; + + cvat.webhooks.get.implementation = async (filter) => { + checkFilter(filter, { + page: isInteger, + id: isInteger, + projectId: isInteger, + filter: isString, + search: isString, + sort: isString, + }); + + checkExclusiveFields(filter, ['id', 'projectId'], ['page']); + const searchParams = {}; + for (const key of Object.keys(filter)) { + if (['page', 'id', 'filter', 'search', 'sort'].includes(key)) { + searchParams[key] = filter[key]; + } + } + + if (filter.projectId) { + if (searchParams.filter) { + const parsed = JSON.parse(searchParams.filter); + searchParams.filter = JSON.stringify({ and: [parsed, { '==': [{ var: 'project_id' }, filter.projectId] }] }); + } else { + searchParams.filter = JSON.stringify({ and: [{ '==': [{ var: 'project_id' }, filter.projectId] }] }); + } + } + + const webhooksData = await serverProxy.webhooks.get(searchParams); + const webhooks = webhooksData.map((webhookData) => new Webhook(webhookData)); + webhooks.count = webhooksData.count; + return webhooks; + }; + + return cvat; +} diff --git a/cvat-core/src/api.js b/cvat-core/src/api.ts similarity index 88% rename from cvat-core/src/api.js rename to cvat-core/src/api.ts index 722ce925c7f0..aee7129415d3 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.ts @@ -1,4 +1,5 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -7,33 +8,36 @@ * @module API */ -function build() { - const PluginRegistry = require('./plugins'); - const loggerStorage = require('./logger-storage'); - const Log = require('./log'); - const ObjectState = require('./object-state'); - const Statistics = require('./statistics'); - const Comment = require('./comment'); - const Issue = require('./issue'); - const { Job, Task } = require('./session'); - const { Project } = require('./project'); - const implementProject = require('./project-implementation'); - const { Attribute, Label } = require('./labels'); - const MLModel = require('./ml-model'); - const { FrameData } = require('./frames'); - const { CloudStorage } = require('./cloud-storage'); - const Organization = require('./organization'); +import PluginRegistry from './plugins'; +import loggerStorage from './logger-storage'; +import { Log } from './log'; +import ObjectState from './object-state'; +import Statistics from './statistics'; +import Comment from './comment'; +import Issue from './issue'; +import { Job, Task } from './session'; +import Project from './project'; +import implementProject from './project-implementation'; +import { Attribute, Label } from './labels'; +import MLModel from './ml-model'; +import { FrameData } from './frames'; +import CloudStorage from './cloud-storage'; +import Organization from './organization'; +import Webhook from './webhook'; + +import * as enums from './enums'; - const enums = require('./enums'); +import { + Exception, ArgumentError, DataError, ScriptingError, PluginError, ServerError, +} from './exceptions'; - const { - Exception, ArgumentError, DataError, ScriptingError, PluginError, ServerError, - } = require('./exceptions'); +import User from './user'; +import pjson from '../package.json'; +import config from './config'; - const User = require('./user'); - const pjson = require('../package.json'); - const config = require('./config'); +import implementAPI from './api-implementation'; +function build() { /** * API entrypoint * @namespace cvat @@ -125,22 +129,20 @@ function build() { * @param {string} firstName A first name for the new account * @param {string} lastName A last name for the new account * @param {string} email A email address for the new account - * @param {string} password1 A password for the new account - * @param {string} password2 The confirmation password for the new account + * @param {string} password A password for the new account * @param {Object} userConfirmations An user confirmations of terms of use if needed * @returns {Object} response data * @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.ServerError} */ - async register(username, firstName, lastName, email, password1, password2, userConfirmations) { + async register(username, firstName, lastName, email, password, userConfirmations) { const result = await PluginRegistry.apiWrapper( cvat.server.register, username, firstName, lastName, email, - password1, - password2, + password, userConfirmations, ); return result; @@ -151,12 +153,13 @@ function build() { * @async * @memberof module:API.cvat.server * @param {string} username An username of an account + * @param {string} email Email to be updated * @param {string} password A password of an account * @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.ServerError} */ - async login(username, password) { - const result = await PluginRegistry.apiWrapper(cvat.server.login, username, password); + async login(username, email, password) { + const result = await PluginRegistry.apiWrapper(cvat.server.login, username, email, password); return result; }, /** @@ -171,6 +174,10 @@ function build() { const result = await PluginRegistry.apiWrapper(cvat.server.logout); return result; }, + async advancedAuthentication() { + const result = await PluginRegistry.apiWrapper(cvat.server.advancedAuthentication); + return result; + }, /** * Method allows to change user password * @method changePassword @@ -239,6 +246,26 @@ function build() { const result = await PluginRegistry.apiWrapper(cvat.server.authorized); return result; }, + /** + * Method allows to health check the server + * @method healthCheck + * @async + * @memberof module:API.cvat.server + * @param {number} requestTimeout + * @returns {Object | undefined} response data if exist + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + */ + async healthCheck(maxRetries = 1, checkPeriod = 3000, requestTimeout = 5000, progressCallback = undefined) { + const result = await PluginRegistry.apiWrapper( + cvat.server.healthCheck, + maxRetries, + checkPeriod, + requestTimeout, + progressCallback, + ); + return result; + }, /** * Method allows to do requests via cvat-core with authorization headers * @method request @@ -268,6 +295,18 @@ function build() { const result = await PluginRegistry.apiWrapper(cvat.server.installedApps); return result; }, + async loginWithSocialAccount( + provider: string, + code: string, + authParams?: string, + process?: string, + scope?: string, + ) { + const result = await PluginRegistry.apiWrapper( + cvat.server.loginWithSocialAccount, provider, code, authParams, process, scope, + ); + return result; + }, }, /** * Namespace is used for getting projects @@ -280,8 +319,8 @@ function build() { * @property {string} name Check if name contains this value * @property {module:API.cvat.enums.ProjectStatus} status * Check if status contains this value - * @property {integer} id Check if id equals this value - * @property {integer} page Get specific page + * @property {number} id Check if id equals this value + * @property {number} page Get specific page * (default REST API returns 20 projects per request. * In order to get more, it is need to specify next page) * @property {string} owner Check if owner user contains this value @@ -336,11 +375,11 @@ function build() { * Check if status contains this value * @property {module:API.cvat.enums.TaskMode} mode * Check if mode contains this value - * @property {integer} id Check if id equals this value - * @property {integer} page Get specific page + * @property {number} id Check if id equals this value + * @property {number} page Get specific page * (default REST API returns 20 tasks per request. * In order to get more, it is need to specify next page) - * @property {integer} projectId Check if project_id field contains this value + * @property {number} projectId Check if project_id field contains this value * @property {string} owner Check if owner user contains this value * @property {string} assignee Check if assigneed contains this value * @property {string} search Combined search of contains among all fields @@ -371,8 +410,8 @@ function build() { /** * @typedef {Object} JobFilter * Only one of fields is allowed simultaneously - * @property {integer} taskID filter all jobs of specific task - * @property {integer} jobID filter job with a specific id + * @property {number} taskID filter all jobs of specific task + * @property {number} jobID filter job with a specific id * @global */ @@ -701,6 +740,9 @@ function build() { * @memberof module:API.cvat.config * @property {number} uploadChunkSize max size of one data request in mb * @memberof module:API.cvat.config + * @property {number} removeUnderlyingMaskPixels defines if after adding/changing + * a mask it should remove overlapped pixels from other objects + * @memberof module:API.cvat.config */ get backendAPI() { return config.backendAPI; @@ -726,6 +768,12 @@ function build() { set uploadChunkSize(value) { config.uploadChunkSize = value; }, + get removeUnderlyingMaskPixels(): boolean { + return config.removeUnderlyingMaskPixels; + }, + set removeUnderlyingMaskPixels(value: boolean) { + config.removeUnderlyingMaskPixels = value; + }, }, /** * Namespace contains some library information e.g. api version @@ -775,8 +823,8 @@ function build() { * @property {string} displayName Check if displayName contains this value * @property {string} resource Check if resource name contains this value * @property {module:API.cvat.enums.ProviderType} providerType Check if providerType equal this value - * @property {integer} id Check if id equals this value - * @property {integer} page Get specific page + * @property {number} id Check if id equals this value + * @property {number} page Get specific page * (default REST API returns 20 clouds storages per request. * In order to get more, it is need to specify next page) * @property {string} owner Check if an owner name contains this value @@ -843,6 +891,26 @@ function build() { return result; }, }, + /** + * This namespace could be used to get webhooks list from the server + * @namespace webhooks + * @memberof module:API.cvat + */ + webhooks: { + /** + * Method returns a list of organizations + * @method get + * @async + * @memberof module:API.cvat.webhooks + * @returns {module:API.cvat.classes.Webhook[]} + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + */ + async get(filter: any) { + const result = await PluginRegistry.apiWrapper(cvat.webhooks.get, filter); + return result; + }, + }, /** * Namespace is used for access to classes * @namespace classes @@ -864,6 +932,7 @@ function build() { FrameData, CloudStorage, Organization, + Webhook, }, }; @@ -879,14 +948,8 @@ function build() { cvat.cloudStorages = Object.freeze(cvat.cloudStorages); cvat.organizations = Object.freeze(cvat.organizations); - const implementAPI = require('./api-implementation'); - - Math.clamp = function clamp(value, min, max) { - return Math.min(Math.max(value, min), max); - }; - const implemented = Object.freeze(implementAPI(cvat)); return implemented; } -module.exports = build(); +export default build(); diff --git a/cvat-core/src/cloud-storage.js b/cvat-core/src/cloud-storage.js deleted file mode 100644 index 9be108b3ffa2..000000000000 --- a/cvat-core/src/cloud-storage.js +++ /dev/null @@ -1,511 +0,0 @@ -// Copyright (C) 2021-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const PluginRegistry = require('./plugins'); - const serverProxy = require('./server-proxy'); - const { isBrowser, isNode } = require('browser-or-node'); - const { ArgumentError } = require('./exceptions'); - const { CloudStorageCredentialsType, CloudStorageProviderType } = require('./enums'); - - function validateNotEmptyString(value) { - if (typeof value !== 'string') { - throw new ArgumentError(`Value must be a string. ${typeof value} was found`); - } else if (!value.trim().length) { - throw new ArgumentError('Value mustn\'t be empty string'); - } - } - - /** - * Class representing a cloud storage - * @memberof module:API.cvat.classes - */ - class CloudStorage { - constructor(initialData) { - const data = { - id: undefined, - display_name: undefined, - description: undefined, - credentials_type: undefined, - provider_type: undefined, - resource: undefined, - account_name: undefined, - key: undefined, - secret_key: undefined, - session_token: undefined, - key_file: undefined, - specific_attributes: undefined, - owner: undefined, - created_date: undefined, - updated_date: undefined, - manifest_path: undefined, - manifests: undefined, - }; - - for (const property in data) { - if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { - data[property] = initialData[property]; - } - } - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - */ - id: { - get: () => data.id, - }, - /** - * Storage name - * @name displayName - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - displayName: { - get: () => data.display_name, - set: (value) => { - validateNotEmptyString(value); - data.display_name = value; - }, - }, - /** - * Storage description - * @name description - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - description: { - get: () => data.description, - set: (value) => { - if (typeof value !== 'string') { - throw new ArgumentError('Value must be string'); - } - data.description = value; - }, - }, - /** - * Azure account name - * @name accountName - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - accountName: { - get: () => data.account_name, - set: (value) => { - validateNotEmptyString(value); - data.account_name = value; - }, - }, - /** - * AWS access key id - * @name accessKey - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - accessKey: { - get: () => data.key, - set: (value) => { - validateNotEmptyString(value); - data.key = value; - }, - }, - /** - * AWS secret key - * @name secretKey - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - secretKey: { - get: () => data.secret_key, - set: (value) => { - validateNotEmptyString(value); - data.secret_key = value; - }, - }, - /** - * Session token - * @name token - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - token: { - get: () => data.session_token, - set: (value) => { - validateNotEmptyString(value); - data.session_token = value; - }, - }, - /** - * Key file - * @name keyFile - * @type {File} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - keyFile: { - get: () => data.key_file, - set: (file) => { - if (file instanceof File) { - data.key_file = file; - } else { - throw new ArgumentError(`Should be a file. ${typeof file} was found`); - } - }, - }, - /** - * Unique resource name - * @name resource - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - resource: { - get: () => data.resource, - set: (value) => { - validateNotEmptyString(value); - data.resource = value; - }, - }, - /** - * @name manifestPath - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - manifestPath: { - get: () => data.manifest_path, - set: (value) => { - validateNotEmptyString(value); - data.manifest_path = value; - }, - }, - /** - * @name providerType - * @type {module:API.cvat.enums.ProviderType} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - providerType: { - get: () => data.provider_type, - set: (key) => { - if (key !== undefined && !!CloudStorageProviderType[key]) { - data.provider_type = CloudStorageProviderType[key]; - } else { - throw new ArgumentError('Value must be one CloudStorageProviderType keys'); - } - }, - }, - /** - * @name credentialsType - * @type {module:API.cvat.enums.CloudStorageCredentialsType} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - credentialsType: { - get: () => data.credentials_type, - set: (key) => { - if (key !== undefined && !!CloudStorageCredentialsType[key]) { - data.credentials_type = CloudStorageCredentialsType[key]; - } else { - throw new ArgumentError('Value must be one CloudStorageCredentialsType keys'); - } - }, - }, - /** - * @name specificAttributes - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - specificAttributes: { - get: () => data.specific_attributes, - set: (attributesValue) => { - if (typeof attributesValue === 'string') { - const attrValues = new URLSearchParams( - Array.from(new URLSearchParams(attributesValue).entries()).filter( - ([key, value]) => !!key && !!value, - ), - ).toString(); - if (!attrValues) { - throw new ArgumentError('Value must match the key1=value1&key2=value2'); - } - data.specific_attributes = attributesValue; - } else { - throw new ArgumentError('Value must be a string'); - } - }, - }, - /** - * Instance of a user who has created the cloud storage - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - */ - owner: { - get: () => data.owner, - }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - */ - createdDate: { - get: () => data.created_date, - }, - /** - * @name updatedDate - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - */ - updatedDate: { - get: () => data.updated_date, - }, - /** - * @name manifests - * @type {string[]} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - manifests: { - get: () => data.manifests, - set: (manifests) => { - if (Array.isArray(manifests)) { - for (const elem of manifests) { - if (typeof elem !== 'string') { - throw new ArgumentError('Each element of the manifests array must be a string'); - } - } - data.manifests = manifests; - } else { - throw new ArgumentError('Value must be an array'); - } - }, - }, - }), - ); - } - - /** - * Method updates data of a created cloud storage or creates new cloud storage - * @method save - * @returns {module:API.cvat.classes.CloudStorage} - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async save() { - const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.save); - return result; - } - - /** - * Method deletes a cloud storage from a server - * @method delete - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async delete() { - const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.delete); - return result; - } - - /** - * Method returns cloud storage content - * @method getContent - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async getContent() { - const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getContent); - return result; - } - - /** - * Method returns the cloud storage preview - * @method getPreview - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async getPreview() { - const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getPreview); - return result; - } - - /** - * Method returns cloud storage status - * @method getStatus - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async getStatus() { - const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getStatus); - return result; - } - } - - CloudStorage.prototype.save.implementation = async function () { - function prepareOptionalFields(cloudStorageInstance) { - const data = {}; - if (cloudStorageInstance.description !== undefined) { - data.description = cloudStorageInstance.description; - } - - if (cloudStorageInstance.accountName) { - data.account_name = cloudStorageInstance.accountName; - } - - if (cloudStorageInstance.accessKey) { - data.key = cloudStorageInstance.accessKey; - } - - if (cloudStorageInstance.secretKey) { - data.secret_key = cloudStorageInstance.secretKey; - } - - if (cloudStorageInstance.token) { - data.session_token = cloudStorageInstance.token; - } - - if (cloudStorageInstance.keyFile) { - data.key_file = cloudStorageInstance.keyFile; - } - - if (cloudStorageInstance.specificAttributes !== undefined) { - data.specific_attributes = cloudStorageInstance.specificAttributes; - } - return data; - } - // update - if (typeof this.id !== 'undefined') { - // provider_type and recource should not change; - // send to the server only the values that have changed - const initialData = {}; - if (this.displayName) { - initialData.display_name = this.displayName; - } - if (this.credentialsType) { - initialData.credentials_type = this.credentialsType; - } - - if (this.manifests) { - initialData.manifests = this.manifests; - } - - const cloudStorageData = { - ...initialData, - ...prepareOptionalFields(this), - }; - - await serverProxy.cloudStorages.update(this.id, cloudStorageData); - return this; - } - - // create - const initialData = { - display_name: this.displayName, - credentials_type: this.credentialsType, - provider_type: this.providerType, - resource: this.resource, - manifests: this.manifests, - }; - - const cloudStorageData = { - ...initialData, - ...prepareOptionalFields(this), - }; - - const cloudStorage = await serverProxy.cloudStorages.create(cloudStorageData); - return new CloudStorage(cloudStorage); - }; - - CloudStorage.prototype.delete.implementation = async function () { - const result = await serverProxy.cloudStorages.delete(this.id); - return result; - }; - - CloudStorage.prototype.getContent.implementation = async function () { - const result = await serverProxy.cloudStorages.getContent(this.id, this.manifestPath); - return result; - }; - - CloudStorage.prototype.getPreview.implementation = async function getPreview() { - return new Promise((resolve, reject) => { - serverProxy.cloudStorages - .getPreview(this.id) - .then((result) => { - if (isNode) { - resolve(global.Buffer.from(result, 'binary').toString('base64')); - } else if (isBrowser) { - const reader = new FileReader(); - reader.onload = () => { - resolve(reader.result); - }; - reader.readAsDataURL(result); - } - }) - .catch((error) => { - reject(error); - }); - }); - }; - - CloudStorage.prototype.getStatus.implementation = async function () { - const result = await serverProxy.cloudStorages.getStatus(this.id); - return result; - }; - - module.exports = { - CloudStorage, - }; -})(); diff --git a/cvat-core/src/cloud-storage.ts b/cvat-core/src/cloud-storage.ts new file mode 100644 index 000000000000..1aab6ef40598 --- /dev/null +++ b/cvat-core/src/cloud-storage.ts @@ -0,0 +1,393 @@ +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { isBrowser, isNode } from 'browser-or-node'; +import PluginRegistry from './plugins'; +import serverProxy from './server-proxy'; +import { ArgumentError } from './exceptions'; +import { CloudStorageCredentialsType, CloudStorageProviderType, CloudStorageStatus } from './enums'; +import User from './user'; + +function validateNotEmptyString(value: string): void { + if (typeof value !== 'string') { + throw new ArgumentError(`Value must be a string. ${typeof value} was found`); + } else if (!value.trim().length) { + throw new ArgumentError('Value mustn\'t be empty string'); + } +} + +interface RawCloudStorageData { + id?: number; + display_name?: string; + description?: string, + credentials_type?: CloudStorageCredentialsType, + provider_type?: CloudStorageProviderType, + resource?: string, + account_name?: string, + key?: string, + secret_key?: string, + session_token?: string, + key_file?: File, + specific_attributes?: string, + owner?: any, + created_date?: string, + updated_date?: string, + manifest_path?: string, + manifests?: string[], +} + +export default class CloudStorage { + public readonly id: number; + public displayName: string; + public description: string; + public accountName: string; + public accessKey: string; + public secretKey: string; + public token: string; + public keyFile: File; + public resource: string; + public manifestPath: string; + public provider_type: CloudStorageProviderType; + public credentials_type: CloudStorageCredentialsType; + public specificAttributes: string; + public manifests: string[]; + public readonly owner: User; + public readonly createdDate: string; + public readonly updatedDate: string; + + constructor(initialData: RawCloudStorageData) { + const data: RawCloudStorageData = { + id: undefined, + display_name: undefined, + description: undefined, + credentials_type: undefined, + provider_type: undefined, + resource: undefined, + account_name: undefined, + key: undefined, + secret_key: undefined, + session_token: undefined, + key_file: undefined, + specific_attributes: undefined, + owner: undefined, + created_date: undefined, + updated_date: undefined, + manifest_path: undefined, + manifests: undefined, + }; + + for (const property in data) { + if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { + data[property] = initialData[property]; + } + } + + Object.defineProperties( + this, + Object.freeze({ + id: { + get: () => data.id, + }, + displayName: { + get: () => data.display_name, + set: (value) => { + validateNotEmptyString(value); + data.display_name = value; + }, + }, + description: { + get: () => data.description, + set: (value) => { + if (typeof value !== 'string') { + throw new ArgumentError('Value must be string'); + } + data.description = value; + }, + }, + accountName: { + get: () => data.account_name, + set: (value) => { + validateNotEmptyString(value); + data.account_name = value; + }, + }, + accessKey: { + get: () => data.key, + set: (value) => { + validateNotEmptyString(value); + data.key = value; + }, + }, + secretKey: { + get: () => data.secret_key, + set: (value) => { + validateNotEmptyString(value); + data.secret_key = value; + }, + }, + token: { + get: () => data.session_token, + set: (value) => { + validateNotEmptyString(value); + data.session_token = value; + }, + }, + keyFile: { + get: () => data.key_file, + set: (file) => { + if (file instanceof File) { + data.key_file = file; + } else { + throw new ArgumentError(`Should be a file. ${typeof file} was found`); + } + }, + }, + resource: { + get: () => data.resource, + set: (value) => { + validateNotEmptyString(value); + data.resource = value; + }, + }, + manifestPath: { + get: () => data.manifest_path, + set: (value) => { + validateNotEmptyString(value); + data.manifest_path = value; + }, + }, + providerType: { + get: () => data.provider_type, + set: (key) => { + if (key !== undefined && !!CloudStorageProviderType[key]) { + data.provider_type = CloudStorageProviderType[key]; + } else { + throw new ArgumentError('Value must be one CloudStorageProviderType keys'); + } + }, + }, + credentialsType: { + get: () => data.credentials_type, + set: (key) => { + if (key !== undefined && !!CloudStorageCredentialsType[key]) { + data.credentials_type = CloudStorageCredentialsType[key]; + } else { + throw new ArgumentError('Value must be one CloudStorageCredentialsType keys'); + } + }, + }, + specificAttributes: { + get: () => data.specific_attributes, + set: (attributesValue) => { + if (typeof attributesValue === 'string') { + const attrValues = new URLSearchParams( + Array.from(new URLSearchParams(attributesValue).entries()).filter( + ([key, value]) => !!key && !!value, + ), + ).toString(); + if (!attrValues) { + throw new ArgumentError('Value must match the key1=value1&key2=value2'); + } + data.specific_attributes = attributesValue; + } else { + throw new ArgumentError('Value must be a string'); + } + }, + }, + owner: { + get: () => data.owner, + }, + createdDate: { + get: () => data.created_date, + }, + updatedDate: { + get: () => data.updated_date, + }, + manifests: { + get: () => data.manifests, + set: (manifests) => { + if (Array.isArray(manifests)) { + for (const elem of manifests) { + if (typeof elem !== 'string') { + throw new ArgumentError('Each element of the manifests array must be a string'); + } + } + data.manifests = manifests; + } else { + throw new ArgumentError('Value must be an array'); + } + }, + }, + }), + ); + } + + // Method updates data of a created cloud storage or creates new cloud storage + public async save(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.save); + return result; + } + + public async delete(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.delete); + return result; + } + + public async getContent(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getContent); + return result; + } + + public async getPreview(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getPreview); + return result; + } + + public async getStatus(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getStatus); + return result; + } +} + +Object.defineProperties(CloudStorage.prototype.save, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(): Promise { + function prepareOptionalFields(cloudStorageInstance: CloudStorage): RawCloudStorageData { + const data: RawCloudStorageData = {}; + if (cloudStorageInstance.description !== undefined) { + data.description = cloudStorageInstance.description; + } + + if (cloudStorageInstance.accountName) { + data.account_name = cloudStorageInstance.accountName; + } + + if (cloudStorageInstance.accessKey) { + data.key = cloudStorageInstance.accessKey; + } + + if (cloudStorageInstance.secretKey) { + data.secret_key = cloudStorageInstance.secretKey; + } + + if (cloudStorageInstance.token) { + data.session_token = cloudStorageInstance.token; + } + + if (cloudStorageInstance.keyFile) { + data.key_file = cloudStorageInstance.keyFile; + } + + if (cloudStorageInstance.specificAttributes !== undefined) { + data.specific_attributes = cloudStorageInstance.specificAttributes; + } + return data; + } + // update + if (typeof this.id !== 'undefined') { + // provider_type and recource should not change; + // send to the server only the values that have changed + const initialData: RawCloudStorageData = {}; + if (this.displayName) { + initialData.display_name = this.displayName; + } + if (this.credentialsType) { + initialData.credentials_type = this.credentialsType; + } + + if (this.manifests) { + initialData.manifests = this.manifests; + } + + const cloudStorageData = { + ...initialData, + ...prepareOptionalFields(this), + }; + + await serverProxy.cloudStorages.update(this.id, cloudStorageData); + return this; + } + + // create + const initialData: RawCloudStorageData = { + display_name: this.displayName, + credentials_type: this.credentialsType, + provider_type: this.providerType, + resource: this.resource, + manifests: this.manifests, + }; + + const cloudStorageData = { + ...initialData, + ...prepareOptionalFields(this), + }; + + const cloudStorage = await serverProxy.cloudStorages.create(cloudStorageData); + return new CloudStorage(cloudStorage); + }, + }, +}); + +Object.defineProperties(CloudStorage.prototype.delete, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(): Promise { + const result = await serverProxy.cloudStorages.delete(this.id); + return result; + }, + }, +}); + +Object.defineProperties(CloudStorage.prototype.getContent, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(): Promise { + const result = await serverProxy.cloudStorages.getContent(this.id, this.manifestPath); + return result; + }, + }, +}); + +Object.defineProperties(CloudStorage.prototype.getPreview, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(): Promise { + return new Promise((resolve, reject) => { + serverProxy.cloudStorages + .getPreview(this.id) + .then((result) => { + if (isNode) { + resolve(global.Buffer.from(result, 'binary').toString('base64')); + } else if (isBrowser) { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(result); + } + }) + .catch((error) => { + reject(error); + }); + }); + }, + }, +}); + +Object.defineProperties(CloudStorage.prototype.getStatus, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(): Promise { + const result = await serverProxy.cloudStorages.getStatus(this.id); + return result; + }, + }, +}); diff --git a/cvat-core/src/comment.js b/cvat-core/src/comment.ts similarity index 55% rename from cvat-core/src/comment.js rename to cvat-core/src/comment.ts index cee8724b4a68..347e23f81094 100644 --- a/cvat-core/src/comment.js +++ b/cvat-core/src/comment.ts @@ -1,18 +1,33 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -const User = require('./user'); -const { ArgumentError } = require('./exceptions'); +import User from './user'; +import { ArgumentError } from './exceptions'; -/** - * Class representing a single comment - * @memberof module:API.cvat.classes - * @hideconstructor - */ -class Comment { - constructor(initialData) { - const data = { +export interface RawCommentData { + id?: number; + message?: string; + created_date?: string; + updated_date?: string; + owner?: any; +} + +interface SerializedCommentData extends RawCommentData{ + owner_id?: number; + issue?: number; +} + +export default class Comment { + public readonly id: number; + public readonly createdDate: string; + public readonly updatedDate: string; + public readonly owner: User; + public message: string; + + constructor(initialData: RawCommentData) { + const data: RawCommentData = { id: undefined, message: undefined, created_date: undefined, @@ -34,23 +49,9 @@ class Comment { Object.defineProperties( this, Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ id: { get: () => data.id, }, - /** - * @name message - * @type {string} - * @memberof module:API.cvat.classes.Comment - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ message: { get: () => data.message, set: (value) => { @@ -60,34 +61,12 @@ class Comment { data.message = value; }, }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ createdDate: { get: () => data.created_date, }, - /** - * @name updatedDate - * @type {string} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ updatedDate: { get: () => data.updated_date, }, - /** - * Instance of a user who has created the comment - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ owner: { get: () => data.owner, }, @@ -98,8 +77,8 @@ class Comment { ); } - serialize() { - const data = { + public serialize(): SerializedCommentData { + const data: SerializedCommentData = { message: this.message, }; @@ -119,5 +98,3 @@ class Comment { return data; } } - -module.exports = Comment; diff --git a/cvat-core/src/common.js b/cvat-core/src/common.js deleted file mode 100644 index e0b2b2b88dbb..000000000000 --- a/cvat-core/src/common.js +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (C) 2019-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const { ArgumentError } = require('./exceptions'); - - function isBoolean(value) { - return typeof value === 'boolean'; - } - - function isInteger(value) { - return typeof value === 'number' && Number.isInteger(value); - } - - // Called with specific Enum context - function isEnum(value) { - for (const key in this) { - if (Object.prototype.hasOwnProperty.call(this, key)) { - if (this[key] === value) { - return true; - } - } - } - - return false; - } - - function isString(value) { - return typeof value === 'string'; - } - - function checkFilter(filter, fields) { - for (const prop in filter) { - if (Object.prototype.hasOwnProperty.call(filter, prop)) { - if (!(prop in fields)) { - throw new ArgumentError(`Unsupported filter property has been received: "${prop}"`); - } else if (!fields[prop](filter[prop])) { - throw new ArgumentError(`Received filter property "${prop}" does not satisfy API`); - } - } - } - } - - function checkExclusiveFields(obj, exclusive, ignore) { - const fields = { - exclusive: [], - other: [], - }; - for (const field in Object.keys(obj)) { - if (!(field in ignore)) { - if (field in exclusive) { - if (fields.other.length) { - throw new ArgumentError(`Do not use the filter field "${field}" with others`); - } - fields.exclusive.push(field); - } else { - fields.other.push(field); - } - } - } - } - - function checkObjectType(name, value, type, instance) { - if (type) { - if (typeof value !== type) { - // specific case for integers which aren't native type in JS - if (type === 'integer' && Number.isInteger(value)) { - return true; - } - - throw new ArgumentError(`"${name}" is expected to be "${type}", but "${typeof value}" has been got.`); - } - } else if (instance) { - if (!(value instanceof instance)) { - if (value !== undefined) { - throw new ArgumentError( - `"${name}" is expected to be ${instance.name}, but ` + - `"${value.constructor.name}" has been got`, - ); - } - - throw new ArgumentError(`"${name}" is expected to be ${instance.name}, but "undefined" has been got.`); - } - } - - return true; - } - - class FieldUpdateTrigger { - constructor() { - let updatedFlags = {}; - - Object.defineProperties( - this, - Object.freeze({ - reset: { - value: () => { - updatedFlags = {}; - }, - }, - update: { - value: (name) => { - updatedFlags[name] = true; - }, - }, - getUpdated: { - value: (data, propMap = {}) => { - const result = {}; - for (const updatedField of Object.keys(updatedFlags)) { - result[propMap[updatedField] || updatedField] = data[updatedField]; - } - return result; - }, - }, - }), - ); - } - } - - module.exports = { - isBoolean, - isInteger, - isEnum, - isString, - checkFilter, - checkObjectType, - checkExclusiveFields, - FieldUpdateTrigger, - }; -})(); diff --git a/cvat-core/src/common.ts b/cvat-core/src/common.ts new file mode 100644 index 000000000000..10eed7cf0002 --- /dev/null +++ b/cvat-core/src/common.ts @@ -0,0 +1,127 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { ArgumentError } from './exceptions'; + +export function isBoolean(value): boolean { + return typeof value === 'boolean'; +} + +export function isInteger(value): boolean { + return typeof value === 'number' && Number.isInteger(value); +} + +export function isEmail(value): boolean { + return typeof value === 'string' && RegExp(/^[^\s@]+@[^\s@]+\.[^\s@]+$/).test(value); +} + +// Called with specific Enum context +export function isEnum(value): boolean { + for (const key in this) { + if (Object.prototype.hasOwnProperty.call(this, key)) { + if (this[key] === value) { + return true; + } + } + } + + return false; +} + +export function isString(value): boolean { + return typeof value === 'string'; +} + +export function checkFilter(filter, fields): void { + for (const prop in filter) { + if (Object.prototype.hasOwnProperty.call(filter, prop)) { + if (!(prop in fields)) { + throw new ArgumentError(`Unsupported filter property has been received: "${prop}"`); + } else if (!fields[prop](filter[prop])) { + throw new ArgumentError(`Received filter property "${prop}" does not satisfy API`); + } + } + } +} + +export function checkExclusiveFields(obj, exclusive, ignore): void { + const fields = { + exclusive: [], + other: [], + }; + for (const field in Object.keys(obj)) { + if (!(field in ignore)) { + if (field in exclusive) { + if (fields.other.length) { + throw new ArgumentError(`Do not use the filter field "${field}" with others`); + } + fields.exclusive.push(field); + } else { + fields.other.push(field); + } + } + } +} + +export function checkObjectType(name, value, type, instance?): boolean { + if (type) { + if (typeof value !== type) { + // specific case for integers which aren't native type in JS + if (type === 'integer' && Number.isInteger(value)) { + return true; + } + + throw new ArgumentError(`"${name}" is expected to be "${type}", but "${typeof value}" has been got.`); + } + } else if (instance) { + if (!(value instanceof instance)) { + if (value !== undefined) { + throw new ArgumentError( + `"${name}" is expected to be ${instance.name}, but ` + + `"${value.constructor.name}" has been got`, + ); + } + + throw new ArgumentError(`"${name}" is expected to be ${instance.name}, but "undefined" has been got.`); + } + } + + return true; +} + +export class FieldUpdateTrigger { + constructor() { + let updatedFlags = {}; + + Object.defineProperties( + this, + Object.freeze({ + reset: { + value: () => { + updatedFlags = {}; + }, + }, + update: { + value: (name) => { + updatedFlags[name] = true; + }, + }, + getUpdated: { + value: (data, propMap = {}) => { + const result = {}; + for (const updatedField of Object.keys(updatedFlags)) { + result[propMap[updatedField] || updatedField] = data[updatedField]; + } + return result; + }, + }, + }), + ); + } +} + +export function clamp(value: number, min: number, max: number): number { + return Math.min(Math.max(value, min), max); +} diff --git a/cvat-core/src/config.js b/cvat-core/src/config.ts similarity index 61% rename from cvat-core/src/config.js rename to cvat-core/src/config.ts index 1fbcbf9d39ca..b44d136295b8 100644 --- a/cvat-core/src/config.js +++ b/cvat-core/src/config.ts @@ -1,11 +1,15 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -module.exports = { +const config = { backendAPI: '/api', proxy: false, organizationID: null, origin: '', uploadChunkSize: 100, + removeUnderlyingMaskPixels: false, }; + +export default config; diff --git a/cvat-core/src/enums.js b/cvat-core/src/enums.js deleted file mode 100644 index 88d7d5b55330..000000000000 --- a/cvat-core/src/enums.js +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright (C) 2019-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - /** - * Share files types - * @enum {string} - * @name ShareFileType - * @memberof module:API.cvat.enums - * @property {string} DIR 'DIR' - * @property {string} REG 'REG' - * @readonly - */ - const ShareFileType = Object.freeze({ - DIR: 'DIR', - REG: 'REG', - }); - - /** - * Task statuses - * @enum {string} - * @name TaskStatus - * @memberof module:API.cvat.enums - * @property {string} ANNOTATION 'annotation' - * @property {string} VALIDATION 'validation' - * @property {string} COMPLETED 'completed' - * @readonly - */ - const TaskStatus = Object.freeze({ - ANNOTATION: 'annotation', - VALIDATION: 'validation', - COMPLETED: 'completed', - }); - - /** - * Job stages - * @enum {string} - * @name JobStage - * @memberof module:API.cvat.enums - * @property {string} ANNOTATION 'annotation' - * @property {string} VALIDATION 'validation' - * @property {string} ACCEPTANCE 'acceptance' - * @readonly - */ - const JobStage = Object.freeze({ - ANNOTATION: 'annotation', - VALIDATION: 'validation', - ACCEPTANCE: 'acceptance', - }); - - /** - * Job states - * @enum {string} - * @name JobState - * @memberof module:API.cvat.enums - * @property {string} NEW 'new' - * @property {string} IN_PROGRESS 'in progress' - * @property {string} COMPLETED 'completed' - * @property {string} REJECTED 'rejected' - * @readonly - */ - const JobState = Object.freeze({ - NEW: 'new', - IN_PROGRESS: 'in progress', - COMPLETED: 'completed', - REJECTED: 'rejected', - }); - - /** - * Task dimension - * @enum - * @name DimensionType - * @memberof module:API.cvat.enums - * @property {string} DIMENSION_2D '2d' - * @property {string} DIMENSION_3D '3d' - * @readonly - */ - const DimensionType = Object.freeze({ - DIMENSION_2D: '2d', - DIMENSION_3D: '3d', - }); - - /** - * List of RQ statuses - * @enum {string} - * @name RQStatus - * @memberof module:API.cvat.enums - * @property {string} QUEUED 'queued' - * @property {string} STARTED 'started' - * @property {string} FINISHED 'finished' - * @property {string} FAILED 'failed' - * @property {string} UNKNOWN 'unknown' - * @readonly - */ - const RQStatus = Object.freeze({ - QUEUED: 'queued', - STARTED: 'started', - FINISHED: 'finished', - FAILED: 'failed', - UNKNOWN: 'unknown', - }); - - /** - * Task modes - * @enum {string} - * @name TaskMode - * @memberof module:API.cvat.enums - * @property {string} ANNOTATION 'annotation' - * @property {string} INTERPOLATION 'interpolation' - * @readonly - */ - const TaskMode = Object.freeze({ - ANNOTATION: 'annotation', - INTERPOLATION: 'interpolation', - }); - - /** - * Attribute types - * @enum {string} - * @name AttributeType - * @memberof module:API.cvat.enums - * @property {string} CHECKBOX 'checkbox' - * @property {string} SELECT 'select' - * @property {string} RADIO 'radio' - * @property {string} NUMBER 'number' - * @property {string} TEXT 'text' - * @readonly - */ - const AttributeType = Object.freeze({ - CHECKBOX: 'checkbox', - RADIO: 'radio', - SELECT: 'select', - NUMBER: 'number', - TEXT: 'text', - }); - - /** - * Object types - * @enum {string} - * @name ObjectType - * @memberof module:API.cvat.enums - * @property {string} TAG 'tag' - * @property {string} SHAPE 'shape' - * @property {string} TRACK 'track' - * @readonly - */ - const ObjectType = Object.freeze({ - TAG: 'tag', - SHAPE: 'shape', - TRACK: 'track', - }); - - /** - * Object shapes - * @enum {string} - * @name ObjectShape - * @memberof module:API.cvat.enums - * @property {string} RECTANGLE 'rectangle' - * @property {string} POLYGON 'polygon' - * @property {string} POLYLINE 'polyline' - * @property {string} POINTS 'points' - * @property {string} CUBOID 'cuboid' - * @readonly - */ - const ObjectShape = Object.freeze({ - RECTANGLE: 'rectangle', - POLYGON: 'polygon', - POLYLINE: 'polyline', - POINTS: 'points', - ELLIPSE: 'ellipse', - CUBOID: 'cuboid', - }); - - /** - * Annotation type - * @enum {string} - * @name Source - * @memberof module:API.cvat.enums - * @property {string} MANUAL 'manual' - * @property {string} AUTO 'auto' - * @readonly - */ - const Source = Object.freeze({ - MANUAL: 'manual', - AUTO: 'auto', - }); - - /** - * Logger event types - * @enum {string} - * @name LogType - * @memberof module:API.cvat.enums - * @property {string} loadJob Load job - * @property {string} saveJob Save job - * @property {string} restoreJob Restore job - * @property {string} uploadAnnotations Upload annotations - * @property {string} sendUserActivity Send user activity - * @property {string} sendException Send exception - * @property {string} sendTaskInfo Send task info - - * @property {string} drawObject Draw object - * @property {string} pasteObject Paste object - * @property {string} copyObject Copy object - * @property {string} propagateObject Propagate object - * @property {string} dragObject Drag object - * @property {string} resizeObject Resize object - * @property {string} deleteObject Delete object - * @property {string} lockObject Lock object - * @property {string} mergeObjects Merge objects - * @property {string} changeAttribute Change attribute - * @property {string} changeLabel Change label - - * @property {string} changeFrame Change frame - * @property {string} moveImage Move image - * @property {string} zoomImage Zoom image - * @property {string} fitImage Fit image - * @property {string} rotateImage Rotate image - - * @property {string} undoAction Undo action - * @property {string} redoAction Redo action - - * @property {string} pressShortcut Press shortcut - * @property {string} debugInfo Debug info - * @readonly - */ - const LogType = Object.freeze({ - loadJob: 'Load job', - saveJob: 'Save job', - restoreJob: 'Restore job', - uploadAnnotations: 'Upload annotations', - sendUserActivity: 'Send user activity', - sendException: 'Send exception', - sendTaskInfo: 'Send task info', - - drawObject: 'Draw object', - pasteObject: 'Paste object', - copyObject: 'Copy object', - propagateObject: 'Propagate object', - dragObject: 'Drag object', - resizeObject: 'Resize object', - deleteObject: 'Delete object', - lockObject: 'Lock object', - mergeObjects: 'Merge objects', - changeAttribute: 'Change attribute', - changeLabel: 'Change label', - - changeFrame: 'Change frame', - moveImage: 'Move image', - zoomImage: 'Zoom image', - fitImage: 'Fit image', - rotateImage: 'Rotate image', - - undoAction: 'Undo action', - redoAction: 'Redo action', - - pressShortcut: 'Press shortcut', - debugInfo: 'Debug info', - }); - - /** - * Types of actions with annotations - * @enum {string} - * @name HistoryActions - * @memberof module:API.cvat.enums - * @property {string} CHANGED_LABEL Changed label - * @property {string} CHANGED_ATTRIBUTES Changed attributes - * @property {string} CHANGED_POINTS Changed points - * @property {string} CHANGED_OUTSIDE Changed outside - * @property {string} CHANGED_OCCLUDED Changed occluded - * @property {string} CHANGED_ZORDER Changed z-order - * @property {string} CHANGED_LOCK Changed lock - * @property {string} CHANGED_COLOR Changed color - * @property {string} CHANGED_HIDDEN Changed hidden - * @property {string} CHANGED_SOURCE Changed source - * @property {string} MERGED_OBJECTS Merged objects - * @property {string} SPLITTED_TRACK Splitted track - * @property {string} GROUPED_OBJECTS Grouped objects - * @property {string} CREATED_OBJECTS Created objects - * @property {string} REMOVED_OBJECT Removed object - * @readonly - */ - const HistoryActions = Object.freeze({ - CHANGED_LABEL: 'Changed label', - CHANGED_ATTRIBUTES: 'Changed attributes', - CHANGED_POINTS: 'Changed points', - CHANGED_OUTSIDE: 'Changed outside', - CHANGED_OCCLUDED: 'Changed occluded', - CHANGED_ZORDER: 'Changed z-order', - CHANGED_KEYFRAME: 'Changed keyframe', - CHANGED_LOCK: 'Changed lock', - CHANGED_PINNED: 'Changed pinned', - CHANGED_COLOR: 'Changed color', - CHANGED_HIDDEN: 'Changed hidden', - CHANGED_SOURCE: 'Changed source', - MERGED_OBJECTS: 'Merged objects', - SPLITTED_TRACK: 'Splitted track', - GROUPED_OBJECTS: 'Grouped objects', - CREATED_OBJECTS: 'Created objects', - REMOVED_OBJECT: 'Removed object', - }); - - /** - * Enum string values. - * @name ModelType - * @memberof module:API.cvat.enums - * @enum {string} - */ - const ModelType = { - DETECTOR: 'detector', - INTERACTOR: 'interactor', - TRACKER: 'tracker', - }; - - /** - * Array of hex colors - * @name colors - * @memberof module:API.cvat.enums - * @type {string[]} - * @readonly - */ - const colors = [ - '#33ddff', - '#fa3253', - '#34d1b7', - '#ff007c', - '#ff6037', - '#ddff33', - '#24b353', - '#b83df5', - '#66ff66', - '#32b7fa', - '#ffcc33', - '#83e070', - '#fafa37', - '#5986b3', - '#8c78f0', - '#ff6a4d', - '#f078f0', - '#2a7dd1', - '#b25050', - '#cc3366', - '#cc9933', - '#aaf0d1', - '#ff00cc', - '#3df53d', - '#fa32b7', - '#fa7dbb', - '#ff355e', - '#f59331', - '#3d3df5', - '#733380', - ]; - - /** - * Types of cloud storage providers - * @enum {string} - * @name CloudStorageProviderType - * @memberof module:API.cvat.enums - * @property {string} AWS_S3 'AWS_S3_BUCKET' - * @property {string} AZURE 'AZURE_CONTAINER' - * @property {string} GOOGLE_CLOUD_STORAGE 'GOOGLE_CLOUD_STORAGE' - * @readonly - */ - const CloudStorageProviderType = Object.freeze({ - AWS_S3_BUCKET: 'AWS_S3_BUCKET', - AZURE_CONTAINER: 'AZURE_CONTAINER', - GOOGLE_CLOUD_STORAGE: 'GOOGLE_CLOUD_STORAGE', - }); - - /** - * Types of cloud storage credentials - * @enum {string} - * @name CloudStorageCredentialsType - * @memberof module:API.cvat.enums - * @property {string} KEY_SECRET_KEY_PAIR 'KEY_SECRET_KEY_PAIR' - * @property {string} ACCOUNT_NAME_TOKEN_PAIR 'ACCOUNT_NAME_TOKEN_PAIR' - * @property {string} ANONYMOUS_ACCESS 'ANONYMOUS_ACCESS' - * @property {string} KEY_FILE_PATH 'KEY_FILE_PATH' - * @readonly - */ - const CloudStorageCredentialsType = Object.freeze({ - KEY_SECRET_KEY_PAIR: 'KEY_SECRET_KEY_PAIR', - ACCOUNT_NAME_TOKEN_PAIR: 'ACCOUNT_NAME_TOKEN_PAIR', - ANONYMOUS_ACCESS: 'ANONYMOUS_ACCESS', - KEY_FILE_PATH: 'KEY_FILE_PATH', - }); - - /** - * Task statuses - * @enum {string} - * @name MembershipRole - * @memberof module:API.cvat.enums - * @property {string} WORKER 'worker' - * @property {string} SUPERVISOR 'supervisor' - * @property {string} MAINTAINER 'maintainer' - * @property {string} OWNER 'owner' - * @readonly - */ - const MembershipRole = Object.freeze({ - WORKER: 'worker', - SUPERVISOR: 'supervisor', - MAINTAINER: 'maintainer', - OWNER: 'owner', - }); - - /** - * Sorting methods - * @enum {string} - * @name SortingMethod - * @memberof module:API.cvat.enums - * @property {string} LEXICOGRAPHICAL 'lexicographical' - * @property {string} NATURAL 'natural' - * @property {string} PREDEFINED 'predefined' - * @property {string} RANDOM 'random' - * @readonly - */ - const SortingMethod = Object.freeze({ - LEXICOGRAPHICAL: 'lexicographical', - NATURAL: 'natural', - PREDEFINED: 'predefined', - RANDOM: 'random', - }); - - module.exports = { - ShareFileType, - TaskStatus, - JobStage, - JobState, - TaskMode, - AttributeType, - ObjectType, - ObjectShape, - LogType, - ModelType, - HistoryActions, - RQStatus, - colors, - Source, - DimensionType, - CloudStorageProviderType, - CloudStorageCredentialsType, - MembershipRole, - SortingMethod, - }; -})(); diff --git a/cvat-core/src/enums.ts b/cvat-core/src/enums.ts new file mode 100644 index 000000000000..9d20f28a5752 --- /dev/null +++ b/cvat-core/src/enums.ts @@ -0,0 +1,496 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier = MIT + +/** + * Share files types + * @enum {string} + * @name ShareFileType + * @memberof module:API.cvat.enums + * @property {string} DIR 'DIR' + * @property {string} REG 'REG' + * @readonly +*/ +export enum ShareFileType { + DIR = 'DIR', + REG = 'REG', +} + +/** + * Task statuses + * @enum {string} + * @name TaskStatus + * @memberof module:API.cvat.enums + * @property {string} ANNOTATION 'annotation' + * @property {string} VALIDATION 'validation' + * @property {string} COMPLETED 'completed' + * @readonly +*/ +export enum TaskStatus { + ANNOTATION = 'annotation', + VALIDATION = 'validation', + COMPLETED = 'completed', +} + +/** + * Job stages + * @enum {string} + * @name JobStage + * @memberof module:API.cvat.enums + * @property {string} ANNOTATION 'annotation' + * @property {string} VALIDATION 'validation' + * @property {string} ACCEPTANCE 'acceptance' + * @readonly +*/ +export enum JobStage { + ANNOTATION = 'annotation', + VALIDATION = 'validation', + ACCEPTANCE = 'acceptance', +} + +/** + * Job states + * @enum {string} + * @name JobState + * @memberof module:API.cvat.enums + * @property {string} NEW 'new' + * @property {string} IN_PROGRESS 'in progress' + * @property {string} COMPLETED 'completed' + * @property {string} REJECTED 'rejected' + * @readonly +*/ +export enum JobState { + NEW = 'new', + IN_PROGRESS = 'in progress', + COMPLETED = 'completed', + REJECTED = 'rejected', +} + +/** + * Task dimension + * @enum + * @name DimensionType + * @memberof module:API.cvat.enums + * @property {string} DIMENSION_2D '2d' + * @property {string} DIMENSION_3D '3d' + * @readonly +*/ +export enum DimensionType { + DIMENSION_2D = '2d', + DIMENSION_3D = '3d', +} + +/** + * List of RQ statuses + * @enum {string} + * @name RQStatus + * @memberof module:API.cvat.enums + * @property {string} QUEUED 'queued' + * @property {string} STARTED 'started' + * @property {string} FINISHED 'finished' + * @property {string} FAILED 'failed' + * @property {string} UNKNOWN 'unknown' + * @readonly +*/ +export enum RQStatus { + QUEUED = 'queued', + STARTED = 'started', + FINISHED = 'finished', + FAILED = 'failed', + UNKNOWN = 'unknown', +} + +/** + * Task modes + * @enum {string} + * @name TaskMode + * @memberof module:API.cvat.enums + * @property {string} ANNOTATION 'annotation' + * @property {string} INTERPOLATION 'interpolation' + * @readonly +*/ +export enum TaskMode { + ANNOTATION = 'annotation', + INTERPOLATION = 'interpolation', +} + +/** + * Attribute types + * @enum {string} + * @name AttributeType + * @memberof module:API.cvat.enums + * @property {string} CHECKBOX 'checkbox' + * @property {string} SELECT 'select' + * @property {string} RADIO 'radio' + * @property {string} NUMBER 'number' + * @property {string} TEXT 'text' + * @readonly +*/ +export enum AttributeType { + CHECKBOX = 'checkbox', + RADIO = 'radio', + SELECT = 'select', + NUMBER = 'number', + TEXT = 'text', +} + +/** + * Object types + * @enum {string} + * @name ObjectType + * @memberof module:API.cvat.enums + * @property {string} TAG 'tag' + * @property {string} SHAPE 'shape' + * @property {string} TRACK 'track' + * @readonly +*/ +export enum ObjectType { + TAG = 'tag', + SHAPE = 'shape', + TRACK = 'track', +} + +/** + * Object shapes + * @enum {string} + * @name ShapeType + * @memberof module:API.cvat.enums + * @property {string} RECTANGLE 'rectangle' + * @property {string} POLYGON 'polygon' + * @property {string} POLYLINE 'polyline' + * @property {string} POINTS 'points' + * @property {string} CUBOID 'cuboid' + * @property {string} SKELETON 'skeleton' + * @readonly +*/ +export enum ShapeType { + RECTANGLE = 'rectangle', + POLYGON = 'polygon', + POLYLINE = 'polyline', + POINTS = 'points', + ELLIPSE = 'ellipse', + CUBOID = 'cuboid', + SKELETON = 'skeleton', + MASK = 'mask', +} + +/** + * Annotation type + * @enum {string} + * @name Source + * @memberof module:API.cvat.enums + * @property {string} MANUAL 'manual' + * @property {string} AUTO 'auto' + * @readonly +*/ +export enum Source { + MANUAL = 'manual', + AUTO = 'auto', +} + +/** + * Logger event types + * @enum {string} + * @name LogType + * @memberof module:API.cvat.enums + * @property {string} loadJob Load job + * @property {string} saveJob Save job + * @property {string} restoreJob Restore job + * @property {string} uploadAnnotations Upload annotations + * @property {string} sendUserActivity Send user activity + * @property {string} sendException Send exception + * @property {string} sendTaskInfo Send task info + * @property {string} drawObject Draw object + * @property {string} pasteObject Paste object + * @property {string} copyObject Copy object + * @property {string} propagateObject Propagate object + * @property {string} dragObject Drag object + * @property {string} resizeObject Resize object + * @property {string} deleteObject Delete object + * @property {string} lockObject Lock object + * @property {string} mergeObjects Merge objects + * @property {string} changeAttribute Change attribute + * @property {string} changeLabel Change label + * @property {string} changeFrame Change frame + * @property {string} moveImage Move image + * @property {string} zoomImage Zoom image + * @property {string} fitImage Fit image + * @property {string} rotateImage Rotate image + * @property {string} undoAction Undo action + * @property {string} redoAction Redo action + * @property {string} pressShortcut Press shortcut + * @property {string} debugInfo Debug info + * @readonly +*/ +export enum LogType { + loadJob = 'Load job', + saveJob = 'Save job', + restoreJob = 'Restore job', + uploadAnnotations = 'Upload annotations', + sendUserActivity = 'Send user activity', + sendException = 'Send exception', + sendTaskInfo = 'Send task info', + + drawObject = 'Draw object', + pasteObject = 'Paste object', + copyObject = 'Copy object', + propagateObject = 'Propagate object', + dragObject = 'Drag object', + resizeObject = 'Resize object', + deleteObject = 'Delete object', + lockObject = 'Lock object', + mergeObjects = 'Merge objects', + changeAttribute = 'Change attribute', + changeLabel = 'Change label', + + changeFrame = 'Change frame', + moveImage = 'Move image', + zoomImage = 'Zoom image', + fitImage = 'Fit image', + rotateImage = 'Rotate image', + + undoAction = 'Undo action', + redoAction = 'Redo action', + + pressShortcut = 'Press shortcut', + debugInfo = 'Debug info', +} + +/** + * Types of actions with annotations + * @enum {string} + * @name HistoryActions + * @memberof module:API.cvat.enums + * @property {string} CHANGED_LABEL Changed label + * @property {string} CHANGED_ATTRIBUTES Changed attributes + * @property {string} CHANGED_POINTS Changed points + * @property {string} CHANGED_OUTSIDE Changed outside + * @property {string} CHANGED_OCCLUDED Changed occluded + * @property {string} CHANGED_ZORDER Changed z-order + * @property {string} CHANGED_LOCK Changed lock + * @property {string} CHANGED_COLOR Changed color + * @property {string} CHANGED_HIDDEN Changed hidden + * @property {string} CHANGED_SOURCE Changed source + * @property {string} MERGED_OBJECTS Merged objects + * @property {string} SPLITTED_TRACK Splitted track + * @property {string} GROUPED_OBJECTS Grouped objects + * @property {string} CREATED_OBJECTS Created objects + * @property {string} REMOVED_OBJECT Removed object + * @property {string} REMOVED_FRAME Removed frame + * @property {string} RESTORED_FRAME Restored frame + * @readonly +*/ +export enum HistoryActions { + CHANGED_LABEL = 'Changed label', + CHANGED_ATTRIBUTES = 'Changed attributes', + CHANGED_POINTS = 'Changed points', + CHANGED_ROTATION = 'Object rotated', + CHANGED_OUTSIDE = 'Changed outside', + CHANGED_OCCLUDED = 'Changed occluded', + CHANGED_ZORDER = 'Changed z-order', + CHANGED_KEYFRAME = 'Changed keyframe', + CHANGED_LOCK = 'Changed lock', + CHANGED_PINNED = 'Changed pinned', + CHANGED_COLOR = 'Changed color', + CHANGED_HIDDEN = 'Changed hidden', + CHANGED_SOURCE = 'Changed source', + MERGED_OBJECTS = 'Merged objects', + SPLITTED_TRACK = 'Splitted track', + GROUPED_OBJECTS = 'Grouped objects', + CREATED_OBJECTS = 'Created objects', + REMOVED_OBJECT = 'Removed object', + REMOVED_FRAME = 'Removed frame', + RESTORED_FRAME = 'Restored frame', +} + +/** + * Enum string values. + * @name ModelType + * @memberof module:API.cvat.enums + * @enum {string} +*/ +export enum ModelType { + DETECTOR = 'detector', + INTERACTOR = 'interactor', + TRACKER = 'tracker', +} + +/** + * Array of hex colors + * @name colors + * @memberof module:API.cvat.enums + * @type {string[]} + * @readonly +*/ +export const colors = [ + '#33ddff', + '#fa3253', + '#34d1b7', + '#ff007c', + '#ff6037', + '#ddff33', + '#24b353', + '#b83df5', + '#66ff66', + '#32b7fa', + '#ffcc33', + '#83e070', + '#fafa37', + '#5986b3', + '#8c78f0', + '#ff6a4d', + '#f078f0', + '#2a7dd1', + '#b25050', + '#cc3366', + '#cc9933', + '#aaf0d1', + '#ff00cc', + '#3df53d', + '#fa32b7', + '#fa7dbb', + '#ff355e', + '#f59331', + '#3d3df5', + '#733380', +]; + +/** + * Types of cloud storage providers + * @enum {string} + * @name CloudStorageProviderType + * @memberof module:API.cvat.enums + * @property {string} AWS_S3 'AWS_S3_BUCKET' + * @property {string} AZURE 'AZURE_CONTAINER' + * @property {string} GOOGLE_CLOUD_STORAGE 'GOOGLE_CLOUD_STORAGE' + * @readonly +*/ +export enum CloudStorageProviderType { + AWS_S3_BUCKET = 'AWS_S3_BUCKET', + AZURE_CONTAINER = 'AZURE_CONTAINER', + GOOGLE_CLOUD_STORAGE = 'GOOGLE_CLOUD_STORAGE', +} + +/** + * Types of cloud storage credentials + * @enum {string} + * @name CloudStorageCredentialsType + * @memberof module:API.cvat.enums + * @property {string} KEY_SECRET_KEY_PAIR 'KEY_SECRET_KEY_PAIR' + * @property {string} ACCOUNT_NAME_TOKEN_PAIR 'ACCOUNT_NAME_TOKEN_PAIR' + * @property {string} ANONYMOUS_ACCESS 'ANONYMOUS_ACCESS' + * @property {string} KEY_FILE_PATH 'KEY_FILE_PATH' + * @readonly + */ +export enum CloudStorageCredentialsType { + KEY_SECRET_KEY_PAIR = 'KEY_SECRET_KEY_PAIR', + ACCOUNT_NAME_TOKEN_PAIR = 'ACCOUNT_NAME_TOKEN_PAIR', + ANONYMOUS_ACCESS = 'ANONYMOUS_ACCESS', + KEY_FILE_PATH = 'KEY_FILE_PATH', +} + +/** + * Types of cloud storage statuses + * @enum {string} + * @name CloudStorageStatus + * @memberof module:API.cvat.enums + * @property {string} AVAILABLE 'AVAILABLE' + * @property {string} NOT_FOUND 'NOT_FOUND' + * @property {string} FORBIDDEN 'FORBIDDEN' + * @readonly + */ +export enum CloudStorageStatus { + AVAILABLE = 'AVAILABLE', + NOT_FOUND = 'NOT_FOUND', + FORBIDDEN = 'FORBIDDEN', +} + +/** + * Membership roles + * @enum {string} + * @name MembershipRole + * @memberof module:API.cvat.enums + * @property {string} WORKER 'worker' + * @property {string} SUPERVISOR 'supervisor' + * @property {string} MAINTAINER 'maintainer' + * @property {string} OWNER 'owner' + * @readonly +*/ +export enum MembershipRole { + WORKER = 'worker', + SUPERVISOR = 'supervisor', + MAINTAINER = 'maintainer', + OWNER = 'owner', +} + +/** + * Sorting methods + * @enum {string} + * @name SortingMethod + * @memberof module:API.cvat.enums + * @property {string} LEXICOGRAPHICAL 'lexicographical' + * @property {string} NATURAL 'natural' + * @property {string} PREDEFINED 'predefined' + * @property {string} RANDOM 'random' + * @readonly +*/ +export enum SortingMethod { + LEXICOGRAPHICAL = 'lexicographical', + NATURAL = 'natural', + PREDEFINED = 'predefined', + RANDOM = 'random', +} + +/** + * Types of storage locations + * @enum {string} + * @name StorageLocation + * @memberof module:API.cvat.enums + * @property {string} LOCAL 'local' + * @property {string} CLOUD_STORAGE 'cloud_storage' + * @readonly +*/ +export enum StorageLocation { + LOCAL = 'local', + CLOUD_STORAGE = 'cloud_storage', +} + +/** + * Webhook source types + * @enum {string} + * @name WebhookSourceType + * @memberof module:API.cvat.enums + * @property {string} ORGANIZATION 'organization' + * @property {string} PROJECT 'project' + * @readonly +*/ +export enum WebhookSourceType { + ORGANIZATION = 'organization', + PROJECT = 'project', +} + +/** + * Webhook content types + * @enum {string} + * @name WebhookContentType + * @memberof module:API.cvat.enums + * @property {string} JSON 'json' + * @readonly +*/ +export enum WebhookContentType { + JSON = 'application/json', +} + +export enum LabelType { + ANY = 'any', + RECTANGLE = 'rectangle', + POLYGON = 'polygon', + POLYLINE = 'polyline', + POINTS = 'points', + ELLIPSE = 'ellipse', + CUBOID = 'cuboid', + SKELETON = 'skeleton', + MASK = 'mask', + TAG = 'tag', +} diff --git a/cvat-core/src/exceptions.js b/cvat-core/src/exceptions.js deleted file mode 100644 index 8511b32d0574..000000000000 --- a/cvat-core/src/exceptions.js +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (C) 2019-2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const Platform = require('platform'); - const ErrorStackParser = require('error-stack-parser'); - const config = require('./config'); - - /** - * Base exception class - * @memberof module:API.cvat.exceptions - * @extends Error - * @ignore - */ - class Exception extends Error { - /** - * @param {string} message - Exception message - */ - constructor(message) { - super(message); - - const time = new Date().toISOString(); - const system = Platform.os.toString(); - const client = `${Platform.name} ${Platform.version}`; - const info = ErrorStackParser.parse(this)[0]; - const filename = `${info.fileName}`; - const line = info.lineNumber; - const column = info.columnNumber; - const { jobID, taskID, clientID } = config; - - const projID = undefined; // wasn't implemented - - Object.defineProperties( - this, - Object.freeze({ - system: { - /** - * @name system - * @type {string} - * @memberof module:API.cvat.exceptions.Exception - * @readonly - * @instance - */ - get: () => system, - }, - client: { - /** - * @name client - * @type {string} - * @memberof module:API.cvat.exceptions.Exception - * @readonly - * @instance - */ - get: () => client, - }, - time: { - /** - * @name time - * @type {string} - * @memberof module:API.cvat.exceptions.Exception - * @readonly - * @instance - */ - get: () => time, - }, - jobID: { - /** - * @name jobID - * @type {integer} - * @memberof module:API.cvat.exceptions.Exception - * @readonly - * @instance - */ - get: () => jobID, - }, - taskID: { - /** - * @name taskID - * @type {integer} - * @memberof module:API.cvat.exceptions.Exception - * @readonly - * @instance - */ - get: () => taskID, - }, - projID: { - /** - * @name projID - * @type {integer} - * @memberof module:API.cvat.exceptions.Exception - * @readonly - * @instance - */ - get: () => projID, - }, - clientID: { - /** - * @name clientID - * @type {integer} - * @memberof module:API.cvat.exceptions.Exception - * @readonly - * @instance - */ - get: () => clientID, - }, - filename: { - /** - * @name filename - * @type {string} - * @memberof module:API.cvat.exceptions.Exception - * @readonly - * @instance - */ - get: () => filename, - }, - line: { - /** - * @name line - * @type {integer} - * @memberof module:API.cvat.exceptions.Exception - * @readonly - * @instance - */ - get: () => line, - }, - column: { - /** - * @name column - * @type {integer} - * @memberof module:API.cvat.exceptions.Exception - * @readonly - * @instance - */ - get: () => column, - }, - }), - ); - } - - /** - * Save an exception on a server - * @name save - * @method - * @memberof Exception - * @instance - * @async - */ - async save() { - const exceptionObject = { - system: this.system, - client: this.client, - time: this.time, - job_id: this.jobID, - task_id: this.taskID, - proj_id: this.projID, - client_id: this.clientID, - message: this.message, - filename: this.filename, - line: this.line, - column: this.column, - stack: this.stack, - }; - - try { - const serverProxy = require('./server-proxy'); - await serverProxy.server.exception(exceptionObject); - } catch (exception) { - // add event - } - } - } - - /** - * Exceptions are referred with arguments data - * @memberof module:API.cvat.exceptions - * @extends module:API.cvat.exceptions.Exception - */ - class ArgumentError extends Exception { - /** - * @param {string} message - Exception message - */ - constructor(message) { - super(message); - } - } - - /** - * Unexpected problems with data which are not connected with a user input - * @memberof module:API.cvat.exceptions - * @extends module:API.cvat.exceptions.Exception - */ - class DataError extends Exception { - /** - * @param {string} message - Exception message - */ - constructor(message) { - super(message); - } - } - - /** - * Unexpected situations in code - * @memberof module:API.cvat.exceptions - * @extends module:API.cvat.exceptions.Exception - */ - class ScriptingError extends Exception { - /** - * @param {string} message - Exception message - */ - constructor(message) { - super(message); - } - } - - /** - * Plugin-referred exceptions - * @memberof module:API.cvat.exceptions - * @extends module:API.cvat.exceptions.Exception - */ - class PluginError extends Exception { - /** - * @param {string} message - Exception message - */ - constructor(message) { - super(message); - } - } - - /** - * Exceptions in interaction with a server - * @memberof module:API.cvat.exceptions - * @extends module:API.cvat.exceptions.Exception - */ - class ServerError extends Exception { - /** - * @param {string} message - Exception message - * @param {(string|integer)} code - Response code - */ - constructor(message, code) { - super(message); - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name code - * @type {(string|integer)} - * @memberof module:API.cvat.exceptions.ServerError - * @readonly - * @instance - */ - code: { - get: () => code, - }, - }), - ); - } - } - - module.exports = { - Exception, - ArgumentError, - DataError, - ScriptingError, - PluginError, - ServerError, - }; -})(); diff --git a/cvat-core/src/exceptions.ts b/cvat-core/src/exceptions.ts new file mode 100644 index 000000000000..ba40b0a8e6f2 --- /dev/null +++ b/cvat-core/src/exceptions.ts @@ -0,0 +1,255 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import Platform from 'platform'; +import ErrorStackParser from 'error-stack-parser'; + +/** + * Base exception class + * @memberof module:API.cvat.exceptions + * @extends Error + * @ignore + */ +export class Exception extends Error { + private readonly time: string; + private readonly system: string; + private readonly client: string; + private readonly info: string; + private readonly filename: string; + private readonly line: number; + private readonly column: number; + + /** + * @param {string} message - Exception message + */ + constructor(message) { + super(message); + const time = new Date().toISOString(); + const system = Platform.os.toString(); + const client = `${Platform.name} ${Platform.version}`; + const info = ErrorStackParser.parse(this)[0]; + const filename = `${info.fileName}`; + const line = info.lineNumber; + const column = info.columnNumber; + + // TODO: NOT IMPLEMENTED? + // const { + // jobID, taskID, clientID, projID, + // } = config; + + Object.defineProperties( + this, + Object.freeze({ + system: { + /** + * @name system + * @type {string} + * @memberof module:API.cvat.exceptions.Exception + * @readonly + * @instance + */ + get: () => system, + }, + client: { + /** + * @name client + * @type {string} + * @memberof module:API.cvat.exceptions.Exception + * @readonly + * @instance + */ + get: () => client, + }, + time: { + /** + * @name time + * @type {string} + * @memberof module:API.cvat.exceptions.Exception + * @readonly + * @instance + */ + get: () => time, + }, + // jobID: { + // /** + // * @name jobID + // * @type {number} + // * @memberof module:API.cvat.exceptions.Exception + // * @readonly + // * @instance + // */ + // get: () => jobID, + // }, + // taskID: { + // /** + // * @name taskID + // * @type {number} + // * @memberof module:API.cvat.exceptions.Exception + // * @readonly + // * @instance + // */ + // get: () => taskID, + // }, + // projID: { + // /** + // * @name projID + // * @type {number} + // * @memberof module:API.cvat.exceptions.Exception + // * @readonly + // * @instance + // */ + // get: () => projID, + // }, + // clientID: { + // /** + // * @name clientID + // * @type {number} + // * @memberof module:API.cvat.exceptions.Exception + // * @readonly + // * @instance + // */ + // get: () => clientID, + // }, + filename: { + /** + * @name filename + * @type {string} + * @memberof module:API.cvat.exceptions.Exception + * @readonly + * @instance + */ + get: () => filename, + }, + line: { + /** + * @name line + * @type {number} + * @memberof module:API.cvat.exceptions.Exception + * @readonly + * @instance + */ + get: () => line, + }, + column: { + /** + * @name column + * @type {number} + * @memberof module:API.cvat.exceptions.Exception + * @readonly + * @instance + */ + get: () => column, + }, + }), + ); + } + + /** + * Save an exception on a server + * @name save + * @method + * @memberof Exception + * @instance + * @async + */ + async save(): Promise { + const exceptionObject = { + system: this.system, + client: this.client, + time: this.time, + // job_id: this.jobID, + // task_id: this.taskID, + // proj_id: this.projID, + // client_id: this.clientID, + message: this.message, + filename: this.filename, + line: this.line, + column: this.column, + stack: this.stack, + }; + + try { + const serverProxy = require('./server-proxy').default; + await serverProxy.server.exception(exceptionObject); + } catch (exception) { + // add event + } + } +} + +/** + * Exceptions are referred with arguments data + * @memberof module:API.cvat.exceptions + * @extends module:API.cvat.exceptions.Exception + */ +export class ArgumentError extends Exception { + /** + * @param {string} message - Exception message + */ +} + +/** + * Unexpected problems with data which are not connected with a user input + * @memberof module:API.cvat.exceptions + * @extends module:API.cvat.exceptions.Exception + */ +export class DataError extends Exception { + /** + * @param {string} message - Exception message + */ +} + +/** + * Unexpected situations in code + * @memberof module:API.cvat.exceptions + * @extends module:API.cvat.exceptions.Exception + */ +export class ScriptingError extends Exception { + /** + * @param {string} message - Exception message + */ +} + +/** + * Plugin-referred exceptions + * @memberof module:API.cvat.exceptions + * @extends module:API.cvat.exceptions.Exception + */ +export class PluginError extends Exception { + /** + * @param {string} message - Exception message + */ +} + +/** + * Exceptions in interaction with a server + * @memberof module:API.cvat.exceptions + * @extends module:API.cvat.exceptions.Exception + */ +export class ServerError extends Exception { + /** + * @param {string} message - Exception message + * @param {(string|number)} code - Response code + */ + constructor(message, code) { + super(message); + + Object.defineProperties( + this, + Object.freeze({ + /** + * @name code + * @type {(string|number)} + * @memberof module:API.cvat.exceptions.ServerError + * @readonly + * @instance + */ + code: { + get: () => code, + }, + }), + ); + } +} diff --git a/cvat-core/src/frames.js b/cvat-core/src/frames.js deleted file mode 100644 index 532795221239..000000000000 --- a/cvat-core/src/frames.js +++ /dev/null @@ -1,759 +0,0 @@ -// Copyright (C) 2021-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const cvatData = require('cvat-data'); - const PluginRegistry = require('./plugins'); - const serverProxy = require('./server-proxy'); - const { isBrowser, isNode } = require('browser-or-node'); - const { Exception, ArgumentError, DataError } = require('./exceptions'); - - // This is the frames storage - const frameDataCache = {}; - - /** - * Class provides meta information about specific frame and frame itself - * @memberof module:API.cvat.classes - * @hideconstructor - */ - class FrameData { - constructor({ - width, - height, - name, - taskID, - jobID, - frameNumber, - startFrame, - stopFrame, - decodeForward, - has_related_context: hasRelatedContext, - }) { - Object.defineProperties( - this, - Object.freeze({ - /** - * @name filename - * @type {string} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - filename: { - value: name, - writable: false, - }, - /** - * @name width - * @type {integer} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - width: { - value: width, - writable: false, - }, - /** - * @name height - * @type {integer} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - height: { - value: height, - writable: false, - }, - /** - * task ID - * @name tid - * @type {integer} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - tid: { - value: taskID, - writable: false, - }, - /** - * @name jid - * @type {integer} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - jid: { - value: jobID, - writable: false, - }, - /** - * @name number - * @type {integer} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - number: { - value: frameNumber, - writable: false, - }, - /** - * True if some context images are associated with this frame - * @name hasRelatedContext - * @type {boolean} - * @memberof module:API.cvat.classes.FrameData - * @readonly - * @instance - */ - hasRelatedContext: { - value: hasRelatedContext, - writable: false, - }, - startFrame: { - value: startFrame, - writable: false, - }, - stopFrame: { - value: stopFrame, - writable: false, - }, - decodeForward: { - value: decodeForward, - writable: false, - }, - }), - ); - } - - /** - * Method returns URL encoded image which can be placed in the img tag - * @method data - * @returns {string} - * @memberof module:API.cvat.classes.FrameData - * @instance - * @async - * @param {function} [onServerRequest = () => {}] - * callback which will be called if data absences local - * @throws {module:API.cvat.exception.ServerError} - * @throws {module:API.cvat.exception.PluginError} - */ - async data(onServerRequest = () => {}) { - const result = await PluginRegistry.apiWrapper.call(this, FrameData.prototype.data, onServerRequest); - return result; - } - - get imageData() { - return this._data.imageData; - } - - set imageData(imageData) { - this._data.imageData = imageData; - } - } - - FrameData.prototype.data.implementation = async function (onServerRequest) { - return new Promise((resolve, reject) => { - const resolveWrapper = (data) => { - this._data = { - imageData: data, - renderWidth: this.width, - renderHeight: this.height, - }; - return resolve(this._data); - }; - - if (this._data) { - resolve(this._data); - return; - } - - const { provider } = frameDataCache[this.tid]; - const { chunkSize } = frameDataCache[this.tid]; - const start = parseInt(this.number / chunkSize, 10) * chunkSize; - const stop = Math.min(this.stopFrame, (parseInt(this.number / chunkSize, 10) + 1) * chunkSize - 1); - const chunkNumber = Math.floor(this.number / chunkSize); - - const onDecodeAll = async (frameNumber) => { - if ( - frameDataCache[this.tid].activeChunkRequest && - chunkNumber === frameDataCache[this.tid].activeChunkRequest.chunkNumber - ) { - const callbackArray = frameDataCache[this.tid].activeChunkRequest.callbacks; - for (let i = callbackArray.length - 1; i >= 0; --i) { - if (callbackArray[i].frameNumber === frameNumber) { - const callback = callbackArray[i]; - callbackArray.splice(i, 1); - callback.resolve(await provider.frame(callback.frameNumber)); - } - } - if (callbackArray.length === 0) { - frameDataCache[this.tid].activeChunkRequest = null; - } - } - }; - - const rejectRequestAll = () => { - if ( - frameDataCache[this.tid].activeChunkRequest && - chunkNumber === frameDataCache[this.tid].activeChunkRequest.chunkNumber - ) { - for (const r of frameDataCache[this.tid].activeChunkRequest.callbacks) { - r.reject(r.frameNumber); - } - frameDataCache[this.tid].activeChunkRequest = null; - } - }; - - const makeActiveRequest = () => { - const taskDataCache = frameDataCache[this.tid]; - const activeChunk = taskDataCache.activeChunkRequest; - activeChunk.request = serverProxy.frames - .getData(this.tid, this.jid, activeChunk.chunkNumber) - .then((chunk) => { - frameDataCache[this.tid].activeChunkRequest.completed = true; - if (!taskDataCache.nextChunkRequest) { - provider.requestDecodeBlock( - chunk, - taskDataCache.activeChunkRequest.start, - taskDataCache.activeChunkRequest.stop, - taskDataCache.activeChunkRequest.onDecodeAll, - taskDataCache.activeChunkRequest.rejectRequestAll, - ); - } - }) - .catch((exception) => { - if (exception instanceof Exception) { - reject(exception); - } else { - reject(new Exception(exception.message)); - } - }) - .finally(() => { - if (taskDataCache.nextChunkRequest) { - if (taskDataCache.activeChunkRequest) { - for (const r of taskDataCache.activeChunkRequest.callbacks) { - r.reject(r.frameNumber); - } - } - taskDataCache.activeChunkRequest = taskDataCache.nextChunkRequest; - taskDataCache.nextChunkRequest = null; - makeActiveRequest(); - } - }); - }; - - if (isNode) { - resolve('Dummy data'); - } else if (isBrowser) { - provider - .frame(this.number) - .then((frame) => { - if (frame === null) { - onServerRequest(); - const activeRequest = frameDataCache[this.tid].activeChunkRequest; - if (!provider.isChunkCached(start, stop)) { - if ( - !activeRequest || - (activeRequest && - activeRequest.completed && - activeRequest.chunkNumber !== chunkNumber) - ) { - if (activeRequest && activeRequest.rejectRequestAll) { - activeRequest.rejectRequestAll(); - } - frameDataCache[this.tid].activeChunkRequest = { - request: null, - chunkNumber, - start, - stop, - onDecodeAll, - rejectRequestAll, - completed: false, - callbacks: [ - { - resolve: resolveWrapper, - reject, - frameNumber: this.number, - }, - ], - }; - makeActiveRequest(); - } else if (activeRequest.chunkNumber === chunkNumber) { - if (!activeRequest.onDecodeAll && !activeRequest.rejectRequestAll) { - activeRequest.onDecodeAll = onDecodeAll; - activeRequest.rejectRequestAll = rejectRequestAll; - } - activeRequest.callbacks.push({ - resolve: resolveWrapper, - reject, - frameNumber: this.number, - }); - } else { - if (frameDataCache[this.tid].nextChunkRequest) { - const { callbacks } = frameDataCache[this.tid].nextChunkRequest; - for (const r of callbacks) { - r.reject(r.frameNumber); - } - } - frameDataCache[this.tid].nextChunkRequest = { - request: null, - chunkNumber, - start, - stop, - onDecodeAll, - rejectRequestAll, - completed: false, - callbacks: [ - { - resolve: resolveWrapper, - reject, - frameNumber: this.number, - }, - ], - }; - } - } else { - activeRequest.callbacks.push({ - resolve: resolveWrapper, - reject, - frameNumber: this.number, - }); - provider.requestDecodeBlock(null, start, stop, onDecodeAll, rejectRequestAll); - } - } else { - if ( - this.number % chunkSize > chunkSize / 4 && - provider.decodedBlocksCacheSize > 1 && - this.decodeForward && - !provider.isNextChunkExists(this.number) - ) { - const nextChunkNumber = Math.floor(this.number / chunkSize) + 1; - if (nextChunkNumber * chunkSize < this.stopFrame) { - provider.setReadyToLoading(nextChunkNumber); - const nextStart = nextChunkNumber * chunkSize; - const nextStop = Math.min(this.stopFrame, (nextChunkNumber + 1) * chunkSize - 1); - if (!provider.isChunkCached(nextStart, nextStop)) { - if (!frameDataCache[this.tid].activeChunkRequest) { - frameDataCache[this.tid].activeChunkRequest = { - request: null, - chunkNumber: nextChunkNumber, - start: nextStart, - stop: nextStop, - onDecodeAll: null, - rejectRequestAll: null, - completed: false, - callbacks: [], - }; - makeActiveRequest(); - } - } else { - provider.requestDecodeBlock(null, nextStart, nextStop, null, null); - } - } - } - resolveWrapper(frame); - } - }) - .catch((exception) => { - if (exception instanceof Exception) { - reject(exception); - } else { - reject(new Exception(exception.message)); - } - }); - } - }); - }; - - function getFrameMeta(taskID, frame) { - const { meta, mode } = frameDataCache[taskID]; - let size = null; - if (mode === 'interpolation') { - [size] = meta.frames; - } else if (mode === 'annotation') { - if (frame >= meta.size) { - throw new ArgumentError(`Meta information about frame ${frame} can't be received from the server`); - } else { - size = meta.frames[frame]; - } - } else { - throw new DataError(`Invalid mode is specified ${mode}`); - } - return size; - } - - class FrameBuffer { - constructor(size, chunkSize, stopFrame, taskID, jobID) { - this._size = size; - this._buffer = {}; - this._contextImage = {}; - this._requestedChunks = {}; - this._chunkSize = chunkSize; - this._stopFrame = stopFrame; - this._activeFillBufferRequest = false; - this._taskID = taskID; - this._jobID = jobID; - } - - isContextImageAvailable(frame) { - return frame in this._contextImage; - } - - getContextImage(frame) { - return this._contextImage[frame] || null; - } - - addContextImage(frame, data) { - this._contextImage[frame] = data; - } - - getFreeBufferSize() { - let requestedFrameCount = 0; - for (const chunk of Object.values(this._requestedChunks)) { - requestedFrameCount += chunk.requestedFrames.size; - } - - return this._size - Object.keys(this._buffer).length - requestedFrameCount; - } - - requestOneChunkFrames(chunkIdx) { - return new Promise((resolve, reject) => { - this._requestedChunks[chunkIdx] = { - ...this._requestedChunks[chunkIdx], - resolve, - reject, - }; - for (const frame of this._requestedChunks[chunkIdx].requestedFrames.entries()) { - const requestedFrame = frame[1]; - const frameMeta = getFrameMeta(this._taskID, requestedFrame); - const frameData = new FrameData({ - ...frameMeta, - taskID: this._taskID, - jobID: this._jobID, - frameNumber: requestedFrame, - startFrame: frameDataCache[this._taskID].startFrame, - stopFrame: frameDataCache[this._taskID].stopFrame, - decodeForward: false, - }); - - frameData - .data() - .then(() => { - if ( - !(chunkIdx in this._requestedChunks) || - !this._requestedChunks[chunkIdx].requestedFrames.has(requestedFrame) - ) { - reject(chunkIdx); - } else { - this._requestedChunks[chunkIdx].requestedFrames.delete(requestedFrame); - this._requestedChunks[chunkIdx].buffer[requestedFrame] = frameData; - if (this._requestedChunks[chunkIdx].requestedFrames.size === 0) { - const bufferedframes = Object.keys(this._requestedChunks[chunkIdx].buffer).map( - (f) => +f, - ); - this._requestedChunks[chunkIdx].resolve(new Set(bufferedframes)); - } - } - }) - .catch(() => { - reject(chunkIdx); - }); - } - }); - } - - fillBuffer(startFrame, frameStep = 1, count = null) { - const freeSize = this.getFreeBufferSize(); - const requestedFrameCount = count ? count * frameStep : freeSize * frameStep; - const stopFrame = Math.min(startFrame + requestedFrameCount, this._stopFrame + 1); - - for (let i = startFrame; i < stopFrame; i += frameStep) { - const chunkIdx = Math.floor(i / this._chunkSize); - if (!(chunkIdx in this._requestedChunks)) { - this._requestedChunks[chunkIdx] = { - requestedFrames: new Set(), - resolve: null, - reject: null, - buffer: {}, - }; - } - this._requestedChunks[chunkIdx].requestedFrames.add(i); - } - - let bufferedFrames = new Set(); - - // if we send one request to get frame 1 with filling the buffer - // then quicky send one more request to get frame 1 - // frame 1 will be already decoded and written to buffer - // the second request gets frame 1 from the buffer, removes it from there and returns - // after the first request finishes decoding it tries to get frame 1, but failed - // because frame 1 was already removed from the buffer by the second request - // to prevent this behavior we do not write decoded frames to buffer till the end of decoding all chunks - const buffersToBeCommited = []; - const commitBuffers = () => { - for (const buffer of buffersToBeCommited) { - this._buffer = { - ...this._buffer, - ...buffer, - }; - } - }; - - // Need to decode chunks in sequence - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - for (const chunkIdx of Object.keys(this._requestedChunks)) { - try { - const chunkFrames = await this.requestOneChunkFrames(chunkIdx); - if (chunkIdx in this._requestedChunks) { - bufferedFrames = new Set([...bufferedFrames, ...chunkFrames]); - - buffersToBeCommited.push(this._requestedChunks[chunkIdx].buffer); - delete this._requestedChunks[chunkIdx]; - if (Object.keys(this._requestedChunks).length === 0) { - commitBuffers(); - resolve(bufferedFrames); - } - } else { - commitBuffers(); - reject(chunkIdx); - break; - } - } catch (error) { - commitBuffers(); - reject(error); - break; - } - } - }); - } - - async makeFillRequest(start, step, count = null) { - if (!this._activeFillBufferRequest) { - this._activeFillBufferRequest = true; - try { - await this.fillBuffer(start, step, count); - this._activeFillBufferRequest = false; - } catch (error) { - if (typeof error === 'number' && error in this._requestedChunks) { - this._activeFillBufferRequest = false; - } - throw error; - } - } - } - - async require(frameNumber, taskID, jobID, fillBuffer, frameStep) { - for (const frame in this._buffer) { - if (+frame < frameNumber || +frame >= frameNumber + this._size * frameStep) { - delete this._buffer[frame]; - } - } - - this._required = frameNumber; - const frameMeta = getFrameMeta(taskID, frameNumber); - let frame = new FrameData({ - ...frameMeta, - taskID, - jobID, - frameNumber, - startFrame: frameDataCache[taskID].startFrame, - stopFrame: frameDataCache[taskID].stopFrame, - decodeForward: !fillBuffer, - }); - - if (frameNumber in this._buffer) { - frame = this._buffer[frameNumber]; - delete this._buffer[frameNumber]; - const cachedFrames = this.cachedFrames(); - if ( - fillBuffer && - !this._activeFillBufferRequest && - this._size > this._chunkSize && - cachedFrames.length < (this._size * 3) / 4 - ) { - const maxFrame = cachedFrames ? Math.max(...cachedFrames) : frameNumber; - if (maxFrame < this._stopFrame) { - this.makeFillRequest(maxFrame + 1, frameStep).catch((e) => { - if (e !== 'not needed') { - throw e; - } - }); - } - } - } else if (fillBuffer) { - this.clear(); - await this.makeFillRequest(frameNumber, frameStep, fillBuffer ? null : 1); - frame = this._buffer[frameNumber]; - } else { - this.clear(); - } - - return frame; - } - - clear() { - for (const chunkIdx in this._requestedChunks) { - if ( - Object.prototype.hasOwnProperty.call(this._requestedChunks, chunkIdx) && - this._requestedChunks[chunkIdx].reject - ) { - this._requestedChunks[chunkIdx].reject('not needed'); - } - } - this._activeFillBufferRequest = false; - this._requestedChunks = {}; - this._buffer = {}; - } - - cachedFrames() { - return Object.keys(this._buffer).map((f) => +f); - } - } - - async function getImageContext(jobID, frame) { - return new Promise((resolve, reject) => { - serverProxy.frames - .getImageContext(jobID, frame) - .then((result) => { - if (isNode) { - // eslint-disable-next-line no-undef - resolve(global.Buffer.from(result, 'binary').toString('base64')); - } else if (isBrowser) { - const reader = new FileReader(); - reader.onload = () => { - resolve(reader.result); - }; - reader.readAsDataURL(result); - } - }) - .catch((error) => { - reject(error); - }); - }); - } - - async function getContextImage(taskID, jobID, frame) { - if (frameDataCache[taskID].frameBuffer.isContextImageAvailable(frame)) { - return frameDataCache[taskID].frameBuffer.getContextImage(frame); - } - const response = getImageContext(jobID, frame); - frameDataCache[taskID].frameBuffer.addContextImage(frame, response); - return frameDataCache[taskID].frameBuffer.getContextImage(frame); - } - - async function getPreview(taskID = null, jobID = null) { - return new Promise((resolve, reject) => { - // Just go to server and get preview (no any cache) - serverProxy.frames - .getPreview(taskID, jobID) - .then((result) => { - if (isNode) { - // eslint-disable-next-line no-undef - resolve(global.Buffer.from(result, 'binary').toString('base64')); - } else if (isBrowser) { - const reader = new FileReader(); - reader.onload = () => { - resolve(reader.result); - }; - reader.readAsDataURL(result); - } - }) - .catch((error) => { - reject(error); - }); - }); - } - - async function getFrame( - taskID, - jobID, - chunkSize, - chunkType, - mode, - frame, - startFrame, - stopFrame, - isPlaying, - step, - dimension, - ) { - if (!(taskID in frameDataCache)) { - const blockType = chunkType === 'video' ? cvatData.BlockType.MP4VIDEO : cvatData.BlockType.ARCHIVE; - - const meta = await serverProxy.frames.getMeta(taskID); - const mean = meta.frames.reduce((a, b) => a + b.width * b.height, 0) / meta.frames.length; - const stdDev = Math.sqrt( - meta.frames.map((x) => (x.width * x.height - mean) ** 2).reduce((a, b) => a + b) / - meta.frames.length, - ); - - // limit of decoded frames cache by 2GB - const decodedBlocksCacheSize = Math.floor(2147483648 / (mean + stdDev) / 4 / chunkSize) || 1; - - frameDataCache[taskID] = { - meta, - chunkSize, - mode, - startFrame, - stopFrame, - provider: new cvatData.FrameProvider( - blockType, - chunkSize, - Math.max(decodedBlocksCacheSize, 9), - decodedBlocksCacheSize, - 1, - dimension, - ), - frameBuffer: new FrameBuffer( - Math.min(180, decodedBlocksCacheSize * chunkSize), - chunkSize, - stopFrame, - taskID, - jobID, - ), - decodedBlocksCacheSize, - activeChunkRequest: null, - nextChunkRequest: null, - }; - const frameMeta = getFrameMeta(taskID, frame); - // actual only for video chunks - frameDataCache[taskID].provider.setRenderSize(frameMeta.width, frameMeta.height); - } - - return frameDataCache[taskID].frameBuffer.require(frame, taskID, jobID, isPlaying, step); - } - - function getRanges(taskID) { - if (!(taskID in frameDataCache)) { - return { - decoded: [], - buffered: [], - }; - } - - return { - decoded: frameDataCache[taskID].provider.cachedFrames, - buffered: frameDataCache[taskID].frameBuffer.cachedFrames(), - }; - } - - function clear(taskID) { - if (taskID in frameDataCache) { - frameDataCache[taskID].frameBuffer.clear(); - delete frameDataCache[taskID]; - } - } - - module.exports = { - FrameData, - getFrame, - getRanges, - getPreview, - clear, - getContextImage, - }; -})(); diff --git a/cvat-core/src/frames.ts b/cvat-core/src/frames.ts new file mode 100644 index 000000000000..6e0d3961c7c9 --- /dev/null +++ b/cvat-core/src/frames.ts @@ -0,0 +1,833 @@ +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import * as cvatData from 'cvat-data'; +import { isBrowser, isNode } from 'browser-or-node'; +import PluginRegistry from './plugins'; +import serverProxy from './server-proxy'; +import { Exception, ArgumentError, DataError } from './exceptions'; + +// This is the frames storage +const frameDataCache = {}; + +/** + * Class provides meta information about specific frame and frame itself + * @memberof module:API.cvat.classes + * @hideconstructor + */ +export class FrameData { + constructor({ + width, + height, + name, + jobID, + frameNumber, + startFrame, + stopFrame, + decodeForward, + deleted, + has_related_context: hasRelatedContext, + }) { + Object.defineProperties( + this, + Object.freeze({ + /** + * @name filename + * @type {string} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + filename: { + value: name, + writable: false, + }, + /** + * @name width + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + width: { + value: width, + writable: false, + }, + /** + * @name height + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + height: { + value: height, + writable: false, + }, + /** + * @name jid + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + jid: { + value: jobID, + writable: false, + }, + /** + * @name number + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + number: { + value: frameNumber, + writable: false, + }, + /** + * True if some context images are associated with this frame + * @name hasRelatedContext + * @type {boolean} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + hasRelatedContext: { + value: hasRelatedContext, + writable: false, + }, + /** + * Start frame of the frame in the job + * @name startFrame + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + startFrame: { + value: startFrame, + writable: false, + }, + /** + * Stop frame of the frame in the job + * @name stopFrame + * @type {number} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + stopFrame: { + value: stopFrame, + writable: false, + }, + decodeForward: { + value: decodeForward, + writable: false, + }, + /** + * True if frame was deleted from the task data + * @name deleted + * @type {boolean} + * @memberof module:API.cvat.classes.FrameData + * @readonly + * @instance + */ + deleted: { + value: deleted, + writable: false, + }, + }), + ); + } + + /** + * Method returns URL encoded image which can be placed in the img tag + * @method data + * @returns {string} + * @memberof module:API.cvat.classes.FrameData + * @instance + * @async + * @param {function} [onServerRequest = () => {}] + * callback which will be called if data absences local + * @throws {module:API.cvat.exception.ServerError} + * @throws {module:API.cvat.exception.PluginError} + */ + async data(onServerRequest = () => {}) { + const result = await PluginRegistry.apiWrapper.call(this, FrameData.prototype.data, onServerRequest); + return result; + } + + get imageData() { + return this._data.imageData; + } + + set imageData(imageData) { + this._data.imageData = imageData; + } +} + +FrameData.prototype.data.implementation = async function (onServerRequest) { + return new Promise((resolve, reject) => { + const resolveWrapper = (data) => { + this._data = { + imageData: data, + renderWidth: this.width, + renderHeight: this.height, + }; + return resolve(this._data); + }; + + if (this._data) { + resolve(this._data); + return; + } + + const { provider } = frameDataCache[this.jid]; + const { chunkSize } = frameDataCache[this.jid]; + const start = parseInt(this.number / chunkSize, 10) * chunkSize; + const stop = Math.min(this.stopFrame, (parseInt(this.number / chunkSize, 10) + 1) * chunkSize - 1); + const chunkNumber = Math.floor(this.number / chunkSize); + + const onDecodeAll = async (frameNumber) => { + if ( + frameDataCache[this.jid].activeChunkRequest && + chunkNumber === frameDataCache[this.jid].activeChunkRequest.chunkNumber + ) { + const callbackArray = frameDataCache[this.jid].activeChunkRequest.callbacks; + for (let i = callbackArray.length - 1; i >= 0; --i) { + if (callbackArray[i].frameNumber === frameNumber) { + const callback = callbackArray[i]; + callbackArray.splice(i, 1); + callback.resolve(await provider.frame(callback.frameNumber)); + } + } + if (callbackArray.length === 0) { + frameDataCache[this.jid].activeChunkRequest = null; + } + } + }; + + const rejectRequestAll = () => { + if ( + frameDataCache[this.jid].activeChunkRequest && + chunkNumber === frameDataCache[this.jid].activeChunkRequest.chunkNumber + ) { + for (const r of frameDataCache[this.jid].activeChunkRequest.callbacks) { + r.reject(r.frameNumber); + } + frameDataCache[this.jid].activeChunkRequest = null; + } + }; + + const makeActiveRequest = () => { + const taskDataCache = frameDataCache[this.jid]; + const activeChunk = taskDataCache.activeChunkRequest; + activeChunk.request = serverProxy.frames + .getData(null, this.jid, activeChunk.chunkNumber) + .then((chunk) => { + frameDataCache[this.jid].activeChunkRequest.completed = true; + if (!taskDataCache.nextChunkRequest) { + provider.requestDecodeBlock( + chunk, + taskDataCache.activeChunkRequest.start, + taskDataCache.activeChunkRequest.stop, + taskDataCache.activeChunkRequest.onDecodeAll, + taskDataCache.activeChunkRequest.rejectRequestAll, + ); + } + }) + .catch((exception) => { + if (exception instanceof Exception) { + reject(exception); + } else { + reject(new Exception(exception.message)); + } + }) + .finally(() => { + if (taskDataCache.nextChunkRequest) { + if (taskDataCache.activeChunkRequest) { + for (const r of taskDataCache.activeChunkRequest.callbacks) { + r.reject(r.frameNumber); + } + } + taskDataCache.activeChunkRequest = taskDataCache.nextChunkRequest; + taskDataCache.nextChunkRequest = null; + makeActiveRequest(); + } + }); + }; + + if (isNode) { + resolve('Dummy data'); + } else if (isBrowser) { + provider + .frame(this.number) + .then((frame) => { + if (frame === null) { + onServerRequest(); + const activeRequest = frameDataCache[this.jid].activeChunkRequest; + if (!provider.isChunkCached(start, stop)) { + if ( + !activeRequest || + (activeRequest && + activeRequest.completed && + activeRequest.chunkNumber !== chunkNumber) + ) { + if (activeRequest && activeRequest.rejectRequestAll) { + activeRequest.rejectRequestAll(); + } + frameDataCache[this.jid].activeChunkRequest = { + request: null, + chunkNumber, + start, + stop, + onDecodeAll, + rejectRequestAll, + completed: false, + callbacks: [ + { + resolve: resolveWrapper, + reject, + frameNumber: this.number, + }, + ], + }; + makeActiveRequest(); + } else if (activeRequest.chunkNumber === chunkNumber) { + if (!activeRequest.onDecodeAll && !activeRequest.rejectRequestAll) { + activeRequest.onDecodeAll = onDecodeAll; + activeRequest.rejectRequestAll = rejectRequestAll; + } + activeRequest.callbacks.push({ + resolve: resolveWrapper, + reject, + frameNumber: this.number, + }); + } else { + if (frameDataCache[this.jid].nextChunkRequest) { + const { callbacks } = frameDataCache[this.jid].nextChunkRequest; + for (const r of callbacks) { + r.reject(r.frameNumber); + } + } + frameDataCache[this.jid].nextChunkRequest = { + request: null, + chunkNumber, + start, + stop, + onDecodeAll, + rejectRequestAll, + completed: false, + callbacks: [ + { + resolve: resolveWrapper, + reject, + frameNumber: this.number, + }, + ], + }; + } + } else { + activeRequest.callbacks.push({ + resolve: resolveWrapper, + reject, + frameNumber: this.number, + }); + provider.requestDecodeBlock(null, start, stop, onDecodeAll, rejectRequestAll); + } + } else { + if ( + this.number % chunkSize > chunkSize / 4 && + provider.decodedBlocksCacheSize > 1 && + this.decodeForward && + !provider.isNextChunkExists(this.number) + ) { + const nextChunkNumber = Math.floor(this.number / chunkSize) + 1; + if (nextChunkNumber * chunkSize < this.stopFrame) { + provider.setReadyToLoading(nextChunkNumber); + const nextStart = nextChunkNumber * chunkSize; + const nextStop = Math.min(this.stopFrame, (nextChunkNumber + 1) * chunkSize - 1); + if (!provider.isChunkCached(nextStart, nextStop)) { + if (!frameDataCache[this.jid].activeChunkRequest) { + frameDataCache[this.jid].activeChunkRequest = { + request: null, + chunkNumber: nextChunkNumber, + start: nextStart, + stop: nextStop, + onDecodeAll: null, + rejectRequestAll: null, + completed: false, + callbacks: [], + }; + makeActiveRequest(); + } + } else { + provider.requestDecodeBlock(null, nextStart, nextStop, null, null); + } + } + } + resolveWrapper(frame); + } + }) + .catch((exception) => { + if (exception instanceof Exception) { + reject(exception); + } else { + reject(new Exception(exception.message)); + } + }); + } + }); +}; + +function getFrameMeta(jobID, frame) { + const { meta, mode, startFrame } = frameDataCache[jobID]; + let size = null; + if (mode === 'interpolation') { + [size] = meta.frames; + } else if (mode === 'annotation') { + if (frame >= meta.size) { + throw new ArgumentError(`Meta information about frame ${frame} can't be received from the server`); + } else { + size = meta.frames[frame - startFrame]; + } + } else { + throw new DataError(`Invalid mode is specified ${mode}`); + } + return size; +} + +class FrameBuffer { + constructor(size, chunkSize, stopFrame, jobID) { + this._size = size; + this._buffer = {}; + this._contextImage = {}; + this._requestedChunks = {}; + this._chunkSize = chunkSize; + this._stopFrame = stopFrame; + this._activeFillBufferRequest = false; + this._jobID = jobID; + } + + isContextImageAvailable(frame) { + return frame in this._contextImage; + } + + getContextImage(frame) { + return this._contextImage[frame] || null; + } + + addContextImage(frame, data) { + this._contextImage[frame] = data; + } + + getFreeBufferSize() { + let requestedFrameCount = 0; + for (const chunk of Object.values(this._requestedChunks)) { + requestedFrameCount += chunk.requestedFrames.size; + } + + return this._size - Object.keys(this._buffer).length - requestedFrameCount; + } + + requestOneChunkFrames(chunkIdx) { + return new Promise((resolve, reject) => { + this._requestedChunks[chunkIdx] = { + ...this._requestedChunks[chunkIdx], + resolve, + reject, + }; + for (const frame of this._requestedChunks[chunkIdx].requestedFrames.entries()) { + const requestedFrame = frame[1]; + const frameMeta = getFrameMeta(this._jobID, requestedFrame); + const frameData = new FrameData({ + ...frameMeta, + jobID: this._jobID, + frameNumber: requestedFrame, + startFrame: frameDataCache[this._jobID].startFrame, + stopFrame: frameDataCache[this._jobID].stopFrame, + decodeForward: false, + deleted: requestedFrame in frameDataCache[this._jobID].meta, + }); + + frameData + .data() + .then(() => { + if ( + !(chunkIdx in this._requestedChunks) || + !this._requestedChunks[chunkIdx].requestedFrames.has(requestedFrame) + ) { + reject(chunkIdx); + } else { + this._requestedChunks[chunkIdx].requestedFrames.delete(requestedFrame); + this._requestedChunks[chunkIdx].buffer[requestedFrame] = frameData; + if (this._requestedChunks[chunkIdx].requestedFrames.size === 0) { + const bufferedframes = Object.keys(this._requestedChunks[chunkIdx].buffer).map( + (f) => +f, + ); + this._requestedChunks[chunkIdx].resolve(new Set(bufferedframes)); + } + } + }) + .catch(() => { + reject(chunkIdx); + }); + } + }); + } + + fillBuffer(startFrame, frameStep = 1, count = null) { + const freeSize = this.getFreeBufferSize(); + const requestedFrameCount = count ? count * frameStep : freeSize * frameStep; + const stopFrame = Math.min(startFrame + requestedFrameCount, this._stopFrame + 1); + + for (let i = startFrame; i < stopFrame; i += frameStep) { + const chunkIdx = Math.floor(i / this._chunkSize); + if (!(chunkIdx in this._requestedChunks)) { + this._requestedChunks[chunkIdx] = { + requestedFrames: new Set(), + resolve: null, + reject: null, + buffer: {}, + }; + } + this._requestedChunks[chunkIdx].requestedFrames.add(i); + } + + let bufferedFrames = new Set(); + + // if we send one request to get frame 1 with filling the buffer + // then quicky send one more request to get frame 1 + // frame 1 will be already decoded and written to buffer + // the second request gets frame 1 from the buffer, removes it from there and returns + // after the first request finishes decoding it tries to get frame 1, but failed + // because frame 1 was already removed from the buffer by the second request + // to prevent this behavior we do not write decoded frames to buffer till the end of decoding all chunks + const buffersToBeCommited = []; + const commitBuffers = () => { + for (const buffer of buffersToBeCommited) { + this._buffer = { + ...this._buffer, + ...buffer, + }; + } + }; + + // Need to decode chunks in sequence + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + for (const chunkIdx of Object.keys(this._requestedChunks)) { + try { + const chunkFrames = await this.requestOneChunkFrames(chunkIdx); + if (chunkIdx in this._requestedChunks) { + bufferedFrames = new Set([...bufferedFrames, ...chunkFrames]); + + buffersToBeCommited.push(this._requestedChunks[chunkIdx].buffer); + delete this._requestedChunks[chunkIdx]; + if (Object.keys(this._requestedChunks).length === 0) { + commitBuffers(); + resolve(bufferedFrames); + } + } else { + commitBuffers(); + reject(chunkIdx); + break; + } + } catch (error) { + commitBuffers(); + reject(error); + break; + } + } + }); + } + + async makeFillRequest(start, step, count = null) { + if (!this._activeFillBufferRequest) { + this._activeFillBufferRequest = true; + try { + await this.fillBuffer(start, step, count); + this._activeFillBufferRequest = false; + } catch (error) { + if (typeof error === 'number' && error in this._requestedChunks) { + this._activeFillBufferRequest = false; + } + throw error; + } + } + } + + async require(frameNumber, jobID, fillBuffer, frameStep) { + for (const frame in this._buffer) { + if (+frame < frameNumber || +frame >= frameNumber + this._size * frameStep) { + delete this._buffer[frame]; + } + } + + this._required = frameNumber; + const frameMeta = getFrameMeta(jobID, frameNumber); + let frame = new FrameData({ + ...frameMeta, + jobID, + frameNumber, + startFrame: frameDataCache[jobID].startFrame, + stopFrame: frameDataCache[jobID].stopFrame, + decodeForward: !fillBuffer, + deleted: frameNumber in frameDataCache[jobID].meta.deleted_frames, + }); + + if (frameNumber in this._buffer) { + frame = this._buffer[frameNumber]; + delete this._buffer[frameNumber]; + const cachedFrames = this.cachedFrames(); + if ( + fillBuffer && + !this._activeFillBufferRequest && + this._size > this._chunkSize && + cachedFrames.length < (this._size * 3) / 4 + ) { + const maxFrame = cachedFrames ? Math.max(...cachedFrames) : frameNumber; + if (maxFrame < this._stopFrame) { + this.makeFillRequest(maxFrame + 1, frameStep).catch((e) => { + if (e !== 'not needed') { + throw e; + } + }); + } + } + } else if (fillBuffer) { + this.clear(); + await this.makeFillRequest(frameNumber, frameStep, fillBuffer ? null : 1); + frame = this._buffer[frameNumber]; + } else { + this.clear(); + } + + return frame; + } + + clear() { + for (const chunkIdx in this._requestedChunks) { + if ( + Object.prototype.hasOwnProperty.call(this._requestedChunks, chunkIdx) && + this._requestedChunks[chunkIdx].reject + ) { + this._requestedChunks[chunkIdx].reject('not needed'); + } + } + this._activeFillBufferRequest = false; + this._requestedChunks = {}; + this._buffer = {}; + } + + cachedFrames() { + return Object.keys(this._buffer).map((f) => +f); + } +} + +async function getImageContext(jobID, frame) { + return new Promise((resolve, reject) => { + serverProxy.frames + .getImageContext(jobID, frame) + .then((result) => { + if (isNode) { + // eslint-disable-next-line no-undef + resolve(global.Buffer.from(result, 'binary').toString('base64')); + } else if (isBrowser) { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(result); + } + }) + .catch((error) => { + reject(error); + }); + }); +} + +export async function getContextImage(jobID, frame) { + if (frameDataCache[jobID].frameBuffer.isContextImageAvailable(frame)) { + return frameDataCache[jobID].frameBuffer.getContextImage(frame); + } + const response = getImageContext(jobID, frame); + frameDataCache[jobID].frameBuffer.addContextImage(frame, response); + return frameDataCache[jobID].frameBuffer.getContextImage(frame); +} + +export async function getPreview(taskID = null, jobID = null) { + return new Promise((resolve, reject) => { + // Just go to server and get preview (no any cache) + serverProxy.frames + .getPreview(taskID, jobID) + .then((result) => { + if (isNode) { + // eslint-disable-next-line no-undef + resolve(global.Buffer.from(result, 'binary').toString('base64')); + } else if (isBrowser) { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(result); + } + }) + .catch((error) => { + reject(error); + }); + }); +} + +export async function getFrame( + jobID, + chunkSize, + chunkType, + mode, + frame, + startFrame, + stopFrame, + isPlaying, + step, + dimension, +) { + if (!(jobID in frameDataCache)) { + const blockType = chunkType === 'video' ? cvatData.BlockType.MP4VIDEO : cvatData.BlockType.ARCHIVE; + const meta = await serverProxy.frames.getMeta('job', jobID); + meta.deleted_frames = Object.fromEntries(meta.deleted_frames.map((_frame) => [_frame, true])); + const mean = meta.frames.reduce((a, b) => a + b.width * b.height, 0) / meta.frames.length; + const stdDev = Math.sqrt( + meta.frames.map((x) => (x.width * x.height - mean) ** 2).reduce((a, b) => a + b) / + meta.frames.length, + ); + + // limit of decoded frames cache by 2GB + const decodedBlocksCacheSize = Math.floor(2147483648 / (mean + stdDev) / 4 / chunkSize) || 1; + + frameDataCache[jobID] = { + meta, + chunkSize, + mode, + startFrame, + stopFrame, + provider: new cvatData.FrameProvider( + blockType, + chunkSize, + Math.max(decodedBlocksCacheSize, 9), + decodedBlocksCacheSize, + 1, + dimension, + ), + frameBuffer: new FrameBuffer( + Math.min(180, decodedBlocksCacheSize * chunkSize), + chunkSize, + stopFrame, + jobID, + ), + decodedBlocksCacheSize, + activeChunkRequest: null, + nextChunkRequest: null, + }; + const frameMeta = getFrameMeta(jobID, frame); + // actual only for video chunks + frameDataCache[jobID].provider.setRenderSize(frameMeta.width, frameMeta.height); + } + + return frameDataCache[jobID].frameBuffer.require(frame, jobID, isPlaying, step); +} + +export async function getDeletedFrames(instanceType, id) { + if (instanceType === 'job') { + const { meta } = frameDataCache[id]; + return meta.deleted_frames; + } + + if (instanceType === 'task') { + const meta = await serverProxy.frames.getMeta('job', id); + meta.deleted_frames = Object.fromEntries(meta.deleted_frames.map((_frame) => [_frame, true])); + return meta; + } + + throw new Exception(`getDeletedFrames is not implemented for ${instanceType}`); +} + +export function deleteFrame(jobID, frame) { + const { meta } = frameDataCache[jobID]; + meta.deleted_frames[frame] = true; +} + +export function restoreFrame(jobID, frame) { + const { meta } = frameDataCache[jobID]; + if (frame in meta.deleted_frames) { + delete meta.deleted_frames[frame]; + } +} + +export async function patchMeta(jobID) { + const { meta } = frameDataCache[jobID]; + const newMeta = await serverProxy.frames.saveMeta('job', jobID, { + deleted_frames: Object.keys(meta.deleted_frames), + }); + const prevDeletedFrames = meta.deleted_frames; + + // it is important do not overwrite the object, it is why we working on keys in two loops below + for (const frame of Object.keys(prevDeletedFrames)) { + delete prevDeletedFrames[frame]; + } + for (const frame of newMeta.deleted_frames) { + prevDeletedFrames[frame] = true; + } + + frameDataCache[jobID].meta = newMeta; + frameDataCache[jobID].meta.deleted_frames = prevDeletedFrames; +} + +export async function findNotDeletedFrame(jobID, frameFrom, frameTo, offset) { + let meta; + if (!frameDataCache[jobID]) { + meta = await serverProxy.frames.getMeta('job', jobID); + } else { + meta = frameDataCache[jobID].meta; + } + const sign = Math.sign(frameTo - frameFrom); + const predicate = sign > 0 ? (frame) => frame <= frameTo : (frame) => frame >= frameTo; + const update = sign > 0 ? (frame) => frame + 1 : (frame) => frame - 1; + let framesCounter = 0; + let lastUndeletedFrame = null; + for (let frame = frameFrom; predicate(frame); frame = update(frame)) { + if (!(frame in meta.deleted_frames)) { + lastUndeletedFrame = frame; + framesCounter++; + if (framesCounter === offset) { + return lastUndeletedFrame; + } + } + } + + return lastUndeletedFrame; +} + +export function getRanges(jobID) { + if (!(jobID in frameDataCache)) { + return { + decoded: [], + buffered: [], + }; + } + + return { + decoded: frameDataCache[jobID].provider.cachedFrames, + buffered: frameDataCache[jobID].frameBuffer.cachedFrames(), + }; +} + +export function clear(jobID) { + if (jobID in frameDataCache) { + frameDataCache[jobID].frameBuffer.clear(); + delete frameDataCache[jobID]; + } +} diff --git a/cvat-core/src/issue.js b/cvat-core/src/issue.js deleted file mode 100644 index a83a2a475bfa..000000000000 --- a/cvat-core/src/issue.js +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (C) 2020-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -const quickhull = require('quickhull'); - -const PluginRegistry = require('./plugins'); -const Comment = require('./comment'); -const User = require('./user'); -const { ArgumentError } = require('./exceptions'); -const serverProxy = require('./server-proxy'); - -/** - * Class representing a single issue - * @memberof module:API.cvat.classes - * @hideconstructor - */ -class Issue { - constructor(initialData) { - const data = { - id: undefined, - job: undefined, - position: undefined, - comments: [], - frame: undefined, - created_date: undefined, - owner: undefined, - resolved: undefined, - }; - - for (const property in data) { - if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { - data[property] = initialData[property]; - } - } - - if (data.owner && !(data.owner instanceof User)) data.owner = new User(data.owner); - - if (data.comments) { - data.comments = data.comments.map((comment) => new Comment(comment)); - } - - if (typeof data.created_date === 'undefined') { - data.created_date = new Date().toISOString(); - } - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - id: { - get: () => data.id, - }, - /** - * Region of interests of the issue - * @name position - * @type {number[]} - * @memberof module:API.cvat.classes.Issue - * @instance - * @readonly - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - position: { - get: () => data.position, - set: (value) => { - if (Array.isArray(value) || value.some((coord) => typeof coord !== 'number')) { - throw new ArgumentError(`Array of numbers is expected. Got ${value}`); - } - data.position = value; - }, - }, - /** - * ID of a job, the issue is linked with - * @name job - * @type {number} - * @memberof module:API.cvat.classes.Issue - * @instance - * @readonly - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - job: { - get: () => data.job, - }, - /** - * List of comments attached to the issue - * @name comments - * @type {module:API.cvat.classes.Comment[]} - * @memberof module:API.cvat.classes.Issue - * @instance - * @readonly - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - comments: { - get: () => [...data.comments], - }, - /** - * @name frame - * @type {integer} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - frame: { - get: () => data.frame, - }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - createdDate: { - get: () => data.created_date, - }, - /** - * An instance of a user who has raised the issue - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - owner: { - get: () => data.owner, - }, - /** - * The flag defines issue status - * @name resolved - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - resolved: { - get: () => data.resolved, - }, - __internal: { - get: () => data, - }, - }), - ); - } - - static hull(coordinates) { - if (coordinates.length > 4) { - const points = coordinates.reduce((acc, coord, index, arr) => { - if (index % 2) acc.push({ x: arr[index - 1], y: coord }); - return acc; - }, []); - - return quickhull(points) - .map((point) => [point.x, point.y]) - .flat(); - } - - return coordinates; - } - - /** - * @typedef {Object} CommentData - * @property {string} message a comment message - * @global - */ - /** - * Method appends a comment to the issue - * For a new issue it saves comment locally, for a saved issue it saves comment on the server - * @method comment - * @memberof module:API.cvat.classes.Issue - * @param {CommentData} data - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async comment(data) { - const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.comment, data); - return result; - } - - /** - * The method resolves the issue - * New issues are resolved locally, server-saved issues are resolved on the server - * @method resolve - * @memberof module:API.cvat.classes.Issue - * @param {module:API.cvat.classes.User} user - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async resolve(user) { - const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.resolve, user); - return result; - } - - /** - * The method resolves the issue - * New issues are reopened locally, server-saved issues are reopened on the server - * @method reopen - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async reopen() { - const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.reopen); - return result; - } - - /** - * The method deletes the issue - * Deletes local or server-saved issues - * @method delete - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async delete() { - await PluginRegistry.apiWrapper.call(this, Issue.prototype.delete); - } - - serialize() { - const { comments } = this; - const data = { - position: this.position, - frame: this.frame, - comments: comments.map((comment) => comment.serialize()), - }; - - if (typeof this.id === 'number') { - data.id = this.id; - } - if (typeof this.job === 'number') { - data.job = this.job; - } - if (typeof this.createdDate === 'string') { - data.created_date = this.createdDate; - } - if (typeof this.resolved === 'boolean') { - data.resolved = this.resolved; - } - if (this.owner instanceof User) { - data.owner = this.owner.serialize().id; - } - - return data; - } -} - -Issue.prototype.comment.implementation = async function (data) { - if (typeof data !== 'object' || data === null) { - throw new ArgumentError(`The argument "data" must be an object. Got "${data}"`); - } - if (typeof data.message !== 'string' || data.message.length < 1) { - throw new ArgumentError(`Comment message must be a not empty string. Got "${data.message}"`); - } - - const comment = new Comment(data); - if (typeof this.id === 'number') { - const serialized = comment.serialize(); - serialized.issue = this.id; - const response = await serverProxy.comments.create(serialized); - const savedComment = new Comment(response); - this.__internal.comments.push(savedComment); - } else { - this.__internal.comments.push(comment); - } -}; - -Issue.prototype.resolve.implementation = async function (user) { - if (!(user instanceof User)) { - throw new ArgumentError(`The argument "user" must be an instance of a User class. Got "${typeof user}"`); - } - - if (typeof this.id === 'number') { - const response = await serverProxy.issues.update(this.id, { resolved: true }); - this.__internal.resolved = response.resolved; - } else { - this.__internal.resolved = true; - } -}; - -Issue.prototype.reopen.implementation = async function () { - if (typeof this.id === 'number') { - const response = await serverProxy.issues.update(this.id, { resolved: false }); - this.__internal.resolved = response.resolved; - } else { - this.__internal.resolved = false; - } -}; - -Issue.prototype.delete.implementation = async function () { - const { id } = this; - if (id >= 0) { - await serverProxy.issues.delete(id); - } -}; - -module.exports = Issue; diff --git a/cvat-core/src/issue.ts b/cvat-core/src/issue.ts new file mode 100644 index 000000000000..82e6969f74da --- /dev/null +++ b/cvat-core/src/issue.ts @@ -0,0 +1,246 @@ +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import quickhull from 'quickhull'; + +import { Job } from 'session'; +import PluginRegistry from './plugins'; +import Comment, { RawCommentData } from './comment'; +import User from './user'; +import { ArgumentError } from './exceptions'; +import serverProxy from './server-proxy'; + +interface RawIssueData { + id?: number; + job?: any; + position?: number[]; + comments?: any; + frame?: number; + owner?: any; + resolved?: boolean; + created_date?: string; +} + +export default class Issue { + public readonly id: number; + public readonly job: Job; + public readonly comments: Comment[]; + public readonly frame: number; + public readonly owner: User; + public readonly resolved: boolean; + public readonly createdDate: string; + public position: number[]; + + constructor(initialData: RawIssueData) { + const data: RawIssueData = { + id: undefined, + job: undefined, + position: undefined, + comments: [], + frame: undefined, + created_date: undefined, + owner: undefined, + resolved: undefined, + }; + + for (const property in data) { + if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { + data[property] = initialData[property]; + } + } + + if (data.owner && !(data.owner instanceof User)) data.owner = new User(data.owner); + + if (data.comments) { + data.comments = data.comments.map((comment) => new Comment(comment)); + } + + if (typeof data.created_date === 'undefined') { + data.created_date = new Date().toISOString(); + } + + Object.defineProperties( + this, + Object.freeze({ + id: { + get: () => data.id, + }, + position: { + get: () => data.position, + set: (value) => { + if (Array.isArray(value) || value.some((coord) => typeof coord !== 'number')) { + throw new ArgumentError(`Array of numbers is expected. Got ${value}`); + } + data.position = value; + }, + }, + job: { + get: () => data.job, + }, + comments: { + get: () => [...data.comments], + }, + frame: { + get: () => data.frame, + }, + createdDate: { + get: () => data.created_date, + }, + owner: { + get: () => data.owner, + }, + resolved: { + get: () => data.resolved, + }, + __internal: { + get: () => data, + }, + }), + ); + } + + public static hull(coordinates: number[]): number[] { + if (coordinates.length > 4) { + const points = coordinates.reduce((acc, coord, index, arr) => { + if (index % 2) acc.push({ x: arr[index - 1], y: coord }); + return acc; + }, []); + + return quickhull(points) + .map((point) => [point.x, point.y]) + .flat(); + } + + return coordinates; + } + + // Method appends a comment to the issue + // For a new issue it saves comment locally, for a saved issue it saves comment on the server + public async comment(data: RawCommentData): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.comment, data); + return result; + } + + // The method resolves the issue + // New issues are resolved locally, server-saved issues are resolved on the server + public async resolve(user: User): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.resolve, user); + return result; + } + + // The method reopens the issue + // New issues are reopened locally, server-saved issues are reopened on the server + public async reopen(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.reopen); + return result; + } + + // The method deletes the issue + // Deletes local or server-saved issues + public async delete(): Promise { + await PluginRegistry.apiWrapper.call(this, Issue.prototype.delete); + } + + public serialize(): RawIssueData { + const { comments } = this; + const data: RawIssueData = { + position: this.position, + frame: this.frame, + comments: comments.map((comment) => comment.serialize()), + }; + + if (typeof this.id === 'number') { + data.id = this.id; + } + if (typeof this.job === 'number') { + data.job = this.job; + } + if (typeof this.createdDate === 'string') { + data.created_date = this.createdDate; + } + if (typeof this.resolved === 'boolean') { + data.resolved = this.resolved; + } + if (this.owner instanceof User) { + data.owner = this.owner.serialize().id; + } + + return data; + } +} + +Object.defineProperties(Issue.prototype.comment, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(data: RawCommentData) { + if (typeof data !== 'object' || data === null) { + throw new ArgumentError(`The argument "data" must be an object. Got "${data}"`); + } + if (typeof data.message !== 'string' || data.message.length < 1) { + throw new ArgumentError(`Comment message must be a not empty string. Got "${data.message}"`); + } + + const comment = new Comment(data); + if (typeof this.id === 'number') { + const serialized = comment.serialize(); + serialized.issue = this.id; + const response = await serverProxy.comments.create(serialized); + const savedComment = new Comment(response); + this.__internal.comments.push(savedComment); + } else { + this.__internal.comments.push(comment); + } + }, + }, +}); + +Object.defineProperties(Issue.prototype.resolve, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(user: User) { + if (!(user instanceof User)) { + throw new ArgumentError(`The argument "user" must be an + instance of a User class. Got "${typeof user}"`); + } + + if (typeof this.id === 'number') { + const response = await serverProxy.issues.update(this.id, { resolved: true }); + this.__internal.resolved = response.resolved; + } else { + this.__internal.resolved = true; + } + }, + }, +}); + +Object.defineProperties(Issue.prototype.reopen, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + if (typeof this.id === 'number') { + const response = await serverProxy.issues.update(this.id, { resolved: false }); + this.__internal.resolved = response.resolved; + } else { + this.__internal.resolved = false; + } + }, + }, +}); + +Object.defineProperties(Issue.prototype.delete, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + const { id } = this; + if (id >= 0) { + await serverProxy.issues.delete(id); + } + }, + }, +}); diff --git a/cvat-core/src/labels.js b/cvat-core/src/labels.js deleted file mode 100644 index e7db2b357782..000000000000 --- a/cvat-core/src/labels.js +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (C) 2019-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const { AttributeType } = require('./enums'); - const { ArgumentError } = require('./exceptions'); - - /** - * Class representing an attribute - * @memberof module:API.cvat.classes - * @hideconstructor - */ - class Attribute { - constructor(initialData) { - const data = { - id: undefined, - default_value: undefined, - input_type: undefined, - mutable: undefined, - name: undefined, - values: undefined, - }; - - for (const key in data) { - if (Object.prototype.hasOwnProperty.call(data, key)) { - if (Object.prototype.hasOwnProperty.call(initialData, key)) { - if (Array.isArray(initialData[key])) { - data[key] = [...initialData[key]]; - } else { - data[key] = initialData[key]; - } - } - } - } - - if (!Object.values(AttributeType).includes(data.input_type)) { - throw new ArgumentError(`Got invalid attribute type ${data.input_type}`); - } - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ - id: { - get: () => data.id, - }, - /** - * @name defaultValue - * @type {(string|integer|boolean)} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ - defaultValue: { - get: () => data.default_value, - }, - /** - * @name inputType - * @type {module:API.cvat.enums.AttributeType} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ - inputType: { - get: () => data.input_type, - }, - /** - * @name mutable - * @type {boolean} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ - mutable: { - get: () => data.mutable, - }, - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ - name: { - get: () => data.name, - }, - /** - * @name values - * @type {(string[]|integer[]|boolean[])} - * @memberof module:API.cvat.classes.Attribute - * @readonly - * @instance - */ - values: { - get: () => [...data.values], - }, - }), - ); - } - - toJSON() { - const object = { - name: this.name, - mutable: this.mutable, - input_type: this.inputType, - default_value: this.defaultValue, - values: this.values, - }; - - if (typeof this.id !== 'undefined') { - object.id = this.id; - } - - return object; - } - } - - /** - * Class representing a label - * @memberof module:API.cvat.classes - * @hideconstructor - */ - class Label { - constructor(initialData) { - const data = { - id: undefined, - name: undefined, - color: undefined, - deleted: false, - }; - - for (const key in data) { - if (Object.prototype.hasOwnProperty.call(data, key)) { - if (Object.prototype.hasOwnProperty.call(initialData, key)) { - data[key] = initialData[key]; - } - } - } - - data.attributes = []; - - if ( - Object.prototype.hasOwnProperty.call(initialData, 'attributes') && - Array.isArray(initialData.attributes) - ) { - for (const attrData of initialData.attributes) { - data.attributes.push(new Attribute(attrData)); - } - } - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.Label - * @readonly - * @instance - */ - id: { - get: () => data.id, - }, - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Label - * @instance - */ - name: { - get: () => data.name, - set: (name) => { - if (typeof name !== 'string') { - throw new ArgumentError(`Name must be a string, but ${typeof name} was given`); - } - data.name = name; - }, - }, - /** - * @name color - * @type {string} - * @memberof module:API.cvat.classes.Label - * @instance - */ - color: { - get: () => data.color, - set: (color) => { - if (typeof color === 'string' && color.match(/^#[0-9a-f]{6}$|^$/)) { - data.color = color; - } else { - throw new ArgumentError('Trying to set wrong color format'); - } - }, - }, - /** - * @name attributes - * @type {module:API.cvat.classes.Attribute[]} - * @memberof module:API.cvat.classes.Label - * @readonly - * @instance - */ - attributes: { - get: () => [...data.attributes], - }, - deleted: { - get: () => data.deleted, - set: (value) => { - data.deleted = value; - }, - }, - }), - ); - } - - toJSON() { - const object = { - name: this.name, - attributes: [...this.attributes.map((el) => el.toJSON())], - color: this.color, - }; - - if (typeof this.id !== 'undefined') { - object.id = this.id; - } - - if (this.deleted) { - object.deleted = this.deleted; - } - - return object; - } - } - - module.exports = { - Attribute, - Label, - }; -})(); diff --git a/cvat-core/src/labels.ts b/cvat-core/src/labels.ts new file mode 100644 index 000000000000..1e609f760ed0 --- /dev/null +++ b/cvat-core/src/labels.ts @@ -0,0 +1,356 @@ +// Copyright (C) 2019-2022 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { ShapeType, AttributeType } from './enums'; +import { ArgumentError } from './exceptions'; + +type AttrInputType = 'select' | 'radio' | 'checkbox' | 'number' | 'text'; + +export interface RawAttribute { + name: string; + mutable: boolean; + input_type: AttrInputType; + default_value: string; + values: string[]; + id?: number; +} + +/** + * Class representing an attribute + * @memberof module:API.cvat.classes + * @hideconstructor + */ +export class Attribute { + public id?: number; + public defaultValue: string; + public inputType: AttrInputType; + public mutable: boolean; + public name: string; + public values: string[]; + + constructor(initialData: RawAttribute) { + const data = { + id: undefined, + default_value: undefined, + input_type: undefined, + mutable: undefined, + name: undefined, + values: undefined, + }; + + for (const key in data) { + if (Object.prototype.hasOwnProperty.call(data, key)) { + if (Object.prototype.hasOwnProperty.call(initialData, key)) { + if (Array.isArray(initialData[key])) { + data[key] = [...initialData[key]]; + } else { + data[key] = initialData[key]; + } + } + } + } + + if (!Object.values(AttributeType).includes(data.input_type)) { + throw new ArgumentError(`Got invalid attribute type ${data.input_type}`); + } + + Object.defineProperties( + this, + Object.freeze({ + /** + * @name id + * @type {number} + * @memberof module:API.cvat.classes.Attribute + * @readonly + * @instance + */ + id: { + get: () => data.id, + }, + /** + * @name defaultValue + * @type {string} + * @memberof module:API.cvat.classes.Attribute + * @readonly + * @instance + */ + defaultValue: { + get: () => data.default_value, + }, + /** + * @name inputType + * @type {module:API.cvat.enums.AttributeType} + * @memberof module:API.cvat.classes.Attribute + * @readonly + * @instance + */ + inputType: { + get: () => data.input_type, + }, + /** + * @name mutable + * @type {boolean} + * @memberof module:API.cvat.classes.Attribute + * @readonly + * @instance + */ + mutable: { + get: () => data.mutable, + }, + /** + * @name name + * @type {string} + * @memberof module:API.cvat.classes.Attribute + * @readonly + * @instance + */ + name: { + get: () => data.name, + }, + /** + * @name values + * @type {string[]} + * @memberof module:API.cvat.classes.Attribute + * @readonly + * @instance + */ + values: { + get: () => [...data.values], + }, + }), + ); + } + + toJSON(): RawAttribute { + const object: RawAttribute = { + name: this.name, + mutable: this.mutable, + input_type: this.inputType, + default_value: this.defaultValue, + values: this.values, + }; + + if (typeof this.id !== 'undefined') { + object.id = this.id; + } + + return object; + } +} + +type LabelType = 'rectangle' | 'polygon' | 'polyline' | 'points' | 'ellipse' | 'cuboid' | 'skeleton' | 'mask' | 'tag' | 'any'; +export interface RawLabel { + id?: number; + name: string; + color?: string; + type: LabelType; + svg?: string; + sublabels?: RawLabel[]; + has_parent?: boolean; + deleted?: boolean; + attributes: RawAttribute[]; +} + +/** + * Class representing a label + * @memberof module:API.cvat.classes + * @hideconstructor + */ +export class Label { + public name: string; + public readonly id?: number; + public readonly color?: string; + public readonly attributes: Attribute[]; + public readonly type: LabelType; + public structure: { + sublabels: Label[]; + svg: string; + } | null; + public deleted: boolean; + public readonly hasParent?: boolean; + + constructor(initialData: RawLabel) { + const data = { + id: undefined, + name: undefined, + color: undefined, + type: undefined, + structure: undefined, + has_parent: false, + deleted: false, + svg: undefined, + elements: undefined, + sublabels: undefined, + attributes: [], + }; + + for (const key of Object.keys(data)) { + if (Object.prototype.hasOwnProperty.call(initialData, key)) { + data[key] = initialData[key]; + } + } + + data.attributes = []; + + if ( + Object.prototype.hasOwnProperty.call(initialData, 'attributes') && + Array.isArray(initialData.attributes) + ) { + for (const attrData of initialData.attributes) { + data.attributes.push(new Attribute(attrData)); + } + } + + if (data.type === 'skeleton') { + data.sublabels = data.sublabels.map((internalLabel) => new Label({ ...internalLabel, has_parent: true })); + } + + Object.defineProperties( + this, + Object.freeze({ + /** + * @name id + * @type {number} + * @memberof module:API.cvat.classes.Label + * @readonly + * @instance + */ + id: { + get: () => data.id, + }, + /** + * @name name + * @type {string} + * @memberof module:API.cvat.classes.Label + * @instance + */ + name: { + get: () => data.name, + set: (name) => { + if (typeof name !== 'string') { + throw new ArgumentError(`Name must be a string, but ${typeof name} was given`); + } + data.name = name; + }, + }, + /** + * @name color + * @type {string} + * @memberof module:API.cvat.classes.Label + * @instance + */ + color: { + get: () => data.color, + set: (color) => { + if (typeof color === 'string' && color.match(/^#[0-9a-f]{6}$|^$/)) { + data.color = color; + } else { + throw new ArgumentError('Trying to set wrong color format'); + } + }, + }, + /** + * @name attributes + * @type {module:API.cvat.classes.Attribute[]} + * @memberof module:API.cvat.classes.Label + * @readonly + * @instance + */ + attributes: { + get: () => [...data.attributes], + }, + /** + * @typedef {Object} SkeletonStructure + * @property {module:API.cvat.classes.Label[]} sublabels A list of labels the skeleton includes + * @property {Object[]} svg An SVG representation of the skeleton + * A type of a file + * @global + */ + /** + * @name type + * @type {string | undefined} + * @memberof module:API.cvat.classes.Label + * @readonly + * @instance + */ + type: { + get: () => data.type, + }, + /** + * @name type + * @type {SkeletonStructure | undefined} + * @memberof module:API.cvat.classes.Label + * @readonly + * @instance + */ + structure: { + get: () => { + if (data.type === ShapeType.SKELETON) { + return { + svg: data.svg, + sublabels: [...data.sublabels], + }; + } + + return null; + }, + }, + /** + * @name deleted + * @type {boolean} + * @memberof module:API.cvat.classes.Label + * @instance + */ + deleted: { + get: () => data.deleted, + set: (value) => { + data.deleted = value; + }, + }, + /** + * @name hasParent + * @type {boolean} + * @memberof module:API.cvat.classes.Label + * @readonly + * @instance + */ + hasParent: { + get: () => data.has_parent, + }, + }), + ); + } + + toJSON(): RawLabel { + const object: RawLabel = { + name: this.name, + attributes: [...this.attributes.map((el) => el.toJSON())], + type: this.type, + }; + + if (typeof this.color !== 'undefined') { + object.color = this.color; + } + + if (typeof this.id !== 'undefined') { + object.id = this.id; + } + + if (this.deleted) { + object.deleted = this.deleted; + } + + if (this.type) { + object.type = this.type; + } + + const { structure } = this; + if (structure) { + object.svg = structure.svg; + object.sublabels = structure.sublabels.map((internalLabel) => internalLabel.toJSON()); + } + + return object; + } +} diff --git a/cvat-core/src/lambda-manager.js b/cvat-core/src/lambda-manager.ts similarity index 85% rename from cvat-core/src/lambda-manager.js rename to cvat-core/src/lambda-manager.ts index 66014953189c..184723ab08f6 100644 --- a/cvat-core/src/lambda-manager.js +++ b/cvat-core/src/lambda-manager.ts @@ -1,19 +1,23 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -const serverProxy = require('./server-proxy'); -const { ArgumentError } = require('./exceptions'); -const MLModel = require('./ml-model'); -const { RQStatus } = require('./enums'); +import serverProxy from './server-proxy'; +import { ArgumentError } from './exceptions'; +import MLModel from './ml-model'; +import { RQStatus } from './enums'; class LambdaManager { + private listening: any; + private cachedList: any; + constructor() { this.listening = {}; this.cachedList = null; } - async list() { + async list(): Promise { if (Array.isArray(this.cachedList)) { return [...this.cachedList]; } @@ -34,7 +38,7 @@ class LambdaManager { return models; } - async run(taskID, model, args) { + async run(taskID: number, model: MLModel, args: any) { if (!Number.isInteger(taskID) || taskID < 0) { throw new ArgumentError(`Argument taskID must be a positive integer. Got "${taskID}"`); } @@ -78,7 +82,7 @@ class LambdaManager { return result.filter((request) => ['queued', 'started'].includes(request.status)); } - async cancel(requestID) { + async cancel(requestID): Promise { if (typeof requestID !== 'string') { throw new ArgumentError(`Request id argument is required to be a string. But got ${requestID}`); } @@ -90,8 +94,8 @@ class LambdaManager { await serverProxy.lambda.cancel(requestID); } - async listen(requestID, onUpdate) { - const timeoutCallback = async () => { + async listen(requestID, onUpdate): Promise { + const timeoutCallback = async (): Promise => { try { this.listening[requestID].timeout = null; const response = await serverProxy.lambda.status(requestID); @@ -124,4 +128,4 @@ class LambdaManager { } } -module.exports = new LambdaManager(); +export default new LambdaManager(); diff --git a/cvat-core/src/log.js b/cvat-core/src/log.ts similarity index 75% rename from cvat-core/src/log.js rename to cvat-core/src/log.ts index 31efffc762cf..40d7aea585fc 100644 --- a/cvat-core/src/log.js +++ b/cvat-core/src/log.ts @@ -1,19 +1,23 @@ -// Copyright (C) 2019-2021 Intel Corporation +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -const { detect } = require('detect-browser'); -const PluginRegistry = require('./plugins'); -const { ArgumentError } = require('./exceptions'); -const { LogType } = require('./enums'); - -/** - * Class representing a single log - * @memberof module:API.cvat.classes - * @hideconstructor - */ -class Log { - constructor(logType, payload) { +import { detect } from 'detect-browser'; +import PluginRegistry from './plugins'; +import { LogType } from './enums'; +import { ArgumentError } from './exceptions'; + +export class Log { + public readonly id: number; + public readonly type: LogType; + public readonly time: Date; + + public payload: any; + + protected onCloseCallback: (() => void) | null; + + constructor(logType: LogType, payload: any) { this.onCloseCallback = null; this.type = logType; @@ -21,11 +25,11 @@ class Log { this.time = new Date(); } - onClose(callback) { + public onClose(callback: () => void): void { this.onCloseCallback = callback; } - validatePayload() { + public validatePayload(): void { if (typeof this.payload !== 'object') { throw new ArgumentError('Payload must be an object'); } @@ -38,7 +42,7 @@ class Log { } } - dump() { + public dump(): any { const payload = { ...this.payload }; const body = { name: this.type, @@ -58,38 +62,33 @@ class Log { }; } - /** - * Method saves a durable log in a storage
- * Note then you can call close() multiple times
- * Log duration will be computed based on the latest call
- * All payloads will be shallowly combined (all top level properties will exist) - * @method close - * @memberof module:API.cvat.classes.Log - * @param {object} [payload] part of payload can be added when close a log - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async close(payload = {}) { + // Method saves a durable log in a storage + // Note then you can call close() multiple times + // Log duration will be computed based on the latest call + // All payloads will be shallowly combined (all top level properties will exist) + public async close(payload = {}): Promise { const result = await PluginRegistry.apiWrapper.call(this, Log.prototype.close, payload); return result; } } -Log.prototype.close.implementation = function (payload) { - this.payload.duration = Date.now() - this.time.getTime(); - this.payload = { ...this.payload, ...payload }; - - if (this.onCloseCallback) { - this.onCloseCallback(); - } -}; +Object.defineProperties(Log.prototype.close, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(payload: any) { + this.payload.duration = Date.now() - this.time.getTime(); + this.payload = { ...this.payload, ...payload }; + if (this.onCloseCallback) { + this.onCloseCallback(); + } + }, + }, +}); class LogWithCount extends Log { - validatePayload() { - Log.prototype.validatePayload.call(this); + public validatePayload(): void { + super.validatePayload.call(this); if (!Number.isInteger(this.payload.count) || this.payload.count < 1) { const message = `The field "count" is required for "${this.type}" log. It must be a positive integer`; throw new ArgumentError(message); @@ -98,8 +97,8 @@ class LogWithCount extends Log { } class LogWithObjectsInfo extends Log { - validatePayload() { - const generateError = (name, range) => { + public validatePayload(): void { + const generateError = (name: string, range: string): void => { const message = `The field "${name}" is required for "${this.type}" log. ${range}`; throw new ArgumentError(message); }; @@ -139,14 +138,13 @@ class LogWithObjectsInfo extends Log { } class LogWithWorkingTime extends Log { - validatePayload() { - Log.prototype.validatePayload.call(this); + public validatePayload(): void { + super.validatePayload.call(this); if ( - !( - 'working_time' in this.payload) || - !typeof this.payload.working_time === 'number' || - this.payload.working_time < 0 + !('working_time' in this.payload) || + !(typeof this.payload.working_time === 'number') || + this.payload.working_time < 0 ) { const message = ` The field "working_time" is required for ${this.type} log. It must be a number not less than 0 @@ -157,8 +155,8 @@ class LogWithWorkingTime extends Log { } class LogWithExceptionInfo extends Log { - validatePayload() { - Log.prototype.validatePayload.call(this); + public validatePayload(): void { + super.validatePayload.call(this); if (typeof this.payload.message !== 'string') { const message = `The field "message" is required for ${this.type} log. It must be a string`; @@ -186,7 +184,7 @@ class LogWithExceptionInfo extends Log { } } - dump() { + public dump(): any { let body = super.dump(); const { payload } = body; const client = detect(); @@ -212,7 +210,7 @@ class LogWithExceptionInfo extends Log { } } -function logFactory(logType, payload) { +export default function logFactory(logType: LogType, payload: any): Log { const logsWithCount = [ LogType.deleteObject, LogType.mergeObjects, @@ -238,5 +236,3 @@ function logFactory(logType, payload) { return new Log(logType, payload); } - -module.exports = logFactory; diff --git a/cvat-core/src/logger-storage.js b/cvat-core/src/logger-storage.js deleted file mode 100644 index 607c3527b3ae..000000000000 --- a/cvat-core/src/logger-storage.js +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (C) 2019-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -const PluginRegistry = require('./plugins'); -const serverProxy = require('./server-proxy'); -const logFactory = require('./log'); -const { ArgumentError } = require('./exceptions'); -const { LogType } = require('./enums'); - -const WORKING_TIME_THRESHOLD = 100000; // ms, 1.66 min - -function sleep(ms) { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} - -class LoggerStorage { - constructor() { - this.clientID = Date.now().toString().substr(-6); - this.lastLogTime = Date.now(); - this.workingTime = 0; - this.collection = []; - this.ignoreRules = {}; // by event - this.isActiveChecker = null; - this.saving = false; - - this.ignoreRules[LogType.zoomImage] = { - lastLog: null, - timeThreshold: 1000, - ignore(previousLog) { - return Date.now() - previousLog.time < this.timeThreshold; - }, - }; - - this.ignoreRules[LogType.changeAttribute] = { - lastLog: null, - ignore(previousLog, currentPayload) { - return ( - currentPayload.object_id === previousLog.payload.object_id && - currentPayload.id === previousLog.payload.id - ); - }, - }; - } - - updateWorkingTime() { - if (!this.isActiveChecker || this.isActiveChecker()) { - const lastLogTime = Date.now(); - const diff = lastLogTime - this.lastLogTime; - this.workingTime += diff < WORKING_TIME_THRESHOLD ? diff : 0; - this.lastLogTime = lastLogTime; - } - } - - async configure(isActiveChecker, activityHelper) { - const result = await PluginRegistry.apiWrapper.call( - this, - LoggerStorage.prototype.configure, - isActiveChecker, - activityHelper, - ); - return result; - } - - async log(logType, payload = {}, wait = false) { - const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.log, logType, payload, wait); - return result; - } - - async save() { - const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.save); - return result; - } -} - -LoggerStorage.prototype.configure.implementation = function (isActiveChecker, userActivityCallback) { - if (typeof isActiveChecker !== 'function') { - throw new ArgumentError('isActiveChecker argument must be callable'); - } - - if (!Array.isArray(userActivityCallback)) { - throw new ArgumentError('userActivityCallback argument must be an array'); - } - - this.isActiveChecker = () => !!isActiveChecker(); - userActivityCallback.push(this.updateWorkingTime.bind(this)); -}; - -LoggerStorage.prototype.log.implementation = function (logType, payload, wait) { - if (typeof payload !== 'object') { - throw new ArgumentError('Payload must be an object'); - } - - if (typeof wait !== 'boolean') { - throw new ArgumentError('Payload must be an object'); - } - - if (logType in this.ignoreRules) { - const ignoreRule = this.ignoreRules[logType]; - const { lastLog } = ignoreRule; - if (lastLog && ignoreRule.ignore(lastLog, payload)) { - lastLog.payload = { - ...lastLog.payload, - ...payload, - }; - - this.updateWorkingTime(); - return ignoreRule.lastLog; - } - } - - const logPayload = { ...payload }; - logPayload.client_id = this.clientID; - if (this.isActiveChecker) { - logPayload.is_active = this.isActiveChecker(); - } - - const log = logFactory(logType, { ...logPayload }); - if (logType in this.ignoreRules) { - this.ignoreRules[logType].lastLog = log; - } - - const pushEvent = () => { - this.updateWorkingTime(); - log.validatePayload(); - log.onClose(null); - this.collection.push(log); - }; - - if (log.type === LogType.sendException) { - serverProxy.server.exception(log.dump()).catch(() => { - pushEvent(); - }); - - return log; - } - - if (wait) { - log.onClose(pushEvent); - } else { - pushEvent(); - } - - return log; -}; - -LoggerStorage.prototype.save.implementation = async function () { - while (this.saving) { - await sleep(100); - } - - const collectionToSend = [...this.collection]; - const lastLog = this.collection[this.collection.length - 1]; - - const logPayload = {}; - logPayload.client_id = this.clientID; - logPayload.working_time = this.workingTime; - if (this.isActiveChecker) { - logPayload.is_active = this.isActiveChecker(); - } - - if (lastLog && lastLog.type === LogType.sendTaskInfo) { - logPayload.job_id = lastLog.payload.job_id; - logPayload.task_id = lastLog.payload.task_id; - } - - const userActivityLog = logFactory(LogType.sendUserActivity, logPayload); - collectionToSend.push(userActivityLog); - - try { - this.saving = true; - await serverProxy.logs.save(collectionToSend.map((log) => log.dump())); - for (const rule of Object.values(this.ignoreRules)) { - rule.lastLog = null; - } - this.collection = []; - this.workingTime = 0; - this.lastLogTime = Date.now(); - } finally { - this.saving = false; - } -}; - -module.exports = new LoggerStorage(); diff --git a/cvat-core/src/logger-storage.ts b/cvat-core/src/logger-storage.ts new file mode 100644 index 000000000000..c9a5593a94e4 --- /dev/null +++ b/cvat-core/src/logger-storage.ts @@ -0,0 +1,220 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import PluginRegistry from './plugins'; +import serverProxy from './server-proxy'; +import logFactory, { Log } from './log'; +import { LogType } from './enums'; +import { ArgumentError } from './exceptions'; + +const WORKING_TIME_THRESHOLD = 100000; // ms, 1.66 min + +function sleep(ms): Promise { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +interface IgnoreRule { + lastLog: Log | null; + timeThreshold?: number; + ignore: (previousLog: Log, currentPayload: any) => boolean; +} + +class LoggerStorage { + public clientID: string; + public lastLogTime: number; + public workingTime: number; + public collection: Array; + public ignoreRules: Record; + public isActiveChecker: (() => boolean) | null; + public saving: boolean; + + constructor() { + this.clientID = Date.now().toString().substr(-6); + this.lastLogTime = Date.now(); + this.workingTime = 0; + this.collection = []; + this.isActiveChecker = null; + this.saving = false; + this.ignoreRules = { + [LogType.zoomImage]: { + lastLog: null, + timeThreshold: 1000, + ignore(previousLog: Log) { + return (Date.now() - previousLog.time.getTime()) < this.timeThreshold; + }, + }, + [LogType.changeAttribute]: { + lastLog: null, + ignore(previousLog: Log, currentPayload: any) { + return ( + currentPayload.object_id === previousLog.payload.object_id && + currentPayload.id === previousLog.payload.id + ); + }, + }, + }; + } + + protected updateWorkingTime(): void { + if (!this.isActiveChecker || this.isActiveChecker()) { + const lastLogTime = Date.now(); + const diff = lastLogTime - this.lastLogTime; + this.workingTime += diff < WORKING_TIME_THRESHOLD ? diff : 0; + this.lastLogTime = lastLogTime; + } + } + + public async configure(isActiveChecker, activityHelper): Promise { + const result = await PluginRegistry.apiWrapper.call( + this, + LoggerStorage.prototype.configure, + isActiveChecker, + activityHelper, + ); + return result; + } + + public async log(logType: LogType, payload = {}, wait = false): Promise { + const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.log, logType, payload, wait); + return result; + } + + public async save(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.save); + return result; + } +} + +Object.defineProperties(LoggerStorage.prototype.configure, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(isActiveChecker: () => boolean, userActivityCallback: Array) { + if (typeof isActiveChecker !== 'function') { + throw new ArgumentError('isActiveChecker argument must be callable'); + } + + if (!Array.isArray(userActivityCallback)) { + throw new ArgumentError('userActivityCallback argument must be an array'); + } + + this.isActiveChecker = () => !!isActiveChecker(); + userActivityCallback.push(this.updateWorkingTime.bind(this)); + }, + }, +}); + +Object.defineProperties(LoggerStorage.prototype.log, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(logType: LogType, payload: any, wait: boolean) { + if (typeof payload !== 'object') { + throw new ArgumentError('Payload must be an object'); + } + + if (typeof wait !== 'boolean') { + throw new ArgumentError('Wait must be boolean'); + } + + if (logType in this.ignoreRules) { + const ignoreRule = this.ignoreRules[logType]; + const { lastLog } = ignoreRule; + if (lastLog && ignoreRule.ignore(lastLog, payload)) { + lastLog.payload = { + ...lastLog.payload, + ...payload, + }; + + this.updateWorkingTime(); + return ignoreRule.lastLog; + } + } + + const logPayload = { ...payload }; + logPayload.client_id = this.clientID; + if (this.isActiveChecker) { + logPayload.is_active = this.isActiveChecker(); + } + + const log = logFactory(logType, { ...logPayload }); + if (logType in this.ignoreRules) { + this.ignoreRules[logType].lastLog = log; + } + + const pushEvent = (): void => { + this.updateWorkingTime(); + log.validatePayload(); + log.onClose(null); + this.collection.push(log); + }; + + if (log.type === LogType.sendException) { + serverProxy.server.exception(log.dump()).catch(() => { + pushEvent(); + }); + + return log; + } + + if (wait) { + log.onClose(pushEvent); + } else { + pushEvent(); + } + + return log; + }, + }, +}); + +Object.defineProperties(LoggerStorage.prototype.save, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + while (this.saving) { + await sleep(100); + } + + const collectionToSend = [...this.collection]; + const lastLog = this.collection[this.collection.length - 1]; + + const logPayload: any = { + client_id: this.clientID, + working_time: this.workingTime, + }; + + if (this.isActiveChecker) { + logPayload.is_active = this.isActiveChecker(); + } + + if (lastLog && lastLog.type === LogType.sendTaskInfo) { + logPayload.job_id = lastLog.payload.job_id; + logPayload.task_id = lastLog.payload.task_id; + } + + const userActivityLog = logFactory(LogType.sendUserActivity, logPayload); + collectionToSend.push(userActivityLog); + + try { + this.saving = true; + await serverProxy.logs.save(collectionToSend.map((log) => log.dump())); + for (const rule of Object.values(this.ignoreRules)) { + rule.lastLog = null; + } + this.collection = []; + this.workingTime = 0; + this.lastLogTime = Date.now(); + } finally { + this.saving = false; + } + }, + }, +}); + +export default new LoggerStorage(); diff --git a/cvat-core/src/ml-model.js b/cvat-core/src/ml-model.js deleted file mode 100644 index b2089a134436..000000000000 --- a/cvat-core/src/ml-model.js +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (C) 2019-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -/** - * Class representing a machine learning model - * @memberof module:API.cvat.classes - */ -class MLModel { - constructor(data) { - this._id = data.id; - this._name = data.name; - this._labels = data.labels; - this._framework = data.framework; - this._description = data.description; - this._type = data.type; - this._tip = { - message: data.help_message, - gif: data.animated_gif, - }; - this._params = { - canvas: { - minPosVertices: data.min_pos_points, - minNegVertices: data.min_neg_points, - startWithBox: data.startswith_box, - }, - }; - } - - /** - * @returns {string} - * @readonly - */ - get id() { - return this._id; - } - - /** - * @returns {string} - * @readonly - */ - get name() { - return this._name; - } - - /** - * @returns {string[]} - * @readonly - */ - get labels() { - if (Array.isArray(this._labels)) { - return [...this._labels]; - } - - return []; - } - - /** - * @returns {string} - * @readonly - */ - get framework() { - return this._framework; - } - - /** - * @returns {string} - * @readonly - */ - get description() { - return this._description; - } - - /** - * @returns {module:API.cvat.enums.ModelType} - * @readonly - */ - get type() { - return this._type; - } - - /** - * @returns {object} - * @readonly - */ - get params() { - return { - canvas: { ...this._params.canvas }, - }; - } - - /** - * @typedef {Object} MlModelTip - * @property {string} message A short message for a user about the model - * @property {string} gif A gif URL to be shawn to a user as an example - * @returns {MlModelTip} - * @readonly - */ - get tip() { - return { ...this._tip }; - } - - /** - * @callback onRequestStatusChange - * @param {string} event - * @global - */ - /** - * @param {onRequestStatusChange} onRequestStatusChange Set canvas onChangeToolsBlockerState callback - * @returns {void} - */ - set onChangeToolsBlockerState(onChangeToolsBlockerState) { - this._params.canvas.onChangeToolsBlockerState = onChangeToolsBlockerState; - } -} - -module.exports = MLModel; diff --git a/cvat-core/src/ml-model.ts b/cvat-core/src/ml-model.ts new file mode 100644 index 000000000000..13cdd102bc76 --- /dev/null +++ b/cvat-core/src/ml-model.ts @@ -0,0 +1,111 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { ModelType } from './enums'; + +interface ModelAttribute { + name: string; + values: string[]; + input_type: 'select' | 'number' | 'checkbox' | 'radio' | 'text'; +} + +interface ModelParams { + canvas: { + minPosVertices?: number; + minNegVertices?: number; + startWithBox?: boolean; + onChangeToolsBlockerState?: (event: string) => void; + }; +} + +interface ModelTip { + message: string; + gif: string; +} + +interface SerializedModel { + id: string; + name: string; + labels: string[]; + version: number; + attributes: Record; + framework: string; + description: string; + type: ModelType; + help_message?: string; + animated_gif?: string; + min_pos_points?: number; + min_neg_points?: number; + startswith_box?: boolean; +} + +export default class MLModel { + private serialized: SerializedModel; + private changeToolsBlockerStateCallback?: (event: string) => void; + + constructor(serialized: SerializedModel) { + this.serialized = { ...serialized }; + } + + public get id(): string { + return this.serialized.id; + } + + public get name(): string { + return this.serialized.name; + } + + public get labels(): string[] { + return Array.isArray(this.serialized.labels) ? [...this.serialized.labels] : []; + } + + public get version(): number { + return this.serialized.version; + } + + public get attributes(): Record { + return this.serialized.attributes || {}; + } + + public get framework(): string { + return this.serialized.framework; + } + + public get description(): string { + return this.serialized.description; + } + + public get type(): ModelType { + return this.serialized.type; + } + + public get params(): ModelParams { + const result: ModelParams = { + canvas: { + minPosVertices: this.serialized.min_pos_points, + minNegVertices: this.serialized.min_neg_points, + startWithBox: this.serialized.startswith_box, + }, + }; + + if (this.changeToolsBlockerStateCallback) { + result.canvas.onChangeToolsBlockerState = this.changeToolsBlockerStateCallback; + } + + return result; + } + + public get tip(): ModelTip { + return { + message: this.serialized.help_message, + gif: this.serialized.animated_gif, + }; + } + + // Used to set a callback when the tool is blocked in UI + public set onChangeToolsBlockerState(onChangeToolsBlockerState: (event: string) => void) { + this.changeToolsBlockerStateCallback = onChangeToolsBlockerState; + } +} diff --git a/cvat-core/src/object-state.js b/cvat-core/src/object-state.js deleted file mode 100644 index d1fc8784908d..000000000000 --- a/cvat-core/src/object-state.js +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright (C) 2019-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -const { Source } = require('./enums'); - -(() => { - const PluginRegistry = require('./plugins'); - const { ArgumentError } = require('./exceptions'); - - /** - * Class representing a state of an object on a specific frame - * @memberof module:API.cvat.classes - */ - class ObjectState { - /** - * @param {Object} serialized - is an dictionary which contains - * initial information about an ObjectState; - *
Necessary fields: objectType, shapeType, frame, updated, group - *
Optional fields: keyframes, clientID, serverID - *
Optional fields which can be set later: points, zOrder, outside, - * occluded, hidden, attributes, lock, label, color, keyframe, source - */ - constructor(serialized) { - const data = { - label: null, - attributes: {}, - descriptions: [], - - points: null, - rotation: null, - outside: null, - occluded: null, - keyframe: null, - - zOrder: null, - lock: null, - color: null, - hidden: null, - pinned: null, - source: Source.MANUAL, - keyframes: serialized.keyframes, - group: serialized.group, - updated: serialized.updated, - - clientID: serialized.clientID, - serverID: serialized.serverID, - - frame: serialized.frame, - objectType: serialized.objectType, - shapeType: serialized.shapeType, - updateFlags: {}, - }; - - // Shows whether any properties updated since last reset() or interpolation - Object.defineProperty(data.updateFlags, 'reset', { - value: function reset() { - this.label = false; - this.attributes = false; - this.descriptions = false; - - this.points = false; - this.outside = false; - this.occluded = false; - this.keyframe = false; - - this.zOrder = false; - this.pinned = false; - this.lock = false; - this.color = false; - this.hidden = false; - - return reset; - }, - writable: false, - enumerable: false, - }); - - Object.defineProperties( - this, - Object.freeze({ - // Internal property. We don't need document it. - updateFlags: { - get: () => data.updateFlags, - }, - frame: { - /** - * @name frame - * @type {integer} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ - get: () => data.frame, - }, - objectType: { - /** - * @name objectType - * @type {module:API.cvat.enums.ObjectType} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ - get: () => data.objectType, - }, - shapeType: { - /** - * @name shapeType - * @type {module:API.cvat.enums.ObjectShape} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ - get: () => data.shapeType, - }, - source: { - /** - * @name source - * @type {module:API.cvat.enums.Source} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ - get: () => data.source, - }, - clientID: { - /** - * @name clientID - * @type {integer} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ - get: () => data.clientID, - }, - serverID: { - /** - * @name serverID - * @type {integer} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ - get: () => data.serverID, - }, - label: { - /** - * @name shape - * @type {module:API.cvat.classes.Label} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ - get: () => data.label, - set: (labelInstance) => { - data.updateFlags.label = true; - data.label = labelInstance; - }, - }, - color: { - /** - * @name color - * @type {string} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ - get: () => data.color, - set: (color) => { - data.updateFlags.color = true; - data.color = color; - }, - }, - hidden: { - /** - * @name hidden - * @type {boolean} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ - get: () => data.hidden, - set: (hidden) => { - data.updateFlags.hidden = true; - data.hidden = hidden; - }, - }, - points: { - /** - * @name points - * @type {number[]} - * @memberof module:API.cvat.classes.ObjectState - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - */ - get: () => data.points, - set: (points) => { - if (Array.isArray(points)) { - data.updateFlags.points = true; - data.points = [...points]; - } else { - throw new ArgumentError( - 'Points are expected to be an array ' + - `but got ${ - typeof points === 'object' ? points.constructor.name : typeof points - }`, - ); - } - }, - }, - rotation: { - /** - * @name rotation - * @type {number} angle measured by degrees - * @memberof module:API.cvat.classes.ObjectState - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - */ - get: () => data.rotation, - set: (rotation) => { - if (typeof rotation === 'number') { - data.updateFlags.points = true; - data.rotation = rotation; - } else { - throw new ArgumentError( - `Rotation is expected to be a number, but got ${ - typeof rotation === 'object' ? rotation.constructor.name : typeof points - }`, - ); - } - }, - }, - group: { - /** - * Object with short group info { color, id } - * @name group - * @type {object} - * @memberof module:API.cvat.classes.ObjectState - * @instance - * @readonly - */ - get: () => data.group, - }, - zOrder: { - /** - * @name zOrder - * @type {integer | null} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ - get: () => data.zOrder, - set: (zOrder) => { - data.updateFlags.zOrder = true; - data.zOrder = zOrder; - }, - }, - outside: { - /** - * @name outside - * @type {boolean} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ - get: () => data.outside, - set: (outside) => { - data.updateFlags.outside = true; - data.outside = outside; - }, - }, - keyframe: { - /** - * @name keyframe - * @type {boolean} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ - get: () => data.keyframe, - set: (keyframe) => { - data.updateFlags.keyframe = true; - data.keyframe = keyframe; - }, - }, - keyframes: { - /** - * Object of keyframes { first, prev, next, last } - * @name keyframes - * @type {object | null} - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - */ - get: () => { - if (typeof data.keyframes === 'object') { - return { ...data.keyframes }; - } - - return null; - }, - }, - occluded: { - /** - * @name occluded - * @type {boolean} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ - get: () => data.occluded, - set: (occluded) => { - data.updateFlags.occluded = true; - data.occluded = occluded; - }, - }, - lock: { - /** - * @name lock - * @type {boolean} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ - get: () => data.lock, - set: (lock) => { - data.updateFlags.lock = true; - data.lock = lock; - }, - }, - pinned: { - /** - * @name pinned - * @type {boolean | null} - * @memberof module:API.cvat.classes.ObjectState - * @instance - */ - get: () => { - if (typeof data.pinned === 'boolean') { - return data.pinned; - } - - return null; - }, - set: (pinned) => { - data.updateFlags.pinned = true; - data.pinned = pinned; - }, - }, - updated: { - /** - * Timestamp of the latest updated of the object - * @name updated - * @type {number} - * @memberof module:API.cvat.classes.ObjectState - * @instance - * @readonly - */ - get: () => data.updated, - }, - attributes: { - /** - * Object is id:value pairs where "id" is an integer - * attribute identifier and "value" is an attribute value - * @name attributes - * @type {Object} - * @memberof module:API.cvat.classes.ObjectState - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - */ - get: () => data.attributes, - set: (attributes) => { - if (typeof attributes !== 'object') { - throw new ArgumentError( - 'Attributes are expected to be an object ' + - `but got ${ - typeof attributes === 'object' ? - attributes.constructor.name : - typeof attributes - }`, - ); - } - - for (const attrID of Object.keys(attributes)) { - data.updateFlags.attributes = true; - data.attributes[attrID] = attributes[attrID]; - } - }, - }, - descriptions: { - /** - * Additional text information displayed on canvas - * @name descripttions - * @type {string[]} - * @memberof module:API.cvat.classes.ObjectState - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - */ - get: () => [...data.descriptions], - set: (descriptions) => { - if ( - !Array.isArray(descriptions) || - descriptions.some((description) => typeof description !== 'string') - ) { - throw new ArgumentError( - `Descriptions are expected to be an array of strings but got ${data.descriptions}`, - ); - } - - data.updateFlags.descriptions = true; - data.descriptions = [...descriptions]; - }, - }, - }), - ); - - this.label = serialized.label; - this.lock = serialized.lock; - - if ([Source.MANUAL, Source.AUTO].includes(serialized.source)) { - data.source = serialized.source; - } - if (typeof serialized.zOrder === 'number') { - this.zOrder = serialized.zOrder; - } - if (typeof serialized.occluded === 'boolean') { - this.occluded = serialized.occluded; - } - if (typeof serialized.outside === 'boolean') { - this.outside = serialized.outside; - } - if (typeof serialized.keyframe === 'boolean') { - this.keyframe = serialized.keyframe; - } - if (typeof serialized.pinned === 'boolean') { - this.pinned = serialized.pinned; - } - if (typeof serialized.hidden === 'boolean') { - this.hidden = serialized.hidden; - } - if (typeof serialized.color === 'string') { - this.color = serialized.color; - } - if (typeof serialized.rotation === 'number') { - this.rotation = serialized.rotation; - } - if (Array.isArray(serialized.points)) { - this.points = serialized.points; - } - if ( - Array.isArray(serialized.descriptions) && - serialized.descriptions.every((desc) => typeof desc === 'string') - ) { - this.descriptions = serialized.descriptions; - } - if (typeof serialized.attributes === 'object') { - this.attributes = serialized.attributes; - } - - data.updateFlags.reset(); - } - - /** - * Method saves/updates an object state in a collection - * @method save - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @returns {module:API.cvat.classes.ObjectState} updated state of an object - */ - async save() { - const result = await PluginRegistry.apiWrapper.call(this, ObjectState.prototype.save); - return result; - } - - /** - * Method deletes an object from a collection - * @method delete - * @memberof module:API.cvat.classes.ObjectState - * @readonly - * @instance - * @param {integer} frame current frame number - * @param {boolean} [force=false] delete object even if it is locked - * @async - * @returns {boolean} true if object has been deleted - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async delete(frame, force = false) { - const result = await PluginRegistry.apiWrapper.call(this, ObjectState.prototype.delete, frame, force); - return result; - } - } - - // Updates element in collection which contains it - ObjectState.prototype.save.implementation = function () { - if (this.__internal && this.__internal.save) { - return this.__internal.save(); - } - - return this; - }; - - // Delete element from a collection which contains it - ObjectState.prototype.delete.implementation = function (frame, force) { - if (this.__internal && this.__internal.delete) { - if (!Number.isInteger(+frame) || +frame < 0) { - throw new ArgumentError('Frame argument must be a non negative integer'); - } - - return this.__internal.delete(frame, force); - } - - return false; - }; - - module.exports = ObjectState; -})(); diff --git a/cvat-core/src/object-state.ts b/cvat-core/src/object-state.ts new file mode 100644 index 000000000000..958c4103b34f --- /dev/null +++ b/cvat-core/src/object-state.ts @@ -0,0 +1,745 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { Source, ShapeType, ObjectType } from './enums'; +import PluginRegistry from './plugins'; +import { ArgumentError } from './exceptions'; +import { Label } from './labels'; +import { isEnum } from './common'; + +interface UpdateFlags { + label: boolean; + attributes: boolean; + description: boolean; + points: boolean; + rotation: boolean; + outside: boolean; + occluded: boolean; + keyframe: boolean; + zOrder: boolean; + pinned: boolean; + lock: boolean; + color: boolean; + hidden: boolean; + descriptions: boolean; + reset: () => void; +} + +export interface SerializedData { + objectType: ObjectType; + label: Label; + frame: number; + + shapeType?: ShapeType; + clientID?: number; + serverID?: number; + parentID?: number; + lock?: boolean; + hidden?: boolean; + pinned?: boolean; + attributes?: Record; + group?: { color: string; id: number; }; + color?: string; + updated?: number; + source?: Source; + zOrder?: number; + points?: number[]; + occluded?: boolean; + outside?: boolean; + keyframe?: boolean; + rotation?: number; + descriptions?: string[]; + keyframes?: { + prev: number | null; + next: number | null; + first: number | null; + last: number | null; + }; + elements?: SerializedData[]; + __internal: { + save: (objectState: ObjectState) => ObjectState; + delete: (frame: number, force: boolean) => boolean; + }; +} + +/** + * Class representing a state of an object on a specific frame + * @memberof module:API.cvat.classes +*/ +export default class ObjectState { + private readonly __internal: { + save: (objectState: ObjectState) => ObjectState; + delete: (frame: number, force: boolean) => boolean; + }; + + public readonly updateFlags: UpdateFlags; + public readonly frame: number; + public readonly objectType: ObjectType; + public readonly shapeType: ShapeType; + public readonly source: Source; + public readonly clientID: number | null; + public readonly serverID: number | null; + public readonly parentID: number | null; + public readonly updated: number; + public readonly group: { color: string; id: number; } | null; + public readonly keyframes: { + first: number | null; + prev: number | null; + next: number | null; + last: number | null; + } | null; + public label: Label; + public color: string; + public hidden: boolean; + public pinned: boolean; + public points: number[] | null; + public rotation: number | null; + public zOrder: number; + public outside: boolean; + public occluded: boolean; + public keyframe: boolean; + public lock: boolean; + public attributes: Record; + public descriptions: string[]; + public elements: ObjectState[]; + + /** + * @param {Object} serialized - is an dictionary which contains + * initial information about an ObjectState; + *
Necessary fields: objectType, shapeType, frame, updated, group + *
Optional fields: keyframes, clientID, serverID, parentID + *
Optional fields which can be set later: points, zOrder, outside, + * occluded, hidden, attributes, lock, label, color, keyframe, source + */ + constructor(serialized: SerializedData) { + if (!isEnum.call(ObjectType, serialized.objectType)) { + throw new ArgumentError( + `ObjectState must be provided its objectType, got wrong value ${serialized.objectType}`, + ); + } + + if (!(serialized.label instanceof Label)) { + throw new ArgumentError( + `ObjectState must be provided correct Label, got wrong value ${serialized.label}`, + ); + } + + if (!Number.isInteger(serialized.frame)) { + throw new ArgumentError( + `ObjectState must be provided correct frame, got wrong value ${serialized.frame}`, + ); + } + + const updateFlags: UpdateFlags = {} as UpdateFlags; + // Shows whether any properties updated since the object initialization + Object.defineProperty(updateFlags, 'reset', { + value: function reset() { + this.label = false; + this.attributes = false; + this.descriptions = false; + + this.points = false; + this.rotation = false; + this.outside = false; + this.occluded = false; + this.keyframe = false; + + this.zOrder = false; + this.pinned = false; + this.lock = false; + this.color = false; + this.hidden = false; + this.descriptions = false; + + return reset; + }, + writable: false, + enumerable: false, + }); + + const data = { + label: serialized.label, + attributes: {}, + descriptions: [], + elements: Array.isArray(serialized.elements) ? + serialized.elements.map((element) => new ObjectState(element)) : null, + + points: null, + rotation: null, + outside: false, + occluded: false, + keyframe: true, + + zOrder: 0, + lock: serialized.lock || false, + color: '#000000', + hidden: false, + pinned: false, + source: Source.MANUAL, + keyframes: serialized.keyframes || null, + group: serialized.group || null, + updated: serialized.updated || Date.now(), + + clientID: serialized.clientID || null, + serverID: serialized.serverID || null, + parentID: serialized.parentID || null, + + frame: serialized.frame, + objectType: serialized.objectType, + shapeType: serialized.shapeType || null, + updateFlags, + }; + + Object.defineProperties( + this, + Object.freeze({ + // Internal property. We don't need document it. + updateFlags: { + get: () => data.updateFlags, + }, + frame: { + /** + * @name frame + * @type {number} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => data.frame, + }, + objectType: { + /** + * @name objectType + * @type {module:API.cvat.enums.ObjectType} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => data.objectType, + }, + shapeType: { + /** + * @name shapeType + * @type {module:API.cvat.enums.ShapeType} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => data.shapeType, + }, + source: { + /** + * @name source + * @type {module:API.cvat.enums.Source} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => data.source, + }, + clientID: { + /** + * @name clientID + * @type {number} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => data.clientID, + }, + serverID: { + /** + * @name serverID + * @type {number} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => data.serverID, + }, + parentID: { + /** + * @name parentID + * @type {number | null} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => data.parentID, + }, + label: { + /** + * @name shape + * @type {module:API.cvat.classes.Label} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => data.label, + set: (labelInstance) => { + data.updateFlags.label = true; + data.label = labelInstance; + }, + }, + color: { + /** + * @name color + * @type {string} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => data.color, + set: (color) => { + data.updateFlags.color = true; + data.color = color; + }, + }, + hidden: { + /** + * @name hidden + * @type {boolean} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => { + if (data.shapeType === ShapeType.SKELETON) { + return data.elements.every((element: ObjectState) => element.hidden); + } + + return data.hidden; + }, + set: (hidden) => { + if (data.shapeType === ShapeType.SKELETON) { + data.elements.forEach((element: ObjectState) => { + element.hidden = hidden; + }); + } else { + data.updateFlags.hidden = true; + data.hidden = hidden; + } + }, + }, + points: { + /** + * @name points + * @type {number[]} + * @memberof module:API.cvat.classes.ObjectState + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + */ + get: () => { + if (data.shapeType === ShapeType.SKELETON) { + return data.elements.map((element) => element.points).flat(); + } + + if (Array.isArray(data.points)) { + return data.points; + } + + return []; + }, + set: (points) => { + if (!Array.isArray(points) || points.some((coord) => typeof coord !== 'number')) { + throw new ArgumentError( + 'Points are expected to be an array of numbers ' + + `but got ${ + typeof points === 'object' ? points.constructor.name : typeof points + }`, + ); + } + + if (data.shapeType === ShapeType.SKELETON) { + const { points: currentPoints } = this; + if (points.length !== currentPoints.length) { + throw new ArgumentError( + 'Tried to set wrong number of points for a skeleton' + + `(${points.length} vs ${currentPoints.length}})`, + ); + } + + const copy = points; + for (const element of this.elements) { + element.points = copy.splice(0, element.points.length); + } + } else { + data.updateFlags.points = true; + } + + data.points = points.slice(); + }, + }, + rotation: { + /** + * @name rotation + * @description angle measured by degrees + * @type {number} + * @memberof module:API.cvat.classes.ObjectState + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + */ + get: () => data.rotation, + set: (rotation) => { + if (typeof rotation === 'number') { + if (rotation === data.rotation) return; + data.updateFlags.rotation = true; + data.rotation = rotation; + } else { + throw new ArgumentError( + `Rotation is expected to be a number, but got ${ + typeof rotation === 'object' ? rotation.constructor.name : typeof rotation + }`, + ); + } + }, + }, + group: { + /** + * Object with short group info { color, id } + * @name group + * @type {object} + * @memberof module:API.cvat.classes.ObjectState + * @instance + * @readonly + */ + get: () => data.group, + }, + zOrder: { + /** + * @name zOrder + * @type {integer | null} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => data.zOrder, + set: (zOrder) => { + data.updateFlags.zOrder = true; + data.zOrder = zOrder; + }, + }, + outside: { + /** + * @name outside + * @type {boolean} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => { + if (data.shapeType === ShapeType.SKELETON) { + return data.elements.every((el) => el.outside); + } + return data.outside; + }, + set: (outside) => { + if (data.shapeType === ShapeType.SKELETON) { + for (const element of this.elements) { + element.outside = outside; + } + } else { + data.outside = outside; + data.updateFlags.outside = true; + } + }, + }, + keyframe: { + /** + * @name keyframe + * @type {boolean} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => { + if (data.shapeType === ShapeType.SKELETON) { + return data.keyframe || data.elements.some((el) => el.keyframe); + } + + return data.keyframe; + }, + set: (keyframe) => { + if (data.shapeType === ShapeType.SKELETON) { + for (const element of this.elements) { + element.keyframe = keyframe; + } + } + + data.updateFlags.keyframe = true; + data.keyframe = keyframe; + }, + }, + keyframes: { + /** + * Object of keyframes { first, prev, next, last } + * @name keyframes + * @type {object | null} + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + */ + get: () => { + if (typeof data.keyframes === 'object') { + return { ...data.keyframes }; + } + + return null; + }, + }, + occluded: { + /** + * @name occluded + * @type {boolean} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => { + if (data.shapeType === ShapeType.SKELETON) { + return data.elements.every((el) => el.occluded); + } + return data.occluded; + }, + set: (occluded) => { + if (data.shapeType === ShapeType.SKELETON) { + for (const element of this.elements) { + element.occluded = occluded; + } + } else { + data.occluded = occluded; + data.updateFlags.occluded = true; + } + }, + }, + lock: { + /** + * @name lock + * @type {boolean} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => { + if (data.shapeType === ShapeType.SKELETON) { + return data.elements.every((el) => el.lock); + } + return data.lock; + }, + set: (lock) => { + if (data.shapeType === ShapeType.SKELETON) { + for (const element of this.elements) { + element.lock = lock; + } + } else { + data.updateFlags.lock = true; + data.lock = lock; + } + }, + }, + pinned: { + /** + * @name pinned + * @type {boolean | null} + * @memberof module:API.cvat.classes.ObjectState + * @instance + */ + get: () => { + if (typeof data.pinned === 'boolean') { + return data.pinned; + } + + return null; + }, + set: (pinned) => { + data.updateFlags.pinned = true; + data.pinned = pinned; + }, + }, + updated: { + /** + * Timestamp of the latest updated of the object + * @name updated + * @type {number} + * @memberof module:API.cvat.classes.ObjectState + * @instance + * @readonly + */ + get: () => data.updated, + }, + attributes: { + /** + * Object is id:value pairs where "id" is an integer + * attribute identifier and "value" is an attribute value + * @name attributes + * @type {Object} + * @memberof module:API.cvat.classes.ObjectState + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + */ + get: () => data.attributes, + set: (attributes) => { + if (typeof attributes !== 'object') { + throw new ArgumentError( + 'Attributes are expected to be an object ' + + `but got ${ + typeof attributes === 'object' ? + attributes.constructor.name : + typeof attributes + }`, + ); + } + + for (const attrID of Object.keys(attributes)) { + data.updateFlags.attributes = true; + data.attributes[attrID] = attributes[attrID]; + } + }, + }, + descriptions: { + /** + * Additional text information displayed on canvas + * @name descripttions + * @type {string[]} + * @memberof module:API.cvat.classes.ObjectState + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + */ + get: () => [...data.descriptions], + set: (descriptions) => { + if ( + !Array.isArray(descriptions) || + descriptions.some((description) => typeof description !== 'string') + ) { + throw new ArgumentError( + `Descriptions are expected to be an array of strings but got ${data.descriptions}`, + ); + } + + data.updateFlags.descriptions = true; + data.descriptions = [...descriptions]; + }, + }, + elements: { + /** + * Returns a list of object states for compound objects (like skeletons) + * @name elements + * @type {string[]} + * @memberof module:API.cvat.classes.ObjectState + * @throws {module:API.cvat.exceptions.ArgumentError} + * @readonly + * @instance + */ + get: () => { + if (data.elements) { + return [...data.elements]; + } + return []; + }, + }, + }), + ); + + if ([Source.MANUAL, Source.AUTO].includes(serialized.source)) { + data.source = serialized.source; + } + if (typeof serialized.zOrder === 'number') { + data.zOrder = serialized.zOrder; + } + if (typeof serialized.occluded === 'boolean') { + data.occluded = serialized.occluded; + } + if (typeof serialized.outside === 'boolean') { + data.outside = serialized.outside; + } + if (typeof serialized.keyframe === 'boolean') { + data.keyframe = serialized.keyframe; + } + if (typeof serialized.pinned === 'boolean') { + data.pinned = serialized.pinned; + } + if (typeof serialized.hidden === 'boolean') { + data.hidden = serialized.hidden; + } + if (typeof serialized.color === 'string') { + data.color = serialized.color; + } + if (typeof serialized.rotation === 'number') { + data.rotation = serialized.rotation; + } + if (Array.isArray(serialized.points)) { + data.points = serialized.points; + } + if ( + Array.isArray(serialized.descriptions) && + serialized.descriptions.every((desc) => typeof desc === 'string') + ) { + data.descriptions = serialized.descriptions; + } + if (typeof serialized.attributes === 'object') { + data.attributes = serialized.attributes; + } + + data.updateFlags.reset(); + + /* eslint-disable-next-line no-underscore-dangle */ + if (serialized.__internal) { + /* eslint-disable-next-line no-underscore-dangle */ + this.__internal = serialized.__internal; + } + } + + /** + * Method saves/updates an object state in a collection + * @method save + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @returns {module:API.cvat.classes.ObjectState} updated state of an object + */ + async save(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, ObjectState.prototype.save); + return result; + } + + /** + * Method deletes an object from a collection + * @method delete + * @memberof module:API.cvat.classes.ObjectState + * @readonly + * @instance + * @param {integer} frame current frame number + * @param {boolean} [force=false] delete object even if it is locked + * @async + * @returns {boolean} true if object has been deleted + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + async delete(frame, force = false): Promise { + const result = await PluginRegistry.apiWrapper.call(this, ObjectState.prototype.delete, frame, force); + return result; + } +} + +Object.defineProperty(ObjectState.prototype.save, 'implementation', { + value: function save(): ObjectState { + if (this.__internal && this.__internal.save) { + return this.__internal.save(this); + } + + return this; + }, + writable: false, +}); + +Object.defineProperty(ObjectState.prototype.delete, 'implementation', { + value: function remove(frame: number, force: boolean): boolean { + if (this.__internal && this.__internal.delete) { + if (!Number.isInteger(+frame) || +frame < 0) { + throw new ArgumentError('Frame argument must be a non negative integer'); + } + + return this.__internal.delete(frame, force); + } + + return false; + }, + writable: false, +}); diff --git a/cvat-core/src/object-utils.ts b/cvat-core/src/object-utils.ts new file mode 100644 index 000000000000..46ee961b4f69 --- /dev/null +++ b/cvat-core/src/object-utils.ts @@ -0,0 +1,279 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { DataError, ArgumentError } from './exceptions'; +import { Attribute } from './labels'; +import { ShapeType, AttributeType } from './enums'; + +export function checkNumberOfPoints(shapeType: ShapeType, points: number[]): void { + if (shapeType === ShapeType.RECTANGLE) { + if (points.length / 2 !== 2) { + throw new DataError(`Rectangle must have 2 points, but got ${points.length / 2}`); + } + } else if (shapeType === ShapeType.POLYGON) { + if (points.length / 2 < 3) { + throw new DataError(`Polygon must have at least 3 points, but got ${points.length / 2}`); + } + } else if (shapeType === ShapeType.POLYLINE) { + if (points.length / 2 < 2) { + throw new DataError(`Polyline must have at least 2 points, but got ${points.length / 2}`); + } + } else if (shapeType === ShapeType.POINTS) { + if (points.length / 2 < 1) { + throw new DataError(`Points must have at least 1 points, but got ${points.length / 2}`); + } + } else if (shapeType === ShapeType.CUBOID) { + if (points.length / 2 !== 8) { + throw new DataError(`Cuboid must have 8 points, but got ${points.length / 2}`); + } + } else if (shapeType === ShapeType.ELLIPSE) { + if (points.length / 2 !== 2) { + throw new DataError(`Ellipse must have 1 point, rx and ry but got ${points.toString()}`); + } + } else if (shapeType === ShapeType.MASK) { + const [left, top, right, bottom] = points.slice(-4); + const [width, height] = [right - left, bottom - top]; + if (width < 0 || !Number.isInteger(width) || height < 0 || !Number.isInteger(height)) { + throw new DataError(`Mask width, height must be positive integers, but got ${width}x${height}`); + } + + if (points.length !== width * height + 4) { + throw new DataError(`Points array must have length ${width}x${height} + 4, got ${points.length}`); + } + } else { + throw new ArgumentError(`Unknown value of shapeType has been received ${shapeType}`); + } +} + +export function attrsAsAnObject(attributes: Attribute[]): Record { + return attributes.reduce((accumulator, value) => { + accumulator[value.id] = value; + return accumulator; + }, {}); +} + +export function findAngleDiff(rightAngle: number, leftAngle: number): number { + let angleDiff = rightAngle - leftAngle; + angleDiff = ((angleDiff + 180) % 360) - 180; + if (Math.abs(angleDiff) >= 180) { + // if the main arc is bigger than 180, go another arc + // to find it, just substract absolute value from 360 and inverse sign + angleDiff = 360 - Math.abs(angleDiff) * Math.sign(angleDiff) * -1; + } + return angleDiff; +} + +export function checkShapeArea(shapeType: ShapeType, points: number[]): boolean { + const MIN_SHAPE_LENGTH = 3; + const MIN_SHAPE_AREA = 9; + const MIN_MASK_SHAPE_AREA = 1; + + if (shapeType === ShapeType.POINTS) { + return true; + } + + if (shapeType === ShapeType.MASK) { + const [left, top, right, bottom] = points.slice(-4); + const area = (right - left + 1) * (bottom - top + 1); + return area >= MIN_MASK_SHAPE_AREA; + } + + if (shapeType === ShapeType.ELLIPSE) { + const [cx, cy, rightX, topY] = points; + const [rx, ry] = [rightX - cx, cy - topY]; + return rx * ry * Math.PI > MIN_SHAPE_AREA; + } + + let xmin = Number.MAX_SAFE_INTEGER; + let xmax = Number.MIN_SAFE_INTEGER; + let ymin = Number.MAX_SAFE_INTEGER; + let ymax = Number.MIN_SAFE_INTEGER; + + for (let i = 0; i < points.length - 1; i += 2) { + xmin = Math.min(xmin, points[i]); + xmax = Math.max(xmax, points[i]); + ymin = Math.min(ymin, points[i + 1]); + ymax = Math.max(ymax, points[i + 1]); + } + + if (shapeType === ShapeType.POLYLINE) { + const length = Math.max(xmax - xmin, ymax - ymin); + return length >= MIN_SHAPE_LENGTH; + } + + const area = (xmax - xmin) * (ymax - ymin); + return area >= MIN_SHAPE_AREA; +} + +export function rotatePoint(x: number, y: number, angle: number, cx = 0, cy = 0): number[] { + const sin = Math.sin((angle * Math.PI) / 180); + const cos = Math.cos((angle * Math.PI) / 180); + const rotX = (x - cx) * cos - (y - cy) * sin + cx; + const rotY = (y - cy) * cos + (x - cx) * sin + cy; + return [rotX, rotY]; +} + +export function computeWrappingBox(points: number[], margin = 0): { + xtl: number; + ytl: number; + xbr: number; + ybr: number; + x: number; + y: number; + width: number; + height: number; +} { + let xtl = Number.MAX_SAFE_INTEGER; + let ytl = Number.MAX_SAFE_INTEGER; + let xbr = Number.MIN_SAFE_INTEGER; + let ybr = Number.MIN_SAFE_INTEGER; + + for (let i = 0; i < points.length; i += 2) { + const [x, y] = [points[i], points[i + 1]]; + xtl = Math.min(xtl, x); + ytl = Math.min(ytl, y); + xbr = Math.max(xbr, x); + ybr = Math.max(ybr, y); + } + + const box = { + xtl: xtl - margin, + ytl: ytl - margin, + xbr: xbr + margin, + ybr: ybr + margin, + }; + + return { + ...box, + x: box.xtl, + y: box.ytl, + width: box.xbr - box.xtl, + height: box.ybr - box.ytl, + }; +} + +export function validateAttributeValue(value: string, attr: Attribute): boolean { + const { values } = attr; + const type = attr.inputType; + + if (typeof value !== 'string') { + throw new ArgumentError(`Attribute value is expected to be string, but got ${typeof value}`); + } + + if (type === AttributeType.NUMBER) { + return +value >= +values[0] && +value <= +values[1]; + } + + if (type === AttributeType.CHECKBOX) { + return ['true', 'false'].includes(value.toLowerCase()); + } + + if (type === AttributeType.TEXT) { + return true; + } + + return values.includes(value); +} + +export function truncateMask(points: number[], _: number, width: number, height: number): number[] { + const [currentLeft, currentTop, currentRight, currentBottom] = points.slice(-4); + const [currentWidth, currentHeight] = [currentRight - currentLeft + 1, currentBottom - currentTop + 1]; + + let left = width; + let right = 0; + let top = height; + let bottom = 0; + let atLeastOnePixel = false; + const truncatedPoints = []; + + for (let y = 0; y < currentHeight; y++) { + const absY = y + currentTop; + + for (let x = 0; x < currentWidth; x++) { + const absX = x + currentLeft; + const offset = y * currentWidth + x; + + if (absX >= width || absY >= height || absX < 0 || absY < 0) { + points[offset] = 0; + } + + if (points[offset]) { + atLeastOnePixel = true; + left = Math.min(left, absX); + top = Math.min(top, absY); + right = Math.max(right, absX); + bottom = Math.max(bottom, absY); + } + } + } + + if (!atLeastOnePixel) { + // if mask is empty, set its size as 0 + left = 0; + top = 0; + } + + // TODO: check corner case when right = left = 0 + const [newWidth, newHeight] = [right - left + 1, bottom - top + 1]; + for (let y = 0; y < newHeight; y++) { + for (let x = 0; x < newWidth; x++) { + const leftDiff = left - currentLeft; + const topDiff = top - currentTop; + const offset = (y + topDiff) * currentWidth + (x + leftDiff); + truncatedPoints.push(points[offset]); + } + } + + truncatedPoints.push(left, top, right, bottom); + if (!checkShapeArea(ShapeType.MASK, truncatedPoints)) { + return []; + } + + return truncatedPoints; +} + +export function mask2Rle(mask: number[]): number[] { + return mask.reduce((acc, val, idx, arr) => { + if (idx > 0) { + if (arr[idx - 1] === val) { + acc[acc.length - 1] += 1; + } else { + acc.push(1); + } + + return acc; + } + + if (val > 0) { + // 0, 0, 0, 1 => [3, 1] + // 1, 1, 0, 0 => [0, 2, 2] + acc.push(0, 1); + } else { + acc.push(1); + } + + return acc; + }, []); +} + +export function rle2Mask(rle: number[], width: number, height: number): number[] { + const decoded = Array(width * height).fill(0); + const { length } = rle; + let decodedIdx = 0; + let value = 0; + let i = 0; + + while (i < length) { + let count = rle[i]; + while (count > 0) { + decoded[decodedIdx] = value; + decodedIdx++; + count--; + } + i++; + value = Math.abs(value - 1); + } + + return decoded; +} diff --git a/cvat-core/src/organization.js b/cvat-core/src/organization.js deleted file mode 100644 index e116415f5fd3..000000000000 --- a/cvat-core/src/organization.js +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright (C) 2021-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -const { checkObjectType, isEnum } = require('./common'); -const config = require('./config'); -const { MembershipRole } = require('./enums'); -const { ArgumentError, ServerError } = require('./exceptions'); -const PluginRegistry = require('./plugins'); -const serverProxy = require('./server-proxy'); -const User = require('./user'); - -/** - * Class representing an organization - * @memberof module:API.cvat.classes - */ -class Organization { - /** - * @param {object} initialData - Object which is used for initialization - *
It must contains keys: - *
  • slug - - *
    It can contains keys: - *
  • name - *
  • description - *
  • owner - *
  • created_date - *
  • updated_date - *
  • contact - */ - constructor(initialData) { - const data = { - id: undefined, - slug: undefined, - name: undefined, - description: undefined, - created_date: undefined, - updated_date: undefined, - owner: undefined, - contact: undefined, - }; - - for (const prop of Object.keys(data)) { - if (prop in initialData) { - data[prop] = initialData[prop]; - } - } - - if (data.owner) data.owner = new User(data.owner); - - checkObjectType('slug', data.slug, 'string'); - if (typeof data.name !== 'undefined') { - checkObjectType('name', data.name, 'string'); - } - - if (typeof data.description !== 'undefined') { - checkObjectType('description', data.description, 'string'); - } - - if (typeof data.id !== 'undefined') { - checkObjectType('id', data.id, 'number'); - } - - if (typeof data.contact !== 'undefined') { - checkObjectType('contact', data.contact, 'object'); - for (const prop in data.contact) { - if (typeof data.contact[prop] !== 'string') { - throw ArgumentError(`Contact fields must be strings, tried to set ${typeof data.contact[prop]}`); - } - } - } - - if (typeof data.owner !== 'undefined' && data.owner !== null) { - checkObjectType('owner', data.owner, null, User); - } - - Object.defineProperties(this, { - id: { - get: () => data.id, - }, - slug: { - get: () => data.slug, - }, - name: { - get: () => data.name, - set: (name) => { - if (typeof name !== 'string') { - throw ArgumentError(`Name property must be a string, tried to set ${typeof description}`); - } - data.name = name; - }, - }, - description: { - get: () => data.description, - set: (description) => { - if (typeof description !== 'string') { - throw ArgumentError( - `Description property must be a string, tried to set ${typeof description}`, - ); - } - data.description = description; - }, - }, - contact: { - get: () => ({ ...data.contact }), - set: (contact) => { - if (typeof contact !== 'object') { - throw ArgumentError(`Contact property must be an object, tried to set ${typeof contact}`); - } - for (const prop in contact) { - if (typeof contact[prop] !== 'string') { - throw ArgumentError(`Contact fields must be strings, tried to set ${typeof contact[prop]}`); - } - } - data.contact = { ...contact }; - }, - }, - owner: { - get: () => data.owner, - }, - createdDate: { - get: () => data.created_date, - }, - updatedDate: { - get: () => data.updated_date, - }, - }); - } - - /** - * Method updates organization data if it was created before, or creates a new organization - * @method save - * @returns {module:API.cvat.classes.Organization} - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async save() { - const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.save); - return result; - } - - /** - * Method returns paginatable list of organization members - * @method save - * @returns {module:API.cvat.classes.Organization} - * @param page page number - * @param page_size number of results per page - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async members(page = 1, page_size = 10) { - const result = await PluginRegistry.apiWrapper.call( - this, - Organization.prototype.members, - this.slug, - page, - page_size, - ); - return result; - } - - /** - * Method removes the organization - * @method remove - * @returns {module:API.cvat.classes.Organization} - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async remove() { - const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.remove); - return result; - } - - /** - * Method invites new members by email - * @method invite - * @returns {module:API.cvat.classes.Organization} - * @param {string} email - * @param {string} role - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async invite(email, role) { - const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.invite, email, role); - return result; - } - - /** - * Method allows a user to get out from an organization - * The difference between deleteMembership is that membershipId is unknown in this case - * @method leave - * @returns {module:API.cvat.classes.Organization} - * @memberof module:API.cvat.classes.Organization - * @param {module:API.cvat.classes.User} user - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async leave(user) { - const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.leave, user); - return result; - } - - /** - * Method allows to change a membership role - * @method updateMembership - * @returns {module:API.cvat.classes.Organization} - * @param {number} membershipId - * @param {string} role - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async updateMembership(membershipId, role) { - const result = await PluginRegistry.apiWrapper.call( - this, - Organization.prototype.updateMembership, - membershipId, - role, - ); - return result; - } - - /** - * Method allows to kick a user from an organization - * @method deleteMembership - * @returns {module:API.cvat.classes.Organization} - * @param {number} membershipId - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async deleteMembership(membershipId) { - const result = await PluginRegistry.apiWrapper.call( - this, - Organization.prototype.deleteMembership, - membershipId, - ); - return result; - } -} - -Organization.prototype.save.implementation = async function () { - if (typeof this.id === 'number') { - const organizationData = { - name: this.name || this.slug, - description: this.description, - contact: this.contact, - }; - - const result = await serverProxy.organizations.update(this.id, organizationData); - return new Organization(result); - } - - const organizationData = { - slug: this.slug, - name: this.name || this.slug, - description: this.description, - contact: this.contact, - }; - - const result = await serverProxy.organizations.create(organizationData); - return new Organization(result); -}; - -Organization.prototype.members.implementation = async function (orgSlug, page, pageSize) { - checkObjectType('orgSlug', orgSlug, 'string'); - checkObjectType('page', page, 'number'); - checkObjectType('pageSize', pageSize, 'number'); - - const result = await serverProxy.organizations.members(orgSlug, page, pageSize); - await Promise.all( - result.results.map((membership) => { - const { invitation } = membership; - membership.user = new User(membership.user); - if (invitation) { - return serverProxy.organizations - .invitation(invitation) - .then((invitationData) => { - membership.invitation = invitationData; - }) - .catch(() => { - membership.invitation = null; - }); - } - - return Promise.resolve(); - }), - ); - - result.results.count = result.count; - return result.results; -}; - -Organization.prototype.remove.implementation = async function () { - if (typeof this.id === 'number') { - await serverProxy.organizations.delete(this.id); - config.organizationID = null; - } -}; - -Organization.prototype.invite.implementation = async function (email, role) { - checkObjectType('email', email, 'string'); - if (!isEnum.bind(MembershipRole)(role)) { - throw new ArgumentError(`Role must be one of: ${Object.values(MembershipRole).toString()}`); - } - - if (typeof this.id === 'number') { - await serverProxy.organizations.invite(this.id, { email, role }); - } -}; - -Organization.prototype.updateMembership.implementation = async function (membershipId, role) { - checkObjectType('membershipId', membershipId, 'number'); - if (!isEnum.bind(MembershipRole)(role)) { - throw new ArgumentError(`Role must be one of: ${Object.values(MembershipRole).toString()}`); - } - - if (typeof this.id === 'number') { - await serverProxy.organizations.updateMembership(membershipId, { role }); - } -}; - -Organization.prototype.deleteMembership.implementation = async function (membershipId) { - checkObjectType('membershipId', membershipId, 'number'); - if (typeof this.id === 'number') { - await serverProxy.organizations.deleteMembership(membershipId); - } -}; - -Organization.prototype.leave.implementation = async function (user) { - checkObjectType('user', user, null, User); - if (typeof this.id === 'number') { - const result = await serverProxy.organizations.members(this.slug, 1, 10, { - filter: JSON.stringify({ - and: [{ - '==': [{ var: 'user' }, user.id], - }], - }), - }); - const [membership] = result.results; - if (!membership) { - throw new ServerError(`Could not find membership for user ${user.username} in organization ${this.slug}`); - } - await serverProxy.organizations.deleteMembership(membership.id); - } -}; - -module.exports = Organization; diff --git a/cvat-core/src/organization.ts b/cvat-core/src/organization.ts new file mode 100644 index 000000000000..84856bf096ed --- /dev/null +++ b/cvat-core/src/organization.ts @@ -0,0 +1,362 @@ +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { checkObjectType, isEnum } from './common'; +import config from './config'; +import { MembershipRole } from './enums'; +import { ArgumentError, ServerError } from './exceptions'; +import PluginRegistry from './plugins'; +import serverProxy from './server-proxy'; +import User from './user'; + +interface RawOrganizationData { + id?: number, + slug?: string, + name?: string, + description?: string, + created_date?: string, + updated_date?: string, + owner?: any, + contact?: OrganizationContact, +} + +interface OrganizationContact { + email?: string; + location?: string; + phoneNumber?: string +} + +interface Membership { + user: User; + is_active: boolean; + joined_date: string; + role: MembershipRole; + invitation: { + created_date: string; + owner: User; + } | null; +} + +export default class Organization { + public readonly id: number; + public readonly slug: string; + public readonly createdDate: string; + public readonly updatedDate: string; + public readonly owner: User; + public contact: OrganizationContact; + public name: string; + public description: string; + + constructor(initialData: RawOrganizationData) { + const data: RawOrganizationData = { + id: undefined, + slug: undefined, + name: undefined, + description: undefined, + created_date: undefined, + updated_date: undefined, + owner: undefined, + contact: undefined, + }; + + for (const prop of Object.keys(data)) { + if (prop in initialData) { + data[prop] = initialData[prop]; + } + } + + if (data.owner) data.owner = new User(data.owner); + + checkObjectType('slug', data.slug, 'string'); + if (typeof data.name !== 'undefined') { + checkObjectType('name', data.name, 'string'); + } + + if (typeof data.description !== 'undefined') { + checkObjectType('description', data.description, 'string'); + } + + if (typeof data.id !== 'undefined') { + checkObjectType('id', data.id, 'number'); + } + + if (typeof data.contact !== 'undefined') { + checkObjectType('contact', data.contact, 'object'); + for (const prop in data.contact) { + if (typeof data.contact[prop] !== 'string') { + throw new ArgumentError( + `Contact fields must be strings,tried to set ${typeof data.contact[prop]}`, + ); + } + } + } + + if (typeof data.owner !== 'undefined' && data.owner !== null) { + checkObjectType('owner', data.owner, null, User); + } + + Object.defineProperties(this, { + id: { + get: () => data.id, + }, + slug: { + get: () => data.slug, + }, + name: { + get: () => data.name, + set: (name) => { + if (typeof name !== 'string') { + throw new ArgumentError(`Name property must be a string, tried to set ${typeof name}`); + } + data.name = name; + }, + }, + description: { + get: () => data.description, + set: (description) => { + if (typeof description !== 'string') { + throw new ArgumentError( + `Description property must be a string, tried to set ${typeof description}`, + ); + } + data.description = description; + }, + }, + contact: { + get: () => ({ ...data.contact }), + set: (contact) => { + if (typeof contact !== 'object') { + throw new ArgumentError(`Contact property must be an object, tried to set ${typeof contact}`); + } + for (const prop in contact) { + if (typeof contact[prop] !== 'string') { + throw new ArgumentError( + `Contact fields must be strings, tried to set ${typeof contact[prop]}`, + ); + } + } + data.contact = { ...contact }; + }, + }, + owner: { + get: () => data.owner, + }, + createdDate: { + get: () => data.created_date, + }, + updatedDate: { + get: () => data.updated_date, + }, + }); + } + + // Method updates organization data if it was created before, or creates a new organization + public async save(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.save); + return result; + } + + // Method returns paginatable list of organization members + public async members(page = 1, page_size = 10): Promise { + const result = await PluginRegistry.apiWrapper.call( + this, + Organization.prototype.members, + this.slug, + page, + page_size, + ); + return result; + } + + // Method removes the organization + public async remove(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.remove); + return result; + } + + // Method invites new members by email + public async invite(email: string, role: MembershipRole): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.invite, email, role); + return result; + } + + // Method allows a user to get out from an organization + // The difference between deleteMembership is that membershipId is unknown in this case + public async leave(user: User): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.leave, user); + return result; + } + + // Method allows to change a membership role + public async updateMembership(membershipId: number, role: MembershipRole): Promise { + const result = await PluginRegistry.apiWrapper.call( + this, + Organization.prototype.updateMembership, + membershipId, + role, + ); + return result; + } + + // Method allows to kick a user from an organization + public async deleteMembership(membershipId: number): Promise { + const result = await PluginRegistry.apiWrapper.call( + this, + Organization.prototype.deleteMembership, + membershipId, + ); + return result; + } +} + +Object.defineProperties(Organization.prototype.save, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + if (typeof this.id === 'number') { + const organizationData = { + name: this.name || this.slug, + description: this.description, + contact: this.contact, + }; + + const result = await serverProxy.organizations.update(this.id, organizationData); + return new Organization(result); + } + + const organizationData = { + slug: this.slug, + name: this.name || this.slug, + description: this.description, + contact: this.contact, + }; + + const result = await serverProxy.organizations.create(organizationData); + return new Organization(result); + }, + }, +}); + +Object.defineProperties(Organization.prototype.members, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(orgSlug: string, page: number, pageSize: number) { + checkObjectType('orgSlug', orgSlug, 'string'); + checkObjectType('page', page, 'number'); + checkObjectType('pageSize', pageSize, 'number'); + + const result = await serverProxy.organizations.members(orgSlug, page, pageSize); + await Promise.all( + result.results.map((membership) => { + const { invitation } = membership; + membership.user = new User(membership.user); + if (invitation) { + return serverProxy.organizations + .invitation(invitation) + .then((invitationData) => { + membership.invitation = invitationData; + }) + .catch(() => { + membership.invitation = null; + }); + } + + return Promise.resolve(); + }), + ); + + result.results.count = result.count; + return result.results; + }, + }, +}); + +Object.defineProperties(Organization.prototype.remove, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + if (typeof this.id === 'number') { + await serverProxy.organizations.delete(this.id); + config.organizationID = null; + } + }, + }, +}); + +Object.defineProperties(Organization.prototype.invite, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(email: string, role: MembershipRole) { + checkObjectType('email', email, 'string'); + if (!isEnum.bind(MembershipRole)(role)) { + throw new ArgumentError(`Role must be one of: ${Object.values(MembershipRole).toString()}`); + } + + if (typeof this.id === 'number') { + await serverProxy.organizations.invite(this.id, { email, role }); + } + }, + }, +}); + +Object.defineProperties(Organization.prototype.updateMembership, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(membershipId: number, role: MembershipRole) { + checkObjectType('membershipId', membershipId, 'number'); + if (!isEnum.bind(MembershipRole)(role)) { + throw new ArgumentError(`Role must be one of: ${Object.values(MembershipRole).toString()}`); + } + + if (typeof this.id === 'number') { + await serverProxy.organizations.updateMembership(membershipId, { role }); + } + }, + }, +}); + +Object.defineProperties(Organization.prototype.deleteMembership, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(membershipId: number) { + checkObjectType('membershipId', membershipId, 'number'); + if (typeof this.id === 'number') { + await serverProxy.organizations.deleteMembership(membershipId); + } + }, + }, +}); + +Object.defineProperties(Organization.prototype.leave, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(user: User) { + checkObjectType('user', user, null, User); + if (typeof this.id === 'number') { + const result = await serverProxy.organizations.members(this.slug, 1, 10, { + filter: JSON.stringify({ + and: [{ + '==': [{ var: 'user' }, user.id], + }], + }), + }); + const [membership] = result.results; + if (!membership) { + throw new ServerError( + `Could not find membership for user ${user.username} in organization ${this.slug}`, + ); + } + await serverProxy.organizations.deleteMembership(membership.id); + } + }, + }, +}); diff --git a/cvat-core/src/plugins.js b/cvat-core/src/plugins.js deleted file mode 100644 index e858bdec8afd..000000000000 --- a/cvat-core/src/plugins.js +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (C) 2019-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const { PluginError } = require('./exceptions'); - - const plugins = []; - class PluginRegistry { - static async apiWrapper(wrappedFunc, ...args) { - // I have to optimize the wrapper - const pluginList = await PluginRegistry.list(); - for (const plugin of pluginList) { - const pluginDecorators = plugin.functions.filter((obj) => obj.callback === wrappedFunc)[0]; - if (pluginDecorators && pluginDecorators.enter) { - try { - await pluginDecorators.enter.call(this, plugin, ...args); - } catch (exception) { - if (exception instanceof PluginError) { - throw exception; - } else { - throw new PluginError(`Exception in plugin ${plugin.name}: ${exception.toString()}`); - } - } - } - } - - let result = await wrappedFunc.implementation.call(this, ...args); - - for (const plugin of pluginList) { - const pluginDecorators = plugin.functions.filter((obj) => obj.callback === wrappedFunc)[0]; - if (pluginDecorators && pluginDecorators.leave) { - try { - result = await pluginDecorators.leave.call(this, plugin, result, ...args); - } catch (exception) { - if (exception instanceof PluginError) { - throw exception; - } else { - throw new PluginError(`Exception in plugin ${plugin.name}: ${exception.toString()}`); - } - } - } - } - - return result; - } - - // Called with cvat context - static async register(plug) { - const functions = []; - - if (typeof plug !== 'object') { - throw new PluginError(`Plugin should be an object, but got "${typeof plug}"`); - } - - if (!('name' in plug) || typeof plug.name !== 'string') { - throw new PluginError('Plugin must contain a "name" field and it must be a string'); - } - - if (!('description' in plug) || typeof plug.description !== 'string') { - throw new PluginError('Plugin must contain a "description" field and it must be a string'); - } - - if ('functions' in plug) { - throw new PluginError('Plugin must not contain a "functions" field'); - } - - function traverse(plugin, api) { - const decorator = {}; - for (const key in plugin) { - if (Object.prototype.hasOwnProperty.call(plugin, key)) { - if (typeof plugin[key] === 'object') { - if (Object.prototype.hasOwnProperty.call(api, key)) { - traverse(plugin[key], api[key]); - } - } else if ( - ['enter', 'leave'].includes(key) && - typeof api === 'function' && - typeof (plugin[key] === 'function') - ) { - decorator.callback = api; - decorator[key] = plugin[key]; - } - } - } - - if (Object.keys(decorator).length) { - functions.push(decorator); - } - } - - traverse(plug, { cvat: this }); - - Object.defineProperty(plug, 'functions', { - value: functions, - writable: false, - }); - - plugins.push(plug); - } - - static async list() { - return plugins; - } - } - - module.exports = PluginRegistry; -})(); diff --git a/cvat-core/src/plugins.ts b/cvat-core/src/plugins.ts new file mode 100644 index 000000000000..1a676a739e13 --- /dev/null +++ b/cvat-core/src/plugins.ts @@ -0,0 +1,104 @@ +// Copyright (C) 2019-2022 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { PluginError } from './exceptions'; + +const plugins = []; +export default class PluginRegistry { + static async apiWrapper(wrappedFunc, ...args) { + // I have to optimize the wrapper + const pluginList = await PluginRegistry.list(); + for (const plugin of pluginList) { + const pluginDecorators = plugin.functions.filter((obj) => obj.callback === wrappedFunc)[0]; + if (pluginDecorators && pluginDecorators.enter) { + try { + await pluginDecorators.enter.call(this, plugin, ...args); + } catch (exception) { + if (exception instanceof PluginError) { + throw exception; + } else { + throw new PluginError(`Exception in plugin ${plugin.name}: ${exception.toString()}`); + } + } + } + } + + let result = await wrappedFunc.implementation.call(this, ...args); + + for (const plugin of pluginList) { + const pluginDecorators = plugin.functions.filter((obj) => obj.callback === wrappedFunc)[0]; + if (pluginDecorators && pluginDecorators.leave) { + try { + result = await pluginDecorators.leave.call(this, plugin, result, ...args); + } catch (exception) { + if (exception instanceof PluginError) { + throw exception; + } else { + throw new PluginError(`Exception in plugin ${plugin.name}: ${exception.toString()}`); + } + } + } + } + + return result; + } + + // Called with cvat context + static async register(plug) { + const functions = []; + + if (typeof plug !== 'object') { + throw new PluginError(`Plugin should be an object, but got "${typeof plug}"`); + } + + if (!('name' in plug) || typeof plug.name !== 'string') { + throw new PluginError('Plugin must contain a "name" field and it must be a string'); + } + + if (!('description' in plug) || typeof plug.description !== 'string') { + throw new PluginError('Plugin must contain a "description" field and it must be a string'); + } + + if ('functions' in plug) { + throw new PluginError('Plugin must not contain a "functions" field'); + } + + function traverse(plugin, api) { + const decorator = {}; + for (const key in plugin) { + if (Object.prototype.hasOwnProperty.call(plugin, key)) { + if (typeof plugin[key] === 'object') { + if (Object.prototype.hasOwnProperty.call(api, key)) { + traverse(plugin[key], api[key]); + } + } else if ( + ['enter', 'leave'].includes(key) && + typeof api === 'function' && + typeof (plugin[key] === 'function') + ) { + decorator.callback = api; + decorator[key] = plugin[key]; + } + } + } + + if (Object.keys(decorator).length) { + functions.push(decorator); + } + } + + traverse(plug, { cvat: this }); + + Object.defineProperty(plug, 'functions', { + value: functions, + writable: false, + }); + + plugins.push(plug); + } + + static async list() { + return plugins; + } +} diff --git a/cvat-core/src/project-implementation.js b/cvat-core/src/project-implementation.js deleted file mode 100644 index 92908617785a..000000000000 --- a/cvat-core/src/project-implementation.js +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (C) 2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const serverProxy = require('./server-proxy'); - const { getPreview } = require('./frames'); - - const { Project } = require('./project'); - const { exportDataset, importDataset } = require('./annotations'); - - function implementProject(projectClass) { - projectClass.prototype.save.implementation = async function () { - if (typeof this.id !== 'undefined') { - const projectData = this._updateTrigger.getUpdated(this, { - bugTracker: 'bug_tracker', - trainingProject: 'training_project', - assignee: 'assignee_id', - }); - if (projectData.assignee_id) { - projectData.assignee_id = projectData.assignee_id.id; - } - if (projectData.labels) { - projectData.labels = projectData.labels.map((el) => el.toJSON()); - } - - await serverProxy.projects.save(this.id, projectData); - this._updateTrigger.reset(); - return this; - } - - // initial creating - const projectSpec = { - name: this.name, - labels: this.labels.map((el) => el.toJSON()), - }; - - if (this.bugTracker) { - projectSpec.bug_tracker = this.bugTracker; - } - - if (this.trainingProject) { - projectSpec.training_project = this.trainingProject; - } - - const project = await serverProxy.projects.create(projectSpec); - return new Project(project); - }; - - projectClass.prototype.delete.implementation = async function () { - const result = await serverProxy.projects.delete(this.id); - return result; - }; - - projectClass.prototype.preview.implementation = async function () { - if (!this._internalData.task_ids.length) { - return ''; - } - const frameData = await getPreview(this._internalData.task_ids[0]); - return frameData; - }; - - projectClass.prototype.annotations.exportDataset.implementation = async function ( - format, - saveImages, - customName, - ) { - const result = exportDataset(this, format, customName, saveImages); - return result; - }; - projectClass.prototype.annotations.importDataset.implementation = async function ( - format, - file, - updateStatusCallback, - ) { - return importDataset(this, format, file, updateStatusCallback); - }; - - projectClass.prototype.backup.implementation = async function () { - const result = await serverProxy.projects.backupProject(this.id); - return result; - }; - - projectClass.restore.implementation = async function (file) { - const result = await serverProxy.projects.restoreProject(file); - return result.id; - }; - - return projectClass; - } - - module.exports = implementProject; -})(); diff --git a/cvat-core/src/project-implementation.ts b/cvat-core/src/project-implementation.ts new file mode 100644 index 000000000000..bfdbdbe85f92 --- /dev/null +++ b/cvat-core/src/project-implementation.ts @@ -0,0 +1,111 @@ +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { Storage } from './storage'; + +const serverProxy = require('./server-proxy').default; +const { getPreview } = require('./frames'); + +const Project = require('./project').default; +const { exportDataset, importDataset } = require('./annotations'); + +export default function implementProject(projectClass) { + projectClass.prototype.save.implementation = async function () { + if (typeof this.id !== 'undefined') { + const projectData = this._updateTrigger.getUpdated(this, { + bugTracker: 'bug_tracker', + trainingProject: 'training_project', + assignee: 'assignee_id', + }); + if (projectData.assignee_id) { + projectData.assignee_id = projectData.assignee_id.id; + } + if (projectData.labels) { + projectData.labels = projectData.labels.map((el) => el.toJSON()); + } + + await serverProxy.projects.save(this.id, projectData); + this._updateTrigger.reset(); + return this; + } + + // initial creating + const projectSpec: any = { + name: this.name, + labels: this.labels.map((el) => el.toJSON()), + }; + + if (this.bugTracker) { + projectSpec.bug_tracker = this.bugTracker; + } + + if (this.trainingProject) { + projectSpec.training_project = this.trainingProject; + } + + if (this.targetStorage) { + projectSpec.target_storage = this.targetStorage.toJSON(); + } + + if (this.sourceStorage) { + projectSpec.source_storage = this.sourceStorage.toJSON(); + } + + const project = await serverProxy.projects.create(projectSpec); + return new Project(project); + }; + + projectClass.prototype.delete.implementation = async function () { + const result = await serverProxy.projects.delete(this.id); + return result; + }; + + projectClass.prototype.preview.implementation = async function () { + if (!this._internalData.task_ids.length) { + return ''; + } + const frameData = await getPreview(this._internalData.task_ids[0]); + return frameData; + }; + + projectClass.prototype.annotations.exportDataset.implementation = async function ( + format: string, + saveImages: boolean, + useDefaultSettings: boolean, + targetStorage: Storage, + customName?: string, + ) { + const result = exportDataset(this, format, saveImages, useDefaultSettings, targetStorage, customName); + return result; + }; + projectClass.prototype.annotations.importDataset.implementation = async function ( + format: string, + useDefaultSettings: boolean, + sourceStorage: Storage, + file: File | string, + options?: { + convMaskToPoly?: boolean, + updateStatusCallback?: (s: string, n: number) => void, + }, + ) { + return importDataset(this, format, useDefaultSettings, sourceStorage, file, options); + }; + + projectClass.prototype.backup.implementation = async function ( + targetStorage: Storage, + useDefaultSettings: boolean, + fileName?: string, + ) { + const result = await serverProxy.projects.backup(this.id, targetStorage, useDefaultSettings, fileName); + return result; + }; + + projectClass.restore.implementation = async function (storage: Storage, file: File | string) { + const result = await serverProxy.projects.restore(storage, file); + return result; + }; + + return projectClass; +} diff --git a/cvat-core/src/project.js b/cvat-core/src/project.js deleted file mode 100644 index 45467a433c44..000000000000 --- a/cvat-core/src/project.js +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (C) 2019-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const PluginRegistry = require('./plugins'); - const { ArgumentError } = require('./exceptions'); - const { Label } = require('./labels'); - const User = require('./user'); - const { FieldUpdateTrigger } = require('./common'); - - /** - * Class representing a project - * @memberof module:API.cvat.classes - */ - class Project { - /** - * In a fact you need use the constructor only if you want to create a project - * @param {object} initialData - Object which is used for initialization - *
    It can contain keys: - *
  • name - *
  • labels - */ - constructor(initialData) { - const data = { - id: undefined, - name: undefined, - status: undefined, - assignee: undefined, - owner: undefined, - bug_tracker: undefined, - created_date: undefined, - updated_date: undefined, - task_subsets: undefined, - training_project: undefined, - task_ids: undefined, - dimension: undefined, - }; - - const updateTrigger = new FieldUpdateTrigger(); - - for (const property in data) { - if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { - data[property] = initialData[property]; - } - } - - data.labels = []; - - if (Array.isArray(initialData.labels)) { - for (const label of initialData.labels) { - const classInstance = new Label(label); - data.labels.push(classInstance); - } - } - - if (typeof initialData.training_project === 'object') { - data.training_project = { ...initialData.training_project }; - } - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ - id: { - get: () => data.id, - }, - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Project - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - name: { - get: () => data.name, - set: (value) => { - if (!value.trim().length) { - throw new ArgumentError('Value must not be empty'); - } - data.name = value; - updateTrigger.update('name'); - }, - }, - - /** - * @name status - * @type {module:API.cvat.enums.TaskStatus} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ - status: { - get: () => data.status, - }, - /** - * Instance of a user who was assigned for the project - * @name assignee - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ - assignee: { - get: () => data.assignee, - set: (assignee) => { - if (assignee !== null && !(assignee instanceof User)) { - throw new ArgumentError('Value must be a user instance'); - } - data.assignee = assignee; - updateTrigger.update('assignee'); - }, - }, - /** - * Instance of a user who has created the project - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ - owner: { - get: () => data.owner, - }, - /** - * @name bugTracker - * @type {string} - * @memberof module:API.cvat.classes.Project - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - bugTracker: { - get: () => data.bug_tracker, - set: (tracker) => { - data.bug_tracker = tracker; - updateTrigger.update('bugTracker'); - }, - }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ - createdDate: { - get: () => data.created_date, - }, - /** - * @name updatedDate - * @type {string} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ - updatedDate: { - get: () => data.updated_date, - }, - /** - * Dimesion of the tasks in the project, if no task dimension is null - * @name dimension - * @type {string} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ - dimension: { - get: () => data.dimension, - }, - /** - * After project has been created value can be appended only. - * @name labels - * @type {module:API.cvat.classes.Label[]} - * @memberof module:API.cvat.classes.Project - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - labels: { - get: () => [...data.labels], - set: (labels) => { - if (!Array.isArray(labels)) { - throw new ArgumentError('Value must be an array of Labels'); - } - - if (!Array.isArray(labels) || labels.some((label) => !(label instanceof Label))) { - throw new ArgumentError( - `Each array value must be an instance of Label. ${typeof label} was found`, - ); - } - - const IDs = labels.map((_label) => _label.id); - const deletedLabels = data.labels.filter((_label) => !IDs.includes(_label.id)); - deletedLabels.forEach((_label) => { - _label.deleted = true; - }); - - data.labels = [...deletedLabels, ...labels]; - updateTrigger.update('labels'); - }, - }, - /** - * Subsets array for related tasks - * @name subsets - * @type {string[]} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ - subsets: { - get: () => [...data.task_subsets], - }, - /** - * Training project associated with this annotation project - * This is a simple object which contains - * keys like host, username, password, enabled, project_class - * @name trainingProject - * @type {object} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - */ - trainingProject: { - get: () => { - if (typeof data.training_project === 'object') { - return { ...data.training_project }; - } - return data.training_project; - }, - set: (updatedProject) => { - if (typeof training === 'object') { - data.training_project = { ...updatedProject }; - } else { - data.training_project = updatedProject; - } - updateTrigger.update('trainingProject'); - }, - }, - _internalData: { - get: () => data, - }, - _updateTrigger: { - get: () => updateTrigger, - }, - }), - ); - - // When we call a function, for example: project.annotations.get() - // In the method get we lose the project context - // So, we need return it - this.annotations = { - exportDataset: Object.getPrototypeOf(this).annotations.exportDataset.bind(this), - importDataset: Object.getPrototypeOf(this).annotations.importDataset.bind(this), - }; - } - - /** - * Get the first frame of the first task of a project for preview - * @method preview - * @memberof Project - * @returns {string} - jpeg encoded image - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async preview() { - const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.preview); - return result; - } - - /** - * Method updates data of a created project or creates new project from scratch - * @method save - * @returns {module:API.cvat.classes.Project} - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async save() { - const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.save); - return result; - } - - /** - * Method deletes a project from a server - * @method delete - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async delete() { - const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.delete); - return result; - } - - /** - * Method makes a backup of a project - * @method export - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @returns {string} URL to get result archive - */ - async backup() { - const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.backup); - return result; - } - - /** - * Method restores a project from a backup - * @method restore - * @memberof module:API.cvat.classes.Project - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @returns {number} ID of the imported project - */ - static async restore(file) { - const result = await PluginRegistry.apiWrapper.call(this, Project.restore, file); - return result; - } - } - - Object.defineProperties( - Project.prototype, - Object.freeze({ - annotations: Object.freeze({ - value: { - async exportDataset(format, saveImages, customName = '') { - const result = await PluginRegistry.apiWrapper.call( - this, - Project.prototype.annotations.exportDataset, - format, - saveImages, - customName, - ); - return result; - }, - async importDataset(format, file, updateStatusCallback = null) { - const result = await PluginRegistry.apiWrapper.call( - this, - Project.prototype.annotations.importDataset, - format, - file, - updateStatusCallback, - ); - return result; - }, - }, - writable: true, - }), - }), - ); - - module.exports = { - Project, - }; -})(); diff --git a/cvat-core/src/project.ts b/cvat-core/src/project.ts new file mode 100644 index 000000000000..de10e7c4f0d8 --- /dev/null +++ b/cvat-core/src/project.ts @@ -0,0 +1,432 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { StorageLocation } from './enums'; +import { Storage } from './storage'; + +const PluginRegistry = require('./plugins').default; +const { ArgumentError } = require('./exceptions'); +const { Label } = require('./labels'); +const User = require('./user').default; +const { FieldUpdateTrigger } = require('./common'); + +/** + * Class representing a project + * @memberof module:API.cvat.classes + */ +export default class Project { + /** + * In a fact you need use the constructor only if you want to create a project + * @param {object} initialData - Object which is used for initialization + *
    It can contain keys: + *
  • name + *
  • labels + */ + constructor(initialData) { + const data = { + id: undefined, + name: undefined, + status: undefined, + assignee: undefined, + owner: undefined, + bug_tracker: undefined, + created_date: undefined, + updated_date: undefined, + task_subsets: undefined, + training_project: undefined, + task_ids: undefined, + dimension: undefined, + source_storage: undefined, + target_storage: undefined, + labels: undefined, + }; + + const updateTrigger = new FieldUpdateTrigger(); + + for (const property in data) { + if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { + data[property] = initialData[property]; + } + } + + data.labels = []; + + if (Array.isArray(initialData.labels)) { + data.labels = initialData.labels + .map((labelData) => new Label(labelData)).filter((label) => !label.hasParent); + } + + if (typeof initialData.training_project === 'object') { + data.training_project = { ...initialData.training_project }; + } + + Object.defineProperties( + this, + Object.freeze({ + /** + * @name id + * @type {number} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + id: { + get: () => data.id, + }, + /** + * @name name + * @type {string} + * @memberof module:API.cvat.classes.Project + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + name: { + get: () => data.name, + set: (value) => { + if (!value.trim().length) { + throw new ArgumentError('Value must not be empty'); + } + data.name = value; + updateTrigger.update('name'); + }, + }, + + /** + * @name status + * @type {module:API.cvat.enums.TaskStatus} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + status: { + get: () => data.status, + }, + /** + * Instance of a user who was assigned for the project + * @name assignee + * @type {module:API.cvat.classes.User} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + assignee: { + get: () => data.assignee, + set: (assignee) => { + if (assignee !== null && !(assignee instanceof User)) { + throw new ArgumentError('Value must be a user instance'); + } + data.assignee = assignee; + updateTrigger.update('assignee'); + }, + }, + /** + * Instance of a user who has created the project + * @name owner + * @type {module:API.cvat.classes.User} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + owner: { + get: () => data.owner, + }, + /** + * @name bugTracker + * @type {string} + * @memberof module:API.cvat.classes.Project + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + bugTracker: { + get: () => data.bug_tracker, + set: (tracker) => { + data.bug_tracker = tracker; + updateTrigger.update('bugTracker'); + }, + }, + /** + * @name createdDate + * @type {string} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + createdDate: { + get: () => data.created_date, + }, + /** + * @name updatedDate + * @type {string} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + updatedDate: { + get: () => data.updated_date, + }, + /** + * Dimesion of the tasks in the project, if no task dimension is null + * @name dimension + * @type {string} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + dimension: { + get: () => data.dimension, + }, + /** + * After project has been created value can be appended only. + * @name labels + * @type {module:API.cvat.classes.Label[]} + * @memberof module:API.cvat.classes.Project + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + labels: { + get: () => [...data.labels], + set: (labels) => { + if (!Array.isArray(labels)) { + throw new ArgumentError('Value must be an array of Labels'); + } + + if (!Array.isArray(labels) || labels.some((label) => !(label instanceof Label))) { + throw new ArgumentError( + `Each array value must be an instance of Label. ${typeof label} was found`, + ); + } + + const IDs = labels.map((_label) => _label.id); + const deletedLabels = data.labels.filter((_label) => !IDs.includes(_label.id)); + deletedLabels.forEach((_label) => { + _label.deleted = true; + }); + + data.labels = [...deletedLabels, ...labels]; + updateTrigger.update('labels'); + }, + }, + /** + * Subsets array for related tasks + * @name subsets + * @type {string[]} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + subsets: { + get: () => [...data.task_subsets], + }, + /** + * Training project associated with this annotation project + * This is a simple object which contains + * keys like host, username, password, enabled, project_class + * @name trainingProject + * @type {object} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + trainingProject: { + get: () => { + if (typeof data.training_project === 'object') { + return { ...data.training_project }; + } + return data.training_project; + }, + set: (updatedProject) => { + if (typeof training === 'object') { + data.training_project = { ...updatedProject }; + } else { + data.training_project = updatedProject; + } + updateTrigger.update('trainingProject'); + }, + }, + /** + * Source storage for import resources. + * @name sourceStorage + * @type {module:API.cvat.classes.Storage} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + sourceStorage: { + get: () => ( + new Storage({ + location: data.source_storage?.location || StorageLocation.LOCAL, + cloudStorageId: data.source_storage?.cloud_storage_id, + }) + ), + }, + /** + * Target storage for export resources. + * @name targetStorage + * @type {module:API.cvat.classes.Storage} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + */ + targetStorage: { + get: () => ( + new Storage({ + location: data.target_storage?.location || StorageLocation.LOCAL, + cloudStorageId: data.target_storage?.cloud_storage_id, + }) + ), + }, + _internalData: { + get: () => data, + }, + _updateTrigger: { + get: () => updateTrigger, + }, + }), + ); + + // When we call a function, for example: project.annotations.get() + // In the method get we lose the project context + // So, we need return it + this.annotations = { + exportDataset: Object.getPrototypeOf(this).annotations.exportDataset.bind(this), + importDataset: Object.getPrototypeOf(this).annotations.importDataset.bind(this), + }; + } + + /** + * Get the first frame of the first task of a project for preview + * @method preview + * @memberof Project + * @returns {string} - jpeg encoded image + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + async preview() { + const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.preview); + return result; + } + + /** + * Method updates data of a created project or creates new project from scratch + * @method save + * @returns {module:API.cvat.classes.Project} + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async save() { + const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.save); + return result; + } + + /** + * Method deletes a project from a server + * @method delete + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async delete() { + const result = await PluginRegistry.apiWrapper.call(this, Project.prototype.delete); + return result; + } + + /** + * Method makes a backup of a project + * @method backup + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + * @returns {string} URL to get result archive + */ + async backup(targetStorage: Storage, useDefaultSettings: boolean, fileName?: string) { + const result = await PluginRegistry.apiWrapper.call( + this, + Project.prototype.backup, + targetStorage, + useDefaultSettings, + fileName, + ); + return result; + } + + /** + * Method restores a project from a backup + * @method restore + * @memberof module:API.cvat.classes.Project + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + * @returns {number} ID of the imported project + */ + static async restore(storage: Storage, file: File | string) { + const result = await PluginRegistry.apiWrapper.call(this, Project.restore, storage, file); + return result; + } +} + +Object.defineProperties( + Project.prototype, + Object.freeze({ + annotations: Object.freeze({ + value: { + async exportDataset( + format: string, + saveImages: boolean, + useDefaultSettings: boolean, + targetStorage: Storage, + customName?: string, + ) { + const result = await PluginRegistry.apiWrapper.call( + this, + Project.prototype.annotations.exportDataset, + format, + saveImages, + useDefaultSettings, + targetStorage, + customName, + ); + return result; + }, + async importDataset( + format: string, + useDefaultSettings: boolean, + sourceStorage: Storage, + file: File | string, + options?: { + convMaskToPoly?: boolean, + updateStatusCallback?: (s: string, n: number) => void, + }, + ) { + const result = await PluginRegistry.apiWrapper.call( + this, + Project.prototype.annotations.importDataset, + format, + useDefaultSettings, + sourceStorage, + file, + options, + ); + return result; + }, + }, + writable: true, + }), + }), +); diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js deleted file mode 100644 index 3924f7bb7fb5..000000000000 --- a/cvat-core/src/server-proxy.js +++ /dev/null @@ -1,1985 +0,0 @@ -// Copyright (C) 2019-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const FormData = require('form-data'); - const { ServerError } = require('./exceptions'); - const store = require('store'); - const config = require('./config'); - const DownloadWorker = require('./download.worker'); - const Axios = require('axios'); - const tus = require('tus-js-client'); - - function enableOrganization() { - return { org: config.organizationID || '' }; - } - - function removeToken() { - Axios.defaults.headers.common.Authorization = ''; - store.remove('token'); - } - - function waitFor(frequencyHz, predicate) { - return new Promise((resolve, reject) => { - if (typeof predicate !== 'function') { - reject(new Error(`Predicate must be a function, got ${typeof predicate}`)); - } - - const internalWait = () => { - let result = false; - try { - result = predicate(); - } catch (error) { - reject(error); - } - - if (result) { - resolve(); - } else { - setTimeout(internalWait, 1000 / frequencyHz); - } - }; - - setTimeout(internalWait); - }); - } - - async function chunkUpload(file, uploadConfig) { - const params = enableOrganization(); - const { - endpoint, chunkSize, totalSize, onUpdate, - } = uploadConfig; - let { totalSentSize } = uploadConfig; - return new Promise((resolve, reject) => { - const upload = new tus.Upload(file, { - endpoint, - metadata: { - filename: file.name, - filetype: file.type, - }, - headers: { - Authorization: Axios.defaults.headers.common.Authorization, - }, - chunkSize, - retryDelays: null, - onError(error) { - reject(error); - }, - onBeforeRequest(req) { - const xhr = req.getUnderlyingObject(); - const { org } = params; - req.setHeader('X-Organization', org); - xhr.withCredentials = true; - }, - onProgress(bytesUploaded) { - if (onUpdate && Number.isInteger(totalSentSize) && Number.isInteger(totalSize)) { - const currentUploadedSize = totalSentSize + bytesUploaded; - const percentage = Math.round(currentUploadedSize / totalSize); - onUpdate(percentage); - } - }, - onSuccess() { - if (totalSentSize) totalSentSize += file.size; - resolve(totalSentSize); - }, - }); - upload.start(); - }); - } - - function generateError(errorData) { - if (errorData.response) { - const message = `${errorData.message}. ${JSON.stringify(errorData.response.data) || ''}.`; - return new ServerError(message, errorData.response.status); - } - - // Server is unavailable (no any response) - const message = `${errorData.message}.`; // usually is "Error Network" - return new ServerError(message, 0); - } - - function prepareData(details) { - const data = new FormData(); - for (const [key, value] of Object.entries(details)) { - if (Array.isArray(value)) { - value.forEach((element, idx) => { - data.append(`${key}[${idx}]`, element); - }); - } else { - data.set(key, value); - } - } - return data; - } - - class WorkerWrappedAxios { - constructor(requestInterseptor) { - const worker = new DownloadWorker(requestInterseptor); - const requests = {}; - let requestId = 0; - - worker.onmessage = (e) => { - if (e.data.id in requests) { - if (e.data.isSuccess) { - requests[e.data.id].resolve(e.data.responseData); - } else { - requests[e.data.id].reject({ - response: { - status: e.data.status, - data: e.data.responseData, - }, - }); - } - - delete requests[e.data.id]; - } - }; - - worker.onerror = (e) => { - if (e.data.id in requests) { - requests[e.data.id].reject(e); - delete requests[e.data.id]; - } - }; - - function getRequestId() { - return requestId++; - } - - async function get(url, requestConfig) { - return new Promise((resolve, reject) => { - const newRequestId = getRequestId(); - requests[newRequestId] = { - resolve, - reject, - }; - worker.postMessage({ - url, - config: requestConfig, - id: newRequestId, - }); - }); - } - - Object.defineProperties( - this, - Object.freeze({ - get: { - value: get, - writable: false, - }, - }), - ); - } - } - - class ServerProxy { - constructor() { - Axios.defaults.withCredentials = true; - Axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN'; - Axios.defaults.xsrfCookieName = 'csrftoken'; - const workerAxios = new WorkerWrappedAxios(); - Axios.interceptors.request.use((reqConfig) => { - if ('params' in reqConfig && 'org' in reqConfig.params) { - return reqConfig; - } - - reqConfig.params = { ...enableOrganization(), ...(reqConfig.params || {}) }; - return reqConfig; - }); - - let token = store.get('token'); - if (token) { - Axios.defaults.headers.common.Authorization = `Token ${token}`; - } - - async function about() { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/server/about`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function share(directoryArg) { - const { backendAPI } = config; - const directory = encodeURI(directoryArg); - - let response = null; - try { - response = await Axios.get(`${backendAPI}/server/share`, { - proxy: config.proxy, - params: { directory }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function exception(exceptionObject) { - const { backendAPI } = config; - - try { - await Axios.post(`${backendAPI}/server/exception`, JSON.stringify(exceptionObject), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function formats() { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/server/annotation/formats`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function userAgreements() { - const { backendAPI } = config; - let response = null; - try { - response = await Axios.get(`${backendAPI}/restrictions/user-agreements`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function register(username, firstName, lastName, email, password1, password2, confirmations) { - let response = null; - try { - const data = JSON.stringify({ - username, - first_name: firstName, - last_name: lastName, - email, - password1, - password2, - confirmations, - }); - response = await Axios.post(`${config.backendAPI}/auth/register`, data, { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function login(username, password) { - const authenticationData = [ - `${encodeURIComponent('username')}=${encodeURIComponent(username)}`, - `${encodeURIComponent('password')}=${encodeURIComponent(password)}`, - ] - .join('&') - .replace(/%20/g, '+'); - - removeToken(); - let authenticationResponse = null; - try { - authenticationResponse = await Axios.post(`${config.backendAPI}/auth/login`, authenticationData, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - if (authenticationResponse.headers['set-cookie']) { - // Browser itself setup cookie and header is none - // In NodeJS we need do it manually - const cookies = authenticationResponse.headers['set-cookie'].join(';'); - Axios.defaults.headers.common.Cookie = cookies; - } - - token = authenticationResponse.data.key; - store.set('token', token); - Axios.defaults.headers.common.Authorization = `Token ${token}`; - } - - async function logout() { - try { - await Axios.post(`${config.backendAPI}/auth/logout`, { - proxy: config.proxy, - }); - removeToken(); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function changePassword(oldPassword, newPassword1, newPassword2) { - try { - const data = JSON.stringify({ - old_password: oldPassword, - new_password1: newPassword1, - new_password2: newPassword2, - }); - await Axios.post(`${config.backendAPI}/auth/password/change`, data, { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function requestPasswordReset(email) { - try { - const data = JSON.stringify({ - email, - }); - await Axios.post(`${config.backendAPI}/auth/password/reset`, data, { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function resetPassword(newPassword1, newPassword2, uid, _token) { - try { - const data = JSON.stringify({ - new_password1: newPassword1, - new_password2: newPassword2, - uid, - token: _token, - }); - await Axios.post(`${config.backendAPI}/auth/password/reset/confirm`, data, { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function authorized() { - try { - await module.exports.users.self(); - } catch (serverError) { - if (serverError.code === 401) { - removeToken(); - return false; - } - - throw serverError; - } - - return true; - } - - async function serverRequest(url, data) { - try { - return ( - await Axios({ - url, - ...data, - }) - ).data; - } catch (errorData) { - throw generateError(errorData); - } - } - - async function searchProjectNames(search, limit) { - const { backendAPI, proxy } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/projects`, { - proxy, - params: { - names_only: true, - page: 1, - page_size: limit, - search, - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - response.data.results.count = response.data.count; - return response.data.results; - } - - async function getProjects(filter = {}) { - const { backendAPI, proxy } = config; - - let response = null; - try { - if ('id' in filter) { - response = await Axios.get(`${backendAPI}/projects/${filter.id}`, { - proxy, - }); - const results = [response.data]; - results.count = 1; - return results; - } - - response = await Axios.get(`${backendAPI}/projects`, { - params: { - ...filter, - page_size: 12, - }, - proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - response.data.results.count = response.data.count; - return response.data.results; - } - - async function saveProject(id, projectData) { - const { backendAPI } = config; - - try { - await Axios.patch(`${backendAPI}/projects/${id}`, JSON.stringify(projectData), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function deleteProject(id) { - const { backendAPI } = config; - - try { - await Axios.delete(`${backendAPI}/projects/${id}`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function createProject(projectSpec) { - const { backendAPI } = config; - - try { - const response = await Axios.post(`${backendAPI}/projects`, JSON.stringify(projectSpec), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - return response.data; - } catch (errorData) { - throw generateError(errorData); - } - } - - async function getTasks(filter = {}) { - const { backendAPI } = config; - - let response = null; - try { - if ('id' in filter) { - response = await Axios.get(`${backendAPI}/tasks/${filter.id}`, { - proxy: config.proxy, - }); - const results = [response.data]; - results.count = 1; - return results; - } - - response = await Axios.get(`${backendAPI}/tasks`, { - params: { - ...filter, - page_size: 10, - }, - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - response.data.results.count = response.data.count; - return response.data.results; - } - - async function saveTask(id, taskData) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.patch(`${backendAPI}/tasks/${id}`, JSON.stringify(taskData), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function deleteTask(id, organizationID = null) { - const { backendAPI } = config; - - try { - await Axios.delete(`${backendAPI}/tasks/${id}`, { - ...(organizationID ? { org: organizationID } : {}), - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - function exportDataset(instanceType) { - return async function (id, format, name, saveImages) { - const { backendAPI } = config; - const baseURL = `${backendAPI}/${instanceType}/${id}/${saveImages ? 'dataset' : 'annotations'}`; - const params = { - ...enableOrganization(), - format, - }; - - if (name) { - params.filename = name.replace(/\//g, '_'); - } - - return new Promise((resolve, reject) => { - async function request() { - Axios.get(baseURL, { - proxy: config.proxy, - params, - }) - .then((response) => { - if (response.status === 202) { - setTimeout(request, 3000); - } else { - params.action = 'download'; - resolve(`${baseURL}?${new URLSearchParams(params).toString()}`); - } - }) - .catch((errorData) => { - reject(generateError(errorData)); - }); - } - - setTimeout(request); - }); - }; - } - - async function importDataset(id, format, file, onUpdate) { - const { backendAPI, origin } = config; - const params = { - ...enableOrganization(), - format, - filename: file.name, - }; - const uploadConfig = { - chunkSize: config.uploadChunkSize * 1024 * 1024, - endpoint: `${origin}${backendAPI}/projects/${id}/dataset/`, - totalSentSize: 0, - totalSize: file.size, - onUpdate: (percentage) => { - onUpdate('The dataset is being uploaded to the server', percentage); - }, - }; - const url = `${backendAPI}/projects/${id}/dataset`; - - try { - await Axios.post(url, - new FormData(), { - params, - proxy: config.proxy, - headers: { 'Upload-Start': true }, - }); - await chunkUpload(file, uploadConfig); - await Axios.post(url, - new FormData(), { - params, - proxy: config.proxy, - headers: { 'Upload-Finish': true }, - }); - return new Promise((resolve, reject) => { - async function requestStatus() { - try { - const response = await Axios.get(url, { - params: { ...params, action: 'import_status' }, - proxy: config.proxy, - }); - if (response.status === 202) { - if (onUpdate && response.data.message) { - onUpdate(response.data.message, response.data.progress || 0); - } - setTimeout(requestStatus, 3000); - } else if (response.status === 201) { - resolve(); - } else { - reject(generateError(response)); - } - } catch (error) { - reject(generateError(error)); - } - } - setTimeout(requestStatus, 2000); - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function exportTask(id) { - const { backendAPI } = config; - const params = { - ...enableOrganization(), - }; - const url = `${backendAPI}/tasks/${id}/backup`; - - return new Promise((resolve, reject) => { - async function request() { - try { - const response = await Axios.get(url, { - proxy: config.proxy, - params, - }); - if (response.status === 202) { - setTimeout(request, 3000); - } else { - params.action = 'download'; - resolve(`${url}?${new URLSearchParams(params).toString()}`); - } - } catch (errorData) { - reject(generateError(errorData)); - } - } - - setTimeout(request); - }); - } - - async function importTask(file) { - const { backendAPI } = config; - // keep current default params to 'freeze" them during this request - const params = enableOrganization(); - - let taskData = new FormData(); - taskData.append('task_file', file); - - return new Promise((resolve, reject) => { - async function request() { - try { - const response = await Axios.post(`${backendAPI}/tasks/backup`, taskData, { - proxy: config.proxy, - params, - }); - if (response.status === 202) { - taskData = new FormData(); - taskData.append('rq_id', response.data.rq_id); - setTimeout(request, 3000); - } else { - // to be able to get the task after it was created, pass frozen params - const importedTask = await getTasks({ id: response.data.id, ...params }); - resolve(importedTask[0]); - } - } catch (errorData) { - reject(generateError(errorData)); - } - } - - setTimeout(request); - }); - } - - async function backupProject(id) { - const { backendAPI } = config; - // keep current default params to 'freeze" them during this request - const params = enableOrganization(); - const url = `${backendAPI}/projects/${id}/backup`; - - return new Promise((resolve, reject) => { - async function request() { - try { - const response = await Axios.get(url, { - proxy: config.proxy, - params, - }); - if (response.status === 202) { - setTimeout(request, 3000); - } else { - params.action = 'download'; - resolve(`${url}?${new URLSearchParams(params).toString()}`); - } - } catch (errorData) { - reject(generateError(errorData)); - } - } - - setTimeout(request); - }); - } - - async function restoreProject(file) { - const { backendAPI } = config; - // keep current default params to 'freeze" them during this request - const params = enableOrganization(); - - let data = new FormData(); - data.append('project_file', file); - - return new Promise((resolve, reject) => { - async function request() { - try { - const response = await Axios.post(`${backendAPI}/projects/backup`, data, { - proxy: config.proxy, - params, - }); - if (response.status === 202) { - data = new FormData(); - data.append('rq_id', response.data.rq_id); - setTimeout(request, 3000); - } else { - // to be able to get the task after it was created, pass frozen params - const restoredProject = await getProjects({ id: response.data.id, ...params }); - resolve(restoredProject[0]); - } - } catch (errorData) { - reject(generateError(errorData)); - } - } - - setTimeout(request); - }); - } - - async function createTask(taskSpec, taskDataSpec, onUpdate) { - const { backendAPI, origin } = config; - // keep current default params to 'freeze" them during this request - const params = enableOrganization(); - - async function wait(id) { - return new Promise((resolve, reject) => { - async function checkStatus() { - try { - const response = await Axios.get(`${backendAPI}/tasks/${id}/status`, { params }); - if (['Queued', 'Started'].includes(response.data.state)) { - if (response.data.message !== '') { - onUpdate(response.data.message, response.data.progress || 0); - } - setTimeout(checkStatus, 1000); - } else if (response.data.state === 'Finished') { - resolve(); - } else if (response.data.state === 'Failed') { - // If request has been successful, but task hasn't been created - // Then passed data is wrong and we can pass code 400 - const message = ` - Could not create the task on the server. ${response.data.message}. - `; - reject(new ServerError(message, 400)); - } else { - // If server has another status, it is unexpected - // Therefore it is server error and we can pass code 500 - reject( - new ServerError( - `Unknown task state has been received: ${response.data.state}`, - 500, - ), - ); - } - } catch (errorData) { - reject(generateError(errorData)); - } - } - - setTimeout(checkStatus, 1000); - }); - } - - const chunkSize = config.uploadChunkSize * 1024 * 1024; - const clientFiles = taskDataSpec.client_files; - const chunkFiles = []; - const bulkFiles = []; - let totalSize = 0; - let totalSentSize = 0; - for (const file of clientFiles) { - if (file.size > chunkSize) { - chunkFiles.push(file); - } else { - bulkFiles.push(file); - } - totalSize += file.size; - } - delete taskDataSpec.client_files; - - const taskData = new FormData(); - for (const [key, value] of Object.entries(taskDataSpec)) { - if (Array.isArray(value)) { - value.forEach((element, idx) => { - taskData.append(`${key}[${idx}]`, element); - }); - } else { - taskData.set(key, value); - } - } - - let response = null; - - onUpdate('The task is being created on the server..', null); - try { - response = await Axios.post(`${backendAPI}/tasks`, JSON.stringify(taskSpec), { - proxy: config.proxy, - params, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - onUpdate('The data are being uploaded to the server..', null); - - async function bulkUpload(taskId, files) { - const fileBulks = files.reduce((fileGroups, file) => { - const lastBulk = fileGroups[fileGroups.length - 1]; - if (chunkSize - lastBulk.size >= file.size) { - lastBulk.files.push(file); - lastBulk.size += file.size; - } else { - fileGroups.push({ files: [file], size: file.size }); - } - return fileGroups; - }, [{ files: [], size: 0 }]); - const totalBulks = fileBulks.length; - let currentChunkNumber = 0; - while (currentChunkNumber < totalBulks) { - for (const [idx, element] of fileBulks[currentChunkNumber].files.entries()) { - taskData.append(`client_files[${idx}]`, element); - } - const percentage = totalSentSize / totalSize; - onUpdate('The data are being uploaded to the server', percentage); - await Axios.post(`${backendAPI}/tasks/${taskId}/data`, taskData, { - ...params, - proxy: config.proxy, - headers: { 'Upload-Multiple': true }, - }); - for (let i = 0; i < fileBulks[currentChunkNumber].files.length; i++) { - taskData.delete(`client_files[${i}]`); - } - totalSentSize += fileBulks[currentChunkNumber].size; - currentChunkNumber++; - } - } - - try { - await Axios.post(`${backendAPI}/tasks/${response.data.id}/data`, - taskData, { - ...params, - proxy: config.proxy, - headers: { 'Upload-Start': true }, - }); - const uploadConfig = { - endpoint: `${origin}${backendAPI}/tasks/${response.data.id}/data/`, - onUpdate: (percentage) => { - onUpdate('The data are being uploaded to the server', percentage); - }, - chunkSize, - totalSize, - totalSentSize, - }; - for (const file of chunkFiles) { - uploadConfig.totalSentSize += await chunkUpload(file, uploadConfig); - } - if (bulkFiles.length > 0) { - await bulkUpload(response.data.id, bulkFiles); - } - await Axios.post(`${backendAPI}/tasks/${response.data.id}/data`, - taskData, { - ...params, - proxy: config.proxy, - headers: { 'Upload-Finish': true }, - }); - } catch (errorData) { - try { - await deleteTask(response.data.id, params.org || null); - } catch (_) { - // ignore - } - throw generateError(errorData); - } - - try { - await wait(response.data.id); - } catch (createException) { - await deleteTask(response.data.id, params.org || null); - throw createException; - } - - // to be able to get the task after it was created, pass frozen params - const createdTask = await getTasks({ id: response.data.id, ...params }); - return createdTask[0]; - } - - async function getJobs(filter = {}) { - const { backendAPI } = config; - const id = filter.id || null; - - let response = null; - try { - if (id !== null) { - response = await Axios.get(`${backendAPI}/jobs/${id}`, { - proxy: config.proxy, - }); - } else { - response = await Axios.get(`${backendAPI}/jobs`, { - proxy: config.proxy, - params: { - ...filter, - page_size: 12, - }, - }); - } - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function getJobIssues(jobID) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/jobs/${jobID}/issues`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function createComment(data) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.post(`${backendAPI}/comments`, JSON.stringify(data), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function createIssue(data) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.post(`${backendAPI}/issues`, JSON.stringify(data), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function updateIssue(issueID, data) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.patch(`${backendAPI}/issues/${issueID}`, JSON.stringify(data), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function deleteIssue(issueID) { - const { backendAPI } = config; - - try { - await Axios.delete(`${backendAPI}/issues/${issueID}`); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function saveJob(id, jobData) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.patch(`${backendAPI}/jobs/${id}`, JSON.stringify(jobData), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function getUsers(filter = { page_size: 'all' }) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/users`, { - proxy: config.proxy, - params: { - ...filter, - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data.results; - } - - async function getSelf() { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/users/self`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function getPreview(tid, jid) { - const { backendAPI } = config; - - let response = null; - try { - const url = `${backendAPI}/${jid !== null ? 'jobs' : 'tasks'}/${jid || tid}/data`; - response = await Axios.get(url, { - params: { - type: 'preview', - }, - proxy: config.proxy, - responseType: 'blob', - }); - } catch (errorData) { - const code = errorData.response ? errorData.response.status : errorData.code; - throw new ServerError(`Could not get preview frame for the task ${tid} from the server`, code); - } - - return response.data; - } - - async function getImageContext(jid, frame) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/jobs/${jid}/data`, { - params: { - quality: 'original', - type: 'context_image', - number: frame, - }, - proxy: config.proxy, - responseType: 'blob', - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function getData(tid, jid, chunk) { - const { backendAPI } = config; - - const url = jid === null ? `tasks/${tid}/data` : `jobs/${jid}/data`; - - let response = null; - try { - response = await workerAxios.get(`${backendAPI}/${url}`, { - params: { - ...enableOrganization(), - quality: 'compressed', - type: 'chunk', - number: chunk, - }, - proxy: config.proxy, - responseType: 'arraybuffer', - }); - } catch (errorData) { - throw generateError({ - message: '', - response: { - ...errorData.response, - data: String.fromCharCode.apply(null, new Uint8Array(errorData.response.data)), - }, - }); - } - - return response; - } - - async function getMeta(tid) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/tasks/${tid}/data/meta`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - // Session is 'task' or 'job' - async function getAnnotations(session, id) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/${session}s/${id}/annotations`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - // Session is 'task' or 'job' - async function updateAnnotations(session, id, data, action) { - const { backendAPI } = config; - const url = `${backendAPI}/${session}s/${id}/annotations`; - const params = {}; - let requestFunc = null; - - if (action.toUpperCase() === 'PUT') { - requestFunc = Axios.put.bind(Axios); - } else { - requestFunc = Axios.patch.bind(Axios); - params.action = action; - } - - let response = null; - try { - response = await requestFunc(url, JSON.stringify(data), { - proxy: config.proxy, - params, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - // Session is 'task' or 'job' - async function uploadAnnotations(session, id, file, format) { - const { backendAPI, origin } = config; - const params = { - ...enableOrganization(), - format, - filename: file.name, - }; - const chunkSize = config.uploadChunkSize * 1024 * 1024; - const uploadConfig = { - chunkSize, - endpoint: `${origin}${backendAPI}/${session}s/${id}/annotations/`, - }; - try { - await Axios.post(`${backendAPI}/${session}s/${id}/annotations`, - new FormData(), { - params, - proxy: config.proxy, - headers: { 'Upload-Start': true }, - }); - await chunkUpload(file, uploadConfig); - await Axios.post(`${backendAPI}/${session}s/${id}/annotations`, - new FormData(), { - params, - proxy: config.proxy, - headers: { 'Upload-Finish': true }, - }); - return new Promise((resolve, reject) => { - async function requestStatus() { - try { - const response = await Axios.put( - `${backendAPI}/${session}s/${id}/annotations`, - new FormData(), - { - params, - proxy: config.proxy, - }, - ); - if (response.status === 202) { - setTimeout(requestStatus, 3000); - } else { - resolve(); - } - } catch (errorData) { - reject(generateError(errorData)); - } - } - setTimeout(requestStatus); - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - // Session is 'task' or 'job' - async function dumpAnnotations(id, name, format) { - const { backendAPI } = config; - const baseURL = `${backendAPI}/tasks/${id}/annotations`; - const params = enableOrganization(); - params.format = encodeURIComponent(format); - if (name) { - const filename = name.replace(/\//g, '_'); - params.filename = encodeURIComponent(filename); - } - - return new Promise((resolve, reject) => { - async function request() { - Axios.get(baseURL, { - proxy: config.proxy, - params, - }) - .then((response) => { - if (response.status === 202) { - setTimeout(request, 3000); - } else { - params.action = 'download'; - resolve(`${baseURL}?${new URLSearchParams(params).toString()}`); - } - }) - .catch((errorData) => { - reject(generateError(errorData)); - }); - } - - setTimeout(request); - }); - } - - async function saveLogs(logs) { - const { backendAPI } = config; - - try { - await Axios.post(`${backendAPI}/server/logs`, JSON.stringify(logs), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function getLambdaFunctions() { - const { backendAPI } = config; - - try { - const response = await Axios.get(`${backendAPI}/lambda/functions`, { - proxy: config.proxy, - }); - return response.data; - } catch (errorData) { - throw generateError(errorData); - } - } - - async function runLambdaRequest(body) { - const { backendAPI } = config; - - try { - const response = await Axios.post(`${backendAPI}/lambda/requests`, JSON.stringify(body), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - - return response.data; - } catch (errorData) { - throw generateError(errorData); - } - } - - async function callLambdaFunction(funId, body) { - const { backendAPI } = config; - - try { - const response = await Axios.post(`${backendAPI}/lambda/functions/${funId}`, JSON.stringify(body), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - - return response.data; - } catch (errorData) { - throw generateError(errorData); - } - } - - async function getLambdaRequests() { - const { backendAPI } = config; - - try { - const response = await Axios.get(`${backendAPI}/lambda/requests`, { - proxy: config.proxy, - }); - - return response.data; - } catch (errorData) { - throw generateError(errorData); - } - } - - async function getRequestStatus(requestID) { - const { backendAPI } = config; - - try { - const response = await Axios.get(`${backendAPI}/lambda/requests/${requestID}`, { - proxy: config.proxy, - }); - return response.data; - } catch (errorData) { - throw generateError(errorData); - } - } - - async function cancelLambdaRequest(requestId) { - const { backendAPI } = config; - - try { - await Axios.delete(`${backendAPI}/lambda/requests/${requestId}`, { - method: 'DELETE', - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - function predictorStatus(projectId) { - const { backendAPI } = config; - - return new Promise((resolve, reject) => { - async function request() { - try { - const response = await Axios.get(`${backendAPI}/predict/status`, { - params: { - project: projectId, - }, - }); - return response.data; - } catch (errorData) { - throw generateError(errorData); - } - } - - const timeoutCallback = async () => { - let data = null; - try { - data = await request(); - if (data.status === 'queued') { - setTimeout(timeoutCallback, 1000); - } else if (data.status === 'done') { - resolve(data); - } else { - throw new Error(`Unknown status was received "${data.status}"`); - } - } catch (error) { - reject(error); - } - }; - - setTimeout(timeoutCallback); - }); - } - - function predictAnnotations(taskId, frame) { - return new Promise((resolve, reject) => { - const { backendAPI } = config; - - async function request() { - try { - const response = await Axios.get(`${backendAPI}/predict/frame`, { - params: { - task: taskId, - frame, - }, - }); - return response.data; - } catch (errorData) { - throw generateError(errorData); - } - } - - const timeoutCallback = async () => { - let data = null; - try { - data = await request(); - if (data.status === 'queued') { - setTimeout(timeoutCallback, 1000); - } else if (data.status === 'done') { - predictAnnotations.latestRequest.fetching = false; - resolve(data.annotation); - } else { - throw new Error(`Unknown status was received "${data.status}"`); - } - } catch (error) { - predictAnnotations.latestRequest.fetching = false; - reject(error); - } - }; - - const closureId = Date.now(); - predictAnnotations.latestRequest.id = closureId; - const predicate = () => !predictAnnotations.latestRequest.fetching || - predictAnnotations.latestRequest.id !== closureId; - if (predictAnnotations.latestRequest.fetching) { - waitFor(5, predicate).then(() => { - if (predictAnnotations.latestRequest.id !== closureId) { - resolve(null); - } else { - predictAnnotations.latestRequest.fetching = true; - setTimeout(timeoutCallback); - } - }); - } else { - predictAnnotations.latestRequest.fetching = true; - setTimeout(timeoutCallback); - } - }); - } - - predictAnnotations.latestRequest = { - fetching: false, - id: null, - }; - - async function installedApps() { - const { backendAPI } = config; - try { - const response = await Axios.get(`${backendAPI}/server/plugins`, { - proxy: config.proxy, - }); - return response.data; - } catch (errorData) { - throw generateError(errorData); - } - } - - async function createCloudStorage(storageDetail) { - const { backendAPI } = config; - - const storageDetailData = prepareData(storageDetail); - try { - const response = await Axios.post(`${backendAPI}/cloudstorages`, storageDetailData, { - proxy: config.proxy, - }); - return response.data; - } catch (errorData) { - throw generateError(errorData); - } - } - - async function updateCloudStorage(id, storageDetail) { - const { backendAPI } = config; - - const storageDetailData = prepareData(storageDetail); - try { - await Axios.patch(`${backendAPI}/cloudstorages/${id}`, storageDetailData, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function getCloudStorages(filter = {}) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/cloudstorages`, { - proxy: config.proxy, - params: filter, - page_size: 12, - }); - } catch (errorData) { - throw generateError(errorData); - } - - response.data.results.count = response.data.count; - return response.data.results; - } - - async function getCloudStorageContent(id, manifestPath) { - const { backendAPI } = config; - - let response = null; - try { - const url = `${backendAPI}/cloudstorages/${id}/content${ - manifestPath ? `?manifest_path=${manifestPath}` : '' - }`; - response = await Axios.get(url, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function getCloudStoragePreview(id) { - const { backendAPI } = config; - - let response = null; - try { - const url = `${backendAPI}/cloudstorages/${id}/preview`; - response = await workerAxios.get(url, { - params: enableOrganization(), - proxy: config.proxy, - responseType: 'arraybuffer', - }); - } catch (errorData) { - throw generateError({ - message: '', - response: { - ...errorData.response, - data: String.fromCharCode.apply(null, new Uint8Array(errorData.response.data)), - }, - }); - } - - return new Blob([new Uint8Array(response)]); - } - - async function getCloudStorageStatus(id) { - const { backendAPI } = config; - - let response = null; - try { - const url = `${backendAPI}/cloudstorages/${id}/status`; - response = await Axios.get(url, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function deleteCloudStorage(id) { - const { backendAPI } = config; - - try { - await Axios.delete(`${backendAPI}/cloudstorages/${id}`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function getOrganizations() { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/organizations`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function createOrganization(data) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.post(`${backendAPI}/organizations`, JSON.stringify(data), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function updateOrganization(id, data) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.patch(`${backendAPI}/organizations/${id}`, JSON.stringify(data), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function deleteOrganization(id) { - const { backendAPI } = config; - - try { - await Axios.delete(`${backendAPI}/organizations/${id}`, { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function getOrganizationMembers(orgSlug, page, pageSize, filters = {}) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/memberships`, { - proxy: config.proxy, - params: { - ...filters, - org: orgSlug, - page, - page_size: pageSize, - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function inviteOrganizationMembers(orgId, data) { - const { backendAPI } = config; - try { - await Axios.post( - `${backendAPI}/invitations`, - { - ...data, - organization: orgId, - }, - { - proxy: config.proxy, - }, - ); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function updateOrganizationMembership(membershipId, data) { - const { backendAPI } = config; - let response = null; - try { - response = await Axios.patch( - `${backendAPI}/memberships/${membershipId}`, - { - ...data, - }, - { - proxy: config.proxy, - }, - ); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function deleteOrganizationMembership(membershipId) { - const { backendAPI } = config; - - try { - await Axios.delete(`${backendAPI}/memberships/${membershipId}`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - } - - async function getMembershipInvitation(id) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/invitations/${id}`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - Object.defineProperties( - this, - Object.freeze({ - server: { - value: Object.freeze({ - about, - share, - formats, - exception, - login, - logout, - changePassword, - requestPasswordReset, - resetPassword, - authorized, - register, - request: serverRequest, - userAgreements, - installedApps, - }), - writable: false, - }, - - projects: { - value: Object.freeze({ - get: getProjects, - searchNames: searchProjectNames, - save: saveProject, - create: createProject, - delete: deleteProject, - exportDataset: exportDataset('projects'), - backupProject, - restoreProject, - importDataset, - }), - writable: false, - }, - - tasks: { - value: Object.freeze({ - get: getTasks, - save: saveTask, - create: createTask, - delete: deleteTask, - exportDataset: exportDataset('tasks'), - export: exportTask, - import: importTask, - }), - writable: false, - }, - - jobs: { - value: Object.freeze({ - get: getJobs, - save: saveJob, - }), - writable: false, - }, - - users: { - value: Object.freeze({ - get: getUsers, - self: getSelf, - }), - writable: false, - }, - - frames: { - value: Object.freeze({ - getData, - getMeta, - getPreview, - getImageContext, - }), - writable: false, - }, - - annotations: { - value: Object.freeze({ - updateAnnotations, - getAnnotations, - dumpAnnotations, - uploadAnnotations, - }), - writable: false, - }, - - logs: { - value: Object.freeze({ - save: saveLogs, - }), - writable: false, - }, - - lambda: { - value: Object.freeze({ - list: getLambdaFunctions, - status: getRequestStatus, - requests: getLambdaRequests, - run: runLambdaRequest, - call: callLambdaFunction, - cancel: cancelLambdaRequest, - }), - writable: false, - }, - - issues: { - value: Object.freeze({ - create: createIssue, - update: updateIssue, - get: getJobIssues, - delete: deleteIssue, - }), - writable: false, - }, - - comments: { - value: Object.freeze({ - create: createComment, - }), - writable: false, - }, - - predictor: { - value: Object.freeze({ - status: predictorStatus, - predict: predictAnnotations, - }), - writable: false, - }, - - cloudStorages: { - value: Object.freeze({ - get: getCloudStorages, - getContent: getCloudStorageContent, - getPreview: getCloudStoragePreview, - getStatus: getCloudStorageStatus, - create: createCloudStorage, - delete: deleteCloudStorage, - update: updateCloudStorage, - }), - writable: false, - }, - - organizations: { - value: Object.freeze({ - get: getOrganizations, - create: createOrganization, - update: updateOrganization, - members: getOrganizationMembers, - invitation: getMembershipInvitation, - delete: deleteOrganization, - invite: inviteOrganizationMembers, - updateMembership: updateOrganizationMembership, - deleteMembership: deleteOrganizationMembership, - }), - writable: false, - }, - }), - ); - } - } - - const serverProxy = new ServerProxy(); - module.exports = serverProxy; -})(); diff --git a/cvat-core/src/server-proxy.ts b/cvat-core/src/server-proxy.ts new file mode 100644 index 000000000000..28de4228f0de --- /dev/null +++ b/cvat-core/src/server-proxy.ts @@ -0,0 +1,2396 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import FormData from 'form-data'; +import store from 'store'; +import Axios from 'axios'; +import * as tus from 'tus-js-client'; +import { Storage } from './storage'; +import { StorageLocation, WebhookSourceType } from './enums'; +import { isEmail } from './common'; +import config from './config'; +import DownloadWorker from './download.worker'; +import { ServerError } from './exceptions'; + +type Params = { + org: number | string, + use_default_location?: boolean, + location?: StorageLocation, + cloud_storage_id?: number, + format?: string, + filename?: string, + action?: string, +}; + +function enableOrganization() { + return { org: config.organizationID || '' }; +} + +function configureStorage(storage: Storage, useDefaultLocation = false): Partial { + return { + use_default_location: useDefaultLocation, + ...(!useDefaultLocation ? { + location: storage.location, + ...(storage.cloudStorageId ? { + cloud_storage_id: storage.cloudStorageId, + } : {}), + } : {}), + }; +} + +function removeToken() { + Axios.defaults.headers.common.Authorization = ''; + store.remove('token'); +} + +function waitFor(frequencyHz, predicate) { + return new Promise((resolve, reject) => { + if (typeof predicate !== 'function') { + reject(new Error(`Predicate must be a function, got ${typeof predicate}`)); + } + + const internalWait = () => { + let result = false; + try { + result = predicate(); + } catch (error) { + reject(error); + } + + if (result) { + resolve(); + } else { + setTimeout(internalWait, 1000 / frequencyHz); + } + }; + + setTimeout(internalWait); + }); +} + +async function chunkUpload(file, uploadConfig) { + const params = enableOrganization(); + const { + endpoint, chunkSize, totalSize, onUpdate, metadata, + } = uploadConfig; + const { totalSentSize } = uploadConfig; + const uploadResult = { totalSentSize }; + return new Promise((resolve, reject) => { + const upload = new tus.Upload(file, { + endpoint, + metadata: { + filename: file.name, + filetype: file.type, + ...metadata, + }, + headers: { + Authorization: Axios.defaults.headers.common.Authorization, + }, + chunkSize, + retryDelays: null, + onError(error) { + reject(error); + }, + onBeforeRequest(req) { + const xhr = req.getUnderlyingObject(); + const { org } = params; + req.setHeader('X-Organization', org); + xhr.withCredentials = true; + }, + onProgress(bytesUploaded) { + if (onUpdate && Number.isInteger(totalSentSize) && Number.isInteger(totalSize)) { + const currentUploadedSize = totalSentSize + bytesUploaded; + const percentage = currentUploadedSize / totalSize; + onUpdate(percentage); + } + }, + onAfterResponse(request, response) { + const uploadFilename = response.getHeader('Upload-Filename'); + if (uploadFilename) uploadResult.filename = uploadFilename; + }, + onSuccess() { + if (totalSentSize) uploadResult.totalSentSize += file.size; + resolve(uploadResult); + }, + }); + upload.start(); + }); +} + +function generateError(errorData) { + if (errorData.response) { + const message = `${errorData.message}. ${JSON.stringify(errorData.response.data) || ''}.`; + return new ServerError(message, errorData.response.status); + } + + // Server is unavailable (no any response) + const message = `${errorData.message}.`; // usually is "Error Network" + return new ServerError(message, 0); +} + +function prepareData(details) { + const data = new FormData(); + for (const [key, value] of Object.entries(details)) { + if (Array.isArray(value)) { + value.forEach((element, idx) => { + data.append(`${key}[${idx}]`, element); + }); + } else { + data.set(key, value); + } + } + return data; +} + +class WorkerWrappedAxios { + constructor(requestInterseptor) { + const worker = new DownloadWorker(requestInterseptor); + const requests = {}; + let requestId = 0; + + worker.onmessage = (e) => { + if (e.data.id in requests) { + if (e.data.isSuccess) { + requests[e.data.id].resolve(e.data.responseData); + } else { + requests[e.data.id].reject({ + response: { + status: e.data.status, + data: e.data.responseData, + }, + }); + } + + delete requests[e.data.id]; + } + }; + + worker.onerror = (e) => { + if (e.data.id in requests) { + requests[e.data.id].reject(e); + delete requests[e.data.id]; + } + }; + + function getRequestId() { + return requestId++; + } + + async function get(url, requestConfig) { + return new Promise((resolve, reject) => { + const newRequestId = getRequestId(); + requests[newRequestId] = { + resolve, + reject, + }; + worker.postMessage({ + url, + config: requestConfig, + id: newRequestId, + }); + }); + } + + Object.defineProperties( + this, + Object.freeze({ + get: { + value: get, + writable: false, + }, + }), + ); + } +} + +Axios.defaults.withCredentials = true; +Axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN'; +Axios.defaults.xsrfCookieName = 'csrftoken'; +const workerAxios = new WorkerWrappedAxios(); +Axios.interceptors.request.use((reqConfig) => { + if ('params' in reqConfig && 'org' in reqConfig.params) { + return reqConfig; + } + + const organization = enableOrganization(); + // for users when organization is unset + // we are interested in getting all the users, + // not only those who are not in any organization + if (reqConfig.url.endsWith('/users') && !organization.org) { + return reqConfig; + } + + reqConfig.params = { ...organization, ...(reqConfig.params || {}) }; + return reqConfig; +}); + +let token = store.get('token'); +if (token) { + Axios.defaults.headers.common.Authorization = `Token ${token}`; +} + +async function about() { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/server/about`, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function share(directoryArg) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/server/share`, { + proxy: config.proxy, + params: { directory: directoryArg }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function exception(exceptionObject) { + const { backendAPI } = config; + + try { + await Axios.post(`${backendAPI}/server/exception`, JSON.stringify(exceptionObject), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function formats() { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/server/annotation/formats`, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function userAgreements() { + const { backendAPI } = config; + let response = null; + try { + response = await Axios.get(`${backendAPI}/user-agreements`, { + proxy: config.proxy, + validateStatus: (status) => status === 200 || status === 404, + }); + + if (response.status === 200) { + return response.data; + } + + return []; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function register(username, firstName, lastName, email, password, confirmations) { + let response = null; + try { + const data = JSON.stringify({ + username, + first_name: firstName, + last_name: lastName, + email, + password1: password, + password2: password, + confirmations, + }); + response = await Axios.post(`${config.backendAPI}/auth/register`, data, { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function login(username, email, password) { + const authenticationData = [ + // Uncomment this for sending email in request + // `${encodeURIComponent('email')}=${encodeURIComponent(email)}`, + `${encodeURIComponent('username')}=${encodeURIComponent(username)}`, + `${encodeURIComponent('email')}=${encodeURIComponent(email)}`, + `${encodeURIComponent('password')}=${encodeURIComponent(password)}`, + ] + .join('&') + .replace(/%20/g, '+'); + + removeToken(); + let authenticationResponse = null; + try { + authenticationResponse = await Axios.post(`${config.backendAPI}/auth/login`, authenticationData, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + if (authenticationResponse.headers['set-cookie']) { + // Browser itself setup cookie and header is none + // In NodeJS we need do it manually + const cookies = authenticationResponse.headers['set-cookie'].join(';'); + Axios.defaults.headers.common.Cookie = cookies; + } + + token = authenticationResponse.data.key; + store.set('token', token); + Axios.defaults.headers.common.Authorization = `Token ${token}`; +} + +async function loginWithSocialAccount( + provider: string, + code: string, + authParams?: string, + process?: string, + scope?: string, +) { + removeToken(); + const data = { + code, + ...(process ? { process } : {}), + ...(scope ? { scope } : {}), + ...(authParams ? { auth_params: authParams } : {}), + }; + let authenticationResponse = null; + try { + authenticationResponse = await Axios.post(`${config.backendAPI}/auth/${provider}/login/token`, data, + { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + token = authenticationResponse.data.key; + store.set('token', token); + Axios.defaults.headers.common.Authorization = `Token ${token}`; +} + +async function logout() { + try { + await Axios.post(`${config.backendAPI}/auth/logout`, { + proxy: config.proxy, + }); + removeToken(); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function changePassword(oldPassword, newPassword1, newPassword2) { + try { + const data = JSON.stringify({ + old_password: oldPassword, + new_password1: newPassword1, + new_password2: newPassword2, + }); + await Axios.post(`${config.backendAPI}/auth/password/change`, data, { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function requestPasswordReset(email) { + try { + const data = JSON.stringify({ + email, + }); + await Axios.post(`${config.backendAPI}/auth/password/reset`, data, { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function resetPassword(newPassword1, newPassword2, uid, _token) { + try { + const data = JSON.stringify({ + new_password1: newPassword1, + new_password2: newPassword2, + uid, + token: _token, + }); + await Axios.post(`${config.backendAPI}/auth/password/reset/confirm`, data, { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getSelf() { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/users/self`, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function authorized() { + try { + await getSelf(); + } catch (serverError) { + if (serverError.code === 401) { + // In CVAT app we use two types of authentication, + // So here we are forcing user have both credential types + // First request will fail if session is expired, then we check + // for precense of token + await logout(); + return false; + } + + throw serverError; + } + + return true; +} + +async function healthCheck(maxRetries, checkPeriod, requestTimeout, progressCallback, attempt = 0) { + const { backendAPI } = config; + const url = `${backendAPI}/server/health/?format=json`; + + if (progressCallback) { + progressCallback(`${attempt}/${attempt + maxRetries}`); + } + + return Axios.get(url, { + proxy: config.proxy, + timeout: requestTimeout, + }) + .then((response) => response.data) + .catch((errorData) => { + if (maxRetries > 0) { + return new Promise((resolve) => setTimeout(resolve, checkPeriod)) + .then(() => healthCheck(maxRetries - 1, checkPeriod, + requestTimeout, progressCallback, attempt + 1)); + } + throw generateError(errorData); + }); +} + +async function serverRequest(url, data) { + try { + return ( + await Axios({ + url, + ...data, + }) + ).data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function searchProjectNames(search, limit) { + const { backendAPI, proxy } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/projects`, { + proxy, + params: { + names_only: true, + page: 1, + page_size: limit, + search, + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + response.data.results.count = response.data.count; + return response.data.results; +} + +async function getProjects(filter = {}) { + const { backendAPI, proxy } = config; + + let response = null; + try { + if ('id' in filter) { + response = await Axios.get(`${backendAPI}/projects/${filter.id}`, { + proxy, + }); + const results = [response.data]; + results.count = 1; + return results; + } + + response = await Axios.get(`${backendAPI}/projects`, { + params: { + ...filter, + page_size: 12, + }, + proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + response.data.results.count = response.data.count; + return response.data.results; +} + +async function saveProject(id, projectData) { + const { backendAPI } = config; + + try { + await Axios.patch(`${backendAPI}/projects/${id}`, JSON.stringify(projectData), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function deleteProject(id) { + const { backendAPI } = config; + + try { + await Axios.delete(`${backendAPI}/projects/${id}`, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function createProject(projectSpec) { + const { backendAPI } = config; + + try { + const response = await Axios.post(`${backendAPI}/projects`, JSON.stringify(projectSpec), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getTasks(filter = {}) { + const { backendAPI } = config; + + let response = null; + try { + if ('id' in filter) { + response = await Axios.get(`${backendAPI}/tasks/${filter.id}`, { + proxy: config.proxy, + }); + const results = [response.data]; + results.count = 1; + return results; + } + + response = await Axios.get(`${backendAPI}/tasks`, { + params: { + ...filter, + page_size: 10, + }, + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + response.data.results.count = response.data.count; + return response.data.results; +} + +async function saveTask(id, taskData) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.patch(`${backendAPI}/tasks/${id}`, JSON.stringify(taskData), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function deleteTask(id, organizationID = null) { + const { backendAPI } = config; + + try { + await Axios.delete(`${backendAPI}/tasks/${id}`, { + ...(organizationID ? { org: organizationID } : {}), + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +function exportDataset(instanceType) { + return async function ( + id: number, + format: string, + saveImages: boolean, + useDefaultSettings: boolean, + targetStorage: Storage, + name?: string, + ) { + const { backendAPI } = config; + const baseURL = `${backendAPI}/${instanceType}/${id}/${saveImages ? 'dataset' : 'annotations'}`; + const params: Params = { + ...enableOrganization(), + ...configureStorage(targetStorage, useDefaultSettings), + ...(name ? { filename: name.replace(/\//g, '_') } : {}), + format, + }; + + return new Promise((resolve, reject) => { + async function request() { + Axios.get(baseURL, { + proxy: config.proxy, + params, + }) + .then((response) => { + const isCloudStorage = targetStorage.location === StorageLocation.CLOUD_STORAGE; + const { status } = response; + if (status === 201) params.action = 'download'; + if (status === 202 || (isCloudStorage && status === 201)) { + setTimeout(request, 3000); + } else if (status === 201) { + resolve(`${baseURL}?${new URLSearchParams(params).toString()}`); + } else if (isCloudStorage && status === 200) { + resolve(); + } + }) + .catch((errorData) => { + reject(generateError(errorData)); + }); + } + + setTimeout(request); + }); + }; +} + +async function importDataset( + id: number, + format: string, + useDefaultLocation: boolean, + sourceStorage: Storage, + file: File | string, + options: { + convMaskToPoly: boolean, + updateStatusCallback: (s: string, n: number) => void, + }, +): Promise { + const { backendAPI, origin } = config; + const params: Params & { conv_mask_to_poly: boolean } = { + ...enableOrganization(), + ...configureStorage(sourceStorage, useDefaultLocation), + format, + filename: typeof file === 'string' ? file : file.name, + conv_mask_to_poly: options.convMaskToPoly, + }; + + const url = `${backendAPI}/projects/${id}/dataset`; + + async function wait() { + return new Promise((resolve, reject) => { + async function requestStatus() { + try { + const response = await Axios.get(url, { + params: { ...params, action: 'import_status' }, + proxy: config.proxy, + }); + if (response.status === 202) { + if (response.data.message) { + options.updateStatusCallback(response.data.message, response.data.progress || 0); + } + setTimeout(requestStatus, 3000); + } else if (response.status === 201) { + resolve(); + } else { + reject(generateError(response)); + } + } catch (error) { + reject(generateError(error)); + } + } + setTimeout(requestStatus, 2000); + }); + } + const isCloudStorage = sourceStorage.location === StorageLocation.CLOUD_STORAGE; + + if (isCloudStorage) { + try { + await Axios.post(url, + new FormData(), { + params, + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + } else { + const uploadConfig = { + chunkSize: config.uploadChunkSize * 1024 * 1024, + endpoint: `${origin}${backendAPI}/projects/${id}/dataset/`, + totalSentSize: 0, + totalSize: (file as File).size, + onUpdate: (percentage) => { + options.updateStatusCallback('The dataset is being uploaded to the server', percentage); + }, + }; + + try { + await Axios.post(url, + new FormData(), { + params, + proxy: config.proxy, + headers: { 'Upload-Start': true }, + }); + await chunkUpload(file, uploadConfig); + await Axios.post(url, + new FormData(), { + params, + proxy: config.proxy, + headers: { 'Upload-Finish': true }, + }); + } catch (errorData) { + throw generateError(errorData); + } + } + try { + return await wait(); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function backupTask(id: number, targetStorage: Storage, useDefaultSettings: boolean, fileName?: string) { + const { backendAPI } = config; + const params: Params = { + ...enableOrganization(), + ...configureStorage(targetStorage, useDefaultSettings), + ...(fileName ? { filename: fileName } : {}), + }; + const url = `${backendAPI}/tasks/${id}/backup`; + + return new Promise((resolve, reject) => { + async function request() { + try { + const response = await Axios.get(url, { + proxy: config.proxy, + params, + }); + const isCloudStorage = targetStorage.location === StorageLocation.CLOUD_STORAGE; + const { status } = response; + if (status === 201) params.action = 'download'; + if (status === 202 || (isCloudStorage && status === 201)) { + setTimeout(request, 3000); + } else if (status === 201) { + resolve(`${url}?${new URLSearchParams(params).toString()}`); + } else if (isCloudStorage && status === 200) { + resolve(); + } + } catch (errorData) { + reject(generateError(errorData)); + } + } + + setTimeout(request); + }); +} + +async function restoreTask(storage: Storage, file: File | string) { + const { backendAPI } = config; + // keep current default params to 'freeze" them during this request + const params: Params = { + ...enableOrganization(), + ...configureStorage(storage), + }; + + const url = `${backendAPI}/tasks/backup`; + const taskData = new FormData(); + let response; + + async function wait() { + return new Promise((resolve, reject) => { + async function checkStatus() { + try { + taskData.set('rq_id', response.data.rq_id); + response = await Axios.post(url, taskData, { + proxy: config.proxy, + params, + }); + if (response.status === 202) { + setTimeout(checkStatus, 3000); + } else { + // to be able to get the task after it was created, pass frozen params + const importedTask = await getTasks({ id: response.data.id, ...params }); + resolve(importedTask[0]); + } + } catch (errorData) { + reject(generateError(errorData)); + } + } + setTimeout(checkStatus); + }); + } + const isCloudStorage = storage.location === StorageLocation.CLOUD_STORAGE; + + if (isCloudStorage) { + params.filename = file as string; + response = await Axios.post(url, + new FormData(), { + params, + proxy: config.proxy, + }); + } else { + const uploadConfig = { + chunkSize: config.uploadChunkSize * 1024 * 1024, + endpoint: `${origin}${backendAPI}/tasks/backup/`, + totalSentSize: 0, + totalSize: (file as File).size, + }; + await Axios.post(url, + new FormData(), { + params, + proxy: config.proxy, + headers: { 'Upload-Start': true }, + }); + const { filename } = await chunkUpload(file, uploadConfig); + response = await Axios.post(url, + new FormData(), { + params: { ...params, filename }, + proxy: config.proxy, + headers: { 'Upload-Finish': true }, + }); + } + return wait(); +} + +async function backupProject( + id: number, + targetStorage: Storage, + useDefaultSettings: boolean, + fileName?: string, +) { + const { backendAPI } = config; + // keep current default params to 'freeze" them during this request + const params: Params = { + ...enableOrganization(), + ...configureStorage(targetStorage, useDefaultSettings), + ...(fileName ? { filename: fileName } : {}), + }; + + const url = `${backendAPI}/projects/${id}/backup`; + + return new Promise((resolve, reject) => { + async function request() { + try { + const response = await Axios.get(url, { + proxy: config.proxy, + params, + }); + const isCloudStorage = targetStorage.location === StorageLocation.CLOUD_STORAGE; + const { status } = response; + if (status === 201) params.action = 'download'; + if (status === 202 || (isCloudStorage && status === 201)) { + setTimeout(request, 3000); + } else if (status === 201) { + resolve(`${url}?${new URLSearchParams(params).toString()}`); + } else if (isCloudStorage && status === 200) { + resolve(); + } + } catch (errorData) { + reject(generateError(errorData)); + } + } + + setTimeout(request); + }); +} + +async function restoreProject(storage: Storage, file: File | string) { + const { backendAPI } = config; + // keep current default params to 'freeze" them during this request + const params: Params = { + ...enableOrganization(), + ...configureStorage(storage), + }; + + const url = `${backendAPI}/projects/backup`; + const projectData = new FormData(); + let response; + + async function wait() { + return new Promise((resolve, reject) => { + async function request() { + try { + projectData.set('rq_id', response.data.rq_id); + response = await Axios.post(`${backendAPI}/projects/backup`, projectData, { + proxy: config.proxy, + params, + }); + if (response.status === 202) { + setTimeout(request, 3000); + } else { + // to be able to get the task after it was created, pass frozen params + const restoredProject = await getProjects({ id: response.data.id, ...params }); + resolve(restoredProject[0]); + } + } catch (errorData) { + reject(generateError(errorData)); + } + } + + setTimeout(request); + }); + } + + const isCloudStorage = storage.location === StorageLocation.CLOUD_STORAGE; + + if (isCloudStorage) { + params.filename = file; + response = await Axios.post(url, + new FormData(), { + params, + proxy: config.proxy, + }); + } else { + const uploadConfig = { + chunkSize: config.uploadChunkSize * 1024 * 1024, + endpoint: `${origin}${backendAPI}/projects/backup/`, + totalSentSize: 0, + totalSize: (file as File).size, + }; + await Axios.post(url, + new FormData(), { + params, + proxy: config.proxy, + headers: { 'Upload-Start': true }, + }); + const { filename } = await chunkUpload(file, uploadConfig); + response = await Axios.post(url, + new FormData(), { + params: { ...params, filename }, + proxy: config.proxy, + headers: { 'Upload-Finish': true }, + }); + } + return wait(); +} + +async function createTask(taskSpec, taskDataSpec, onUpdate) { + const { backendAPI, origin } = config; + // keep current default params to 'freeze" them during this request + const params = enableOrganization(); + + async function wait(id) { + return new Promise((resolve, reject) => { + async function checkStatus() { + try { + const response = await Axios.get(`${backendAPI}/tasks/${id}/status`, { params }); + if (['Queued', 'Started'].includes(response.data.state)) { + if (response.data.message !== '') { + onUpdate(response.data.message, response.data.progress || 0); + } + setTimeout(checkStatus, 1000); + } else if (response.data.state === 'Finished') { + resolve(); + } else if (response.data.state === 'Failed') { + // If request has been successful, but task hasn't been created + // Then passed data is wrong and we can pass code 400 + const message = ` + Could not create the task on the server. ${response.data.message}. + `; + reject(new ServerError(message, 400)); + } else { + // If server has another status, it is unexpected + // Therefore it is server error and we can pass code 500 + reject( + new ServerError( + `Unknown task state has been received: ${response.data.state}`, + 500, + ), + ); + } + } catch (errorData) { + reject(generateError(errorData)); + } + } + + setTimeout(checkStatus, 1000); + }); + } + + const chunkSize = config.uploadChunkSize * 1024 * 1024; + const clientFiles = taskDataSpec.client_files; + const chunkFiles = []; + const bulkFiles = []; + let totalSize = 0; + let totalSentSize = 0; + for (const file of clientFiles) { + if (file.size > chunkSize) { + chunkFiles.push(file); + } else { + bulkFiles.push(file); + } + totalSize += file.size; + } + delete taskDataSpec.client_files; + + const taskData = new FormData(); + for (const [key, value] of Object.entries(taskDataSpec)) { + if (Array.isArray(value)) { + value.forEach((element, idx) => { + taskData.append(`${key}[${idx}]`, element); + }); + } else { + taskData.set(key, value); + } + } + + let response = null; + + onUpdate('The task is being created on the server..', null); + try { + response = await Axios.post(`${backendAPI}/tasks`, JSON.stringify(taskSpec), { + proxy: config.proxy, + params, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + onUpdate('The data are being uploaded to the server..', null); + + async function bulkUpload(taskId, files) { + const fileBulks = files.reduce((fileGroups, file) => { + const lastBulk = fileGroups[fileGroups.length - 1]; + if (chunkSize - lastBulk.size >= file.size) { + lastBulk.files.push(file); + lastBulk.size += file.size; + } else { + fileGroups.push({ files: [file], size: file.size }); + } + return fileGroups; + }, [{ files: [], size: 0 }]); + const totalBulks = fileBulks.length; + let currentChunkNumber = 0; + while (currentChunkNumber < totalBulks) { + for (const [idx, element] of fileBulks[currentChunkNumber].files.entries()) { + taskData.append(`client_files[${idx}]`, element); + } + const percentage = totalSentSize / totalSize; + onUpdate('The data are being uploaded to the server', percentage); + await Axios.post(`${backendAPI}/tasks/${taskId}/data`, taskData, { + ...params, + proxy: config.proxy, + headers: { 'Upload-Multiple': true }, + }); + for (let i = 0; i < fileBulks[currentChunkNumber].files.length; i++) { + taskData.delete(`client_files[${i}]`); + } + totalSentSize += fileBulks[currentChunkNumber].size; + currentChunkNumber++; + } + } + + try { + await Axios.post(`${backendAPI}/tasks/${response.data.id}/data`, + taskData, { + ...params, + proxy: config.proxy, + headers: { 'Upload-Start': true }, + }); + const uploadConfig = { + endpoint: `${origin}${backendAPI}/tasks/${response.data.id}/data/`, + onUpdate: (percentage) => { + onUpdate('The data are being uploaded to the server', percentage); + }, + chunkSize, + totalSize, + totalSentSize, + }; + for (const file of chunkFiles) { + uploadConfig.totalSentSize += await chunkUpload(file, uploadConfig); + } + if (bulkFiles.length > 0) { + await bulkUpload(response.data.id, bulkFiles); + } + await Axios.post(`${backendAPI}/tasks/${response.data.id}/data`, + taskData, { + ...params, + proxy: config.proxy, + headers: { 'Upload-Finish': true }, + }); + } catch (errorData) { + try { + await deleteTask(response.data.id, params.org || null); + } catch (_) { + // ignore + } + throw generateError(errorData); + } + + try { + await wait(response.data.id); + } catch (createException) { + await deleteTask(response.data.id, params.org || null); + throw createException; + } + + // to be able to get the task after it was created, pass frozen params + const createdTask = await getTasks({ id: response.data.id, ...params }); + return createdTask[0]; +} + +async function getJobs(filter = {}) { + const { backendAPI } = config; + const id = filter.id || null; + + let response = null; + try { + if (id !== null) { + response = await Axios.get(`${backendAPI}/jobs/${id}`, { + proxy: config.proxy, + }); + } else { + response = await Axios.get(`${backendAPI}/jobs`, { + proxy: config.proxy, + params: { + ...filter, + page_size: 12, + }, + }); + } + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function getJobIssues(jobID) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/jobs/${jobID}/issues`, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function createComment(data) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.post(`${backendAPI}/comments`, JSON.stringify(data), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function createIssue(data) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.post(`${backendAPI}/issues`, JSON.stringify(data), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function updateIssue(issueID, data) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.patch(`${backendAPI}/issues/${issueID}`, JSON.stringify(data), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function deleteIssue(issueID) { + const { backendAPI } = config; + + try { + await Axios.delete(`${backendAPI}/issues/${issueID}`); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function saveJob(id, jobData) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.patch(`${backendAPI}/jobs/${id}`, JSON.stringify(jobData), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function getUsers(filter = { page_size: 'all' }) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/users`, { + proxy: config.proxy, + params: { + ...filter, + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data.results; +} + +async function getPreview(tid, jid) { + const { backendAPI } = config; + + let response = null; + try { + const url = `${backendAPI}/${jid !== null ? 'jobs' : 'tasks'}/${jid || tid}/data`; + response = await Axios.get(url, { + params: { + type: 'preview', + }, + proxy: config.proxy, + responseType: 'blob', + }); + } catch (errorData) { + const code = errorData.response ? errorData.response.status : errorData.code; + throw new ServerError(`Could not get preview frame for the task ${tid} from the server`, code); + } + + return response.data; +} + +async function getImageContext(jid, frame) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/jobs/${jid}/data`, { + params: { + quality: 'original', + type: 'context_image', + number: frame, + }, + proxy: config.proxy, + responseType: 'blob', + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function getData(tid, jid, chunk) { + const { backendAPI } = config; + + const url = jid === null ? `tasks/${tid}/data` : `jobs/${jid}/data`; + + let response = null; + try { + response = await workerAxios.get(`${backendAPI}/${url}`, { + params: { + ...enableOrganization(), + quality: 'compressed', + type: 'chunk', + number: chunk, + }, + proxy: config.proxy, + responseType: 'arraybuffer', + }); + } catch (errorData) { + throw generateError({ + message: '', + response: { + ...errorData.response, + data: String.fromCharCode.apply(null, new Uint8Array(errorData.response.data)), + }, + }); + } + + return response; +} + +async function getMeta(session, jid) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/${session}s/${jid}/data/meta`, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function saveMeta(session, jid, meta) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.patch(`${backendAPI}/${session}s/${jid}/data/meta`, meta, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +// Session is 'task' or 'job' +async function getAnnotations(session, id) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/${session}s/${id}/annotations`, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +// Session is 'task' or 'job' +async function updateAnnotations(session, id, data, action) { + const { backendAPI } = config; + const url = `${backendAPI}/${session}s/${id}/annotations`; + const params = {}; + let requestFunc = null; + + if (action.toUpperCase() === 'PUT') { + requestFunc = Axios.put.bind(Axios); + } else { + requestFunc = Axios.patch.bind(Axios); + params.action = action; + } + + let response = null; + try { + response = await requestFunc(url, JSON.stringify(data), { + proxy: config.proxy, + params, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +// Session is 'task' or 'job' +async function uploadAnnotations( + session, + id: number, + format: string, + useDefaultLocation: boolean, + sourceStorage: Storage, + file: File | string, + options: { convMaskToPoly: boolean }, +): Promise { + const { backendAPI, origin } = config; + const params: Params & { conv_mask_to_poly: boolean } = { + ...enableOrganization(), + ...configureStorage(sourceStorage, useDefaultLocation), + format, + filename: typeof file === 'string' ? file : file.name, + conv_mask_to_poly: options.convMaskToPoly, + }; + + const url = `${backendAPI}/${session}s/${id}/annotations`; + + async function wait() { + return new Promise((resolve, reject) => { + async function requestStatus() { + try { + const response = await Axios.put( + url, + new FormData(), + { + params, + proxy: config.proxy, + }, + ); + if (response.status === 202) { + setTimeout(requestStatus, 3000); + } else { + resolve(); + } + } catch (errorData) { + reject(generateError(errorData)); + } + } + setTimeout(requestStatus); + }); + } + const isCloudStorage = sourceStorage.location === StorageLocation.CLOUD_STORAGE; + + if (isCloudStorage) { + try { + await Axios.post(url, + new FormData(), { + params, + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + } else { + const chunkSize = config.uploadChunkSize * 1024 * 1024; + const uploadConfig = { + chunkSize, + endpoint: `${origin}${backendAPI}/${session}s/${id}/annotations/`, + }; + + try { + await Axios.post(url, + new FormData(), { + params, + proxy: config.proxy, + headers: { 'Upload-Start': true }, + }); + await chunkUpload(file, uploadConfig); + await Axios.post(url, + new FormData(), { + params, + proxy: config.proxy, + headers: { 'Upload-Finish': true }, + }); + } catch (errorData) { + throw generateError(errorData); + } + } + + try { + return await wait(); + } catch (errorData) { + throw generateError(errorData); + } +} + +// Session is 'task' or 'job' +async function dumpAnnotations(id, name, format) { + const { backendAPI } = config; + const baseURL = `${backendAPI}/tasks/${id}/annotations`; + const params = enableOrganization(); + params.format = encodeURIComponent(format); + if (name) { + const filename = name.replace(/\//g, '_'); + params.filename = encodeURIComponent(filename); + } + + return new Promise((resolve, reject) => { + async function request() { + Axios.get(baseURL, { + proxy: config.proxy, + params, + }) + .then((response) => { + if (response.status === 202) { + setTimeout(request, 3000); + } else { + params.action = 'download'; + resolve(`${baseURL}?${new URLSearchParams(params).toString()}`); + } + }) + .catch((errorData) => { + reject(generateError(errorData)); + }); + } + + setTimeout(request); + }); +} + +async function saveLogs(logs) { + const { backendAPI } = config; + + try { + await Axios.post(`${backendAPI}/server/logs`, JSON.stringify(logs), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getLambdaFunctions() { + const { backendAPI } = config; + + try { + const response = await Axios.get(`${backendAPI}/lambda/functions`, { + proxy: config.proxy, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function runLambdaRequest(body) { + const { backendAPI } = config; + + try { + const response = await Axios.post(`${backendAPI}/lambda/requests`, JSON.stringify(body), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function callLambdaFunction(funId, body) { + const { backendAPI } = config; + + try { + const response = await Axios.post(`${backendAPI}/lambda/functions/${funId}`, JSON.stringify(body), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getLambdaRequests() { + const { backendAPI } = config; + + try { + const response = await Axios.get(`${backendAPI}/lambda/requests`, { + proxy: config.proxy, + }); + + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getRequestStatus(requestID) { + const { backendAPI } = config; + + try { + const response = await Axios.get(`${backendAPI}/lambda/requests/${requestID}`, { + proxy: config.proxy, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function cancelLambdaRequest(requestId) { + const { backendAPI } = config; + + try { + await Axios.delete(`${backendAPI}/lambda/requests/${requestId}`, { + method: 'DELETE', + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +function predictorStatus(projectId) { + const { backendAPI } = config; + + return new Promise((resolve, reject) => { + async function request() { + try { + const response = await Axios.get(`${backendAPI}/predict/status`, { + params: { + project: projectId, + }, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } + } + + const timeoutCallback = async () => { + let data = null; + try { + data = await request(); + if (data.status === 'queued') { + setTimeout(timeoutCallback, 1000); + } else if (data.status === 'done') { + resolve(data); + } else { + throw new Error(`Unknown status was received "${data.status}"`); + } + } catch (error) { + reject(error); + } + }; + + setTimeout(timeoutCallback); + }); +} + +function predictAnnotations(taskId, frame) { + return new Promise((resolve, reject) => { + const { backendAPI } = config; + + async function request() { + try { + const response = await Axios.get(`${backendAPI}/predict/frame`, { + params: { + task: taskId, + frame, + }, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } + } + + const timeoutCallback = async () => { + let data = null; + try { + data = await request(); + if (data.status === 'queued') { + setTimeout(timeoutCallback, 1000); + } else if (data.status === 'done') { + predictAnnotations.latestRequest.fetching = false; + resolve(data.annotation); + } else { + throw new Error(`Unknown status was received "${data.status}"`); + } + } catch (error) { + predictAnnotations.latestRequest.fetching = false; + reject(error); + } + }; + + const closureId = Date.now(); + predictAnnotations.latestRequest.id = closureId; + const predicate = () => !predictAnnotations.latestRequest.fetching || + predictAnnotations.latestRequest.id !== closureId; + if (predictAnnotations.latestRequest.fetching) { + waitFor(5, predicate).then(() => { + if (predictAnnotations.latestRequest.id !== closureId) { + resolve(null); + } else { + predictAnnotations.latestRequest.fetching = true; + setTimeout(timeoutCallback); + } + }); + } else { + predictAnnotations.latestRequest.fetching = true; + setTimeout(timeoutCallback); + } + }); +} + +predictAnnotations.latestRequest = { + fetching: false, + id: null, +}; + +async function installedApps() { + const { backendAPI } = config; + try { + const response = await Axios.get(`${backendAPI}/server/plugins`, { + proxy: config.proxy, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function createCloudStorage(storageDetail) { + const { backendAPI } = config; + + const storageDetailData = prepareData(storageDetail); + try { + const response = await Axios.post(`${backendAPI}/cloudstorages`, storageDetailData, { + proxy: config.proxy, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function updateCloudStorage(id, storageDetail) { + const { backendAPI } = config; + + const storageDetailData = prepareData(storageDetail); + try { + await Axios.patch(`${backendAPI}/cloudstorages/${id}`, storageDetailData, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getCloudStorages(filter = {}) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/cloudstorages`, { + proxy: config.proxy, + params: filter, + page_size: 12, + }); + } catch (errorData) { + throw generateError(errorData); + } + + response.data.results.count = response.data.count; + return response.data.results; +} + +async function getCloudStorageContent(id, manifestPath) { + const { backendAPI } = config; + + let response = null; + try { + const url = `${backendAPI}/cloudstorages/${id}/content${ + manifestPath ? `?manifest_path=${manifestPath}` : '' + }`; + response = await Axios.get(url, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function getCloudStoragePreview(id) { + const { backendAPI } = config; + + let response = null; + try { + const url = `${backendAPI}/cloudstorages/${id}/preview`; + response = await workerAxios.get(url, { + params: enableOrganization(), + proxy: config.proxy, + responseType: 'arraybuffer', + }); + } catch (errorData) { + throw generateError({ + message: '', + response: { + ...errorData.response, + data: String.fromCharCode.apply(null, new Uint8Array(errorData.response.data)), + }, + }); + } + + return new Blob([new Uint8Array(response)]); +} + +async function getCloudStorageStatus(id) { + const { backendAPI } = config; + + let response = null; + try { + const url = `${backendAPI}/cloudstorages/${id}/status`; + response = await Axios.get(url, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function deleteCloudStorage(id) { + const { backendAPI } = config; + + try { + await Axios.delete(`${backendAPI}/cloudstorages/${id}`, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getOrganizations() { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/organizations`, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function createOrganization(data) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.post(`${backendAPI}/organizations`, JSON.stringify(data), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function updateOrganization(id, data) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.patch(`${backendAPI}/organizations/${id}`, JSON.stringify(data), { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function deleteOrganization(id) { + const { backendAPI } = config; + + try { + await Axios.delete(`${backendAPI}/organizations/${id}`, { + proxy: config.proxy, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getOrganizationMembers(orgSlug, page, pageSize, filters = {}) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/memberships`, { + proxy: config.proxy, + params: { + ...filters, + org: orgSlug, + page, + page_size: pageSize, + }, + }); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function inviteOrganizationMembers(orgId, data) { + const { backendAPI } = config; + try { + await Axios.post( + `${backendAPI}/invitations`, + { + ...data, + organization: orgId, + }, + { + proxy: config.proxy, + }, + ); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function updateOrganizationMembership(membershipId, data) { + const { backendAPI } = config; + let response = null; + try { + response = await Axios.patch( + `${backendAPI}/memberships/${membershipId}`, + { + ...data, + }, + { + proxy: config.proxy, + }, + ); + } catch (errorData) { + throw generateError(errorData); + } + + return response.data; +} + +async function deleteOrganizationMembership(membershipId) { + const { backendAPI } = config; + + try { + await Axios.delete(`${backendAPI}/memberships/${membershipId}`, { + proxy: config.proxy, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getMembershipInvitation(id) { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/invitations/${id}`, { + proxy: config.proxy, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getWebhookDelivery(webhookID: number, deliveryID: number): Promise { + const params = enableOrganization(); + const { backendAPI } = config; + + try { + const response = await Axios.get(`${backendAPI}/webhooks/${webhookID}/deliveries/${deliveryID}`, { + proxy: config.proxy, + params, + headers: { + 'Content-Type': 'application/json', + }, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function getWebhooks(filter, pageSize = 10): Promise { + const params = enableOrganization(); + const { backendAPI } = config; + + try { + const response = await Axios.get(`${backendAPI}/webhooks`, { + proxy: config.proxy, + params: { + ...params, + ...filter, + page_size: pageSize, + }, + headers: { + 'Content-Type': 'application/json', + }, + }); + + response.data.results.count = response.data.count; + return response.data.results; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function createWebhook(webhookData: any): Promise { + const params = enableOrganization(); + const { backendAPI } = config; + + try { + const response = await Axios.post(`${backendAPI}/webhooks`, JSON.stringify(webhookData), { + proxy: config.proxy, + params, + headers: { + 'Content-Type': 'application/json', + }, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function updateWebhook(webhookID: number, webhookData: any): Promise { + const params = enableOrganization(); + const { backendAPI } = config; + + try { + const response = await Axios + .patch(`${backendAPI}/webhooks/${webhookID}`, JSON.stringify(webhookData), { + proxy: config.proxy, + params, + headers: { + 'Content-Type': 'application/json', + }, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function deleteWebhook(webhookID: number): Promise { + const params = enableOrganization(); + const { backendAPI } = config; + + try { + await Axios.delete(`${backendAPI}/webhooks/${webhookID}`, { + proxy: config.proxy, + params, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (errorData) { + throw generateError(errorData); + } +} + +async function pingWebhook(webhookID: number): Promise { + const params = enableOrganization(); + const { backendAPI } = config; + + async function waitPingDelivery(deliveryID: number): Promise { + return new Promise((resolve) => { + async function checkStatus(): Promise { + const delivery = await getWebhookDelivery(webhookID, deliveryID); + if (delivery.status_code) { + resolve(delivery); + } else { + setTimeout(checkStatus, 1000); + } + } + setTimeout(checkStatus, 1000); + }); + } + + try { + const response = await Axios.post(`${backendAPI}/webhooks/${webhookID}/ping`, { + proxy: config.proxy, + params, + headers: { + 'Content-Type': 'application/json', + }, + }); + + const deliveryID = response.data.id; + const delivery = await waitPingDelivery(deliveryID); + return delivery; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function receiveWebhookEvents(type: WebhookSourceType): Promise { + const { backendAPI } = config; + + try { + const response = await Axios.get(`${backendAPI}/webhooks/events`, { + proxy: config.proxy, + params: { + type, + }, + headers: { + 'Content-Type': 'application/json', + }, + }); + return response.data.events; + } catch (errorData) { + throw generateError(errorData); + } +} + +async function advancedAuthentication(): Promise { + const { backendAPI } = config; + try { + const response = await Axios.get(`${backendAPI}/server/advanced-auth`, { + proxy: config.proxy, + }); + return response.data; + } catch (errorData) { + throw generateError(errorData); + } +} + +export default Object.freeze({ + server: Object.freeze({ + about, + share, + formats, + exception, + login, + logout, + advancedAuthentication, + changePassword, + requestPasswordReset, + resetPassword, + authorized, + healthCheck, + register, + request: serverRequest, + userAgreements, + installedApps, + loginWithSocialAccount, + }), + + projects: Object.freeze({ + get: getProjects, + searchNames: searchProjectNames, + save: saveProject, + create: createProject, + delete: deleteProject, + exportDataset: exportDataset('projects'), + backup: backupProject, + restore: restoreProject, + importDataset, + }), + + tasks: Object.freeze({ + get: getTasks, + save: saveTask, + create: createTask, + delete: deleteTask, + exportDataset: exportDataset('tasks'), + backup: backupTask, + restore: restoreTask, + }), + + jobs: Object.freeze({ + get: getJobs, + save: saveJob, + exportDataset: exportDataset('jobs'), + }), + + users: Object.freeze({ + get: getUsers, + self: getSelf, + }), + + frames: Object.freeze({ + getData, + getMeta, + saveMeta, + getPreview, + getImageContext, + }), + + annotations: Object.freeze({ + updateAnnotations, + getAnnotations, + dumpAnnotations, + uploadAnnotations, + }), + + logs: Object.freeze({ + save: saveLogs, + }), + + lambda: Object.freeze({ + list: getLambdaFunctions, + status: getRequestStatus, + requests: getLambdaRequests, + run: runLambdaRequest, + call: callLambdaFunction, + cancel: cancelLambdaRequest, + }), + + issues: Object.freeze({ + create: createIssue, + update: updateIssue, + get: getJobIssues, + delete: deleteIssue, + }), + + comments: Object.freeze({ + create: createComment, + }), + + predictor: Object.freeze({ + status: predictorStatus, + predict: predictAnnotations, + }), + + cloudStorages: Object.freeze({ + get: getCloudStorages, + getContent: getCloudStorageContent, + getPreview: getCloudStoragePreview, + getStatus: getCloudStorageStatus, + create: createCloudStorage, + delete: deleteCloudStorage, + update: updateCloudStorage, + }), + + organizations: Object.freeze({ + get: getOrganizations, + create: createOrganization, + update: updateOrganization, + members: getOrganizationMembers, + invitation: getMembershipInvitation, + delete: deleteOrganization, + invite: inviteOrganizationMembers, + updateMembership: updateOrganizationMembership, + deleteMembership: deleteOrganizationMembership, + }), + + webhooks: Object.freeze({ + get: getWebhooks, + create: createWebhook, + update: updateWebhook, + delete: deleteWebhook, + ping: pingWebhook, + events: receiveWebhookEvents, + }), +}); diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js deleted file mode 100644 index 5803fcc20b1b..000000000000 --- a/cvat-core/src/session.js +++ /dev/null @@ -1,2418 +0,0 @@ -// Copyright (C) 2019-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - const PluginRegistry = require('./plugins'); - const loggerStorage = require('./logger-storage'); - const serverProxy = require('./server-proxy'); - const { - getFrame, getRanges, getPreview, clear: clearFrames, getContextImage, - } = require('./frames'); - const { ArgumentError, DataError } = require('./exceptions'); - const { JobStage, JobState } = require('./enums'); - const { Label } = require('./labels'); - const User = require('./user'); - const Issue = require('./issue'); - const { FieldUpdateTrigger, checkObjectType } = require('./common'); - - function buildDuplicatedAPI(prototype) { - Object.defineProperties(prototype, { - annotations: Object.freeze({ - value: { - async upload(file, loader) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.annotations.upload, - file, - loader, - ); - return result; - }, - - async save(onUpdate) { - const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.save, onUpdate); - return result; - }, - - async clear( - reload = false, startframe = undefined, endframe = undefined, delTrackKeyframesOnly = true, - ) { - const result = await PluginRegistry.apiWrapper.call( - this, prototype.annotations.clear, reload, startframe, endframe, delTrackKeyframesOnly, - ); - return result; - }, - - async statistics() { - const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.statistics); - return result; - }, - - async put(arrayOfObjects = []) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.annotations.put, - arrayOfObjects, - ); - return result; - }, - - async get(frame, allTracks = false, filters = []) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.annotations.get, - frame, - allTracks, - filters, - ); - return result; - }, - - async search(filters, frameFrom, frameTo) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.annotations.search, - filters, - frameFrom, - frameTo, - ); - return result; - }, - - async searchEmpty(frameFrom, frameTo) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.annotations.searchEmpty, - frameFrom, - frameTo, - ); - return result; - }, - - async select(objectStates, x, y) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.annotations.select, - objectStates, - x, - y, - ); - return result; - }, - - async merge(objectStates) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.annotations.merge, - objectStates, - ); - return result; - }, - - async split(objectState, frame) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.annotations.split, - objectState, - frame, - ); - return result; - }, - - async group(objectStates, reset = false) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.annotations.group, - objectStates, - reset, - ); - return result; - }, - - async import(data) { - const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.import, data); - return result; - }, - - async export() { - const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.export); - return result; - }, - - async exportDataset(format, saveImages, customName = '') { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.annotations.exportDataset, - format, - saveImages, - customName, - ); - return result; - }, - - hasUnsavedChanges() { - const result = prototype.annotations.hasUnsavedChanges.implementation.call(this); - return result; - }, - }, - writable: true, - }), - frames: Object.freeze({ - value: { - async get(frame, isPlaying = false, step = 1) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.frames.get, - frame, - isPlaying, - step, - ); - return result; - }, - async ranges() { - const result = await PluginRegistry.apiWrapper.call(this, prototype.frames.ranges); - return result; - }, - async preview() { - const result = await PluginRegistry.apiWrapper.call(this, prototype.frames.preview); - return result; - }, - async contextImage(frameId) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.frames.contextImage, - frameId, - ); - return result; - }, - }, - writable: true, - }), - logger: Object.freeze({ - value: { - async log(logType, payload = {}, wait = false) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.logger.log, - logType, - payload, - wait, - ); - return result; - }, - }, - writable: true, - }), - actions: Object.freeze({ - value: { - async undo(count = 1) { - const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.undo, count); - return result; - }, - async redo(count = 1) { - const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.redo, count); - return result; - }, - async freeze(frozen) { - const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.freeze, frozen); - return result; - }, - async clear() { - const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.clear); - return result; - }, - async get() { - const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.get); - return result; - }, - }, - writable: true, - }), - events: Object.freeze({ - value: { - async subscribe(evType, callback) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.events.subscribe, - evType, - callback, - ); - return result; - }, - async unsubscribe(evType, callback = null) { - const result = await PluginRegistry.apiWrapper.call( - this, - prototype.events.unsubscribe, - evType, - callback, - ); - return result; - }, - }, - writable: true, - }), - predictor: Object.freeze({ - value: { - async status() { - const result = await PluginRegistry.apiWrapper.call(this, prototype.predictor.status); - return result; - }, - async predict(frame) { - const result = await PluginRegistry.apiWrapper.call(this, prototype.predictor.predict, frame); - return result; - }, - }, - writable: true, - }), - }); - } - - /** - * Base abstract class for Task and Job. It contains common members. - * @hideconstructor - * @virtual - */ - class Session { - constructor() { - /** - * An interaction with annotations - * @namespace annotations - * @memberof Session - */ - /** - * Upload annotations from a dump file - * You need upload annotations from a server again after successful executing - * @method upload - * @memberof Session.annotations - * @param {File} annotations - a file with annotations - * @param {module:API.cvat.classes.Loader} loader - a loader - * which will be used to upload - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - /** - * Save all changes in annotations on a server - * Objects which hadn't been saved on a server before, - * get a serverID after saving. But received object states aren't updated. - * So, after successful saving it's recommended to update them manually - * (call the annotations.get() again) - * @method save - * @memberof Session.annotations - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @instance - * @async - * @param {function} [onUpdate] saving can be long. - * This callback can be used to notify a user about current progress - * Its argument is a text string - */ - /** - * Remove all annotations and optionally reinitialize it - * @method clear - * @memberof Session.annotations - * @param {boolean} [reload = false] reset all changes and - * reinitialize annotations by data from a server - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @instance - * @async - */ - /** - * Collect short statistics about a task or a job. - * @method statistics - * @memberof Session.annotations - * @returns {module:API.cvat.classes.Statistics} statistics object - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Create new objects from one-frame states - * After successful adding you need to update object states on a frame - * @method put - * @memberof Session.annotations - * @param {module:API.cvat.classes.ObjectState[]} data - * @returns {number[]} identificators of added objects - * array of objects on the specific frame - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.DataError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Get annotations for a specific frame - *
    Filter supports following operators: - * ==, !=, >, >=, <, <=, ~= and (), |, & for grouping. - *
    Filter supports properties: - * width, height, label, serverID, clientID, type, shape, occluded - *
    All prop values are case-sensitive. CVAT uses json queries for search. - *
    Examples: - *
      - *
    • label=="car" | label==["road sign"]
    • - *
    • width >= height
    • - *
    • attr["Attribute 1"] == attr["Attribute 2"]
    • - *
    • type=="track" & shape="rectangle"
    • - *
    • clientID == 50
    • - *
    • (label=="car" & attr["parked"]==true) - * | (label=="pedestrian" & width > 150)
    • - *
    • (( label==["car \"mazda\""]) & - * (attr["sunglass ( help ) es"]==true | - * (width > 150 | height > 150 & (clientID == serverID)))))
    • - *
    - * If you have double quotes in your query string, - * please escape them using back slash: \" - * @method get - * @param {integer} frame get objects from the frame - * @param {boolean} allTracks show all tracks - * even if they are outside and not keyframe - * @param {any[]} [filters = []] - * get only objects that satisfied to specific filters - * @returns {module:API.cvat.classes.ObjectState[]} - * @memberof Session.annotations - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Find a frame in the range [from, to] - * that contains at least one object satisfied to a filter - * @method search - * @memberof Session.annotations - * @param {ObjectFilter} [filter = []] filter - * @param {integer} from lower bound of a search - * @param {integer} to upper bound of a search - * @returns {integer|null} a frame that contains objects according to the filter - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Find the nearest empty frame without any annotations - * @method searchEmpty - * @memberof Session.annotations - * @param {integer} from lower bound of a search - * @param {integer} to upper bound of a search - * @returns {integer|null} a frame that contains objects according to the filter - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Select shape under a cursor by using minimal distance - * between a cursor and a shape edge or a shape point - * For closed shapes a cursor is placed inside a shape - * @method select - * @memberof Session.annotations - * @param {module:API.cvat.classes.ObjectState[]} objectStates - * objects which can be selected - * @param {float} x horizontal coordinate - * @param {float} y vertical coordinate - * @returns {Object} - * a pair of {state: ObjectState, distance: number} for selected object. - * Pair values can be null if there aren't any sutisfied objects - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Method unites several shapes and tracks into the one - * All shapes must be the same (rectangle, polygon, etc) - * All labels must be the same - * After successful merge you need to update object states on a frame - * @method merge - * @memberof Session.annotations - * @param {module:API.cvat.classes.ObjectState[]} objectStates - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Method splits a track into two parts - * (start frame: previous frame), (frame, last frame) - * After successful split you need to update object states on a frame - * @method split - * @memberof Session.annotations - * @param {module:API.cvat.classes.ObjectState} objectState - * @param {integer} frame - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Method creates a new group and put all passed objects into it - * After successful split you need to update object states on a frame - * @method group - * @memberof Session.annotations - * @param {module:API.cvat.classes.ObjectState[]} objectStates - * @param {boolean} reset pass "true" to reset group value (set it to 0) - * @returns {integer} an ID of created group - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Method indicates if there are any changes in - * annotations which haven't been saved on a server - *
    This function cannot be wrapped with a plugin - * @method hasUnsavedChanges - * @memberof Session.annotations - * @returns {boolean} - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - */ - /** - * - * Import raw data in a collection - * @method import - * @memberof Session.annotations - * @param {Object} data - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * - * Export a collection as a row data - * @method export - * @memberof Session.annotations - * @returns {Object} data - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Export as a dataset. - * Method builds a dataset in the specified format. - * @method exportDataset - * @memberof Session.annotations - * @param {module:String} format - a format - * @returns {string} An URL to the dataset file - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Namespace is used for an interaction with frames - * @namespace frames - * @memberof Session - */ - /** - * Get frame by its number - * @method get - * @memberof Session.frames - * @param {integer} frame number of frame which you want to get - * @returns {module:API.cvat.classes.FrameData} - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.DataError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - /** - * Get the first frame of a task for preview - * @method preview - * @memberof Session.frames - * @returns {string} - jpeg encoded image - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - /** - * Returns the ranges of cached frames - * @method ranges - * @memberof Session.frames - * @returns {Array.} - * @instance - * @async - */ - /** - * Namespace is used for an interaction with logs - * @namespace logger - * @memberof Session - */ - /** - * Create a log and add it to a log collection
    - * Durable logs will be added after "close" method is called for them
    - * The fields "task_id" and "job_id" automatically added when add logs - * through a task or a job
    - * Ignore rules exist for some logs (e.g. zoomImage, changeAttribute)
    - * Payload of ignored logs are shallowly combined to previous logs of the same type - * @method log - * @memberof Session.logger - * @param {module:API.cvat.enums.LogType | string} type - log type - * @param {Object} [payload = {}] - any other data that will be appended to the log - * @param {boolean} [wait = false] - specifies if log is durable - * @returns {module:API.cvat.classes.Log} - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - /** - * Namespace is used for an interaction with actions - * @namespace actions - * @memberof Session - */ - /** - * @typedef {Object} HistoryActions - * @property {string[]} [undo] - array of possible actions to undo - * @property {string[]} [redo] - array of possible actions to redo - * @global - */ - /** - * Make undo - * @method undo - * @memberof Session.actions - * @param {number} [count=1] number of actions to undo - * @returns {number[]} Array of affected objects - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Make redo - * @method redo - * @memberof Session.actions - * @param {number} [count=1] number of actions to redo - * @returns {number[]} Array of affected objects - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Freeze history (do not save new actions) - * @method freeze - * @memberof Session.actions - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Remove all actions from history - * @method clear - * @memberof Session.actions - * @throws {module:API.cvat.exceptions.PluginError} - * @instance - * @async - */ - /** - * Get actions - * @method get - * @memberof Session.actions - * @returns {HistoryActions} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @returns {Array.>} - * array of pairs [action name, frame number] - * @instance - * @async - */ - /** - * Namespace is used for an interaction with events - * @namespace events - * @memberof Session - */ - /** - * Subscribe on an event - * @method subscribe - * @memberof Session.events - * @param {module:API.cvat.enums.EventType} type - event type - * @param {functions} callback - function which will be called on event - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * Unsubscribe from an event. If callback is not provided, - * all callbacks will be removed from subscribers for the event - * @method unsubscribe - * @memberof Session.events - * @param {module:API.cvat.enums.EventType} type - event type - * @param {functions} [callback = null] - function which is called on event - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @instance - * @async - */ - /** - * @typedef {Object} PredictorStatus - * @property {string} message - message for a user to be displayed somewhere - * @property {number} projectScore - model accuracy - * @global - */ - /** - * Namespace is used for an interaction with events - * @namespace predictor - * @memberof Session - */ - /** - * Subscribe to updates of a ML model binded to the project - * @method status - * @memberof Session.predictor - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ServerError} - * @returns {PredictorStatus} - * @instance - * @async - */ - /** - * Get predictions from a ML model binded to the project - * @method predict - * @memberof Session.predictor - * @param {number} frame - number of frame to inference - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.DataError} - * @returns {object[] | null} annotations - * @instance - * @async - */ - } - } - - /** - * Class representing a job. - * @memberof module:API.cvat.classes - * @hideconstructor - * @extends Session - */ - class Job extends Session { - constructor(initialData) { - super(); - const data = { - id: undefined, - assignee: null, - stage: undefined, - state: undefined, - start_frame: undefined, - stop_frame: undefined, - project_id: null, - task_id: undefined, - labels: undefined, - dimension: undefined, - data_compressed_chunk_type: undefined, - data_chunk_size: undefined, - bug_tracker: null, - mode: undefined, - }; - - const updateTrigger = new FieldUpdateTrigger(); - - for (const property in data) { - if (Object.prototype.hasOwnProperty.call(data, property)) { - if (property in initialData) { - data[property] = initialData[property]; - } - - if (data[property] === undefined) { - throw new ArgumentError(`Job field "${property}" was not initialized`); - } - } - } - - if (data.assignee) data.assignee = new User(data.assignee); - if (Array.isArray(initialData.labels)) { - data.labels = initialData.labels.map((labelData) => { - // can be already wrapped to the class - // when create this job from Task constructor - if (labelData instanceof Label) { - return labelData; - } - - return new Label(labelData); - }); - } else { - throw new Error('Job labels must be an array'); - } - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ - id: { - get: () => data.id, - }, - /** - * Instance of a user who is responsible for the job annotations - * @name assignee - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Job - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - assignee: { - get: () => data.assignee, - set: (assignee) => { - if (assignee !== null && !(assignee instanceof User)) { - throw new ArgumentError('Value must be a user instance'); - } - updateTrigger.update('assignee'); - data.assignee = assignee; - }, - }, - /** - * @name stage - * @type {module:API.cvat.enums.JobStage} - * @memberof module:API.cvat.classes.Job - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - stage: { - get: () => data.stage, - set: (stage) => { - const type = JobStage; - let valueInEnum = false; - for (const value in type) { - if (type[value] === stage) { - valueInEnum = true; - break; - } - } - - if (!valueInEnum) { - throw new ArgumentError( - 'Value must be a value from the enumeration cvat.enums.JobStage', - ); - } - - updateTrigger.update('stage'); - data.stage = stage; - }, - }, - /** - * @name state - * @type {module:API.cvat.enums.JobState} - * @memberof module:API.cvat.classes.Job - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - state: { - get: () => data.state, - set: (state) => { - const type = JobState; - let valueInEnum = false; - for (const value in type) { - if (type[value] === state) { - valueInEnum = true; - break; - } - } - - if (!valueInEnum) { - throw new ArgumentError( - 'Value must be a value from the enumeration cvat.enums.JobState', - ); - } - - updateTrigger.update('state'); - data.state = state; - }, - }, - /** - * @name startFrame - * @type {integer} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ - startFrame: { - get: () => data.start_frame, - }, - /** - * @name stopFrame - * @type {integer} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ - stopFrame: { - get: () => data.stop_frame, - }, - /** - * @name projectId - * @type {integer|null} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ - projectId: { - get: () => data.project_id, - }, - /** - * @name taskId - * @type {integer} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ - taskId: { - get: () => data.task_id, - }, - /** - * @name labels - * @type {module:API.cvat.classes.Label[]} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ - labels: { - get: () => data.labels.filter((_label) => !_label.deleted), - }, - /** - * @name dimension - * @type {module:API.cvat.enums.DimensionType} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ - dimension: { - get: () => data.dimension, - }, - /** - * @name dataChunkSize - * @type {integer} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ - dataChunkSize: { - get: () => data.data_chunk_size, - set: (chunkSize) => { - if (typeof chunkSize !== 'number' || chunkSize < 1) { - throw new ArgumentError( - `Chunk size value must be a positive number. But value ${chunkSize} has been got.`, - ); - } - - data.data_chunk_size = chunkSize; - }, - }, - /** - * @name dataChunkSize - * @type {string} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ - dataChunkType: { - get: () => data.data_compressed_chunk_type, - }, - /** - * @name mode - * @type {string} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - */ - mode: { - get: () => data.mode, - }, - /** - * @name bugTracker - * @type {string|null} - * @memberof module:API.cvat.classes.Job - * @instance - * @readonly - */ - bugTracker: { - get: () => data.bug_tracker, - }, - _updateTrigger: { - get: () => updateTrigger, - }, - }), - ); - - // When we call a function, for example: task.annotations.get() - // In the method get we lose the task context - // So, we need return it - this.annotations = { - get: Object.getPrototypeOf(this).annotations.get.bind(this), - put: Object.getPrototypeOf(this).annotations.put.bind(this), - save: Object.getPrototypeOf(this).annotations.save.bind(this), - merge: Object.getPrototypeOf(this).annotations.merge.bind(this), - split: Object.getPrototypeOf(this).annotations.split.bind(this), - group: Object.getPrototypeOf(this).annotations.group.bind(this), - clear: Object.getPrototypeOf(this).annotations.clear.bind(this), - search: Object.getPrototypeOf(this).annotations.search.bind(this), - searchEmpty: Object.getPrototypeOf(this).annotations.searchEmpty.bind(this), - upload: Object.getPrototypeOf(this).annotations.upload.bind(this), - select: Object.getPrototypeOf(this).annotations.select.bind(this), - import: Object.getPrototypeOf(this).annotations.import.bind(this), - export: Object.getPrototypeOf(this).annotations.export.bind(this), - statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), - hasUnsavedChanges: Object.getPrototypeOf(this).annotations.hasUnsavedChanges.bind(this), - exportDataset: Object.getPrototypeOf(this).annotations.exportDataset.bind(this), - }; - - this.actions = { - undo: Object.getPrototypeOf(this).actions.undo.bind(this), - redo: Object.getPrototypeOf(this).actions.redo.bind(this), - freeze: Object.getPrototypeOf(this).actions.freeze.bind(this), - clear: Object.getPrototypeOf(this).actions.clear.bind(this), - get: Object.getPrototypeOf(this).actions.get.bind(this), - }; - - this.frames = { - get: Object.getPrototypeOf(this).frames.get.bind(this), - ranges: Object.getPrototypeOf(this).frames.ranges.bind(this), - preview: Object.getPrototypeOf(this).frames.preview.bind(this), - contextImage: Object.getPrototypeOf(this).frames.contextImage.bind(this), - }; - - this.logger = { - log: Object.getPrototypeOf(this).logger.log.bind(this), - }; - - this.predictor = { - status: Object.getPrototypeOf(this).predictor.status.bind(this), - predict: Object.getPrototypeOf(this).predictor.predict.bind(this), - }; - } - - /** - * Method updates job data like state, stage or assignee - * @method save - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async save() { - const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.save); - return result; - } - - /** - * Method returns a list of issues for a job - * @method issues - * @memberof module:API.cvat.classes.Job - * @returns {module:API.cvat.classes.Issue[]} - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async issues() { - const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.issues); - return result; - } - - /** - * Method adds a new issue to a job - * @method openIssue - * @memberof module:API.cvat.classes.Job - * @returns {module:API.cvat.classes.Issue} - * @param {module:API.cvat.classes.Issue} issue - * @param {string} message - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async openIssue(issue, message) { - const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.openIssue, issue, message); - return result; - } - - /** - * Method removes all job related data from the client (annotations, history, etc.) - * @method close - * @returns {module:API.cvat.classes.Job} - * @memberof module:API.cvat.classes.Job - * @readonly - * @async - * @instance - * @throws {module:API.cvat.exceptions.PluginError} - */ - async close() { - const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.close); - return result; - } - } - - /** - * Class representing a task - * @memberof module:API.cvat.classes - * @extends Session - */ - class Task extends Session { - /** - * In a fact you need use the constructor only if you want to create a task - * @param {object} initialData - Object which is used for initialization - *
    It can contain keys: - *
  • name - *
  • assignee - *
  • bug_tracker - *
  • labels - *
  • segment_size - *
  • overlap - */ - constructor(initialData) { - super(); - const data = { - id: undefined, - name: undefined, - project_id: null, - status: undefined, - size: undefined, - mode: undefined, - owner: null, - assignee: null, - created_date: undefined, - updated_date: undefined, - bug_tracker: undefined, - subset: undefined, - overlap: undefined, - segment_size: undefined, - image_quality: undefined, - start_frame: undefined, - stop_frame: undefined, - frame_filter: undefined, - data_chunk_size: undefined, - data_compressed_chunk_type: undefined, - data_original_chunk_type: undefined, - use_zip_chunks: undefined, - use_cache: undefined, - copy_data: undefined, - dimension: undefined, - cloud_storage_id: undefined, - sorting_method: undefined, - }; - - const updateTrigger = new FieldUpdateTrigger(); - - for (const property in data) { - if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { - data[property] = initialData[property]; - } - } - - if (data.assignee) data.assignee = new User(data.assignee); - if (data.owner) data.owner = new User(data.owner); - - data.labels = []; - data.jobs = []; - data.files = Object.freeze({ - server_files: [], - client_files: [], - remote_files: [], - }); - - if (Array.isArray(initialData.labels)) { - for (const label of initialData.labels) { - const classInstance = new Label(label); - data.labels.push(classInstance); - } - } - - if (Array.isArray(initialData.segments)) { - for (const segment of initialData.segments) { - if (Array.isArray(segment.jobs)) { - for (const job of segment.jobs) { - const jobInstance = new Job({ - url: job.url, - id: job.id, - assignee: job.assignee, - state: job.state, - stage: job.stage, - start_frame: segment.start_frame, - stop_frame: segment.stop_frame, - // following fields also returned when doing API request /jobs/ - // here we know them from task and append to constructor - task_id: data.id, - project_id: data.project_id, - labels: data.labels, - bug_tracker: data.bug_tracker, - mode: data.mode, - dimension: data.dimension, - data_compressed_chunk_type: data.data_compressed_chunk_type, - data_chunk_size: data.data_chunk_size, - }); - - data.jobs.push(jobInstance); - } - } - } - } - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ - id: { - get: () => data.id, - }, - /** - * @name name - * @type {string} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - name: { - get: () => data.name, - set: (value) => { - if (!value.trim().length) { - throw new ArgumentError('Value must not be empty'); - } - updateTrigger.update('name'); - data.name = value; - }, - }, - /** - * @name projectId - * @type {integer|null} - * @memberof module:API.cvat.classes.Task - * @instance - */ - projectId: { - get: () => data.project_id, - set: (projectId) => { - if (!Number.isInteger(projectId) || projectId <= 0) { - throw new ArgumentError('Value must be a positive integer'); - } - - updateTrigger.update('projectId'); - data.project_id = projectId; - }, - }, - /** - * @name status - * @type {module:API.cvat.enums.TaskStatus} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ - status: { - get: () => data.status, - }, - /** - * @name size - * @type {integer} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ - size: { - get: () => data.size, - }, - /** - * @name mode - * @type {TaskMode} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ - mode: { - get: () => data.mode, - }, - /** - * Instance of a user who has created the task - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ - owner: { - get: () => data.owner, - }, - /** - * Instance of a user who is responsible for the task - * @name assignee - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - assignee: { - get: () => data.assignee, - set: (assignee) => { - if (assignee !== null && !(assignee instanceof User)) { - throw new ArgumentError('Value must be a user instance'); - } - updateTrigger.update('assignee'); - data.assignee = assignee; - }, - }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ - createdDate: { - get: () => data.created_date, - }, - /** - * @name updatedDate - * @type {string} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ - updatedDate: { - get: () => data.updated_date, - }, - /** - * @name bugTracker - * @type {string} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - bugTracker: { - get: () => data.bug_tracker, - set: (tracker) => { - if (typeof tracker !== 'string') { - throw new ArgumentError( - `Subset value must be a string. But ${typeof tracker} has been got.`, - ); - } - - updateTrigger.update('bugTracker'); - data.bug_tracker = tracker; - }, - }, - /** - * @name subset - * @type {string} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exception.ArgumentError} - */ - subset: { - get: () => data.subset, - set: (subset) => { - if (typeof subset !== 'string') { - throw new ArgumentError( - `Subset value must be a string. But ${typeof subset} has been got.`, - ); - } - - updateTrigger.update('subset'); - data.subset = subset; - }, - }, - /** - * @name overlap - * @type {integer} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - overlap: { - get: () => data.overlap, - set: (overlap) => { - if (!Number.isInteger(overlap) || overlap < 0) { - throw new ArgumentError('Value must be a non negative integer'); - } - data.overlap = overlap; - }, - }, - /** - * @name segmentSize - * @type {integer} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - segmentSize: { - get: () => data.segment_size, - set: (segment) => { - if (!Number.isInteger(segment) || segment < 0) { - throw new ArgumentError('Value must be a positive integer'); - } - data.segment_size = segment; - }, - }, - /** - * @name imageQuality - * @type {integer} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - imageQuality: { - get: () => data.image_quality, - set: (quality) => { - if (!Number.isInteger(quality) || quality < 0) { - throw new ArgumentError('Value must be a positive integer'); - } - data.image_quality = quality; - }, - }, - /** - * @name useZipChunks - * @type {boolean} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - useZipChunks: { - get: () => data.use_zip_chunks, - set: (useZipChunks) => { - if (typeof useZipChunks !== 'boolean') { - throw new ArgumentError('Value must be a boolean'); - } - data.use_zip_chunks = useZipChunks; - }, - }, - /** - * @name useCache - * @type {boolean} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - useCache: { - get: () => data.use_cache, - set: (useCache) => { - if (typeof useCache !== 'boolean') { - throw new ArgumentError('Value must be a boolean'); - } - data.use_cache = useCache; - }, - }, - /** - * @name copyData - * @type {boolean} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - copyData: { - get: () => data.copy_data, - set: (copyData) => { - if (typeof copyData !== 'boolean') { - throw new ArgumentError('Value must be a boolean'); - } - data.copy_data = copyData; - }, - }, - /** - * @name labels - * @type {module:API.cvat.classes.Label[]} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - labels: { - get: () => data.labels.filter((_label) => !_label.deleted), - set: (labels) => { - if (!Array.isArray(labels)) { - throw new ArgumentError('Value must be an array of Labels'); - } - - for (const label of labels) { - if (!(label instanceof Label)) { - throw new ArgumentError( - `Each array value must be an instance of Label. ${typeof label} was found`, - ); - } - } - - const IDs = labels.map((_label) => _label.id); - const deletedLabels = data.labels.filter((_label) => !IDs.includes(_label.id)); - deletedLabels.forEach((_label) => { - _label.deleted = true; - }); - - updateTrigger.update('labels'); - data.labels = [...deletedLabels, ...labels]; - }, - }, - /** - * @name jobs - * @type {module:API.cvat.classes.Job[]} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ - jobs: { - get: () => [...data.jobs], - }, - /** - * List of files from shared resource or list of cloud storage files - * @name serverFiles - * @type {string[]} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - serverFiles: { - get: () => [...data.files.server_files], - set: (serverFiles) => { - if (!Array.isArray(serverFiles)) { - throw new ArgumentError( - `Value must be an array. But ${typeof serverFiles} has been got.`, - ); - } - - for (const value of serverFiles) { - if (typeof value !== 'string') { - throw new ArgumentError( - `Array values must be a string. But ${typeof value} has been got.`, - ); - } - } - - Array.prototype.push.apply(data.files.server_files, serverFiles); - }, - }, - /** - * List of files from client host - * @name clientFiles - * @type {File[]} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - clientFiles: { - get: () => [...data.files.client_files], - set: (clientFiles) => { - if (!Array.isArray(clientFiles)) { - throw new ArgumentError( - `Value must be an array. But ${typeof clientFiles} has been got.`, - ); - } - - for (const value of clientFiles) { - if (!(value instanceof File)) { - throw new ArgumentError( - `Array values must be a File. But ${value.constructor.name} has been got.`, - ); - } - } - - Array.prototype.push.apply(data.files.client_files, clientFiles); - }, - }, - /** - * List of files from remote host - * @name remoteFiles - * @type {File[]} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - remoteFiles: { - get: () => [...data.files.remote_files], - set: (remoteFiles) => { - if (!Array.isArray(remoteFiles)) { - throw new ArgumentError( - `Value must be an array. But ${typeof remoteFiles} has been got.`, - ); - } - - for (const value of remoteFiles) { - if (typeof value !== 'string') { - throw new ArgumentError( - `Array values must be a string. But ${typeof value} has been got.`, - ); - } - } - - Array.prototype.push.apply(data.files.remote_files, remoteFiles); - }, - }, - /** - * The first frame of a video to annotation - * @name startFrame - * @type {integer} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - startFrame: { - get: () => data.start_frame, - set: (frame) => { - if (!Number.isInteger(frame) || frame < 0) { - throw new ArgumentError('Value must be a not negative integer'); - } - data.start_frame = frame; - }, - }, - /** - * The last frame of a video to annotation - * @name stopFrame - * @type {integer} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - stopFrame: { - get: () => data.stop_frame, - set: (frame) => { - if (!Number.isInteger(frame) || frame < 0) { - throw new ArgumentError('Value must be a not negative integer'); - } - data.stop_frame = frame; - }, - }, - /** - * Filter to ignore some frames during task creation - * @name frameFilter - * @type {string} - * @memberof module:API.cvat.classes.Task - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - frameFilter: { - get: () => data.frame_filter, - set: (filter) => { - if (typeof filter !== 'string') { - throw new ArgumentError( - `Filter value must be a string. But ${typeof filter} has been got.`, - ); - } - - data.frame_filter = filter; - }, - }, - dataChunkSize: { - get: () => data.data_chunk_size, - set: (chunkSize) => { - if (typeof chunkSize !== 'number' || chunkSize < 1) { - throw new ArgumentError( - `Chunk size value must be a positive number. But value ${chunkSize} has been got.`, - ); - } - - data.data_chunk_size = chunkSize; - }, - }, - dataChunkType: { - get: () => data.data_compressed_chunk_type, - }, - /** - * @name dimension - * @type {module:API.cvat.enums.DimensionType} - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - */ - dimension: { - get: () => data.dimension, - }, - /** - * @name cloudStorageId - * @type {integer|null} - * @memberof module:API.cvat.classes.Task - * @instance - */ - cloudStorageId: { - get: () => data.cloud_storage_id, - }, - sortingMethod: { - /** - * @name sortingMethod - * @type {module:API.cvat.enums.SortingMethod} - * @memberof module:API.cvat.classes.Task - * @instance - * @readonly - */ - get: () => data.sorting_method, - }, - _internalData: { - get: () => data, - }, - _updateTrigger: { - get: () => updateTrigger, - }, - }), - ); - - // When we call a function, for example: task.annotations.get() - // In the method get we lose the task context - // So, we need return it - this.annotations = { - get: Object.getPrototypeOf(this).annotations.get.bind(this), - put: Object.getPrototypeOf(this).annotations.put.bind(this), - save: Object.getPrototypeOf(this).annotations.save.bind(this), - merge: Object.getPrototypeOf(this).annotations.merge.bind(this), - split: Object.getPrototypeOf(this).annotations.split.bind(this), - group: Object.getPrototypeOf(this).annotations.group.bind(this), - clear: Object.getPrototypeOf(this).annotations.clear.bind(this), - search: Object.getPrototypeOf(this).annotations.search.bind(this), - searchEmpty: Object.getPrototypeOf(this).annotations.searchEmpty.bind(this), - upload: Object.getPrototypeOf(this).annotations.upload.bind(this), - select: Object.getPrototypeOf(this).annotations.select.bind(this), - import: Object.getPrototypeOf(this).annotations.import.bind(this), - export: Object.getPrototypeOf(this).annotations.export.bind(this), - statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), - hasUnsavedChanges: Object.getPrototypeOf(this).annotations.hasUnsavedChanges.bind(this), - exportDataset: Object.getPrototypeOf(this).annotations.exportDataset.bind(this), - }; - - this.actions = { - undo: Object.getPrototypeOf(this).actions.undo.bind(this), - redo: Object.getPrototypeOf(this).actions.redo.bind(this), - freeze: Object.getPrototypeOf(this).actions.freeze.bind(this), - clear: Object.getPrototypeOf(this).actions.clear.bind(this), - get: Object.getPrototypeOf(this).actions.get.bind(this), - }; - - this.frames = { - get: Object.getPrototypeOf(this).frames.get.bind(this), - ranges: Object.getPrototypeOf(this).frames.ranges.bind(this), - preview: Object.getPrototypeOf(this).frames.preview.bind(this), - contextImage: Object.getPrototypeOf(this).frames.contextImage.bind(this), - }; - - this.logger = { - log: Object.getPrototypeOf(this).logger.log.bind(this), - }; - - this.predictor = { - status: Object.getPrototypeOf(this).predictor.status.bind(this), - predict: Object.getPrototypeOf(this).predictor.predict.bind(this), - }; - } - - /** - * Method removes all task related data from the client (annotations, history, etc.) - * @method close - * @returns {module:API.cvat.classes.Task} - * @memberof module:API.cvat.classes.Task - * @readonly - * @async - * @instance - * @throws {module:API.cvat.exceptions.PluginError} - */ - async close() { - const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.close); - return result; - } - - /** - * Method updates data of a created task or creates new task from scratch - * @method save - * @returns {module:API.cvat.classes.Task} - * @memberof module:API.cvat.classes.Task - * @param {function} [onUpdate] - the function which is used only if task hasn't - * been created yet. It called in order to notify about creation status. - * It receives the string parameter which is a status message - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async save(onUpdate = () => {}) { - const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.save, onUpdate); - return result; - } - - /** - * Method deletes a task from a server - * @method delete - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async delete() { - const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.delete); - return result; - } - - /** - * Method makes a backup of a task - * @method export - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async export() { - const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.export); - return result; - } - - /** - * Method imports a task from a backup - * @method import - * @memberof module:API.cvat.classes.Task - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - static async import(file) { - const result = await PluginRegistry.apiWrapper.call(this, Task.import, file); - return result; - } - } - - module.exports = { - Job, - Task, - }; - - const { - getAnnotations, - putAnnotations, - saveAnnotations, - hasUnsavedChanges, - searchAnnotations, - searchEmptyFrame, - mergeAnnotations, - splitAnnotations, - groupAnnotations, - clearAnnotations, - selectObject, - annotationsStatistics, - uploadAnnotations, - importAnnotations, - exportAnnotations, - exportDataset, - undoActions, - redoActions, - freezeHistory, - clearActions, - getActions, - closeSession, - } = require('./annotations'); - - buildDuplicatedAPI(Job.prototype); - buildDuplicatedAPI(Task.prototype); - - Job.prototype.save.implementation = async function () { - if (this.id) { - const jobData = this._updateTrigger.getUpdated(this); - if (jobData.assignee) { - jobData.assignee = jobData.assignee.id; - } - - const data = await serverProxy.jobs.save(this.id, jobData); - this._updateTrigger.reset(); - return new Job(data); - } - - throw new ArgumentError('Could not save job without id'); - }; - - Job.prototype.issues.implementation = async function () { - const result = await serverProxy.issues.get(this.id); - return result.map((issue) => new Issue(issue)); - }; - - Job.prototype.openIssue.implementation = async function (issue, message) { - checkObjectType('issue', issue, null, Issue); - checkObjectType('message', message, 'string'); - const result = await serverProxy.issues.create({ - ...issue.serialize(), - message, - }); - return new Issue(result); - }; - - Job.prototype.frames.get.implementation = async function (frame, isPlaying, step) { - if (!Number.isInteger(frame) || frame < 0) { - throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); - } - - if (frame < this.startFrame || frame > this.stopFrame) { - throw new ArgumentError(`The frame with number ${frame} is out of the job`); - } - - const frameData = await getFrame( - this.taskId, - this.id, - this.dataChunkSize, - this.dataChunkType, - this.mode, - frame, - this.startFrame, - this.stopFrame, - isPlaying, - step, - this.dimension, - ); - return frameData; - }; - - Job.prototype.frames.ranges.implementation = async function () { - const rangesData = await getRanges(this.taskId); - return rangesData; - }; - - Job.prototype.frames.preview.implementation = async function () { - if (this.id === null || this.taskId === null) { - return ''; - } - - const frameData = await getPreview(this.taskId, this.id); - return frameData; - }; - - // TODO: Check filter for annotations - Job.prototype.annotations.get.implementation = async function (frame, allTracks, filters) { - if (!Array.isArray(filters)) { - throw new ArgumentError('Filters must be an array'); - } - - if (!Number.isInteger(frame)) { - throw new ArgumentError('The frame argument must be an integer'); - } - - if (frame < this.startFrame || frame > this.stopFrame) { - throw new ArgumentError(`Frame ${frame} does not exist in the job`); - } - - const annotationsData = await getAnnotations(this, frame, allTracks, filters); - return annotationsData; - }; - - Job.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) { - if (!Array.isArray(filters)) { - throw new ArgumentError('Filters must be an array'); - } - - if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { - throw new ArgumentError('The start and end frames both must be an integer'); - } - - if (frameFrom < this.startFrame || frameFrom > this.stopFrame) { - throw new ArgumentError('The start frame is out of the job'); - } - - if (frameTo < this.startFrame || frameTo > this.stopFrame) { - throw new ArgumentError('The stop frame is out of the job'); - } - - const result = searchAnnotations(this, filters, frameFrom, frameTo); - return result; - }; - - Job.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) { - if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { - throw new ArgumentError('The start and end frames both must be an integer'); - } - - if (frameFrom < this.startFrame || frameFrom > this.stopFrame) { - throw new ArgumentError('The start frame is out of the job'); - } - - if (frameTo < this.startFrame || frameTo > this.stopFrame) { - throw new ArgumentError('The stop frame is out of the job'); - } - - const result = searchEmptyFrame(this, frameFrom, frameTo); - return result; - }; - - Job.prototype.annotations.save.implementation = async function (onUpdate) { - const result = await saveAnnotations(this, onUpdate); - return result; - }; - - Job.prototype.annotations.merge.implementation = async function (objectStates) { - const result = await mergeAnnotations(this, objectStates); - return result; - }; - - Job.prototype.annotations.split.implementation = async function (objectState, frame) { - const result = await splitAnnotations(this, objectState, frame); - return result; - }; - - Job.prototype.annotations.group.implementation = async function (objectStates, reset) { - const result = await groupAnnotations(this, objectStates, reset); - return result; - }; - - Job.prototype.annotations.hasUnsavedChanges.implementation = function () { - const result = hasUnsavedChanges(this); - return result; - }; - - Job.prototype.annotations.clear.implementation = async function ( - reload, startframe, endframe, delTrackKeyframesOnly, - ) { - const result = await clearAnnotations(this, reload, startframe, endframe, delTrackKeyframesOnly); - return result; - }; - - Job.prototype.annotations.select.implementation = function (frame, x, y) { - const result = selectObject(this, frame, x, y); - return result; - }; - - Job.prototype.annotations.statistics.implementation = function () { - const result = annotationsStatistics(this); - return result; - }; - - Job.prototype.annotations.put.implementation = function (objectStates) { - const result = putAnnotations(this, objectStates); - return result; - }; - - Job.prototype.annotations.upload.implementation = async function (file, loader) { - const result = await uploadAnnotations(this, file, loader); - return result; - }; - - Job.prototype.annotations.import.implementation = function (data) { - const result = importAnnotations(this, data); - return result; - }; - - Job.prototype.annotations.export.implementation = function () { - const result = exportAnnotations(this); - return result; - }; - - Job.prototype.annotations.exportDataset.implementation = async function (format, saveImages, customName) { - const result = await exportDataset(this, format, customName, saveImages); - return result; - }; - - Job.prototype.actions.undo.implementation = function (count) { - const result = undoActions(this, count); - return result; - }; - - Job.prototype.actions.redo.implementation = function (count) { - const result = redoActions(this, count); - return result; - }; - - Job.prototype.actions.freeze.implementation = function (frozen) { - const result = freezeHistory(this, frozen); - return result; - }; - - Job.prototype.actions.clear.implementation = function () { - const result = clearActions(this); - return result; - }; - - Job.prototype.actions.get.implementation = function () { - const result = getActions(this); - return result; - }; - - Job.prototype.logger.log.implementation = async function (logType, payload, wait) { - const result = await loggerStorage.log(logType, { ...payload, task_id: this.taskId, job_id: this.id }, wait); - return result; - }; - - Job.prototype.predictor.status.implementation = async function () { - if (!Number.isInteger(this.projectId)) { - throw new DataError('The job must belong to a project to use the feature'); - } - - const result = await serverProxy.predictor.status(this.projectId); - return { - message: result.message, - progress: result.progress, - projectScore: result.score, - timeRemaining: result.time_remaining, - mediaAmount: result.media_amount, - annotationAmount: result.annotation_amount, - }; - }; - - Job.prototype.predictor.predict.implementation = async function (frame) { - if (!Number.isInteger(frame) || frame < 0) { - throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); - } - - if (frame < this.startFrame || frame > this.stopFrame) { - throw new ArgumentError(`The frame with number ${frame} is out of the job`); - } - - if (!Number.isInteger(this.projectId)) { - throw new DataError('The job must belong to a project to use the feature'); - } - - const result = await serverProxy.predictor.predict(this.taskId, frame); - return result; - }; - - Job.prototype.frames.contextImage.implementation = async function (frameId) { - const result = await getContextImage(this.taskId, this.id, frameId); - return result; - }; - - Job.prototype.close.implementation = function closeTask() { - clearFrames(this.taskId); - closeSession(this); - return this; - }; - - Task.prototype.close.implementation = function closeTask() { - clearFrames(this.id); - for (const job of this.jobs) { - closeSession(job); - } - - closeSession(this); - return this; - }; - - Task.prototype.save.implementation = async function (onUpdate) { - // TODO: Add ability to change an owner and an assignee - if (typeof this.id !== 'undefined') { - // If the task has been already created, we update it - const taskData = this._updateTrigger.getUpdated(this, { - bugTracker: 'bug_tracker', - projectId: 'project_id', - assignee: 'assignee_id', - }); - if (taskData.assignee_id) { - taskData.assignee_id = taskData.assignee_id.id; - } - if (taskData.labels) { - taskData.labels = this._internalData.labels; - taskData.labels = taskData.labels.map((el) => el.toJSON()); - } - - const data = await serverProxy.tasks.save(this.id, taskData); - this._updateTrigger.reset(); - return new Task(data); - } - - const taskSpec = { - name: this.name, - labels: this.labels.map((el) => el.toJSON()), - }; - - if (typeof this.bugTracker !== 'undefined') { - taskSpec.bug_tracker = this.bugTracker; - } - if (typeof this.segmentSize !== 'undefined') { - taskSpec.segment_size = this.segmentSize; - } - if (typeof this.overlap !== 'undefined') { - taskSpec.overlap = this.overlap; - } - if (typeof this.projectId !== 'undefined') { - taskSpec.project_id = this.projectId; - } - if (typeof this.subset !== 'undefined') { - taskSpec.subset = this.subset; - } - - const taskDataSpec = { - client_files: this.clientFiles, - server_files: this.serverFiles, - remote_files: this.remoteFiles, - image_quality: this.imageQuality, - use_zip_chunks: this.useZipChunks, - use_cache: this.useCache, - sorting_method: this.sortingMethod, - }; - - if (typeof this.startFrame !== 'undefined') { - taskDataSpec.start_frame = this.startFrame; - } - if (typeof this.stopFrame !== 'undefined') { - taskDataSpec.stop_frame = this.stopFrame; - } - if (typeof this.frameFilter !== 'undefined') { - taskDataSpec.frame_filter = this.frameFilter; - } - if (typeof this.dataChunkSize !== 'undefined') { - taskDataSpec.chunk_size = this.dataChunkSize; - } - if (typeof this.copyData !== 'undefined') { - taskDataSpec.copy_data = this.copyData; - } - if (typeof this.cloudStorageId !== 'undefined') { - taskDataSpec.cloud_storage_id = this.cloudStorageId; - } - - const task = await serverProxy.tasks.create(taskSpec, taskDataSpec, onUpdate); - return new Task(task); - }; - - Task.prototype.delete.implementation = async function () { - const result = await serverProxy.tasks.delete(this.id); - return result; - }; - - Task.prototype.export.implementation = async function () { - const result = await serverProxy.tasks.export(this.id); - return result; - }; - - Task.import.implementation = async function (file) { - // eslint-disable-next-line no-unsanitized/method - const result = await serverProxy.tasks.import(file); - return result; - }; - - Task.prototype.frames.get.implementation = async function (frame, isPlaying, step) { - if (!Number.isInteger(frame) || frame < 0) { - throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); - } - - if (frame >= this.size) { - throw new ArgumentError(`The frame with number ${frame} is out of the task`); - } - - const result = await getFrame( - this.id, - null, - this.dataChunkSize, - this.dataChunkType, - this.mode, - frame, - 0, - this.size - 1, - isPlaying, - step, - ); - return result; - }; - - Task.prototype.frames.ranges.implementation = async function () { - const rangesData = await getRanges(this.id); - return rangesData; - }; - - Task.prototype.frames.preview.implementation = async function () { - if (this.id === null) { - return ''; - } - - const frameData = await getPreview(this.id); - return frameData; - }; - - // TODO: Check filter for annotations - Task.prototype.annotations.get.implementation = async function (frame, allTracks, filters) { - if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) { - throw new ArgumentError('The filters argument must be an array of strings'); - } - - if (!Number.isInteger(frame) || frame < 0) { - throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); - } - - if (frame >= this.size) { - throw new ArgumentError(`Frame ${frame} does not exist in the task`); - } - - const result = await getAnnotations(this, frame, allTracks, filters); - return result; - }; - - Task.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) { - if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) { - throw new ArgumentError('The filters argument must be an array of strings'); - } - - if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { - throw new ArgumentError('The start and end frames both must be an integer'); - } - - if (frameFrom < 0 || frameFrom >= this.size) { - throw new ArgumentError('The start frame is out of the task'); - } - - if (frameTo < 0 || frameTo >= this.size) { - throw new ArgumentError('The stop frame is out of the task'); - } - - const result = searchAnnotations(this, filters, frameFrom, frameTo); - return result; - }; - - Task.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) { - if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { - throw new ArgumentError('The start and end frames both must be an integer'); - } - - if (frameFrom < 0 || frameFrom >= this.size) { - throw new ArgumentError('The start frame is out of the task'); - } - - if (frameTo < 0 || frameTo >= this.size) { - throw new ArgumentError('The stop frame is out of the task'); - } - - const result = searchEmptyFrame(this, frameFrom, frameTo); - return result; - }; - - Task.prototype.annotations.save.implementation = async function (onUpdate) { - const result = await saveAnnotations(this, onUpdate); - return result; - }; - - Task.prototype.annotations.merge.implementation = async function (objectStates) { - const result = await mergeAnnotations(this, objectStates); - return result; - }; - - Task.prototype.annotations.split.implementation = async function (objectState, frame) { - const result = await splitAnnotations(this, objectState, frame); - return result; - }; - - Task.prototype.annotations.group.implementation = async function (objectStates, reset) { - const result = await groupAnnotations(this, objectStates, reset); - return result; - }; - - Task.prototype.annotations.hasUnsavedChanges.implementation = function () { - const result = hasUnsavedChanges(this); - return result; - }; - - Task.prototype.annotations.clear.implementation = async function (reload) { - const result = await clearAnnotations(this, reload); - return result; - }; - - Task.prototype.annotations.select.implementation = function (frame, x, y) { - const result = selectObject(this, frame, x, y); - return result; - }; - - Task.prototype.annotations.statistics.implementation = function () { - const result = annotationsStatistics(this); - return result; - }; - - Task.prototype.annotations.put.implementation = function (objectStates) { - const result = putAnnotations(this, objectStates); - return result; - }; - - Task.prototype.annotations.upload.implementation = async function (file, loader) { - const result = await uploadAnnotations(this, file, loader); - return result; - }; - - Task.prototype.annotations.import.implementation = function (data) { - const result = importAnnotations(this, data); - return result; - }; - - Task.prototype.annotations.export.implementation = function () { - const result = exportAnnotations(this); - return result; - }; - - Task.prototype.annotations.exportDataset.implementation = async function (format, saveImages, customName) { - const result = await exportDataset(this, format, customName, saveImages); - return result; - }; - - Task.prototype.actions.undo.implementation = function (count) { - const result = undoActions(this, count); - return result; - }; - - Task.prototype.actions.redo.implementation = function (count) { - const result = redoActions(this, count); - return result; - }; - - Task.prototype.actions.freeze.implementation = function (frozen) { - const result = freezeHistory(this, frozen); - return result; - }; - - Task.prototype.actions.clear.implementation = function () { - const result = clearActions(this); - return result; - }; - - Task.prototype.actions.get.implementation = function () { - const result = getActions(this); - return result; - }; - - Task.prototype.logger.log.implementation = async function (logType, payload, wait) { - const result = await loggerStorage.log(logType, { ...payload, task_id: this.id }, wait); - return result; - }; - - Task.prototype.predictor.status.implementation = async function () { - if (!Number.isInteger(this.projectId)) { - throw new DataError('The task must belong to a project to use the feature'); - } - - const result = await serverProxy.predictor.status(this.projectId); - return { - message: result.message, - progress: result.progress, - projectScore: result.score, - timeRemaining: result.time_remaining, - mediaAmount: result.media_amount, - annotationAmount: result.annotation_amount, - }; - }; - - Task.prototype.predictor.predict.implementation = async function (frame) { - if (!Number.isInteger(frame) || frame < 0) { - throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); - } - - if (frame >= this.size) { - throw new ArgumentError(`The frame with number ${frame} is out of the task`); - } - - if (!Number.isInteger(this.projectId)) { - throw new DataError('The task must belong to a project to use the feature'); - } - - const result = await serverProxy.predictor.predict(this.id, frame); - return result; - }; -})(); diff --git a/cvat-core/src/session.ts b/cvat-core/src/session.ts new file mode 100644 index 000000000000..8c7362ac0f88 --- /dev/null +++ b/cvat-core/src/session.ts @@ -0,0 +1,2756 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { StorageLocation } from './enums'; +import { Storage } from './storage'; + +const PluginRegistry = require('./plugins').default; +const loggerStorage = require('./logger-storage').default; +const serverProxy = require('./server-proxy').default; +const { + getFrame, + deleteFrame, + restoreFrame, + getRanges, + getPreview, + clear: clearFrames, + findNotDeletedFrame, + getContextImage, + patchMeta, + getDeletedFrames, +} = require('./frames'); +const { ArgumentError, DataError } = require('./exceptions'); +const { + JobStage, JobState, HistoryActions, +} = require('./enums'); +const { Label } = require('./labels'); +const User = require('./user').default; +const Issue = require('./issue').default; +const { FieldUpdateTrigger, checkObjectType } = require('./common'); + +function buildDuplicatedAPI(prototype) { + Object.defineProperties(prototype, { + annotations: Object.freeze({ + value: { + async upload( + format: string, + useDefaultLocation: boolean, + sourceStorage: Storage, + file: File | string, + options?: { convMaskToPoly?: boolean }, + ) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.annotations.upload, + format, + useDefaultLocation, + sourceStorage, + file, + options, + ); + return result; + }, + + async save(onUpdate) { + const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.save, onUpdate); + return result; + }, + + async clear( + reload = false, startframe = undefined, endframe = undefined, delTrackKeyframesOnly = true, + ) { + const result = await PluginRegistry.apiWrapper.call( + this, prototype.annotations.clear, reload, startframe, endframe, delTrackKeyframesOnly, + ); + return result; + }, + + async statistics() { + const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.statistics); + return result; + }, + + async put(arrayOfObjects = []) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.annotations.put, + arrayOfObjects, + ); + return result; + }, + + async get(frame, allTracks = false, filters = []) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.annotations.get, + frame, + allTracks, + filters, + ); + return result; + }, + + async search(filters, frameFrom, frameTo) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.annotations.search, + filters, + frameFrom, + frameTo, + ); + return result; + }, + + async searchEmpty(frameFrom, frameTo) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.annotations.searchEmpty, + frameFrom, + frameTo, + ); + return result; + }, + + async select(objectStates, x, y) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.annotations.select, + objectStates, + x, + y, + ); + return result; + }, + + async merge(objectStates) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.annotations.merge, + objectStates, + ); + return result; + }, + + async split(objectState, frame) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.annotations.split, + objectState, + frame, + ); + return result; + }, + + async group(objectStates, reset = false) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.annotations.group, + objectStates, + reset, + ); + return result; + }, + + async import(data) { + const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.import, data); + return result; + }, + + async export() { + const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.export); + return result; + }, + + async exportDataset( + format: string, + saveImages: boolean, + useDefaultSettings: boolean, + targetStorage: Storage, + customName?: string, + ) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.annotations.exportDataset, + format, + saveImages, + useDefaultSettings, + targetStorage, + customName, + ); + return result; + }, + + hasUnsavedChanges() { + const result = prototype.annotations.hasUnsavedChanges.implementation.call(this); + return result; + }, + }, + writable: true, + }), + frames: Object.freeze({ + value: { + async get(frame, isPlaying = false, step = 1) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.frames.get, + frame, + isPlaying, + step, + ); + return result; + }, + async delete(frame) { + await PluginRegistry.apiWrapper.call( + this, + prototype.frames.delete, + frame, + ); + }, + async restore(frame) { + await PluginRegistry.apiWrapper.call( + this, + prototype.frames.restore, + frame, + ); + }, + async save() { + await PluginRegistry.apiWrapper.call( + this, + prototype.frames.save, + ); + }, + async ranges() { + const result = await PluginRegistry.apiWrapper.call(this, prototype.frames.ranges); + return result; + }, + async preview() { + const result = await PluginRegistry.apiWrapper.call(this, prototype.frames.preview); + return result; + }, + async search(filters, frameFrom, frameTo) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.frames.search, + filters, + frameFrom, + frameTo, + ); + return result; + }, + async contextImage(frameId) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.frames.contextImage, + frameId, + ); + return result; + }, + }, + writable: true, + }), + logger: Object.freeze({ + value: { + async log(logType, payload = {}, wait = false) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.logger.log, + logType, + payload, + wait, + ); + return result; + }, + }, + writable: true, + }), + actions: Object.freeze({ + value: { + async undo(count = 1) { + const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.undo, count); + return result; + }, + async redo(count = 1) { + const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.redo, count); + return result; + }, + async freeze(frozen) { + const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.freeze, frozen); + return result; + }, + async clear() { + const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.clear); + return result; + }, + async get() { + const result = await PluginRegistry.apiWrapper.call(this, prototype.actions.get); + return result; + }, + }, + writable: true, + }), + events: Object.freeze({ + value: { + async subscribe(evType, callback) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.events.subscribe, + evType, + callback, + ); + return result; + }, + async unsubscribe(evType, callback = null) { + const result = await PluginRegistry.apiWrapper.call( + this, + prototype.events.unsubscribe, + evType, + callback, + ); + return result; + }, + }, + writable: true, + }), + predictor: Object.freeze({ + value: { + async status() { + const result = await PluginRegistry.apiWrapper.call(this, prototype.predictor.status); + return result; + }, + async predict(frame) { + const result = await PluginRegistry.apiWrapper.call(this, prototype.predictor.predict, frame); + return result; + }, + }, + writable: true, + }), + }); +} + +/** + * Base abstract class for Task and Job. It contains common members. + * @hideconstructor + * @virtual + */ +export class Session { + constructor() { + /** + * An interaction with annotations + * @namespace annotations + * @memberof Session + */ + /** + * Upload annotations from a dump file + * You need upload annotations from a server again after successful executing + * @method upload + * @memberof Session.annotations + * @param {File} annotations - a file with annotations + * @param {module:API.cvat.classes.Loader} loader - a loader + * which will be used to upload + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + /** + * Save all changes in annotations on a server + * Objects which hadn't been saved on a server before, + * get a serverID after saving. But received object states aren't updated. + * So, after successful saving it's recommended to update them manually + * (call the annotations.get() again) + * @method save + * @memberof Session.annotations + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @instance + * @async + * @param {function} [onUpdate] saving can be long. + * This callback can be used to notify a user about current progress + * Its argument is a text string + */ + /** + * Remove all annotations and optionally reinitialize it + * @method clear + * @memberof Session.annotations + * @param {boolean} [reload = false] reset all changes and + * reinitialize annotations by data from a server + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @throws {module:API.cvat.exceptions.ServerError} + * @instance + * @async + */ + /** + * Collect short statistics about a task or a job. + * @method statistics + * @memberof Session.annotations + * @returns {module:API.cvat.classes.Statistics} statistics object + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + * @async + */ + /** + * Create new objects from one-frame states + * After successful adding you need to update object states on a frame + * @method put + * @memberof Session.annotations + * @param {module:API.cvat.classes.ObjectState[]} data + * @returns {number[]} identificators of added objects + * array of objects on the specific frame + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.DataError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Get annotations for a specific frame + *
    Filter supports following operators: + * ==, !=, >, >=, <, <=, ~= and (), |, & for grouping. + *
    Filter supports properties: + * width, height, label, serverID, clientID, type, shape, occluded + *
    All prop values are case-sensitive. CVAT uses json queries for search. + *
    Examples: + *
      + *
    • label=="car" | label==["road sign"]
    • + *
    • width >= height
    • + *
    • attr["Attribute 1"] == attr["Attribute 2"]
    • + *
    • type=="track" & shape="rectangle"
    • + *
    • clientID == 50
    • + *
    • (label=="car" & attr["parked"]==true) + * | (label=="pedestrian" & width > 150)
    • + *
    • (( label==["car \"mazda\""]) & + * (attr["sunglass ( help ) es"]==true | + * (width > 150 | height > 150 & (clientID == serverID)))))
    • + *
    + * If you have double quotes in your query string, + * please escape them using back slash: \" + * @method get + * @param {number} frame get objects from the frame + * @param {boolean} allTracks show all tracks + * even if they are outside and not keyframe + * @param {any[]} [filters = []] + * get only objects that satisfied to specific filters + * @returns {module:API.cvat.classes.ObjectState[]} + * @memberof Session.annotations + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Find a frame in the range [from, to] + * that contains at least one object satisfied to a filter + * @method search + * @memberof Session.annotations + * @param {ObjectFilter} [filter = []] filter + * @param {number} from lower bound of a search + * @param {number} to upper bound of a search + * @returns {number|null} a frame that contains objects according to the filter + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Find the nearest empty frame without any annotations + * @method searchEmpty + * @memberof Session.annotations + * @param {number} from lower bound of a search + * @param {number} to upper bound of a search + * @returns {number|null} a empty frame according boundaries + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Select shape under a cursor by using minimal distance + * between a cursor and a shape edge or a shape point + * For closed shapes a cursor is placed inside a shape + * @method select + * @memberof Session.annotations + * @param {module:API.cvat.classes.ObjectState[]} objectStates + * objects which can be selected + * @param {float} x horizontal coordinate + * @param {float} y vertical coordinate + * @returns {Object} + * a pair of {state: ObjectState, distance: number} for selected object. + * Pair values can be null if there aren't any sutisfied objects + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Method unites several shapes and tracks into the one + * All shapes must be the same (rectangle, polygon, etc) + * All labels must be the same + * After successful merge you need to update object states on a frame + * @method merge + * @memberof Session.annotations + * @param {module:API.cvat.classes.ObjectState[]} objectStates + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Method splits a track into two parts + * (start frame: previous frame), (frame, last frame) + * After successful split you need to update object states on a frame + * @method split + * @memberof Session.annotations + * @param {module:API.cvat.classes.ObjectState} objectState + * @param {number} frame + * @throws {module:API.cvat.exceptions.ArgumentError} + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + * @async + */ + /** + * Method creates a new group and put all passed objects into it + * After successful split you need to update object states on a frame + * @method group + * @memberof Session.annotations + * @param {module:API.cvat.classes.ObjectState[]} objectStates + * @param {boolean} reset pass "true" to reset group value (set it to 0) + * @returns {number} an ID of created group + * @throws {module:API.cvat.exceptions.ArgumentError} + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + * @async + */ + /** + * Method indicates if there are any changes in + * annotations which haven't been saved on a server + *
    This function cannot be wrapped with a plugin + * @method hasUnsavedChanges + * @memberof Session.annotations + * @returns {boolean} + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + */ + /** + * + * Import raw data in a collection + * @method import + * @memberof Session.annotations + * @param {Object} data + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * + * Export a collection as a row data + * @method export + * @memberof Session.annotations + * @returns {Object} data + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Export as a dataset. + * Method builds a dataset in the specified format. + * @method exportDataset + * @memberof Session.annotations + * @param {module:String} format - a format + * @returns {string} An URL to the dataset file + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Namespace is used for an interaction with frames + * @namespace frames + * @memberof Session + */ + /** + * Get frame by its number + * @method get + * @memberof Session.frames + * @param {number} frame number of frame which you want to get + * @returns {module:API.cvat.classes.FrameData} + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.DataError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + /** + * @typedef {Object} FrameSearchFilters + * @property {boolean} notDeleted if true will search for non-deleted frames + * @property {number} offset defines frame step during search + /** + * Find frame that match the condition + * @method search + * @memberof Session.frames + * @param {FrameSearchFilters} filters filters to search frame for + * @param {number} from lower bound of a search + * @param {number} to upper bound of a search + * @returns {number|null} a non-deleted frame according boundaries + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Delete frame from the job + * @method delete + * @memberof Session.frames + * @param {number} frame number of frame which you want to delete + * @throws {module:API.cvat.exceptions.ArgumentError} + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + * @async + */ + /** + * Restore frame from the job + * @method delete + * @memberof Session.frames + * @param {number} frame number of frame which you want to restore + * @throws {module:API.cvat.exceptions.ArgumentError} + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + * @async + */ + /** + * Save any changes in frames if some of them were deleted/restored + * @method save + * @memberof Session.frames + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + * @async + */ + /** + * Get the first frame of a task for preview + * @method preview + * @memberof Session.frames + * @returns {string} - jpeg encoded image + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + /** + * Returns the ranges of cached frames + * @method ranges + * @memberof Session.frames + * @returns {Array.} + * @instance + * @async + */ + /** + * Namespace is used for an interaction with logs + * @namespace logger + * @memberof Session + */ + /** + * Create a log and add it to a log collection
    + * Durable logs will be added after "close" method is called for them
    + * The fields "task_id" and "job_id" automatically added when add logs + * through a task or a job
    + * Ignore rules exist for some logs (e.g. zoomImage, changeAttribute)
    + * Payload of ignored logs are shallowly combined to previous logs of the same type + * @method log + * @memberof Session.logger + * @param {module:API.cvat.enums.LogType | string} type - log type + * @param {Object} [payload = {}] - any other data that will be appended to the log + * @param {boolean} [wait = false] - specifies if log is durable + * @returns {module:API.cvat.classes.Log} + * @instance + * @async + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + /** + * Namespace is used for an interaction with actions + * @namespace actions + * @memberof Session + */ + /** + * @typedef {Object} HistoryActions + * @property {string[]} [undo] - array of possible actions to undo + * @property {string[]} [redo] - array of possible actions to redo + * @global + */ + /** + * Make undo + * @method undo + * @memberof Session.actions + * @param {number} [count=1] number of actions to undo + * @returns {number[]} Array of affected objects + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Make redo + * @method redo + * @memberof Session.actions + * @param {number} [count=1] number of actions to redo + * @returns {number[]} Array of affected objects + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Freeze history (do not save new actions) + * @method freeze + * @memberof Session.actions + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + * @async + */ + /** + * Remove all actions from history + * @method clear + * @memberof Session.actions + * @throws {module:API.cvat.exceptions.PluginError} + * @instance + * @async + */ + /** + * Get actions + * @method get + * @memberof Session.actions + * @returns {HistoryActions} + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @returns {Array.>} + * array of pairs [action name, frame number] + * @instance + * @async + */ + /** + * Namespace is used for an interaction with events + * @namespace events + * @memberof Session + */ + /** + * Subscribe on an event + * @method subscribe + * @memberof Session.events + * @param {module:API.cvat.enums.EventType} type - event type + * @param {functions} callback - function which will be called on event + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * Unsubscribe from an event. If callback is not provided, + * all callbacks will be removed from subscribers for the event + * @method unsubscribe + * @memberof Session.events + * @param {module:API.cvat.enums.EventType} type - event type + * @param {functions} [callback = null] - function which is called on event + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ + /** + * @typedef {Object} PredictorStatus + * @property {string} message - message for a user to be displayed somewhere + * @property {number} projectScore - model accuracy + * @global + */ + /** + * Namespace is used for an interaction with events + * @namespace predictor + * @memberof Session + */ + /** + * Subscribe to updates of a ML model binded to the project + * @method status + * @memberof Session.predictor + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @returns {PredictorStatus} + * @instance + * @async + */ + /** + * Get predictions from a ML model binded to the project + * @method predict + * @memberof Session.predictor + * @param {number} frame - number of frame to inference + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.DataError} + * @returns {object[] | null} annotations + * @instance + * @async + */ + } +} + +/** + * Class representing a job. + * @memberof module:API.cvat.classes + * @hideconstructor + * @extends Session + */ +export class Job extends Session { + constructor(initialData) { + super(); + const data = { + id: undefined, + assignee: null, + stage: undefined, + state: undefined, + start_frame: undefined, + stop_frame: undefined, + project_id: null, + task_id: undefined, + labels: undefined, + dimension: undefined, + data_compressed_chunk_type: undefined, + data_chunk_size: undefined, + bug_tracker: null, + mode: undefined, + }; + + const updateTrigger = new FieldUpdateTrigger(); + + for (const property in data) { + if (Object.prototype.hasOwnProperty.call(data, property)) { + if (property in initialData) { + data[property] = initialData[property]; + } + + if (data[property] === undefined) { + throw new ArgumentError(`Job field "${property}" was not initialized`); + } + } + } + + if (data.assignee) data.assignee = new User(data.assignee); + if (Array.isArray(initialData.labels)) { + data.labels = initialData.labels.map((labelData) => { + // can be already wrapped to the class + // when create this job from Task constructor + if (labelData instanceof Label) { + return labelData; + } + + return new Label(labelData); + }).filter((label) => !label.hasParent); + } else { + throw new Error('Job labels must be an array'); + } + + Object.defineProperties( + this, + Object.freeze({ + /** + * @name id + * @type {number} + * @memberof module:API.cvat.classes.Job + * @readonly + * @instance + */ + id: { + get: () => data.id, + }, + /** + * Instance of a user who is responsible for the job annotations + * @name assignee + * @type {module:API.cvat.classes.User} + * @memberof module:API.cvat.classes.Job + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + assignee: { + get: () => data.assignee, + set: (assignee) => { + if (assignee !== null && !(assignee instanceof User)) { + throw new ArgumentError('Value must be a user instance'); + } + updateTrigger.update('assignee'); + data.assignee = assignee; + }, + }, + /** + * @name stage + * @type {module:API.cvat.enums.JobStage} + * @memberof module:API.cvat.classes.Job + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + stage: { + get: () => data.stage, + set: (stage) => { + const type = JobStage; + let valueInEnum = false; + for (const value in type) { + if (type[value] === stage) { + valueInEnum = true; + break; + } + } + + if (!valueInEnum) { + throw new ArgumentError( + 'Value must be a value from the enumeration cvat.enums.JobStage', + ); + } + + updateTrigger.update('stage'); + data.stage = stage; + }, + }, + /** + * @name state + * @type {module:API.cvat.enums.JobState} + * @memberof module:API.cvat.classes.Job + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + state: { + get: () => data.state, + set: (state) => { + const type = JobState; + let valueInEnum = false; + for (const value in type) { + if (type[value] === state) { + valueInEnum = true; + break; + } + } + + if (!valueInEnum) { + throw new ArgumentError( + 'Value must be a value from the enumeration cvat.enums.JobState', + ); + } + + updateTrigger.update('state'); + data.state = state; + }, + }, + /** + * @name startFrame + * @type {number} + * @memberof module:API.cvat.classes.Job + * @readonly + * @instance + */ + startFrame: { + get: () => data.start_frame, + }, + /** + * @name stopFrame + * @type {number} + * @memberof module:API.cvat.classes.Job + * @readonly + * @instance + */ + stopFrame: { + get: () => data.stop_frame, + }, + /** + * @name projectId + * @type {number|null} + * @memberof module:API.cvat.classes.Job + * @readonly + * @instance + */ + projectId: { + get: () => data.project_id, + }, + /** + * @name taskId + * @type {number} + * @memberof module:API.cvat.classes.Job + * @readonly + * @instance + */ + taskId: { + get: () => data.task_id, + }, + /** + * @name labels + * @type {module:API.cvat.classes.Label[]} + * @memberof module:API.cvat.classes.Job + * @readonly + * @instance + */ + labels: { + get: () => data.labels.filter((_label) => !_label.deleted), + }, + /** + * @name dimension + * @type {module:API.cvat.enums.DimensionType} + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + */ + dimension: { + get: () => data.dimension, + }, + /** + * @name dataChunkSize + * @type {number} + * @memberof module:API.cvat.classes.Job + * @readonly + * @instance + */ + dataChunkSize: { + get: () => data.data_chunk_size, + set: (chunkSize) => { + if (typeof chunkSize !== 'number' || chunkSize < 1) { + throw new ArgumentError( + `Chunk size value must be a positive number. But value ${chunkSize} has been got.`, + ); + } + + data.data_chunk_size = chunkSize; + }, + }, + /** + * @name dataChunkSize + * @type {string} + * @memberof module:API.cvat.classes.Job + * @readonly + * @instance + */ + dataChunkType: { + get: () => data.data_compressed_chunk_type, + }, + /** + * @name mode + * @type {string} + * @memberof module:API.cvat.classes.Job + * @readonly + * @instance + */ + mode: { + get: () => data.mode, + }, + /** + * @name bugTracker + * @type {string|null} + * @memberof module:API.cvat.classes.Job + * @instance + * @readonly + */ + bugTracker: { + get: () => data.bug_tracker, + }, + _updateTrigger: { + get: () => updateTrigger, + }, + }), + ); + + // When we call a function, for example: task.annotations.get() + // In the method get we lose the task context + // So, we need return it + this.annotations = { + get: Object.getPrototypeOf(this).annotations.get.bind(this), + put: Object.getPrototypeOf(this).annotations.put.bind(this), + save: Object.getPrototypeOf(this).annotations.save.bind(this), + merge: Object.getPrototypeOf(this).annotations.merge.bind(this), + split: Object.getPrototypeOf(this).annotations.split.bind(this), + group: Object.getPrototypeOf(this).annotations.group.bind(this), + clear: Object.getPrototypeOf(this).annotations.clear.bind(this), + search: Object.getPrototypeOf(this).annotations.search.bind(this), + searchEmpty: Object.getPrototypeOf(this).annotations.searchEmpty.bind(this), + upload: Object.getPrototypeOf(this).annotations.upload.bind(this), + select: Object.getPrototypeOf(this).annotations.select.bind(this), + import: Object.getPrototypeOf(this).annotations.import.bind(this), + export: Object.getPrototypeOf(this).annotations.export.bind(this), + statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), + hasUnsavedChanges: Object.getPrototypeOf(this).annotations.hasUnsavedChanges.bind(this), + exportDataset: Object.getPrototypeOf(this).annotations.exportDataset.bind(this), + }; + + this.actions = { + undo: Object.getPrototypeOf(this).actions.undo.bind(this), + redo: Object.getPrototypeOf(this).actions.redo.bind(this), + freeze: Object.getPrototypeOf(this).actions.freeze.bind(this), + clear: Object.getPrototypeOf(this).actions.clear.bind(this), + get: Object.getPrototypeOf(this).actions.get.bind(this), + }; + + this.frames = { + get: Object.getPrototypeOf(this).frames.get.bind(this), + delete: Object.getPrototypeOf(this).frames.delete.bind(this), + restore: Object.getPrototypeOf(this).frames.restore.bind(this), + save: Object.getPrototypeOf(this).frames.save.bind(this), + ranges: Object.getPrototypeOf(this).frames.ranges.bind(this), + preview: Object.getPrototypeOf(this).frames.preview.bind(this), + search: Object.getPrototypeOf(this).frames.search.bind(this), + contextImage: Object.getPrototypeOf(this).frames.contextImage.bind(this), + }; + + this.logger = { + log: Object.getPrototypeOf(this).logger.log.bind(this), + }; + + this.predictor = { + status: Object.getPrototypeOf(this).predictor.status.bind(this), + predict: Object.getPrototypeOf(this).predictor.predict.bind(this), + }; + } + + /** + * Method updates job data like state, stage or assignee + * @method save + * @memberof module:API.cvat.classes.Job + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async save() { + const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.save); + return result; + } + + /** + * Method returns a list of issues for a job + * @method issues + * @memberof module:API.cvat.classes.Job + * @returns {module:API.cvat.classes.Issue[]} + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async issues() { + const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.issues); + return result; + } + + /** + * Method adds a new issue to a job + * @method openIssue + * @memberof module:API.cvat.classes.Job + * @returns {module:API.cvat.classes.Issue} + * @param {module:API.cvat.classes.Issue} issue + * @param {string} message + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ArgumentError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async openIssue(issue, message) { + const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.openIssue, issue, message); + return result; + } + + /** + * Method removes all job related data from the client (annotations, history, etc.) + * @method close + * @returns {module:API.cvat.classes.Job} + * @memberof module:API.cvat.classes.Job + * @readonly + * @async + * @instance + * @throws {module:API.cvat.exceptions.PluginError} + */ + async close() { + const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.close); + return result; + } +} + +/** + * Class representing a task + * @memberof module:API.cvat.classes + * @extends Session + */ +export class Task extends Session { + /** + * In a fact you need use the constructor only if you want to create a task + * @param {object} initialData - Object which is used for initialization + *
    It can contain keys: + *
  • name + *
  • assignee + *
  • bug_tracker + *
  • labels + *
  • segment_size + *
  • overlap + */ + constructor(initialData) { + super(); + const data = { + id: undefined, + name: undefined, + project_id: null, + status: undefined, + size: undefined, + mode: undefined, + owner: null, + assignee: null, + created_date: undefined, + updated_date: undefined, + bug_tracker: undefined, + subset: undefined, + overlap: undefined, + segment_size: undefined, + image_quality: undefined, + start_frame: undefined, + stop_frame: undefined, + frame_filter: undefined, + data_chunk_size: undefined, + data_compressed_chunk_type: undefined, + data_original_chunk_type: undefined, + deleted_frames: undefined, + use_zip_chunks: undefined, + use_cache: undefined, + copy_data: undefined, + dimension: undefined, + cloud_storage_id: undefined, + sorting_method: undefined, + source_storage: undefined, + target_storage: undefined, + }; + + const updateTrigger = new FieldUpdateTrigger(); + + for (const property in data) { + if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { + data[property] = initialData[property]; + } + } + + if (data.assignee) data.assignee = new User(data.assignee); + if (data.owner) data.owner = new User(data.owner); + + data.labels = []; + data.jobs = []; + data.files = Object.freeze({ + server_files: [], + client_files: [], + remote_files: [], + }); + + if (Array.isArray(initialData.labels)) { + data.labels = initialData.labels + .map((labelData) => new Label(labelData)).filter((label) => !label.hasParent); + } + + if (Array.isArray(initialData.segments)) { + for (const segment of initialData.segments) { + if (Array.isArray(segment.jobs)) { + for (const job of segment.jobs) { + const jobInstance = new Job({ + url: job.url, + id: job.id, + assignee: job.assignee, + state: job.state, + stage: job.stage, + start_frame: segment.start_frame, + stop_frame: segment.stop_frame, + // following fields also returned when doing API request /jobs/ + // here we know them from task and append to constructor + task_id: data.id, + project_id: data.project_id, + labels: data.labels, + bug_tracker: data.bug_tracker, + mode: data.mode, + dimension: data.dimension, + data_compressed_chunk_type: data.data_compressed_chunk_type, + data_chunk_size: data.data_chunk_size, + }); + + data.jobs.push(jobInstance); + } + } + } + } + + Object.defineProperties( + this, + Object.freeze({ + /** + * @name id + * @type {number} + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + */ + id: { + get: () => data.id, + }, + /** + * @name name + * @type {string} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + name: { + get: () => data.name, + set: (value) => { + if (!value.trim().length) { + throw new ArgumentError('Value must not be empty'); + } + updateTrigger.update('name'); + data.name = value; + }, + }, + /** + * @name projectId + * @type {number|null} + * @memberof module:API.cvat.classes.Task + * @instance + */ + projectId: { + get: () => data.project_id, + set: (projectId) => { + if (!Number.isInteger(projectId) || projectId <= 0) { + throw new ArgumentError('Value must be a positive integer'); + } + + updateTrigger.update('projectId'); + data.project_id = projectId; + }, + }, + /** + * @name status + * @type {module:API.cvat.enums.TaskStatus} + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + */ + status: { + get: () => data.status, + }, + /** + * @name size + * @type {number} + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + */ + size: { + get: () => data.size, + }, + /** + * @name mode + * @type {TaskMode} + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + */ + mode: { + get: () => data.mode, + }, + /** + * Instance of a user who has created the task + * @name owner + * @type {module:API.cvat.classes.User} + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + */ + owner: { + get: () => data.owner, + }, + /** + * Instance of a user who is responsible for the task + * @name assignee + * @type {module:API.cvat.classes.User} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + assignee: { + get: () => data.assignee, + set: (assignee) => { + if (assignee !== null && !(assignee instanceof User)) { + throw new ArgumentError('Value must be a user instance'); + } + updateTrigger.update('assignee'); + data.assignee = assignee; + }, + }, + /** + * @name createdDate + * @type {string} + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + */ + createdDate: { + get: () => data.created_date, + }, + /** + * @name updatedDate + * @type {string} + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + */ + updatedDate: { + get: () => data.updated_date, + }, + /** + * @name bugTracker + * @type {string} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + bugTracker: { + get: () => data.bug_tracker, + set: (tracker) => { + if (typeof tracker !== 'string') { + throw new ArgumentError( + `Subset value must be a string. But ${typeof tracker} has been got.`, + ); + } + + updateTrigger.update('bugTracker'); + data.bug_tracker = tracker; + }, + }, + /** + * @name subset + * @type {string} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exception.ArgumentError} + */ + subset: { + get: () => data.subset, + set: (subset) => { + if (typeof subset !== 'string') { + throw new ArgumentError( + `Subset value must be a string. But ${typeof subset} has been got.`, + ); + } + + updateTrigger.update('subset'); + data.subset = subset; + }, + }, + /** + * @name overlap + * @type {number} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + overlap: { + get: () => data.overlap, + set: (overlap) => { + if (!Number.isInteger(overlap) || overlap < 0) { + throw new ArgumentError('Value must be a non negative integer'); + } + data.overlap = overlap; + }, + }, + /** + * @name segmentSize + * @type {number} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + segmentSize: { + get: () => data.segment_size, + set: (segment) => { + if (!Number.isInteger(segment) || segment < 0) { + throw new ArgumentError('Value must be a positive integer'); + } + data.segment_size = segment; + }, + }, + /** + * @name imageQuality + * @type {number} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + imageQuality: { + get: () => data.image_quality, + set: (quality) => { + if (!Number.isInteger(quality) || quality < 0) { + throw new ArgumentError('Value must be a positive integer'); + } + data.image_quality = quality; + }, + }, + /** + * @name useZipChunks + * @type {boolean} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + useZipChunks: { + get: () => data.use_zip_chunks, + set: (useZipChunks) => { + if (typeof useZipChunks !== 'boolean') { + throw new ArgumentError('Value must be a boolean'); + } + data.use_zip_chunks = useZipChunks; + }, + }, + /** + * @name useCache + * @type {boolean} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + useCache: { + get: () => data.use_cache, + set: (useCache) => { + if (typeof useCache !== 'boolean') { + throw new ArgumentError('Value must be a boolean'); + } + data.use_cache = useCache; + }, + }, + /** + * @name copyData + * @type {boolean} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + copyData: { + get: () => data.copy_data, + set: (copyData) => { + if (typeof copyData !== 'boolean') { + throw new ArgumentError('Value must be a boolean'); + } + data.copy_data = copyData; + }, + }, + /** + * @name labels + * @type {module:API.cvat.classes.Label[]} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + labels: { + get: () => data.labels.filter((_label) => !_label.deleted), + set: (labels) => { + if (!Array.isArray(labels)) { + throw new ArgumentError('Value must be an array of Labels'); + } + + for (const label of labels) { + if (!(label instanceof Label)) { + throw new ArgumentError( + `Each array value must be an instance of Label. ${typeof label} was found`, + ); + } + } + + const IDs = labels.map((_label) => _label.id); + const deletedLabels = data.labels.filter((_label) => !IDs.includes(_label.id)); + deletedLabels.forEach((_label) => { + _label.deleted = true; + }); + + updateTrigger.update('labels'); + data.labels = [...deletedLabels, ...labels]; + }, + }, + /** + * @name jobs + * @type {module:API.cvat.classes.Job[]} + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + */ + jobs: { + get: () => [...data.jobs], + }, + /** + * List of files from shared resource or list of cloud storage files + * @name serverFiles + * @type {string[]} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + serverFiles: { + get: () => [...data.files.server_files], + set: (serverFiles) => { + if (!Array.isArray(serverFiles)) { + throw new ArgumentError( + `Value must be an array. But ${typeof serverFiles} has been got.`, + ); + } + + for (const value of serverFiles) { + if (typeof value !== 'string') { + throw new ArgumentError( + `Array values must be a string. But ${typeof value} has been got.`, + ); + } + } + + Array.prototype.push.apply(data.files.server_files, serverFiles); + }, + }, + /** + * List of files from client host + * @name clientFiles + * @type {File[]} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + clientFiles: { + get: () => [...data.files.client_files], + set: (clientFiles) => { + if (!Array.isArray(clientFiles)) { + throw new ArgumentError( + `Value must be an array. But ${typeof clientFiles} has been got.`, + ); + } + + for (const value of clientFiles) { + if (!(value instanceof File)) { + throw new ArgumentError( + `Array values must be a File. But ${value.constructor.name} has been got.`, + ); + } + } + + Array.prototype.push.apply(data.files.client_files, clientFiles); + }, + }, + /** + * List of files from remote host + * @name remoteFiles + * @type {File[]} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + remoteFiles: { + get: () => [...data.files.remote_files], + set: (remoteFiles) => { + if (!Array.isArray(remoteFiles)) { + throw new ArgumentError( + `Value must be an array. But ${typeof remoteFiles} has been got.`, + ); + } + + for (const value of remoteFiles) { + if (typeof value !== 'string') { + throw new ArgumentError( + `Array values must be a string. But ${typeof value} has been got.`, + ); + } + } + + Array.prototype.push.apply(data.files.remote_files, remoteFiles); + }, + }, + /** + * The first frame of a video to annotation + * @name startFrame + * @type {number} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + startFrame: { + get: () => data.start_frame, + set: (frame) => { + if (!Number.isInteger(frame) || frame < 0) { + throw new ArgumentError('Value must be a not negative integer'); + } + data.start_frame = frame; + }, + }, + /** + * The last frame of a video to annotation + * @name stopFrame + * @type {number} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + stopFrame: { + get: () => data.stop_frame, + set: (frame) => { + if (!Number.isInteger(frame) || frame < 0) { + throw new ArgumentError('Value must be a not negative integer'); + } + data.stop_frame = frame; + }, + }, + /** + * Filter to ignore some frames during task creation + * @name frameFilter + * @type {string} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + frameFilter: { + get: () => data.frame_filter, + set: (filter) => { + if (typeof filter !== 'string') { + throw new ArgumentError( + `Filter value must be a string. But ${typeof filter} has been got.`, + ); + } + + data.frame_filter = filter; + }, + }, + dataChunkSize: { + get: () => data.data_chunk_size, + set: (chunkSize) => { + if (typeof chunkSize !== 'number' || chunkSize < 1) { + throw new ArgumentError( + `Chunk size value must be a positive number. But value ${chunkSize} has been got.`, + ); + } + + data.data_chunk_size = chunkSize; + }, + }, + dataChunkType: { + get: () => data.data_compressed_chunk_type, + }, + /** + * @name dimension + * @type {module:API.cvat.enums.DimensionType} + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + */ + dimension: { + get: () => data.dimension, + }, + /** + * @name cloudStorageId + * @type {integer|null} + * @memberof module:API.cvat.classes.Task + * @instance + */ + cloudStorageId: { + get: () => data.cloud_storage_id, + }, + sortingMethod: { + /** + * @name sortingMethod + * @type {module:API.cvat.enums.SortingMethod} + * @memberof module:API.cvat.classes.Task + * @instance + * @readonly + */ + get: () => data.sorting_method, + }, + /** + * Source storage for import resources. + * @name sourceStorage + * @type {module:API.cvat.classes.Storage} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + sourceStorage: { + get: () => ( + new Storage({ + location: data.source_storage?.location || StorageLocation.LOCAL, + cloudStorageId: data.source_storage?.cloud_storage_id, + }) + ), + }, + /** + * Target storage for export resources. + * @name targetStorage + * @type {module:API.cvat.classes.Storage} + * @memberof module:API.cvat.classes.Task + * @instance + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + targetStorage: { + get: () => ( + new Storage({ + location: data.target_storage?.location || StorageLocation.LOCAL, + cloudStorageId: data.target_storage?.cloud_storage_id, + }) + ), + }, + _internalData: { + get: () => data, + }, + _updateTrigger: { + get: () => updateTrigger, + }, + }), + ); + + // When we call a function, for example: task.annotations.get() + // In the method get we lose the task context + // So, we need return it + this.annotations = { + get: Object.getPrototypeOf(this).annotations.get.bind(this), + put: Object.getPrototypeOf(this).annotations.put.bind(this), + save: Object.getPrototypeOf(this).annotations.save.bind(this), + merge: Object.getPrototypeOf(this).annotations.merge.bind(this), + split: Object.getPrototypeOf(this).annotations.split.bind(this), + group: Object.getPrototypeOf(this).annotations.group.bind(this), + clear: Object.getPrototypeOf(this).annotations.clear.bind(this), + search: Object.getPrototypeOf(this).annotations.search.bind(this), + searchEmpty: Object.getPrototypeOf(this).annotations.searchEmpty.bind(this), + upload: Object.getPrototypeOf(this).annotations.upload.bind(this), + select: Object.getPrototypeOf(this).annotations.select.bind(this), + import: Object.getPrototypeOf(this).annotations.import.bind(this), + export: Object.getPrototypeOf(this).annotations.export.bind(this), + statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), + hasUnsavedChanges: Object.getPrototypeOf(this).annotations.hasUnsavedChanges.bind(this), + exportDataset: Object.getPrototypeOf(this).annotations.exportDataset.bind(this), + }; + + this.actions = { + undo: Object.getPrototypeOf(this).actions.undo.bind(this), + redo: Object.getPrototypeOf(this).actions.redo.bind(this), + freeze: Object.getPrototypeOf(this).actions.freeze.bind(this), + clear: Object.getPrototypeOf(this).actions.clear.bind(this), + get: Object.getPrototypeOf(this).actions.get.bind(this), + }; + + this.frames = { + get: Object.getPrototypeOf(this).frames.get.bind(this), + delete: Object.getPrototypeOf(this).frames.delete.bind(this), + restore: Object.getPrototypeOf(this).frames.restore.bind(this), + save: Object.getPrototypeOf(this).frames.save.bind(this), + ranges: Object.getPrototypeOf(this).frames.ranges.bind(this), + preview: Object.getPrototypeOf(this).frames.preview.bind(this), + contextImage: Object.getPrototypeOf(this).frames.contextImage.bind(this), + search: Object.getPrototypeOf(this).frames.search.bind(this), + }; + + this.logger = { + log: Object.getPrototypeOf(this).logger.log.bind(this), + }; + + this.predictor = { + status: Object.getPrototypeOf(this).predictor.status.bind(this), + predict: Object.getPrototypeOf(this).predictor.predict.bind(this), + }; + } + + /** + * Method removes all task related data from the client (annotations, history, etc.) + * @method close + * @returns {module:API.cvat.classes.Task} + * @memberof module:API.cvat.classes.Task + * @readonly + * @async + * @instance + * @throws {module:API.cvat.exceptions.PluginError} + */ + async close() { + const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.close); + return result; + } + + /** + * Method updates data of a created task or creates new task from scratch + * @method save + * @returns {module:API.cvat.classes.Task} + * @memberof module:API.cvat.classes.Task + * @param {function} [onUpdate] - the function which is used only if task hasn't + * been created yet. It called in order to notify about creation status. + * It receives the string parameter which is a status message + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async save(onUpdate = () => {}) { + const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.save, onUpdate); + return result; + } + + /** + * Method deletes a task from a server + * @method delete + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async delete() { + const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.delete); + return result; + } + + /** + * Method makes a backup of a task + * @method backup + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + async backup(targetStorage: Storage, useDefaultSettings: boolean, fileName?: string) { + const result = await PluginRegistry.apiWrapper.call( + this, + Task.prototype.backup, + targetStorage, + useDefaultSettings, + fileName, + ); + return result; + } + + /** + * Method restores a task from a backup + * @method restore + * @memberof module:API.cvat.classes.Task + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + */ + static async restore(storage: Storage, file: File | string) { + const result = await PluginRegistry.apiWrapper.call(this, Task.restore, storage, file); + return result; + } +} + +buildDuplicatedAPI(Job.prototype); +buildDuplicatedAPI(Task.prototype); + +(async () => { + const annotations = await import('./annotations'); + const { + getAnnotations, putAnnotations, saveAnnotations, + hasUnsavedChanges, searchAnnotations, searchEmptyFrame, + mergeAnnotations, splitAnnotations, groupAnnotations, + clearAnnotations, selectObject, annotationsStatistics, + importCollection, exportCollection, importDataset, + exportDataset, undoActions, redoActions, + freezeHistory, clearActions, getActions, + clearCache, getHistory, + } = annotations; + + + Job.prototype.save.implementation = async function () { + if (this.id) { + const jobData = this._updateTrigger.getUpdated(this); + if (jobData.assignee) { + jobData.assignee = jobData.assignee.id; + } + + const data = await serverProxy.jobs.save(this.id, jobData); + this._updateTrigger.reset(); + return new Job(data); + } + + throw new ArgumentError('Could not save job without id'); + }; + + Job.prototype.issues.implementation = async function () { + const result = await serverProxy.issues.get(this.id); + return result.map((issue) => new Issue(issue)); + }; + + Job.prototype.openIssue.implementation = async function (issue, message) { + checkObjectType('issue', issue, null, Issue); + checkObjectType('message', message, 'string'); + const result = await serverProxy.issues.create({ + ...issue.serialize(), + message, + }); + return new Issue(result); + }; + + Job.prototype.frames.get.implementation = async function (frame, isPlaying, step) { + if (!Number.isInteger(frame) || frame < 0) { + throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); + } + + if (frame < this.startFrame || frame > this.stopFrame) { + throw new ArgumentError(`The frame with number ${frame} is out of the job`); + } + + const frameData = await getFrame( + this.id, + this.dataChunkSize, + this.dataChunkType, + this.mode, + frame, + this.startFrame, + this.stopFrame, + isPlaying, + step, + this.dimension, + ); + return frameData; + }; + + // must be called with task/job context + async function deleteFrameWrapper(jobID, frame) { + const history = getHistory(this); + const redo = async () => { + deleteFrame(jobID, frame); + }; + + await redo(); + history.do(HistoryActions.REMOVED_FRAME, async () => { + restoreFrame(jobID, frame); + }, redo, [], frame); + } + + async function restoreFrameWrapper(jobID, frame) { + const history = getHistory(this); + const redo = async () => { + restoreFrame(jobID, frame); + }; + + await redo(); + history.do(HistoryActions.RESTORED_FRAME, async () => { + deleteFrame(jobID, frame); + }, redo, [], frame); + } + + Job.prototype.frames.delete.implementation = async function (frame) { + if (!Number.isInteger(frame)) { + throw new Error(`Frame must be an integer. Got: "${frame}"`); + } + + if (frame < this.startFrame || frame > this.stopFrame) { + throw new Error('The frame is out of the job'); + } + + await deleteFrameWrapper.call(this, this.id, frame); + }; + + Job.prototype.frames.restore.implementation = async function (frame) { + if (!Number.isInteger(frame)) { + throw new Error(`Frame must be an integer. Got: "${frame}"`); + } + + if (frame < this.startFrame || frame > this.stopFrame) { + throw new Error('The frame is out of the job'); + } + + await restoreFrameWrapper.call(this, this.id, frame); + }; + + Job.prototype.frames.save.implementation = async function () { + const result = await patchMeta(this.id); + return result; + }; + + Job.prototype.frames.ranges.implementation = async function () { + const rangesData = await getRanges(this.id); + return rangesData; + }; + + Job.prototype.frames.preview.implementation = async function () { + if (this.id === null || this.taskId === null) { + return ''; + } + + const frameData = await getPreview(this.taskId, this.id); + return frameData; + }; + + Job.prototype.frames.contextImage.implementation = async function (frameId) { + const result = await getContextImage(this.id, frameId); + return result; + }; + + Job.prototype.frames.search.implementation = async function (filters, frameFrom, frameTo) { + if (typeof filters !== 'object') { + throw new ArgumentError('Filters should be an object'); + } + + if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { + throw new ArgumentError('The start and end frames both must be an integer'); + } + + if (frameFrom < this.startFrame || frameFrom > this.stopFrame) { + throw new ArgumentError('The start frame is out of the job'); + } + + if (frameTo < this.startFrame || frameTo > this.stopFrame) { + throw new ArgumentError('The stop frame is out of the job'); + } + if (filters.notDeleted) { + return findNotDeletedFrame(this.id, frameFrom, frameTo, filters.offset || 1); + } + return null; + }; + + // TODO: Check filter for annotations + Job.prototype.annotations.get.implementation = async function (frame, allTracks, filters) { + if (!Array.isArray(filters)) { + throw new ArgumentError('Filters must be an array'); + } + + if (!Number.isInteger(frame)) { + throw new ArgumentError('The frame argument must be an integer'); + } + + if (frame < this.startFrame || frame > this.stopFrame) { + throw new ArgumentError(`Frame ${frame} does not exist in the job`); + } + + const annotationsData = await getAnnotations(this, frame, allTracks, filters); + const deletedFrames = await getDeletedFrames('job', this.id); + if (frame in deletedFrames) { + return []; + } + + return annotationsData; + }; + + Job.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) { + if (!Array.isArray(filters)) { + throw new ArgumentError('Filters must be an array'); + } + + if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { + throw new ArgumentError('The start and end frames both must be an integer'); + } + + if (frameFrom < this.startFrame || frameFrom > this.stopFrame) { + throw new ArgumentError('The start frame is out of the job'); + } + + if (frameTo < this.startFrame || frameTo > this.stopFrame) { + throw new ArgumentError('The stop frame is out of the job'); + } + + const result = searchAnnotations(this, filters, frameFrom, frameTo); + return result; + }; + + Job.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) { + if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { + throw new ArgumentError('The start and end frames both must be an integer'); + } + + if (frameFrom < this.startFrame || frameFrom > this.stopFrame) { + throw new ArgumentError('The start frame is out of the job'); + } + + if (frameTo < this.startFrame || frameTo > this.stopFrame) { + throw new ArgumentError('The stop frame is out of the job'); + } + + const result = searchEmptyFrame(this, frameFrom, frameTo); + return result; + }; + + Job.prototype.annotations.save.implementation = async function (onUpdate) { + const result = await saveAnnotations(this, onUpdate); + return result; + }; + + Job.prototype.annotations.merge.implementation = async function (objectStates) { + const result = await mergeAnnotations(this, objectStates); + return result; + }; + + Job.prototype.annotations.split.implementation = async function (objectState, frame) { + const result = await splitAnnotations(this, objectState, frame); + return result; + }; + + Job.prototype.annotations.group.implementation = async function (objectStates, reset) { + const result = await groupAnnotations(this, objectStates, reset); + return result; + }; + + Job.prototype.annotations.hasUnsavedChanges.implementation = function () { + const result = hasUnsavedChanges(this); + return result; + }; + + Job.prototype.annotations.clear.implementation = async function ( + reload, startframe, endframe, delTrackKeyframesOnly, + ) { + const result = await clearAnnotations(this, reload, startframe, endframe, delTrackKeyframesOnly); + return result; + }; + + Job.prototype.annotations.select.implementation = function (frame, x, y) { + const result = selectObject(this, frame, x, y); + return result; + }; + + Job.prototype.annotations.statistics.implementation = function () { + const result = annotationsStatistics(this); + return result; + }; + + Job.prototype.annotations.put.implementation = function (objectStates) { + const result = putAnnotations(this, objectStates); + return result; + }; + + Job.prototype.annotations.upload.implementation = async function ( + format: string, + useDefaultLocation: boolean, + sourceStorage: Storage, + file: File | string, + options?: { convMaskToPoly?: boolean }, + ) { + const result = await importDataset(this, format, useDefaultLocation, sourceStorage, file, options); + return result; + }; + + Job.prototype.annotations.import.implementation = function (data) { + const result = importCollection(this, data); + return result; + }; + + Job.prototype.annotations.export.implementation = function () { + const result = exportCollection(this); + return result; + }; + + Job.prototype.annotations.exportDataset.implementation = async function ( + format: string, + saveImages: boolean, + useDefaultSettings: boolean, + targetStorage: Storage, + customName?: string, + ) { + const result = await exportDataset(this, format, saveImages, useDefaultSettings, targetStorage, customName); + return result; + }; + + Job.prototype.actions.undo.implementation = async function (count) { + const result = await undoActions(this, count); + return result; + }; + + Job.prototype.actions.redo.implementation = async function (count) { + const result = await redoActions(this, count); + return result; + }; + + Job.prototype.actions.freeze.implementation = function (frozen) { + const result = freezeHistory(this, frozen); + return result; + }; + + Job.prototype.actions.clear.implementation = function () { + const result = clearActions(this); + return result; + }; + + Job.prototype.actions.get.implementation = function () { + const result = getActions(this); + return result; + }; + + Job.prototype.logger.log.implementation = async function (logType, payload, wait) { + const result = await loggerStorage.log(logType, { ...payload, task_id: this.taskId, job_id: this.id }, wait); + return result; + }; + + Job.prototype.predictor.status.implementation = async function () { + if (!Number.isInteger(this.projectId)) { + throw new DataError('The job must belong to a project to use the feature'); + } + + const result = await serverProxy.predictor.status(this.projectId); + return { + message: result.message, + progress: result.progress, + projectScore: result.score, + timeRemaining: result.time_remaining, + mediaAmount: result.media_amount, + annotationAmount: result.annotation_amount, + }; + }; + + Job.prototype.predictor.predict.implementation = async function (frame) { + if (!Number.isInteger(frame) || frame < 0) { + throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); + } + + if (frame < this.startFrame || frame > this.stopFrame) { + throw new ArgumentError(`The frame with number ${frame} is out of the job`); + } + + if (!Number.isInteger(this.projectId)) { + throw new DataError('The job must belong to a project to use the feature'); + } + + const result = await serverProxy.predictor.predict(this.taskId, frame); + return result; + }; + + Job.prototype.close.implementation = function closeTask() { + clearFrames(this.id); + clearCache(this); + return this; + }; + + Task.prototype.close.implementation = function closeTask() { + for (const job of this.jobs) { + clearFrames(job.id); + clearCache(job); + } + + clearCache(this); + return this; + }; + + Task.prototype.save.implementation = async function (onUpdate) { + // TODO: Add ability to change an owner and an assignee + if (typeof this.id !== 'undefined') { + // If the task has been already created, we update it + const taskData = this._updateTrigger.getUpdated(this, { + bugTracker: 'bug_tracker', + projectId: 'project_id', + assignee: 'assignee_id', + }); + if (taskData.assignee_id) { + taskData.assignee_id = taskData.assignee_id.id; + } + if (taskData.labels) { + taskData.labels = this._internalData.labels; + taskData.labels = taskData.labels.map((el) => el.toJSON()); + } + + const data = await serverProxy.tasks.save(this.id, taskData); + this._updateTrigger.reset(); + return new Task(data); + } + + const taskSpec: any = { + name: this.name, + labels: this.labels.map((el) => el.toJSON()), + }; + + if (typeof this.bugTracker !== 'undefined') { + taskSpec.bug_tracker = this.bugTracker; + } + if (typeof this.segmentSize !== 'undefined') { + taskSpec.segment_size = this.segmentSize; + } + if (typeof this.overlap !== 'undefined') { + taskSpec.overlap = this.overlap; + } + if (typeof this.projectId !== 'undefined') { + taskSpec.project_id = this.projectId; + } + if (typeof this.subset !== 'undefined') { + taskSpec.subset = this.subset; + } + + if (this.targetStorage) { + taskSpec.target_storage = this.targetStorage.toJSON(); + } + + if (this.sourceStorage) { + taskSpec.source_storage = this.sourceStorage.toJSON(); + } + + const taskDataSpec = { + client_files: this.clientFiles, + server_files: this.serverFiles, + remote_files: this.remoteFiles, + image_quality: this.imageQuality, + use_zip_chunks: this.useZipChunks, + use_cache: this.useCache, + sorting_method: this.sortingMethod, + }; + + if (typeof this.startFrame !== 'undefined') { + taskDataSpec.start_frame = this.startFrame; + } + if (typeof this.stopFrame !== 'undefined') { + taskDataSpec.stop_frame = this.stopFrame; + } + if (typeof this.frameFilter !== 'undefined') { + taskDataSpec.frame_filter = this.frameFilter; + } + if (typeof this.dataChunkSize !== 'undefined') { + taskDataSpec.chunk_size = this.dataChunkSize; + } + if (typeof this.copyData !== 'undefined') { + taskDataSpec.copy_data = this.copyData; + } + if (typeof this.cloudStorageId !== 'undefined') { + taskDataSpec.cloud_storage_id = this.cloudStorageId; + } + + const task = await serverProxy.tasks.create(taskSpec, taskDataSpec, onUpdate); + return new Task(task); + }; + + Task.prototype.delete.implementation = async function () { + const result = await serverProxy.tasks.delete(this.id); + return result; + }; + + Task.prototype.backup.implementation = async function ( + targetStorage: Storage, + useDefaultSettings: boolean, + fileName?: string, + ) { + const result = await serverProxy.tasks.backup(this.id, targetStorage, useDefaultSettings, fileName); + return result; + }; + + Task.restore.implementation = async function (storage: Storage, file: File | string) { + // eslint-disable-next-line no-unsanitized/method + const result = await serverProxy.tasks.restore(storage, file); + return result; + }; + + Task.prototype.frames.get.implementation = async function (frame, isPlaying, step) { + if (!Number.isInteger(frame) || frame < 0) { + throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); + } + + if (frame >= this.size) { + throw new ArgumentError(`The frame with number ${frame} is out of the task`); + } + + const job = this.jobs.filter((_job) => _job.startFrame <= frame && _job.stopFrame >= frame)[0]; + + const result = await getFrame( + job.id, + this.dataChunkSize, + this.dataChunkType, + this.mode, + frame, + job.startFrame, + job.stopFrame, + isPlaying, + step, + ); + return result; + }; + + Task.prototype.frames.ranges.implementation = async function () { + const rangesData = { + decoded: [], + buffered: [], + }; + for (const job of this.jobs) { + const { decoded, buffered } = await getRanges(job.id); + rangesData.decoded.push(decoded); + rangesData.buffered.push(buffered); + } + return rangesData; + }; + + Task.prototype.frames.preview.implementation = async function () { + if (this.id === null) { + return ''; + } + + const frameData = await getPreview(this.id); + return frameData; + }; + + Task.prototype.frames.delete.implementation = async function (frame) { + if (!Number.isInteger(frame)) { + throw new Error(`Frame must be an integer. Got: "${frame}"`); + } + + if (frame < 0 || frame >= this.size) { + throw new Error('The frame is out of the task'); + } + + const job = this.jobs.filter((_job) => _job.startFrame <= frame && _job.stopFrame >= frame)[0]; + if (job) { + await deleteFrameWrapper.call(this, job.id, frame); + } + }; + + Task.prototype.frames.restore.implementation = async function (frame) { + if (!Number.isInteger(frame)) { + throw new Error(`Frame must be an integer. Got: "${frame}"`); + } + + if (frame < 0 || frame >= this.size) { + throw new Error('The frame is out of the task'); + } + + const job = this.jobs.filter((_job) => _job.startFrame <= frame && _job.stopFrame >= frame)[0]; + if (job) { + await restoreFrameWrapper.call(this, job.id, frame); + } + }; + + Task.prototype.frames.save.implementation = async function () { + return Promise.all(this.jobs.map((job) => patchMeta(job.id))); + }; + + Task.prototype.frames.search.implementation = async function (filters, frameFrom, frameTo) { + if (typeof filters !== 'object') { + throw new ArgumentError('Filters should be an object'); + } + + if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { + throw new ArgumentError('The start and end frames both must be an integer'); + } + + if (frameFrom < 0 || frameFrom > this.size) { + throw new ArgumentError('The start frame is out of the task'); + } + + if (frameTo < 0 || frameTo > this.size) { + throw new ArgumentError('The stop frame is out of the task'); + } + + const jobs = this.jobs.filter((_job) => ( + (frameFrom >= _job.startFrame && frameFrom <= _job.stopFrame) || + (frameTo >= _job.startFrame && frameTo <= _job.stopFrame) || + (frameFrom < _job.startFrame && frameTo > _job.stopFrame) + )); + + if (filters.notDeleted) { + for (const job of jobs) { + const result = await findNotDeletedFrame( + job.id, Math.max(frameFrom, job.startFrame), Math.min(frameTo, job.stopFrame), 1, + ); + + if (result !== null) return result; + } + } + + return null; + }; + + // TODO: Check filter for annotations + Task.prototype.annotations.get.implementation = async function (frame, allTracks, filters) { + if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) { + throw new ArgumentError('The filters argument must be an array of strings'); + } + + if (!Number.isInteger(frame) || frame < 0) { + throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); + } + + if (frame >= this.size) { + throw new ArgumentError(`Frame ${frame} does not exist in the task`); + } + + const result = await getAnnotations(this, frame, allTracks, filters); + const deletedFrames = await getDeletedFrames('task', this.id); + if (frame in deletedFrames) { + return []; + } + + return result; + }; + + Task.prototype.annotations.search.implementation = function (filters, frameFrom, frameTo) { + if (!Array.isArray(filters) || filters.some((filter) => typeof filter !== 'string')) { + throw new ArgumentError('The filters argument must be an array of strings'); + } + + if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { + throw new ArgumentError('The start and end frames both must be an integer'); + } + + if (frameFrom < 0 || frameFrom >= this.size) { + throw new ArgumentError('The start frame is out of the task'); + } + + if (frameTo < 0 || frameTo >= this.size) { + throw new ArgumentError('The stop frame is out of the task'); + } + + const result = searchAnnotations(this, filters, frameFrom, frameTo); + return result; + }; + + Task.prototype.annotations.searchEmpty.implementation = function (frameFrom, frameTo) { + if (!Number.isInteger(frameFrom) || !Number.isInteger(frameTo)) { + throw new ArgumentError('The start and end frames both must be an integer'); + } + + if (frameFrom < 0 || frameFrom >= this.size) { + throw new ArgumentError('The start frame is out of the task'); + } + + if (frameTo < 0 || frameTo >= this.size) { + throw new ArgumentError('The stop frame is out of the task'); + } + + const result = searchEmptyFrame(this, frameFrom, frameTo); + return result; + }; + + Task.prototype.annotations.save.implementation = async function (onUpdate) { + const result = await saveAnnotations(this, onUpdate); + return result; + }; + + Task.prototype.annotations.merge.implementation = async function (objectStates) { + const result = await mergeAnnotations(this, objectStates); + return result; + }; + + Task.prototype.annotations.split.implementation = async function (objectState, frame) { + const result = await splitAnnotations(this, objectState, frame); + return result; + }; + + Task.prototype.annotations.group.implementation = async function (objectStates, reset) { + const result = await groupAnnotations(this, objectStates, reset); + return result; + }; + + Task.prototype.annotations.hasUnsavedChanges.implementation = function () { + const result = hasUnsavedChanges(this); + return result; + }; + + Task.prototype.annotations.clear.implementation = async function (reload) { + const result = await clearAnnotations(this, reload); + return result; + }; + + Task.prototype.annotations.select.implementation = function (frame, x, y) { + const result = selectObject(this, frame, x, y); + return result; + }; + + Task.prototype.annotations.statistics.implementation = function () { + const result = annotationsStatistics(this); + return result; + }; + + Task.prototype.annotations.put.implementation = function (objectStates) { + const result = putAnnotations(this, objectStates); + return result; + }; + + Task.prototype.annotations.upload.implementation = async function ( + format: string, + useDefaultLocation: boolean, + sourceStorage: Storage, + file: File | string, + options?: { convMaskToPoly?: boolean }, + ) { + const result = await importDataset(this, format, useDefaultLocation, sourceStorage, file, options); + return result; + }; + + Task.prototype.annotations.import.implementation = function (data) { + const result = importCollection(this, data); + return result; + }; + + Task.prototype.annotations.export.implementation = function () { + const result = exportCollection(this); + return result; + }; + + Task.prototype.annotations.exportDataset.implementation = async function ( + format: string, + saveImages: boolean, + useDefaultSettings: boolean, + targetStorage: Storage, + customName?: string, + ) { + const result = await exportDataset(this, format, saveImages, useDefaultSettings, targetStorage, customName); + return result; + }; + + Task.prototype.actions.undo.implementation = function (count) { + const result = undoActions(this, count); + return result; + }; + + Task.prototype.actions.redo.implementation = function (count) { + const result = redoActions(this, count); + return result; + }; + + Task.prototype.actions.freeze.implementation = function (frozen) { + const result = freezeHistory(this, frozen); + return result; + }; + + Task.prototype.actions.clear.implementation = function () { + const result = clearActions(this); + return result; + }; + + Task.prototype.actions.get.implementation = function () { + const result = getActions(this); + return result; + }; + + Task.prototype.logger.log.implementation = async function (logType, payload, wait) { + const result = await loggerStorage.log(logType, { ...payload, task_id: this.id }, wait); + return result; + }; + + Task.prototype.predictor.status.implementation = async function () { + if (!Number.isInteger(this.projectId)) { + throw new DataError('The task must belong to a project to use the feature'); + } + + const result = await serverProxy.predictor.status(this.projectId); + return { + message: result.message, + progress: result.progress, + projectScore: result.score, + timeRemaining: result.time_remaining, + mediaAmount: result.media_amount, + annotationAmount: result.annotation_amount, + }; + }; + + Task.prototype.predictor.predict.implementation = async function (frame) { + if (!Number.isInteger(frame) || frame < 0) { + throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); + } + + if (frame >= this.size) { + throw new ArgumentError(`The frame with number ${frame} is out of the task`); + } + + if (!Number.isInteger(this.projectId)) { + throw new DataError('The task must belong to a project to use the feature'); + } + + const result = await serverProxy.predictor.predict(this.id, frame); + return result; + }; + +})(); diff --git a/cvat-core/src/statistics.js b/cvat-core/src/statistics.js deleted file mode 100644 index 2ad6c56914e6..000000000000 --- a/cvat-core/src/statistics.js +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (C) 2019-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - /** - * Class representing collection statistics - * @memberof module:API.cvat.classes - * @hideconstructor - */ - class Statistics { - constructor(label, total) { - Object.defineProperties( - this, - Object.freeze({ - /** - * Statistics by labels with a structure: - * @example - * { - * label: { - * boxes: { - * tracks: 10, - * shapes: 11, - * }, - * polygons: { - * tracks: 13, - * shapes: 14, - * }, - * polylines: { - * tracks: 16, - * shapes: 17, - * }, - * points: { - * tracks: 19, - * shapes: 20, - * }, - * ellipse: { - * tracks: 13, - * shapes: 15, - * }, - * cuboids: { - * tracks: 21, - * shapes: 22, - * }, - * tags: 66, - * manually: 186, - * interpolated: 500, - * total: 608, - * } - * } - * @name label - * @type {Object} - * @memberof module:API.cvat.classes.Statistics - * @readonly - * @instance - */ - label: { - get: () => JSON.parse(JSON.stringify(label)), - }, - /** - * Total statistics (covers all labels) with a structure: - * @example - * { - * boxes: { - * tracks: 10, - * shapes: 11, - * }, - * polygons: { - * tracks: 13, - * shapes: 14, - * }, - * polylines: { - * tracks: 16, - * shapes: 17, - * }, - * points: { - * tracks: 19, - * shapes: 20, - * }, - * ellipse: { - * tracks: 13, - * shapes: 15, - * }, - * cuboids: { - * tracks: 21, - * shapes: 22, - * }, - * tags: 66, - * manually: 186, - * interpolated: 500, - * total: 608, - * } - * @name total - * @type {Object} - * @memberof module:API.cvat.classes.Statistics - * @readonly - * @instance - */ - total: { - get: () => JSON.parse(JSON.stringify(total)), - }, - }), - ); - } - } - - module.exports = Statistics; -})(); diff --git a/cvat-core/src/statistics.ts b/cvat-core/src/statistics.ts new file mode 100644 index 000000000000..d30baf83dc85 --- /dev/null +++ b/cvat-core/src/statistics.ts @@ -0,0 +1,44 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +interface ObjectStatistics { + track: number; + shape: number; +} + +interface StatisticsBody { + rectangle: ObjectStatistics; + polygon: ObjectStatistics; + polyline: ObjectStatistics; + points: ObjectStatistics; + ellipse: ObjectStatistics; + cuboid: ObjectStatistics; + skeleton: ObjectStatistics; + mask: { + shape: number; + }; + tag: number; + manually: number; + interpolated: number; + total: number; +} + +export default class Statistics { + private labelData: Record; + private totalData: StatisticsBody; + + constructor(label: Statistics['labelData'], total: Statistics['totalData']) { + this.labelData = label; + this.totalData = total; + } + + public get label(): Record { + return JSON.parse(JSON.stringify(this.labelData)); + } + + public get total(): StatisticsBody { + return JSON.parse(JSON.stringify(this.totalData)); + } +} diff --git a/cvat-core/src/storage.ts b/cvat-core/src/storage.ts new file mode 100644 index 000000000000..9c0e8d3284d7 --- /dev/null +++ b/cvat-core/src/storage.ts @@ -0,0 +1,66 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { StorageLocation } from './enums'; + +export interface StorageData { + location: StorageLocation; + cloudStorageId?: number; +} + +interface StorageJsonData { + location: StorageLocation; + cloud_storage_id?: number; +} + +/** + * Class representing a storage for import and export resources + * @memberof module:API.cvat.classes + * @hideconstructor + */ +export class Storage { + public location: StorageLocation; + public cloudStorageId: number; + + constructor(initialData: StorageData) { + const data: StorageData = { + location: initialData.location, + cloudStorageId: initialData?.cloudStorageId, + }; + + Object.defineProperties( + this, + Object.freeze({ + /** + * @name location + * @type {module:API.cvat.enums.StorageLocation} + * @memberof module:API.cvat.classes.Storage + * @instance + * @readonly + */ + location: { + get: () => data.location, + }, + /** + * @name cloudStorageId + * @type {number} + * @memberof module:API.cvat.classes.Storage + * @instance + * @readonly + */ + cloudStorageId: { + get: () => data.cloudStorageId, + }, + }), + ); + } + toJSON(): StorageJsonData { + return { + location: this.location, + ...(this.cloudStorageId ? { + cloud_storage_id: this.cloudStorageId, + } : {}), + }; + } +} diff --git a/cvat-core/src/user.js b/cvat-core/src/user.js deleted file mode 100644 index d83be1fe8003..000000000000 --- a/cvat-core/src/user.js +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (C) 2019-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -(() => { - /** - * Class representing a user - * @memberof module:API.cvat.classes - * @hideconstructor - */ - class User { - constructor(initialData) { - const data = { - id: null, - username: null, - email: null, - first_name: null, - last_name: null, - groups: null, - last_login: null, - date_joined: null, - is_staff: null, - is_superuser: null, - is_active: null, - email_verification_required: null, - }; - - for (const property in data) { - if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { - data[property] = initialData[property]; - } - } - - Object.defineProperties( - this, - Object.freeze({ - id: { - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => data.id, - }, - username: { - /** - * @name username - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => data.username, - }, - email: { - /** - * @name email - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => data.email, - }, - firstName: { - /** - * @name firstName - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => data.first_name, - }, - lastName: { - /** - * @name lastName - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => data.last_name, - }, - groups: { - /** - * @name groups - * @type {string[]} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => JSON.parse(JSON.stringify(data.groups)), - }, - lastLogin: { - /** - * @name lastLogin - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => data.last_login, - }, - dateJoined: { - /** - * @name dateJoined - * @type {string} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => data.date_joined, - }, - isStaff: { - /** - * @name isStaff - * @type {boolean} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => data.is_staff, - }, - isSuperuser: { - /** - * @name isSuperuser - * @type {boolean} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => data.is_superuser, - }, - isActive: { - /** - * @name isActive - * @type {boolean} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => data.is_active, - }, - isVerified: { - /** - * @name isVerified - * @type {boolean} - * @memberof module:API.cvat.classes.User - * @readonly - * @instance - */ - get: () => !data.email_verification_required, - }, - }), - ); - } - - serialize() { - return { - id: this.id, - username: this.username, - email: this.email, - first_name: this.firstName, - last_name: this.lastName, - groups: this.groups, - last_login: this.lastLogin, - date_joined: this.dateJoined, - is_staff: this.isStaff, - is_superuser: this.isSuperuser, - is_active: this.isActive, - email_verification_required: this.isVerified, - }; - } - } - - module.exports = User; -})(); diff --git a/cvat-core/src/user.ts b/cvat-core/src/user.ts new file mode 100644 index 000000000000..540f72a72085 --- /dev/null +++ b/cvat-core/src/user.ts @@ -0,0 +1,200 @@ +// Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +interface RawUserData { + id: number; + username: string; + email: string; + first_name: string; + last_name: string; + groups: string[]; + last_login: string; + date_joined: string; + is_staff: boolean; + is_superuser: boolean; + is_active: boolean; + email_verification_required: boolean; +} + +export default class User { + public readonly id: number; + public readonly username: string; + public readonly email: string; + public readonly firstName: string; + public readonly lastName: string; + public readonly groups: string[]; + public readonly lastLogin: string; + public readonly dateJoined: string; + public readonly isStaff: boolean; + public readonly isSuperuser: boolean; + public readonly isActive: boolean; + public readonly isVerified: boolean; + + constructor(initialData: RawUserData) { + const data = { + id: null, + username: null, + email: null, + first_name: null, + last_name: null, + groups: null, + last_login: null, + date_joined: null, + is_staff: null, + is_superuser: null, + is_active: null, + email_verification_required: null, + }; + + for (const property in data) { + if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { + data[property] = initialData[property]; + } + } + + Object.defineProperties( + this, + Object.freeze({ + id: { + /** + * @name id + * @type {number} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => data.id, + }, + username: { + /** + * @name username + * @type {string} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => data.username, + }, + email: { + /** + * @name email + * @type {string} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => data.email, + }, + firstName: { + /** + * @name firstName + * @type {string} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => data.first_name, + }, + lastName: { + /** + * @name lastName + * @type {string} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => data.last_name, + }, + groups: { + /** + * @name groups + * @type {string[]} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => JSON.parse(JSON.stringify(data.groups)), + }, + lastLogin: { + /** + * @name lastLogin + * @type {string} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => data.last_login, + }, + dateJoined: { + /** + * @name dateJoined + * @type {string} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => data.date_joined, + }, + isStaff: { + /** + * @name isStaff + * @type {boolean} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => data.is_staff, + }, + isSuperuser: { + /** + * @name isSuperuser + * @type {boolean} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => data.is_superuser, + }, + isActive: { + /** + * @name isActive + * @type {boolean} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => data.is_active, + }, + isVerified: { + /** + * @name isVerified + * @type {boolean} + * @memberof module:API.cvat.classes.User + * @readonly + * @instance + */ + get: () => !data.email_verification_required, + }, + }), + ); + } + + serialize(): RawUserData { + return { + id: this.id, + username: this.username, + email: this.email, + first_name: this.firstName, + last_name: this.lastName, + groups: this.groups, + last_login: this.lastLogin, + date_joined: this.dateJoined, + is_staff: this.isStaff, + is_superuser: this.isSuperuser, + is_active: this.isActive, + email_verification_required: this.isVerified, + }; + } +} diff --git a/cvat-core/src/webhook.ts b/cvat-core/src/webhook.ts new file mode 100644 index 000000000000..09424cfff009 --- /dev/null +++ b/cvat-core/src/webhook.ts @@ -0,0 +1,351 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import PluginRegistry from './plugins'; +import User from './user'; +import serverProxy from './server-proxy'; +import { WebhookSourceType, WebhookContentType } from './enums'; +import { isEnum } from './common'; + +interface RawWebhookData { + id?: number; + type: WebhookSourceType; + target_url: string; + organization_id?: number; + project_id?: number; + events: string[]; + content_type: WebhookContentType; + secret?: string; + enable_ssl: boolean; + description?: string; + is_active?: boolean; + owner?: any; + created_date?: string; + updated_date?: string; + last_delivery_date?: string; + last_status?: number; +} + +export default class Webhook { + public readonly id: number; + public readonly type: WebhookSourceType; + public readonly organizationID: number | null; + public readonly projectID: number | null; + public readonly owner: User; + public readonly lastStatus: number; + public readonly lastDeliveryDate?: string; + public readonly createdDate: string; + public readonly updatedDate: string; + + public targetURL: string; + public events: string[]; + public contentType: RawWebhookData['content_type']; + public description?: string; + public secret?: string; + public isActive?: boolean; + public enableSSL: boolean; + + static async availableEvents(type: WebhookSourceType): Promise { + return serverProxy.webhooks.events(type); + } + + constructor(initialData: RawWebhookData) { + const data: RawWebhookData = { + id: undefined, + target_url: '', + type: WebhookSourceType.ORGANIZATION, + events: [], + content_type: WebhookContentType.JSON, + organization_id: null, + project_id: null, + description: undefined, + secret: '', + is_active: undefined, + enable_ssl: undefined, + owner: undefined, + created_date: undefined, + updated_date: undefined, + last_delivery_date: undefined, + last_status: 0, + }; + + for (const property in data) { + if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { + data[property] = initialData[property]; + } + } + + if (data.owner) { + data.owner = new User(data.owner); + } + + Object.defineProperties( + this, + Object.freeze({ + id: { + get: () => data.id, + }, + type: { + get: () => data.type, + }, + targetURL: { + get: () => data.target_url, + set: (value: string) => { + if (typeof value !== 'string') { + throw ArgumentError( + `targetURL property must be a string, tried to set ${typeof value}`, + ); + } + data.target_url = value; + }, + }, + events: { + get: () => data.events, + set: (events: string[]) => { + if (!Array.isArray(events)) { + throw ArgumentError( + `Events must be an array, tried to set ${typeof events}`, + ); + } + events.forEach((event: string) => { + if (typeof event !== 'string') { + throw ArgumentError( + `Event must be a string, tried to set ${typeof event}`, + ); + } + }); + data.events = [...events]; + }, + }, + contentType: { + get: () => data.content_type, + set: (value: WebhookContentType) => { + if (!isEnum.call(WebhookContentType, value)) { + throw new ArgumentError( + `Webhook contentType must be member of WebhookContentType, + got wrong value ${typeof value}`, + ); + } + data.content_type = value; + }, + }, + organizationID: { + get: () => data.organization_id, + }, + projectID: { + get: () => data.project_id, + }, + description: { + get: () => data.description, + set: (value: string) => { + if (typeof value !== 'string') { + throw ArgumentError( + `Description property must be a string, tried to set ${typeof value}`, + ); + } + data.description = value; + }, + }, + secret: { + get: () => data.secret, + set: (value: string) => { + if (typeof value !== 'string') { + throw ArgumentError( + `Secret property must be a string, tried to set ${typeof value}`, + ); + } + data.secret = value; + }, + }, + isActive: { + get: () => data.is_active, + set: (value: boolean) => { + if (typeof value !== 'boolean') { + throw ArgumentError( + `isActive property must be a boolean, tried to set ${typeof value}`, + ); + } + data.is_active = value; + }, + }, + enableSSL: { + get: () => data.enable_ssl, + set: (value: boolean) => { + if (typeof value !== 'boolean') { + throw ArgumentError( + `enableSSL property must be a boolean, tried to set ${typeof value}`, + ); + } + data.enable_ssl = value; + }, + }, + owner: { + get: () => data.owner, + }, + createdDate: { + get: () => data.created_date, + }, + updatedDate: { + get: () => data.updated_date, + }, + lastDeliveryDate: { + get: () => data.last_delivery_date, + }, + lastStatus: { + get: () => data.last_status, + }, + }), + ); + } + + public toJSON(): RawWebhookData { + const result: RawWebhookData = { + target_url: this.targetURL, + events: [...this.events], + content_type: this.contentType, + enable_ssl: this.enableSSL, + type: this.type || WebhookSourceType.ORGANIZATION, + }; + + if (Number.isInteger(this.id)) { + result.id = this.id; + } + + if (Number.isInteger(this.organizationID)) { + result.organization_id = this.organizationID; + } + + if (Number.isInteger(this.projectID)) { + result.project_id = this.projectID; + } + + if (this.description) { + result.description = this.description; + } + + if (this.secret) { + result.secret = this.secret; + } + + if (typeof this.isActive === 'boolean') { + result.is_active = this.isActive; + } + + return result; + } + + public async save(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Webhook.prototype.save); + return result; + } + + public async delete(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Webhook.prototype.delete); + return result; + } + + public async ping(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, Webhook.prototype.ping); + return result; + } +} + +interface RawWebhookDeliveryData { + id?: number; + event?: string; + webhook_id?: number; + status_code?: string; + redelivery?: boolean; + created_date?: string; + updated_date?: string; +} + +export class WebhookDelivery { + public readonly id?: number; + public readonly event: string; + public readonly webhookId: number; + public readonly statusCode: string; + public readonly createdDate?: string; + public readonly updatedDate?: string; + + constructor(initialData: RawWebhookDeliveryData) { + const data: RawWebhookDeliveryData = { + id: undefined, + event: '', + webhook_id: undefined, + status_code: undefined, + created_date: undefined, + updated_date: undefined, + }; + + for (const property in data) { + if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { + data[property] = initialData[property]; + } + } + + Object.defineProperties( + this, + Object.freeze({ + id: { + get: () => data.id, + }, + event: { + get: () => data.event, + }, + webhookId: { + get: () => data.webhook_id, + }, + statusCode: { + get: () => data.status_code, + }, + createdDate: { + get: () => data.created_date, + }, + updatedDate: { + get: () => data.updated_date, + }, + }), + ); + } +} + +Object.defineProperties(Webhook.prototype.save, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + if (Number.isInteger(this.id)) { + const result = await serverProxy.webhooks.update(this.id, this.toJSON()); + return new Webhook(result); + } + + const result = await serverProxy.webhooks.create(this.toJSON()); + return new Webhook(result); + }, + }, +}); + +Object.defineProperties(Webhook.prototype.delete, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + if (Number.isInteger(this.id)) { + await serverProxy.webhooks.delete(this.id); + } + }, + }, +}); + +Object.defineProperties(Webhook.prototype.ping, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + const result = await serverProxy.webhooks.ping(this.id); + return new WebhookDelivery(result); + }, + }, +}); diff --git a/cvat-core/tests/api/annotations.js b/cvat-core/tests/api/annotations.js index 1fd5888e86bd..3fd6727b78a9 100644 --- a/cvat-core/tests/api/annotations.js +++ b/cvat-core/tests/api/annotations.js @@ -1,16 +1,20 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corp +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT // Setup mock for a server jest.mock('../../src/server-proxy', () => { - const mock = require('../mocks/server-proxy.mock'); - return mock; + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; }); // Initialize api -window.cvat = require('../../src/api'); -const serverProxy = require('../../src/server-proxy'); +window.cvat = require('../../src/api').default; +const serverProxy = require('../../src/server-proxy').default; // Test cases describe('Feature: get annotations', () => { @@ -64,6 +68,28 @@ describe('Feature: get annotations', () => { expect(annotations).toHaveLength(1); expect(annotations[0].shapeType).toBe('ellipse'); }); + + test('get skeletons with a filter', async () => { + const job = (await window.cvat.jobs.get({ jobID: 40 }))[0]; + const annotations = await job.annotations.get(0, false, JSON.parse('[{"and":[{"==":[{"var":"shape"},"skeleton"]}]}]')); + expect(Array.isArray(annotations)).toBeTruthy(); + expect(annotations).toHaveLength(2); + for (const object of annotations) { + expect(object.shapeType).toBe('skeleton'); + expect(object.elements).toBeInstanceOf(Array); + const label = object.label; + let points = []; + object.elements.forEach((element, idx) => { + expect(element).toBeInstanceOf(cvat.classes.ObjectState); + expect(element.label.id).toBe(label.structure.sublabels[idx].id); + expect(element.shapeType).toBe('points'); + points = [...points, ...element.points]; + }); + expect(points).toEqual(object.points); + } + + expect(annotations[0].shapeType).toBe('skeleton'); + }) }); describe('Feature: get interpolated annotations', () => { @@ -119,7 +145,7 @@ describe('Feature: put annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 1, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], @@ -141,7 +167,7 @@ describe('Feature: put annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 5, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + shapeType: window.cvat.enums.ShapeType.RECTANGLE, points: [0, 0, 100, 100], occluded: false, label: job.labels[0], @@ -163,7 +189,7 @@ describe('Feature: put annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 5, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.ELLIPSE, + shapeType: window.cvat.enums.ShapeType.ELLIPSE, points: [500, 500, 800, 100], occluded: true, label: job.labels[0], @@ -185,7 +211,7 @@ describe('Feature: put annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 1, objectType: window.cvat.enums.ObjectType.TRACK, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], @@ -207,7 +233,7 @@ describe('Feature: put annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 5, objectType: window.cvat.enums.ObjectType.TRACK, - shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + shapeType: window.cvat.enums.ShapeType.RECTANGLE, points: [0, 0, 100, 100], occluded: false, label: job.labels[0], @@ -224,16 +250,14 @@ describe('Feature: put annotations', () => { test('put object without objectType to a task', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; await task.annotations.clear(true); - const state = new window.cvat.classes.ObjectState({ + expect(() => new window.cvat.classes.ObjectState({ frame: 1, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], zOrder: 0, - }); - - expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError); + })).toThrow(window.cvat.exceptions.ArgumentError); }); test('put shape with bad attributes to a task', async () => { @@ -242,7 +266,7 @@ describe('Feature: put annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 1, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], attributes: { 'bad key': 55 }, occluded: true, @@ -259,7 +283,7 @@ describe('Feature: put annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 1, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], attributes: { 'bad key': 55 }, occluded: true, @@ -272,7 +296,7 @@ describe('Feature: put annotations', () => { const state1 = new window.cvat.classes.ObjectState({ frame: 1, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], attributes: { 'bad key': 55 }, occluded: true, @@ -289,20 +313,19 @@ describe('Feature: put annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 1, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, occluded: true, - points: [], label: task.labels[0], zOrder: 0, }); - await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.DataError); + expect(() => state.points = ['150,50 250,30']).toThrow(window.cvat.exceptions.ArgumentError); delete state.points; - await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.DataError); + expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.DataError); - state.points = ['150,50 250,30']; - expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError); + state.points = []; + expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.DataError); }); test('put shape without type to a task', async () => { @@ -323,38 +346,97 @@ describe('Feature: put annotations', () => { test('put shape without label and with bad label to a task', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; await task.annotations.clear(true); - const state = new window.cvat.classes.ObjectState({ + const state = { frame: 1, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, zOrder: 0, - }); - - await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError); - - state.label = 'bad label'; - await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError); + }; - state.label = {}; - await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError); + expect(() => new window.cvat.classes.ObjectState(state)) + .toThrow(window.cvat.exceptions.ArgumentError); + expect(() => new window.cvat.classes.ObjectState({ ...state, label: 'bad label' })) + .toThrow(window.cvat.exceptions.ArgumentError); + expect(() => new window.cvat.classes.ObjectState({ ...state, label: {} })) + .toThrow(window.cvat.exceptions.ArgumentError); }); test('put shape with bad frame to a task', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; await task.annotations.clear(true); - const state = new window.cvat.classes.ObjectState({ + expect(() => new window.cvat.classes.ObjectState({ frame: '5', objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], zOrder: 0, + })).toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('put a skeleton shape to a job', async() => { + const job = (await window.cvat.jobs.get({ jobID: 40 }))[0]; + const label = job.labels[0]; + await job.annotations.clear(true); + await job.annotations.clear(); + const skeleton = new window.cvat.classes.ObjectState({ + frame: 0, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ShapeType.SKELETON, + points: [], + label, + elements: label.structure.sublabels.map((sublabel, idx) => ({ + frame: 0, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ShapeType.POINTS, + points: [idx * 10, idx * 10], + label: sublabel, + })) }); - expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError); + await job.annotations.put([skeleton]); + const annotations = await job.annotations.get(0); + expect(annotations.length).toBe(1); + expect(annotations[0].objectType).toBe(window.cvat.enums.ObjectType.SHAPE); + expect(annotations[0].shapeType).toBe(window.cvat.enums.ShapeType.SKELETON); + for (const element of annotations[0].elements) { + expect(element.objectType).toBe(window.cvat.enums.ObjectType.SHAPE); + expect(element.shapeType).toBe(window.cvat.enums.ShapeType.POINTS); + } + }); + + test('put a skeleton track to a task', async() => { + const task = (await window.cvat.tasks.get({ id: 40 }))[0]; + const label = task.labels[0]; + await task.annotations.clear(true); + await task.annotations.clear(); + const skeleton = new window.cvat.classes.ObjectState({ + frame: 0, + objectType: window.cvat.enums.ObjectType.TRACK, + shapeType: window.cvat.enums.ShapeType.SKELETON, + points: [], + label, + elements: label.structure.sublabels.map((sublabel, idx) => ({ + frame: 0, + objectType: window.cvat.enums.ObjectType.TRACK, + shapeType: window.cvat.enums.ShapeType.POINTS, + points: [idx * 10, idx * 10], + label: sublabel, + })) + }); + + await task.annotations.put([skeleton]); + const annotations = await task.annotations.get(2); + expect(annotations.length).toBe(1); + expect(annotations[0].objectType).toBe(window.cvat.enums.ObjectType.TRACK); + expect(annotations[0].shapeType).toBe(window.cvat.enums.ShapeType.SKELETON); + for (const element of annotations[0].elements) { + expect(element.objectType).toBe(window.cvat.enums.ObjectType.TRACK); + expect(element.shapeType).toBe(window.cvat.enums.ShapeType.POINTS); + } }); }); @@ -389,7 +471,7 @@ describe('Feature: save annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 0, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], @@ -411,7 +493,7 @@ describe('Feature: save annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 0, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], @@ -457,7 +539,7 @@ describe('Feature: save annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 0, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, label: job.labels[0], @@ -574,7 +656,7 @@ describe('Feature: merge annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 0, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], @@ -602,7 +684,7 @@ describe('Feature: merge annotations', () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const annotations0 = await task.annotations.get(0); const annotations1 = (await task.annotations.get(1)).filter( - (state) => state.shapeType === window.cvat.enums.ObjectShape.POLYGON, + (state) => state.shapeType === window.cvat.enums.ShapeType.POLYGON, ); const states = [annotations0[0], annotations1[0]]; @@ -619,6 +701,7 @@ describe('Feature: merge annotations', () => { name: 'new_label', attributes: [], }); + await states[1].save(); expect(task.annotations.merge(states)).rejects.toThrow(window.cvat.exceptions.ArgumentError); }); @@ -691,7 +774,7 @@ describe('Feature: group annotations', () => { const state = new window.cvat.classes.ObjectState({ frame: 0, objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.POLYGON, + shapeType: window.cvat.enums.ShapeType.POLYGON, points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], @@ -776,6 +859,21 @@ describe('Feature: get statistics', () => { expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics); expect(statistics.total.total).toBe(1012); }); + + test('get statistics from a job with skeletons', async () => { + const job = (await window.cvat.jobs.get({ jobID: 40 }))[0]; + await job.annotations.clear(true); + const statistics = await job.annotations.statistics(); + expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics); + expect(statistics.total.total).toBe(30); + const labelName = job.labels[0].name; + expect(statistics.label[labelName].skeleton.shape).toBe(1); + expect(statistics.label[labelName].skeleton.track).toBe(1); + expect(statistics.label[labelName].manually).toBe(2); + expect(statistics.label[labelName].interpolated).toBe(3); + expect(statistics.label[labelName].total).toBe(5); + + }); }); describe('Feature: select object', () => { @@ -783,18 +881,18 @@ describe('Feature: select object', () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const annotations = await task.annotations.get(0); let result = await task.annotations.select(annotations, 1430, 765); - expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE); + expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.RECTANGLE); result = await task.annotations.select(annotations, 1415, 765); - expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON); + expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.POLYGON); expect(result.state.points).toHaveLength(10); result = await task.annotations.select(annotations, 1083, 543); - expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POINTS); + expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.POINTS); expect(result.state.points).toHaveLength(16); result = await task.annotations.select(annotations, 613, 811); - expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON); + expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.POLYGON); expect(result.state.points).toHaveLength(94); result = await task.annotations.select(annotations, 600, 900); - expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.CUBOID); + expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.CUBOID); expect(result.state.points).toHaveLength(16); }); @@ -802,16 +900,16 @@ describe('Feature: select object', () => { const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; const annotations = await job.annotations.get(0); let result = await job.annotations.select(annotations, 490, 540); - expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE); + expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.RECTANGLE); result = await job.annotations.select(annotations, 430, 260); - expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYLINE); + expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.POLYLINE); result = await job.annotations.select(annotations, 1473, 250); - expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE); + expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.RECTANGLE); result = await job.annotations.select(annotations, 1490, 237); - expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON); + expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.POLYGON); expect(result.state.points).toHaveLength(94); result = await job.annotations.select(annotations, 600, 900); - expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.CUBOID); + expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.CUBOID); expect(result.state.points).toHaveLength(16); }); diff --git a/cvat-core/tests/api/cloud-storages.js b/cvat-core/tests/api/cloud-storages.js index 66ccf11abdcb..a671b0a2072e 100644 --- a/cvat-core/tests/api/cloud-storages.js +++ b/cvat-core/tests/api/cloud-storages.js @@ -1,17 +1,20 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT // Setup mock for a server jest.mock('../../src/server-proxy', () => { - const mock = require('../mocks/server-proxy.mock'); - return mock; + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; -const { CloudStorage } = require('../../src/cloud-storage'); +const CloudStorage= require('../../src/cloud-storage').default; const { cloudStoragesDummyData } = require('../mocks/dummy-data.mock'); describe('Feature: get cloud storages', () => { diff --git a/cvat-core/tests/api/frames.js b/cvat-core/tests/api/frames.js index 2d43e7cd50df..8f9299ab2895 100644 --- a/cvat-core/tests/api/frames.js +++ b/cvat-core/tests/api/frames.js @@ -1,15 +1,18 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT // Setup mock for a server jest.mock('../../src/server-proxy', () => { - const mock = require('../mocks/server-proxy.mock'); - return mock; + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const { FrameData } = require('../../src/frames'); @@ -43,6 +46,48 @@ describe('Feature: get frame meta', () => { }); }); +describe('Feature: delete/restore frame', () => { + test('delete frame from job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + await job.annotations.clear(true); + let frame = await job.frames.get(0); + expect(frame.deleted).toBe(false); + await job.frames.delete(0); + frame = await job.frames.get(0); + expect(frame.deleted).toBe(true); + }); + + test('restore frame from job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + await job.annotations.clear(true); + let frame = await job.frames.get(8); + expect(frame.deleted).toBe(true); + await job.frames.restore(8); + frame = await job.frames.get(8); + expect(frame.deleted).toBe(false); + }); + + test('delete frame from task', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + await task.annotations.clear(true); + let frame = await task.frames.get(1); + expect(frame.deleted).toBe(false); + await task.frames.delete(1); + frame = await task.frames.get(1); + expect(frame.deleted).toBe(true); + }); + + test('restore frame from task', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + await task.annotations.clear(true); + let frame = await task.frames.get(7); + expect(frame.deleted).toBe(true); + await task.frames.restore(7); + frame = await task.frames.get(7); + expect(frame.deleted).toBe(false); + }); +}); + describe('Feature: get frame data', () => { test('get frame data for a task', async () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; diff --git a/cvat-core/tests/api/jobs.js b/cvat-core/tests/api/jobs.js index 41d473e050f4..7d457f172828 100644 --- a/cvat-core/tests/api/jobs.js +++ b/cvat-core/tests/api/jobs.js @@ -1,15 +1,18 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT // Setup mock for a server jest.mock('../../src/server-proxy', () => { - const mock = require('../mocks/server-proxy.mock'); - return mock; + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const { Job } = require('../../src/session'); diff --git a/cvat-core/tests/api/object-state.js b/cvat-core/tests/api/object-state.js index ef6e1f5f2f4b..65ce7b1ad906 100644 --- a/cvat-core/tests/api/object-state.js +++ b/cvat-core/tests/api/object-state.js @@ -1,21 +1,25 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT // Setup mock for a server jest.mock('../../src/server-proxy', () => { - const mock = require('../mocks/server-proxy.mock'); - return mock; + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; describe('Feature: set attributes for an object state', () => { test('set a valid value', () => { const state = new window.cvat.classes.ObjectState({ + label: new window.cvat.classes.Label({ name: 'test label', id: 1, color: '#000000', attributes: [] }), objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + shapeType: window.cvat.enums.ShapeType.RECTANGLE, frame: 5, }); @@ -30,8 +34,9 @@ describe('Feature: set attributes for an object state', () => { test('trying to set a bad value', () => { const state = new window.cvat.classes.ObjectState({ + label: new window.cvat.classes.Label({ name: 'test label', id: 1, color: '#000000', attributes: [] }), objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + shapeType: window.cvat.enums.ShapeType.RECTANGLE, frame: 5, }); @@ -55,8 +60,9 @@ describe('Feature: set attributes for an object state', () => { describe('Feature: set points for an object state', () => { test('set a valid value', () => { const state = new window.cvat.classes.ObjectState({ + label: new window.cvat.classes.Label({ name: 'test label', id: 1, color: '#000000', attributes: [] }), objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + shapeType: window.cvat.enums.ShapeType.RECTANGLE, frame: 5, }); @@ -67,8 +73,9 @@ describe('Feature: set points for an object state', () => { test('trying to set a bad value', () => { const state = new window.cvat.classes.ObjectState({ + label: new window.cvat.classes.Label({ name: 'test label', id: 1, color: '#000000', attributes: [] }), objectType: window.cvat.enums.ObjectType.SHAPE, - shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + shapeType: window.cvat.enums.ShapeType.RECTANGLE, frame: 5, }); @@ -100,7 +107,7 @@ describe('Feature: save object from its state', () => { const annotations = await task.annotations.get(0); let state = annotations[0]; expect(state.objectType).toBe(window.cvat.enums.ObjectType.SHAPE); - expect(state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE); + expect(state.shapeType).toBe(window.cvat.enums.ShapeType.RECTANGLE); state.points = [0, 0, 100, 100]; state.occluded = true; [, state.label] = task.labels; @@ -118,7 +125,7 @@ describe('Feature: save object from its state', () => { const annotations = await task.annotations.get(10); let state = annotations[1]; expect(state.objectType).toBe(window.cvat.enums.ObjectType.TRACK); - expect(state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE); + expect(state.shapeType).toBe(window.cvat.enums.ShapeType.RECTANGLE); state.occluded = true; state.lock = true; @@ -163,12 +170,9 @@ describe('Feature: save object from its state', () => { state.occluded = 'false'; await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError); - const oldPoints = state.points; state.occluded = false; - state.points = ['100', '50', '100', {}]; - await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError); + expect(() => state.points = ['100', '50', '100', {}]).toThrow(window.cvat.exceptions.ArgumentError); - state.points = oldPoints; state.lock = 'true'; await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError); @@ -190,12 +194,9 @@ describe('Feature: save object from its state', () => { state.occluded = 'false'; await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError); - const oldPoints = state.points; state.occluded = false; - state.points = ['100', '50', '100', {}]; - await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError); + expect(() => state.points = ['100', '50', '100', {}]).toThrow(window.cvat.exceptions.ArgumentError); - state.points = oldPoints; state.lock = 'true'; await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError); @@ -284,3 +285,40 @@ describe('Feature: delete object', () => { expect(annotationsAfter).toHaveLength(length - 1); }); }); + +describe('Feature: skeletons', () => { + test('lock, hide, occluded, outside for skeletons', async () => { + const job = (await window.cvat.jobs.get({ jobID: 40 }))[0]; + let [skeleton] = await job.annotations.get(0, false, JSON.parse('[{"and":[{"==":[{"var":"shape"},"skeleton"]}]}]')); + expect(skeleton.shapeType).toBe('skeleton'); + skeleton.lock = true; + skeleton.outside = true; + skeleton.occluded = true; + skeleton.hidden = true; + skeleton = await skeleton.save(); + expect(skeleton.lock).toBe(true); + expect(skeleton.outside).toBe(true); + expect(skeleton.occluded).toBe(true); + expect(skeleton.hidden).toBe(true); + expect(skeleton.elements).toBeInstanceOf(Array); + expect(skeleton.elements.length).toBe(skeleton.label.structure.sublabels.length); + for (const element of skeleton.elements) { + expect(element.lock).toBe(true); + expect(element.outside).toBe(true); + expect(element.occluded).toBe(true); + expect(element.hidden).toBe(true); + } + + skeleton.elements[0].lock = false; + skeleton.elements[0].outside = false; + skeleton.elements[0].occluded = false; + skeleton.elements[0].hidden = false; + skeleton.elements[0].save(); + + [skeleton] = await job.annotations.get(0, false, JSON.parse('[{"and":[{"==":[{"var":"shape"},"skeleton"]}]}]')); + expect(skeleton.lock).toBe(false); + expect(skeleton.outside).toBe(false); + expect(skeleton.occluded).toBe(false); + expect(skeleton.hidden).toBe(false); + }); +}); \ No newline at end of file diff --git a/cvat-core/tests/api/plugins.js b/cvat-core/tests/api/plugins.js index 20b4023d4529..8b791a894558 100644 --- a/cvat-core/tests/api/plugins.js +++ b/cvat-core/tests/api/plugins.js @@ -1,15 +1,18 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT // Setup mock for a server jest.mock('../../src/server-proxy', () => { - const mock = require('../mocks/server-proxy.mock'); - return mock; + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; describe('Feature: dummy feature', () => { test('dummy test', async () => { diff --git a/cvat-core/tests/api/projects.js b/cvat-core/tests/api/projects.js index ea278c241467..082cd0342b0f 100644 --- a/cvat-core/tests/api/projects.js +++ b/cvat-core/tests/api/projects.js @@ -1,17 +1,20 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT // Setup mock for a server jest.mock('../../src/server-proxy', () => { - const mock = require('../mocks/server-proxy.mock'); - return mock; + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; -const { Project } = require('../../src/project'); +const Project = require('../../src/project').default; describe('Feature: get projects', () => { test('get all projects', async () => { diff --git a/cvat-core/tests/api/server.js b/cvat-core/tests/api/server.js index 1fa607b07782..138388a8a48d 100644 --- a/cvat-core/tests/api/server.js +++ b/cvat-core/tests/api/server.js @@ -1,15 +1,18 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT // Setup mock for a server jest.mock('../../src/server-proxy', () => { - const mock = require('../mocks/server-proxy.mock'); - return mock; + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const { AnnotationFormats, Loader, Dumper } = require('../../src/annotation-formats'); // Test cases @@ -23,51 +26,51 @@ describe('Feature: get info about cvat', () => { }); }); -describe('Feature: get share storage info', () => { - test('get files in a root of a share storage', async () => { - const result = await window.cvat.server.share(); - expect(Array.isArray(result)).toBeTruthy(); - expect(result).toHaveLength(5); - }); +// describe('Feature: get share storage info', () => { +// test('get files in a root of a share storage', async () => { +// const result = await window.cvat.server.share(); +// expect(Array.isArray(result)).toBeTruthy(); +// expect(result).toHaveLength(5); +// }); - test('get files in a some dir of a share storage', async () => { - const result = await window.cvat.server.share('images'); - expect(Array.isArray(result)).toBeTruthy(); - expect(result).toHaveLength(8); - }); +// test('get files in a some dir of a share storage', async () => { +// const result = await window.cvat.server.share('images'); +// expect(Array.isArray(result)).toBeTruthy(); +// expect(result).toHaveLength(8); +// }); - test('get files in a some unknown dir of a share storage', async () => { - expect(window.cvat.server.share('Unknown Directory')).rejects.toThrow(window.cvat.exceptions.ServerError); - }); -}); +// test('get files in a some unknown dir of a share storage', async () => { +// expect(window.cvat.server.share('Unknown Directory')).rejects.toThrow(window.cvat.exceptions.ServerError); +// }); +// }); -describe('Feature: get annotation formats', () => { - test('get annotation formats from a server', async () => { - const result = await window.cvat.server.formats(); - expect(result).toBeInstanceOf(AnnotationFormats); - }); -}); +// describe('Feature: get annotation formats', () => { +// test('get annotation formats from a server', async () => { +// const result = await window.cvat.server.formats(); +// expect(result).toBeInstanceOf(AnnotationFormats); +// }); +// }); -describe('Feature: get annotation loaders', () => { - test('get annotation formats from a server', async () => { - const result = await window.cvat.server.formats(); - expect(result).toBeInstanceOf(AnnotationFormats); - const { loaders } = result; - expect(Array.isArray(loaders)).toBeTruthy(); - for (const loader of loaders) { - expect(loader).toBeInstanceOf(Loader); - } - }); -}); +// describe('Feature: get annotation loaders', () => { +// test('get annotation formats from a server', async () => { +// const result = await window.cvat.server.formats(); +// expect(result).toBeInstanceOf(AnnotationFormats); +// const { loaders } = result; +// expect(Array.isArray(loaders)).toBeTruthy(); +// for (const loader of loaders) { +// expect(loader).toBeInstanceOf(Loader); +// } +// }); +// }); -describe('Feature: get annotation dumpers', () => { - test('get annotation formats from a server', async () => { - const result = await window.cvat.server.formats(); - expect(result).toBeInstanceOf(AnnotationFormats); - const { dumpers } = result; - expect(Array.isArray(dumpers)).toBeTruthy(); - for (const dumper of dumpers) { - expect(dumper).toBeInstanceOf(Dumper); - } - }); -}); +// describe('Feature: get annotation dumpers', () => { +// test('get annotation formats from a server', async () => { +// const result = await window.cvat.server.formats(); +// expect(result).toBeInstanceOf(AnnotationFormats); +// const { dumpers } = result; +// expect(Array.isArray(dumpers)).toBeTruthy(); +// for (const dumper of dumpers) { +// expect(dumper).toBeInstanceOf(Dumper); +// } +// }); +// }); diff --git a/cvat-core/tests/api/tasks.js b/cvat-core/tests/api/tasks.js index ea1299dda4c4..96ccd95eff48 100644 --- a/cvat-core/tests/api/tasks.js +++ b/cvat-core/tests/api/tasks.js @@ -1,15 +1,18 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT // Setup mock for a server jest.mock('../../src/server-proxy', () => { - const mock = require('../mocks/server-proxy.mock'); - return mock; + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; const { Task } = require('../../src/session'); @@ -18,7 +21,7 @@ describe('Feature: get a list of tasks', () => { test('get all tasks', async () => { const result = await window.cvat.tasks.get(); expect(Array.isArray(result)).toBeTruthy(); - expect(result).toHaveLength(6); + expect(result).toHaveLength(7); for (const el of result) { expect(el).toBeInstanceOf(Task); } @@ -34,6 +37,33 @@ describe('Feature: get a list of tasks', () => { expect(result[0].id).toBe(3); }); + test('get a task with skeletons by an id', async () => { + const result = await window.cvat.tasks.get({ + id: 40, + }); + + expect(Array.isArray(result)).toBeTruthy(); + expect(result).toHaveLength(1); + expect(result[0]).toBeInstanceOf(Task); + expect(result[0].id).toBe(40); + expect(result[0].labels).toBeInstanceOf(Array); + + for (const label of result[0].labels) { + expect(label).toBeInstanceOf(window.cvat.classes.Label); + if (label.type === 'skeleton') { + expect(label.hasParent).toBe(false); + expect(label.structure.sublabels).toBeInstanceOf(Array); + expect(typeof label.structure.svg).toBe('string'); + expect(label.structure.svg.length).not.toBe(0); + + for (const sublabel of label.structure.sublabels) { + expect(sublabel).toBeInstanceOf(window.cvat.classes.Label); + expect(sublabel.hasParent).toBe(true); + } + } + } + }); + test('get a task by an unknown id', async () => { const result = await window.cvat.tasks.get({ id: 50, @@ -154,12 +184,61 @@ describe('Feature: save a task', () => { project_id: 2, bug_tracker: 'bug tracker value', image_quality: 50, - z_order: true, }); const result = await task.save(); expect(result.projectId).toBe(2); }); + + test('create a new task with skeletons', async () => { + const svgSpec = ` + + + + + + + + + + + `; + + const task = new window.cvat.classes.Task({ + name: 'task with skeletons', + labels: [{ + name: 'star skeleton', + type: 'skeleton', + attributes: [], + svg: svgSpec, + sublabels: [{ + name: '1', + type: 'points', + attributes: [] + }, { + name: '2', + type: 'points', + attributes: [] + }, { + name: '3', + type: 'points', + attributes: [] + }, { + name: '4', + type: 'points', + attributes: [] + }, { + name: '5', + type: 'points', + attributes: [] + }] + }], + project_id: null, + }); + + const result = await task.save(); + expect(typeof result.id).toBe('number'); + }); }); describe('Feature: delete a task', () => { diff --git a/cvat-core/tests/api/user.js b/cvat-core/tests/api/user.js index 27fe581417c2..96e965ffa824 100644 --- a/cvat-core/tests/api/user.js +++ b/cvat-core/tests/api/user.js @@ -1,17 +1,20 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT // Setup mock for a server jest.mock('../../src/server-proxy', () => { - const mock = require('../mocks/server-proxy.mock'); - return mock; + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; }); // Initialize api -window.cvat = require('../../src/api'); +window.cvat = require('../../src/api').default; -const User = require('../../src/user'); +const User = require('../../src/user').default; // Test cases describe('Feature: get a list of users', () => { diff --git a/cvat-core/tests/api/webhooks.js b/cvat-core/tests/api/webhooks.js new file mode 100644 index 000000000000..9a2c2283958b --- /dev/null +++ b/cvat-core/tests/api/webhooks.js @@ -0,0 +1,124 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +// Setup mock for a server +jest.mock('../../src/server-proxy', () => { + return { + __esModule: true, + default: require('../mocks/server-proxy.mock'), + }; +}); + +// Initialize api +window.cvat = require('../../src/api').default; + +const Webhook = require('../../src/webhook').default; +const { webhooksDummyData, webhooksEventsDummyData } = require('../mocks/dummy-data.mock'); +const { WebhookSourceType } = require('../../src/enums'); + +describe('Feature: get webhooks', () => { + test('get all webhooks', async () => { + const result = await window.cvat.webhooks.get({}); + expect(Array.isArray(result)).toBeTruthy(); + expect(result).toHaveLength(webhooksDummyData.count); + for (const item of result) { + expect(item).toBeInstanceOf(Webhook); + } + }); + + test('get webhook events', async () => { + function checkEvents(events) { + expect(Array.isArray(result)).toBeTruthy(); + for (const event of events) { + expect(event).toMatch(/((create)|(update)|(delete)):/); + } + } + let result = await Webhook.availableEvents(WebhookSourceType.PROJECT); + checkEvents(result); + + result = await Webhook.availableEvents(WebhookSourceType.ORGANIZATION); + checkEvents(result); + }); + + test('get webhook by id', async () => { + const result = await window.cvat.webhooks.get({ + id: 1, + }); + const [webhook] = result; + + expect(Array.isArray(result)).toBeTruthy(); + expect(result).toHaveLength(1); + expect(webhook).toBeInstanceOf(Webhook); + expect(webhook.id).toBe(1); + expect(webhook.targetURL).toBe('https://localhost:3001/project/hook'); + expect(webhook.description).toBe('Project webhook'); + expect(webhook.contentType).toBe('application/json'); + expect(webhook.enableSSL).toBeTruthy(); + expect(webhook.events).toEqual(webhooksEventsDummyData[WebhookSourceType.PROJECT].events); + }); +}); + + + +describe('Feature: create a webhook', () => { + test('create new webhook', async () => { + const webhook = new window.cvat.classes.Webhook({ + description: 'New webhook', + target_url: 'https://localhost:3001/hook', + content_type: 'application/json', + secret: 'secret', + enable_ssl: true, + is_active: true, + events: webhooksEventsDummyData[WebhookSourceType.PROJECT].events, + project_id: 1, + type:WebhookSourceType.PROJECT, + }); + + const result = await webhook.save(); + expect(typeof result.id).toBe('number'); + }); +}); + +describe('Feature: update a webhook', () => { + test('update some webhook fields', async () => { + const newValues = new Map([ + ['description', 'New description'], + ['isActive', false], + ['targetURL', 'https://localhost:3001/new/url'], + ]); + + let result = await window.cvat.webhooks.get({ + id: 1, + }); + let [webhook] = result; + for (const [key, value] of newValues) { + webhook[key] = value; + } + webhook.save(); + + result = await window.cvat.webhooks.get({ + id: 1, + }); + [webhook] = result; + newValues.forEach((value, key) => { + expect(webhook[key]).toBe(value); + }); + }); +}); + +describe('Feature: delete a webhook', () => { + test('delete a webhook', async () => { + let result = await window.cvat.webhooks.get({ + id: 2, + }); + const [webhook] = result; + await webhook.delete(); + + result = await window.cvat.webhooks.get({ + id: 2, + }); + expect(Array.isArray(result)).toBeTruthy(); + expect(result).toHaveLength(0); + }); +}); diff --git a/cvat-core/tests/mocks/dummy-data.mock.js b/cvat-core/tests/mocks/dummy-data.mock.js index 3e5944c5956b..ef8b15d66213 100644 --- a/cvat-core/tests/mocks/dummy-data.mock.js +++ b/cvat-core/tests/mocks/dummy-data.mock.js @@ -783,6 +783,105 @@ const tasksDummyData = { stop_frame: 5001, frame_filter: '', }, + { + url: 'http://localhost:7000/api/tasks/40', + id: 40, + name: 'test', + project_id: null, + mode: 'annotation', + owner: { + url: 'http://localhost:7000/api/users/1', + id: 1, + username: 'admin', + first_name: '', + last_name: '', + }, + assignee: null, + bug_tracker: '', + created_date: '2022-08-25T12:10:45.471663Z', + updated_date: '2022-08-25T12:10:45.993989Z', + overlap: 0, + segment_size: 4, + status: 'annotation', + labels: [{ + id: 54, + name: 'star skeleton', + color: '#9cb75a', + attributes: [], + type: 'skeleton', + sublabels: [{ + id: 55, + name: '1', + color: '#d12345', + attributes: [], + type: 'points', + has_parent: true + }, { + id: 56, + name: '2', + color: '#350dea', + attributes: [], + type: 'points', + has_parent: true + }, { + id: 57, + name: '3', + color: '#479ffe', + attributes: [], + type: 'points', + has_parent: true + }, { + id: 58, + name: '4', + color: '#4a649f', + attributes: [], + type: 'points', + has_parent: true + }, { + id: 59, + name: '5', + color: '#478144', + attributes: [], + type: 'points', + has_parent: true + }], + has_parent: false, + svg: + ` + + + + + + + + + ` + }], + segments: [{ + start_frame: 0, + stop_frame: 3, + jobs: [{ + url: 'http://localhost:7000/api/jobs/40', + id: 40, + assignee: null, + status: 'annotation', + stage: 'annotation', + state: 'new', + }] + }], + data_chunk_size: 17, + data_compressed_chunk_type: 'imageset', + data_original_chunk_type: 'imageset', + size: 4, + image_quality: 70, + data: 12, + dimension: '2d', + subset: '', + organization: null, + target_storage: null, + source_storage: null + }, { url: 'http://localhost:7000/api/tasks/3', id: 3, @@ -2532,6 +2631,230 @@ const taskAnnotationsDummyData = { ], tracks: [], }, + 40: { + version: 0, + tags: [], + shapes: [{ + type: 'skeleton', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [], + id: 23, + frame: 0, + label_id: 54, + group: 0, + source: 'manual', + attributes: [], + elements: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 908.0654296875, + 768.8268729552019 + ], + id: 24, + frame: 0, + label_id: 55, + group: 0, + source: 'manual', + attributes: [] + }, { + type: "points", + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 1230.1533057030483, + 523.7802734375 + ], + id: 25, + frame: 0, + label_id: 56, + group: 0, + source: 'manual', + attributes: [] + }, { + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 1525.9969940892624, + 772.6557444966547 + ], + id: 26, + frame: 0, + label_id: 57, + group: 0, + source: 'manual', + attributes: [] + }, { + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 1468.7369236136856, + 1270.4066429432623 + ], + id: 27, + frame: 0, + label_id: 58, + group: 0, + source: 'manual', + attributes: [] + }, { + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 989.1838401839595, + 1258.9201156622657 + ], + id: 28, + frame: 0, + label_id: 59, + group: 0, + source: 'manual', + attributes: [] + }] + }], + tracks: [{ + id: 1, + frame: 0, + label_id: 54, + group: 0, + source: 'manual', + shapes: [{ + type: 'skeleton', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [], + id: 1, + frame: 0, + attributes: [] + }], + attributes: [], + elements: [{ + id: 2, + frame: 0, + label_id: 55, + group: 0, + source: 'manual', + shapes: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 88.4140625, + 332.85145482411826 + ], + id: 2, + frame: 0, + attributes: [] + }], + attributes: [] + }, { + id: 3, + frame: 0, + label_id: 56, + group: 0, + source: 'manual', + shapes: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 437.3386217629577, + 96.447265625 + ], + id: 3, + frame: 0, + attributes: [] + }], + attributes: [] + }, { + id: 4, + frame: 0, + label_id: 57, + group: 0, + source: 'manual', + shapes: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 757.8323014937105, + 336.54528805456357 + ], + id: 4, + frame: 0, + attributes: [] + }], + attributes: [] + }, { + id: 5, + frame: 0, + label_id: 58, + group: 0, + source: 'manual', + shapes: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 695.8012648051717, + 816.7412907822327 + ], + id: 5, + frame: 0, + attributes: [] + }], + attributes: [] + }, { + id: 6, + frame: 0, + label_id: 59, + group: 0, + source: 'manual', + shapes: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 176.29133990867558, + 805.659875353811 + ], + id: 6, + frame: 0, + attributes: [] + }], + attributes: [] + }] + }] + } }; const jobAnnotationsDummyData = JSON.parse(JSON.stringify(taskAnnotationsDummyData)); @@ -2544,6 +2867,7 @@ const frameMetaDummyData = { start_frame: 0, stop_frame: 8, frame_filter: '', + deleted_frames: [], frames: [ { width: 1920, @@ -2590,6 +2914,7 @@ const frameMetaDummyData = { start_frame: 0, stop_frame: 74, frame_filter: '', + deleted_frames: [], frames: [ { width: 1920, @@ -2602,8 +2927,24 @@ const frameMetaDummyData = { size: 5002, image_quality: 50, start_frame: 0, + stop_frame: 4999, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 4: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 4995, stop_frame: 5001, frame_filter: '', + deleted_frames: [], frames: [ { width: 1888, @@ -2611,6 +2952,36 @@ const frameMetaDummyData = { }, ], }, + 40: { + chunk_size: 17, + size: 4, + image_quality: 70, + start_frame: 0, + stop_frame: 3, + frame_filter: '', + frames: [{ + width: 2560, + height: 1703, + name: '1598296101_1033667.jpg', + has_related_context: false + }, { + width: 1600, + height: 1200, + name: '30fdce7f27b9c7b1d50108d7c16d23ef.jpg', + has_related_context: false + }, { + width: 2880, + height: 1800, + name: '567362-ily-comedy-drama-1finding-3.jpg', + has_related_context: false + }, { + width: 1920, + height: 1080, + name: '730443-under-the-sea-wallpapers-1920x1080-windows-10.jpg', + has_related_context: false + }], + deleted_frames: [] + }, 100: { chunk_size: 36, size: 9, @@ -2618,6 +2989,7 @@ const frameMetaDummyData = { start_frame: 0, stop_frame: 8, frame_filter: '', + deleted_frames: [7, 8], frames: [ { width: 1920, @@ -2662,8 +3034,9 @@ const frameMetaDummyData = { size: 5002, image_quality: 50, start_frame: 0, - stop_frame: 5001, + stop_frame: 499, frame_filter: '', + deleted_frames: [], frames: [ { width: 1888, @@ -2672,12 +3045,163 @@ const frameMetaDummyData = { ], }, 102: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 495, + stop_frame: 994, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 103: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 990, + stop_frame: 1489, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 104: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 1485, + stop_frame: 1984, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 105: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 1980, + stop_frame: 2479, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 106: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 2475, + stop_frame: 2974, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 107: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 2970, + stop_frame: 3469, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 108: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 3465, + stop_frame: 3964, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 109: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 3960, + stop_frame: 4459, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 110: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 4455, + stop_frame: 4954, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 111: { + chunk_size: 36, + size: 5002, + image_quality: 50, + start_frame: 4950, + stop_frame: 5001, + frame_filter: '', + deleted_frames: [], + frames: [ + { + width: 1888, + height: 1408, + }, + ], + }, + 112: { chunk_size: 36, size: 1, image_quality: 50, start_frame: 0, stop_frame: 0, frame_filter: '', + deleted_frames: [], frames: [ { width: 1920, @@ -2758,6 +3282,165 @@ const cloudStoragesDummyData = { ] }; +const webhooksDummyData = { + count: 3, + next: null, + previous: null, + results: [ + { + id: 1, + url: "http://localhost:7000/api/webhooks/1", + target_url: "https://localhost:3001/project/hook", + description: "Project webhook", + type: "project", + content_type: "application/json", + is_active: true, + enable_ssl: true, + created_date: "2022-09-23T06:29:12.337276Z", + updated_date: "2022-09-23T06:29:12.337316Z", + owner: { + url: "http://localhost:7000/api/users/1", + id: 1, + username: "kirill", + first_name: "", + last_name: "" + }, + project: 1, + organization: 1, + events: [ + "create:comment", + "create:issue", + "create:task", + "delete:comment", + "delete:issue", + "delete:task", + "update:comment", + "update:job", + "update:project", + "update:task" + ], + last_status: "Failed to connect to target url", + last_delivery_date: "2022-09-23T06:28:48.313010Z" + }, + { + id: 2, + url: "http://localhost:7000/api/webhooks/2", + target_url: "https://localhost:3001/example/route", + description: "Second webhook", + type: "organization", + content_type: "application/json", + is_active: true, + enable_ssl: true, + created_date: "2022-09-23T06:28:32.430437Z", + updated_date: "2022-09-23T06:28:32.430474Z", + owner: { + url: "http://localhost:7000/api/users/1", + id: 1, + username: "kirill", + first_name: "", + last_name: "" + }, + project: 1, + organization: 1, + events: [ + "create:project", + "create:task", + "delete:project", + "delete:task", + "update:job", + "update:project", + "update:task" + ], + last_status: "200", + last_delivery_date: "2022-09-23T06:28:48.313010Z" + }, + { + id: 3, + url: "http://localhost:7000/api/webhooks/3", + target_url: "https://localhost:3001/example1", + description: "Example webhook", + type: "organization", + content_type: "application/json", + is_active: true, + enable_ssl: true, + created_date: "2022-09-23T06:27:52.888204Z", + updated_date: "2022-09-23T06:27:52.888245Z", + owner: { + url: "http://localhost:7000/api/users/1", + id: 1, + username: "kirill", + first_name: "", + last_name: "" + }, + project: 1, + organization: 1, + events: [ + "create:comment", + "create:invitation", + "create:issue", + "create:project", + "create:task", + "delete:comment", + "delete:invitation", + "delete:issue", + "delete:membership", + "delete:project", + "delete:task", + "update:comment", + "update:invitation", + "update:job", + "update:membership", + "update:organization", + "update:project", + "update:task" + ], + last_status: "200", + last_delivery_date: "2022-09-23T06:28:48.283962Z" + } + ] +}; + +const webhooksEventsDummyData = { + project: { + webhook_type: "project", + events: [ + "create:comment", + "create:issue", + "create:task", + "delete:comment", + "delete:issue", + "delete:task", + "update:comment", + "update:job", + "update:project", + "update:task" + ] + }, + organization: { + webhook_type: "organization", + events: [ + "create:comment", + "create:invitation", + "create:issue", + "create:project", + "create:task", + "delete:comment", + "delete:invitation", + "delete:issue", + "delete:membership", + "delete:project", + "delete:task", + "update:comment", + "update:invitation", + "update:job", + "update:membership", + "update:organization", + "update:project", + "update:task" + ] + }, +} + module.exports = { tasksDummyData, projectsDummyData, @@ -2769,4 +3452,6 @@ module.exports = { frameMetaDummyData, formatsDummyData, cloudStoragesDummyData, + webhooksDummyData, + webhooksEventsDummyData, }; diff --git a/cvat-core/tests/mocks/server-proxy.mock.js b/cvat-core/tests/mocks/server-proxy.mock.js index 08aa6fc10983..9491f9c0bcf6 100644 --- a/cvat-core/tests/mocks/server-proxy.mock.js +++ b/cvat-core/tests/mocks/server-proxy.mock.js @@ -13,6 +13,8 @@ const { jobAnnotationsDummyData, frameMetaDummyData, cloudStoragesDummyData, + webhooksDummyData, + webhooksEventsDummyData, } = require('./dummy-data.mock'); function QueryStringToJSON(query, ignoreList = []) { @@ -285,8 +287,33 @@ class ServerProxy { return 'DUMMY_IMAGE'; } - async function getMeta(tid) { - return JSON.parse(JSON.stringify(frameMetaDummyData[tid])); + async function getMeta(session, jid) { + if (session !== 'job') { + throw new Error('not implemented test'); + } + + return JSON.parse(JSON.stringify(frameMetaDummyData[jid])); + } + + async function saveMeta(session, jid, meta) { + if (session !== 'job') { + throw new Error('not implemented test'); + } + const object = frameMetaDummyData[jid]; + for (const prop in meta) { + if ( + Object.prototype.hasOwnProperty.call(meta, prop) && + Object.prototype.hasOwnProperty.call(object, prop) + ) { + if (prop === 'labels') { + object[prop] = meta[prop].filter((label) => !label.deleted); + } else { + object[prop] = meta[prop]; + } + } + } + + return getMeta(jid); } async function getAnnotations(session, id) { @@ -387,6 +414,71 @@ class ServerProxy { } } + async function getWebhooks(filter = '') { + const queries = QueryStringToJSON(filter); + const result = webhooksDummyData.results.filter((item) => { + for (const key in queries) { + if (Object.prototype.hasOwnProperty.call(queries, key)) { + if (queries[key] !== item[key]) { + return false; + } + } + } + return true; + }); + return result; + } + + async function createWebhook(webhookData) { + const id = Math.max(...webhooksDummyData.results.map((item) => item.id)) + 1; + webhooksDummyData.results.push({ + id, + description: webhookData.description, + target_url: webhookData.target_url, + content_type: webhookData.content_type, + secret: webhookData.secret, + enable_ssl: webhookData.enable_ssl, + is_active: webhookData.is_active, + events: webhookData.events, + organization_id: webhookData.organization_id ? webhookData.organization_id : null, + project_id: webhookData.project_id ? webhookData.project_id : null, + type: webhookData.type, + owner: { id: 1 }, + created_date: '2022-09-23T06:29:12.337276Z', + updated_date: '2022-09-23T06:29:12.337276Z', + }); + + const result = await getWebhooks(`?id=${id}`); + return result[0]; + } + + async function updateWebhook(webhookID, webhookData) { + const webhook = webhooksDummyData.results.find((item) => item.id === webhookID); + if (webhook) { + for (const prop in webhookData) { + if ( + Object.prototype.hasOwnProperty.call(webhookData, prop) && + Object.prototype.hasOwnProperty.call(webhook, prop) + ) { + webhook[prop] = webhookData[prop]; + } + } + } + return webhook; + } + + async function receiveWebhookEvents(type) { + return webhooksEventsDummyData[type]?.events; + } + + async function deleteWebhook(webhookID) { + const webhooks = webhooksDummyData.results; + const webhookIdx = webhooks.findIndex((item) => item.id === webhookID); + if (webhookIdx !== -1) { + webhooks.splice(webhookIdx); + } + } + Object.defineProperties( this, Object.freeze({ @@ -442,6 +534,7 @@ class ServerProxy { value: Object.freeze({ getData, getMeta, + saveMeta, getPreview, }), writable: false, @@ -463,6 +556,17 @@ class ServerProxy { }), writable: false, }, + + webhooks: { + value: Object.freeze({ + get: getWebhooks, + create: createWebhook, + update: updateWebhook, + delete: deleteWebhook, + events: receiveWebhookEvents, + }), + writable: false, + }, }), ); } diff --git a/cvat-core/tsconfig.json b/cvat-core/tsconfig.json new file mode 100644 index 000000000000..ba78d461d1f0 --- /dev/null +++ b/cvat-core/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2020", + "allowJs": true, + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "isolatedModules": true, + "noEmit": true, + "baseUrl": "src", + "resolveJsonModule": true, + }, + "include": ["src/*.ts"] +} diff --git a/cvat-core/webpack.config.js b/cvat-core/webpack.config.js index 1234ee726930..24098cf87608 100644 --- a/cvat-core/webpack.config.js +++ b/cvat-core/webpack.config.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -12,17 +12,31 @@ const nodeConfig = { target: 'node', mode: 'development', devtool: 'source-map', - entry: './src/api.js', + entry: './src/api.ts', output: { path: path.resolve(__dirname, 'dist'), filename: 'cvat-core.node.js', libraryTarget: 'commonjs', }, + resolve: { + extensions: ['.ts', '.js'], + }, module: { rules: [ { - test: /.js?$/, + test: /.ts?$/, exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + plugins: [ + '@babel/plugin-proposal-class-properties', + '@babel/plugin-proposal-optional-chaining', + ], + presets: ['@babel/preset-env', '@babel/typescript'], + sourceType: 'unambiguous', + }, + }, }, ], }, @@ -36,7 +50,7 @@ const webConfig = { mode: 'production', devtool: 'source-map', entry: { - 'cvat-core': './src/api.js', + 'cvat-core': './src/api.ts', }, output: { path: path.resolve(__dirname, 'dist'), @@ -44,15 +58,22 @@ const webConfig = { library: 'cvat', libraryTarget: 'window', }, + resolve: { + extensions: ['.ts', '.js'], + }, module: { rules: [ { - test: /.js?$/, + test: /.ts?$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { - presets: ['@babel/preset-env'], + plugins: [ + '@babel/plugin-proposal-class-properties', + '@babel/plugin-proposal-optional-chaining', + ], + presets: ['@babel/preset-env', '@babel/typescript'], sourceType: 'unambiguous', }, }, @@ -63,7 +84,8 @@ const webConfig = { loader: 'worker-loader', options: { publicPath: '/static/engine/js/3rdparty/', - name: '[name].[contenthash].js', + filename: '[name].[contenthash].js', + esModule: false, }, }, }, @@ -74,7 +96,8 @@ const webConfig = { loader: 'worker-loader', options: { publicPath: '/static/engine/js/', - name: '[name].[contenthash].js', + filename: '[name].[contenthash].js', + esModule: false, }, }, }, diff --git a/cvat-data/.eslintrc.js b/cvat-data/.eslintrc.js index eba79c05ebf7..3aa39c7d09a9 100644 --- a/cvat-data/.eslintrc.js +++ b/cvat-data/.eslintrc.js @@ -1,12 +1,18 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT module.exports = { + ignorePatterns: [ + '.eslintrc.js', + 'webpack.config.js', + 'src/3rdparty/**', + 'node_modules/**', + 'dist/**', + ], parserOptions: { - parser: 'babel-eslint', - sourceType: 'module', - ecmaVersion: 2018, + project: './tsconfig.json', + tsconfigRootDir: __dirname, }, - ignorePatterns: ['.eslintrc.js', 'webpack.config.js', 'src/3rdparty/**', 'node_modules/**', 'dist/**'], + plugins: ['jest'], }; diff --git a/cvat-data/README.md b/cvat-data/README.md index 96baf6506000..1fae6821dfd5 100644 --- a/cvat-data/README.md +++ b/cvat-data/README.md @@ -1,15 +1,15 @@ # cvat-data module ```bash -npm run build # build with minification -npm run build -- --mode=development # build without minification -npm run server # run debug server +yarn run build # build with minification +yarn run build --mode=development # build without minification +yarn run server # run debug server ``` ## Versioning If you make changes in this package, please do following: -- After not important changes (typos, backward compatible bug fixes, refactoring) do: `npm version patch` -- After changing API (backward compatible new features) do: `npm version minor` -- After changing API (changes that break backward compatibility) do: `npm version major` +- After not important changes (typos, backward compatible bug fixes, refactoring) do: `yarn version --patch` +- After changing API (backward compatible new features) do: `yarn version --minor` +- After changing API (changes that break backward compatibility) do: `yarn version --major` diff --git a/cvat-data/package-lock.json b/cvat-data/package-lock.json deleted file mode 100644 index e7765b2e96cd..000000000000 --- a/cvat-data/package-lock.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "name": "cvat-data", - "version": "1.0.2", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "cvat-data", - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "async-mutex": "^0.3.2", - "jszip": "3.7.1" - }, - "devDependencies": {} - }, - "node_modules/async-mutex": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", - "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - } - }, - "dependencies": { - "async-mutex": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", - "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", - "requires": { - "tslib": "^2.3.1" - } - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", - "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" - } - }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "requires": { - "immediate": "~3.0.5" - } - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - } - } -} diff --git a/cvat-data/package.json b/cvat-data/package.json index eae7762ac1c4..2158e87ad650 100644 --- a/cvat-data/package.json +++ b/cvat-data/package.json @@ -2,13 +2,15 @@ "name": "cvat-data", "version": "1.0.2", "description": "", - "main": "src/js/cvat-data.js", + "main": "src/ts/cvat-data.ts", "scripts": { - "patch": "cd src/js && patch --dry-run --forward -p0 < 3rdparty_patch.diff >> /dev/null && patch -p0 < 3rdparty_patch.diff; true", - "build": "npm run patch; webpack --config ./webpack.config.js", - "server": "npm run patch; nodemon --watch config --exec 'webpack-dev-server --config ./webpack.config.js --mode=development --open'" + "patch": "cd src/ts && patch --dry-run --forward -p0 < 3rdparty_patch.diff >> /dev/null && patch -p0 < 3rdparty_patch.diff; true", + "build": "yarn run patch; webpack --config ./webpack.config.js", + "server": "yarn run patch; nodemon --watch config --exec 'webpack-dev-server --config ./webpack.config.js --mode=development --open'", + "type-check": "tsc --noEmit", + "type-check:watch": "yarn run type-check --watch" }, - "author": "Intel", + "author": "CVAT.ai", "license": "MIT", "browserslist": [ "Chrome >= 63", @@ -18,7 +20,7 @@ ], "devDependencies": {}, "dependencies": { - "async-mutex": "^0.3.2", - "jszip": "3.7.1" + "async-mutex": "^0.4.0", + "jszip": "3.10.1" } } diff --git a/cvat-data/src/js/3rdparty/Decoder.js b/cvat-data/src/ts/3rdparty/Decoder.js similarity index 100% rename from cvat-data/src/js/3rdparty/Decoder.js rename to cvat-data/src/ts/3rdparty/Decoder.js diff --git a/cvat-data/src/js/3rdparty/Decoder.worker.js b/cvat-data/src/ts/3rdparty/Decoder.worker.js similarity index 100% rename from cvat-data/src/js/3rdparty/Decoder.worker.js rename to cvat-data/src/ts/3rdparty/Decoder.worker.js diff --git a/cvat-data/src/js/3rdparty/README.md b/cvat-data/src/ts/3rdparty/README.md similarity index 100% rename from cvat-data/src/js/3rdparty/README.md rename to cvat-data/src/ts/3rdparty/README.md diff --git a/cvat-data/src/js/3rdparty/avc.wasm b/cvat-data/src/ts/3rdparty/avc.wasm similarity index 100% rename from cvat-data/src/js/3rdparty/avc.wasm rename to cvat-data/src/ts/3rdparty/avc.wasm diff --git a/cvat-data/src/js/3rdparty/mp4.js b/cvat-data/src/ts/3rdparty/mp4.js similarity index 100% rename from cvat-data/src/js/3rdparty/mp4.js rename to cvat-data/src/ts/3rdparty/mp4.js diff --git a/cvat-data/src/js/3rdparty_patch.diff b/cvat-data/src/ts/3rdparty_patch.diff similarity index 100% rename from cvat-data/src/js/3rdparty_patch.diff rename to cvat-data/src/ts/3rdparty_patch.diff diff --git a/cvat-data/src/js/cvat-data.js b/cvat-data/src/ts/cvat-data.ts similarity index 97% rename from cvat-data/src/js/cvat-data.js rename to cvat-data/src/ts/cvat-data.ts index e9c0c1d60a8b..1484fff07f4a 100644 --- a/cvat-data/src/js/cvat-data.js +++ b/cvat-data/src/ts/cvat-data.ts @@ -1,24 +1,24 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT -const { Mutex } = require('async-mutex'); +import { Mutex } from 'async-mutex'; // eslint-disable-next-line max-classes-per-file -const { MP4Reader, Bytestream } = require('./3rdparty/mp4'); -const ZipDecoder = require('./unzip_imgs.worker'); -const H264Decoder = require('./3rdparty/Decoder.worker'); +import { MP4Reader, Bytestream } from './3rdparty/mp4'; +import ZipDecoder from './unzip_imgs.worker'; +import H264Decoder from './3rdparty/Decoder.worker'; -const BlockType = Object.freeze({ +export const BlockType = Object.freeze({ MP4VIDEO: 'mp4video', ARCHIVE: 'archive', }); -const DimensionType = Object.freeze({ +export const DimensionType = Object.freeze({ DIM_3D: '3d', DIM_2D: '2d', }); -class FrameProvider { +export class FrameProvider { constructor( blockType, blockSize, @@ -373,9 +373,3 @@ class FrameProvider { return [...this._blocksRanges].sort((a, b) => a.split(':')[0] - b.split(':')[0]); } } - -module.exports = { - FrameProvider, - BlockType, - DimensionType, -}; diff --git a/cvat-data/src/js/unzip_imgs.worker.js b/cvat-data/src/ts/unzip_imgs.worker.js similarity index 97% rename from cvat-data/src/js/unzip_imgs.worker.js rename to cvat-data/src/ts/unzip_imgs.worker.js index 3434e3b8aca0..355106b8b239 100644 --- a/cvat-data/src/js/unzip_imgs.worker.js +++ b/cvat-data/src/ts/unzip_imgs.worker.js @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-data/tsconfig.json b/cvat-data/tsconfig.json new file mode 100644 index 000000000000..0091ff8d4a1a --- /dev/null +++ b/cvat-data/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2020", + "allowJs": true, + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "isolatedModules": true, + "noEmit": true, + "baseUrl": "src", + }, + "include": ["src/ts/*.ts"] +} diff --git a/cvat-data/webpack.config.js b/cvat-data/webpack.config.js index 1b90f2e010d7..71f8663f72e3 100644 --- a/cvat-data/webpack.config.js +++ b/cvat-data/webpack.config.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -13,7 +13,7 @@ const cvatData = { target: 'web', mode: 'production', entry: { - 'cvat-data': './src/js/cvat-data.js', + 'cvat-data': './src/js/cvat-data.ts', }, output: { path: path.resolve(__dirname, 'dist'), @@ -21,15 +21,22 @@ const cvatData = { library: 'cvatData', libraryTarget: 'window', }, + resolve: { + extensions: ['.ts', '.js'], + }, module: { rules: [ { - test: /.js?$/, + test: /.ts?$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { - presets: ['@babel/preset-env'], + plugins: [ + '@babel/plugin-proposal-class-properties', + '@babel/plugin-proposal-optional-chaining', + ], + presets: ['@babel/preset-env', '@babel/typescript'], sourceType: 'unambiguous', }, }, @@ -41,7 +48,8 @@ const cvatData = { loader: 'worker-loader', options: { publicPath: '/', - name: '[name].[contenthash].js', + filename: '[name].[contenthash].js', + esModule: false, }, }, }, @@ -51,13 +59,14 @@ const cvatData = { loader: 'worker-loader', options: { publicPath: '/3rdparty/', - name: '3rdparty/[name].[contenthash].js', + filename: '3rdparty/[name].[contenthash].js', + esModule: false, }, }, }, ], }, - plugins: [new CopyPlugin(['./src/js/3rdparty/avc.wasm'])], + plugins: [new CopyPlugin({ patterns: ['./src/js/3rdparty/avc.wasm'] })], }; module.exports = cvatData; diff --git a/cvat-sdk/.gitignore b/cvat-sdk/.gitignore new file mode 100644 index 000000000000..d01a61d14490 --- /dev/null +++ b/cvat-sdk/.gitignore @@ -0,0 +1,79 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.venv/ +.python-version +.pytest_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints + +# Workflow +schema/ +.openapi-generator/ + +# Generated code +/cvat_sdk/api_client/ +/cvat_sdk/version.py +/requirements/ +/docs/ +/setup.py +/README.md +/MANIFEST.in \ No newline at end of file diff --git a/cvat-sdk/.openapi-generator-ignore b/cvat-sdk/.openapi-generator-ignore new file mode 100644 index 000000000000..44b0733aa739 --- /dev/null +++ b/cvat-sdk/.openapi-generator-ignore @@ -0,0 +1,40 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md + +# For safety +/cvat_sdk/__init__.py +/config +/gen +/helpers.py +/utils.py +/types.py + +# Don't generate these files +/git_push.sh +/setup.cfg +/test-requirements.txt +/tox.ini +/.gitlab-ci.yml +/.travis.yml +/.gitignore diff --git a/cvat-sdk/.remarkignore b/cvat-sdk/.remarkignore new file mode 100644 index 000000000000..4f1b9bbf8425 --- /dev/null +++ b/cvat-sdk/.remarkignore @@ -0,0 +1,2 @@ +cvat-sdk/docs/ +cvat-sdk/README.md diff --git a/cvat-sdk/cvat_sdk/__init__.py b/cvat-sdk/cvat_sdk/__init__.py new file mode 100644 index 000000000000..1a6103abbc25 --- /dev/null +++ b/cvat-sdk/cvat_sdk/__init__.py @@ -0,0 +1,6 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from cvat_sdk.core.client import Client, Config, make_client +from cvat_sdk.version import VERSION as __version__ diff --git a/cvat-sdk/cvat_sdk/core/__init__.py b/cvat-sdk/cvat_sdk/core/__init__.py new file mode 100644 index 000000000000..1a6103abbc25 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/__init__.py @@ -0,0 +1,6 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from cvat_sdk.core.client import Client, Config, make_client +from cvat_sdk.version import VERSION as __version__ diff --git a/cvat-sdk/cvat_sdk/core/client.py b/cvat-sdk/cvat_sdk/core/client.py new file mode 100644 index 000000000000..616814c56423 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/client.py @@ -0,0 +1,323 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + + +from __future__ import annotations + +import logging +import urllib.parse +from contextlib import suppress +from time import sleep +from typing import Any, Dict, Optional, Sequence, Tuple + +import attrs +import packaging.version as pv +import urllib3 +import urllib3.exceptions + +from cvat_sdk.api_client import ApiClient, Configuration, exceptions, models +from cvat_sdk.core.exceptions import IncompatibleVersionException, InvalidHostException +from cvat_sdk.core.helpers import expect_status +from cvat_sdk.core.proxies.issues import CommentsRepo, IssuesRepo +from cvat_sdk.core.proxies.jobs import JobsRepo +from cvat_sdk.core.proxies.model_proxy import Repo +from cvat_sdk.core.proxies.projects import ProjectsRepo +from cvat_sdk.core.proxies.tasks import TasksRepo +from cvat_sdk.core.proxies.users import UsersRepo +from cvat_sdk.version import VERSION + + +@attrs.define +class Config: + """ + Allows to tweak behavior of Client instances. + """ + + status_check_period: float = 5 + """Operation status check period, in seconds""" + + allow_unsupported_server: bool = True + """Allow to use SDK with an unsupported server version. If disabled, raise an exception""" + + verify_ssl: Optional[bool] = None + """Whether to verify host SSL certificate or not""" + + +class Client: + """ + Provides session management, implements authentication operations + and simplifies access to server APIs. + """ + + SUPPORTED_SERVER_VERSIONS = ( + pv.Version("2.0"), + pv.Version("2.1"), + pv.Version("2.2"), + pv.Version("2.3"), + ) + + def __init__( + self, + url: str, + *, + logger: Optional[logging.Logger] = None, + config: Optional[Config] = None, + check_server_version: bool = True, + ) -> None: + url = self._validate_and_prepare_url(url) + + self.logger = logger or logging.getLogger(__name__) + """The root logger""" + + self.config = config or Config() + """Configuration for this object""" + + self.api_map = CVAT_API_V2(url) + """Handles server API URL interaction logic""" + + self.api_client = ApiClient( + Configuration(host=self.api_map.host, verify_ssl=self.config.verify_ssl) + ) + """Provides low-level access to the CVAT server""" + + if check_server_version: + self.check_server_version() + + self._repos: Dict[str, Repo] = {} + """A cache for created Repository instances""" + + ALLOWED_SCHEMAS = ("https", "http") + + @classmethod + def _validate_and_prepare_url(cls, url: str) -> str: + url_parts = url.split("://", maxsplit=1) + if len(url_parts) == 2: + schema, base_url = url_parts + else: + schema = "" + base_url = url + + base_url = base_url.rstrip("/") + + if schema and schema not in cls.ALLOWED_SCHEMAS: + raise InvalidHostException( + f"Invalid url schema '{schema}', expected " + f"one of , {', '.join(cls.ALLOWED_SCHEMAS)}" + ) + + if not schema: + schema = cls._detect_schema(base_url) + url = f"{schema}://{base_url}" + + return url + + @classmethod + def _detect_schema(cls, base_url: str) -> str: + for schema in cls.ALLOWED_SCHEMAS: + with ApiClient(Configuration(host=f"{schema}://{base_url}")) as api_client: + with suppress(urllib3.exceptions.RequestError): + (_, response) = api_client.server_api.retrieve_about( + _request_timeout=5, _parse_response=False, _check_status=False + ) + + if response.status in [200, 401]: + # Server versions prior to 2.3.0 respond with unauthorized + # 2.3.0 allows unauthorized access + return schema + + raise InvalidHostException( + "Failed to detect host schema automatically, please check " + "the server url and try to specify 'https://' or 'http://' explicitly" + ) + + def __enter__(self): + self.api_client.__enter__() + return self + + def __exit__(self, exc_type, exc_value, traceback) -> None: + return self.api_client.__exit__(exc_type, exc_value, traceback) + + def close(self) -> None: + return self.__exit__(None, None, None) + + def login(self, credentials: Tuple[str, str]) -> None: + (auth, _) = self.api_client.auth_api.create_login( + models.LoginSerializerExRequest(username=credentials[0], password=credentials[1]) + ) + + assert "sessionid" in self.api_client.cookies + assert "csrftoken" in self.api_client.cookies + self.api_client.set_default_header("Authorization", "Token " + auth.key) + + def has_credentials(self) -> bool: + return ( + ("sessionid" in self.api_client.cookies) + or ("csrftoken" in self.api_client.cookies) + or bool(self.api_client.get_common_headers().get("Authorization", "")) + ) + + def logout(self) -> None: + if self.has_credentials(): + self.api_client.auth_api.create_logout() + self.api_client.cookies.pop("sessionid", None) + self.api_client.cookies.pop("csrftoken", None) + self.api_client.default_headers.pop("Authorization", None) + + def wait_for_completion( + self: Client, + url: str, + *, + success_status: int, + status_check_period: Optional[int] = None, + query_params: Optional[Dict[str, Any]] = None, + post_params: Optional[Dict[str, Any]] = None, + method: str = "POST", + positive_statuses: Optional[Sequence[int]] = None, + ) -> urllib3.HTTPResponse: + if status_check_period is None: + status_check_period = self.config.status_check_period + + positive_statuses = set(positive_statuses) | {success_status} + + while True: + sleep(status_check_period) + + response = self.api_client.rest_client.request( + method=method, + url=url, + headers=self.api_client.get_common_headers(), + query_params=query_params, + post_params=post_params, + ) + + self.logger.debug("STATUS %s", response.status) + expect_status(positive_statuses, response) + if response.status == success_status: + break + + return response + + def check_server_version(self, fail_if_unsupported: Optional[bool] = None) -> None: + if fail_if_unsupported is None: + fail_if_unsupported = not self.config.allow_unsupported_server + + try: + server_version = self.get_server_version() + except exceptions.ApiException as e: + msg = ( + "Failed to retrieve server API version: %s. " + "Some SDK functions may not work properly with this server." + ) % (e,) + self.logger.warning(msg) + if fail_if_unsupported: + raise IncompatibleVersionException(msg) + return + + sdk_version = pv.Version(VERSION) + + # We only check base version match. Micro releases and fixes do not affect + # API compatibility in general. + if all( + server_version.base_version != sv.base_version for sv in self.SUPPORTED_SERVER_VERSIONS + ): + msg = ( + "Server version '%s' is not compatible with SDK version '%s'. " + "Some SDK functions may not work properly with this server. " + "You can continue using this SDK, or you can " + "try to update with 'pip install cvat-sdk'." + ) % (server_version, sdk_version) + self.logger.warning(msg) + if fail_if_unsupported: + raise IncompatibleVersionException(msg) + + def get_server_version(self) -> pv.Version: + # TODO: allow to use this endpoint unauthorized + (about, _) = self.api_client.server_api.retrieve_about() + return pv.Version(about.version) + + def _get_repo(self, key: str) -> Repo: + _repo_map = { + "tasks": TasksRepo, + "projects": ProjectsRepo, + "jobs": JobsRepo, + "users": UsersRepo, + "issues": IssuesRepo, + "comments": CommentsRepo, + } + + repo = self._repos.get(key, None) + if repo is None: + repo = _repo_map[key](self) + self._repos[key] = repo + return repo + + @property + def tasks(self) -> TasksRepo: + return self._get_repo("tasks") + + @property + def projects(self) -> ProjectsRepo: + return self._get_repo("projects") + + @property + def jobs(self) -> JobsRepo: + return self._get_repo("jobs") + + @property + def users(self) -> UsersRepo: + return self._get_repo("users") + + @property + def issues(self) -> IssuesRepo: + return self._get_repo("issues") + + @property + def comments(self) -> CommentsRepo: + return self._get_repo("comments") + + +class CVAT_API_V2: + """Build parameterized API URLs""" + + def __init__(self, host: str): + self.host = host.rstrip("/") + self.base = self.host + "/api/" + self.git = self.host + "/git/repository/" + + def git_create(self, task_id: int) -> str: + return self.git + f"create/{task_id}" + + def git_check(self, rq_id: int) -> str: + return self.git + f"check/{rq_id}" + + def git_get(self, task_id: int) -> str: + return self.git + f"get/{task_id}" + + def make_endpoint_url( + self, + path: str, + *, + psub: Optional[Sequence[Any]] = None, + kwsub: Optional[Dict[str, Any]] = None, + query_params: Optional[Dict[str, Any]] = None, + ) -> str: + url = self.host + path + if psub or kwsub: + url = url.format(*(psub or []), **(kwsub or {})) + if query_params: + url += "?" + urllib.parse.urlencode(query_params) + return url + + +def make_client( + host: str, *, port: Optional[int] = None, credentials: Optional[Tuple[int, int]] = None +) -> Client: + url = host.rstrip("/") + if port: + url = f"{url}:{port}" + + client = Client(url=url) + if credentials is not None: + client.login(credentials) + return client diff --git a/cvat-sdk/cvat_sdk/core/downloading.py b/cvat-sdk/cvat_sdk/core/downloading.py new file mode 100644 index 000000000000..a7ba9a1391ca --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/downloading.py @@ -0,0 +1,103 @@ +# Copyright (C) 2020-2022 Intel Corporation +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from contextlib import closing +from pathlib import Path +from typing import TYPE_CHECKING, Any, Dict, Optional + +from cvat_sdk.api_client.api_client import Endpoint +from cvat_sdk.core.progress import ProgressReporter +from cvat_sdk.core.utils import atomic_writer + +if TYPE_CHECKING: + from cvat_sdk.core.client import Client + + +class Downloader: + """ + Implements common downloading protocols + """ + + def __init__(self, client: Client): + self._client = client + + def download_file( + self, + url: str, + output_path: Path, + *, + timeout: int = 60, + pbar: Optional[ProgressReporter] = None, + ) -> None: + """ + Downloads the file from url into a temporary file, then renames it to the requested name. + """ + + CHUNK_SIZE = 10 * 2**20 + + assert not output_path.exists() + + response = self._client.api_client.rest_client.GET( + url, + _request_timeout=timeout, + headers=self._client.api_client.get_common_headers(), + _parse_response=False, + ) + with closing(response): + try: + file_size = int(response.getheader("Content-Length", 0)) + except ValueError: + file_size = None + + with atomic_writer(output_path, "wb") as fd: + if pbar is not None: + pbar.start(file_size, desc="Downloading") + + while True: + chunk = response.read(amt=CHUNK_SIZE, decode_content=False) + if not chunk: + break + + if pbar is not None: + pbar.advance(len(chunk)) + + fd.write(chunk) + + def prepare_and_download_file_from_endpoint( + self, + endpoint: Endpoint, + filename: Path, + *, + url_params: Optional[Dict[str, Any]] = None, + query_params: Optional[Dict[str, Any]] = None, + pbar: Optional[ProgressReporter] = None, + status_check_period: Optional[int] = None, + ): + client = self._client + if status_check_period is None: + status_check_period = client.config.status_check_period + + client.logger.info("Waiting for the server to prepare the file...") + + url = client.api_map.make_endpoint_url( + endpoint.path, kwsub=url_params, query_params=query_params + ) + client.wait_for_completion( + url, + method="GET", + positive_statuses=[202], + success_status=201, + status_check_period=status_check_period, + ) + + query_params = dict(query_params or {}) + query_params["action"] = "download" + url = client.api_map.make_endpoint_url( + endpoint.path, kwsub=url_params, query_params=query_params + ) + downloader = Downloader(client) + downloader.download_file(url, output_path=filename, pbar=pbar) diff --git a/cvat-sdk/cvat_sdk/core/exceptions.py b/cvat-sdk/cvat_sdk/core/exceptions.py new file mode 100644 index 000000000000..b90a8fc18f54 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/exceptions.py @@ -0,0 +1,15 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + + +class CvatSdkException(Exception): + """Base class for SDK exceptions""" + + +class InvalidHostException(CvatSdkException): + """Indicates an invalid hostname error""" + + +class IncompatibleVersionException(CvatSdkException): + """Indicates server and SDK version mismatch""" diff --git a/cvat-sdk/cvat_sdk/core/git.py b/cvat-sdk/cvat_sdk/core/git.py new file mode 100644 index 000000000000..8347e6ca5097 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/git.py @@ -0,0 +1,59 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import json +from time import sleep +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from cvat_sdk.core.client import Client + + +def create_git_repo( + client: Client, + *, + task_id: int, + repo_url: str, + status_check_period: int = None, + use_lfs: bool = True, +): + if status_check_period is None: + status_check_period = client.config.status_check_period + + common_headers = client.api_client.get_common_headers() + + response = client.api_client.rest_client.POST( + client.api_map.git_create(task_id), + post_params={"path": repo_url, "lfs": use_lfs, "tid": task_id}, + headers=common_headers, + ) + response_json = json.loads(response.data) + rq_id = response_json["rq_id"] + client.logger.info(f"Create RQ ID: {rq_id}") + + client.logger.debug("Awaiting a dataset repository to be created for the task %s...", task_id) + check_url = client.api_map.git_check(rq_id) + status = None + while status != "finished": + sleep(status_check_period) + response = client.api_client.rest_client.GET(check_url, headers=common_headers) + response_json = json.loads(response.data) + status = response_json["status"] + if status == "failed" or status == "unknown": + client.logger.error( + "Dataset repository creation request for task %s failed" "with status %s.", + task_id, + status, + ) + break + + client.logger.debug( + "Awaiting a dataset repository to be created for the task %s. Response status: %s", + task_id, + status, + ) + + client.logger.debug("Dataset repository creation completed with status: %s.", status) diff --git a/cvat-sdk/cvat_sdk/core/helpers.py b/cvat-sdk/cvat_sdk/core/helpers.py new file mode 100644 index 000000000000..b548d85cdb15 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/helpers.py @@ -0,0 +1,113 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import io +import json +from typing import Any, Dict, Iterable, List, Optional, Union + +import tqdm +import urllib3 + +from cvat_sdk import exceptions +from cvat_sdk.api_client.api_client import Endpoint +from cvat_sdk.core.progress import ProgressReporter + + +def get_paginated_collection( + endpoint: Endpoint, *, return_json: bool = False, **kwargs +) -> Union[List, List[Dict[str, Any]]]: + """ + Accumulates results from all the pages + """ + + results = [] + page = 1 + while True: + (page_contents, response) = endpoint.call_with_http_info(**kwargs, page=page) + expect_status(200, response) + + if return_json: + results.extend(json.loads(response.data).get("results", [])) + else: + results.extend(page_contents.results) + + if not page_contents.next: + break + page += 1 + + return results + + +class TqdmProgressReporter(ProgressReporter): + def __init__(self, instance: tqdm.tqdm) -> None: + super().__init__() + self.tqdm = instance + + @property + def period(self) -> float: + return 0 + + def start(self, total: int, *, desc: Optional[str] = None): + self.tqdm.reset(total) + self.tqdm.set_description_str(desc) + + def report_status(self, progress: int): + self.tqdm.update(progress - self.tqdm.n) + + def advance(self, delta: int): + self.tqdm.update(delta) + + def finish(self): + self.tqdm.refresh() + + +class StreamWithProgress: + def __init__(self, stream: io.RawIOBase, pbar: ProgressReporter, length: Optional[int] = None): + self.stream = stream + self.pbar = pbar + + if hasattr(stream, "__len__"): + length = len(stream) + + self.length = length + pbar.start(length) + + def read(self, size=-1): + chunk = self.stream.read(size) + if chunk is not None: + self.pbar.advance(len(chunk)) + return chunk + + def __len__(self): + return self.length + + def seek(self, pos, start=0): + self.stream.seek(pos, start) + self.pbar.report_status(pos) + + def tell(self): + return self.stream.tell() + + def __enter__(self) -> StreamWithProgress: + return self + + def __exit__(self, exc_type, exc_value, exc_traceback) -> None: + self.pbar.finish() + + +def expect_status(codes: Union[int, Iterable[int]], response: urllib3.HTTPResponse) -> None: + if not hasattr(codes, "__iter__"): + codes = [codes] + + if response.status in codes: + return + + if 300 <= response.status <= 500: + raise exceptions.ApiException(response.status, reason=response.msg, http_resp=response) + else: + raise exceptions.ApiException( + response.status, reason="Unexpected status code received", http_resp=response + ) diff --git a/cvat-sdk/cvat_sdk/core/progress.py b/cvat-sdk/cvat_sdk/core/progress.py new file mode 100644 index 000000000000..f620e13c50c1 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/progress.py @@ -0,0 +1,123 @@ +# Copyright (C) 2022 Intel Corporation +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import math +from typing import Iterable, Optional, Tuple, TypeVar + +T = TypeVar("T") + + +class ProgressReporter: + """ + Only one set of methods must be called: + - start - report_status / advance - finish + - iter + - split + + This class is supposed to manage the state of children progress bars + and release of their resources, if necessary. + """ + + @property + def period(self) -> float: + """ + Returns reporting period. + + For example, 0.1 would mean every 10%. + """ + raise NotImplementedError + + def start(self, total: int, *, desc: Optional[str] = None): + """Initializes the progress bar""" + raise NotImplementedError + + def report_status(self, progress: int): + """Updates the progress bar""" + raise NotImplementedError + + def advance(self, delta: int): + """Updates the progress bar""" + raise NotImplementedError + + def finish(self): + """Finishes the progress bar""" + pass # pylint: disable=unnecessary-pass + + def iter( + self, + iterable: Iterable[T], + *, + total: Optional[int] = None, + desc: Optional[str] = None, + ) -> Iterable[T]: + """ + Traverses the iterable and reports progress simultaneously. + + Starts and finishes the progress bar automatically. + + Args: + iterable: An iterable to be traversed + total: The expected number of iterations. If not provided, will + try to use iterable.__len__. + desc: The status message + + Returns: + An iterable over elements of the input sequence + """ + + if total is None and hasattr(iterable, "__len__"): + total = len(iterable) + + self.start(total, desc=desc) + + if total: + display_step = math.ceil(total * self.period) + + for i, elem in enumerate(iterable): + if not total or i % display_step == 0: + self.report_status(i) + + yield elem + + self.finish() + + def split(self, count: int) -> Tuple[ProgressReporter, ...]: + """ + Splits the progress bar into few independent parts. + In case of 0 must return an empty tuple. + + This class is supposed to manage the state of children progress bars + and release of their resources, if necessary. + """ + raise NotImplementedError + + +class NullProgressReporter(ProgressReporter): + @property + def period(self) -> float: + return 0 + + def start(self, total: int, *, desc: Optional[str] = None): + pass + + def report_status(self, progress: int): + pass + + def advance(self, delta: int): + pass + + def iter( + self, + iterable: Iterable[T], + *, + total: Optional[int] = None, + desc: Optional[str] = None, + ) -> Iterable[T]: + yield from iterable + + def split(self, count: int) -> Tuple[ProgressReporter]: + return (self,) * count diff --git a/cvat-sdk/cvat_sdk/core/proxies/__init__.py b/cvat-sdk/cvat_sdk/core/proxies/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cvat-sdk/cvat_sdk/core/proxies/annotations.py b/cvat-sdk/cvat_sdk/core/proxies/annotations.py new file mode 100644 index 000000000000..96c50b692d0a --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/proxies/annotations.py @@ -0,0 +1,66 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from abc import ABC +from enum import Enum +from typing import Optional, Sequence + +from cvat_sdk import models +from cvat_sdk.core.proxies.model_proxy import _EntityT + + +class AnnotationUpdateAction(Enum): + CREATE = "create" + UPDATE = "update" + DELETE = "delete" + + +class AnnotationCrudMixin(ABC): + # TODO: refactor + + @property + def _put_annotations_data_param(self) -> str: + ... + + def get_annotations(self: _EntityT) -> models.ILabeledData: + (annotations, _) = self.api.retrieve_annotations(getattr(self, self._model_id_field)) + return annotations + + def set_annotations(self: _EntityT, data: models.ILabeledDataRequest): + self.api.update_annotations( + getattr(self, self._model_id_field), **{self._put_annotations_data_param: data} + ) + + def update_annotations( + self: _EntityT, + data: models.IPatchedLabeledDataRequest, + *, + action: AnnotationUpdateAction = AnnotationUpdateAction.UPDATE, + ): + self.api.partial_update_annotations( + action=action.value, + id=getattr(self, self._model_id_field), + patched_labeled_data_request=data, + ) + + def remove_annotations(self: _EntityT, *, ids: Optional[Sequence[int]] = None): + if ids: + anns = self.get_annotations() + + if not isinstance(ids, set): + ids = set(ids) + + anns_to_remove = models.PatchedLabeledDataRequest( + tags=[models.LabeledImageRequest(**a.to_dict()) for a in anns.tags if a.id in ids], + tracks=[ + models.LabeledTrackRequest(**a.to_dict()) for a in anns.tracks if a.id in ids + ], + shapes=[ + models.LabeledShapeRequest(**a.to_dict()) for a in anns.shapes if a.id in ids + ], + ) + + self.update_annotations(anns_to_remove, action=AnnotationUpdateAction.DELETE) + else: + self.api.destroy_annotations(getattr(self, self._model_id_field)) diff --git a/cvat-sdk/cvat_sdk/core/proxies/issues.py b/cvat-sdk/cvat_sdk/core/proxies/issues.py new file mode 100644 index 000000000000..5583fd083c13 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/proxies/issues.py @@ -0,0 +1,60 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from cvat_sdk.api_client import apis, models +from cvat_sdk.core.proxies.model_proxy import ( + ModelCreateMixin, + ModelDeleteMixin, + ModelListMixin, + ModelRetrieveMixin, + ModelUpdateMixin, + build_model_bases, +) + +_CommentEntityBase, _CommentRepoBase = build_model_bases( + models.CommentRead, apis.CommentsApi, api_member_name="comments_api" +) + + +class Comment( + models.ICommentRead, + _CommentEntityBase, + ModelUpdateMixin[models.IPatchedCommentWriteRequest], + ModelDeleteMixin, +): + _model_partial_update_arg = "patched_comment_write_request" + + +class CommentsRepo( + _CommentRepoBase, + ModelListMixin[Comment], + ModelCreateMixin[Comment, models.ICommentWriteRequest], + ModelRetrieveMixin[Comment], +): + _entity_type = Comment + + +_IssueEntityBase, _IssueRepoBase = build_model_bases( + models.IssueRead, apis.IssuesApi, api_member_name="issues_api" +) + + +class Issue( + models.IIssueRead, + _IssueEntityBase, + ModelUpdateMixin[models.IPatchedIssueWriteRequest], + ModelDeleteMixin, +): + _model_partial_update_arg = "patched_issue_write_request" + + +class IssuesRepo( + _IssueRepoBase, + ModelListMixin[Issue], + ModelCreateMixin[Issue, models.IIssueWriteRequest], + ModelRetrieveMixin[Issue], +): + _entity_type = Issue diff --git a/cvat-sdk/cvat_sdk/core/proxies/jobs.py b/cvat-sdk/cvat_sdk/core/proxies/jobs.py new file mode 100644 index 000000000000..e8259c427777 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/proxies/jobs.py @@ -0,0 +1,175 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import io +import mimetypes +from pathlib import Path +from typing import TYPE_CHECKING, List, Optional, Sequence + +from PIL import Image + +from cvat_sdk.api_client import apis, models +from cvat_sdk.core.downloading import Downloader +from cvat_sdk.core.helpers import get_paginated_collection +from cvat_sdk.core.progress import ProgressReporter +from cvat_sdk.core.proxies.annotations import AnnotationCrudMixin +from cvat_sdk.core.proxies.issues import Issue +from cvat_sdk.core.proxies.model_proxy import ( + ModelListMixin, + ModelRetrieveMixin, + ModelUpdateMixin, + build_model_bases, +) +from cvat_sdk.core.uploading import AnnotationUploader + +if TYPE_CHECKING: + from _typeshed import StrPath + +_JobEntityBase, _JobRepoBase = build_model_bases( + models.JobRead, apis.JobsApi, api_member_name="jobs_api" +) + + +class Job( + models.IJobRead, + _JobEntityBase, + ModelUpdateMixin[models.IPatchedJobWriteRequest], + AnnotationCrudMixin, +): + _model_partial_update_arg = "patched_job_write_request" + _put_annotations_data_param = "job_annotations_update_request" + + def import_annotations( + self, + format_name: str, + filename: StrPath, + *, + status_check_period: Optional[int] = None, + pbar: Optional[ProgressReporter] = None, + ): + """ + Upload annotations for a job in the specified format (e.g. 'YOLO ZIP 1.0'). + """ + + filename = Path(filename) + + AnnotationUploader(self._client).upload_file_and_wait( + self.api.create_annotations_endpoint, + filename, + format_name, + url_params={"id": self.id}, + pbar=pbar, + status_check_period=status_check_period, + ) + + self._client.logger.info(f"Annotation file '{filename}' for job #{self.id} uploaded") + + def export_dataset( + self, + format_name: str, + filename: StrPath, + *, + pbar: Optional[ProgressReporter] = None, + status_check_period: Optional[int] = None, + include_images: bool = True, + ) -> None: + """ + Download annotations for a job in the specified format (e.g. 'YOLO ZIP 1.0'). + """ + + filename = Path(filename) + + if include_images: + endpoint = self.api.retrieve_dataset_endpoint + else: + endpoint = self.api.retrieve_annotations_endpoint + + Downloader(self._client).prepare_and_download_file_from_endpoint( + endpoint=endpoint, + filename=filename, + url_params={"id": self.id}, + query_params={"format": format_name}, + pbar=pbar, + status_check_period=status_check_period, + ) + + self._client.logger.info(f"Dataset for job {self.id} has been downloaded to {filename}") + + def get_frame( + self, + frame_id: int, + *, + quality: Optional[str] = None, + ) -> io.RawIOBase: + (_, response) = self.api.retrieve_data( + self.id, number=frame_id, quality=quality, type="frame" + ) + return io.BytesIO(response.data) + + def get_preview( + self, + ) -> io.RawIOBase: + (_, response) = self.api.retrieve_data(self.id, type="preview") + return io.BytesIO(response.data) + + def download_frames( + self, + frame_ids: Sequence[int], + *, + outdir: StrPath = ".", + quality: str = "original", + filename_pattern: str = "frame_{frame_id:06d}{frame_ext}", + ) -> Optional[List[Image.Image]]: + """ + Download the requested frame numbers for a job and save images as outdir/filename_pattern + """ + # TODO: add arg descriptions in schema + + outdir = Path(outdir) + outdir.mkdir(parents=True, exist_ok=True) + + for frame_id in frame_ids: + frame_bytes = self.get_frame(frame_id, quality=quality) + + im = Image.open(frame_bytes) + mime_type = im.get_format_mimetype() or "image/jpg" + im_ext = mimetypes.guess_extension(mime_type) + + # FIXME It is better to use meta information from the server + # to determine the extension + # replace '.jpe' or '.jpeg' with a more used '.jpg' + if im_ext in (".jpe", ".jpeg", None): + im_ext = ".jpg" + + outfile = filename_pattern.format(frame_id=frame_id, frame_ext=im_ext) + im.save(outdir / outfile) + + def get_meta(self) -> models.IDataMetaRead: + (meta, _) = self.api.retrieve_data_meta(self.id) + return meta + + def get_frames_info(self) -> List[models.IFrameMeta]: + return self.get_meta().frames + + def remove_frames_by_ids(self, ids: Sequence[int]) -> None: + self._client.api_client.tasks_api.jobs_partial_update_data_meta( + self.id, + patched_data_meta_write_request=models.PatchedDataMetaWriteRequest(deleted_frames=ids), + ) + + def get_issues(self) -> List[Issue]: + return [Issue(self._client, m) for m in self.api.list_issues(id=self.id)[0]] + + def get_commits(self) -> List[models.IJobCommit]: + return get_paginated_collection(self.api.list_commits_endpoint, id=self.id) + + +class JobsRepo( + _JobRepoBase, + ModelListMixin[Job], + ModelRetrieveMixin[Job], +): + _entity_type = Job diff --git a/cvat-sdk/cvat_sdk/core/proxies/model_proxy.py b/cvat-sdk/cvat_sdk/core/proxies/model_proxy.py new file mode 100644 index 000000000000..9f71fdd93658 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/proxies/model_proxy.py @@ -0,0 +1,215 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import json +from abc import ABC +from copy import deepcopy +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + overload, +) + +from typing_extensions import Literal, Self + +from cvat_sdk.api_client.model_utils import IModelData, ModelNormal, to_json +from cvat_sdk.core.helpers import get_paginated_collection + +if TYPE_CHECKING: + from cvat_sdk.core.client import Client + +IModel = TypeVar("IModel", bound=IModelData) +ModelType = TypeVar("ModelType", bound=ModelNormal) +ApiType = TypeVar("ApiType") + + +class ModelProxy(ABC, Generic[ModelType, ApiType]): + _client: Client + + @property + def _api_member_name(self) -> str: + ... + + def __init__(self, client: Client) -> None: + self.__dict__["_client"] = client + + @classmethod + def get_api(cls, client: Client) -> ApiType: + return getattr(client.api_client, cls._api_member_name) + + @property + def api(self) -> ApiType: + return self.get_api(self._client) + + +class Entity(ModelProxy[ModelType, ApiType]): + """ + Represents a single object. Implements related operations and provides read access + to data members. + """ + + _model: ModelType + + def __init__(self, client: Client, model: ModelType) -> None: + super().__init__(client) + self.__dict__["_model"] = model + + @property + def _model_id_field(self) -> str: + return "id" + + def __getattr__(self, __name: str) -> Any: + # NOTE: be aware of potential problems with throwing AttributeError from @property + # in derived classes! + # https://medium.com/@ceshine/python-debugging-pitfall-mixed-use-of-property-and-getattr-f89e0ede13f1 + return self._model[__name] + + def __str__(self) -> str: + return str(self._model) + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}: id={getattr(self, self._model_id_field)}>" + + +class Repo(ModelProxy[ModelType, ApiType]): + """ + Represents a collection of corresponding Entity objects. + Implements group and management operations for entities. + """ + + _entity_type: Type[Entity[ModelType, ApiType]] + + +### Utilities + + +def build_model_bases( + mt: Type[ModelType], at: Type[ApiType], *, api_member_name: Optional[str] = None +) -> Tuple[Type[Entity[ModelType, ApiType]], Type[Repo[ModelType, ApiType]]]: + """ + Helps to remove code duplication in declarations of derived classes + """ + + class _EntityBase(Entity[ModelType, ApiType]): + if api_member_name: + _api_member_name = api_member_name + + class _RepoBase(Repo[ModelType, ApiType]): + if api_member_name: + _api_member_name = api_member_name + + return _EntityBase, _RepoBase + + +### CRUD mixins + +_EntityT = TypeVar("_EntityT", bound=Entity) + +#### Repo mixins + + +class ModelCreateMixin(Generic[_EntityT, IModel]): + def create(self: Repo, spec: Union[Dict[str, Any], IModel]) -> _EntityT: + """ + Creates a new object on the server and returns the corresponding local object + """ + + (model, _) = self.api.create(spec) + return self._entity_type(self._client, model) + + +class ModelRetrieveMixin(Generic[_EntityT]): + def retrieve(self: Repo, obj_id: int) -> _EntityT: + """ + Retrieves an object from the server by ID + """ + + (model, _) = self.api.retrieve(id=obj_id) + return self._entity_type(self._client, model) + + +class ModelListMixin(Generic[_EntityT]): + @overload + def list(self: Repo, *, return_json: Literal[False] = False) -> List[_EntityT]: + ... + + @overload + def list(self: Repo, *, return_json: Literal[True] = False) -> List[Any]: + ... + + def list(self: Repo, *, return_json: bool = False) -> List[Union[_EntityT, Any]]: + """ + Retrieves all objects from the server and returns them in basic or JSON format. + """ + + results = get_paginated_collection(endpoint=self.api.list_endpoint, return_json=return_json) + + if return_json: + return json.dumps(results) + return [self._entity_type(self._client, model) for model in results] + + +#### Entity mixins + + +class ModelUpdateMixin(ABC, Generic[IModel]): + @property + def _model_partial_update_arg(self: Entity) -> str: + ... + + def _export_update_fields( + self: Entity, overrides: Optional[Union[Dict[str, Any], IModel]] = None + ) -> Dict[str, Any]: + # TODO: support field conversion and assignment updating + # fields = to_json(self._model) + + if isinstance(overrides, ModelNormal): + overrides = to_json(overrides) + fields = deepcopy(overrides) + + return fields + + def fetch(self: Entity) -> Self: + """ + Updates the current object from the server + """ + + # TODO: implement revision checking + (self._model, _) = self.api.retrieve(id=getattr(self, self._model_id_field)) + return self + + def update(self: Entity, values: Union[Dict[str, Any], IModel]) -> Self: + """ + Commits model changes to the server + + The local object is updated from the server after this operation. + """ + + # TODO: implement revision checking + self.api.partial_update( + id=getattr(self, self._model_id_field), + **{self._model_partial_update_arg: self._export_update_fields(values)}, + ) + + # TODO: use the response model, once input and output models are same + return self.fetch() + + +class ModelDeleteMixin: + def remove(self: Entity) -> None: + """ + Removes current object on the server + """ + + self.api.destroy(id=getattr(self, self._model_id_field)) diff --git a/cvat-sdk/cvat_sdk/core/proxies/projects.py b/cvat-sdk/cvat_sdk/core/proxies/projects.py new file mode 100644 index 000000000000..05646ee89d11 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/proxies/projects.py @@ -0,0 +1,209 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import json +from pathlib import Path +from typing import TYPE_CHECKING, List, Optional + +from cvat_sdk.api_client import apis, models +from cvat_sdk.core.downloading import Downloader +from cvat_sdk.core.progress import ProgressReporter +from cvat_sdk.core.proxies.model_proxy import ( + ModelCreateMixin, + ModelDeleteMixin, + ModelListMixin, + ModelRetrieveMixin, + ModelUpdateMixin, + build_model_bases, +) +from cvat_sdk.core.proxies.tasks import Task +from cvat_sdk.core.uploading import DatasetUploader, Uploader + +if TYPE_CHECKING: + from _typeshed import StrPath + +_ProjectEntityBase, _ProjectRepoBase = build_model_bases( + models.ProjectRead, apis.ProjectsApi, api_member_name="projects_api" +) + + +class Project( + _ProjectEntityBase, + models.IProjectRead, + ModelUpdateMixin[models.IPatchedProjectWriteRequest], + ModelDeleteMixin, +): + _model_partial_update_arg = "patched_project_write_request" + + def import_dataset( + self, + format_name: str, + filename: StrPath, + *, + status_check_period: Optional[int] = None, + pbar: Optional[ProgressReporter] = None, + ): + """ + Import dataset for a project in the specified format (e.g. 'YOLO ZIP 1.0'). + """ + + filename = Path(filename) + + DatasetUploader(self._client).upload_file_and_wait( + self.api.create_dataset_endpoint, + self.api.retrieve_dataset_endpoint, + filename, + format_name, + url_params={"id": self.id}, + pbar=pbar, + status_check_period=status_check_period, + ) + + self._client.logger.info(f"Annotation file '{filename}' for project #{self.id} uploaded") + + def export_dataset( + self, + format_name: str, + filename: StrPath, + *, + pbar: Optional[ProgressReporter] = None, + status_check_period: Optional[int] = None, + include_images: bool = True, + ) -> None: + """ + Download annotations for a project in the specified format (e.g. 'YOLO ZIP 1.0'). + """ + + filename = Path(filename) + + if include_images: + endpoint = self.api.retrieve_dataset_endpoint + else: + endpoint = self.api.retrieve_annotations_endpoint + + Downloader(self._client).prepare_and_download_file_from_endpoint( + endpoint=endpoint, + filename=filename, + url_params={"id": self.id}, + query_params={"format": format_name}, + pbar=pbar, + status_check_period=status_check_period, + ) + + self._client.logger.info(f"Dataset for project {self.id} has been downloaded to {filename}") + + def download_backup( + self, + filename: StrPath, + *, + status_check_period: int = None, + pbar: Optional[ProgressReporter] = None, + ) -> None: + """ + Download a project backup + """ + + filename = Path(filename) + + Downloader(self._client).prepare_and_download_file_from_endpoint( + self.api.retrieve_backup_endpoint, + filename=filename, + pbar=pbar, + status_check_period=status_check_period, + url_params={"id": self.id}, + ) + + self._client.logger.info(f"Backup for project {self.id} has been downloaded to {filename}") + + def get_annotations(self) -> models.ILabeledData: + (annotations, _) = self.api.retrieve_annotations(self.id) + return annotations + + def get_tasks(self) -> List[Task]: + return [Task(self._client, m) for m in self.api.list_tasks(id=self.id)[0].results] + + +class ProjectsRepo( + _ProjectRepoBase, + ModelCreateMixin[Project, models.IProjectWriteRequest], + ModelListMixin[Project], + ModelRetrieveMixin[Project], +): + _entity_type = Project + + def create_from_dataset( + self, + spec: models.IProjectWriteRequest, + *, + dataset_path: str = "", + dataset_format: str = "CVAT XML 1.1", + status_check_period: int = None, + pbar: Optional[ProgressReporter] = None, + ) -> Project: + """ + Create a new project with the given name and labels JSON and + add the files to it. + + Returns: id of the created project + """ + project = self.create(spec=spec) + self._client.logger.info("Created project ID: %s NAME: %s", project.id, project.name) + + if dataset_path: + project.import_dataset( + format_name=dataset_format, + filename=dataset_path, + pbar=pbar, + status_check_period=status_check_period, + ) + + project.fetch() + return project + + def create_from_backup( + self, + filename: StrPath, + *, + status_check_period: int = None, + pbar: Optional[ProgressReporter] = None, + ) -> Project: + """ + Import a project from a backup file + """ + + filename = Path(filename) + + if status_check_period is None: + status_check_period = self._client.config.status_check_period + + params = {"filename": filename.name} + url = self._client.api_map.make_endpoint_url(self.api.create_backup_endpoint.path) + + uploader = Uploader(self._client) + response = uploader.upload_file( + url, + filename, + meta=params, + query_params=params, + pbar=pbar, + logger=self._client.logger.debug, + ) + + rq_id = json.loads(response.data)["rq_id"] + response = self._client.wait_for_completion( + url, + success_status=201, + positive_statuses=[202], + post_params={"rq_id": rq_id}, + status_check_period=status_check_period, + ) + + project_id = json.loads(response.data)["id"] + self._client.logger.info( + f"Project has been imported successfully. Project ID: {project_id}" + ) + + return self.retrieve(project_id) diff --git a/cvat-sdk/cvat_sdk/core/proxies/tasks.py b/cvat-sdk/cvat_sdk/core/proxies/tasks.py new file mode 100644 index 000000000000..56d7bf5a54e6 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/proxies/tasks.py @@ -0,0 +1,423 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import io +import json +import mimetypes +import shutil +from enum import Enum +from pathlib import Path +from time import sleep +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence + +from PIL import Image + +from cvat_sdk.api_client import apis, exceptions, models +from cvat_sdk.core import git +from cvat_sdk.core.downloading import Downloader +from cvat_sdk.core.progress import ProgressReporter +from cvat_sdk.core.proxies.annotations import AnnotationCrudMixin +from cvat_sdk.core.proxies.jobs import Job +from cvat_sdk.core.proxies.model_proxy import ( + ModelCreateMixin, + ModelDeleteMixin, + ModelListMixin, + ModelRetrieveMixin, + ModelUpdateMixin, + build_model_bases, +) +from cvat_sdk.core.uploading import AnnotationUploader, DataUploader, Uploader +from cvat_sdk.core.utils import filter_dict + +if TYPE_CHECKING: + from _typeshed import StrPath, SupportsWrite + + +class ResourceType(Enum): + LOCAL = 0 + SHARE = 1 + REMOTE = 2 + + def __str__(self): + return self.name.lower() + + def __repr__(self): + return str(self) + + +_TaskEntityBase, _TaskRepoBase = build_model_bases( + models.TaskRead, apis.TasksApi, api_member_name="tasks_api" +) + + +class Task( + _TaskEntityBase, + models.ITaskRead, + ModelUpdateMixin[models.IPatchedTaskWriteRequest], + ModelDeleteMixin, + AnnotationCrudMixin, +): + _model_partial_update_arg = "patched_task_write_request" + _put_annotations_data_param = "task_annotations_update_request" + + def upload_data( + self, + resource_type: ResourceType, + resources: Sequence[StrPath], + *, + pbar: Optional[ProgressReporter] = None, + params: Optional[Dict[str, Any]] = None, + ) -> None: + """ + Add local, remote, or shared files to an existing task. + """ + params = params or {} + + data = {"image_quality": 70} + + data.update( + filter_dict( + params, + keep=[ + "chunk_size", + "copy_data", + "image_quality", + "sorting_method", + "start_frame", + "stop_frame", + "use_cache", + "use_zip_chunks", + ], + ) + ) + if params.get("frame_step") is not None: + data["frame_filter"] = f"step={params.get('frame_step')}" + + if resource_type in [ResourceType.REMOTE, ResourceType.SHARE]: + for resource in resources: + if not isinstance(resource, str): + raise TypeError(f"resources: expected instances of str, got {type(resource)}") + + if resource_type is ResourceType.REMOTE: + data["remote_files"] = resources + elif resource_type is ResourceType.SHARE: + data["server_files"] = resources + + self.api.create_data( + self.id, + data_request=models.DataRequest(**data), + ) + elif resource_type == ResourceType.LOCAL: + url = self._client.api_map.make_endpoint_url( + self.api.create_data_endpoint.path, kwsub={"id": self.id} + ) + + DataUploader(self._client).upload_files( + url, list(map(Path, resources)), pbar=pbar, **data + ) + + def import_annotations( + self, + format_name: str, + filename: StrPath, + *, + status_check_period: Optional[int] = None, + pbar: Optional[ProgressReporter] = None, + ): + """ + Upload annotations for a task in the specified format (e.g. 'YOLO ZIP 1.0'). + """ + + filename = Path(filename) + + AnnotationUploader(self._client).upload_file_and_wait( + self.api.create_annotations_endpoint, + filename, + format_name, + url_params={"id": self.id}, + pbar=pbar, + status_check_period=status_check_period, + ) + + self._client.logger.info(f"Annotation file '{filename}' for task #{self.id} uploaded") + + def get_frame( + self, + frame_id: int, + *, + quality: Optional[str] = None, + ) -> io.RawIOBase: + params = {} + if quality: + params["quality"] = quality + (_, response) = self.api.retrieve_data(self.id, number=frame_id, **params, type="frame") + return io.BytesIO(response.data) + + def get_preview( + self, + ) -> io.RawIOBase: + (_, response) = self.api.retrieve_data(self.id, type="preview") + return io.BytesIO(response.data) + + def download_chunk( + self, + chunk_id: int, + output_file: SupportsWrite[bytes], + *, + quality: Optional[str] = None, + ) -> None: + params = {} + if quality: + params["quality"] = quality + (_, response) = self.api.retrieve_data( + self.id, number=chunk_id, **params, type="chunk", _parse_response=False + ) + + with response: + shutil.copyfileobj(response, output_file) + + def download_frames( + self, + frame_ids: Sequence[int], + *, + outdir: StrPath = ".", + quality: str = "original", + filename_pattern: str = "frame_{frame_id:06d}{frame_ext}", + ) -> Optional[List[Image.Image]]: + """ + Download the requested frame numbers for a task and save images as outdir/filename_pattern + """ + # TODO: add arg descriptions in schema + + outdir = Path(outdir) + outdir.mkdir(exist_ok=True) + + for frame_id in frame_ids: + frame_bytes = self.get_frame(frame_id, quality=quality) + + im = Image.open(frame_bytes) + mime_type = im.get_format_mimetype() or "image/jpg" + im_ext = mimetypes.guess_extension(mime_type) + + # FIXME It is better to use meta information from the server + # to determine the extension + # replace '.jpe' or '.jpeg' with a more used '.jpg' + if im_ext in (".jpe", ".jpeg", None): + im_ext = ".jpg" + + outfile = filename_pattern.format(frame_id=frame_id, frame_ext=im_ext) + im.save(outdir / outfile) + + def export_dataset( + self, + format_name: str, + filename: StrPath, + *, + pbar: Optional[ProgressReporter] = None, + status_check_period: Optional[int] = None, + include_images: bool = True, + ) -> None: + """ + Download annotations for a task in the specified format (e.g. 'YOLO ZIP 1.0'). + """ + + filename = Path(filename) + + if include_images: + endpoint = self.api.retrieve_dataset_endpoint + else: + endpoint = self.api.retrieve_annotations_endpoint + + Downloader(self._client).prepare_and_download_file_from_endpoint( + endpoint=endpoint, + filename=filename, + url_params={"id": self.id}, + query_params={"format": format_name}, + pbar=pbar, + status_check_period=status_check_period, + ) + + self._client.logger.info(f"Dataset for task {self.id} has been downloaded to {filename}") + + def download_backup( + self, + filename: StrPath, + *, + status_check_period: int = None, + pbar: Optional[ProgressReporter] = None, + ) -> None: + """ + Download a task backup + """ + + filename = Path(filename) + + Downloader(self._client).prepare_and_download_file_from_endpoint( + self.api.retrieve_backup_endpoint, + filename=filename, + pbar=pbar, + status_check_period=status_check_period, + url_params={"id": self.id}, + ) + + self._client.logger.info(f"Backup for task {self.id} has been downloaded to {filename}") + + def get_jobs(self) -> List[Job]: + return [Job(self._client, m) for m in self.api.list_jobs(id=self.id)[0]] + + def get_meta(self) -> models.IDataMetaRead: + (meta, _) = self.api.retrieve_data_meta(self.id) + return meta + + def get_frames_info(self) -> List[models.IFrameMeta]: + return self.get_meta().frames + + def remove_frames_by_ids(self, ids: Sequence[int]) -> None: + self.api.partial_update_data_meta( + self.id, + patched_data_meta_write_request=models.PatchedDataMetaWriteRequest(deleted_frames=ids), + ) + + +class TasksRepo( + _TaskRepoBase, + ModelCreateMixin[Task, models.ITaskWriteRequest], + ModelRetrieveMixin[Task], + ModelListMixin[Task], + ModelDeleteMixin, +): + _entity_type = Task + + def create_from_data( + self, + spec: models.ITaskWriteRequest, + resource_type: ResourceType, + resources: Sequence[str], + *, + data_params: Optional[Dict[str, Any]] = None, + annotation_path: str = "", + annotation_format: str = "CVAT XML 1.1", + status_check_period: int = None, + dataset_repository_url: str = "", + use_lfs: bool = False, + pbar: Optional[ProgressReporter] = None, + ) -> Task: + """ + Create a new task with the given name and labels JSON and + add the files to it. + + Returns: id of the created task + """ + if status_check_period is None: + status_check_period = self._client.config.status_check_period + + if getattr(spec, "project_id", None) and getattr(spec, "labels", None): + raise exceptions.ApiValueError( + "Can't set labels to a task inside a project. " + "Tasks inside a project use project's labels.", + ["labels"], + ) + + task = self.create(spec=spec) + self._client.logger.info("Created task ID: %s NAME: %s", task.id, task.name) + + task.upload_data(resource_type, resources, pbar=pbar, params=data_params) + + self._client.logger.info("Awaiting for task %s creation...", task.id) + status: models.RqStatus = None + while status != models.RqStatusStateEnum.allowed_values[("value",)]["FINISHED"]: + sleep(status_check_period) + (status, response) = self.api.retrieve_status(task.id) + + self._client.logger.info( + "Task %s creation status=%s, message=%s", + task.id, + status.state.value, + status.message, + ) + + if status.state.value == models.RqStatusStateEnum.allowed_values[("value",)]["FAILED"]: + raise exceptions.ApiException( + status=status.state.value, reason=status.message, http_resp=response + ) + + status = status.state.value + + if annotation_path: + task.import_annotations(annotation_format, annotation_path, pbar=pbar) + + if dataset_repository_url: + git.create_git_repo( + self._client, + task_id=task.id, + repo_url=dataset_repository_url, + status_check_period=status_check_period, + use_lfs=use_lfs, + ) + + task.fetch() + + return task + + def remove_by_ids(self, task_ids: Sequence[int]) -> None: + """ + Delete a list of tasks, ignoring those which don't exist. + """ + + for task_id in task_ids: + (_, response) = self.api.destroy(task_id, _check_status=False) + + if 200 <= response.status <= 299: + self._client.logger.info(f"Task ID {task_id} deleted") + elif response.status == 404: + self._client.logger.info(f"Task ID {task_id} not found") + else: + self._client.logger.warning( + f"Failed to delete task ID {task_id}: " + f"{response.msg} (status {response.status})" + ) + + def create_from_backup( + self, + filename: StrPath, + *, + status_check_period: int = None, + pbar: Optional[ProgressReporter] = None, + ) -> Task: + """ + Import a task from a backup file + """ + + filename = Path(filename) + + if status_check_period is None: + status_check_period = self._client.config.status_check_period + + params = {"filename": filename.name} + url = self._client.api_map.make_endpoint_url(self.api.create_backup_endpoint.path) + uploader = Uploader(self._client) + response = uploader.upload_file( + url, + filename, + meta=params, + query_params=params, + pbar=pbar, + logger=self._client.logger.debug, + ) + + rq_id = json.loads(response.data)["rq_id"] + response = self._client.wait_for_completion( + url, + success_status=201, + positive_statuses=[202], + post_params={"rq_id": rq_id}, + status_check_period=status_check_period, + ) + + task_id = json.loads(response.data)["id"] + self._client.logger.info(f"Task has been imported sucessfully. Task ID: {task_id}") + + return self.retrieve(task_id) diff --git a/cvat-sdk/cvat_sdk/core/proxies/users.py b/cvat-sdk/cvat_sdk/core/proxies/users.py new file mode 100644 index 000000000000..bc3a69bc02cc --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/proxies/users.py @@ -0,0 +1,35 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from cvat_sdk.api_client import apis, models +from cvat_sdk.core.proxies.model_proxy import ( + ModelDeleteMixin, + ModelListMixin, + ModelRetrieveMixin, + ModelUpdateMixin, + build_model_bases, +) + +_UserEntityBase, _UserRepoBase = build_model_bases( + models.User, apis.UsersApi, api_member_name="users_api" +) + + +class User( + models.IUser, _UserEntityBase, ModelUpdateMixin[models.IPatchedUserRequest], ModelDeleteMixin +): + _model_partial_update_arg = "patched_user_request" + + +class UsersRepo( + _UserRepoBase, + ModelListMixin[User], + ModelRetrieveMixin[User], +): + _entity_type = User + + def retrieve_current_user(self) -> User: + return User(self._client, self.api.retrieve_self()[0]) diff --git a/cvat-sdk/cvat_sdk/core/schema.py b/cvat-sdk/cvat_sdk/core/schema.py new file mode 100644 index 000000000000..fb6ebf1b87e8 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/schema.py @@ -0,0 +1,28 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + + +from typing import Optional + +import requests + + +def detect_schema(url: str) -> Optional[str]: + """ + Attempts to detect URL schema (http or https) if none provided in the URL. + """ + + if url.startswith("http://") or url.startswith("https://"): + return url + + for schema in ["https://", "http://"]: + try: + v = schema + url + response = requests.request("GET", v) + response.raise_for_status() + return v + except requests.HTTPError: + pass + + return None diff --git a/cvat-sdk/cvat_sdk/core/uploading.py b/cvat-sdk/cvat_sdk/core/uploading.py new file mode 100644 index 000000000000..c6f592f79fba --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/uploading.py @@ -0,0 +1,403 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import os +from contextlib import ExitStack, closing +from pathlib import Path +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple + +import requests +import urllib3 + +from cvat_sdk.api_client.api_client import ApiClient, Endpoint +from cvat_sdk.api_client.exceptions import ApiException +from cvat_sdk.api_client.rest import RESTClientObject +from cvat_sdk.core.helpers import StreamWithProgress, expect_status +from cvat_sdk.core.progress import NullProgressReporter, ProgressReporter + +if TYPE_CHECKING: + from cvat_sdk.core.client import Client + +import tusclient.uploader as tus_uploader +from tusclient.client import TusClient as _TusClient +from tusclient.client import Uploader as _TusUploader +from tusclient.request import TusRequest as _TusRequest +from tusclient.request import TusUploadFailed as _TusUploadFailed + +MAX_REQUEST_SIZE = 100 * 2**20 + + +class _RestClientAdapter: + # Provides requests.Session-like interface for REST client + # only patch is called in the tus client + + def __init__(self, rest_client: RESTClientObject): + self.rest_client = rest_client + + def _request(self, method, url, data=None, json=None, **kwargs): + raw = self.rest_client.request( + method=method, + url=url, + headers=kwargs.get("headers"), + query_params=kwargs.get("params"), + post_params=json, + body=data, + _parse_response=False, + _request_timeout=kwargs.get("timeout"), + _check_status=False, + ) + + result = requests.Response() + result._content = raw.data + result.raw = raw + result.headers.update(raw.headers) + result.status_code = raw.status + result.reason = raw.msg + return result + + def patch(self, *args, **kwargs): + return self._request("PATCH", *args, **kwargs) + + +class _MyTusUploader(_TusUploader): + # Adjusts the library code for CVAT server + # Allows to reuse session + + def __init__(self, *_args, api_client: ApiClient, **_kwargs): + self._api_client = api_client + super().__init__(*_args, **_kwargs) + + def _do_request(self): + self.request = _TusRequest(self) + self.request.handle = _RestClientAdapter(self._api_client.rest_client) + try: + self.request.perform() + self.verify_upload() + except _TusUploadFailed as error: + self._retry_or_cry(error) + + @tus_uploader._catch_requests_error + def create_url(self): + """ + Return upload url. + + Makes request to tus server to create a new upload url for the required file upload. + """ + headers = self.headers + headers["upload-length"] = str(self.file_size) + headers["upload-metadata"] = ",".join(self.encode_metadata()) + resp = self._api_client.rest_client.POST(self.client.url, headers=headers) + url = resp.headers.get("location") + if url is None: + msg = "Attempt to retrieve create file url with status {}".format(resp.status_code) + raise tus_uploader.TusCommunicationError(msg, resp.status_code, resp.content) + return tus_uploader.urljoin(self.client.url, url) + + @tus_uploader._catch_requests_error + def get_offset(self): + """ + Return offset from tus server. + + This is different from the instance attribute 'offset' because this makes an + http request to the tus server to retrieve the offset. + """ + try: + resp = self._api_client.rest_client.HEAD(self.url, headers=self.headers) + except ApiException as ex: + if ex.status == 405: # Method Not Allowed + # In CVAT up to version 2.2.0, HEAD requests were internally + # converted to GET by mod_wsgi, and subsequently rejected by the server. + # For compatibility with old servers, we'll handle such rejections by + # restarting the upload from the beginning. + return 0 + + raise tus_uploader.TusCommunicationError( + f"Attempt to retrieve offset failed with status {ex.status}", + ex.status, + ex.body, + ) from ex + + offset = resp.headers.get("upload-offset") + if offset is None: + raise tus_uploader.TusCommunicationError( + f"Attempt to retrieve offset failed with status {resp.status}", + resp.status, + resp.data, + ) + + return int(offset) + + +class Uploader: + """ + Implements common uploading protocols + """ + + _CHUNK_SIZE = 10 * 2**20 + + def __init__(self, client: Client): + self._client = client + + def upload_file( + self, + url: str, + filename: Path, + *, + meta: Dict[str, Any], + query_params: Dict[str, Any] = None, + fields: Optional[Dict[str, Any]] = None, + pbar: Optional[ProgressReporter] = None, + logger=None, + ) -> urllib3.HTTPResponse: + """ + Annotation uploads: + - have "filename" meta field in chunks + - have "filename" and "format" query params in the "Upload-Finished" request + + + Data (image, video, ...) uploads: + - have "filename" meta field in chunks + - have a number of fields in the "Upload-Finished" request + + + Backup uploads: + - have "filename" meta field in chunks + - have "filename" query params in the "Upload-Finished" request + + OR + - have "task_file" field in the POST request data (a file) + + meta['filename'] is always required. It must be set to the "visible" file name or path + + Returns: + response of the last request (the "Upload-Finished" one) + """ + # "CVAT-TUS" protocol has 2 extra messages + # query params are used only in the extra messages + assert meta["filename"] + + self._tus_start_upload(url, query_params=query_params) + self._upload_file_data_with_tus( + url=url, filename=filename, meta=meta, pbar=pbar, logger=logger + ) + return self._tus_finish_upload(url, query_params=query_params, fields=fields) + + def _wait_for_completion( + self, + url: str, + *, + success_status: int, + status_check_period: Optional[int] = None, + query_params: Optional[Dict[str, Any]] = None, + post_params: Optional[Dict[str, Any]] = None, + method: str = "POST", + positive_statuses: Optional[Sequence[int]] = None, + ) -> urllib3.HTTPResponse: + return self._client.wait_for_completion( + url, + success_status=success_status, + status_check_period=status_check_period, + query_params=query_params, + post_params=post_params, + method=method, + positive_statuses=positive_statuses, + ) + + def _split_files_by_requests( + self, filenames: List[Path] + ) -> Tuple[List[Tuple[List[Path], int]], List[Path], int]: + bulk_files: Dict[str, int] = {} + separate_files: Dict[str, int] = {} + + # sort by size + for filename in filenames: + filename = filename.resolve() + file_size = filename.stat().st_size + if MAX_REQUEST_SIZE < file_size: + separate_files[filename] = file_size + else: + bulk_files[filename] = file_size + + total_size = sum(bulk_files.values()) + sum(separate_files.values()) + + # group small files by requests + bulk_file_groups: List[Tuple[List[str], int]] = [] + current_group_size: int = 0 + current_group: List[str] = [] + for filename, file_size in bulk_files.items(): + if MAX_REQUEST_SIZE < current_group_size + file_size: + bulk_file_groups.append((current_group, current_group_size)) + current_group_size = 0 + current_group = [] + + current_group.append(filename) + current_group_size += file_size + if current_group: + bulk_file_groups.append((current_group, current_group_size)) + + return bulk_file_groups, separate_files, total_size + + @staticmethod + def _make_tus_uploader(api_client: ApiClient, url: str, **kwargs): + # Add headers required by CVAT server + headers = {} + headers["Origin"] = api_client.configuration.host + headers.update(api_client.get_common_headers()) + + client = _TusClient(url, headers=headers) + + return _MyTusUploader(client=client, api_client=api_client, **kwargs) + + def _upload_file_data_with_tus(self, url, filename, *, meta=None, pbar=None, logger=None): + file_size = filename.stat().st_size + if pbar is None: + pbar = NullProgressReporter() + + with open(filename, "rb") as input_file, StreamWithProgress( + input_file, pbar, length=file_size + ) as input_file_with_progress: + tus_uploader = self._make_tus_uploader( + self._client.api_client, + url=url.rstrip("/") + "/", + metadata=meta, + file_stream=input_file_with_progress, + chunk_size=Uploader._CHUNK_SIZE, + log_func=logger, + ) + tus_uploader.upload() + + def _tus_start_upload(self, url, *, query_params=None): + response = self._client.api_client.rest_client.POST( + url, + query_params=query_params, + headers={ + "Upload-Start": "", + **self._client.api_client.get_common_headers(), + }, + ) + expect_status(202, response) + return response + + def _tus_finish_upload(self, url, *, query_params=None, fields=None): + response = self._client.api_client.rest_client.POST( + url, + headers={ + "Upload-Finish": "", + **self._client.api_client.get_common_headers(), + }, + query_params=query_params, + post_params=fields, + ) + expect_status(202, response) + return response + + +class AnnotationUploader(Uploader): + def upload_file_and_wait( + self, + endpoint: Endpoint, + filename: Path, + format_name: str, + *, + url_params: Optional[Dict[str, Any]] = None, + pbar: Optional[ProgressReporter] = None, + status_check_period: Optional[int] = None, + ): + url = self._client.api_map.make_endpoint_url(endpoint.path, kwsub=url_params) + params = {"format": format_name, "filename": filename.name} + self.upload_file( + url, filename, pbar=pbar, query_params=params, meta={"filename": params["filename"]} + ) + + self._wait_for_completion( + url, + success_status=201, + positive_statuses=[202], + status_check_period=status_check_period, + query_params=params, + method="POST", + ) + + +class DatasetUploader(Uploader): + def upload_file_and_wait( + self, + upload_endpoint: Endpoint, + retrieve_endpoint: Endpoint, + filename: Path, + format_name: str, + *, + url_params: Optional[Dict[str, Any]] = None, + pbar: Optional[ProgressReporter] = None, + status_check_period: Optional[int] = None, + ): + url = self._client.api_map.make_endpoint_url(upload_endpoint.path, kwsub=url_params) + params = {"format": format_name, "filename": filename.name} + self.upload_file( + url, filename, pbar=pbar, query_params=params, meta={"filename": params["filename"]} + ) + + url = self._client.api_map.make_endpoint_url(retrieve_endpoint.path, kwsub=url_params) + params = {"action": "import_status"} + self._wait_for_completion( + url, + success_status=201, + positive_statuses=[202], + status_check_period=status_check_period, + query_params=params, + method="GET", + ) + + +class DataUploader(Uploader): + def upload_files( + self, + url: str, + resources: List[Path], + *, + pbar: Optional[ProgressReporter] = None, + **kwargs, + ): + bulk_file_groups, separate_files, total_size = self._split_files_by_requests(resources) + + if pbar is not None: + pbar.start(total_size, desc="Uploading data") + + self._tus_start_upload(url) + + for group, group_size in bulk_file_groups: + with ExitStack() as es: + files = {} + for i, filename in enumerate(group): + files[f"client_files[{i}]"] = ( + os.fspath(filename), + es.enter_context(closing(open(filename, "rb"))).read(), + ) + response = self._client.api_client.rest_client.POST( + url, + post_params=dict(**kwargs, **files), + headers={ + "Content-Type": "multipart/form-data", + "Upload-Multiple": "", + **self._client.api_client.get_common_headers(), + }, + ) + expect_status(200, response) + + if pbar is not None: + pbar.advance(group_size) + + for filename in separate_files: + # TODO: check if basename produces invalid paths here, can lead to overwriting + self._upload_file_data_with_tus( + url, + filename, + meta={"filename": filename.name}, + pbar=pbar, + logger=self._client.logger.debug, + ) + + self._tus_finish_upload(url, fields=kwargs) diff --git a/cvat-sdk/cvat_sdk/core/utils.py b/cvat-sdk/cvat_sdk/core/utils.py new file mode 100644 index 000000000000..e7c28e90e9f9 --- /dev/null +++ b/cvat-sdk/cvat_sdk/core/utils.py @@ -0,0 +1,82 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import contextlib +import itertools +import os +from typing import ( + IO, + Any, + BinaryIO, + ContextManager, + Dict, + Iterator, + Sequence, + TextIO, + Union, + overload, +) + +from typing_extensions import Literal + + +def filter_dict( + d: Dict[str, Any], *, keep: Sequence[str] = None, drop: Sequence[str] = None +) -> Dict[str, Any]: + return {k: v for k, v in d.items() if (not keep or k in keep) and (not drop or k not in drop)} + + +@overload +def atomic_writer(path: Union[os.PathLike, str], mode: Literal["wb"]) -> ContextManager[BinaryIO]: + ... + + +@overload +def atomic_writer( + path: Union[os.PathLike, str], mode: Literal["w"], encoding: str = "UTF-8" +) -> ContextManager[TextIO]: + ... + + +@contextlib.contextmanager +def atomic_writer( + path: Union[os.PathLike, str], mode: Literal["w", "wb"], encoding: str = "UTF-8" +) -> Iterator[IO]: + """ + Returns a context manager that, when entered, returns a handle to a temporary + file opened with the specified `mode` and `encoding`. If the context manager + is exited via an exception, the temporary file is deleted. If the context manager + is exited normally, the file is renamed to `path`. + + In other words, this function works like `open()`, but the file does not appear + at the specified path until and unless the context manager is exited + normally. + """ + + path_str = os.fspath(path) + + for counter in itertools.count(): + tmp_path = f"{path_str}.tmp{counter}" + + try: + if mode == "w": + tmp_file = open(tmp_path, "xt", encoding=encoding) + elif mode == "wb": + tmp_file = open(tmp_path, "xb") + else: + raise ValueError(f"Unsupported mode: {mode!r}") + + break + except FileExistsError: + pass # try next counter value + + try: + with tmp_file: + yield tmp_file + os.rename(tmp_path, path) + except: + os.unlink(tmp_path) + raise diff --git a/cvat-sdk/cvat_sdk/exceptions.py b/cvat-sdk/cvat_sdk/exceptions.py new file mode 100644 index 000000000000..e28c7d38ee58 --- /dev/null +++ b/cvat-sdk/cvat_sdk/exceptions.py @@ -0,0 +1,14 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +# pylint: disable=unused-import +from cvat_sdk.api_client.exceptions import ( + ApiAttributeError, + ApiException, + ApiKeyError, + ApiTypeError, + ApiValueError, + OpenApiException, +) +from cvat_sdk.core.exceptions import CvatSdkException diff --git a/cvat-sdk/cvat_sdk/models.py b/cvat-sdk/cvat_sdk/models.py new file mode 100644 index 000000000000..f768826a7d65 --- /dev/null +++ b/cvat-sdk/cvat_sdk/models.py @@ -0,0 +1,5 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from cvat_sdk.api_client.models import * # pylint: disable=unused-import,redefined-builtin diff --git a/cvat-sdk/cvat_sdk/pytorch/__init__.py b/cvat-sdk/cvat_sdk/pytorch/__init__.py new file mode 100644 index 000000000000..fa6b38a00623 --- /dev/null +++ b/cvat-sdk/cvat_sdk/pytorch/__init__.py @@ -0,0 +1,381 @@ +import base64 +import collections +import json +import os +import shutil +import types +import zipfile +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from typing import ( + Callable, + Dict, + FrozenSet, + List, + Mapping, + Optional, + Sequence, + Tuple, + Type, + TypeVar, +) + +import appdirs +import attrs +import attrs.validators +import PIL.Image +import torch +import torchvision.datasets +from typing_extensions import TypedDict + +import cvat_sdk.core +import cvat_sdk.core.exceptions +from cvat_sdk.api_client.model_utils import to_json +from cvat_sdk.core.utils import atomic_writer +from cvat_sdk.models import DataMetaRead, LabeledData, LabeledImage, LabeledShape, TaskRead + +_ModelType = TypeVar("_ModelType") + +_CACHE_DIR = Path(appdirs.user_cache_dir("cvat-sdk", "CVAT.ai")) +_NUM_DOWNLOAD_THREADS = 4 + + +class UnsupportedDatasetError(cvat_sdk.core.exceptions.CvatSdkException): + pass + + +@attrs.frozen +class FrameAnnotations: + """ + Contains annotations that pertain to a single frame. + """ + + tags: List[LabeledImage] = attrs.Factory(list) + shapes: List[LabeledShape] = attrs.Factory(list) + + +@attrs.frozen +class Target: + """ + Non-image data for a dataset sample. + """ + + annotations: FrameAnnotations + """Annotations for the frame corresponding to the sample.""" + + label_id_to_index: Mapping[int, int] + """ + A mapping from label_id values in `LabeledImage` and `LabeledShape` objects + to an integer index. This mapping is consistent across all samples for a given task. + """ + + +class TaskVisionDataset(torchvision.datasets.VisionDataset): + """ + Represents a task on a CVAT server as a PyTorch Dataset. + + This dataset contains one sample for each frame in the task, in the same + order as the frames are in the task. Deleted frames are omitted. + Before transforms are applied, each sample is a tuple of + (image, target), where: + + * image is a `PIL.Image.Image` object for the corresponding frame. + * target is a `Target` object containing annotations for the frame. + + This class caches all data and annotations for the task on the local file system + during construction. If the task is updated on the server, the cache is updated. + + Limitations: + + * Only tasks with image (not video) data are supported at the moment. + * Track annotations are currently not accessible. + """ + + def __init__( + self, + client: cvat_sdk.core.Client, + task_id: int, + *, + transforms: Optional[Callable] = None, + transform: Optional[Callable] = None, + target_transform: Optional[Callable] = None, + label_name_to_index: Mapping[str, int] = None, + ) -> None: + """ + Creates a dataset corresponding to the task with ID `task_id` on the + server that `client` is connected to. + + `transforms`, `transform` and `target_transforms` are optional transformation + functions; see the documentation for `torchvision.datasets.VisionDataset` for + more information. + + `label_name_to_index` affects the `label_id_to_index` member in `Target` objects + returned by the dataset. If it is specified, then it must contain an entry for + each label name in the task. The `label_id_to_index` mapping will be constructed + so that each label will be mapped to the index corresponding to the label's name + in `label_name_to_index`. + + If `label_name_to_index` is unspecified or set to `None`, then `label_id_to_index` + will map each label ID to a distinct integer in the range [0, `num_labels`), where + `num_labels` is the number of labels defined in the task. This mapping will be + generally unpredictable, but consistent for a given task. + """ + + self._logger = client.logger + + self._logger.info(f"Fetching task {task_id}...") + self._task = client.tasks.retrieve(task_id) + + if not self._task.size or not self._task.data_chunk_size: + raise UnsupportedDatasetError("The task has no data") + + if self._task.data_original_chunk_type != "imageset": + raise UnsupportedDatasetError( + f"{self.__class__.__name__} only supports tasks with image chunks;" + f" current chunk type is {self._task.data_original_chunk_type!r}" + ) + + # Base64-encode the name to avoid FS-unsafe characters (like slashes) + server_dir_name = ( + base64.urlsafe_b64encode(client.api_map.host.encode()).rstrip(b"=").decode() + ) + server_dir = _CACHE_DIR / f"servers/{server_dir_name}" + + self._task_dir = server_dir / f"tasks/{self._task.id}" + self._initialize_task_dir() + + super().__init__( + os.fspath(self._task_dir), + transforms=transforms, + transform=transform, + target_transform=target_transform, + ) + + data_meta = self._ensure_model( + "data_meta.json", DataMetaRead, self._task.get_meta, "data metadata" + ) + self._active_frame_indexes = sorted( + set(range(self._task.size)) - set(data_meta.deleted_frames) + ) + + self._logger.info("Downloading chunks...") + + self._chunk_dir = self._task_dir / "chunks" + self._chunk_dir.mkdir(exist_ok=True, parents=True) + + needed_chunks = { + index // self._task.data_chunk_size for index in self._active_frame_indexes + } + + with ThreadPoolExecutor(_NUM_DOWNLOAD_THREADS) as pool: + for _ in pool.map(self._ensure_chunk, sorted(needed_chunks)): + # just need to loop through all results so that any exceptions are propagated + pass + + self._logger.info("All chunks downloaded") + + if label_name_to_index is None: + self._label_id_to_index = types.MappingProxyType( + { + label.id: label_index + for label_index, label in enumerate( + sorted(self._task.labels, key=lambda l: l.id) + ) + } + ) + else: + self._label_id_to_index = types.MappingProxyType( + {label.id: label_name_to_index[label.name] for label in self._task.labels} + ) + + annotations = self._ensure_model( + "annotations.json", LabeledData, self._task.get_annotations, "annotations" + ) + + self._frame_annotations: Dict[int, FrameAnnotations] = collections.defaultdict( + FrameAnnotations + ) + + for tag in annotations.tags: + self._frame_annotations[tag.frame].tags.append(tag) + + for shape in annotations.shapes: + self._frame_annotations[shape.frame].shapes.append(shape) + + # TODO: tracks? + + def _initialize_task_dir(self) -> None: + task_json_path = self._task_dir / "task.json" + + try: + with open(task_json_path, "rb") as task_json_file: + saved_task = TaskRead._new_from_openapi_data(**json.load(task_json_file)) + except Exception: + self._logger.info("Task is not yet cached or the cache is corrupted") + + # If the cache was corrupted, the directory might already be there; clear it. + if self._task_dir.exists(): + shutil.rmtree(self._task_dir) + else: + if saved_task.updated_date < self._task.updated_date: + self._logger.info( + "Task has been updated on the server since it was cached; purging the cache" + ) + shutil.rmtree(self._task_dir) + + self._task_dir.mkdir(exist_ok=True, parents=True) + + with atomic_writer(task_json_path, "w", encoding="UTF-8") as task_json_file: + json.dump(to_json(self._task._model), task_json_file, indent=4) + print(file=task_json_file) # add final newline + + def _ensure_chunk(self, chunk_index: int) -> None: + chunk_path = self._chunk_dir / f"{chunk_index}.zip" + if chunk_path.exists(): + return # already downloaded previously + + self._logger.info(f"Downloading chunk #{chunk_index}...") + + with atomic_writer(chunk_path, "wb") as chunk_file: + self._task.download_chunk(chunk_index, chunk_file, quality="original") + + def _ensure_model( + self, + filename: str, + model_type: Type[_ModelType], + download: Callable[[], _ModelType], + model_description: str, + ) -> _ModelType: + path = self._task_dir / filename + + try: + with open(path, "rb") as f: + model = model_type._new_from_openapi_data(**json.load(f)) + self._logger.info(f"Loaded {model_description} from cache") + return model + except FileNotFoundError: + pass + except Exception: + self._logger.warning(f"Failed to load {model_description} from cache", exc_info=True) + + self._logger.info(f"Downloading {model_description}...") + model = download() + self._logger.info(f"Downloaded {model_description}") + + with atomic_writer(path, "w", encoding="UTF-8") as f: + json.dump(to_json(model), f, indent=4) + print(file=f) # add final newline + + return model + + def __getitem__(self, sample_index: int): + """ + Returns the sample with index `sample_index`. + + `sample_index` must satisfy the condition `0 <= sample_index < len(self)`. + """ + + frame_index = self._active_frame_indexes[sample_index] + chunk_index = frame_index // self._task.data_chunk_size + member_index = frame_index % self._task.data_chunk_size + + with zipfile.ZipFile(self._chunk_dir / f"{chunk_index}.zip", "r") as chunk_zip: + with chunk_zip.open(chunk_zip.infolist()[member_index]) as chunk_member: + sample_image = PIL.Image.open(chunk_member) + sample_image.load() + + sample_target = Target( + annotations=self._frame_annotations[frame_index], + label_id_to_index=self._label_id_to_index, + ) + + if self.transforms: + sample_image, sample_target = self.transforms(sample_image, sample_target) + return sample_image, sample_target + + def __len__(self) -> int: + """Returns the number of samples in the dataset.""" + return len(self._active_frame_indexes) + + +@attrs.frozen +class ExtractSingleLabelIndex: + """ + A target transform that takes a `Target` object and produces a single label index + based on the tag in that object, as a 0-dimensional tensor. + + This makes the dataset samples compatible with the image classification networks + in torchvision. + + If the annotations contain no tags, or multiple tags, raises a `ValueError`. + """ + + def __call__(self, target: Target) -> int: + tags = target.annotations.tags + if not tags: + raise ValueError("sample has no tags") + + if len(tags) > 1: + raise ValueError("sample has multiple tags") + + return torch.tensor(target.label_id_to_index[tags[0].label_id], dtype=torch.long) + + +class LabeledBoxes(TypedDict): + boxes: torch.Tensor + labels: torch.Tensor + + +_SUPPORTED_SHAPE_TYPES = frozenset(["rectangle", "polygon", "polyline", "points", "ellipse"]) + + +@attrs.frozen +class ExtractBoundingBoxes: + """ + A target transform that takes a `Target` object and returns a dictionary compatible + with the object detection networks in torchvision. + + The dictionary contains the following entries: + + "boxes": a tensor with shape [N, 4], where each row represents a bounding box of a shape + in the annotations in the (xmin, ymin, xmax, ymax) format. + "labels": a tensor with shape [N] containing corresponding label indices. + + Limitations: + + * Only the following shape types are supported: rectangle, polygon, polyline, + points, ellipse. + * Rotated shapes are not supported. + + Unsupported shapes will cause a `UnsupportedDatasetError` exception to be + raised unless they are filtered out by `include_shape_types`. + """ + + include_shape_types: FrozenSet[str] = attrs.field( + converter=frozenset, + validator=attrs.validators.deep_iterable(attrs.validators.in_(_SUPPORTED_SHAPE_TYPES)), + kw_only=True, + ) + """Shapes whose type is not in this set will be ignored.""" + + def __call__(self, target: Target) -> LabeledBoxes: + boxes = [] + labels = [] + + for shape in target.annotations.shapes: + if shape.type.value not in self.include_shape_types: + continue + + if shape.rotation != 0: + raise UnsupportedDatasetError("Rotated shapes are not supported") + + x_coords = shape.points[0::2] + y_coords = shape.points[1::2] + + boxes.append((min(x_coords), min(y_coords), max(x_coords), max(y_coords))) + labels.append(target.label_id_to_index[shape.label_id]) + + return LabeledBoxes( + boxes=torch.tensor(boxes, dtype=torch.float), + labels=torch.tensor(labels, dtype=torch.long), + ) diff --git a/cvat-sdk/gen/generate.sh b/cvat-sdk/gen/generate.sh new file mode 100755 index 000000000000..903f65e9a249 --- /dev/null +++ b/cvat-sdk/gen/generate.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +set -e + +GENERATOR_VERSION="v6.0.1" + +VERSION="2.1.0.post1" +LIB_NAME="cvat_sdk" +LAYER1_LIB_NAME="${LIB_NAME}/api_client" +DST_DIR="." +TEMPLATE_DIR="gen" +POST_PROCESS_SCRIPT="${TEMPLATE_DIR}/postprocess.py" + +mkdir -p "${DST_DIR}/" +rm -f -r "${DST_DIR}/docs" "${DST_DIR}/${LAYER1_LIB_NAME}" "requirements/" +cp "${TEMPLATE_DIR}/templates/openapi-generator/.openapi-generator-ignore" "${DST_DIR}/" + +# Pass template dir here +# https://github.com/OpenAPITools/openapi-generator/issues/8420 +docker run --rm -v "$PWD":"/local" -u "$(id -u)":"$(id -g)" \ + openapitools/openapi-generator-cli:${GENERATOR_VERSION} generate \ + -t "/local/${TEMPLATE_DIR}/templates/openapi-generator/" \ + -i "/local/schema/schema.yml" \ + --config "/local/${TEMPLATE_DIR}/generator-config.yml" \ + -g python \ + -o "/local/${DST_DIR}/" + +sed -e "s|{{packageVersion}}|${VERSION}|g" "${TEMPLATE_DIR}/templates/version.py.template" > "${DST_DIR}/${LIB_NAME}/version.py" +cp -r "${TEMPLATE_DIR}/templates/requirements" "${DST_DIR}/" +cp -r "${TEMPLATE_DIR}/templates/MANIFEST.in" "${DST_DIR}/" +mv "${DST_DIR}/requirements.txt" "${DST_DIR}/requirements/api_client.txt" + +# Do custom postprocessing for code files +"${POST_PROCESS_SCRIPT}" --schema "schema/schema.yml" --input-path "${DST_DIR}/${LIB_NAME}" + +# Do custom postprocessing for docs files +"${POST_PROCESS_SCRIPT}" --schema "schema/schema.yml" --input-path "${DST_DIR}/docs" --file-ext '.md' +"${POST_PROCESS_SCRIPT}" --schema "schema/schema.yml" --input-path "${DST_DIR}/README.md" + +API_DOCS_DIR="${DST_DIR}/docs/apis/" +MODEL_DOCS_DIR="${DST_DIR}/docs/models/" +mkdir "${API_DOCS_DIR}" +mkdir "${MODEL_DOCS_DIR}" +mv "${DST_DIR}/docs/"*Api.md "${API_DOCS_DIR}" +mv "${DST_DIR}/docs/"*.md "${MODEL_DOCS_DIR}" +mv "${DST_DIR}/README.md" "${DST_DIR}/docs/" + +cp "${TEMPLATE_DIR}/templates/README.md.template" "${DST_DIR}/README.md" diff --git a/cvat-sdk/gen/generator-config.yml b/cvat-sdk/gen/generator-config.yml new file mode 100644 index 000000000000..614ecb3cd56a --- /dev/null +++ b/cvat-sdk/gen/generator-config.yml @@ -0,0 +1,12 @@ +additionalProperties: + projectName: "cvat_sdk" + packageVersion: "2.0-alpha" + packageUrl: "https://github.com/cvat-ai/cvat" + packageName: "cvat_sdk.api_client" + initRequiredVars: true + generateSourceCodeOnly: false + generatorLanguageVersion: '>=3.7' +globalProperties: + generateAliasAsModel: true + apiTests: false + modelTests: false diff --git a/cvat-sdk/gen/postprocess.py b/cvat-sdk/gen/postprocess.py new file mode 100755 index 000000000000..376ad2f353a0 --- /dev/null +++ b/cvat-sdk/gen/postprocess.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python + +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +import argparse +import os.path as osp +import re +import sys +from glob import glob + +from inflection import underscore +from ruamel.yaml import YAML + + +def collect_operations(schema): + endpoints = schema.get("paths", {}) + + operations = {} + + for endpoint_name, endpoint_schema in endpoints.items(): + for method_name, method_schema in endpoint_schema.items(): + method_schema = dict(method_schema) + method_schema["method"] = method_name + method_schema["endpoint"] = endpoint_name + operations[method_schema["operationId"]] = method_schema + + return operations + + +class Replacer: + REPLACEMENT_TOKEN = r"%%%" + ARGS_TOKEN = r"!!!" + + def __init__(self, schema): + self._schema = schema + self._operations = collect_operations(self._schema) + + def make_operation_id(self, name: str) -> str: + operation = self._operations[name] + + new_name = name + + tokenized_path = operation["endpoint"].split("/") + assert 3 <= len(tokenized_path) + assert tokenized_path[0] == "" and tokenized_path[1] == "api" + tokenized_path = tokenized_path[2:] + + prefix = tokenized_path[0] + "_" + if new_name.startswith(prefix) and tokenized_path[0] in operation["tags"]: + new_name = new_name[len(prefix) :] + + return new_name + + def make_api_name(self, name: str) -> str: + return underscore(name) + + def make_type_annotation(self, type_repr: str) -> str: + type_repr = type_repr.replace("[", "typing.List[") + type_repr = type_repr.replace("(", "typing.Union[").replace(")", "]") + type_repr = type_repr.replace("{", "typing.Dict[").replace(":", ",").replace("}", "]") + + ANY_pattern = "bool, date, datetime, dict, float, int, list, str" + type_repr = type_repr.replace(ANY_pattern, "typing.Any") + + # single optional arg pattern + type_repr = re.sub(r"^(.+, none_type)$", r"typing.Union[\1]", type_repr) + + return type_repr + + allowed_actions = { + "make_operation_id", + "make_api_name", + "make_type_annotation", + } + + def _process_file(self, contents: str): + processor_pattern = re.compile( + f"{self.REPLACEMENT_TOKEN}(.*?){self.ARGS_TOKEN}(.*?){self.REPLACEMENT_TOKEN}" + ) + + matches = list(processor_pattern.finditer(contents)) + for match in reversed(matches): + action = match.group(1) + args = match.group(2).split(self.ARGS_TOKEN) + + if action not in self.allowed_actions: + raise Exception(f"Replacement action '{action}' is not allowed") + + replacement = getattr(self, action)(*args) + contents = contents[: match.start(0)] + replacement + contents[match.end(0) :] + + return contents + + def process_file(self, src_path: str): + with open(src_path, "r") as f: + contents = f.read() + + contents = self._process_file(contents) + + with open(src_path, "w") as f: + f.write(contents) + + def process_dir(self, dir_path: str, *, file_ext: str = ".py"): + for filename in glob(dir_path + f"/**/*{file_ext}", recursive=True): + try: + self.process_file(filename) + except Exception as e: + print(f"Failed to process file '{osp.basename(filename)}': {e}") + + +def parse_schema(path): + yaml = YAML(typ="safe") + with open(path, "r") as f: + return yaml.load(f) + + +def parse_args(args=None): + parser = argparse.ArgumentParser( + add_help=True, + formatter_class=argparse.RawTextHelpFormatter, + description="""\ +Processes generator output files in a custom way, saves results inplace. + +Replacement token: '%(repl_token)s'. +Arg separator token: '%(args_token)s'. +Replaces the following patterns in files: + '%(repl_token)sREPLACER%(args_token)sARG1%(args_token)sARG2...%(repl_token)s' + -> + REPLACER(ARG1, ARG2, ...) value + +Available REPLACERs: + %(replacers)s + """ + % { + "repl_token": Replacer.REPLACEMENT_TOKEN, + "args_token": Replacer.ARGS_TOKEN, + "replacers": "\n ".join(Replacer.allowed_actions), + }, + ) + parser.add_argument("--schema", required=True, help="Path to server schema yaml") + parser.add_argument("--input-path", required=True, help="Path to target file or directory") + parser.add_argument( + "--file-ext", + default=".py", + help="If working on a directory, look for " + "files with the specified extension (default: %(default)s)", + ) + + return parser.parse_args(args) + + +def main(args=None): + args = parse_args(args) + + schema = parse_schema(args.schema) + processor = Replacer(schema=schema) + + if osp.isdir(args.input_path): + processor.process_dir(args.input_path, file_ext=args.file_ext) + elif osp.isfile(args.input_path): + processor.process_file(args.input_path) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/cvat-sdk/gen/requirements.txt b/cvat-sdk/gen/requirements.txt new file mode 100644 index 000000000000..f20a92b8e6b4 --- /dev/null +++ b/cvat-sdk/gen/requirements.txt @@ -0,0 +1,6 @@ +# can't have a dependency on base.txt, because it depends on the generated file + +black>=22.1.0 +inflection >= 0.5.1 +isort>=5.10.1 +ruamel.yaml>=0.17.21 diff --git a/cvat-sdk/gen/templates/MANIFEST.in b/cvat-sdk/gen/templates/MANIFEST.in new file mode 100644 index 000000000000..0174a262bedb --- /dev/null +++ b/cvat-sdk/gen/templates/MANIFEST.in @@ -0,0 +1,3 @@ +include README.md +include requirements/api_client.txt +include requirements/base.txt \ No newline at end of file diff --git a/cvat-sdk/gen/templates/README.md.template b/cvat-sdk/gen/templates/README.md.template new file mode 100644 index 000000000000..a2d85c2371d8 --- /dev/null +++ b/cvat-sdk/gen/templates/README.md.template @@ -0,0 +1,26 @@ +# SDK for [Computer Vision Annotation Tool (CVAT)](https://github.com/cvat-ai/cvat) + +This package provides a Python client library for CVAT server. It can be useful for +workflow automation and writing custom CVAT server clients. + +The SDK API includes 2 layers: +- Server API wrappers (`ApiClient`). Located in at `cvat_sdk.api_client` +- High-level tools (`Core`). Located at `cvat_sdk.core` + +Package documentation is available [here](https://opencv.github.io/cvat/docs/api_sdk/sdk). + +## Installation & Usage + +To install a prebuilt package, run the following command in the terminal: + +```bash +pip install cvat-sdk +``` + +To install from the local directory, follow [the developer guide](https://opencv.github.io/cvat/docs/api_sdk/sdk/developer_guide). + +After installation you can import the package: + +```python +import cvat_sdk +``` diff --git a/cvat-sdk/gen/templates/_debug_model_template.yml b/cvat-sdk/gen/templates/_debug_model_template.yml new file mode 100644 index 000000000000..602a51371e4b --- /dev/null +++ b/cvat-sdk/gen/templates/_debug_model_template.yml @@ -0,0 +1,92 @@ + openApiType={{{openApiType}}} + baseName={{{baseName}}} + complexType={{{complexType}}} + getter={{{getter}}} + setter={{{setter}}} + description={{{description}}} + dataType={{{dataType}}} + datatypeWithEnum={{{datatypeWithEnum}}} + dataFormat={{{dataFormat}}} + name={{{name}}} + min={{{min}}} + max={{{max}}} + defaultValue={{{defaultValue}}} + defaultValueWithParam={{{defaultValueWithParam}}} + baseType={{{baseType}}} + containerType={{{containerType}}} + title={{{title}}} + unescapedDescription={{{unescapedDescription}}} + maxLength={{{maxLength}}} + minLength={{{minLength}}} + pattern={{{pattern}}} + minimum={{{minimum}}} + maximum={{{maximum}}} + exclusiveMinimum={{{exclusiveMinimum}}} + exclusiveMaximum={{{exclusiveMaximum}}} + required={{{required}}} + deprecated={{{deprecated}}} + hasMoreNonReadOnly={{{hasMoreNonReadOnly}}} + isPrimitiveType={{{isPrimitiveType}}} + isModel={{{isModel}}} + isContainer={{{isContainer}}} + isString={{{isString}}} + isNumeric={{{isNumeric}}} + isInteger={{{isInteger}}} + isShort={{{isShort}}} + isLong={{{isLong}}} + isUnboundedInteger={{{isUnboundedInteger}}} + isNumber={{{isNumber}}} + isFloat={{{isFloat}}} + isDouble={{{isDouble}}} + isDecimal={{{isDecimal}}} + isByteArray={{{isByteArray}}} + isBinary={{{isBinary}}} + isFile={{{isFile}}} + isBoolean={{{isBoolean}}} + isDate={{{isDate}}} + isDateTime={{{isDateTime}}} + isUuid={{{isUuid}}} + isUri={{{isUri}}} + isEmail={{{isEmail}}} + isFreeFormObject={{{isFreeFormObject}}} + isArray={{{isArray}}} + isMap={{{isMap}}} + isEnum={{{isEnum}}} + isAnyType={{{isAnyType}}} + isReadOnly={{{isReadOnly}}} + isWriteOnly={{{isWriteOnly}}} + isNullable={{{isNullable}}} + isSelfReference={{{isSelfReference}}} + isCircularReference={{{isCircularReference}}} + isDiscriminator={{{isDiscriminator}}} + _enum={{{_enum}}} + allowableValues={{{allowableValues}}} + items={{{items}}} + additionalProperties={{{additionalProperties}}} + vars={{{vars}}} + requiredVars={{{requiredVars}}} + mostInnerItems={{{mostInnerItems}}} + vendorExtensions={{{vendorExtensions}}} + hasValidation={{{hasValidation}}} + isInherited={{{isInherited}}} + discriminatorValue={{{discriminatorValue}}} + nameInCamelCase={{{nameInCamelCase}}} + nameInSnakeCase={{{nameInSnakeCase}}} + enumName={{{enumName}}} + maxItems={{{maxItems}}} + minItems={{{minItems}}} + maxProperties={{{maxProperties}}} + minProperties={{{minProperties}}} + uniqueItems={{{uniqueItems}}} + multipleOf={{{multipleOf}}} + isXmlAttribute={{{isXmlAttribute}}} + xmlPrefix={{{xmlPrefix}}} + xmlName={{{xmlName}}} + xmlNamespace={{{xmlNamespace}}} + isXmlWrapped={{{isXmlWrapped}}} + isNull={{{isNull}}} + getAdditionalPropertiesIsAnyType={{{getAdditionalPropertiesIsAnyType}}})) + getHasVars={{{getHasVars}}})) + getHasRequired={{{getHasRequired}}})) + getHasDiscriminatorWithNonEmptyMapping={{{hasDiscriminatorWithNonEmptyMapping}}} + hasMultipleTypes={{{hasMultipleTypes}}} \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/.openapi-generator-ignore b/cvat-sdk/gen/templates/openapi-generator/.openapi-generator-ignore new file mode 100644 index 000000000000..44b0733aa739 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/.openapi-generator-ignore @@ -0,0 +1,40 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md + +# For safety +/cvat_sdk/__init__.py +/config +/gen +/helpers.py +/utils.py +/types.py + +# Don't generate these files +/git_push.sh +/setup.cfg +/test-requirements.txt +/tox.ini +/.gitlab-ci.yml +/.travis.yml +/.gitignore diff --git a/cvat-sdk/gen/templates/openapi-generator/README.mustache b/cvat-sdk/gen/templates/openapi-generator/README.mustache new file mode 100644 index 000000000000..95480268f579 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/README.mustache @@ -0,0 +1,36 @@ +# {{{projectName}}} +{{#appDescriptionWithNewLines}} +{{{.}}} +{{/appDescriptionWithNewLines}} + +This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: + +- API version: {{appVersion}} +- Package version: {{packageVersion}} +{{^hideGenerationTimestamp}} +- Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} +- Build package: {{generatorClass}} +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +## Installation & Usage + +To install a prebuilt package, run the following command in the terminal: + +```sh +pip install cvat-sdk +``` + +To install from the local directory, follow [the developer guide](https://opencv.github.io/cvat/docs/api_sdk/sdk/developer_guide). + +After installation you can import the package: + +```python +import cvat_sdk +``` + +## Getting Started + +{{> README_common }} diff --git a/cvat-sdk/gen/templates/openapi-generator/README_common.mustache b/cvat-sdk/gen/templates/openapi-generator/README_common.mustache new file mode 100644 index 000000000000..a14bbfe36a32 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/README_common.mustache @@ -0,0 +1,176 @@ +API includes 2 layers: +- REST API wrappers (`ApiClient`). Located in at `cvat_sdk.api_client` +- high-level tools (`core`). Located at `cvat_sdk.core` + +### `ApiClient` (low level) + +This layer is useful if you need to work directly with REST API, but want +to have data validation and syntax assistance from your code editor. The code +on this layer is autogenerated. + +#### Example + +Let's see how a task with local files can be created. We will use the basic auth +to make things simpler. + +```python +from time import sleep +from cvat_sdk.api_client import Configuration, ApiClient, models, apis, exceptions + +configuration = Configuration( + host="{{{basePath}}}", + username='YOUR_USERNAME', + password='YOUR_PASSWORD', +) + +# Enter a context with an instance of the API client +with ApiClient(configuration) as api_client: + # Parameters can be passed as a plain dict with JSON-serialized data + # or as model objects (from cvat_sdk.api_client.models), including + # mixed variants. + # + # In case of dicts, keys must be the same as members of models.I + # interfaces and values must be convertible to the corresponding member + # value types (e.g. a date or string enum value can be parsed from a string). + # + # In case of model objects, data must be of the corresponding + # models. types. + # + # Let's use a dict here. It should look like models.ITaskWriteRequest + task_spec = { + 'name': 'example task', + "labels": [{ + "name": "car", + "color": "#ff00ff", + "attributes": [ + { + "name": "a", + "mutable": True, + "input_type": "number", + "default_value": "5", + "values": ["4", "5", "6"] + } + ] + }], + } + + try: + # Apis can be accessed as ApiClient class members + # We use different models for input and output data. For input data, + # models are typically called like "*Request". Output data models have + # no suffix. + (task, response) = api_client.tasks_api.create(task_spec) + except exceptions.ApiException as e: + # We can catch the basic exception type, or a derived type + print("Exception when trying to create a task: %s\n" % e) + + # Here we will use models instead of a dict + task_data = models.DataRequest( + image_quality=75, + start_frame=2, + stop_frame=5, + client_files=[ + open('image1.jpg', 'rb'), + open('image2.jpg', 'rb'), + ], + ) + + # If we pass binary file objects, we need to specify content type. + # For this endpoint, we don't have response data + (_, response) = api_client.tasks_api.create_data(task.id, + data_request=task_data, + _content_type="multipart/form-data", + + # we can choose to check the response status manually + # and disable the response data parsing + _check_status=False, _parse_response=False + ) + assert response.status == 202, response.msg + + # Wait till task data is processed + for _ in range(100): + (status, _) = api_client.tasks_api.retrieve_status(task.id) + if status.state.value in ['Finished', 'Failed']: + break + sleep(0.1) + assert status.state.value == 'Finished', status.message + + # Update the task object and check the task size + (task, _) = api_client.tasks_api.retrieve(task.id) + assert task.size == 4 +``` + +### `Core` (high-level) + +This layer provides high-level APIs, allowing easier access to server operations. +API includes *Repositories* and *Entities*. Repositories provide management +operations for Entitites. Entitites represent separate objects on the server +(e.g. tasks, jobs etc). + +#### Example + +```python +from cvat_sdk import make_client, models +from cvat_sdk.core.proxies.tasks import ResourceType, Task + +with make_client(host="{{{basePath}}}") as client: + # Authorize using the basic auth + client.login(('YOUR_USERNAME', 'YOUR_PASSWORD')) + + # Models are used the same way as in the layer 1 + task_spec = { + "name": "example task 2", + "labels": [ + { + "name": "car", + "color": "#ff00ff", + "attributes": [ + { + "name": "a", + "mutable": True, + "input_type": "number", + "default_value": "5", + "values": ["4", "5", "6"], + } + ], + } + ], + } + + # Different repositories can be accessed as the Client class members. + # They may provide both simple and complex operations, + # such as entity creation, retrieval and removal. + task = client.tasks.create_from_data( + spec=task_spec, + resource_type=ResourceType.LOCAL, + resources=['image1.jpg', 'image2.png'], + ) + + # Task object is already up-to-date with its server counterpart + assert task.size == 2 + + # An entity needs to be fetch()-ed to reflect the latest changes. + # It can be update()-d and remove()-d depending on the entity type. + task.update({'name': 'mytask'}) + task.remove() +``` + +## Available API Endpoints + +All URIs are relative to _{{basePath}}_ + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}_{{classname}}_ | [**{{>operation_name}}**](apis/{{classname}}#{{>operation_name}}) | **{{httpMethod}}** {{path}} | {{summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Available Models + +{{#models}}{{#model}} - {{{classname}}} +{{/model}}{{/models}} + + +## Author + +{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}} +{{/-last}}{{/apis}}{{/apiInfo}} diff --git a/cvat-sdk/gen/templates/openapi-generator/__init__.mustache b/cvat-sdk/gen/templates/openapi-generator/__init__.mustache new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cvat-sdk/gen/templates/openapi-generator/__init__api.mustache b/cvat-sdk/gen/templates/openapi-generator/__init__api.mustache new file mode 100644 index 000000000000..1ea64eca8b5e --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/__init__api.mustache @@ -0,0 +1,10 @@ +{{#apiInfo}} +{{#apis}} +{{#-first}} +{{>partial_header}} +# do not import all apis into this module because that uses a lot of memory and stack frames +# if you need the ability to import all apis from one package, import them with +# from {{packageName}}.apis import {{classname}} +{{/-first}} +{{/apis}} +{{/apiInfo}} diff --git a/cvat-sdk/gen/templates/openapi-generator/__init__apis.mustache b/cvat-sdk/gen/templates/openapi-generator/__init__apis.mustache new file mode 100644 index 000000000000..55729f887aad --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/__init__apis.mustache @@ -0,0 +1,23 @@ +{{#apiInfo}} +{{#apis}} +{{#-first}} + +{{>partial_header}} + +# Import all APIs into this package. +# If you have many APIs here with many many models used in each API this may +# raise a `RecursionError`. +# In order to avoid this, import only the API that you directly need like: +# +# from {{packageName}}.api.{{classFilename}} import {{classname}} +# +# or import this package, but before doing it, use: +# +# import sys +# sys.setrecursionlimit(n) + +# Import APIs into API package: +{{/-first}} +from {{apiPackage}}.{{classFilename}} import {{classname}} +{{/apis}} +{{/apiInfo}} diff --git a/cvat-sdk/gen/templates/openapi-generator/__init__model.mustache b/cvat-sdk/gen/templates/openapi-generator/__init__model.mustache new file mode 100644 index 000000000000..b6b698b04528 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/__init__model.mustache @@ -0,0 +1,5 @@ +# we can not import model classes here because that would create a circular +# reference which would not work in python2 +# do not import all models into this module because that uses a lot of memory and stack frames +# if you need the ability to import all models from one package, import them with +# from {{packageName}}.models import ModelA, ModelB diff --git a/cvat-sdk/gen/templates/openapi-generator/__init__models.mustache b/cvat-sdk/gen/templates/openapi-generator/__init__models.mustache new file mode 100644 index 000000000000..9231ab232fd3 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/__init__models.mustache @@ -0,0 +1,16 @@ +{{>partial_header}} + +# import all models into this package +# if you have many models here with many references from one model to another this may +# raise a RecursionError +# to avoid this, import only the models that you directly need like: +# from from {{modelPackage}}.pet import Pet +# or import this package, but before doing it, use: +# import sys +# sys.setrecursionlimit(n) + +{{#models}} +{{#model}} +from {{modelPackage}}.{{classFilename}} import {{classname}}{{^interfaces}}, I{{classname}}{{/interfaces}} +{{/model}} +{{/models}} diff --git a/cvat-sdk/gen/templates/openapi-generator/__init__package.mustache b/cvat-sdk/gen/templates/openapi-generator/__init__package.mustache new file mode 100644 index 000000000000..91e318ad3212 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/__init__package.mustache @@ -0,0 +1,21 @@ +{{>partial_header}} + +__version__ = "{{packageVersion}}" + +from {{packageName}}.api_client import ApiClient + +from {{packageName}}.configuration import Configuration +{{#hasHttpSignatureMethods}} +from {{packageName}}.signing import HttpSigningConfiguration +{{/hasHttpSignatureMethods}} + +from {{packageName}}.exceptions import OpenApiException +from {{packageName}}.exceptions import ApiAttributeError +from {{packageName}}.exceptions import ApiTypeError +from {{packageName}}.exceptions import ApiValueError +from {{packageName}}.exceptions import ApiKeyError +from {{packageName}}.exceptions import ApiException +{{#recursionLimit}} + +__import__('sys').setrecursionlimit({{{.}}}) +{{/recursionLimit}} diff --git a/cvat-sdk/gen/templates/openapi-generator/api.mustache b/cvat-sdk/gen/templates/openapi-generator/api.mustache new file mode 100644 index 000000000000..160d641bc305 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/api.mustache @@ -0,0 +1,329 @@ +{{>partial_header}} + +from __future__ import annotations + +import typing +import urllib3 + +import re # noqa: F401 + +from {{packageName}}.api_client import ApiClient, Endpoint as _Endpoint +from {{packageName}}.model_utils import ( # noqa: F401 + date, + datetime, + file_type, + none_type, +) +{{#imports}} +{{{import}}} +{{/imports}} + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + # Enable introspection. Can't work normally due to cyclic imports + from {{packageName}}.apis import * + from {{packageName}}.models import * + + +class {{classname}}(object): + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client +{{#operations}} +{{#operation}} + self.{{>operation_name}}_endpoint = _Endpoint( + settings={ + 'response_schema': {{#returnType}}({{{.}}},){{/returnType}}{{^returnType}}None{{/returnType}}, +{{#authMethods}} +{{#-first}} + 'auth': [ +{{/-first}} + '{{name}}'{{^-last}},{{/-last}} +{{#-last}} + ], +{{/-last}} +{{/authMethods}} +{{^authMethods}} + 'auth': [], +{{/authMethods}} + 'endpoint_path': '{{{path}}}', + 'operation_id': '{{>operation_name}}', + 'http_method': '{{httpMethod}}', +{{#servers}} +{{#-first}} + 'servers': [ +{{/-first}} + { + 'url': "{{{url}}}", + 'description': "{{{description}}}{{^description}}No description provided{{/description}}", + {{#variables}} + {{#-first}} + 'variables': { + {{/-first}} + '{{{name}}}': { + 'description': "{{{description}}}{{^description}}No description provided{{/description}}", + 'default_value': "{{{defaultValue}}}", + {{#enumValues}} + {{#-first}} + 'enum_values': [ + {{/-first}} + "{{{.}}}"{{^-last}},{{/-last}} + {{#-last}} + ] + {{/-last}} + {{/enumValues}} + }{{^-last}},{{/-last}} + {{#-last}} + } + {{/-last}} + {{/variables}} + }, +{{#-last}} + ] +{{/-last}} +{{/servers}} +{{^servers}} + 'servers': None, +{{/servers}} + }, + params_map={ + 'all': [ +{{#allParams}} + '{{paramName}}', +{{/allParams}} + ], +{{#requiredParams}} +{{#-first}} + 'required': [ +{{/-first}} + '{{paramName}}', +{{#-last}} + ], +{{/-last}} +{{/requiredParams}} +{{^requiredParams}} + 'required': [], +{{/requiredParams}} + 'nullable': [ +{{#allParams}} +{{#isNullable}} + '{{paramName}}', +{{/isNullable}} +{{/allParams}} + ], + 'enum': [ +{{#allParams}} +{{#isEnum}} + '{{paramName}}', +{{/isEnum}} +{{/allParams}} + ], + 'validation': [ +{{#allParams}} +{{#hasValidation}} + '{{paramName}}', +{{/hasValidation}} +{{/allParams}} + ] + }, + root_map={ + 'validations': { +{{#allParams}} +{{#hasValidation}} + ('{{paramName}}',): { +{{#maxLength}} + 'max_length': {{.}},{{/maxLength}}{{#minLength}} + 'min_length': {{.}},{{/minLength}}{{#maxItems}} + 'max_items': {{.}},{{/maxItems}}{{#minItems}} + 'min_items': {{.}},{{/minItems}}{{#maximum}} + {{#exclusiveMaximum}}'exclusive_maximum'{{/exclusiveMaximum}}{{^exclusiveMaximum}}'inclusive_maximum'{{/exclusiveMaximum}}: {{maximum}},{{/maximum}}{{#minimum}} + {{#exclusiveMinimum}}'exclusive_minimum'{{/exclusiveMinimum}}{{^exclusiveMinimum}}'inclusive_minimum'{{/exclusiveMinimum}}: {{minimum}},{{/minimum}}{{#pattern}} + 'regex': { + 'pattern': r'{{{vendorExtensions.x-regex}}}', # noqa: E501{{#vendorExtensions.x-modifiers}} + {{#-first}}'flags': (re.{{.}}{{/-first}}{{^-first}} re.{{.}}{{/-first}}{{^-last}} | {{/-last}}{{#-last}}){{/-last}}{{/vendorExtensions.x-modifiers}} + },{{/pattern}} + }, +{{/hasValidation}} +{{/allParams}} + }, + 'allowed_values': { +{{#allParams}} +{{#isEnum}} + ('{{paramName}}',): { +{{#isNullable}} + 'None': None,{{/isNullable}}{{#allowableValues}}{{#enumVars}} + "{{name}}": {{{value}}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} + }, +{{/isEnum}} +{{/allParams}} + }, + 'openapi_types': { +{{#allParams}} + '{{paramName}}': + ({{{dataType}}},), +{{/allParams}} + }, + 'attribute_map': { +{{#allParams}} +{{^isBodyParam}} + '{{paramName}}': '{{baseName}}', +{{/isBodyParam}} +{{/allParams}} + }, + 'location_map': { +{{#allParams}} + '{{paramName}}': '{{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isCookieParam}}cookie{{/isCookieParam}}{{#isBodyParam}}body{{/isBodyParam}}', +{{/allParams}} + }, + 'collection_format_map': { +{{#allParams}} +{{#collectionFormat}} + '{{paramName}}': '{{collectionFormat}}', +{{/collectionFormat}} +{{/allParams}} + } + }, + headers_map={ +{{#hasProduces}} + 'accept': [ +{{#produces}} + '{{{mediaType}}}'{{^-last}},{{/-last}} +{{/produces}} + ], +{{/hasProduces}} +{{^hasProduces}} + 'accept': [], +{{/hasProduces}} +{{#hasConsumes}} + 'content_type': [ +{{#consumes}} + '{{{mediaType}}}'{{^-last}},{{/-last}} +{{/consumes}} + ] +{{/hasConsumes}} +{{^hasConsumes}} + 'content_type': [], +{{/hasConsumes}} + }, + api_client=api_client + ) +{{/operation}} +{{/operations}} + +{{#operations}} +{{#operation}} + def {{>operation_name}}( + self, +{{#requiredParams}} +{{^defaultValue}} + {{paramName}}: {{>model_templates/type_annotation_cleaned}}, +{{/defaultValue}} +{{/requiredParams}} +{{#requiredParams}} +{{#defaultValue}} + {{paramName}}: {{>model_templates/type_annotation_cleaned}} = {{{defaultValue}}}, +{{/defaultValue}} +{{/requiredParams}} + *, + _parse_response: bool = True, + _request_timeout: typing.Union[int, float, tuple] = None, + _validate_inputs: bool = True, + _validate_outputs: bool = True, + _check_status: bool = True, + _spec_property_naming: bool = False, + _content_type: typing.Optional[str] = None, + _host_index: typing.Optional[int] = None, + _request_auths: typing.Optional[typing.List] = None, + _async_call: bool = False, + **kwargs, + ) -> typing.Tuple[typing.Optional[{{>return_type}}], urllib3.HTTPResponse]: + """{{{summary}}}{{^summary}}{{>operation_name}}{{/summary}} # noqa: E501 + +{{#notes}} + {{{.}}} # noqa: E501 +{{/notes}} + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass _async_call=True + + >>> thread = api.{{>operation_name}}({{#requiredParams}}{{^defaultValue}}{{paramName}}, {{/defaultValue}}{{/requiredParams}}{{#requiredParams}}{{#defaultValue}}{{paramName}}={{{defaultValue}}}, {{/defaultValue}}{{/requiredParams}}_async_call=True) + >>> result = thread.get() + +{{#requiredParams}} +{{#-last}} + Args: +{{/-last}} +{{/requiredParams}} +{{#requiredParams}} +{{^defaultValue}} + {{paramName}} ({{dataType}}):{{#description}} {{{.}}}{{/description}} +{{/defaultValue}} +{{/requiredParams}} +{{#requiredParams}} +{{#defaultValue}} + {{paramName}} ({{dataType}}):{{#description}} {{{.}}}.{{/description}} defaults to {{{defaultValue}}}, must be one of [{{{defaultValue}}}] +{{/defaultValue}} +{{/requiredParams}} + + Keyword Args:{{#optionalParams}} + {{paramName}} ({{dataType}}):{{#description}} {{{.}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}}{{/optionalParams}} + _parse_response (bool): if False, the response data will not be parsed, + None is returned for data. + Default is True. + _request_timeout (int/float/tuple): timeout setting for this request. If + one number provided, it will be total request timeout. It can also + be a pair (tuple) of (connection, read) timeouts. + Default is None. + _validate_inputs (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _validate_outputs (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _check_status (bool): whether to check response status + for being positive or not. + Default is True + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _content_type (str/None): force body content-type. + Default is None and content-type will be predicted by allowed + content-types and body. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + _request_auths (list): set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + Default is None + _async_call (bool): execute request asynchronously + + Returns: + ({{returnType}}{{^returnType}}None{{/returnType}}, HTTPResponse) + If the method is called asynchronously, returns the request + thread. + """ + kwargs['_async_call'] = _async_call + kwargs['_parse_response'] = _parse_response + kwargs['_request_timeout'] = _request_timeout + kwargs['_validate_inputs'] = _validate_inputs + kwargs['_validate_outputs'] = _validate_outputs + kwargs['_check_status'] = _check_status + kwargs['_spec_property_naming'] = _spec_property_naming + kwargs['_content_type'] = _content_type + kwargs['_host_index'] = _host_index + kwargs['_request_auths'] = _request_auths +{{#requiredParams}} + kwargs['{{paramName}}'] = {{paramName}} +{{/requiredParams}} + return self.{{>operation_name}}_endpoint.call_with_http_info(**kwargs) + +{{/operation}} +{{/operations}} diff --git a/cvat-sdk/gen/templates/openapi-generator/api_client.mustache b/cvat-sdk/gen/templates/openapi-generator/api_client.mustache new file mode 100644 index 000000000000..0cf16488f099 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/api_client.mustache @@ -0,0 +1,1036 @@ +{{>partial_header}} + +from __future__ import annotations + +import json +import atexit +import mimetypes +import importlib +from multiprocessing.pool import ThreadPool +import io +import os +import re +import typing +from http.cookies import SimpleCookie +from urllib.parse import quote +from urllib3 import HTTPResponse +from urllib3.fields import RequestField + +{{#tornado}} +import tornado.gen +{{/tornado}} + +from {{packageName}} import rest +from {{packageName}}.configuration import Configuration +from {{packageName}}.exceptions import ApiTypeError, ApiValueError, ApiException +from {{packageName}}.model_utils import ( + ModelNormal, + ModelSimple, + ModelComposed, + check_allowed_values, + check_validations, + date, + datetime, + deserialize_file, + file_type, + model_to_dict, + none_type, + validate_and_convert_types, + to_json, + get_file_data_and_close_file, +) + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + # Enable introspection. Can't work normally due to cyclic imports + from {{packageName}}.apis import * + from {{packageName}}.models import * + + +class ApiClient(object): + """Generic API client for OpenAPI client library builds. + + OpenAPI generic API client. This client handles the client- + server communication, and is invariant across implementations. Specifics of + the methods and models for each application are generated from the OpenAPI + templates. + + NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + Do not edit the class manually. + + Class members: +{{#apiInfo}}{{#apis}} + {{>api_name}}: {{classname}}{{/apis}}{{/apiInfo}} + """ + + _pool = None + + def __init__(self, + configuration: typing.Optional[Configuration] = None, + headers: typing.Optional[typing.Dict[str, str]] = None, + cookies: typing.Optional[typing.Dict[str, str]] = None, + pool_threads: int = 1): + """ + :param configuration: configuration object for this client + :param headers: header to include when making calls to the API + :param cookies: cookies to include when making calls to the API + :param pool_threads: The number of threads to use for async requests + to the API. More threads means more concurrent API requests. + """ + + if configuration is None: + configuration = Configuration.get_default_copy() + self.configuration = configuration + self.pool_threads = pool_threads + + self.rest_client = rest.RESTClientObject(configuration) + self.default_headers: typing.Dict[str, str] = headers or {} + self.cookies = SimpleCookie() + if cookies: + self.cookies.update(cookies) + # Set default User-Agent. + self.user_agent = '{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/{{{packageVersion}}}/python{{/httpUserAgent}}' + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def close(self): + if self._pool: + self._pool.close() + self._pool.join() + self._pool = None + if hasattr(atexit, 'unregister'): + atexit.unregister(self.close) + + @property + def pool(self): + """Create thread pool on first request + avoids instantiating unused threadpool for blocking clients. + """ + if self._pool is None: + atexit.register(self.close) + self._pool = ThreadPool(self.pool_threads) + return self._pool + + @property + def user_agent(self): + """User agent for this API client""" + return self.default_headers['User-Agent'] + + @user_agent.setter + def user_agent(self, value): + self.default_headers['User-Agent'] = value + + def _serialize_post_parameter(self, obj): + if isinstance(obj, (str, int, float, none_type, bool)): + return ('', json.dumps(obj), 'application/json') + elif isinstance(obj, io.IOBase): + return self._serialize_file(obj) + raise ApiValueError( + 'Unable to prepare type {} for serialization'.format( + obj.__class__.__name__)) + + def _convert_body_to_post_params(self, body): + # body must be a flat structure, lists of primitives is possible + body = self.sanitize_for_serialization(body, read_files=False) + assert isinstance(body, dict), type(body) + + post_params = [] + for k, v in body.items(): + if isinstance(v, (tuple, list)): + for i, entry in enumerate(v): + post_params.append(( + f'{k}[{i}]', + self._serialize_post_parameter(entry) + )) + else: + post_params.append((k, self._serialize_post_parameter(v))) + return post_params + + def set_default_header(self, header_name, header_value): + self.default_headers[header_name] = header_value + + {{#tornado}} + @tornado.gen.coroutine + {{/tornado}} + {{#asyncio}}async {{/asyncio}}def __call_api( + self, + resource_path: str, + method: str, + path_params: typing.Optional[typing.Dict[str, typing.Any]] = None, + query_params: typing.Optional[typing.List[typing.Tuple[str, typing.Any]]] = None, + header_params: typing.Optional[typing.Dict[str, typing.Any]] = None, + body: typing.Optional[typing.Any] = None, + post_params: typing.Optional[typing.List[typing.Tuple[str, typing.Any]]] = None, + files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None, + response_schema: typing.Optional[typing.Tuple[typing.Any]] = None, + auth_settings: typing.Optional[typing.List[str]] = None, + collection_formats: typing.Optional[typing.Dict[str, str]] = None, + *, + _parse_response: bool = True, + _request_timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + _host: typing.Optional[str] = None, + _check_type: typing.Optional[bool] = None, + _check_status: bool = True, + _request_auths: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None + ): + + config = self.configuration + + # header parameters + header_params = header_params or {} + header_params.update(self.get_common_headers()) + if header_params: + header_params = self.sanitize_for_serialization(header_params) + header_params = dict(self.parameters_to_tuples(header_params, + collection_formats)) + + # path parameters + if path_params: + path_params = self.sanitize_for_serialization(path_params) + path_params = self.parameters_to_tuples(path_params, + collection_formats) + for k, v in path_params: + # specified safe chars, encode everything + resource_path = resource_path.replace( + '{%s}' % k, + quote(str(v), safe=config.safe_chars_for_path_param) + ) + + # query parameters + if query_params: + query_params = self.sanitize_for_serialization(query_params) + query_params = self.parameters_to_tuples(query_params, + collection_formats) + + # post parameters + post_params = post_params if post_params else [] + if post_params or files: + post_params = self.sanitize_for_serialization(post_params) + post_params = self.parameters_to_tuples(post_params, + collection_formats) + post_params.extend(self.files_parameters(files)) + + if header_params.get('Content-Type', '').startswith("multipart"): + if body: + post_params.extend(self._convert_body_to_post_params(body)) + body = None + + if post_params: + post_params = self.parameters_to_multipart(post_params, (dict)) + else: + # body + if body: + body = self.sanitize_for_serialization(body) + + # auth setting + self.update_params_for_auth(header_params, query_params, + auth_settings, resource_path, method, body, + request_auths=_request_auths) + + # request url + if _host is None: + url = self.configuration.host + resource_path + else: + # use server/host defined in path or operation instead + url = _host + resource_path + + try: + # perform request and return response + response = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request( + method, url, query_params=query_params, headers=header_params, + post_params=post_params, body=body, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status) + except ApiException as e: + e.body = e.body.decode('utf-8') + raise e + + self.last_response = response + + return_data = None + if _parse_response: + self._update_cookies_from_response(response) + + if response_schema: + return_data = self.deserialize( + response, + response_schema, + _check_type=_check_type + ) + +{{#tornado}} + raise tornado.gen.Return((return_data, response)) +{{/tornado}} +{{^tornado}} + return (return_data, response) +{{/tornado}} + + def get_common_headers(self) -> typing.Dict[str, str]: + """ + Returns a headers dict with all the required headers for requests + """ + + headers = {} + headers.update(self.default_headers) + if self.cookies: + headers['Cookie'] = self.cookies.output(attrs=[], header="", sep=";").strip() + return headers + + def _update_cookies_from_response(self, response: HTTPResponse): + self.cookies.update(SimpleCookie(response.getheader("Set-Cookie"))) + + def parameters_to_multipart(self, params, collection_types): + """Get parameters as list of tuples, formatting as json if value is collection_types + + :param params: Parameters as list of two-tuples + :param dict collection_types: Parameter collection types + :return: Parameters as list of tuple or urllib3.fields.RequestField + """ + new_params = [] + if collection_types is None: + collection_types = (dict) + for k, v in params.items() if isinstance(params, dict) else params: # noqa: E501 + if isinstance( + v, collection_types): # v is instance of collection_type, formatting as application/json + v = json.dumps(v, ensure_ascii=False).encode("utf-8") + field = RequestField(k, v) + field.make_multipart(content_type="application/json; charset=utf-8") + new_params.append(field) + else: + new_params.append((k, v)) + return new_params + + @classmethod + def sanitize_for_serialization(cls, obj, *, + read_files: bool = True): + """Prepares data for transmission before it is sent with the rest client + If obj is None, return None. + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is OpenAPI model, return the properties dict. + If obj is io.IOBase, return the bytes + :param obj: The data to serialize. + :param read_files: Whether to read file data or leave files as is. + :return: The serialized form of data. + """ + return to_json(obj, read_files=read_files) + + def deserialize(self, response: HTTPResponse, response_schema: typing.Tuple, *, _check_type: bool): + """Deserializes response into an object. + + :param response (urllib3.HTTPResponse): object to be deserialized. + :param response_schema: For the response, a tuple containing: + valid classes + a list containing valid classes (for list schemas) + a dict containing a tuple of valid classes as the value + Example values: + (str,) + (Pet,) + (float, none_type) + ([int, none_type],) + ({str: (bool, str, int, float, date, datetime, str, none_type)},) + :param _check_type (bool): whether to check the types of the data + received from the server + + :return: deserialized object + """ + + if response_schema == (file_type,): + # TODO: response schema can be "oneOf" with a file option, + # this implementation does not cover this. + + # handle file downloading + # save response body into a tmp file and return the instance + content_disposition = response.getheader("Content-Disposition") + return deserialize_file(response.data, self.configuration, + content_disposition=content_disposition) + + encoding = "utf-8" + content_type = response.getheader('content-type') + if content_type is not None: + match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type) + if match: + encoding = match.group(1) + response_data = response.data.decode(encoding) + + # fetch data from response object + try: + received_data = json.loads(response_data) + except ValueError: + received_data = response_data + + # store our data under the key of 'received_data' so users have some + # context if they are deserializing a string and the data type is wrong + deserialized_data = validate_and_convert_types( + received_data, + response_schema, + ['received_data'], + True, + _check_type, + configuration=self.configuration + ) + return deserialized_data + + def call_api( + self, + resource_path: str, + method: str, + path_params: typing.Optional[typing.Dict[str, typing.Any]] = None, + query_params: typing.Optional[typing.List[typing.Tuple[str, typing.Any]]] = None, + header_params: typing.Optional[typing.Dict[str, typing.Any]] = None, + body: typing.Optional[typing.Any] = None, + post_params: typing.Optional[typing.List[typing.Tuple[str, typing.Any]]] = None, + files: typing.Optional[typing.Dict[str, typing.List[io.IOBase]]] = None, + response_schema: typing.Optional[typing.Tuple[typing.Any]] = None, + auth_settings: typing.Optional[typing.List[str]] = None, + collection_formats: typing.Optional[typing.Dict[str, str]] = None, + *, + _async_call: typing.Optional[bool] = None, + _parse_response: bool = True, + _request_timeout: typing.Optional[typing.Union[int, float, typing.Tuple]] = None, + _host: typing.Optional[str] = None, + _check_type: typing.Optional[bool] = None, + _request_auths: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None, + _check_status: bool = True, + ): + """Makes the HTTP request (synchronous) and returns deserialized data. + + To make an _async_call request, set the _async_call parameter. + + :param resource_path: Path to method endpoint. + :param method: Method to call. + :param path_params: Path parameters in the url. + :param query_params: Query parameters in the url. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param auth_settings list: Auth Settings names for the request. + :param response_schema: For the response, a tuple containing: + valid classes + a list containing valid classes (for list schemas) + a dict containing a tuple of valid classes as the value + Example values: + (str,) + (Pet,) + (float, none_type) + ([int, none_type],) + ({str: (bool, str, int, float, date, datetime, str, none_type)},) + :param files: key -> field name, value -> a list of open file + objects for `multipart/form-data`. + :type files: dict + :param _async_call bool: execute request asynchronously + :type _async_call: bool, optional + :param collection_formats: dict of collection formats for path, query, + header, and post parameters. + :type collection_formats: dict, optional + :param _parse_response: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Response headers will not be + processed (cookies as well). Default is True. + :type _parse_response: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _check_type: boolean describing if the data back from the server + should have its type checked. + :type _check_type: bool, optional + :param _request_auths: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :type _request_auths: list, optional + :return: + If _async_call parameter is True, + the request will be called asynchronously. + The method will return the request thread. + If parameter _async_call is False or missing, + then the method will return the response directly. + """ + params = { + "resource_path": resource_path, + "method": method, + "path_params": path_params, + "query_params": query_params, + "header_params": header_params, + "body": body, + "post_params": post_params, + "files": files, + "response_schema": response_schema, + "auth_settings": auth_settings, + "collection_formats": collection_formats, + "_parse_response": _parse_response, + "_request_timeout": _request_timeout, + "_host": _host, + "_check_type": _check_type, + "_request_auths": _request_auths, + "_check_status": _check_status, + } + + if not _async_call: + return self.__call_api(**params) + + return self.pool.apply_async(self.__call_api, (), kwds=params) + + def request(self, method, url, query_params=None, headers=None, + post_params=None, body=None, *, _parse_response=True, + _request_timeout=None, _check_status=True): + """Makes the HTTP request using RESTClient.""" + if method == "GET": + return self.rest_client.GET(url, + query_params=query_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + headers=headers, + _check_status=_check_status) + elif method == "HEAD": + return self.rest_client.HEAD(url, + query_params=query_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + headers=headers, + _check_status=_check_status) + elif method == "OPTIONS": + return self.rest_client.OPTIONS(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + body=body) + elif method == "POST": + return self.rest_client.POST(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + body=body) + elif method == "PUT": + return self.rest_client.PUT(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + body=body) + elif method == "PATCH": + return self.rest_client.PATCH(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + body=body) + elif method == "DELETE": + return self.rest_client.DELETE(url, + query_params=query_params, + headers=headers, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + body=body) + else: + raise ApiValueError( + "http method must be `GET`, `HEAD`, `OPTIONS`," + " `POST`, `PATCH`, `PUT` or `DELETE`." + ) + + def parameters_to_tuples(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: Parameters as list of tuples, collections formatted + """ + new_params = [] + if collection_formats is None: + collection_formats = {} + for k, v in params.items() if isinstance(params, dict) else params: # noqa: E501 + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, value) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(str(value) for value in v))) + else: + new_params.append((k, v)) + return new_params + + def _serialize_file(self, file_instance: io.IOBase) -> typing.Tuple[str, typing.Union[str, bytes], str]: + if file_instance.closed is True: + raise ApiValueError("Cannot read a closed file.") + filename = os.path.basename(file_instance.name) + + filedata = get_file_data_and_close_file(file_instance) + + mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' + + return filename, filedata, mimetype + + def files_parameters(self, + files: typing.Optional[typing.Dict[str, + typing.List[io.IOBase]]] = None): + """Builds form parameters. + + :param files: None or a dict with key=param_name and + value is a list of open file objects + :return: List of tuples of form parameters with file data + """ + if files is None: + return [] + + params = [] + for param_name, file_instances in files.items(): + if file_instances is None: + # if the file field is nullable, skip None values + continue + for file_instance in file_instances: + if file_instance is None: + # if the file field is nullable, skip None values + continue + + try: + params.append((param_name, self._serialize_file(file_instance))) + except ApiValueError as e: + raise ApiValueError("The passed in file_type " + "for %s must be open." % param_name) from e + + return params + + def select_header_accept(self, accepts): + """Returns `Accept` based on an array of accepts provided. + + :param accepts: List of headers. + :return: Accept (e.g. application/json). + """ + if not accepts: + return + + accepts = [x.lower() for x in accepts] + + if 'application/json' in accepts: + return 'application/json' + else: + return ', '.join(accepts) + + def select_header_content_type(self, content_types, method=None, body=None): + """Returns `Content-Type` based on an array of content_types provided. + + :param content_types: List of content-types. + :param method: http method (e.g. POST, PATCH). + :param body: http body to send. + :return: Content-Type (e.g. application/json). + """ + if not content_types: + return None + + content_types = [x.lower() for x in content_types] + + if (method == 'PATCH' and + 'application/json-patch+json' in content_types and + isinstance(body, list)): + return 'application/json-patch+json' + + if 'application/json' in content_types or '*/*' in content_types: + return 'application/json' + else: + return content_types[0] + + def update_params_for_auth(self, headers, queries, auth_settings, + resource_path, method, body, request_auths=None): + """Updates header and query params based on authentication setting. + + :param headers: Header parameters dict to be updated. + :param queries: Query parameters tuple list to be updated. + :param auth_settings: Authentication setting identifiers list. + :param resource_path: A string representation of the HTTP request resource path. + :param method: A string representation of the HTTP request method. + :param body: A object representing the body of the HTTP request. + The object type is the return value of _encoder.default(). + :param request_auths: if set, the provided settings will + override the token in the configuration. + """ + if not auth_settings: + return + + if request_auths: + for auth_setting in request_auths: + self._apply_auth_params( + headers, queries, resource_path, method, body, auth_setting) + return + + for auth in auth_settings: + auth_setting = self.configuration.auth_settings().get(auth) + if auth_setting: + self._apply_auth_params( + headers, queries, resource_path, method, body, auth_setting) + + def _apply_auth_params(self, headers, queries, resource_path, method, body, auth_setting): + if auth_setting['in'] == 'cookie': + headers['Cookie'] = auth_setting['key'] + "=" + auth_setting['value'] + elif auth_setting['in'] == 'header': + if auth_setting['type'] != 'http-signature': + headers[auth_setting['key']] = auth_setting['value'] +{{#hasHttpSignatureMethods}} + else: + # The HTTP signature scheme requires multiple HTTP headers + # that are calculated dynamically. + signing_info = self.configuration.signing_info + auth_headers = signing_info.get_http_signature_headers( + resource_path, method, headers, body, queries) + headers.update(auth_headers) +{{/hasHttpSignatureMethods}} + elif auth_setting['in'] == 'query': + queries.append((auth_setting['key'], auth_setting['value'])) + else: + raise ApiValueError( + 'Authentication token must be in `query` or `header`' + ) + +{{#apiInfo}}{{#apis}} + {{>api_name}}: '{{classname}}'{{/apis}}{{/apiInfo}} + + _apis: typing.Dict[str, object] = { {{#apiInfo}}{{#apis}} + '{{>api_name}}': [None, '{{classname}}'],{{/apis}}{{/apiInfo}} + } + + def _make_api_instance(self, klass_name): + package = __name__.rsplit('.', maxsplit=1)[0] + module = importlib.import_module(package + '.apis') + api_klass = getattr(module, klass_name) + return api_klass(self) + + def __getattr__(self, key): + notfound = object() + api_instance, api_klassname = self._apis.get(key, notfound) + if api_instance is notfound: + raise AttributeError(f"Can't find the '{key}' attribute") + + if api_instance is None: + api_instance = self._make_api_instance(api_klassname) + setattr(self, key, api_instance) + + return api_instance + + +class Endpoint(object): + def __init__(self, + settings: typing.Optional[typing.Dict[str, typing.Any]] = None, + params_map: typing.Optional[typing.Dict[str, typing.Any]] = None, + root_map: typing.Optional[typing.Dict[str, typing.Any]] = None, + headers_map: typing.Optional[typing.Dict[str, typing.Any]] = None, + api_client: typing.Optional[ApiClient] = None + ): + """Creates an endpoint + + Args: + settings (dict): see below key value pairs + 'response_schema' (tuple/None): response type + 'auth' (list): a list of auth type keys + 'endpoint_path' (str): the endpoint path + 'operation_id' (str): endpoint string identifier + 'http_method' (str): POST/PUT/PATCH/GET etc + 'servers' (list): list of str servers that this endpoint is at + params_map (dict): see below key value pairs + 'all' (list): list of str endpoint parameter names + 'required' (list): list of required parameter names + 'nullable' (list): list of nullable parameter names + 'enum' (list): list of parameters with enum values + 'validation' (list): list of parameters with validations + root_map (dict): + 'validations' (dict): the dict mapping endpoint parameter tuple + paths to their validation dictionaries + 'allowed_values' (dict): the dict mapping endpoint parameter + tuple paths to their allowed_values (enum) dictionaries + 'openapi_types' (dict): param_name to openapi type + 'attribute_map' (dict): param_name to camelCase name + 'location_map' (dict): param_name to 'body', 'file', 'form', + 'header', 'path', 'query' + collection_format_map (dict): param_name to `csv` etc. + headers_map (dict): see below key value pairs + 'accept' (list): list of Accept header strings + 'content_type' (list): list of Content-Type header strings + api_client (ApiClient) api client instance + """ + + self.settings = settings + self.params_map = params_map + self.params_map['all'].extend([ + '_async_call', + '_host_index', + '_parse_response', + '_request_timeout', + '_validate_inputs', + '_validate_outputs', + '_check_status', + '_content_type', + '_spec_property_naming', + '_request_auths' + ]) + self.params_map['nullable'].extend(['_request_timeout']) + self.validations = root_map['validations'] + self.allowed_values = root_map['allowed_values'] + self.openapi_types = root_map['openapi_types'] + extra_types = { + '_async_call': (bool,), + '_host_index': (none_type, int), + '_parse_response': (bool,), + '_request_timeout': (none_type, float, (float,), [float], int, (int,), [int]), + '_validate_inputs': (bool,), + '_validate_outputs': (bool,), + '_check_status': (bool,), + '_spec_property_naming': (bool,), + '_content_type': (none_type, str), + '_request_auths': (none_type, list) + } + self.openapi_types.update(extra_types) + self.attribute_map = root_map['attribute_map'] + self.location_map = root_map['location_map'] + self.collection_format_map = root_map['collection_format_map'] + self.headers_map = headers_map + self.api_client = api_client + + @property + def path(self) -> str: + return self.settings['endpoint_path'] + + def __validate_inputs(self, kwargs): + for param in self.params_map['enum']: + if param in kwargs: + check_allowed_values( + self.allowed_values, + (param,), + kwargs[param] + ) + + for param in self.params_map['validation']: + if param in kwargs: + check_validations( + self.validations, + (param,), + kwargs[param], + configuration=self.api_client.configuration + ) + + if kwargs['_validate_inputs'] is False: + return + + for key, value in kwargs.items(): + fixed_val = validate_and_convert_types( + value, + self.openapi_types[key], + [key], + kwargs['_spec_property_naming'], + kwargs['_validate_inputs'], + configuration=self.api_client.configuration + ) + kwargs[key] = fixed_val + + def __gather_params(self, kwargs): + params = { + 'body': None, + 'collection_format': {}, + 'file': {}, + 'form': [], + 'header': {}, + 'path': {}, + 'query': [] + } + + for param_name, param_value in kwargs.items(): + param_location = self.location_map.get(param_name) + if param_location is None: + continue + if param_location: + if param_location == 'body': + params['body'] = param_value + continue + base_name = self.attribute_map[param_name] + if (param_location == 'form' and + self.openapi_types[param_name] == (file_type,)): + params['file'][base_name] = [param_value] + elif (param_location == 'form' and + self.openapi_types[param_name] == ([file_type],)): + # param_value is already a list + params['file'][base_name] = param_value + elif param_location in {'form', 'query'}: + param_value_full = (base_name, param_value) + params[param_location].append(param_value_full) + if param_location not in {'form', 'query'}: + params[param_location][base_name] = param_value + collection_format = self.collection_format_map.get(param_name) + if collection_format: + params['collection_format'][base_name] = collection_format + + return params + + def call_with_http_info(self, + _parse_response: bool = True, + _request_timeout: typing.Union[int, float, tuple] = None, + _validate_inputs: bool = True, + _validate_outputs: bool = True, + _check_status: bool = True, + _spec_property_naming: bool = False, + _content_type: typing.Optional[str] = None, + _host_index: typing.Optional[int] = None, + _request_auths: typing.Optional[typing.List] = None, + _async_call: bool = False, + **kwargs) -> typing.Tuple[typing.Optional[typing.Any], HTTPResponse]: + """ + Keyword Args: + endpoint args + _parse_response (bool): if False, the response data will not be parsed, + None is returned for data. + Default is True. + _request_timeout (int/float/tuple): timeout setting for this request. If + one number provided, it will be total request timeout. It can also + be a pair (tuple) of (connection, read) timeouts. + Default is None. + _validate_inputs (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _validate_outputs (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _check_status (bool): whether to check response status + for being positive or not. + Default is True + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _content_type (str/None): force body content-type. + Default is None and content-type will be predicted by allowed + content-types and body. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + _request_auths (list): set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + Default is None + _async_call (bool): execute request asynchronously + + Returns: + (parsed_model, response) + If the method is called asynchronously, returns the request + thread. + """ + + kwargs['_parse_response'] = _parse_response + kwargs['_request_timeout'] = _request_timeout + kwargs['_validate_inputs'] = _validate_inputs + kwargs['_validate_outputs'] = _validate_outputs + kwargs['_check_status'] = _check_status + kwargs['_spec_property_naming'] = _spec_property_naming + kwargs['_content_type'] = _content_type + kwargs['_host_index'] = _host_index + kwargs['_request_auths'] = _request_auths + kwargs['_async_call'] = _async_call + + try: + index = self.api_client.configuration.server_operation_index.get( + self.settings['operation_id'], self.api_client.configuration.server_index + ) if kwargs['_host_index'] is None else kwargs['_host_index'] + server_variables = self.api_client.configuration.server_operation_variables.get( + self.settings['operation_id'], self.api_client.configuration.server_variables + ) + _host = self.api_client.configuration.get_host_from_settings( + index, variables=server_variables, servers=self.settings['servers'] + ) + except IndexError: + if self.settings['servers']: + raise ApiValueError( + "Invalid host index. Must be 0 <= index < %s" % + len(self.settings['servers']) + ) + _host = None + + for key, value in kwargs.items(): + if key not in self.params_map['all']: + raise ApiTypeError( + "Got an unexpected parameter '%s'" + " to method `%s`" % + (key, self.settings['operation_id']) + ) + # only throw this nullable ApiValueError if _validate_inputs + # is False, if _validate_inputs==True we catch this case + # in self.__validate_inputs + if (key not in self.params_map['nullable'] and value is None + and kwargs['_validate_inputs'] is False): + raise ApiValueError( + "Value may not be None for non-nullable parameter `%s`" + " when calling `%s`" % + (key, self.settings['operation_id']) + ) + + for key in self.params_map['required']: + if key not in kwargs.keys(): + raise ApiValueError( + "Missing the required parameter `%s` when calling " + "`%s`" % (key, self.settings['operation_id']) + ) + + self.__validate_inputs(kwargs) + + params = self.__gather_params(kwargs) + + accept_headers_list = self.headers_map['accept'] + if accept_headers_list: + params['header']['Accept'] = self.api_client.select_header_accept( + accept_headers_list) + + if kwargs.get('_content_type'): + params['header']['Content-Type'] = kwargs['_content_type'] + else: + content_type_headers_list = self.headers_map['content_type'] + if content_type_headers_list: + if params['body'] != "": + content_types_list = self.api_client.select_header_content_type( + content_type_headers_list, self.settings['http_method'], + params['body']) + if content_types_list: + params['header']['Content-Type'] = content_types_list + + return self.api_client.call_api( + self.settings['endpoint_path'], self.settings['http_method'], + params['path'], + params['query'], + params['header'], + body=params['body'], + post_params=params['form'], + files=params['file'], + response_schema=self.settings['response_schema'], + auth_settings=self.settings['auth'], + _async_call=kwargs['_async_call'], + _check_type=kwargs['_validate_outputs'], + _check_status=kwargs['_check_status'], + _parse_response=kwargs['_parse_response'], + _request_timeout=kwargs['_request_timeout'], + _host=_host, + _request_auths=kwargs['_request_auths'], + collection_formats=params['collection_format']) diff --git a/cvat-sdk/gen/templates/openapi-generator/api_doc.mustache b/cvat-sdk/gen/templates/openapi-generator/api_doc.mustache new file mode 100644 index 000000000000..ed8d97d51f0a --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/api_doc.mustache @@ -0,0 +1,59 @@ +# {{classname}} + +{{#description}}{{.}} +{{/description}} + +All URIs are relative to _{{basePath}}_ + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{>operation_name}}**]({{classname}}#{{>operation_name}}) | **{{httpMethod}}** {{path}} | {{summary}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} +## **{{>operation_name}}** +> {{#returnType}}{{{.}}} {{/returnType}}{{>operation_name}}({{#requiredParams}}{{^defaultValue}}{{paramName}}{{^-last}}, {{/-last}}{{/defaultValue}}{{/requiredParams}}) + +{{{summary}}}{{#notes}} + +{{{.}}}{{/notes}} + +### Example +{{> api_doc_example }} + +### Parameters +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} +{{#requiredParams}}{{^defaultValue}} **{{paramName}}** | {{^baseType}}**{{dataType}}**{{/baseType}}{{#baseType}}[**{{dataType}}**](../models/{{baseType}}){{/baseType}}| {{description}} | +{{/defaultValue}}{{/requiredParams}}{{#requiredParams}}{{#defaultValue}} **{{paramName}}** | {{^baseType}}**{{dataType}}**{{/baseType}}{{#baseType}}[**{{dataType}}**](../models/{{baseType}}){{/baseType}}| {{description}} | defaults to {{{.}}} +{{/defaultValue}}{{/requiredParams}}{{#optionalParams}} **{{paramName}}** | {{^baseType}}**{{dataType}}**{{/baseType}}{{#baseType}}[**{{dataType}}**](../models/{{baseType}}){{/baseType}}| {{description}} | [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} +{{/optionalParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**](../models/{{returnBaseType}}){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}None (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}{{{name}}}{{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} + +{{#responses.0}} + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +{{#responses}} +**{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}}
    {{/headers}}{{^headers.0}} - {{/headers.0}} | +{{/responses}} +{{/responses.0}} + +{{/operation}} +{{/operations}} diff --git a/cvat-sdk/gen/templates/openapi-generator/api_doc_example.mustache b/cvat-sdk/gen/templates/openapi-generator/api_doc_example.mustache new file mode 100644 index 000000000000..d671039b3b96 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/api_doc_example.mustache @@ -0,0 +1,44 @@ +```python +import time +from {{{packageName}}} import Configuration, ApiClient, exceptions +{{#imports}} +{{.}} +{{/imports}} +from pprint import pprint + +# Set up an API client +# Read Configuration class docs for more info about parameters and authentication methods +configuration = Configuration( + host = "{{{basePath}}}",{{#hasAuthMethods}} +{{#authMethods}} +{{#isBasic}} +{{#isBasicBasic}} + username = 'YOUR_USERNAME', + password = 'YOUR_PASSWORD', +{{/isBasicBasic}} +{{/isBasic}} +{{/authMethods}} +{{/hasAuthMethods}} +) + +with ApiClient(configuration) as api_client: +{{#requiredParams}} +{{^defaultValue}} + {{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}} +{{/defaultValue}} +{{/requiredParams}} +{{#optionalParams}} + {{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} +{{/optionalParams}} + + try: + {{#returnType}}(data, response) = {{/returnType}}%%%make_api_name!!!{{classname}}%%%.{{{operationId}}}({{#requiredParams}} + {{^defaultValue}}{{paramName}},{{/defaultValue}}{{/requiredParams}}{{#optionalParams}} + {{paramName}}={{paramName}},{{#-last}} + {{/-last}}{{/optionalParams}}) +{{#returnType}} + pprint(data) +{{/returnType}} + except exceptions.ApiException as e: + print("Exception when calling {{classname}}.{{operationId}}: %s\n" % e) +``` diff --git a/cvat-sdk/gen/templates/openapi-generator/api_name.mustache b/cvat-sdk/gen/templates/openapi-generator/api_name.mustache new file mode 100644 index 000000000000..e5ad6154c1d4 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/api_name.mustache @@ -0,0 +1 @@ +%%%make_api_name!!!{{classname}}%%% \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/configuration.mustache b/cvat-sdk/gen/templates/openapi-generator/configuration.mustache new file mode 100644 index 000000000000..cb64afb6126b --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/configuration.mustache @@ -0,0 +1,662 @@ +{{>partial_header}} + +import copy +import logging +{{^asyncio}} +import multiprocessing +{{/asyncio}} +import sys +import typing +import urllib3 + +from http import client as http_client +from {{packageName}}.exceptions import ApiValueError + + +JSON_SCHEMA_VALIDATION_KEYWORDS = { + 'multipleOf', 'maximum', 'exclusiveMaximum', + 'minimum', 'exclusiveMinimum', 'maxLength', + 'minLength', 'pattern', 'maxItems', 'minItems' +} + +class Configuration: + """ + NOTE: This class is auto generated by OpenAPI Generator + + Ref: https://openapi-generator.tech + Do not edit the class manually. + + :param host: Base url + :param api_key: Dict to store API key(s). + Each entry in the dict specifies an API key. + The dict key is the name of the security scheme in the OAS specification. + The dict value is the API key secret. + Supported key names:{{#authMethods}}{{#isApiKey}} + '{{name}}'{{/isApiKey}}{{/authMethods}} + :param api_key_prefix: Dict to store API prefix (e.g. Bearer) + The dict key is the name of the security scheme in the OAS specification. + The dict value is an API key prefix when generating the auth data. + {{#authMethods}}{{#-first}}Default prefixes for API keys:{{/-first}}{{#isApiKey}}{{#vendorExtensions.x-token-prefix}} + {{name}}: '{{.}}'{{/vendorExtensions.x-token-prefix}}{{/isApiKey}}{{/authMethods}} + :param username: Username for HTTP basic authentication + :param password: Password for HTTP basic authentication + :param discard_unknown_keys: Boolean value indicating whether to discard + unknown properties. A server may send a response that includes additional + properties that are not known by the client in the following scenarios: + 1. The OpenAPI document is incomplete, i.e. it does not match the server + implementation. + 2. The client was generated using an older version of the OpenAPI document + and the server has been upgraded since then. + If a schema in the OpenAPI document defines the additionalProperties attribute, + then all undeclared properties received by the server are injected into the + additional properties map. In that case, there are undeclared properties, and + nothing to discard. + :param disabled_client_side_validations (string): Comma-separated list of + JSON schema validation keywords to disable JSON schema structural validation + rules. The following keywords may be specified: multipleOf, maximum, + exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern, + maxItems, minItems. + By default, the validation is performed for data generated locally by the client + and data received from the server, independent of any validation performed by + the server side. If the input data does not satisfy the JSON schema validation + rules specified in the OpenAPI document, an exception is raised. + If disabled_client_side_validations is set, structural validation is + disabled. This can be useful to troubleshoot data validation problem, such as + when the OpenAPI document validation rules do not match the actual API data + received by the server. +{{#hasHttpSignatureMethods}} + :param signing_info: Configuration parameters for the HTTP signature security scheme. + Must be an instance of {{{packageName}}}.signing.HttpSigningConfiguration +{{/hasHttpSignatureMethods}} + :param server_index: Index to servers configuration. + :param server_variables: Mapping with string values to replace variables in + templated server configuration. The validation of enums is performed for + variables with defined enum values before. + :param server_operation_index: Mapping from operation ID to an index to server + configuration. + :param server_operation_variables: Mapping from operation ID to a mapping with + string values to replace variables in templated server configuration. + The validation of enums is performed for variables with defined enum values before. + :param ssl_ca_cert: the path to a file of concatenated CA certificates in PEM format + :param verify_ssl: whether to verify server SSL certificates or not. + +{{#hasAuthMethods}} + :Example: +{{#hasApiKeyMethods}} + + API Key Authentication Example. + + You can authorize with API token after doing the basic auth the following way: + + conf = {{{packageName}}}.Configuration( + ... + api_key={ + "sessionAuth": , + "csrfAuth": , + "tokenAuth": , + } + ) + + You need to specify all the 3 keys for this kind of auth. + + If your custom server uses another token prefix, use the 'api_key_prefix' parameter. + +{{/hasApiKeyMethods}} +{{#hasHttpBasicMethods}} + + HTTP Basic Authentication Example. + Given the following security scheme in the OpenAPI specification: + components: + securitySchemes: + http_basic_auth: + type: http + scheme: basic + + Configure API client with HTTP basic authentication: + + conf = {{{packageName}}}.Configuration( + ..., + username='the-user', + password='the-password', + ) + +{{/hasHttpBasicMethods}} +{{#hasHttpSignatureMethods}} + + HTTP Signature Authentication Example. + Given the following security scheme in the OpenAPI specification: + components: + securitySchemes: + http_basic_auth: + type: http + scheme: signature + + Configure API client with HTTP signature authentication. Use the 'hs2019' signature scheme, + sign the HTTP requests with the RSA-SSA-PSS signature algorithm, and set the expiration time + of the signature to 5 minutes after the signature has been created. + Note you can use the constants defined in the {{{packageName}}}.signing module, and you can + also specify arbitrary HTTP headers to be included in the HTTP signature, except for the + 'Authorization' header, which is used to carry the signature. + + One may be tempted to sign all headers by default, but in practice it rarely works. + This is because explicit proxies, transparent proxies, TLS termination endpoints or + load balancers may add/modify/remove headers. Include the HTTP headers that you know + are not going to be modified in transit. + + conf = {{{packageName}}}.Configuration( + signing_info = {{{packageName}}}.signing.HttpSigningConfiguration( + key_id = 'my-key-id', + private_key_path = 'rsa.pem', + signing_scheme = {{{packageName}}}.signing.SCHEME_HS2019, + signing_algorithm = {{{packageName}}}.signing.ALGORITHM_RSASSA_PSS, + signed_headers = [{{{packageName}}}.signing.HEADER_REQUEST_TARGET, + {{{packageName}}}.signing.HEADER_CREATED, + {{{packageName}}}.signing.HEADER_EXPIRES, + {{{packageName}}}.signing.HEADER_HOST, + {{{packageName}}}.signing.HEADER_DATE, + {{{packageName}}}.signing.HEADER_DIGEST, + 'Content-Type', + 'User-Agent' + ], + signature_max_validity = datetime.timedelta(minutes=5) + ) +) +{{/hasHttpSignatureMethods}} +{{/hasAuthMethods}} + """ + + _default = None + + def __init__(self, + host: typing.Optional[str] = None, + api_key: typing.Optional[typing.Dict[str, str]] = None, + api_key_prefix: typing.Optional[typing.Dict[str, str]] = None, + username: typing.Optional[str] = None, + password: typing.Optional[str]=None, + discard_unknown_keys: bool = False, + disabled_client_side_validations: str = "", +{{#hasHttpSignatureMethods}} + signing_info=None, +{{/hasHttpSignatureMethods}} + server_index: typing.Optional[int] = None, + server_variables: typing.Optional[typing.Dict[str, str]] = None, + server_operation_index: typing.Optional[int] = None, + server_operation_variables: typing.Optional[typing.Dict[str, str]] = None, + ssl_ca_cert: typing.Optional[str] = None, + verify_ssl: typing.Optional[bool] = None, + ) -> None: + self._base_path = self._fix_host_url("{{{basePath}}}" if host is None else host) + """Default Base url""" + + self.server_index = 0 if server_index is None and host is None else server_index + """Default server index""" + + self.server_operation_index = server_operation_index or {} + """Default server operation index""" + + self.server_variables = server_variables or {} + """Default server variables""" + + self.server_operation_variables = server_operation_variables or {} + """Default server variables""" + + self.temp_folder_path = None + """Temp file folder for downloading files""" + + # Authentication Settings + self.access_token = None + """Bearer API token""" + + self.api_key = {} + """dict to store API key(s)""" + if api_key: + self.api_key = api_key + + self.api_key_prefix = { {{#authMethods}}{{#isApiKey}}{{#vendorExtensions.x-token-prefix}} + '{{name}}': '{{.}}'{{/vendorExtensions.x-token-prefix}}{{/isApiKey}}{{/authMethods}} + } + """dict to store API prefix (e.g. Bearer)""" + if api_key_prefix: + self.api_key_prefix.update(api_key_prefix) + + self.refresh_api_key_hook = None + """function hook to refresh API key if expired""" + + self.username = username + """Username for HTTP basic authentication""" + + self.password = password + """Password for HTTP basic authentication""" + + self.discard_unknown_keys = discard_unknown_keys + """A flag to control unknown key deserialization behaviour""" + + self.disabled_client_side_validations = disabled_client_side_validations + """A flag to enable or disable specific model field validation in the client""" + +{{#hasHttpSignatureMethods}} + if signing_info is not None: + signing_info.host = host + self.signing_info = signing_info + """The HTTP signing configuration + """ +{{/hasHttpSignatureMethods}} + self.logger = {} + """Logging Settings""" + + self.logger["package_logger"] = logging.getLogger("{{packageName}}") + self.logger["urllib3_logger"] = logging.getLogger("urllib3") + + self.logger_format = '%(asctime)s %(levelname)s %(message)s' + """Log format""" + + self.logger_stream_handler = None + """Log stream handler""" + + self.logger_file_handler = None + """Log file handler""" + + self.logger_file = None + """Debug file location""" + + self.debug = False + """Debug switch""" + + self.verify_ssl = verify_ssl if verify_ssl is not None else True + """ + SSL/TLS verification + Set this to false to skip verifying SSL certificate when calling API + from https server. + """ + + self.ssl_ca_cert = ssl_ca_cert + """Set this to customize the certificate file to verify the peer.""" + + self.cert_file = None + """client certificate file""" + + self.key_file = None + """client key file""" + + self.assert_hostname = None + """Set this to True/False to enable/disable SSL hostname verification.""" + + {{#asyncio}} + self.connection_pool_maxsize = 100 + """This value is passed to the aiohttp to limit simultaneous connections. + Default values is 100, None means no-limit. + """ + {{/asyncio}} + {{^asyncio}} + self.connection_pool_maxsize = multiprocessing.cpu_count() * 5 + """urllib3 connection pool's maximum number of connections saved + per pool. urllib3 uses 1 connection as default value, but this is + not the best value when you are making a lot of possibly parallel + requests to the same host, which is often the case here. + cpu_count * 5 is used as default value to increase performance. + """ + {{/asyncio}} + + self.proxy = None + """Proxy URL""" + + self.no_proxy = None + """bypass proxy for host in the no_proxy list.""" + + self.proxy_headers = None + """Proxy headers""" + + self.safe_chars_for_path_param = '' + """Safe chars for path_param""" + + self.retries = None + """Adding retries to override urllib3 default value 3""" + + # Enable client side validation + self.client_side_validation = True + + # Options to pass down to the underlying urllib3 socket + self.socket_options = None + + def __deepcopy__(self, memo): + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result + for k, v in self.__dict__.items(): + if k not in ('logger', 'logger_file_handler'): + setattr(result, k, copy.deepcopy(v, memo)) + # shallow copy of loggers + result.logger = copy.copy(self.logger) + # use setters to configure loggers + result.logger_file = self.logger_file + result.debug = self.debug + return result + + def __setattr__(self, name, value): + object.__setattr__(self, name, value) + if name == 'disabled_client_side_validations': + s = set(filter(None, value.split(','))) + for v in s: + if v not in JSON_SCHEMA_VALIDATION_KEYWORDS: + raise ApiValueError( + "Invalid keyword: '{0}''".format(v)) + self._disabled_client_side_validations = s +{{#hasHttpSignatureMethods}} + if name == "signing_info" and value is not None: + # Ensure the host parameter from signing info is the same as + # Configuration.host. + value.host = self.host +{{/hasHttpSignatureMethods}} + + @classmethod + def set_default(cls, default): + """Set default instance of configuration. + + It stores default configuration, which can be + returned by get_default_copy method. + + :param default: object of Configuration + """ + cls._default = copy.deepcopy(default) + + @classmethod + def get_default_copy(cls): + """Return new instance of configuration. + + This method returns newly created, based on default constructor, + object of Configuration class or returns a copy of default + configuration passed by the set_default method. + + :return: The configuration object. + """ + if cls._default is not None: + return copy.deepcopy(cls._default) + return Configuration() + + @property + def logger_file(self): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + return self.__logger_file + + @logger_file.setter + def logger_file(self, value): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + self.__logger_file = value + if self.__logger_file: + # If set logging file, + # then add file handler and remove stream handler. + self.logger_file_handler = logging.FileHandler(self.__logger_file) + self.logger_file_handler.setFormatter(self.logger_formatter) + for _, logger in self.logger.items(): + logger.addHandler(self.logger_file_handler) + + @property + def debug(self): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + return self.__debug + + @debug.setter + def debug(self, value): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + self.__debug = value + if self.__debug: + # if debug status is True, turn on debug logging + for _, logger in self.logger.items(): + logger.setLevel(logging.DEBUG) + # turn on http_client debug + http_client.HTTPConnection.debuglevel = 1 + else: + # if debug status is False, turn off debug logging, + # setting log level to default `logging.WARNING` + for _, logger in self.logger.items(): + logger.setLevel(logging.WARNING) + # turn off http_client debug + http_client.HTTPConnection.debuglevel = 0 + + @property + def logger_format(self): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + return self.__logger_format + + @logger_format.setter + def logger_format(self, value): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + self.__logger_format = value + self.logger_formatter = logging.Formatter(self.__logger_format) + + def get_api_key_with_prefix(self, identifier: str, *, + alias: typing.Optional[str] = None + ) -> typing.Optional[str]: + """Gets API key (with prefix if set). + + :param identifier: The identifier of apiKey. + :param alias: The alternative identifier of apiKey. + :return: The token for api key authentication. + """ + if self.refresh_api_key_hook is not None: + self.refresh_api_key_hook(self) + key = self.api_key.get(identifier, self.api_key.get(alias) if alias is not None else None) + if key: + prefix = self.api_key_prefix.get(identifier) + if prefix: + return "%s %s" % (prefix, key) + else: + return key + + def get_basic_auth_token(self): + """Gets HTTP basic authentication header (string). + + :return: The token for basic HTTP authentication. + """ + username = "" + if self.username is not None: + username = self.username + password = "" + if self.password is not None: + password = self.password + return urllib3.util.make_headers( + basic_auth=username + ':' + password + ).get('authorization') + + def auth_settings(self): + """Gets Auth Settings dict for api client. + + :return: The Auth Settings information dict. + """ + auth = {} +{{#authMethods}} +{{#isApiKey}} + if '{{name}}' in self.api_key{{#vendorExtensions.x-auth-id-alias}} or '{{.}}' in self.api_key{{/vendorExtensions.x-auth-id-alias}}: + auth['{{name}}'] = { + 'type': 'api_key', + 'in': {{#isKeyInCookie}}'cookie'{{/isKeyInCookie}}{{#isKeyInHeader}}'header'{{/isKeyInHeader}}{{#isKeyInQuery}}'query'{{/isKeyInQuery}}, + 'key': '{{keyParamName}}', + 'value': self.get_api_key_with_prefix( + '{{name}}',{{#vendorExtensions.x-auth-id-alias}} + alias='{{.}}',{{/vendorExtensions.x-auth-id-alias}} + ), + } +{{/isApiKey}} +{{#isBasic}} + {{#isBasicBasic}} + if self.username is not None and self.password is not None: + auth['{{name}}'] = { + 'type': 'basic', + 'in': 'header', + 'key': 'Authorization', + 'value': self.get_basic_auth_token() + } + {{/isBasicBasic}} + {{#isBasicBearer}} + if self.access_token is not None: + auth['{{name}}'] = { + 'type': 'bearer', + 'in': 'header', + {{#bearerFormat}} + 'format': '{{{.}}}', + {{/bearerFormat}} + 'key': 'Authorization', + 'value': 'Bearer ' + self.access_token + } + {{/isBasicBearer}} + {{#isHttpSignature}} + if self.signing_info is not None: + auth['{{name}}'] = { + 'type': 'http-signature', + 'in': 'header', + 'key': 'Authorization', + 'value': None # Signature headers are calculated for every HTTP request + } + {{/isHttpSignature}} +{{/isBasic}} +{{#isOAuth}} + if self.access_token is not None: + auth['{{name}}'] = { + 'type': 'oauth2', + 'in': 'header', + 'key': 'Authorization', + 'value': 'Bearer ' + self.access_token + } +{{/isOAuth}} +{{/authMethods}} + return auth + + def to_debug_report(self): + """Gets the essential information for debugging. + + :return: The report for debugging. + """ + return "Python SDK Debug Report:\n"\ + "OS: {env}\n"\ + "Python Version: {pyversion}\n"\ + "Version of the API: {{version}}\n"\ + "SDK Package Version: {{packageVersion}}".\ + format(env=sys.platform, pyversion=sys.version) + + def get_host_settings(self): + """Gets an array of host settings + + :return: An array of host settings + """ + return [ + {{#servers}} + { + 'url': "{{{url}}}", + 'description': "{{{description}}}{{^description}}No description provided{{/description}}", + {{#variables}} + {{#-first}} + 'variables': { + {{/-first}} + '{{{name}}}': { + 'description': "{{{description}}}{{^description}}No description provided{{/description}}", + 'default_value': "{{{defaultValue}}}", + {{#enumValues}} + {{#-first}} + 'enum_values': [ + {{/-first}} + "{{{.}}}"{{^-last}},{{/-last}} + {{#-last}} + ] + {{/-last}} + {{/enumValues}} + }{{^-last}},{{/-last}} + {{#-last}} + } + {{/-last}} + {{/variables}} + }{{^-last}},{{/-last}} + {{/servers}} + ] + + def get_host_from_settings(self, index, variables=None, servers=None): + """Gets host URL based on the index and variables + :param index: array index of the host settings + :param variables: hash of variable and the corresponding value + :param servers: an array of host settings or None + :return: URL based on host settings + """ + if index is None: + return self._base_path + + variables = {} if variables is None else variables + servers = self.get_host_settings() if servers is None else servers + + try: + server = servers[index] + except IndexError: + raise ValueError( + "Invalid index {0} when selecting the host settings. " + "Must be less than {1}".format(index, len(servers))) + + url = server['url'] + + # go through variables and replace placeholders + for variable_name, variable in server.get('variables', {}).items(): + used_value = variables.get( + variable_name, variable['default_value']) + + if 'enum_values' in variable \ + and used_value not in variable['enum_values']: + raise ValueError( + "The variable `{0}` in the host URL has invalid value " + "{1}. Must be {2}.".format( + variable_name, variables[variable_name], + variable['enum_values'])) + + url = url.replace("{" + variable_name + "}", used_value) + + return url + + @property + def host(self): + """Return generated host.""" + return self.get_host_from_settings(self.server_index, variables=self.server_variables) + + @host.setter + def host(self, value): + """Fix base path.""" + self._base_path = value + self.server_index = None + + @staticmethod + def _fix_host_url(url): + url = url.rstrip("/") + + if not (url.startswith("http://") or url.startswith("https://")): + url = "http://" + url + + return url diff --git a/cvat-sdk/gen/templates/openapi-generator/model.mustache b/cvat-sdk/gen/templates/openapi-generator/model.mustache new file mode 100644 index 000000000000..3a063f330ae8 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model.mustache @@ -0,0 +1,69 @@ +{{> partial_header }} + +from __future__ import annotations + +import typing + +import re # noqa: F401 +import sys # noqa: F401 + +from {{packageName}}.model_utils import ( # noqa: F401 + ApiTypeError, + IModelData, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, + OpenApiModel +) +from {{packageName}}.exceptions import ApiAttributeError + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + # Enable introspection. Can't work normally due to cyclic imports + from {{packageName}}.apis import * + from {{packageName}}.models import * + +{{#models}} +{{#model}} +{{#imports}} +{{#-first}} + +def lazy_import(): +{{/-first}} + {{{.}}} +{{/imports}} + + +{{^interfaces}} +{{#isArray}} +{{> model_templates/model_simple }} +{{/isArray}} +{{#isEnum}} +{{> model_templates/model_simple }} +{{/isEnum}} +{{#isAlias}} +{{> model_templates/model_simple }} +{{/isAlias}} +{{^isArray}} +{{^isEnum}} +{{^isAlias}} +{{> model_templates/model_normal }} +{{/isAlias}} +{{/isEnum}} +{{/isArray}} +{{/interfaces}} +{{#interfaces}} +{{#-last}} +{{> model_templates/model_composed }} +{{/-last}} +{{/interfaces}} +{{/model}} +{{/models}} diff --git a/cvat-sdk/gen/templates/openapi-generator/model_doc.mustache b/cvat-sdk/gen/templates/openapi-generator/model_doc.mustache new file mode 100644 index 000000000000..94d13c14e327 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_doc.mustache @@ -0,0 +1,32 @@ +{{#models}}{{#model}}# {{classname}} + +{{#description}}{{&description}} +{{/description}} + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#isEnum}} +**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}}{{#allowableValues}}{{#defaultValue}}, {{/defaultValue}} must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} +{{/isEnum}} +{{#isAlias}} +**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}} +{{/isAlias}} +{{#isArray}} +**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}}{{#arrayModelType}}[**{{dataType}}**]({{arrayModelType}}){{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}} +{{/isArray}} +{{#requiredVars}} +{{^defaultValue}} +**{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}){{/complexType}} | {{description}} | {{#isReadOnly}}[readonly] {{/isReadOnly}} +{{/defaultValue}} +{{/requiredVars}} +{{#requiredVars}} +{{#defaultValue}} +**{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}){{/complexType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}defaults to {{{.}}}{{/defaultValue}} +{{/defaultValue}} +{{/requiredVars}} +{{#optionalVars}} +**{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}){{/complexType}} | {{description}} | [optional] {{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} +{{/optionalVars}} + +{{/model}}{{/models}} diff --git a/cvat-sdk/gen/templates/openapi-generator/model_templates/classvars.mustache b/cvat-sdk/gen/templates/openapi-generator/model_templates/classvars.mustache new file mode 100644 index 000000000000..01acbc458522 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_templates/classvars.mustache @@ -0,0 +1,138 @@ + allowed_values = { +{{#isEnum}} + ('value',): { +{{#isNullable}} + 'None': None, +{{/isNullable}} +{{#allowableValues}} +{{#enumVars}} + '{{name}}': {{{value}}}, +{{/enumVars}} +{{/allowableValues}} + }, +{{/isEnum}} +{{#requiredVars}} +{{#isEnum}} + ('{{name}}',): { +{{#isNullable}} + 'None': None, +{{/isNullable}} +{{#allowableValues}} +{{#enumVars}} + '{{name}}': {{{value}}}, +{{/enumVars}} +{{/allowableValues}} + }, +{{/isEnum}} +{{/requiredVars}} +{{#optionalVars}} +{{#isEnum}} + ('{{name}}',): { +{{#isNullable}} + 'None': None, +{{/isNullable}} +{{#allowableValues}} +{{#enumVars}} + '{{name}}': {{{value}}}, +{{/enumVars}} +{{/allowableValues}} + }, +{{/isEnum}} +{{/optionalVars}} + } + + validations = { +{{#hasValidation}} + ('value',): { +{{> model_templates/validations }} +{{/hasValidation}} +{{#requiredVars}} +{{#hasValidation}} + ('{{name}}',): { +{{> model_templates/validations }} +{{/hasValidation}} +{{/requiredVars}} +{{#optionalVars}} +{{#hasValidation}} + ('{{name}}',): { +{{> model_templates/validations }} +{{/hasValidation}} +{{/optionalVars}} + } + +{{#additionalProperties}} + @cached_property + def additional_properties_type(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + """ +{{#imports}} +{{#-first}} + lazy_import() +{{/-first}} +{{/imports}} + return ({{{dataType}}},) # noqa: E501 +{{/additionalProperties}} +{{^additionalProperties}} + additional_properties_type = None +{{/additionalProperties}} + + _nullable = {{#isNullable}}True{{/isNullable}}{{^isNullable}}False{{/isNullable}} + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ +{{#imports}} +{{#-first}} + lazy_import() +{{/-first}} +{{/imports}} + return { +{{#isAlias}} + 'value': ({{{dataType}}},), +{{/isAlias}} +{{#isEnum}} + 'value': ({{{dataType}}},), +{{/isEnum}} +{{#isArray}} + 'value': ({{{dataType}}},), +{{/isArray}} +{{#requiredVars}} + '{{name}}': ({{{dataType}}},), # noqa: E501 +{{/requiredVars}} +{{#optionalVars}} + '{{name}}': ({{{dataType}}},), # noqa: E501 +{{/optionalVars}} + } + + @cached_property + def discriminator(): +{{^discriminator}} + return None +{{/discriminator}} +{{#discriminator}} +{{#mappedModels}} +{{#-first}} +{{#imports}} +{{#-first}} + lazy_import() +{{/-first}} +{{/imports}} +{{/-first}} +{{/mappedModels}} + val = { +{{#mappedModels}} + '{{mappingName}}': {{{modelName}}}, +{{/mappedModels}} + } + if not val: + return None + return {'{{{discriminatorName}}}': val}{{/discriminator}} diff --git a/cvat-sdk/gen/templates/openapi-generator/model_templates/method_from_openapi_data_composed.mustache b/cvat-sdk/gen/templates/openapi-generator/model_templates/method_from_openapi_data_composed.mustache new file mode 100644 index 000000000000..97d3cb930c27 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_templates/method_from_openapi_data_composed.mustache @@ -0,0 +1,80 @@ + @classmethod + @convert_js_args_to_python_args + {{#initRequiredVars}} + def _from_openapi_data(cls{{#requiredVars}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/requiredVars}}, *args, **kwargs) -> {{classname}}: # noqa: E501 + {{/initRequiredVars}} + {{^initRequiredVars}} + def _from_openapi_data(cls, *args, **kwargs) -> {{classname}}: # noqa: E501 + {{/initRequiredVars}} + """{{classname}} - a model defined in OpenAPI + + Keyword Args: +{{#requiredVars}} +{{#defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 +{{/defaultValue}} +{{^defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}{{/description}} +{{/defaultValue}} +{{/requiredVars}} +{{> model_templates/docstring_init_required_kwargs }} +{{#optionalVars}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} # noqa: E501 +{{/optionalVars}} + """ + from {{packageName}}.configuration import Configuration + +{{#requiredVars}} +{{#defaultValue}} + {{name}} = kwargs.get('{{name}}', {{{defaultValue}}}) +{{/defaultValue}} +{{/requiredVars}} + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', Configuration()) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + self = super(OpenApiModel, cls).__new__(cls) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + constant_args = { + '_check_type': _check_type, + '_path_to_item': _path_to_item, + '_spec_property_naming': _spec_property_naming, + '_configuration': _configuration, + '_visited_composed_classes': self._visited_composed_classes, + } + {{#initRequiredVars}} + required_args = { +{{#requiredVars}} + '{{name}}': {{name}}, +{{/requiredVars}} + } + kwargs.update(required_args) + {{/initRequiredVars}} + composed_info = validate_get_composed_info( + constant_args, kwargs, self) + self._composed_instances = composed_info[0] + self._var_name_to_model_instances = composed_info[1] + self._additional_properties_model_instances = composed_info[2] + discarded_args = composed_info[3] + + for var_name, var_value in kwargs.items(): + if var_name in discarded_args and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self._additional_properties_model_instances: + # discard variable. + continue + setattr(self, var_name, var_value) + + return self \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/model_templates/method_from_openapi_data_shared.mustache b/cvat-sdk/gen/templates/openapi-generator/model_templates/method_from_openapi_data_shared.mustache new file mode 100644 index 000000000000..4c149f22ce88 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_templates/method_from_openapi_data_shared.mustache @@ -0,0 +1,52 @@ + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls{{#requiredVars}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/requiredVars}}, *args, **kwargs): # noqa: E501 + """{{classname}} - a model defined in OpenAPI + +{{#requiredVars}} +{{#-first}} + Args: +{{/-first}} +{{^defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}{{/description}} +{{/defaultValue}} +{{#-last}} + +{{/-last}} +{{/requiredVars}} + Keyword Args: +{{#requiredVars}} +{{#defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 + +{{/defaultValue}} +{{/requiredVars}} +{{#optionalVars}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} # noqa: E501 + +{{/optionalVars}} +{{> model_templates/docstring_init_required_kwargs }} +""" + from {{packageName}}.configuration import Configuration + +{{#requiredVars}} +{{#defaultValue}} + {{name}} = kwargs.get('{{name}}', {{{defaultValue}}}) +{{/defaultValue}} +{{/requiredVars}} + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', True) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', Configuration()) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + self = super(OpenApiModel, cls).__new__(cls) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/model_templates/method_from_openapi_data_simple.mustache b/cvat-sdk/gen/templates/openapi-generator/model_templates/method_from_openapi_data_simple.mustache new file mode 100644 index 000000000000..853532e9f5ca --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_templates/method_from_openapi_data_simple.mustache @@ -0,0 +1,64 @@ + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls, *args, **kwargs): + """{{classname}} - a model defined in OpenAPI + + Note that value can be passed either in args or in kwargs, but not in both. + + Args: + args[0] ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{.}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 + + Keyword Args: + value ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{.}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 +{{> model_templates/docstring_init_required_kwargs }} + """ + from {{packageName}}.configuration import Configuration + + # required up here when default value is not given + _path_to_item = kwargs.pop('_path_to_item', ()) + + self = super(OpenApiModel, cls).__new__(cls) + + if 'value' in kwargs: + value = kwargs.pop('value') + elif args: + args = list(args) + value = args.pop(0) +{{#defaultValue}} + else: + value = {{{.}}} +{{/defaultValue}} +{{^defaultValue}} + else: + raise ApiTypeError( + "value is required, but not passed in args or kwargs and doesn't have default", + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) +{{/defaultValue}} + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _configuration = kwargs.pop('_configuration', Configuration()) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + self.value = value + if kwargs: + raise ApiTypeError( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % ( + kwargs, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + return self \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/model_templates/method_init_shared.mustache b/cvat-sdk/gen/templates/openapi-generator/model_templates/method_init_shared.mustache new file mode 100644 index 000000000000..998b4841b7e7 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_templates/method_init_shared.mustache @@ -0,0 +1,55 @@ + @convert_js_args_to_python_args + def __init__(self{{#requiredVars}}{{^isReadOnly}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/isReadOnly}}{{/requiredVars}}, *args, **kwargs): # noqa: E501 + """{{classname}} - a model defined in OpenAPI + +{{#requiredVars}} +{{^isReadOnly}} +{{#-first}} + Args: +{{/-first}} +{{^defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}{{/description}} +{{/defaultValue}} +{{#-last}} + +{{/-last}} +{{/isReadOnly}} +{{/requiredVars}} + Keyword Args: +{{#requiredVars}} +{{^isReadOnly}} +{{#defaultValue}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 + +{{/defaultValue}} +{{/isReadOnly}} +{{/requiredVars}} +{{#optionalVars}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} # noqa: E501 + +{{/optionalVars}} +{{> model_templates/docstring_init_required_kwargs }} +""" + from {{packageName}}.configuration import Configuration + +{{#requiredVars}} +{{^isReadOnly}} +{{#defaultValue}} + {{name}} = kwargs.get('{{name}}', {{{defaultValue}}}) +{{/defaultValue}} +{{/isReadOnly}} +{{/requiredVars}} + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', Configuration()) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/model_templates/method_init_simple.mustache b/cvat-sdk/gen/templates/openapi-generator/model_templates/method_init_simple.mustache new file mode 100644 index 000000000000..8c8b42ce1f49 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_templates/method_init_simple.mustache @@ -0,0 +1,68 @@ + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, *args, **kwargs): + """{{classname}} - a model defined in OpenAPI + + Note that value can be passed either in args or in kwargs, but not in both. + + Args: + args[0] ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{.}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 + + Keyword Args: + value ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{.}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 +{{> model_templates/docstring_init_required_kwargs }} + """ + from {{packageName}}.configuration import Configuration + + # required up here when default value is not given + _path_to_item = kwargs.pop('_path_to_item', ()) + + if 'value' in kwargs: + value = kwargs.pop('value') + elif args: + args = list(args) + value = args.pop(0) +{{#defaultValue}} + else: + value = {{{.}}} +{{/defaultValue}} +{{^defaultValue}} + else: + raise ApiTypeError( + "value is required, but not passed in args or kwargs and doesn't have default", + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) +{{/defaultValue}} + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _configuration = kwargs.pop('_configuration', Configuration()) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + +{{> model_templates/invalid_pos_args }} + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + self.value = value + if kwargs: + raise ApiTypeError( + "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % ( + kwargs, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/model_templates/model_normal.mustache b/cvat-sdk/gen/templates/openapi-generator/model_templates/model_normal.mustache new file mode 100644 index 000000000000..73ef8d59903f --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_templates/model_normal.mustache @@ -0,0 +1,77 @@ + +class I{{classname}}(IModelData): + """ + NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + +{{#vars}} + {{#-first}} + # member type declarations + {{/-first}} + {{name}}: {{> model_templates/type_annotation_cleaned }} # noqa: E501 + """{{^required}} + [optional{{#defaultValue}}, default: {{{.}}}{{/defaultValue}}]{{/required}}{{#isContainer}} + {{{dataType}}}{{/isContainer}}{{#description}} + {{{.}}}.{{/description}} + """ + +{{/vars}} + +class {{classname}}(ModelNormal, I{{classname}}): + """ + NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: +{{#requiredVars}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}{{/description}} + +{{/requiredVars}} +{{#optionalVars}} + {{name}} ({{{dataType}}}):{{#description}} {{{.}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{.}}}{{/defaultValue}} # noqa: E501 + +{{/optionalVars}} + +{{> model_templates/docstring_allowed }} + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. +{{> model_templates/docstring_openapi_validations }} + + """ + +{{> model_templates/classvars }} + + attribute_map = { +{{#requiredVars}} + '{{name}}': '{{baseName}}', # noqa: E501 +{{/requiredVars}} +{{#optionalVars}} + '{{name}}': '{{baseName}}', # noqa: E501 +{{/optionalVars}} + } + + read_only_vars = { +{{#requiredVars}} +{{#isReadOnly}} + '{{name}}', # noqa: E501 +{{/isReadOnly}} +{{/requiredVars}} +{{#optionalVars}} +{{#isReadOnly}} + '{{name}}', # noqa: E501 +{{/isReadOnly}} +{{/optionalVars}} + } + + _composed_schemas = {} + +{{> model_templates/method_from_openapi_data_normal}} + +{{> model_templates/method_init_normal}} diff --git a/cvat-sdk/gen/templates/openapi-generator/model_templates/model_simple.mustache b/cvat-sdk/gen/templates/openapi-generator/model_templates/model_simple.mustache new file mode 100644 index 000000000000..66487bf47ad1 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_templates/model_simple.mustache @@ -0,0 +1,38 @@ + +class I{{classname}}(IModelData): + """ + NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + value: {{> model_templates/type_annotation_cleaned}} + """{{^required}} + [optional{{#defaultValue}}, default: {{{.}}}{{/defaultValue}}]{{/required}} + + {{#allowableValues}}One of: {{#enumVars}}{{#-first}}{{{value}}}{{/-first}}{{^-first}}, {{{value}}}{{/-first}}{{/enumVars}}{{/allowableValues}} # noqa: E501 + """ + +class {{classname}}(ModelSimple, I{{classname}}): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: +{{> model_templates/docstring_allowed }} +{{> model_templates/docstring_openapi_validations }} + """ + +{{> model_templates/classvars }} + + attribute_map = {} + + read_only_vars = set() + + _composed_schemas = None + +{{> model_templates/method_init_simple}} + +{{> model_templates/method_from_openapi_data_simple}} \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/model_templates/type_annotation_cleaned.mustache b/cvat-sdk/gen/templates/openapi-generator/model_templates/type_annotation_cleaned.mustache new file mode 100644 index 000000000000..e45d493174fe --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_templates/type_annotation_cleaned.mustache @@ -0,0 +1 @@ +%%%make_type_annotation!!!{{{dataType}}}%%% \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/model_utils.mustache b/cvat-sdk/gen/templates/openapi-generator/model_utils.mustache new file mode 100644 index 000000000000..09887e414b2e --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/model_utils.mustache @@ -0,0 +1,1791 @@ +{{>partial_header}} + +from datetime import date, datetime # noqa: F401 +from copy import deepcopy +import inspect +import io +import os +import pprint +import re +import tempfile +import uuid + +from dateutil.parser import parse + +from {{packageName}}.exceptions import ( + ApiKeyError, + ApiAttributeError, + ApiTypeError, + ApiValueError, +) + +none_type = type(None) +file_type = io.IOBase + + +def convert_js_args_to_python_args(fn): + from functools import wraps + @wraps(fn) + def wrapped_init(_self, *args, **kwargs): + """ + An attribute named `self` received from the api will conflicts with the reserved `self` + parameter of a class method. During generation, `self` attributes are mapped + to `_self` in models. Here, we name `_self` instead of `self` to avoid conflicts. + """ + spec_property_naming = kwargs.get('_spec_property_naming', False) + if spec_property_naming: + kwargs = change_keys_js_to_python( + kwargs, _self if isinstance( + _self, type) else _self.__class__) + return fn(_self, *args, **kwargs) + return wrapped_init + + +class cached_property(object): + # this caches the result of the function call for fn with no inputs + # use this as a decorator on function methods that you want converted + # into cached properties + result_key = '_results' + + def __init__(self, fn): + self._fn = fn + + def __get__(self, instance, cls=None): + if self.result_key in vars(self): + return vars(self)[self.result_key] + else: + result = self._fn() + setattr(self, self.result_key, result) + return result + + +PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type) + + +def allows_single_value_input(cls): + """ + This function returns True if the input composed schema model or any + descendant model allows a value only input + This is true for cases where oneOf contains items like: + oneOf: + - float + - NumberWithValidation + - StringEnum + - ArrayModel + - null + TODO: lru_cache this + """ + if ( + issubclass(cls, ModelSimple) or + cls in PRIMITIVE_TYPES + ): + return True + elif issubclass(cls, ModelComposed): + if not cls._composed_schemas['oneOf']: + return False + return any(allows_single_value_input(c) for c in cls._composed_schemas['oneOf']) + return False + + +def composed_model_input_classes(cls): + """ + This function returns a list of the possible models that can be accepted as + inputs. + TODO: lru_cache this + """ + if issubclass(cls, ModelSimple) or cls in PRIMITIVE_TYPES: + return [cls] + elif issubclass(cls, ModelNormal): + if cls.discriminator is None: + return [cls] + else: + return get_discriminated_classes(cls) + elif issubclass(cls, ModelComposed): + if not cls._composed_schemas['oneOf']: + return [] + if cls.discriminator is None: + input_classes = [] + for c in cls._composed_schemas['oneOf']: + input_classes.extend(composed_model_input_classes(c)) + return input_classes + else: + return get_discriminated_classes(cls) + return [] + + +class IModelData: + """ + The base class for model data. Declares model fields and their types for better introspection + """ + +class OpenApiModel(object): + """The base class for all OpenAPIModels""" + +{{> model_templates/method_set_attribute }} + +{{> model_templates/methods_shared }} + + def __new__(cls, *args, **kwargs): + # this function uses the discriminator to + # pick a new schema/class to instantiate because a discriminator + # propertyName value was passed in + + if len(args) == 1: + arg = args[0] + if arg is None and is_type_nullable(cls): + # The input data is the 'null' value and the type is nullable. + return None + + if issubclass(cls, ModelComposed) and allows_single_value_input(cls): + model_kwargs = {} + oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg) + return oneof_instance + + visited_composed_classes = kwargs.get('_visited_composed_classes', ()) + if ( + cls.discriminator is None or + cls in visited_composed_classes + ): + # Use case 1: this openapi schema (cls) does not have a discriminator + # Use case 2: we have already visited this class before and are sure that we + # want to instantiate it this time. We have visited this class deserializing + # a payload with a discriminator. During that process we traveled through + # this class but did not make an instance of it. Now we are making an + # instance of a composed class which contains cls in it, so this time make an instance of cls. + # + # Here's an example of use case 2: If Animal has a discriminator + # petType and we pass in "Dog", and the class Dog + # allOf includes Animal, we move through Animal + # once using the discriminator, and pick Dog. + # Then in the composed schema dog Dog, we will make an instance of the + # Animal class (because Dal has allOf: Animal) but this time we won't travel + # through Animal's discriminator because we passed in + # _visited_composed_classes = (Animal,) + + return super(OpenApiModel, cls).__new__(cls) + + # Get the name and value of the discriminator property. + # The discriminator name is obtained from the discriminator meta-data + # and the discriminator value is obtained from the input data. + discr_propertyname_py = list(cls.discriminator.keys())[0] + discr_propertyname_js = cls.attribute_map[discr_propertyname_py] + if discr_propertyname_js in kwargs: + discr_value = kwargs[discr_propertyname_js] + elif discr_propertyname_py in kwargs: + discr_value = kwargs[discr_propertyname_py] + else: + # The input data does not contain the discriminator property. + path_to_item = kwargs.get('_path_to_item', ()) + raise ApiValueError( + "Cannot deserialize input data due to missing discriminator. " + "The discriminator property '%s' is missing at path: %s" % + (discr_propertyname_js, path_to_item) + ) + + # Implementation note: the last argument to get_discriminator_class + # is a list of visited classes. get_discriminator_class may recursively + # call itself and update the list of visited classes, and the initial + # value must be an empty list. Hence not using 'visited_composed_classes' + new_cls = get_discriminator_class( + cls, discr_propertyname_py, discr_value, []) + if new_cls is None: + path_to_item = kwargs.get('_path_to_item', ()) + disc_prop_value = kwargs.get( + discr_propertyname_js, kwargs.get(discr_propertyname_py)) + raise ApiValueError( + "Cannot deserialize input data due to invalid discriminator " + "value. The OpenAPI document has no mapping for discriminator " + "property '%s'='%s' at path: %s" % + (discr_propertyname_js, disc_prop_value, path_to_item) + ) + + if new_cls in visited_composed_classes: + # if we are making an instance of a composed schema Descendent + # which allOf includes Ancestor, then Ancestor contains + # a discriminator that includes Descendent. + # So if we make an instance of Descendent, we have to make an + # instance of Ancestor to hold the allOf properties. + # This code detects that use case and makes the instance of Ancestor + # For example: + # When making an instance of Dog, _visited_composed_classes = (Dog,) + # then we make an instance of Animal to include in dog._composed_instances + # so when we are here, cls is Animal + # cls.discriminator != None + # cls not in _visited_composed_classes + # new_cls = Dog + # but we know we know that we already have Dog + # because it is in visited_composed_classes + # so make Animal here + return super(OpenApiModel, cls).__new__(cls) + + # Build a list containing all oneOf and anyOf descendants. + oneof_anyof_classes = None + if cls._composed_schemas is not None: + oneof_anyof_classes = ( + cls._composed_schemas.get('oneOf', ()) + + cls._composed_schemas.get('anyOf', ())) + oneof_anyof_child = new_cls in oneof_anyof_classes + kwargs['_visited_composed_classes'] = visited_composed_classes + (cls,) + + if cls._composed_schemas.get('allOf') and oneof_anyof_child: + # Validate that we can make self because when we make the + # new_cls it will not include the allOf validations in self + self_inst = super(OpenApiModel, cls).__new__(cls) + self_inst.__init__(*args, **kwargs) + + if kwargs.get("_spec_property_naming", False): + # when true, implies new is from deserialization + new_inst = new_cls._new_from_openapi_data(*args, **kwargs) + else: + new_inst = new_cls.__new__(new_cls, *args, **kwargs) + new_inst.__init__(*args, **kwargs) + + return new_inst + + @classmethod + @convert_js_args_to_python_args + def _new_from_openapi_data(cls, *args, **kwargs): + # this function uses the discriminator to + # pick a new schema/class to instantiate because a discriminator + # propertyName value was passed in + + if len(args) == 1: + arg = args[0] + if arg is None and is_type_nullable(cls): + # The input data is the 'null' value and the type is nullable. + return None + + if issubclass(cls, ModelComposed) and allows_single_value_input(cls): + model_kwargs = {} + oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg) + return oneof_instance + + visited_composed_classes = kwargs.get('_visited_composed_classes', ()) + if ( + cls.discriminator is None or + cls in visited_composed_classes + ): + # Use case 1: this openapi schema (cls) does not have a discriminator + # Use case 2: we have already visited this class before and are sure that we + # want to instantiate it this time. We have visited this class deserializing + # a payload with a discriminator. During that process we traveled through + # this class but did not make an instance of it. Now we are making an + # instance of a composed class which contains cls in it, so this time make an instance of cls. + # + # Here's an example of use case 2: If Animal has a discriminator + # petType and we pass in "Dog", and the class Dog + # allOf includes Animal, we move through Animal + # once using the discriminator, and pick Dog. + # Then in the composed schema dog Dog, we will make an instance of the + # Animal class (because Dal has allOf: Animal) but this time we won't travel + # through Animal's discriminator because we passed in + # _visited_composed_classes = (Animal,) + + return cls._from_openapi_data(*args, **kwargs) + + # Get the name and value of the discriminator property. + # The discriminator name is obtained from the discriminator meta-data + # and the discriminator value is obtained from the input data. + discr_propertyname_py = list(cls.discriminator.keys())[0] + discr_propertyname_js = cls.attribute_map[discr_propertyname_py] + if discr_propertyname_js in kwargs: + discr_value = kwargs[discr_propertyname_js] + elif discr_propertyname_py in kwargs: + discr_value = kwargs[discr_propertyname_py] + else: + # The input data does not contain the discriminator property. + path_to_item = kwargs.get('_path_to_item', ()) + raise ApiValueError( + "Cannot deserialize input data due to missing discriminator. " + "The discriminator property '%s' is missing at path: %s" % + (discr_propertyname_js, path_to_item) + ) + + # Implementation note: the last argument to get_discriminator_class + # is a list of visited classes. get_discriminator_class may recursively + # call itself and update the list of visited classes, and the initial + # value must be an empty list. Hence not using 'visited_composed_classes' + new_cls = get_discriminator_class( + cls, discr_propertyname_py, discr_value, []) + if new_cls is None: + path_to_item = kwargs.get('_path_to_item', ()) + disc_prop_value = kwargs.get( + discr_propertyname_js, kwargs.get(discr_propertyname_py)) + raise ApiValueError( + "Cannot deserialize input data due to invalid discriminator " + "value. The OpenAPI document has no mapping for discriminator " + "property '%s'='%s' at path: %s" % + (discr_propertyname_js, disc_prop_value, path_to_item) + ) + + if new_cls in visited_composed_classes: + # if we are making an instance of a composed schema Descendent + # which allOf includes Ancestor, then Ancestor contains + # a discriminator that includes Descendent. + # So if we make an instance of Descendent, we have to make an + # instance of Ancestor to hold the allOf properties. + # This code detects that use case and makes the instance of Ancestor + # For example: + # When making an instance of Dog, _visited_composed_classes = (Dog,) + # then we make an instance of Animal to include in dog._composed_instances + # so when we are here, cls is Animal + # cls.discriminator != None + # cls not in _visited_composed_classes + # new_cls = Dog + # but we know we know that we already have Dog + # because it is in visited_composed_classes + # so make Animal here + return cls._from_openapi_data(*args, **kwargs) + + # Build a list containing all oneOf and anyOf descendants. + oneof_anyof_classes = None + if cls._composed_schemas is not None: + oneof_anyof_classes = ( + cls._composed_schemas.get('oneOf', ()) + + cls._composed_schemas.get('anyOf', ())) + oneof_anyof_child = new_cls in oneof_anyof_classes + kwargs['_visited_composed_classes'] = visited_composed_classes + (cls,) + + if cls._composed_schemas.get('allOf') and oneof_anyof_child: + # Validate that we can make self because when we make the + # new_cls it will not include the allOf validations in self + self_inst = cls._from_openapi_data(*args, **kwargs) + + new_inst = new_cls._new_from_openapi_data(*args, **kwargs) + return new_inst + + +class ModelSimple(OpenApiModel): + """the parent class of models whose type != object in their + swagger/openapi""" + +{{> model_templates/methods_setattr_getattr_normal }} + +{{> model_templates/methods_tostr_eq_simple }} + + +class ModelNormal(OpenApiModel): + """the parent class of models whose type == object in their + swagger/openapi""" + +{{> model_templates/methods_setattr_getattr_normal }} + +{{> model_templates/methods_todict_tostr_eq_shared}} + + +class ModelComposed(OpenApiModel): + """the parent class of models whose type == object in their + swagger/openapi and have oneOf/allOf/anyOf + + When one sets a property we use var_name_to_model_instances to store the value in + the correct class instances + run any type checking + validation code. + When one gets a property we use var_name_to_model_instances to get the value + from the correct class instances. + This allows multiple composed schemas to contain the same property with additive + constraints on the value. + + _composed_schemas (dict) stores the anyOf/allOf/oneOf classes + key (str): allOf/oneOf/anyOf + value (list): the classes in the XOf definition. + Note: none_type can be included when the openapi document version >= 3.1.0 + _composed_instances (list): stores a list of instances of the composed schemas + defined in _composed_schemas. When properties are accessed in the self instance, + they are returned from the self._data_store or the data stores in the instances + in self._composed_schemas + _var_name_to_model_instances (dict): maps between a variable name on self and + the composed instances (self included) which contain that data + key (str): property name + value (list): list of class instances, self or instances in _composed_instances + which contain the value that the key is referring to. + """ + +{{> model_templates/methods_setattr_getattr_composed }} + +{{> model_templates/methods_todict_tostr_eq_shared}} + + +COERCION_INDEX_BY_TYPE = { + ModelComposed: 0, + ModelNormal: 1, + ModelSimple: 2, + none_type: 3, # The type of 'None'. + list: 4, + dict: 5, + float: 6, + int: 7, + bool: 8, + datetime: 9, + date: 10, + str: 11, + file_type: 12, # 'file_type' is an alias for the built-in 'file' or 'io.IOBase' type. +} + +# these are used to limit what type conversions we try to do +# when we have a valid type already and we want to try converting +# to another type +UPCONVERSION_TYPE_PAIRS = ( + (str, datetime), + (str, date), + # A float may be serialized as an integer, e.g. '3' is a valid serialized float. + (int, float), + (list, ModelComposed), + (dict, ModelComposed), + (str, ModelComposed), + (int, ModelComposed), + (float, ModelComposed), + (list, ModelComposed), + (list, ModelNormal), + (dict, ModelNormal), + (str, ModelSimple), + (int, ModelSimple), + (float, ModelSimple), + (list, ModelSimple), +) + +COERCIBLE_TYPE_PAIRS = { + False: ( # client instantiation of a model with client data + # (dict, ModelComposed), + # (list, ModelComposed), + # (dict, ModelNormal), + # (list, ModelNormal), + # (str, ModelSimple), + # (int, ModelSimple), + # (float, ModelSimple), + # (list, ModelSimple), + # (str, int), + # (str, float), + # (str, datetime), + # (str, date), + # (int, str), + # (float, str), + ), + True: ( # server -> client data + (dict, ModelComposed), + (list, ModelComposed), + (dict, ModelNormal), + (list, ModelNormal), + (str, ModelSimple), + (int, ModelSimple), + (float, ModelSimple), + (list, ModelSimple), + # (str, int), + # (str, float), + (str, datetime), + (str, date), + # (int, str), + # (float, str), + (str, file_type) + ), +} + + +def get_simple_class(input_value): + """Returns an input_value's simple class that we will use for type checking + Python2: + float and int will return int, where int is the python3 int backport + str and unicode will return str, where str is the python3 str backport + Note: float and int ARE both instances of int backport + Note: str_py2 and unicode_py2 are NOT both instances of str backport + + Args: + input_value (class/class_instance): the item for which we will return + the simple class + """ + if isinstance(input_value, type): + # input_value is a class + return input_value + elif isinstance(input_value, tuple): + return tuple + elif isinstance(input_value, list): + return list + elif isinstance(input_value, dict): + return dict + elif isinstance(input_value, none_type): + return none_type + elif isinstance(input_value, file_type): + return file_type + elif isinstance(input_value, bool): + # this must be higher than the int check because + # isinstance(True, int) == True + return bool + elif isinstance(input_value, int): + return int + elif isinstance(input_value, datetime): + # this must be higher than the date check because + # isinstance(datetime_instance, date) == True + return datetime + elif isinstance(input_value, date): + return date + elif isinstance(input_value, str): + return str + return type(input_value) + + +def check_allowed_values(allowed_values, input_variable_path, input_values): + """Raises an exception if the input_values are not allowed + + Args: + allowed_values (dict): the allowed_values dict + input_variable_path (tuple): the path to the input variable + input_values (list/str/int/float/date/datetime): the values that we + are checking to see if they are in allowed_values + """ + these_allowed_values = list(allowed_values[input_variable_path].values()) + if (isinstance(input_values, list) + and not set(input_values).issubset( + set(these_allowed_values))): + invalid_values = ", ".join( + map(str, set(input_values) - set(these_allowed_values))), + raise ApiValueError( + "Invalid values for `%s` [%s], must be a subset of [%s]" % + ( + input_variable_path[0], + invalid_values, + ", ".join(map(str, these_allowed_values)) + ) + ) + elif (isinstance(input_values, dict) + and not set( + input_values.keys()).issubset(set(these_allowed_values))): + invalid_values = ", ".join( + map(str, set(input_values.keys()) - set(these_allowed_values))) + raise ApiValueError( + "Invalid keys in `%s` [%s], must be a subset of [%s]" % + ( + input_variable_path[0], + invalid_values, + ", ".join(map(str, these_allowed_values)) + ) + ) + elif (not isinstance(input_values, (list, dict)) + and input_values not in these_allowed_values): + raise ApiValueError( + "Invalid value for `%s` (%s), must be one of %s" % + ( + input_variable_path[0], + input_values, + these_allowed_values + ) + ) + + +def is_json_validation_enabled(schema_keyword, configuration=None): + """Returns true if JSON schema validation is enabled for the specified + validation keyword. This can be used to skip JSON schema structural validation + as requested in the configuration. + + Args: + schema_keyword (string): the name of a JSON schema validation keyword. + configuration (Configuration): the configuration class. + """ + + return (configuration is None or + not hasattr(configuration, '_disabled_client_side_validations') or + schema_keyword not in configuration._disabled_client_side_validations) + + +def check_validations( + validations, input_variable_path, input_values, + configuration=None): + """Raises an exception if the input_values are invalid + + Args: + validations (dict): the validation dictionary. + input_variable_path (tuple): the path to the input variable. + input_values (list/str/int/float/date/datetime): the values that we + are checking. + configuration (Configuration): the configuration class. + """ + + if input_values is None: + return + + current_validations = validations[input_variable_path] + if (is_json_validation_enabled('multipleOf', configuration) and + 'multiple_of' in current_validations and + isinstance(input_values, (int, float)) and + not (float(input_values) / current_validations['multiple_of']).is_integer()): + # Note 'multipleOf' will be as good as the floating point arithmetic. + raise ApiValueError( + "Invalid value for `%s`, value must be a multiple of " + "`%s`" % ( + input_variable_path[0], + current_validations['multiple_of'] + ) + ) + + if (is_json_validation_enabled('maxLength', configuration) and + 'max_length' in current_validations and + len(input_values) > current_validations['max_length']): + raise ApiValueError( + "Invalid value for `%s`, length must be less than or equal to " + "`%s`" % ( + input_variable_path[0], + current_validations['max_length'] + ) + ) + + if (is_json_validation_enabled('minLength', configuration) and + 'min_length' in current_validations and + len(input_values) < current_validations['min_length']): + raise ApiValueError( + "Invalid value for `%s`, length must be greater than or equal to " + "`%s`" % ( + input_variable_path[0], + current_validations['min_length'] + ) + ) + + if (is_json_validation_enabled('maxItems', configuration) and + 'max_items' in current_validations and + len(input_values) > current_validations['max_items']): + raise ApiValueError( + "Invalid value for `%s`, number of items must be less than or " + "equal to `%s`" % ( + input_variable_path[0], + current_validations['max_items'] + ) + ) + + if (is_json_validation_enabled('minItems', configuration) and + 'min_items' in current_validations and + len(input_values) < current_validations['min_items']): + raise ValueError( + "Invalid value for `%s`, number of items must be greater than or " + "equal to `%s`" % ( + input_variable_path[0], + current_validations['min_items'] + ) + ) + + items = ('exclusive_maximum', 'inclusive_maximum', 'exclusive_minimum', + 'inclusive_minimum') + if (any(item in current_validations for item in items)): + if isinstance(input_values, list): + max_val = max(input_values) + min_val = min(input_values) + elif isinstance(input_values, dict): + max_val = max(input_values.values()) + min_val = min(input_values.values()) + else: + max_val = input_values + min_val = input_values + + if (is_json_validation_enabled('exclusiveMaximum', configuration) and + 'exclusive_maximum' in current_validations and + max_val >= current_validations['exclusive_maximum']): + raise ApiValueError( + "Invalid value for `%s`, must be a value less than `%s`" % ( + input_variable_path[0], + current_validations['exclusive_maximum'] + ) + ) + + if (is_json_validation_enabled('maximum', configuration) and + 'inclusive_maximum' in current_validations and + max_val > current_validations['inclusive_maximum']): + raise ApiValueError( + "Invalid value for `%s`, must be a value less than or equal to " + "`%s`" % ( + input_variable_path[0], + current_validations['inclusive_maximum'] + ) + ) + + if (is_json_validation_enabled('exclusiveMinimum', configuration) and + 'exclusive_minimum' in current_validations and + min_val <= current_validations['exclusive_minimum']): + raise ApiValueError( + "Invalid value for `%s`, must be a value greater than `%s`" % + ( + input_variable_path[0], + current_validations['exclusive_maximum'] + ) + ) + + if (is_json_validation_enabled('minimum', configuration) and + 'inclusive_minimum' in current_validations and + min_val < current_validations['inclusive_minimum']): + raise ApiValueError( + "Invalid value for `%s`, must be a value greater than or equal " + "to `%s`" % ( + input_variable_path[0], + current_validations['inclusive_minimum'] + ) + ) + flags = current_validations.get('regex', {}).get('flags', 0) + if (is_json_validation_enabled('pattern', configuration) and + 'regex' in current_validations and + not re.search(current_validations['regex']['pattern'], + input_values, flags=flags)): + err_msg = r"Invalid value for `%s`, must match regular expression `%s`" % ( + input_variable_path[0], + current_validations['regex']['pattern'] + ) + if flags != 0: + # Don't print the regex flags if the flags are not + # specified in the OAS document. + err_msg = r"%s with flags=`%s`" % (err_msg, flags) + raise ApiValueError(err_msg) + + +def order_response_types(required_types): + """Returns the required types sorted in coercion order + + Args: + required_types (list/tuple): collection of classes or instance of + list or dict with class information inside it. + + Returns: + (list): coercion order sorted collection of classes or instance + of list or dict with class information inside it. + """ + + def index_getter(class_or_instance): + if isinstance(class_or_instance, list): + return COERCION_INDEX_BY_TYPE[list] + elif isinstance(class_or_instance, dict): + return COERCION_INDEX_BY_TYPE[dict] + elif (inspect.isclass(class_or_instance) + and issubclass(class_or_instance, ModelComposed)): + return COERCION_INDEX_BY_TYPE[ModelComposed] + elif (inspect.isclass(class_or_instance) + and issubclass(class_or_instance, ModelNormal)): + return COERCION_INDEX_BY_TYPE[ModelNormal] + elif (inspect.isclass(class_or_instance) + and issubclass(class_or_instance, ModelSimple)): + return COERCION_INDEX_BY_TYPE[ModelSimple] + elif class_or_instance in COERCION_INDEX_BY_TYPE: + return COERCION_INDEX_BY_TYPE[class_or_instance] + raise ApiValueError("Unsupported type: %s" % class_or_instance) + + sorted_types = sorted( + required_types, + key=lambda class_or_instance: index_getter(class_or_instance) + ) + return sorted_types + + +def remove_uncoercible(required_types_classes, current_item, spec_property_naming, + must_convert=True): + """Only keeps the type conversions that are possible + + Args: + required_types_classes (tuple): tuple of classes that are required + these should be ordered by COERCION_INDEX_BY_TYPE + spec_property_naming (bool): True if the variable names in the input + data are serialized names as specified in the OpenAPI document. + False if the variables names in the input data are python + variable names in PEP-8 snake case. + current_item (any): the current item (input data) to be converted + + Keyword Args: + must_convert (bool): if True the item to convert is of the wrong + type and we want a big list of coercibles + if False, we want a limited list of coercibles + + Returns: + (list): the remaining coercible required types, classes only + """ + current_type_simple = get_simple_class(current_item) + + results_classes = [] + for required_type_class in required_types_classes: + # convert our models to OpenApiModel + required_type_class_simplified = required_type_class + if isinstance(required_type_class_simplified, type): + if issubclass(required_type_class_simplified, ModelComposed): + required_type_class_simplified = ModelComposed + elif issubclass(required_type_class_simplified, ModelNormal): + required_type_class_simplified = ModelNormal + elif issubclass(required_type_class_simplified, ModelSimple): + required_type_class_simplified = ModelSimple + + if required_type_class_simplified == current_type_simple: + # don't consider converting to one's own class + continue + + class_pair = (current_type_simple, required_type_class_simplified) + if must_convert and class_pair in COERCIBLE_TYPE_PAIRS[spec_property_naming]: + results_classes.append(required_type_class) + elif class_pair in UPCONVERSION_TYPE_PAIRS: + results_classes.append(required_type_class) + return results_classes + + +def get_discriminated_classes(cls): + """ + Returns all the classes that a discriminator converts to + TODO: lru_cache this + """ + possible_classes = [] + key = list(cls.discriminator.keys())[0] + if is_type_nullable(cls): + possible_classes.append(cls) + for discr_cls in cls.discriminator[key].values(): + if hasattr(discr_cls, 'discriminator') and discr_cls.discriminator is not None: + possible_classes.extend(get_discriminated_classes(discr_cls)) + else: + possible_classes.append(discr_cls) + return possible_classes + + +def get_possible_classes(cls, from_server_context): + # TODO: lru_cache this + possible_classes = [cls] + if from_server_context: + return possible_classes + if hasattr(cls, 'discriminator') and cls.discriminator is not None: + possible_classes = [] + possible_classes.extend(get_discriminated_classes(cls)) + elif issubclass(cls, ModelComposed): + possible_classes.extend(composed_model_input_classes(cls)) + return possible_classes + + +def get_required_type_classes(required_types_mixed, spec_property_naming): + """Converts the tuple required_types into a tuple and a dict described + below + + Args: + required_types_mixed (tuple/list): will contain either classes or + instance of list or dict + spec_property_naming (bool): if True these values came from the + server, and we use the data types in our endpoints. + If False, we are client side and we need to include + oneOf and discriminator classes inside the data types in our endpoints + + Returns: + (valid_classes, dict_valid_class_to_child_types_mixed): + valid_classes (tuple): the valid classes that the current item + should be + dict_valid_class_to_child_types_mixed (dict): + valid_class (class): this is the key + child_types_mixed (list/dict/tuple): describes the valid child + types + """ + valid_classes = [] + child_req_types_by_current_type = {} + for required_type in required_types_mixed: + if isinstance(required_type, list): + valid_classes.append(list) + child_req_types_by_current_type[list] = required_type + elif isinstance(required_type, tuple): + valid_classes.append(tuple) + child_req_types_by_current_type[tuple] = required_type + elif isinstance(required_type, dict): + valid_classes.append(dict) + child_req_types_by_current_type[dict] = required_type[str] + else: + valid_classes.extend(get_possible_classes(required_type, spec_property_naming)) + return tuple(valid_classes), child_req_types_by_current_type + + +def change_keys_js_to_python(input_dict, model_class): + """ + Converts from javascript_key keys in the input_dict to python_keys in + the output dict using the mapping in model_class. + If the input_dict contains a key which does not declared in the model_class, + the key is added to the output dict as is. The assumption is the model_class + may have undeclared properties (additionalProperties attribute in the OAS + document). + """ + + if getattr(model_class, 'attribute_map', None) is None: + return input_dict + output_dict = {} + reversed_attr_map = {value: key for key, value in + model_class.attribute_map.items()} + for javascript_key, value in input_dict.items(): + python_key = reversed_attr_map.get(javascript_key) + if python_key is None: + # if the key is unknown, it is in error or it is an + # additionalProperties variable + python_key = javascript_key + output_dict[python_key] = value + return output_dict + + +def get_type_error(var_value, path_to_item, valid_classes, key_type=False): + error_msg = type_error_message( + var_name=path_to_item[-1], + var_value=var_value, + valid_classes=valid_classes, + key_type=key_type + ) + return ApiTypeError( + error_msg, + path_to_item=path_to_item, + valid_classes=valid_classes, + key_type=key_type + ) + + +def deserialize_primitive(data, klass, path_to_item): + """Deserializes string to primitive type. + + :param data: str/int/float + :param klass: str/class the class to convert to + + :return: int, float, str, bool, date, datetime + """ + additional_message = "" + try: + if klass in {datetime, date}: + additional_message = ( + "If you need your parameter to have a fallback " + "string value, please set its type as `type: {}` in your " + "spec. That allows the value to be any type. " + ) + if klass == datetime: + if len(data) < 8: + raise ValueError("This is not a datetime") + # The string should be in iso8601 datetime format. + parsed_datetime = parse(data) + date_only = ( + parsed_datetime.hour == 0 and + parsed_datetime.minute == 0 and + parsed_datetime.second == 0 and + parsed_datetime.tzinfo is None and + 8 <= len(data) <= 10 + ) + if date_only: + raise ValueError("This is a date, not a datetime") + return parsed_datetime + elif klass == date: + if len(data) < 8: + raise ValueError("This is not a date") + return parse(data).date() + else: + converted_value = klass(data) + if isinstance(data, str) and klass == float: + if str(converted_value) != data: + # '7' -> 7.0 -> '7.0' != '7' + raise ValueError('This is not a float') + return converted_value + except (OverflowError, ValueError) as ex: + # parse can raise OverflowError + raise ApiValueError( + "{0}Failed to parse {1} as {2}".format( + additional_message, repr(data), klass.__name__ + ), + path_to_item=path_to_item + ) from ex + + +def get_discriminator_class(model_class, + discr_name, + discr_value, cls_visited): + """Returns the child class specified by the discriminator. + + Args: + model_class (OpenApiModel): the model class. + discr_name (string): the name of the discriminator property. + discr_value (any): the discriminator value. + cls_visited (list): list of model classes that have been visited. + Used to determine the discriminator class without + visiting circular references indefinitely. + + Returns: + used_model_class (class/None): the chosen child class that will be used + to deserialize the data, for example dog.Dog. + If a class is not found, None is returned. + """ + + if model_class in cls_visited: + # The class has already been visited and no suitable class was found. + return None + cls_visited.append(model_class) + used_model_class = None + if discr_name in model_class.discriminator: + class_name_to_discr_class = model_class.discriminator[discr_name] + used_model_class = class_name_to_discr_class.get(discr_value) + if used_model_class is None: + # We didn't find a discriminated class in class_name_to_discr_class. + # So look in the ancestor or descendant discriminators + # The discriminator mapping may exist in a descendant (anyOf, oneOf) + # or ancestor (allOf). + # Ancestor example: in the GrandparentAnimal -> ParentPet -> ChildCat + # hierarchy, the discriminator mappings may be defined at any level + # in the hierarchy. + # Descendant example: mammal -> whale/zebra/Pig -> BasquePig/DanishPig + # if we try to make BasquePig from mammal, we need to travel through + # the oneOf descendant discriminators to find BasquePig + descendant_classes = model_class._composed_schemas.get('oneOf', ()) + \ + model_class._composed_schemas.get('anyOf', ()) + ancestor_classes = model_class._composed_schemas.get('allOf', ()) + possible_classes = descendant_classes + ancestor_classes + for cls in possible_classes: + # Check if the schema has inherited discriminators. + if hasattr(cls, 'discriminator') and cls.discriminator is not None: + used_model_class = get_discriminator_class( + cls, discr_name, discr_value, cls_visited) + if used_model_class is not None: + return used_model_class + return used_model_class + + +def deserialize_model(model_data, model_class, path_to_item, check_type, + configuration, spec_property_naming): + """Deserializes model_data to model instance. + + Args: + model_data (int/str/float/bool/none_type/list/dict): data to instantiate the model + model_class (OpenApiModel): the model class + path_to_item (list): path to the model in the received data + check_type (bool): whether to check the data tupe for the values in + the model + configuration (Configuration): the instance to use to convert files + spec_property_naming (bool): True if the variable names in the input + data are serialized names as specified in the OpenAPI document. + False if the variables names in the input data are python + variable names in PEP-8 snake case. + + Returns: + model instance + + Raise: + ApiTypeError + ApiValueError + ApiKeyError + """ + + kw_args = dict(_check_type=check_type, + _path_to_item=path_to_item, + _configuration=configuration, + _spec_property_naming=spec_property_naming) + + if issubclass(model_class, ModelSimple): + return model_class._new_from_openapi_data(model_data, **kw_args) + elif isinstance(model_data, list): + return model_class._new_from_openapi_data(*model_data, **kw_args) + if isinstance(model_data, dict): + kw_args.update(model_data) + return model_class._new_from_openapi_data(**kw_args) + elif isinstance(model_data, PRIMITIVE_TYPES): + return model_class._new_from_openapi_data(model_data, **kw_args) + + +def deserialize_file(response_data, configuration, content_disposition=None): + """Deserializes body to file + + Saves response body into a file in a temporary folder, + using the filename from the `Content-Disposition` header if provided. + + Args: + param response_data (str): the file data to write + configuration (Configuration): the instance to use to convert files + + Keyword Args: + content_disposition (str): the value of the Content-Disposition + header + + Returns: + (file_type): the deserialized file which is open + The user is responsible for closing and reading the file + """ + fd, path = tempfile.mkstemp(dir=configuration.temp_folder_path) + os.close(fd) + os.remove(path) + + if content_disposition: + filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', + content_disposition, + flags=re.I) + if filename is not None: + filename = filename.group(1) + else: + filename = "default_" + str(uuid.uuid4()) + + path = os.path.join(os.path.dirname(path), filename) + + with open(path, "wb") as f: + if isinstance(response_data, str): + # change str to bytes so we can write it + response_data = response_data.encode('utf-8') + f.write(response_data) + + f = open(path, "rb") + return f + + +def attempt_convert_item(input_value, valid_classes, path_to_item, + configuration, spec_property_naming, key_type=False, + must_convert=False, check_type=True): + """ + Args: + input_value (any): the data to convert + valid_classes (any): the classes that are valid + path_to_item (list): the path to the item to convert + configuration (Configuration): the instance to use to convert files + spec_property_naming (bool): True if the variable names in the input + data are serialized names as specified in the OpenAPI document. + False if the variables names in the input data are python + variable names in PEP-8 snake case. + key_type (bool): if True we need to convert a key type (not supported) + must_convert (bool): if True we must convert + check_type (bool): if True we check the type or the returned data in + ModelComposed/ModelNormal/ModelSimple instances + + Returns: + instance (any) the fixed item + + Raises: + ApiTypeError + ApiValueError + ApiKeyError + """ + valid_classes_ordered = order_response_types(valid_classes) + valid_classes_coercible = remove_uncoercible( + valid_classes_ordered, input_value, spec_property_naming) + if not valid_classes_coercible or key_type: + # we do not handle keytype errors, json will take care + # of this for us + if configuration is None or not configuration.discard_unknown_keys: + raise get_type_error(input_value, path_to_item, valid_classes, + key_type=key_type) + for valid_class in valid_classes_coercible: + try: + if issubclass(valid_class, OpenApiModel): + return deserialize_model(input_value, valid_class, + path_to_item, check_type, + configuration, spec_property_naming) + elif valid_class == file_type: + return deserialize_file(input_value, configuration) + return deserialize_primitive(input_value, valid_class, + path_to_item) + except (ApiTypeError, ApiValueError, ApiKeyError) as conversion_exc: + if must_convert: + raise conversion_exc + # if we have conversion errors when must_convert == False + # we ignore the exception and move on to the next class + continue + # we were unable to convert, must_convert == False + return input_value + + +def is_type_nullable(input_type): + """ + Returns true if None is an allowed value for the specified input_type. + + A type is nullable if at least one of the following conditions is true: + 1. The OAS 'nullable' attribute has been specified, + 1. The type is the 'null' type, + 1. The type is a anyOf/oneOf composed schema, and a child schema is + the 'null' type. + Args: + input_type (type): the class of the input_value that we are + checking + Returns: + bool + """ + if input_type is none_type: + return True + if issubclass(input_type, OpenApiModel) and input_type._nullable: + return True + if issubclass(input_type, ModelComposed): + # If oneOf/anyOf, check if the 'null' type is one of the allowed types. + for t in input_type._composed_schemas.get('oneOf', ()): + if is_type_nullable(t): + return True + for t in input_type._composed_schemas.get('anyOf', ()): + if is_type_nullable(t): + return True + return False + + +def is_valid_type(input_class_simple, valid_classes): + """ + Args: + input_class_simple (class): the class of the input_value that we are + checking + valid_classes (tuple): the valid classes that the current item + should be + Returns: + bool + """ + if issubclass(input_class_simple, OpenApiModel) and \ + valid_classes == (bool, date, datetime, dict, float, int, list, str, none_type,): + return True + valid_type = input_class_simple in valid_classes + if not valid_type and ( + issubclass(input_class_simple, OpenApiModel) or + input_class_simple is none_type): + for valid_class in valid_classes: + if input_class_simple is none_type and is_type_nullable(valid_class): + # Schema is oneOf/anyOf and the 'null' type is one of the allowed types. + return True + if not (issubclass(valid_class, OpenApiModel) and valid_class.discriminator): + continue + discr_propertyname_py = list(valid_class.discriminator.keys())[0] + discriminator_classes = ( + valid_class.discriminator[discr_propertyname_py].values() + ) + valid_type = is_valid_type(input_class_simple, discriminator_classes) + if valid_type: + return True + return valid_type + + +def validate_and_convert_types(input_value, required_types_mixed, path_to_item, + spec_property_naming, _check_type, configuration=None): + """Raises a TypeError is there is a problem, otherwise returns value + + Args: + input_value (any): the data to validate/convert + required_types_mixed (list/dict/tuple): A list of + valid classes, or a list tuples of valid classes, or a dict where + the value is a tuple of value classes + path_to_item: (list) the path to the data being validated + this stores a list of keys or indices to get to the data being + validated + spec_property_naming (bool): True if the variable names in the input + data are serialized names as specified in the OpenAPI document. + False if the variables names in the input data are python + variable names in PEP-8 snake case. + _check_type: (boolean) if true, type will be checked and conversion + will be attempted. + configuration: (Configuration): the configuration class to use + when converting file_type items. + If passed, conversion will be attempted when possible + If not passed, no conversions will be attempted and + exceptions will be raised + + Returns: + the correctly typed value + + Raises: + ApiTypeError + """ + results = get_required_type_classes(required_types_mixed, spec_property_naming) + valid_classes, child_req_types_by_current_type = results + + input_class_simple = get_simple_class(input_value) + valid_type = is_valid_type(input_class_simple, valid_classes) + if not valid_type: + if (configuration + or (input_class_simple == dict + and dict not in valid_classes)): + # if input_value is not valid_type try to convert it + converted_instance = attempt_convert_item( + input_value, + valid_classes, + path_to_item, + configuration, + spec_property_naming, + key_type=False, + must_convert=True, + check_type=_check_type + ) + return converted_instance + else: + raise get_type_error(input_value, path_to_item, valid_classes, + key_type=False) + + # input_value's type is in valid_classes + if len(valid_classes) > 1 and configuration: + # there are valid classes which are not the current class + valid_classes_coercible = remove_uncoercible( + valid_classes, input_value, spec_property_naming, must_convert=False) + if valid_classes_coercible: + converted_instance = attempt_convert_item( + input_value, + valid_classes_coercible, + path_to_item, + configuration, + spec_property_naming, + key_type=False, + must_convert=False, + check_type=_check_type + ) + return converted_instance + + if child_req_types_by_current_type == {}: + # all types are of the required types and there are no more inner + # variables left to look at + return input_value + inner_required_types = child_req_types_by_current_type.get( + type(input_value) + ) + if inner_required_types is None: + # for this type, there are not more inner variables left to look at + return input_value + if isinstance(input_value, list): + if input_value == []: + # allow an empty list + return input_value + for index, inner_value in enumerate(input_value): + inner_path = list(path_to_item) + inner_path.append(index) + input_value[index] = validate_and_convert_types( + inner_value, + inner_required_types, + inner_path, + spec_property_naming, + _check_type, + configuration=configuration + ) + elif isinstance(input_value, dict): + if input_value == {}: + # allow an empty dict + return input_value + for inner_key, inner_val in input_value.items(): + inner_path = list(path_to_item) + inner_path.append(inner_key) + if get_simple_class(inner_key) != str: + raise get_type_error(inner_key, inner_path, valid_classes, + key_type=True) + input_value[inner_key] = validate_and_convert_types( + inner_val, + inner_required_types, + inner_path, + spec_property_naming, + _check_type, + configuration=configuration + ) + return input_value + + +def model_to_dict(model_instance, serialize=True): + """Returns the model properties as a dict + + Args: + model_instance (one of your model instances): the model instance that + will be converted to a dict. + + Keyword Args: + serialize (bool): if True, the keys in the dict will be values from + attribute_map + """ + result = {} + + def extract_item(item): return ( + item[0], model_to_dict( + item[1], serialize=serialize)) if hasattr( + item[1], '_data_store') else item + + model_instances = [model_instance] + if model_instance._composed_schemas: + model_instances.extend(model_instance._composed_instances) + seen_json_attribute_names = set() + used_fallback_python_attribute_names = set() + py_to_json_map = {} + for model_instance in model_instances: + for attr, value in model_instance._data_store.items(): + if serialize: + # we use get here because additional property key names do not + # exist in attribute_map + try: + attr = model_instance.attribute_map[attr] + py_to_json_map.update(model_instance.attribute_map) + seen_json_attribute_names.add(attr) + except KeyError: + used_fallback_python_attribute_names.add(attr) + if isinstance(value, list): + if not value: + # empty list or None + result[attr] = value + else: + res = [] + for v in value: + if isinstance(v, PRIMITIVE_TYPES) or v is None: + res.append(v) + elif isinstance(v, ModelSimple): + res.append(v.value) + elif isinstance(v, dict): + res.append(dict(map( + extract_item, + v.items() + ))) + else: + res.append(model_to_dict(v, serialize=serialize)) + result[attr] = res + elif isinstance(value, dict): + result[attr] = dict(map( + extract_item, + value.items() + )) + elif isinstance(value, ModelSimple): + result[attr] = value.value + elif hasattr(value, '_data_store'): + result[attr] = model_to_dict(value, serialize=serialize) + else: + result[attr] = value + if serialize: + for python_key in used_fallback_python_attribute_names: + json_key = py_to_json_map.get(python_key) + if json_key is None: + continue + if python_key == json_key: + continue + json_key_assigned_no_need_for_python_key = json_key in seen_json_attribute_names + if json_key_assigned_no_need_for_python_key: + del result[python_key] + + return result + + +def type_error_message(var_value=None, var_name=None, valid_classes=None, + key_type=None): + """ + Keyword Args: + var_value (any): the variable which has the type_error + var_name (str): the name of the variable which has the typ error + valid_classes (tuple): the accepted classes for current_item's + value + key_type (bool): False if our value is a value in a dict + True if it is a key in a dict + False if our item is an item in a list + """ + key_or_value = 'value' + if key_type: + key_or_value = 'key' + valid_classes_phrase = get_valid_classes_phrase(valid_classes) + msg = ( + "Invalid type for variable '{0}'. Required {1} type {2} and " + "passed type was {3}".format( + var_name, + key_or_value, + valid_classes_phrase, + type(var_value).__name__, + ) + ) + return msg + + +def get_valid_classes_phrase(input_classes): + """Returns a string phrase describing what types are allowed + """ + all_classes = list(input_classes) + all_classes = sorted(all_classes, key=lambda cls: cls.__name__) + all_class_names = [cls.__name__ for cls in all_classes] + if len(all_class_names) == 1: + return 'is {0}'.format(all_class_names[0]) + return "is one of [{0}]".format(", ".join(all_class_names)) + + +def get_allof_instances(self, model_args, constant_args): + """ + Args: + self: the class we are handling + model_args (dict): var_name to var_value + used to make instances + constant_args (dict): + metadata arguments: + _check_type + _path_to_item + _spec_property_naming + _configuration + _visited_composed_classes + + Returns + composed_instances (list) + """ + composed_instances = [] + for allof_class in self._composed_schemas['allOf']: + + try: + if constant_args.get('_spec_property_naming'): + allof_instance = allof_class._from_openapi_data(**model_args, **constant_args) + else: + allof_instance = allof_class(**model_args, **constant_args) + composed_instances.append(allof_instance) + except Exception as ex: + raise ApiValueError( + "Invalid inputs given to generate an instance of '%s'. The " + "input data was invalid for the allOf schema '%s' in the composed " + "schema '%s'. Error=%s" % ( + allof_class.__name__, + allof_class.__name__, + self.__class__.__name__, + str(ex) + ) + ) from ex + return composed_instances + + +def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None): + """ + Find the oneOf schema that matches the input data (e.g. payload). + If exactly one schema matches the input data, an instance of that schema + is returned. + If zero or more than one schema match the input data, an exception is raised. + In OAS 3.x, the payload MUST, by validation, match exactly one of the + schemas described by oneOf. + + Args: + cls: the class we are handling + model_kwargs (dict): var_name to var_value + The input data, e.g. the payload that must match a oneOf schema + in the OpenAPI document. + constant_kwargs (dict): var_name to var_value + args that every model requires, including configuration, server + and path to item. + + Kwargs: + model_arg: (int, float, bool, str, date, datetime, ModelSimple, None): + the value to assign to a primitive class or ModelSimple class + Notes: + - this is only passed in when oneOf includes types which are not object + - None is used to suppress handling of model_arg, nullable models are handled in __new__ + + Returns + oneof_instance (instance) + """ + if len(cls._composed_schemas['oneOf']) == 0: + return None + + oneof_instances = [] + # Iterate over each oneOf schema and determine if the input data + # matches the oneOf schemas. + for oneof_class in cls._composed_schemas['oneOf']: + # The composed oneOf schema allows the 'null' type and the input data + # is the null value. This is a OAS >= 3.1 feature. + if oneof_class is none_type: + # skip none_types because we are deserializing dict data. + # none_type deserialization is handled in the __new__ method + continue + + single_value_input = allows_single_value_input(oneof_class) + + try: + if not single_value_input: + if constant_kwargs.get('_spec_property_naming'): + oneof_instance = oneof_class._from_openapi_data( + **model_kwargs, **constant_kwargs) + else: + oneof_instance = oneof_class(**model_kwargs, **constant_kwargs) + else: + if issubclass(oneof_class, ModelSimple): + if constant_kwargs.get('_spec_property_naming'): + oneof_instance = oneof_class._from_openapi_data( + model_arg, **constant_kwargs) + else: + oneof_instance = oneof_class(model_arg, **constant_kwargs) + elif oneof_class in PRIMITIVE_TYPES: + oneof_instance = validate_and_convert_types( + model_arg, + (oneof_class,), + constant_kwargs['_path_to_item'], + constant_kwargs['_spec_property_naming'], + constant_kwargs['_check_type'], + configuration=constant_kwargs['_configuration'] + ) + oneof_instances.append(oneof_instance) + except Exception: + pass + if len(oneof_instances) == 0: + raise ApiValueError( + "Invalid inputs given to generate an instance of %s. None " + "of the oneOf schemas matched the input data." % + cls.__name__ + ) + elif len(oneof_instances) > 1: + raise ApiValueError( + "Invalid inputs given to generate an instance of %s. Multiple " + "oneOf schemas matched the inputs, but a max of one is allowed." % + cls.__name__ + ) + return oneof_instances[0] + + +def get_anyof_instances(self, model_args, constant_args): + """ + Args: + self: the class we are handling + model_args (dict): var_name to var_value + The input data, e.g. the payload that must match at least one + anyOf child schema in the OpenAPI document. + constant_args (dict): var_name to var_value + args that every model requires, including configuration, server + and path to item. + + Returns + anyof_instances (list) + """ + anyof_instances = [] + if len(self._composed_schemas['anyOf']) == 0: + return anyof_instances + + for anyof_class in self._composed_schemas['anyOf']: + # The composed oneOf schema allows the 'null' type and the input data + # is the null value. This is a OAS >= 3.1 feature. + if anyof_class is none_type: + # skip none_types because we are deserializing dict data. + # none_type deserialization is handled in the __new__ method + continue + + try: + if constant_args.get('_spec_property_naming'): + anyof_instance = anyof_class._from_openapi_data(**model_args, **constant_args) + else: + anyof_instance = anyof_class(**model_args, **constant_args) + anyof_instances.append(anyof_instance) + except Exception: + pass + if len(anyof_instances) == 0: + raise ApiValueError( + "Invalid inputs given to generate an instance of %s. None of the " + "anyOf schemas matched the inputs." % + self.__class__.__name__ + ) + return anyof_instances + + +def get_discarded_args(self, composed_instances, model_args): + """ + Gathers the args that were discarded by configuration.discard_unknown_keys + """ + model_arg_keys = model_args.keys() + discarded_args = set() + # arguments passed to self were already converted to python names + # before __init__ was called + for instance in composed_instances: + if instance.__class__ in self._composed_schemas['allOf']: + try: + keys = instance.to_dict().keys() + discarded_keys = model_args - keys + discarded_args.update(discarded_keys) + except Exception: + # allOf integer schema will throw exception + pass + else: + try: + all_keys = set(model_to_dict(instance, serialize=False).keys()) + js_keys = model_to_dict(instance, serialize=True).keys() + all_keys.update(js_keys) + discarded_keys = model_arg_keys - all_keys + discarded_args.update(discarded_keys) + except Exception: + # allOf integer schema will throw exception + pass + return discarded_args + + +def validate_get_composed_info(constant_args, model_args, self): + """ + For composed schemas, generate schema instances for + all schemas in the oneOf/anyOf/allOf definition. If additional + properties are allowed, also assign those properties on + all matched schemas that contain additionalProperties. + Openapi schemas are python classes. + + Exceptions are raised if: + - 0 or > 1 oneOf schema matches the model_args input data + - no anyOf schema matches the model_args input data + - any of the allOf schemas do not match the model_args input data + + Args: + constant_args (dict): these are the args that every model requires + model_args (dict): these are the required and optional spec args that + were passed in to make this model + self (class): the class that we are instantiating + This class contains self._composed_schemas + + Returns: + composed_info (list): length three + composed_instances (list): the composed instances which are not + self + var_name_to_model_instances (dict): a dict going from var_name + to the model_instance which holds that var_name + the model_instance may be self or an instance of one of the + classes in self.composed_instances() + additional_properties_model_instances (list): a list of the + model instances which have the property + additional_properties_type. This list can include self + """ + # create composed_instances + composed_instances = [] + allof_instances = get_allof_instances(self, model_args, constant_args) + composed_instances.extend(allof_instances) + oneof_instance = get_oneof_instance(self.__class__, model_args, constant_args) + if oneof_instance is not None: + composed_instances.append(oneof_instance) + anyof_instances = get_anyof_instances(self, model_args, constant_args) + composed_instances.extend(anyof_instances) + """ + set additional_properties_model_instances + additional properties must be evaluated at the schema level + so self's additional properties are most important + If self is a composed schema with: + - no properties defined in self + - additionalProperties: False + Then for object payloads every property is an additional property + and they are not allowed, so only empty dict is allowed + + Properties must be set on all matching schemas + so when a property is assigned toa composed instance, it must be set on all + composed instances regardless of additionalProperties presence + keeping it to prevent breaking changes in v5.0.1 + TODO remove cls._additional_properties_model_instances in 6.0.0 + """ + additional_properties_model_instances = [] + if self.additional_properties_type is not None: + additional_properties_model_instances = [self] + + """ + no need to set properties on self in here, they will be set in __init__ + By here all composed schema oneOf/anyOf/allOf instances have their properties set using + model_args + """ + discarded_args = get_discarded_args(self, composed_instances, model_args) + + # map variable names to composed_instances + var_name_to_model_instances = {} + for prop_name in model_args: + if prop_name not in discarded_args: + var_name_to_model_instances[prop_name] = [self] + list( + filter( + lambda x: prop_name in x.openapi_types, composed_instances)) + + return [ + composed_instances, + var_name_to_model_instances, + additional_properties_model_instances, + discarded_args + ] + +def to_json(obj, *, read_files: bool = True): + """ + Prepares data for transmission before it is sent with the rest client + If obj is None, return None. + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is OpenAPI model, return the properties dict. + If obj is io.IOBase, return the bytes + :param obj: The data to serialize. + :param read_files: Whether to read file data or leave files as is. + :return: The serialized form of data. + """ + if isinstance(obj, (ModelNormal, ModelComposed)): + return { + key: to_json(val, read_files=read_files) + for key, val in model_to_dict(obj, serialize=True).items() + } + elif isinstance(obj, io.IOBase): + if read_files: + return get_file_data_and_close_file(obj) + else: + return obj + elif isinstance(obj, (str, int, float, none_type, bool)): + return obj + elif isinstance(obj, (datetime, date)): + return obj.isoformat() + elif isinstance(obj, ModelSimple): + return to_json(obj.value, read_files=read_files) + elif isinstance(obj, (list, tuple)): + return [to_json(item, read_files=read_files) for item in obj] + if isinstance(obj, dict): + return { + key: to_json(val, read_files=read_files) + for key, val in obj.items() + } + raise ApiValueError(f'Unable to prepare type {obj.__class__.__name__} for serialization') + +def get_file_data_and_close_file(file_instance: file_type) -> bytes: + file_data = file_instance.read() + file_instance.close() + return file_data diff --git a/cvat-sdk/gen/templates/openapi-generator/operation_name.mustache b/cvat-sdk/gen/templates/openapi-generator/operation_name.mustache new file mode 100644 index 000000000000..5f475b890bdd --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/operation_name.mustache @@ -0,0 +1 @@ +%%%make_operation_id!!!{{operationId}}%%% \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/partial_header.mustache b/cvat-sdk/gen/templates/openapi-generator/partial_header.mustache new file mode 100644 index 000000000000..46024e3d1884 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/partial_header.mustache @@ -0,0 +1,19 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +{{#appName}} +# {{{.}}} +{{/appName}} +# +{{#appDescription}} +# {{{.}}} # noqa: E501 +{{/appDescription}} +# +{{#version}} +# The version of the OpenAPI document: {{{.}}} +{{/version}} +{{#infoEmail}} +# Contact: {{{.}}} +{{/infoEmail}} +# Generated by: https://openapi-generator.tech diff --git a/cvat-sdk/gen/templates/openapi-generator/rest.mustache b/cvat-sdk/gen/templates/openapi-generator/rest.mustache new file mode 100644 index 000000000000..b293e3716cc0 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/rest.mustache @@ -0,0 +1,330 @@ +{{>partial_header}} + +import io +import json +import logging +import re +import ssl +from urllib.parse import urlencode +from urllib.parse import urlparse +from urllib.request import proxy_bypass_environment +import urllib3 +import ipaddress + +from {{packageName}}.exceptions import ApiException, UnauthorizedException, ForbiddenException, NotFoundException, ServiceException, ApiValueError + + +logger = logging.getLogger(__name__) + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=None): + # urllib3.PoolManager will pass all kw parameters to connectionpool + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501 + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501 + # maxsize is the number of requests to host that are allowed in parallel # noqa: E501 + # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501 + + # cert_reqs + if configuration.verify_ssl: + cert_reqs = ssl.CERT_REQUIRED + else: + cert_reqs = ssl.CERT_NONE + + addition_pool_args = {} + if configuration.assert_hostname is not None: + addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501 + + if configuration.retries is not None: + addition_pool_args['retries'] = configuration.retries + + if configuration.socket_options is not None: + addition_pool_args['socket_options'] = configuration.socket_options + + if maxsize is None: + if configuration.connection_pool_maxsize is not None: + maxsize = configuration.connection_pool_maxsize + else: + maxsize = 4 + + # https pool manager + if configuration.proxy and not should_bypass_proxies( + configuration.host, no_proxy=configuration.no_proxy or ''): + self.pool_manager = urllib3.ProxyManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=configuration.ssl_ca_cert, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + proxy_url=configuration.proxy, + proxy_headers=configuration.proxy_headers, + **addition_pool_args + ) + else: + self.pool_manager = urllib3.PoolManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=configuration.ssl_ca_cert, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + **addition_pool_args + ) + + def request(self, method, url, query_params=None, headers=None, + body=None, post_params=None, *, _parse_response=True, + _request_timeout=None, _check_status=True) -> urllib3.HTTPResponse: + """Perform requests. + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: plain parameters for POST/PUT/PATCH requests, + when 'Content-Type' is + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _parse_response: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ApiValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + + timeout = None + if _request_timeout: + if isinstance(_request_timeout, (int, float)): # noqa: E501,F821 + timeout = urllib3.Timeout(total=_request_timeout) + elif (isinstance(_request_timeout, tuple) and + len(_request_timeout) == 2): + timeout = urllib3.Timeout( + connect=_request_timeout[0], read=_request_timeout[1]) + + try: + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + # Only set a default Content-Type for POST, PUT, PATCH and OPTIONS requests + if (method != 'DELETE') and ('Content-Type' not in headers): + headers['Content-Type'] = 'application/json' + if query_params: + url += '?' + urlencode(query_params) + if ('Content-Type' not in headers) or (re.search('json', + headers['Content-Type'], re.IGNORECASE)): + request_body = None + if body is not None: + request_body = json.dumps(body) + elif post_params and method == "POST": + request_body = json.dumps(post_params) + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=_parse_response, + timeout=timeout, + headers=headers) + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + r = self.pool_manager.request( + method, url, + fields=post_params, + encode_multipart=False, + preload_content=_parse_response, + timeout=timeout, + headers=headers) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by urllib3 will be + # overwritten. + del headers['Content-Type'] + r = self.pool_manager.request( + method, url, + fields=post_params, + encode_multipart=True, + preload_content=_parse_response, + timeout=timeout, + headers=headers) + # Pass a `string` parameter directly in the body to support + # other content types than Json when `body` argument is + # provided in serialized form + elif isinstance(body, str) or isinstance(body, bytes): + request_body = body + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=_parse_response, + timeout=timeout, + headers=headers) + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + # For `GET`, `HEAD` + else: + r = self.pool_manager.request(method, url, + fields=query_params, + preload_content=_parse_response, + timeout=timeout, + headers=headers) + except urllib3.exceptions.SSLError as e: + msg = "{0}\n{1}".format(type(e).__name__, str(e)) + raise ApiException(status=0, reason=msg) + + if _check_status and not 200 <= r.status <= 299: + if r.status == 401: + raise UnauthorizedException(http_resp=r) + + if r.status == 403: + raise ForbiddenException(http_resp=r) + + if r.status == 404: + raise NotFoundException(http_resp=r) + + if 500 <= r.status <= 599: + raise ServiceException(http_resp=r) + + raise ApiException(http_resp=r) + + return r + + def GET(self, url, headers=None, query_params=None, _parse_response=True, + _request_timeout=None, _check_status=True): + return self.request("GET", url, + headers=headers, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + query_params=query_params) + + def HEAD(self, url, headers=None, query_params=None, _parse_response=True, + _request_timeout=None, _check_status=True): + return self.request("HEAD", url, + headers=headers, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + query_params=query_params, + _check_status=_check_status) + + def OPTIONS(self, url, headers=None, query_params=None, post_params=None, + body=None, _parse_response=True, _request_timeout=None, _check_status=True): + return self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + body=body) + + def DELETE(self, url, headers=None, query_params=None, body=None, + _parse_response=True, _request_timeout=None, _check_status=True): + return self.request("DELETE", url, + headers=headers, + query_params=query_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + body=body) + + def POST(self, url, headers=None, query_params=None, post_params=None, + body=None, _parse_response=True, _request_timeout=None, _check_status=True): + return self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + body=body) + + def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _parse_response=True, _request_timeout=None, _check_status=True): + return self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + body=body) + + def PATCH(self, url, headers=None, query_params=None, post_params=None, + body=None, _parse_response=True, _request_timeout=None, _check_status=True): + return self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _parse_response=_parse_response, + _request_timeout=_request_timeout, + _check_status=_check_status, + body=body) + +# end of class RESTClientObject + + +def is_ipv4(target): + """ Test if IPv4 address or not + """ + try: + chk = ipaddress.IPv4Address(target) + return True + except ipaddress.AddressValueError: + return False + + +def in_ipv4net(target, net): + """ Test if target belongs to given IPv4 network + """ + try: + nw = ipaddress.IPv4Network(net) + ip = ipaddress.IPv4Address(target) + if ip in nw: + return True + return False + except ipaddress.AddressValueError: + return False + except ipaddress.NetmaskValueError: + return False + + +def should_bypass_proxies(url, no_proxy=None): + """ Yet another requests.should_bypass_proxies + Test if proxies should not be used for a particular url. + """ + + parsed = urlparse(url) + + # special cases + if parsed.hostname in [None, '']: + return True + + # special cases + if no_proxy in [None, '']: + return False + if no_proxy == '*': + return True + + no_proxy = no_proxy.lower().replace(' ', ''); + entries = ( + host for host in no_proxy.split(',') if host + ) + + if is_ipv4(parsed.hostname): + for item in entries: + if in_ipv4net(parsed.hostname, item): + return True + return proxy_bypass_environment(parsed.hostname, {'no': no_proxy}) diff --git a/cvat-sdk/gen/templates/openapi-generator/return_type.mustache b/cvat-sdk/gen/templates/openapi-generator/return_type.mustache new file mode 100644 index 000000000000..28075a512eb2 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/return_type.mustache @@ -0,0 +1 @@ +%%%make_type_annotation!!!{{#returnType}}{{{.}}}{{/returnType}}{{^returnType}}None{{/returnType}}%%% \ No newline at end of file diff --git a/cvat-sdk/gen/templates/openapi-generator/setup.mustache b/cvat-sdk/gen/templates/openapi-generator/setup.mustache new file mode 100644 index 000000000000..13c2a9535966 --- /dev/null +++ b/cvat-sdk/gen/templates/openapi-generator/setup.mustache @@ -0,0 +1,90 @@ +{{>partial_header}} + +import os.path as osp +import re +from setuptools import find_packages, setup + +{{#apiInfo}} +{{#apis}} +{{#-last}} +# To install the library, run the following +# +# python -m pip install . +# +# prerequisite: setuptools +# http://pypi.python.org/pypi/setuptools + +def find_version(project_dir=None): + if not project_dir: + project_dir = osp.dirname(osp.abspath(__file__)) + + file_path = osp.join(project_dir, "version.py") + + with open(file_path, "r") as version_file: + version_text = version_file.read() + + # PEP440: + # https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions + pep_regex = r"([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*((a|b|rc)(0|[1-9]\d*))?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*))?" + version_regex = r"VERSION\s*=\s*.(" + pep_regex + ")." + match = re.match(version_regex, version_text) + if not match: + raise RuntimeError("Failed to find version string in '%s'" % file_path) + + version = version_text[match.start(1) : match.end(1)] + return version + + +BASE_REQUIREMENTS_FILE = "requirements/base.txt" + + +def parse_requirements(filename=BASE_REQUIREMENTS_FILE): + with open(filename) as fh: + reqs = [line.strip() for line in fh.readlines()] + + for req in reqs[:]: + if req.startswith('-r '): + dep = req.split(maxsplit=2)[1] + reqs.extend(parse_requirements(osp.join(osp.dirname(filename), dep.lstrip('/\\')))) + + for req in reqs[:]: + if req.startswith('-r '): + reqs.remove(req) + + return reqs + + +BASE_REQUIREMENTS = parse_requirements(BASE_REQUIREMENTS_FILE) + +with open("README.md", "r") as fh: + long_description = fh.read() + +setup( + name="{{{projectName}}}", + version=find_version(project_dir="cvat_sdk"), + description="{{appName}}", + long_description=long_description, + long_description_content_type="text/markdown", + author="{{infoName}}{{^infoName}}OpenAPI Generator community{{/infoName}}", + author_email="{{infoEmail}}{{^infoEmail}}team@openapitools.org{{/infoEmail}}", + url="{{packageUrl}}", + keywords=["OpenAPI", "OpenAPI-Generator", "{{{appName}}}"], + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires="{{{generatorLanguageVersion}}}", + install_requires=BASE_REQUIREMENTS, + extras_require={ + "pytorch": ['appdirs', 'torch', 'torchvision'], + }, + package_dir={"": "."}, + packages=find_packages(include=["cvat_sdk*"]), + include_package_data=True, + {{#licenseInfo}}license="{{.}}", + {{/licenseInfo}} +) +{{/-last}} +{{/apis}} +{{/apiInfo}} diff --git a/cvat-sdk/gen/templates/requirements/base.txt b/cvat-sdk/gen/templates/requirements/base.txt new file mode 100644 index 000000000000..ffc88d7e7eff --- /dev/null +++ b/cvat-sdk/gen/templates/requirements/base.txt @@ -0,0 +1,8 @@ +-r api_client.txt + +attrs >= 21.4.0 +packaging >= 21.3 +Pillow >= 9.0.1 +tqdm >= 4.64.0 +tuspy == 0.2.5 # have it pinned, because SDK has lots of patched TUS code +typing_extensions >= 4.2.0 \ No newline at end of file diff --git a/cvat-sdk/gen/templates/version.py.template b/cvat-sdk/gen/templates/version.py.template new file mode 100644 index 000000000000..9d40f77c020a --- /dev/null +++ b/cvat-sdk/gen/templates/version.py.template @@ -0,0 +1 @@ +VERSION = "{{packageVersion}}" diff --git a/cvat-sdk/pyproject.toml b/cvat-sdk/pyproject.toml new file mode 100644 index 000000000000..67280a49cac1 --- /dev/null +++ b/cvat-sdk/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.isort] +profile = "black" +forced_separate = ["tests"] +line_length = 100 +skip_gitignore = true # align tool behavior with Black + +# Can't just use a pyproject in the root dir, so duplicate +# https://github.com/psf/black/issues/2863 +[tool.black] +line-length = 100 +target-version = ['py38'] diff --git a/cvat-ui/.env b/cvat-ui/.env index 424e2c3cc7ee..e2a36ff2fc78 100644 --- a/cvat-ui/.env +++ b/cvat-ui/.env @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/.eslintrc.js b/cvat-ui/.eslintrc.js index d4dbc2166da5..01fb3aea1d70 100644 --- a/cvat-ui/.eslintrc.js +++ b/cvat-ui/.eslintrc.js @@ -1,16 +1,11 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT const globalConfig = require('../.eslintrc.js'); module.exports = { - env: { - node: true, - }, parserOptions: { - parser: '@typescript-eslint/parser', - ecmaVersion: 6, project: './tsconfig.json', tsconfigRootDir: __dirname, }, @@ -20,27 +15,9 @@ module.exports = { 'node_modules/**', 'dist/**', ], - plugins: ['@typescript-eslint'], - extends: ['plugin:@typescript-eslint/recommended', 'airbnb-typescript'], + extends: ['airbnb-typescript'], rules: { - ...globalConfig.rules, - - '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/indent': ['error', 4], - '@typescript-eslint/lines-between-class-members': 0, - '@typescript-eslint/no-explicit-any': [0], - '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }], - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/ban-types': [ - 'error', - { - types: { - '{}': false, // TODO: try to fix with Record - object: false, // TODO: try to fix with Record - Function: false, // TODO: try to fix somehow - }, - }, - ], + ...globalConfig.rules, // need to import rules again because they've been redefined by "airbnb-typescript" 'react/no-did-update-set-state': 0, // https://github.com/airbnb/javascript/issues/1875 'react/require-default-props': 'off', @@ -52,11 +29,4 @@ module.exports = { 'react/jsx-props-no-spreading': 0, 'jsx-quotes': ['error', 'prefer-single'], }, - // settings: { - // 'import/resolver': { - // node: { - // paths: ['src', `${__dirname}/src`], - // }, - // }, - // }, }; diff --git a/cvat-ui/README.md b/cvat-ui/README.md index 349d6ff2b2af..b89fc6449423 100644 --- a/cvat-ui/README.md +++ b/cvat-ui/README.md @@ -8,33 +8,33 @@ This is a client UI for Computer Vision Annotation Tool based on React, Redux an If you make changes in this package, please do following: -- After not important changes (typos, bug fixes, refactoring) do: `npm version patch` -- After adding new features do: `npm version minor` -- After significant UI redesign do: `npm version major` +- After not important changes (typos, bug fixes, refactoring) do: `yarn version --patch` +- After adding new features do: `yarn version --minor` +- After significant UI redesign do: `yarn version --major` Important: If you have changed versions for `cvat-core`, `cvat-canvas`, `cvat-data`, -you also need to do `npm install` to update `package-lock.json` +you also need to do `yarn install` to update `package-lock.json` ## Commands - Installing dependencies: ```bash -cd ../cvat-core && npm ci && cd - && npm ci +cd ../cvat-core && yarn --frozen-lockfile && cd - && yarn --frozen-lockfile ``` - Running development UI server with autorebuild on change ```bash -npm start +yarn run start ``` - Building the module from sources in the `dist` directory: ```bash -npm run build -npm run build -- --mode=development # without a minification +yarn run build +yarn run build --mode=development # without a minification ``` -Important: You also have to run CVAT REST API server (please read `https://openvinotoolkit.github.io/cvat/docs/contributing/`) +Important: You also have to run CVAT REST API server (please read `https://opencv.github.io/cvat/docs/contributing/`) to correct working since UI gets all necessary data (tasks, users, annotations) from there diff --git a/cvat-ui/index.d.ts b/cvat-ui/index.d.ts index 30ccab9bd5d5..5a18ea07ae22 100644 --- a/cvat-ui/index.d.ts +++ b/cvat-ui/index.d.ts @@ -1,6 +1,8 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT +import 'redux-thunk/extend-redux'; declare module '*.svg'; declare module 'cvat-core/src/api'; diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json deleted file mode 100644 index ad917a1e9023..000000000000 --- a/cvat-ui/package-lock.json +++ /dev/null @@ -1,11093 +0,0 @@ -{ - "name": "cvat-ui", - "version": "1.37.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "cvat-ui", - "version": "1.37.0", - "license": "MIT", - "dependencies": { - "@ant-design/icons": "^4.6.3", - "@types/lodash": "^4.14.172", - "@types/platform": "^1.3.4", - "@types/react": "^16.14.15", - "@types/react-color": "^3.0.5", - "@types/react-dom": "^16.9.14", - "@types/react-redux": "^7.1.18", - "@types/react-resizable": "^1.7.3", - "@types/react-router": "^5.1.16", - "@types/react-router-dom": "^5.1.9", - "@types/react-share": "^3.0.3", - "@types/redux-logger": "^3.0.9", - "@types/resize-observer-browser": "^0.1.6", - "antd": "^4.17.0", - "copy-to-clipboard": "^3.3.1", - "cvat-canvas": "file:../cvat-canvas", - "cvat-canvas3d": "file:../cvat-canvas3d", - "cvat-core": "file:../cvat-core", - "dotenv-webpack": "^1.8.0", - "error-stack-parser": "^2.0.6", - "lodash": "^4.17.21", - "moment": "^2.29.1", - "mousetrap": "^1.6.5", - "platform": "^1.3.6", - "prop-types": "^15.7.2", - "react": "^16.14.0", - "react-awesome-query-builder": "^4.5.1", - "react-color": "^2.19.3", - "react-cookie": "^4.0.3", - "react-dom": "^16.14.0", - "react-moment": "^1.1.1", - "react-redux": "^7.2.5", - "react-resizable": "^1.11.1", - "react-router": "^5.1.0", - "react-router-dom": "^5.1.0", - "react-share": "^4.4.0", - "react-sortable-hoc": "^2.0.0", - "redux": "^4.1.1", - "redux-devtools-extension": "^2.13.9", - "redux-logger": "^3.0.6", - "redux-thunk": "^2.3.0" - } - }, - "../cvat-canvas": { - "version": "2.13.2", - "license": "MIT", - "dependencies": { - "@types/polylabel": "^1.0.5", - "polylabel": "^1.1.0", - "svg.draggable.js": "2.2.2", - "svg.draw.js": "^2.0.4", - "svg.js": "2.7.1", - "svg.resize.js": "1.4.3", - "svg.select.js": "3.0.1" - } - }, - "../cvat-canvas3d": { - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "@types/three": "^0.125.3", - "camera-controls": "^1.25.3", - "three": "^0.126.1" - }, - "devDependencies": {} - }, - "../cvat-core": { - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "axios": "^0.21.4", - "browser-or-node": "^1.2.1", - "cvat-data": "../cvat-data", - "detect-browser": "^5.2.1", - "error-stack-parser": "^2.0.2", - "form-data": "^2.5.0", - "jest-config": "^26.6.3", - "js-cookie": "^2.2.0", - "json-logic-js": "^2.0.1", - "platform": "^1.3.5", - "quickhull": "^1.0.3", - "store": "^2.0.12", - "tus-js-client": "^2.3.0" - }, - "devDependencies": { - "coveralls": "^3.0.5", - "jest": "^26.6.3", - "jest-junit": "^6.4.0", - "jsdoc": "^3.6.6" - } - }, - "node_modules/@ant-design/colors": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", - "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", - "dependencies": { - "@ctrl/tinycolor": "^3.4.0" - } - }, - "node_modules/@ant-design/icons": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.7.0.tgz", - "integrity": "sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==", - "dependencies": { - "@ant-design/colors": "^6.0.0", - "@ant-design/icons-svg": "^4.2.1", - "@babel/runtime": "^7.11.2", - "classnames": "^2.2.6", - "rc-util": "^5.9.4" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "react": ">=16.0.0", - "react-dom": ">=16.0.0" - } - }, - "node_modules/@ant-design/icons-svg": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz", - "integrity": "sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==" - }, - "node_modules/@ant-design/icons/node_modules/rc-util": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.14.0.tgz", - "integrity": "sha512-2vy6/Z1BJUcwLjm/UEJb/htjUTQPigITUIemCcFEo1fQevAumc9sA32x2z5qyWoa9uhrXbiAjSDpPIUqyg65sA==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/@ant-design/icons/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@ctrl/tinycolor": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", - "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@date-io/core": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz", - "integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==" - }, - "node_modules/@date-io/moment": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-1.3.13.tgz", - "integrity": "sha512-3kJYusJtQuOIxq6byZlzAHoW/18iExJer9qfRF5DyyzdAk074seTuJfdofjz4RFfTd/Idk8WylOQpWtERqvFuQ==", - "dependencies": { - "@date-io/core": "^1.3.13" - }, - "peerDependencies": { - "moment": "^2.24.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", - "peer": true - }, - "node_modules/@material-ui/core": { - "version": "4.12.3", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.3.tgz", - "integrity": "sha512-sdpgI/PL56QVsEJldwEe4FFaFTLUqN+rd7sSZiRCdx2E/C7z5yK0y/khAWVBH24tXwto7I1hCzNWfJGZIYJKnw==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.4.4", - "@material-ui/styles": "^4.11.4", - "@material-ui/system": "^4.12.1", - "@material-ui/types": "5.1.0", - "@material-ui/utils": "^4.11.2", - "@types/react-transition-group": "^4.2.0", - "clsx": "^1.0.4", - "hoist-non-react-statics": "^3.3.2", - "popper.js": "1.16.1-lts", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0", - "react-transition-group": "^4.4.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/material-ui" - }, - "peerDependencies": { - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/core/node_modules/@material-ui/styles": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz", - "integrity": "sha512-KNTIZcnj/zprG5LW0Sao7zw+yG3O35pviHzejMdcSGCdWbiO8qzRgOYL8JAxAsWBKOKYwVZxXtHWaB5T2Kvxew==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.4.4", - "@emotion/hash": "^0.8.0", - "@material-ui/types": "5.1.0", - "@material-ui/utils": "^4.11.2", - "clsx": "^1.0.4", - "csstype": "^2.5.2", - "hoist-non-react-statics": "^3.3.2", - "jss": "^10.5.1", - "jss-plugin-camel-case": "^10.5.1", - "jss-plugin-default-unit": "^10.5.1", - "jss-plugin-global": "^10.5.1", - "jss-plugin-nested": "^10.5.1", - "jss-plugin-props-sort": "^10.5.1", - "jss-plugin-rule-value-function": "^10.5.1", - "jss-plugin-vendor-prefixer": "^10.5.1", - "prop-types": "^15.7.2" - }, - "engines": { - "node": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/material-ui" - }, - "peerDependencies": { - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/core/node_modules/@material-ui/system": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.1.tgz", - "integrity": "sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.11.2", - "csstype": "^2.5.2", - "prop-types": "^15.7.2" - }, - "engines": { - "node": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/material-ui" - }, - "peerDependencies": { - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/core/node_modules/@material-ui/types": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", - "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", - "peer": true, - "peerDependencies": { - "@types/react": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/core/node_modules/@material-ui/utils": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz", - "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/@material-ui/core/node_modules/csstype": { - "version": "2.6.18", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz", - "integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==", - "peer": true - }, - "node_modules/@material-ui/core/node_modules/react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/@material-ui/icons": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz", - "integrity": "sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.4.4" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "@material-ui/core": "^4.0.0", - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/lab": { - "version": "4.0.0-alpha.60", - "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.60.tgz", - "integrity": "sha512-fadlYsPJF+0fx2lRuyqAuJj7hAS1tLDdIEEdov5jlrpb5pp4b+mRDUqQTUxi4inRZHS1bEXpU8QWUhO6xX88aA==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.11.2", - "clsx": "^1.0.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "@material-ui/core": "^4.12.1", - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@material-ui/lab/node_modules/@material-ui/utils": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz", - "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/@material-ui/pickers": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.3.10.tgz", - "integrity": "sha512-hS4pxwn1ZGXVkmgD4tpFpaumUaAg2ZzbTrxltfC5yPw4BJV+mGkfnQOB4VpWEYZw2jv65Z0wLwDE/piQiPPZ3w==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.6.0", - "@date-io/core": "1.x", - "@types/styled-jsx": "^2.2.8", - "clsx": "^1.0.2", - "react-transition-group": "^4.0.0", - "rifm": "^0.7.0" - }, - "peerDependencies": { - "@date-io/core": "^1.3.6", - "@material-ui/core": "^4.0.0", - "prop-types": "^15.6.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/@material-ui/pickers/node_modules/react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/@material-ui/pickers/node_modules/rifm": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz", - "integrity": "sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.3.1" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/@types/cookie": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", - "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" - }, - "node_modules/@types/history": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz", - "integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==" - }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "node_modules/@types/lodash": { - "version": "4.14.175", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", - "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==" - }, - "node_modules/@types/platform": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/platform/-/platform-1.3.4.tgz", - "integrity": "sha512-U0o4K+GNiK0PNxoDwd8xRnvLVe4kzei6opn3/FCjAriqaP+rfrDdSl1kP/hLL6Y3/Y3hhGnBwD4dCkkAqs1W/Q==" - }, - "node_modules/@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" - }, - "node_modules/@types/react": { - "version": "16.14.16", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.16.tgz", - "integrity": "sha512-7waDQ0h1TkAk99S04wV0LUiiSXpT02lzrdDF4WZFqn2W0XE5ICXLBMtqXWZ688aX2dJislQ3knmZX/jH53RluQ==", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-color": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.5.tgz", - "integrity": "sha512-0VZy8Uq5x04cW5QFz24Qw8MMMlsMi8Bb+XG5h59ATqPnWVq6OheHtrwv5LeakdTRDaECQnExJNSFOsSe4Eo/zQ==", - "dependencies": { - "@types/react": "*", - "@types/reactcss": "*" - } - }, - "node_modules/@types/react-dom": { - "version": "16.9.14", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.14.tgz", - "integrity": "sha512-FIX2AVmPTGP30OUJ+0vadeIFJJ07Mh1m+U0rxfgyW34p3rTlXI+nlenvAxNn4BP36YyI9IJ/+UJ7Wu22N1pI7A==", - "dependencies": { - "@types/react": "^16" - } - }, - "node_modules/@types/react-redux": { - "version": "7.1.18", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz", - "integrity": "sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ==", - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "node_modules/@types/react-resizable": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-1.7.3.tgz", - "integrity": "sha512-DAx+hdnHFMJHgl8geiKo3jLt1GCT838SwQixjCtbRRfqCBawAKriVLCZ1nvp7B/2Pxd94MWod8NyJEnAAmNHNA==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-router": { - "version": "5.1.17", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz", - "integrity": "sha512-RNSXOyb3VyRs/EOGmjBhhGKTbnN6fHWvy5FNLzWfOWOGjgVUKqJZXfpKzLmgoU8h6Hj8mpALj/mbXQASOb92wQ==", - "dependencies": { - "@types/history": "*", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.1.tgz", - "integrity": "sha512-UvyRy73318QI83haXlaMwmklHHzV9hjl3u71MmM6wYNu0hOVk9NLTa0vGukf8zXUqnwz4O06ig876YSPpeK28A==", - "dependencies": { - "@types/history": "*", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "node_modules/@types/react-share": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/react-share/-/react-share-3.0.3.tgz", - "integrity": "sha512-GpKAVNbwMBgad0995uVLkOdKWp3CjCrvIeUt4qZcsrgLtf7SMR7gIfMgC9X2rsfLgN6saT/nr2T4QLJE9cCZiA==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.3.tgz", - "integrity": "sha512-fUx5muOWSYP8Bw2BUQ9M9RK9+W1XBK/7FLJ8PTQpnpTEkn0ccyMffyEQvan4C3h53gHdx7KE5Qrxi/LnUGQtdg==", - "peer": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/reactcss": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.4.tgz", - "integrity": "sha512-1rhVqteMSD6KQjO+dPBObE1OkKadw00HVJkG5WCYsyvMwGgdTZ530wF7Bkrg/4TWxB2AtINIzFotjW51eViw+w==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/redux-logger": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/redux-logger/-/redux-logger-3.0.9.tgz", - "integrity": "sha512-cwYhVbYNgH01aepeMwhd0ABX6fhVB2rcQ9m80u8Fl50ZODhsZ8RhQArnLTkE7/Zrfq4Sz/taNoF7DQy9pCZSKg==", - "dependencies": { - "redux": "^4.0.0" - } - }, - "node_modules/@types/resize-observer-browser": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.6.tgz", - "integrity": "sha512-61IfTac0s9jvNtBCpyo86QeaN8qqpMGHdK0uGKCCIy2dt5/Yk84VduHIdWAcmkC5QvdkPL0p5eWYgUZtHKKUVg==" - }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "node_modules/@types/styled-jsx": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/@types/styled-jsx/-/styled-jsx-2.2.9.tgz", - "integrity": "sha512-W/iTlIkGEyTBGTEvZCey8EgQlQ5l0DwMqi3iOXlLs2kyBwYTXHKEiU6IZ5EwoRwngL8/dGYuzezSup89ttVHLw==", - "peer": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", - "peer": true, - "dependencies": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", - "peer": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", - "peer": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "peer": true - }, - "node_modules/@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "peer": true, - "dependencies": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", - "peer": true - }, - "node_modules/@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", - "peer": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "peer": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", - "peer": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", - "peer": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "peer": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "peer": true - }, - "node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "peer": true, - "peerDependencies": { - "ajv": ">=5.0.0" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peer": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/antd": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/antd/-/antd-4.17.0.tgz", - "integrity": "sha512-V2xBGzBK+s2Iy7Re5JOcOBtAvaZtJ9t7R1fFOP51T6ynfSvJqaRtG4DjBu7i9inhXkCzrt7eGcX3vMqLCqXV8g==", - "dependencies": { - "@ant-design/colors": "^6.0.0", - "@ant-design/icons": "^4.7.0", - "@ant-design/react-slick": "~0.28.1", - "@babel/runtime": "^7.12.5", - "@ctrl/tinycolor": "^3.4.0", - "array-tree-filter": "^2.1.0", - "classnames": "^2.2.6", - "copy-to-clipboard": "^3.2.0", - "lodash": "^4.17.21", - "moment": "^2.25.3", - "rc-cascader": "~2.1.0", - "rc-checkbox": "~2.3.0", - "rc-collapse": "~3.1.0", - "rc-dialog": "~8.6.0", - "rc-drawer": "~4.4.2", - "rc-dropdown": "~3.2.0", - "rc-field-form": "~1.21.0", - "rc-image": "~5.2.5", - "rc-input-number": "~7.3.0", - "rc-mentions": "~1.6.1", - "rc-menu": "~9.0.12", - "rc-motion": "^2.4.4", - "rc-notification": "~4.5.7", - "rc-pagination": "~3.1.9", - "rc-picker": "~2.5.17", - "rc-progress": "~3.1.0", - "rc-rate": "~2.9.0", - "rc-resize-observer": "^1.0.0", - "rc-select": "~13.1.0-alpha.0", - "rc-slider": "~9.7.4", - "rc-steps": "~4.1.0", - "rc-switch": "~3.2.0", - "rc-table": "~7.19.0", - "rc-tabs": "~11.10.0", - "rc-textarea": "~0.3.0", - "rc-tooltip": "~5.1.1", - "rc-tree": "~5.2.0", - "rc-tree-select": "~4.6.0", - "rc-trigger": "^5.2.10", - "rc-upload": "~4.3.0", - "rc-util": "^5.14.0", - "scroll-into-view-if-needed": "^2.2.25" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ant-design" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/@ant-design/react-slick": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.28.4.tgz", - "integrity": "sha512-j9eAHTn7GxbXUFNknJoHS2ceAsqrQi2j8XykjZE1IXCD8kJF+t28EvhBLniDpbOsBk/3kjalnhriTfZcjBHNqg==", - "dependencies": { - "@babel/runtime": "^7.10.4", - "classnames": "^2.2.5", - "json2mq": "^0.2.0", - "lodash": "^4.17.21", - "resize-observer-polyfill": "^1.5.0" - }, - "peerDependencies": { - "react": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-cascader": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-2.1.5.tgz", - "integrity": "sha512-FiGPfSxKmSft2CT2XSr6HeKihqcxM+1ozmH6FGXTDthVNNvV0ai82CA6l30iPmMmlflwDfSm/623qkekqNq4BQ==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "array-tree-filter": "^2.1.0", - "rc-tree-select": "~4.6.0", - "rc-trigger": "^5.0.4", - "rc-util": "^5.0.1", - "warning": "^4.0.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-checkbox": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-2.3.2.tgz", - "integrity": "sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-collapse": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.1.2.tgz", - "integrity": "sha512-HujcKq7mghk/gVKeI6EjzTbb8e19XUZpakrYazu1MblEZ3Hu3WBMSN4A3QmvbF6n1g7x6lUlZvsHZ5shABWYOQ==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.3.4", - "rc-util": "^5.2.1", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-dialog": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-8.6.0.tgz", - "integrity": "sha512-GSbkfqjqxpZC5/zc+8H332+q5l/DKUhpQr0vdX2uDsxo5K0PhvaMEVjyoJUTkZ3+JstEADQji1PVLVb/2bJeOQ==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-motion": "^2.3.0", - "rc-util": "^5.6.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-drawer": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-4.4.3.tgz", - "integrity": "sha512-FYztwRs3uXnFOIf1hLvFxIQP9MiZJA+0w+Os8dfDh/90X7z/HqP/Yg+noLCIeHEbKln1Tqelv8ymCAN24zPcfQ==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-util": "^5.7.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-dropdown": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-3.2.0.tgz", - "integrity": "sha512-j1HSw+/QqlhxyTEF6BArVZnTmezw2LnSmRk6I9W7BCqNCKaRwleRmMMs1PHbuaG8dKHVqP6e21RQ7vPBLVnnNw==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-trigger": "^5.0.4" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/antd/node_modules/rc-field-form": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.21.2.tgz", - "integrity": "sha512-LR/bURt/Tf5g39mb0wtMtQuWn42d/7kEzpzlC5fNC7yaRVmLTtlPP4sBBlaViETM9uZQKLoaB0Pt9Mubhm9gow==", - "dependencies": { - "@babel/runtime": "^7.8.4", - "async-validator": "^4.0.2", - "rc-util": "^5.8.0" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">= 16.9.0", - "react-dom": ">= 16.9.0" - } - }, - "node_modules/antd/node_modules/rc-image": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-5.2.5.tgz", - "integrity": "sha512-qUfZjYIODxO0c8a8P5GeuclYXZjzW4hV/5hyo27XqSFo1DmTCs2HkVeQObkcIk5kNsJtgsj1KoPThVsSc/PXOw==", - "dependencies": { - "@babel/runtime": "^7.11.2", - "classnames": "^2.2.6", - "rc-dialog": "~8.6.0", - "rc-util": "^5.0.6" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-input-number": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.3.4.tgz", - "integrity": "sha512-W9uqSzuvJUnz8H8vsVY4kx+yK51SsAxNTwr8SNH4G3XqQNocLVmKIibKFRjocnYX1RDHMND9FFbgj2h7E7nvGA==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-util": "^5.9.8" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-mentions": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-1.6.1.tgz", - "integrity": "sha512-LDzGI8jJVGnkhpTZxZuYBhMz3avcZZqPGejikchh97xPni/g4ht714Flh7DVvuzHQ+BoKHhIjobHnw1rcP8erg==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-menu": "^9.0.0", - "rc-textarea": "^0.3.0", - "rc-trigger": "^5.0.4", - "rc-util": "^5.0.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-menu": { - "version": "9.0.12", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.0.12.tgz", - "integrity": "sha512-8uy47DL36iDEwVZdUO/fjhhW5+4j0tYlrCsOzw6iy8MJqKL7/HC2pj7sL/S9ayp2+hk9fYQYB9Tu+UN+N2OOOQ==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.4.3", - "rc-overflow": "^1.2.0", - "rc-trigger": "^5.1.2", - "rc-util": "^5.12.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-menu/node_modules/rc-overflow": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.2.tgz", - "integrity": "sha512-X5kj9LDU1ue5wHkqvCprJWLKC+ZLs3p4He/oxjZ1Q4NKaqKBaYf5OdSzRSgh3WH8kSdrfU8LjvlbWnHgJOEkNQ==", - "dependencies": { - "@babel/runtime": "^7.11.1", - "classnames": "^2.2.1", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.5.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-motion": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.4.4.tgz", - "integrity": "sha512-ms7n1+/TZQBS0Ydd2Q5P4+wJTSOrhIrwNxLXCZpR7Fa3/oac7Yi803HDALc2hLAKaCTQtw9LmQeB58zcwOsqlQ==", - "dependencies": { - "@babel/runtime": "^7.11.1", - "classnames": "^2.2.1", - "rc-util": "^5.2.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-notification": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-4.5.7.tgz", - "integrity": "sha512-zhTGUjBIItbx96SiRu3KVURcLOydLUHZCPpYEn1zvh+re//Tnq/wSxN4FKgp38n4HOgHSVxcLEeSxBMTeBBDdw==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.2.0", - "rc-util": "^5.0.1" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-pagination": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-3.1.9.tgz", - "integrity": "sha512-IKBKaJ4icVPeEk9qRHrFBJmHxBUrCp3+nENBYob4Ofqsu3RXjBOy4N36zONO7oubgLyiG3PxVmyAuVlTkoc7Jg==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-picker": { - "version": "2.5.18", - "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.5.18.tgz", - "integrity": "sha512-XyieTl8GOC5TeQFEvYbjx/Mtc0/CjruS7mKFT6Fy65FbGXmoFsWoWvIi+ylFx/BQHPGQi7a7vCNoZJ2TTqcZoA==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "date-fns": "2.x", - "dayjs": "1.x", - "moment": "^2.24.0", - "rc-trigger": "^5.0.4", - "rc-util": "^5.4.0", - "shallowequal": "^1.1.0" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-progress": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.1.4.tgz", - "integrity": "sha512-XBAif08eunHssGeIdxMXOmRQRULdHaDdIFENQ578CMb4dyewahmmfJRyab+hw4KH4XssEzzYOkAInTLS7JJG+Q==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-rate": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.9.1.tgz", - "integrity": "sha512-MmIU7FT8W4LYRRHJD1sgG366qKtSaKb67D0/vVvJYR0lrCuRrCiVQ5qhfT5ghVO4wuVIORGpZs7ZKaYu+KMUzA==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-util": "^5.0.1" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-resize-observer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.0.1.tgz", - "integrity": "sha512-OxO2mJI9e8610CAWBFfm52SPvWib0eNKjaSsRbbKHmLaJIxw944P+D61DlLJ/w2vuOjGNcalJu8VdqyNm/XCRg==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "rc-util": "^5.0.0", - "resize-observer-polyfill": "^1.5.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-select": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-13.1.1.tgz", - "integrity": "sha512-Oy4L27x5QgGR8902pw0bJVjrTWFnKPKvdLHzJl5pjiA+jM1hpzDfLGg/bY2ntk5ElxxQKZUwbFKUeqfCQU7SrQ==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.0.1", - "rc-overflow": "^1.0.0", - "rc-trigger": "^5.0.4", - "rc-util": "^5.9.8", - "rc-virtual-list": "^3.2.0" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/antd/node_modules/rc-select/node_modules/rc-overflow": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.2.tgz", - "integrity": "sha512-X5kj9LDU1ue5wHkqvCprJWLKC+ZLs3p4He/oxjZ1Q4NKaqKBaYf5OdSzRSgh3WH8kSdrfU8LjvlbWnHgJOEkNQ==", - "dependencies": { - "@babel/runtime": "^7.11.1", - "classnames": "^2.2.1", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.5.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-select/node_modules/rc-virtual-list": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz", - "integrity": "sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==", - "dependencies": { - "classnames": "^2.2.6", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.0.7" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/antd/node_modules/rc-slider": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-9.7.5.tgz", - "integrity": "sha512-LV/MWcXFjco1epPbdw1JlLXlTgmWpB9/Y/P2yinf8Pg3wElHxA9uajN21lJiWtZjf5SCUekfSP6QMJfDo4t1hg==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-tooltip": "^5.0.1", - "rc-util": "^5.16.1", - "shallowequal": "^1.1.0" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-slider/node_modules/rc-util": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.16.1.tgz", - "integrity": "sha512-kSCyytvdb3aRxQacS/71ta6c+kBWvM1v8/2h9d/HaNWauc3qB8pLnF20PJ8NajkNN8gb+rR1l0eWO+D4Pz+LLQ==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-steps": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-4.1.4.tgz", - "integrity": "sha512-qoCqKZWSpkh/b03ASGx1WhpKnuZcRWmvuW+ZUu4mvMdfvFzVxblTwUM+9aBd0mlEUFmt6GW8FXhMpHkK3Uzp3w==", - "dependencies": { - "@babel/runtime": "^7.10.2", - "classnames": "^2.2.3", - "rc-util": "^5.0.1" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-switch": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-3.2.2.tgz", - "integrity": "sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "rc-util": "^5.0.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-table": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.19.2.tgz", - "integrity": "sha512-NdpnoM50MK02H5/hGOsObfxCvGFUG5cHB9turE5BKJ81T5Ycbq193w5tLhnpILXe//Oanzr47MdMxkUnVGP+qg==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.14.0", - "shallowequal": "^1.1.0" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-tabs": { - "version": "11.10.1", - "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-11.10.1.tgz", - "integrity": "sha512-ey1i2uMyfnRNYbViLcUYGH+Y7hueJbdCVSLaXnXki9hxBcGqxJMPy9t5xR0n/3QFQspj7Tf6+2VTXVtmO7Yaug==", - "dependencies": { - "@babel/runtime": "^7.11.2", - "classnames": "2.x", - "rc-dropdown": "^3.2.0", - "rc-menu": "^9.0.0", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.5.0" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-textarea": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-0.3.5.tgz", - "integrity": "sha512-qa+k5vDn9ct65qr+SgD2KwJ9Xz6P84lG2z+TDht/RBr71WnM/K61PqHUAcUyU6YqTJD26IXgjPuuhZR7HMw7eA==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.7.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-tooltip": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.1.1.tgz", - "integrity": "sha512-alt8eGMJulio6+4/uDm7nvV+rJq9bsfxFDCI0ljPdbuoygUscbsMYb6EQgwib/uqsXQUvzk+S7A59uYHmEgmDA==", - "dependencies": { - "@babel/runtime": "^7.11.2", - "rc-trigger": "^5.0.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-tree": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.2.2.tgz", - "integrity": "sha512-ZQPGi5rGmipXvSUqeMbh0Rm0Cn2zFVWQFvS3sinH+lis5VNCChkFs2dAFpWZnb9/d/SZPeMfYG/x2XFq/q3UTA==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.0.1", - "rc-util": "^5.0.0", - "rc-virtual-list": "^3.4.1" - }, - "engines": { - "node": ">=10.x" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/antd/node_modules/rc-tree-select": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-4.6.3.tgz", - "integrity": "sha512-VymfystOnW8EfoWaWehgB8zpYKgRZf4ILu9KHf7FJZVZ/1dnBEHDqg1bBi43/1BYLwYFKSKKSjkYyNYntWJM4A==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-select": "~13.1.0-alpha.0", - "rc-tree": "~5.2.0", - "rc-util": "^5.7.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/antd/node_modules/rc-tree/node_modules/rc-virtual-list": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz", - "integrity": "sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==", - "dependencies": { - "classnames": "^2.2.6", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.0.7" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/antd/node_modules/rc-trigger": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.2.10.tgz", - "integrity": "sha512-FkUf4H9BOFDaIwu42fvRycXMAvkttph9AlbCZXssZDVzz2L+QZ0ERvfB/4nX3ZFPh1Zd+uVGr1DEDeXxq4J1TA==", - "dependencies": { - "@babel/runtime": "^7.11.2", - "classnames": "^2.2.6", - "rc-align": "^4.0.0", - "rc-motion": "^2.0.0", - "rc-util": "^5.5.0" - }, - "engines": { - "node": ">=8.x" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-trigger/node_modules/rc-align": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-4.0.11.tgz", - "integrity": "sha512-n9mQfIYQbbNTbefyQnRHZPWuTEwG1rY4a9yKlIWHSTbgwI+XUMGRYd0uJ5pE2UbrNX0WvnMBA1zJ3Lrecpra/A==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "dom-align": "^1.7.0", - "lodash": "^4.17.21", - "rc-util": "^5.3.0", - "resize-observer-polyfill": "^1.5.1" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-upload": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.3.1.tgz", - "integrity": "sha512-W8Iyv0LRyEnFEzpv90ET/i1XG2jlPzPxKkkOVtDfgh9c3f4lZV770vgpUfiyQza+iLtQLVco3qIvgue8aDiOsQ==", - "dependencies": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-util": "^5.2.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/rc-util": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.14.0.tgz", - "integrity": "sha512-2vy6/Z1BJUcwLjm/UEJb/htjUTQPigITUIemCcFEo1fQevAumc9sA32x2z5qyWoa9uhrXbiAjSDpPIUqyg65sA==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/antd/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "optional": true, - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "peer": true - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-tree-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", - "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==" - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "peer": true, - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - }, - "node_modules/assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "peer": true, - "dependencies": { - "object-assign": "^4.1.1", - "util": "0.10.3" - } - }, - "node_modules/assert/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "peer": true - }, - "node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "peer": true, - "dependencies": { - "inherits": "2.0.1" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "optional": true, - "peer": true - }, - "node_modules/async-validator": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz", - "integrity": "sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ==" - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "peer": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "peer": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "peer": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "peer": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "peer": true - }, - "node_modules/bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", - "peer": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "peer": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/braces/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "peer": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "peer": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "peer": true, - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "peer": true, - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "peer": true, - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "peer": true, - "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "peer": true, - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "peer": true, - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "peer": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "peer": true - }, - "node_modules/buffer/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "peer": true - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "peer": true - }, - "node_modules/cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "peer": true, - "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "peer": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "optional": true, - "peer": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "optional": true, - "peer": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/chokidar/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "optional": true, - "peer": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "peer": true - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "peer": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "peer": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "peer": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "peer": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "peer": true - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "peer": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "peer": true - }, - "node_modules/compute-scroll-into-view": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz", - "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "peer": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "peer": true - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "peer": true - }, - "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "peer": true, - "dependencies": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-to-clipboard": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", - "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", - "dependencies": { - "toggle-selection": "^1.0.6" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "peer": true - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "peer": true, - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "peer": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "peer": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "peer": true, - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/css-vendor": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", - "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.8.3", - "is-in-browser": "^1.0.2" - } - }, - "node_modules/csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" - }, - "node_modules/cvat-canvas": { - "resolved": "../cvat-canvas", - "link": true - }, - "node_modules/cvat-canvas3d": { - "resolved": "../cvat-canvas3d", - "link": true - }, - "node_modules/cvat-core": { - "resolved": "../cvat-core", - "link": true - }, - "node_modules/cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", - "peer": true - }, - "node_modules/date-fns": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz", - "integrity": "sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w==", - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/dayjs": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", - "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "peer": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/deep-diff": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", - "integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=" - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "peer": true, - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "peer": true, - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - }, - "node_modules/dom-align": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.2.tgz", - "integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg==" - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "peer": true, - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, - "node_modules/dotenv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", - "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==", - "engines": { - "node": ">=6" - } - }, - "node_modules/dotenv-defaults": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-1.1.1.tgz", - "integrity": "sha512-6fPRo9o/3MxKvmRZBD3oNFdxODdhJtIy1zcJeUSCs6HCy4tarUpd+G67UTU9tF6OWXeSPqsm4fPAB+2eY9Rt9Q==", - "dependencies": { - "dotenv": "^6.2.0" - } - }, - "node_modules/dotenv-webpack": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-1.8.0.tgz", - "integrity": "sha512-o8pq6NLBehtrqA8Jv8jFQNtG9nhRtVqmoD4yWbgUyoU3+9WBlPe+c2EAiaJok9RB28QvrWvdWLZGeTT5aATDMg==", - "dependencies": { - "dotenv-defaults": "^1.0.2" - }, - "peerDependencies": { - "webpack": "^1 || ^2 || ^3 || ^4" - } - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "peer": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "peer": true, - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "peer": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/enhanced-resolve/node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "peer": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "peer": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-stack-parser": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", - "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", - "dependencies": { - "stackframe": "^1.1.1" - } - }, - "node_modules/eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "peer": true, - "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "peer": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "peer": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "peer": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "peer": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "peer": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "peer": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "peer": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "peer": true - }, - "node_modules/figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "peer": true - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true, - "peer": true - }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "peer": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fill-range/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "peer": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "peer": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "peer": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "peer": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "peer": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "peer": true - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "peer": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "peer": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "peer": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "peer": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "peer": true - }, - "node_modules/hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==", - "peer": true - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, - "node_modules/iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "peer": true - }, - "node_modules/immutable": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", - "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "peer": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "peer": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "peer": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "peer": true - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "peer": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "optional": true, - "peer": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "peer": true - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "peer": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "peer": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "optional": true, - "peer": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=", - "peer": true - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "peer": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "peer": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "peer": true - }, - "node_modules/json2mq": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", - "integrity": "sha1-tje9O6nqvhIsg+lyBIOusQ0skEo=", - "dependencies": { - "string-convert": "^0.2.0" - } - }, - "node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "peer": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsonp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/jsonp/-/jsonp-0.2.1.tgz", - "integrity": "sha1-pltPoPEL2nGaBUQep7lMVfPhW64=", - "dependencies": { - "debug": "^2.1.3" - } - }, - "node_modules/jss": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.8.0.tgz", - "integrity": "sha512-6fAMLJrVQ8epM5ghghxWqCwRR0ZamP2cKbOAtzPudcCMSNdAqtvmzQvljUZYR8OXJIeb/IpZeOXA1sDXms4R1w==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "csstype": "^3.0.2", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/jss" - } - }, - "node_modules/jss-plugin-camel-case": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.8.0.tgz", - "integrity": "sha512-yxlXrXwcCdGw+H4BC187dEu/RFyW8joMcWfj8Rk9UPgWTKu2Xh7Sib4iW3xXjHe/t5phOHF1rBsHleHykWix7g==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.8.0" - } - }, - "node_modules/jss-plugin-default-unit": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.8.0.tgz", - "integrity": "sha512-9XJV546cY9zV9OvIE/v/dOaxSi4062VfYQQfwbplRExcsU2a79Yn+qDz/4ciw6P4LV1Naq90U+OffAGRHfNq/Q==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.8.0" - } - }, - "node_modules/jss-plugin-global": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.8.0.tgz", - "integrity": "sha512-H/8h/bHd4e7P0MpZ9zaUG8NQSB2ie9rWo/vcCP6bHVerbKLGzj+dsY22IY3+/FNRS8zDmUyqdZx3rD8k4nmH4w==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.8.0" - } - }, - "node_modules/jss-plugin-nested": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.8.0.tgz", - "integrity": "sha512-MhmINZkSxyFILcFBuDoZmP1+wj9fik/b9SsjoaggkGjdvMQCES21mj4K5ZnRGVm448gIXyi9j/eZjtDzhaHUYQ==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.8.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-props-sort": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.8.0.tgz", - "integrity": "sha512-VY+Wt5WX5GMsXDmd+Ts8+O16fpiCM81svbox++U3LDbJSM/g9FoMx3HPhwUiDfmgHL9jWdqEuvSl/JAk+mh6mQ==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.8.0" - } - }, - "node_modules/jss-plugin-rule-value-function": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.8.0.tgz", - "integrity": "sha512-R8N8Ma6Oye1F9HroiUuHhVjpPsVq97uAh+rMI6XwKLqirIu2KFb5x33hPj+vNBMxSHc9jakhf5wG0BbQ7fSDOg==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.8.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-vendor-prefixer": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.8.0.tgz", - "integrity": "sha512-G1zD0J8dFwKZQ+GaZaay7A/Tg7lhDw0iEkJ/iFFA5UPuvZFpMprCMQttXcTBhLlhhWnyZ8YPn4yqp+amrhQekw==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.8", - "jss": "10.8.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "peer": true, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "peer": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "peer": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "peer": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "peer": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "peer": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/material-colors": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", - "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" - }, - "node_modules/material-ui-confirm": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/material-ui-confirm/-/material-ui-confirm-2.1.3.tgz", - "integrity": "sha512-3tu1wk5mo7l03QVzo5qiUv8tL28uoVhwUdu/wpIfMsYVAQmak3eFHMWXq/26ZAyqQKJWIqcF3c43hP5+Q7Wi7w==", - "peer": true, - "peerDependencies": { - "@material-ui/core": ">= 3.0.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "peer": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "peer": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "peer": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "peer": true, - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "peer": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "peer": true - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "peer": true - }, - "node_modules/mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "peer": true, - "dependencies": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "peer": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "peer": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, - "node_modules/mousetrap": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", - "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==" - }, - "node_modules/move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "peer": true, - "dependencies": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "optional": true, - "peer": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "peer": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "peer": true - }, - "node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "peer": true, - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "peer": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "peer": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "peer": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "peer": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "peer": true - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "peer": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "peer": true - }, - "node_modules/parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "peer": true, - "dependencies": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "peer": true, - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "peer": true - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "optional": true, - "peer": true - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "peer": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "peer": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/platform": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", - "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==" - }, - "node_modules/popper.js": { - "version": "1.16.1-lts", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", - "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==", - "peer": true - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "peer": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "peer": true - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "peer": true - }, - "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "peer": true - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "peer": true, - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "peer": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "peer": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "peer": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "peer": true - }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "peer": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "peer": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "peer": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "peer": true, - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-awesome-query-builder": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/react-awesome-query-builder/-/react-awesome-query-builder-4.5.2.tgz", - "integrity": "sha512-WOdUFFVY1ky+U12XrGyC/fgAnAOTZHeovR1PDceWa2AxOrlRmKJ0bgwGbO3uLXhyUS7PUh4C90cXPthrWRmKvg==", - "dependencies": { - "@date-io/moment": "^1.3.13", - "classnames": "^2.3.1", - "clone": "^2.1.2", - "immutable": "^3.8.2", - "lodash": "^4.17.21", - "moment": "^2.29.1", - "prop-types": "^15.7.2", - "react-redux": "~7.1.3", - "redux": "^4.1.0", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">=12.13", - "npm": ">=6" - }, - "funding": { - "url": "https://opencollective.com/react-awesome-query-builder" - }, - "peerDependencies": { - "@material-ui/core": "^4.9.0", - "@material-ui/icons": "^4.0.0", - "@material-ui/lab": "^4.0.0-alpha.57", - "@material-ui/pickers": "^3.2.10", - "antd": "^4.0.0", - "material-ui-confirm": "^2.0.1", - "react": "^16.8.4 || ^17.0.1", - "react-dom": "^16.8.4 || ^17.0.1" - } - }, - "node_modules/react-awesome-query-builder/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-awesome-query-builder/node_modules/react-redux": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.3.tgz", - "integrity": "sha512-uI1wca+ECG9RoVkWQFF4jDMqmaw0/qnvaSvOoL/GA4dNxf6LoV8sUAcNDvE5NWKs4hFpn0t6wswNQnY3f7HT3w==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.9.0" - }, - "peerDependencies": { - "react": "^16.8.3", - "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/react-color": { - "version": "2.19.3", - "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", - "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", - "dependencies": { - "@icons/material": "^0.2.4", - "lodash": "^4.17.15", - "lodash-es": "^4.17.15", - "material-colors": "^1.2.1", - "prop-types": "^15.5.10", - "reactcss": "^1.2.0", - "tinycolor2": "^1.4.1" - }, - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-color/node_modules/@icons/material": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", - "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-cookie": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz", - "integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==", - "dependencies": { - "@types/hoist-non-react-statics": "^3.0.1", - "hoist-non-react-statics": "^3.0.0", - "universal-cookie": "^4.0.0" - }, - "peerDependencies": { - "react": ">= 16.3.0" - } - }, - "node_modules/react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - }, - "peerDependencies": { - "react": "^16.14.0" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "peer": true - }, - "node_modules/react-moment": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/react-moment/-/react-moment-1.1.1.tgz", - "integrity": "sha512-WjwvxBSnmLMRcU33do0KixDB+9vP3e84eCse+rd+HNklAMNWyRgZTDEQlay/qK6lcXFPRuEIASJTpEt6pyK7Ww==", - "peerDependencies": { - "moment": "^2.29.0", - "prop-types": "^15.7.0", - "react": "^16.0 || ^17.0.0" - } - }, - "node_modules/react-redux": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.5.tgz", - "integrity": "sha512-Dt29bNyBsbQaysp6s/dN0gUodcq+dVKKER8Qv82UrpeygwYeX1raTtil7O/fftw/rFqzaf6gJhDZRkkZnn6bjg==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/react-redux": "^7.1.16", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.13.1" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/react-redux/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-resizable": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-1.11.1.tgz", - "integrity": "sha512-S70gbLaAYqjuAd49utRHibtHLrHXInh7GuOR+6OO6RO6uleQfuBnWmZjRABfqNEx3C3Z6VPLg0/0uOYFrkfu9Q==", - "dependencies": { - "prop-types": "15.x", - "react-draggable": "^4.0.3" - }, - "peerDependencies": { - "react": "0.14.x || 15.x || 16.x || 17.x", - "react-dom": "0.14.x || 15.x || 16.x || 17.x" - } - }, - "node_modules/react-resizable/node_modules/react-draggable": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.4.tgz", - "integrity": "sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA==", - "dependencies": { - "clsx": "^1.1.1", - "prop-types": "^15.6.0" - }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" - } - }, - "node_modules/react-router": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", - "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router-dom": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.0.tgz", - "integrity": "sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.1", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router/node_modules/mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - }, - "peerDependencies": { - "prop-types": "^15.0.0", - "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/react-router/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-share": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/react-share/-/react-share-4.4.0.tgz", - "integrity": "sha512-POe8Ge/JT9Ew9iyW7CiYsCCWCb8uMJWqFl9S7W0fJ/oH5gBJNzukH0bL5vSr17KKG5h15d3GfKaoviI22BKeYA==", - "dependencies": { - "classnames": "^2.2.5", - "jsonp": "^0.2.1" - }, - "engines": { - "node": ">=6.9.0", - "npm": ">=5.0.0" - }, - "peerDependencies": { - "react": "^16.3.0 || ^17" - } - }, - "node_modules/react-sortable-hoc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz", - "integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==", - "dependencies": { - "@babel/runtime": "^7.2.0", - "invariant": "^2.2.4", - "prop-types": "^15.5.7" - }, - "peerDependencies": { - "prop-types": "^15.5.7", - "react": "^16.3.0 || ^17.0.0", - "react-dom": "^16.3.0 || ^17.0.0" - } - }, - "node_modules/reactcss": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", - "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", - "dependencies": { - "lodash": "^4.0.1" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "peer": true - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "peer": true - }, - "node_modules/readable-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/redux": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz", - "integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-devtools-extension": { - "version": "2.13.9", - "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz", - "integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==", - "peerDependencies": { - "redux": "^3.1.0 || ^4.0.0" - } - }, - "node_modules/redux-logger": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz", - "integrity": "sha1-91VZZvMJjzyIYExEnPC69XeCdL8=", - "dependencies": { - "deep-diff": "^0.3.5" - } - }, - "node_modules/redux-thunk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", - "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" - }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "peer": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "optional": true, - "peer": true - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "peer": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "peer": true - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "peer": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "peer": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "peer": true, - "dependencies": { - "aproba": "^1.1.1" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "peer": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "peer": true - }, - "node_modules/scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "peer": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/scroll-into-view-if-needed": { - "version": "2.2.28", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz", - "integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==", - "dependencies": { - "compute-scroll-into-view": "^1.0.17" - } - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "peer": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "peer": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "peer": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "peer": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "peer": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "peer": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "peer": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "peer": true - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "peer": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "peer": true - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "peer": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sqlstring": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", - "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ssri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", - "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", - "peer": true, - "dependencies": { - "figgy-pudding": "^3.5.1" - } - }, - "node_modules/stackframe": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", - "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "peer": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "peer": true, - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "peer": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "peer": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "peer": true - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "peer": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-convert": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", - "integrity": "sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c=" - }, - "node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "peer": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "peer": true, - "dependencies": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "engines": { - "node": ">= 6.9.0" - }, - "peerDependencies": { - "webpack": "^4.0.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "peer": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "peer": true, - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "node_modules/tinycolor2": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", - "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==", - "engines": { - "node": "*" - } - }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "peer": true - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "peer": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "peer": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" - }, - "node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "peer": true - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "peer": true - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "peer": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "peer": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "peer": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/universal-cookie": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", - "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", - "dependencies": { - "@types/cookie": "^0.3.3", - "cookie": "^0.4.0" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "peer": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "peer": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "peer": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "peer": true - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "optional": true, - "peer": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "peer": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "peer": true - }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "peer": true, - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "peer": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "peer": true, - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "peer": true - }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "peer": true - }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "peer": true - }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" - }, - "optionalDependencies": { - "chokidar": "^3.4.1", - "watchpack-chokidar2": "^2.0.1" - } - }, - "node_modules/watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "optional": true, - "peer": true, - "dependencies": { - "chokidar": "^2.1.8" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "optional": true, - "peer": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "optional": true, - "peer": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "peer": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "optional": true, - "peer": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "optional": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/webpack": { - "version": "4.46.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", - "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.5.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - }, - "webpack-command": { - "optional": true - } - } - }, - "node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "peer": true, - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/webpack-sources/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "peer": true, - "dependencies": { - "errno": "~0.1.7" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "peer": true - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "peer": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "peer": true - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "peer": true - } - }, - "dependencies": { - "@ant-design/colors": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", - "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", - "requires": { - "@ctrl/tinycolor": "^3.4.0" - } - }, - "@ant-design/icons": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.7.0.tgz", - "integrity": "sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==", - "requires": { - "@ant-design/colors": "^6.0.0", - "@ant-design/icons-svg": "^4.2.1", - "@babel/runtime": "^7.11.2", - "classnames": "^2.2.6", - "rc-util": "^5.9.4" - }, - "dependencies": { - "rc-util": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.14.0.tgz", - "integrity": "sha512-2vy6/Z1BJUcwLjm/UEJb/htjUTQPigITUIemCcFEo1fQevAumc9sA32x2z5qyWoa9uhrXbiAjSDpPIUqyg65sA==", - "requires": { - "@babel/runtime": "^7.12.5", - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "@ant-design/icons-svg": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz", - "integrity": "sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==" - }, - "@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@ctrl/tinycolor": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", - "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==" - }, - "@date-io/core": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz", - "integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==" - }, - "@date-io/moment": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-1.3.13.tgz", - "integrity": "sha512-3kJYusJtQuOIxq6byZlzAHoW/18iExJer9qfRF5DyyzdAk074seTuJfdofjz4RFfTd/Idk8WylOQpWtERqvFuQ==", - "requires": { - "@date-io/core": "^1.3.13" - } - }, - "@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", - "peer": true - }, - "@material-ui/core": { - "version": "4.12.3", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.3.tgz", - "integrity": "sha512-sdpgI/PL56QVsEJldwEe4FFaFTLUqN+rd7sSZiRCdx2E/C7z5yK0y/khAWVBH24tXwto7I1hCzNWfJGZIYJKnw==", - "peer": true, - "requires": { - "@babel/runtime": "^7.4.4", - "@material-ui/styles": "^4.11.4", - "@material-ui/system": "^4.12.1", - "@material-ui/types": "5.1.0", - "@material-ui/utils": "^4.11.2", - "@types/react-transition-group": "^4.2.0", - "clsx": "^1.0.4", - "hoist-non-react-statics": "^3.3.2", - "popper.js": "1.16.1-lts", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0", - "react-transition-group": "^4.4.0" - }, - "dependencies": { - "@material-ui/styles": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz", - "integrity": "sha512-KNTIZcnj/zprG5LW0Sao7zw+yG3O35pviHzejMdcSGCdWbiO8qzRgOYL8JAxAsWBKOKYwVZxXtHWaB5T2Kvxew==", - "peer": true, - "requires": { - "@babel/runtime": "^7.4.4", - "@emotion/hash": "^0.8.0", - "@material-ui/types": "5.1.0", - "@material-ui/utils": "^4.11.2", - "clsx": "^1.0.4", - "csstype": "^2.5.2", - "hoist-non-react-statics": "^3.3.2", - "jss": "^10.5.1", - "jss-plugin-camel-case": "^10.5.1", - "jss-plugin-default-unit": "^10.5.1", - "jss-plugin-global": "^10.5.1", - "jss-plugin-nested": "^10.5.1", - "jss-plugin-props-sort": "^10.5.1", - "jss-plugin-rule-value-function": "^10.5.1", - "jss-plugin-vendor-prefixer": "^10.5.1", - "prop-types": "^15.7.2" - } - }, - "@material-ui/system": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.1.tgz", - "integrity": "sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw==", - "peer": true, - "requires": { - "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.11.2", - "csstype": "^2.5.2", - "prop-types": "^15.7.2" - } - }, - "@material-ui/types": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", - "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", - "peer": true, - "requires": {} - }, - "@material-ui/utils": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz", - "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==", - "peer": true, - "requires": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - } - }, - "csstype": { - "version": "2.6.18", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz", - "integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==", - "peer": true - }, - "react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "peer": true, - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - } - } - }, - "@material-ui/icons": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz", - "integrity": "sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==", - "peer": true, - "requires": { - "@babel/runtime": "^7.4.4" - } - }, - "@material-ui/lab": { - "version": "4.0.0-alpha.60", - "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.60.tgz", - "integrity": "sha512-fadlYsPJF+0fx2lRuyqAuJj7hAS1tLDdIEEdov5jlrpb5pp4b+mRDUqQTUxi4inRZHS1bEXpU8QWUhO6xX88aA==", - "peer": true, - "requires": { - "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.11.2", - "clsx": "^1.0.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - }, - "dependencies": { - "@material-ui/utils": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz", - "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==", - "peer": true, - "requires": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.0 || ^17.0.0" - } - } - } - }, - "@material-ui/pickers": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.3.10.tgz", - "integrity": "sha512-hS4pxwn1ZGXVkmgD4tpFpaumUaAg2ZzbTrxltfC5yPw4BJV+mGkfnQOB4VpWEYZw2jv65Z0wLwDE/piQiPPZ3w==", - "peer": true, - "requires": { - "@babel/runtime": "^7.6.0", - "@date-io/core": "1.x", - "@types/styled-jsx": "^2.2.8", - "clsx": "^1.0.2", - "react-transition-group": "^4.0.0", - "rifm": "^0.7.0" - }, - "dependencies": { - "react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "peer": true, - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - }, - "rifm": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz", - "integrity": "sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==", - "peer": true, - "requires": { - "@babel/runtime": "^7.3.1" - } - } - } - }, - "@types/cookie": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", - "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" - }, - "@types/history": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz", - "integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==" - }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "@types/lodash": { - "version": "4.14.175", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", - "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==" - }, - "@types/platform": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/platform/-/platform-1.3.4.tgz", - "integrity": "sha512-U0o4K+GNiK0PNxoDwd8xRnvLVe4kzei6opn3/FCjAriqaP+rfrDdSl1kP/hLL6Y3/Y3hhGnBwD4dCkkAqs1W/Q==" - }, - "@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" - }, - "@types/react": { - "version": "16.14.16", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.16.tgz", - "integrity": "sha512-7waDQ0h1TkAk99S04wV0LUiiSXpT02lzrdDF4WZFqn2W0XE5ICXLBMtqXWZ688aX2dJislQ3knmZX/jH53RluQ==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-color": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.5.tgz", - "integrity": "sha512-0VZy8Uq5x04cW5QFz24Qw8MMMlsMi8Bb+XG5h59ATqPnWVq6OheHtrwv5LeakdTRDaECQnExJNSFOsSe4Eo/zQ==", - "requires": { - "@types/react": "*", - "@types/reactcss": "*" - } - }, - "@types/react-dom": { - "version": "16.9.14", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.14.tgz", - "integrity": "sha512-FIX2AVmPTGP30OUJ+0vadeIFJJ07Mh1m+U0rxfgyW34p3rTlXI+nlenvAxNn4BP36YyI9IJ/+UJ7Wu22N1pI7A==", - "requires": { - "@types/react": "^16" - } - }, - "@types/react-redux": { - "version": "7.1.18", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz", - "integrity": "sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ==", - "requires": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "@types/react-resizable": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-1.7.3.tgz", - "integrity": "sha512-DAx+hdnHFMJHgl8geiKo3jLt1GCT838SwQixjCtbRRfqCBawAKriVLCZ1nvp7B/2Pxd94MWod8NyJEnAAmNHNA==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-router": { - "version": "5.1.17", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz", - "integrity": "sha512-RNSXOyb3VyRs/EOGmjBhhGKTbnN6fHWvy5FNLzWfOWOGjgVUKqJZXfpKzLmgoU8h6Hj8mpALj/mbXQASOb92wQ==", - "requires": { - "@types/history": "*", - "@types/react": "*" - } - }, - "@types/react-router-dom": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.1.tgz", - "integrity": "sha512-UvyRy73318QI83haXlaMwmklHHzV9hjl3u71MmM6wYNu0hOVk9NLTa0vGukf8zXUqnwz4O06ig876YSPpeK28A==", - "requires": { - "@types/history": "*", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "@types/react-share": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/react-share/-/react-share-3.0.3.tgz", - "integrity": "sha512-GpKAVNbwMBgad0995uVLkOdKWp3CjCrvIeUt4qZcsrgLtf7SMR7gIfMgC9X2rsfLgN6saT/nr2T4QLJE9cCZiA==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-transition-group": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.3.tgz", - "integrity": "sha512-fUx5muOWSYP8Bw2BUQ9M9RK9+W1XBK/7FLJ8PTQpnpTEkn0ccyMffyEQvan4C3h53gHdx7KE5Qrxi/LnUGQtdg==", - "peer": true, - "requires": { - "@types/react": "*" - } - }, - "@types/reactcss": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.4.tgz", - "integrity": "sha512-1rhVqteMSD6KQjO+dPBObE1OkKadw00HVJkG5WCYsyvMwGgdTZ530wF7Bkrg/4TWxB2AtINIzFotjW51eViw+w==", - "requires": { - "@types/react": "*" - } - }, - "@types/redux-logger": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/redux-logger/-/redux-logger-3.0.9.tgz", - "integrity": "sha512-cwYhVbYNgH01aepeMwhd0ABX6fhVB2rcQ9m80u8Fl50ZODhsZ8RhQArnLTkE7/Zrfq4Sz/taNoF7DQy9pCZSKg==", - "requires": { - "redux": "^4.0.0" - } - }, - "@types/resize-observer-browser": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.6.tgz", - "integrity": "sha512-61IfTac0s9jvNtBCpyo86QeaN8qqpMGHdK0uGKCCIy2dt5/Yk84VduHIdWAcmkC5QvdkPL0p5eWYgUZtHKKUVg==" - }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "@types/styled-jsx": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/@types/styled-jsx/-/styled-jsx-2.2.9.tgz", - "integrity": "sha512-W/iTlIkGEyTBGTEvZCey8EgQlQ5l0DwMqi3iOXlLs2kyBwYTXHKEiU6IZ5EwoRwngL8/dGYuzezSup89ttVHLw==", - "peer": true, - "requires": { - "@types/react": "*" - } - }, - "@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", - "peer": true, - "requires": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", - "peer": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", - "peer": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "peer": true - }, - "@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "peer": true, - "requires": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", - "peer": true - }, - "@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.9.0" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", - "peer": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "peer": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", - "peer": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", - "peer": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "peer": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "peer": true - }, - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "peer": true - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "peer": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "peer": true, - "requires": {} - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peer": true, - "requires": {} - }, - "antd": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/antd/-/antd-4.17.0.tgz", - "integrity": "sha512-V2xBGzBK+s2Iy7Re5JOcOBtAvaZtJ9t7R1fFOP51T6ynfSvJqaRtG4DjBu7i9inhXkCzrt7eGcX3vMqLCqXV8g==", - "requires": { - "@ant-design/colors": "^6.0.0", - "@ant-design/icons": "^4.7.0", - "@ant-design/react-slick": "~0.28.1", - "@babel/runtime": "^7.12.5", - "@ctrl/tinycolor": "^3.4.0", - "array-tree-filter": "^2.1.0", - "classnames": "^2.2.6", - "copy-to-clipboard": "^3.2.0", - "lodash": "^4.17.21", - "moment": "^2.25.3", - "rc-cascader": "~2.1.0", - "rc-checkbox": "~2.3.0", - "rc-collapse": "~3.1.0", - "rc-dialog": "~8.6.0", - "rc-drawer": "~4.4.2", - "rc-dropdown": "~3.2.0", - "rc-field-form": "~1.21.0", - "rc-image": "~5.2.5", - "rc-input-number": "~7.3.0", - "rc-mentions": "~1.6.1", - "rc-menu": "~9.0.12", - "rc-motion": "^2.4.4", - "rc-notification": "~4.5.7", - "rc-pagination": "~3.1.9", - "rc-picker": "~2.5.17", - "rc-progress": "~3.1.0", - "rc-rate": "~2.9.0", - "rc-resize-observer": "^1.0.0", - "rc-select": "~13.1.0-alpha.0", - "rc-slider": "~9.7.4", - "rc-steps": "~4.1.0", - "rc-switch": "~3.2.0", - "rc-table": "~7.19.0", - "rc-tabs": "~11.10.0", - "rc-textarea": "~0.3.0", - "rc-tooltip": "~5.1.1", - "rc-tree": "~5.2.0", - "rc-tree-select": "~4.6.0", - "rc-trigger": "^5.2.10", - "rc-upload": "~4.3.0", - "rc-util": "^5.14.0", - "scroll-into-view-if-needed": "^2.2.25" - }, - "dependencies": { - "@ant-design/react-slick": { - "version": "0.28.4", - "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.28.4.tgz", - "integrity": "sha512-j9eAHTn7GxbXUFNknJoHS2ceAsqrQi2j8XykjZE1IXCD8kJF+t28EvhBLniDpbOsBk/3kjalnhriTfZcjBHNqg==", - "requires": { - "@babel/runtime": "^7.10.4", - "classnames": "^2.2.5", - "json2mq": "^0.2.0", - "lodash": "^4.17.21", - "resize-observer-polyfill": "^1.5.0" - } - }, - "rc-cascader": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-2.1.5.tgz", - "integrity": "sha512-FiGPfSxKmSft2CT2XSr6HeKihqcxM+1ozmH6FGXTDthVNNvV0ai82CA6l30iPmMmlflwDfSm/623qkekqNq4BQ==", - "requires": { - "@babel/runtime": "^7.12.5", - "array-tree-filter": "^2.1.0", - "rc-tree-select": "~4.6.0", - "rc-trigger": "^5.0.4", - "rc-util": "^5.0.1", - "warning": "^4.0.1" - } - }, - "rc-checkbox": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-2.3.2.tgz", - "integrity": "sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1" - } - }, - "rc-collapse": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.1.2.tgz", - "integrity": "sha512-HujcKq7mghk/gVKeI6EjzTbb8e19XUZpakrYazu1MblEZ3Hu3WBMSN4A3QmvbF6n1g7x6lUlZvsHZ5shABWYOQ==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.3.4", - "rc-util": "^5.2.1", - "shallowequal": "^1.1.0" - } - }, - "rc-dialog": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-8.6.0.tgz", - "integrity": "sha512-GSbkfqjqxpZC5/zc+8H332+q5l/DKUhpQr0vdX2uDsxo5K0PhvaMEVjyoJUTkZ3+JstEADQji1PVLVb/2bJeOQ==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-motion": "^2.3.0", - "rc-util": "^5.6.1" - } - }, - "rc-drawer": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-4.4.3.tgz", - "integrity": "sha512-FYztwRs3uXnFOIf1hLvFxIQP9MiZJA+0w+Os8dfDh/90X7z/HqP/Yg+noLCIeHEbKln1Tqelv8ymCAN24zPcfQ==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-util": "^5.7.0" - } - }, - "rc-dropdown": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-3.2.0.tgz", - "integrity": "sha512-j1HSw+/QqlhxyTEF6BArVZnTmezw2LnSmRk6I9W7BCqNCKaRwleRmMMs1PHbuaG8dKHVqP6e21RQ7vPBLVnnNw==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-trigger": "^5.0.4" - } - }, - "rc-field-form": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.21.2.tgz", - "integrity": "sha512-LR/bURt/Tf5g39mb0wtMtQuWn42d/7kEzpzlC5fNC7yaRVmLTtlPP4sBBlaViETM9uZQKLoaB0Pt9Mubhm9gow==", - "requires": { - "@babel/runtime": "^7.8.4", - "async-validator": "^4.0.2", - "rc-util": "^5.8.0" - } - }, - "rc-image": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-5.2.5.tgz", - "integrity": "sha512-qUfZjYIODxO0c8a8P5GeuclYXZjzW4hV/5hyo27XqSFo1DmTCs2HkVeQObkcIk5kNsJtgsj1KoPThVsSc/PXOw==", - "requires": { - "@babel/runtime": "^7.11.2", - "classnames": "^2.2.6", - "rc-dialog": "~8.6.0", - "rc-util": "^5.0.6" - } - }, - "rc-input-number": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.3.4.tgz", - "integrity": "sha512-W9uqSzuvJUnz8H8vsVY4kx+yK51SsAxNTwr8SNH4G3XqQNocLVmKIibKFRjocnYX1RDHMND9FFbgj2h7E7nvGA==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-util": "^5.9.8" - } - }, - "rc-mentions": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-1.6.1.tgz", - "integrity": "sha512-LDzGI8jJVGnkhpTZxZuYBhMz3avcZZqPGejikchh97xPni/g4ht714Flh7DVvuzHQ+BoKHhIjobHnw1rcP8erg==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6", - "rc-menu": "^9.0.0", - "rc-textarea": "^0.3.0", - "rc-trigger": "^5.0.4", - "rc-util": "^5.0.1" - } - }, - "rc-menu": { - "version": "9.0.12", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.0.12.tgz", - "integrity": "sha512-8uy47DL36iDEwVZdUO/fjhhW5+4j0tYlrCsOzw6iy8MJqKL7/HC2pj7sL/S9ayp2+hk9fYQYB9Tu+UN+N2OOOQ==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.4.3", - "rc-overflow": "^1.2.0", - "rc-trigger": "^5.1.2", - "rc-util": "^5.12.0", - "shallowequal": "^1.1.0" - }, - "dependencies": { - "rc-overflow": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.2.tgz", - "integrity": "sha512-X5kj9LDU1ue5wHkqvCprJWLKC+ZLs3p4He/oxjZ1Q4NKaqKBaYf5OdSzRSgh3WH8kSdrfU8LjvlbWnHgJOEkNQ==", - "requires": { - "@babel/runtime": "^7.11.1", - "classnames": "^2.2.1", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.5.1" - } - } - } - }, - "rc-motion": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.4.4.tgz", - "integrity": "sha512-ms7n1+/TZQBS0Ydd2Q5P4+wJTSOrhIrwNxLXCZpR7Fa3/oac7Yi803HDALc2hLAKaCTQtw9LmQeB58zcwOsqlQ==", - "requires": { - "@babel/runtime": "^7.11.1", - "classnames": "^2.2.1", - "rc-util": "^5.2.1" - } - }, - "rc-notification": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-4.5.7.tgz", - "integrity": "sha512-zhTGUjBIItbx96SiRu3KVURcLOydLUHZCPpYEn1zvh+re//Tnq/wSxN4FKgp38n4HOgHSVxcLEeSxBMTeBBDdw==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.2.0", - "rc-util": "^5.0.1" - } - }, - "rc-pagination": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-3.1.9.tgz", - "integrity": "sha512-IKBKaJ4icVPeEk9qRHrFBJmHxBUrCp3+nENBYob4Ofqsu3RXjBOy4N36zONO7oubgLyiG3PxVmyAuVlTkoc7Jg==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1" - } - }, - "rc-picker": { - "version": "2.5.18", - "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.5.18.tgz", - "integrity": "sha512-XyieTl8GOC5TeQFEvYbjx/Mtc0/CjruS7mKFT6Fy65FbGXmoFsWoWvIi+ylFx/BQHPGQi7a7vCNoZJ2TTqcZoA==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "date-fns": "2.x", - "dayjs": "1.x", - "moment": "^2.24.0", - "rc-trigger": "^5.0.4", - "rc-util": "^5.4.0", - "shallowequal": "^1.1.0" - } - }, - "rc-progress": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.1.4.tgz", - "integrity": "sha512-XBAif08eunHssGeIdxMXOmRQRULdHaDdIFENQ578CMb4dyewahmmfJRyab+hw4KH4XssEzzYOkAInTLS7JJG+Q==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.6" - } - }, - "rc-rate": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.9.1.tgz", - "integrity": "sha512-MmIU7FT8W4LYRRHJD1sgG366qKtSaKb67D0/vVvJYR0lrCuRrCiVQ5qhfT5ghVO4wuVIORGpZs7ZKaYu+KMUzA==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-util": "^5.0.1" - } - }, - "rc-resize-observer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.0.1.tgz", - "integrity": "sha512-OxO2mJI9e8610CAWBFfm52SPvWib0eNKjaSsRbbKHmLaJIxw944P+D61DlLJ/w2vuOjGNcalJu8VdqyNm/XCRg==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "rc-util": "^5.0.0", - "resize-observer-polyfill": "^1.5.1" - } - }, - "rc-select": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-13.1.1.tgz", - "integrity": "sha512-Oy4L27x5QgGR8902pw0bJVjrTWFnKPKvdLHzJl5pjiA+jM1hpzDfLGg/bY2ntk5ElxxQKZUwbFKUeqfCQU7SrQ==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.0.1", - "rc-overflow": "^1.0.0", - "rc-trigger": "^5.0.4", - "rc-util": "^5.9.8", - "rc-virtual-list": "^3.2.0" - }, - "dependencies": { - "rc-overflow": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.2.tgz", - "integrity": "sha512-X5kj9LDU1ue5wHkqvCprJWLKC+ZLs3p4He/oxjZ1Q4NKaqKBaYf5OdSzRSgh3WH8kSdrfU8LjvlbWnHgJOEkNQ==", - "requires": { - "@babel/runtime": "^7.11.1", - "classnames": "^2.2.1", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.5.1" - } - }, - "rc-virtual-list": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz", - "integrity": "sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==", - "requires": { - "classnames": "^2.2.6", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.0.7" - } - } - } - }, - "rc-slider": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-9.7.5.tgz", - "integrity": "sha512-LV/MWcXFjco1epPbdw1JlLXlTgmWpB9/Y/P2yinf8Pg3wElHxA9uajN21lJiWtZjf5SCUekfSP6QMJfDo4t1hg==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-tooltip": "^5.0.1", - "rc-util": "^5.16.1", - "shallowequal": "^1.1.0" - }, - "dependencies": { - "rc-util": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.16.1.tgz", - "integrity": "sha512-kSCyytvdb3aRxQacS/71ta6c+kBWvM1v8/2h9d/HaNWauc3qB8pLnF20PJ8NajkNN8gb+rR1l0eWO+D4Pz+LLQ==", - "requires": { - "@babel/runtime": "^7.12.5", - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - } - } - } - }, - "rc-steps": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-4.1.4.tgz", - "integrity": "sha512-qoCqKZWSpkh/b03ASGx1WhpKnuZcRWmvuW+ZUu4mvMdfvFzVxblTwUM+9aBd0mlEUFmt6GW8FXhMpHkK3Uzp3w==", - "requires": { - "@babel/runtime": "^7.10.2", - "classnames": "^2.2.3", - "rc-util": "^5.0.1" - } - }, - "rc-switch": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-3.2.2.tgz", - "integrity": "sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "rc-util": "^5.0.1" - } - }, - "rc-table": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.19.2.tgz", - "integrity": "sha512-NdpnoM50MK02H5/hGOsObfxCvGFUG5cHB9turE5BKJ81T5Ycbq193w5tLhnpILXe//Oanzr47MdMxkUnVGP+qg==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.14.0", - "shallowequal": "^1.1.0" - } - }, - "rc-tabs": { - "version": "11.10.1", - "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-11.10.1.tgz", - "integrity": "sha512-ey1i2uMyfnRNYbViLcUYGH+Y7hueJbdCVSLaXnXki9hxBcGqxJMPy9t5xR0n/3QFQspj7Tf6+2VTXVtmO7Yaug==", - "requires": { - "@babel/runtime": "^7.11.2", - "classnames": "2.x", - "rc-dropdown": "^3.2.0", - "rc-menu": "^9.0.0", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.5.0" - } - }, - "rc-textarea": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-0.3.5.tgz", - "integrity": "sha512-qa+k5vDn9ct65qr+SgD2KwJ9Xz6P84lG2z+TDht/RBr71WnM/K61PqHUAcUyU6YqTJD26IXgjPuuhZR7HMw7eA==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.1", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.7.0" - } - }, - "rc-tooltip": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.1.1.tgz", - "integrity": "sha512-alt8eGMJulio6+4/uDm7nvV+rJq9bsfxFDCI0ljPdbuoygUscbsMYb6EQgwib/uqsXQUvzk+S7A59uYHmEgmDA==", - "requires": { - "@babel/runtime": "^7.11.2", - "rc-trigger": "^5.0.0" - } - }, - "rc-tree": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.2.2.tgz", - "integrity": "sha512-ZQPGi5rGmipXvSUqeMbh0Rm0Cn2zFVWQFvS3sinH+lis5VNCChkFs2dAFpWZnb9/d/SZPeMfYG/x2XFq/q3UTA==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-motion": "^2.0.1", - "rc-util": "^5.0.0", - "rc-virtual-list": "^3.4.1" - }, - "dependencies": { - "rc-virtual-list": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz", - "integrity": "sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==", - "requires": { - "classnames": "^2.2.6", - "rc-resize-observer": "^1.0.0", - "rc-util": "^5.0.7" - } - } - } - }, - "rc-tree-select": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-4.6.3.tgz", - "integrity": "sha512-VymfystOnW8EfoWaWehgB8zpYKgRZf4ILu9KHf7FJZVZ/1dnBEHDqg1bBi43/1BYLwYFKSKKSjkYyNYntWJM4A==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "rc-select": "~13.1.0-alpha.0", - "rc-tree": "~5.2.0", - "rc-util": "^5.7.0" - } - }, - "rc-trigger": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.2.10.tgz", - "integrity": "sha512-FkUf4H9BOFDaIwu42fvRycXMAvkttph9AlbCZXssZDVzz2L+QZ0ERvfB/4nX3ZFPh1Zd+uVGr1DEDeXxq4J1TA==", - "requires": { - "@babel/runtime": "^7.11.2", - "classnames": "^2.2.6", - "rc-align": "^4.0.0", - "rc-motion": "^2.0.0", - "rc-util": "^5.5.0" - }, - "dependencies": { - "rc-align": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-4.0.11.tgz", - "integrity": "sha512-n9mQfIYQbbNTbefyQnRHZPWuTEwG1rY4a9yKlIWHSTbgwI+XUMGRYd0uJ5pE2UbrNX0WvnMBA1zJ3Lrecpra/A==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "2.x", - "dom-align": "^1.7.0", - "lodash": "^4.17.21", - "rc-util": "^5.3.0", - "resize-observer-polyfill": "^1.5.1" - } - } - } - }, - "rc-upload": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.3.1.tgz", - "integrity": "sha512-W8Iyv0LRyEnFEzpv90ET/i1XG2jlPzPxKkkOVtDfgh9c3f4lZV770vgpUfiyQza+iLtQLVco3qIvgue8aDiOsQ==", - "requires": { - "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5", - "rc-util": "^5.2.0" - } - }, - "rc-util": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.14.0.tgz", - "integrity": "sha512-2vy6/Z1BJUcwLjm/UEJb/htjUTQPigITUIemCcFEo1fQevAumc9sA32x2z5qyWoa9uhrXbiAjSDpPIUqyg65sA==", - "requires": { - "@babel/runtime": "^7.12.5", - "react-is": "^16.12.0", - "shallowequal": "^1.1.0" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "optional": true, - "peer": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "peer": true - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "peer": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "peer": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "peer": true - }, - "array-tree-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", - "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "peer": true - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "peer": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - } - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "peer": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "peer": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "peer": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "peer": true - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "optional": true, - "peer": true - }, - "async-validator": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz", - "integrity": "sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ==" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "peer": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "peer": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "peer": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "peer": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "peer": true - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "peer": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "optional": true, - "peer": true - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "peer": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "peer": true - }, - "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", - "peer": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "peer": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "peer": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true - } - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "peer": true - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "peer": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "peer": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "peer": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "peer": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "peer": true, - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "peer": true, - "requires": { - "pako": "~1.0.5" - } - }, - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "peer": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "peer": true - } - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "peer": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "peer": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "peer": true - }, - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "peer": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "peer": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "optional": true, - "peer": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "optional": true, - "peer": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "optional": true, - "peer": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "optional": true, - "peer": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "optional": true, - "peer": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "peer": true - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "peer": true - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "peer": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "peer": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "peer": true - } - } - }, - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" - }, - "clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "peer": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "peer": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "peer": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "peer": true - }, - "compute-scroll-into-view": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz", - "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "peer": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "peer": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "peer": true - }, - "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "peer": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "peer": true - }, - "copy-to-clipboard": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", - "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", - "requires": { - "toggle-selection": "^1.0.6" - } - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "peer": true - }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "peer": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "peer": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "peer": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "peer": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "css-vendor": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", - "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", - "peer": true, - "requires": { - "@babel/runtime": "^7.8.3", - "is-in-browser": "^1.0.2" - } - }, - "csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" - }, - "cvat-canvas": { - "version": "file:../cvat-canvas", - "requires": { - "@types/polylabel": "^1.0.5", - "polylabel": "^1.1.0", - "svg.draggable.js": "2.2.2", - "svg.draw.js": "^2.0.4", - "svg.js": "2.7.1", - "svg.resize.js": "1.4.3", - "svg.select.js": "3.0.1" - } - }, - "cvat-canvas3d": { - "version": "file:../cvat-canvas3d", - "requires": { - "@types/three": "^0.125.3", - "camera-controls": "^1.25.3", - "three": "^0.126.1" - } - }, - "cvat-core": { - "version": "file:../cvat-core", - "requires": { - "axios": "^0.21.4", - "browser-or-node": "^1.2.1", - "coveralls": "^3.0.5", - "cvat-data": "../cvat-data", - "detect-browser": "^5.2.1", - "error-stack-parser": "^2.0.2", - "form-data": "^2.5.0", - "jest": "^26.6.3", - "jest-config": "^26.6.3", - "jest-junit": "^6.4.0", - "js-cookie": "^2.2.0", - "jsdoc": "^3.6.6", - "json-logic-js": "^2.0.1", - "platform": "^1.3.5", - "quickhull": "^1.0.3", - "store": "^2.0.12", - "tus-js-client": "^2.3.0" - } - }, - "cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", - "peer": true - }, - "date-fns": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz", - "integrity": "sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w==" - }, - "dayjs": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", - "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "peer": true - }, - "deep-diff": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", - "integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=" - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "peer": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "peer": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "peer": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - } - } - }, - "dom-align": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.2.tgz", - "integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg==" - }, - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "peer": true, - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "peer": true - }, - "dotenv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", - "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" - }, - "dotenv-defaults": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-1.1.1.tgz", - "integrity": "sha512-6fPRo9o/3MxKvmRZBD3oNFdxODdhJtIy1zcJeUSCs6HCy4tarUpd+G67UTU9tF6OWXeSPqsm4fPAB+2eY9Rt9Q==", - "requires": { - "dotenv": "^6.2.0" - } - }, - "dotenv-webpack": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-1.8.0.tgz", - "integrity": "sha512-o8pq6NLBehtrqA8Jv8jFQNtG9nhRtVqmoD4yWbgUyoU3+9WBlPe+c2EAiaJok9RB28QvrWvdWLZGeTT5aATDMg==", - "requires": { - "dotenv-defaults": "^1.0.2" - } - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "peer": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "peer": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - } - } - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "peer": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "peer": true, - "requires": { - "once": "^1.4.0" - } - }, - "enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "peer": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "peer": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - } - } - }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "peer": true, - "requires": { - "prr": "~1.0.1" - } - }, - "error-stack-parser": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", - "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", - "requires": { - "stackframe": "^1.1.1" - } - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "peer": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "peer": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "peer": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "peer": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "peer": true - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "peer": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "peer": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "peer": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "peer": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "peer": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "peer": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "peer": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "peer": true - }, - "figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "peer": true - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true, - "peer": true - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "peer": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true - } - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "peer": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "peer": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "peer": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "peer": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "peer": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "peer": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "peer": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "peer": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true, - "peer": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "peer": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "peer": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "optional": true, - "peer": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "peer": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "peer": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "peer": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "peer": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "peer": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "peer": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "peer": true - }, - "hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==", - "peer": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "peer": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "peer": true - }, - "immutable": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", - "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=" - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "peer": true - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "peer": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "peer": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "peer": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "peer": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "optional": true, - "peer": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "peer": true - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "peer": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "peer": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "peer": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "optional": true, - "peer": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "optional": true, - "peer": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=", - "peer": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "peer": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "peer": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "peer": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "peer": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "peer": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "peer": true - }, - "json2mq": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", - "integrity": "sha1-tje9O6nqvhIsg+lyBIOusQ0skEo=", - "requires": { - "string-convert": "^0.2.0" - } - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "peer": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "jsonp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/jsonp/-/jsonp-0.2.1.tgz", - "integrity": "sha1-pltPoPEL2nGaBUQep7lMVfPhW64=", - "requires": { - "debug": "^2.1.3" - } - }, - "jss": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.8.0.tgz", - "integrity": "sha512-6fAMLJrVQ8epM5ghghxWqCwRR0ZamP2cKbOAtzPudcCMSNdAqtvmzQvljUZYR8OXJIeb/IpZeOXA1sDXms4R1w==", - "peer": true, - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^3.0.2", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-camel-case": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.8.0.tgz", - "integrity": "sha512-yxlXrXwcCdGw+H4BC187dEu/RFyW8joMcWfj8Rk9UPgWTKu2Xh7Sib4iW3xXjHe/t5phOHF1rBsHleHykWix7g==", - "peer": true, - "requires": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.8.0" - } - }, - "jss-plugin-default-unit": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.8.0.tgz", - "integrity": "sha512-9XJV546cY9zV9OvIE/v/dOaxSi4062VfYQQfwbplRExcsU2a79Yn+qDz/4ciw6P4LV1Naq90U+OffAGRHfNq/Q==", - "peer": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.8.0" - } - }, - "jss-plugin-global": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.8.0.tgz", - "integrity": "sha512-H/8h/bHd4e7P0MpZ9zaUG8NQSB2ie9rWo/vcCP6bHVerbKLGzj+dsY22IY3+/FNRS8zDmUyqdZx3rD8k4nmH4w==", - "peer": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.8.0" - } - }, - "jss-plugin-nested": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.8.0.tgz", - "integrity": "sha512-MhmINZkSxyFILcFBuDoZmP1+wj9fik/b9SsjoaggkGjdvMQCES21mj4K5ZnRGVm448gIXyi9j/eZjtDzhaHUYQ==", - "peer": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.8.0", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-props-sort": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.8.0.tgz", - "integrity": "sha512-VY+Wt5WX5GMsXDmd+Ts8+O16fpiCM81svbox++U3LDbJSM/g9FoMx3HPhwUiDfmgHL9jWdqEuvSl/JAk+mh6mQ==", - "peer": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.8.0" - } - }, - "jss-plugin-rule-value-function": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.8.0.tgz", - "integrity": "sha512-R8N8Ma6Oye1F9HroiUuHhVjpPsVq97uAh+rMI6XwKLqirIu2KFb5x33hPj+vNBMxSHc9jakhf5wG0BbQ7fSDOg==", - "peer": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.8.0", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-vendor-prefixer": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.8.0.tgz", - "integrity": "sha512-G1zD0J8dFwKZQ+GaZaay7A/Tg7lhDw0iEkJ/iFFA5UPuvZFpMprCMQttXcTBhLlhhWnyZ8YPn4yqp+amrhQekw==", - "peer": true, - "requires": { - "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.8", - "jss": "10.8.0" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "peer": true - }, - "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "peer": true - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "peer": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "peer": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "peer": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "peer": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "peer": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "peer": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "material-colors": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", - "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" - }, - "material-ui-confirm": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/material-ui-confirm/-/material-ui-confirm-2.1.3.tgz", - "integrity": "sha512-3tu1wk5mo7l03QVzo5qiUv8tL28uoVhwUdu/wpIfMsYVAQmak3eFHMWXq/26ZAyqQKJWIqcF3c43hP5+Q7Wi7w==", - "peer": true, - "requires": {} - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "peer": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "peer": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "peer": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "peer": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - } - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "peer": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "peer": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "peer": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "peer": true - }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "peer": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "peer": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "peer": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" - }, - "mousetrap": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", - "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==" - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "peer": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "optional": true, - "peer": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "peer": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "peer": true - }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "peer": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "optional": true, - "peer": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "peer": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "peer": true - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "peer": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "peer": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "peer": true, - "requires": { - "wrappy": "1" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "peer": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "peer": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "peer": true - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "peer": true - }, - "parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "peer": true, - "requires": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "peer": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "peer": true - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "peer": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "optional": true, - "peer": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "peer": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "peer": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "requires": { - "isarray": "0.0.1" - } - }, - "pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "peer": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "optional": true, - "peer": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "peer": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "peer": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "platform": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", - "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==" - }, - "popper.js": { - "version": "1.16.1-lts", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", - "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==", - "peer": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "peer": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "peer": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "peer": true - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "peer": true - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "peer": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "peer": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "peer": true - } - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "peer": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "peer": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "peer": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "peer": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "peer": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "peer": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "peer": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "peer": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, - "react-awesome-query-builder": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/react-awesome-query-builder/-/react-awesome-query-builder-4.5.2.tgz", - "integrity": "sha512-WOdUFFVY1ky+U12XrGyC/fgAnAOTZHeovR1PDceWa2AxOrlRmKJ0bgwGbO3uLXhyUS7PUh4C90cXPthrWRmKvg==", - "requires": { - "@date-io/moment": "^1.3.13", - "classnames": "^2.3.1", - "clone": "^2.1.2", - "immutable": "^3.8.2", - "lodash": "^4.17.21", - "moment": "^2.29.1", - "prop-types": "^15.7.2", - "react-redux": "~7.1.3", - "redux": "^4.1.0", - "sqlstring": "^2.3.2" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "react-redux": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.3.tgz", - "integrity": "sha512-uI1wca+ECG9RoVkWQFF4jDMqmaw0/qnvaSvOoL/GA4dNxf6LoV8sUAcNDvE5NWKs4hFpn0t6wswNQnY3f7HT3w==", - "requires": { - "@babel/runtime": "^7.5.5", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.9.0" - } - } - } - }, - "react-color": { - "version": "2.19.3", - "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", - "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", - "requires": { - "@icons/material": "^0.2.4", - "lodash": "^4.17.15", - "lodash-es": "^4.17.15", - "material-colors": "^1.2.1", - "prop-types": "^15.5.10", - "reactcss": "^1.2.0", - "tinycolor2": "^1.4.1" - }, - "dependencies": { - "@icons/material": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", - "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", - "requires": {} - } - } - }, - "react-cookie": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz", - "integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==", - "requires": { - "@types/hoist-non-react-statics": "^3.0.1", - "hoist-non-react-statics": "^3.0.0", - "universal-cookie": "^4.0.0" - } - }, - "react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "peer": true - }, - "react-moment": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/react-moment/-/react-moment-1.1.1.tgz", - "integrity": "sha512-WjwvxBSnmLMRcU33do0KixDB+9vP3e84eCse+rd+HNklAMNWyRgZTDEQlay/qK6lcXFPRuEIASJTpEt6pyK7Ww==", - "requires": {} - }, - "react-redux": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.5.tgz", - "integrity": "sha512-Dt29bNyBsbQaysp6s/dN0gUodcq+dVKKER8Qv82UrpeygwYeX1raTtil7O/fftw/rFqzaf6gJhDZRkkZnn6bjg==", - "requires": { - "@babel/runtime": "^7.12.1", - "@types/react-redux": "^7.1.16", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.13.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "react-resizable": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-1.11.1.tgz", - "integrity": "sha512-S70gbLaAYqjuAd49utRHibtHLrHXInh7GuOR+6OO6RO6uleQfuBnWmZjRABfqNEx3C3Z6VPLg0/0uOYFrkfu9Q==", - "requires": { - "prop-types": "15.x", - "react-draggable": "^4.0.3" - }, - "dependencies": { - "react-draggable": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.4.tgz", - "integrity": "sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA==", - "requires": { - "clsx": "^1.1.1", - "prop-types": "^15.6.0" - } - } - } - }, - "react-router": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", - "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", - "requires": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "dependencies": { - "mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "requires": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "react-router-dom": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.0.tgz", - "integrity": "sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==", - "requires": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.1", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } - }, - "react-share": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/react-share/-/react-share-4.4.0.tgz", - "integrity": "sha512-POe8Ge/JT9Ew9iyW7CiYsCCWCb8uMJWqFl9S7W0fJ/oH5gBJNzukH0bL5vSr17KKG5h15d3GfKaoviI22BKeYA==", - "requires": { - "classnames": "^2.2.5", - "jsonp": "^0.2.1" - } - }, - "react-sortable-hoc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz", - "integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==", - "requires": { - "@babel/runtime": "^7.2.0", - "invariant": "^2.2.4", - "prop-types": "^15.5.7" - } - }, - "reactcss": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", - "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", - "requires": { - "lodash": "^4.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "peer": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "peer": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "peer": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "peer": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "optional": true, - "peer": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "redux": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz", - "integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==", - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "redux-devtools-extension": { - "version": "2.13.9", - "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz", - "integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==", - "requires": {} - }, - "redux-logger": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz", - "integrity": "sha1-91VZZvMJjzyIYExEnPC69XeCdL8=", - "requires": { - "deep-diff": "^0.3.5" - } - }, - "redux-thunk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", - "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" - }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "peer": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "optional": true, - "peer": true - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "peer": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "peer": true - }, - "resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, - "resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "peer": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "peer": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "peer": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "peer": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "peer": true, - "requires": { - "aproba": "^1.1.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "peer": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "peer": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "peer": true - }, - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "peer": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "scroll-into-view-if-needed": { - "version": "2.2.28", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz", - "integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==", - "requires": { - "compute-scroll-into-view": "^1.0.17" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "peer": true - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "peer": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "peer": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "peer": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "peer": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "peer": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "peer": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "peer": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "peer": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "peer": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "peer": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "peer": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "peer": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true - } - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "peer": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "peer": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sqlstring": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", - "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==" - }, - "ssri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", - "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", - "peer": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "stackframe": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", - "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "peer": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "peer": true - } - } - }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "peer": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "peer": true, - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "peer": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "peer": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "peer": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-convert": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", - "integrity": "sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c=" - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "peer": true - }, - "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "peer": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true - } - } - }, - "terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "peer": true, - "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true - } - } - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "peer": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "peer": true, - "requires": { - "setimmediate": "^1.0.4" - } - }, - "tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "tinycolor2": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", - "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "peer": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "peer": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "peer": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "peer": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "peer": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "peer": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "peer": true - } - } - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "peer": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "peer": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "universal-cookie": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", - "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", - "requires": { - "@types/cookie": "^0.3.3", - "cookie": "^0.4.0" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "peer": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "peer": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "peer": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "peer": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "peer": true - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "optional": true, - "peer": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "peer": true, - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "peer": true - } - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "peer": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "peer": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "peer": true - } - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "peer": true - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "peer": true, - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "peer": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "peer": true - }, - "value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "peer": true - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", - "peer": true, - "requires": { - "chokidar": "^3.4.1", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0", - "watchpack-chokidar2": "^2.0.1" - } - }, - "watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "optional": true, - "peer": true, - "requires": { - "chokidar": "^2.1.8" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "optional": true, - "peer": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "optional": true, - "peer": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "optional": true, - "peer": true - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "optional": true, - "peer": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "peer": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "peer": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "peer": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "optional": true, - "peer": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "optional": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - } - } - }, - "webpack": { - "version": "4.46.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", - "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.5.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" - } - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "peer": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "peer": true - } - } - }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "peer": true, - "requires": { - "errno": "~0.1.7" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "peer": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "peer": true - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "peer": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "peer": true - } - } -} diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 41301fd425bb..7355bfc8e83b 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,13 +1,13 @@ { "name": "cvat-ui", - "version": "1.37.0", + "version": "1.45.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { "build": "webpack --config ./webpack.config.js", - "start": "webpack-dev-server --env.API_URL=http://localhost:7000 --config ./webpack.config.js --mode=development", + "start": "webpack serve --env API_URL=http://localhost:7000 --config ./webpack.config.js --mode=development", "type-check": "tsc --noEmit", - "type-check:watch": "npm run type-check -- --watch", + "type-check:watch": "yarn run type-check --watch", "lint": "eslint './src/**/*.{ts,tsx}'", "lint:fix": "eslint './src/**/*.{ts,tsx}' --fix" }, @@ -17,7 +17,7 @@ "not IE 11", "> 2%" ], - "author": "Intel", + "author": "CVAT.ai", "license": "MIT", "dependencies": { "@ant-design/icons": "^4.6.3", @@ -27,21 +27,21 @@ "@types/react-color": "^3.0.5", "@types/react-dom": "^16.9.14", "@types/react-redux": "^7.1.18", - "@types/react-resizable": "^1.7.3", + "@types/react-resizable": "^3.0.1", "@types/react-router": "^5.1.16", "@types/react-router-dom": "^5.1.9", "@types/react-share": "^3.0.3", "@types/redux-logger": "^3.0.9", "@types/resize-observer-browser": "^0.1.6", - "antd": "^4.17.0", + "antd": "~4.18.9", "copy-to-clipboard": "^3.3.1", - "cvat-canvas": "file:../cvat-canvas", - "cvat-canvas3d": "file:../cvat-canvas3d", - "cvat-core": "file:../cvat-core", - "dotenv-webpack": "^1.8.0", + "cvat-canvas": "link:./../cvat-canvas", + "cvat-canvas3d": "link:./../cvat-canvas3d", + "cvat-core": "link:./../cvat-core", + "dotenv-webpack": "^8.0.1", "error-stack-parser": "^2.0.6", "lodash": "^4.17.21", - "moment": "^2.29.1", + "moment": "^2.29.2", "mousetrap": "^1.6.5", "platform": "^1.3.6", "prop-types": "^15.7.2", @@ -51,15 +51,15 @@ "react-cookie": "^4.0.3", "react-dom": "^16.14.0", "react-moment": "^1.1.1", - "react-redux": "^7.2.5", - "react-resizable": "^1.11.1", + "react-redux": "^8.0.2", + "react-resizable": "^3.0.4", "react-router": "^5.1.0", "react-router-dom": "^5.1.0", "react-share": "^4.4.0", "react-sortable-hoc": "^2.0.0", "redux": "^4.1.1", "redux-devtools-extension": "^2.13.9", - "redux-logger": "^3.0.6", + "redux-logger": "^4.0.0", "redux-thunk": "^2.3.0" } } diff --git a/cvat-ui/src/actions/about-actions.ts b/cvat-ui/src/actions/about-actions.ts index c1b61ca4380c..eab65ca19b20 100644 --- a/cvat-ui/src/actions/about-actions.ts +++ b/cvat-ui/src/actions/about-actions.ts @@ -1,9 +1,9 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; const core = getCore(); diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 00869d1a3192..5c5c928cadb8 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -7,8 +8,11 @@ import { } from 'redux'; import { ThunkAction } from 'utils/redux'; import isAbleToChangeFrame from 'utils/is-able-to-change-frame'; -import { RectDrawingMethod, CuboidDrawingMethod, Canvas } from 'cvat-canvas-wrapper'; -import getCore from 'cvat-core-wrapper'; +import { CanvasMode as Canvas3DMode } from 'cvat-canvas3d-wrapper'; +import { + RectDrawingMethod, CuboidDrawingMethod, Canvas, CanvasMode as Canvas2DMode, +} from 'cvat-canvas-wrapper'; +import { getCore } from 'cvat-core-wrapper'; import logger, { LogType } from 'cvat-logger'; import { getCVATStore } from 'cvat-store'; @@ -25,7 +29,7 @@ import { ShapeType, Task, Workspace, -} from 'reducers/interfaces'; +} from 'reducers'; import { updateJobAsync } from './tasks-actions'; import { switchToolsBlockerState } from './settings-actions'; @@ -46,7 +50,7 @@ function getStore(): Store { return store; } -function receiveAnnotationsParameters(): AnnotationsParameters { +export function receiveAnnotationsParameters(): AnnotationsParameters { if (store === null) { store = getCVATStore(); } @@ -85,7 +89,7 @@ export function computeZRange(states: any[]): number[] { return [minZ, maxZ]; } -async function jobInfoGenerator(job: any): Promise> { +export async function jobInfoGenerator(job: any): Promise> { const { total } = await job.annotations.statistics(); return { 'frame count': job.stopFrame - job.startFrame + 1, @@ -106,7 +110,7 @@ async function jobInfoGenerator(job: any): Promise> { 'polyline count': total.polyline.shape + total.polyline.track, 'points count': total.points.shape + total.points.track, 'cuboids count': total.cuboid.shape + total.cuboid.track, - 'tag count': total.tags, + 'tag count': total.tag, }; } @@ -136,7 +140,7 @@ export enum AnnotationActionTypes { REPEAT_DRAW_SHAPE = 'REPEAT_DRAW_SHAPE', SHAPE_DRAWN = 'SHAPE_DRAWN', RESET_CANVAS = 'RESET_CANVAS', - REMEMBER_CREATED_OBJECT = 'REMEMBER_CREATED_OBJECT', + REMEMBER_OBJECT = 'REMEMBER_OBJECT', UPDATE_ANNOTATIONS_SUCCESS = 'UPDATE_ANNOTATIONS_SUCCESS', UPDATE_ANNOTATIONS_FAILED = 'UPDATE_ANNOTATIONS_FAILED', CREATE_ANNOTATIONS_SUCCESS = 'CREATE_ANNOTATIONS_SUCCESS', @@ -153,12 +157,12 @@ export enum AnnotationActionTypes { COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE', COLLAPSE_OBJECT_ITEMS = 'COLLAPSE_OBJECT_ITEMS', ACTIVATE_OBJECT = 'ACTIVATE_OBJECT', + REMOVE_OBJECT = 'REMOVE_OBJECT', REMOVE_OBJECT_SUCCESS = 'REMOVE_OBJECT_SUCCESS', REMOVE_OBJECT_FAILED = 'REMOVE_OBJECT_FAILED', - PROPAGATE_OBJECT = 'PROPAGATE_OBJECT', PROPAGATE_OBJECT_SUCCESS = 'PROPAGATE_OBJECT_SUCCESS', PROPAGATE_OBJECT_FAILED = 'PROPAGATE_OBJECT_FAILED', - CHANGE_PROPAGATE_FRAMES = 'CHANGE_PROPAGATE_FRAMES', + SWITCH_PROPAGATE_VISIBILITY = 'SWITCH_PROPAGATE_VISIBILITY', SWITCH_SHOWING_STATISTICS = 'SWITCH_SHOWING_STATISTICS', SWITCH_SHOWING_FILTERS = 'SWITCH_SHOWING_FILTERS', COLLECT_STATISTICS = 'COLLECT_STATISTICS', @@ -197,6 +201,13 @@ export enum AnnotationActionTypes { GET_CONTEXT_IMAGE_SUCCESS = 'GET_CONTEXT_IMAGE_SUCCESS', GET_CONTEXT_IMAGE_FAILED = 'GET_CONTEXT_IMAGE_FAILED', SWITCH_NAVIGATION_BLOCKED = 'SWITCH_NAVIGATION_BLOCKED', + DELETE_FRAME = 'DELETE_FRAME', + DELETE_FRAME_SUCCESS = 'DELETE_FRAME_SUCCESS', + DELETE_FRAME_FAILED = 'DELETE_FRAME_FAILED', + RESTORE_FRAME = 'RESTORE_FRAME', + RESTORE_FRAME_SUCCESS = 'RESTORE_FRAME_SUCCESS', + RESTORE_FRAME_FAILED = 'RESTORE_FRAME_FAILED', + UPDATE_BRUSH_TOOLS_CONFIG = 'UPDATE_BRUSH_TOOLS_CONFIG', } export function saveLogsAsync(): ThunkAction { @@ -308,6 +319,15 @@ export function updateCanvasContextMenu( }; } +export function updateCanvasBrushTools(config: { + visible?: boolean, left?: number, top?: number +}): AnyAction { + return { + type: AnnotationActionTypes.UPDATE_BRUSH_TOOLS_CONFIG, + payload: config, + }; +} + export function removeAnnotationsAsync( startFrame: number, endFrame: number, delTrackKeyframesOnly: boolean, ): ThunkAction { @@ -339,74 +359,6 @@ export function removeAnnotationsAsync( }; } -export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): ThunkAction { - return async (dispatch: ActionCreator): Promise => { - try { - const state: CombinedState = getStore().getState(); - const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); - - if (state.tasks.activities.loads[job.taskId]) { - throw Error('Annotations is being uploaded for the task'); - } - if (state.annotation.activities.loads[job.id]) { - throw Error('Only one uploading of annotations for a job allowed at the same time'); - } - - dispatch({ - type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS, - payload: { - job, - loader, - }, - }); - - const frame = state.annotation.player.frame.number; - await job.annotations.upload(file, loader); - - await job.logger.log(LogType.uploadAnnotations, { - ...(await jobInfoGenerator(job)), - }); - - await job.annotations.clear(true); - await job.actions.clear(); - const history = await job.actions.get(); - - // One more update to escape some problems - // in canvas when shape with the same - // clientID has different type (polygon, rectangle) for example - dispatch({ - type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_SUCCESS, - payload: { - job, - states: [], - history, - }, - }); - - const states = await job.annotations.get(frame, showAllInterpolationTracks, filters); - - setTimeout(() => { - dispatch({ - type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_SUCCESS, - payload: { - history, - job, - states, - }, - }); - }); - } catch (error) { - dispatch({ - type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_FAILED, - payload: { - job, - error, - }, - }); - } - }; -} - export function collectStatisticsAsync(sessionInstance: any): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { @@ -451,25 +403,54 @@ export function showFilters(visible: boolean): AnyAction { }; } -export function propagateObjectAsync(sessionInstance: any, objectState: any, from: number, to: number): ThunkAction { - return async (dispatch: ActionCreator): Promise => { +export function switchPropagateVisibility(visible: boolean): AnyAction { + return { + type: AnnotationActionTypes.SWITCH_PROPAGATE_VISIBILITY, + payload: { visible }, + }; +} + +export function propagateObjectAsync(from: number, to: number): ThunkAction { + return async (dispatch: ActionCreator, getState): Promise => { + const state = getState(); + const { + job: { + instance: sessionInstance, + }, + annotations: { + activatedStateID, + states: objectStates, + }, + } = state.annotation; + try { - const copy = { - attributes: objectState.attributes, - points: objectState.points, - occluded: objectState.occluded, - objectType: objectState.objectType !== ObjectType.TRACK ? objectState.objectType : ObjectType.SHAPE, - shapeType: objectState.shapeType, - label: objectState.label, - zOrder: objectState.zOrder, + const objectState = objectStates.find((_state: any) => _state.clientID === activatedStateID); + if (!objectState) { + throw new Error('There is not an activated object state to be propagated'); + } + + const getCopyFromState = (_objectState: any): any => ({ + attributes: _objectState.attributes, + points: _objectState.shapeType === 'skeleton' ? null : _objectState.points, + occluded: _objectState.occluded, + objectType: _objectState.objectType !== ObjectType.TRACK ? _objectState.objectType : ObjectType.SHAPE, + shapeType: _objectState.shapeType, + label: _objectState.label, + zOrder: _objectState.zOrder, + rotation: _objectState.rotation, frame: from, - source: objectState.source, - }; + elements: _objectState.shapeType === 'skeleton' ? _objectState.elements + .map((element: any): any => getCopyFromState(element)) : [], + source: _objectState.source, + }); - await sessionInstance.logger.log(LogType.propagateObject, { count: to - from + 1 }); + const copy = getCopyFromState(objectState); + await sessionInstance.logger.log(LogType.propagateObject, { count: Math.abs(to - from) }); const states = []; - for (let frame = from; frame <= to; frame++) { + const sign = Math.sign(to - from); + for (let frame = from + sign; sign > 0 ? frame <= to : frame >= to; frame += sign) { copy.frame = frame; + copy.elements.forEach((element: any) => { element.frame = frame; }); const newState = new cvat.classes.ObjectState(copy); states.push(newState); } @@ -479,40 +460,17 @@ export function propagateObjectAsync(sessionInstance: any, objectState: any, fro dispatch({ type: AnnotationActionTypes.PROPAGATE_OBJECT_SUCCESS, - payload: { - objectState, - history, - }, + payload: { history }, }); } catch (error) { dispatch({ type: AnnotationActionTypes.PROPAGATE_OBJECT_FAILED, - payload: { - error, - }, + payload: { error }, }); } }; } -export function propagateObject(objectState: any | null): AnyAction { - return { - type: AnnotationActionTypes.PROPAGATE_OBJECT, - payload: { - objectState, - }, - }; -} - -export function changePropagateFrames(frames: number): AnyAction { - return { - type: AnnotationActionTypes.CHANGE_PROPAGATE_FRAMES, - payload: { - frames, - }, - }; -} - export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { @@ -544,6 +502,16 @@ export function removeObjectAsync(sessionInstance: any, objectState: any, force: }; } +export function removeObject(objectState: any, force: boolean): AnyAction { + return { + type: AnnotationActionTypes.REMOVE_OBJECT, + payload: { + objectState, + force, + }, + }; +} + export function editShape(enabled: boolean): AnyAction { return { type: AnnotationActionTypes.EDIT_SHAPE, @@ -565,11 +533,16 @@ export function copyShape(objectState: any): AnyAction { }; } -export function activateObject(activatedStateID: number | null, activatedAttributeID: number | null): AnyAction { +export function activateObject( + activatedStateID: number | null, + activatedElementID: number | null, + activatedAttributeID: number | null, +): AnyAction { return { type: AnnotationActionTypes.ACTIVATE_OBJECT, payload: { activatedStateID, + activatedElementID, activatedAttributeID, }, }; @@ -688,6 +661,13 @@ export function getPredictionsAsync(): ThunkAction { }; } +export function confirmCanvasReady(): AnyAction { + return { + type: AnnotationActionTypes.CONFIRM_CANVAS_READY, + payload: {}, + }; +} + export function changeFrameAsync( toFrame: number, fillBuffer?: boolean, @@ -697,6 +677,14 @@ export function changeFrameAsync( return async (dispatch: ActionCreator, getState: () => CombinedState): Promise => { const state: CombinedState = getState(); const { instance: job } = state.annotation.job; + const { + propagate: { + visible: propagateVisible, + }, + statistics: { + visible: statisticsVisible, + }, + } = state.annotation; const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); try { @@ -704,9 +692,9 @@ export function changeFrameAsync( throw Error(`Required frame ${toFrame} is out of the current job`); } - const abortAction = (): AnyAction => { + const abortAction = (): void => { const currentState = getState(); - return ({ + dispatch({ type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, payload: { number: currentState.annotation.player.frame.number, @@ -721,6 +709,8 @@ export function changeFrameAsync( curZ: currentState.annotation.annotations.zLayer.cur, }, }); + + dispatch(confirmCanvasReady()); }; dispatch({ @@ -729,17 +719,17 @@ export function changeFrameAsync( }); if (toFrame === frame && !forceUpdate) { - dispatch(abortAction()); + abortAction(); return; } const data = await job.frames.get(toFrame, fillBuffer, frameStep); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); - if (!isAbleToChangeFrame()) { + if (!isAbleToChangeFrame() || statisticsVisible || propagateVisible) { // while doing async actions above, canvas can become used by a user in another way // so, we need an additional check and if it is used, we do not update state - dispatch(abortAction()); + abortAction(); return; } @@ -822,6 +812,7 @@ export function undoActionAsync(sessionInstance: any, frame: number): ThunkActio await sessionInstance.actions.undo(); const history = await sessionInstance.actions.get(); const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters); + const frameData = await sessionInstance.frames.get(frame); const [minZ, maxZ] = computeZRange(states); await undoLog.close(); @@ -832,12 +823,13 @@ export function undoActionAsync(sessionInstance: any, frame: number): ThunkActio states, minZ, maxZ, + frameData, }, }); const undoOnFrame = undo[1]; - if (frame !== undoOnFrame) { - dispatch(changeFrameAsync(undoOnFrame)); + if (frame !== undoOnFrame || ['Removed frame', 'Restored frame'].includes(undo[0])) { + dispatch(changeFrameAsync(undoOnFrame, undefined, undefined, true)); } } catch (error) { dispatch({ @@ -872,8 +864,8 @@ export function redoActionAsync(sessionInstance: any, frame: number): ThunkActio const history = await sessionInstance.actions.get(); const states = await sessionInstance.annotations.get(frame, showAllInterpolationTracks, filters); const [minZ, maxZ] = computeZRange(states); + const frameData = await sessionInstance.frames.get(frame); await redoLog.close(); - dispatch({ type: AnnotationActionTypes.REDO_ACTION_SUCCESS, payload: { @@ -881,12 +873,14 @@ export function redoActionAsync(sessionInstance: any, frame: number): ThunkActio states, minZ, maxZ, + frameData, }, }); const redoOnFrame = redo[1]; - if (frame !== redoOnFrame) { - dispatch(changeFrameAsync(redoOnFrame)); + + if (frame !== redoOnFrame || ['Removed frame', 'Restored frame'].includes(redo[0])) { + dispatch(changeFrameAsync(redoOnFrame, undefined, undefined, true)); } } catch (error) { dispatch({ @@ -956,13 +950,6 @@ export function resetCanvas(): AnyAction { }; } -export function confirmCanvasReady(): AnyAction { - return { - type: AnnotationActionTypes.CONFIRM_CANVAS_READY, - payload: {}, - }; -} - export function closeJob(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { jobInstance } = receiveAnnotationsParameters(); @@ -976,7 +963,9 @@ export function closeJob(): ThunkAction { }; } -export function getJobAsync(tid: number, jid: number, initialFrame: number, initialFilters: object[]): ThunkAction { +export function getJobAsync( + tid: number, jid: number, initialFrame: number | null, initialFilters: object[], +): ThunkAction { return async (dispatch: ActionCreator, getState): Promise => { try { const state = getState(); @@ -984,6 +973,7 @@ export function getJobAsync(tid: number, jid: number, initialFrame: number, init const { settings: { workspace: { showAllInterpolationTracks }, + player: { showDeletedFrames }, }, } = state; @@ -1017,7 +1007,16 @@ export function getJobAsync(tid: number, jid: number, initialFrame: number, init [job] = await cvat.jobs.get({ jobID: jid }); } - const frameNumber = Math.max(Math.min(job.stopFrame, initialFrame), job.startFrame); + // opening correct first frame according to setup + let frameNumber; + if (initialFrame === null && !showDeletedFrames) { + frameNumber = (await job.frames.search( + { notDeleted: true }, job.startFrame, job.stopFrame, + )) || job.startFrame; + } else { + frameNumber = Math.max(Math.min(job.stopFrame, initialFrame || 0), job.startFrame); + } + const frameData = await job.frames.get(frameNumber); // call first getting of frame data before rendering interface // to load and decode first chunk @@ -1115,6 +1114,11 @@ export function saveAnnotationsAsync(sessionInstance: any, afterSave?: () => voi try { const saveJobEvent = await sessionInstance.logger.log(LogType.saveJob, {}, true); + dispatch({ + type: AnnotationActionTypes.SAVE_UPDATE_ANNOTATIONS_STATUS, + payload: { status: 'Saving frames' }, + }); + await sessionInstance.frames.save(); await sessionInstance.annotations.save((status: string) => { dispatch({ type: AnnotationActionTypes.SAVE_UPDATE_ANNOTATIONS_STATUS, @@ -1165,7 +1169,7 @@ export function rememberObject(createParams: { activeCuboidDrawingMethod?: CuboidDrawingMethod; }): AnyAction { return { - type: AnnotationActionTypes.REMEMBER_CREATED_OBJECT, + type: AnnotationActionTypes.REMEMBER_OBJECT, payload: createParams, }; } @@ -1222,11 +1226,19 @@ export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction { try { if (statesToUpdate.some((state: any): boolean => state.updateFlags.zOrder)) { // deactivate object to visualize changes immediately (UX) - dispatch(activateObject(null, null)); + dispatch(activateObject(null, null, null)); } const promises = statesToUpdate.map((objectState: any): Promise => objectState.save()); const states = await Promise.all(promises); + + const needToUpdateAll = states + .some((state: any) => state.shapeType === ShapeType.MASK || state.parentID !== null); + if (needToUpdateAll) { + dispatch(fetchAnnotationsAsync()); + return; + } + const history = await jobInstance.actions.get(); const [minZ, maxZ] = computeZRange(states); @@ -1377,20 +1389,39 @@ export function changeGroupColorAsync(group: number, color: string): ThunkAction const groupStates = state.annotation.annotations.states.filter( (_state: any): boolean => _state.group.id === group, ); - if (groupStates.length) { - groupStates[0].group.color = color; - dispatch(updateAnnotationsAsync(groupStates)); - } else { - dispatch(updateAnnotationsAsync([])); + + for (const objectState of groupStates) { + objectState.group.color = color; } + + dispatch(updateAnnotationsAsync(groupStates)); }; } export function searchAnnotationsAsync(sessionInstance: any, frameFrom: number, frameTo: number): ThunkAction { - return async (dispatch: ActionCreator): Promise => { + return async (dispatch: ActionCreator, getState): Promise => { try { - const { filters } = receiveAnnotationsParameters(); - const frame = await sessionInstance.annotations.search(filters, frameFrom, frameTo); + const { + settings: { + player: { showDeletedFrames }, + }, + annotation: { + annotations: { filters }, + }, + } = getState(); + + const sign = Math.sign(frameTo - frameFrom); + let frame = await sessionInstance.annotations.search(filters, frameFrom, frameTo); + while (frame !== null) { + const isDeleted = (await sessionInstance.frames.get(frame)).deleted; + if (!isDeleted || showDeletedFrames) { + break; + } else if (sign > 0 ? frame < frameTo : frame > frameTo) { + frame = await sessionInstance.annotations.search(filters, frame + sign, frameTo); + } else { + frame = null; + } + } if (frame !== null) { dispatch(changeFrameAsync(frame)); } @@ -1406,9 +1437,26 @@ export function searchAnnotationsAsync(sessionInstance: any, frameFrom: number, } export function searchEmptyFrameAsync(sessionInstance: any, frameFrom: number, frameTo: number): ThunkAction { - return async (dispatch: ActionCreator): Promise => { + return async (dispatch: ActionCreator, getState): Promise => { try { - const frame = await sessionInstance.annotations.searchEmpty(frameFrom, frameTo); + const { + settings: { + player: { showDeletedFrames }, + }, + } = getState(); + + const sign = Math.sign(frameTo - frameFrom); + let frame = await sessionInstance.annotations.searchEmpty(frameFrom, frameTo); + while (frame !== null) { + const isDeleted = (await sessionInstance.frames.get(frame)).deleted; + if (!isDeleted || showDeletedFrames) { + break; + } else if (sign > 0 ? frame < frameTo : frame > frameTo) { + frame = await sessionInstance.annotations.searchEmpty(frame + sign, frameTo); + } else { + frame = null; + } + } if (frame !== null) { dispatch(changeFrameAsync(frame)); } @@ -1423,6 +1471,17 @@ export function searchEmptyFrameAsync(sessionInstance: any, frameFrom: number, f }; } +const ShapeTypeToControl: Record = { + [ShapeType.RECTANGLE]: ActiveControl.DRAW_RECTANGLE, + [ShapeType.POLYLINE]: ActiveControl.DRAW_POLYLINE, + [ShapeType.POLYGON]: ActiveControl.DRAW_POLYGON, + [ShapeType.POINTS]: ActiveControl.DRAW_POINTS, + [ShapeType.CUBOID]: ActiveControl.DRAW_CUBOID, + [ShapeType.ELLIPSE]: ActiveControl.DRAW_ELLIPSE, + [ShapeType.SKELETON]: ActiveControl.DRAW_SKELETON, + [ShapeType.MASK]: ActiveControl.DRAW_MASK, +}; + export function pasteShapeAsync(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { @@ -1434,29 +1493,17 @@ export function pasteShapeAsync(): ThunkAction { drawing: { activeInitialState: initialState }, } = getStore().getState().annotation; - if (initialState) { - let activeControl = ActiveControl.CURSOR; - if (initialState.shapeType === ShapeType.RECTANGLE) { - activeControl = ActiveControl.DRAW_RECTANGLE; - } else if (initialState.shapeType === ShapeType.POINTS) { - activeControl = ActiveControl.DRAW_POINTS; - } else if (initialState.shapeType === ShapeType.POLYGON) { - activeControl = ActiveControl.DRAW_POLYGON; - } else if (initialState.shapeType === ShapeType.POLYLINE) { - activeControl = ActiveControl.DRAW_POLYLINE; - } else if (initialState.shapeType === ShapeType.CUBOID) { - activeControl = ActiveControl.DRAW_CUBOID; - } + if (initialState && canvasInstance) { + const activeControl = ShapeTypeToControl[initialState.shapeType as ShapeType] || ActiveControl.CURSOR; + canvasInstance.cancel(); dispatch({ type: AnnotationActionTypes.PASTE_SHAPE, payload: { activeControl, }, }); - if (canvasInstance instanceof Canvas) { - canvasInstance.cancel(); - } + if (initialState.objectType === ObjectType.TAG) { const objectState = new cvat.classes.ObjectState({ objectType: ObjectType.TAG, @@ -1469,6 +1516,8 @@ export function pasteShapeAsync(): ThunkAction { canvasInstance.draw({ enabled: true, initialState, + ...(initialState.shapeType === ShapeType.SKELETON ? + { skeletonSVG: initialState.label.structure.svg } : {}), }); } } @@ -1489,6 +1538,7 @@ export function repeatDrawShapeAsync(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { canvas: { instance: canvasInstance }, + annotations: { states }, job: { labels, instance: jobInstance }, player: { frame: { number: frameNumber }, @@ -1524,19 +1574,8 @@ export function repeatDrawShapeAsync(): ThunkAction { return; } - if (activeShapeType === ShapeType.RECTANGLE) { - activeControl = ActiveControl.DRAW_RECTANGLE; - } else if (activeShapeType === ShapeType.POINTS) { - activeControl = ActiveControl.DRAW_POINTS; - } else if (activeShapeType === ShapeType.POLYGON) { - activeControl = ActiveControl.DRAW_POLYGON; - } else if (activeShapeType === ShapeType.POLYLINE) { - activeControl = ActiveControl.DRAW_POLYLINE; - } else if (activeShapeType === ShapeType.CUBOID) { - activeControl = ActiveControl.DRAW_CUBOID; - } else if (activeShapeType === ShapeType.ELLIPSE) { - activeControl = ActiveControl.DRAW_ELLIPSE; - } + + activeControl = ShapeTypeToControl[activeShapeType]; dispatch({ type: AnnotationActionTypes.REPEAT_DRAW_SHAPE, @@ -1548,13 +1587,22 @@ export function repeatDrawShapeAsync(): ThunkAction { if (canvasInstance instanceof Canvas) { canvasInstance.cancel(); } + + const [activeLabel] = labels.filter((label: any) => label.id === activeLabelID); + if (!activeLabel) { + throw new Error(`Label with ID ${activeLabelID}, was not found`); + } + if (activeObjectType === ObjectType.TAG) { - const objectState = new cvat.classes.ObjectState({ - objectType: ObjectType.TAG, - label: labels.filter((label: any) => label.id === activeLabelID)[0], - frame: frameNumber, - }); - dispatch(createAnnotationsAsync(jobInstance, frameNumber, [objectState])); + const tags = states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG); + if (tags.every((objectState: any): boolean => objectState.label.id !== activeLabelID)) { + const objectState = new cvat.classes.ObjectState({ + objectType: ObjectType.TAG, + label: labels.filter((label: any) => label.id === activeLabelID)[0], + frame: frameNumber, + }); + dispatch(createAnnotationsAsync(jobInstance, frameNumber, [objectState])); + } } else if (canvasInstance) { canvasInstance.draw({ enabled: true, @@ -1563,6 +1611,7 @@ export function repeatDrawShapeAsync(): ThunkAction { numberOfPoints: activeNumOfPoints, shapeType: activeShapeType, crosshair: [ShapeType.RECTANGLE, ShapeType.CUBOID, ShapeType.ELLIPSE].includes(activeShapeType), + skeletonSVG: activeShapeType === ShapeType.SKELETON ? activeLabel.structure.svg : undefined, }); } }; @@ -1578,29 +1627,20 @@ export function redrawShapeAsync(): ThunkAction { if (activatedStateID !== null) { const [state] = states.filter((_state: any): boolean => _state.clientID === activatedStateID); if (state && state.objectType !== ObjectType.TAG) { - let activeControl = ActiveControl.CURSOR; - if (state.shapeType === ShapeType.RECTANGLE) { - activeControl = ActiveControl.DRAW_RECTANGLE; - } else if (state.shapeType === ShapeType.POINTS) { - activeControl = ActiveControl.DRAW_POINTS; - } else if (state.shapeType === ShapeType.POLYGON) { - activeControl = ActiveControl.DRAW_POLYGON; - } else if (state.shapeType === ShapeType.POLYLINE) { - activeControl = ActiveControl.DRAW_POLYLINE; - } else if (state.shapeType === ShapeType.CUBOID) { - activeControl = ActiveControl.DRAW_CUBOID; - } - + const activeControl = ShapeTypeToControl[state.shapeType as ShapeType] || ActiveControl.CURSOR; dispatch({ type: AnnotationActionTypes.REPEAT_DRAW_SHAPE, payload: { activeControl, }, }); + if (canvasInstance instanceof Canvas) { canvasInstance.cancel(); } + canvasInstance.draw({ + skeletonSVG: state.shapeType === ShapeType.SKELETON ? state.label.structure.svg : undefined, enabled: true, redraw: activatedStateID, shapeType: state.shapeType, @@ -1671,3 +1711,98 @@ export function switchNavigationBlocked(navigationBlocked: boolean): AnyAction { }, }; } + +export function deleteFrameAsync(frame: number): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + const state: CombinedState = getStore().getState(); + const { + annotation: { + annotations: { filters }, + job: { + instance: jobInstance, + }, + canvas: { + instance: canvasInstance, + }, + }, + settings: { + player: { showDeletedFrames }, + workspace: { showAllInterpolationTracks }, + }, + } = state; + + try { + dispatch({ type: AnnotationActionTypes.DELETE_FRAME }); + + if (canvasInstance && + canvasInstance.mode() !== Canvas2DMode.IDLE && + canvasInstance.mode() !== Canvas3DMode.IDLE) { + canvasInstance.cancel(); + } + await jobInstance.frames.delete(frame); + dispatch({ + type: AnnotationActionTypes.DELETE_FRAME_SUCCESS, + payload: { + data: await jobInstance.frames.get(frame), + history: await jobInstance.actions.get(), + states: await jobInstance.annotations.get(frame, showAllInterpolationTracks, filters), + }, + }); + + if (!showDeletedFrames) { + let notDeletedFrame = await jobInstance.frames.search( + { notDeleted: true }, frame, jobInstance.stopFrame, + ); + if (notDeletedFrame === null && jobInstance.startFrame !== frame) { + notDeletedFrame = await jobInstance.frames.search( + { notDeleted: true }, frame, jobInstance.startFrame, + ); + } + if (notDeletedFrame !== null) { + dispatch(changeFrameAsync(notDeletedFrame)); + } + } + } catch (error) { + dispatch({ + type: AnnotationActionTypes.DELETE_FRAME_FAILED, + payload: { error }, + }); + } + }; +} + +export function restoreFrameAsync(frame: number): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + const state: CombinedState = getStore().getState(); + const { + annotation: { + job: { + instance: jobInstance, + }, + annotations: { filters }, + }, + settings: { + workspace: { showAllInterpolationTracks }, + }, + } = state; + + try { + dispatch({ type: AnnotationActionTypes.RESTORE_FRAME }); + + await jobInstance.frames.restore(frame); + dispatch({ + type: AnnotationActionTypes.RESTORE_FRAME_SUCCESS, + payload: { + data: await jobInstance.frames.get(frame), + history: await jobInstance.actions.get(), + states: await jobInstance.annotations.get(frame, showAllInterpolationTracks, filters), + }, + }); + } catch (error) { + dispatch({ + type: AnnotationActionTypes.RESTORE_FRAME_FAILED, + payload: { error }, + }); + } + }; +} diff --git a/cvat-ui/src/actions/auth-actions.ts b/cvat-ui/src/actions/auth-actions.ts index b10a378be3e7..b4063ed50fbb 100644 --- a/cvat-ui/src/actions/auth-actions.ts +++ b/cvat-ui/src/actions/auth-actions.ts @@ -1,11 +1,13 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; import { UserConfirmation } from 'components/register-page/register-form'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; import isReachable from 'utils/url-checker'; +import { AdvancedAuthMethodsList } from '../reducers'; const cvat = getCore(); @@ -34,6 +36,9 @@ export enum AuthActionTypes { LOAD_AUTH_ACTIONS = 'LOAD_AUTH_ACTIONS', LOAD_AUTH_ACTIONS_SUCCESS = 'LOAD_AUTH_ACTIONS_SUCCESS', LOAD_AUTH_ACTIONS_FAILED = 'LOAD_AUTH_ACTIONS_FAILED', + LOAD_ADVANCED_AUTHENTICATION = 'LOAD_ADVANCED_AUTHENTICATION', + LOAD_ADVANCED_AUTHENTICATION_SUCCESS = 'LOAD_ADVANCED_AUTHENTICATION_SUCCESS', + LOAD_ADVANCED_AUTHENTICATION_FAILED = 'LOAD_ADVANCED_AUTHENTICATION_FAILED', } export const authActions = { @@ -41,7 +46,9 @@ export const authActions = { authorizeFailed: (error: any) => createAction(AuthActionTypes.AUTHORIZED_FAILED, { error }), login: () => createAction(AuthActionTypes.LOGIN), loginSuccess: (user: any) => createAction(AuthActionTypes.LOGIN_SUCCESS, { user }), - loginFailed: (error: any) => createAction(AuthActionTypes.LOGIN_FAILED, { error }), + loginFailed: (error: any, hasEmailVerificationBeenSent = false) => ( + createAction(AuthActionTypes.LOGIN_FAILED, { error, hasEmailVerificationBeenSent }) + ), register: () => createAction(AuthActionTypes.REGISTER), registerSuccess: (user: any) => createAction(AuthActionTypes.REGISTER_SUCCESS, { user }), registerFailed: (error: any) => createAction(AuthActionTypes.REGISTER_FAILED, { error }), @@ -68,6 +75,13 @@ export const authActions = { }) ), loadServerAuthActionsFailed: (error: any) => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED, { error }), + loadAdvancedAuth: () => createAction(AuthActionTypes.LOAD_ADVANCED_AUTHENTICATION), + loadAdvancedAuthSuccess: (list: AdvancedAuthMethodsList) => ( + createAction(AuthActionTypes.LOAD_ADVANCED_AUTHENTICATION_SUCCESS, { list }) + ), + loadAdvancedAuthFailed: (error: any) => ( + createAction(AuthActionTypes.LOAD_ADVANCED_AUTHENTICATION_FAILED, { error }) + ), }; export type AuthActions = ActionUnion; @@ -77,8 +91,7 @@ export const registerAsync = ( firstName: string, lastName: string, email: string, - password1: string, - password2: string, + password: string, confirmations: UserConfirmation[], ): ThunkAction => async (dispatch) => { dispatch(authActions.register()); @@ -89,8 +102,7 @@ export const registerAsync = ( firstName, lastName, email, - password1, - password2, + password, confirmations, ); @@ -100,15 +112,16 @@ export const registerAsync = ( } }; -export const loginAsync = (username: string, password: string): ThunkAction => async (dispatch) => { +export const loginAsync = (username: string, email: string, password: string): ThunkAction => async (dispatch) => { dispatch(authActions.login()); try { - await cvat.server.login(username, password); + await cvat.server.login(username, email, password); const users = await cvat.users.get({ self: true }); dispatch(authActions.loginSuccess(users[0])); } catch (error) { - dispatch(authActions.loginFailed(error)); + const hasEmailVerificationBeenSent = error.message.includes('Unverified email'); + dispatch(authActions.loginFailed(error, hasEmailVerificationBeenSent)); } }; @@ -196,3 +209,13 @@ export const loadAuthActionsAsync = (): ThunkAction => async (dispatch) => { dispatch(authActions.loadServerAuthActionsFailed(error)); } }; + +export const loadAdvancedAuthAsync = (): ThunkAction => async (dispatch): Promise => { + dispatch(authActions.loadAdvancedAuth()); + try { + const list: AdvancedAuthMethodsList = await cvat.server.advancedAuthentication(); + dispatch(authActions.loadAdvancedAuthSuccess(list)); + } catch (error) { + dispatch(authActions.loadAdvancedAuthFailed(error)); + } +}; diff --git a/cvat-ui/src/actions/boundaries-actions.ts b/cvat-ui/src/actions/boundaries-actions.ts index 71c8f9668b26..faa1cac4f8a3 100644 --- a/cvat-ui/src/actions/boundaries-actions.ts +++ b/cvat-ui/src/actions/boundaries-actions.ts @@ -1,11 +1,12 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction, ThunkDispatch, } from 'utils/redux'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; import { LogType } from 'cvat-logger'; import { computeZRange } from './annotation-actions'; @@ -17,23 +18,19 @@ export enum BoundariesActionTypes { } export const boundariesActions = { - resetAfterError: ( - job: any, - states: any[], - frameNumber: number, - frameData: any | null, - minZ: number, - maxZ: number, - colors: string[], - ) => createAction(BoundariesActionTypes.RESET_AFTER_ERROR, { - job, - states, - frameNumber, - frameData, - minZ, - maxZ, - colors, - }), + resetAfterError: (payload?: { + job: any; + states: any[]; + openTime: number; + frameNumber: number; + frameFilename: string; + frameHasRelatedContext: boolean; + colors: string[]; + filters: string[]; + frameData: any; + minZ: number; + maxZ: number; + }) => createAction(BoundariesActionTypes.RESET_AFTER_ERROR, payload), throwResetError: () => createAction(BoundariesActionTypes.THROW_RESET_ERROR), }; @@ -55,9 +52,19 @@ export function resetAfterErrorAsync(): ThunkAction { await job.logger.log(LogType.restoreJob); - dispatch(boundariesActions.resetAfterError(job, states, frameNumber, frameData, minZ, maxZ, colors)); - } else { - dispatch(boundariesActions.resetAfterError(null, [], 0, null, 0, 0, [])); + dispatch(boundariesActions.resetAfterError({ + job, + states, + openTime: state.annotation.job.openTime || Date.now(), + frameNumber, + frameFilename: frameData.filename, + frameHasRelatedContext: frameData.hasRelatedContext, + colors, + filters: [], + frameData, + minZ, + maxZ, + })); } } catch (error) { dispatch(boundariesActions.throwResetError()); diff --git a/cvat-ui/src/actions/cloud-storage-actions.ts b/cvat-ui/src/actions/cloud-storage-actions.ts index 051bf45634ee..ef438afec4e4 100644 --- a/cvat-ui/src/actions/cloud-storage-actions.ts +++ b/cvat-ui/src/actions/cloud-storage-actions.ts @@ -4,8 +4,8 @@ import { Dispatch, ActionCreator } from 'redux'; import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import getCore from 'cvat-core-wrapper'; -import { CloudStoragesQuery, CloudStorage, Indexable } from 'reducers/interfaces'; +import { getCore } from 'cvat-core-wrapper'; +import { CloudStoragesQuery, CloudStorage, Indexable } from 'reducers'; const cvat = getCore(); diff --git a/cvat-ui/src/actions/export-actions.ts b/cvat-ui/src/actions/export-actions.ts index beda9d0d7523..fe38dc73913f 100644 --- a/cvat-ui/src/actions/export-actions.ts +++ b/cvat-ui/src/actions/export-actions.ts @@ -1,51 +1,130 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import { getCore, Storage } from 'cvat-core-wrapper'; + +const core = getCore(); + export enum ExportActionTypes { - OPEN_EXPORT_MODAL = 'OPEN_EXPORT_MODAL', - CLOSE_EXPORT_MODAL = 'CLOSE_EXPORT_MODAL', + OPEN_EXPORT_DATASET_MODAL = 'OPEN_EXPORT_DATASET_MODAL', + CLOSE_EXPORT_DATASET_MODAL = 'CLOSE_EXPORT_DATASET_MODAL', EXPORT_DATASET = 'EXPORT_DATASET', EXPORT_DATASET_SUCCESS = 'EXPORT_DATASET_SUCCESS', EXPORT_DATASET_FAILED = 'EXPORT_DATASET_FAILED', + OPEN_EXPORT_BACKUP_MODAL = 'OPEN_EXPORT_BACKUP_MODAL', + CLOSE_EXPORT_BACKUP_MODAL = 'CLOSE_EXPORT_BACKUP_MODAL', + EXPORT_BACKUP = 'EXPORT_BACKUP', + EXPORT_BACKUP_SUCCESS = 'EXPORT_BACKUP_SUCCESS', + EXPORT_BACKUP_FAILED = 'EXPORT_BACKUP_FAILED', } export const exportActions = { - openExportModal: (instance: any) => createAction(ExportActionTypes.OPEN_EXPORT_MODAL, { instance }), - closeExportModal: () => createAction(ExportActionTypes.CLOSE_EXPORT_MODAL), + openExportDatasetModal: (instance: any) => ( + createAction(ExportActionTypes.OPEN_EXPORT_DATASET_MODAL, { instance }) + ), + closeExportDatasetModal: (instance: any) => ( + createAction(ExportActionTypes.CLOSE_EXPORT_DATASET_MODAL, { instance }) + ), exportDataset: (instance: any, format: string) => ( createAction(ExportActionTypes.EXPORT_DATASET, { instance, format }) ), - exportDatasetSuccess: (instance: any, format: string) => ( - createAction(ExportActionTypes.EXPORT_DATASET_SUCCESS, { instance, format }) + exportDatasetSuccess: ( + instance: any, + instanceType: 'project' | 'task' | 'job', + format: string, + isLocal: boolean, + resource: 'Dataset' | 'Annotations', + ) => ( + createAction(ExportActionTypes.EXPORT_DATASET_SUCCESS, { + instance, + instanceType, + format, + isLocal, + resource, + }) ), - exportDatasetFailed: (instance: any, format: string, error: any) => ( + exportDatasetFailed: (instance: any, instanceType: 'project' | 'task' | 'job', format: string, error: any) => ( createAction(ExportActionTypes.EXPORT_DATASET_FAILED, { instance, + instanceType, format, error, }) ), + openExportBackupModal: (instance: any) => ( + createAction(ExportActionTypes.OPEN_EXPORT_BACKUP_MODAL, { instance }) + ), + closeExportBackupModal: (instance: any) => ( + createAction(ExportActionTypes.CLOSE_EXPORT_BACKUP_MODAL, { instance }) + ), + exportBackup: (instance: any) => ( + createAction(ExportActionTypes.EXPORT_BACKUP, { instance }) + ), + exportBackupSuccess: (instance: any, instanceType: 'task' | 'project', isLocal: boolean) => ( + createAction(ExportActionTypes.EXPORT_BACKUP_SUCCESS, { instance, instanceType, isLocal }) + ), + exportBackupFailed: (instance: any, instanceType: 'task' | 'project', error: any) => ( + createAction(ExportActionTypes.EXPORT_BACKUP_FAILED, { instance, instanceType, error }) + ), }; export const exportDatasetAsync = ( instance: any, format: string, - name: string, saveImages: boolean, + useDefaultSettings: boolean, + targetStorage: Storage, + name?: string, ): ThunkAction => async (dispatch) => { dispatch(exportActions.exportDataset(instance, format)); + let instanceType: 'project' | 'task' | 'job'; + if (instance instanceof core.classes.Project) { + instanceType = 'project'; + } else if (instance instanceof core.classes.Task) { + instanceType = 'task'; + } else { + instanceType = 'job'; + } + + try { + const result = await instance.annotations + .exportDataset(format, saveImages, useDefaultSettings, targetStorage, name); + if (result) { + const downloadAnchor = window.document.getElementById('downloadAnchor') as HTMLAnchorElement; + downloadAnchor.href = result; + downloadAnchor.click(); + } + const resource = saveImages ? 'Dataset' : 'Annotations'; + dispatch(exportActions.exportDatasetSuccess(instance, instanceType, format, !!result, resource)); + } catch (error) { + dispatch(exportActions.exportDatasetFailed(instance, instanceType, format, error)); + } +}; + +export const exportBackupAsync = ( + instance: any, + targetStorage: Storage, + useDefaultSetting: boolean, + fileName?: string, +): ThunkAction => async (dispatch) => { + dispatch(exportActions.exportBackup(instance)); + const instanceType = (instance instanceof core.classes.Project) ? 'project' : 'task'; + try { - const url = await instance.annotations.exportDataset(format, saveImages, name); - const downloadAnchor = window.document.getElementById('downloadAnchor') as HTMLAnchorElement; - downloadAnchor.href = url; - downloadAnchor.click(); - dispatch(exportActions.exportDatasetSuccess(instance, format)); + const result = await instance.backup(targetStorage, useDefaultSetting, fileName); + if (result) { + const downloadAnchor = window.document.getElementById('downloadAnchor') as HTMLAnchorElement; + downloadAnchor.href = result; + downloadAnchor.click(); + } + dispatch(exportActions.exportBackupSuccess(instance, instanceType, !!result)); } catch (error) { - dispatch(exportActions.exportDatasetFailed(instance, format, error)); + dispatch(exportActions.exportBackupFailed(instance, instanceType, error as Error)); } }; diff --git a/cvat-ui/src/actions/formats-actions.ts b/cvat-ui/src/actions/formats-actions.ts index 4925fc2a2129..575d98dc96de 100644 --- a/cvat-ui/src/actions/formats-actions.ts +++ b/cvat-ui/src/actions/formats-actions.ts @@ -1,9 +1,9 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; const cvat = getCore(); diff --git a/cvat-ui/src/actions/import-actions.ts b/cvat-ui/src/actions/import-actions.ts index b237e35382c8..7e40afbf709f 100644 --- a/cvat-ui/src/actions/import-actions.ts +++ b/cvat-ui/src/actions/import-actions.ts @@ -1,58 +1,170 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import { createAction, ActionUnion, ThunkAction } from 'utils/redux'; -import { CombinedState } from 'reducers/interfaces'; +import { CombinedState } from 'reducers'; +import { getCore, Storage } from 'cvat-core-wrapper'; +import { LogType } from 'cvat-logger'; import { getProjectsAsync } from './projects-actions'; +import { jobInfoGenerator, receiveAnnotationsParameters, AnnotationActionTypes } from './annotation-actions'; + +const core = getCore(); export enum ImportActionTypes { - OPEN_IMPORT_MODAL = 'OPEN_IMPORT_MODAL', - CLOSE_IMPORT_MODAL = 'CLOSE_IMPORT_MODAL', + OPEN_IMPORT_DATASET_MODAL = 'OPEN_IMPORT_DATASET_MODAL', + CLOSE_IMPORT_DATASET_MODAL = 'CLOSE_IMPORT_DATASET_MODAL', IMPORT_DATASET = 'IMPORT_DATASET', IMPORT_DATASET_SUCCESS = 'IMPORT_DATASET_SUCCESS', IMPORT_DATASET_FAILED = 'IMPORT_DATASET_FAILED', IMPORT_DATASET_UPDATE_STATUS = 'IMPORT_DATASET_UPDATE_STATUS', + OPEN_IMPORT_BACKUP_MODAL = 'OPEN_IMPORT_BACKUP_MODAL', + CLOSE_IMPORT_BACKUP_MODAL = 'CLOSE_IMPORT_BACKUP_MODAL', + IMPORT_BACKUP = 'IMPORT_BACKUP', + IMPORT_BACKUP_SUCCESS = 'IMPORT_BACKUP_SUCCESS', + IMPORT_BACKUP_FAILED = 'IMPORT_BACKUP_FAILED', } export const importActions = { - openImportModal: (instance: any) => createAction(ImportActionTypes.OPEN_IMPORT_MODAL, { instance }), - closeImportModal: () => createAction(ImportActionTypes.CLOSE_IMPORT_MODAL), - importDataset: (projectId: number) => ( - createAction(ImportActionTypes.IMPORT_DATASET, { id: projectId }) + openImportDatasetModal: (instance: any) => ( + createAction(ImportActionTypes.OPEN_IMPORT_DATASET_MODAL, { instance }) + ), + closeImportDatasetModal: (instance: any) => ( + createAction(ImportActionTypes.CLOSE_IMPORT_DATASET_MODAL, { instance }) + ), + importDataset: (instance: any, format: string) => ( + createAction(ImportActionTypes.IMPORT_DATASET, { instance, format }) ), - importDatasetSuccess: () => ( - createAction(ImportActionTypes.IMPORT_DATASET_SUCCESS) + importDatasetSuccess: (instance: any, resource: 'dataset' | 'annotation') => ( + createAction(ImportActionTypes.IMPORT_DATASET_SUCCESS, { instance, resource }) ), - importDatasetFailed: (instance: any, error: any) => ( + importDatasetFailed: (instance: any, resource: 'dataset' | 'annotation', error: any) => ( createAction(ImportActionTypes.IMPORT_DATASET_FAILED, { instance, + resource, error, }) ), - importDatasetUpdateStatus: (progress: number, status: string) => ( - createAction(ImportActionTypes.IMPORT_DATASET_UPDATE_STATUS, { progress, status }) + importDatasetUpdateStatus: (instance: any, progress: number, status: string) => ( + createAction(ImportActionTypes.IMPORT_DATASET_UPDATE_STATUS, { instance, progress, status }) + ), + openImportBackupModal: (instanceType: 'project' | 'task') => ( + createAction(ImportActionTypes.OPEN_IMPORT_BACKUP_MODAL, { instanceType }) + ), + closeImportBackupModal: (instanceType: 'project' | 'task') => ( + createAction(ImportActionTypes.CLOSE_IMPORT_BACKUP_MODAL, { instanceType }) + ), + importBackup: () => createAction(ImportActionTypes.IMPORT_BACKUP), + importBackupSuccess: (instanceId: number, instanceType: 'project' | 'task') => ( + createAction(ImportActionTypes.IMPORT_BACKUP_SUCCESS, { instanceId, instanceType }) + ), + importBackupFailed: (instanceType: 'project' | 'task', error: any) => ( + createAction(ImportActionTypes.IMPORT_BACKUP_FAILED, { instanceType, error }) ), }; -export const importDatasetAsync = (instance: any, format: string, file: File): ThunkAction => ( +export const importDatasetAsync = ( + instance: any, + format: string, + useDefaultSettings: boolean, + sourceStorage: Storage, + file: File | string, + convMaskToPoly: boolean, +): ThunkAction => ( async (dispatch, getState) => { + const resource = instance instanceof core.classes.Project ? 'dataset' : 'annotation'; + try { const state: CombinedState = getState(); - if (state.import.importingId !== null) { - throw Error('Only one importing of dataset allowed at the same time'); + + if (instance instanceof core.classes.Project) { + if (state.import.projects.dataset.current?.[instance.id]) { + throw Error('Only one importing of annotation/dataset allowed at the same time'); + } + dispatch(importActions.importDataset(instance, format)); + await instance.annotations + .importDataset(format, useDefaultSettings, sourceStorage, file, { + convMaskToPoly, + updateStatusCallback: (message: string, progress: number) => ( + dispatch(importActions.importDatasetUpdateStatus( + instance, Math.floor(progress * 100), message, + )) + ), + }); + } else if (instance instanceof core.classes.Task) { + if (state.import.tasks.dataset.current?.[instance.id]) { + throw Error('Only one importing of annotation/dataset allowed at the same time'); + } + dispatch(importActions.importDataset(instance, format)); + await instance.annotations.upload(format, useDefaultSettings, sourceStorage, file, { convMaskToPoly }); + } else { // job + if (state.import.tasks.dataset.current?.[instance.taskId]) { + throw Error('Annotations is being uploaded for the task'); + } + if (state.import.jobs.dataset.current?.[instance.id]) { + throw Error('Only one uploading of annotations for a job allowed at the same time'); + } + const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); + + dispatch(importActions.importDataset(instance, format)); + + const frame = state.annotation.player.frame.number; + await instance.annotations.upload(format, useDefaultSettings, sourceStorage, file, { convMaskToPoly }); + + await instance.logger.log(LogType.uploadAnnotations, { + ...(await jobInfoGenerator(instance)), + }); + + await instance.annotations.clear(true); + await instance.actions.clear(); + const history = await instance.actions.get(); + + // One more update to escape some problems + // in canvas when shape with the same + // clientID has different type (polygon, rectangle) for example + dispatch({ + type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_SUCCESS, + payload: { + states: [], + history, + }, + }); + + const states = await instance.annotations.get(frame, showAllInterpolationTracks, filters); + + setTimeout(() => { + dispatch({ + type: AnnotationActionTypes.UPLOAD_JOB_ANNOTATIONS_SUCCESS, + payload: { + history, + states, + }, + }); + }); } - dispatch(importActions.importDataset(instance.id)); - await instance.annotations.importDataset(format, file, (message: string, progress: number) => ( - dispatch(importActions.importDatasetUpdateStatus(progress * 100, message)) - )); } catch (error) { - dispatch(importActions.importDatasetFailed(instance, error)); + dispatch(importActions.importDatasetFailed(instance, resource, error)); return; } - dispatch(importActions.importDatasetSuccess()); - dispatch(getProjectsAsync({ id: instance.id }, getState().projects.tasksGettingQuery)); + dispatch(importActions.importDatasetSuccess(instance, resource)); + if (instance instanceof core.classes.Project) { + dispatch(getProjectsAsync({ id: instance.id }, getState().projects.tasksGettingQuery)); + } + } +); + +export const importBackupAsync = (instanceType: 'project' | 'task', storage: Storage, file: File | string): ThunkAction => ( + async (dispatch) => { + dispatch(importActions.importBackup()); + try { + const inctanceClass = (instanceType === 'task') ? core.classes.Task : core.classes.Project; + const instance = await inctanceClass.restore(storage, file); + dispatch(importActions.importBackupSuccess(instance.id, instanceType)); + } catch (error) { + dispatch(importActions.importBackupFailed(instanceType, error)); + } } ); diff --git a/cvat-ui/src/actions/jobs-actions.ts b/cvat-ui/src/actions/jobs-actions.ts index 7c333e1af2c0..5f982fb2953a 100644 --- a/cvat-ui/src/actions/jobs-actions.ts +++ b/cvat-ui/src/actions/jobs-actions.ts @@ -3,8 +3,8 @@ // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import getCore from 'cvat-core-wrapper'; -import { Indexable, JobsQuery } from 'reducers/interfaces'; +import { getCore } from 'cvat-core-wrapper'; +import { Indexable, JobsQuery } from 'reducers'; const cvat = getCore(); diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index 39dbe5e5b003..dae9d451e25c 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -1,10 +1,10 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import { Model, ActiveInference, RQStatus } from 'reducers/interfaces'; -import getCore from 'cvat-core-wrapper'; +import { Model, ActiveInference, RQStatus } from 'reducers'; +import { getCore } from 'cvat-core-wrapper'; export enum ModelsActionTypes { GET_MODELS = 'GET_MODELS', diff --git a/cvat-ui/src/actions/notification-actions.ts b/cvat-ui/src/actions/notification-actions.ts index 3f47d9879171..d2cf1b628208 100644 --- a/cvat-ui/src/actions/notification-actions.ts +++ b/cvat-ui/src/actions/notification-actions.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/actions/organization-actions.ts b/cvat-ui/src/actions/organization-actions.ts index 2781482712ed..4a2c72936af8 100644 --- a/cvat-ui/src/actions/organization-actions.ts +++ b/cvat-ui/src/actions/organization-actions.ts @@ -1,10 +1,10 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT import { Store } from 'antd/lib/form/interface'; import { User } from 'components/task-page/user-selector'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; const core = getCore(); diff --git a/cvat-ui/src/actions/plugins-actions.ts b/cvat-ui/src/actions/plugins-actions.ts index 20e5c850dc54..49006023a33b 100644 --- a/cvat-ui/src/actions/plugins-actions.ts +++ b/cvat-ui/src/actions/plugins-actions.ts @@ -1,10 +1,10 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import { PluginsList } from 'reducers/interfaces'; -import getCore from '../cvat-core-wrapper'; +import { PluginsList } from 'reducers'; +import { getCore } from 'cvat-core-wrapper'; const core = getCore(); diff --git a/cvat-ui/src/actions/projects-actions.ts b/cvat-ui/src/actions/projects-actions.ts index 0af733cce409..eb01b017e9bf 100644 --- a/cvat-ui/src/actions/projects-actions.ts +++ b/cvat-ui/src/actions/projects-actions.ts @@ -1,4 +1,5 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -7,10 +8,10 @@ import { Dispatch, ActionCreator } from 'redux'; import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; import { ProjectsQuery, TasksQuery, CombinedState, Indexable, -} from 'reducers/interfaces'; +} from 'reducers'; import { getTasksAsync } from 'actions/tasks-actions'; import { getCVATStore } from 'cvat-store'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; const cvat = getCore(); @@ -28,12 +29,6 @@ export enum ProjectsActionTypes { DELETE_PROJECT = 'DELETE_PROJECT', DELETE_PROJECT_SUCCESS = 'DELETE_PROJECT_SUCCESS', DELETE_PROJECT_FAILED = 'DELETE_PROJECT_FAILED', - BACKUP_PROJECT = 'BACKUP_PROJECT', - BACKUP_PROJECT_SUCCESS = 'BACKUP_PROJECT_SUCCESS', - BACKUP_PROJECT_FAILED = 'BACKUP_PROJECT_FAILED', - RESTORE_PROJECT = 'IMPORT_PROJECT', - RESTORE_PROJECT_SUCCESS = 'IMPORT_PROJECT_SUCCESS', - RESTORE_PROJECT_FAILED = 'IMPORT_PROJECT_FAILED', } // prettier-ignore @@ -63,20 +58,6 @@ const projectActions = { deleteProjectFailed: (projectId: number, error: any) => ( createAction(ProjectsActionTypes.DELETE_PROJECT_FAILED, { projectId, error }) ), - backupProject: (projectId: number) => createAction(ProjectsActionTypes.BACKUP_PROJECT, { projectId }), - backupProjectSuccess: (projectID: number) => ( - createAction(ProjectsActionTypes.BACKUP_PROJECT_SUCCESS, { projectID }) - ), - backupProjectFailed: (projectID: number, error: any) => ( - createAction(ProjectsActionTypes.BACKUP_PROJECT_FAILED, { projectId: projectID, error }) - ), - restoreProject: () => createAction(ProjectsActionTypes.RESTORE_PROJECT), - restoreProjectSuccess: (projectID: number) => ( - createAction(ProjectsActionTypes.RESTORE_PROJECT_SUCCESS, { projectID }) - ), - restoreProjectFailed: (error: any) => ( - createAction(ProjectsActionTypes.RESTORE_PROJECT_FAILED, { error }) - ), }; export type ProjectActions = ActionUnion; @@ -149,8 +130,10 @@ export function createProjectAsync(data: any): ThunkAction { try { const savedProject = await projectInstance.save(); dispatch(projectActions.createProjectSuccess(savedProject.id)); + return savedProject; } catch (error) { dispatch(projectActions.createProjectFailed(error)); + throw error; } }; } @@ -188,31 +171,3 @@ export function deleteProjectAsync(projectInstance: any): ThunkAction { } }; } - -export function restoreProjectAsync(file: File): ThunkAction { - return async (dispatch: ActionCreator): Promise => { - dispatch(projectActions.restoreProject()); - try { - const projectInstance = await cvat.classes.Project.restore(file); - dispatch(projectActions.restoreProjectSuccess(projectInstance)); - } catch (error) { - dispatch(projectActions.restoreProjectFailed(error)); - } - }; -} - -export function backupProjectAsync(projectInstance: any): ThunkAction { - return async (dispatch: ActionCreator): Promise => { - dispatch(projectActions.backupProject(projectInstance.id)); - - try { - const url = await projectInstance.backup(); - const downloadAnchor = window.document.getElementById('downloadAnchor') as HTMLAnchorElement; - downloadAnchor.href = url; - downloadAnchor.click(); - dispatch(projectActions.backupProjectSuccess(projectInstance.id)); - } catch (error) { - dispatch(projectActions.backupProjectFailed(projectInstance.id, error)); - } - }; -} diff --git a/cvat-ui/src/actions/review-actions.ts b/cvat-ui/src/actions/review-actions.ts index a41aff68b0b1..4db2dc323d3f 100644 --- a/cvat-ui/src/actions/review-actions.ts +++ b/cvat-ui/src/actions/review-actions.ts @@ -1,9 +1,9 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; const cvat = getCore(); diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index 56430553b14d..70e980e5ce03 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -1,11 +1,11 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT import { AnyAction } from 'redux'; import { GridColor, ColorBy, SettingsState, ToolsBlockerState, -} from 'reducers/interfaces'; +} from 'reducers'; export enum SettingsActionTypes { SWITCH_ROTATE_ALL = 'SWITCH_ROTATE_ALL', @@ -24,6 +24,7 @@ export enum SettingsActionTypes { SWITCH_RESET_ZOOM = 'SWITCH_RESET_ZOOM', SWITCH_SMOOTH_IMAGE = 'SWITCH_SMOOTH_IMAGE', SWITCH_TEXT_FONT_SIZE = 'SWITCH_TEXT_FONT_SIZE', + SWITCH_CONTROL_POINTS_SIZE = 'SWITCH_CONTROL_POINTS_SIZE', SWITCH_TEXT_POSITION = 'SWITCH_TEXT_POSITION', SWITCH_TEXT_CONTENT = 'SWITCH_TEXT_CONTENT', CHANGE_BRIGHTNESS_LEVEL = 'CHANGE_BRIGHTNESS_LEVEL', @@ -41,6 +42,8 @@ export enum SettingsActionTypes { SWITCH_SETTINGS_DIALOG = 'SWITCH_SETTINGS_DIALOG', SET_SETTINGS = 'SET_SETTINGS', SWITCH_TOOLS_BLOCKER_STATE = 'SWITCH_TOOLS_BLOCKER_STATE', + SWITCH_SHOWING_DELETED_FRAMES = 'SWITCH_SHOWING_DELETED_FRAMES', + SWITCH_SHOWING_TAGS_ON_FRAME = 'SWITCH_SHOWING_TAGS_ON_FRAME', } export function changeShapesOpacity(opacity: number): AnyAction { @@ -188,6 +191,15 @@ export function switchTextFontSize(fontSize: number): AnyAction { }; } +export function switchControlPointsSize(pointsSize: number): AnyAction { + return { + type: SettingsActionTypes.SWITCH_CONTROL_POINTS_SIZE, + payload: { + controlPointsSize: pointsSize, + }, + }; +} + export function switchTextPosition(position: 'auto' | 'center'): AnyAction { return { type: SettingsActionTypes.SWITCH_TEXT_POSITION, @@ -197,11 +209,11 @@ export function switchTextPosition(position: 'auto' | 'center'): AnyAction { }; } -export function switchTextContent(textContent: string): AnyAction { +export function switchTextContent(textContent: string[]): AnyAction { return { type: SettingsActionTypes.SWITCH_TEXT_CONTENT, payload: { - textContent, + textContent: textContent.join(','), }, }; } @@ -340,3 +352,21 @@ export function setSettings(settings: Partial): AnyAction { }, }; } + +export function switchShowingDeletedFrames(showDeletedFrames: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_SHOWING_DELETED_FRAMES, + payload: { + showDeletedFrames, + }, + }; +} + +export function switchShowingTagsOnFrame(showTagsOnFrame: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_SHOWING_TAGS_ON_FRAME, + payload: { + showTagsOnFrame, + }, + }; +} diff --git a/cvat-ui/src/actions/share-actions.ts b/cvat-ui/src/actions/share-actions.ts index 3fb6dc4c5cf0..1ceed81e99c4 100644 --- a/cvat-ui/src/actions/share-actions.ts +++ b/cvat-ui/src/actions/share-actions.ts @@ -1,11 +1,12 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; -import { ShareFileInfo } from 'reducers/interfaces'; +import { ShareFileInfo } from 'reducers'; const core = getCore(); @@ -28,16 +29,16 @@ const shareActions = { export type ShareActions = ActionUnion; -export function loadShareDataAsync(directory: string, success: () => void, failure: () => void): ThunkAction { - return async (dispatch): Promise => { +export function loadShareDataAsync(directory: string): ThunkAction { + return async (dispatch): Promise => { try { dispatch(shareActions.loadShareData()); const values = await core.server.share(directory); - success(); dispatch(shareActions.loadShareDataSuccess(values as ShareFileInfo[], directory)); + return (values as ShareFileInfo[]); } catch (error) { - failure(); dispatch(shareActions.loadShareDataFailed(error)); + throw error; } }; } diff --git a/cvat-ui/src/actions/shortcuts-actions.ts b/cvat-ui/src/actions/shortcuts-actions.ts index 2d793cc6d8dd..8f35d0a57e63 100644 --- a/cvat-ui/src/actions/shortcuts-actions.ts +++ b/cvat-ui/src/actions/shortcuts-actions.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction } from 'utils/redux'; diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index f99e04c5f997..336e2924aa44 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -1,12 +1,14 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import { AnyAction, Dispatch, ActionCreator } from 'redux'; import { ThunkAction } from 'redux-thunk'; -import { TasksQuery, CombinedState, Indexable } from 'reducers/interfaces'; -import { getCVATStore } from 'cvat-store'; -import getCore from 'cvat-core-wrapper'; +import { + TasksQuery, CombinedState, Indexable, StorageLocation, +} from 'reducers'; +import { getCore, Storage } from 'cvat-core-wrapper'; import { getInferenceStatusAsync } from './models-actions'; const cvat = getCore(); @@ -15,15 +17,9 @@ export enum TasksActionTypes { GET_TASKS = 'GET_TASKS', GET_TASKS_SUCCESS = 'GET_TASKS_SUCCESS', GET_TASKS_FAILED = 'GET_TASKS_FAILED', - LOAD_ANNOTATIONS = 'LOAD_ANNOTATIONS', - LOAD_ANNOTATIONS_SUCCESS = 'LOAD_ANNOTATIONS_SUCCESS', - LOAD_ANNOTATIONS_FAILED = 'LOAD_ANNOTATIONS_FAILED', DELETE_TASK = 'DELETE_TASK', DELETE_TASK_SUCCESS = 'DELETE_TASK_SUCCESS', DELETE_TASK_FAILED = 'DELETE_TASK_FAILED', - CREATE_TASK = 'CREATE_TASK', - CREATE_TASK_STATUS_UPDATED = 'CREATE_TASK_STATUS_UPDATED', - CREATE_TASK_SUCCESS = 'CREATE_TASK_SUCCESS', CREATE_TASK_FAILED = 'CREATE_TASK_FAILED', UPDATE_TASK = 'UPDATE_TASK', UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS', @@ -32,16 +28,10 @@ export enum TasksActionTypes { UPDATE_JOB_SUCCESS = 'UPDATE_JOB_SUCCESS', UPDATE_JOB_FAILED = 'UPDATE_JOB_FAILED', HIDE_EMPTY_TASKS = 'HIDE_EMPTY_TASKS', - EXPORT_TASK = 'EXPORT_TASK', - EXPORT_TASK_SUCCESS = 'EXPORT_TASK_SUCCESS', - EXPORT_TASK_FAILED = 'EXPORT_TASK_FAILED', - IMPORT_TASK = 'IMPORT_TASK', - IMPORT_TASK_SUCCESS = 'IMPORT_TASK_SUCCESS', - IMPORT_TASK_FAILED = 'IMPORT_TASK_FAILED', SWITCH_MOVE_TASK_MODAL_VISIBLE = 'SWITCH_MOVE_TASK_MODAL_VISIBLE', } -function getTasks(query: TasksQuery, updateQuery: boolean): AnyAction { +function getTasks(query: Partial, updateQuery: boolean): AnyAction { const action = { type: TasksActionTypes.GET_TASKS, payload: { @@ -75,7 +65,10 @@ function getTasksFailed(error: any): AnyAction { return action; } -export function getTasksAsync(query: TasksQuery, updateQuery = true): ThunkAction, {}, {}, AnyAction> { +export function getTasksAsync( + query: Partial, + updateQuery = true, +): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { dispatch(getTasks(query, updateQuery)); @@ -103,157 +96,6 @@ export function getTasksAsync(query: TasksQuery, updateQuery = true): ThunkActio }; } -function loadAnnotations(task: any, loader: any): AnyAction { - const action = { - type: TasksActionTypes.LOAD_ANNOTATIONS, - payload: { - task, - loader, - }, - }; - - return action; -} - -function loadAnnotationsSuccess(task: any): AnyAction { - const action = { - type: TasksActionTypes.LOAD_ANNOTATIONS_SUCCESS, - payload: { - task, - }, - }; - - return action; -} - -function loadAnnotationsFailed(task: any, error: any): AnyAction { - const action = { - type: TasksActionTypes.LOAD_ANNOTATIONS_FAILED, - payload: { - task, - error, - }, - }; - - return action; -} - -export function loadAnnotationsAsync( - task: any, - loader: any, - file: File, -): ThunkAction, {}, {}, AnyAction> { - return async (dispatch: ActionCreator): Promise => { - try { - const store = getCVATStore(); - const state: CombinedState = store.getState(); - if (state.tasks.activities.loads[task.id]) { - throw Error('Only one loading of annotations for a task allowed at the same time'); - } - dispatch(loadAnnotations(task, loader)); - await task.annotations.upload(file, loader); - } catch (error) { - dispatch(loadAnnotationsFailed(task, error)); - return; - } - - dispatch(loadAnnotationsSuccess(task)); - }; -} - -function importTask(): AnyAction { - const action = { - type: TasksActionTypes.IMPORT_TASK, - payload: {}, - }; - - return action; -} - -function importTaskSuccess(task: any): AnyAction { - const action = { - type: TasksActionTypes.IMPORT_TASK_SUCCESS, - payload: { - task, - }, - }; - - return action; -} - -function importTaskFailed(error: any): AnyAction { - const action = { - type: TasksActionTypes.IMPORT_TASK_FAILED, - payload: { - error, - }, - }; - - return action; -} - -export function importTaskAsync(file: File): ThunkAction, {}, {}, AnyAction> { - return async (dispatch: ActionCreator): Promise => { - try { - dispatch(importTask()); - const taskInstance = await cvat.classes.Task.import(file); - dispatch(importTaskSuccess(taskInstance)); - } catch (error) { - dispatch(importTaskFailed(error)); - } - }; -} - -function exportTask(taskID: number): AnyAction { - const action = { - type: TasksActionTypes.EXPORT_TASK, - payload: { - taskID, - }, - }; - - return action; -} - -function exportTaskSuccess(taskID: number): AnyAction { - const action = { - type: TasksActionTypes.EXPORT_TASK_SUCCESS, - payload: { - taskID, - }, - }; - - return action; -} - -function exportTaskFailed(taskID: number, error: Error): AnyAction { - const action = { - type: TasksActionTypes.EXPORT_TASK_FAILED, - payload: { - taskID, - error, - }, - }; - - return action; -} - -export function exportTaskAsync(taskInstance: any): ThunkAction, {}, {}, AnyAction> { - return async (dispatch: ActionCreator): Promise => { - dispatch(exportTask(taskInstance.id)); - - try { - const url = await taskInstance.export(); - const downloadAnchor = window.document.getElementById('downloadAnchor') as HTMLAnchorElement; - downloadAnchor.href = url; - downloadAnchor.click(); - dispatch(exportTaskSuccess(taskInstance.id)); - } catch (error) { - dispatch(exportTaskFailed(taskInstance.id, error as Error)); - } - }; -} - function deleteTask(taskID: number): AnyAction { const action = { type: TasksActionTypes.DELETE_TASK, @@ -302,26 +144,6 @@ export function deleteTaskAsync(taskInstance: any): ThunkAction, { }; } -function createTask(): AnyAction { - const action = { - type: TasksActionTypes.CREATE_TASK, - payload: {}, - }; - - return action; -} - -function createTaskSuccess(taskId: number): AnyAction { - const action = { - type: TasksActionTypes.CREATE_TASK_SUCCESS, - payload: { - taskId, - }, - }; - - return action; -} - function createTaskFailed(error: any): AnyAction { const action = { type: TasksActionTypes.CREATE_TASK_FAILED, @@ -333,19 +155,9 @@ function createTaskFailed(error: any): AnyAction { return action; } -function createTaskUpdateStatus(status: string): AnyAction { - const action = { - type: TasksActionTypes.CREATE_TASK_STATUS_UPDATED, - payload: { - status, - }, - }; - - return action; -} - -export function createTaskAsync(data: any): ThunkAction, {}, {}, AnyAction> { - return async (dispatch: ActionCreator): Promise => { +export function createTaskAsync(data: any, onProgress?: (status: string) => void): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch): Promise => { const description: any = { name: data.basic.name, labels: data.labels, @@ -353,6 +165,8 @@ export function createTaskAsync(data: any): ThunkAction, {}, {}, A use_zip_chunks: data.advanced.useZipChunks, use_cache: data.advanced.useCache, sorting_method: data.advanced.sortingMethod, + source_storage: new Storage(data.advanced.sourceStorage || { location: StorageLocation.LOCAL }).toJSON(), + target_storage: new Storage(data.advanced.targetStorage || { location: StorageLocation.LOCAL }).toJSON(), }; if (data.projectId) { @@ -402,7 +216,7 @@ export function createTaskAsync(data: any): ThunkAction, {}, {}, A if (gitPlugin) { gitPlugin.callbacks.onStatusChange = (status: string): void => { - dispatch(createTaskUpdateStatus(status)); + onProgress?.(status); }; gitPlugin.data.task = taskInstance; gitPlugin.data.repos = data.advanced.repository; @@ -411,14 +225,14 @@ export function createTaskAsync(data: any): ThunkAction, {}, {}, A } } - dispatch(createTask()); try { const savedTask = await taskInstance.save((status: string, progress: number): void => { - dispatch(createTaskUpdateStatus(status + (progress !== null ? ` ${Math.floor(progress * 100)}%` : ''))); + onProgress?.(status + (progress !== null ? ` ${Math.floor(progress * 100)}%` : '')); }); - dispatch(createTaskSuccess(savedTask.id)); + return savedTask; } catch (error) { dispatch(createTaskFailed(error)); + throw error; } }; } @@ -441,37 +255,37 @@ export function updateTaskSuccess(task: any, taskID: number): AnyAction { return action; } -function updateJob(): AnyAction { +function updateTaskFailed(error: any, task: any): AnyAction { const action = { - type: TasksActionTypes.UPDATE_JOB, - payload: { }, + type: TasksActionTypes.UPDATE_TASK_FAILED, + payload: { error, task }, }; return action; } -function updateJobSuccess(jobInstance: any): AnyAction { +function updateJob(jobID: number): AnyAction { const action = { - type: TasksActionTypes.UPDATE_JOB_SUCCESS, - payload: { jobInstance }, + type: TasksActionTypes.UPDATE_JOB, + payload: { jobID }, }; return action; } -function updateJobFailed(jobID: number, error: any): AnyAction { +function updateJobSuccess(jobInstance: any, jobID: number): AnyAction { const action = { - type: TasksActionTypes.UPDATE_JOB_FAILED, - payload: { jobID, error }, + type: TasksActionTypes.UPDATE_JOB_SUCCESS, + payload: { jobID, jobInstance }, }; return action; } -function updateTaskFailed(error: any, task: any): AnyAction { +function updateJobFailed(jobID: number, error: any): AnyAction { const action = { - type: TasksActionTypes.UPDATE_TASK_FAILED, - payload: { error, task }, + type: TasksActionTypes.UPDATE_JOB_FAILED, + payload: { jobID, error }, }; return action; @@ -503,9 +317,9 @@ export function updateTaskAsync(taskInstance: any): ThunkAction, C export function updateJobAsync(jobInstance: any): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { - dispatch(updateJob()); + dispatch(updateJob(jobInstance.id)); const newJob = await jobInstance.save(); - dispatch(updateJobSuccess(newJob)); + dispatch(updateJobSuccess(newJob, newJob.id)); } catch (error) { dispatch(updateJobFailed(jobInstance.id, error)); } diff --git a/cvat-ui/src/actions/useragreements-actions.ts b/cvat-ui/src/actions/useragreements-actions.ts index 98e0a6fc91f3..279b7303cabe 100644 --- a/cvat-ui/src/actions/useragreements-actions.ts +++ b/cvat-ui/src/actions/useragreements-actions.ts @@ -1,10 +1,10 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import getCore from 'cvat-core-wrapper'; -import { UserAgreement } from 'reducers/interfaces'; +import { getCore } from 'cvat-core-wrapper'; +import { UserAgreement } from 'reducers'; const core = getCore(); diff --git a/cvat-ui/src/actions/webhooks-actions.ts b/cvat-ui/src/actions/webhooks-actions.ts new file mode 100644 index 000000000000..1b20b1ee41c7 --- /dev/null +++ b/cvat-ui/src/actions/webhooks-actions.ts @@ -0,0 +1,113 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { getCore, Webhook } from 'cvat-core-wrapper'; +import { Dispatch, ActionCreator, Store } from 'redux'; +import { Indexable, WebhooksQuery } from 'reducers'; +import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; + +const cvat = getCore(); + +export enum WebhooksActionsTypes { + GET_WEBHOOKS = 'GET_WEBHOOKS', + GET_WEBHOOKS_SUCCESS = 'GET_WEBHOOKS_SUCCESS', + GET_WEBHOOKS_FAILED = 'GET_WEBHOOKS_FAILED', + CREATE_WEBHOOK = 'CREATE_WEBHOOK', + CREATE_WEBHOOK_SUCCESS = 'CREATE_WEBHOOK_SUCCESS', + CREATE_WEBHOOK_FAILED = 'CREATE_WEBHOOK_FAILED', + UPDATE_WEBHOOK = 'UPDATE_WEBHOOK', + UPDATE_WEBHOOK_SUCCESS = 'UPDATE_WEBHOOK_SUCCESS', + UPDATE_WEBHOOK_FAILED = 'UPDATE_WEBHOOK_FAILED', + DELETE_WEBHOOK = 'DELETE_WEBHOOK', + DELETE_WEBHOOK_SUCCESS = 'DELETE_WEBHOOK_SUCCESS', + DELETE_WEBHOOK_FAILED = 'DELETE_WEBHOOK_FAILED', +} + +const webhooksActions = { + getWebhooks: (query: WebhooksQuery) => createAction(WebhooksActionsTypes.GET_WEBHOOKS, { query }), + getWebhooksSuccess: (webhooks: Webhook[], count: number) => createAction( + WebhooksActionsTypes.GET_WEBHOOKS_SUCCESS, { webhooks, count }, + ), + getWebhooksFailed: (error: any) => createAction(WebhooksActionsTypes.GET_WEBHOOKS_FAILED, { error }), + createWebhook: () => createAction(WebhooksActionsTypes.CREATE_WEBHOOK), + createWebhookSuccess: (webhook: Webhook) => createAction(WebhooksActionsTypes.CREATE_WEBHOOK_SUCCESS, { webhook }), + createWebhookFailed: (error: any) => createAction(WebhooksActionsTypes.CREATE_WEBHOOK_FAILED, { error }), + updateWebhook: () => createAction(WebhooksActionsTypes.UPDATE_WEBHOOK), + updateWebhookSuccess: (webhook: any) => createAction(WebhooksActionsTypes.UPDATE_WEBHOOK_SUCCESS, { webhook }), + updateWebhookFailed: (error: any) => createAction(WebhooksActionsTypes.UPDATE_WEBHOOK_FAILED, { error }), + deleteWebhook: () => createAction(WebhooksActionsTypes.DELETE_WEBHOOK), + deleteWebhookSuccess: () => createAction(WebhooksActionsTypes.DELETE_WEBHOOK_SUCCESS), + deleteWebhookFailed: (webhookID: number, error: any) => createAction( + WebhooksActionsTypes.DELETE_WEBHOOK_FAILED, { webhookID, error }, + ), +}; + +export const getWebhooksAsync = (query: WebhooksQuery): ThunkAction => ( + async (dispatch: ActionCreator): Promise => { + dispatch(webhooksActions.getWebhooks(query)); + + // We remove all keys with null values from the query + const filteredQuery = { ...query }; + for (const key of Object.keys(query)) { + if ((filteredQuery as Indexable)[key] === null) { + delete (filteredQuery as Indexable)[key]; + } + } + + let result = null; + try { + result = await cvat.webhooks.get(filteredQuery); + } catch (error) { + dispatch(webhooksActions.getWebhooksFailed(error)); + return; + } + + const array: Array = Array.from(result); + + dispatch(webhooksActions.getWebhooksSuccess(array, result.count)); + } +); + +export function createWebhookAsync(webhookData: Store): ThunkAction { + return async function (dispatch) { + const webhook = new cvat.classes.Webhook(webhookData); + dispatch(webhooksActions.createWebhook()); + + try { + const createdWebhook = await webhook.save(); + dispatch(webhooksActions.createWebhookSuccess(createdWebhook)); + } catch (error) { + dispatch(webhooksActions.createWebhookFailed(error)); + throw error; + } + }; +} + +export function updateWebhookAsync(webhook: Webhook): ThunkAction { + return async function (dispatch) { + dispatch(webhooksActions.updateWebhook()); + + try { + const updatedWebhook = await webhook.save(); + dispatch(webhooksActions.updateWebhookSuccess(updatedWebhook)); + } catch (error) { + dispatch(webhooksActions.updateWebhookFailed(error)); + throw error; + } + }; +} + +export function deleteWebhookAsync(webhook: Webhook): ThunkAction { + return async function (dispatch) { + try { + await webhook.delete(); + dispatch(webhooksActions.deleteWebhookSuccess()); + } catch (error) { + dispatch(webhooksActions.deleteWebhookFailed(webhook.id, error)); + throw error; + } + }; +} + +export type WebhooksActions = ActionUnion; diff --git a/cvat-ui/src/assets/back-arrow-icon.svg b/cvat-ui/src/assets/back-arrow-icon.svg new file mode 100644 index 000000000000..203318e40eea --- /dev/null +++ b/cvat-ui/src/assets/back-arrow-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/cvat-ui/src/assets/background-icon.svg b/cvat-ui/src/assets/background-icon.svg index a074bf87498f..cec1dc77c682 100644 --- a/cvat-ui/src/assets/background-icon.svg +++ b/cvat-ui/src/assets/background-icon.svg @@ -1,5 +1,5 @@ diff --git a/cvat-ui/src/assets/brush-icon.svg b/cvat-ui/src/assets/brush-icon.svg new file mode 100644 index 000000000000..b57b27e35574 --- /dev/null +++ b/cvat-ui/src/assets/brush-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/cvat-ui/src/assets/check-icon.svg b/cvat-ui/src/assets/check-icon.svg new file mode 100644 index 000000000000..f099a0f237bc --- /dev/null +++ b/cvat-ui/src/assets/check-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/cvat-ui/src/assets/clear-icon.svg b/cvat-ui/src/assets/clear-icon.svg new file mode 100644 index 000000000000..bae640d7991e --- /dev/null +++ b/cvat-ui/src/assets/clear-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/cvat-ui/src/assets/cvat-minimalistic-logo.svg b/cvat-ui/src/assets/cvat-minimalistic-logo.svg new file mode 100644 index 000000000000..790c859def37 --- /dev/null +++ b/cvat-ui/src/assets/cvat-minimalistic-logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/cvat-ui/src/assets/eraser-icon.svg b/cvat-ui/src/assets/eraser-icon.svg new file mode 100644 index 000000000000..99175e72a000 --- /dev/null +++ b/cvat-ui/src/assets/eraser-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/cvat-ui/src/assets/foreground-icon.svg b/cvat-ui/src/assets/foreground-icon.svg index e8e12787f4f7..1057b1d704a1 100644 --- a/cvat-ui/src/assets/foreground-icon.svg +++ b/cvat-ui/src/assets/foreground-icon.svg @@ -1,5 +1,5 @@ diff --git a/cvat-ui/src/assets/index.d.ts b/cvat-ui/src/assets/index.d.ts new file mode 100644 index 000000000000..82255972f22d --- /dev/null +++ b/cvat-ui/src/assets/index.d.ts @@ -0,0 +1,5 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +declare module '*.svg'; diff --git a/cvat-ui/src/assets/move-icon.svg b/cvat-ui/src/assets/move-icon.svg index e6c169b41e8c..ff6d45f7246b 100644 --- a/cvat-ui/src/assets/move-icon.svg +++ b/cvat-ui/src/assets/move-icon.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + diff --git a/cvat-ui/src/assets/multi-plus-icon.svg b/cvat-ui/src/assets/multi-plus-icon.svg new file mode 100644 index 000000000000..ec93171eff54 --- /dev/null +++ b/cvat-ui/src/assets/multi-plus-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/cvat-ui/src/assets/opencv.svg b/cvat-ui/src/assets/opencv.svg index 7608729daf81..b185a2eb6222 100644 --- a/cvat-ui/src/assets/opencv.svg +++ b/cvat-ui/src/assets/opencv.svg @@ -4,7 +4,7 @@ The file has been modified --> - + diff --git a/cvat-ui/src/assets/openvino.svg b/cvat-ui/src/assets/openvino.svg deleted file mode 100644 index c68b4fc33f9a..000000000000 --- a/cvat-ui/src/assets/openvino.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/cvat-ui/src/assets/plus-icon.svg b/cvat-ui/src/assets/plus-icon.svg new file mode 100644 index 000000000000..353102d3b819 --- /dev/null +++ b/cvat-ui/src/assets/plus-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/cvat-ui/src/assets/point-icon.svg b/cvat-ui/src/assets/point-icon.svg index de8b5b353968..681ff2603a58 100644 --- a/cvat-ui/src/assets/point-icon.svg +++ b/cvat-ui/src/assets/point-icon.svg @@ -1 +1,8 @@ - \ No newline at end of file + + + + + + + + diff --git a/cvat-ui/src/assets/polygon-minus.svg b/cvat-ui/src/assets/polygon-minus.svg new file mode 100644 index 000000000000..e8edd1f6a51b --- /dev/null +++ b/cvat-ui/src/assets/polygon-minus.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/cvat-ui/src/assets/polygon-plus.svg b/cvat-ui/src/assets/polygon-plus.svg new file mode 100644 index 000000000000..dd2f59b161ba --- /dev/null +++ b/cvat-ui/src/assets/polygon-plus.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/cvat-ui/src/assets/restore-icon.svg b/cvat-ui/src/assets/restore-icon.svg new file mode 100644 index 000000000000..90bdf9f46678 --- /dev/null +++ b/cvat-ui/src/assets/restore-icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/cvat-ui/src/assets/rotate-icon (copy).svg b/cvat-ui/src/assets/rotate-icon (copy).svg new file mode 100644 index 000000000000..ce7fd8802c17 --- /dev/null +++ b/cvat-ui/src/assets/rotate-icon (copy).svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/cvat-ui/src/assets/show-password.svg b/cvat-ui/src/assets/show-password.svg new file mode 100644 index 000000000000..5830b25e3829 --- /dev/null +++ b/cvat-ui/src/assets/show-password.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/cvat-ui/src/assets/signing-background.svg b/cvat-ui/src/assets/signing-background.svg new file mode 100644 index 000000000000..773fde503561 --- /dev/null +++ b/cvat-ui/src/assets/signing-background.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/cvat-ui/src/assets/skeleton-icon.svg b/cvat-ui/src/assets/skeleton-icon.svg new file mode 100644 index 000000000000..fd1a64087b13 --- /dev/null +++ b/cvat-ui/src/assets/skeleton-icon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/cvat-ui/src/assets/social-github-logo.svg b/cvat-ui/src/assets/social-github-logo.svg new file mode 100644 index 000000000000..07199e72363e --- /dev/null +++ b/cvat-ui/src/assets/social-github-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/cvat-ui/src/assets/social-google-logo.svg b/cvat-ui/src/assets/social-google-logo.svg new file mode 100644 index 000000000000..c2a4eaa634cc --- /dev/null +++ b/cvat-ui/src/assets/social-google-logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/cvat-ui/src/base.scss b/cvat-ui/src/base.scss index 23092de0ab5f..7ec4e42e414e 100644 --- a/cvat-ui/src/base.scss +++ b/cvat-ui/src/base.scss @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/actions-menu/actions-menu.tsx b/cvat-ui/src/components/actions-menu/actions-menu.tsx index 13763f579f36..272393e90d17 100644 --- a/cvat-ui/src/components/actions-menu/actions-menu.tsx +++ b/cvat-ui/src/components/actions-menu/actions-menu.tsx @@ -1,4 +1,5 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -9,9 +10,7 @@ import Modal from 'antd/lib/modal'; import { LoadingOutlined } from '@ant-design/icons'; // eslint-disable-next-line import/no-extraneous-dependencies import { MenuInfo } from 'rc-menu/lib/interface'; - -import LoadSubmenu from './load-submenu'; -import { DimensionType } from '../../reducers/interfaces'; +import { DimensionType } from '../../reducers'; interface Props { taskID: number; @@ -19,12 +18,10 @@ interface Props { bugTracker: string; loaders: any[]; dumpers: any[]; - loadActivity: string | null; inferenceIsActive: boolean; taskDimension: DimensionType; + backupIsActive: boolean; onClickMenu: (params: MenuInfo) => void; - onUploadAnnotations: (format: string, file: File) => void; - exportIsActive: boolean; } export enum Actions { @@ -34,7 +31,7 @@ export enum Actions { RUN_AUTO_ANNOTATION = 'run_auto_annotation', MOVE_TASK_TO_PROJECT = 'move_task_to_project', OPEN_BUG_TRACKER = 'open_bug_tracker', - EXPORT_TASK = 'export_task', + BACKUP_TASK = 'backup_task', } function ActionsMenuComponent(props: Props): JSX.Element { @@ -42,12 +39,8 @@ function ActionsMenuComponent(props: Props): JSX.Element { taskID, bugTracker, inferenceIsActive, - loaders, + backupIsActive, onClickMenu, - onUploadAnnotations, - loadActivity, - taskDimension, - exportIsActive, } = props; const onClickMenuWrapper = useCallback( @@ -79,38 +72,16 @@ function ActionsMenuComponent(props: Props): JSX.Element { return ( - {LoadSubmenu({ - loaders, - loadActivity, - onFileUpload: (format: string, file: File): void => { - if (file) { - Modal.confirm({ - title: 'Current annotation will be lost', - content: 'You are going to upload new annotations to this task. Continue?', - className: 'cvat-modal-content-load-task-annotation', - onOk: () => { - onUploadAnnotations(format, file); - }, - okButtonProps: { - type: 'primary', - danger: true, - }, - okText: 'Update', - }); - } - }, - menuKey: Actions.LOAD_TASK_ANNO, - taskDimension, - })} + Upload annotations Export task dataset {!!bugTracker && Open bug tracker} Automatic annotation } + key={Actions.BACKUP_TASK} + disabled={backupIsActive} + icon={backupIsActive && } > Backup Task diff --git a/cvat-ui/src/components/actions-menu/load-submenu.tsx b/cvat-ui/src/components/actions-menu/load-submenu.tsx deleted file mode 100644 index 40eef920b010..000000000000 --- a/cvat-ui/src/components/actions-menu/load-submenu.tsx +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2020-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import Menu from 'antd/lib/menu'; -import Upload from 'antd/lib/upload'; -import Button from 'antd/lib/button'; -import Text from 'antd/lib/typography/Text'; -import { UploadOutlined, LoadingOutlined } from '@ant-design/icons'; -import { DimensionType } from '../../reducers/interfaces'; - -interface Props { - menuKey: string; - loaders: any[]; - loadActivity: string | null; - onFileUpload(format: string, file: File): void; - taskDimension: DimensionType; -} - -export default function LoadSubmenu(props: Props): JSX.Element { - const { - menuKey, loaders, loadActivity, onFileUpload, taskDimension, - } = props; - - return ( - - {loaders - .sort((a: any, b: any) => a.name.localeCompare(b.name)) - .filter((loader: any): boolean => loader.dimension === taskDimension) - .map( - (loader: any): JSX.Element => { - const accept = loader.format - .split(',') - .map((x: string) => `.${x.trimStart()}`) - .join(', '); // add '.' to each extension in a list - const pending = loadActivity === loader.name; - const disabled = !loader.enabled || !!loadActivity; - const format = loader.name; - return ( - - { - onFileUpload(format, file); - return false; - }} - > - - - - ); - }, - )} - - ); -} diff --git a/cvat-ui/src/components/actions-menu/styles.scss b/cvat-ui/src/components/actions-menu/styles.scss index 0d61b4531259..1d05bb42b009 100644 --- a/cvat-ui/src/components/actions-menu/styles.scss +++ b/cvat-ui/src/components/actions-menu/styles.scss @@ -1,4 +1,5 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -17,7 +18,6 @@ } } -.cvat-menu-load-submenu-item, .cvat-menu-dump-submenu-item, .cvat-menu-export-submenu-item { > span[role='img'] { @@ -29,28 +29,12 @@ } } -.ant-menu-item.cvat-menu-load-submenu-item { - margin: 0; - padding: 0; - - > span > .ant-upload { - width: 100%; - height: 100%; - - > span > button { - width: 100%; - height: 100%; - text-align: left; - } - } -} - .cvat-menu-icon { font-size: 16px; margin-left: 8px; align-self: center; } -#cvat-export-task-loading { +#cvat-backup-task-loading { margin-left: 10; } diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index cf89f225e938..7cb79b706070 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -17,7 +17,7 @@ import TagAnnotationWorkspace from 'components/annotation-page/tag-annotation-wo import FiltersModalComponent from 'components/annotation-page/top-bar/filters-modal'; import StatisticsModalComponent from 'components/annotation-page/top-bar/statistics-modal'; import AnnotationTopBarContainer from 'containers/annotation-page/top-bar/top-bar'; -import { Workspace } from 'reducers/interfaces'; +import { Workspace } from 'reducers'; import { usePrevious } from 'utils/hooks'; import './styles.scss'; import Button from 'antd/lib/button'; diff --git a/cvat-ui/src/components/annotation-page/appearance-block.tsx b/cvat-ui/src/components/annotation-page/appearance-block.tsx index 4ef79e5870d1..3e0edf335941 100644 --- a/cvat-ui/src/components/annotation-page/appearance-block.tsx +++ b/cvat-ui/src/components/annotation-page/appearance-block.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -14,7 +14,7 @@ import Button from 'antd/lib/button'; import ColorPicker from 'components/annotation-page/standard-workspace/objects-side-bar/color-picker'; import { ColorizeIcon } from 'icons'; -import { ColorBy, CombinedState, DimensionType } from 'reducers/interfaces'; +import { ColorBy, CombinedState, DimensionType } from 'reducers'; import { collapseAppearance as collapseAppearanceAction } from 'actions/annotation-actions'; import { changeShapesColorBy as changeShapesColorByAction, diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index e9b12a564048..e6c4244b335c 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -1,14 +1,16 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; -import { SelectValue } from 'antd/lib/select'; import Layout, { SiderProps } from 'antd/lib/layout'; import Text from 'antd/lib/typography/Text'; +import { filterApplicableLabels } from 'utils/filter-applicable-labels'; +import { Label } from 'cvat-core-wrapper'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { LogType } from 'cvat-logger'; @@ -23,7 +25,7 @@ import { ThunkDispatch } from 'utils/redux'; import AppearanceBlock from 'components/annotation-page/appearance-block'; import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons'; import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; -import { CombinedState, ObjectType } from 'reducers/interfaces'; +import { CombinedState, ObjectType } from 'reducers'; import AttributeEditor from './attribute-editor'; import AttributeSwitcher from './attribute-switcher'; import ObjectBasicsEditor from './object-basics-edtior'; @@ -84,7 +86,7 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { return { activateObject(clientID: number, attrID: number): void { - dispatch(activateObjectAction(clientID, attrID)); + dispatch(activateObjectAction(clientID, null, attrID)); }, updateAnnotations(states): void { dispatch(updateAnnotationsAsync(states)); @@ -147,6 +149,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. activatedStateID === null || activatedIndex === -1 ? null : filteredStates[activatedIndex]; const activeAttribute = activeObjectState ? labelAttrMap[activeObjectState.label.id] : null; + const applicableLabels = activeObjectState ? filterApplicableLabels(activeObjectState, labels) : []; if (canvasIsReady) { if (activeObjectState) { @@ -308,12 +311,10 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. nextObject={nextObject} /> { - const labelName = value as string; - const [newLabel] = labels.filter((_label): boolean => _label.name === labelName); - activeObjectState.label = newLabel; + currentLabel={activeObjectState.label.id} + labels={applicableLabels} + changeLabel={(value: Label): void => { + activeObjectState.label = value; updateAnnotations([activeObjectState]); }} /> diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx index aba08979230f..4ce38057394a 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx index 59a17593da00..d15363aaaa79 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx index 48c3f13e24a5..f82e9d3d6acf 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx @@ -1,14 +1,16 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React from 'react'; -import Select, { SelectValue } from 'antd/lib/select'; +import { Label } from 'cvat-core-wrapper'; +import LabelSelector from 'components/label-selector/label-selector'; interface Props { - currentLabel: string; - labels: any[]; - changeLabel(value: SelectValue): void; + currentLabel: number; + labels: Label[]; + changeLabel(value: Label): void; } function ObjectBasicsEditor(props: Props): JSX.Element { @@ -16,15 +18,12 @@ function ObjectBasicsEditor(props: Props): JSX.Element { return (
    - +
    ); } diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx index b64d2c5fc41d..8bdaf0a010df 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx index 5cd8a079d0c5..7a50b02d7665 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss index e9daa708d769..d1e905a96024 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss @@ -1,4 +1,5 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -10,7 +11,7 @@ .attribute-annotation-sidebar:not(.ant-layout-sider-collapsed) { background: $background-color-2; - padding: 5px; + padding: $grid-unit-size; > .ant-layout-sider-children { display: flex; @@ -24,7 +25,7 @@ align-items: center; justify-content: space-between; font-size: 18px; - margin-top: 10px; + margin-top: $grid-unit-size; > span { max-width: 60%; @@ -40,13 +41,13 @@ .cvat-attribute-annotation-sidebar-basics-editor { display: flex; align-items: center; - justify-content: space-between; + justify-content: center; font-size: 18px; - margin: 10px 0; + margin: $grid-unit-size 0; } .attribute-annotations-sidebar-not-found-wrapper { - margin-top: 20px; + margin-top: $grid-unit-size * 3; text-align: center; flex-grow: 10; } @@ -56,7 +57,7 @@ } .attribute-annotation-sidebar-attr-list-wrapper { - margin: 10px 0 10px 10px; + margin: $grid-unit-size 0 $grid-unit-size $grid-unit-size; } .attribute-annotation-sidebar-attr-elem-wrapper { @@ -75,5 +76,5 @@ } .cvat-sidebar-collapse-button-spacer { - height: 32px; + height: $grid-unit-size * 4; } diff --git a/cvat-ui/src/components/annotation-page/canvas/brush-toolbox-styles.scss b/cvat-ui/src/components/annotation-page/canvas/brush-toolbox-styles.scss new file mode 100644 index 000000000000..31060b9afd2e --- /dev/null +++ b/cvat-ui/src/components/annotation-page/canvas/brush-toolbox-styles.scss @@ -0,0 +1,57 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +@import '../../../base.scss'; + +.cvat-brush-tools-toolbox { + position: absolute; + margin: $grid-unit-size; + padding: 0 $grid-unit-size; + border-radius: 4px; + background: $background-color-2; + display: flex; + align-items: center; + z-index: 100; + box-shadow: $box-shadow-base; + + > hr { + width: 1px; + height: $grid-unit-size * 4; + background: $border-color-1; + } + + > * { + margin-right: $grid-unit-size; + } + + > button { + font-size: 20px; + + > span.anticon { + font-size: inherit; + } + } + + .cvat-brush-tools-brush, + .cvat-brush-tools-eraser { + svg { + width: 24px; + height: 25px; + } + } + + .cvat-brush-tools-draggable-area { + display: flex; + font-size: 20px; + + svg { + width: 24px; + height: 25px; + } + } + + .cvat-brush-tools-active-tool { + background: $header-color; + } +} diff --git a/cvat-ui/src/components/annotation-page/canvas/brush-tools.tsx b/cvat-ui/src/components/annotation-page/canvas/brush-tools.tsx new file mode 100644 index 000000000000..79c947380a9a --- /dev/null +++ b/cvat-ui/src/components/annotation-page/canvas/brush-tools.tsx @@ -0,0 +1,281 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import './brush-toolbox-styles.scss'; + +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import Button from 'antd/lib/button'; +import Icon, { VerticalAlignBottomOutlined } from '@ant-design/icons'; +import InputNumber from 'antd/lib/input-number'; +import Select from 'antd/lib/select'; + +import { filterApplicableForType } from 'utils/filter-applicable-labels'; +import { getCore, Label, LabelType } from 'cvat-core-wrapper'; +import { Canvas, CanvasMode } from 'cvat-canvas-wrapper'; +import { + BrushIcon, EraserIcon, PolygonMinusIcon, PolygonPlusIcon, + PlusIcon, CheckIcon, MoveIcon, +} from 'icons'; +import CVATTooltip from 'components/common/cvat-tooltip'; +import { CombinedState, ObjectType, ShapeType } from 'reducers'; +import LabelSelector from 'components/label-selector/label-selector'; +import { rememberObject, updateCanvasBrushTools } from 'actions/annotation-actions'; +import useDraggable from './draggable-hoc'; + +const DraggableArea = ( +
    + +
    +); + +const MIN_BRUSH_SIZE = 1; +function BrushTools(): React.ReactPortal | null { + const dispatch = useDispatch(); + const defaultLabelID = useSelector((state: CombinedState) => state.annotation.drawing.activeLabelID); + const config = useSelector((state: CombinedState) => state.annotation.canvas.brushTools); + const canvasInstance = useSelector((state: CombinedState) => state.annotation.canvas.instance); + const labels = useSelector((state: CombinedState) => state.annotation.job.labels); + const { visible } = config; + + const [editableState, setEditableState] = useState(null); + const [currentTool, setCurrentTool] = useState<'brush' | 'eraser' | 'polygon-plus' | 'polygon-minus'>('brush'); + const [brushForm, setBrushForm] = useState<'circle' | 'square'>('circle'); + const [[top, left], setTopLeft] = useState([0, 0]); + const [brushSize, setBrushSize] = useState(10); + const [applicableLabels, setApplicableLabels] = useState([]); + + const [removeUnderlyingPixels, setRemoveUnderlyingPixels] = useState(false); + const dragBar = useDraggable( + (): number[] => { + const [element] = window.document.getElementsByClassName('cvat-brush-tools-toolbox'); + if (element) { + const { offsetTop, offsetLeft } = element as HTMLDivElement; + return [offsetTop, offsetLeft]; + } + + return [0, 0]; + }, + (newTop, newLeft) => setTopLeft([newTop, newLeft]), + DraggableArea, + ); + + useEffect(() => { + const label = labels.find((_label: any) => _label.id === defaultLabelID); + getCore().config.removeUnderlyingMaskPixels = removeUnderlyingPixels; + if (visible && label && canvasInstance instanceof Canvas) { + const onUpdateConfiguration = ({ brushTool }: any): void => { + if (brushTool?.size) { + setBrushSize(Math.max(MIN_BRUSH_SIZE, brushTool.size)); + } + }; + + if (canvasInstance.mode() === CanvasMode.DRAW) { + canvasInstance.draw({ + enabled: true, + shapeType: ShapeType.MASK, + crosshair: false, + brushTool: { + type: currentTool, + size: brushSize, + form: brushForm, + color: label.color, + }, + onUpdateConfiguration, + }); + } else if (canvasInstance.mode() === CanvasMode.EDIT && editableState) { + canvasInstance.edit({ + enabled: true, + state: editableState, + brushTool: { + type: currentTool, + size: brushSize, + form: brushForm, + color: label.color, + }, + onUpdateConfiguration, + }); + } + } + }, [currentTool, brushSize, brushForm, visible, defaultLabelID, editableState]); + + useEffect(() => { + setApplicableLabels(filterApplicableForType(LabelType.MASK, labels)); + }, [labels]); + + useEffect(() => { + const canvasContainer = window.document.getElementsByClassName('cvat-canvas-container')[0]; + if (canvasContainer) { + const { offsetTop, offsetLeft } = canvasContainer.parentElement as HTMLElement; + setTopLeft([offsetTop, offsetLeft]); + } + }, []); + + useEffect(() => { + const hideToolset = (): void => { + if (visible) { + dispatch(updateCanvasBrushTools({ visible: false })); + } + }; + + const showToolset = (e: Event): void => { + const evt = e as CustomEvent; + if (evt.detail?.state?.shapeType === ShapeType.MASK || + (evt.detail?.drawData?.shapeType === ShapeType.MASK && !evt.detail?.drawData?.initialState)) { + dispatch(updateCanvasBrushTools({ visible: true })); + } + }; + + const updateEditableState = (e: Event): void => { + const evt = e as CustomEvent; + if (evt.type === 'canvas.editstart' && evt.detail.state) { + setEditableState(evt.detail.state); + } else if (editableState) { + setEditableState(null); + } + }; + + if (canvasInstance instanceof Canvas) { + canvasInstance.html().addEventListener('canvas.drawn', hideToolset); + canvasInstance.html().addEventListener('canvas.canceled', hideToolset); + canvasInstance.html().addEventListener('canvas.canceled', updateEditableState); + canvasInstance.html().addEventListener('canvas.drawstart', showToolset); + canvasInstance.html().addEventListener('canvas.editstart', showToolset); + canvasInstance.html().addEventListener('canvas.editstart', updateEditableState); + canvasInstance.html().addEventListener('canvas.editdone', updateEditableState); + } + + return () => { + if (canvasInstance instanceof Canvas) { + canvasInstance.html().removeEventListener('canvas.drawn', hideToolset); + canvasInstance.html().removeEventListener('canvas.canceled', hideToolset); + canvasInstance.html().removeEventListener('canvas.canceled', updateEditableState); + canvasInstance.html().removeEventListener('canvas.drawstart', showToolset); + canvasInstance.html().removeEventListener('canvas.editstart', showToolset); + canvasInstance.html().removeEventListener('canvas.editstart', updateEditableState); + canvasInstance.html().removeEventListener('canvas.editdone', updateEditableState); + } + }; + }, [visible, editableState]); + + if (!labels.length) { + return null; + } + + return ReactDOM.createPortal(( +
    +
    + ), window.document.body); +} + +export default React.memo(BrushTools); diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-context-menu.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-context-menu.tsx index 9cf44456f9eb..fae0b6144109 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-context-menu.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-context-menu.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -8,22 +9,24 @@ import Menu from 'antd/lib/menu'; // eslint-disable-next-line import/no-extraneous-dependencies import { MenuInfo } from 'rc-menu/lib/interface'; +import ObjectItemElementComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item-element'; import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item'; -import { Workspace } from 'reducers/interfaces'; +import { ShapeType, Workspace } from 'reducers'; import { rotatePoint } from 'utils/math'; import consts from 'consts'; interface Props { readonly: boolean; workspace: Workspace; + contextMenuParentID: number | null; contextMenuClientID: number | null; objectStates: any[]; visible: boolean; left: number; top: number; + latestComments: string[]; onStartIssue(position: number[]): void; openIssue(position: number[], message: string): void; - latestComments: string[]; } interface ReviewContextMenuProps { @@ -79,6 +82,7 @@ function ReviewContextMenu({ export default function CanvasContextMenu(props: Props): JSX.Element | null { const { contextMenuClientID, + contextMenuParentID, objectStates, visible, left, @@ -102,49 +106,49 @@ export default function CanvasContextMenu(props: Props): JSX.Element | null { left={left} latestComments={latestComments} onClick={(param: MenuInfo) => { - const [state] = objectStates.filter( - (_state: any): boolean => _state.clientID === contextMenuClientID, - ); - if (param.key === ReviewContextMenuKeys.OPEN_ISSUE) { - if (state) { - let { points } = state; - if (['ellipse', 'rectangle'].includes(state.shapeType)) { - const [cx, cy] = state.shapeType === 'ellipse' ? state.points : [ - (state.points[0] + state.points[2]) / 2, - (state.points[1] + state.points[3]) / 2, - ]; - const [rx, ry] = [state.points[2] - cx, cy - state.points[3]]; - points = state.shapeType === 'ellipse' ? [ - state.points[0] - rx, - state.points[1] - ry, - state.points[0] + rx, - state.points[1] + ry, - ] : state.points; + const state = objectStates.find((_state: any): boolean => _state.clientID === contextMenuClientID); + if (state) { + let { points } = state; + if ([ShapeType.ELLIPSE, ShapeType.RECTANGLE].includes(state.shapeType)) { + const [cx, cy] = state.shapeType === 'ellipse' ? state.points : [ + (state.points[0] + state.points[2]) / 2, + (state.points[1] + state.points[3]) / 2, + ]; + const [rx, ry] = [state.points[2] - cx, cy - state.points[3]]; + points = state.shapeType === 'ellipse' ? [ + state.points[0] - rx, + state.points[1] - ry, + state.points[0] + rx, + state.points[1] + ry, + ] : state.points; - points = [ - [points[0], points[1]], - [points[2], points[1]], - [points[2], points[3]], - [points[0], points[3]], - ].map(([x, y]: number[]) => rotatePoint(x, y, state.rotation, cx, cy)).flat(); - } + points = [ + [points[0], points[1]], + [points[2], points[1]], + [points[2], points[3]], + [points[0], points[3]], + ].map(([x, y]: number[]) => rotatePoint(x, y, state.rotation, cx, cy)).flat(); + } else if (state.shapeType === ShapeType.MASK) { + points = state.points.slice(-4); + points = [ + points[0], points[1], + points[2], points[1], + points[2], points[3], + points[0], points[3], + ]; + } + if (param.key === ReviewContextMenuKeys.OPEN_ISSUE) { onStartIssue(points); - } - } else if (param.key === ReviewContextMenuKeys.QUICK_ISSUE_POSITION) { - if (state) { - openIssue(state.points, consts.QUICK_ISSUE_INCORRECT_POSITION_TEXT); - } - } else if (param.key === ReviewContextMenuKeys.QUICK_ISSUE_ATTRIBUTE) { - if (state) { - openIssue(state.points, consts.QUICK_ISSUE_INCORRECT_ATTRIBUTE_TEXT); - } - } else if ( - param.keyPath.length === 2 && - param.keyPath[1] === ReviewContextMenuKeys.QUICK_ISSUE_FROM_LATEST - ) { - if (state) { - openIssue(state.points, latestComments[+param.keyPath[0]]); + } else if (param.key === ReviewContextMenuKeys.QUICK_ISSUE_POSITION) { + openIssue(points, consts.QUICK_ISSUE_INCORRECT_POSITION_TEXT); + } else if (param.key === ReviewContextMenuKeys.QUICK_ISSUE_ATTRIBUTE) { + openIssue(points, consts.QUICK_ISSUE_INCORRECT_ATTRIBUTE_TEXT); + } else if ( + param.keyPath.length === 2 && + param.keyPath[1] === ReviewContextMenuKeys.QUICK_ISSUE_FROM_LATEST + ) { + openIssue(points, latestComments[+param.keyPath[0]]); } } }} @@ -153,6 +157,20 @@ export default function CanvasContextMenu(props: Props): JSX.Element | null { ); } + if (Number.isInteger(contextMenuParentID)) { + return ReactDOM.createPortal( +
    + +
    , + window.document.body, + ); + } + return ReactDOM.createPortal(
    , window.document.body, diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-point-context-menu.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-point-context-menu.tsx index 490359584143..0f2eebf84556 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-point-context-menu.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-point-context-menu.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,7 +8,7 @@ import Button from 'antd/lib/button'; import { DeleteOutlined, EnvironmentOutlined } from '@ant-design/icons'; import { connect } from 'react-redux'; -import { CombinedState, ContextMenuType } from 'reducers/interfaces'; +import { CombinedState, ContextMenuType } from 'reducers'; import { updateAnnotationsAsync, updateCanvasContextMenu } from 'actions/annotation-actions'; import CVATTooltip from 'components/common/cvat-tooltip'; diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx index 6b1e8fbae13d..58eda99dbfc9 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -11,14 +12,16 @@ import { PlusCircleOutlined, UpOutlined } from '@ant-design/icons'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import { ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType, -} from 'reducers/interfaces'; +} from 'reducers'; import { LogType } from 'cvat-logger'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; import consts from 'consts'; import CVATTooltip from 'components/common/cvat-tooltip'; +import FrameTags from 'components/annotation-page/tag-annotation-workspace/frame-tags'; import ImageSetupsContent from './image-setups-content'; +import BrushTools from './brush-tools'; import ContextImage from '../standard-workspace/context-image/context-image'; const cvat = getCore(); @@ -30,6 +33,7 @@ interface Props { canvasInstance: Canvas | Canvas3d | null; jobInstance: any; activatedStateID: number | null; + activatedElementID: number | null; activatedAttributeID: number | null; annotations: any[]; frameData: any; @@ -60,6 +64,7 @@ interface Props { aamZoomMargin: number; showObjectsTextAlways: boolean; textFontSize: number; + controlPointsSize: number; textPosition: 'auto' | 'center'; textContent: string; showAllInterpolationTracks: boolean; @@ -69,6 +74,7 @@ interface Props { keyMap: KeyMap; canvasBackgroundColor: string; switchableAutomaticBordering: boolean; + showTagsOnFrame: boolean; onSetupCanvas: () => void; onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; @@ -83,7 +89,7 @@ interface Props { onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void; onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; onSplitAnnotations(sessionInstance: any, frame: number, state: any): void; - onActivateObject(activatedStateID: number | null): void; + onActivateObject(activatedStateID: number | null, activatedElementID?: number | null): void; onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void; onAddZLayer(): void; onSwitchZLayer(cur: number): void; @@ -108,10 +114,15 @@ export default class CanvasWrapperComponent extends React.PureComponent { workspace, showProjections, selectedOpacity, + opacity, smoothImage, textFontSize, + controlPointsSize, textPosition, textContent, + colorBy, + outlined, + outlineColor, } = this.props; const { canvasInstance } = this.props as { canvasInstance: Canvas }; @@ -121,14 +132,18 @@ export default class CanvasWrapperComponent extends React.PureComponent { wrapper.appendChild(canvasInstance.html()); canvasInstance.configure({ - smoothImage, - autoborders: automaticBordering, + forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE, undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, displayAllText: showObjectsTextAlways, - forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE, - intelligentPolygonCrop, + autoborders: automaticBordering, showProjections, - creationOpacity: selectedOpacity, + intelligentPolygonCrop, + selectedShapeOpacity: selectedOpacity, + controlPointsSize, + shapeOpacity: opacity, + smoothImage, + colorBy, + outlinedBorders: outlined ? outlineColor || 'black' : false, textFontSize, textPosition, textContent, @@ -141,7 +156,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { public componentDidUpdate(prevProps: Props): void { const { opacity, - colorBy, selectedOpacity, outlined, outlineColor, @@ -165,6 +179,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { frameFetching, showObjectsTextAlways, textFontSize, + controlPointsSize, textPosition, textContent, showAllInterpolationTracks, @@ -172,19 +187,26 @@ export default class CanvasWrapperComponent extends React.PureComponent { intelligentPolygonCrop, showProjections, canvasBackgroundColor, + colorBy, onFetchAnnotation, } = this.props; const { canvasInstance } = this.props as { canvasInstance: Canvas }; + if ( prevProps.showObjectsTextAlways !== showObjectsTextAlways || prevProps.automaticBordering !== automaticBordering || prevProps.showProjections !== showProjections || prevProps.intelligentPolygonCrop !== intelligentPolygonCrop || + prevProps.opacity !== opacity || prevProps.selectedOpacity !== selectedOpacity || prevProps.smoothImage !== smoothImage || prevProps.textFontSize !== textFontSize || + prevProps.controlPointsSize !== controlPointsSize || prevProps.textPosition !== textPosition || - prevProps.textContent !== textContent + prevProps.textContent !== textContent || + prevProps.colorBy !== colorBy || + prevProps.outlineColor !== outlineColor || + prevProps.outlined !== outlined ) { canvasInstance.configure({ undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, @@ -192,9 +214,13 @@ export default class CanvasWrapperComponent extends React.PureComponent { autoborders: automaticBordering, showProjections, intelligentPolygonCrop, - creationOpacity: selectedOpacity, + selectedShapeOpacity: selectedOpacity, + shapeOpacity: opacity, smoothImage, + colorBy, + outlinedBorders: outlined ? outlineColor || 'black' : false, textFontSize, + controlPointsSize, textPosition, textContent, }); @@ -219,10 +245,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { if (prevProps.activatedStateID !== null && prevProps.activatedStateID !== activatedStateID) { canvasInstance.activate(null); - const el = window.document.getElementById(`cvat_canvas_shape_${prevProps.activatedStateID}`); - if (el) { - (el as any).instance.fill({ opacity }); - } } if (gridSize !== prevProps.gridSize) { @@ -246,11 +268,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { contrastLevel !== prevProps.contrastLevel || saturationLevel !== prevProps.saturationLevel ) { - const backgroundElement = window.document.getElementById('cvat_canvas_background'); - if (backgroundElement) { - const filter = `brightness(${brightnessLevel}) contrast(${contrastLevel}) saturate(${saturationLevel})`; - backgroundElement.style.filter = filter; - } + canvasInstance.configure({ + CSSImageFilter: + `brightness(${brightnessLevel}) contrast(${contrastLevel}) saturate(${saturationLevel})`, + }); } if ( @@ -271,16 +292,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { ); } - if ( - prevProps.opacity !== opacity || - prevProps.outlined !== outlined || - prevProps.outlineColor !== outlineColor || - prevProps.selectedOpacity !== selectedOpacity || - prevProps.colorBy !== colorBy - ) { - this.updateShapesView(); - } - if (prevProps.showBitmap !== showBitmap) { canvasInstance.bitmap(showBitmap); } @@ -329,7 +340,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().removeEventListener('mousedown', this.onCanvasMouseDown); canvasInstance.html().removeEventListener('click', this.onCanvasClicked); - canvasInstance.html().removeEventListener('contextmenu', this.onCanvasContextMenu); canvasInstance.html().removeEventListener('canvas.editstart', this.onCanvasEditStart); canvasInstance.html().removeEventListener('canvas.edited', this.onCanvasEditDone); canvasInstance.html().removeEventListener('canvas.dragstart', this.onCanvasDragStart); @@ -354,7 +364,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().removeEventListener('canvas.regionselected', this.onCanvasPositionSelected); canvasInstance.html().removeEventListener('canvas.splitted', this.onCanvasTrackSplitted); - canvasInstance.html().removeEventListener('canvas.contextmenu', this.onCanvasPointContextMenu); canvasInstance.html().removeEventListener('canvas.error', this.onCanvasErrorOccurrence); window.removeEventListener('resize', this.fitCanvas); @@ -385,9 +394,22 @@ export default class CanvasWrapperComponent extends React.PureComponent { state.objectType = state.objectType || activeObjectType; state.label = state.label || jobInstance.labels.filter((label: any) => label.id === activeLabelID)[0]; - state.occluded = state.occluded || false; state.frame = frame; state.rotation = state.rotation || 0; + state.occluded = state.occluded || false; + state.outside = state.outside || false; + if (state.shapeType === ShapeType.SKELETON && Array.isArray(state.elements)) { + state.elements.forEach((element: Record) => { + element.objectType = state.objectType; + element.label = element.label || state.label.structure + .sublabels.find((label: any) => label.id === element.labelID); + element.frame = state.frame; + element.rotation = 0; + element.occluded = element.occluded || false; + element.outside = element.outside || false; + }); + } + const objectState = new cvat.classes.ObjectState(state); onCreateAnnotations(jobInstance, frame, [objectState]); }; @@ -454,22 +476,12 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasClicked = (): void => { - const { onUpdateContextMenu } = this.props; const { canvasInstance } = this.props as { canvasInstance: Canvas }; - onUpdateContextMenu(false, 0, 0, ContextMenuType.CANVAS_SHAPE); if (!canvasInstance.html().contains(document.activeElement) && document.activeElement instanceof HTMLElement) { document.activeElement.blur(); } }; - private onCanvasContextMenu = (e: MouseEvent): void => { - const { activatedStateID, onUpdateContextMenu } = this.props; - - if (e.target && !(e.target as HTMLElement).classList.contains('svg_select_points')) { - onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY, ContextMenuType.CANVAS_SHAPE); - } - }; - private onCanvasShapeDragged = (e: any): void => { const { jobInstance } = this.props; const { id } = e.detail; @@ -493,8 +505,14 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasShapeClicked = (e: any): void => { - const { clientID } = e.detail.state; - const sidebarItem = window.document.getElementById(`cvat-objects-sidebar-state-item-${clientID}`); + const { clientID, parentID } = e.detail.state; + let sidebarItem = null; + if (Number.isInteger(parentID)) { + sidebarItem = window.document.getElementById(`cvat-objects-sidebar-state-item-element-${clientID}`); + } else { + sidebarItem = window.document.getElementById(`cvat-objects-sidebar-state-item-${clientID}`); + } + if (sidebarItem) { sidebarItem.scrollIntoView(); } @@ -514,7 +532,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { private onCanvasCursorMoved = async (event: any): Promise => { const { - jobInstance, activatedStateID, workspace, onActivateObject, + jobInstance, activatedStateID, activatedElementID, workspace, onActivateObject, } = this.props; if (![Workspace.STANDARD, Workspace.REVIEW_WORKSPACE].includes(workspace)) { @@ -524,14 +542,15 @@ export default class CanvasWrapperComponent extends React.PureComponent { const result = await jobInstance.annotations.select(event.detail.states, event.detail.x, event.detail.y); if (result && result.state) { - if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { + if (['polyline', 'points'].includes(result.state.shapeType)) { if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { return; } } - if (activatedStateID !== result.state.clientID) { - onActivateObject(result.state.clientID); + const newActivatedElement = event.detail.activatedElementID || null; + if (activatedStateID !== result.state.clientID || activatedElementID !== newActivatedElement) { + onActivateObject(result.state.clientID, event.detail.activatedElementID || null); } } }; @@ -576,7 +595,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { private onCanvasSetup = (): void => { const { onSetupCanvas } = this.props; onSetupCanvas(); - this.updateShapesView(); this.activateOnCanvas(); }; @@ -592,7 +610,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { const result = await jobInstance.annotations.select(e.detail.states, e.detail.x, e.detail.y); if (result && result.state) { - if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { + if (['polyline', 'points'].includes(result.state.shapeType)) { if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { return; } @@ -606,7 +624,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { activatedStateID, onUpdateContextMenu, annotations } = this.props; const [state] = annotations.filter((el: any) => el.clientID === activatedStateID); - if (![ShapeType.CUBOID, ShapeType.RECTANGLE].includes(state.shapeType)) { + if (![ShapeType.CUBOID, ShapeType.RECTANGLE, ShapeType.ELLIPSE, ShapeType.SKELETON].includes(state.shapeType)) { onUpdateContextMenu( activatedStateID !== null, e.detail.mouseEvent.clientX, @@ -621,7 +639,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { activatedStateID, activatedAttributeID, - selectedOpacity, aamZoomMargin, workspace, annotations, @@ -640,40 +657,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { if (activatedState && activatedState.objectType !== ObjectType.TAG) { canvasInstance.activate(activatedStateID, activatedAttributeID); } - const el = window.document.getElementById(`cvat_canvas_shape_${activatedStateID}`); - if (el) { - ((el as any) as SVGElement).setAttribute('fill-opacity', `${selectedOpacity}`); - } - } - } - - private updateShapesView(): void { - const { - annotations, opacity, colorBy, outlined, outlineColor, - } = this.props; - - for (const state of annotations) { - let shapeColor = ''; - - if (colorBy === ColorBy.INSTANCE) { - shapeColor = state.color; - } else if (colorBy === ColorBy.GROUP) { - shapeColor = state.group.color; - } else if (colorBy === ColorBy.LABEL) { - shapeColor = state.label.color; - } - - // TODO: In this approach CVAT-UI know details of implementations CVAT-CANVAS (svg.js) - const shapeView = window.document.getElementById(`cvat_canvas_shape_${state.clientID}`); - if (shapeView) { - const handler = (shapeView as any).instance.remember('_selectHandler'); - if (handler && handler.nested) { - handler.nested.fill({ color: shapeColor }); - } - - (shapeView as any).instance.fill({ color: shapeColor, opacity }); - (shapeView as any).instance.stroke({ color: outlined ? outlineColor : shapeColor }); - } } } @@ -685,7 +668,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { if (frameData !== null && canvasInstance) { canvasInstance.setup( frameData, - annotations.filter((e) => e.objectType !== ObjectType.TAG), + frameData.deleted ? [] : annotations.filter((e) => e.objectType !== ObjectType.TAG), curZLayer, ); } @@ -720,13 +703,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { } canvasInstance.grid(gridSize, gridSize); - // Filters - const backgroundElement = window.document.getElementById('cvat_canvas_background'); - if (backgroundElement) { - const filter = `brightness(${brightnessLevel}) contrast(${contrastLevel}) saturate(${saturationLevel})`; - backgroundElement.style.filter = filter; - } - + canvasInstance.configure({ + CSSImageFilter: + `brightness(${brightnessLevel}) contrast(${contrastLevel}) saturate(${saturationLevel})`, + }); const canvasWrapperElement = window.document .getElementsByClassName('cvat-canvas-container') .item(0) as HTMLElement | null; @@ -747,7 +727,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('mousedown', this.onCanvasMouseDown); canvasInstance.html().addEventListener('click', this.onCanvasClicked); - canvasInstance.html().addEventListener('contextmenu', this.onCanvasContextMenu); canvasInstance.html().addEventListener('canvas.editstart', this.onCanvasEditStart); canvasInstance.html().addEventListener('canvas.edited', this.onCanvasEditDone); canvasInstance.html().addEventListener('canvas.dragstart', this.onCanvasDragStart); @@ -772,7 +751,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.html().addEventListener('canvas.regionselected', this.onCanvasPositionSelected); canvasInstance.html().addEventListener('canvas.splitted', this.onCanvasTrackSplitted); - canvasInstance.html().addEventListener('canvas.contextmenu', this.onCanvasPointContextMenu); canvasInstance.html().addEventListener('canvas.error', this.onCanvasErrorOccurrence); } @@ -784,6 +762,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { keyMap, switchableAutomaticBordering, automaticBordering, + showTagsOnFrame, onSwitchAutomaticBordering, onSwitchZLayer, onAddZLayer, @@ -826,6 +805,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { /> + }> @@ -846,6 +826,13 @@ export default class CanvasWrapperComponent extends React.PureComponent { + + {showTagsOnFrame ? ( +
    + +
    + ) : null} + ; ); } diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx index 037f730fb51a..783460e600c3 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper3D.tsx @@ -1,4 +1,5 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -12,7 +13,7 @@ import { import { ResizableBox } from 'react-resizable'; import { ColorBy, ContextMenuType, ObjectType, Workspace, -} from 'reducers/interfaces'; +} from 'reducers'; import { CameraAction, Canvas3d, ViewType, ViewsDOM, } from 'cvat-canvas3d-wrapper'; @@ -20,7 +21,7 @@ import { Canvas } from 'cvat-canvas-wrapper'; import ContextImage from 'components/annotation-page/standard-workspace/context-image/context-image'; import CVATTooltip from 'components/common/cvat-tooltip'; import { LogType } from 'cvat-logger'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; const cvat = getCore(); @@ -34,12 +35,11 @@ interface Props { canvasInstance: Canvas3d | Canvas; jobInstance: any; frameData: any; - curZLayer: number; annotations: any[]; contextMenuVisibility: boolean; activeLabelID: number; - activatedStateID: number | null; activeObjectType: ObjectType; + activatedStateID: number | null; onSetupCanvas: () => void; onGroupObjects: (enabled: boolean) => void; onResetCanvas(): void; @@ -52,9 +52,8 @@ interface Props { onDragCanvas: (enabled: boolean) => void; onShapeDrawn: () => void; workspace: Workspace; - automaticBordering: boolean; - showObjectsTextAlways: boolean; frame: number; + resetZoom: boolean; } interface ViewSize { @@ -184,6 +183,8 @@ const CanvasWrapperComponent = (props: Props): ReactElement => { frame, jobInstance, activeLabelID, + activatedStateID, + resetZoom, activeObjectType, onShapeDrawn, onCreateAnnotations, @@ -258,6 +259,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => { canvasInstanceDOM.perspective.addEventListener('canvas.canceled', onCanvasCancel); canvasInstanceDOM.perspective.addEventListener('canvas.dragstart', onCanvasDragStart); canvasInstanceDOM.perspective.addEventListener('canvas.dragstop', onCanvasDragDone); + canvasInstance.configure({ resetZoom }); }; const keyControlsKeyDown = (key: KeyboardEvent): void => { @@ -336,6 +338,14 @@ const CanvasWrapperComponent = (props: Props): ReactElement => { }; }, []); + useEffect(() => { + canvasInstance.activate(activatedStateID); + }, [activatedStateID]); + + useEffect(() => { + canvasInstance.configure({ resetZoom }); + }, [resetZoom]); + const updateShapesView = (): void => { (canvasInstance as Canvas3d).configureShapes({ opacity, @@ -518,16 +528,18 @@ const CanvasWrapperComponent = (props: Props): ReactElement => { handle={} onResize={(e: SyntheticEvent) => setViewSize({ type: ViewType.PERSPECTIVE, e })} > - {frameFetching ? ( - - - - ) : null} -
    -
    - - -
    + <> + {frameFetching ? ( + + + + ) : null} +
    +
    + + +
    +
    number[], + onDrag: (diffX: number, diffY: number) => void, + component: JSX.Element, +): JSX.Element { + const ref = useRef(null); + useEffect(() => { + if (!ref.current) return () => {}; + const click = [0, 0]; + const position = getPosition(); + + const mouseMoveListener = (event: MouseEvent): void => { + const dy = event.clientY - click[0]; + const dx = event.clientX - click[1]; + onDrag(position[0] + dy, position[1] + dx); + event.stopPropagation(); + event.preventDefault(); + }; + + const mouseDownListener = (event: MouseEvent): void => { + const [initialTop, initialLeft] = getPosition(); + position[0] = initialTop; + position[1] = initialLeft; + click[0] = event.clientY; + click[1] = event.clientX; + window.addEventListener('mousemove', mouseMoveListener); + event.stopPropagation(); + event.preventDefault(); + }; + + const mouseUpListener = (): void => { + window.removeEventListener('mousemove', mouseMoveListener); + }; + + window.document.addEventListener('mouseup', mouseUpListener); + ref.current.addEventListener('mousedown', mouseDownListener); + + return () => { + window.document.removeEventListener('mouseup', mouseUpListener); + if (ref.current) { + ref.current.removeEventListener('mousedown', mouseDownListener); + } + }; + }, [ref.current]); + + return ( +
    + {component} +
    + ); +} diff --git a/cvat-ui/src/components/annotation-page/canvas/image-setups-content.tsx b/cvat-ui/src/components/annotation-page/canvas/image-setups-content.tsx index 00659a079129..9c725458d6c5 100644 --- a/cvat-ui/src/components/annotation-page/canvas/image-setups-content.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/image-setups-content.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -22,7 +22,7 @@ import { changeGridSize, } from 'actions/settings-actions'; import { clamp } from 'utils/math'; -import { GridColor, CombinedState, PlayerSettingsState } from 'reducers/interfaces'; +import { GridColor, CombinedState, PlayerSettingsState } from 'reducers'; const minGridSize = 5; const maxGridSize = 1000; diff --git a/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx index e89cfd3a9095..af7f5cadd44a 100644 --- a/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ import React from 'react'; import Layout from 'antd/lib/layout'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; -import { ActiveControl, Rotation } from 'reducers/interfaces'; +import { ActiveControl, Rotation } from 'reducers'; import { Canvas } from 'cvat-canvas-wrapper'; import RotateControl from 'components/annotation-page/standard-workspace/controls-side-bar/rotate-control'; @@ -21,6 +21,7 @@ interface Props { activeControl: ActiveControl; keyMap: KeyMap; normalizedKeyMap: Record; + frameIsDeleted: boolean; rotateFrame(rotation: Rotation): void; selectIssuePosition(enabled: boolean): void; @@ -28,9 +29,11 @@ interface Props { export default function ControlsSideBarComponent(props: Props): JSX.Element { const { - canvasInstance, activeControl, normalizedKeyMap, keyMap, rotateFrame, selectIssuePosition, + canvasInstance, activeControl, normalizedKeyMap, keyMap, rotateFrame, selectIssuePosition, frameIsDeleted, } = props; + const controlsDisabled = frameIsDeleted; + const preventDefault = (event: KeyboardEvent | undefined): void => { if (event) { event.preventDefault(); @@ -42,26 +45,32 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { OPEN_REVIEW_ISSUE: keyMap.OPEN_REVIEW_ISSUE, }; - const handlers = { + let handlers: any = { CANCEL: (event: KeyboardEvent | undefined) => { preventDefault(event); if (activeControl !== ActiveControl.CURSOR) { canvasInstance.cancel(); } }, - OPEN_REVIEW_ISSUE: (event: KeyboardEvent | undefined) => { - preventDefault(event); - if (activeControl === ActiveControl.OPEN_ISSUE) { - canvasInstance.selectRegion(false); - selectIssuePosition(false); - } else { - canvasInstance.cancel(); - canvasInstance.selectRegion(true); - selectIssuePosition(true); - } - }, }; + if (!controlsDisabled) { + handlers = { + ...handlers, + OPEN_REVIEW_ISSUE: (event: KeyboardEvent | undefined) => { + preventDefault(event); + if (activeControl === ActiveControl.OPEN_ISSUE) { + canvasInstance.selectRegion(false); + selectIssuePosition(false); + } else { + canvasInstance.cancel(); + canvasInstance.selectRegion(true); + selectIssuePosition(true); + } + }, + }; + } + return ( @@ -87,6 +96,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { canvasInstance={canvasInstance} activeControl={activeControl} selectIssuePosition={selectIssuePosition} + disabled={controlsDisabled} /> ); diff --git a/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/issue-control.tsx b/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/issue-control.tsx index 8448b16a84bc..f0315d14079f 100644 --- a/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/issue-control.tsx +++ b/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/issue-control.tsx @@ -5,7 +5,7 @@ import React from 'react'; import Icon from '@ant-design/icons'; -import { ActiveControl } from 'reducers/interfaces'; +import { ActiveControl } from 'reducers'; import { Canvas } from 'cvat-canvas-wrapper'; import { RectangleIcon } from 'icons'; import CVATTooltip from 'components/common/cvat-tooltip'; @@ -13,33 +13,40 @@ import CVATTooltip from 'components/common/cvat-tooltip'; interface Props { canvasInstance: Canvas; activeControl: ActiveControl; + disabled: boolean; selectIssuePosition(enabled: boolean): void; } function CreateIssueControl(props: Props): JSX.Element { - const { activeControl, canvasInstance, selectIssuePosition } = props; + const { + activeControl, canvasInstance, selectIssuePosition, disabled, + } = props; return ( - - { - if (activeControl === ActiveControl.OPEN_ISSUE) { - canvasInstance.selectRegion(false); - selectIssuePosition(false); - } else { - canvasInstance.cancel(); - canvasInstance.selectRegion(true); - selectIssuePosition(true); + disabled ? ( + + ) : ( + + - + onClick={(): void => { + if (activeControl === ActiveControl.OPEN_ISSUE) { + canvasInstance.selectRegion(false); + selectIssuePosition(false); + } else { + canvasInstance.cancel(); + canvasInstance.selectRegion(true); + selectIssuePosition(true); + } + }} + /> + + ) ); } diff --git a/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx b/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx index 8b3e88c732af..cae74fefc0f0 100644 --- a/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/review-workspace/styles.scss b/cvat-ui/src/components/annotation-page/review-workspace/styles.scss index 1c9d33483d74..d525026a0d76 100644 --- a/cvat-ui/src/components/annotation-page/review-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/review-workspace/styles.scss @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -11,6 +11,12 @@ .cvat-issue-control { font-size: 40px; + &.cvat-disabled-canvas-control { + &::after { + opacity: 0.3; + } + } + &::after { content: '\FE56'; font-size: 32px; diff --git a/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx b/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx index 019c233b00ff..55782936a633 100644 --- a/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx +++ b/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx @@ -6,7 +6,7 @@ import './styles.scss'; import React, { useState, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { CombinedState } from 'reducers/interfaces'; +import { CombinedState } from 'reducers'; import { Canvas } from 'cvat-canvas/src/typescript/canvas'; import { commentIssueAsync, resolveIssueAsync, reopenIssueAsync } from 'actions/review-actions'; diff --git a/cvat-ui/src/components/annotation-page/review/styles.scss b/cvat-ui/src/components/annotation-page/review/styles.scss index be2381c9c7c2..949844d26ebc 100644 --- a/cvat-ui/src/components/annotation-page/review/styles.scss +++ b/cvat-ui/src/components/annotation-page/review/styles.scss @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx index 2bb97dc15579..229635a88fcc 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/context-image/context-image.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -9,7 +9,7 @@ import { QuestionCircleOutlined, ShrinkOutlined } from '@ant-design/icons'; import Spin from 'antd/lib/spin'; import Image from 'antd/lib/image'; -import { CombinedState } from 'reducers/interfaces'; +import { CombinedState } from 'reducers'; import { hideShowContextImage, getContextImageAsync } from 'actions/annotation-actions'; import CVATTooltip from 'components/common/cvat-tooltip'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx index 2bfbf7fb32c3..1aec24a25684 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/control-visibility-observer.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/control-visibility-observer.tsx index 37233d141ca5..28f96b44e262 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/control-visibility-observer.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/control-visibility-observer.tsx @@ -1,4 +1,5 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -8,6 +9,7 @@ import { SmallDashOutlined } from '@ant-design/icons'; import Popover from 'antd/lib/popover'; import React, { useEffect, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; +import { ConnectedComponent } from 'react-redux'; const extraControlsContentClassName = 'cvat-extra-controls-control-content'; @@ -48,7 +50,7 @@ export function ExtraControlsControl(): JSX.Element { } export default function ControlVisibilityObserver

    ( - ControlComponent: React.FunctionComponent

    , + ControlComponent: React.FunctionComponent

    | ConnectedComponent, ): React.FunctionComponent

    { let visibilityHeightThreshold = 0; // minimum value of height when element can be pushed to main panel diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index 606cf9c6d14d..6fa50213a3e7 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -1,13 +1,17 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React from 'react'; import Layout from 'antd/lib/layout'; -import { ActiveControl, Rotation } from 'reducers/interfaces'; +import { + ActiveControl, ObjectType, Rotation, ShapeType, +} from 'reducers'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; -import { Canvas } from 'cvat-canvas-wrapper'; +import { Canvas, CanvasMode } from 'cvat-canvas-wrapper'; +import { LabelOptColor } from 'components/labels-editor/common'; import ControlVisibilityObserver, { ExtraControlsControl } from './control-visibility-observer'; import RotateControl, { Props as RotateControlProps } from './rotate-control'; @@ -23,6 +27,8 @@ import DrawPolylineControl, { Props as DrawPolylineControlProps } from './draw-p import DrawPointsControl, { Props as DrawPointsControlProps } from './draw-points-control'; import DrawEllipseControl, { Props as DrawEllipseControlProps } from './draw-ellipse-control'; import DrawCuboidControl, { Props as DrawCuboidControlProps } from './draw-cuboid-control'; +import DrawMaskControl, { Props as DrawMaskControlProps } from './draw-mask-control'; +import DrawSkeletonControl, { Props as DrawSkeletonControlProps } from './draw-skeleton-control'; import SetupTagControl, { Props as SetupTagControlProps } from './setup-tag-control'; import MergeControl, { Props as MergeControlProps } from './merge-control'; import GroupControl, { Props as GroupControlProps } from './group-control'; @@ -34,6 +40,7 @@ interface Props { keyMap: KeyMap; normalizedKeyMap: Record; labels: any[]; + frameData: any; mergeObjects(enabled: boolean): void; groupObjects(enabled: boolean): void; @@ -60,6 +67,8 @@ const ObservedDrawPolylineControl = ControlVisibilityObserver(DrawPointsControl); const ObservedDrawEllipseControl = ControlVisibilityObserver(DrawEllipseControl); const ObservedDrawCuboidControl = ControlVisibilityObserver(DrawCuboidControl); +const ObservedDrawMaskControl = ControlVisibilityObserver(DrawMaskControl); +const ObservedDrawSkeletonControl = ControlVisibilityObserver(DrawSkeletonControl); const ObservedSetupTagControl = ControlVisibilityObserver(SetupTagControl); const ObservedMergeControl = ControlVisibilityObserver(MergeControl); const ObservedGroupControl = ControlVisibilityObserver(GroupControl); @@ -80,8 +89,31 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { pasteShape, resetGroup, redrawShape, + frameData, } = props; + const controlsDisabled = !labels.length || frameData.deleted; + const withUnspecifiedType = labels.some((label: any) => label.type === 'any' && !label.hasParent); + let rectangleControlVisible = withUnspecifiedType; + let polygonControlVisible = withUnspecifiedType; + let polylineControlVisible = withUnspecifiedType; + let pointsControlVisible = withUnspecifiedType; + let ellipseControlVisible = withUnspecifiedType; + let cuboidControlVisible = withUnspecifiedType; + let maskControlVisible = withUnspecifiedType; + let tagControlVisible = withUnspecifiedType; + const skeletonControlVisible = labels.some((label: LabelOptColor) => label.type === 'skeleton'); + labels.forEach((label: LabelOptColor) => { + rectangleControlVisible = rectangleControlVisible || label.type === ShapeType.RECTANGLE; + polygonControlVisible = polygonControlVisible || label.type === ShapeType.POLYGON; + polylineControlVisible = polylineControlVisible || label.type === ShapeType.POLYLINE; + pointsControlVisible = pointsControlVisible || label.type === ShapeType.POINTS; + ellipseControlVisible = ellipseControlVisible || label.type === ShapeType.ELLIPSE; + cuboidControlVisible = cuboidControlVisible || label.type === ShapeType.CUBOID; + maskControlVisible = maskControlVisible || label.type === ShapeType.MASK; + tagControlVisible = tagControlVisible || label.type === ObjectType.TAG; + }); + const preventDefault = (event: KeyboardEvent | undefined): void => { if (event) { event.preventDefault(); @@ -111,7 +143,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { }, }; - if (labels.length) { + if (!controlsDisabled) { handlers = { ...handlers, PASTE_SHAPE: (event: KeyboardEvent | undefined) => { @@ -127,11 +159,22 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { ActiveControl.DRAW_POLYLINE, ActiveControl.DRAW_RECTANGLE, ActiveControl.DRAW_CUBOID, + ActiveControl.DRAW_ELLIPSE, + ActiveControl.DRAW_SKELETON, + ActiveControl.DRAW_MASK, ActiveControl.AI_TOOLS, ActiveControl.OPENCV_TOOLS, ].includes(activeControl); + const editing = canvasInstance.mode() === CanvasMode.EDIT; if (!drawing) { + if (editing) { + // users probably will press N as they are used to do when they want to finish editing + // in this case, if a mask is being edited we probably want to finish editing first + canvasInstance.edit({ enabled: false }); + return; + } + canvasInstance.cancel(); // repeateDrawShapes gets all the latest parameters // and calls canvasInstance.draw() with them @@ -223,38 +266,86 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {


    - - - - - - - - + { + rectangleControlVisible && ( + + ) + } + { + polygonControlVisible && ( + + ) + } + { + polylineControlVisible && ( + + ) + } + { + pointsControlVisible && ( + + ) + } + { + ellipseControlVisible && ( + + ) + } + { + cuboidControlVisible && ( + + ) + } + { + maskControlVisible && ( + + ) + } + { + skeletonControlVisible && ( + + ) + } + { + tagControlVisible && ( + + ) + }
    diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx index f17217d5c8e9..a8f16fa052c6 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/cursor-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ import React from 'react'; import Icon from '@ant-design/icons'; import { CursorIcon } from 'icons'; -import { ActiveControl } from 'reducers/interfaces'; +import { ActiveControl } from 'reducers'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; import CVATTooltip from 'components/common/cvat-tooltip'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control.tsx index 59791f215bb2..8036d2a9a8a0 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,7 +8,7 @@ import Icon from '@ant-design/icons'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; -import { ShapeType } from 'reducers/interfaces'; +import { ShapeType } from 'reducers'; import { CubeIcon } from 'icons'; @@ -22,7 +22,7 @@ export interface Props { } const CustomPopover = withVisibilityHandling(Popover, 'draw-cuboid'); -function DrawPolygonControl(props: Props): JSX.Element { +function DrawCuboidControl(props: Props): JSX.Element { const { canvasInstance, isDrawing, disabled } = props; const dynamicPopoverProps = isDrawing ? { overlayStyle: { @@ -53,4 +53,4 @@ function DrawPolygonControl(props: Props): JSX.Element { ); } -export default React.memo(DrawPolygonControl); +export default React.memo(DrawCuboidControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-ellipse-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-ellipse-control.tsx index d6531a59a6ce..8c312dba7d64 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-ellipse-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-ellipse-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,7 +8,7 @@ import Icon from '@ant-design/icons'; import { Canvas } from 'cvat-canvas-wrapper'; import { EllipseIcon } from 'icons'; -import { ShapeType } from 'reducers/interfaces'; +import { ShapeType } from 'reducers'; import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; import withVisibilityHandling from './handle-popover-visibility'; @@ -20,7 +20,7 @@ export interface Props { } const CustomPopover = withVisibilityHandling(Popover, 'draw-ellipse'); -function DrawPointsControl(props: Props): JSX.Element { +function DrawEllipseControl(props: Props): JSX.Element { const { canvasInstance, isDrawing, disabled } = props; const dynamicPopoverProps = isDrawing ? { overlayStyle: { @@ -51,4 +51,4 @@ function DrawPointsControl(props: Props): JSX.Element { ); } -export default React.memo(DrawPointsControl); +export default React.memo(DrawEllipseControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-mask-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-mask-control.tsx new file mode 100644 index 000000000000..ff2a8190db96 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-mask-control.tsx @@ -0,0 +1,54 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import Popover from 'antd/lib/popover'; +import Icon from '@ant-design/icons'; + +import { Canvas } from 'cvat-canvas-wrapper'; +import { BrushIcon } from 'icons'; +import { ShapeType } from 'reducers'; + +import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; +import withVisibilityHandling from './handle-popover-visibility'; + +export interface Props { + canvasInstance: Canvas; + isDrawing: boolean; + disabled?: boolean; +} + +const CustomPopover = withVisibilityHandling(Popover, 'draw-mask'); +function DrawPointsControl(props: Props): JSX.Element { + const { canvasInstance, isDrawing, disabled } = props; + const dynamicPopoverProps = isDrawing ? { + overlayStyle: { + display: 'none', + }, + } : {}; + + const dynamicIconProps = isDrawing ? { + className: 'cvat-draw-mask-control cvat-active-canvas-control', + onClick: (): void => { + canvasInstance.draw({ enabled: false }); + }, + } : { + className: 'cvat-draw-mask-control', + }; + + return disabled ? ( + + ) : ( + } + > + + + ); +} + +export default React.memo(DrawPointsControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx index 7ed2f0e6bd25..d27f75eea48d 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-points-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,7 +8,7 @@ import Icon from '@ant-design/icons'; import { Canvas } from 'cvat-canvas-wrapper'; import { PointIcon } from 'icons'; -import { ShapeType } from 'reducers/interfaces'; +import { ShapeType } from 'reducers'; import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; import withVisibilityHandling from './handle-popover-visibility'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx index 0a1f2fe2e7df..92dc95806683 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polygon-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,7 +8,7 @@ import Icon from '@ant-design/icons'; import { Canvas } from 'cvat-canvas-wrapper'; import { PolygonIcon } from 'icons'; -import { ShapeType } from 'reducers/interfaces'; +import { ShapeType } from 'reducers'; import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; import withVisibilityHandling from './handle-popover-visibility'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx index aa1b9b3a7e48..993ffdaea7c2 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-polyline-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,7 +8,7 @@ import Icon from '@ant-design/icons'; import { Canvas } from 'cvat-canvas-wrapper'; import { PolylineIcon } from 'icons'; -import { ShapeType } from 'reducers/interfaces'; +import { ShapeType } from 'reducers'; import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; import withVisibilityHandling from './handle-popover-visibility'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx index 362d74f74e02..dee293c735cb 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-rectangle-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,7 +8,7 @@ import Icon from '@ant-design/icons'; import { Canvas } from 'cvat-canvas-wrapper'; import { RectangleIcon } from 'icons'; -import { ShapeType } from 'reducers/interfaces'; +import { ShapeType } from 'reducers'; import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; import withVisibilityHandling from './handle-popover-visibility'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx index 0e1a1c9ae78e..5677cc3d2af0 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -1,4 +1,5 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -10,10 +11,11 @@ import Radio, { RadioChangeEvent } from 'antd/lib/radio'; import Text from 'antd/lib/typography/Text'; import { RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper'; -import { DimensionType, ShapeType } from 'reducers/interfaces'; +import { DimensionType, ShapeType } from 'reducers'; import { clamp } from 'utils/math'; import LabelSelector from 'components/label-selector/label-selector'; import CVATTooltip from 'components/common/cvat-tooltip'; +import { Label } from 'cvat-core-wrapper'; interface Props { shapeType: ShapeType; @@ -22,9 +24,9 @@ interface Props { rectDrawingMethod?: RectDrawingMethod; cuboidDrawingMethod?: CuboidDrawingMethod; numberOfPoints?: number; - selectedLabelID: number; + selectedLabelID: number | null; repeatShapeShortcut: string; - onChangeLabel(value: string): void; + onChangeLabel(value: Label | null): void; onChangePoints(value: number | undefined): void; onChangeRectDrawingMethod(event: RadioChangeEvent): void; onChangeCuboidDrawingMethod(event: RadioChangeEvent): void; @@ -53,7 +55,6 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { } = props; const is2D = jobInstance.dimension === DimensionType.DIM_2D; - return (
    @@ -126,7 +127,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { )} - {is2D && ![ShapeType.RECTANGLE, ShapeType.CUBOID, ShapeType.ELLIPSE].includes(shapeType) ? ( + {is2D && [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.POINTS].includes(shapeType) ? ( Number of points: @@ -154,7 +155,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { - {is2D && ( + {is2D && shapeType !== ShapeType.MASK && ( diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-skeleton-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-skeleton-control.tsx new file mode 100644 index 000000000000..46aff72b60c1 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-skeleton-control.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import Popover from 'antd/lib/popover'; +import Icon from '@ant-design/icons'; + +import { Canvas } from 'cvat-canvas-wrapper'; +import { Canvas3d } from 'cvat-canvas3d-wrapper'; +import { ShapeType } from 'reducers'; + +import { SkeletonIcon } from 'icons'; + +import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; +import withVisibilityHandling from './handle-popover-visibility'; + +export interface Props { + canvasInstance: Canvas | Canvas3d; + isDrawing: boolean; + disabled: boolean; +} + +const CustomPopover = withVisibilityHandling(Popover, 'draw-skeleton'); +function DrawSkeletonControl(props: Props): JSX.Element { + const { canvasInstance, isDrawing, disabled } = props; + const dynamicPopoverProps = isDrawing ? { + overlayStyle: { + display: 'none', + }, + } : {}; + + const dynamicIconProps = isDrawing ? { + className: 'cvat-draw-skeleton-control cvat-active-canvas-control', + onClick: (): void => { + canvasInstance.draw({ enabled: false }); + }, + } : { + className: 'cvat-draw-skeleton-control', + }; + + return disabled ? ( + + ) : ( + } + > + + + ); +} + +export default React.memo(DrawSkeletonControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx index 9921d86d6b99..c011cf0c2375 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/fit-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx index 379a4e05ca1e..2d2af45668c6 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/group-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,7 +8,7 @@ import Icon from '@ant-design/icons'; import { GroupIcon } from 'icons'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; -import { ActiveControl, DimensionType } from 'reducers/interfaces'; +import { ActiveControl, DimensionType } from 'reducers'; import CVATTooltip from 'components/common/cvat-tooltip'; export interface Props { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/handle-popover-visibility.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/handle-popover-visibility.tsx index d0c47c96a0e0..23850c86db11 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/handle-popover-visibility.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/handle-popover-visibility.tsx @@ -1,14 +1,19 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT import React, { useState } from 'react'; import Popover, { PopoverProps } from 'antd/lib/popover'; +interface OwnProps { + overlayClassName?: string; + onVisibleChange?: (visible: boolean) => void; +} + export default function withVisibilityHandling(WrappedComponent: typeof Popover, popoverType: string) { - return (props: PopoverProps): JSX.Element => { + return (props: OwnProps & PopoverProps): JSX.Element => { const [visible, setVisible] = useState(false); - const { overlayClassName, ...rest } = props; + const { overlayClassName, onVisibleChange, ...rest } = props; const overlayClassNames = typeof overlayClassName === 'string' ? overlayClassName.split(/\s+/) : []; const popoverClassName = `cvat-${popoverType}-popover`; overlayClassNames.push(popoverClassName); @@ -34,6 +39,7 @@ export default function withVisibilityHandling(WrappedComponent: typeof Popover, } } setVisible(_visible); + if (onVisibleChange) onVisibleChange(_visible); }} /> ); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx index 6ee62ed1e6ae..b1dfaf67f7ed 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/interactor-tooltips.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx index 804aa38bf5c8..eaa1e5f25017 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/merge-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -7,7 +7,7 @@ import Icon from '@ant-design/icons'; import { MergeIcon } from 'icons'; import { Canvas } from 'cvat-canvas-wrapper'; -import { ActiveControl } from 'reducers/interfaces'; +import { ActiveControl } from 'reducers'; import CVATTooltip from 'components/common/cvat-tooltip'; export interface Props { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx index be6eed9a3c16..9bb66103033e 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/move-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ import React from 'react'; import Icon from '@ant-design/icons'; import { MoveIcon } from 'icons'; -import { ActiveControl } from 'reducers/interfaces'; +import { ActiveControl } from 'reducers'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; import CVATTooltip from 'components/common/cvat-tooltip'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx index daa76386883e..c3fbe9e7687f 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/opencv-control.tsx @@ -17,12 +17,12 @@ import message from 'antd/lib/message'; import { OpenCVIcon } from 'icons'; import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper'; -import getCore from 'cvat-core-wrapper'; +import { getCore } from 'cvat-core-wrapper'; import openCVWrapper from 'utils/opencv-wrapper/opencv-wrapper'; import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors'; import { CombinedState, ActiveControl, OpenCVTool, ObjectType, ShapeType, ToolsBlockerState, -} from 'reducers/interfaces'; +} from 'reducers'; import { interactWithCanvas, fetchAnnotationsAsync, @@ -210,6 +210,7 @@ class OpenCVControlComponent extends React.PureComponent => { @@ -286,7 +287,7 @@ class OpenCVControlComponent extends React.PureComponent { shape.shapePoints = points; - }).catch((error) => { + }).catch((error: any) => { reject(error); }); } @@ -486,12 +487,13 @@ class OpenCVControlComponent extends React.PureComponent 1 ? 'objects are' : 'object is' } being tracked..`, - 0, - ); + duration: 0, + className: 'cvat-tracking-notice', + }); const imageData = this.getCanvasImageData(); for (const shape of trackingData[trackerID]) { const [objectState] = objectStates.filter( @@ -589,6 +591,33 @@ class OpenCVControlComponent extends React.PureComponent { + try { + this.setState({ + initializationError: false, + initializationProgress: 0, + }); + await openCVWrapper.initialize((progress: number) => { + this.setState({ initializationProgress: progress }); + }); + const trackers = Object.values(openCVWrapper.tracking); + this.setState({ + libraryInitialized: true, + activeTracker: trackers[0], + trackers, + }); + } catch (error: any) { + notification.error({ + description: error.toString(), + message: 'Could not initialize OpenCV library', + }); + this.setState({ + initializationError: true, + initializationProgress: -1, + }); + } + } + private renderDrawingContent(): JSX.Element { const { activeLabelID } = this.state; const { labels, canvasInstance, onInteractionStart } = this.props; @@ -772,42 +801,21 @@ class OpenCVControlComponent extends React.PureComponent - = 0 ? 17 : 24}> - + + { + initializationProgress >= 0 ? + OpenCV is loading : ( + + ) + } {initializationProgress >= 0 && ( - + ) : ( <> @@ -854,8 +864,13 @@ class OpenCVControlComponent extends React.PureComponent { - if (libraryInitialized !== openCVWrapper.isInitialized) { + onVisibleChange={(visible: boolean) => { + const { initializationProgress } = this.state; + if (!visible || initializationProgress >= 0) return; + + if (!openCVWrapper.isInitialized || openCVWrapper.initializationInProgress) { + this.initializeOpenCV(); + } else if (libraryInitialized !== openCVWrapper.isInitialized) { this.setState({ libraryInitialized: openCVWrapper.isInitialized, }); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx index ec461b1d7e4d..8169039abdb6 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/resize-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ import React from 'react'; import Icon from '@ant-design/icons'; import { ZoomIcon } from 'icons'; -import { ActiveControl } from 'reducers/interfaces'; +import { ActiveControl } from 'reducers'; import { Canvas } from 'cvat-canvas-wrapper'; import CVATTooltip from 'components/common/cvat-tooltip'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx index 5c5a33cb457f..edbbfa6fb3d5 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -7,7 +7,7 @@ import Icon from '@ant-design/icons'; import Popover from 'antd/lib/popover'; import { RotateIcon } from 'icons'; -import { Rotation } from 'reducers/interfaces'; +import { Rotation } from 'reducers'; import CVATTooltip from 'components/common/cvat-tooltip'; import withVisibilityHandling from './handle-popover-visibility'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx index 23874edadb97..3b070ed075e5 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -14,25 +14,17 @@ import withVisibilityHandling from './handle-popover-visibility'; export interface Props { canvasInstance: Canvas; - isDrawing: boolean; disabled?: boolean; } const CustomPopover = withVisibilityHandling(Popover, 'setup-tag'); function SetupTagControl(props: Props): JSX.Element { - const { isDrawing, disabled } = props; - const dynamicPopoverProps = isDrawing ? - { - overlayStyle: { - display: 'none', - }, - } : - {}; + const { disabled } = props; return disabled ? ( ) : ( - }> + }> ); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx index a202e3467a42..513fbe2e8f82 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,14 +8,15 @@ import Button from 'antd/lib/button'; import Text from 'antd/lib/typography/Text'; import LabelSelector from 'components/label-selector/label-selector'; +import { PlusOutlined } from '@ant-design/icons'; import CVATTooltip from 'components/common/cvat-tooltip'; interface Props { labels: any[]; - selectedLabelID: number; + selectedLabelID: number | null; repeatShapeShortcut: string; onChangeLabel(value: string): void; - onSetup(labelID: number): void; + onSetup(): void; } function SetupTagPopover(props: Props): JSX.Element { @@ -37,20 +38,21 @@ function SetupTagPopover(props: Props): JSX.Element { Label - - + + onSetup()} /> - - - - - + + + + ); +} + function PropagateItem(props: ItemProps): JSX.Element { const { toolProps, ...rest } = props; const { propagateShortcut, propagate } = toolProps; @@ -179,27 +194,14 @@ function SwitchColorItem(props: ItemProps): JSX.Element { function RemoveItem(props: ItemProps): JSX.Element { const { toolProps, ...rest } = props; - const { removeShortcut, locked, remove } = toolProps; + const { removeShortcut, remove } = toolProps; return ( @@ -223,6 +225,7 @@ export default function ItemMenu(props: Props): JSX.Element { TO_FOREGROUND = 'to_foreground', SWITCH_COLOR = 'switch_color', REMOVE_ITEM = 'remove_item', + EDIT_MASK = 'edit_mask', } const is2D = jobInstance.dimension === DimensionType.DIM_2D; @@ -230,7 +233,10 @@ export default function ItemMenu(props: Props): JSX.Element { return ( - {!readonly && } + {!readonly && objectType !== ObjectType.TAG && ( + + )} + {!readonly && } {!readonly && } {is2D && !readonly && [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && ( diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index c406fd33b7d4..b5909e8de46f 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -1,12 +1,17 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -import React from 'react'; +import React, { useCallback } from 'react'; +import Text from 'antd/lib/typography/Text'; +import Collapse from 'antd/lib/collapse'; import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons'; -import { ObjectType, ShapeType, ColorBy } from 'reducers/interfaces'; -import ItemDetails, { attrValuesAreEqual } from './object-item-details'; +import ItemDetailsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item-details'; +import { ObjectType, ShapeType, ColorBy } from 'reducers'; +import { ObjectState } from 'cvat-core-wrapper'; +import ObjectItemElementComponent from './object-item-element'; import ItemBasics from './object-item-basics'; interface Props { @@ -19,14 +24,13 @@ interface Props { serverID: number | undefined; labelID: number; locked: boolean; - attrValues: Record; + elements: any[]; color: string; colorBy: ColorBy; labels: any[]; attributes: any[]; - collapsed: boolean; jobInstance: any; - activate(): void; + activate(activeElementID?: number): void; copy(): void; propagate(): void; createURL(): void; @@ -35,30 +39,9 @@ interface Props { toForeground(): void; remove(): void; changeLabel(label: any): void; - changeAttribute(attrID: number, value: string): void; changeColor(color: string): void; - collapse(): void; resetCuboidPerspective(): void; -} - -function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean { - return ( - nextProps.activated === prevProps.activated && - nextProps.readonly === prevProps.readonly && - nextProps.locked === prevProps.locked && - nextProps.labelID === prevProps.labelID && - nextProps.color === prevProps.color && - nextProps.clientID === prevProps.clientID && - nextProps.serverID === prevProps.serverID && - nextProps.objectType === prevProps.objectType && - nextProps.shapeType === prevProps.shapeType && - nextProps.collapsed === prevProps.collapsed && - nextProps.labels === prevProps.labels && - nextProps.attributes === prevProps.attributes && - nextProps.normalizedKeyMap === prevProps.normalizedKeyMap && - nextProps.colorBy === prevProps.colorBy && - attrValuesAreEqual(nextProps.attrValues, prevProps.attrValues) - ); + edit(): void; } function ObjectItemComponent(props: Props): JSX.Element { @@ -70,13 +53,12 @@ function ObjectItemComponent(props: Props): JSX.Element { clientID, serverID, locked, - attrValues, labelID, color, colorBy, + elements, attributes, labels, - collapsed, normalizedKeyMap, activate, copy, @@ -87,10 +69,9 @@ function ObjectItemComponent(props: Props): JSX.Element { toForeground, remove, changeLabel, - changeAttribute, changeColor, - collapse, resetCuboidPerspective, + edit, jobInstance, } = props; @@ -103,11 +84,15 @@ function ObjectItemComponent(props: Props): JSX.Element { 'cvat-objects-sidebar-state-item' : 'cvat-objects-sidebar-state-item cvat-objects-sidebar-state-active-item'; + const activateState = useCallback(() => { + activate(); + }, []); + return (
    {!!attributes.length && ( - )} + {!!elements.length && ( + <> + + + PARTS +
    + + )} + key='elements' + > + {elements.map((element: ObjectState) => ( + + ))} +
    +
    + + )}
    ); } -export default React.memo(ObjectItemComponent, objectItemsAreEqual); +export default React.memo(ObjectItemComponent); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx index 7d83207129b9..57ffe6c59ceb 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -15,7 +15,7 @@ import { Col, Row } from 'antd/lib/grid'; import StatesOrderingSelector from 'components/annotation-page/standard-workspace/objects-side-bar/states-ordering-selector'; import CVATTooltip from 'components/common/cvat-tooltip'; -import { StatesOrdering } from 'reducers/interfaces'; +import { StatesOrdering } from 'reducers'; interface Props { readonly: boolean; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx index 2036673e4731..2b27a07c0318 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -1,11 +1,15 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React from 'react'; -import { StatesOrdering } from 'reducers/interfaces'; +import Text from 'antd/lib/typography/Text'; + +import { StatesOrdering } from 'reducers'; import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item'; +import { ObjectState } from 'cvat-core-wrapper'; import ObjectListHeader from './objects-list-header'; interface Props { @@ -47,6 +51,7 @@ function ObjectListComponent(props: Props): JSX.Element { showAllStates, } = props; + let latestZOrder: number | null = null; return ( <>
    {sortedStatesID.map( - (id: number): JSX.Element => ( - - ), + (id: number): JSX.Element => { + const object = objectStates.find((state: ObjectState) => state.clientID === id); + const zOrder = object?.zOrder || latestZOrder; + + const renderZLayer = latestZOrder !== zOrder && statesOrdering === StatesOrdering.Z_ORDER; + if (renderZLayer) { + latestZOrder = zOrder; + } + + return ( + + {renderZLayer && ( +
    + + {`Layer ${zOrder}`} + +
    + )} + +
    + ); + }, )}
    diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index 659f26d82fe1..b4239e5fcf43 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -13,7 +13,7 @@ import Layout from 'antd/lib/layout'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; -import { CombinedState, DimensionType } from 'reducers/interfaces'; +import { CombinedState, DimensionType } from 'reducers'; import LabelsList from 'components/annotation-page/standard-workspace/objects-side-bar/labels-list'; import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; import { collapseSidebar as collapseSidebarAction } from 'actions/annotation-actions'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/shared.ts b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/shared.ts new file mode 100644 index 000000000000..0bcf4d933872 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/shared.ts @@ -0,0 +1,15 @@ +import { ObjectState } from 'cvat-core-wrapper'; +import { ColorBy } from 'reducers'; + +export function getColor(state: ObjectState, colorBy: ColorBy): string { + let color = ''; + if (colorBy === ColorBy.INSTANCE) { + color = state.color; + } else if (colorBy === ColorBy.GROUP) { + color = state.group?.color || '#000'; + } else if (colorBy === ColorBy.LABEL) { + color = state.label.color as string; + } + + return color; +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/states-ordering-selector.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/states-ordering-selector.tsx index 23edadcb1978..be1219e29a1b 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/states-ordering-selector.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/states-ordering-selector.tsx @@ -1,4 +1,5 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -7,7 +8,7 @@ import { Col } from 'antd/lib/grid'; import Select from 'antd/lib/select'; import Text from 'antd/lib/typography/Text'; -import { StatesOrdering } from 'reducers/interfaces'; +import { StatesOrdering } from 'reducers'; interface StatesOrderingSelectorComponentProps { statesOrdering: StatesOrdering; @@ -35,6 +36,9 @@ function StatesOrderingSelectorComponent(props: StatesOrderingSelectorComponentP {StatesOrdering.UPDATED} + + {StatesOrdering.Z_ORDER} + ); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss index e4c3d99bacb4..c147989426e8 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss @@ -1,4 +1,5 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -158,6 +159,10 @@ overflow-y: auto; } +.cvat-objects-sidebar-z-layer-mark { + text-align: center; +} + .cvat-objects-sidebar-state-item.cvat-objects-sidebar-state-active-item { border-top: 2px solid $object-item-border-color; border-right: 2px solid $object-item-border-color; @@ -208,9 +213,10 @@ } } -.cvat-objects-sidebar-state-item-collapse { - border: 0; - background: inherit; +.cvat-objects-sidebar-state-item-collapse, +.cvat-objects-sidebar-state-item-elements-collapse { + border: unset; + background: unset; width: 100%; > .ant-collapse-item { @@ -218,7 +224,7 @@ border: none; > .ant-collapse-header { - background: inherit; + align-items: baseline; padding-top: 2px; padding-bottom: 2px; @@ -230,9 +236,10 @@ > .ant-collapse-content { > .ant-collapse-content-box { - padding: 3px; + padding: 0; } + border: unset; background: inherit; } } @@ -423,3 +430,22 @@ pointer-events: none; } } + +.cvat-objects-sidebar-state-item-elements { + padding: $grid-unit-size * 0.5; + border: 1px solid $object-item-border-color; + border-bottom: 0; + padding-top: 1px; + padding-left: 1px; + padding-right: 1px; + padding-bottom: 2px; + + &:last-child { + border-bottom: 1px solid $object-item-border-color; + } +} + +.cvat-objects-sidebar-state-item-elements.cvat-objects-sidebar-state-active-element { + border: 2px dashed $object-item-border-color; + padding: 0; +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx index 6b5b6bd97fea..3d6f747285fa 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx @@ -1,82 +1,155 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -import React from 'react'; - +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import Modal from 'antd/lib/modal'; import InputNumber from 'antd/lib/input-number'; import Text from 'antd/lib/typography/Text'; +import Radio, { RadioChangeEvent } from 'antd/lib/radio'; +import { Row, Col } from 'antd/lib/grid'; +import Slider from 'antd/lib/slider'; import { clamp } from 'utils/math'; +import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons'; +import { propagateObjectAsync, switchPropagateVisibility } from 'actions/annotation-actions'; +import { CombinedState } from 'reducers'; -interface Props { - visible: boolean; - propagateFrames: number; - propagateUpToFrame: number; - stopFrame: number; - frameNumber: number; - propagateObject(): void; - cancel(): void; - changePropagateFrames(value: number): void; - changeUpToFrame(value: number): void; +export enum PropagateDirection { + FORWARD = 'forward', + BACKWARD = 'backward', } -export default function PropagateConfirmComponent(props: Props): JSX.Element { - const { - visible, - propagateFrames, - propagateUpToFrame, - stopFrame, - frameNumber, - propagateObject, - changePropagateFrames, - changeUpToFrame, - cancel, - } = props; +function PropagateConfirmComponent(): JSX.Element { + const dispatch = useDispatch(); + const visible = useSelector((state: CombinedState) => state.annotation.propagate.visible); + const frameNumber = useSelector((state: CombinedState) => state.annotation.player.frame.number); + const startFrame = useSelector((state: CombinedState) => state.annotation.job.instance.startFrame); + const stopFrame = useSelector((state: CombinedState) => state.annotation.job.instance.stopFrame); + const [targetFrame, setTargetFrame] = useState(frameNumber); + + const propagateFrames = Math.abs(targetFrame - frameNumber); + const propagateDirection = targetFrame >= frameNumber ? PropagateDirection.FORWARD : PropagateDirection.BACKWARD; - const minPropagateFrames = 1; + useEffect(() => { + const propagateForwardAvailable = stopFrame - frameNumber >= 1; + const propagateBackwardAvailable = frameNumber - startFrame >= 1; + if (propagateForwardAvailable) { + setTargetFrame(stopFrame); + } else if (propagateBackwardAvailable) { + setTargetFrame(startFrame); + } + }, [visible]); + + const updateTargetFrame = (direction: PropagateDirection, _propagateFrames: number): void => { + if (direction === PropagateDirection.FORWARD) { + setTargetFrame(clamp(frameNumber + _propagateFrames, startFrame, stopFrame)); + } else { + setTargetFrame(clamp(frameNumber - _propagateFrames, startFrame, stopFrame)); + } + }; return ( { + dispatch(propagateObjectAsync(frameNumber, targetFrame)) + .then(() => dispatch(switchPropagateVisibility(false))); + }} + onCancel={() => dispatch(switchPropagateVisibility(false))} title='Confirm propagation' visible={visible} + destroyOnClose + okButtonProps={{ disabled: !propagateFrames }} >
    - Do you want to make a copy of the object on - { - if (typeof value !== 'undefined') { - changePropagateFrames( - Math.floor(clamp(+value, minPropagateFrames, Number.MAX_SAFE_INTEGER)), - ); - } - }} - /> - {propagateFrames > 1 ? frames : frame } - up to the - { - if (typeof value !== 'undefined') { - changeUpToFrame(Math.floor(clamp(+value, frameNumber + 1, stopFrame))); - } - }} - /> - frame + + + Please, specify a direction + + + updateTargetFrame(e.target.value, propagateFrames)} + > + + + + + + + + + + + How many copies do you want to create? + + { + if (typeof value !== 'undefined') { + updateTargetFrame(propagateDirection, +value); + } + }} + /> + + +
    + + + Or specify a range where copies will be created + + + { + const value = value1 === frameNumber || value1 === targetFrame ? value2 : value1; + if (value < frameNumber) { + setTargetFrame(clamp(value, startFrame, frameNumber)); + } else { + setTargetFrame(clamp(value, frameNumber, stopFrame)); + } + }} + value={[frameNumber, targetFrame] as [number, number]} + /> + + + { + if (typeof value !== 'undefined') { + if (value > frameNumber) { + setTargetFrame(clamp(+value, frameNumber, stopFrame)); + } else if (value < frameNumber) { + setTargetFrame(clamp(+value, startFrame, frameNumber)); + } else { + setTargetFrame(frameNumber); + } + } + }} + /> + +
    ); } + +export default React.memo(PropagateConfirmComponent); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/remove-confirm.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/remove-confirm.tsx new file mode 100644 index 000000000000..f817dd0b48af --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/remove-confirm.tsx @@ -0,0 +1,80 @@ +// Copyright (C) 2022 Intel Corporation +// Copyright (C) CVAT.ai corp +// +// SPDX-License-Identifier: MIT + +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { CombinedState, ObjectType } from 'reducers'; +import Text from 'antd/lib/typography/Text'; +import Modal from 'antd/lib/modal'; + +import consts from 'consts'; +import { removeObjectAsync, removeObject as removeObjectAction } from 'actions/annotation-actions'; + +export default function RemoveConfirmComponent(): JSX.Element | null { + const [visible, setVisible] = useState(false); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(<>); + const objectState = useSelector((state: CombinedState) => state.annotation.remove.objectState); + const force = useSelector((state: CombinedState) => state.annotation.remove.force); + const jobInstance = useSelector((state: CombinedState) => state.annotation.job.instance); + const dispatch = useDispatch(); + + const onOk = useCallback(() => { + dispatch(removeObjectAsync(jobInstance, objectState, true)); + }, [jobInstance, objectState]); + + const onCancel = useCallback(() => { + dispatch(removeObjectAction(null, false)); + }, []); + + useEffect(() => { + const newVisible = (!!objectState && !force && objectState.lock) || + (objectState?.objectType === ObjectType.TRACK && !force); + setTitle(objectState?.lock ? 'Object is locked' : 'Remove object'); + let descriptionMessage: string | JSX.Element = 'Are you sure you want to remove it?'; + + if (objectState?.objectType === ObjectType.TRACK && !force) { + descriptionMessage = ( + <> + + { + `The object you are trying to remove is a track. + If you continue, it removes many drawn objects on different frames. + If you want to hide it only on this frame, use the outside feature instead. + ${descriptionMessage}` + } + +
    + {/* eslint-disable-next-line */} + +
    + + ); + } + + setDescription(descriptionMessage); + setVisible(newVisible); + if (!newVisible && objectState) { + dispatch(removeObjectAsync(jobInstance, objectState, true)); + } + }, [objectState, force]); + + return ( + +
    + {description} +
    +
    + ); +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index 75adab1514d7..096d62fe5c0d 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -1,4 +1,5 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporations // // SPDX-License-Identifier: MIT @@ -8,12 +9,13 @@ import Layout from 'antd/lib/layout'; import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper'; import ControlsSideBarContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; -import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm'; import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu'; import IssueAggregatorComponent from 'components/annotation-page/review/issues-aggregator'; +import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm'; +import PropagateConfirmComponent from 'components/annotation-page/standard-workspace/propagate-confirm'; export default function StandardWorkspaceComponent(): JSX.Element { return ( @@ -21,10 +23,11 @@ export default function StandardWorkspaceComponent(): JSX.Element { } /> - + + ); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index 78bc89c807e5..ef788cf583cd 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -1,4 +1,5 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -115,7 +116,9 @@ .cvat-draw-polyline-control, .cvat-draw-points-control, .cvat-draw-ellipse-control, +.cvat-draw-mask-control, .cvat-draw-cuboid-control, +.cvat-draw-skeleton-control, .cvat-setup-tag-control, .cvat-merge-control, .cvat-group-control, @@ -190,7 +193,7 @@ .cvat-tools-track-button, .cvat-tools-interact-button { width: 100%; - margin-top: 10px; + margin-top: $grid-unit-size; } .cvat-interactors-tips-icon-container { @@ -198,6 +201,15 @@ font-size: 20px; } +.cvat-interactors-setups-container { + margin-top: $grid-unit-size; + margin-bottom: $grid-unit-size; + + > button { + margin-right: $grid-unit-size; + } +} + .cvat-interactor-tip-container { background: $background-color-2; padding: $grid-unit-size; @@ -226,6 +238,10 @@ width: $grid-unit-size * 14; justify-content: center; } + + .ant-progress { + margin-left: $grid-unit-size; + } } .cvat-opencv-initialization-button { @@ -268,8 +284,7 @@ margin-top: $grid-unit-size; } -.cvat-setup-tag-popover-content, -.cvat-draw-shape-popover-content { +.cvat-popover-content { padding: $grid-unit-size; border-radius: $grid-unit-size; background: $background-color-2; @@ -278,6 +293,22 @@ > div { margin-top: $grid-unit-size; } +} + +.cvat-setup-tag-popover-content { + @extend .cvat-popover-content; + + .ant-select { + width: 27 * $grid-unit-size; + } + + > div:last-child { + margin-bottom: $grid-unit-size; + } +} + +.cvat-draw-shape-popover-content { + @extend .cvat-popover-content; > div:nth-child(3) > div > div { width: 100%; @@ -303,8 +334,28 @@ } .cvat-propagate-confirm { - > .ant-input-number { - width: 70px; - margin: 0 5px; + > .ant-row { + align-items: flex-start; + margin-bottom: $grid-unit-size * 2; + } + + .ant-input-number { + margin-left: $grid-unit-size; + margin-right: $grid-unit-size; + width: $grid-unit-size * 7; } + + .cvat-propagate-slider-wrapper { + height: $grid-unit-size; + } + + .cvat-propagate-up-to-wrapper > div:first-child { + margin-bottom: $grid-unit-size; + } +} + +.cvat-remove-object-confirm-wrapper { + display: flex; + justify-content: center; + margin-top: $grid-unit-size * 2; } diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx index acd88c61acf7..f93d33016e96 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx @@ -1,10 +1,12 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React from 'react'; import Layout from 'antd/lib/layout'; -import { ActiveControl } from 'reducers/interfaces'; +import { ActiveControl } from 'reducers'; +import { Label, LabelType } from 'cvat-core-wrapper'; import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper'; import MoveControl, { Props as MoveControlProps, @@ -20,13 +22,14 @@ import GroupControl, { } from 'components/annotation-page/standard-workspace/controls-side-bar/group-control'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import ControlVisibilityObserver from 'components/annotation-page/standard-workspace/controls-side-bar/control-visibility-observer'; +import { filterApplicableForType } from 'utils/filter-applicable-labels'; interface Props { keyMap: KeyMap; canvasInstance: Canvas; activeControl: ActiveControl; normalizedKeyMap: Record; - labels: any[]; + labels: Label[]; jobInstance: any; repeatDrawShape(): void; redrawShape(): void; @@ -55,6 +58,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { jobInstance, } = props; + const applicableLabels = filterApplicableForType(LabelType.CUBOID, labels); const preventDefault = (event: KeyboardEvent | undefined): void => { if (event) { event.preventDefault(); @@ -74,7 +78,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { }, }; - if (labels.length) { + if (applicableLabels.length) { handlers = { ...handlers, PASTE_SHAPE: (event: KeyboardEvent | undefined) => { @@ -138,7 +142,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx index e5723149ea33..5ebb696aeb2e 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/photo-context.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -8,7 +8,7 @@ import CameraIcon from '@ant-design/icons/CameraOutlined'; import CVATTooltip from 'components/common/cvat-tooltip'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { Canvas } from 'cvat-canvas-wrapper'; -import { ActiveControl } from 'reducers/interfaces'; +import { ActiveControl } from 'reducers'; interface Props { canvasInstance: Canvas3d | Canvas; diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx index b9394fd220b2..18f70140b0a2 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Intel Corporation +// Copyright (C) 2021-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -10,9 +10,10 @@ import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wra import ControlsSideBarContainer from 'containers/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar'; import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; -import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm'; import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu'; +import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm'; +import PropagateConfirmComponent from 'components/annotation-page/standard-workspace/propagate-confirm'; export default function StandardWorkspace3DComponent(): JSX.Element { return ( @@ -20,9 +21,10 @@ export default function StandardWorkspace3DComponent(): JSX.Element { } /> - + + ); } diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss index 1eb76bf23071..b4ab69aecafe 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/styles.scss @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index adbc9b7390db..fcdfe480e772 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -11,6 +11,7 @@ .cvat-annotation-layout-content { height: 100%; + overflow-y: clip; } .ant-layout-header.cvat-annotation-header { @@ -179,14 +180,16 @@ button.cvat-predictor-button { } .cvat-player-filename-wrapper { - max-width: 300px; + max-width: $grid-unit-size * 30; overflow: hidden; text-overflow: ellipsis; user-select: none; word-break: break-all; } -.cvat-player-frame-url-icon { +.cvat-player-frame-url-icon, +.cvat-player-delete-frame, +.cvat-player-restore-frame { opacity: 0.7; color: $objects-bar-icons-color; @@ -199,6 +202,11 @@ button.cvat-predictor-button { } } +.cvat-player-delete-frame, +.cvat-player-restore-frame { + margin-left: $grid-unit-size * 2; +} + .cvat-player-frame-selector { width: 5em; padding-right: 5px; diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/frame-tags.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/frame-tags.tsx new file mode 100644 index 000000000000..70e0bf4b7ddb --- /dev/null +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/frame-tags.tsx @@ -0,0 +1,79 @@ +// Copyright (C) 2022 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React, { useState, useEffect } from 'react'; +import Tag from 'antd/lib/tag'; +import { connect } from 'react-redux'; +import { Action } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; + +import { + removeObject as removeObjectAction, +} from 'actions/annotation-actions'; +import { CombinedState, ObjectType } from 'reducers'; + +interface StateToProps { + states: any[]; +} + +interface DispatchToProps { + removeObject(objectState: any): void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + annotations: { states }, + }, + } = state; + + return { + states, + }; +} + +function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { + return { + removeObject(objectState: any): void { + dispatch(removeObjectAction(objectState, false)); + }, + }; +} + +function FrameTags(props: StateToProps & DispatchToProps): JSX.Element { + const { + states, + removeObject, + } = props; + + const [frameTags, setFrameTags] = useState([] as any[]); + + const onRemoveState = (objectState: any): void => { + removeObject(objectState); + }; + + useEffect(() => { + setFrameTags(states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG)); + }, [states]); + + return ( + <> + {frameTags.map((tag: any) => ( + { + onRemoveState(tag); + }} + key={tag.clientID} + closable + > + {tag.label.name} + + ))} + + ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(FrameTags); diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss index a7c1855013a4..dc0e1ebd8e47 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/styles.scss @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation // // SPDX-License-Identifier: MIT @@ -10,42 +10,79 @@ .cvat-tag-annotation-sidebar:not(.ant-layout-sider-collapsed) { background: $background-color-2; - padding: 5px; - - > div > .ant-row > .ant-col > .ant-tag { - margin: 4px; - } + padding: $grid-unit-size * 0.5; + padding-left: $grid-unit-size * 1.25; + overflow-y: auto; } .cvat-tag-annotation-sidebar-label-select { - padding-top: 40px; - padding-bottom: 15px; + padding-top: $grid-unit-size * 1.25; + padding-bottom: $grid-unit-size * 1.8; > .ant-col > .ant-select { - padding-left: 5px; - width: 230px; + width: $grid-unit-size * 25; } } .cvat-tag-annotation-sidebar-shortcut-help { - padding-top: 15px; + padding-top: $grid-unit-size * 1.8; text-align: center; } -.cvat-tag-annotation-sidebar-buttons, .cvat-tag-annotation-sidebar-checkbox-skip-frame { - padding-bottom: 15px; + padding-bottom: $grid-unit-size * 1.8; } .cvat-tag-annotation-label-selects { - padding-top: 10px; + padding-top: $grid-unit-size * 1.25; .ant-select { - width: 230px; - padding: 5px 10px; + width: $grid-unit-size * 29; + margin: $grid-unit-size * 0.5 0; + } + + .cvat-tag-annotation-shortcut-key { + margin-left: $grid-unit-size * 1.25; } } .labels-tag-annotation-sidebar-not-found-wrapper { margin-top: $grid-unit-size * 4; } + +.cvat-frame-tags { + .ant-tag { + margin: $grid-unit-size * 0.25; + display: inline-flex; + justify-content: center; + align-items: center; + + .ant-tag-close-icon { + margin-left: $grid-unit-size * 0.5; + font-size: $grid-unit-size * 1.5; + } + } +} + +.cvat-canvas-frame-tags { + @extend .cvat-frame-tags; + + position: absolute; + top: $layout-sm-grid-size; + left: $grid-unit-size; + z-index: 3; + + .ant-tag { + user-select: none; + } +} + +.cvat-tag-annotation-sidebar-tag-label { + margin-top: $grid-unit-size * 1.8; +} + +.cvat-add-tag-button { + margin-left: $grid-unit-size * 1.25; + width: $grid-unit-size * 3.5; + height: $grid-unit-size * 3.5; +} diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/shortcuts-select.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/shortcuts-select.tsx index 1c495a15344d..2c8fd4aa2a76 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/shortcuts-select.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/shortcuts-select.tsx @@ -1,14 +1,15 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React, { useState, useEffect } from 'react'; -import { useSelector } from 'react-redux'; import { Row, Col } from 'antd/lib/grid'; import Text from 'antd/lib/typography/Text'; import Select from 'antd/lib/select'; -import { CombinedState } from 'reducers/interfaces'; +import { DimensionType } from 'reducers'; +import { Label } from 'cvat-core-wrapper'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import { shift } from 'utils/math'; @@ -17,7 +18,8 @@ interface ShortcutLabelMap { } type Props = { - onAddTag(labelID: number): void; + onShortcutPress(event: KeyboardEvent | undefined, labelID: number): void; + labels: Label[]; }; const defaultShortcutLabelMap = { @@ -34,8 +36,7 @@ const defaultShortcutLabelMap = { } as ShortcutLabelMap; const ShortcutsSelect = (props: Props): JSX.Element => { - const { onAddTag } = props; - const { labels } = useSelector((state: CombinedState) => state.annotation.job); + const { labels, onShortcutPress } = props; const [shortcutLabelMap, setShortcutLabelMap] = useState(defaultShortcutLabelMap); const keyMap: KeyMap = {}; @@ -60,16 +61,16 @@ const ShortcutsSelect = (props: Props): JSX.Element => { keyMap[key] = { name: `Setup ${label.name} tag`, description: `Setup tag with "${label.name}" label`, - sequences: [`${id}`], + sequences: [`${id}`, `shift+${id}`], action: 'keydown', + applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D], }; handlers[key] = (event: KeyboardEvent | undefined) => { if (event) { event.preventDefault(); } - - onAddTag(label.id); + onShortcutPress(event, label.id as number); }; }); @@ -92,7 +93,6 @@ const ShortcutsSelect = (props: Props): JSX.Element => { .map((id) => ( - {`Key ${id}:`} + {`Key ${id}`} ))} diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx index 0923745fdc59..c67258a55541 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx @@ -1,4 +1,5 @@ -// Copyright (C) 2020-2021 Intel Corporation +// Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -7,25 +8,27 @@ import { connect } from 'react-redux'; import { Action } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; import { Row, Col } from 'antd/lib/grid'; -import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; +import { + MenuFoldOutlined, MenuUnfoldOutlined, PlusOutlined, +} from '@ant-design/icons'; import Layout, { SiderProps } from 'antd/lib/layout'; import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox/Checkbox'; import Button from 'antd/lib/button/button'; import Text from 'antd/lib/typography/Text'; -import Tag from 'antd/lib/tag'; import { createAnnotationsAsync, - removeObjectAsync, + removeObject as removeObjectAction, changeFrameAsync, rememberObject, } from 'actions/annotation-actions'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; -import { CombinedState, ObjectType } from 'reducers/interfaces'; +import { getCore, Label, LabelType } from 'cvat-core-wrapper'; +import { CombinedState, ObjectType } from 'reducers'; +import { filterApplicableForType } from 'utils/filter-applicable-labels'; import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; import LabelSelector from 'components/label-selector/label-selector'; -import getCore from 'cvat-core-wrapper'; import isAbleToChangeFrame from 'utils/is-able-to-change-frame'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import ShortcutsSelect from './shortcuts-select'; @@ -40,10 +43,11 @@ interface StateToProps { frameNumber: number; keyMap: KeyMap; normalizedKeyMap: Record; + frameData: any; } interface DispatchToProps { - removeObject(jobInstance: any, objectState: any): void; + removeObject(objectState: any): void; createAnnotations(jobInstance: any, frame: number, objectStates: any[]): void; changeFrame(frame: number, fillBuffer?: boolean, frameStep?: number): void; onRememberObject(labelID: number): void; @@ -53,7 +57,7 @@ function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { player: { - frame: { number: frameNumber }, + frame: { number: frameNumber, data: frameData }, }, annotations: { states }, job: { instance: jobInstance, labels }, @@ -66,10 +70,11 @@ function mapStateToProps(state: CombinedState): StateToProps { jobInstance, labels, states, - canvasInstance, + canvasInstance: canvasInstance as Canvas | Canvas3d, frameNumber, keyMap, normalizedKeyMap, + frameData, }; } @@ -81,8 +86,8 @@ function mapDispatchToProps(dispatch: ThunkDispatch): createAnnotations(jobInstance: any, frame: number, objectStates: any[]): void { dispatch(createAnnotationsAsync(jobInstance, frame, objectStates)); }, - removeObject(jobInstance: any, objectState: any): void { - dispatch(removeObjectAsync(jobInstance, objectState, true)); + removeObject(objectState: any): void { + dispatch(removeObjectAction(objectState, false)); }, onRememberObject(labelID: number): void { dispatch(rememberObject({ activeObjectType: ObjectType.TAG, activeLabelID: labelID })); @@ -102,6 +107,7 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen onRememberObject, createAnnotations, keyMap, + frameData, } = props; const preventDefault = (event: KeyboardEvent | undefined): void => { @@ -110,11 +116,15 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen } }; - const defaultLabelID = labels.length ? labels[0].id : null; + const [applicableLabels, setApplicableLabels] = useState( + filterApplicableForType(LabelType.TAG, labels), + ); + const controlsDisabled = !applicableLabels.length || frameData.deleted; + const defaultLabelID = applicableLabels.length ? applicableLabels[0].id as number : null; const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [frameTags, setFrameTags] = useState([] as any[]); - const [selectedLabelID, setSelectedLabelID] = useState(defaultLabelID); + const [selectedLabelID, setSelectedLabelID] = useState(defaultLabelID); const [skipFrame, setSkipFrame] = useState(false); useEffect(() => { @@ -123,6 +133,10 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen } }, []); + useEffect(() => { + setApplicableLabels(filterApplicableForType(LabelType.TAG, labels)); + }, [labels]); + useEffect(() => { const listener = (event: Event): void => { if ( @@ -144,7 +158,8 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen }, []); useEffect(() => { - setFrameTags(states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG)); + const tags = states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG); + setFrameTags(tags); }, [states]); const siderProps: SiderProps = { @@ -162,8 +177,9 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen setSelectedLabelID(value.id); }; - const onRemoveState = (objectState: any): void => { - removeObject(jobInstance, objectState); + const onRemoveState = (labelID: number): void => { + const objectState = frameTags.find((tag: any): boolean => tag.label.id === labelID); + if (objectState) removeObject(objectState); }; const onChangeFrame = (): void => { @@ -175,17 +191,26 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen }; const onAddTag = (labelID: number): void => { - onRememberObject(labelID); + if (frameTags.every((objectState: any): boolean => objectState.label.id !== labelID)) { + onRememberObject(labelID); - const objectState = new cvat.classes.ObjectState({ - objectType: ObjectType.TAG, - label: labels.filter((label: any) => label.id === labelID)[0], - frame: frameNumber, - }); + const objectState = new cvat.classes.ObjectState({ + objectType: ObjectType.TAG, + label: labels.filter((label: any) => label.id === labelID)[0], + frame: frameNumber, + }); + createAnnotations(jobInstance, frameNumber, [objectState]); - createAnnotations(jobInstance, frameNumber, [objectState]); + if (skipFrame) onChangeFrame(); + } + }; - if (skipFrame) onChangeFrame(); + const onShortcutPress = (event: KeyboardEvent | undefined, labelID: number): void => { + if (event?.shiftKey) { + onRemoveState(labelID); + } else { + onAddTag(labelID); + } }; const subKeyMap = { @@ -195,11 +220,13 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen const handlers = { SWITCH_DRAW_MODE: (event: KeyboardEvent | undefined) => { preventDefault(event); - onAddTag(selectedLabelID); + if (selectedLabelID !== null) { + onShortcutPress(event, selectedLabelID); + } }, }; - return !labels.length ? ( + return controlsDisabled ? ( {/* eslint-disable-next-line */} - No labels are available. + Can't place tag on this frame. @@ -235,18 +262,25 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen > {sidebarCollapsed ? : } - + - Tag label - + Tag label: - - - - - - + + + +