diff --git a/.circleci/config.yml b/.circleci/config.yml index 9aa3bc95c6..90897ae360 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,22 +13,24 @@ version: 2.1 ## orbs: codecov: codecov/codecov@1.0.5 - cypress: cypress-io/cypress@1.26.0 + cypress: cypress-io/cypress@3.1.4 + executors: - # Custom executor to override Cypress config - deploy-to-prod-executor: - docker: - - image: cimg/node:16.14 - environment: - CYPRESS_BASE_URL: https://ohif-staging.netlify.com/ - chrome-and-pacs: + cypress-custom: + description: | + Single Docker container used to run Cypress Tests docker: - # Primary container image where all steps run. - - image: 'cypress/browsers:node16.14.2-slim-chrome103-ff102' + - image: cimg/node:<< parameters.node-version >>-browsers + parameters: + node-version: + default: '18.16.1' + description: | + The version of Node to run your tests with. + type: string defaults: &defaults docker: - - image: cimg/node:16.14-browsers + - image: cimg/node:18.18 environment: TERM: xterm # Enable colors in term QUICK_BUILD: true @@ -43,6 +45,7 @@ jobs: steps: # Update yarn - run: yarn -v + - run: node --version # Checkout code and ALL Git Tags - checkout - restore_cache: @@ -93,39 +96,39 @@ jobs: ### # Workflow: PR_OPTIONAL_DOCKER_PUBLISH ### - DOCKER_PR_PUBLISH: - <<: *defaults - steps: - # Enable yarn workspaces - - run: yarn config set workspaces-experimental true + # DOCKER_PR_PUBLISH: + # <<: *defaults + # steps: + # # Enable yarn workspaces + # - run: yarn config set workspaces-experimental true - # Checkout code and ALL Git Tags - - checkout - - restore_cache: - name: Restore Yarn and Cypress Package Cache - keys: - # when lock file changes, use increasingly general patterns to restore cache - - yarn-packages-{{ checksum "yarn.lock" }} - - yarn-packages- + # # Checkout code and ALL Git Tags + # - checkout + # - restore_cache: + # name: Restore Yarn and Cypress Package Cache + # keys: + # # when lock file changes, use increasingly general patterns to restore cache + # - yarn-packages-{{ checksum "yarn.lock" }} + # - yarn-packages- - - run: - name: Install Dependencies - command: yarn install --frozen-lockfile + # - run: + # name: Install Dependencies + # command: yarn install --frozen-lockfile - - setup_remote_docker: - docker_layer_caching: false + # - setup_remote_docker: + # docker_layer_caching: false - - run: - name: Build and push Docker image - command: | - # Remove npm config - rm -f ./.npmrc - # Set our version number using vars - echo $CIRCLE_BUILD_NUM - # Build our image, auth, and push - docker build --tag ohif/app:PR_BUILD-$CIRCLE_BUILD_NUM . - echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin - docker push ohif/app:PR_BUILD-$CIRCLE_BUILD_NUM + # - run: + # name: Build and push Docker image + # command: | + # # Remove npm config + # rm -f ./.npmrc + # # Set our version number using vars + # echo $CIRCLE_BUILD_NUM + # # Build our image, auth, and push + # docker build --tag ohif/app:PR_BUILD-$CIRCLE_BUILD_NUM . + # echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin + # docker push ohif/app:PR_BUILD-$CIRCLE_BUILD_NUM ### # Workflow: DEPLOY @@ -177,63 +180,6 @@ jobs: - commit.txt - version.json - # DEPLOY_TO_DEV: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy - - # DEPLOY_TO_STAGING: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: c7502ae3-b150-493c-8422-05701e44a969 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy - - # DEPLOY_TO_PRODUCTION: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: 79c4a5da-5c95-4dc9-84f7-45fd9dfe21b0 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy - - # DEPLOY_TO_RELEASE_DEV: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: 3270878-22 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: - # cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy - ### # Workflow: RELEASE ### @@ -269,8 +215,7 @@ jobs: git config --global user.name "ohif-bot" - run: name: Authenticate with NPM registry - command: - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc + command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc - run: name: Increase the event emitter limit command: | @@ -283,14 +228,21 @@ jobs: name: build the other half of the packages command: | yarn run build:package-all-1 + - run: + name: increase min time out + command: | + npm config set fetch-retry-mintimeout 20000 + - run: + name: increase max time out + command: | + npm config set fetch-retry-maxtimeout 120000 - run: name: publish package versions command: | node ./publish-version.mjs - run: name: Again set the NPM registry (was deleted in the version script) - command: - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc + command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc - run: name: publish package dist command: | @@ -337,8 +289,7 @@ jobs: - setup_remote_docker: docker_layer_caching: false - run: - name: - Build and push Docker image from the master branch (beta releases) + name: Build and push Docker image from the master branch (beta releases) command: | echo $(ls -l) @@ -365,71 +316,147 @@ jobs: docker push ohif/app:$IMAGE_VERSION_FULL fi -workflows: - version: 2 + # This is copied from the Cypress orb since the default for cypress/run is node 16 and + # we migrated to 18 + CYPRESS_CUSTOM_RUN: + description: | + A single, complete job to run Cypress end-to-end tests in your application. + executor: cypress-custom + parallelism: << parameters.parallelism >> + parameters: + cypress-cache-key: + default: cypress-cache-{{ arch }}-{{ checksum "package.json" }} + description: Cache key used to cache the Cypress binary. + type: string + cypress-cache-path: + default: ~/.cache/Cypress + description: | + By default, this will cache the '~/.cache/Cypress' directory so that the Cypress binary is cached. You can override this by providing your own cache path. + type: string + cypress-command: + default: npx cypress run + description: Command used to run your Cypress tests + type: string + include-branch-in-node-cache-key: + default: false + description: | + If true, this cache will only apply to runs within the same branch. (Adds -{{ .Branch }}- to the node cache key) + type: boolean + install-browsers: + default: false + description: | + Cypress runs by default in the Electron browser. Use this flag to install additional browsers to run your tests in. + This is only needed if you are passing the `--browser` flag in your `cypress-command`. + This parameter leverages the `circleci/browser-tools` orb and includes Chrome and FireFox. + If you need additional browser support you can set this to false and use an executor with a docker image + that includes the browsers of your choosing. See https://hub.docker.com/r/cypress/browsers/tags + type: boolean + install-command: + default: '' + description: Overrides the default NPM command (npm ci) + type: string + node-cache-version: + default: v1 + description: + Change the default node cache version if you need to clear the cache for any reason. + type: string + package-manager: + default: npm + description: Select the default node package manager to use. NPM v5+ Required. + enum: + - npm + - yarn + - yarn-berry + type: enum + parallelism: + default: 1 + description: | + Number of Circle machines to use for load balancing, min 1 + (requires `parallel` and `record` flags in your `cypress-command`) + type: integer + post-install: + default: '' + description: | + Additional commands to run after running install but before verifying Cypress and saving cache. + type: string + start-command: + default: '' + description: Command used to start your local dev server for Cypress to tests against + type: string + working-directory: + default: '' + description: Directory containing package.json + type: string + steps: + - cypress/install: + cypress-cache-key: << parameters.cypress-cache-key >> + cypress-cache-path: << parameters.cypress-cache-path >> + include-branch-in-node-cache-key: << parameters.include-branch-in-node-cache-key >> + install-browsers: << parameters.install-browsers >> + install-command: << parameters.install-command >> + node-cache-version: << parameters.node-cache-version >> + package-manager: << parameters.package-manager >> + post-install: << parameters.post-install >> + working-directory: << parameters.working-directory >> + - cypress/run-tests: + cypress-command: << parameters.cypress-command >> + start-command: << parameters.start-command >> + working-directory: << parameters.working-directory >> +workflows: PR_CHECKS: jobs: - UNIT_TESTS - # E2E: PWA - - cypress/run: - name: 'E2E: PWA' - executor: chrome-and-pacs - browser: chrome - pre-steps: - - run: | - # Clear yarn cache; use yarn from image (update image to update yarn) - rm -rf ~/.yarn - yarn -v - yarn: true - record: true - store_artifacts: true - working_directory: platform/app - build: yarn test:data - start: yarn run test:e2e:serve - spec: 'cypress/integration/**/*' - wait-on: 'http://localhost:3000' - cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' - no-workspace: true # Don't persist workspace - post-steps: - - store_artifacts: - path: platform/app/cypress/screenshots - - store_artifacts: - path: platform/app/cypress/videos - - store_test_results: - path: platform/app/cypress/results + - CYPRESS_CUSTOM_RUN: + name: 'Cypress Tests' + context: cypress + matrix: + parameters: + start-command: + - yarn run test:data && yarn run test:e2e:serve + install-browsers: + - true + cypress-command: + - 'npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run + --record --browser chrome --parallel' + package-manager: + - 'yarn' + cypress-cache-key: + - 'yarn-packages-{{ checksum "yarn.lock" }}' + cypress-cache-path: + - '~/.cache/Cypress' requires: - UNIT_TESTS - PR_OPTIONAL_VISUAL_TESTS: - jobs: - - AWAIT_APPROVAL: - type: approval - # Update hub.docker.org - - cypress/run: - name: 'Generate Percy Snapshots' - executor: cypress/browsers-chrome76 - browser: chrome - pre-steps: - - run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on' - yarn: true - store_artifacts: false - working_directory: platform/app - build: - yarn test:data && npx cross-env QUICK_BUILD=true - APP_CONFIG=config/dicomweb-server.js yarn run build - # start server --> verify running --> percy + chrome + cypress - command: yarn run test:e2e:dist - cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' - no-workspace: true # Don't persist workspace - post-steps: - - store_artifacts: - path: platform/app/cypress/screenshots - - store_artifacts: - path: platform/app/cypress/videos - requires: - - AWAIT_APPROVAL + # PR_OPTIONAL_VISUAL_TESTS: + # jobs: + # - AWAIT_APPROVAL: + # type: approval + # # Update hub.docker.org + # - cypress/run: + # name: 'Generate Percy Snapshots' + # executor: cypress/browsers-chrome76 + # browser: chrome + # pre-steps: + # - run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on' + # yarn: true + # store_artifacts: false + # working_directory: platform/app + # build: + # yarn test:data && npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js + # yarn run build + # # start server --> verify running --> percy + chrome + cypress + # command: yarn run test:e2e:dist + # cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' + # no-workspace: true # Don't persist workspace + # post-steps: + # - store_artifacts: + # path: platform/app/cypress/screenshots + # - store_artifacts: + # path: platform/app/cypress/videos + # requires: + # - AWAIT_APPROVAL # Our master branch deploys to viewer-dev.ohif.org, the viewer.ohif.org is # deployed from the release branch which is more stable and less frequently updated. diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000000..ab57ad157a --- /dev/null +++ b/.codespellrc @@ -0,0 +1,6 @@ +[codespell] +skip = .git,*.pdf,*.svg,yarn.lock,*.min.js,locales +# ignore words ending with … and some camelcased variables and names +ignore-regex = \b\S+…\S*|\b(doubleClick|afterAll|PostgresSQL)\b|\bWee, L\.|.*te.*Telugu.* +# some odd variables +ignore-words-list = datea,ser,childrens diff --git a/.docker/Nginx-Orthanc/config/nginx.conf b/.docker/Nginx-Orthanc/config/nginx.conf deleted file mode 100644 index c38ee5813d..0000000000 --- a/.docker/Nginx-Orthanc/config/nginx.conf +++ /dev/null @@ -1,48 +0,0 @@ -worker_processes 1; - -events { worker_connections 1024; } - -http { - - upstream orthanc-server { - server orthanc:8042; - } - - server { - listen [::]:80 default_server; - listen 80; - - # CORS Magic - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow_Credentials' 'true'; - add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; - add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH'; - - location / { - - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow_Credentials' 'true'; - add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; - add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH'; - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Type' 'text/plain charset=UTF-8'; - add_header 'Content-Length' 0; - return 204; - } - - proxy_pass http://orthanc:8042; - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $server_name; - - # CORS Magic - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow_Credentials' 'true'; - add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; - add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH'; - } - } -} diff --git a/.docker/Nginx-Orthanc/config/orthanc.json b/.docker/Nginx-Orthanc/config/orthanc.json deleted file mode 100644 index 2e10723c04..0000000000 --- a/.docker/Nginx-Orthanc/config/orthanc.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "Name": "Orthanc inside Docker", - "StorageDirectory": "/var/lib/orthanc/db", - "IndexDirectory": "/var/lib/orthanc/db", - "StorageCompression": false, - "MaximumStorageSize": 0, - "MaximumPatientCount": 0, - "LuaScripts": [], - "Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"], - "ConcurrentJobs": 2, - "HttpServerEnabled": true, - "HttpPort": 8042, - "HttpDescribeErrors": true, - "HttpCompressionEnabled": true, - "DicomServerEnabled": true, - "DicomAet": "ORTHANC", - "DicomCheckCalledAet": false, - "DicomPort": 4242, - "DefaultEncoding": "Latin1", - "DeflatedTransferSyntaxAccepted": true, - "JpegTransferSyntaxAccepted": true, - "Jpeg2000TransferSyntaxAccepted": true, - "JpegLosslessTransferSyntaxAccepted": true, - "JpipTransferSyntaxAccepted": true, - "Mpeg2TransferSyntaxAccepted": true, - "RleTransferSyntaxAccepted": true, - "UnknownSopClassAccepted": false, - "DicomScpTimeout": 30, - - "RemoteAccessAllowed": true, - "SslEnabled": false, - "SslCertificate": "certificate.pem", - "AuthenticationEnabled": false, - "RegisteredUsers": { - "test": "test" - }, - "DicomModalities": {}, - "DicomModalitiesInDatabase": false, - "DicomAlwaysAllowEcho": true, - "DicomAlwaysAllowStore": true, - "DicomCheckModalityHost": false, - "DicomScuTimeout": 10, - "OrthancPeers": {}, - "OrthancPeersInDatabase": false, - "HttpProxy": "", - - "HttpVerbose": true, - - "HttpTimeout": 10, - "HttpsVerifyPeers": true, - "HttpsCACertificates": "", - "UserMetadata": {}, - "UserContentType": {}, - "StableAge": 60, - "StrictAetComparison": false, - "StoreMD5ForAttachments": true, - "LimitFindResults": 0, - "LimitFindInstances": 0, - "LimitJobs": 10, - "LogExportedResources": false, - "KeepAlive": true, - "TcpNoDelay": true, - "HttpThreadsCount": 50, - "StoreDicom": true, - "DicomAssociationCloseDelay": 5, - "QueryRetrieveSize": 10, - "CaseSensitivePN": false, - "LoadPrivateDictionary": true, - "Dictionary": {}, - "SynchronousCMove": true, - "JobsHistorySize": 10, - "SaveJobs": true, - "OverwriteInstances": false, - "MediaArchiveSize": 1, - "StorageAccessOnFind": "Always", - "MetricsEnabled": true, - - "DicomWeb": { - "Enable": true, - "Root": "/dicom-web/", - "EnableWado": true, - "WadoRoot": "/wado", - "Host": "127.0.0.1", - "Ssl": false, - "StowMaxInstances": 10, - "StowMaxSize": 10, - "QidoCaseSensitive": false - } -} diff --git a/.docker/Nginx-Orthanc/docker-compose.yml b/.docker/Nginx-Orthanc/docker-compose.yml deleted file mode 100644 index eba7911a31..0000000000 --- a/.docker/Nginx-Orthanc/docker-compose.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: '3.5' - -services: - orthanc: - image: jodogne/orthanc-plugins:1.11.0 - hostname: orthanc - volumes: - # Config - - ./config/orthanc.json:/etc/orthanc/orthanc.json:ro - # Persist data - - ./volumes/orthanc-db/:/var/lib/orthanc/db/ - ports: - - '4242:4242' # DICOM - - '8042:8042' # Web - restart: unless-stopped - nginx: - image: nginx:latest - volumes: - - ./config/nginx.conf:/etc/nginx/nginx.conf - ports: - - '80:80' #ngnix proxy - depends_on: - - orthanc diff --git a/.docker/Nginx-Orthanc/volumes/orthanc-db/.gitignore b/.docker/Nginx-Orthanc/volumes/orthanc-db/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/.docker/Nginx-Orthanc/volumes/orthanc-db/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/.docker/Viewer-v3.x/default.conf.template b/.docker/Viewer-v3.x/default.conf.template index 91776166ff..153ea70862 100644 --- a/.docker/Viewer-v3.x/default.conf.template +++ b/.docker/Viewer-v3.x/default.conf.template @@ -1,5 +1,6 @@ server { - listen ${PORT}; + listen ${PORT} default_server; + listen [::]:${PORT} default_server; location / { root /usr/share/nginx/html; index index.html index.htm; diff --git a/.docker/Viewer-v3.x/default.ssl.conf.template b/.docker/Viewer-v3.x/default.ssl.conf.template new file mode 100644 index 0000000000..06ed4e505d --- /dev/null +++ b/.docker/Viewer-v3.x/default.ssl.conf.template @@ -0,0 +1,22 @@ +server { + listen ${SSL_PORT} ssl http2 default_server; + listen [::]:${SSL_PORT} ssl http2 default_server; + ssl_certificate /etc/ssl/certs/ssl-certificate.crt; + ssl_certificate_key /etc/ssl/private/ssl-private-key.key; + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + add_header Cross-Origin-Opener-Policy same-origin; + add_header Cross-Origin-Embedder-Policy require-corp; + add_header Cross-Origin-Resource-Policy same-origin; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/.docker/Viewer-v3.x/entrypoint.sh b/.docker/Viewer-v3.x/entrypoint.sh index 400226413f..988e087e83 100644 --- a/.docker/Viewer-v3.x/entrypoint.sh +++ b/.docker/Viewer-v3.x/entrypoint.sh @@ -1,6 +1,16 @@ #!/bin/sh -envsubst '${PORT}' < /usr/src/default.conf.template > /etc/nginx/conf.d/default.conf +if [ -n "$SSL_PORT" ] + then + envsubst '${SSL_PORT}:${PORT}' < /usr/src/default.ssl.conf.template > /etc/nginx/conf.d/default.conf + else + envsubst '${PORT}' < /usr/src/default.conf.template > /etc/nginx/conf.d/default.conf +fi + +if [ -n "$APP_CONFIG" ] + then + echo "$APP_CONFIG" > /usr/share/nginx/html/app-config.js +fi if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ] then diff --git a/.dockerignore b/.dockerignore index d4a539c50d..ca74c53988 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,12 @@ # Reduces size of context and hides # files from Docker (can't COPY or ADD these) +# Note that typically the Docker context for various OHIF containers is the +# directory of this file (i.e. the root of the source). As such, this is +# the .dockerignore file for ALL Docker containers that are built. For example, +# the Docker containers built from the recipes in ./platform/app/.recipes will +# have this file as their .dockerignore. + # Output dist/ build/ @@ -28,3 +34,4 @@ dockerfile .vscode/ coverage/ docs/ +testdata/ diff --git a/.eslintrc.json b/.eslintrc.json index 65affdc01d..bc37a7dcf4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,10 +1,5 @@ { - "plugins": [ - "@typescript-eslint", - "import", - "eslint-plugin-tsdoc", - "prettier" - ], + "plugins": ["@typescript-eslint", "import", "eslint-plugin-tsdoc", "prettier"], "extends": [ "react-app", "eslint:recommended", @@ -21,6 +16,10 @@ "version": "detect" } }, + "rules": { + // Enforce consistent brace style for all control statements for readability + "curly": "error" + }, "globals": { "cy": true, "before": true, diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 5a2907b18f..83577dd7c1 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -39,8 +39,7 @@ body: attributes: label: The current behavior description: - 'A clear and concise description of what happens instead of the expected - behavior.' + 'A clear and concise description of what happens instead of the expected behavior.' validations: required: true @@ -48,8 +47,7 @@ body: id: expected_behavior attributes: label: The expected behavior - description: - 'A clear and concise description of what you expected to happen.' + description: 'A clear and concise description of what you expected to happen.' validations: required: true @@ -66,7 +64,7 @@ body: attributes: label: 'Node version' description: 'Your Node.js version.' - placeholder: 'e.g., 16.14.0' + placeholder: 'e.g., 18.16.1' validations: required: true - type: input @@ -81,6 +79,6 @@ body: - type: markdown attributes: value: > - > :warning: Reports we cannot reproduce are at risk of being marked - stale and > closed. The more information you can provide, the more - likely we are to look > into and address your issue. + > :warning: Reports we cannot reproduce are at risk of being marked stale and > closed. The + more information you can provide, the more likely we are to look > into and address your + issue. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index e03e8d9afe..0331ba9bfa 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -20,17 +20,15 @@ body: attributes: label: 'What feature or change would you like to see made?' description: - 'Please include as much detail as possible including possibly mock up - screen shots, workflow or logic flow diagrams etc.' + 'Please include as much detail as possible including possibly mock up screen shots, workflow + or logic flow diagrams etc.' placeholder: '...' validations: required: true - type: textarea attributes: label: 'Why should we prioritize this feature?' - description: - 'Discuss if and how the requested feature interacts with existing - features.' + description: 'Discuss if and how the requested feature interacts with existing features.' placeholder: '...' validations: required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cc3752267f..6a1753d87d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -83,7 +83,7 @@ after the commits are squashed. #### Tested Environment - [] OS: -- [] Node version: +- [] Node version: - [] Browser: diff --git a/.github/stale.yml b/.github/stale.yml index 4d6cdd4e35..28535cca62 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -19,8 +19,7 @@ exemptLabels: staleLabel: 'Stale :baguette_bread:' # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. + This issue has been automatically marked as stale because it has not had recent activity. It will + be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 0000000000..7373affc38 --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,22 @@ +--- +name: Codespell + +on: + push: + branches: [master] + pull_request: + branches: [master] + +permissions: + contents: read + +jobs: + codespell: + name: Check for spelling errors + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Codespell + uses: codespell-project/actions-codespell@v2 diff --git a/.gitignore b/.gitignore index 2911f13930..cd3230b538 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ platform/app/src/pluginImports.js /Viewers.iml platform/app/.recipes/Nginx-Dcm4Che/dcm4che/dcm4che-arc/* platform/app/.recipes/OpenResty-Orthanc/logs/* +.vercel diff --git a/.gitmodules b/.gitmodules index 413cf77c17..f787a67a4b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "testdata"] path = testdata url = https://github.com/OHIF/viewer-testdata-dicomweb.git + branch = main diff --git a/.netlify/www/index.html b/.netlify/www/index.html index 3d889be829..c817960c50 100644 --- a/.netlify/www/index.html +++ b/.netlify/www/index.html @@ -1,23 +1,21 @@ + + OHIF Viewer: Deploy Preview + - - OHIF Viewer: Deploy Preview - - - -

Index of Previews

- - - + +

Index of Previews

+ + diff --git a/.node-version b/.node-version index 832d385064..3876fd4986 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -16.14.0 +18.16.1 diff --git a/.prettierrc b/.prettierrc index 6a64b74e2d..c7eeb08def 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,9 +1,12 @@ { + "plugins": ["prettier-plugin-tailwindcss"], "trailingComma": "es5", - "printWidth": 80, + "printWidth": 100, "proseWrap": "always", "tabWidth": 2, "semi": true, "singleQuote": true, - "arrowParens": "avoid" + "arrowParens": "avoid", + "singleAttributePerLine": true, + "endOfLine": "auto" } diff --git a/.webpack/helpers/excludeNodeModulesExcept.js b/.webpack/helpers/excludeNodeModulesExcept.js index f7234de1a2..ce4d3c3e29 100644 --- a/.webpack/helpers/excludeNodeModulesExcept.js +++ b/.webpack/helpers/excludeNodeModulesExcept.js @@ -5,11 +5,11 @@ function excludeNodeModulesExcept(modules) { if (pathSep == '\\') // must be quoted for use in a regexp: pathSep = '\\\\'; - var moduleRegExps = modules.map(function(modName) { + var moduleRegExps = modules.map(function (modName) { return new RegExp('node_modules' + pathSep + modName); }); - return function(modulePath) { + return function (modulePath) { if (/node_modules/.test(modulePath)) { for (var i = 0; i < moduleRegExps.length; i++) if (moduleRegExps[i].test(modulePath)) return false; diff --git a/.webpack/rules/cssToJavaScript.js b/.webpack/rules/cssToJavaScript.js index 05365d31bc..0cd60e909b 100644 --- a/.webpack/rules/cssToJavaScript.js +++ b/.webpack/rules/cssToJavaScript.js @@ -1,9 +1,7 @@ const autoprefixer = require('autoprefixer'); const path = require('path'); const tailwindcss = require('tailwindcss'); -const tailwindConfigPath = path.resolve( - '../../platform/app/tailwind.config.js' -); +const tailwindConfigPath = path.resolve('../../platform/app/tailwind.config.js'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const devMode = process.env.NODE_ENV !== 'production'; diff --git a/.webpack/rules/transpileJavaScript.js b/.webpack/rules/transpileJavaScript.js index dc84a4973e..e6eb8d596f 100644 --- a/.webpack/rules/transpileJavaScript.js +++ b/.webpack/rules/transpileJavaScript.js @@ -33,7 +33,11 @@ function transpileJavaScript(mode) { rootMode: 'upward', envName: mode, cacheCompression: false, - cacheDirectory: true, + // Note: This was causing a lot of issues with yarn link of the cornerstone + // only set this to true if you don't have a yarn link to external libs + // otherwise expect the lib changes not to be reflected in the dev server + // as it will be cached + cacheDirectory: false, }, }; } diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js index ec8ec5291e..88a009c217 100644 --- a/.webpack/webpack.base.js +++ b/.webpack/webpack.base.js @@ -7,8 +7,7 @@ const fs = require('fs'); const webpack = require('webpack'); // ~~ PLUGINS -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin; +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const TerserJSPlugin = require('terser-webpack-plugin'); // ~~ PackageJSON @@ -26,11 +25,9 @@ const QUICK_BUILD = process.env.QUICK_BUILD; const BUILD_NUM = process.env.CIRCLE_BUILD_NUM || '0'; // read from ../version.txt -const VERSION_NUMBER = - fs.readFileSync(path.join(__dirname, '../version.txt'), 'utf8') || ''; +const VERSION_NUMBER = fs.readFileSync(path.join(__dirname, '../version.txt'), 'utf8') || ''; -const COMMIT_HASH = - fs.readFileSync(path.join(__dirname, '../commit.txt'), 'utf8') || ''; +const COMMIT_HASH = fs.readFileSync(path.join(__dirname, '../commit.txt'), 'utf8') || ''; // dotenv.config(); @@ -73,21 +70,17 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { children: false, warnings: true, }, - devServer: { - open: true, - port: 3000, - historyApiFallback: true, - headers: { - 'Cross-Origin-Embedder-Policy': 'require-corp', - 'Cross-Origin-Opener-Policy': 'same-origin', - }, - }, cache: { type: 'filesystem', }, module: { noParse: [/(codec)/, /(dicomicc)/], rules: [ + { + test: /\.js$/, + enforce: 'pre', + use: 'source-map-loader', + }, transpileJavaScriptRule(mode), loadWebWorkersRule, // loadShadersRule, @@ -109,10 +102,7 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { alias: { // Viewer project '@': path.resolve(__dirname, '../platform/app/src'), - '@components': path.resolve( - __dirname, - '../platform/app/src/components' - ), + '@components': path.resolve(__dirname, '../platform/app/src/components'), '@hooks': path.resolve(__dirname, '../platform/app/src/hooks'), '@routes': path.resolve(__dirname, '../platform/app/src/routes'), '@state': path.resolve(__dirname, '../platform/app/src/state'), @@ -135,7 +125,12 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { extensions: ['.js', '.jsx', '.json', '.ts', '.tsx', '*'], // symlinked resources are resolved to their real path, not their symlinked location symlinks: true, - fallback: { fs: false, path: false, zlib: false }, + fallback: { + fs: false, + path: false, + zlib: false, + buffer: require.resolve('buffer'), + }, }, plugins: [ new webpack.DefinePlugin({ @@ -149,15 +144,12 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { 'process.env.COMMIT_HASH': JSON.stringify(COMMIT_HASH), /* i18n */ 'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''), - 'process.env.LOCIZE_PROJECTID': JSON.stringify( - process.env.LOCIZE_PROJECTID || '' - ), - 'process.env.LOCIZE_API_KEY': JSON.stringify( - process.env.LOCIZE_API_KEY || '' - ), - 'process.env.REACT_APP_I18N_DEBUG': JSON.stringify( - process.env.REACT_APP_I18N_DEBUG || '' - ), + 'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''), + 'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''), + 'process.env.REACT_APP_I18N_DEBUG': JSON.stringify(process.env.REACT_APP_I18N_DEBUG || ''), + }), + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'], }), // Uncomment to generate bundle analyzer // new BundleAnalyzerPlugin(), diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..5e17fee797 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,292 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + + +### Bug Fixes + +* **toggleOneUp:** fixed one up for main tmtv layout ([#3677](https://github.com/OHIF/Viewers/issues/3677)) ([86f54d0](https://github.com/OHIF/Viewers/commit/86f54d0d07042750a863ae876aa8dd5fb16029a5)) + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Bug Fixes + +* **react-select:** update react select package ([#3622](https://github.com/OHIF/Viewers/issues/3622)) ([04ca10d](https://github.com/OHIF/Viewers/commit/04ca10d8779dd15454920002f3d48afa8830de8a)) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) +* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + + +### Bug Fixes + +* **mpr:** Return the original/raw hanging protocol when fetching and preserving the current active protocol. ([#3670](https://github.com/OHIF/Viewers/issues/3670)) ([221dedd](https://github.com/OHIF/Viewers/commit/221dedde5dd4df086276406a9fa2da1cc23b4eb1)) + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + + +### Bug Fixes + +* **keyCloak:** fix openresty keycloak deployment recipe ([#3655](https://github.com/OHIF/Viewers/issues/3655)) ([2d7721c](https://github.com/OHIF/Viewers/commit/2d7721cb581f55dc49e3baeca2411b18dd78ad74)) + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + + +### Bug Fixes + +* **DicomJson:** retrieve.series.metadata method should be async ([#3659](https://github.com/OHIF/Viewers/issues/3659)) ([2737903](https://github.com/OHIF/Viewers/commit/2737903386cf97399473e0fa64fe53ad14da155a)) + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + + +### Bug Fixes + +* **measurements:** Update the calibration tool to match changes in CS3D ([#3505](https://github.com/OHIF/Viewers/issues/3505)) ([38af311](https://github.com/OHIF/Viewers/commit/38af3112ec1f94f36c0ef64ff1cf9d21c0981c81)) + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + + +### Bug Fixes + +* **health imaging:** studies not loading from healthimaging if imagepositionpatient is missing ([#3646](https://github.com/OHIF/Viewers/issues/3646)) ([74e62a1](https://github.com/OHIF/Viewers/commit/74e62a176374f720080d4e777972f70e7f2d8b2b)) + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + + +### Bug Fixes + +* **suv:** import calculate-suv library version that prevents SUV calculation for a zero PatientWeight ([#3638](https://github.com/OHIF/Viewers/issues/3638)) ([0d10f46](https://github.com/OHIF/Viewers/commit/0d10f46b885fe54ec3dae1848134da658eb6280a)) + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + + +### Bug Fixes + +* **hotkeys:** preserve hotkeys if changed, and reduce re-rendering ([#3635](https://github.com/OHIF/Viewers/issues/3635)) ([94f7cfb](https://github.com/OHIF/Viewers/commit/94f7cfb08e3490488394efc42ef089ebe55e86be)) + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + + +### Bug Fixes + +* **nginx archive recipe:** Fixes to various configuration files. ([#3624](https://github.com/OHIF/Viewers/issues/3624)) ([3ce7225](https://github.com/OHIF/Viewers/commit/3ce72254b390f32c9aa207a0589e688805e2659d)) + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + + +### Bug Fixes + +* **OpenIdConnectRoutes:** fix handleUnauthenticated ([#3617](https://github.com/OHIF/Viewers/issues/3617)) ([35fc30c](https://github.com/OHIF/Viewers/commit/35fc30c5359d8199cc38ffa670c08687d2672f11)) + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + + +### Bug Fixes + +* **PT Metadata:** Allow for PatientWeight to be missing from the metadata ([#3621](https://github.com/OHIF/Viewers/issues/3621)) ([44f101d](https://github.com/OHIF/Viewers/commit/44f101d3f2b3204b67e31f4e4939062e65a246ee)) + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) diff --git a/Dockerfile b/Dockerfile index 500ad5ae10..e6310c3ba3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ # Stage 1: Build the application # docker build -t ohif/viewer:latest . -FROM node:16.15.0-slim as json-copier +FROM node:18.16.1-slim as json-copier RUN mkdir /usr/src/app WORKDIR /usr/src/app @@ -37,7 +37,8 @@ COPY platform /usr/src/app/platform #RUN find platform \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf # Copy Files -FROM node:16.15.0-slim as builder +FROM node:18.16.1-slim as builder +RUN apt-get update && apt-get install -y build-essential python3 RUN mkdir /usr/src/app WORKDIR /usr/src/app @@ -61,7 +62,7 @@ RUN yarn run build # Stage 3: Bundle the built application into a Docker container # which runs Nginx using Alpine Linux -FROM nginxinc/nginx-unprivileged:1.23.1-alpine as final +FROM nginxinc/nginx-unprivileged:1.25-alpine as final #RUN apk add --no-cache bash ENV PORT=80 RUN rm /etc/nginx/conf.d/default.conf @@ -69,5 +70,10 @@ USER nginx COPY --chown=nginx:nginx .docker/Viewer-v3.x /usr/src RUN chmod 777 /usr/src/entrypoint.sh COPY --from=builder /usr/src/app/platform/app/dist /usr/share/nginx/html +# In entrypoint.sh, app-config.js might be overwritten, so chmod it to be writeable. +# The nginx user cannot chmod it, so change to root. +USER root +RUN chmod 666 /usr/share/nginx/html/app-config.js +USER nginx ENTRYPOINT ["/usr/src/entrypoint.sh"] CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index 2e3d68b687..5befea75c4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

OHIF Medical Imaging Viewer

The OHIF Viewer is a zero-footprint medical image viewer -provided by the Open Health Imaging Foundation (OHIF). It is a configurable and extensible progressive web application with out-of-the-box support for image archives which support DICOMweb.

+provided by the Open Health Imaging Foundation (OHIF). It is a configurable and extensible progressive web application with out-of-the-box support for image archives which support DICOMweb.

@@ -38,7 +38,16 @@ provided by the Open Health Imaging Foundation (OHIF -![Alt text](platform/docs/docs/assets/img/OHIF-Viewer.jpg) + +| | | | +| :-: | :--- | :--- | +| Measurement tracking | Measurement Tracking | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5) | +| Segmentations | Labelmap Segmentations | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046) | +| Hanging Protocols | Fusion and Custom Hanging protocols | [Demo](https://viewer.ohif.org/tmtv?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463) | +| Microscopy | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.275741864483510678566144889372061815320) | +| Volume Rendering | Volume Rendering | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&hangingprotocolId=mprAnd3DVolumeViewport) | + + ## About @@ -85,17 +94,14 @@ forking). ### Support -We offer support through -[GitHub Issues](https://github.com/OHIF/Viewers/issues/new/choose). You can: - - [Report a Bug 🐛](https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Community%3A+Report+%3Abug%3A%2CAwaiting+Reproduction&projects=&template=bug-report.yml&title=%5BBug%5D+) - [Request a Feature 🚀](https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Community%3A+Request+%3Ahand%3A&projects=&template=feature-request.yml&title=%5BFeature+Request%5D+) - [Ask a Question 🤗](community.ohif.org) - [Slack Channel](https://join.slack.com/t/cornerstonejs/shared_invite/zt-1r8xb2zau-dOxlD6jit3TN0Uwf928w9Q) For commercial support, academic collaborations, and answers to common -questions; please read our -[documented FAQ](https://docs.ohif.org/faq/index.html#does-ohif-offer-commercial-support). +questions; please use [Get Support](https://ohif.org/get-support/) to contact +us. ## Developing @@ -128,9 +134,6 @@ Here is a schematic representation of our development workflow: - - - ### Requirements - [Yarn 1.17.3+](https://yarnpkg.com/en/docs/install) @@ -164,7 +167,7 @@ yarn install These commands are available from the root directory. Each project directory also supports a number of commands that can be found in their respective -`README.md` and `project.json` files. +`README.md` and `package.json` files. | Yarn Commands | Description | | ---------------------------- | ------------------------------------------------------------- | @@ -222,15 +225,6 @@ you'll see the following: └── README.md # This file ``` -Want to better understand why and how we've structured this repository? Read -more about it in our [Architecture Documentation][ohif-architecture]. - - - -| Name | Description | Links | -| ---------------------------------------------------- | ----------------------------------------------------- | ---------------------- | -| [@ohif/extension-cornerstone][extension-cornerstone] | 2D image viewing, annotation, and segementation tools | [NPM][cornerstone-npm] | - ## Acknowledgments To acknowledge the OHIF Viewer in an academic publication, please cite @@ -259,7 +253,7 @@ or, for v1, please cite: > [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334) **Note:** If you use or find this repository helpful, please take the time to -star this repository on Github. This is an easy way for us to assess adoption +star this repository on GitHub. This is an easy way for us to assess adoption and it can help us obtain future funding for the project. This work is supported primarily by the National Institutes of Health, National @@ -267,7 +261,11 @@ Cancer Institute, Informatics Technology for Cancer Research (ITCR) program, under a [grant to Dr. Gordon Harris at Massachusetts General Hospital (U24 CA199460)](https://projectreporter.nih.gov/project_info_description.cfm?aid=8971104). -This project is tested with BrowserStack. Thank you for supporting open source +[NCI Imaging Data Commons (IDC) project](https://imaging.datacommons.cancer.gov/) supported the development of new features and bug fixes marked with ["IDC:priority"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Apriority), +["IDC:candidate"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acandidate) or ["IDC:collaboration"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acollaboration). NCI Imaging Data Commons is supported by contract number 19X037Q from +Leidos Biomedical Research under Task Order HHSN26100071 from NCI. [IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized version of the OHIF Viewer. + +This project is tested with BrowserStack. Thank you for supporting open-source! ## License diff --git a/babel.config.js b/babel.config.js index d2aad74c27..b55cbfdfa0 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,22 +3,22 @@ const { extendDefaultPlugins } = require('svgo'); module.exports = { babelrcRoots: ['./platform/*', './extensions/*', './modes/*'], - presets: [ - '@babel/preset-env', - '@babel/preset-react', - '@babel/preset-typescript', - ], + presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'], plugins: [ [ 'inline-react-svg', { svgo: { - plugins: extendDefaultPlugins([ + plugins: [ { - name: 'removeViewBox', - active: false, + name: 'preset-default', + params: { + overrides: { + removeViewBox: false, + }, + }, }, - ]), + ], }, }, ], diff --git a/commit.txt b/commit.txt index 39fe445d11..52add76e67 100644 --- a/commit.txt +++ b/commit.txt @@ -1 +1 @@ -1d38fe30a490010c7de487c7a0b1a5bfe3bc75a4 \ No newline at end of file +86f54d0d07042750a863ae876aa8dd5fb16029a5 diff --git a/extensions/_example/src/index.js b/extensions/_example/src/index.js index c4fdb9eb1c..08b3da8360 100644 --- a/extensions/_example/src/index.js +++ b/extensions/_example/src/index.js @@ -54,11 +54,7 @@ const getCommandsModule = () => ({ const ExampleContext = React.createContext(); function ExampleContextProvider({ children }) { - return ( - - {children} - - ); + return {children}; } const getContextModule = () => [ @@ -121,7 +117,7 @@ const getSopClassHandlerModule = (/* ... */) => { const getToolbarModule = () => {}; -// displaySet, viewportIndex, dataSource +// displaySet, dataSource const getViewportModule = () => { const wrappedViewport = props => { return ( diff --git a/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js b/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js index 5182c4a6a9..4ee5cc9fc1 100644 --- a/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js +++ b/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js @@ -3,8 +3,7 @@ const { merge } = require('webpack-merge'); const path = require('path'); const webpackCommon = require('./../../../.webpack/webpack.base.js'); const pkg = require('./../package.json'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin; +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const ROOT_DIR = path.join(__dirname, './..'); const SRC_DIR = path.join(__dirname, '../src'); @@ -38,13 +37,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/cornerstone-dicom-rt/CHANGELOG.md b/extensions/cornerstone-dicom-rt/CHANGELOG.md new file mode 100644 index 0000000000..4067939696 --- /dev/null +++ b/extensions/cornerstone-dicom-rt/CHANGELOG.md @@ -0,0 +1,241 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt diff --git a/extensions/cornerstone-dicom-rt/babel.config.js b/extensions/cornerstone-dicom-rt/babel.config.js index 92fbbdeaf9..a38ddda212 100644 --- a/extensions/cornerstone-dicom-rt/babel.config.js +++ b/extensions/cornerstone-dicom-rt/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/extensions/cornerstone-dicom-rt/package.json b/extensions/cornerstone-dicom-rt/package.json index 8eb9639214..5581fbbbc0 100644 --- a/extensions/cornerstone-dicom-rt/package.json +++ b/extensions/cornerstone-dicom-rt/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-rt", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "DICOM RT read workflow", "author": "OHIF", "license": "MIT", @@ -31,10 +31,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/i18n": "3.6.0", + "@ohif/core": "3.7.0-beta.85", + "@ohif/extension-cornerstone": "3.7.0-beta.85", + "@ohif/extension-default": "3.7.0-beta.85", + "@ohif/i18n": "3.7.0-beta.85", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js index 81ba6aab99..a7a232f198 100644 --- a/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js @@ -7,11 +7,7 @@ const sopClassUids = ['1.2.840.10008.5.1.4.1.1.481.3']; let loadPromises = {}; -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { const instance = instances[0]; const { @@ -56,10 +52,7 @@ function _getDisplaySetsFromSeries( }; let referencedSeriesSequence = instance.ReferencedSeriesSequence; - if ( - instance.ReferencedFrameOfReferenceSequence && - !instance.ReferencedSeriesSequence - ) { + if (instance.ReferencedFrameOfReferenceSequence && !instance.ReferencedSeriesSequence) { instance.ReferencedSeriesSequence = _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence( instance.ReferencedFrameOfReferenceSequence ); @@ -72,8 +65,7 @@ function _getDisplaySetsFromSeries( const referencedSeries = referencedSeriesSequence[0]; - displaySet.referencedImages = - instance.ReferencedSeriesSequence.ReferencedInstanceSequence; + displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence; displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID; displaySet.getReferenceDisplaySet = () => { @@ -88,14 +80,12 @@ function _getDisplaySetsFromSeries( const referencedDisplaySet = referencedDisplaySets[0]; - displaySet.referencedDisplaySetInstanceUID = - referencedDisplaySet.displaySetInstanceUID; + displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; return referencedDisplaySet; }; - displaySet.load = ({ headers }) => - _load(displaySet, servicesManager, extensionManager, headers); + displaySet.load = ({ headers }) => _load(displaySet, servicesManager, extensionManager, headers); return [displaySet]; } @@ -194,11 +184,7 @@ function getSopClassHandlerModule({ servicesManager, extensionManager }) { name: 'dicom-rt', sopClassUids, getDisplaySetsFromSeries: instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }, }, ]; diff --git a/extensions/cornerstone-dicom-rt/src/index.tsx b/extensions/cornerstone-dicom-rt/src/index.tsx index 7d55297767..953c9a7c7d 100644 --- a/extensions/cornerstone-dicom-rt/src/index.tsx +++ b/extensions/cornerstone-dicom-rt/src/index.tsx @@ -4,9 +4,7 @@ import { Types } from '@ohif/core'; import getSopClassHandlerModule from './getSopClassHandlerModule'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport'); }); const OHIFCornerstoneRTViewport = props => { @@ -36,12 +34,14 @@ const extension: Types.Extensions.Extension = { getViewportModule({ servicesManager, extensionManager, + commandsManager, }: Types.Extensions.ExtensionParams) { const ExtendedOHIFCornerstoneRTViewport = props => { return ( ); diff --git a/extensions/cornerstone-dicom-rt/src/loadRTStruct.js b/extensions/cornerstone-dicom-rt/src/loadRTStruct.js index 613c2fb65c..cf58eb40a2 100644 --- a/extensions/cornerstone-dicom-rt/src/loadRTStruct.js +++ b/extensions/cornerstone-dicom-rt/src/loadRTStruct.js @@ -25,23 +25,13 @@ async function checkAndLoadContourData(instance, datasource) { if (Array.isArray(contourData)) { promisesMap.has(referencedROINumber) - ? promisesMap - .get(referencedROINumber) - .push(Promise.resolve(contourData)) - : promisesMap.set(referencedROINumber, [ - Promise.resolve(contourData), - ]); + ? promisesMap.get(referencedROINumber).push(Promise.resolve(contourData)) + : promisesMap.set(referencedROINumber, [Promise.resolve(contourData)]); } else if (contourData && contourData.BulkDataURI) { const bulkDataURI = contourData.BulkDataURI; - if ( - !datasource || - !datasource.retrieve || - !datasource.retrieve.bulkDataURI - ) { - return Promise.reject( - 'Invalid datasource object or retrieve function' - ); + if (!datasource || !datasource.retrieve || !datasource.retrieve.bulkDataURI) { + return Promise.reject('Invalid datasource object or retrieve function'); } const bulkDataPromise = datasource.retrieve.bulkDataURI({ @@ -74,10 +64,7 @@ async function checkAndLoadContourData(instance, datasource) { ROIContour.ContourSequence.forEach((Contour, index) => { const promise = resolvedPromises[index]; if (promise.status === 'fulfilled') { - if ( - Array.isArray(promise.value) && - promise.value.every(Number.isFinite) - ) { + if (Array.isArray(promise.value) && promise.value.every(Number.isFinite)) { // If promise.value is already an array of numbers, use it directly Contour.ContourData = promise.value; } else { @@ -85,13 +72,8 @@ async function checkAndLoadContourData(instance, datasource) { const uint8Array = new Uint8Array(promise.value); const textDecoder = new TextDecoder(); const dataUint8Array = textDecoder.decode(uint8Array); - if ( - typeof dataUint8Array === 'string' && - dataUint8Array.includes('\\') - ) { - Contour.ContourData = dataUint8Array - .split('\\') - .map(parseFloat); + if (typeof dataUint8Array === 'string' && dataUint8Array.includes('\\')) { + Contour.ContourData = dataUint8Array.split('\\').map(parseFloat); } else { Contour.ContourData = []; } @@ -120,9 +102,8 @@ export default async function loadRTStruct( const { bulkDataURI } = dataSource.getConfig?.() || {}; const { dicomLoaderService } = utilityModule.exports; - const imageIdSopInstanceUidPairs = _getImageIdSopInstanceUidPairsForDisplaySet( - referencedDisplaySet - ); + const imageIdSopInstanceUidPairs = + _getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet); // Set here is loading is asynchronous. // If this function throws its set back to false. @@ -137,20 +118,14 @@ export default async function loadRTStruct( ); const dicomData = DicomMessage.readFile(segArrayBuffer); - const rtStructDataset = DicomMetaDictionary.naturalizeDataset( - dicomData.dict - ); + const rtStructDataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict); rtStructDataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta); instance = rtStructDataset; } else { await checkAndLoadContourData(instance, dataSource); } - const { - StructureSetROISequence, - ROIContourSequence, - RTROIObservationsSequence, - } = instance; + const { StructureSetROISequence, ROIContourSequence, RTROIObservationsSequence } = instance; // Define our structure set entry and add it to the rtstruct module state. const structureSet = { @@ -174,19 +149,9 @@ export default async function loadRTStruct( const contourPoints = []; for (let c = 0; c < ContourSequenceArray.length; c++) { - const { - ContourImageSequence, - ContourData, - NumberOfContourPoints, - ContourGeometricType, - } = ContourSequenceArray[c]; - - const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID; - const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID); - - if (!imageId) { - continue; - } + const { ContourImageSequence, ContourData, NumberOfContourPoints, ContourGeometricType } = + ContourSequenceArray[c]; + let isSupported = false; const points = []; @@ -235,9 +200,7 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => { imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID ); - return imageIdSopInstanceUidPairsEntry - ? imageIdSopInstanceUidPairsEntry.imageId - : null; + return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null; }; function _getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet) { @@ -258,8 +221,7 @@ function _setROIContourMetadata( isSupported ) { const StructureSetROI = StructureSetROISequence.find( - structureSetROI => - structureSetROI.ROINumber === ROIContour.ReferencedROINumber + structureSetROI => structureSetROI.ROINumber === ROIContour.ReferencedROINumber ); const ROIContourData = { @@ -299,23 +261,15 @@ function _setROIContourDataColor(ROIContour, ROIContourData) { } } -function _setROIContourRTROIObservations( - ROIContourData, - RTROIObservationsSequence, - ROINumber -) { +function _setROIContourRTROIObservations(ROIContourData, RTROIObservationsSequence, ROINumber) { const RTROIObservations = RTROIObservationsSequence.find( RTROIObservations => RTROIObservations.ReferencedROINumber === ROINumber ); if (RTROIObservations) { // Deep copy so we don't keep the reference to the dcmjs dataset entry. - const { - ObservationNumber, - ROIObservationDescription, - RTROIInterpretedType, - ROIInterpreter, - } = RTROIObservations; + const { ObservationNumber, ROIObservationDescription, RTROIInterpretedType, ROIInterpreter } = + RTROIObservations; ROIContourData.RTROIObservations = { ObservationNumber, diff --git a/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts deleted file mode 100644 index 0668c894b8..0000000000 --- a/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts +++ /dev/null @@ -1,70 +0,0 @@ -async function _hydrateRTDisplaySet({ - rtDisplaySet, - viewportIndex, - servicesManager, -}) { - const { - segmentationService, - hangingProtocolService, - viewportGridService, - } = servicesManager.services; - - const displaySetInstanceUID = rtDisplaySet.referencedDisplaySetInstanceUID; - - let segmentationId = null; - - // We need the hydration to notify panels about the new segmentation added - const suppressEvents = false; - - segmentationId = await segmentationService.createSegmentationForRTDisplaySet( - rtDisplaySet, - segmentationId, - suppressEvents - ); - - segmentationService.hydrateSegmentation(rtDisplaySet.displaySetInstanceUID); - - const { viewports } = viewportGridService.getState(); - - const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, - displaySetInstanceUID - ); - - viewportGridService.setDisplaySetsForViewports(updatedViewports); - - // Todo: fix this after we have a better way for stack viewport segmentations - - // check every viewport in the viewports to see if the displaySetInstanceUID - // is being displayed, if so we need to update the viewport to use volume viewport - // (if already is not using it) since Cornerstone3D currently only supports - // volume viewport for segmentation - viewports.forEach((viewport, index) => { - if (index === viewportIndex) { - return; - } - - const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( - viewport.displaySetInstanceUIDs, - rtDisplaySet.displaySetInstanceUID - ); - - if (shouldDisplaySeg) { - updatedViewports.push({ - viewportIndex: index, - displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, - viewportOptions: { - initialImageOptions: { - preset: 'middle', - }, - }, - }); - } - }); - - // Do the entire update at once - viewportGridService.setDisplaySetsForViewports(updatedViewports); - return true; -} - -export default _hydrateRTDisplaySet; diff --git a/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts b/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts index 826e3b9a6f..f47f0089d8 100644 --- a/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts +++ b/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts @@ -1,12 +1,7 @@ -function createRTToolGroupAndAddTools( - ToolGroupService, - customizationService, - toolGroupId -) { - const { tools } = - customizationService.get('cornerstone.overlayViewportTools') ?? {}; +function createRTToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) { + const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {}; - return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {}); + return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } export default createRTToolGroupAndAddTools; diff --git a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts index 4af78caeeb..91492cbfbe 100644 --- a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts +++ b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts @@ -1,4 +1,4 @@ -import hydrateRTDisplaySet from './_hydrateRT'; +import { ButtonEnums } from '@ohif/ui'; const RESPONSE = { NO_NEVER: -1, @@ -9,21 +9,24 @@ const RESPONSE = { function promptHydrateRT({ servicesManager, rtDisplaySet, - viewportIndex, + viewportId, toolGroupId = 'default', + preHydrateCallbacks, + hydrateRTDisplaySet, }) { const { uiViewportDialogService } = servicesManager.services; - return new Promise(async function(resolve, reject) { - const promptResult = await _askHydrate( - uiViewportDialogService, - viewportIndex - ); + return new Promise(async function (resolve, reject) { + const promptResult = await _askHydrate(uiViewportDialogService, viewportId); if (promptResult === RESPONSE.HYDRATE_SEG) { + preHydrateCallbacks?.forEach(callback => { + callback(); + }); + const isHydrated = await hydrateRTDisplaySet({ rtDisplaySet, - viewportIndex, + viewportId, toolGroupId, servicesManager, }); @@ -33,17 +36,17 @@ function promptHydrateRT({ }); } -function _askHydrate(uiViewportDialogService, viewportIndex) { - return new Promise(function(resolve, reject) { +function _askHydrate(uiViewportDialogService, viewportId) { + return new Promise(function (resolve, reject) { const message = 'Do you want to open this Segmentation?'; const actions = [ { - type: 'secondary', + type: ButtonEnums.type.secondary, text: 'No', value: RESPONSE.CANCEL, }, { - type: 'primary', + type: ButtonEnums.type.primary, text: 'Yes', value: RESPONSE.HYDRATE_SEG, }, @@ -54,7 +57,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, diff --git a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx index 58d190f5a8..e9c7450c69 100644 --- a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx +++ b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx @@ -1,17 +1,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import OHIF, { utils } from '@ohif/core'; -import { - ViewportActionBar, - useViewportGrid, - LoadingIndicatorTotalPercent, -} from '@ohif/ui'; +import { ViewportActionBar, useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui'; -import _hydrateRTdisplaySet from '../utils/_hydrateRT'; import promptHydrateRT from '../utils/promptHydrateRT'; import _getStatusComponent from './_getStatusComponent'; import createRTToolGroupAndAddTools from '../utils/initRTToolGroup'; -import _hydrateRTDisplaySet from '../utils/_hydrateRT'; const { formatDate } = utils; const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup'; @@ -21,10 +15,10 @@ function OHIFCornerstoneRTViewport(props) { children, displaySets, viewportOptions, - viewportIndex, viewportLabel, servicesManager, extensionManager, + commandsManager, } = props; const { @@ -35,7 +29,9 @@ function OHIFCornerstoneRTViewport(props) { customizationService, } = servicesManager.services; - const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportIndex}`; + const viewportId = viewportOptions.viewportId; + + const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportId}`; // RT viewport will always have a single display set if (displaySets.length > 1) { @@ -66,12 +62,10 @@ function OHIFCornerstoneRTViewport(props) { // refs const referencedDisplaySetRef = useRef(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; const referencedDisplaySet = rtDisplaySet.getReferenceDisplaySet(); - const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata( - referencedDisplaySet - ); + const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(referencedDisplaySet); referencedDisplaySetRef.current = { displaySet: referencedDisplaySet, @@ -91,14 +85,27 @@ function OHIFCornerstoneRTViewport(props) { setElement(null); }; + const storePresentationState = useCallback(() => { + viewportGrid?.viewports.forEach(({ viewportId }) => { + commandsManager.runCommand('storePresentation', { + viewportId, + }); + }); + }, [viewportGrid]); + + const hydrateRTDisplaySet = ({ rtDisplaySet, viewportId }) => { + commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', { + displaySets: [rtDisplaySet], + viewportId, + }); + }; + const getCornerstoneViewport = useCallback(() => { const { component: Component } = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.viewportModule.cornerstone' ); - const { - displaySet: referencedDisplaySet, - } = referencedDisplaySetRef.current; + const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current; // Todo: jump to the center of the first segment return ( @@ -115,7 +122,7 @@ function OHIFCornerstoneRTViewport(props) { onElementDisabled={onElementDisabled} > ); - }, [viewportIndex, rtDisplaySet, toolGroupId]); + }, [viewportId, rtDisplaySet, toolGroupId]); const onSegmentChange = useCallback( direction => { @@ -136,11 +143,7 @@ function OHIFCornerstoneRTViewport(props) { newSelectedSegmentIndex = numberOfSegments - 1; } - segmentationService.jumpToSegmentCenter( - segmentationId, - newSelectedSegmentIndex, - toolGroupId - ); + segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, toolGroupId); setSelectedSegment(newSelectedSegmentIndex); }, [selectedSegment] @@ -153,31 +156,29 @@ function OHIFCornerstoneRTViewport(props) { promptHydrateRT({ servicesManager, - viewportIndex, + viewportId, rtDisplaySet, + preHydrateCallbacks: [storePresentationState], + hydrateRTDisplaySet, }).then(isHydrated => { if (isHydrated) { setIsHydrated(true); } }); - }, [servicesManager, viewportIndex, rtDisplaySet, rtIsLoading]); + }, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]); useEffect(() => { const { unsubscribe } = segmentationService.subscribe( segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE, evt => { - if ( - evt.rtDisplaySet.displaySetInstanceUID === - rtDisplaySet.displaySetInstanceUID - ) { + if (evt.rtDisplaySet.displaySetInstanceUID === rtDisplaySet.displaySetInstanceUID) { setRtIsLoading(false); } if (evt.overlappingSegments) { uiNotificationService.show({ title: 'Overlapping Segments', - message: - 'Overlapping segments detected which is not currently supported', + message: 'Overlapping segments detected which is not currently supported', type: 'warning', }); } @@ -212,12 +213,10 @@ function OHIFCornerstoneRTViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; - if ( - displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) - ) { + const activeViewport = viewports.get(activeViewportId); + if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -236,19 +235,13 @@ function OHIFCornerstoneRTViewport(props) { return; } - toolGroup = createRTToolGroupAndAddTools( - toolGroupService, - customizationService, - toolGroupId - ); + toolGroup = createRTToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId); setToolGroupCreated(true); return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); toolGroupService.destroyToolGroup(toolGroupId); }; @@ -259,9 +252,7 @@ function OHIFCornerstoneRTViewport(props) { return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); referencedDisplaySetRef.current = null; }; }, [rtDisplaySet]); @@ -282,7 +273,7 @@ function OHIFCornerstoneRTViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -303,10 +294,16 @@ function OHIFCornerstoneRTViewport(props) { } = referencedDisplaySetRef.current.metadata; const onStatusClick = async () => { - const isHydrated = await _hydrateRTDisplaySet({ + // Before hydrating a RT and make it added to all viewports in the grid + // that share the same frameOfReferenceUID, we need to store the viewport grid + // presentation state, so that we can restore it after hydrating the RT. This is + // required if the user has changed the viewport (other viewport than RT viewport) + // presentation state (w/l and invert) and then opens the RT. If we don't store + // the presentation state, the viewport will be reset to the default presentation + storePresentationState(); + const isHydrated = await hydrateRTDisplaySet({ rtDisplaySet, - viewportIndex, - servicesManager, + viewportId, }); setIsHydrated(isHydrated); @@ -333,26 +330,22 @@ function OHIFCornerstoneRTViewport(props) { currentSeries: SeriesNumber, seriesDescription: `RT Viewport ${SeriesDescription}`, patientInformation: { - patientName: PatientName - ? OHIF.utils.formatPN(PatientName.Alphabetic) - : '', + patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '', patientSex: PatientSex || '', patientAge: PatientAge || '', MRN: PatientID || '', thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', spacing: - SpacingBetweenSlices !== undefined - ? `${SpacingBetweenSlices.toFixed(2)}mm` - : '', + SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` : '', scanner: ManufacturerModelName || '', }, }} /> -
+
{rtIsLoading && ( ; - ToolTipMessage = () => ( -
This Segmentation is loaded in the segmentation panel
- ); + ToolTipMessage = () =>
This Segmentation is loaded in the segmentation panel
; break; case false: - StatusIcon = () => ; + StatusIcon = () => ( + + ); ToolTipMessage = () =>
Click LOAD to load RTSTRUCT.
; } const StatusArea = () => ( -
-
+
+
RTSTRUCT
{!isHydrated && (
@@ -44,7 +47,10 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) { return ( <> {ToolTipMessage && ( - } position="bottom-left"> + } + position="bottom-left" + > )} diff --git a/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js b/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js index 017e4bbf01..3f6eb4b69e 100644 --- a/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js +++ b/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js @@ -40,21 +40,15 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, }), - // new MiniCssExtractPlugin({ - // filename: `./dist/${outputName}.css`, - // chunkFilename: `./dist/${outputName}.css`, - // }), + new MiniCssExtractPlugin({ + filename: `./dist/${outputName}.css`, + chunkFilename: `./dist/${outputName}.css`, + }), ], }); }; diff --git a/extensions/cornerstone-dicom-seg/CHANGELOG.md b/extensions/cornerstone-dicom-seg/CHANGELOG.md new file mode 100644 index 0000000000..8e065c97d8 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/CHANGELOG.md @@ -0,0 +1,242 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) +* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg diff --git a/extensions/cornerstone-dicom-seg/babel.config.js b/extensions/cornerstone-dicom-seg/babel.config.js index 92fbbdeaf9..a38ddda212 100644 --- a/extensions/cornerstone-dicom-seg/babel.config.js +++ b/extensions/cornerstone-dicom-seg/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json index cc7eafdcac..b0f3021093 100644 --- a/extensions/cornerstone-dicom-seg/package.json +++ b/extensions/cornerstone-dicom-seg/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-seg", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "DICOM SEG read workflow", "author": "OHIF", "license": "MIT", @@ -31,10 +31,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/i18n": "3.6.0", + "@ohif/core": "3.7.0-beta.85", + "@ohif/extension-cornerstone": "3.7.0-beta.85", + "@ohif/extension-default": "3.7.0-beta.85", + "@ohif/i18n": "3.7.0-beta.85", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -44,6 +44,7 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", + "@cornerstonejs/tools": "^1.16.4", "react-color": "^2.19.3" } } diff --git a/extensions/cornerstone-dicom-seg/src/commandsModule.ts b/extensions/cornerstone-dicom-seg/src/commandsModule.ts new file mode 100644 index 0000000000..7cffb83f76 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/commandsModule.ts @@ -0,0 +1,383 @@ +import dcmjs from 'dcmjs'; +import { createReportDialogPrompt } from '@ohif/extension-default'; +import { ServicesManager, Types } from '@ohif/core'; +import { cache, metaData } from '@cornerstonejs/core'; +import { segmentation as cornerstoneToolsSegmentation } from '@cornerstonejs/tools'; +import { adaptersSEG, helpers } from '@cornerstonejs/adapters'; +import { DicomMetadataStore } from '@ohif/core'; + +import { + updateViewportsForSegmentationRendering, + getUpdatedViewportsForSegmentation, + getTargetViewport, +} from './utils/hydrationUtils'; + +const { + Cornerstone3D: { + Segmentation: { generateLabelMaps2DFrom3D, generateSegmentation }, + }, +} = adaptersSEG; + +const { downloadDICOMData } = helpers; + +const commandsModule = ({ + servicesManager, + extensionManager, +}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => { + const { + uiNotificationService, + segmentationService, + uiDialogService, + displaySetService, + viewportGridService, + } = (servicesManager as ServicesManager).services; + + const actions = { + /** + * Retrieves a list of viewports that require updates in preparation for segmentation rendering. + * This function evaluates viewports based on their compatibility with the provided segmentation's + * frame of reference UID and appends them to the updated list if they should render the segmentation. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - the ID of the viewport to be updated. + * @param params.servicesManager - The services manager + * @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance. + * + * @returns {Array} Returns an array of viewports that require updates for segmentation rendering. + */ + getUpdatedViewportsForSegmentation, + /** + * Creates an empty segmentation for a specified viewport. + * It first checks if the display set associated with the viewport is reconstructable. + * If not, it raises a notification error. Otherwise, it creates a new segmentation + * for the display set after handling the necessary steps for making the viewport + * a volume viewport first + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - the target viewport ID. + * + */ + createEmptySegmentationForViewport: async ({ viewportId }) => { + const viewport = getTargetViewport({ viewportId, viewportGridService }); + // Todo: add support for multiple display sets + const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0]; + + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + + if (!displaySet.isReconstructable) { + uiNotificationService.show({ + title: 'Segmentation', + message: 'Segmentation is not supported for non-reconstructible displaysets yet', + type: 'error', + }); + return; + } + + updateViewportsForSegmentationRendering({ + viewportId, + servicesManager, + loadFn: async () => { + const currentSegmentations = segmentationService.getSegmentations(); + const segmentationId = await segmentationService.createSegmentationForDisplaySet( + displaySetInstanceUID, + { label: `Segmentation ${currentSegmentations.length + 1}` } + ); + + const toolGroupId = viewport.viewportOptions.toolGroupId; + + await segmentationService.addSegmentationRepresentationToToolGroup( + toolGroupId, + segmentationId + ); + + // Add only one segment for now + segmentationService.addSegment(segmentationId, { + toolGroupId, + segmentIndex: 1, + properties: { + label: 'Segment 1', + }, + }); + + return segmentationId; + }, + }); + }, + /** + * Loads segmentations for a specified viewport. + * The function prepares the viewport for rendering, then loads the segmentation details. + * Additionally, if the segmentation has scalar data, it is set for the corresponding label map volume. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentations - Array of segmentations to be loaded. + * @param params.viewportId - the target viewport ID. + * + */ + loadSegmentationsForViewport: async ({ segmentations, viewportId }) => { + updateViewportsForSegmentationRendering({ + viewportId, + servicesManager, + loadFn: async () => { + // Todo: handle adding more than one segmentation + const viewport = getTargetViewport({ viewportId, viewportGridService }); + const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0]; + + const segmentation = segmentations[0]; + const segmentationId = segmentation.id; + const label = segmentation.label; + const segments = segmentation.segments; + + delete segmentation.segments; + + await segmentationService.createSegmentationForDisplaySet(displaySetInstanceUID, { + segmentationId, + label, + }); + + if (segmentation.scalarData) { + const labelmapVolume = segmentationService.getLabelmapVolume(segmentationId); + labelmapVolume.scalarData.set(segmentation.scalarData); + } + + segmentationService.addOrUpdateSegmentation(segmentation); + + const toolGroupId = viewport.viewportOptions.toolGroupId; + await segmentationService.addSegmentationRepresentationToToolGroup( + toolGroupId, + segmentationId + ); + + segments.forEach(segment => { + if (segment === null) { + return; + } + segmentationService.addSegment(segmentationId, { + segmentIndex: segment.segmentIndex, + toolGroupId, + properties: { + color: segment.color, + label: segment.label, + opacity: segment.opacity, + isLocked: segment.isLocked, + visibility: segment.isVisible, + active: segmentation.activeSegmentIndex === segment.segmentIndex, + }, + }); + }); + + if (segmentation.centroidsIJK) { + segmentationService.setCentroids(segmentation.id, segmentation.centroidsIJK); + } + + return segmentationId; + }, + }); + }, + /** + * Loads segmentation display sets for a specified viewport. + * Depending on the modality of the display set (SEG or RTSTRUCT), + * it chooses the appropriate service function to create + * the segmentation for the display set. + * The function then prepares the viewport for rendering segmentation. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - ID of the viewport where the segmentation display sets should be loaded. + * @param params.displaySets - Array of display sets to be loaded for segmentation. + * + */ + loadSegmentationDisplaySetsForViewport: async ({ viewportId, displaySets }) => { + // Todo: handle adding more than one segmentation + const displaySet = displaySets[0]; + + updateViewportsForSegmentationRendering({ + viewportId, + servicesManager, + referencedDisplaySetInstanceUID: displaySet.referencedDisplaySetInstanceUID, + loadFn: async () => { + const segDisplaySet = displaySet; + const suppressEvents = false; + const serviceFunction = + segDisplaySet.Modality === 'SEG' + ? 'createSegmentationForSEGDisplaySet' + : 'createSegmentationForRTDisplaySet'; + + const boundFn = segmentationService[serviceFunction].bind(segmentationService); + const segmentationId = await boundFn(segDisplaySet, null, suppressEvents); + + return segmentationId; + }, + }); + }, + /** + * Generates a segmentation from a given segmentation ID. + * This function retrieves the associated segmentation and + * its referenced volume, extracts label maps from the + * segmentation volume, and produces segmentation data + * alongside associated metadata. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentationId - ID of the segmentation to be generated. + * @param params.options - Optional configuration for the generation process. + * + * @returns Returns the generated segmentation data. + */ + generateSegmentation: ({ segmentationId, options = {} }) => { + const segmentation = cornerstoneToolsSegmentation.state.getSegmentation(segmentationId); + + const { referencedVolumeId } = segmentation.representationData.LABELMAP; + + const segmentationVolume = cache.getVolume(segmentationId); + const referencedVolume = cache.getVolume(referencedVolumeId); + const referencedImages = referencedVolume.getCornerstoneImages(); + + const labelmapObj = generateLabelMaps2DFrom3D(segmentationVolume); + + // Generate fake metadata as an example + labelmapObj.metadata = []; + + const segmentationInOHIF = segmentationService.getSegmentation(segmentationId); + labelmapObj.segmentsOnLabelmap.forEach(segmentIndex => { + // segmentation service already has a color for each segment + const segment = segmentationInOHIF?.segments[segmentIndex]; + const { label, color } = segment; + + const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB( + color.slice(0, 3).map(value => value / 255) + ).map(value => Math.round(value)); + + const segmentMetadata = { + SegmentNumber: segmentIndex.toString(), + SegmentLabel: label, + SegmentAlgorithmType: 'MANUAL', + SegmentAlgorithmName: 'OHIF Brush', + RecommendedDisplayCIELabValue, + SegmentedPropertyCategoryCodeSequence: { + CodeValue: 'T-D0050', + CodingSchemeDesignator: 'SRT', + CodeMeaning: 'Tissue', + }, + SegmentedPropertyTypeCodeSequence: { + CodeValue: 'T-D0050', + CodingSchemeDesignator: 'SRT', + CodeMeaning: 'Tissue', + }, + }; + labelmapObj.metadata[segmentIndex] = segmentMetadata; + }); + + const generatedSegmentation = generateSegmentation( + referencedImages, + labelmapObj, + metaData, + options + ); + + return generatedSegmentation; + }, + /** + * Downloads a segmentation based on the provided segmentation ID. + * This function retrieves the associated segmentation and + * uses it to generate the corresponding DICOM dataset, which + * is then downloaded with an appropriate filename. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentationId - ID of the segmentation to be downloaded. + * + */ + downloadSegmentation: ({ segmentationId }) => { + const segmentationInOHIF = segmentationService.getSegmentation(segmentationId); + const generatedSegmentation = actions.generateSegmentation({ + segmentationId, + }); + + downloadDICOMData(generatedSegmentation.dataset, `${segmentationInOHIF.label}`); + }, + /** + * Stores a segmentation based on the provided segmentationId into a specified data source. + * The SeriesDescription is derived from user input or defaults to the segmentation label, + * and in its absence, defaults to 'Research Derived Series'. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentationId - ID of the segmentation to be stored. + * @param params.dataSource - Data source where the generated segmentation will be stored. + * + * @returns {Object|void} Returns the naturalized report if successfully stored, + * otherwise throws an error. + */ + storeSegmentation: async ({ segmentationId, dataSource }) => { + const promptResult = await createReportDialogPrompt(uiDialogService, { + extensionManager, + }); + + if (promptResult.action !== 1 && promptResult.value) { + return; + } + + const segmentation = segmentationService.getSegmentation(segmentationId); + + if (!segmentation) { + throw new Error('No segmentation found'); + } + + const { label } = segmentation; + const SeriesDescription = promptResult.value || label || 'Research Derived Series'; + + const generatedData = actions.generateSegmentation({ + segmentationId, + options: { + SeriesDescription, + }, + }); + + if (!generatedData || !generatedData.dataset) { + throw new Error('Error during segmentation generation'); + } + + const { dataset: naturalizedReport } = generatedData; + + await dataSource.store.dicom(naturalizedReport); + + // The "Mode" route listens for DicomMetadataStore changes + // When a new instance is added, it listens and + // automatically calls makeDisplaySets + + // add the information for where we stored it to the instance as well + naturalizedReport.wadoRoot = dataSource.getConfig().wadoRoot; + + DicomMetadataStore.addInstances([naturalizedReport], true); + + return naturalizedReport; + }, + }; + + const definitions = { + getUpdatedViewportsForSegmentation: { + commandFn: actions.getUpdatedViewportsForSegmentation, + }, + loadSegmentationDisplaySetsForViewport: { + commandFn: actions.loadSegmentationDisplaySetsForViewport, + }, + loadSegmentationsForViewport: { + commandFn: actions.loadSegmentationsForViewport, + }, + createEmptySegmentationForViewport: { + commandFn: actions.createEmptySegmentationForViewport, + }, + generateSegmentation: { + commandFn: actions.generateSegmentation, + }, + downloadSegmentation: { + commandFn: actions.downloadSegmentation, + }, + storeSegmentation: { + commandFn: actions.storeSegmentation, + }, + }; + + return { + actions, + definitions, + }; +}; + +export default commandsModule; diff --git a/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts b/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts index 0a2f888e4f..f61136245d 100644 --- a/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts @@ -5,7 +5,6 @@ const segProtocol: Types.HangingProtocol.Protocol = { // Don't store this hanging protocol as it applies to the currently active // display set by default // cacheId: null, - hasUpdatedPriorsInformation: false, name: 'Segmentations', // Just apply this one when specifically listed protocolMatchingRules: [], diff --git a/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx b/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx new file mode 100644 index 0000000000..e626c87d7b --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx @@ -0,0 +1,70 @@ +import React from 'react'; + +import { useAppConfig } from '@state'; +import PanelSegmentation from './panels/PanelSegmentation'; +import SegmentationToolbox from './panels/SegmentationToolbox'; + +const getPanelModule = ({ commandsManager, servicesManager, extensionManager, configuration }) => { + const { customizationService } = servicesManager.services; + + const wrappedPanelSegmentation = configuration => { + const [appConfig] = useAppConfig(); + + const disableEditingForMode = customizationService.get('segmentation.disableEditing'); + + return ( + + ); + }; + + const wrappedPanelSegmentationWithTools = configuration => { + const [appConfig] = useAppConfig(); + return ( + <> + + + + ); + }; + + return [ + { + name: 'panelSegmentation', + iconName: 'tab-segmentation', + iconLabel: 'Segmentation', + label: 'Segmentation', + component: wrappedPanelSegmentation, + }, + { + name: 'panelSegmentationWithTools', + iconName: 'tab-segmentation', + iconLabel: 'Segmentation', + label: 'Segmentation', + component: wrappedPanelSegmentationWithTools, + }, + ]; +}; + +export default getPanelModule; diff --git a/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js index b92da53829..6f7f06e4bd 100644 --- a/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js @@ -1,21 +1,15 @@ -import vtkMath from '@kitware/vtk.js/Common/Core/Math'; - import { utils } from '@ohif/core'; +import { metaData, cache, triggerEvent, eventTarget } from '@cornerstonejs/core'; +import { adaptersSEG, Enums } from '@cornerstonejs/adapters'; import { SOPClassHandlerId } from './id'; -import dcmjs from 'dcmjs'; - -const { DicomMessage, DicomMetaDictionary } = dcmjs.data; +import { dicomlabToRGB } from './utils/dicomlabToRGB'; const sopClassUids = ['1.2.840.10008.5.1.4.1.1.66.4']; let loadPromises = {}; -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { const instance = instances[0]; const { @@ -66,10 +60,9 @@ function _getDisplaySetsFromSeries( throw new Error('ReferencedSeriesSequence is missing for the SEG'); } - const referencedSeries = referencedSeriesSequence[0]; + const referencedSeries = referencedSeriesSequence[0] || referencedSeriesSequence; - displaySet.referencedImages = - instance.ReferencedSeriesSequence.ReferencedInstanceSequence; + displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence; displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID; displaySet.getReferenceDisplaySet = () => { @@ -84,8 +77,7 @@ function _getDisplaySetsFromSeries( const referencedDisplaySet = referencedDisplaySets[0]; - displaySet.referencedDisplaySetInstanceUID = - referencedDisplaySet.displaySetInstanceUID; + displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; // Todo: this needs to be able to work with other reference volumes (other than streaming) such as nifti, etc. displaySet.referencedVolumeURI = referencedDisplaySet.displaySetInstanceUID; @@ -118,17 +110,13 @@ function _load(segDisplaySet, servicesManager, extensionManager, headers) { // We don't want to fire multiple loads, so we'll wait for the first to finish // and also return the same promise to any other callers. loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => { - if ( - !segDisplaySet.segments || - Object.keys(segDisplaySet.segments).length === 0 - ) { - const segments = await _loadSegments( + if (!segDisplaySet.segments || Object.keys(segDisplaySet.segments).length === 0) { + await _loadSegments({ extensionManager, + servicesManager, segDisplaySet, - headers - ); - - segDisplaySet.segments = segments; + headers, + }); } const suppressEvents = true; @@ -147,152 +135,61 @@ function _load(segDisplaySet, servicesManager, extensionManager, headers) { return loadPromises[SOPInstanceUID]; } -async function _loadSegments(extensionManager, segDisplaySet, headers) { +async function _loadSegments({ extensionManager, servicesManager, segDisplaySet, headers }) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.common' ); + const { segmentationService } = servicesManager.services; + const { dicomLoaderService } = utilityModule.exports; - const segArrayBuffer = await dicomLoaderService.findDicomDataPromise( - segDisplaySet, - null, - headers - ); + const arrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers); - const dicomData = DicomMessage.readFile(segArrayBuffer); - const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict); - dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta); + const cachedReferencedVolume = cache.getVolume(segDisplaySet.referencedVolumeId); - if (!Array.isArray(dataset.SegmentSequence)) { - dataset.SegmentSequence = [dataset.SegmentSequence]; + if (!cachedReferencedVolume) { + throw new Error( + 'Referenced Volume is missing for the SEG, and stack viewport SEG is not supported yet' + ); } - const segments = _getSegments(dataset); - return segments; -} + const { imageIds } = cachedReferencedVolume; -function _segmentationExists(segDisplaySet, segmentationService) { - // This should be abstracted with the CornerstoneCacheService - return segmentationService.getSegmentation( - segDisplaySet.displaySetInstanceUID - ); -} + // Todo: what should be defaults here + const tolerance = 0.001; + const skipOverlapping = true; -function _getPixelData(dataset, segments) { - let frameSize = Math.ceil((dataset.Rows * dataset.Columns) / 8); - let nextOffset = 0; - - Object.keys(segments).forEach(segmentKey => { - const segment = segments[segmentKey]; - segment.numberOfFrames = segment.functionalGroups.length; - segment.size = segment.numberOfFrames * frameSize; - segment.offset = nextOffset; - nextOffset = segment.offset + segment.size; - const packedSegment = dataset.PixelData[0].slice( - segment.offset, - nextOffset - ); - - segment.pixelData = dcmjs.data.BitArray.unpack(packedSegment); - segment.geometry = geometryFromFunctionalGroups( - dataset, - segment.functionalGroups - ); + eventTarget.addEventListener(Enums.Events.SEGMENTATION_LOAD_PROGRESS, evt => { + const { percentComplete } = evt.detail; + segmentationService._broadcastEvent(segmentationService.EVENTS.SEGMENT_LOADING_COMPLETE, { + percentComplete, + }); }); - return segments; -} - -function geometryFromFunctionalGroups(dataset, perFrame) { - let pixelMeasures = - dataset.SharedFunctionalGroupsSequence.PixelMeasuresSequence; - let planeOrientation = - dataset.SharedFunctionalGroupsSequence.PlaneOrientationSequence; - let planePosition = perFrame[0].PlanePositionSequence; // TODO: assume sorted frames! - - const geometry = {}; - - // NB: DICOM PixelSpacing is defined as Row then Column, - // unlike ImageOrientationPatient - let spacingBetweenSlices = pixelMeasures.SpacingBetweenSlices; - if (!spacingBetweenSlices) { - if (pixelMeasures.SliceThickness) { - console.log('Using SliceThickness as SpacingBetweenSlices'); - spacingBetweenSlices = pixelMeasures.SliceThickness; - } - } - geometry.spacing = [ - pixelMeasures.PixelSpacing[1], - pixelMeasures.PixelSpacing[0], - spacingBetweenSlices, - ].map(Number); - - geometry.dimensions = [dataset.Columns, dataset.Rows, perFrame.length].map( - Number - ); - - let orientation = planeOrientation.ImageOrientationPatient.map(Number); - const columnStepToPatient = orientation.slice(0, 3); - const rowStepToPatient = orientation.slice(3, 6); - geometry.planeNormal = []; - vtkMath.cross(columnStepToPatient, rowStepToPatient, geometry.planeNormal); - - let firstPosition = perFrame[0].PlanePositionSequence.ImagePositionPatient.map( - Number + const results = await adaptersSEG.Cornerstone3D.Segmentation.generateToolState( + imageIds, + arrayBuffer, + metaData, + { skipOverlapping, tolerance, eventTarget, triggerEvent } ); - let lastPosition = perFrame[ - perFrame.length - 1 - ].PlanePositionSequence.ImagePositionPatient.map(Number); - geometry.sliceStep = []; - vtkMath.subtract(lastPosition, firstPosition, geometry.sliceStep); - vtkMath.normalize(geometry.sliceStep); - geometry.direction = columnStepToPatient - .concat(rowStepToPatient) - .concat(geometry.sliceStep); - geometry.origin = planePosition.ImagePositionPatient.map(Number); - - return geometry; -} - -function _getSegments(dataset) { - const segments = {}; - dataset.SegmentSequence.forEach(segment => { - const cielab = segment.RecommendedDisplayCIELabValue; - const rgba = dcmjs.data.Colors.dicomlab2RGB(cielab).map(x => - Math.round(x * 255) - ); - - rgba.push(255); - const segmentNumber = segment.SegmentNumber; - - segments[segmentNumber] = { - color: rgba, - functionalGroups: [], - offset: null, - size: null, - pixelData: null, - label: segment.SegmentLabel, - }; + results.segMetadata.data.forEach((data, i) => { + if (i > 0) { + data.rgba = dicomlabToRGB(data.RecommendedDisplayCIELabValue); + } }); - // make a list of functional groups per segment - dataset.PerFrameFunctionalGroupsSequence.forEach(functionalGroup => { - const segmentNumber = - functionalGroup.SegmentIdentificationSequence.ReferencedSegmentNumber; - segments[segmentNumber].functionalGroups.push(functionalGroup); - }); + Object.assign(segDisplaySet, results); +} - return _getPixelData(dataset, segments); +function _segmentationExists(segDisplaySet, segmentationService) { + // This should be abstracted with the CornerstoneCacheService + return segmentationService.getSegmentation(segDisplaySet.displaySetInstanceUID); } function getSopClassHandlerModule({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return [ diff --git a/extensions/cornerstone-dicom-seg/src/index.tsx b/extensions/cornerstone-dicom-seg/src/index.tsx index 04ce5f98cb..bb6a6d4b11 100644 --- a/extensions/cornerstone-dicom-seg/src/index.tsx +++ b/extensions/cornerstone-dicom-seg/src/index.tsx @@ -1,16 +1,14 @@ import { id } from './id'; import React from 'react'; -import { Types } from '@ohif/core'; - import getSopClassHandlerModule from './getSopClassHandlerModule'; -import PanelSegmentation from './panels/PanelSegmentation'; import getHangingProtocolModule from './getHangingProtocolModule'; +import getPanelModule from './getPanelModule'; +import getCommandsModule from './commandsModule'; +import preRegistration from './init'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport'); }); const OHIFCornerstoneSEGViewport = props => { @@ -30,6 +28,7 @@ const extension = { * You ID can be anything you want, but it should be unique. */ id, + preRegistration, /** * PanelModule should provide a list of panels that will be available in OHIF @@ -37,31 +36,8 @@ const extension = { * iconName, iconLabel, label, component} object. Example of a panel module * is the StudyBrowserPanel that is provided by the default extension in OHIF. */ - getPanelModule: ({ - servicesManager, - commandsManager, - extensionManager, - }): Types.Panel[] => { - const wrappedPanelSegmentation = () => { - return ( - - ); - }; - - return [ - { - name: 'panelSegmentation', - iconName: 'tab-segmentation', - iconLabel: 'Segmentation', - label: 'Segmentation', - component: wrappedPanelSegmentation, - }, - ]; - }, + getPanelModule, + getCommandsModule, getViewportModule({ servicesManager, extensionManager }) { const ExtendedOHIFCornerstoneSEGViewport = props => { @@ -69,14 +45,13 @@ const extension = { ); }; - return [ - { name: 'dicom-seg', component: ExtendedOHIFCornerstoneSEGViewport }, - ]; + return [{ name: 'dicom-seg', component: ExtendedOHIFCornerstoneSEGViewport }]; }, /** * SopClassHandlerModule should provide a list of sop class handlers that will be @@ -88,4 +63,4 @@ const extension = { getHangingProtocolModule, }; -export default extension; \ No newline at end of file +export default extension; diff --git a/extensions/cornerstone-dicom-seg/src/init.ts b/extensions/cornerstone-dicom-seg/src/init.ts new file mode 100644 index 0000000000..9702aa570b --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/init.ts @@ -0,0 +1,5 @@ +import { addTool, BrushTool } from '@cornerstonejs/tools'; + +export default function init({ configuration = {} }): void { + addTool(BrushTool); +} diff --git a/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx b/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx index bc645652a7..d17c0397b9 100644 --- a/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx +++ b/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx @@ -1,48 +1,28 @@ +import { createReportAsync } from '@ohif/extension-default'; import React, { useEffect, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { SegmentationGroupTable } from '@ohif/ui'; -import callInputDialog from './callInputDialog'; +import callInputDialog from './callInputDialog'; +import callColorPickerDialog from './colorPickerDialog'; import { useTranslation } from 'react-i18next'; export default function PanelSegmentation({ servicesManager, commandsManager, + extensionManager, + configuration, }) { - const { segmentationService, uiDialogService } = servicesManager.services; + const { segmentationService, viewportGridService, uiDialogService } = servicesManager.services; const { t } = useTranslation('PanelSegmentation'); + const [selectedSegmentationId, setSelectedSegmentationId] = useState(null); const [segmentationConfiguration, setSegmentationConfiguration] = useState( segmentationService.getConfiguration() ); - const [segmentations, setSegmentations] = useState(() => - segmentationService.getSegmentations() - ); - - const [isMinimized, setIsMinimized] = useState({}); - - const onToggleMinimizeSegmentation = useCallback( - id => { - setIsMinimized(prevState => ({ - ...prevState, - [id]: !prevState[id], - })); - }, - [setIsMinimized] - ); - - // Only expand the last segmentation added to the list and collapse the rest - useEffect(() => { - const lastSegmentationId = segmentations[segmentations.length - 1]?.id; - if (lastSegmentationId) { - setIsMinimized(prevState => ({ - ...prevState, - [lastSegmentationId]: false, - })); - } - }, [segmentations, setIsMinimized]); + const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations()); useEffect(() => { // ~~ Subscription @@ -67,6 +47,16 @@ export default function PanelSegmentation({ }; }, []); + const getToolGroupIds = segmentationId => { + const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId); + + return toolGroupIds; + }; + + const onSegmentationAdd = async () => { + commandsManager.runCommand('createEmptySegmentationForViewport'); + }; + const onSegmentationClick = (segmentationId: string) => { segmentationService.setActiveSegmentationForToolGroup(segmentationId); }; @@ -75,33 +65,19 @@ export default function PanelSegmentation({ segmentationService.remove(segmentationId); }; - const getToolGroupIds = segmentationId => { - const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation( - segmentationId - ); - - return toolGroupIds; + const onSegmentAdd = segmentationId => { + segmentationService.addSegment(segmentationId); }; const onSegmentClick = (segmentationId, segmentIndex) => { - segmentationService.setActiveSegmentForSegmentation( - segmentationId, - segmentIndex - ); + segmentationService.setActiveSegment(segmentationId, segmentIndex); const toolGroupIds = getToolGroupIds(segmentationId); toolGroupIds.forEach(toolGroupId => { // const toolGroupId = - segmentationService.setActiveSegmentationForToolGroup( - segmentationId, - toolGroupId - ); - segmentationService.jumpToSegmentCenter( - segmentationId, - segmentIndex, - toolGroupId - ); + segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId); + segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex, toolGroupId); }); }; @@ -116,11 +92,7 @@ export default function PanelSegmentation({ return; } - segmentationService.setSegmentLabelForSegmentation( - segmentationId, - segmentIndex, - label - ); + segmentationService.setSegmentLabel(segmentationId, segmentIndex, label); }); }; @@ -145,16 +117,34 @@ export default function PanelSegmentation({ }; const onSegmentColorClick = (segmentationId, segmentIndex) => { - // Todo: Implement color picker later - return; + const segmentation = segmentationService.getSegmentation(segmentationId); + + const segment = segmentation.segments[segmentIndex]; + const { color, opacity } = segment; + + const rgbaColor = { + r: color[0], + g: color[1], + b: color[2], + a: opacity / 255.0, + }; + + callColorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => { + if (actionId === 'cancel') { + return; + } + + segmentationService.setSegmentRGBAColor(segmentationId, segmentIndex, [ + newRgbaColor.r, + newRgbaColor.g, + newRgbaColor.b, + newRgbaColor.a * 255.0, + ]); + }); }; const onSegmentDelete = (segmentationId, segmentIndex) => { - // segmentationService.removeSegmentFromSegmentation( - // segmentationId, - // segmentIndex - // ); - console.warn('not implemented yet'); + segmentationService.removeSegment(segmentationId, segmentIndex); }; const onToggleSegmentVisibility = (segmentationId, segmentIndex) => { @@ -174,6 +164,10 @@ export default function PanelSegmentation({ }); }; + const onToggleSegmentLock = (segmentationId, segmentIndex) => { + segmentationService.toggleSegmentLocked(segmentationId, segmentIndex); + }; + const onToggleSegmentationVisibility = segmentationId => { segmentationService.toggleSegmentationVisibility(segmentationId); }; @@ -188,47 +182,71 @@ export default function PanelSegmentation({ [segmentationService] ); + const onSegmentationDownload = segmentationId => { + commandsManager.runCommand('downloadSegmentation', { + segmentationId, + }); + }; + + const storeSegmentation = async segmentationId => { + const datasources = extensionManager.getActiveDataSource(); + + const displaySetInstanceUIDs = await createReportAsync({ + servicesManager, + getReport: () => + commandsManager.runCommand('storeSegmentation', { + segmentationId, + dataSource: datasources[0], + }), + reportType: 'Segmentation', + }); + + // Show the exported report in the active viewport as read only (similar to SR) + if (displaySetInstanceUIDs) { + // clear the segmentation that we exported, similar to the storeMeasurement + // where we remove the measurements and prompt again the user if they would like + // to re-read the measurements in a SR read only viewport + segmentationService.remove(segmentationId); + + viewportGridService.setDisplaySetsForViewport({ + viewportId: viewportGridService.getActiveViewportId(), + displaySetInstanceUIDs, + }); + } + }; + return ( -
- {/* show segmentation table */} - {segmentations?.length ? ( + <> +
- _setSegmentationConfiguration( - selectedSegmentationId, - 'renderOutline', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value) } setOutlineOpacityActive={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'outlineOpacity', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value) } setRenderFill={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'renderFill', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value) } setRenderInactiveSegmentations={value => _setSegmentationConfiguration( @@ -238,29 +256,17 @@ export default function PanelSegmentation({ ) } setOutlineWidthActive={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'outlineWidthActive', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value) } setFillAlpha={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'fillAlpha', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value) } setFillAlphaInactive={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'fillAlphaInactive', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value) } /> - ) : null} -
+
+ ); } diff --git a/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx b/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx new file mode 100644 index 0000000000..674ae3d2e6 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx @@ -0,0 +1,405 @@ +import React, { useCallback, useEffect, useState, useReducer } from 'react'; +import { AdvancedToolbox, InputDoubleRange, useViewportGrid } from '@ohif/ui'; +import { Types } from '@ohif/extension-cornerstone'; +import { utilities } from '@cornerstonejs/tools'; + +const { segmentation: segmentationUtils } = utilities; + +const TOOL_TYPES = { + CIRCULAR_BRUSH: 'CircularBrush', + SPHERE_BRUSH: 'SphereBrush', + CIRCULAR_ERASER: 'CircularEraser', + SPHERE_ERASER: 'SphereEraser', + CIRCLE_SCISSOR: 'CircleScissor', + RECTANGLE_SCISSOR: 'RectangleScissor', + SPHERE_SCISSOR: 'SphereScissor', + THRESHOLD_CIRCULAR_BRUSH: 'ThresholdCircularBrush', + THRESHOLD_SPHERE_BRUSH: 'ThresholdSphereBrush', +}; + +const ACTIONS = { + SET_TOOL_CONFIG: 'SET_TOOL_CONFIG', + SET_ACTIVE_TOOL: 'SET_ACTIVE_TOOL', +}; + +const initialState = { + Brush: { + brushSize: 15, + mode: 'CircularBrush', // Can be 'CircularBrush' or 'SphereBrush' + }, + Eraser: { + brushSize: 15, + mode: 'CircularEraser', // Can be 'CircularEraser' or 'SphereEraser' + }, + Scissors: { + brushSize: 15, + mode: 'CircleScissor', // E.g., 'CircleScissor', 'RectangleScissor', or 'SphereScissor' + }, + ThresholdBrush: { + brushSize: 15, + thresholdRange: [-500, 500], + }, + activeTool: null, +}; + +function toolboxReducer(state, action) { + switch (action.type) { + case ACTIONS.SET_TOOL_CONFIG: + const { tool, config } = action.payload; + return { + ...state, + [tool]: { + ...state[tool], + ...config, + }, + }; + case ACTIONS.SET_ACTIVE_TOOL: + return { ...state, activeTool: action.payload }; + default: + return state; + } +} + +function SegmentationToolbox({ servicesManager, extensionManager }) { + const { toolbarService, segmentationService, toolGroupService } = + servicesManager.services as Types.CornerstoneServices; + + const [viewportGrid] = useViewportGrid(); + const { viewports, activeViewportId } = viewportGrid; + + const [toolsEnabled, setToolsEnabled] = useState(false); + const [state, dispatch] = useReducer(toolboxReducer, initialState); + + const updateActiveTool = useCallback(() => { + if (!viewports?.size || activeViewportId === undefined) { + return; + } + const viewport = viewports.get(activeViewportId); + + if (!viewport) { + return; + } + + dispatch({ + type: ACTIONS.SET_ACTIVE_TOOL, + payload: toolGroupService.getActiveToolForViewport(viewport.viewportId), + }); + }, [activeViewportId, viewports, toolGroupService, dispatch]); + + const setToolActive = useCallback( + toolName => { + toolbarService.recordInteraction({ + interactionType: 'tool', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName, + }, + }, + ], + }); + + dispatch({ type: ACTIONS.SET_ACTIVE_TOOL, payload: toolName }); + }, + [toolbarService, dispatch] + ); + + /** + * sets the tools enabled IF there are segmentations + */ + useEffect(() => { + const events = [ + segmentationService.EVENTS.SEGMENTATION_ADDED, + segmentationService.EVENTS.SEGMENTATION_UPDATED, + segmentationService.EVENTS.SEGMENTATION_REMOVED, + ]; + + const unsubscriptions = []; + + events.forEach(event => { + const { unsubscribe } = segmentationService.subscribe(event, () => { + const segmentations = segmentationService.getSegmentations(); + + const activeSegmentation = segmentations?.find(seg => seg.isActive); + + setToolsEnabled(activeSegmentation?.segmentCount > 0); + }); + + unsubscriptions.push(unsubscribe); + }); + + updateActiveTool(); + + return () => { + unsubscriptions.forEach(unsubscribe => unsubscribe()); + }; + }, [activeViewportId, viewports, segmentationService, updateActiveTool]); + + /** + * Update the active tool when the toolbar state changes + */ + useEffect(() => { + const { unsubscribe } = toolbarService.subscribe( + toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, + () => { + updateActiveTool(); + } + ); + + return () => { + unsubscribe(); + }; + }, [toolbarService, updateActiveTool]); + + useEffect(() => { + // if the active tool is not a brush tool then do nothing + if (!Object.values(TOOL_TYPES).includes(state.activeTool)) { + return; + } + + // if the tool is Segmentation and it is enabled then do nothing + if (toolsEnabled) { + return; + } + + // if the tool is Segmentation and it is disabled, then switch + // back to the window level tool to not confuse the user when no + // segmentation is active or when there is no segment in the segmentation + setToolActive('WindowLevel'); + }, [toolsEnabled, state.activeTool, setToolActive]); + + const updateBrushSize = useCallback( + (toolName, brushSize) => { + toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { + segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName); + }); + }, + [toolGroupService] + ); + + const onBrushSizeChange = useCallback( + (valueAsStringOrNumber, toolCategory) => { + const value = Number(valueAsStringOrNumber); + + _getToolNamesFromCategory(toolCategory).forEach(toolName => { + updateBrushSize(toolName, value); + }); + + dispatch({ + type: ACTIONS.SET_TOOL_CONFIG, + payload: { + tool: toolCategory, + config: { brushSize: value }, + }, + }); + }, + [toolGroupService, dispatch] + ); + + const handleRangeChange = useCallback( + newRange => { + if ( + newRange[0] === state.ThresholdBrush.thresholdRange[0] && + newRange[1] === state.ThresholdBrush.thresholdRange[1] + ) { + return; + } + + const toolNames = _getToolNamesFromCategory('ThresholdBrush'); + + toolNames.forEach(toolName => { + toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { + const toolGroup = toolGroupService.getToolGroup(toolGroupId); + toolGroup.setToolConfiguration(toolName, { + strategySpecificConfiguration: { + THRESHOLD_INSIDE_CIRCLE: { + threshold: newRange, + }, + }, + }); + }); + }); + + dispatch({ + type: ACTIONS.SET_TOOL_CONFIG, + payload: { + tool: 'ThresholdBrush', + config: { thresholdRange: newRange }, + }, + }); + }, + [toolGroupService, dispatch, state.ThresholdBrush.thresholdRange] + ); + + return ( + setToolActive(TOOL_TYPES.CIRCULAR_BRUSH), + options: [ + { + name: 'Radius (mm)', + id: 'brush-radius', + type: 'range', + min: 0.01, + max: 100, + value: state.Brush.brushSize, + step: 0.5, + onChange: value => onBrushSizeChange(value, 'Brush'), + }, + { + name: 'Mode', + type: 'radio', + id: 'brush-mode', + value: state.Brush.mode, + values: [ + { value: TOOL_TYPES.CIRCULAR_BRUSH, label: 'Circle' }, + { value: TOOL_TYPES.SPHERE_BRUSH, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + ], + }, + { + name: 'Eraser', + icon: 'icon-tool-eraser', + disabled: !toolsEnabled, + active: + state.activeTool === TOOL_TYPES.CIRCULAR_ERASER || + state.activeTool === TOOL_TYPES.SPHERE_ERASER, + onClick: () => setToolActive(TOOL_TYPES.CIRCULAR_ERASER), + options: [ + { + name: 'Radius (mm)', + type: 'range', + id: 'eraser-radius', + min: 0.01, + max: 100, + value: state.Eraser.brushSize, + step: 0.5, + onChange: value => onBrushSizeChange(value, 'Eraser'), + }, + { + name: 'Mode', + type: 'radio', + id: 'eraser-mode', + value: state.Eraser.mode, + values: [ + { value: TOOL_TYPES.CIRCULAR_ERASER, label: 'Circle' }, + { value: TOOL_TYPES.SPHERE_ERASER, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + ], + }, + { + name: 'Scissor', + icon: 'icon-tool-scissor', + disabled: !toolsEnabled, + active: + state.activeTool === TOOL_TYPES.CIRCLE_SCISSOR || + state.activeTool === TOOL_TYPES.RECTANGLE_SCISSOR || + state.activeTool === TOOL_TYPES.SPHERE_SCISSOR, + onClick: () => setToolActive(TOOL_TYPES.CIRCLE_SCISSOR), + options: [ + { + name: 'Mode', + type: 'radio', + value: state.Scissors.mode, + id: 'scissor-mode', + values: [ + { value: TOOL_TYPES.CIRCLE_SCISSOR, label: 'Circle' }, + { value: TOOL_TYPES.RECTANGLE_SCISSOR, label: 'Rectangle' }, + { value: TOOL_TYPES.SPHERE_SCISSOR, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + ], + }, + { + name: 'Threshold Tool', + icon: 'icon-tool-threshold', + disabled: !toolsEnabled, + active: + state.activeTool === TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH || + state.activeTool === TOOL_TYPES.THRESHOLD_SPHERE_BRUSH, + onClick: () => setToolActive(TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH), + options: [ + { + name: 'Radius (mm)', + id: 'threshold-radius', + type: 'range', + min: 0.01, + max: 100, + value: state.ThresholdBrush.brushSize, + step: 0.5, + onChange: value => onBrushSizeChange(value, 'ThresholdBrush'), + }, + { + name: 'Mode', + type: 'radio', + id: 'threshold-mode', + value: state.activeTool, + values: [ + { value: TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH, label: 'Circle' }, + { value: TOOL_TYPES.THRESHOLD_SPHERE_BRUSH, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + { + type: 'custom', + id: 'segmentation-threshold-range', + children: () => { + return ( +
+
+
Threshold
+ +
+ ); + }, + }, + ], + }, + ]} + /> + ); +} + +function _getToolNamesFromCategory(category) { + let toolNames = []; + switch (category) { + case 'Brush': + toolNames = ['CircularBrush', 'SphereBrush']; + break; + case 'Eraser': + toolNames = ['CircularEraser', 'SphereEraser']; + break; + case 'ThresholdBrush': + toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush']; + break; + default: + break; + } + + return toolNames; +} + +export default SegmentationToolbox; diff --git a/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx b/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx index 69c3034697..6de2470d8b 100644 --- a/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx +++ b/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Input, Dialog } from '@ohif/ui'; +import { Input, Dialog, ButtonEnums } from '@ohif/ui'; function callInputDialog(uiDialogService, label, callback) { const dialogId = 'enter-segment-label'; @@ -29,8 +29,8 @@ function callInputDialog(uiDialogService, label, callback) { noCloseButton: true, onClose: () => uiDialogService.dismiss({ id: dialogId }), actions: [ - { id: 'cancel', text: 'Cancel', type: 'primary' }, - { id: 'save', text: 'Confirm', type: 'secondary' }, + { id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary }, + { id: 'save', text: 'Confirm', type: ButtonEnums.type.primary }, ], onSubmit: onSubmitHandler, body: ({ value, setValue }) => { @@ -39,7 +39,7 @@ function callInputDialog(uiDialogService, label, callback) { label="Enter the segment label" labelClassName="text-white text-[14px] leading-[1.2]" autoFocus - className="bg-black border-primary-main" + className="border-primary-main bg-black" type="text" value={value.label} onChange={event => { diff --git a/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.css b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.css new file mode 100644 index 0000000000..1c6bb20670 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.css @@ -0,0 +1,3 @@ +.chrome-picker { + background: #090c29 !important; +} diff --git a/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.tsx b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.tsx new file mode 100644 index 0000000000..38e85efb29 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Dialog } from '@ohif/ui'; +import { ChromePicker } from 'react-color'; + +import './colorPickerDialog.css'; + +function callColorPickerDialog(uiDialogService, rgbaColor, callback) { + const dialogId = 'pick-color'; + + const onSubmitHandler = ({ action, value }) => { + switch (action.id) { + case 'save': + callback(value.rgbaColor, action.id); + break; + case 'cancel': + callback('', action.id); + break; + } + uiDialogService.dismiss({ id: dialogId }); + }; + + if (uiDialogService) { + uiDialogService.create({ + id: dialogId, + centralize: true, + isDraggable: false, + showOverlay: true, + content: Dialog, + contentProps: { + title: 'Segment Color', + value: { rgbaColor }, + noCloseButton: true, + onClose: () => uiDialogService.dismiss({ id: dialogId }), + actions: [ + { id: 'cancel', text: 'Cancel', type: 'primary' }, + { id: 'save', text: 'Save', type: 'secondary' }, + ], + onSubmit: onSubmitHandler, + body: ({ value, setValue }) => { + const handleChange = color => { + setValue({ rgbaColor: color.rgb }); + }; + + return ( + + ); + }, + }, + }); + } +} + +export default callColorPickerDialog; diff --git a/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts b/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts deleted file mode 100644 index f6b1522be7..0000000000 --- a/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts +++ /dev/null @@ -1,69 +0,0 @@ -async function _hydrateSEGDisplaySet({ - segDisplaySet, - viewportIndex, - servicesManager, -}) { - const { - segmentationService, - hangingProtocolService, - viewportGridService, - } = servicesManager.services; - - const displaySetInstanceUID = segDisplaySet.referencedDisplaySetInstanceUID; - - let segmentationId = null; - - // We need the hydration to notify panels about the new segmentation added - const suppressEvents = false; - - segmentationId = await segmentationService.createSegmentationForSEGDisplaySet( - segDisplaySet, - segmentationId, - suppressEvents - ); - - segmentationService.hydrateSegmentation(segDisplaySet.displaySetInstanceUID); - - const { viewports } = viewportGridService.getState(); - - const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, - displaySetInstanceUID - ); - - // Todo: fix this after we have a better way for stack viewport segmentations - - // check every viewport in the viewports to see if the displaySetInstanceUID - // is being displayed, if so we need to update the viewport to use volume viewport - // (if already is not using it) since Cornerstone3D currently only supports - // volume viewport for segmentation - viewports.forEach((viewport, index) => { - if (index === viewportIndex) { - return; - } - - const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( - viewport.displaySetInstanceUIDs, - segDisplaySet.displaySetInstanceUID - ); - - if (shouldDisplaySeg) { - updatedViewports.push({ - viewportIndex: index, - displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, - viewportOptions: { - initialImageOptions: { - preset: 'middle', - }, - }, - }); - } - }); - - // Do the entire update at once - viewportGridService.setDisplaySetsForViewports(updatedViewports); - - return true; -} - -export default _hydrateSEGDisplaySet; diff --git a/extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts b/extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts new file mode 100644 index 0000000000..34ce1e54f1 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts @@ -0,0 +1,14 @@ +import dcmjs from 'dcmjs'; + +/** + * Converts a CIELAB color to an RGB color using the dcmjs library. + * @param cielab - The CIELAB color to convert. + * @returns The RGB color as an array of three integers between 0 and 255. + */ +function dicomlabToRGB(cielab: number[]): number[] { + const rgb = dcmjs.data.Colors.dicomlab2RGB(cielab).map(x => Math.round(x * 255)); + + return rgb; +} + +export { dicomlabToRGB }; diff --git a/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts b/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts new file mode 100644 index 0000000000..fa3c6d47c8 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts @@ -0,0 +1,190 @@ +import { Enums, cache } from '@cornerstonejs/core'; + +/** + * Updates the viewports in preparation for rendering segmentations. + * Evaluates each viewport to determine which need modifications, + * then for those viewports, changes them to a volume type and ensures + * they are ready for segmentation rendering. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - ID of the viewport to be updated. + * @param params.loadFn - Function to load the segmentation data. + * @param params.servicesManager - The services manager. + * @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance. + * + * @returns Returns true upon successful update of viewports for segmentation rendering. + */ +async function updateViewportsForSegmentationRendering({ + viewportId, + loadFn, + servicesManager, + referencedDisplaySetInstanceUID, +}: { + viewportId: string; + loadFn: () => Promise; + servicesManager: any; + referencedDisplaySetInstanceUID?: string; +}) { + const { cornerstoneViewportService, segmentationService, viewportGridService } = + servicesManager.services; + + const viewport = getTargetViewport({ viewportId, viewportGridService }); + const targetViewportId = viewport.viewportOptions.viewportId; + + referencedDisplaySetInstanceUID = + referencedDisplaySetInstanceUID || viewport?.displaySetInstanceUIDs[0]; + + const updatedViewports = getUpdatedViewportsForSegmentation({ + servicesManager, + viewportId, + referencedDisplaySetInstanceUID, + }); + + // create Segmentation callback which needs to be waited until + // the volume is created (if coming from stack) + const createSegmentationForVolume = async () => { + const segmentationId = await loadFn(); + segmentationService.hydrateSegmentation(segmentationId); + }; + + // the reference volume that is used to draw the segmentation. so check if the + // volume exists in the cache (the target Viewport is already a volume viewport) + const volumeExists = Array.from(cache._volumeCache.keys()).some(volumeId => + volumeId.includes(referencedDisplaySetInstanceUID) + ); + + updatedViewports.forEach(async viewport => { + viewport.viewportOptions = { + ...viewport.viewportOptions, + viewportType: 'volume', + needsRerendering: true, + }; + const viewportId = viewport.viewportId; + + const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + const prevCamera = csViewport.getCamera(); + + // only run the createSegmentationForVolume for the targetViewportId + // since the rest will get handled by cornerstoneViewportService + if (volumeExists && viewportId === targetViewportId) { + await createSegmentationForVolume(); + return; + } + + const createNewSegmentationWhenVolumeMounts = async evt => { + const isTheActiveViewportVolumeMounted = evt.detail.volumeActors?.find(ac => + ac.uid.includes(referencedDisplaySetInstanceUID) + ); + + // Note: make sure to re-grab the viewport since it might have changed + // during the time it took for the volume to be mounted, for instance + // the stack viewport has been changed to a volume viewport + const volumeViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + volumeViewport.setCamera(prevCamera); + + volumeViewport.element.removeEventListener( + Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, + createNewSegmentationWhenVolumeMounts + ); + + if (!isTheActiveViewportVolumeMounted) { + // it means it is one of those other updated viewports so just update the camera + return; + } + + if (viewportId === targetViewportId) { + await createSegmentationForVolume(); + } + }; + + csViewport.element.addEventListener( + Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, + createNewSegmentationWhenVolumeMounts + ); + }); + + // Set the displaySets for the viewports that require to be updated + viewportGridService.setDisplaySetsForViewports(updatedViewports); + + return true; +} + +const getTargetViewport = ({ viewportId, viewportGridService }) => { + const { viewports, activeViewportId } = viewportGridService.getState(); + const targetViewportId = viewportId || activeViewportId; + + const viewport = viewports.get(targetViewportId); + + return viewport; +}; + +/** + * Retrieves a list of viewports that require updates in preparation for segmentation rendering. + * This function evaluates viewports based on their compatibility with the provided segmentation's + * frame of reference UID and appends them to the updated list if they should render the segmentation. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - the ID of the viewport to be updated. + * @param params.servicesManager - The services manager + * @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance. + * + * @returns {Array} Returns an array of viewports that require updates for segmentation rendering. + */ +function getUpdatedViewportsForSegmentation({ + viewportId, + servicesManager, + referencedDisplaySetInstanceUID, +}) { + const { hangingProtocolService, displaySetService, segmentationService, viewportGridService } = + servicesManager.services; + + const { viewports } = viewportGridService.getState(); + + const viewport = getTargetViewport({ viewportId, viewportGridService }); + const targetViewportId = viewport.viewportOptions.viewportId; + + const displaySetInstanceUIDs = viewports.get(targetViewportId).displaySetInstanceUIDs; + + const referenceDisplaySetInstanceUID = + referencedDisplaySetInstanceUID || displaySetInstanceUIDs[0]; + + const referencedDisplaySet = displaySetService.getDisplaySetByUID(referenceDisplaySetInstanceUID); + const segmentationFrameOfReferenceUID = referencedDisplaySet.instances[0].FrameOfReferenceUID; + + const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( + targetViewportId, + referenceDisplaySetInstanceUID + ); + + viewports.forEach((viewport, viewportId) => { + if ( + targetViewportId === viewportId || + updatedViewports.find(v => v.viewportId === viewportId) + ) { + return; + } + + const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( + viewport.displaySetInstanceUIDs, + segmentationFrameOfReferenceUID + ); + + if (shouldDisplaySeg) { + updatedViewports.push({ + viewportId, + displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, + viewportOptions: { + viewportType: 'volume', + needsRerendering: true, + }, + }); + } + }); + return updatedViewports; +} + +export { + updateViewportsForSegmentationRendering, + getUpdatedViewportsForSegmentation, + getTargetViewport, +}; diff --git a/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts b/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts index e032542f13..8ddc088c6c 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts @@ -1,12 +1,7 @@ -function createSEGToolGroupAndAddTools( - ToolGroupService, - customizationService, - toolGroupId -) { - const { tools } = - customizationService.get('cornerstone.overlayViewportTools') ?? {}; +function createSEGToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) { + const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {}; - return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {}); + return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } export default createSEGToolGroupAndAddTools; diff --git a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts index c0b9385f13..9f8c1dddf7 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts @@ -1,4 +1,4 @@ -import hydrateSEGDisplaySet from './_hydrateSEG'; +import { ButtonEnums } from '@ohif/ui'; const RESPONSE = { NO_NEVER: -1, @@ -9,21 +9,23 @@ const RESPONSE = { function promptHydrateSEG({ servicesManager, segDisplaySet, - viewportIndex, + viewportId, + preHydrateCallbacks, + hydrateSEGDisplaySet, }) { const { uiViewportDialogService } = servicesManager.services; - return new Promise(async function(resolve, reject) { - const promptResult = await _askHydrate( - uiViewportDialogService, - viewportIndex - ); + return new Promise(async function (resolve, reject) { + const promptResult = await _askHydrate(uiViewportDialogService, viewportId); if (promptResult === RESPONSE.HYDRATE_SEG) { + preHydrateCallbacks?.forEach(callback => { + callback(); + }); + const isHydrated = await hydrateSEGDisplaySet({ segDisplaySet, - viewportIndex, - servicesManager, + viewportId, }); resolve(isHydrated); @@ -31,17 +33,17 @@ function promptHydrateSEG({ }); } -function _askHydrate(uiViewportDialogService, viewportIndex) { - return new Promise(function(resolve, reject) { +function _askHydrate(uiViewportDialogService, viewportId) { + return new Promise(function (resolve, reject) { const message = 'Do you want to open this Segmentation?'; const actions = [ { - type: 'secondary', + type: ButtonEnums.type.secondary, text: 'No', value: RESPONSE.CANCEL, }, { - type: 'primary', + type: ButtonEnums.type.primary, text: 'Yes', value: RESPONSE.HYDRATE_SEG, }, @@ -52,7 +54,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, diff --git a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx index 08ed004f07..fb39f8c36f 100644 --- a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx +++ b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx @@ -2,14 +2,9 @@ import PropTypes from 'prop-types'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import OHIF, { utils } from '@ohif/core'; -import { - LoadingIndicatorTotalPercent, - useViewportGrid, - ViewportActionBar, -} from '@ohif/ui'; +import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionBar } from '@ohif/ui'; import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup'; import promptHydrateSEG from '../utils/promptHydrateSEG'; -import hydrateSEGDisplaySet from '../utils/_hydrateSEG'; import _getStatusComponent from './_getStatusComponent'; const { formatDate } = utils; @@ -20,13 +15,14 @@ function OHIFCornerstoneSEGViewport(props) { children, displaySets, viewportOptions, - viewportIndex, viewportLabel, servicesManager, extensionManager, + commandsManager, } = props; const { t } = useTranslation('SEGViewport'); + const viewportId = viewportOptions.viewportId; const { displaySetService, @@ -36,7 +32,7 @@ function OHIFCornerstoneSEGViewport(props) { customizationService, } = servicesManager.services; - const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportIndex}`; + const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportId}`; // SEG viewport will always have a single display set if (displaySets.length > 1) { @@ -48,7 +44,6 @@ function OHIFCornerstoneSEGViewport(props) { const [viewportGrid, viewportGridService] = useViewportGrid(); // States - const [isToolGroupCreated, setToolGroupCreated] = useState(false); const [selectedSegment, setSelectedSegment] = useState(1); // Hydration means that the SEG is opened and segments are loaded into the @@ -67,11 +62,12 @@ function OHIFCornerstoneSEGViewport(props) { // refs const referencedDisplaySetRef = useRef(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; const referencedDisplaySet = segDisplaySet.getReferenceDisplaySet(); const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata( - referencedDisplaySet + referencedDisplaySet, + segDisplaySet ); referencedDisplaySetRef.current = { @@ -92,14 +88,20 @@ function OHIFCornerstoneSEGViewport(props) { setElement(null); }; + const storePresentationState = useCallback(() => { + viewportGrid?.viewports.forEach(({ viewportId }) => { + commandsManager.runCommand('storePresentation', { + viewportId, + }); + }); + }, [viewportGrid]); + const getCornerstoneViewport = useCallback(() => { const { component: Component } = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.viewportModule.cornerstone' ); - const { - displaySet: referencedDisplaySet, - } = referencedDisplaySetRef.current; + const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current; // Todo: jump to the center of the first segment return ( @@ -117,7 +119,7 @@ function OHIFCornerstoneSEGViewport(props) { // initialImageIndex={initialImageIndex} > ); - }, [viewportIndex, segDisplaySet, toolGroupId]); + }, [viewportId, segDisplaySet, toolGroupId]); const onSegmentChange = useCallback( direction => { @@ -139,11 +141,7 @@ function OHIFCornerstoneSEGViewport(props) { newSelectedSegmentIndex = numberOfSegments - 1; } - segmentationService.jumpToSegmentCenter( - segmentationId, - newSelectedSegmentIndex, - toolGroupId - ); + segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, toolGroupId); setSelectedSegment(newSelectedSegmentIndex); }, [selectedSegment] @@ -156,31 +154,29 @@ function OHIFCornerstoneSEGViewport(props) { promptHydrateSEG({ servicesManager, - viewportIndex, + viewportId, segDisplaySet, + preHydrateCallbacks: [storePresentationState], + hydrateSEGDisplaySet, }).then(isHydrated => { if (isHydrated) { setIsHydrated(true); } }); - }, [servicesManager, viewportIndex, segDisplaySet, segIsLoading]); + }, [servicesManager, viewportId, segDisplaySet, segIsLoading]); useEffect(() => { const { unsubscribe } = segmentationService.subscribe( segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE, evt => { - if ( - evt.segDisplaySet.displaySetInstanceUID === - segDisplaySet.displaySetInstanceUID - ) { + if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) { setSegIsLoading(false); } if (evt.overlappingSegments) { uiNotificationService.show({ title: 'Overlapping Segments', - message: - 'Overlapping segments detected which is not currently supported', + message: 'Overlapping segments detected which is not currently supported', type: 'warning', }); } @@ -215,12 +211,10 @@ function OHIFCornerstoneSEGViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; - if ( - displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) - ) { + const activeViewport = viewports.get(activeViewportId); + if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -241,19 +235,11 @@ function OHIFCornerstoneSEGViewport(props) { // This creates a custom tool group which has the lifetime of this view // only, and does NOT interfere with currently displayed segmentations. - toolGroup = createSEGToolGroupAndAddTools( - toolGroupService, - customizationService, - toolGroupId - ); - - setToolGroupCreated(true); + toolGroup = createSEGToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId); return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); // Only destroy the viewport specific implementation toolGroupService.destroyToolGroup(toolGroupId); @@ -265,9 +251,7 @@ function OHIFCornerstoneSEGViewport(props) { return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); referencedDisplaySetRef.current = null; }; }, [segDisplaySet]); @@ -288,7 +272,7 @@ function OHIFCornerstoneSEGViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -307,16 +291,28 @@ function OHIFCornerstoneSEGViewport(props) { SpacingBetweenSlices, } = referencedDisplaySetRef.current.metadata; + const hydrateSEGDisplaySet = ({ segDisplaySet, viewportId }) => { + commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', { + displaySets: [segDisplaySet], + viewportId, + }); + }; + const onStatusClick = async () => { + // Before hydrating a SEG and make it added to all viewports in the grid + // that share the same frameOfReferenceUID, we need to store the viewport grid + // presentation state, so that we can restore it after hydrating the SEG. This is + // required if the user has changed the viewport (other viewport than SEG viewport) + // presentation state (w/l and invert) and then opens the SEG. If we don't store + // the presentation state, the viewport will be reset to the default presentation + storePresentationState(); const isHydrated = await hydrateSEGDisplaySet({ segDisplaySet, - viewportIndex, - servicesManager, + viewportId, }); setIsHydrated(isHydrated); }; - return ( <> -
+
{segIsLoading && ( ; - ToolTipMessage = () => ( -
This Segmentation is loaded in the segmentation panel
- ); + ToolTipMessage = () =>
This Segmentation is loaded in the segmentation panel
; break; - case false: - StatusIcon = () => ; + case false: + StatusIcon = () => ( + + ); ToolTipMessage = () =>
Click LOAD to load segmentation.
; } const StatusArea = () => ( -
-
+
+
SEG
{!isHydrated && (
@@ -42,11 +44,13 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
); - return ( <> {ToolTipMessage && ( - } position="bottom-left"> + } + position="bottom-left" + > )} diff --git a/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js b/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js index 76dcd9ecfa..9e07dd8ac1 100644 --- a/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js +++ b/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js @@ -41,13 +41,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/cornerstone-dicom-sr/CHANGELOG.md b/extensions/cornerstone-dicom-sr/CHANGELOG.md new file mode 100644 index 0000000000..0538a0ef72 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/CHANGELOG.md @@ -0,0 +1,253 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index 260f898c3a..e384246be9 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-sr", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "OHIF extension for an SR Cornerstone Viewport", "author": "OHIF", "license": "MIT", @@ -32,10 +32,10 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-measurement-tracking": "3.6.0", - "@ohif/ui": "3.6.0", + "@ohif/core": "3.7.0-beta.85", + "@ohif/extension-cornerstone": "3.7.0-beta.85", + "@ohif/extension-measurement-tracking": "3.7.0-beta.85", + "@ohif/ui": "3.7.0-beta.85", "dcmjs": "^0.29.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", @@ -44,9 +44,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^1.1.0", - "@cornerstonejs/core": "^1.1.0", - "@cornerstonejs/tools": "^1.1.0", + "@cornerstonejs/adapters": "^1.16.5", + "@cornerstonejs/core": "^1.16.5", + "@cornerstonejs/tools": "^1.16.5", "classnames": "^2.3.2" } } diff --git a/extensions/cornerstone-dicom-sr/src/commandsModule.js b/extensions/cornerstone-dicom-sr/src/commandsModule.js index f8c9459e4d..ec68cabb95 100644 --- a/extensions/cornerstone-dicom-sr/src/commandsModule.js +++ b/extensions/cornerstone-dicom-sr/src/commandsModule.js @@ -17,11 +17,7 @@ const { log } = OHIF; * @param options Naturalized DICOM JSON headers to merge into the displaySet. * */ -const _generateReport = ( - measurementData, - additionalFindingTypes, - options = {} -) => { +const _generateReport = (measurementData, additionalFindingTypes, options = {}) => { const filteredToolState = getFilteredCornerstoneToolState( measurementData, additionalFindingTypes @@ -54,16 +50,8 @@ const commandsModule = ({}) => { * as opposed to Finding Sites. * that you wish to serialize. */ - downloadReport: ({ - measurementData, - additionalFindingTypes, - options = {}, - }) => { - const srDataset = actions.generateReport( - measurementData, - additionalFindingTypes, - options - ); + downloadReport: ({ measurementData, additionalFindingTypes, options = {} }) => { + const srDataset = actions.generateReport(measurementData, additionalFindingTypes, options); const reportBlob = dcmjs.data.datasetToBlob(srDataset); //Create a URL for the binary. @@ -91,28 +79,19 @@ const commandsModule = ({}) => { log.info('[DICOMSR] storeMeasurements'); if (!dataSource || !dataSource.store || !dataSource.store.dicom) { - log.error( - '[DICOMSR] datasource has no dataSource.store.dicom endpoint!' - ); + log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!'); return Promise.reject({}); } try { - const naturalizedReport = _generateReport( - measurementData, - additionalFindingTypes, - options - ); + const naturalizedReport = _generateReport(measurementData, additionalFindingTypes, options); const { StudyInstanceUID, ContentSequence } = naturalizedReport; // The content sequence has 5 or more elements, of which // the `[4]` element contains the annotation data, so this is // checking that there is some annotation data present. if (!ContentSequence?.[4].ContentSequence?.length) { - console.log( - 'naturalizedReport missing imaging content', - naturalizedReport - ); + console.log('naturalizedReport missing imaging content', naturalizedReport); throw new Error('Invalid report, no content'); } @@ -130,12 +109,8 @@ const commandsModule = ({}) => { return naturalizedReport; } catch (error) { console.warn(error); - log.error( - `[DICOMSR] Error while saving the measurements: ${error.message}` - ); - throw new Error( - error.message || 'Error while saving the measurements.' - ); + log.error(`[DICOMSR] Error while saving the measurements: ${error.message}`); + throw new Error(error.message || 'Error while saving the measurements.'); } }, }; diff --git a/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts b/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts index e9fcf31be5..8d47aca0c5 100644 --- a/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts @@ -5,7 +5,6 @@ const srProtocol: Types.HangingProtocol.Protocol = { // Don't store this hanging protocol as it applies to the currently active // display set by default // cacheId: null, - hasUpdatedPriorsInformation: false, name: 'SR Key Images', // Just apply this one when specifically listed protocolMatchingRules: [], diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts index e020944712..5cdf455f60 100644 --- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts @@ -29,9 +29,7 @@ const validateSameStudyUID = (uid: string, instances): void => { instances.forEach(it => { if (it.StudyInstanceUID !== uid) { console.warn('Not all instances have the same UID', uid, it); - throw new Error( - `Instances ${it.SOPInstanceUID} does not belong to ${uid}` - ); + throw new Error(`Instances ${it.SOPInstanceUID} does not belong to ${uid}`); } }); }; @@ -51,10 +49,7 @@ const CodeNameCodeSequenceValues = { const CodingSchemeDesignators = { SRT: 'SRT', - CornerstoneCodeSchemes: [ - Cornerstone3DCodeScheme.CodingSchemeDesignator, - 'CST4', - ], + CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'], }; const RELATIONSHIP_TYPE = { @@ -71,10 +66,7 @@ const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT'; * @param instances is a list of instances from THIS series that are not * in this DICOM SR Display Set already. */ -function addInstances( - instances: InstanceMetadata[], - displaySetService: DisplaySetService -) { +function addInstances(instances: InstanceMetadata[], displaySetService: DisplaySetService) { this.instances.push(...instances); utils.sortStudyInstances(this.instances); // The last instance is the newest one, so is the one most interesting. @@ -93,11 +85,7 @@ function addInstances( * @param servicesManager is the services that can be used for creating * @returns The list of display sets created for the given instances object */ -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { // If the series has no instances, stop here if (!instances || !instances.length) { throw new Error('No instances were provided'); @@ -123,8 +111,7 @@ function _getDisplaySetsFromSeries( if ( !ConceptNameCodeSequence || - ConceptNameCodeSequence.CodeValue !== - CodeNameCodeSequenceValues.ImagingMeasurementReport + ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.ImagingMeasurementReport ) { servicesManager.services.uiNotificationService.show({ title: 'DICOM SR', @@ -184,36 +171,21 @@ function _load(displaySet, servicesManager, extensionManager) { // Check currently added displaySets and add measurements if the sources exist. displaySetService.activeDisplaySets.forEach(activeDisplaySet => { - _checkIfCanAddMeasurementsToDisplaySet( - displaySet, - activeDisplaySet, - dataSource - ); + _checkIfCanAddMeasurementsToDisplaySet(displaySet, activeDisplaySet, dataSource); }); // Subscribe to new displaySets as the source may come in after. - displaySetService.subscribe( - displaySetService.EVENTS.DISPLAY_SETS_ADDED, - data => { - const { displaySetsAdded } = data; - // If there are still some measurements that have not yet been loaded into cornerstone, - // See if we can load them onto any of the new displaySets. - displaySetsAdded.forEach(newDisplaySet => { - _checkIfCanAddMeasurementsToDisplaySet( - displaySet, - newDisplaySet, - dataSource - ); - }); - } - ); + displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => { + const { displaySetsAdded } = data; + // If there are still some measurements that have not yet been loaded into cornerstone, + // See if we can load them onto any of the new displaySets. + displaySetsAdded.forEach(newDisplaySet => { + _checkIfCanAddMeasurementsToDisplaySet(displaySet, newDisplaySet, dataSource); + }); + }); } -function _checkIfCanAddMeasurementsToDisplaySet( - srDisplaySet, - newDisplaySet, - dataSource -) { +function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dataSource) { let unloadedMeasurements = srDisplaySet.measurements.filter( measurement => measurement.loaded === false ); @@ -248,8 +220,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( const { coords } = measurement; coords.forEach(coord => { - const SOPInstanceUID = - coord.ReferencedSOPSequence.ReferencedSOPInstanceUID; + const SOPInstanceUID = coord.ReferencedSOPSequence.ReferencedSOPInstanceUID; if (!SOPInstanceUIDs.includes(SOPInstanceUID)) { SOPInstanceUIDs.push(SOPInstanceUID); @@ -257,9 +228,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( }); }); - const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet( - newDisplaySet - ); + const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet(newDisplaySet); for (const imageId of imageIdsForDisplaySet) { if (!unloadedMeasurements.length) { @@ -267,25 +236,13 @@ function _checkIfCanAddMeasurementsToDisplaySet( return; } - const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID( - imageId - ); + const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID(imageId); if (SOPInstanceUIDs.includes(SOPInstanceUID)) { for (let j = unloadedMeasurements.length - 1; j >= 0; j--) { const measurement = unloadedMeasurements[j]; - if ( - _measurementReferencesSOPInstanceUID( - measurement, - SOPInstanceUID, - frameNumber - ) - ) { - addMeasurement( - measurement, - imageId, - newDisplaySet.displaySetInstanceUID - ); + if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) { + addMeasurement(measurement, imageId, newDisplaySet.displaySetInstanceUID); unloadedMeasurements.splice(j, 1); } @@ -294,11 +251,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( } } -function _measurementReferencesSOPInstanceUID( - measurement, - SOPInstanceUID, - frameNumber -) { +function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) { const { coords } = measurement; // NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM @@ -308,8 +261,9 @@ function _measurementReferencesSOPInstanceUID( measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) || 1; - if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) + if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) { return false; + } for (let j = 0; j < coords.length; j++) { const coord = coords[j]; @@ -323,11 +277,7 @@ function _measurementReferencesSOPInstanceUID( function getSopClassHandlerModule({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return [ @@ -342,30 +292,22 @@ function getSopClassHandlerModule({ servicesManager, extensionManager }) { function _getMeasurements(ImagingMeasurementReportContentSequence) { const ImagingMeasurements = ImagingMeasurementReportContentSequence.find( item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImagingMeasurements + item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurements ); - const MeasurementGroups = _getSequenceAsArray( - ImagingMeasurements.ContentSequence - ).filter( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.MeasurementGroup + const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter( + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.MeasurementGroup ); - const mergedContentSequencesByTrackingUniqueIdentifiers = _getMergedContentSequencesByTrackingUniqueIdentifiers( - MeasurementGroups - ); + const mergedContentSequencesByTrackingUniqueIdentifiers = + _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups); const measurements = []; Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach( trackingUniqueIdentifier => { const mergedContentSequence = - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ]; + mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier]; const measurement = _processMeasurement(mergedContentSequence); @@ -378,15 +320,11 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) { return measurements; } -function _getMergedContentSequencesByTrackingUniqueIdentifiers( - MeasurementGroups -) { +function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) { const mergedContentSequencesByTrackingUniqueIdentifiers = {}; MeasurementGroups.forEach(MeasurementGroup => { - const ContentSequence = _getSequenceAsArray( - MeasurementGroup.ContentSequence - ); + const ContentSequence = _getSequenceAsArray(MeasurementGroup.ContentSequence); const TrackingUniqueIdentifierItem = ContentSequence.find( item => @@ -395,22 +333,16 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers( ); if (!TrackingUniqueIdentifierItem) { - console.warn( - 'No Tracking Unique Identifier, skipping ambiguous measurement.' - ); + console.warn('No Tracking Unique Identifier, skipping ambiguous measurement.'); } const trackingUniqueIdentifier = TrackingUniqueIdentifierItem.UID; - if ( - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ] === undefined - ) { + if (mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier] === undefined) { // Add the full ContentSequence - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ] = [...ContentSequence]; + mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier] = [ + ...ContentSequence, + ]; } else { // Add the ContentSequence minus the tracking identifier, as we have this // Information in the merged ContentSequence anyway. @@ -419,9 +351,7 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers( item.ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.TrackingUniqueIdentifier ) { - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ].push(item); + mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier].push(item); } }); } @@ -446,18 +376,12 @@ function _processTID1410Measurement(mergedContentSequence) { // Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level, // And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D - const graphicItem = mergedContentSequence.find( - group => group.ValueType === 'SCOORD' - ); + const graphicItem = mergedContentSequence.find(group => group.ValueType === 'SCOORD'); - const UIDREFContentItem = mergedContentSequence.find( - group => group.ValueType === 'UIDREF' - ); + const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF'); const TrackingIdentifierContentItem = mergedContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.TrackingIdentifier + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingIdentifier ); if (!graphicItem) { @@ -467,9 +391,7 @@ function _processTID1410Measurement(mergedContentSequence) { return; } - const NUMContentItems = mergedContentSequence.filter( - group => group.ValueType === 'NUM' - ); + const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM'); const measurement = { loaded: false, @@ -484,10 +406,7 @@ function _processTID1410Measurement(mergedContentSequence) { if (MeasuredValueSequence) { measurement.labels.push( - _getLabelFromMeasuredValueSequence( - ConceptNameCodeSequence, - MeasuredValueSequence - ) + _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) ); } }); @@ -496,32 +415,22 @@ function _processTID1410Measurement(mergedContentSequence) { } function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { - const NUMContentItems = mergedContentSequence.filter( - group => group.ValueType === 'NUM' - ); + const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM'); - const UIDREFContentItem = mergedContentSequence.find( - group => group.ValueType === 'UIDREF' - ); + const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF'); const TrackingIdentifierContentItem = mergedContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.TrackingIdentifier + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingIdentifier ); const finding = mergedContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.Finding + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.Finding ); const findingSites = mergedContentSequence.filter( item => - item.ConceptNameCodeSequence.CodingSchemeDesignator === - CodingSchemeDesignators.SRT && - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.FindingSite + item.ConceptNameCodeSequence.CodingSchemeDesignator === CodingSchemeDesignators.SRT && + item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.FindingSite ); const measurement = { @@ -537,8 +446,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { CodingSchemeDesignators.CornerstoneCodeSchemes.includes( finding.ConceptCodeSequence.CodingSchemeDesignator ) && - finding.ConceptCodeSequence.CodeValue === - CodeNameCodeSequenceValues.CornerstoneFreeText + finding.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText ) { measurement.labels.push({ label: CORNERSTONE_FREETEXT_CODE_VALUE, @@ -553,8 +461,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { CodingSchemeDesignators.CornerstoneCodeSchemes.includes( FindingSite.ConceptCodeSequence.CodingSchemeDesignator ) && - FindingSite.ConceptCodeSequence.CodeValue === - CodeNameCodeSequenceValues.CornerstoneFreeText + FindingSite.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText ); if (cornerstoneFreeTextFindingSite) { @@ -566,18 +473,12 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { } NUMContentItems.forEach(item => { - const { - ConceptNameCodeSequence, - ContentSequence, - MeasuredValueSequence, - } = item; + const { ConceptNameCodeSequence, ContentSequence, MeasuredValueSequence } = item; const { ValueType } = ContentSequence; if (!ValueType === 'SCOORD') { - console.warn( - `Graphic ${ValueType} not currently supported, skipping annotation.` - ); + console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`); return; } @@ -590,10 +491,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { if (MeasuredValueSequence) { measurement.labels.push( - _getLabelFromMeasuredValueSequence( - ConceptNameCodeSequence, - MeasuredValueSequence - ) + _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) ); } }); @@ -633,17 +531,12 @@ function _getCoordsFromSCOORDOrSCOORD3D(item) { return coords; } -function _getLabelFromMeasuredValueSequence( - ConceptNameCodeSequence, - MeasuredValueSequence -) { +function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) { const { CodeMeaning } = ConceptNameCodeSequence; const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence; const { CodeValue } = MeasurementUnitsCodeSequence; - const formatedNumericValue = NumericValue - ? Number(NumericValue).toFixed(2) - : ''; + const formatedNumericValue = NumericValue ? Number(NumericValue).toFixed(2) : ''; return { label: CodeMeaning, @@ -653,24 +546,20 @@ function _getLabelFromMeasuredValueSequence( function _getReferencedImagesList(ImagingMeasurementReportContentSequence) { const ImageLibrary = ImagingMeasurementReportContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImageLibrary + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary ); - const ImageLibraryGroup = _getSequenceAsArray( - ImageLibrary.ContentSequence - ).find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImageLibraryGroup + const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find( + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup ); const referencedImages = []; _getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => { const { ReferencedSOPSequence } = item; - if (!ReferencedSOPSequence) return; + if (!ReferencedSOPSequence) { + return; + } for (const ref of _getSequenceAsArray(ReferencedSOPSequence)) { if (ref.ReferencedSOPClassUID) { const { ReferencedSOPClassUID, ReferencedSOPInstanceUID } = ref; @@ -687,7 +576,9 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) { } function _getSequenceAsArray(sequence) { - if (!sequence) return []; + if (!sequence) { + return []; + } return Array.isArray(sequence) ? sequence : [sequence]; } diff --git a/extensions/cornerstone-dicom-sr/src/index.tsx b/extensions/cornerstone-dicom-sr/src/index.tsx index 9b87ea98cb..9b0700481e 100644 --- a/extensions/cornerstone-dicom-sr/src/index.tsx +++ b/extensions/cornerstone-dicom-sr/src/index.tsx @@ -1,8 +1,6 @@ import React from 'react'; import getSopClassHandlerModule from './getSopClassHandlerModule'; -import getHangingProtocolModule, { - srProtocol, -} from './getHangingProtocolModule'; +import getHangingProtocolModule, { srProtocol } from './getHangingProtocolModule'; import onModeEnter from './onModeEnter'; import getCommandsModule from './commandsModule'; import preRegistration from './init'; @@ -12,9 +10,7 @@ import hydrateStructuredReport from './utils/hydrateStructuredReport'; import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport'); }); const OHIFCornerstoneSRViewport = props => { @@ -58,7 +54,7 @@ const dicomSRExtension = { }, getCommandsModule, getSopClassHandlerModule, - // Include dynmically computed values such as toolNames not known till instantiation + // Include dynamically computed values such as toolNames not known till instantiation getUtilityModule({ servicesManager }) { return [ { diff --git a/extensions/cornerstone-dicom-sr/src/init.ts b/extensions/cornerstone-dicom-sr/src/init.ts index 84cbcaf37f..1ab3300d77 100644 --- a/extensions/cornerstone-dicom-sr/src/init.ts +++ b/extensions/cornerstone-dicom-sr/src/init.ts @@ -18,9 +18,7 @@ import toolNames from './tools/toolNames'; /** * @param {object} configuration */ -export default function init({ - configuration = {}, -}: Types.Extensions.ExtensionParams): void { +export default function init({ configuration = {} }: Types.Extensions.ExtensionParams): void { addTool(DICOMSRDisplayTool); addToolInstance(toolNames.SRLength, LengthTool, {}); addToolInstance(toolNames.SRBidirectional, BidirectionalTool); diff --git a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts index 347159d206..8160ea6501 100644 --- a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts +++ b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts @@ -40,49 +40,32 @@ export default class DICOMSRDisplayTool extends AnnotationTool { isPointNearTool = () => null; getHandleNearImagePoint = () => null; - renderAnnotation = ( - enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + renderAnnotation = (enabledElement: Types.IEnabledElement, svgDrawingHelper: any): void => { const { viewport } = enabledElement; const { element } = viewport; - let annotations = annotation.state.getAnnotations( - this.getToolName(), - element - ); + let annotations = annotation.state.getAnnotations(this.getToolName(), element); // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender if (!annotations?.length) { return; } - annotations = this.filterInteractableAnnotationsForElement( - element, - annotations - ); + annotations = this.filterInteractableAnnotationsForElement(element, annotations); if (!annotations?.length) { return; } - const trackingUniqueIdentifiersForElement = getTrackingUniqueIdentifiersForElement( - element - ); + const trackingUniqueIdentifiersForElement = getTrackingUniqueIdentifiersForElement(element); - const { - activeIndex, - trackingUniqueIdentifiers, - } = trackingUniqueIdentifiersForElement; + const { activeIndex, trackingUniqueIdentifiers } = trackingUniqueIdentifiersForElement; - const activeTrackingUniqueIdentifier = - trackingUniqueIdentifiers[activeIndex]; + const activeTrackingUniqueIdentifier = trackingUniqueIdentifiers[activeIndex]; // Filter toolData to only render the data for the active SR. const filteredAnnotations = annotations.filter(annotation => - trackingUniqueIdentifiers.includes( - annotation.data?.cachedStats?.TrackingUniqueIdentifier - ) + trackingUniqueIdentifiers.includes(annotation.data?.cachedStats?.TrackingUniqueIdentifier) ); if (!viewport._actors?.size) { @@ -138,8 +121,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { break; case SCOORD_TYPES.ELLIPSE: renderMethod = this.renderEllipse; - canvasCoordinatesAdapter = - utilities.math.ellipse.getCanvasEllipseCorners; + canvasCoordinatesAdapter = utilities.math.ellipse.getCanvasEllipseCorners; break; default: throw new Error(`Unsupported GraphicType: ${GraphicType}`); @@ -221,15 +203,9 @@ export default class DICOMSRDisplayTool extends AnnotationTool { renderableData.map((data, index) => { canvasCoordinates = data.map(p => viewport.worldToCanvas(p)); const handleGroupUID = '0'; - drawing.drawHandles( - svgDrawingHelper, - annotationUID, - handleGroupUID, - canvasCoordinates, - { - color: options.color, - } - ); + drawing.drawHandles(svgDrawingHelper, annotationUID, handleGroupUID, canvasCoordinates, { + color: options.color, + }); }); } @@ -248,10 +224,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { canvasCoordinates.push(viewport.worldToCanvas(point)); // We get the other point for the arrow by using the image size - const imagePixelModule = metaData.get( - 'imagePixelModule', - referencedImageId - ); + const imagePixelModule = metaData.get('imagePixelModule', referencedImageId); let xOffset = 10; let yOffset = 10; @@ -309,23 +282,19 @@ export default class DICOMSRDisplayTool extends AnnotationTool { const rotation = viewport.getRotation(); - canvasCoordinates = ellipsePointsWorld.map(p => - viewport.worldToCanvas(p) - ); + canvasCoordinates = ellipsePointsWorld.map(p => viewport.worldToCanvas(p)); let canvasCorners; if (rotation == 90 || rotation == 270) { - canvasCorners = >( - utilities.math.ellipse.getCanvasEllipseCorners([ - canvasCoordinates[2], - canvasCoordinates[3], - canvasCoordinates[0], - canvasCoordinates[1], - ]) - ); + canvasCorners = utilities.math.ellipse.getCanvasEllipseCorners([ + canvasCoordinates[2], + canvasCoordinates[3], + canvasCoordinates[0], + canvasCoordinates[1], + ]) as Array; } else { - canvasCorners = >( - utilities.math.ellipse.getCanvasEllipseCorners(canvasCoordinates) - ); + canvasCorners = utilities.math.ellipse.getCanvasEllipseCorners( + canvasCoordinates + ) as Array; } const lineUID = `${index}`; @@ -368,23 +337,14 @@ export default class DICOMSRDisplayTool extends AnnotationTool { adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates); } const textLines = this._getTextBoxLinesFromLabels(label); - const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas( - adaptedCanvasCoordinates - ); + const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates); - annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld( - canvasTextBoxCoords - ); + annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords); - const textBoxPosition = viewport.worldToCanvas( - annotation.data.handles.textBox.worldPosition - ); + const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition); const textBoxUID = '1'; - const textBoxOptions = this.getLinkedTextBoxStyle( - styleSpecifier, - annotation - ); + const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation); const boundingBox = drawing.drawLinkedTextBox( svgDrawingHelper, diff --git a/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js b/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js index f359e3d273..5636e6563d 100644 --- a/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js +++ b/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js @@ -27,15 +27,11 @@ function setTrackingUniqueIdentifiersForElement( }; } -function setActiveTrackingUniqueIdentifierForElement( - element, - TrackingUniqueIdentifier -) { +function setActiveTrackingUniqueIdentifierForElement(element, TrackingUniqueIdentifier) { const enabledElement = getEnabledElement(element); const { viewport } = enabledElement; - const trackingIdentifiersForElement = - state.trackingIdentifiersByViewportId[viewport.id]; + const trackingIdentifiersForElement = state.trackingIdentifiersByViewportId[viewport.id]; if (trackingIdentifiersForElement) { const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex( diff --git a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts b/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts index ac8e43003f..e78b3c7f15 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts @@ -8,11 +8,7 @@ const EPSILON = 1e-4; const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; -export default function addMeasurement( - measurement, - imageId, - displaySetInstanceUID -) { +export default function addMeasurement(measurement, imageId, displaySetInstanceUID) { // TODO -> Render rotated ellipse . const toolName = toolNames.DICOMSRDisplay; @@ -31,12 +27,7 @@ export default function addMeasurement( } measurementData.renderableData[GraphicType].push( - _getRenderableData( - GraphicType, - GraphicData, - imageId, - measurement.TrackingIdentifier - ) + _getRenderableData(GraphicType, GraphicData, imageId, measurement.TrackingIdentifier) ); }); @@ -86,12 +77,7 @@ export default function addMeasurement( delete measurement.coords; } -function _getRenderableData( - GraphicType, - GraphicData, - imageId, - TrackingIdentifier -) { +function _getRenderableData(GraphicType, GraphicData, imageId, TrackingIdentifier) { const [cornerstoneTag, toolName] = TrackingIdentifier.split(':'); let renderableData: csTypes.Point3[]; @@ -207,38 +193,22 @@ function _getRenderableData( throw new Error('imageId does not have imagePlaneModule metadata'); } - const { - columnCosines, - }: { columnCosines: csTypes.Point3 } = imagePlaneModule; + const { columnCosines }: { columnCosines: csTypes.Point3 } = imagePlaneModule; // find which axis is parallel to the columnCosines const columnCosinesVec = vec3.fromValues(...columnCosines); - const projectedMajorAxisOnColVec = Math.abs( - vec3.dot(columnCosinesVec, majorAxisVec) - ); - const projectedMinorAxisOnColVec = Math.abs( - vec3.dot(columnCosinesVec, minorAxisVec) - ); + const projectedMajorAxisOnColVec = Math.abs(vec3.dot(columnCosinesVec, majorAxisVec)); + const projectedMinorAxisOnColVec = Math.abs(vec3.dot(columnCosinesVec, minorAxisVec)); const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec); const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec); renderableData = []; if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) { - renderableData = [ - pointsWorld[0], - pointsWorld[1], - pointsWorld[2], - pointsWorld[3], - ]; + renderableData = [pointsWorld[0], pointsWorld[1], pointsWorld[2], pointsWorld[3]]; } else if (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) { - renderableData = [ - pointsWorld[2], - pointsWorld[3], - pointsWorld[0], - pointsWorld[1], - ]; + renderableData = [pointsWorld[2], pointsWorld[3], pointsWorld[0], pointsWorld[1]]; } else { console.warn('OBLIQUE ELLIPSE NOT YET SUPPORTED'); } diff --git a/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts b/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts index 889926d812..bd4cf2d1e9 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts @@ -1,10 +1,6 @@ import { addTool } from '@cornerstonejs/tools'; -export default function addToolInstance( - name: string, - toolClass, - configuration? -): void { +export default function addToolInstance(name: string, toolClass, configuration?): void { class InstanceClass extends toolClass { static toolName = name; } diff --git a/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts b/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts index a1dd42d2c2..7241641651 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts @@ -3,12 +3,11 @@ import { DisplaySetService, classes } from '@ohif/core'; const ImageSet = classes.ImageSet; const findInstance = (measurement, displaySetService: DisplaySetService) => { - const { displaySetInstanceUID, ReferencedSOPInstanceUID: sopUid } = - measurement; - const referencedDisplaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); - if (!referencedDisplaySet.images) return; + const { displaySetInstanceUID, ReferencedSOPInstanceUID: sopUid } = measurement; + const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + if (!referencedDisplaySet.images) { + return; + } return referencedDisplaySet.images.find(it => it.SOPInstanceUID === sopUid); }; @@ -16,16 +15,17 @@ const findInstance = (measurement, displaySetService: DisplaySetService) => { * contained within the provided display set. * @return an array of instances referenced. */ -const findReferencedInstances = ( - displaySetService: DisplaySetService, - displaySet -) => { +const findReferencedInstances = (displaySetService: DisplaySetService, displaySet) => { const instances = []; const instanceById = {}; for (const measurement of displaySet.measurements) { const { imageId } = measurement; - if (!imageId) continue; - if (instanceById[imageId]) continue; + if (!imageId) { + continue; + } + if (instanceById[imageId]) { + continue; + } const instance = findInstance(measurement, displaySetService); if (!instance) { @@ -50,7 +50,7 @@ const findReferencedInstances = ( const createReferencedImageDisplaySet = (displaySetService, displaySet) => { const instances = findReferencedInstances(displaySetService, displaySet); // This will be a member function of the created image set - const updateInstances = function() { + const updateInstances = function () { this.images.splice( 0, this.images.length, diff --git a/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js b/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js index 99ffbe0b54..30e6815765 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js +++ b/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js @@ -9,11 +9,12 @@ const findInstanceMetadataBySopInstanceUID = (displaySets, SOPInstanceUID) => { let instanceFound; displaySets.find(displaySet => { - if (!displaySet.images) return false; + if (!displaySet.images) { + return false; + } instanceFound = displaySet.images.find( - instanceMetadata => - instanceMetadata.getSOPInstanceUID() === SOPInstanceUID + instanceMetadata => instanceMetadata.getSOPInstanceUID() === SOPInstanceUID ); return !!instanceFound; diff --git a/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js b/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js index 9f8a3129ad..138d72df98 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js +++ b/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js @@ -18,10 +18,7 @@ const findMostRecentStructuredReport = studies => { } if (isStructuredReportSeries(series)) { - if ( - !mostRecentStructuredReport || - compareSeriesDate(series, mostRecentStructuredReport) - ) { + if (!mostRecentStructuredReport || compareSeriesDate(series, mostRecentStructuredReport)) { mostRecentStructuredReport = series; } } @@ -38,10 +35,7 @@ const findMostRecentStructuredReport = studies => { * @returns {boolean} */ const isStructuredReportSeries = series => { - const supportedSopClassUIDs = [ - '1.2.840.10008.5.1.4.1.1.88.22', - '1.2.840.10008.5.1.4.1.1.11.1', - ]; + const supportedSopClassUIDs = ['1.2.840.10008.5.1.4.1.1.88.22', '1.2.840.10008.5.1.4.1.1.11.1']; const firstInstance = series.getFirstInstance(); const SOPClassUID = firstInstance.getData().metadata.SOPClassUID; @@ -50,7 +44,7 @@ const isStructuredReportSeries = series => { }; /** - * Checkes if series1 is newer than series2 + * Checks if series1 is newer than series2 * * @param {Object} series1 - Series Metadata 1 * @param {Object} series2 - Series Metadata 2 diff --git a/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts b/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts index 18c5b8465e..2c28311d57 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts @@ -2,17 +2,12 @@ import OHIF from '@ohif/core'; import { annotation } from '@cornerstonejs/tools'; const { log } = OHIF; -function getFilteredCornerstoneToolState( - measurementData, - additionalFindingTypes -) { +function getFilteredCornerstoneToolState(measurementData, additionalFindingTypes) { const filteredToolState = {}; function addToFilteredToolState(annotation, toolType) { if (!annotation.metadata?.referencedImageId) { - log.warn( - `[DICOMSR] No referencedImageId found for ${toolType} ${annotation.id}` - ); + log.warn(`[DICOMSR] No referencedImageId found for ${toolType} ${annotation.id}`); return; } @@ -30,9 +25,7 @@ function getFilteredCornerstoneToolState( }; } - const measurementDataI = measurementData.find( - md => md.uid === annotation.annotationUID - ); + const measurementDataI = measurementData.find(md => md.uid === annotation.annotationUID); const toolData = imageIdSpecificToolState[toolType].data; let { finding } = measurementDataI; @@ -77,9 +70,7 @@ function getFilteredCornerstoneToolState( for (let i = 0; i < framesOfReference.length; i++) { const frameOfReference = framesOfReference[i]; - const frameOfReferenceAnnotations = annotationManager.getAnnotations( - frameOfReference - ); + const frameOfReferenceAnnotations = annotationManager.getAnnotations(frameOfReference); const toolTypes = Object.keys(frameOfReferenceAnnotations); @@ -91,9 +82,7 @@ function getFilteredCornerstoneToolState( if (annotations) { for (let k = 0; k < annotations.length; k++) { const annotation = annotations[k]; - const uidIndex = uids.findIndex( - uid => uid === annotation.annotationUID - ); + const uidIndex = uids.findIndex(uid => uid === annotation.annotationUID); if (uidIndex !== -1) { addToFilteredToolState(annotation, toolType); diff --git a/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js b/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js index 18a2ad1f5c..1fa18ee9c5 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js +++ b/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js @@ -9,9 +9,7 @@ export default function getLabelFromDCMJSImportedToolData(toolData) { const { findingSites = [], finding } = toolData; - let freeTextLabel = findingSites.find( - fs => fs.CodeValue === 'CORNERSTONEFREETEXT' - ); + let freeTextLabel = findingSites.find(fs => fs.CodeValue === 'CORNERSTONEFREETEXT'); if (freeTextLabel) { return freeTextLabel.CodeMeaning; diff --git a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js index 346bfc5db1..17216a8442 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js +++ b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js @@ -2,6 +2,8 @@ import { utilities, metaData } from '@cornerstonejs/core'; import OHIF, { DicomMetadataStore } from '@ohif/core'; import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData'; import { adaptersSR } from '@cornerstonejs/adapters'; +import { annotation as CsAnnotation } from '@cornerstonejs/tools'; +const { locking } = CsAnnotation; const { guid } = OHIF.utils; const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D; @@ -12,20 +14,26 @@ const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1'; const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; const convertCode = (codingValues, code) => { - if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') return; + if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') { + return; + } const ref = `${code.CodingSchemeDesignator}:${code.CodeValue}`; const ret = { ...codingValues[ref], ref, ...code, text: code.CodeMeaning }; return ret; }; const convertSites = (codingValues, sites) => { - if (!sites || !sites.length) return; + if (!sites || !sites.length) { + return; + } const ret = []; // Do as a loop to convert away from Proxy instances for (let i = 0; i < sites.length; i++) { // Deal with irregular conversion from dcmjs const site = convertCode(codingValues, sites[i][0] || sites[i]); - if (site) ret.push(site); + if (site) { + ret.push(site); + } } return (ret.length && ret) || undefined; }; @@ -35,23 +43,16 @@ const convertSites = (codingValues, sites) => { * */ export default function hydrateStructuredReport( - { servicesManager, extensionManager }, + { servicesManager, extensionManager, appConfig }, displaySetInstanceUID ) { + const annotationManager = CsAnnotation.state.getAnnotationManager(); + const disableEditing = appConfig?.disableEditing; const dataSource = extensionManager.getActiveDataSource()[0]; - const { - measurementService, - displaySetService, - customizationService, - } = servicesManager.services; - - const codingValues = customizationService.getCustomization( - 'codingValues', - {} - ); - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const { measurementService, displaySetService, customizationService } = servicesManager.services; + + const codingValues = customizationService.getCustomization('codingValues', {}); + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); // TODO -> We should define a strict versioning somewhere. const mappings = measurementService.getSourceMappings( @@ -115,16 +116,14 @@ export default function hydrateStructuredReport( // TODO: notification if no hydratable? Object.keys(hydratableMeasurementsInSR).forEach(annotationType => { - const toolDataForAnnotationType = - hydratableMeasurementsInSR[annotationType]; + const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType]; toolDataForAnnotationType.forEach(toolData => { // Add the measurement to toolState // dcmjs and Cornerstone3D has structural defect in supporting multi-frame // files, and looking up the imageId from sopInstanceUIDToImageId results // in the wrong value. - const frameNumber = - (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; + const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; const imageId = imageIdsForToolState[toolData.sopInstanceUid][frameNumber] || sopInstanceUIDToImageId[toolData.sopInstanceUid]; @@ -140,10 +139,7 @@ export default function hydrateStructuredReport( for (let i = 0; i < imageIds.length; i++) { const imageId = imageIds[i]; - const { SeriesInstanceUID, StudyInstanceUID } = metaData.get( - 'instance', - imageId - ); + const { SeriesInstanceUID, StudyInstanceUID } = metaData.get('instance', imageId); if (!SeriesInstanceUIDs.includes(SeriesInstanceUID)) { SeriesInstanceUIDs.push(SeriesInstanceUID); @@ -152,23 +148,19 @@ export default function hydrateStructuredReport( if (!targetStudyInstanceUID) { targetStudyInstanceUID = StudyInstanceUID; } else if (targetStudyInstanceUID !== StudyInstanceUID) { - console.warn( - 'NO SUPPORT FOR SRs THAT HAVE MEASUREMENTS FROM MULTIPLE STUDIES.' - ); + console.warn('NO SUPPORT FOR SRs THAT HAVE MEASUREMENTS FROM MULTIPLE STUDIES.'); } } Object.keys(hydratableMeasurementsInSR).forEach(annotationType => { - const toolDataForAnnotationType = - hydratableMeasurementsInSR[annotationType]; + const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType]; toolDataForAnnotationType.forEach(toolData => { // Add the measurement to toolState // dcmjs and Cornerstone3D has structural defect in supporting multi-frame // files, and looking up the imageId from sopInstanceUIDToImageId results // in the wrong value. - const frameNumber = - (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; + const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; const imageId = imageIdsForToolState[toolData.sopInstanceUid][frameNumber] || sopInstanceUIDToImageId[toolData.sopInstanceUid]; @@ -198,21 +190,13 @@ export default function hydrateStructuredReport( CORNERSTONE_3D_TOOLS_SOURCE_VERSION ); annotation.data.label = getLabelFromDCMJSImportedToolData(toolData); - annotation.data.finding = convertCode( - codingValues, - toolData.finding?.[0] - ); - annotation.data.findingSites = convertSites( - codingValues, - toolData.findingSites - ); + annotation.data.finding = convertCode(codingValues, toolData.finding?.[0]); + annotation.data.findingSites = convertSites(codingValues, toolData.findingSites); annotation.data.site = annotation.data.findingSites?.[0]; - const matchingMapping = mappings.find( - m => m.annotationType === annotationType - ); + const matchingMapping = mappings.find(m => m.annotationType === annotationType); - measurementService.addRawMeasurement( + const newAnnotationUID = measurementService.addRawMeasurement( source, annotationType, { annotation }, @@ -220,6 +204,11 @@ export default function hydrateStructuredReport( dataSource ); + if (disableEditing) { + const addedAnnotation = annotationManager.getAnnotation(newAnnotationUID); + locking.setAnnotationLocked(addedAnnotation, true); + } + if (!imageIds.includes(imageId)) { imageIds.push(imageId); } @@ -245,15 +234,14 @@ function _mapLegacyDataSet(dataset) { ); // Retrieve the Measurements themselves - const measurementGroups = toArray( - imagingMeasurementContent.ContentSequence - ).filter(codeMeaningEquals(GROUP)); + const measurementGroups = toArray(imagingMeasurementContent.ContentSequence).filter( + codeMeaningEquals(GROUP) + ); // For each of the supported measurement types, compute the measurement data const measurementData = {}; - const cornerstoneToolClasses = - MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; + const cornerstoneToolClasses = MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; const registeredToolClasses = []; @@ -263,13 +251,10 @@ function _mapLegacyDataSet(dataset) { }); measurementGroups.forEach((measurementGroup, index) => { - const measurementGroupContentSequence = toArray( - measurementGroup.ContentSequence - ); + const measurementGroupContentSequence = toArray(measurementGroup.ContentSequence); const TrackingIdentifierGroup = measurementGroupContentSequence.find( - contentItem => - contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER + contentItem => contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER ); const TrackingIdentifier = TrackingIdentifierGroup.TextValue; diff --git a/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js index 41602dd257..d6f6a74841 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js +++ b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js @@ -1,8 +1,7 @@ import { adaptersSR } from '@cornerstonejs/adapters'; const cornerstoneAdapters = - adaptersSR.Cornerstone3D.MeasurementReport - .CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; + adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG; @@ -24,8 +23,7 @@ export default function isRehydratable(displaySet, mappings) { const adapterKeys = Object.keys(cornerstoneAdapters).filter( adapterKey => - typeof cornerstoneAdapters[adapterKey] - .isValidCornerstoneTrackingIdentifier === 'function' + typeof cornerstoneAdapters[adapterKey].isValidCornerstoneTrackingIdentifier === 'function' ); const adapters = []; @@ -48,19 +46,13 @@ export default function isRehydratable(displaySet, mappings) { const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`; - return adapter.isValidCornerstoneTrackingIdentifier( - mappedTrackingIdentifier - ); + return adapter.isValidCornerstoneTrackingIdentifier(mappedTrackingIdentifier); }); if (hydratable) { return true; } - console.log( - 'Measurement is not rehydratable', - TrackingIdentifier, - measurements[i] - ); + console.log('Measurement is not rehydratable', TrackingIdentifier, measurements[i]); } console.log('No measurements found which were rehydratable'); diff --git a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx index 8d56f400d3..a1e728bdda 100644 --- a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx +++ b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx @@ -7,11 +7,11 @@ import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSR import { Icon, Tooltip, useViewportGrid, ViewportActionBar } from '@ohif/ui'; import hydrateStructuredReport from '../utils/hydrateStructuredReport'; +import { useAppConfig } from '@state'; const { formatDate } = utils; -const MEASUREMENT_TRACKING_EXTENSION_ID = - '@ohif/extension-measurement-tracking'; +const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking'; const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup'; @@ -20,18 +20,18 @@ function OHIFCornerstoneSRViewport(props) { children, dataSource, displaySets, - viewportIndex, viewportLabel, viewportOptions, servicesManager, extensionManager, } = props; - const { - displaySetService, - cornerstoneViewportService, - measurementService, - } = servicesManager.services; + const [appConfig] = useAppConfig(); + + const { displaySetService, cornerstoneViewportService, measurementService } = + servicesManager.services; + + const viewportId = viewportOptions.viewportId; // SR viewport will always have a single display set if (displaySets.length > 1) { @@ -43,15 +43,10 @@ function OHIFCornerstoneSRViewport(props) { const [viewportGrid, viewportGridService] = useViewportGrid(); const [measurementSelected, setMeasurementSelected] = useState(0); const [measurementCount, setMeasurementCount] = useState(1); - const [activeImageDisplaySetData, setActiveImageDisplaySetData] = useState( - null - ); - const [ - referencedDisplaySetMetadata, - setReferencedDisplaySetMetadata, - ] = useState(null); + const [activeImageDisplaySetData, setActiveImageDisplaySetData] = useState(null); + const [referencedDisplaySetMetadata, setReferencedDisplaySetMetadata] = useState(null); const [element, setElement] = useState(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; // Optional hook into tracking extension, if present. let trackedMeasurements; @@ -76,16 +71,14 @@ function OHIFCornerstoneSRViewport(props) { sendTrackedMeasurementsEvent = (eventName, { displaySetInstanceUID }) => { measurementService.clearMeasurements(); const { SeriesInstanceUIDs } = hydrateStructuredReport( - { servicesManager, extensionManager }, + { servicesManager, extensionManager, appConfig }, displaySetInstanceUID ); - const displaySets = displaySetService.getDisplaySetsForSeries( - SeriesInstanceUIDs[0] - ); + const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUIDs[0]); if (displaySets.length) { viewportGridService.setDisplaySetsForViewports([ { - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID], }, ]); @@ -123,11 +116,7 @@ function OHIFCornerstoneSRViewport(props) { const updateViewport = useCallback( newMeasurementSelected => { - const { - StudyInstanceUID, - displaySetInstanceUID, - sopClassUids, - } = srDisplaySet; + const { StudyInstanceUID, displaySetInstanceUID, sopClassUids } = srDisplaySet; if (!StudyInstanceUID || !displaySetInstanceUID) { return; @@ -136,9 +125,7 @@ function OHIFCornerstoneSRViewport(props) { if (sopClassUids && sopClassUids.length > 1) { // Todo: what happens if there are multiple SOP Classes? Why we are // not throwing an error? - console.warn( - 'More than one SOPClassUID in the same series is not yet supported.' - ); + console.warn('More than one SOPClassUID in the same series is not yet supported.'); } _getViewportReferencedDisplaySetData( @@ -160,19 +147,11 @@ function OHIFCornerstoneSRViewport(props) { // imageIdIndex will handle it by updating the viewport, but if they // are the same we just need to use measurementService to jump to the // new measurement - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - - const csViewport = cornerstoneViewportService.getCornerstoneViewport( - viewportInfo.getViewportId() - ); + const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); const imageIds = csViewport.getImageIds(); - const imageIdIndex = imageIds.indexOf( - measurements[newMeasurementSelected].imageId - ); + const imageIdIndex = imageIds.indexOf(measurements[newMeasurementSelected].imageId); if (imageIdIndex !== -1) { csViewport.setImageIdIndex(imageIdIndex); @@ -180,7 +159,7 @@ function OHIFCornerstoneSRViewport(props) { } }); }, - [dataSource, srDisplaySet, activeImageDisplaySetData, viewportIndex] + [dataSource, srDisplaySet, activeImageDisplaySetData, viewportId] ); const getCornerstoneViewport = useCallback(() => { @@ -225,9 +204,10 @@ function OHIFCornerstoneSRViewport(props) { }} onElementEnabled={onElementEnabled} initialImageIndex={initialImageIndex} + isJumpToMeasurementDisabled={true} > ); - }, [activeImageDisplaySetData, viewportIndex, measurementSelected]); + }, [activeImageDisplaySetData, viewportId, measurementSelected]); const onMeasurementChange = useCallback( direction => { @@ -250,12 +230,7 @@ function OHIFCornerstoneSRViewport(props) { setTrackingIdentifiers(newMeasurementSelected); updateViewport(newMeasurementSelected); }, - [ - measurementSelected, - measurementCount, - updateViewport, - setTrackingIdentifiers, - ] + [measurementSelected, measurementCount, updateViewport, setTrackingIdentifiers] ); /** @@ -265,12 +240,10 @@ function OHIFCornerstoneSRViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; - if ( - displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) - ) { + const activeViewport = viewports.get(activeViewportId); + if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -337,7 +310,7 @@ function OHIFCornerstoneSRViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -369,7 +342,7 @@ function OHIFCornerstoneSRViewport(props) { getStatusComponent={() => _getStatusComponent({ srDisplaySet, - viewportIndex, + viewportId, isTracked: false, isRehydratable: srDisplaySet.isRehydratable, isLocked, @@ -383,23 +356,19 @@ function OHIFCornerstoneSRViewport(props) { currentSeries: SeriesNumber, seriesDescription: SeriesDescription || '', patientInformation: { - patientName: PatientName - ? OHIF.utils.formatPN(PatientName.Alphabetic) - : '', + patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '', patientSex: PatientSex || '', patientAge: PatientAge || '', MRN: PatientID || '', thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', spacing: - SpacingBetweenSlices !== undefined - ? `${SpacingBetweenSlices.toFixed(2)}mm` - : '', + SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` : '', scanner: ManufacturerModelName || '', }, }} /> -
+
{getCornerstoneViewport()} {childrenWithProps}
@@ -409,7 +378,7 @@ function OHIFCornerstoneSRViewport(props) { OHIFCornerstoneSRViewport.propTypes = { displaySets: PropTypes.arrayOf(PropTypes.object), - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, dataSource: PropTypes.object, children: PropTypes.node, viewportLabel: PropTypes.string, @@ -434,9 +403,7 @@ async function _getViewportReferencedDisplaySetData( const { displaySetInstanceUID } = measurement; - const referencedDisplaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); const image0 = referencedDisplaySet.images[0]; const referencedDisplaySetMetadata = { @@ -458,7 +425,7 @@ async function _getViewportReferencedDisplaySetData( function _getStatusComponent({ srDisplaySet, - viewportIndex, + viewportId, isRehydratable, isLocked, sendTrackedMeasurementsEvent, @@ -466,7 +433,7 @@ function _getStatusComponent({ const handleMouseUp = () => { sendTrackedMeasurementsEvent('HYDRATE_SR', { displaySetInstanceUID: srDisplaySet.displaySetInstanceUID, - viewportIndex, + viewportId, }); }; @@ -476,8 +443,7 @@ function _getStatusComponent({ // 1 - Incompatible // 2 - Locked // 3 - Rehydratable / Open - const state = - isRehydratable && !isLocked ? 3 : isRehydratable && isLocked ? 2 : 1; + const state = isRehydratable && !isLocked ? 3 : isRehydratable && isLocked ? 2 : 1; let ToolTipMessage = null; let StatusIcon = null; @@ -507,22 +473,25 @@ function _getStatusComponent({ ); break; case 3: - StatusIcon = () => ; - - ToolTipMessage = () => ( -
{`Click ${loadStr} to restore measurements.`}
+ StatusIcon = () => ( + ); + + ToolTipMessage = () =>
{`Click ${loadStr} to restore measurements.`}
; } const StatusArea = () => ( -
-
+
+
SR
{state === 3 && (
@@ -535,7 +504,10 @@ function _getStatusComponent({ return ( <> {ToolTipMessage && ( - } position="bottom-left"> + } + position="bottom-left" + > )} diff --git a/extensions/cornerstone/.webpack/webpack.prod.js b/extensions/cornerstone/.webpack/webpack.prod.js index 74868eb44c..c23590e69f 100644 --- a/extensions/cornerstone/.webpack/webpack.prod.js +++ b/extensions/cornerstone/.webpack/webpack.prod.js @@ -40,13 +40,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/cornerstone/CHANGELOG.md b/extensions/cornerstone/CHANGELOG.md index e69de29bb2..5af4f0d4fc 100644 --- a/extensions/cornerstone/CHANGELOG.md +++ b/extensions/cornerstone/CHANGELOG.md @@ -0,0 +1,253 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + + +### Bug Fixes + +* **measurements:** Update the calibration tool to match changes in CS3D ([#3505](https://github.com/OHIF/Viewers/issues/3505)) ([38af311](https://github.com/OHIF/Viewers/commit/38af3112ec1f94f36c0ef64ff1cf9d21c0981c81)) + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index a172ee2245..930986e526 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "OHIF extension for Cornerstone", "author": "OHIF", "license": "MIT", @@ -36,9 +36,9 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", - "@cornerstonejs/dicom-image-loader": "^0.6.8", - "@ohif/core": "3.6.0", - "@ohif/ui": "3.6.0", + "@cornerstonejs/dicom-image-loader": "^1.16.5", + "@ohif/core": "3.7.0-beta.85", + "@ohif/ui": "3.7.0-beta.85", "dcmjs": "^0.29.6", "dicom-parser": "^1.8.21", "hammerjs": "^2.0.8", @@ -52,10 +52,10 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^1.1.0", - "@cornerstonejs/core": "^1.1.0", - "@cornerstonejs/streaming-image-volume-loader": "^1.1.0", - "@cornerstonejs/tools": "^1.1.0", + "@cornerstonejs/adapters": "^1.16.5", + "@cornerstonejs/core": "^1.16.5", + "@cornerstonejs/streaming-image-volume-loader": "^1.16.5", + "@cornerstonejs/tools": "^1.16.5", "@kitware/vtk.js": "27.3.1", "html2canvas": "^1.4.1", "lodash.debounce": "4.0.8", diff --git a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx index a94cc0fc60..3de29dfd61 100644 --- a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx +++ b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx @@ -10,17 +10,8 @@ import { utilities as csUtils, } from '@cornerstonejs/core'; import { MeasurementService } from '@ohif/core'; -import { - CinePlayer, - useCine, - useViewportGrid, - Notification, - useViewportDialog, -} from '@ohif/ui'; -import { - IStackViewport, - IVolumeViewport, -} from '@cornerstonejs/core/dist/esm/types'; +import { Notification, useViewportDialog } from '@ohif/ui'; +import { IStackViewport, IVolumeViewport } from '@cornerstonejs/core/dist/esm/types'; import { setEnabledElement } from '../state'; @@ -28,6 +19,8 @@ import './OHIFCornerstoneViewport.css'; import CornerstoneOverlays from './Overlays/CornerstoneOverlays'; import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes'; import CornerstoneServices from '../types/CornerstoneServices'; +import CinePlayer from '../components/CinePlayer'; +import { Types } from '@ohif/core'; const STACK = 'stack'; @@ -46,24 +39,19 @@ function areEqual(prevProps, nextProps) { return false; } - if ( - prevProps.viewportOptions.orientation !== - nextProps.viewportOptions.orientation - ) { + if (prevProps.viewportOptions.orientation !== nextProps.viewportOptions.orientation) { + return false; + } + + if (prevProps.viewportOptions.toolGroupId !== nextProps.viewportOptions.toolGroupId) { return false; } - if ( - prevProps.viewportOptions.toolGroupId !== - nextProps.viewportOptions.toolGroupId - ) { + if (prevProps.viewportOptions.viewportType !== nextProps.viewportOptions.viewportType) { return false; } - if ( - prevProps.viewportOptions.viewportType !== - nextProps.viewportOptions.viewportType - ) { + if (nextProps.viewportOptions.needsRerendering) { return false; } @@ -79,8 +67,7 @@ function areEqual(prevProps, nextProps) { const foundDisplaySet = nextDisplaySets.find( nextDisplaySet => - nextDisplaySet.displaySetInstanceUID === - prevDisplaySet.displaySetInstanceUID + nextDisplaySet.displaySetInstanceUID === prevDisplaySet.displaySetInstanceUID ); if (!foundDisplaySet) { @@ -95,9 +82,7 @@ function areEqual(prevProps, nextProps) { // check if their imageIds are the same if (foundDisplaySet.images?.length) { for (let j = 0; j < foundDisplaySet.images.length; j++) { - if ( - foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId - ) { + if (foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId) { return false; } } @@ -111,25 +96,24 @@ function areEqual(prevProps, nextProps) { // Then we don't need to worry about the re-renders if the props change. const OHIFCornerstoneViewport = React.memo(props => { const { - viewportIndex, displaySets, dataSource, viewportOptions, displaySetOptions, servicesManager, + commandsManager, onElementEnabled, onElementDisabled, + isJumpToMeasurementDisabled, // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation // of the imageData in the OHIFCornerstoneViewport. This prop is used // to set the initial state of the viewport's first image to render initialImageIndex, } = props; + const viewportId = viewportOptions.viewportId; const [scrollbarHeight, setScrollbarHeight] = useState('100px'); - const [{ isCineEnabled, cines }, cineService] = useCine(); - const [{ activeViewportIndex }] = useViewportGrid(); const [enabledVPElement, setEnabledVPElement] = useState(null); - const elementRef = useRef(); const { @@ -145,74 +129,6 @@ const OHIFCornerstoneViewport = React.memo(props => { } = servicesManager.services as CornerstoneServices; const [viewportDialogState] = useViewportDialog(); - - const cineHandler = () => { - if (!cines || !cines[viewportIndex] || !enabledVPElement) { - return; - } - - const cine = cines[viewportIndex]; - const isPlaying = cine.isPlaying || false; - const frameRate = cine.frameRate || 24; - - const validFrameRate = Math.max(frameRate, 1); - - if (isPlaying) { - cineService.playClip(enabledVPElement, { - framesPerSecond: validFrameRate, - }); - } else { - cineService.stopClip(enabledVPElement); - } - }; - - useEffect(() => { - eventTarget.addEventListener( - Enums.Events.STACK_VIEWPORT_NEW_STACK, - cineHandler - ); - - return () => { - cineService.setCine({ id: viewportIndex, isPlaying: false }); - eventTarget.removeEventListener( - Enums.Events.STACK_VIEWPORT_NEW_STACK, - cineHandler - ); - }; - }, [enabledVPElement]); - - useEffect(() => { - if (!cines || !cines[viewportIndex] || !enabledVPElement) { - return; - } - - cineHandler(); - - return () => { - if (enabledVPElement && cines?.[viewportIndex]?.isPlaying) { - cineService.stopClip(enabledVPElement); - } - }; - }, [cines, viewportIndex, cineService, enabledVPElement, cineHandler]); - - const cine = cines[viewportIndex]; - const isPlaying = (cine && cine.isPlaying) || false; - - const handleCineClose = () => { - toolbarService.recordInteraction({ - groupId: 'MoreTools', - itemId: 'cine', - interactionType: 'toggle', - commands: [ - { - commandName: 'toggleCine', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - }); - }; - // useCallback for scroll bar height calculation const setImageScrollBarHeight = useCallback(() => { const scrollbarHeight = `${elementRef.current.clientHeight - 20}px`; @@ -227,54 +143,17 @@ const OHIFCornerstoneViewport = React.memo(props => { } }, [elementRef]); - const storePresentation = () => { - const currentPresentation = cornerstoneViewportService.getPresentation( - viewportIndex - ); - if (!currentPresentation || !currentPresentation.presentationIds) return; - const { - lutPresentationStore, - positionPresentationStore, - } = stateSyncService.getState(); - const { presentationIds } = currentPresentation; - const { lutPresentationId, positionPresentationId } = presentationIds || {}; - const storeState = {}; - if (lutPresentationId) { - storeState.lutPresentationStore = { - ...lutPresentationStore, - [lutPresentationId]: currentPresentation, - }; - } - if (positionPresentationId) { - storeState.positionPresentationStore = { - ...positionPresentationStore, - [positionPresentationId]: currentPresentation, - }; - } - stateSyncService.store(storeState); - }; - - const cleanUpServices = useCallback(() => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - - if (!viewportInfo) { - return; - } - - const viewportId = viewportInfo.getViewportId(); - const renderingEngineId = viewportInfo.getRenderingEngineId(); - const syncGroups = viewportInfo.getSyncGroups(); + const cleanUpServices = useCallback( + viewportInfo => { + const renderingEngineId = viewportInfo.getRenderingEngineId(); + const syncGroups = viewportInfo.getSyncGroups(); - toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId); + toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId); - syncGroupService.removeViewportFromSyncGroup( - viewportId, - renderingEngineId, - syncGroups - ); - }, [viewportIndex, viewportOptions.viewportId]); + syncGroupService.removeViewportFromSyncGroup(viewportId, renderingEngineId, syncGroups); + }, + [viewportId] + ); const elementEnabledHandler = useCallback( evt => { @@ -284,71 +163,48 @@ const OHIFCornerstoneViewport = React.memo(props => { } const { viewportId, element } = evt.detail; - const viewportInfo = cornerstoneViewportService.getViewportInfo( - viewportId - ); - const viewportIndex = viewportInfo.getViewportIndex(); - - setEnabledElement(viewportIndex, element); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); + setEnabledElement(viewportId, element); setEnabledVPElement(element); const renderingEngineId = viewportInfo.getRenderingEngineId(); const toolGroupId = viewportInfo.getToolGroupId(); const syncGroups = viewportInfo.getSyncGroups(); - toolGroupService.addViewportToToolGroup( - viewportId, - renderingEngineId, - toolGroupId - ); + toolGroupService.addViewportToToolGroup(viewportId, renderingEngineId, toolGroupId); - syncGroupService.addViewportToSyncGroup( - viewportId, - renderingEngineId, - syncGroups - ); + syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups); if (onElementEnabled) { onElementEnabled(evt); } }, - [viewportIndex, onElementEnabled, toolGroupService] + [viewportId, onElementEnabled, toolGroupService] ); // disable the element upon unmounting useEffect(() => { - cornerstoneViewportService.enableViewport( - viewportIndex, - viewportOptions, - elementRef.current - ); + cornerstoneViewportService.enableViewport(viewportId, elementRef.current); - eventTarget.addEventListener( - Enums.Events.ELEMENT_ENABLED, - elementEnabledHandler - ); + eventTarget.addEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler); setImageScrollBarHeight(); return () => { - storePresentation(); - - cleanUpServices(); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + if (!viewportInfo) { + return; + } - cornerstoneViewportService.disableElement(viewportIndex); + cleanUpServices(viewportInfo); + cornerstoneViewportService.storePresentation({ viewportId }); if (onElementDisabled) { onElementDisabled(viewportInfo); } - eventTarget.removeEventListener( - Enums.Events.ELEMENT_ENABLED, - elementEnabledHandler - ); + eventTarget.removeEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler); }; }, []); @@ -363,10 +219,15 @@ const OHIFCornerstoneViewport = React.memo(props => { useEffect(() => { const { unsubscribe } = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, - async invalidatedDisplaySetInstanceUID => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + async ({ + displaySetInstanceUID: invalidatedDisplaySetInstanceUID, + invalidateData, + }: Types.DisplaySetSeriesMetadataInvalidatedEvent) => { + if (!invalidateData) { + return; + } + + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) { const viewportData = viewportInfo.getViewportData(); @@ -378,18 +239,14 @@ const OHIFCornerstoneViewport = React.memo(props => { ); const keepCamera = true; - cornerstoneViewportService.updateViewport( - viewportIndex, - newViewportData, - keepCamera - ); + cornerstoneViewportService.updateViewport(viewportId, newViewportData, keepCamera); } } ); return () => { unsubscribe(); }; - }, [viewportIndex]); + }, [viewportId]); useEffect(() => { // handle the default viewportType to be stack @@ -408,33 +265,37 @@ const OHIFCornerstoneViewport = React.memo(props => { // The presentation state will have been stored previously by closing // a viewport. Otherwise, this viewport will be unchanged and the // presentation information will be directly carried over. - const { - lutPresentationStore, - positionPresentationStore, - } = stateSyncService.getState(); + const { lutPresentationStore, positionPresentationStore } = stateSyncService.getState(); const { presentationIds } = viewportOptions; const presentations = { - positionPresentation: - positionPresentationStore[presentationIds?.positionPresentationId], - lutPresentation: - lutPresentationStore[presentationIds?.lutPresentationId], + positionPresentation: positionPresentationStore[presentationIds?.positionPresentationId], + lutPresentation: lutPresentationStore[presentationIds?.lutPresentationId], }; let measurement; - if (cacheJumpToMeasurementEvent?.viewportIndex === viewportIndex) { + if (cacheJumpToMeasurementEvent?.viewportId === viewportId) { measurement = cacheJumpToMeasurementEvent.measurement; // Delete the position presentation so that viewport navigates direct presentations.positionPresentation = null; cacheJumpToMeasurementEvent = null; } + // Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component + // Used for segmentation hydration right now, since the logic to decide whether + // a viewport needs to render a segmentation lives inside the CornerstoneViewportService + // so we need to re-render (force update via change of the needsRerendering) so that React + // does the diffing and decides we should render this again (although the id and element has not changed) + // so that the CornerstoneViewportService can decide whether to render the segmentation or not. Not that we reached here we can turn it off. + if (viewportOptions.needsRerendering) { + viewportOptions.needsRerendering = false; + } + cornerstoneViewportService.setViewportData( - viewportIndex, + viewportId, viewportData, viewportOptions, displaySetOptions, presentations ); - if (measurement) { cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid); } @@ -454,11 +315,15 @@ const OHIFCornerstoneViewport = React.memo(props => { * the cache for jumping to see if there is any jump queued, then we jump to the correct slice. */ useEffect(() => { + if (isJumpToMeasurementDisabled) { + return; + } + const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents( measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService @@ -468,7 +333,7 @@ const OHIFCornerstoneViewport = React.memo(props => { measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService @@ -477,17 +342,14 @@ const OHIFCornerstoneViewport = React.memo(props => { return () => { unsubscribeFromJumpToMeasurementEvents(); }; - }, [displaySets, elementRef, viewportIndex]); + }, [displaySets, elementRef, viewportId]); return (
@@ -499,34 +361,20 @@ const OHIFCornerstoneViewport = React.memo(props => { ref={elementRef} >
- {isCineEnabled && ( - - cineService.setCine({ - id: activeViewportIndex, - isPlaying, - }) - } - onFrameRateChange={frameRate => - cineService.setCine({ - id: activeViewportIndex, - frameRate, - }) - } - /> - )} +
- {viewportDialogState.viewportIndex === viewportIndex && ( + {viewportDialogState.viewportId === viewportId && ( displaySet.displaySetInstanceUID - ); const { unsubscribe } = measurementService.subscribe( MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT, props => { cacheJumpToMeasurementEvent = props; - const { viewportIndex: jumpIndex, measurement, isConsumed } = props; - if (!measurement || isConsumed) return; + const { viewportId: jumpId, measurement, isConsumed } = props; + if (!measurement || isConsumed) { + return; + } if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) { // Decide on which viewport should handle this - cacheJumpToMeasurementEvent.cornerstoneViewport = cornerstoneViewportService.getViewportIndexToJump( - jumpIndex, - measurement.displaySetInstanceUID, - { referencedImageId: measurement.referencedImageId } - ); + cacheJumpToMeasurementEvent.cornerstoneViewport = + cornerstoneViewportService.getViewportIdToJump( + jumpId, + measurement.displaySetInstanceUID, + { referencedImageId: measurement.referencedImageId } + ); } - if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportIndex) { + if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) { return; } _jumpToMeasurement( measurement, elementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -590,20 +438,22 @@ function _checkForCachedJumpToMeasurementEvents( measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService ) { - if (!cacheJumpToMeasurementEvent) return; + if (!cacheJumpToMeasurementEvent) { + return; + } if (cacheJumpToMeasurementEvent.isConsumed) { cacheJumpToMeasurementEvent = null; return; } - const displaysUIDs = displaySets.map( - displaySet => displaySet.displaySetInstanceUID - ); - if (!displaysUIDs?.length) return; + const displaysUIDs = displaySets.map(displaySet => displaySet.displaySetInstanceUID); + if (!displaysUIDs?.length) { + return; + } // Jump to measurement if the measurement exists const { measurement } = cacheJumpToMeasurementEvent; @@ -612,7 +462,7 @@ function _checkForCachedJumpToMeasurementEvents( _jumpToMeasurement( measurement, elementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -625,7 +475,7 @@ function _checkForCachedJumpToMeasurementEvents( function _jumpToMeasurement( measurement, targetElementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -639,27 +489,19 @@ function _jumpToMeasurement( return; } - const referencedDisplaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); // Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager // to set it properly // setCornerstoneMeasurementActive(measurement); - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); const enabledElement = getEnabledElement(targetElement); - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - if (enabledElement) { // See how the jumpToSlice() of Cornerstone3D deals with imageIdx param. - const viewport = enabledElement.viewport as - | IStackViewport - | IVolumeViewport; + const viewport = enabledElement.viewport as IStackViewport | IVolumeViewport; let imageIdIndex = 0; let viewportCameraDirectionMatch = true; @@ -667,14 +509,9 @@ function _jumpToMeasurement( if (viewport instanceof StackViewport) { const imageIds = viewport.getImageIds(); imageIdIndex = imageIds.findIndex(imageId => { - const { - SOPInstanceUID: aSOPInstanceUID, - frameNumber: aFrameNumber, - } = getSOPInstanceAttributes(imageId); - return ( - aSOPInstanceUID === SOPInstanceUID && - (!frameNumber || frameNumber === aFrameNumber) - ); + const { SOPInstanceUID: aSOPInstanceUID, frameNumber: aFrameNumber } = + getSOPInstanceAttributes(imageId); + return aSOPInstanceUID === SOPInstanceUID && (!frameNumber || frameNumber === aFrameNumber); }); } else { // for volume viewport we can't rely on the imageIdIndex since it can be @@ -689,10 +526,7 @@ function _jumpToMeasurement( // should compare abs for both planes since the direction can be flipped if ( measurementViewPlane && - !csUtils.isEqual( - measurementViewPlane.map(Math.abs), - viewportViewPlane.map(Math.abs) - ) + !csUtils.isEqual(measurementViewPlane.map(Math.abs), viewportViewPlane.map(Math.abs)) ) { viewportCameraDirectionMatch = false; } @@ -716,21 +550,22 @@ function _jumpToMeasurement( // Component displayName OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport'; +OHIFCornerstoneViewport.defaultProps = { + isJumpToMeasurementDisabled: false, +}; + OHIFCornerstoneViewport.propTypes = { - viewportIndex: PropTypes.number.isRequired, displaySets: PropTypes.array.isRequired, dataSource: PropTypes.object.isRequired, viewportOptions: PropTypes.object, displaySetOptions: PropTypes.arrayOf(PropTypes.any), servicesManager: PropTypes.object.isRequired, onElementEnabled: PropTypes.func, + isJumpToMeasurementDisabled: PropTypes.bool, // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation // of the imageData in the OHIFCornerstoneViewport. This prop is used // to set the initial state of the viewport's first image to render - initialImageIdOrIndex: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), + initialImageIdOrIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), }; export default OHIFCornerstoneViewport; diff --git a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx index ea0c42cb90..69f7b10faa 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx @@ -6,7 +6,7 @@ import ViewportOrientationMarkers from './ViewportOrientationMarkers'; import ViewportImageSliceLoadingIndicator from './ViewportImageSliceLoadingIndicator'; function CornerstoneOverlays(props) { - const { viewportIndex, element, scrollbarHeight, servicesManager } = props; + const { viewportId, element, scrollbarHeight, servicesManager } = props; const { cornerstoneViewportService } = servicesManager.services; const [imageSliceData, setImageSliceData] = useState({ imageIndex: 0, @@ -18,7 +18,7 @@ function CornerstoneOverlays(props) { const { unsubscribe } = cornerstoneViewportService.subscribe( cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED, props => { - if (props.viewportIndex !== viewportIndex) { + if (props.viewportId !== viewportId) { return; } @@ -29,15 +29,14 @@ function CornerstoneOverlays(props) { return () => { unsubscribe(); }; - }, [viewportIndex]); + }, [viewportId]); if (!element) { return null; } if (viewportData) { - const viewportInfo = - cornerstoneViewportService.getViewportInfoByIndex(viewportIndex); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) { return null; @@ -47,7 +46,7 @@ function CornerstoneOverlays(props) { return (
@@ -74,7 +73,7 @@ function CornerstoneOverlays(props) { element={element} viewportData={viewportData} servicesManager={servicesManager} - viewportIndex={viewportIndex} + viewportId={viewportId} />
); diff --git a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx index bf7665200c..8745fdec7f 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx @@ -3,12 +3,7 @@ import { vec3 } from 'gl-matrix'; import PropTypes from 'prop-types'; import { metaData, Enums, utilities } from '@cornerstonejs/core'; import { ViewportOverlay } from '@ohif/ui'; -import { - formatPN, - formatDICOMDate, - formatDICOMTime, - formatNumberPrecision, -} from './utils'; +import { formatPN, formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils'; import { InstanceMetadata } from 'platform/core/src/types'; import { ServicesManager } from '@ohif/core'; import { ImageSliceData } from '@cornerstonejs/core/dist/esm/types'; @@ -21,7 +16,6 @@ interface OverlayItemProps { element: any; viewportData: any; imageSliceData: ImageSliceData; - viewportIndex: number | null; servicesManager: ServicesManager; instance: InstanceMetadata; customization: any; @@ -56,13 +50,9 @@ function VOIOverlayItem({ voi, customization }: OverlayItemProps) { style={{ color: (customization && customization.color) || undefined }} > W: - - {windowWidth.toFixed(0)} - + {windowWidth.toFixed(0)} L: - - {windowCenter.toFixed(0)} - + {windowCenter.toFixed(0)}
); } @@ -114,14 +104,11 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, }) { - const { - toolbarService, - cornerstoneViewportService, - customizationService, - } = servicesManager.services; + const { toolbarService, cornerstoneViewportService, customizationService } = + servicesManager.services; const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null }); const [scale, setScale] = useState(1); const [activeTools, setActiveTools] = useState([]); @@ -150,15 +137,10 @@ function CustomizableViewportOverlay({ const instanceNumber = useMemo(() => { if (viewportData != null) { - return _getInstanceNumber( - viewportData, - viewportIndex, - imageIndex, - cornerstoneViewportService - ); + return _getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService); } return null; - }, [viewportData, viewportIndex, imageIndex, cornerstoneViewportService]); + }, [viewportData, viewportId, imageIndex, cornerstoneViewportService]); /** * Initial toolbar state @@ -179,10 +161,7 @@ function CustomizableViewportOverlay({ } const { lower, upper } = range; - const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel( - lower, - upper - ); + const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(lower, upper); setVOI({ windowCenter, windowWidth }); }; @@ -192,7 +171,7 @@ function CustomizableViewportOverlay({ return () => { element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); }; - }, [viewportIndex, viewportData, voi, element]); + }, [viewportId, viewportData, voi, element]); /** * Updating the scale when the viewport changes its zoom @@ -205,9 +184,7 @@ function CustomizableViewportOverlay({ previousCamera.parallelScale !== camera.parallelScale || previousCamera.scale !== camera.scale ) { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; @@ -226,8 +203,7 @@ function CustomizableViewportOverlay({ const { spacing } = imageData; // convert parallel scale to scale - const scale = - (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; + const scale = (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; setScale(scale); } }; @@ -237,7 +213,7 @@ function CustomizableViewportOverlay({ return () => { element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); }; - }, [viewportIndex, viewportData, cornerstoneViewportService, element]); + }, [viewportId, viewportData, cornerstoneViewportService, element]); /** * Updating the active tools when the toolbar changes @@ -262,7 +238,7 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, customization: item, formatters: { @@ -296,7 +272,7 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, customizationService, instance, @@ -343,9 +319,7 @@ function CustomizableViewportOverlay({ return ( <> {items.map((item, i) => ( -
- {_renderOverlayItem(item)} -
+
{_renderOverlayItem(item)}
))} ); @@ -356,9 +330,7 @@ function CustomizableViewportOverlay({ return ( <> {items.map((item, i) => ( -
- {_renderOverlayItem(item)} -
+
{_renderOverlayItem(item)}
))} ); @@ -379,7 +351,7 @@ function _getViewportInstance(viewportData, imageIndex) { if (viewportData.viewportType === Enums.ViewportType.STACK) { imageId = viewportData.data.imageIds[imageIndex]; } else if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) { - const volumes = viewportData.volumes; + const volumes = viewportData.data; if (volumes && volumes.length == 1) { const volume = volumes[0]; imageId = volume.imageIds[imageIndex]; @@ -388,12 +360,7 @@ function _getViewportInstance(viewportData, imageIndex) { return imageId ? metaData.get('instance', imageId) || {} : {}; } -function _getInstanceNumber( - viewportData, - viewportIndex, - imageIndex, - cornerstoneViewportService -) { +function _getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService) { let instanceNumber; if (viewportData.viewportType === Enums.ViewportType.STACK) { @@ -406,7 +373,7 @@ function _getInstanceNumber( instanceNumber = _getInstanceNumberFromVolume( viewportData, imageIndex, - viewportIndex, + viewportId, cornerstoneViewportService ); } @@ -436,12 +403,7 @@ function _getInstanceNumberFromStack(viewportData, imageIndex) { // Since volume viewports can be in any view direction, they can render // a reconstructed image which don't have imageIds; therefore, no instance and instanceNumber // Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber -function _getInstanceNumberFromVolume( - viewportData, - imageIndex, - viewportIndex, - cornerstoneViewportService -) { +function _getInstanceNumberFromVolume(viewportData, viewportId, cornerstoneViewportService) { const volumes = viewportData.volumes; // Todo: support fusion of acquisition plane which has instanceNumber @@ -452,9 +414,7 @@ function _getInstanceNumberFromVolume( const volume = volumes[0]; const { direction, imageIds } = volume; - const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!cornerstoneViewport) { return; @@ -477,8 +437,7 @@ function _getInstanceNumberFromVolume( return {}; } - const { instanceNumber } = - metaData.get('generalImageModule', imageId) || {}; + const { instanceNumber } = metaData.get('generalImageModule', imageId) || {}; return parseInt(instanceNumber); } } @@ -486,7 +445,7 @@ function _getInstanceNumberFromVolume( CustomizableViewportOverlay.propTypes = { viewportData: PropTypes.object, imageIndex: PropTypes.number, - viewportIndex: PropTypes.number, + viewportId: PropTypes.string, }; export default CustomizableViewportOverlay; diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx index 074e0a969f..2f7b71a45d 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx @@ -7,34 +7,24 @@ import { ServicesManger } from '@ohif/core'; function CornerstoneImageScrollbar({ viewportData, - viewportIndex, + viewportId, element, imageSliceData, setImageSliceData, scrollbarHeight, servicesManager, }) { - const { - cineService, - cornerstoneViewportService, - } = (servicesManager as ServicesManger).services; + const { cineService, cornerstoneViewportService } = (servicesManager as ServicesManger).services; - const onImageScrollbarChange = (imageIndex, viewportIndex) => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - - const viewportId = viewportInfo.getViewportId(); - const viewport = cornerstoneViewportService.getCornerstoneViewport( - viewportId - ); + const onImageScrollbarChange = (imageIndex, viewportId) => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); const { isCineEnabled } = cineService.getState(); if (isCineEnabled) { // on image scrollbar change, stop the CINE if it is playing cineService.stopClip(element); - cineService.setCine({ id: viewportIndex, isPlaying: false }); + cineService.setCine({ id: viewportId, isPlaying: false }); } csToolsUtils.jumpToSlice(viewport.element, { @@ -48,9 +38,7 @@ function CornerstoneImageScrollbar({ return; } - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; @@ -79,7 +67,7 @@ function CornerstoneImageScrollbar({ const { imageIndex, numberOfSlices } = sliceData; setImageSliceData({ imageIndex, numberOfSlices }); } - }, [viewportIndex, viewportData]); + }, [viewportId, viewportData]); useEffect(() => { if (viewportData?.viewportType !== Enums.ViewportType.STACK) { @@ -95,16 +83,10 @@ function CornerstoneImageScrollbar({ }); }; - element.addEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - updateStackIndex - ); + element.addEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex); return () => { - element.removeEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - updateStackIndex - ); + element.removeEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex); }; }, [viewportData, element]); @@ -122,19 +104,14 @@ function CornerstoneImageScrollbar({ element.addEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex); return () => { - element.removeEventListener( - Enums.Events.VOLUME_NEW_IMAGE, - updateVolumeIndex - ); + element.removeEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex); }; }, [viewportData, element]); return ( onImageScrollbarChange(evt, viewportIndex)} - max={ - imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0 - } + onChange={evt => onImageScrollbarChange(evt, viewportId)} + max={imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0} height={scrollbarHeight} value={imageSliceData.imageIndex} /> @@ -143,7 +120,7 @@ function CornerstoneImageScrollbar({ CornerstoneImageScrollbar.propTypes = { viewportData: PropTypes.object, - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, element: PropTypes.instanceOf(Element), scrollbarHeight: PropTypes.string, imageSliceData: PropTypes.object.isRequired, diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx index fc483b5f47..70e03e534c 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx @@ -33,26 +33,14 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) { }; useEffect(() => { - element.addEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - setLoadingState - ); + element.addEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState); element.addEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState); - element.addEventListener( - Enums.Events.STACK_NEW_IMAGE, - setFinishLoadingState - ); + element.addEventListener(Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState); return () => { - element.removeEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - setLoadingState - ); + element.removeEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState); - element.removeEventListener( - Enums.Events.STACK_NEW_IMAGE, - setFinishLoadingState - ); + element.removeEventListener(Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState); element.removeEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState); }; @@ -61,8 +49,8 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) { if (error) { return ( <> -
-
+
+

Error Loading Image

An error has occurred.

@@ -78,8 +66,8 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) { return ( // IMPORTANT: we need to use the pointer-events-none class to prevent the loading indicator from // interacting with the mouse, since scrolling should propagate to the viewport underneath -
-
+
+

Loading...

diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx index 8e431ac525..2f33456a42 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx @@ -13,16 +13,13 @@ import { vec3 } from 'gl-matrix'; import './ViewportOrientationMarkers.css'; -const { - getOrientationStringLPS, - invertOrientationStringLPS, -} = utilities.orientation; +const { getOrientationStringLPS, invertOrientationStringLPS } = utilities.orientation; function ViewportOrientationMarkers({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, orientationMarkers = ['top', 'left'], }) { @@ -33,9 +30,7 @@ function ViewportOrientationMarkers({ const { cornerstoneViewportService } = servicesManager.services; useEffect(() => { - const cameraModifiedListener = ( - evt: Types.EventTypes.CameraModifiedEvent - ) => { + const cameraModifiedListener = (evt: Types.EventTypes.CameraModifiedEvent) => { const { rotation, previousCamera, camera } = evt.detail; if (rotation !== undefined) { @@ -57,16 +52,10 @@ function ViewportOrientationMarkers({ } }; - element.addEventListener( - Enums.Events.CAMERA_MODIFIED, - cameraModifiedListener - ); + element.addEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedListener); return () => { - element.removeEventListener( - Enums.Events.CAMERA_MODIFIED, - cameraModifiedListener - ); + element.removeEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedListener); }; }, []); @@ -85,8 +74,7 @@ function ViewportOrientationMarkers({ return false; } - ({ rowCosines, columnCosines } = - metaData.get('imagePlaneModule', imageId) || {}); + ({ rowCosines, columnCosines } = metaData.get('imagePlaneModule', imageId) || {}); } else { if (!element || !getEnabledElement(element)) { return ''; @@ -114,9 +102,7 @@ function ViewportOrientationMarkers({ flipHorizontal ); - const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId); if (!ohifViewport) { console.log('ViewportOrientationMarkers::No viewport'); @@ -126,9 +112,7 @@ function ViewportOrientationMarkers({ // Todo: probably this can be done in a better way in which we identify bright // background - const isLight = backgroundColor - ? csUtils.isEqual(backgroundColor, [1, 1, 1]) - : false; + const isLight = backgroundColor ? csUtils.isEqual(backgroundColor, [1, 1, 1]) : false; return orientationMarkers.map((m, index) => (
{ element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); }; - }, [viewportIndex, viewportData, voi, element]); + }, [viewportId, viewportData, voi, element]); /** * Updating the scale when the viewport changes its zoom @@ -86,9 +80,7 @@ function CornerstoneViewportOverlay({ previousCamera.parallelScale !== camera.parallelScale || previousCamera.scale !== camera.scale ) { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; @@ -107,8 +99,7 @@ function CornerstoneViewportOverlay({ const { spacing } = imageData; // convert parallel scale to scale - const scale = - (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; + const scale = (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; setScale(scale); } }; @@ -118,7 +109,7 @@ function CornerstoneViewportOverlay({ return () => { element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); }; - }, [viewportIndex, viewportData]); + }, [viewportId, viewportData]); const getTopLeftContent = useCallback(() => { const { windowWidth, windowCenter } = voi; @@ -168,7 +159,7 @@ function CornerstoneViewportOverlay({ instanceNumber = _getInstanceNumberFromVolume( viewportData, imageIndex, - viewportIndex, + viewportId, cornerstoneViewportService ); } @@ -183,15 +174,13 @@ function CornerstoneViewportOverlay({
); - }, [imageSliceData, viewportData, viewportIndex]); + }, [imageSliceData, viewportData, viewportId]); if (!viewportData) { return null; } - const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId); if (!ohifViewport) { return null; @@ -201,9 +190,7 @@ function CornerstoneViewportOverlay({ // Todo: probably this can be done in a better way in which we identify bright // background - const isLight = backgroundColor - ? utilities.isEqual(backgroundColor, [1, 1, 1]) - : false; + const isLight = backgroundColor ? utilities.isEqual(backgroundColor, [1, 1, 1]) : false; return ( { - const viewportInfo = cornerstoneViewportService.getViewportInfo( - viewportId - ); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (!viewportInfo) { console.warn('No viewport found for viewportId:', viewportId); return; } - const viewportIndex = viewportInfo.getViewportIndex(); - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); }, arrowTextCallback: ({ callback, data }) => { callInputDialog(uiDialogService, data, callback); @@ -249,9 +230,7 @@ function commandsModule({ const { isCineEnabled } = cineService.getState(); cineService.setIsCineEnabled(!isCineEnabled); toolbarService.setButton('Cine', { props: { isActive: !isCineEnabled } }); - viewports.forEach((_, index) => - cineService.setCine({ id: index, isPlaying: false }) - ); + viewports.forEach((_, index) => cineService.setCine({ id: index, isPlaying: false })); }, setWindowLevel({ window, level, toolGroupId }) { // convert to numbers @@ -259,9 +238,7 @@ function commandsModule({ const windowCenterNum = Number(level); const { viewportId } = _getActiveViewportEnabledElement(); - const viewportToolGroupId = toolGroupService.getToolGroupForViewport( - viewportId - ); + const viewportToolGroupId = toolGroupService.getToolGroupForViewport(viewportId); if (toolGroupId && toolGroupId !== viewportToolGroupId) { return; @@ -271,10 +248,7 @@ function commandsModule({ const renderingEngine = cornerstoneViewportService.getRenderingEngine(); const viewport = renderingEngine.getViewport(viewportId); - const { lower, upper } = csUtils.windowLevel.toLowHighRange( - windowWidthNum, - windowCenterNum - ); + const { lower, upper } = csUtils.windowLevel.toLowHighRange(windowWidthNum, windowCenterNum); viewport.setProperties({ voiRange: { @@ -291,8 +265,7 @@ function commandsModule({ toolbarServiceRecordInteraction: props => { toolbarService.recordInteraction(props); }, - - setToolActive: ({ toolName, toolGroupId = null }) => { + setToolActive: ({ toolName, toolGroupId = null, toggledState }) => { if (toolName === 'Crosshairs') { const activeViewportToolGroup = toolGroupService.getToolGroup(null); @@ -309,9 +282,11 @@ function commandsModule({ } } - const { viewports } = viewportGridService.getState() || { - viewports: [], - }; + const { viewports } = viewportGridService.getState(); + + if (!viewports.size) { + return; + } const toolGroup = toolGroupService.getToolGroup(toolGroupId); const toolGroupViewportIds = toolGroup?.getViewportIds?.(); @@ -321,14 +296,8 @@ function commandsModule({ return; } - const filteredViewports = viewports.filter(viewport => { - if (!viewport.viewportOptions) { - return false; - } - - return toolGroupViewportIds.includes( - viewport.viewportOptions.viewportId - ); + const filteredViewports = Array.from(viewports.values()).filter(viewport => { + return toolGroupViewportIds.includes(viewport.viewportId); }); if (!filteredViewports.length) { @@ -357,6 +326,14 @@ function commandsModule({ toolGroup.setToolPassive(activeToolName); } } + + // If there is a toggle state, then simply set the enabled/disabled state without + // setting the tool active. + if (toggledState != null) { + toggledState ? toolGroup.setToolEnabled(toolName) : toolGroup.setToolDisabled(toolName); + return; + } + // Set the new toolName to be active toolGroup.setToolActive(toolName, { bindings: [ @@ -367,13 +344,9 @@ function commandsModule({ }); }, showDownloadViewportModal: () => { - const { activeViewportIndex } = viewportGridService.getState(); + const { activeViewportId } = viewportGridService.getState(); - if ( - !cornerstoneViewportService.getCornerstoneViewportByIndex( - activeViewportIndex - ) - ) { + if (!cornerstoneViewportService.getCornerstoneViewport(activeViewportId)) { // Cannot download a non-cornerstone viewport (image). uiNotificationService.show({ title: 'Download Image', @@ -390,7 +363,7 @@ function commandsModule({ content: CornerstoneViewportDownloadForm, title: 'Download High Quality Image', contentProps: { - activeViewportIndex, + activeViewportId, onClose: uiModalService.hide, cornerstoneViewportService, }, @@ -457,11 +430,9 @@ function commandsModule({ const { viewport } = enabledElement; - if (viewport instanceof StackViewport) { - const { invert } = viewport.getProperties(); - viewport.setProperties({ invert: !invert }); - viewport.render(); - } + const { invert } = viewport.getProperties(); + viewport.setProperties({ invert: !invert }); + viewport.render(); }, resetViewport: () => { const enabledElement = _getActiveViewportEnabledElement(); @@ -514,9 +485,7 @@ function commandsModule({ } viewport = enabledElement.viewport; } else { - viewport = cornerstoneViewportService.getCornerstoneViewport( - gridViewport.id - ); + viewport = cornerstoneViewportService.getCornerstoneViewport(gridViewport.id); } // Get number of slices @@ -526,14 +495,12 @@ function commandsModule({ if (viewport instanceof StackViewport) { numberOfSlices = viewport.getImageIds().length; } else if (viewport instanceof VolumeViewport) { - numberOfSlices = csUtils.getImageSliceDataForVolumeViewport(viewport) - .numberOfSlices; + numberOfSlices = csUtils.getImageSliceDataForVolumeViewport(viewport).numberOfSlices; } else { throw new Error('Unsupported viewport type'); } - const jumpIndex = - imageIndex < 0 ? numberOfSlices + imageIndex : imageIndex; + const jumpIndex = imageIndex < 0 ? numberOfSlices + imageIndex : imageIndex; if (jumpIndex >= numberOfSlices || jumpIndex < 0) { throw new Error(`Can't jump to ${imageIndex}`); } @@ -554,15 +521,8 @@ function commandsModule({ cstUtils.scroll(viewport, options); }, - setViewportColormap: ({ - viewportIndex, - displaySetInstanceUID, - colormap, - immediate = false, - }) => { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + setViewportColormap: ({ viewportId, displaySetInstanceUID, colormap, immediate = false }) => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); const actorEntries = viewport.getActors(); @@ -578,17 +538,15 @@ function commandsModule({ viewport.render(); } }, - incrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); - const nextViewportIndex = (activeViewportIndex + 1) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); - }, - decrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); + changeActiveViewport: ({ direction = 1 }) => { + const { activeViewportId, viewports } = viewportGridService.getState(); + const viewportIds = Array.from(viewports.keys()); + const currentIndex = viewportIds.indexOf(activeViewportId); const nextViewportIndex = - (activeViewportIndex - 1 + viewports.length) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); + (currentIndex + direction + viewportIds.length) % viewportIds.length; + viewportGridService.setActiveViewportId(viewportIds[nextViewportIndex] as string); }, + toggleStackImageSync: ({ toggledState }) => { toggleStackImageSync({ getEnabledElement, @@ -596,19 +554,13 @@ function commandsModule({ toggledState, }); }, - toggleReferenceLines: ({ toggledState }) => { - const { activeViewportIndex } = viewportGridService.getState(); - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - activeViewportIndex - ); + setSourceViewportForReferenceLinesTool: ({ toggledState }) => { + const { activeViewportId } = viewportGridService.getState(); + const viewportInfo = cornerstoneViewportService.getViewportInfo(activeViewportId); const viewportId = viewportInfo.getViewportId(); const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); - if (!toggledState) { - toolGroup.setToolDisabled(ReferenceLinesTool.toolName); - } - toolGroup.setToolConfiguration( ReferenceLinesTool.toolName, { @@ -616,7 +568,9 @@ function commandsModule({ }, true // overwrite ); - toolGroup.setToolEnabled(ReferenceLinesTool.toolName); + }, + storePresentation: ({ viewportId }) => { + cornerstoneViewportService.storePresentation({ viewportId }); }, }; @@ -673,10 +627,11 @@ function commandsModule({ options: { rotation: -90 }, }, incrementActiveViewport: { - commandFn: actions.incrementActiveViewport, + commandFn: actions.changeActiveViewport, }, decrementActiveViewport: { - commandFn: actions.decrementActiveViewport, + commandFn: actions.changeActiveViewport, + options: { direction: -1 }, }, flipViewportHorizontal: { commandFn: actions.flipViewportHorizontal, @@ -739,8 +694,13 @@ function commandsModule({ toggleStackImageSync: { commandFn: actions.toggleStackImageSync, }, - toggleReferenceLines: { - commandFn: actions.toggleReferenceLines, + setSourceViewportForReferenceLinesTool: { + commandFn: actions.setSourceViewportForReferenceLinesTool, + }, + storePresentation: { + commandFn: actions.storePresentation, + storeContexts: [], + options: {}, }, }; diff --git a/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx b/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx new file mode 100644 index 0000000000..92bb151032 --- /dev/null +++ b/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx @@ -0,0 +1,97 @@ +import React, { useEffect } from 'react'; +import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui'; +import { Enums, eventTarget } from '@cornerstonejs/core'; + +function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) { + const { toolbarService, customizationService } = servicesManager.services; + const [{ isCineEnabled, cines }, cineService] = useCine(); + const [{ activeViewportId }] = useViewportGrid(); + + const { component: CinePlayerComponent = CinePlayer } = + customizationService.get('cinePlayer') ?? {}; + + const handleCineClose = () => { + toolbarService.recordInteraction({ + groupId: 'MoreTools', + interactionType: 'toggle', + commands: [ + { + commandName: 'toggleCine', + commandOptions: {}, + toolName: 'cine', + context: 'CORNERSTONE', + }, + ], + }); + }; + + const cineHandler = () => { + if (!cines || !cines[viewportId] || !enabledVPElement) { + return; + } + + const cine = cines[viewportId]; + const isPlaying = cine.isPlaying || false; + const frameRate = cine.frameRate || 24; + + const validFrameRate = Math.max(frameRate, 1); + + if (isPlaying) { + cineService.playClip(enabledVPElement, { + framesPerSecond: validFrameRate, + }); + } else { + cineService.stopClip(enabledVPElement); + } + }; + + useEffect(() => { + eventTarget.addEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler); + + return () => { + cineService.setCine({ id: viewportId, isPlaying: false }); + eventTarget.removeEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler); + }; + }, [enabledVPElement]); + + useEffect(() => { + if (!cines || !cines[viewportId] || !enabledVPElement) { + return; + } + + cineHandler(); + + return () => { + if (enabledVPElement && cines?.[viewportId]?.isPlaying) { + cineService.stopClip(enabledVPElement); + } + }; + }, [cines, viewportId, cineService, enabledVPElement, cineHandler]); + + const cine = cines[viewportId]; + const isPlaying = (cine && cine.isPlaying) || false; + + return ( + isCineEnabled && ( + + cineService.setCine({ + id: activeViewportId, + isPlaying, + }) + } + onFrameRateChange={frameRate => + cineService.setCine({ + id: activeViewportId, + frameRate, + }) + } + /> + ) + ); +} + +export default WrappedCinePlayer; diff --git a/extensions/cornerstone/src/components/CinePlayer/index.ts b/extensions/cornerstone/src/components/CinePlayer/index.ts new file mode 100644 index 0000000000..d31d4bed76 --- /dev/null +++ b/extensions/cornerstone/src/components/CinePlayer/index.ts @@ -0,0 +1,3 @@ +import CinePlayer from './CinePlayer'; + +export default CinePlayer; diff --git a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css index 55ddf7922e..d37d2a6a64 100644 --- a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css +++ b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css @@ -1,6 +1,23 @@ .dicom-upload-drop-area-border-dash { - background-image: repeating-linear-gradient(to right, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%), repeating-linear-gradient(to right, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%), repeating-linear-gradient(to bottom, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%), repeating-linear-gradient(to bottom, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%); - background-position: left top, left bottom, left top, right top; + background-image: repeating-linear-gradient( + to right, + #7bb2ce 0%, + #7bb2ce 50%, + transparent 50%, + transparent 100% + ), + repeating-linear-gradient(to right, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%), + repeating-linear-gradient(to bottom, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%), + repeating-linear-gradient(to bottom, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%); + background-position: + left top, + left bottom, + left top, + right top; background-repeat: repeat-x, repeat-x, repeat-y, repeat-y; - background-size: 20px 3px, 20px 3px, 3px 20px, 3px 20px; + background-size: + 20px 3px, + 20px 3px, + 3px 20px, + 3px 20px; } diff --git a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx index 26e596e952..8284ee0705 100644 --- a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx +++ b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import DicomFileUploader from '../../utils/DicomFileUploader'; import DicomUploadProgress from './DicomUploadProgress'; -import { Button } from '@ohif/ui'; +import { Button, ButtonEnums } from '@ohif/ui'; import './DicomUpload.css'; type DicomUploadProps = { @@ -14,19 +14,13 @@ type DicomUploadProps = { onStarted: () => void; }; -function DicomUpload({ - dataSource, - onComplete, - onStarted, -}: DicomUploadProps): ReactElement { +function DicomUpload({ dataSource, onComplete, onStarted }: DicomUploadProps): ReactElement { const baseClassNames = 'min-h-[480px] flex flex-col bg-black select-none'; const [dicomFileUploaderArr, setDicomFileUploaderArr] = useState([]); const onDrop = useCallback(async acceptedFiles => { onStarted(); - setDicomFileUploaderArr( - acceptedFiles.map(file => new DicomFileUploader(file, dataSource)) - ); + setDicomFileUploaderArr(acceptedFiles.map(file => new DicomFileUploader(file, dataSource))); }, []); const getDropZoneComponent = (): ReactElement => { @@ -40,15 +34,16 @@ function DicomUpload({ {({ getRootProps }) => (
- + {({ getRootProps, getInputProps }) => (
)}
- + {({ getRootProps, getInputProps }) => (
or drag images or folders here
-
- (DICOM files supported) -
+
(DICOM files supported)
)} @@ -99,9 +93,7 @@ function DicomUpload({ />
) : ( -
- {getDropZoneComponent()} -
+
{getDropZoneComponent()}
)} ); diff --git a/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx b/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx index d16ee4b9f0..c09d969881 100644 --- a/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx +++ b/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx @@ -1,10 +1,4 @@ -import React, { - useCallback, - useEffect, - useRef, - useState, - ReactElement, -} from 'react'; +import React, { useCallback, useEffect, useRef, useState, ReactElement } from 'react'; import PropTypes from 'prop-types'; import { Button, Icon, ProgressLoadingBar } from '@ohif/ui'; import DicomFileUploader, { @@ -39,18 +33,14 @@ const BASE_INTERVAL_TIME = 15000; // calculate the upload rate. const UPLOAD_RATE_THRESHOLD = 75; -const NO_WRAP_ELLIPSIS_CLASS_NAMES = - 'text-ellipsis whitespace-nowrap overflow-hidden'; +const NO_WRAP_ELLIPSIS_CLASS_NAMES = 'text-ellipsis whitespace-nowrap overflow-hidden'; function DicomUploadProgress({ dicomFileUploaderArr, onComplete, }: DicomUploadProgressProps): ReactElement { const [totalUploadSize] = useState( - dicomFileUploaderArr.reduce( - (acc, fileUploader) => acc + fileUploader.getFileSize(), - 0 - ) + dicomFileUploaderArr.reduce((acc, fileUploader) => acc + fileUploader.getFileSize(), 0) ); const currentUploadSizeRef = useRef(0); @@ -83,15 +73,13 @@ function DicomUploadProgress({ let intervalStartTime = Date.now(); const setUploadRateRef = () => { - const uploadSizeFromStartOfInterval = - currentUploadSizeRef.current - intervalStartUploadSize; + const uploadSizeFromStartOfInterval = currentUploadSizeRef.current - intervalStartUploadSize; const now = Date.now(); const timeSinceStartOfInterval = now - intervalStartTime; // Calculate and set the upload rate (ref) - uploadRateRef.current = - uploadSizeFromStartOfInterval / timeSinceStartOfInterval; + uploadRateRef.current = uploadSizeFromStartOfInterval / timeSinceStartOfInterval; // Reset the interval starting values. intervalStartUploadSize = currentUploadSizeRef.current; @@ -134,28 +122,19 @@ function DicomUploadProgress({ const updateProgress = (percentComplete: number) => { const previousFileUploadSize = currentFileUploadSize; - currentFileUploadSize = Math.round( - (percentComplete / 100) * fileUploader.getFileSize() - ); + currentFileUploadSize = Math.round((percentComplete / 100) * fileUploader.getFileSize()); currentUploadSizeRef.current = Math.min( totalUploadSize, - currentUploadSizeRef.current - - previousFileUploadSize + - currentFileUploadSize + currentUploadSizeRef.current - previousFileUploadSize + currentFileUploadSize ); - setPercentComplete( - (currentUploadSizeRef.current / totalUploadSize) * 100 - ); + setPercentComplete((currentUploadSizeRef.current / totalUploadSize) * 100); if (uploadRateRef.current !== 0) { - const uploadSizeRemaining = - totalUploadSize - currentUploadSizeRef.current; + const uploadSizeRemaining = totalUploadSize - currentUploadSizeRef.current; - const timeRemaining = Math.round( - uploadSizeRemaining / uploadRateRef.current - ); + const timeRemaining = Math.round(uploadSizeRemaining / uploadRateRef.current); if (currentTimeRemaining === null) { currentTimeRemaining = timeRemaining; @@ -168,9 +147,7 @@ function DicomUploadProgress({ // due to rounding, inaccuracies in the estimate and slight variations // in upload rates over time. if (timeRemaining < ONE_MINUTE) { - const currentSecondsRemaining = Math.ceil( - currentTimeRemaining / ONE_SECOND - ); + const currentSecondsRemaining = Math.ceil(currentTimeRemaining / ONE_SECOND); const secondsRemaining = Math.ceil(timeRemaining / ONE_SECOND); const delta = secondsRemaining - currentSecondsRemaining; if (delta < 0 || delta > 2) { @@ -181,9 +158,7 @@ function DicomUploadProgress({ } if (timeRemaining < ONE_HOUR) { - const currentMinutesRemaining = Math.ceil( - currentTimeRemaining / ONE_MINUTE - ); + const currentMinutesRemaining = Math.ceil(currentTimeRemaining / ONE_MINUTE); const minutesRemaining = Math.ceil(timeRemaining / ONE_MINUTE); const delta = minutesRemaining - currentMinutesRemaining; if (delta < 0 || delta > 2) { @@ -199,9 +174,7 @@ function DicomUploadProgress({ } }; - const progressCallback = ( - progressEvent: DicomFileUploaderProgressEvent - ) => { + const progressCallback = (progressEvent: DicomFileUploaderProgressEvent) => { updateProgress(progressEvent.percentComplete); }; @@ -249,16 +222,12 @@ function DicomUploadProgress({ if (timeRemaining < ONE_MINUTE) { const secondsRemaining = Math.ceil(timeRemaining / ONE_SECOND); - return `${secondsRemaining} ${ - secondsRemaining === 1 ? 'second' : 'seconds' - }`; + return `${secondsRemaining} ${secondsRemaining === 1 ? 'second' : 'seconds'}`; } if (timeRemaining < ONE_HOUR) { const minutesRemaining = Math.ceil(timeRemaining / ONE_MINUTE); - return `${minutesRemaining} ${ - minutesRemaining === 1 ? 'minute' : 'minutes' - }`; + return `${minutesRemaining} ${minutesRemaining === 1 ? 'minute' : 'minutes'}`; } const hoursRemaining = Math.ceil(timeRemaining / ONE_HOUR); @@ -278,9 +247,7 @@ function DicomUploadProgress({ const showInfiniteProgressBar = useCallback((): boolean => { return ( getPercentCompleteRounded() < 1 && - (progressBarContainerRef?.current?.offsetWidth ?? 0) * - (percentComplete / 100) < - 1 + (progressBarContainerRef?.current?.offsetWidth ?? 0) * (percentComplete / 100) < 1 ); }, [getPercentCompleteRounded, percentComplete]); @@ -300,17 +267,13 @@ function DicomUploadProgress({ const getNumCompletedAndTimeRemainingComponent = (): ReactElement => { return ( -
+
{numFilesCompleted === dicomFileUploaderArr.length ? ( <> - {`${ - dicomFileUploaderArr.length - } ${ + {`${dicomFileUploaderArr.length} ${ dicomFileUploaderArr.length > 1 ? 'files' : 'file' } completed.`} +
+ ); + })} +
+ + )} +
+
+ ); +} + +export default ItemListComponent; diff --git a/extensions/default/src/Components/SidePanelWithServices.tsx b/extensions/default/src/Components/SidePanelWithServices.tsx new file mode 100644 index 0000000000..23e9841b06 --- /dev/null +++ b/extensions/default/src/Components/SidePanelWithServices.tsx @@ -0,0 +1,60 @@ +import React, { useEffect, useState } from 'react'; +import { SidePanel } from '@ohif/ui'; +import { PanelService, ServicesManager } from '@ohif/core'; + +export type SidePanelWithServicesProps = { + servicesManager: ServicesManager; + side: 'left' | 'right'; + className: string; + activeTabIndex: number; + tabs: any; +}; + +const SidePanelWithServices = ({ + servicesManager, + side, + className, + activeTabIndex: activeTabIndexProp, + tabs, +}) => { + const panelService: PanelService = servicesManager?.services?.panelService; + + // Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM. + // Thus going to the Study List page and back to the viewer resets this flag for a SidePanel. + const [hasBeenOpened, setHasBeenOpened] = useState(false); + const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp); + + useEffect(() => { + if (panelService) { + const activatePanelSubscription = panelService.subscribe( + panelService.EVENTS.ACTIVATE_PANEL, + (activatePanelEvent: Types.ActivatePanelEvent) => { + if (!hasBeenOpened || activatePanelEvent.forceActive) { + const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId); + if (tabIndex !== -1) { + setActiveTabIndex(tabIndex); + } + } + } + ); + + return () => { + activatePanelSubscription.unsubscribe(); + }; + } + }, [tabs, hasBeenOpened, panelService]); + + return ( + { + setHasBeenOpened(true); + }} + > + ); +}; + +export default SidePanelWithServices; diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx b/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx index 82879bf02a..9d65b971d8 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx @@ -19,10 +19,7 @@ export default class ContextMenuController { services: Types.Services; menuItems: Menu[] | MenuItem[]; - constructor( - servicesManager: ServicesManager, - commandsManager: CommandsManager - ) { + constructor(servicesManager: ServicesManager, commandsManager: CommandsManager) { this.services = servicesManager.services as Obj; this.commandsManager = commandsManager; } @@ -72,10 +69,9 @@ export default class ContextMenuController { event, content: ContextMenu, - // This naming is part of hte uiDialogService convention - // Clicking outside simpy closes the dialog box. - onClickOutside: () => - this.services.uiDialogService.dismiss({ id: 'context-menu' }), + // This naming is part of the uiDialogService convention + // Clicking outside simply closes the dialog box. + onClickOutside: () => this.services.uiDialogService.dismiss({ id: 'context-menu' }), contentProps: { items, @@ -170,9 +166,7 @@ export default class ContextMenuController { }; static _isValidPosition = (source): boolean => { - return ( - source && typeof source.x === 'number' && typeof source.y === 'number' - ); + return source && typeof source.x === 'number' && typeof source.y === 'number'; }; /** @@ -180,10 +174,7 @@ export default class ContextMenuController { */ static _getDefaultPosition = (canvasPoints, eventDetail, viewerElement) => { function* getPositionIterator() { - yield ContextMenuController._getCanvasPointsPosition( - canvasPoints, - viewerElement - ); + yield ContextMenuController._getCanvasPointsPosition(canvasPoints, viewerElement); yield ContextMenuController._getEventDefaultPosition(eventDetail); yield ContextMenuController._getElementDefaultPosition(viewerElement); yield ContextMenuController.getDefaultPosition(); diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js index b5555f71f1..00ee469dd5 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js @@ -1,4 +1,4 @@ -import ContextMenuItemsBuilder from "./ContextMenuItemsBuilder"; +import ContextMenuItemsBuilder from './ContextMenuItemsBuilder'; const menus = [ { diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts index 2fe20e8d96..8b8b985326 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts @@ -31,16 +31,11 @@ export function findMenuById(menus: Menu[], menuId?: string): Menu { * @param {*} subProps * @returns */ -export function findMenuDefault( - menus: Menu[], - subProps: Record -): Menu { +export function findMenuDefault(menus: Menu[], subProps: Record): Menu { if (!menus) { return null; } - return menus.find( - menu => !menu.selector || menu.selector(subProps.selectorProps) - ); + return menus.find(menu => !menu.selector || menu.selector(subProps.selectorProps)); } /** @@ -53,11 +48,7 @@ export function findMenuDefault( * @param menuIdFilter - menu id identifier (to be considered on selection) * This is intended to support other types of filtering in the future. */ -export function findMenu( - menus: Menu[], - props?: Types.IProps, - menuIdFilter?: string -) { +export function findMenu(menus: Menu[], props?: Types.IProps, menuIdFilter?: string) { const { subMenu } = props; function* findMenuIterator() { @@ -137,10 +128,7 @@ export function getMenuItems( if (!selector || selector(selectorProps)) { if (delegating) { - menuItems = [ - ...menuItems, - ...getMenuItems(selectorProps, event, menus, subMenu), - ]; + menuItems = [...menuItems, ...getMenuItems(selectorProps, event, menus, subMenu)]; } else { const toAdd = adaptItem(item, subProps); menuItems.push(toAdd); @@ -161,10 +149,7 @@ export function getMenuItems( * @returns a MenuItem that is compatible with the base ContextMenu * This requires having a label and set of actions to be called. */ -export function adaptItem( - item: MenuItem, - subProps: ContextMenuProps -): ContextMenuItem { +export function adaptItem(item: MenuItem, subProps: ContextMenuProps): ContextMenuItem { const newItem: ContextMenuItem = { ...item, value: subProps.selectorProps?.value, diff --git a/extensions/default/src/CustomizableContextMenu/types.ts b/extensions/default/src/CustomizableContextMenu/types.ts index 23075d0578..c251fd33d7 100644 --- a/extensions/default/src/CustomizableContextMenu/types.ts +++ b/extensions/default/src/CustomizableContextMenu/types.ts @@ -5,7 +5,7 @@ import { Types } from '@ohif/core'; * menu item for display. * An instance of SelectorProps is provided to the selector functions, which * return true to include the item or false to exclude it. - * The point of this is to allow more specific conext menus which hide + * The point of this is to allow more specific context menus which hide * non-relevant menu options, optimizing the speed of selection of menus */ export interface SelectorProps { @@ -69,7 +69,7 @@ export interface MenuItem { // or more importantly, if the delegating subMenu will be included. selector?: (props: SelectorProps) => boolean; - /** Adapts the item by filling in additional properties as requried */ + /** Adapts the item by filling in additional properties as required */ adaptItem?: (item: MenuItem, props: ContextMenuProps) => UIMenuItem; /** List of commands to run when this item's action is taken. */ diff --git a/extensions/default/src/DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI.ts b/extensions/default/src/DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI.ts new file mode 100644 index 0000000000..4e02cafae9 --- /dev/null +++ b/extensions/default/src/DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI.ts @@ -0,0 +1,236 @@ +import { ExtensionManager, Types } from '@ohif/core'; + +/** + * This file contains the implementations of BaseDataSourceConfigurationAPIItem + * and BaseDataSourceConfigurationAPI for the Google cloud healthcare API. To + * better understand this implementation and/or to implement custom implementations, + * see the platform\core\src\types\DataSourceConfigurationAPI.ts and its JS doc + * comments as a guide. + */ + +/** + * The various Google Cloud Healthcare path item types. + */ +enum ItemType { + projects = 0, + locations = 1, + datasets = 2, + dicomStores = 3, +} + +interface NamedItem { + name: string; +} +interface Project extends NamedItem { + projectId: string; +} + +const initialUrl = 'https://cloudresourcemanager.googleapis.com/v1'; +const baseHealthcareUrl = 'https://healthcare.googleapis.com/v1'; + +class GoogleCloudDataSourceConfigurationAPIItem + implements Types.BaseDataSourceConfigurationAPIItem +{ + id: string; + name: string; + url: string; + itemType: ItemType; +} + +class GoogleCloudDataSourceConfigurationAPI implements Types.BaseDataSourceConfigurationAPI { + private _extensionManager: ExtensionManager; + private _fetchOptions: { method: string; headers: unknown }; + private _dataSourceName: string; + + constructor(dataSourceName, servicesManager, extensionManager) { + this._dataSourceName = dataSourceName; + this._extensionManager = extensionManager; + const userAuthenticationService = servicesManager.services.userAuthenticationService; + this._fetchOptions = { + method: 'GET', + headers: userAuthenticationService.getAuthorizationHeader(), + }; + } + + getItemLabels = () => ['Project', 'Location', 'Data set', 'DICOM store']; + + async initialize(): Promise { + const url = `${initialUrl}/projects`; + + const projects = (await GoogleCloudDataSourceConfigurationAPI._doFetch( + url, + ItemType.projects, + this._fetchOptions + )) as Array; + + if (!projects?.length) { + return []; + } + + const projectItems = projects.map(project => { + return { + id: project.projectId, + name: project.name, + itemType: ItemType.projects, + url: `${baseHealthcareUrl}/projects/${project.projectId}`, + }; + }); + + return projectItems; + } + + async setCurrentItem( + anItem: Types.BaseDataSourceConfigurationAPIItem + ): Promise { + const googleCloudItem = anItem as GoogleCloudDataSourceConfigurationAPIItem; + + if (googleCloudItem.itemType === ItemType.dicomStores) { + // Last configurable item, so update the data source configuration. + const url = `${googleCloudItem.url}/dicomWeb`; + const dataSourceDefCopy = JSON.parse( + JSON.stringify(this._extensionManager.getDataSourceDefinition(this._dataSourceName)) + ); + dataSourceDefCopy.configuration = { + ...dataSourceDefCopy.configuration, + wadoUriRoot: url, + qidoRoot: url, + wadoRoot: url, + }; + + this._extensionManager.updateDataSourceConfiguration( + dataSourceDefCopy.sourceName, + dataSourceDefCopy.configuration + ); + + return []; + } + + const subItemType = googleCloudItem.itemType + 1; + const subItemField = `${ItemType[subItemType]}`; + + const url = `${googleCloudItem.url}/${subItemField}`; + + const fetchedSubItems = await GoogleCloudDataSourceConfigurationAPI._doFetch( + url, + subItemType, + this._fetchOptions + ); + + if (!fetchedSubItems?.length) { + return []; + } + + const subItems = fetchedSubItems.map(subItem => { + const nameSplit = subItem.name.split('/'); + return { + id: subItem.name, + name: nameSplit[nameSplit.length - 1], + itemType: subItemType, + url: `${baseHealthcareUrl}/${subItem.name}`, + }; + }); + + return subItems; + } + + async getConfiguredItems(): Promise> { + const dataSourceDefinition = this._extensionManager.getDataSourceDefinition( + this._dataSourceName + ); + + const url = dataSourceDefinition.configuration.wadoUriRoot; + const projectsIndex = url.indexOf('projects'); + // Split the configured URL into (essentially) pairs (i.e. item type followed by item) + // Explicitly: ['projects','aProject','locations','aLocation','datasets','aDataSet','dicomStores','aDicomStore'] + // Note that a partial configuration will have a subset of the above. + const urlSplit = url.substring(projectsIndex).split('/'); + + const configuredItems = []; + + for ( + let itemType = 0; + // the number of configured items is either the max (4) or the number extracted from the url split + itemType < 4 && (itemType + 1) * 2 < urlSplit.length; + itemType += 1 + ) { + if (itemType === ItemType.projects) { + const projectId = urlSplit[1]; + const projectUrl = `${initialUrl}/projects/${projectId}`; + const data = await GoogleCloudDataSourceConfigurationAPI._doFetch( + projectUrl, + ItemType.projects, + this._fetchOptions + ); + const project = data[0] as Project; + configuredItems.push({ + id: project.projectId, + name: project.name, + itemType: itemType, + url: `${baseHealthcareUrl}/projects/${project.projectId}`, + }); + } else { + const relativePath = urlSplit.slice(0, itemType * 2 + 2).join('/'); + configuredItems.push({ + id: relativePath, + name: urlSplit[itemType * 2 + 1], + itemType: itemType, + url: `${baseHealthcareUrl}/${relativePath}`, + }); + } + } + + return configuredItems; + } + + /** + * Fetches an array of items the specified item type. + * @param urlStr the fetch url + * @param fetchItemType the type to fetch + * @param fetchOptions the header options for the fetch (e.g. authorization header) + * @param fetchSearchParams any search query params; currently only used for paging results + * @returns an array of items of the specified type + */ + private static async _doFetch( + urlStr: string, + fetchItemType: ItemType, + fetchOptions = {}, + fetchSearchParams: Record = {} + ): Promise | Array> { + try { + const url = new URL(urlStr); + url.search = new URLSearchParams(fetchSearchParams).toString(); + + const response = await fetch(url, fetchOptions); + const data = await response.json(); + if (response.status >= 200 && response.status < 300 && data != null) { + if (data.nextPageToken != null) { + fetchSearchParams.pageToken = data.nextPageToken; + const subPageData = await this._doFetch( + urlStr, + fetchItemType, + fetchOptions, + fetchSearchParams + ); + data[ItemType[fetchItemType]] = data[ItemType[fetchItemType]].concat(subPageData); + } + if (data[ItemType[fetchItemType]]) { + return data[ItemType[fetchItemType]]; + } else if (data.name) { + return [data]; + } else { + return []; + } + } else { + const message = + data?.error?.message || + `Error returned from Google Cloud Healthcare: ${response.status} - ${response.statusText}`; + throw new Error(message); + } + } catch (err) { + const message = err?.message || 'Error occurred during fetch request.'; + throw new Error(message); + } + } +} + +export { GoogleCloudDataSourceConfigurationAPI }; diff --git a/extensions/default/src/DicomJSONDataSource/index.js b/extensions/default/src/DicomJSONDataSource/index.js index 241d430af9..c4bfb75ce6 100644 --- a/extensions/default/src/DicomJSONDataSource/index.js +++ b/extensions/default/src/DicomJSONDataSource/index.js @@ -13,6 +13,7 @@ const mappings = { let _store = { urls: [], + studyInstanceUIDMap: new Map(), // map of urls to array of study instance UIDs // { // url: url1 // studies: [Study1, Study2], // if multiple studies @@ -41,11 +42,13 @@ const findStudies = (key, value) => { }; function createDicomJSONApi(dicomJsonConfig) { - const { name, wadoRoot } = dicomJsonConfig; + const { wadoRoot } = dicomJsonConfig; const implementation = { - initialize: async ({ params, query, url }) => { - if (!url) url = query.get('url'); + initialize: async ({ query, url }) => { + if (!url) { + url = query.get('url'); + } let metaData = getMetaDataByURL(url); // if we have already cached the data from this specific url @@ -58,11 +61,7 @@ function createDicomJSONApi(dicomJsonConfig) { } const response = await fetch(url); - let data = await response.json(); - - const studyInstanceUIDs = data.studies.map( - study => study.StudyInstanceUID - ); + const data = await response.json(); let StudyInstanceUID; let SeriesInstanceUID; @@ -89,12 +88,14 @@ function createDicomJSONApi(dicomJsonConfig) { url, studies: [...data.studies], }); - - return studyInstanceUIDs; + _store.studyInstanceUIDMap.set( + url, + data.studies.map(study => study.StudyInstanceUID) + ); }, query: { studies: { - mapParams: () => { }, + mapParams: () => {}, search: async param => { const [key, value] = Object.entries(param)[0]; const mappedParam = mappings[key]; @@ -118,18 +119,18 @@ function createDicomJSONApi(dicomJsonConfig) { }); }, processResults: () => { - console.debug(' DICOMJson QUERY processResults'); + console.warn(' DICOMJson QUERY processResults not implemented'); }, }, series: { // mapParams: mapParams.bind(), search: () => { - console.debug(' DICOMJson QUERY SERIES SEARCH'); + console.warn(' DICOMJson QUERY SERIES SEARCH not implemented'); }, }, instances: { search: () => { - console.debug(' DICOMJson QUERY instances SEARCH'); + console.warn(' DICOMJson QUERY instances SEARCH not implemented'); }, }, }, @@ -151,15 +152,9 @@ function createDicomJSONApi(dicomJsonConfig) { return getDirectURL(wadoRoot, params); }, series: { - metadata: ({ - StudyInstanceUID, - madeInClient = false, - customSort, - } = {}) => { + metadata: async ({ StudyInstanceUID, madeInClient = false, customSort } = {}) => { if (!StudyInstanceUID) { - throw new Error( - 'Unable to query for SeriesMetadata without StudyInstanceUID' - ); + throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID'); } const study = findStudies('StudyInstanceUID', StudyInstanceUID)[0]; @@ -185,16 +180,10 @@ function createDicomJSONApi(dicomJsonConfig) { DicomMetadataStore.addInstances(naturalizedInstances, madeInClient); } - DicomMetadataStore.addSeriesMetadata( - seriesSummaryMetadata, - madeInClient - ); + DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient); function setSuccessFlag() { - const study = DicomMetadataStore.getStudy( - StudyInstanceUID, - madeInClient - ); + const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient); study.isLoaded = true; } @@ -213,14 +202,16 @@ function createDicomJSONApi(dicomJsonConfig) { return obj; }); storeInstances(instances); - if (index === numberOfSeries - 1) setSuccessFlag(); + if (index === numberOfSeries - 1) { + setSuccessFlag(); + } }); }, }, }, store: { dicom: () => { - console.debug(' DICOMJson store dicom'); + console.warn(' DICOMJson store dicom not implemented'); }, }, getImageIdsForDisplaySet(displaySet) { @@ -252,12 +243,13 @@ function createDicomJSONApi(dicomJsonConfig) { return imageIds; }, getImageIdsForInstance({ instance, frame }) { - const imageIds = getImageId({ - instance, - frame, - }); + const imageIds = getImageId({ instance, frame }); return imageIds; }, + getStudyInstanceUIDs: ({ params, query }) => { + const url = query.get('url'); + return _store.studyInstanceUIDMap.get(url); + }, }; return IWebApiDataSource.create(implementation); } diff --git a/extensions/default/src/DicomLocalDataSource/index.js b/extensions/default/src/DicomLocalDataSource/index.js index fc8554b92a..a7628e80a9 100644 --- a/extensions/default/src/DicomLocalDataSource/index.js +++ b/extensions/default/src/DicomLocalDataSource/index.js @@ -12,8 +12,12 @@ const END_MODALITIES = { }; const compareValue = (v1, v2, def = 0) => { - if (v1 === v2) return def; - if (v1 < v2) return -1; + if (v1 === v2) { + return def; + } + if (v1 < v2) { + return -1; + } return 1; }; @@ -41,25 +45,7 @@ function createDicomLocalApi(dicomLocalConfig) { const { name } = dicomLocalConfig; const implementation = { - initialize: ({ params, query }) => { - const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params; - const queryStudyInstanceUIDs = query.getAll('StudyInstanceUIDs'); - - const StudyInstanceUIDs = - queryStudyInstanceUIDs || paramsStudyInstanceUIDs; - const StudyInstanceUIDsAsArray = - StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs) - ? StudyInstanceUIDs - : [StudyInstanceUIDs]; - - // Put SRs at the end of series list to make sure images are loaded first - StudyInstanceUIDsAsArray.forEach(StudyInstanceUID => { - const study = DicomMetadataStore.getStudy(StudyInstanceUID); - study.series = study.series.sort(customSort); - }); - - return StudyInstanceUIDsAsArray; - }, + initialize: ({ params, query }) => {}, query: { studies: { mapParams: () => {}, @@ -99,7 +85,7 @@ function createDicomLocalApi(dicomLocalConfig) { }); }, processResults: () => { - console.debug(' DICOMLocal QUERY processResults'); + console.warn(' DICOMLocal QUERY processResults not implemented'); }, }, series: { @@ -121,7 +107,7 @@ function createDicomLocalApi(dicomLocalConfig) { }, instances: { search: () => { - console.debug(' DICOMLocal QUERY instances SEARCH'); + console.warn(' DICOMLocal QUERY instances SEARCH not implemented'); }, }, }, @@ -141,16 +127,11 @@ function createDicomLocalApi(dicomLocalConfig) { series: { metadata: async ({ StudyInstanceUID, madeInClient = false } = {}) => { if (!StudyInstanceUID) { - throw new Error( - 'Unable to query for SeriesMetadata without StudyInstanceUID' - ); + throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID'); } // Instances metadata already added via local upload - const study = DicomMetadataStore.getStudy( - StudyInstanceUID, - madeInClient - ); + const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient); // Series metadata already added via local upload DicomMetadataStore._broadcastEvent(EVENTS.SERIES_ADDED, { @@ -246,6 +227,28 @@ function createDicomLocalApi(dicomLocalConfig) { deleteStudyMetadataPromise() { console.log('deleteStudyMetadataPromise not implemented'); }, + getStudyInstanceUIDs: ({ params, query }) => { + const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params; + const queryStudyInstanceUIDs = query.getAll('StudyInstanceUIDs'); + + const StudyInstanceUIDs = queryStudyInstanceUIDs || paramsStudyInstanceUIDs; + const StudyInstanceUIDsAsArray = + StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs) + ? StudyInstanceUIDs + : [StudyInstanceUIDs]; + + // Put SRs at the end of series list to make sure images are loaded first + let isStudyInCache = false; + StudyInstanceUIDsAsArray.forEach(StudyInstanceUID => { + const study = DicomMetadataStore.getStudy(StudyInstanceUID); + if (study) { + study.series = study.series.sort(customSort); + isStudyInCache = true; + } + }); + + return isStudyInCache ? StudyInstanceUIDsAsArray : []; + }, }; return IWebApiDataSource.create(implementation); } diff --git a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css index fe6a960963..2845f2f39c 100644 --- a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css +++ b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css @@ -4,7 +4,7 @@ } .dicom-tag-browser-table-wrapper { -/* height: 500px;*/ + /* height: 500px;*/ /*overflow-y: scroll;*/ overflow-x: scroll; } @@ -45,7 +45,7 @@ padding-left: 10px; padding-right: 10px; text-align: center; - color: "#20A5D6"; + color: '#20A5D6'; } .dicom-tag-browser-table th.dicom-tag-browser-table-left { diff --git a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx index 57cf04fa83..22eea9bc85 100644 --- a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx +++ b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx @@ -1,10 +1,9 @@ import dcmjs from 'dcmjs'; import moment from 'moment'; -import React, { useState, useMemo, useEffect, useRef } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; import { classes } from '@ohif/core'; -import { Icon, InputRange, Select, Typography } from '@ohif/ui'; +import { InputRange, Select, Typography, InputFilterText } from '@ohif/ui'; import debounce from 'lodash.debounce'; -import classNames from 'classnames'; import DicomTagTable from './DicomTagTable'; import './DicomTagBrowser.css'; @@ -22,10 +21,8 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { // 3: Value const excludedColumnIndicesForFilter: Set = new Set([1]); - const [ - selectedDisplaySetInstanceUID, - setSelectedDisplaySetInstanceUID, - ] = useState(displaySetInstanceUID); + const [selectedDisplaySetInstanceUID, setSelectedDisplaySetInstanceUID] = + useState(displaySetInstanceUID); const [instanceNumber, setInstanceNumber] = useState(1); const [filterValue, setFilterValue] = useState(''); @@ -34,8 +31,6 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { setInstanceNumber(1); }; - const searchInputRef = useRef(null); - const activeDisplaySet = displaySets.find( ds => ds.displaySetInstanceUID === selectedDisplaySetInstanceUID ); @@ -113,27 +108,31 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { return (
-
-
- +
+
+ Series -
+
debouncedSetFilterValue(event.target.value)} - autoComplete="off" - > - - { - searchInputRef.current.value = ''; - debouncedSetFilterValue(''); - }} - > - - +
+
+
@@ -196,24 +173,14 @@ function getFormattedRowsFromTags(tags, metadata) { tags.forEach(tagInfo => { if (tagInfo.vr === 'SQ') { - rows.push([ - `${tagInfo.tagIndent}${tagInfo.tag}`, - tagInfo.vr, - tagInfo.keyword, - '', - ]); + rows.push([`${tagInfo.tagIndent}${tagInfo.tag}`, tagInfo.vr, tagInfo.keyword, '']); const { values } = tagInfo; values.forEach((item, index) => { const formatedRowsFromTags = getFormattedRowsFromTags(item, metadata); - rows.push([ - `${item[0].tagIndent}(FFFE,E000)`, - '', - `Item #${index}`, - '', - ]); + rows.push([`${item[0].tagIndent}(FFFE,E000)`, '', `Item #${index}`, '']); rows.push(...formatedRowsFromTags); }); @@ -224,17 +191,10 @@ function getFormattedRowsFromTags(tags, metadata) { const originalTagInfo = metadata[tag]; tagInfo.vr = originalTagInfo.vr; } catch (error) { - console.error( - `Failed to parse value representation for tag '${tagInfo.keyword}'` - ); + console.error(`Failed to parse value representation for tag '${tagInfo.keyword}'`); } } - rows.push([ - `${tagInfo.tagIndent}${tagInfo.tag}`, - tagInfo.vr, - tagInfo.keyword, - tagInfo.value, - ]); + rows.push([`${tagInfo.tagIndent}${tagInfo.tag}`, tagInfo.vr, tagInfo.keyword, tagInfo.value]); } }); diff --git a/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx b/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx index 982af013b4..a661da9f7b 100644 --- a/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx +++ b/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx @@ -17,48 +17,40 @@ function ColumnHeaders({ tagRef, vrRef, keywordRef, valueRef }) { return (
-
+
-
+
-
+
-
+
@@ -114,10 +106,7 @@ function DicomTagTable({ rows }) { * When the browser window resizes, update the row virtualization (i.e. row heights) */ useEffect(() => { - const debouncedResize = debounce( - () => listRef.current.resetAfterIndex(0), - 100 - ); + const debouncedResize = debounce(() => listRef.current.resetAfterIndex(0), 100); window.addEventListener('resize', debouncedResize); @@ -135,15 +124,15 @@ function DicomTagTable({ rows }) {
-
{row[0]}
-
{row[1]}
-
{row[2]}
-
{row[3]}
+
{row[0]}
+
{row[1]}
+
{row[2]}
+
{row[3]}
); }, @@ -154,9 +143,7 @@ function DicomTagTable({ rows }) { * Whenever any one of the column headers is set, then the header is rendered. * Here we chose the tag header. */ - const isHeaderRendered = useCallback(() => tagHeaderElem !== null, [ - tagHeaderElem, - ]); + const isHeaderRendered = useCallback(() => tagHeaderElem !== null, [tagHeaderElem]); /** * Get the item/row size. We use the header column widths to calculate the various row heights. @@ -179,11 +166,7 @@ function DicomTagTable({ rows }) { .map((colText, index) => { const colOneLineWidth = context.measureText(colText).width; const numLines = Math.ceil(colOneLineWidth / headerWidths[index]); - return ( - numLines * lineHeightPx + - 2 * rowVerticalPaddingPx + - rowBottomBorderPx - ); + return numLines * lineHeightPx + 2 * rowVerticalPaddingPx + rowBottomBorderPx; }) .reduce((maxHeight, colHeight) => Math.max(maxHeight, colHeight)); }, @@ -204,7 +187,7 @@ function DicomTagTable({ rows }) { valueRef={valueRef} />
{isHeaderRendered() && ( diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index 00f82841ed..b265e4a07a 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -1,11 +1,5 @@ import { api } from 'dicomweb-client'; -import { - DicomMetadataStore, - IWebApiDataSource, - utils, - errorHandler, - classes, -} from '@ohif/core'; +import { DicomMetadataStore, IWebApiDataSource, utils, errorHandler, classes } from '@ohif/core'; import { mapParams, @@ -18,10 +12,7 @@ import dcm4cheeReject from './dcm4cheeReject'; import getImageId from './utils/getImageId'; import dcmjs from 'dcmjs'; -import { - retrieveStudyMetadata, - deleteStudyMetadataPromise, -} from './retrieveStudyMetadata.js'; +import { retrieveStudyMetadata, deleteStudyMetadataPromise } from './retrieveStudyMetadata.js'; import StaticWadoClient from './utils/StaticWadoClient'; import getDirectURL from '../utils/getDirectURL'; import { fixBulkDataURI } from './utils/fixBulkDataURI'; @@ -30,8 +21,7 @@ const { DicomMetaDictionary, DicomDict } = dcmjs.data; const { naturalizeDataset, denaturalizeDataset } = DicomMetaDictionary; -const ImplementationClassUID = - '2.25.270695996825855179949881587723571202391.2.0.0'; +const ImplementationClassUID = '2.25.270695996825855179949881587723571202391.2.0.0'; const ImplementationVersionName = 'OHIF-VIEWER-2.0.0'; const EXPLICIT_VR_LITTLE_ENDIAN = '1.2.840.10008.1.2.1'; @@ -51,82 +41,87 @@ const metadataProvider = classes.MetadataProvider; * @param {string|bool} singlepart - indicates of the retrieves can fetch singlepart. Options are bulkdata, video, image or boolean true */ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { - const { - qidoRoot, - wadoRoot, - enableStudyLazyLoad, - supportsFuzzyMatching, - supportsWildcard, - supportsReject, - staticWado, - singlepart, - } = dicomWebConfig; - - const dicomWebConfigCopy = JSON.parse(JSON.stringify(dicomWebConfig)); - - const qidoConfig = { - url: qidoRoot, - staticWado, - singlepart, - headers: userAuthenticationService.getAuthorizationHeader(), - errorInterceptor: errorHandler.getHTTPErrorHandler(), - }; + let dicomWebConfigCopy, + qidoConfig, + wadoConfig, + qidoDicomWebClient, + wadoDicomWebClient, + getAuthrorizationHeader, + generateWadoHeader; - const wadoConfig = { - url: wadoRoot, - staticWado, - singlepart, - headers: userAuthenticationService.getAuthorizationHeader(), - errorInterceptor: errorHandler.getHTTPErrorHandler(), - }; + const implementation = { + initialize: ({ params, query }) => { + if (dicomWebConfig.onConfiguration && typeof dicomWebConfig.onConfiguration === 'function') { + dicomWebConfig = dicomWebConfig.onConfiguration(dicomWebConfig, { + params, + query, + }); + } - // TODO -> Two clients sucks, but its better than 1000. - // TODO -> We'll need to merge auth later. - const qidoDicomWebClient = staticWado - ? new StaticWadoClient(qidoConfig) - : new api.DICOMwebClient(qidoConfig); + dicomWebConfigCopy = JSON.parse(JSON.stringify(dicomWebConfig)); - const wadoDicomWebClient = staticWado - ? new StaticWadoClient(wadoConfig) - : new api.DICOMwebClient(wadoConfig); + getAuthrorizationHeader = () => { + const xhrRequestHeaders = {}; + const authHeaders = userAuthenticationService.getAuthorizationHeader(); + if (authHeaders && authHeaders.Authorization) { + xhrRequestHeaders.Authorization = authHeaders.Authorization; + } + return xhrRequestHeaders; + }; - const implementation = { - initialize: ({ params, query }) => { - const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params; - const queryStudyInstanceUIDs = utils.splitComma( - query.getAll('StudyInstanceUIDs') - ); + generateWadoHeader = () => { + let authorizationHeader = getAuthrorizationHeader(); + //Generate accept header depending on config params + let formattedAcceptHeader = utils.generateAcceptHeader( + dicomWebConfig.acceptHeader, + dicomWebConfig.requestTransferSyntaxUID, + dicomWebConfig.omitQuotationForMultipartRequest + ); - const StudyInstanceUIDs = - (queryStudyInstanceUIDs.length && queryStudyInstanceUIDs) || - paramsStudyInstanceUIDs; - const StudyInstanceUIDsAsArray = - StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs) - ? StudyInstanceUIDs - : [StudyInstanceUIDs]; - return StudyInstanceUIDsAsArray; + return { + ...authorizationHeader, + Accept: formattedAcceptHeader, + }; + }; + + qidoConfig = { + url: dicomWebConfig.qidoRoot, + staticWado: dicomWebConfig.staticWado, + singlepart: dicomWebConfig.singlepart, + headers: userAuthenticationService.getAuthorizationHeader(), + errorInterceptor: errorHandler.getHTTPErrorHandler(), + }; + + wadoConfig = { + url: dicomWebConfig.wadoRoot, + staticWado: dicomWebConfig.staticWado, + singlepart: dicomWebConfig.singlepart, + headers: userAuthenticationService.getAuthorizationHeader(), + errorInterceptor: errorHandler.getHTTPErrorHandler(), + }; + + // TODO -> Two clients sucks, but its better than 1000. + // TODO -> We'll need to merge auth later. + qidoDicomWebClient = dicomWebConfig.staticWado + ? new StaticWadoClient(qidoConfig) + : new api.DICOMwebClient(qidoConfig); + + wadoDicomWebClient = dicomWebConfig.staticWado + ? new StaticWadoClient(wadoConfig) + : new api.DICOMwebClient(wadoConfig); }, query: { studies: { mapParams: mapParams.bind(), - search: async function(origParams) { - const headers = userAuthenticationService.getAuthorizationHeader(); - if (headers) { - qidoDicomWebClient.headers = headers; - } - + search: async function (origParams) { + qidoDicomWebClient.headers = getAuthrorizationHeader(); const { studyInstanceUid, seriesInstanceUid, ...mappedParams } = mapParams(origParams, { - supportsFuzzyMatching, - supportsWildcard, + supportsFuzzyMatching: dicomWebConfig.supportsFuzzyMatching, + supportsWildcard: dicomWebConfig.supportsWildcard, }) || {}; - const results = await qidoSearch( - qidoDicomWebClient, - undefined, - undefined, - mappedParams - ); + const results = await qidoSearch(qidoDicomWebClient, undefined, undefined, mappedParams); return processResults(results); }, @@ -134,16 +129,9 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }, series: { // mapParams: mapParams.bind(), - search: async function(studyInstanceUid) { - const headers = userAuthenticationService.getAuthorizationHeader(); - if (headers) { - qidoDicomWebClient.headers = headers; - } - - const results = await seriesInStudy( - qidoDicomWebClient, - studyInstanceUid - ); + search: async function (studyInstanceUid) { + qidoDicomWebClient.headers = getAuthrorizationHeader(); + const results = await seriesInStudy(qidoDicomWebClient, studyInstanceUid); return processSeriesResults(results); }, @@ -151,18 +139,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }, instances: { search: (studyInstanceUid, queryParameters) => { - const headers = userAuthenticationService.getAuthorizationHeader(); - if (headers) { - qidoDicomWebClient.headers = headers; - } - - qidoSearch.call( - undefined, - qidoDicomWebClient, - studyInstanceUid, - null, - queryParameters - ); + qidoDicomWebClient.headers = getAuthrorizationHeader(); + qidoSearch.call(undefined, qidoDicomWebClient, studyInstanceUid, null, queryParameters); }, }, }, @@ -179,9 +157,16 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { * or is already retrieved, or a promise to a URL for such use if a BulkDataURI */ directURL: params => { - return getDirectURL({ wadoRoot, singlepart }, params); + return getDirectURL( + { + wadoRoot: dicomWebConfig.wadoRoot, + singlepart: dicomWebConfig.singlepart, + }, + params + ); }, bulkDataURI: async ({ StudyInstanceUID, BulkDataURI }) => { + qidoDicomWebClient.headers = getAuthrorizationHeader(); const options = { multipart: false, BulkDataURI, @@ -200,18 +185,11 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { sortFunction, madeInClient = false, } = {}) => { - const headers = userAuthenticationService.getAuthorizationHeader(); - if (headers) { - wadoDicomWebClient.headers = headers; - } - if (!StudyInstanceUID) { - throw new Error( - 'Unable to query for SeriesMetadata without StudyInstanceUID' - ); + throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID'); } - if (enableStudyLazyLoad) { + if (dicomWebConfig.enableStudyLazyLoad) { return implementation._retrieveSeriesMetadataAsync( StudyInstanceUID, filters, @@ -234,22 +212,16 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { store: { dicom: async (dataset, request) => { - const headers = userAuthenticationService.getAuthorizationHeader(); - if (headers) { - wadoDicomWebClient.headers = headers; - } - + wadoDicomWebClient.headers = getAuthrorizationHeader(); if (dataset instanceof ArrayBuffer) { const options = { datasets: [dataset], request, }; - await wadoDicomWebClient.storeInstances(options); } else { const meta = { - FileMetaInformationVersion: - dataset._meta.FileMetaInformationVersion.Value, + FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value, MediaStorageSOPClassUID: dataset.SOPClassUID, MediaStorageSOPInstanceUID: dataset.SOPInstanceUID, TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN, @@ -282,7 +254,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { madeInClient ) => { const enableStudyLazyLoad = false; - + wadoDicomWebClient.headers = generateWadoHeader(); // data is all SOPInstanceUIDs const data = await retrieveStudyMetadata( wadoDicomWebClient, @@ -323,6 +295,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }); instance.imageId = imageId; + instance.wadoRoot = dicomWebConfig.wadoRoot; + instance.wadoUri = dicomWebConfig.wadoUri; metadataProvider.addImageIdToUIDs(imageId, { StudyInstanceUID, @@ -338,10 +312,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { DicomMetadataStore.addSeriesMetadata(seriesMetadata, madeInClient); Object.keys(instancesPerSeries).forEach(seriesInstanceUID => - DicomMetadataStore.addInstances( - instancesPerSeries[seriesInstanceUID], - madeInClient - ) + DicomMetadataStore.addInstances(instancesPerSeries[seriesInstanceUID], madeInClient) ); }, @@ -353,18 +324,17 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { madeInClient = false ) => { const enableStudyLazyLoad = true; + wadoDicomWebClient.headers = generateWadoHeader(); // Get Series - const { - preLoadData: seriesSummaryMetadata, - promises: seriesPromises, - } = await retrieveStudyMetadata( - wadoDicomWebClient, - StudyInstanceUID, - enableStudyLazyLoad, - filters, - sortCriteria, - sortFunction - ); + const { preLoadData: seriesSummaryMetadata, promises: seriesPromises } = + await retrieveStudyMetadata( + wadoDicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + filters, + sortCriteria, + sortFunction + ); /** * naturalizes the dataset, and adds a retrieve bulkdata method @@ -375,7 +345,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { const addRetrieveBulkData = instance => { const naturalized = naturalizeDataset(instance); - // if we konw the server doesn't use bulkDataURI, then don't + // if we know the server doesn't use bulkDataURI, then don't if (!dicomWebConfig.bulkDataURI?.enabled) { return naturalized; } @@ -409,8 +379,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { // the bulk data and DICOM video cases where the second ArrayBuffer is // the bulk data. Here we play it safe and do a find. const ret = - (val instanceof Array && - val.find(arrayBuffer => arrayBuffer?.byteLength)) || + (val instanceof Array && val.find(arrayBuffer => arrayBuffer?.byteLength)) || undefined; value.Value = ret; return ret; @@ -453,10 +422,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { } function setSuccessFlag() { - const study = DicomMetadataStore.getStudy( - StudyInstanceUID, - madeInClient - ); + const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient); study.isLoaded = true; } @@ -515,10 +481,23 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { getConfig() { return dicomWebConfigCopy; }, + getStudyInstanceUIDs({ params, query }) { + const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params; + const queryStudyInstanceUIDs = utils.splitComma(query.getAll('StudyInstanceUIDs')); + + const StudyInstanceUIDs = + (queryStudyInstanceUIDs.length && queryStudyInstanceUIDs) || paramsStudyInstanceUIDs; + const StudyInstanceUIDsAsArray = + StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs) + ? StudyInstanceUIDs + : [StudyInstanceUIDs]; + + return StudyInstanceUIDsAsArray; + }, }; - if (supportsReject) { - implementation.reject = dcm4cheeReject(wadoRoot); + if (dicomWebConfig.supportsReject) { + implementation.reject = dcm4cheeReject(dicomWebConfig.wadoRoot); } return IWebApiDataSource.create(implementation); diff --git a/extensions/default/src/DicomWebDataSource/qido.js b/extensions/default/src/DicomWebDataSource/qido.js index ce372cd5fc..4bea9a0747 100644 --- a/extensions/default/src/DicomWebDataSource/qido.js +++ b/extensions/default/src/DicomWebDataSource/qido.js @@ -54,10 +54,7 @@ function processResults(qidoStudies) { patientName: utils.formatPN(getName(qidoStudy['00100010'])) || '', instances: Number(getString(qidoStudy['00201208'])) || 0, // number description: getString(qidoStudy['00081030']) || '', - modalities: - getString( - getModalities(qidoStudy['00080060'], qidoStudy['00080061']) - ) || '', + modalities: getString(getModalities(qidoStudy['00080060'], qidoStudy['00080061'])) || '', }) ); @@ -105,12 +102,7 @@ export function processSeriesResults(qidoSeries) { * @param {string} [queryParamaters] * @returns {Promise} - Promise that resolves results */ -async function search( - dicomWebClient, - studyInstanceUid, - seriesInstanceUid, - queryParameters -) { +async function search(dicomWebClient, studyInstanceUid, seriesInstanceUid, queryParameters) { let searchResult = await dicomWebClient.searchForStudies({ studyInstanceUid: undefined, queryParams: queryParameters, @@ -136,10 +128,7 @@ export function seriesInStudy(dicomWebClient, studyInstanceUID) { } export default function searchStudies(server, filter) { - const queryParams = getQIDOQueryParams( - filter, - server.qidoSupportsIncludeField - ); + const queryParams = getQIDOQueryParams(filter, server.qidoSupportsIncludeField); const options = { queryParams, }; diff --git a/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js b/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js index faf5e2c901..e4882d05e8 100644 --- a/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js +++ b/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js @@ -9,7 +9,7 @@ const StudyMetaDataPromises = new Map(); * * @param {Object} server Object with server configuration parameters * @param {string} StudyInstanceUID The UID of the Study to be retrieved - * @param {boolean} enabledStudyLazyLoad Whether the study metadata should be loaded asynchronusly. + * @param {boolean} enabledStudyLazyLoad Whether the study metadata should be loaded asynchronously. * @param {function} storeInstancesCallback A callback used to store the retrieved instance metadata. * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against @@ -28,14 +28,10 @@ export function retrieveStudyMetadata( // corresponding promise from the "StudyMetaDataPromises" map... if (!dicomWebClient) { - throw new Error( - `${moduleName}: Required 'dicomWebClient' parameter not provided.` - ); + throw new Error(`${moduleName}: Required 'dicomWebClient' parameter not provided.`); } if (!StudyInstanceUID) { - throw new Error( - `${moduleName}: Required 'StudyInstanceUID' parameter not provided.` - ); + throw new Error(`${moduleName}: Required 'StudyInstanceUID' parameter not provided.`); } // Already waiting on result? Return cached promise @@ -52,7 +48,7 @@ export function retrieveStudyMetadata( filters, sortCriteria, sortFunction - ).then(function(data) { + ).then(function (data) { resolve(data); }, reject); }); diff --git a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts index 9521e379b0..6d790cd7c7 100644 --- a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts +++ b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts @@ -36,24 +36,21 @@ export default class StaticWadoClient extends api.DICOMwebClient { * @returns */ async searchForStudies(options) { - if (!this.staticWado) return super.searchForStudies(options); + if (!this.staticWado) { + return super.searchForStudies(options); + } const searchResult = await super.searchForStudies(options); const { queryParams } = options; - if (!queryParams) return searchResult; + if (!queryParams) { + return searchResult; + } const lowerParams = this.toLowerParams(queryParams); const filtered = searchResult.filter(study => { for (const key of Object.keys(StaticWadoClient.studyFilterKeys)) { - if ( - !this.filterItem( - key, - lowerParams, - study, - StaticWadoClient.studyFilterKeys - ) - ) { + if (!this.filterItem(key, lowerParams, study, StaticWadoClient.studyFilterKeys)) { return false; } } @@ -63,23 +60,20 @@ export default class StaticWadoClient extends api.DICOMwebClient { } async searchForSeries(options) { - if (!this.staticWado) return super.searchForSeries(options); + if (!this.staticWado) { + return super.searchForSeries(options); + } const searchResult = await super.searchForSeries(options); const { queryParams } = options; - if (!queryParams) return searchResult; + if (!queryParams) { + return searchResult; + } const lowerParams = this.toLowerParams(queryParams); const filtered = searchResult.filter(series => { for (const key of Object.keys(StaticWadoClient.seriesFilterKeys)) { - if ( - !this.filterItem( - key, - lowerParams, - series, - StaticWadoClient.seriesFilterKeys - ) - ) { + if (!this.filterItem(key, lowerParams, series, StaticWadoClient.seriesFilterKeys)) { return false; } } @@ -112,18 +106,19 @@ export default class StaticWadoClient extends api.DICOMwebClient { actual = actual.Alphabetic; } if (typeof actual == 'string') { - if (actual.length === 0) return true; - if (desired.length === 0 || desired === '*') return true; + if (actual.length === 0) { + return true; + } + if (desired.length === 0 || desired === '*') { + return true; + } if (desired[0] === '*' && desired[desired.length - 1] === '*') { // console.log(`Comparing ${actual} to ${desired.substring(1, desired.length - 1)}`) return actual.indexOf(desired.substring(1, desired.length - 1)) != -1; } else if (desired[desired.length - 1] === '*') { return actual.indexOf(desired.substring(0, desired.length - 1)) != -1; } else if (desired[0] === '*') { - return ( - actual.indexOf(desired.substring(1)) === - actual.length - desired.length + 1 - ); + return actual.indexOf(desired.substring(1)) === actual.length - desired.length + 1; } } return desired === actual; @@ -131,9 +126,13 @@ export default class StaticWadoClient extends api.DICOMwebClient { /** Compares a pair of dates to see if the value is within the range */ compareDateRange(range, value) { - if (!value) return true; + if (!value) { + return true; + } const dash = range.indexOf('-'); - if (dash === -1) return this.compareValues(range, value); + if (dash === -1) { + return this.compareValues(range, value); + } const start = range.substring(0, dash); const end = range.substring(dash + 1); return (!start || value >= start) && (!end || value <= end); @@ -150,11 +149,17 @@ export default class StaticWadoClient extends api.DICOMwebClient { */ filterItem(key: string, queryParams, study, sourceFilterMap) { const altKey = sourceFilterMap[key] || key; - if (!queryParams) return true; + if (!queryParams) { + return true; + } const testValue = queryParams[key] || queryParams[altKey]; - if (!testValue) return true; + if (!testValue) { + return true; + } const valueElem = study[key] || study[altKey]; - if (!valueElem) return false; + if (!valueElem) { + return false; + } if (valueElem.vr == 'DA') { return this.compareDateRange(testValue, valueElem.Value[0]); } diff --git a/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts b/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts index 1a408b597f..2e592c7570 100644 --- a/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts +++ b/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts @@ -20,10 +20,7 @@ function fixBulkDataURI(value, instance, dicomWebConfig) { // in case of the relative path, make it absolute. The current DICOM standard says // the bulkdataURI is relative to the series. However, there are situations where // it can be relative to the study too - if ( - !value.BulkDataURI.startsWith('http') && - !value.BulkDataURI.startsWith('/') - ) { + if (!value.BulkDataURI.startsWith('http') && !value.BulkDataURI.startsWith('/')) { if (dicomWebConfig.bulkDataURI?.relativeResolution === 'studies') { value.BulkDataURI = `${dicomWebConfig.wadoRoot}/studies/${instance.StudyInstanceUID}/${value.BulkDataURI}`; } else if ( diff --git a/extensions/default/src/DicomWebDataSource/utils/getImageId.js b/extensions/default/src/DicomWebDataSource/utils/getImageId.js index a493a3d3ad..29877bd432 100644 --- a/extensions/default/src/DicomWebDataSource/utils/getImageId.js +++ b/extensions/default/src/DicomWebDataSource/utils/getImageId.js @@ -24,12 +24,7 @@ function buildInstanceWadoUrl(config, instance) { * @param thumbnail * @returns {string} The imageId to be used by Cornerstone */ -export default function getImageId({ - instance, - frame, - config, - thumbnail = false, -}) { +export default function getImageId({ instance, frame, config, thumbnail = false }) { if (!instance) { return; } diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js index d70a2cdccd..ba62cca1da 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js @@ -20,9 +20,7 @@ async function RetrieveMetadata( sortFunction ) { const RetrieveMetadataLoader = - enableStudyLazyLoad !== false - ? RetrieveMetadataLoaderAsync - : RetrieveMetadataLoaderSync; + enableStudyLazyLoad !== false ? RetrieveMetadataLoaderAsync : RetrieveMetadataLoaderSync; const retrieveMetadataLoader = new RetrieveMetadataLoader( dicomWebClient, diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js index 49302f7a83..da747c88bb 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js @@ -14,13 +14,7 @@ export default class RetrieveMetadataLoader { * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against * @param {Function} [sortSeries] - Custom sort function for series */ - constructor( - client, - studyInstanceUID, - filters = {}, - sortCriteria, - sortFunction - ) { + constructor(client, studyInstanceUID, filters = {}, sortCriteria, sortFunction) { this.client = client; this.studyInstanceUID = studyInstanceUID; this.filters = filters; diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js index 096c3c8183..bca3094292 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js @@ -1,8 +1,5 @@ import dcmjs from 'dcmjs'; -import { - sortStudySeries, - sortingCriteria, -} from '@ohif/core/src/utils/sortStudy'; +import { sortStudySeries, sortingCriteria } from '@ohif/core/src/utils/sortStudy'; import RetrieveMetadataLoader from './retrieveMetadataLoader'; /** @@ -12,11 +9,7 @@ import RetrieveMetadataLoader from './retrieveMetadataLoader'; * @param {Array} seriesInstanceUIDList A list of Series Instance UIDs * @returns {Object} Returns an object which supports loading of instances from each of given Series Instance UID */ -function makeSeriesAsyncLoader( - client, - studyInstanceUID, - seriesInstanceUIDList -) { +function makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDList) { return Object.freeze({ hasNext() { return seriesInstanceUIDList.length > 0; @@ -43,11 +36,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader */ *getPreLoaders() { const preLoaders = []; - const { - studyInstanceUID, - filters: { seriesInstanceUID } = {}, - client, - } = this; + const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this; if (seriesInstanceUID) { const options = { @@ -73,8 +62,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader return sortStudySeries( naturalized, - sortCriteria || - sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria, + sortCriteria || sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria, sortFunction ); } @@ -84,11 +72,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader const seriesInstanceUIDs = preLoadData.map(s => s.SeriesInstanceUID); - const seriesAsyncLoader = makeSeriesAsyncLoader( - client, - studyInstanceUID, - seriesInstanceUIDs - ); + const seriesAsyncLoader = makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDs); const promises = []; diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js index 6ca47ce9db..1a7cd9d500 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js @@ -31,11 +31,7 @@ export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader { */ *getLoaders() { const loaders = []; - const { - studyInstanceUID, - filters: { seriesInstanceUID } = {}, - client, - } = this; + const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this; if (seriesInstanceUID) { loaders.push( @@ -46,9 +42,7 @@ export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader { ); } - loaders.push( - client.retrieveStudyMetadata.bind(client, { studyInstanceUID }) - ); + loaders.push(client.retrieveStudyMetadata.bind(client, { studyInstanceUID })); yield* loaders; } diff --git a/extensions/default/src/DicomWebProxyDataSource/index.js b/extensions/default/src/DicomWebProxyDataSource/index.js index e873237dde..ab1d8e49e3 100644 --- a/extensions/default/src/DicomWebProxyDataSource/index.js +++ b/extensions/default/src/DicomWebProxyDataSource/index.js @@ -9,24 +9,12 @@ import { createDicomWebApi } from '../DicomWebDataSource/index'; * dicomWeb configuration array * */ -function createDicomWebProxyApi( - dicomWebProxyConfig, - UserAuthenticationService -) { +function createDicomWebProxyApi(dicomWebProxyConfig, UserAuthenticationService) { const { name } = dicomWebProxyConfig; let dicomWebDelegate = undefined; const implementation = { initialize: async ({ params, query }) => { - let studyInstanceUIDs = []; - - // there seem to be a couple of variations of the case for this parameter - const queryStudyInstanceUIDs = - query.get('studyInstanceUIDs') || query.get('studyInstanceUids'); - if (!queryStudyInstanceUIDs) { - throw new Error(`No studyInstanceUids in request for '${name}'`); - } - const url = query.get('url'); if (!url) { @@ -39,12 +27,11 @@ function createDicomWebProxyApi( } dicomWebDelegate = createDicomWebApi( - data.servers.dicomWeb[0], + data.servers.dicomWeb[0].configuration, UserAuthenticationService ); - studyInstanceUIDs = queryStudyInstanceUIDs.split(';'); + dicomWebDelegate.initialize({ params, query }); } - return studyInstanceUIDs; }, query: { studies: { @@ -55,28 +42,33 @@ function createDicomWebProxyApi( }, instances: { search: (studyInstanceUid, queryParameters) => - dicomWebDelegate.query.instances.search( - studyInstanceUid, - queryParameters - ), + dicomWebDelegate.query.instances.search(studyInstanceUid, queryParameters), }, }, retrieve: { directURL: (...args) => dicomWebDelegate.retrieve.directURL(...args), series: { - metadata: (...args) => - dicomWebDelegate.retrieve.series.metadata(...args), + metadata: async (...args) => dicomWebDelegate.retrieve.series.metadata(...args), }, }, store: { dicom: (...args) => dicomWebDelegate.store(...args), }, - deleteStudyMetadataPromise: (...args) => - dicomWebDelegate.deleteStudyMetadataPromise(...args), - getImageIdsForDisplaySet: (...args) => - dicomWebDelegate.getImageIdsForDisplaySet(...args), - getImageIdsForInstance: (...args) => - dicomWebDelegate.getImageIdsForInstance(...args), + deleteStudyMetadataPromise: (...args) => dicomWebDelegate.deleteStudyMetadataPromise(...args), + getImageIdsForDisplaySet: (...args) => dicomWebDelegate.getImageIdsForDisplaySet(...args), + getImageIdsForInstance: (...args) => dicomWebDelegate.getImageIdsForInstance(...args), + getStudyInstanceUIDs({ params, query }) { + let studyInstanceUIDs = []; + + // there seem to be a couple of variations of the case for this parameter + const queryStudyInstanceUIDs = + query.get('studyInstanceUIDs') || query.get('studyInstanceUids'); + if (!queryStudyInstanceUIDs) { + throw new Error(`No studyInstanceUids in request for '${name}'`); + } + studyInstanceUIDs = queryStudyInstanceUIDs.split(';'); + return studyInstanceUIDs; + }, }; return IWebApiDataSource.create(implementation); } diff --git a/extensions/default/src/Panels/ActionButtons.tsx b/extensions/default/src/Panels/ActionButtons.tsx index f372a1ee08..c21f8b6b65 100644 --- a/extensions/default/src/Panels/ActionButtons.tsx +++ b/extensions/default/src/Panels/ActionButtons.tsx @@ -2,21 +2,31 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; -import { Button, ButtonGroup } from '@ohif/ui'; +import { LegacyButton, LegacyButtonGroup } from '@ohif/ui'; function ActionButtons({ onExportClick, onCreateReportClick }) { const { t } = useTranslation('MeasurementTable'); return ( - - - - + + ); } diff --git a/extensions/default/src/Panels/DataSourceSelector.tsx b/extensions/default/src/Panels/DataSourceSelector.tsx index decb24ad83..dcb62c2839 100644 --- a/extensions/default/src/Panels/DataSourceSelector.tsx +++ b/extensions/default/src/Panels/DataSourceSelector.tsx @@ -3,7 +3,7 @@ import classnames from 'classnames'; import { useNavigate } from 'react-router-dom'; import { useAppConfig } from '@state'; -import { Button } from '@ohif/ui'; +import { Button, ButtonEnums } from '@ohif/ui'; function DataSourceSelector() { const [appConfig] = useAppConfig(); @@ -15,25 +15,24 @@ function DataSourceSelector() { return (
-
-
+
+
OHIF -
+
{dsConfigs - .filter( - it => - it.sourceName !== 'dicomjson' && - it.sourceName !== 'dicomlocal' - ) + .filter(it => it.sourceName !== 'dicomjson' && it.sourceName !== 'dicomlocal') .map(ds => (
-

{ds.friendlyName}

+

+ {ds.configuration?.friendlyName || ds.friendlyName} +

+
} diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx index 415b80015e..628cb14eab 100644 --- a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Button, ButtonGroup } from '@ohif/ui'; +import { LegacyButton, LegacyButtonGroup } from '@ohif/ui'; import { useTranslation } from 'react-i18next'; function ExportReports({ segmentations, tmtvValue, config, commandsManager }) { @@ -8,9 +8,13 @@ function ExportReports({ segmentations, tmtvValue, config, commandsManager }) { return ( <> {segmentations?.length ? ( -
- - - - - - + +
) : null} diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx index a18dee1c2d..306c39c946 100644 --- a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx @@ -5,9 +5,7 @@ import { SegmentationTable, Button, Icon } from '@ohif/ui'; import { useTranslation } from 'react-i18next'; import segmentationEditHandler from './segmentationEditHandler'; import ExportReports from './ExportReports'; -import ROIThresholdConfiguration, { - ROI_STAT, -} from './ROIThresholdConfiguration'; +import ROIThresholdConfiguration, { ROI_STAT } from './ROIThresholdConfiguration'; const LOWER_CT_THRESHOLD_DEFAULT = -1024; const UPPER_CT_THRESHOLD_DEFAULT = 1024; @@ -44,19 +42,14 @@ function reducer(state, action) { } } -export default function PanelRoiThresholdSegmentation({ - servicesManager, - commandsManager, -}) { +export default function PanelRoiThresholdSegmentation({ servicesManager, commandsManager }) { const { segmentationService } = servicesManager.services; const { t } = useTranslation('PanelSUV'); const [showConfig, setShowConfig] = useState(false); const [labelmapLoading, setLabelmapLoading] = useState(false); const [selectedSegmentationId, setSelectedSegmentationId] = useState(null); - const [segmentations, setSegmentations] = useState(() => - segmentationService.getSegmentations() - ); + const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations()); const [config, dispatch] = useReducer(reducer, { strategy: DEFAULT_STRATEGY, @@ -95,9 +88,7 @@ export default function PanelRoiThresholdSegmentation({ const lesionGlyoclysisStats = lesionStats.volume * lesionStats.meanValue; // update segDetails with the suv peak for the active segmentation - const segmentation = segmentationService.getSegmentation( - selectedSegmentationId - ); + const segmentation = segmentationService.getSegmentation(selectedSegmentationId); const cachedStats = { lesionStats, @@ -178,10 +169,9 @@ export default function PanelRoiThresholdSegmentation({ return ( <>
-
-
+
+
- +
{ setShowConfig(!showConfig); }} > -
- {t('ROI Threshold Configuration')} -
+
{t('ROI Threshold Configuration')}
{showConfig && ( {tmtvValue !== null ? ( -
- +
+ {'TMTV:'}
{`${tmtvValue} mL`}
@@ -265,13 +251,10 @@ export default function PanelRoiThresholdSegmentation({
{ // navigate to a url in a new tab - window.open( - 'https://github.com/OHIF/Viewers/blob/master/modes/tmtv/README.md', - '_blank' - ); + window.open('https://github.com/OHIF/Viewers/blob/master/modes/tmtv/README.md', '_blank'); }} > +
-
+
)} {config.strategy !== ROI_STAT && ( -
+
- - -
+
- + +
- + +
- -
+ ); }, actions: [ - // temp: swap button types until colors are updated - { id: 'cancel', text: 'Cancel', type: 'primary' }, - { id: 'save', text: 'Save', type: 'secondary' }, + { id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary }, + { id: 'save', text: 'Save', type: ButtonEnums.type.primary }, ], onSubmit: onSubmitHandler, }, diff --git a/extensions/tmtv/src/commandsModule.js b/extensions/tmtv/src/commandsModule.js index b633d231cc..73409ee411 100644 --- a/extensions/tmtv/src/commandsModule.js +++ b/extensions/tmtv/src/commandsModule.js @@ -14,11 +14,7 @@ const metadataProvider = classes.MetadataProvider; const RECTANGLE_ROI_THRESHOLD_MANUAL = 'RectangleROIStartEndThreshold'; const LABELMAP = csTools.Enums.SegmentationRepresentations.Labelmap; -const commandsModule = ({ - servicesManager, - commandsManager, - extensionManager, -}) => { +const commandsModule = ({ servicesManager, commandsManager, extensionManager }) => { const { viewportGridService, uiNotificationService, @@ -36,8 +32,8 @@ const commandsModule = ({ const { getEnabledElement } = utilityModule.exports; function _getActiveViewportsEnabledElement() { - const { activeViewportIndex } = viewportGridService.getState(); - const { element } = getEnabledElement(activeViewportIndex) || {}; + const { activeViewportId } = viewportGridService.getState(); + const { element } = getEnabledElement(activeViewportId) || {}; const enabledElement = cs.getEnabledElement(element); return enabledElement; } @@ -45,8 +41,8 @@ const commandsModule = ({ function _getMatchedViewportsToolGroupIds() { const { viewportMatchDetails } = hangingProtocolService.getMatchDetails(); const toolGroupIds = []; - viewportMatchDetails.forEach((value, key) => { - const { viewportOptions } = value; + viewportMatchDetails.forEach(viewport => { + const { viewportOptions } = viewport; const { toolGroupId } = viewportOptions; if (toolGroupIds.indexOf(toolGroupId) === -1) { toolGroupIds.push(toolGroupId); @@ -64,7 +60,7 @@ const commandsModule = ({ // corrected PT vs the non-attenuation correct PT) let ptDisplaySet = null; - for (const [viewportIndex, viewportDetails] of viewportMatchDetails) { + for (const [viewportId, viewportDetails] of viewportMatchDetails) { const { displaySetsInfo } = viewportDetails; const displaySets = displaySetsInfo.map(({ displaySetInstanceUID }) => displaySetService.getDisplaySetByUID(displaySetInstanceUID) @@ -74,9 +70,7 @@ const commandsModule = ({ continue; } - ptDisplaySet = displaySets.find( - displaySet => displaySet.Modality === 'PT' - ); + ptDisplaySet = displaySets.find(displaySet => displaySet.Modality === 'PT'); if (ptDisplaySet) { break; @@ -102,17 +96,13 @@ const commandsModule = ({ PatientWeight: instance.PatientWeight, RadiopharmaceuticalInformationSequence: { RadionuclideTotalDose: - instance.RadiopharmaceuticalInformationSequence[0] - .RadionuclideTotalDose, + instance.RadiopharmaceuticalInformationSequence[0].RadionuclideTotalDose, RadionuclideHalfLife: - instance.RadiopharmaceuticalInformationSequence[0] - .RadionuclideHalfLife, + instance.RadiopharmaceuticalInformationSequence[0].RadionuclideHalfLife, RadiopharmaceuticalStartTime: - instance.RadiopharmaceuticalInformationSequence[0] - .RadiopharmaceuticalStartTime, + instance.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartTime, RadiopharmaceuticalStartDateTime: - instance.RadiopharmaceuticalInformationSequence[0] - .RadiopharmaceuticalStartDateTime, + instance.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartDateTime, }, }; @@ -137,7 +127,6 @@ const commandsModule = ({ // Add Segmentation to all toolGroupIds in the viewer const toolGroupIds = _getMatchedViewportsToolGroupIds(); - const representationType = LABELMAP; for (const toolGroupId of toolGroupIds) { @@ -149,10 +138,7 @@ const commandsModule = ({ representationType ); - segmentationService.setActiveSegmentationForToolGroup( - segmentationId, - toolGroupId - ); + segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId); } return segmentationId; @@ -161,21 +147,14 @@ const commandsModule = ({ const toolGroupIds = _getMatchedViewportsToolGroupIds(); toolGroupIds.forEach(toolGroupId => { - segmentationService.setActiveSegmentationForToolGroup( - segmentationId, - toolGroupId - ); + segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId); }); }, thresholdSegmentationByRectangleROITool: ({ segmentationId, config }) => { - const segmentation = csTools.segmentation.state.getSegmentation( - segmentationId - ); + const segmentation = csTools.segmentation.state.getSegmentation(segmentationId); const { representationData } = segmentation; - const { - displaySetMatchDetails: matchDetails, - } = hangingProtocolService.getMatchDetails(); + const { displaySetMatchDetails: matchDetails } = hangingProtocolService.getMatchDetails(); const volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to use const ctDisplaySet = matchDetails.get('ctDisplaySet'); @@ -249,9 +228,7 @@ const commandsModule = ({ getLesionStats: ({ labelmap, segmentIndex = 1 }) => { const { scalarData, spacing } = labelmap; - const { scalarData: referencedScalarData } = cs.cache.getVolume( - labelmap.referencedVolumeId - ); + const { scalarData: referencedScalarData } = cs.cache.getVolume(labelmap.referencedVolumeId); let segmentationMax = -Infinity; let segmentationMin = Infinity; @@ -293,9 +270,7 @@ const commandsModule = ({ }; }, calculateTMTV: ({ segmentations }) => { - const labelmaps = segmentations.map(s => - segmentationService.getLabelmapVolume(s.id) - ); + const labelmaps = segmentations.map(s => segmentationService.getLabelmapVolume(s.id)); if (!labelmaps.length) { return; @@ -318,17 +293,14 @@ const commandsModule = ({ createAndDownloadTMTVReport(segReport, additionalReportRows); }, getTotalLesionGlycolysis: ({ segmentations }) => { - const labelmapVolumes = segmentations.map(s => - segmentationService.getLabelmapVolume(s.id) - ); + const labelmapVolumes = segmentations.map(s => segmentationService.getLabelmapVolume(s.id)); let mergedLabelmap; // merge labelmap will through an error if labels maps are not the same size // or same direction or .... try { - mergedLabelmap = csTools.utilities.segmentation.createMergedLabelmapForIndex( - labelmapVolumes - ); + mergedLabelmap = + csTools.utilities.segmentation.createMergedLabelmapForIndex(labelmapVolumes); } catch (e) { console.error('commandsModule::getTotalLesionGlycolysis', e); return; @@ -338,9 +310,7 @@ const commandsModule = ({ const { referencedVolumeId, spacing } = labelmapVolumes[0]; if (!referencedVolumeId) { - console.error( - 'commandsModule::getTotalLesionGlycolysis:No referencedVolumeId found' - ); + console.error('commandsModule::getTotalLesionGlycolysis:No referencedVolumeId found'); } const ptVolume = cs.cache.getVolume(referencedVolumeId); @@ -366,14 +336,7 @@ const commandsModule = ({ const averageSuv = suv / totalLesionVoxelCount; // total Lesion Glycolysis [suv * ml] - return ( - averageSuv * - totalLesionVoxelCount * - spacing[0] * - spacing[1] * - spacing[2] * - 1e-3 - ); + return averageSuv * totalLesionVoxelCount * spacing[0] * spacing[1] * spacing[2] * 1e-3; }, setStartSliceForROIThresholdTool: () => { const { viewport } = _getActiveViewportsEnabledElement(); @@ -489,9 +452,7 @@ const commandsModule = ({ const referencedVolumeId = labelmapVolume.referencedVolumeId; segReport.referencedVolumeId = referencedVolumeId; - const referencedVolume = segmentationService.getLabelmapVolume( - referencedVolumeId - ); + const referencedVolume = segmentationService.getLabelmapVolume(referencedVolumeId); if (!referencedVolume) { report[id] = segReport; @@ -504,10 +465,7 @@ const commandsModule = ({ } const firstImageId = referencedVolume.imageIds[0]; - const instance = OHIF.classes.MetadataProvider.get( - 'instance', - firstImageId - ); + const instance = OHIF.classes.MetadataProvider.get('instance', firstImageId); if (!instance) { report[id] = segReport; @@ -545,20 +503,22 @@ const commandsModule = ({ let viewports = []; fusionViewportIds.forEach(viewportId => { - const viewportInfo = cornerstoneViewportService.getViewportInfo( - viewportId - ); - - const viewportIndex = viewportInfo.getViewportIndex(); commandsManager.runCommand('setViewportColormap', { - viewportIndex, + viewportId, displaySetInstanceUID: ptDisplaySet.displaySetInstanceUID, - colormap, + colormap: { + name: colormap, + // TODO: This opacity mapping matches that in hpViewports, but + // ideally making this editable in a side panel would be useful + opacity: [ + { value: 0, opacity: 0 }, + { value: 0.1, opacity: 0.9 }, + { value: 1, opacity: 0.95 }, + ], + }, }); - viewports.push( - cornerstoneViewportService.getCornerstoneViewport(viewportId) - ); + viewports.push(cornerstoneViewportService.getCornerstoneViewport(viewportId)); }); viewports.forEach(viewport => { diff --git a/extensions/tmtv/src/getHangingProtocolModule.js b/extensions/tmtv/src/getHangingProtocolModule.js index 45720c10d5..e123a79176 100644 --- a/extensions/tmtv/src/getHangingProtocolModule.js +++ b/extensions/tmtv/src/getHangingProtocolModule.js @@ -217,7 +217,6 @@ const stage4 = { const ptCT = { id: '@ohif/extension-tmtv.hangingProtocolModule.ptCT', locked: true, - hasUpdatedPriorsInformation: false, name: 'Default', createdDate: '2021-02-23T19:22:08.894Z', modifiedDate: '2022-10-04T19:22:08.894Z', diff --git a/extensions/tmtv/src/getPanelModule.tsx b/extensions/tmtv/src/getPanelModule.tsx index 94e221fc93..0449ebe48b 100644 --- a/extensions/tmtv/src/getPanelModule.tsx +++ b/extensions/tmtv/src/getPanelModule.tsx @@ -6,11 +6,7 @@ import { PanelPetSUV, PanelROIThresholdSegmentation } from './Panels'; // - cancel promises when component is destroyed // - show errors in UI for thumbnails if promise fails -function getPanelModule({ - commandsManager, - extensionManager, - servicesManager, -}) { +function getPanelModule({ commandsManager, extensionManager, servicesManager }) { const wrappedPanelPetSuv = () => { return ( true, - callback, - boundsIJK - ); + utilities.pointInShapeCallback(labelmapImageData, () => true, callback, boundsIJK); - const direction = labelmapImageData - .getDirection() - .slice(0, 3) as Types.Point3; + const direction = labelmapImageData.getDirection().slice(0, 3) as Types.Point3; /** * 2. Find the bottom and top of the great circle for the second sphere (1cc sphere) @@ -117,13 +98,10 @@ function calculateSuvPeak( const secondaryCircleWorld = vec3.create(); const bottomWorld = vec3.create(); const topWorld = vec3.create(); - referenceVolumeImageData.indexToWorld(maxIJK, secondaryCircleWorld); + referenceVolumeImageData.indexToWorld(maxIJK as vec3, secondaryCircleWorld); vec3.scaleAndAdd(bottomWorld, secondaryCircleWorld, direction, -diameter / 2); vec3.scaleAndAdd(topWorld, secondaryCircleWorld, direction, diameter / 2); - const suvPeakCirclePoints = [bottomWorld, topWorld] as [ - Types.Point3, - Types.Point3 - ]; + const suvPeakCirclePoints = [bottomWorld, topWorld] as [Types.Point3, Types.Point3]; /** * 3. Find the Mean and Max of the 1cc sphere centered on the suv Max of the previous diff --git a/extensions/tmtv/src/utils/calculateTMTV.ts b/extensions/tmtv/src/utils/calculateTMTV.ts index f09375c346..8d9f5f3e5d 100644 --- a/extensions/tmtv/src/utils/calculateTMTV.ts +++ b/extensions/tmtv/src/utils/calculateTMTV.ts @@ -11,10 +11,7 @@ import { utilities } from '@cornerstonejs/tools'; * @param {number} segmentIndex * @returns {number} TMTV in ml */ -function calculateTMTV( - labelmaps: Array, - segmentIndex = 1 -): number { +function calculateTMTV(labelmaps: Array, segmentIndex = 1): number { const volumeId = 'mergedLabelmap'; const mergedLabelmap = utilities.segmentation.createMergedLabelmapForIndex( @@ -24,10 +21,7 @@ function calculateTMTV( ); const { imageData, spacing } = mergedLabelmap; - const values = imageData - .getPointData() - .getScalars() - .getData(); + const values = imageData.getPointData().getScalars().getData(); // count non-zero values inside the outputData, this would // consider the overlapping regions to be only counted once diff --git a/extensions/tmtv/src/utils/colormaps/index.js b/extensions/tmtv/src/utils/colormaps/index.js index 34761d2596..b5afdd984a 100644 --- a/extensions/tmtv/src/utils/colormaps/index.js +++ b/extensions/tmtv/src/utils/colormaps/index.js @@ -3,9270 +3,1552 @@ export default [ ColorSpace: 'RGB', Name: 'hot_iron', RGBPoints: [ - 0.0, - 0.0039215686, - 0.0039215686, - 0.0156862745, - 0.00392156862745098, - 0.0039215686, - 0.0039215686, - 0.0156862745, - 0.00784313725490196, - 0.0039215686, - 0.0039215686, - 0.031372549, - 0.011764705882352941, - 0.0039215686, - 0.0039215686, - 0.0470588235, - 0.01568627450980392, - 0.0039215686, - 0.0039215686, - 0.062745098, - 0.0196078431372549, - 0.0039215686, - 0.0039215686, - 0.0784313725, - 0.023529411764705882, - 0.0039215686, - 0.0039215686, - 0.0941176471, - 0.027450980392156862, - 0.0039215686, - 0.0039215686, - 0.1098039216, - 0.03137254901960784, - 0.0039215686, - 0.0039215686, - 0.1254901961, - 0.03529411764705882, - 0.0039215686, - 0.0039215686, - 0.1411764706, - 0.0392156862745098, - 0.0039215686, - 0.0039215686, - 0.1568627451, - 0.043137254901960784, - 0.0039215686, - 0.0039215686, - 0.1725490196, - 0.047058823529411764, - 0.0039215686, - 0.0039215686, - 0.1882352941, - 0.050980392156862744, - 0.0039215686, - 0.0039215686, - 0.2039215686, - 0.054901960784313725, - 0.0039215686, - 0.0039215686, - 0.2196078431, - 0.05882352941176471, - 0.0039215686, - 0.0039215686, - 0.2352941176, - 0.06274509803921569, - 0.0039215686, - 0.0039215686, - 0.2509803922, - 0.06666666666666667, - 0.0039215686, - 0.0039215686, - 0.262745098, - 0.07058823529411765, - 0.0039215686, - 0.0039215686, - 0.2784313725, - 0.07450980392156863, - 0.0039215686, - 0.0039215686, - 0.2941176471, - 0.0784313725490196, - 0.0039215686, - 0.0039215686, - 0.3098039216, - 0.08235294117647059, - 0.0039215686, - 0.0039215686, - 0.3254901961, - 0.08627450980392157, - 0.0039215686, - 0.0039215686, - 0.3411764706, - 0.09019607843137255, - 0.0039215686, - 0.0039215686, - 0.3568627451, - 0.09411764705882353, - 0.0039215686, - 0.0039215686, - 0.3725490196, - 0.09803921568627451, - 0.0039215686, - 0.0039215686, - 0.3882352941, - 0.10196078431372549, - 0.0039215686, - 0.0039215686, - 0.4039215686, - 0.10588235294117647, - 0.0039215686, - 0.0039215686, - 0.4196078431, - 0.10980392156862745, - 0.0039215686, - 0.0039215686, - 0.4352941176, - 0.11372549019607843, - 0.0039215686, - 0.0039215686, - 0.4509803922, - 0.11764705882352942, - 0.0039215686, - 0.0039215686, - 0.4666666667, - 0.12156862745098039, - 0.0039215686, - 0.0039215686, - 0.4823529412, - 0.12549019607843137, - 0.0039215686, - 0.0039215686, - 0.4980392157, - 0.12941176470588237, - 0.0039215686, - 0.0039215686, - 0.5137254902, - 0.13333333333333333, - 0.0039215686, - 0.0039215686, - 0.5294117647, - 0.13725490196078433, - 0.0039215686, - 0.0039215686, - 0.5450980392, - 0.1411764705882353, - 0.0039215686, - 0.0039215686, - 0.5607843137, - 0.1450980392156863, - 0.0039215686, - 0.0039215686, - 0.5764705882, - 0.14901960784313725, - 0.0039215686, - 0.0039215686, - 0.5921568627, - 0.15294117647058825, - 0.0039215686, - 0.0039215686, - 0.6078431373, - 0.1568627450980392, - 0.0039215686, - 0.0039215686, - 0.6235294118, - 0.1607843137254902, - 0.0039215686, - 0.0039215686, - 0.6392156863, - 0.16470588235294117, - 0.0039215686, - 0.0039215686, - 0.6549019608, - 0.16862745098039217, - 0.0039215686, - 0.0039215686, - 0.6705882353, - 0.17254901960784313, - 0.0039215686, - 0.0039215686, - 0.6862745098, - 0.17647058823529413, - 0.0039215686, - 0.0039215686, - 0.7019607843, - 0.1803921568627451, - 0.0039215686, - 0.0039215686, - 0.7176470588, - 0.1843137254901961, - 0.0039215686, - 0.0039215686, - 0.7333333333, - 0.18823529411764706, - 0.0039215686, - 0.0039215686, - 0.7490196078, - 0.19215686274509805, - 0.0039215686, - 0.0039215686, - 0.7607843137, - 0.19607843137254902, - 0.0039215686, - 0.0039215686, - 0.7764705882, - 0.2, - 0.0039215686, - 0.0039215686, - 0.7921568627, - 0.20392156862745098, - 0.0039215686, - 0.0039215686, - 0.8078431373, - 0.20784313725490197, - 0.0039215686, - 0.0039215686, - 0.8235294118, - 0.21176470588235294, - 0.0039215686, - 0.0039215686, - 0.8392156863, - 0.21568627450980393, - 0.0039215686, - 0.0039215686, - 0.8549019608, - 0.2196078431372549, - 0.0039215686, - 0.0039215686, - 0.8705882353, - 0.2235294117647059, - 0.0039215686, - 0.0039215686, - 0.8862745098, - 0.22745098039215686, - 0.0039215686, - 0.0039215686, - 0.9019607843, - 0.23137254901960785, - 0.0039215686, - 0.0039215686, - 0.9176470588, - 0.23529411764705885, - 0.0039215686, - 0.0039215686, - 0.9333333333, - 0.23921568627450984, - 0.0039215686, - 0.0039215686, - 0.9490196078, - 0.24313725490196078, - 0.0039215686, - 0.0039215686, - 0.9647058824, - 0.24705882352941178, - 0.0039215686, - 0.0039215686, - 0.9803921569, - 0.25098039215686274, - 0.0039215686, - 0.0039215686, - 0.9960784314, - 0.2549019607843137, - 0.0039215686, - 0.0039215686, - 0.9960784314, - 0.25882352941176473, - 0.0156862745, - 0.0039215686, - 0.9803921569, - 0.2627450980392157, - 0.031372549, - 0.0039215686, - 0.9647058824, - 0.26666666666666666, - 0.0470588235, - 0.0039215686, - 0.9490196078, - 0.27058823529411763, - 0.062745098, - 0.0039215686, - 0.9333333333, - 0.27450980392156865, - 0.0784313725, - 0.0039215686, - 0.9176470588, - 0.2784313725490196, - 0.0941176471, - 0.0039215686, - 0.9019607843, - 0.2823529411764706, - 0.1098039216, - 0.0039215686, - 0.8862745098, - 0.28627450980392155, - 0.1254901961, - 0.0039215686, - 0.8705882353, - 0.2901960784313726, - 0.1411764706, - 0.0039215686, - 0.8549019608, - 0.29411764705882354, - 0.1568627451, - 0.0039215686, - 0.8392156863, - 0.2980392156862745, - 0.1725490196, - 0.0039215686, - 0.8235294118, - 0.30196078431372547, - 0.1882352941, - 0.0039215686, - 0.8078431373, - 0.3058823529411765, - 0.2039215686, - 0.0039215686, - 0.7921568627, - 0.30980392156862746, - 0.2196078431, - 0.0039215686, - 0.7764705882, - 0.3137254901960784, - 0.2352941176, - 0.0039215686, - 0.7607843137, - 0.3176470588235294, - 0.2509803922, - 0.0039215686, - 0.7490196078, - 0.3215686274509804, - 0.262745098, - 0.0039215686, - 0.7333333333, - 0.3254901960784314, - 0.2784313725, - 0.0039215686, - 0.7176470588, - 0.32941176470588235, - 0.2941176471, - 0.0039215686, - 0.7019607843, - 0.3333333333333333, - 0.3098039216, - 0.0039215686, - 0.6862745098, - 0.33725490196078434, - 0.3254901961, - 0.0039215686, - 0.6705882353, - 0.3411764705882353, - 0.3411764706, - 0.0039215686, - 0.6549019608, - 0.34509803921568627, - 0.3568627451, - 0.0039215686, - 0.6392156863, - 0.34901960784313724, - 0.3725490196, - 0.0039215686, - 0.6235294118, - 0.35294117647058826, - 0.3882352941, - 0.0039215686, - 0.6078431373, - 0.3568627450980392, - 0.4039215686, - 0.0039215686, - 0.5921568627, - 0.3607843137254902, - 0.4196078431, - 0.0039215686, - 0.5764705882, - 0.36470588235294116, - 0.4352941176, - 0.0039215686, - 0.5607843137, - 0.3686274509803922, - 0.4509803922, - 0.0039215686, - 0.5450980392, - 0.37254901960784315, - 0.4666666667, - 0.0039215686, - 0.5294117647, - 0.3764705882352941, - 0.4823529412, - 0.0039215686, - 0.5137254902, - 0.3803921568627451, - 0.4980392157, - 0.0039215686, - 0.4980392157, - 0.3843137254901961, - 0.5137254902, - 0.0039215686, - 0.4823529412, - 0.38823529411764707, - 0.5294117647, - 0.0039215686, - 0.4666666667, - 0.39215686274509803, - 0.5450980392, - 0.0039215686, - 0.4509803922, - 0.396078431372549, - 0.5607843137, - 0.0039215686, - 0.4352941176, - 0.4, - 0.5764705882, - 0.0039215686, - 0.4196078431, - 0.403921568627451, - 0.5921568627, - 0.0039215686, - 0.4039215686, - 0.40784313725490196, - 0.6078431373, - 0.0039215686, - 0.3882352941, - 0.4117647058823529, - 0.6235294118, - 0.0039215686, - 0.3725490196, - 0.41568627450980394, - 0.6392156863, - 0.0039215686, - 0.3568627451, - 0.4196078431372549, - 0.6549019608, - 0.0039215686, - 0.3411764706, - 0.4235294117647059, - 0.6705882353, - 0.0039215686, - 0.3254901961, - 0.42745098039215684, - 0.6862745098, - 0.0039215686, - 0.3098039216, - 0.43137254901960786, - 0.7019607843, - 0.0039215686, - 0.2941176471, - 0.43529411764705883, - 0.7176470588, - 0.0039215686, - 0.2784313725, - 0.4392156862745098, - 0.7333333333, - 0.0039215686, - 0.262745098, - 0.44313725490196076, - 0.7490196078, - 0.0039215686, - 0.2509803922, - 0.4470588235294118, - 0.7607843137, - 0.0039215686, - 0.2352941176, - 0.45098039215686275, - 0.7764705882, - 0.0039215686, - 0.2196078431, - 0.4549019607843137, - 0.7921568627, - 0.0039215686, - 0.2039215686, - 0.4588235294117647, - 0.8078431373, - 0.0039215686, - 0.1882352941, - 0.4627450980392157, - 0.8235294118, - 0.0039215686, - 0.1725490196, - 0.4666666666666667, - 0.8392156863, - 0.0039215686, - 0.1568627451, - 0.4705882352941177, - 0.8549019608, - 0.0039215686, - 0.1411764706, - 0.4745098039215686, - 0.8705882353, - 0.0039215686, - 0.1254901961, - 0.4784313725490197, - 0.8862745098, - 0.0039215686, - 0.1098039216, - 0.48235294117647065, - 0.9019607843, - 0.0039215686, - 0.0941176471, - 0.48627450980392156, - 0.9176470588, - 0.0039215686, - 0.0784313725, - 0.49019607843137253, - 0.9333333333, - 0.0039215686, - 0.062745098, - 0.49411764705882355, - 0.9490196078, - 0.0039215686, - 0.0470588235, - 0.4980392156862745, - 0.9647058824, - 0.0039215686, - 0.031372549, - 0.5019607843137255, - 0.9803921569, - 0.0039215686, - 0.0156862745, - 0.5058823529411764, - 0.9960784314, - 0.0039215686, - 0.0039215686, - 0.5098039215686274, - 0.9960784314, - 0.0156862745, - 0.0039215686, - 0.5137254901960784, - 0.9960784314, - 0.031372549, - 0.0039215686, - 0.5176470588235295, - 0.9960784314, - 0.0470588235, - 0.0039215686, - 0.5215686274509804, - 0.9960784314, - 0.062745098, - 0.0039215686, - 0.5254901960784314, - 0.9960784314, - 0.0784313725, - 0.0039215686, - 0.5294117647058824, - 0.9960784314, - 0.0941176471, - 0.0039215686, - 0.5333333333333333, - 0.9960784314, - 0.1098039216, - 0.0039215686, - 0.5372549019607843, - 0.9960784314, - 0.1254901961, - 0.0039215686, - 0.5411764705882353, - 0.9960784314, - 0.1411764706, - 0.0039215686, - 0.5450980392156862, - 0.9960784314, - 0.1568627451, - 0.0039215686, - 0.5490196078431373, - 0.9960784314, - 0.1725490196, - 0.0039215686, - 0.5529411764705883, - 0.9960784314, - 0.1882352941, - 0.0039215686, - 0.5568627450980392, - 0.9960784314, - 0.2039215686, - 0.0039215686, - 0.5607843137254902, - 0.9960784314, - 0.2196078431, - 0.0039215686, - 0.5647058823529412, - 0.9960784314, - 0.2352941176, - 0.0039215686, - 0.5686274509803921, - 0.9960784314, - 0.2509803922, - 0.0039215686, - 0.5725490196078431, - 0.9960784314, - 0.262745098, - 0.0039215686, - 0.5764705882352941, - 0.9960784314, - 0.2784313725, - 0.0039215686, - 0.5803921568627451, - 0.9960784314, - 0.2941176471, - 0.0039215686, - 0.5843137254901961, - 0.9960784314, - 0.3098039216, - 0.0039215686, - 0.5882352941176471, - 0.9960784314, - 0.3254901961, - 0.0039215686, - 0.592156862745098, - 0.9960784314, - 0.3411764706, - 0.0039215686, - 0.596078431372549, - 0.9960784314, - 0.3568627451, - 0.0039215686, - 0.6, - 0.9960784314, - 0.3725490196, - 0.0039215686, - 0.6039215686274509, - 0.9960784314, - 0.3882352941, - 0.0039215686, - 0.6078431372549019, - 0.9960784314, - 0.4039215686, - 0.0039215686, - 0.611764705882353, - 0.9960784314, - 0.4196078431, - 0.0039215686, - 0.615686274509804, - 0.9960784314, - 0.4352941176, - 0.0039215686, - 0.6196078431372549, - 0.9960784314, - 0.4509803922, - 0.0039215686, - 0.6235294117647059, - 0.9960784314, - 0.4666666667, - 0.0039215686, - 0.6274509803921569, - 0.9960784314, - 0.4823529412, - 0.0039215686, - 0.6313725490196078, - 0.9960784314, - 0.4980392157, - 0.0039215686, - 0.6352941176470588, - 0.9960784314, - 0.5137254902, - 0.0039215686, - 0.6392156862745098, - 0.9960784314, - 0.5294117647, - 0.0039215686, - 0.6431372549019608, - 0.9960784314, - 0.5450980392, - 0.0039215686, - 0.6470588235294118, - 0.9960784314, - 0.5607843137, - 0.0039215686, - 0.6509803921568628, - 0.9960784314, - 0.5764705882, - 0.0039215686, - 0.6549019607843137, - 0.9960784314, - 0.5921568627, - 0.0039215686, - 0.6588235294117647, - 0.9960784314, - 0.6078431373, - 0.0039215686, - 0.6627450980392157, - 0.9960784314, - 0.6235294118, - 0.0039215686, - 0.6666666666666666, - 0.9960784314, - 0.6392156863, - 0.0039215686, - 0.6705882352941176, - 0.9960784314, - 0.6549019608, - 0.0039215686, - 0.6745098039215687, - 0.9960784314, - 0.6705882353, - 0.0039215686, - 0.6784313725490196, - 0.9960784314, - 0.6862745098, - 0.0039215686, - 0.6823529411764706, - 0.9960784314, - 0.7019607843, - 0.0039215686, - 0.6862745098039216, - 0.9960784314, - 0.7176470588, - 0.0039215686, - 0.6901960784313725, - 0.9960784314, - 0.7333333333, - 0.0039215686, - 0.6941176470588235, - 0.9960784314, - 0.7490196078, - 0.0039215686, - 0.6980392156862745, - 0.9960784314, - 0.7607843137, - 0.0039215686, - 0.7019607843137254, - 0.9960784314, - 0.7764705882, - 0.0039215686, - 0.7058823529411765, - 0.9960784314, - 0.7921568627, - 0.0039215686, - 0.7098039215686275, - 0.9960784314, - 0.8078431373, - 0.0039215686, - 0.7137254901960784, - 0.9960784314, - 0.8235294118, - 0.0039215686, - 0.7176470588235294, - 0.9960784314, - 0.8392156863, - 0.0039215686, - 0.7215686274509804, - 0.9960784314, - 0.8549019608, - 0.0039215686, - 0.7254901960784313, - 0.9960784314, - 0.8705882353, - 0.0039215686, - 0.7294117647058823, - 0.9960784314, - 0.8862745098, - 0.0039215686, - 0.7333333333333333, - 0.9960784314, - 0.9019607843, - 0.0039215686, - 0.7372549019607844, - 0.9960784314, - 0.9176470588, - 0.0039215686, - 0.7411764705882353, - 0.9960784314, - 0.9333333333, - 0.0039215686, - 0.7450980392156863, - 0.9960784314, - 0.9490196078, - 0.0039215686, - 0.7490196078431373, - 0.9960784314, - 0.9647058824, - 0.0039215686, - 0.7529411764705882, - 0.9960784314, - 0.9803921569, - 0.0039215686, - 0.7568627450980392, - 0.9960784314, - 0.9960784314, - 0.0039215686, - 0.7607843137254902, - 0.9960784314, - 0.9960784314, - 0.0196078431, - 0.7647058823529411, - 0.9960784314, - 0.9960784314, - 0.0352941176, - 0.7686274509803922, - 0.9960784314, - 0.9960784314, - 0.0509803922, - 0.7725490196078432, - 0.9960784314, - 0.9960784314, - 0.0666666667, - 0.7764705882352941, - 0.9960784314, - 0.9960784314, - 0.0823529412, - 0.7803921568627451, - 0.9960784314, - 0.9960784314, - 0.0980392157, - 0.7843137254901961, - 0.9960784314, - 0.9960784314, - 0.1137254902, - 0.788235294117647, - 0.9960784314, - 0.9960784314, - 0.1294117647, - 0.792156862745098, - 0.9960784314, - 0.9960784314, - 0.1450980392, - 0.796078431372549, - 0.9960784314, - 0.9960784314, - 0.1607843137, - 0.8, - 0.9960784314, - 0.9960784314, - 0.1764705882, - 0.803921568627451, - 0.9960784314, - 0.9960784314, - 0.1921568627, - 0.807843137254902, - 0.9960784314, - 0.9960784314, - 0.2078431373, - 0.8117647058823529, - 0.9960784314, - 0.9960784314, - 0.2235294118, - 0.8156862745098039, - 0.9960784314, - 0.9960784314, - 0.2392156863, - 0.8196078431372549, - 0.9960784314, - 0.9960784314, - 0.2509803922, - 0.8235294117647058, - 0.9960784314, - 0.9960784314, - 0.2666666667, - 0.8274509803921568, - 0.9960784314, - 0.9960784314, - 0.2823529412, - 0.8313725490196079, - 0.9960784314, - 0.9960784314, - 0.2980392157, - 0.8352941176470589, - 0.9960784314, - 0.9960784314, - 0.3137254902, - 0.8392156862745098, - 0.9960784314, - 0.9960784314, - 0.3333333333, - 0.8431372549019608, - 0.9960784314, - 0.9960784314, - 0.3490196078, - 0.8470588235294118, - 0.9960784314, - 0.9960784314, - 0.3647058824, - 0.8509803921568627, - 0.9960784314, - 0.9960784314, - 0.3803921569, - 0.8549019607843137, - 0.9960784314, - 0.9960784314, - 0.3960784314, - 0.8588235294117647, - 0.9960784314, - 0.9960784314, - 0.4117647059, - 0.8627450980392157, - 0.9960784314, - 0.9960784314, - 0.4274509804, - 0.8666666666666667, - 0.9960784314, - 0.9960784314, - 0.4431372549, - 0.8705882352941177, - 0.9960784314, - 0.9960784314, - 0.4588235294, - 0.8745098039215686, - 0.9960784314, - 0.9960784314, - 0.4745098039, - 0.8784313725490196, - 0.9960784314, - 0.9960784314, - 0.4901960784, - 0.8823529411764706, - 0.9960784314, - 0.9960784314, - 0.5058823529, - 0.8862745098039215, - 0.9960784314, - 0.9960784314, - 0.5215686275, - 0.8901960784313725, - 0.9960784314, - 0.9960784314, - 0.537254902, - 0.8941176470588236, - 0.9960784314, - 0.9960784314, - 0.5529411765, - 0.8980392156862745, - 0.9960784314, - 0.9960784314, - 0.568627451, - 0.9019607843137255, - 0.9960784314, - 0.9960784314, - 0.5843137255, - 0.9058823529411765, - 0.9960784314, - 0.9960784314, - 0.6, - 0.9098039215686274, - 0.9960784314, - 0.9960784314, - 0.6156862745, - 0.9137254901960784, - 0.9960784314, - 0.9960784314, - 0.631372549, - 0.9176470588235294, - 0.9960784314, - 0.9960784314, - 0.6470588235, - 0.9215686274509803, - 0.9960784314, - 0.9960784314, - 0.6666666667, - 0.9254901960784314, - 0.9960784314, - 0.9960784314, - 0.6823529412, - 0.9294117647058824, - 0.9960784314, - 0.9960784314, - 0.6980392157, - 0.9333333333333333, - 0.9960784314, - 0.9960784314, - 0.7137254902, - 0.9372549019607843, - 0.9960784314, - 0.9960784314, - 0.7294117647, - 0.9411764705882354, - 0.9960784314, - 0.9960784314, - 0.7450980392, - 0.9450980392156864, - 0.9960784314, - 0.9960784314, - 0.7568627451, - 0.9490196078431372, - 0.9960784314, - 0.9960784314, - 0.7725490196, - 0.9529411764705882, - 0.9960784314, - 0.9960784314, - 0.7882352941, - 0.9568627450980394, - 0.9960784314, - 0.9960784314, - 0.8039215686, - 0.9607843137254903, - 0.9960784314, - 0.9960784314, - 0.8196078431, - 0.9647058823529413, - 0.9960784314, - 0.9960784314, - 0.8352941176, - 0.9686274509803922, - 0.9960784314, - 0.9960784314, - 0.8509803922, - 0.9725490196078431, - 0.9960784314, - 0.9960784314, - 0.8666666667, - 0.9764705882352941, - 0.9960784314, - 0.9960784314, - 0.8823529412, - 0.9803921568627451, - 0.9960784314, - 0.9960784314, - 0.8980392157, - 0.984313725490196, - 0.9960784314, - 0.9960784314, - 0.9137254902, - 0.9882352941176471, - 0.9960784314, - 0.9960784314, - 0.9294117647, - 0.9921568627450981, - 0.9960784314, - 0.9960784314, - 0.9450980392, - 0.996078431372549, - 0.9960784314, - 0.9960784314, - 0.9607843137, - 1.0, - 0.9960784314, - 0.9960784314, - 0.9607843137, + 0.0, 0.0039215686, 0.0039215686, 0.0156862745, 0.00392156862745098, 0.0039215686, + 0.0039215686, 0.0156862745, 0.00784313725490196, 0.0039215686, 0.0039215686, 0.031372549, + 0.011764705882352941, 0.0039215686, 0.0039215686, 0.0470588235, 0.01568627450980392, + 0.0039215686, 0.0039215686, 0.062745098, 0.0196078431372549, 0.0039215686, 0.0039215686, + 0.0784313725, 0.023529411764705882, 0.0039215686, 0.0039215686, 0.0941176471, + 0.027450980392156862, 0.0039215686, 0.0039215686, 0.1098039216, 0.03137254901960784, + 0.0039215686, 0.0039215686, 0.1254901961, 0.03529411764705882, 0.0039215686, 0.0039215686, + 0.1411764706, 0.0392156862745098, 0.0039215686, 0.0039215686, 0.1568627451, + 0.043137254901960784, 0.0039215686, 0.0039215686, 0.1725490196, 0.047058823529411764, + 0.0039215686, 0.0039215686, 0.1882352941, 0.050980392156862744, 0.0039215686, 0.0039215686, + 0.2039215686, 0.054901960784313725, 0.0039215686, 0.0039215686, 0.2196078431, + 0.05882352941176471, 0.0039215686, 0.0039215686, 0.2352941176, 0.06274509803921569, + 0.0039215686, 0.0039215686, 0.2509803922, 0.06666666666666667, 0.0039215686, 0.0039215686, + 0.262745098, 0.07058823529411765, 0.0039215686, 0.0039215686, 0.2784313725, + 0.07450980392156863, 0.0039215686, 0.0039215686, 0.2941176471, 0.0784313725490196, + 0.0039215686, 0.0039215686, 0.3098039216, 0.08235294117647059, 0.0039215686, 0.0039215686, + 0.3254901961, 0.08627450980392157, 0.0039215686, 0.0039215686, 0.3411764706, + 0.09019607843137255, 0.0039215686, 0.0039215686, 0.3568627451, 0.09411764705882353, + 0.0039215686, 0.0039215686, 0.3725490196, 0.09803921568627451, 0.0039215686, 0.0039215686, + 0.3882352941, 0.10196078431372549, 0.0039215686, 0.0039215686, 0.4039215686, + 0.10588235294117647, 0.0039215686, 0.0039215686, 0.4196078431, 0.10980392156862745, + 0.0039215686, 0.0039215686, 0.4352941176, 0.11372549019607843, 0.0039215686, 0.0039215686, + 0.4509803922, 0.11764705882352942, 0.0039215686, 0.0039215686, 0.4666666667, + 0.12156862745098039, 0.0039215686, 0.0039215686, 0.4823529412, 0.12549019607843137, + 0.0039215686, 0.0039215686, 0.4980392157, 0.12941176470588237, 0.0039215686, 0.0039215686, + 0.5137254902, 0.13333333333333333, 0.0039215686, 0.0039215686, 0.5294117647, + 0.13725490196078433, 0.0039215686, 0.0039215686, 0.5450980392, 0.1411764705882353, + 0.0039215686, 0.0039215686, 0.5607843137, 0.1450980392156863, 0.0039215686, 0.0039215686, + 0.5764705882, 0.14901960784313725, 0.0039215686, 0.0039215686, 0.5921568627, + 0.15294117647058825, 0.0039215686, 0.0039215686, 0.6078431373, 0.1568627450980392, + 0.0039215686, 0.0039215686, 0.6235294118, 0.1607843137254902, 0.0039215686, 0.0039215686, + 0.6392156863, 0.16470588235294117, 0.0039215686, 0.0039215686, 0.6549019608, + 0.16862745098039217, 0.0039215686, 0.0039215686, 0.6705882353, 0.17254901960784313, + 0.0039215686, 0.0039215686, 0.6862745098, 0.17647058823529413, 0.0039215686, 0.0039215686, + 0.7019607843, 0.1803921568627451, 0.0039215686, 0.0039215686, 0.7176470588, + 0.1843137254901961, 0.0039215686, 0.0039215686, 0.7333333333, 0.18823529411764706, + 0.0039215686, 0.0039215686, 0.7490196078, 0.19215686274509805, 0.0039215686, 0.0039215686, + 0.7607843137, 0.19607843137254902, 0.0039215686, 0.0039215686, 0.7764705882, 0.2, + 0.0039215686, 0.0039215686, 0.7921568627, 0.20392156862745098, 0.0039215686, 0.0039215686, + 0.8078431373, 0.20784313725490197, 0.0039215686, 0.0039215686, 0.8235294118, + 0.21176470588235294, 0.0039215686, 0.0039215686, 0.8392156863, 0.21568627450980393, + 0.0039215686, 0.0039215686, 0.8549019608, 0.2196078431372549, 0.0039215686, 0.0039215686, + 0.8705882353, 0.2235294117647059, 0.0039215686, 0.0039215686, 0.8862745098, + 0.22745098039215686, 0.0039215686, 0.0039215686, 0.9019607843, 0.23137254901960785, + 0.0039215686, 0.0039215686, 0.9176470588, 0.23529411764705885, 0.0039215686, 0.0039215686, + 0.9333333333, 0.23921568627450984, 0.0039215686, 0.0039215686, 0.9490196078, + 0.24313725490196078, 0.0039215686, 0.0039215686, 0.9647058824, 0.24705882352941178, + 0.0039215686, 0.0039215686, 0.9803921569, 0.25098039215686274, 0.0039215686, 0.0039215686, + 0.9960784314, 0.2549019607843137, 0.0039215686, 0.0039215686, 0.9960784314, + 0.25882352941176473, 0.0156862745, 0.0039215686, 0.9803921569, 0.2627450980392157, + 0.031372549, 0.0039215686, 0.9647058824, 0.26666666666666666, 0.0470588235, 0.0039215686, + 0.9490196078, 0.27058823529411763, 0.062745098, 0.0039215686, 0.9333333333, + 0.27450980392156865, 0.0784313725, 0.0039215686, 0.9176470588, 0.2784313725490196, + 0.0941176471, 0.0039215686, 0.9019607843, 0.2823529411764706, 0.1098039216, 0.0039215686, + 0.8862745098, 0.28627450980392155, 0.1254901961, 0.0039215686, 0.8705882353, + 0.2901960784313726, 0.1411764706, 0.0039215686, 0.8549019608, 0.29411764705882354, + 0.1568627451, 0.0039215686, 0.8392156863, 0.2980392156862745, 0.1725490196, 0.0039215686, + 0.8235294118, 0.30196078431372547, 0.1882352941, 0.0039215686, 0.8078431373, + 0.3058823529411765, 0.2039215686, 0.0039215686, 0.7921568627, 0.30980392156862746, + 0.2196078431, 0.0039215686, 0.7764705882, 0.3137254901960784, 0.2352941176, 0.0039215686, + 0.7607843137, 0.3176470588235294, 0.2509803922, 0.0039215686, 0.7490196078, + 0.3215686274509804, 0.262745098, 0.0039215686, 0.7333333333, 0.3254901960784314, 0.2784313725, + 0.0039215686, 0.7176470588, 0.32941176470588235, 0.2941176471, 0.0039215686, 0.7019607843, + 0.3333333333333333, 0.3098039216, 0.0039215686, 0.6862745098, 0.33725490196078434, + 0.3254901961, 0.0039215686, 0.6705882353, 0.3411764705882353, 0.3411764706, 0.0039215686, + 0.6549019608, 0.34509803921568627, 0.3568627451, 0.0039215686, 0.6392156863, + 0.34901960784313724, 0.3725490196, 0.0039215686, 0.6235294118, 0.35294117647058826, + 0.3882352941, 0.0039215686, 0.6078431373, 0.3568627450980392, 0.4039215686, 0.0039215686, + 0.5921568627, 0.3607843137254902, 0.4196078431, 0.0039215686, 0.5764705882, + 0.36470588235294116, 0.4352941176, 0.0039215686, 0.5607843137, 0.3686274509803922, + 0.4509803922, 0.0039215686, 0.5450980392, 0.37254901960784315, 0.4666666667, 0.0039215686, + 0.5294117647, 0.3764705882352941, 0.4823529412, 0.0039215686, 0.5137254902, + 0.3803921568627451, 0.4980392157, 0.0039215686, 0.4980392157, 0.3843137254901961, + 0.5137254902, 0.0039215686, 0.4823529412, 0.38823529411764707, 0.5294117647, 0.0039215686, + 0.4666666667, 0.39215686274509803, 0.5450980392, 0.0039215686, 0.4509803922, + 0.396078431372549, 0.5607843137, 0.0039215686, 0.4352941176, 0.4, 0.5764705882, 0.0039215686, + 0.4196078431, 0.403921568627451, 0.5921568627, 0.0039215686, 0.4039215686, + 0.40784313725490196, 0.6078431373, 0.0039215686, 0.3882352941, 0.4117647058823529, + 0.6235294118, 0.0039215686, 0.3725490196, 0.41568627450980394, 0.6392156863, 0.0039215686, + 0.3568627451, 0.4196078431372549, 0.6549019608, 0.0039215686, 0.3411764706, + 0.4235294117647059, 0.6705882353, 0.0039215686, 0.3254901961, 0.42745098039215684, + 0.6862745098, 0.0039215686, 0.3098039216, 0.43137254901960786, 0.7019607843, 0.0039215686, + 0.2941176471, 0.43529411764705883, 0.7176470588, 0.0039215686, 0.2784313725, + 0.4392156862745098, 0.7333333333, 0.0039215686, 0.262745098, 0.44313725490196076, + 0.7490196078, 0.0039215686, 0.2509803922, 0.4470588235294118, 0.7607843137, 0.0039215686, + 0.2352941176, 0.45098039215686275, 0.7764705882, 0.0039215686, 0.2196078431, + 0.4549019607843137, 0.7921568627, 0.0039215686, 0.2039215686, 0.4588235294117647, + 0.8078431373, 0.0039215686, 0.1882352941, 0.4627450980392157, 0.8235294118, 0.0039215686, + 0.1725490196, 0.4666666666666667, 0.8392156863, 0.0039215686, 0.1568627451, + 0.4705882352941177, 0.8549019608, 0.0039215686, 0.1411764706, 0.4745098039215686, + 0.8705882353, 0.0039215686, 0.1254901961, 0.4784313725490197, 0.8862745098, 0.0039215686, + 0.1098039216, 0.48235294117647065, 0.9019607843, 0.0039215686, 0.0941176471, + 0.48627450980392156, 0.9176470588, 0.0039215686, 0.0784313725, 0.49019607843137253, + 0.9333333333, 0.0039215686, 0.062745098, 0.49411764705882355, 0.9490196078, 0.0039215686, + 0.0470588235, 0.4980392156862745, 0.9647058824, 0.0039215686, 0.031372549, 0.5019607843137255, + 0.9803921569, 0.0039215686, 0.0156862745, 0.5058823529411764, 0.9960784314, 0.0039215686, + 0.0039215686, 0.5098039215686274, 0.9960784314, 0.0156862745, 0.0039215686, + 0.5137254901960784, 0.9960784314, 0.031372549, 0.0039215686, 0.5176470588235295, 0.9960784314, + 0.0470588235, 0.0039215686, 0.5215686274509804, 0.9960784314, 0.062745098, 0.0039215686, + 0.5254901960784314, 0.9960784314, 0.0784313725, 0.0039215686, 0.5294117647058824, + 0.9960784314, 0.0941176471, 0.0039215686, 0.5333333333333333, 0.9960784314, 0.1098039216, + 0.0039215686, 0.5372549019607843, 0.9960784314, 0.1254901961, 0.0039215686, + 0.5411764705882353, 0.9960784314, 0.1411764706, 0.0039215686, 0.5450980392156862, + 0.9960784314, 0.1568627451, 0.0039215686, 0.5490196078431373, 0.9960784314, 0.1725490196, + 0.0039215686, 0.5529411764705883, 0.9960784314, 0.1882352941, 0.0039215686, + 0.5568627450980392, 0.9960784314, 0.2039215686, 0.0039215686, 0.5607843137254902, + 0.9960784314, 0.2196078431, 0.0039215686, 0.5647058823529412, 0.9960784314, 0.2352941176, + 0.0039215686, 0.5686274509803921, 0.9960784314, 0.2509803922, 0.0039215686, + 0.5725490196078431, 0.9960784314, 0.262745098, 0.0039215686, 0.5764705882352941, 0.9960784314, + 0.2784313725, 0.0039215686, 0.5803921568627451, 0.9960784314, 0.2941176471, 0.0039215686, + 0.5843137254901961, 0.9960784314, 0.3098039216, 0.0039215686, 0.5882352941176471, + 0.9960784314, 0.3254901961, 0.0039215686, 0.592156862745098, 0.9960784314, 0.3411764706, + 0.0039215686, 0.596078431372549, 0.9960784314, 0.3568627451, 0.0039215686, 0.6, 0.9960784314, + 0.3725490196, 0.0039215686, 0.6039215686274509, 0.9960784314, 0.3882352941, 0.0039215686, + 0.6078431372549019, 0.9960784314, 0.4039215686, 0.0039215686, 0.611764705882353, 0.9960784314, + 0.4196078431, 0.0039215686, 0.615686274509804, 0.9960784314, 0.4352941176, 0.0039215686, + 0.6196078431372549, 0.9960784314, 0.4509803922, 0.0039215686, 0.6235294117647059, + 0.9960784314, 0.4666666667, 0.0039215686, 0.6274509803921569, 0.9960784314, 0.4823529412, + 0.0039215686, 0.6313725490196078, 0.9960784314, 0.4980392157, 0.0039215686, + 0.6352941176470588, 0.9960784314, 0.5137254902, 0.0039215686, 0.6392156862745098, + 0.9960784314, 0.5294117647, 0.0039215686, 0.6431372549019608, 0.9960784314, 0.5450980392, + 0.0039215686, 0.6470588235294118, 0.9960784314, 0.5607843137, 0.0039215686, + 0.6509803921568628, 0.9960784314, 0.5764705882, 0.0039215686, 0.6549019607843137, + 0.9960784314, 0.5921568627, 0.0039215686, 0.6588235294117647, 0.9960784314, 0.6078431373, + 0.0039215686, 0.6627450980392157, 0.9960784314, 0.6235294118, 0.0039215686, + 0.6666666666666666, 0.9960784314, 0.6392156863, 0.0039215686, 0.6705882352941176, + 0.9960784314, 0.6549019608, 0.0039215686, 0.6745098039215687, 0.9960784314, 0.6705882353, + 0.0039215686, 0.6784313725490196, 0.9960784314, 0.6862745098, 0.0039215686, + 0.6823529411764706, 0.9960784314, 0.7019607843, 0.0039215686, 0.6862745098039216, + 0.9960784314, 0.7176470588, 0.0039215686, 0.6901960784313725, 0.9960784314, 0.7333333333, + 0.0039215686, 0.6941176470588235, 0.9960784314, 0.7490196078, 0.0039215686, + 0.6980392156862745, 0.9960784314, 0.7607843137, 0.0039215686, 0.7019607843137254, + 0.9960784314, 0.7764705882, 0.0039215686, 0.7058823529411765, 0.9960784314, 0.7921568627, + 0.0039215686, 0.7098039215686275, 0.9960784314, 0.8078431373, 0.0039215686, + 0.7137254901960784, 0.9960784314, 0.8235294118, 0.0039215686, 0.7176470588235294, + 0.9960784314, 0.8392156863, 0.0039215686, 0.7215686274509804, 0.9960784314, 0.8549019608, + 0.0039215686, 0.7254901960784313, 0.9960784314, 0.8705882353, 0.0039215686, + 0.7294117647058823, 0.9960784314, 0.8862745098, 0.0039215686, 0.7333333333333333, + 0.9960784314, 0.9019607843, 0.0039215686, 0.7372549019607844, 0.9960784314, 0.9176470588, + 0.0039215686, 0.7411764705882353, 0.9960784314, 0.9333333333, 0.0039215686, + 0.7450980392156863, 0.9960784314, 0.9490196078, 0.0039215686, 0.7490196078431373, + 0.9960784314, 0.9647058824, 0.0039215686, 0.7529411764705882, 0.9960784314, 0.9803921569, + 0.0039215686, 0.7568627450980392, 0.9960784314, 0.9960784314, 0.0039215686, + 0.7607843137254902, 0.9960784314, 0.9960784314, 0.0196078431, 0.7647058823529411, + 0.9960784314, 0.9960784314, 0.0352941176, 0.7686274509803922, 0.9960784314, 0.9960784314, + 0.0509803922, 0.7725490196078432, 0.9960784314, 0.9960784314, 0.0666666667, + 0.7764705882352941, 0.9960784314, 0.9960784314, 0.0823529412, 0.7803921568627451, + 0.9960784314, 0.9960784314, 0.0980392157, 0.7843137254901961, 0.9960784314, 0.9960784314, + 0.1137254902, 0.788235294117647, 0.9960784314, 0.9960784314, 0.1294117647, 0.792156862745098, + 0.9960784314, 0.9960784314, 0.1450980392, 0.796078431372549, 0.9960784314, 0.9960784314, + 0.1607843137, 0.8, 0.9960784314, 0.9960784314, 0.1764705882, 0.803921568627451, 0.9960784314, + 0.9960784314, 0.1921568627, 0.807843137254902, 0.9960784314, 0.9960784314, 0.2078431373, + 0.8117647058823529, 0.9960784314, 0.9960784314, 0.2235294118, 0.8156862745098039, + 0.9960784314, 0.9960784314, 0.2392156863, 0.8196078431372549, 0.9960784314, 0.9960784314, + 0.2509803922, 0.8235294117647058, 0.9960784314, 0.9960784314, 0.2666666667, + 0.8274509803921568, 0.9960784314, 0.9960784314, 0.2823529412, 0.8313725490196079, + 0.9960784314, 0.9960784314, 0.2980392157, 0.8352941176470589, 0.9960784314, 0.9960784314, + 0.3137254902, 0.8392156862745098, 0.9960784314, 0.9960784314, 0.3333333333, + 0.8431372549019608, 0.9960784314, 0.9960784314, 0.3490196078, 0.8470588235294118, + 0.9960784314, 0.9960784314, 0.3647058824, 0.8509803921568627, 0.9960784314, 0.9960784314, + 0.3803921569, 0.8549019607843137, 0.9960784314, 0.9960784314, 0.3960784314, + 0.8588235294117647, 0.9960784314, 0.9960784314, 0.4117647059, 0.8627450980392157, + 0.9960784314, 0.9960784314, 0.4274509804, 0.8666666666666667, 0.9960784314, 0.9960784314, + 0.4431372549, 0.8705882352941177, 0.9960784314, 0.9960784314, 0.4588235294, + 0.8745098039215686, 0.9960784314, 0.9960784314, 0.4745098039, 0.8784313725490196, + 0.9960784314, 0.9960784314, 0.4901960784, 0.8823529411764706, 0.9960784314, 0.9960784314, + 0.5058823529, 0.8862745098039215, 0.9960784314, 0.9960784314, 0.5215686275, + 0.8901960784313725, 0.9960784314, 0.9960784314, 0.537254902, 0.8941176470588236, 0.9960784314, + 0.9960784314, 0.5529411765, 0.8980392156862745, 0.9960784314, 0.9960784314, 0.568627451, + 0.9019607843137255, 0.9960784314, 0.9960784314, 0.5843137255, 0.9058823529411765, + 0.9960784314, 0.9960784314, 0.6, 0.9098039215686274, 0.9960784314, 0.9960784314, 0.6156862745, + 0.9137254901960784, 0.9960784314, 0.9960784314, 0.631372549, 0.9176470588235294, 0.9960784314, + 0.9960784314, 0.6470588235, 0.9215686274509803, 0.9960784314, 0.9960784314, 0.6666666667, + 0.9254901960784314, 0.9960784314, 0.9960784314, 0.6823529412, 0.9294117647058824, + 0.9960784314, 0.9960784314, 0.6980392157, 0.9333333333333333, 0.9960784314, 0.9960784314, + 0.7137254902, 0.9372549019607843, 0.9960784314, 0.9960784314, 0.7294117647, + 0.9411764705882354, 0.9960784314, 0.9960784314, 0.7450980392, 0.9450980392156864, + 0.9960784314, 0.9960784314, 0.7568627451, 0.9490196078431372, 0.9960784314, 0.9960784314, + 0.7725490196, 0.9529411764705882, 0.9960784314, 0.9960784314, 0.7882352941, + 0.9568627450980394, 0.9960784314, 0.9960784314, 0.8039215686, 0.9607843137254903, + 0.9960784314, 0.9960784314, 0.8196078431, 0.9647058823529413, 0.9960784314, 0.9960784314, + 0.8352941176, 0.9686274509803922, 0.9960784314, 0.9960784314, 0.8509803922, + 0.9725490196078431, 0.9960784314, 0.9960784314, 0.8666666667, 0.9764705882352941, + 0.9960784314, 0.9960784314, 0.8823529412, 0.9803921568627451, 0.9960784314, 0.9960784314, + 0.8980392157, 0.984313725490196, 0.9960784314, 0.9960784314, 0.9137254902, 0.9882352941176471, + 0.9960784314, 0.9960784314, 0.9294117647, 0.9921568627450981, 0.9960784314, 0.9960784314, + 0.9450980392, 0.996078431372549, 0.9960784314, 0.9960784314, 0.9607843137, 1.0, 0.9960784314, + 0.9960784314, 0.9607843137, ], }, { ColorSpace: 'RGB', Name: 'red_hot', RGBPoints: [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.00392156862745098, - 0.0, - 0.0, - 0.0, - 0.00784313725490196, - 0.0, - 0.0, - 0.0, - 0.011764705882352941, - 0.0, - 0.0, - 0.0, - 0.01568627450980392, - 0.0039215686, - 0.0039215686, - 0.0039215686, - 0.0196078431372549, - 0.0039215686, - 0.0039215686, - 0.0039215686, - 0.023529411764705882, - 0.0039215686, - 0.0039215686, - 0.0039215686, - 0.027450980392156862, - 0.0039215686, - 0.0039215686, - 0.0039215686, - 0.03137254901960784, - 0.0039215686, - 0.0039215686, - 0.0039215686, - 0.03529411764705882, - 0.0156862745, - 0.0, - 0.0, - 0.0392156862745098, - 0.0274509804, - 0.0, - 0.0, - 0.043137254901960784, - 0.0392156863, - 0.0, - 0.0, - 0.047058823529411764, - 0.0509803922, - 0.0, - 0.0, - 0.050980392156862744, - 0.062745098, - 0.0, - 0.0, - 0.054901960784313725, - 0.0784313725, - 0.0, - 0.0, - 0.05882352941176471, - 0.0901960784, - 0.0, - 0.0, - 0.06274509803921569, - 0.1058823529, - 0.0, - 0.0, - 0.06666666666666667, - 0.1176470588, - 0.0, - 0.0, - 0.07058823529411765, - 0.1294117647, - 0.0, - 0.0, - 0.07450980392156863, - 0.1411764706, - 0.0, - 0.0, - 0.0784313725490196, - 0.1529411765, - 0.0, - 0.0, - 0.08235294117647059, - 0.1647058824, - 0.0, - 0.0, - 0.08627450980392157, - 0.1764705882, - 0.0, - 0.0, - 0.09019607843137255, - 0.1882352941, - 0.0, - 0.0, - 0.09411764705882353, - 0.2039215686, - 0.0, - 0.0, - 0.09803921568627451, - 0.2156862745, - 0.0, - 0.0, - 0.10196078431372549, - 0.2274509804, - 0.0, - 0.0, - 0.10588235294117647, - 0.2392156863, - 0.0, - 0.0, - 0.10980392156862745, - 0.2549019608, - 0.0, - 0.0, - 0.11372549019607843, - 0.2666666667, - 0.0, - 0.0, - 0.11764705882352942, - 0.2784313725, - 0.0, - 0.0, - 0.12156862745098039, - 0.2901960784, - 0.0, - 0.0, - 0.12549019607843137, - 0.3058823529, - 0.0, - 0.0, - 0.12941176470588237, - 0.3176470588, - 0.0, - 0.0, - 0.13333333333333333, - 0.3294117647, - 0.0, - 0.0, - 0.13725490196078433, - 0.3411764706, - 0.0, - 0.0, - 0.1411764705882353, - 0.3529411765, - 0.0, - 0.0, - 0.1450980392156863, - 0.3647058824, - 0.0, - 0.0, - 0.14901960784313725, - 0.3764705882, - 0.0, - 0.0, - 0.15294117647058825, - 0.3882352941, - 0.0, - 0.0, - 0.1568627450980392, - 0.4039215686, - 0.0, - 0.0, - 0.1607843137254902, - 0.4156862745, - 0.0, - 0.0, - 0.16470588235294117, - 0.431372549, - 0.0, - 0.0, - 0.16862745098039217, - 0.4431372549, - 0.0, - 0.0, - 0.17254901960784313, - 0.4588235294, - 0.0, - 0.0, - 0.17647058823529413, - 0.4705882353, - 0.0, - 0.0, - 0.1803921568627451, - 0.4823529412, - 0.0, - 0.0, - 0.1843137254901961, - 0.4941176471, - 0.0, - 0.0, - 0.18823529411764706, - 0.5098039216, - 0.0, - 0.0, - 0.19215686274509805, - 0.5215686275, - 0.0, - 0.0, - 0.19607843137254902, - 0.5333333333, - 0.0, - 0.0, - 0.2, - 0.5450980392, - 0.0, - 0.0, - 0.20392156862745098, - 0.5568627451, - 0.0, - 0.0, - 0.20784313725490197, - 0.568627451, - 0.0, - 0.0, - 0.21176470588235294, - 0.5803921569, - 0.0, - 0.0, - 0.21568627450980393, - 0.5921568627, - 0.0, - 0.0, - 0.2196078431372549, - 0.6078431373, - 0.0, - 0.0, - 0.2235294117647059, - 0.6196078431, - 0.0, - 0.0, - 0.22745098039215686, - 0.631372549, - 0.0, - 0.0, - 0.23137254901960785, - 0.6431372549, - 0.0, - 0.0, - 0.23529411764705885, - 0.6588235294, - 0.0, - 0.0, - 0.23921568627450984, - 0.6705882353, - 0.0, - 0.0, - 0.24313725490196078, - 0.6823529412, - 0.0, - 0.0, - 0.24705882352941178, - 0.6941176471, - 0.0, - 0.0, - 0.25098039215686274, - 0.7098039216, - 0.0, - 0.0, - 0.2549019607843137, - 0.7215686275, - 0.0, - 0.0, - 0.25882352941176473, - 0.7333333333, - 0.0, - 0.0, - 0.2627450980392157, - 0.7450980392, - 0.0, - 0.0, - 0.26666666666666666, - 0.7568627451, - 0.0, - 0.0, - 0.27058823529411763, - 0.768627451, - 0.0, - 0.0, - 0.27450980392156865, - 0.7843137255, - 0.0, - 0.0, - 0.2784313725490196, - 0.7960784314, - 0.0, - 0.0, - 0.2823529411764706, - 0.8117647059, - 0.0, - 0.0, - 0.28627450980392155, - 0.8235294118, - 0.0, - 0.0, - 0.2901960784313726, - 0.8352941176, - 0.0, - 0.0, - 0.29411764705882354, - 0.8470588235, - 0.0, - 0.0, - 0.2980392156862745, - 0.862745098, - 0.0, - 0.0, - 0.30196078431372547, - 0.8745098039, - 0.0, - 0.0, - 0.3058823529411765, - 0.8862745098, - 0.0, - 0.0, - 0.30980392156862746, - 0.8980392157, - 0.0, - 0.0, - 0.3137254901960784, - 0.9137254902, - 0.0, - 0.0, - 0.3176470588235294, - 0.9254901961, - 0.0, - 0.0, - 0.3215686274509804, - 0.937254902, - 0.0, - 0.0, - 0.3254901960784314, - 0.9490196078, - 0.0, - 0.0, - 0.32941176470588235, - 0.9607843137, - 0.0, - 0.0, - 0.3333333333333333, - 0.968627451, - 0.0, - 0.0, - 0.33725490196078434, - 0.9803921569, - 0.0039215686, - 0.0, - 0.3411764705882353, - 0.9882352941, - 0.0078431373, - 0.0, - 0.34509803921568627, - 1.0, - 0.0117647059, - 0.0, - 0.34901960784313724, - 1.0, - 0.0235294118, - 0.0, - 0.35294117647058826, - 1.0, - 0.0352941176, - 0.0, - 0.3568627450980392, - 1.0, - 0.0470588235, - 0.0, - 0.3607843137254902, - 1.0, - 0.062745098, - 0.0, - 0.36470588235294116, - 1.0, - 0.0745098039, - 0.0, - 0.3686274509803922, - 1.0, - 0.0862745098, - 0.0, - 0.37254901960784315, - 1.0, - 0.0980392157, - 0.0, - 0.3764705882352941, - 1.0, - 0.1137254902, - 0.0, - 0.3803921568627451, - 1.0, - 0.1254901961, - 0.0, - 0.3843137254901961, - 1.0, - 0.137254902, - 0.0, - 0.38823529411764707, - 1.0, - 0.1490196078, - 0.0, - 0.39215686274509803, - 1.0, - 0.1647058824, - 0.0, - 0.396078431372549, - 1.0, - 0.1764705882, - 0.0, - 0.4, - 1.0, - 0.1882352941, - 0.0, - 0.403921568627451, - 1.0, - 0.2, - 0.0, - 0.40784313725490196, - 1.0, - 0.2156862745, - 0.0, - 0.4117647058823529, - 1.0, - 0.2274509804, - 0.0, - 0.41568627450980394, - 1.0, - 0.2392156863, - 0.0, - 0.4196078431372549, - 1.0, - 0.2509803922, - 0.0, - 0.4235294117647059, - 1.0, - 0.2666666667, - 0.0, - 0.42745098039215684, - 1.0, - 0.2784313725, - 0.0, - 0.43137254901960786, - 1.0, - 0.2901960784, - 0.0, - 0.43529411764705883, - 1.0, - 0.3019607843, - 0.0, - 0.4392156862745098, - 1.0, - 0.3176470588, - 0.0, - 0.44313725490196076, - 1.0, - 0.3294117647, - 0.0, - 0.4470588235294118, - 1.0, - 0.3411764706, - 0.0, - 0.45098039215686275, - 1.0, - 0.3529411765, - 0.0, - 0.4549019607843137, - 1.0, - 0.368627451, - 0.0, - 0.4588235294117647, - 1.0, - 0.3803921569, - 0.0, - 0.4627450980392157, - 1.0, - 0.3921568627, - 0.0, - 0.4666666666666667, - 1.0, - 0.4039215686, - 0.0, - 0.4705882352941177, - 1.0, - 0.4156862745, - 0.0, - 0.4745098039215686, - 1.0, - 0.4274509804, - 0.0, - 0.4784313725490197, - 1.0, - 0.4392156863, - 0.0, - 0.48235294117647065, - 1.0, - 0.4509803922, - 0.0, - 0.48627450980392156, - 1.0, - 0.4666666667, - 0.0, - 0.49019607843137253, - 1.0, - 0.4784313725, - 0.0, - 0.49411764705882355, - 1.0, - 0.4941176471, - 0.0, - 0.4980392156862745, - 1.0, - 0.5058823529, - 0.0, - 0.5019607843137255, - 1.0, - 0.5215686275, - 0.0, - 0.5058823529411764, - 1.0, - 0.5333333333, - 0.0, - 0.5098039215686274, - 1.0, - 0.5450980392, - 0.0, - 0.5137254901960784, - 1.0, - 0.5568627451, - 0.0, - 0.5176470588235295, - 1.0, - 0.568627451, - 0.0, - 0.5215686274509804, - 1.0, - 0.5803921569, - 0.0, - 0.5254901960784314, - 1.0, - 0.5921568627, - 0.0, - 0.5294117647058824, - 1.0, - 0.6039215686, - 0.0, - 0.5333333333333333, - 1.0, - 0.6196078431, - 0.0, - 0.5372549019607843, - 1.0, - 0.631372549, - 0.0, - 0.5411764705882353, - 1.0, - 0.6431372549, - 0.0, - 0.5450980392156862, - 1.0, - 0.6549019608, - 0.0, - 0.5490196078431373, - 1.0, - 0.6705882353, - 0.0, - 0.5529411764705883, - 1.0, - 0.6823529412, - 0.0, - 0.5568627450980392, - 1.0, - 0.6941176471, - 0.0, - 0.5607843137254902, - 1.0, - 0.7058823529, - 0.0, - 0.5647058823529412, - 1.0, - 0.7215686275, - 0.0, - 0.5686274509803921, - 1.0, - 0.7333333333, - 0.0, - 0.5725490196078431, - 1.0, - 0.7450980392, - 0.0, - 0.5764705882352941, - 1.0, - 0.7568627451, - 0.0, - 0.5803921568627451, - 1.0, - 0.7725490196, - 0.0, - 0.5843137254901961, - 1.0, - 0.7843137255, - 0.0, - 0.5882352941176471, - 1.0, - 0.7960784314, - 0.0, - 0.592156862745098, - 1.0, - 0.8078431373, - 0.0, - 0.596078431372549, - 1.0, - 0.8196078431, - 0.0, - 0.6, - 1.0, - 0.831372549, - 0.0, - 0.6039215686274509, - 1.0, - 0.8470588235, - 0.0, - 0.6078431372549019, - 1.0, - 0.8588235294, - 0.0, - 0.611764705882353, - 1.0, - 0.8745098039, - 0.0, - 0.615686274509804, - 1.0, - 0.8862745098, - 0.0, - 0.6196078431372549, - 1.0, - 0.8980392157, - 0.0, - 0.6235294117647059, - 1.0, - 0.9098039216, - 0.0, - 0.6274509803921569, - 1.0, - 0.9254901961, - 0.0, - 0.6313725490196078, - 1.0, - 0.937254902, - 0.0, - 0.6352941176470588, - 1.0, - 0.9490196078, - 0.0, - 0.6392156862745098, - 1.0, - 0.9607843137, - 0.0, - 0.6431372549019608, - 1.0, - 0.9764705882, - 0.0, - 0.6470588235294118, - 1.0, - 0.9803921569, - 0.0039215686, - 0.6509803921568628, - 1.0, - 0.9882352941, - 0.0117647059, - 0.6549019607843137, - 1.0, - 0.9921568627, - 0.0156862745, - 0.6588235294117647, - 1.0, - 1.0, - 0.0235294118, - 0.6627450980392157, - 1.0, - 1.0, - 0.0352941176, - 0.6666666666666666, - 1.0, - 1.0, - 0.0470588235, - 0.6705882352941176, - 1.0, - 1.0, - 0.0588235294, - 0.6745098039215687, - 1.0, - 1.0, - 0.0745098039, - 0.6784313725490196, - 1.0, - 1.0, - 0.0862745098, - 0.6823529411764706, - 1.0, - 1.0, - 0.0980392157, - 0.6862745098039216, - 1.0, - 1.0, - 0.1098039216, - 0.6901960784313725, - 1.0, - 1.0, - 0.1254901961, - 0.6941176470588235, - 1.0, - 1.0, - 0.137254902, - 0.6980392156862745, - 1.0, - 1.0, - 0.1490196078, - 0.7019607843137254, - 1.0, - 1.0, - 0.1607843137, - 0.7058823529411765, - 1.0, - 1.0, - 0.1764705882, - 0.7098039215686275, - 1.0, - 1.0, - 0.1882352941, - 0.7137254901960784, - 1.0, - 1.0, - 0.2, - 0.7176470588235294, - 1.0, - 1.0, - 0.2117647059, - 0.7215686274509804, - 1.0, - 1.0, - 0.2274509804, - 0.7254901960784313, - 1.0, - 1.0, - 0.2392156863, - 0.7294117647058823, - 1.0, - 1.0, - 0.2509803922, - 0.7333333333333333, - 1.0, - 1.0, - 0.262745098, - 0.7372549019607844, - 1.0, - 1.0, - 0.2784313725, - 0.7411764705882353, - 1.0, - 1.0, - 0.2901960784, - 0.7450980392156863, - 1.0, - 1.0, - 0.3019607843, - 0.7490196078431373, - 1.0, - 1.0, - 0.3137254902, - 0.7529411764705882, - 1.0, - 1.0, - 0.3294117647, - 0.7568627450980392, - 1.0, - 1.0, - 0.3411764706, - 0.7607843137254902, - 1.0, - 1.0, - 0.3529411765, - 0.7647058823529411, - 1.0, - 1.0, - 0.3647058824, - 0.7686274509803922, - 1.0, - 1.0, - 0.3803921569, - 0.7725490196078432, - 1.0, - 1.0, - 0.3921568627, - 0.7764705882352941, - 1.0, - 1.0, - 0.4039215686, - 0.7803921568627451, - 1.0, - 1.0, - 0.4156862745, - 0.7843137254901961, - 1.0, - 1.0, - 0.431372549, - 0.788235294117647, - 1.0, - 1.0, - 0.4431372549, - 0.792156862745098, - 1.0, - 1.0, - 0.4549019608, - 0.796078431372549, - 1.0, - 1.0, - 0.4666666667, - 0.8, - 1.0, - 1.0, - 0.4784313725, - 0.803921568627451, - 1.0, - 1.0, - 0.4901960784, - 0.807843137254902, - 1.0, - 1.0, - 0.5019607843, - 0.8117647058823529, - 1.0, - 1.0, - 0.5137254902, - 0.8156862745098039, - 1.0, - 1.0, - 0.5294117647, - 0.8196078431372549, - 1.0, - 1.0, - 0.5411764706, - 0.8235294117647058, - 1.0, - 1.0, - 0.5568627451, - 0.8274509803921568, - 1.0, - 1.0, - 0.568627451, - 0.8313725490196079, - 1.0, - 1.0, - 0.5843137255, - 0.8352941176470589, - 1.0, - 1.0, - 0.5960784314, - 0.8392156862745098, - 1.0, - 1.0, - 0.6078431373, - 0.8431372549019608, - 1.0, - 1.0, - 0.6196078431, - 0.8470588235294118, - 1.0, - 1.0, - 0.631372549, - 0.8509803921568627, - 1.0, - 1.0, - 0.6431372549, - 0.8549019607843137, - 1.0, - 1.0, - 0.6549019608, - 0.8588235294117647, - 1.0, - 1.0, - 0.6666666667, - 0.8627450980392157, - 1.0, - 1.0, - 0.6823529412, - 0.8666666666666667, - 1.0, - 1.0, - 0.6941176471, - 0.8705882352941177, - 1.0, - 1.0, - 0.7058823529, - 0.8745098039215686, - 1.0, - 1.0, - 0.7176470588, - 0.8784313725490196, - 1.0, - 1.0, - 0.7333333333, - 0.8823529411764706, - 1.0, - 1.0, - 0.7450980392, - 0.8862745098039215, - 1.0, - 1.0, - 0.7568627451, - 0.8901960784313725, - 1.0, - 1.0, - 0.768627451, - 0.8941176470588236, - 1.0, - 1.0, - 0.7843137255, - 0.8980392156862745, - 1.0, - 1.0, - 0.7960784314, - 0.9019607843137255, - 1.0, - 1.0, - 0.8078431373, - 0.9058823529411765, - 1.0, - 1.0, - 0.8196078431, - 0.9098039215686274, - 1.0, - 1.0, - 0.8352941176, - 0.9137254901960784, - 1.0, - 1.0, - 0.8470588235, - 0.9176470588235294, - 1.0, - 1.0, - 0.8588235294, - 0.9215686274509803, - 1.0, - 1.0, - 0.8705882353, - 0.9254901960784314, - 1.0, - 1.0, - 0.8823529412, - 0.9294117647058824, - 1.0, - 1.0, - 0.8941176471, - 0.9333333333333333, - 1.0, - 1.0, - 0.9098039216, - 0.9372549019607843, - 1.0, - 1.0, - 0.9215686275, - 0.9411764705882354, - 1.0, - 1.0, - 0.937254902, - 0.9450980392156864, - 1.0, - 1.0, - 0.9490196078, - 0.9490196078431372, - 1.0, - 1.0, - 0.9607843137, - 0.9529411764705882, - 1.0, - 1.0, - 0.9725490196, - 0.9568627450980394, - 1.0, - 1.0, - 0.9882352941, - 0.9607843137254903, - 1.0, - 1.0, - 0.9882352941, - 0.9647058823529413, - 1.0, - 1.0, - 0.9921568627, - 0.9686274509803922, - 1.0, - 1.0, - 0.9960784314, - 0.9725490196078431, - 1.0, - 1.0, - 1.0, - 0.9764705882352941, - 1.0, - 1.0, - 1.0, - 0.9803921568627451, - 1.0, - 1.0, - 1.0, - 0.984313725490196, - 1.0, - 1.0, - 1.0, - 0.9882352941176471, - 1.0, - 1.0, - 1.0, - 0.9921568627450981, - 1.0, - 1.0, - 1.0, - 0.996078431372549, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, + 0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0, 0.0, 0.0, 0.00784313725490196, 0.0, 0.0, 0.0, + 0.011764705882352941, 0.0, 0.0, 0.0, 0.01568627450980392, 0.0039215686, 0.0039215686, + 0.0039215686, 0.0196078431372549, 0.0039215686, 0.0039215686, 0.0039215686, + 0.023529411764705882, 0.0039215686, 0.0039215686, 0.0039215686, 0.027450980392156862, + 0.0039215686, 0.0039215686, 0.0039215686, 0.03137254901960784, 0.0039215686, 0.0039215686, + 0.0039215686, 0.03529411764705882, 0.0156862745, 0.0, 0.0, 0.0392156862745098, 0.0274509804, + 0.0, 0.0, 0.043137254901960784, 0.0392156863, 0.0, 0.0, 0.047058823529411764, 0.0509803922, + 0.0, 0.0, 0.050980392156862744, 0.062745098, 0.0, 0.0, 0.054901960784313725, 0.0784313725, + 0.0, 0.0, 0.05882352941176471, 0.0901960784, 0.0, 0.0, 0.06274509803921569, 0.1058823529, 0.0, + 0.0, 0.06666666666666667, 0.1176470588, 0.0, 0.0, 0.07058823529411765, 0.1294117647, 0.0, 0.0, + 0.07450980392156863, 0.1411764706, 0.0, 0.0, 0.0784313725490196, 0.1529411765, 0.0, 0.0, + 0.08235294117647059, 0.1647058824, 0.0, 0.0, 0.08627450980392157, 0.1764705882, 0.0, 0.0, + 0.09019607843137255, 0.1882352941, 0.0, 0.0, 0.09411764705882353, 0.2039215686, 0.0, 0.0, + 0.09803921568627451, 0.2156862745, 0.0, 0.0, 0.10196078431372549, 0.2274509804, 0.0, 0.0, + 0.10588235294117647, 0.2392156863, 0.0, 0.0, 0.10980392156862745, 0.2549019608, 0.0, 0.0, + 0.11372549019607843, 0.2666666667, 0.0, 0.0, 0.11764705882352942, 0.2784313725, 0.0, 0.0, + 0.12156862745098039, 0.2901960784, 0.0, 0.0, 0.12549019607843137, 0.3058823529, 0.0, 0.0, + 0.12941176470588237, 0.3176470588, 0.0, 0.0, 0.13333333333333333, 0.3294117647, 0.0, 0.0, + 0.13725490196078433, 0.3411764706, 0.0, 0.0, 0.1411764705882353, 0.3529411765, 0.0, 0.0, + 0.1450980392156863, 0.3647058824, 0.0, 0.0, 0.14901960784313725, 0.3764705882, 0.0, 0.0, + 0.15294117647058825, 0.3882352941, 0.0, 0.0, 0.1568627450980392, 0.4039215686, 0.0, 0.0, + 0.1607843137254902, 0.4156862745, 0.0, 0.0, 0.16470588235294117, 0.431372549, 0.0, 0.0, + 0.16862745098039217, 0.4431372549, 0.0, 0.0, 0.17254901960784313, 0.4588235294, 0.0, 0.0, + 0.17647058823529413, 0.4705882353, 0.0, 0.0, 0.1803921568627451, 0.4823529412, 0.0, 0.0, + 0.1843137254901961, 0.4941176471, 0.0, 0.0, 0.18823529411764706, 0.5098039216, 0.0, 0.0, + 0.19215686274509805, 0.5215686275, 0.0, 0.0, 0.19607843137254902, 0.5333333333, 0.0, 0.0, 0.2, + 0.5450980392, 0.0, 0.0, 0.20392156862745098, 0.5568627451, 0.0, 0.0, 0.20784313725490197, + 0.568627451, 0.0, 0.0, 0.21176470588235294, 0.5803921569, 0.0, 0.0, 0.21568627450980393, + 0.5921568627, 0.0, 0.0, 0.2196078431372549, 0.6078431373, 0.0, 0.0, 0.2235294117647059, + 0.6196078431, 0.0, 0.0, 0.22745098039215686, 0.631372549, 0.0, 0.0, 0.23137254901960785, + 0.6431372549, 0.0, 0.0, 0.23529411764705885, 0.6588235294, 0.0, 0.0, 0.23921568627450984, + 0.6705882353, 0.0, 0.0, 0.24313725490196078, 0.6823529412, 0.0, 0.0, 0.24705882352941178, + 0.6941176471, 0.0, 0.0, 0.25098039215686274, 0.7098039216, 0.0, 0.0, 0.2549019607843137, + 0.7215686275, 0.0, 0.0, 0.25882352941176473, 0.7333333333, 0.0, 0.0, 0.2627450980392157, + 0.7450980392, 0.0, 0.0, 0.26666666666666666, 0.7568627451, 0.0, 0.0, 0.27058823529411763, + 0.768627451, 0.0, 0.0, 0.27450980392156865, 0.7843137255, 0.0, 0.0, 0.2784313725490196, + 0.7960784314, 0.0, 0.0, 0.2823529411764706, 0.8117647059, 0.0, 0.0, 0.28627450980392155, + 0.8235294118, 0.0, 0.0, 0.2901960784313726, 0.8352941176, 0.0, 0.0, 0.29411764705882354, + 0.8470588235, 0.0, 0.0, 0.2980392156862745, 0.862745098, 0.0, 0.0, 0.30196078431372547, + 0.8745098039, 0.0, 0.0, 0.3058823529411765, 0.8862745098, 0.0, 0.0, 0.30980392156862746, + 0.8980392157, 0.0, 0.0, 0.3137254901960784, 0.9137254902, 0.0, 0.0, 0.3176470588235294, + 0.9254901961, 0.0, 0.0, 0.3215686274509804, 0.937254902, 0.0, 0.0, 0.3254901960784314, + 0.9490196078, 0.0, 0.0, 0.32941176470588235, 0.9607843137, 0.0, 0.0, 0.3333333333333333, + 0.968627451, 0.0, 0.0, 0.33725490196078434, 0.9803921569, 0.0039215686, 0.0, + 0.3411764705882353, 0.9882352941, 0.0078431373, 0.0, 0.34509803921568627, 1.0, 0.0117647059, + 0.0, 0.34901960784313724, 1.0, 0.0235294118, 0.0, 0.35294117647058826, 1.0, 0.0352941176, 0.0, + 0.3568627450980392, 1.0, 0.0470588235, 0.0, 0.3607843137254902, 1.0, 0.062745098, 0.0, + 0.36470588235294116, 1.0, 0.0745098039, 0.0, 0.3686274509803922, 1.0, 0.0862745098, 0.0, + 0.37254901960784315, 1.0, 0.0980392157, 0.0, 0.3764705882352941, 1.0, 0.1137254902, 0.0, + 0.3803921568627451, 1.0, 0.1254901961, 0.0, 0.3843137254901961, 1.0, 0.137254902, 0.0, + 0.38823529411764707, 1.0, 0.1490196078, 0.0, 0.39215686274509803, 1.0, 0.1647058824, 0.0, + 0.396078431372549, 1.0, 0.1764705882, 0.0, 0.4, 1.0, 0.1882352941, 0.0, 0.403921568627451, + 1.0, 0.2, 0.0, 0.40784313725490196, 1.0, 0.2156862745, 0.0, 0.4117647058823529, 1.0, + 0.2274509804, 0.0, 0.41568627450980394, 1.0, 0.2392156863, 0.0, 0.4196078431372549, 1.0, + 0.2509803922, 0.0, 0.4235294117647059, 1.0, 0.2666666667, 0.0, 0.42745098039215684, 1.0, + 0.2784313725, 0.0, 0.43137254901960786, 1.0, 0.2901960784, 0.0, 0.43529411764705883, 1.0, + 0.3019607843, 0.0, 0.4392156862745098, 1.0, 0.3176470588, 0.0, 0.44313725490196076, 1.0, + 0.3294117647, 0.0, 0.4470588235294118, 1.0, 0.3411764706, 0.0, 0.45098039215686275, 1.0, + 0.3529411765, 0.0, 0.4549019607843137, 1.0, 0.368627451, 0.0, 0.4588235294117647, 1.0, + 0.3803921569, 0.0, 0.4627450980392157, 1.0, 0.3921568627, 0.0, 0.4666666666666667, 1.0, + 0.4039215686, 0.0, 0.4705882352941177, 1.0, 0.4156862745, 0.0, 0.4745098039215686, 1.0, + 0.4274509804, 0.0, 0.4784313725490197, 1.0, 0.4392156863, 0.0, 0.48235294117647065, 1.0, + 0.4509803922, 0.0, 0.48627450980392156, 1.0, 0.4666666667, 0.0, 0.49019607843137253, 1.0, + 0.4784313725, 0.0, 0.49411764705882355, 1.0, 0.4941176471, 0.0, 0.4980392156862745, 1.0, + 0.5058823529, 0.0, 0.5019607843137255, 1.0, 0.5215686275, 0.0, 0.5058823529411764, 1.0, + 0.5333333333, 0.0, 0.5098039215686274, 1.0, 0.5450980392, 0.0, 0.5137254901960784, 1.0, + 0.5568627451, 0.0, 0.5176470588235295, 1.0, 0.568627451, 0.0, 0.5215686274509804, 1.0, + 0.5803921569, 0.0, 0.5254901960784314, 1.0, 0.5921568627, 0.0, 0.5294117647058824, 1.0, + 0.6039215686, 0.0, 0.5333333333333333, 1.0, 0.6196078431, 0.0, 0.5372549019607843, 1.0, + 0.631372549, 0.0, 0.5411764705882353, 1.0, 0.6431372549, 0.0, 0.5450980392156862, 1.0, + 0.6549019608, 0.0, 0.5490196078431373, 1.0, 0.6705882353, 0.0, 0.5529411764705883, 1.0, + 0.6823529412, 0.0, 0.5568627450980392, 1.0, 0.6941176471, 0.0, 0.5607843137254902, 1.0, + 0.7058823529, 0.0, 0.5647058823529412, 1.0, 0.7215686275, 0.0, 0.5686274509803921, 1.0, + 0.7333333333, 0.0, 0.5725490196078431, 1.0, 0.7450980392, 0.0, 0.5764705882352941, 1.0, + 0.7568627451, 0.0, 0.5803921568627451, 1.0, 0.7725490196, 0.0, 0.5843137254901961, 1.0, + 0.7843137255, 0.0, 0.5882352941176471, 1.0, 0.7960784314, 0.0, 0.592156862745098, 1.0, + 0.8078431373, 0.0, 0.596078431372549, 1.0, 0.8196078431, 0.0, 0.6, 1.0, 0.831372549, 0.0, + 0.6039215686274509, 1.0, 0.8470588235, 0.0, 0.6078431372549019, 1.0, 0.8588235294, 0.0, + 0.611764705882353, 1.0, 0.8745098039, 0.0, 0.615686274509804, 1.0, 0.8862745098, 0.0, + 0.6196078431372549, 1.0, 0.8980392157, 0.0, 0.6235294117647059, 1.0, 0.9098039216, 0.0, + 0.6274509803921569, 1.0, 0.9254901961, 0.0, 0.6313725490196078, 1.0, 0.937254902, 0.0, + 0.6352941176470588, 1.0, 0.9490196078, 0.0, 0.6392156862745098, 1.0, 0.9607843137, 0.0, + 0.6431372549019608, 1.0, 0.9764705882, 0.0, 0.6470588235294118, 1.0, 0.9803921569, + 0.0039215686, 0.6509803921568628, 1.0, 0.9882352941, 0.0117647059, 0.6549019607843137, 1.0, + 0.9921568627, 0.0156862745, 0.6588235294117647, 1.0, 1.0, 0.0235294118, 0.6627450980392157, + 1.0, 1.0, 0.0352941176, 0.6666666666666666, 1.0, 1.0, 0.0470588235, 0.6705882352941176, 1.0, + 1.0, 0.0588235294, 0.6745098039215687, 1.0, 1.0, 0.0745098039, 0.6784313725490196, 1.0, 1.0, + 0.0862745098, 0.6823529411764706, 1.0, 1.0, 0.0980392157, 0.6862745098039216, 1.0, 1.0, + 0.1098039216, 0.6901960784313725, 1.0, 1.0, 0.1254901961, 0.6941176470588235, 1.0, 1.0, + 0.137254902, 0.6980392156862745, 1.0, 1.0, 0.1490196078, 0.7019607843137254, 1.0, 1.0, + 0.1607843137, 0.7058823529411765, 1.0, 1.0, 0.1764705882, 0.7098039215686275, 1.0, 1.0, + 0.1882352941, 0.7137254901960784, 1.0, 1.0, 0.2, 0.7176470588235294, 1.0, 1.0, 0.2117647059, + 0.7215686274509804, 1.0, 1.0, 0.2274509804, 0.7254901960784313, 1.0, 1.0, 0.2392156863, + 0.7294117647058823, 1.0, 1.0, 0.2509803922, 0.7333333333333333, 1.0, 1.0, 0.262745098, + 0.7372549019607844, 1.0, 1.0, 0.2784313725, 0.7411764705882353, 1.0, 1.0, 0.2901960784, + 0.7450980392156863, 1.0, 1.0, 0.3019607843, 0.7490196078431373, 1.0, 1.0, 0.3137254902, + 0.7529411764705882, 1.0, 1.0, 0.3294117647, 0.7568627450980392, 1.0, 1.0, 0.3411764706, + 0.7607843137254902, 1.0, 1.0, 0.3529411765, 0.7647058823529411, 1.0, 1.0, 0.3647058824, + 0.7686274509803922, 1.0, 1.0, 0.3803921569, 0.7725490196078432, 1.0, 1.0, 0.3921568627, + 0.7764705882352941, 1.0, 1.0, 0.4039215686, 0.7803921568627451, 1.0, 1.0, 0.4156862745, + 0.7843137254901961, 1.0, 1.0, 0.431372549, 0.788235294117647, 1.0, 1.0, 0.4431372549, + 0.792156862745098, 1.0, 1.0, 0.4549019608, 0.796078431372549, 1.0, 1.0, 0.4666666667, 0.8, + 1.0, 1.0, 0.4784313725, 0.803921568627451, 1.0, 1.0, 0.4901960784, 0.807843137254902, 1.0, + 1.0, 0.5019607843, 0.8117647058823529, 1.0, 1.0, 0.5137254902, 0.8156862745098039, 1.0, 1.0, + 0.5294117647, 0.8196078431372549, 1.0, 1.0, 0.5411764706, 0.8235294117647058, 1.0, 1.0, + 0.5568627451, 0.8274509803921568, 1.0, 1.0, 0.568627451, 0.8313725490196079, 1.0, 1.0, + 0.5843137255, 0.8352941176470589, 1.0, 1.0, 0.5960784314, 0.8392156862745098, 1.0, 1.0, + 0.6078431373, 0.8431372549019608, 1.0, 1.0, 0.6196078431, 0.8470588235294118, 1.0, 1.0, + 0.631372549, 0.8509803921568627, 1.0, 1.0, 0.6431372549, 0.8549019607843137, 1.0, 1.0, + 0.6549019608, 0.8588235294117647, 1.0, 1.0, 0.6666666667, 0.8627450980392157, 1.0, 1.0, + 0.6823529412, 0.8666666666666667, 1.0, 1.0, 0.6941176471, 0.8705882352941177, 1.0, 1.0, + 0.7058823529, 0.8745098039215686, 1.0, 1.0, 0.7176470588, 0.8784313725490196, 1.0, 1.0, + 0.7333333333, 0.8823529411764706, 1.0, 1.0, 0.7450980392, 0.8862745098039215, 1.0, 1.0, + 0.7568627451, 0.8901960784313725, 1.0, 1.0, 0.768627451, 0.8941176470588236, 1.0, 1.0, + 0.7843137255, 0.8980392156862745, 1.0, 1.0, 0.7960784314, 0.9019607843137255, 1.0, 1.0, + 0.8078431373, 0.9058823529411765, 1.0, 1.0, 0.8196078431, 0.9098039215686274, 1.0, 1.0, + 0.8352941176, 0.9137254901960784, 1.0, 1.0, 0.8470588235, 0.9176470588235294, 1.0, 1.0, + 0.8588235294, 0.9215686274509803, 1.0, 1.0, 0.8705882353, 0.9254901960784314, 1.0, 1.0, + 0.8823529412, 0.9294117647058824, 1.0, 1.0, 0.8941176471, 0.9333333333333333, 1.0, 1.0, + 0.9098039216, 0.9372549019607843, 1.0, 1.0, 0.9215686275, 0.9411764705882354, 1.0, 1.0, + 0.937254902, 0.9450980392156864, 1.0, 1.0, 0.9490196078, 0.9490196078431372, 1.0, 1.0, + 0.9607843137, 0.9529411764705882, 1.0, 1.0, 0.9725490196, 0.9568627450980394, 1.0, 1.0, + 0.9882352941, 0.9607843137254903, 1.0, 1.0, 0.9882352941, 0.9647058823529413, 1.0, 1.0, + 0.9921568627, 0.9686274509803922, 1.0, 1.0, 0.9960784314, 0.9725490196078431, 1.0, 1.0, 1.0, + 0.9764705882352941, 1.0, 1.0, 1.0, 0.9803921568627451, 1.0, 1.0, 1.0, 0.984313725490196, 1.0, + 1.0, 1.0, 0.9882352941176471, 1.0, 1.0, 1.0, 0.9921568627450981, 1.0, 1.0, 1.0, + 0.996078431372549, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, ], }, { ColorSpace: 'RGB', Name: 's_pet', RGBPoints: [ - 0.0, - 0.0156862745, - 0.0039215686, - 0.0156862745, - 0.00392156862745098, - 0.0156862745, - 0.0039215686, - 0.0156862745, - 0.00784313725490196, - 0.0274509804, - 0.0039215686, - 0.031372549, - 0.011764705882352941, - 0.0352941176, - 0.0039215686, - 0.0509803922, - 0.01568627450980392, - 0.0392156863, - 0.0039215686, - 0.0666666667, - 0.0196078431372549, - 0.0509803922, - 0.0039215686, - 0.0823529412, - 0.023529411764705882, - 0.062745098, - 0.0039215686, - 0.0980392157, - 0.027450980392156862, - 0.0705882353, - 0.0039215686, - 0.1176470588, - 0.03137254901960784, - 0.0745098039, - 0.0039215686, - 0.1333333333, - 0.03529411764705882, - 0.0862745098, - 0.0039215686, - 0.1490196078, - 0.0392156862745098, - 0.0980392157, - 0.0039215686, - 0.1647058824, - 0.043137254901960784, - 0.1058823529, - 0.0039215686, - 0.1843137255, - 0.047058823529411764, - 0.1098039216, - 0.0039215686, - 0.2, - 0.050980392156862744, - 0.1215686275, - 0.0039215686, - 0.2156862745, - 0.054901960784313725, - 0.1333333333, - 0.0039215686, - 0.231372549, - 0.05882352941176471, - 0.137254902, - 0.0039215686, - 0.2509803922, - 0.06274509803921569, - 0.1490196078, - 0.0039215686, - 0.262745098, - 0.06666666666666667, - 0.1607843137, - 0.0039215686, - 0.2784313725, - 0.07058823529411765, - 0.168627451, - 0.0039215686, - 0.2941176471, - 0.07450980392156863, - 0.1725490196, - 0.0039215686, - 0.3137254902, - 0.0784313725490196, - 0.1843137255, - 0.0039215686, - 0.3294117647, - 0.08235294117647059, - 0.1960784314, - 0.0039215686, - 0.3450980392, - 0.08627450980392157, - 0.2039215686, - 0.0039215686, - 0.3607843137, - 0.09019607843137255, - 0.2078431373, - 0.0039215686, - 0.3803921569, - 0.09411764705882353, - 0.2196078431, - 0.0039215686, - 0.3960784314, - 0.09803921568627451, - 0.231372549, - 0.0039215686, - 0.4117647059, - 0.10196078431372549, - 0.2392156863, - 0.0039215686, - 0.4274509804, - 0.10588235294117647, - 0.2431372549, - 0.0039215686, - 0.4470588235, - 0.10980392156862745, - 0.2509803922, - 0.0039215686, - 0.462745098, - 0.11372549019607843, - 0.262745098, - 0.0039215686, - 0.4784313725, - 0.11764705882352942, - 0.2666666667, - 0.0039215686, - 0.4980392157, - 0.12156862745098039, - 0.2666666667, - 0.0039215686, - 0.4980392157, - 0.12549019607843137, - 0.262745098, - 0.0039215686, - 0.5137254902, - 0.12941176470588237, - 0.2509803922, - 0.0039215686, - 0.5294117647, - 0.13333333333333333, - 0.2431372549, - 0.0039215686, - 0.5450980392, - 0.13725490196078433, - 0.2392156863, - 0.0039215686, - 0.5607843137, - 0.1411764705882353, - 0.231372549, - 0.0039215686, - 0.5764705882, - 0.1450980392156863, - 0.2196078431, - 0.0039215686, - 0.5921568627, - 0.14901960784313725, - 0.2078431373, - 0.0039215686, - 0.6078431373, - 0.15294117647058825, - 0.2039215686, - 0.0039215686, - 0.6235294118, - 0.1568627450980392, - 0.1960784314, - 0.0039215686, - 0.6392156863, - 0.1607843137254902, - 0.1843137255, - 0.0039215686, - 0.6549019608, - 0.16470588235294117, - 0.1725490196, - 0.0039215686, - 0.6705882353, - 0.16862745098039217, - 0.168627451, - 0.0039215686, - 0.6862745098, - 0.17254901960784313, - 0.1607843137, - 0.0039215686, - 0.7019607843, - 0.17647058823529413, - 0.1490196078, - 0.0039215686, - 0.7176470588, - 0.1803921568627451, - 0.137254902, - 0.0039215686, - 0.7333333333, - 0.1843137254901961, - 0.1333333333, - 0.0039215686, - 0.7490196078, - 0.18823529411764706, - 0.1215686275, - 0.0039215686, - 0.7607843137, - 0.19215686274509805, - 0.1098039216, - 0.0039215686, - 0.7764705882, - 0.19607843137254902, - 0.1058823529, - 0.0039215686, - 0.7921568627, - 0.2, - 0.0980392157, - 0.0039215686, - 0.8078431373, - 0.20392156862745098, - 0.0862745098, - 0.0039215686, - 0.8235294118, - 0.20784313725490197, - 0.0745098039, - 0.0039215686, - 0.8392156863, - 0.21176470588235294, - 0.0705882353, - 0.0039215686, - 0.8549019608, - 0.21568627450980393, - 0.062745098, - 0.0039215686, - 0.8705882353, - 0.2196078431372549, - 0.0509803922, - 0.0039215686, - 0.8862745098, - 0.2235294117647059, - 0.0392156863, - 0.0039215686, - 0.9019607843, - 0.22745098039215686, - 0.0352941176, - 0.0039215686, - 0.9176470588, - 0.23137254901960785, - 0.0274509804, - 0.0039215686, - 0.9333333333, - 0.23529411764705885, - 0.0156862745, - 0.0039215686, - 0.9490196078, - 0.23921568627450984, - 0.0078431373, - 0.0039215686, - 0.9647058824, - 0.24313725490196078, - 0.0039215686, - 0.0039215686, - 0.9960784314, - 0.24705882352941178, - 0.0039215686, - 0.0039215686, - 0.9960784314, - 0.25098039215686274, - 0.0039215686, - 0.0196078431, - 0.9647058824, - 0.2549019607843137, - 0.0039215686, - 0.0392156863, - 0.9490196078, - 0.25882352941176473, - 0.0039215686, - 0.0549019608, - 0.9333333333, - 0.2627450980392157, - 0.0039215686, - 0.0745098039, - 0.9176470588, - 0.26666666666666666, - 0.0039215686, - 0.0901960784, - 0.9019607843, - 0.27058823529411763, - 0.0039215686, - 0.1098039216, - 0.8862745098, - 0.27450980392156865, - 0.0039215686, - 0.1254901961, - 0.8705882353, - 0.2784313725490196, - 0.0039215686, - 0.1450980392, - 0.8549019608, - 0.2823529411764706, - 0.0039215686, - 0.1607843137, - 0.8392156863, - 0.28627450980392155, - 0.0039215686, - 0.1803921569, - 0.8235294118, - 0.2901960784313726, - 0.0039215686, - 0.1960784314, - 0.8078431373, - 0.29411764705882354, - 0.0039215686, - 0.2156862745, - 0.7921568627, - 0.2980392156862745, - 0.0039215686, - 0.231372549, - 0.7764705882, - 0.30196078431372547, - 0.0039215686, - 0.2509803922, - 0.7607843137, - 0.3058823529411765, - 0.0039215686, - 0.262745098, - 0.7490196078, - 0.30980392156862746, - 0.0039215686, - 0.2823529412, - 0.7333333333, - 0.3137254901960784, - 0.0039215686, - 0.2980392157, - 0.7176470588, - 0.3176470588235294, - 0.0039215686, - 0.3176470588, - 0.7019607843, - 0.3215686274509804, - 0.0039215686, - 0.3333333333, - 0.6862745098, - 0.3254901960784314, - 0.0039215686, - 0.3529411765, - 0.6705882353, - 0.32941176470588235, - 0.0039215686, - 0.368627451, - 0.6549019608, - 0.3333333333333333, - 0.0039215686, - 0.3882352941, - 0.6392156863, - 0.33725490196078434, - 0.0039215686, - 0.4039215686, - 0.6235294118, - 0.3411764705882353, - 0.0039215686, - 0.4235294118, - 0.6078431373, - 0.34509803921568627, - 0.0039215686, - 0.4392156863, - 0.5921568627, - 0.34901960784313724, - 0.0039215686, - 0.4588235294, - 0.5764705882, - 0.35294117647058826, - 0.0039215686, - 0.4745098039, - 0.5607843137, - 0.3568627450980392, - 0.0039215686, - 0.4941176471, - 0.5450980392, - 0.3607843137254902, - 0.0039215686, - 0.5098039216, - 0.5294117647, - 0.36470588235294116, - 0.0039215686, - 0.5294117647, - 0.5137254902, - 0.3686274509803922, - 0.0039215686, - 0.5450980392, - 0.4980392157, - 0.37254901960784315, - 0.0039215686, - 0.5647058824, - 0.4784313725, - 0.3764705882352941, - 0.0039215686, - 0.5803921569, - 0.462745098, - 0.3803921568627451, - 0.0039215686, - 0.6, - 0.4470588235, - 0.3843137254901961, - 0.0039215686, - 0.6156862745, - 0.4274509804, - 0.38823529411764707, - 0.0039215686, - 0.6352941176, - 0.4117647059, - 0.39215686274509803, - 0.0039215686, - 0.6509803922, - 0.3960784314, - 0.396078431372549, - 0.0039215686, - 0.6705882353, - 0.3803921569, - 0.4, - 0.0039215686, - 0.6862745098, - 0.3607843137, - 0.403921568627451, - 0.0039215686, - 0.7058823529, - 0.3450980392, - 0.40784313725490196, - 0.0039215686, - 0.7215686275, - 0.3294117647, - 0.4117647058823529, - 0.0039215686, - 0.7411764706, - 0.3137254902, - 0.41568627450980394, - 0.0039215686, - 0.7529411765, - 0.2941176471, - 0.4196078431372549, - 0.0039215686, - 0.7960784314, - 0.2784313725, - 0.4235294117647059, - 0.0039215686, - 0.7960784314, - 0.262745098, - 0.42745098039215684, - 0.0392156863, - 0.8039215686, - 0.2509803922, - 0.43137254901960786, - 0.0745098039, - 0.8117647059, - 0.231372549, - 0.43529411764705883, - 0.1098039216, - 0.8196078431, - 0.2156862745, - 0.4392156862745098, - 0.1450980392, - 0.8274509804, - 0.2, - 0.44313725490196076, - 0.1803921569, - 0.8352941176, - 0.1843137255, - 0.4470588235294118, - 0.2156862745, - 0.8431372549, - 0.1647058824, - 0.45098039215686275, - 0.2509803922, - 0.8509803922, - 0.1490196078, - 0.4549019607843137, - 0.2823529412, - 0.8588235294, - 0.1333333333, - 0.4588235294117647, - 0.3176470588, - 0.8666666667, - 0.1176470588, - 0.4627450980392157, - 0.3529411765, - 0.8745098039, - 0.0980392157, - 0.4666666666666667, - 0.3882352941, - 0.8823529412, - 0.0823529412, - 0.4705882352941177, - 0.4235294118, - 0.8901960784, - 0.0666666667, - 0.4745098039215686, - 0.4588235294, - 0.8980392157, - 0.0509803922, - 0.4784313725490197, - 0.4941176471, - 0.9058823529, - 0.0431372549, - 0.48235294117647065, - 0.5294117647, - 0.9137254902, - 0.031372549, - 0.48627450980392156, - 0.5647058824, - 0.9215686275, - 0.0196078431, - 0.49019607843137253, - 0.6, - 0.9294117647, - 0.0078431373, - 0.49411764705882355, - 0.6352941176, - 0.937254902, - 0.0039215686, - 0.4980392156862745, - 0.6705882353, - 0.9450980392, - 0.0039215686, - 0.5019607843137255, - 0.7058823529, - 0.9490196078, - 0.0039215686, - 0.5058823529411764, - 0.7411764706, - 0.9568627451, - 0.0039215686, - 0.5098039215686274, - 0.7725490196, - 0.9607843137, - 0.0039215686, - 0.5137254901960784, - 0.8078431373, - 0.968627451, - 0.0039215686, - 0.5176470588235295, - 0.8431372549, - 0.9725490196, - 0.0039215686, - 0.5215686274509804, - 0.8784313725, - 0.9803921569, - 0.0039215686, - 0.5254901960784314, - 0.9137254902, - 0.9843137255, - 0.0039215686, - 0.5294117647058824, - 0.9490196078, - 0.9921568627, - 0.0039215686, - 0.5333333333333333, - 0.9960784314, - 0.9960784314, - 0.0039215686, - 0.5372549019607843, - 0.9960784314, - 0.9960784314, - 0.0039215686, - 0.5411764705882353, - 0.9960784314, - 0.9921568627, - 0.0039215686, - 0.5450980392156862, - 0.9960784314, - 0.9843137255, - 0.0039215686, - 0.5490196078431373, - 0.9960784314, - 0.9764705882, - 0.0039215686, - 0.5529411764705883, - 0.9960784314, - 0.968627451, - 0.0039215686, - 0.5568627450980392, - 0.9960784314, - 0.9607843137, - 0.0039215686, - 0.5607843137254902, - 0.9960784314, - 0.9529411765, - 0.0039215686, - 0.5647058823529412, - 0.9960784314, - 0.9450980392, - 0.0039215686, - 0.5686274509803921, - 0.9960784314, - 0.937254902, - 0.0039215686, - 0.5725490196078431, - 0.9960784314, - 0.9294117647, - 0.0039215686, - 0.5764705882352941, - 0.9960784314, - 0.9215686275, - 0.0039215686, - 0.5803921568627451, - 0.9960784314, - 0.9137254902, - 0.0039215686, - 0.5843137254901961, - 0.9960784314, - 0.9058823529, - 0.0039215686, - 0.5882352941176471, - 0.9960784314, - 0.8980392157, - 0.0039215686, - 0.592156862745098, - 0.9960784314, - 0.8901960784, - 0.0039215686, - 0.596078431372549, - 0.9960784314, - 0.8823529412, - 0.0039215686, - 0.6, - 0.9960784314, - 0.8745098039, - 0.0039215686, - 0.6039215686274509, - 0.9960784314, - 0.8666666667, - 0.0039215686, - 0.6078431372549019, - 0.9960784314, - 0.8588235294, - 0.0039215686, - 0.611764705882353, - 0.9960784314, - 0.8509803922, - 0.0039215686, - 0.615686274509804, - 0.9960784314, - 0.8431372549, - 0.0039215686, - 0.6196078431372549, - 0.9960784314, - 0.8352941176, - 0.0039215686, - 0.6235294117647059, - 0.9960784314, - 0.8274509804, - 0.0039215686, - 0.6274509803921569, - 0.9960784314, - 0.8196078431, - 0.0039215686, - 0.6313725490196078, - 0.9960784314, - 0.8117647059, - 0.0039215686, - 0.6352941176470588, - 0.9960784314, - 0.8039215686, - 0.0039215686, - 0.6392156862745098, - 0.9960784314, - 0.7960784314, - 0.0039215686, - 0.6431372549019608, - 0.9960784314, - 0.7882352941, - 0.0039215686, - 0.6470588235294118, - 0.9960784314, - 0.7803921569, - 0.0039215686, - 0.6509803921568628, - 0.9960784314, - 0.7725490196, - 0.0039215686, - 0.6549019607843137, - 0.9960784314, - 0.7647058824, - 0.0039215686, - 0.6588235294117647, - 0.9960784314, - 0.7568627451, - 0.0039215686, - 0.6627450980392157, - 0.9960784314, - 0.7490196078, - 0.0039215686, - 0.6666666666666666, - 0.9960784314, - 0.7450980392, - 0.0039215686, - 0.6705882352941176, - 0.9960784314, - 0.737254902, - 0.0039215686, - 0.6745098039215687, - 0.9960784314, - 0.7294117647, - 0.0039215686, - 0.6784313725490196, - 0.9960784314, - 0.7215686275, - 0.0039215686, - 0.6823529411764706, - 0.9960784314, - 0.7137254902, - 0.0039215686, - 0.6862745098039216, - 0.9960784314, - 0.7058823529, - 0.0039215686, - 0.6901960784313725, - 0.9960784314, - 0.6980392157, - 0.0039215686, - 0.6941176470588235, - 0.9960784314, - 0.6901960784, - 0.0039215686, - 0.6980392156862745, - 0.9960784314, - 0.6823529412, - 0.0039215686, - 0.7019607843137254, - 0.9960784314, - 0.6745098039, - 0.0039215686, - 0.7058823529411765, - 0.9960784314, - 0.6666666667, - 0.0039215686, - 0.7098039215686275, - 0.9960784314, - 0.6588235294, - 0.0039215686, - 0.7137254901960784, - 0.9960784314, - 0.6509803922, - 0.0039215686, - 0.7176470588235294, - 0.9960784314, - 0.6431372549, - 0.0039215686, - 0.7215686274509804, - 0.9960784314, - 0.6352941176, - 0.0039215686, - 0.7254901960784313, - 0.9960784314, - 0.6274509804, - 0.0039215686, - 0.7294117647058823, - 0.9960784314, - 0.6196078431, - 0.0039215686, - 0.7333333333333333, - 0.9960784314, - 0.6117647059, - 0.0039215686, - 0.7372549019607844, - 0.9960784314, - 0.6039215686, - 0.0039215686, - 0.7411764705882353, - 0.9960784314, - 0.5960784314, - 0.0039215686, - 0.7450980392156863, - 0.9960784314, - 0.5882352941, - 0.0039215686, - 0.7490196078431373, - 0.9960784314, - 0.5803921569, - 0.0039215686, - 0.7529411764705882, - 0.9960784314, - 0.5725490196, - 0.0039215686, - 0.7568627450980392, - 0.9960784314, - 0.5647058824, - 0.0039215686, - 0.7607843137254902, - 0.9960784314, - 0.5568627451, - 0.0039215686, - 0.7647058823529411, - 0.9960784314, - 0.5490196078, - 0.0039215686, - 0.7686274509803922, - 0.9960784314, - 0.5411764706, - 0.0039215686, - 0.7725490196078432, - 0.9960784314, - 0.5333333333, - 0.0039215686, - 0.7764705882352941, - 0.9960784314, - 0.5254901961, - 0.0039215686, - 0.7803921568627451, - 0.9960784314, - 0.5176470588, - 0.0039215686, - 0.7843137254901961, - 0.9960784314, - 0.5098039216, - 0.0039215686, - 0.788235294117647, - 0.9960784314, - 0.5019607843, - 0.0039215686, - 0.792156862745098, - 0.9960784314, - 0.4941176471, - 0.0039215686, - 0.796078431372549, - 0.9960784314, - 0.4862745098, - 0.0039215686, - 0.8, - 0.9960784314, - 0.4784313725, - 0.0039215686, - 0.803921568627451, - 0.9960784314, - 0.4705882353, - 0.0039215686, - 0.807843137254902, - 0.9960784314, - 0.462745098, - 0.0039215686, - 0.8117647058823529, - 0.9960784314, - 0.4549019608, - 0.0039215686, - 0.8156862745098039, - 0.9960784314, - 0.4470588235, - 0.0039215686, - 0.8196078431372549, - 0.9960784314, - 0.4392156863, - 0.0039215686, - 0.8235294117647058, - 0.9960784314, - 0.431372549, - 0.0039215686, - 0.8274509803921568, - 0.9960784314, - 0.4235294118, - 0.0039215686, - 0.8313725490196079, - 0.9960784314, - 0.4156862745, - 0.0039215686, - 0.8352941176470589, - 0.9960784314, - 0.4078431373, - 0.0039215686, - 0.8392156862745098, - 0.9960784314, - 0.4, - 0.0039215686, - 0.8431372549019608, - 0.9960784314, - 0.3921568627, - 0.0039215686, - 0.8470588235294118, - 0.9960784314, - 0.3843137255, - 0.0039215686, - 0.8509803921568627, - 0.9960784314, - 0.3764705882, - 0.0039215686, - 0.8549019607843137, - 0.9960784314, - 0.368627451, - 0.0039215686, - 0.8588235294117647, - 0.9960784314, - 0.3607843137, - 0.0039215686, - 0.8627450980392157, - 0.9960784314, - 0.3529411765, - 0.0039215686, - 0.8666666666666667, - 0.9960784314, - 0.3450980392, - 0.0039215686, - 0.8705882352941177, - 0.9960784314, - 0.337254902, - 0.0039215686, - 0.8745098039215686, - 0.9960784314, - 0.3294117647, - 0.0039215686, - 0.8784313725490196, - 0.9960784314, - 0.3215686275, - 0.0039215686, - 0.8823529411764706, - 0.9960784314, - 0.3137254902, - 0.0039215686, - 0.8862745098039215, - 0.9960784314, - 0.3058823529, - 0.0039215686, - 0.8901960784313725, - 0.9960784314, - 0.2980392157, - 0.0039215686, - 0.8941176470588236, - 0.9960784314, - 0.2901960784, - 0.0039215686, - 0.8980392156862745, - 0.9960784314, - 0.2823529412, - 0.0039215686, - 0.9019607843137255, - 0.9960784314, - 0.2705882353, - 0.0039215686, - 0.9058823529411765, - 0.9960784314, - 0.2588235294, - 0.0039215686, - 0.9098039215686274, - 0.9960784314, - 0.2509803922, - 0.0039215686, - 0.9137254901960784, - 0.9960784314, - 0.2431372549, - 0.0039215686, - 0.9176470588235294, - 0.9960784314, - 0.231372549, - 0.0039215686, - 0.9215686274509803, - 0.9960784314, - 0.2196078431, - 0.0039215686, - 0.9254901960784314, - 0.9960784314, - 0.2117647059, - 0.0039215686, - 0.9294117647058824, - 0.9960784314, - 0.2, - 0.0039215686, - 0.9333333333333333, - 0.9960784314, - 0.1882352941, - 0.0039215686, - 0.9372549019607843, - 0.9960784314, - 0.1764705882, - 0.0039215686, - 0.9411764705882354, - 0.9960784314, - 0.168627451, - 0.0039215686, - 0.9450980392156864, - 0.9960784314, - 0.1568627451, - 0.0039215686, - 0.9490196078431372, - 0.9960784314, - 0.1450980392, - 0.0039215686, - 0.9529411764705882, - 0.9960784314, - 0.1333333333, - 0.0039215686, - 0.9568627450980394, - 0.9960784314, - 0.1254901961, - 0.0039215686, - 0.9607843137254903, - 0.9960784314, - 0.1137254902, - 0.0039215686, - 0.9647058823529413, - 0.9960784314, - 0.1019607843, - 0.0039215686, - 0.9686274509803922, - 0.9960784314, - 0.0901960784, - 0.0039215686, - 0.9725490196078431, - 0.9960784314, - 0.0823529412, - 0.0039215686, - 0.9764705882352941, - 0.9960784314, - 0.0705882353, - 0.0039215686, - 0.9803921568627451, - 0.9960784314, - 0.0588235294, - 0.0039215686, - 0.984313725490196, - 0.9960784314, - 0.0470588235, - 0.0039215686, - 0.9882352941176471, - 0.9960784314, - 0.0392156863, - 0.0039215686, - 0.9921568627450981, - 0.9960784314, - 0.0274509804, - 0.0039215686, - 0.996078431372549, - 0.9960784314, - 0.0156862745, - 0.0039215686, - 1.0, - 0.9960784314, - 0.0156862745, - 0.0039215686, + 0.0, 0.0156862745, 0.0039215686, 0.0156862745, 0.00392156862745098, 0.0156862745, + 0.0039215686, 0.0156862745, 0.00784313725490196, 0.0274509804, 0.0039215686, 0.031372549, + 0.011764705882352941, 0.0352941176, 0.0039215686, 0.0509803922, 0.01568627450980392, + 0.0392156863, 0.0039215686, 0.0666666667, 0.0196078431372549, 0.0509803922, 0.0039215686, + 0.0823529412, 0.023529411764705882, 0.062745098, 0.0039215686, 0.0980392157, + 0.027450980392156862, 0.0705882353, 0.0039215686, 0.1176470588, 0.03137254901960784, + 0.0745098039, 0.0039215686, 0.1333333333, 0.03529411764705882, 0.0862745098, 0.0039215686, + 0.1490196078, 0.0392156862745098, 0.0980392157, 0.0039215686, 0.1647058824, + 0.043137254901960784, 0.1058823529, 0.0039215686, 0.1843137255, 0.047058823529411764, + 0.1098039216, 0.0039215686, 0.2, 0.050980392156862744, 0.1215686275, 0.0039215686, + 0.2156862745, 0.054901960784313725, 0.1333333333, 0.0039215686, 0.231372549, + 0.05882352941176471, 0.137254902, 0.0039215686, 0.2509803922, 0.06274509803921569, + 0.1490196078, 0.0039215686, 0.262745098, 0.06666666666666667, 0.1607843137, 0.0039215686, + 0.2784313725, 0.07058823529411765, 0.168627451, 0.0039215686, 0.2941176471, + 0.07450980392156863, 0.1725490196, 0.0039215686, 0.3137254902, 0.0784313725490196, + 0.1843137255, 0.0039215686, 0.3294117647, 0.08235294117647059, 0.1960784314, 0.0039215686, + 0.3450980392, 0.08627450980392157, 0.2039215686, 0.0039215686, 0.3607843137, + 0.09019607843137255, 0.2078431373, 0.0039215686, 0.3803921569, 0.09411764705882353, + 0.2196078431, 0.0039215686, 0.3960784314, 0.09803921568627451, 0.231372549, 0.0039215686, + 0.4117647059, 0.10196078431372549, 0.2392156863, 0.0039215686, 0.4274509804, + 0.10588235294117647, 0.2431372549, 0.0039215686, 0.4470588235, 0.10980392156862745, + 0.2509803922, 0.0039215686, 0.462745098, 0.11372549019607843, 0.262745098, 0.0039215686, + 0.4784313725, 0.11764705882352942, 0.2666666667, 0.0039215686, 0.4980392157, + 0.12156862745098039, 0.2666666667, 0.0039215686, 0.4980392157, 0.12549019607843137, + 0.262745098, 0.0039215686, 0.5137254902, 0.12941176470588237, 0.2509803922, 0.0039215686, + 0.5294117647, 0.13333333333333333, 0.2431372549, 0.0039215686, 0.5450980392, + 0.13725490196078433, 0.2392156863, 0.0039215686, 0.5607843137, 0.1411764705882353, + 0.231372549, 0.0039215686, 0.5764705882, 0.1450980392156863, 0.2196078431, 0.0039215686, + 0.5921568627, 0.14901960784313725, 0.2078431373, 0.0039215686, 0.6078431373, + 0.15294117647058825, 0.2039215686, 0.0039215686, 0.6235294118, 0.1568627450980392, + 0.1960784314, 0.0039215686, 0.6392156863, 0.1607843137254902, 0.1843137255, 0.0039215686, + 0.6549019608, 0.16470588235294117, 0.1725490196, 0.0039215686, 0.6705882353, + 0.16862745098039217, 0.168627451, 0.0039215686, 0.6862745098, 0.17254901960784313, + 0.1607843137, 0.0039215686, 0.7019607843, 0.17647058823529413, 0.1490196078, 0.0039215686, + 0.7176470588, 0.1803921568627451, 0.137254902, 0.0039215686, 0.7333333333, 0.1843137254901961, + 0.1333333333, 0.0039215686, 0.7490196078, 0.18823529411764706, 0.1215686275, 0.0039215686, + 0.7607843137, 0.19215686274509805, 0.1098039216, 0.0039215686, 0.7764705882, + 0.19607843137254902, 0.1058823529, 0.0039215686, 0.7921568627, 0.2, 0.0980392157, + 0.0039215686, 0.8078431373, 0.20392156862745098, 0.0862745098, 0.0039215686, 0.8235294118, + 0.20784313725490197, 0.0745098039, 0.0039215686, 0.8392156863, 0.21176470588235294, + 0.0705882353, 0.0039215686, 0.8549019608, 0.21568627450980393, 0.062745098, 0.0039215686, + 0.8705882353, 0.2196078431372549, 0.0509803922, 0.0039215686, 0.8862745098, + 0.2235294117647059, 0.0392156863, 0.0039215686, 0.9019607843, 0.22745098039215686, + 0.0352941176, 0.0039215686, 0.9176470588, 0.23137254901960785, 0.0274509804, 0.0039215686, + 0.9333333333, 0.23529411764705885, 0.0156862745, 0.0039215686, 0.9490196078, + 0.23921568627450984, 0.0078431373, 0.0039215686, 0.9647058824, 0.24313725490196078, + 0.0039215686, 0.0039215686, 0.9960784314, 0.24705882352941178, 0.0039215686, 0.0039215686, + 0.9960784314, 0.25098039215686274, 0.0039215686, 0.0196078431, 0.9647058824, + 0.2549019607843137, 0.0039215686, 0.0392156863, 0.9490196078, 0.25882352941176473, + 0.0039215686, 0.0549019608, 0.9333333333, 0.2627450980392157, 0.0039215686, 0.0745098039, + 0.9176470588, 0.26666666666666666, 0.0039215686, 0.0901960784, 0.9019607843, + 0.27058823529411763, 0.0039215686, 0.1098039216, 0.8862745098, 0.27450980392156865, + 0.0039215686, 0.1254901961, 0.8705882353, 0.2784313725490196, 0.0039215686, 0.1450980392, + 0.8549019608, 0.2823529411764706, 0.0039215686, 0.1607843137, 0.8392156863, + 0.28627450980392155, 0.0039215686, 0.1803921569, 0.8235294118, 0.2901960784313726, + 0.0039215686, 0.1960784314, 0.8078431373, 0.29411764705882354, 0.0039215686, 0.2156862745, + 0.7921568627, 0.2980392156862745, 0.0039215686, 0.231372549, 0.7764705882, + 0.30196078431372547, 0.0039215686, 0.2509803922, 0.7607843137, 0.3058823529411765, + 0.0039215686, 0.262745098, 0.7490196078, 0.30980392156862746, 0.0039215686, 0.2823529412, + 0.7333333333, 0.3137254901960784, 0.0039215686, 0.2980392157, 0.7176470588, + 0.3176470588235294, 0.0039215686, 0.3176470588, 0.7019607843, 0.3215686274509804, + 0.0039215686, 0.3333333333, 0.6862745098, 0.3254901960784314, 0.0039215686, 0.3529411765, + 0.6705882353, 0.32941176470588235, 0.0039215686, 0.368627451, 0.6549019608, + 0.3333333333333333, 0.0039215686, 0.3882352941, 0.6392156863, 0.33725490196078434, + 0.0039215686, 0.4039215686, 0.6235294118, 0.3411764705882353, 0.0039215686, 0.4235294118, + 0.6078431373, 0.34509803921568627, 0.0039215686, 0.4392156863, 0.5921568627, + 0.34901960784313724, 0.0039215686, 0.4588235294, 0.5764705882, 0.35294117647058826, + 0.0039215686, 0.4745098039, 0.5607843137, 0.3568627450980392, 0.0039215686, 0.4941176471, + 0.5450980392, 0.3607843137254902, 0.0039215686, 0.5098039216, 0.5294117647, + 0.36470588235294116, 0.0039215686, 0.5294117647, 0.5137254902, 0.3686274509803922, + 0.0039215686, 0.5450980392, 0.4980392157, 0.37254901960784315, 0.0039215686, 0.5647058824, + 0.4784313725, 0.3764705882352941, 0.0039215686, 0.5803921569, 0.462745098, 0.3803921568627451, + 0.0039215686, 0.6, 0.4470588235, 0.3843137254901961, 0.0039215686, 0.6156862745, 0.4274509804, + 0.38823529411764707, 0.0039215686, 0.6352941176, 0.4117647059, 0.39215686274509803, + 0.0039215686, 0.6509803922, 0.3960784314, 0.396078431372549, 0.0039215686, 0.6705882353, + 0.3803921569, 0.4, 0.0039215686, 0.6862745098, 0.3607843137, 0.403921568627451, 0.0039215686, + 0.7058823529, 0.3450980392, 0.40784313725490196, 0.0039215686, 0.7215686275, 0.3294117647, + 0.4117647058823529, 0.0039215686, 0.7411764706, 0.3137254902, 0.41568627450980394, + 0.0039215686, 0.7529411765, 0.2941176471, 0.4196078431372549, 0.0039215686, 0.7960784314, + 0.2784313725, 0.4235294117647059, 0.0039215686, 0.7960784314, 0.262745098, + 0.42745098039215684, 0.0392156863, 0.8039215686, 0.2509803922, 0.43137254901960786, + 0.0745098039, 0.8117647059, 0.231372549, 0.43529411764705883, 0.1098039216, 0.8196078431, + 0.2156862745, 0.4392156862745098, 0.1450980392, 0.8274509804, 0.2, 0.44313725490196076, + 0.1803921569, 0.8352941176, 0.1843137255, 0.4470588235294118, 0.2156862745, 0.8431372549, + 0.1647058824, 0.45098039215686275, 0.2509803922, 0.8509803922, 0.1490196078, + 0.4549019607843137, 0.2823529412, 0.8588235294, 0.1333333333, 0.4588235294117647, + 0.3176470588, 0.8666666667, 0.1176470588, 0.4627450980392157, 0.3529411765, 0.8745098039, + 0.0980392157, 0.4666666666666667, 0.3882352941, 0.8823529412, 0.0823529412, + 0.4705882352941177, 0.4235294118, 0.8901960784, 0.0666666667, 0.4745098039215686, + 0.4588235294, 0.8980392157, 0.0509803922, 0.4784313725490197, 0.4941176471, 0.9058823529, + 0.0431372549, 0.48235294117647065, 0.5294117647, 0.9137254902, 0.031372549, + 0.48627450980392156, 0.5647058824, 0.9215686275, 0.0196078431, 0.49019607843137253, 0.6, + 0.9294117647, 0.0078431373, 0.49411764705882355, 0.6352941176, 0.937254902, 0.0039215686, + 0.4980392156862745, 0.6705882353, 0.9450980392, 0.0039215686, 0.5019607843137255, + 0.7058823529, 0.9490196078, 0.0039215686, 0.5058823529411764, 0.7411764706, 0.9568627451, + 0.0039215686, 0.5098039215686274, 0.7725490196, 0.9607843137, 0.0039215686, + 0.5137254901960784, 0.8078431373, 0.968627451, 0.0039215686, 0.5176470588235295, 0.8431372549, + 0.9725490196, 0.0039215686, 0.5215686274509804, 0.8784313725, 0.9803921569, 0.0039215686, + 0.5254901960784314, 0.9137254902, 0.9843137255, 0.0039215686, 0.5294117647058824, + 0.9490196078, 0.9921568627, 0.0039215686, 0.5333333333333333, 0.9960784314, 0.9960784314, + 0.0039215686, 0.5372549019607843, 0.9960784314, 0.9960784314, 0.0039215686, + 0.5411764705882353, 0.9960784314, 0.9921568627, 0.0039215686, 0.5450980392156862, + 0.9960784314, 0.9843137255, 0.0039215686, 0.5490196078431373, 0.9960784314, 0.9764705882, + 0.0039215686, 0.5529411764705883, 0.9960784314, 0.968627451, 0.0039215686, 0.5568627450980392, + 0.9960784314, 0.9607843137, 0.0039215686, 0.5607843137254902, 0.9960784314, 0.9529411765, + 0.0039215686, 0.5647058823529412, 0.9960784314, 0.9450980392, 0.0039215686, + 0.5686274509803921, 0.9960784314, 0.937254902, 0.0039215686, 0.5725490196078431, 0.9960784314, + 0.9294117647, 0.0039215686, 0.5764705882352941, 0.9960784314, 0.9215686275, 0.0039215686, + 0.5803921568627451, 0.9960784314, 0.9137254902, 0.0039215686, 0.5843137254901961, + 0.9960784314, 0.9058823529, 0.0039215686, 0.5882352941176471, 0.9960784314, 0.8980392157, + 0.0039215686, 0.592156862745098, 0.9960784314, 0.8901960784, 0.0039215686, 0.596078431372549, + 0.9960784314, 0.8823529412, 0.0039215686, 0.6, 0.9960784314, 0.8745098039, 0.0039215686, + 0.6039215686274509, 0.9960784314, 0.8666666667, 0.0039215686, 0.6078431372549019, + 0.9960784314, 0.8588235294, 0.0039215686, 0.611764705882353, 0.9960784314, 0.8509803922, + 0.0039215686, 0.615686274509804, 0.9960784314, 0.8431372549, 0.0039215686, 0.6196078431372549, + 0.9960784314, 0.8352941176, 0.0039215686, 0.6235294117647059, 0.9960784314, 0.8274509804, + 0.0039215686, 0.6274509803921569, 0.9960784314, 0.8196078431, 0.0039215686, + 0.6313725490196078, 0.9960784314, 0.8117647059, 0.0039215686, 0.6352941176470588, + 0.9960784314, 0.8039215686, 0.0039215686, 0.6392156862745098, 0.9960784314, 0.7960784314, + 0.0039215686, 0.6431372549019608, 0.9960784314, 0.7882352941, 0.0039215686, + 0.6470588235294118, 0.9960784314, 0.7803921569, 0.0039215686, 0.6509803921568628, + 0.9960784314, 0.7725490196, 0.0039215686, 0.6549019607843137, 0.9960784314, 0.7647058824, + 0.0039215686, 0.6588235294117647, 0.9960784314, 0.7568627451, 0.0039215686, + 0.6627450980392157, 0.9960784314, 0.7490196078, 0.0039215686, 0.6666666666666666, + 0.9960784314, 0.7450980392, 0.0039215686, 0.6705882352941176, 0.9960784314, 0.737254902, + 0.0039215686, 0.6745098039215687, 0.9960784314, 0.7294117647, 0.0039215686, + 0.6784313725490196, 0.9960784314, 0.7215686275, 0.0039215686, 0.6823529411764706, + 0.9960784314, 0.7137254902, 0.0039215686, 0.6862745098039216, 0.9960784314, 0.7058823529, + 0.0039215686, 0.6901960784313725, 0.9960784314, 0.6980392157, 0.0039215686, + 0.6941176470588235, 0.9960784314, 0.6901960784, 0.0039215686, 0.6980392156862745, + 0.9960784314, 0.6823529412, 0.0039215686, 0.7019607843137254, 0.9960784314, 0.6745098039, + 0.0039215686, 0.7058823529411765, 0.9960784314, 0.6666666667, 0.0039215686, + 0.7098039215686275, 0.9960784314, 0.6588235294, 0.0039215686, 0.7137254901960784, + 0.9960784314, 0.6509803922, 0.0039215686, 0.7176470588235294, 0.9960784314, 0.6431372549, + 0.0039215686, 0.7215686274509804, 0.9960784314, 0.6352941176, 0.0039215686, + 0.7254901960784313, 0.9960784314, 0.6274509804, 0.0039215686, 0.7294117647058823, + 0.9960784314, 0.6196078431, 0.0039215686, 0.7333333333333333, 0.9960784314, 0.6117647059, + 0.0039215686, 0.7372549019607844, 0.9960784314, 0.6039215686, 0.0039215686, + 0.7411764705882353, 0.9960784314, 0.5960784314, 0.0039215686, 0.7450980392156863, + 0.9960784314, 0.5882352941, 0.0039215686, 0.7490196078431373, 0.9960784314, 0.5803921569, + 0.0039215686, 0.7529411764705882, 0.9960784314, 0.5725490196, 0.0039215686, + 0.7568627450980392, 0.9960784314, 0.5647058824, 0.0039215686, 0.7607843137254902, + 0.9960784314, 0.5568627451, 0.0039215686, 0.7647058823529411, 0.9960784314, 0.5490196078, + 0.0039215686, 0.7686274509803922, 0.9960784314, 0.5411764706, 0.0039215686, + 0.7725490196078432, 0.9960784314, 0.5333333333, 0.0039215686, 0.7764705882352941, + 0.9960784314, 0.5254901961, 0.0039215686, 0.7803921568627451, 0.9960784314, 0.5176470588, + 0.0039215686, 0.7843137254901961, 0.9960784314, 0.5098039216, 0.0039215686, 0.788235294117647, + 0.9960784314, 0.5019607843, 0.0039215686, 0.792156862745098, 0.9960784314, 0.4941176471, + 0.0039215686, 0.796078431372549, 0.9960784314, 0.4862745098, 0.0039215686, 0.8, 0.9960784314, + 0.4784313725, 0.0039215686, 0.803921568627451, 0.9960784314, 0.4705882353, 0.0039215686, + 0.807843137254902, 0.9960784314, 0.462745098, 0.0039215686, 0.8117647058823529, 0.9960784314, + 0.4549019608, 0.0039215686, 0.8156862745098039, 0.9960784314, 0.4470588235, 0.0039215686, + 0.8196078431372549, 0.9960784314, 0.4392156863, 0.0039215686, 0.8235294117647058, + 0.9960784314, 0.431372549, 0.0039215686, 0.8274509803921568, 0.9960784314, 0.4235294118, + 0.0039215686, 0.8313725490196079, 0.9960784314, 0.4156862745, 0.0039215686, + 0.8352941176470589, 0.9960784314, 0.4078431373, 0.0039215686, 0.8392156862745098, + 0.9960784314, 0.4, 0.0039215686, 0.8431372549019608, 0.9960784314, 0.3921568627, 0.0039215686, + 0.8470588235294118, 0.9960784314, 0.3843137255, 0.0039215686, 0.8509803921568627, + 0.9960784314, 0.3764705882, 0.0039215686, 0.8549019607843137, 0.9960784314, 0.368627451, + 0.0039215686, 0.8588235294117647, 0.9960784314, 0.3607843137, 0.0039215686, + 0.8627450980392157, 0.9960784314, 0.3529411765, 0.0039215686, 0.8666666666666667, + 0.9960784314, 0.3450980392, 0.0039215686, 0.8705882352941177, 0.9960784314, 0.337254902, + 0.0039215686, 0.8745098039215686, 0.9960784314, 0.3294117647, 0.0039215686, + 0.8784313725490196, 0.9960784314, 0.3215686275, 0.0039215686, 0.8823529411764706, + 0.9960784314, 0.3137254902, 0.0039215686, 0.8862745098039215, 0.9960784314, 0.3058823529, + 0.0039215686, 0.8901960784313725, 0.9960784314, 0.2980392157, 0.0039215686, + 0.8941176470588236, 0.9960784314, 0.2901960784, 0.0039215686, 0.8980392156862745, + 0.9960784314, 0.2823529412, 0.0039215686, 0.9019607843137255, 0.9960784314, 0.2705882353, + 0.0039215686, 0.9058823529411765, 0.9960784314, 0.2588235294, 0.0039215686, + 0.9098039215686274, 0.9960784314, 0.2509803922, 0.0039215686, 0.9137254901960784, + 0.9960784314, 0.2431372549, 0.0039215686, 0.9176470588235294, 0.9960784314, 0.231372549, + 0.0039215686, 0.9215686274509803, 0.9960784314, 0.2196078431, 0.0039215686, + 0.9254901960784314, 0.9960784314, 0.2117647059, 0.0039215686, 0.9294117647058824, + 0.9960784314, 0.2, 0.0039215686, 0.9333333333333333, 0.9960784314, 0.1882352941, 0.0039215686, + 0.9372549019607843, 0.9960784314, 0.1764705882, 0.0039215686, 0.9411764705882354, + 0.9960784314, 0.168627451, 0.0039215686, 0.9450980392156864, 0.9960784314, 0.1568627451, + 0.0039215686, 0.9490196078431372, 0.9960784314, 0.1450980392, 0.0039215686, + 0.9529411764705882, 0.9960784314, 0.1333333333, 0.0039215686, 0.9568627450980394, + 0.9960784314, 0.1254901961, 0.0039215686, 0.9607843137254903, 0.9960784314, 0.1137254902, + 0.0039215686, 0.9647058823529413, 0.9960784314, 0.1019607843, 0.0039215686, + 0.9686274509803922, 0.9960784314, 0.0901960784, 0.0039215686, 0.9725490196078431, + 0.9960784314, 0.0823529412, 0.0039215686, 0.9764705882352941, 0.9960784314, 0.0705882353, + 0.0039215686, 0.9803921568627451, 0.9960784314, 0.0588235294, 0.0039215686, 0.984313725490196, + 0.9960784314, 0.0470588235, 0.0039215686, 0.9882352941176471, 0.9960784314, 0.0392156863, + 0.0039215686, 0.9921568627450981, 0.9960784314, 0.0274509804, 0.0039215686, 0.996078431372549, + 0.9960784314, 0.0156862745, 0.0039215686, 1.0, 0.9960784314, 0.0156862745, 0.0039215686, ], }, { ColorSpace: 'RGB', Name: 'perfusion', RGBPoints: [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.00392156862745098, - 0.0078431373, - 0.0235294118, - 0.0235294118, - 0.00784313725490196, - 0.0078431373, - 0.031372549, - 0.0470588235, - 0.011764705882352941, - 0.0078431373, - 0.0392156863, - 0.062745098, - 0.01568627450980392, - 0.0078431373, - 0.0470588235, - 0.0862745098, - 0.0196078431372549, - 0.0078431373, - 0.0549019608, - 0.1019607843, - 0.023529411764705882, - 0.0078431373, - 0.0549019608, - 0.1254901961, - 0.027450980392156862, - 0.0078431373, - 0.062745098, - 0.1411764706, - 0.03137254901960784, - 0.0078431373, - 0.0705882353, - 0.1647058824, - 0.03529411764705882, - 0.0078431373, - 0.0784313725, - 0.1803921569, - 0.0392156862745098, - 0.0078431373, - 0.0862745098, - 0.2039215686, - 0.043137254901960784, - 0.0078431373, - 0.0862745098, - 0.2196078431, - 0.047058823529411764, - 0.0078431373, - 0.0941176471, - 0.2431372549, - 0.050980392156862744, - 0.0078431373, - 0.1019607843, - 0.2666666667, - 0.054901960784313725, - 0.0078431373, - 0.1098039216, - 0.2823529412, - 0.05882352941176471, - 0.0078431373, - 0.1176470588, - 0.3058823529, - 0.06274509803921569, - 0.0078431373, - 0.1176470588, - 0.3215686275, - 0.06666666666666667, - 0.0078431373, - 0.1254901961, - 0.3450980392, - 0.07058823529411765, - 0.0078431373, - 0.1333333333, - 0.3607843137, - 0.07450980392156863, - 0.0078431373, - 0.1411764706, - 0.3843137255, - 0.0784313725490196, - 0.0078431373, - 0.1490196078, - 0.4, - 0.08235294117647059, - 0.0078431373, - 0.1490196078, - 0.4235294118, - 0.08627450980392157, - 0.0078431373, - 0.1568627451, - 0.4392156863, - 0.09019607843137255, - 0.0078431373, - 0.1647058824, - 0.462745098, - 0.09411764705882353, - 0.0078431373, - 0.1725490196, - 0.4784313725, - 0.09803921568627451, - 0.0078431373, - 0.1803921569, - 0.5019607843, - 0.10196078431372549, - 0.0078431373, - 0.1803921569, - 0.5254901961, - 0.10588235294117647, - 0.0078431373, - 0.1882352941, - 0.5411764706, - 0.10980392156862745, - 0.0078431373, - 0.1960784314, - 0.5647058824, - 0.11372549019607843, - 0.0078431373, - 0.2039215686, - 0.5803921569, - 0.11764705882352942, - 0.0078431373, - 0.2117647059, - 0.6039215686, - 0.12156862745098039, - 0.0078431373, - 0.2117647059, - 0.6196078431, - 0.12549019607843137, - 0.0078431373, - 0.2196078431, - 0.6431372549, - 0.12941176470588237, - 0.0078431373, - 0.2274509804, - 0.6588235294, - 0.13333333333333333, - 0.0078431373, - 0.2352941176, - 0.6823529412, - 0.13725490196078433, - 0.0078431373, - 0.2431372549, - 0.6980392157, - 0.1411764705882353, - 0.0078431373, - 0.2431372549, - 0.7215686275, - 0.1450980392156863, - 0.0078431373, - 0.2509803922, - 0.737254902, - 0.14901960784313725, - 0.0078431373, - 0.2588235294, - 0.7607843137, - 0.15294117647058825, - 0.0078431373, - 0.2666666667, - 0.7843137255, - 0.1568627450980392, - 0.0078431373, - 0.2745098039, - 0.8, - 0.1607843137254902, - 0.0078431373, - 0.2745098039, - 0.8235294118, - 0.16470588235294117, - 0.0078431373, - 0.2823529412, - 0.8392156863, - 0.16862745098039217, - 0.0078431373, - 0.2901960784, - 0.862745098, - 0.17254901960784313, - 0.0078431373, - 0.2980392157, - 0.8784313725, - 0.17647058823529413, - 0.0078431373, - 0.3058823529, - 0.9019607843, - 0.1803921568627451, - 0.0078431373, - 0.3058823529, - 0.9176470588, - 0.1843137254901961, - 0.0078431373, - 0.2980392157, - 0.9411764706, - 0.18823529411764706, - 0.0078431373, - 0.3058823529, - 0.9568627451, - 0.19215686274509805, - 0.0078431373, - 0.2980392157, - 0.9803921569, - 0.19607843137254902, - 0.0078431373, - 0.2980392157, - 0.9882352941, - 0.2, - 0.0078431373, - 0.2901960784, - 0.9803921569, - 0.20392156862745098, - 0.0078431373, - 0.2901960784, - 0.9647058824, - 0.20784313725490197, - 0.0078431373, - 0.2823529412, - 0.9568627451, - 0.21176470588235294, - 0.0078431373, - 0.2823529412, - 0.9411764706, - 0.21568627450980393, - 0.0078431373, - 0.2745098039, - 0.9333333333, - 0.2196078431372549, - 0.0078431373, - 0.2666666667, - 0.9176470588, - 0.2235294117647059, - 0.0078431373, - 0.2666666667, - 0.9098039216, - 0.22745098039215686, - 0.0078431373, - 0.2588235294, - 0.9019607843, - 0.23137254901960785, - 0.0078431373, - 0.2588235294, - 0.8862745098, - 0.23529411764705885, - 0.0078431373, - 0.2509803922, - 0.8784313725, - 0.23921568627450984, - 0.0078431373, - 0.2509803922, - 0.862745098, - 0.24313725490196078, - 0.0078431373, - 0.2431372549, - 0.8549019608, - 0.24705882352941178, - 0.0078431373, - 0.2352941176, - 0.8392156863, - 0.25098039215686274, - 0.0078431373, - 0.2352941176, - 0.831372549, - 0.2549019607843137, - 0.0078431373, - 0.2274509804, - 0.8235294118, - 0.25882352941176473, - 0.0078431373, - 0.2274509804, - 0.8078431373, - 0.2627450980392157, - 0.0078431373, - 0.2196078431, - 0.8, - 0.26666666666666666, - 0.0078431373, - 0.2196078431, - 0.7843137255, - 0.27058823529411763, - 0.0078431373, - 0.2117647059, - 0.7764705882, - 0.27450980392156865, - 0.0078431373, - 0.2039215686, - 0.7607843137, - 0.2784313725490196, - 0.0078431373, - 0.2039215686, - 0.7529411765, - 0.2823529411764706, - 0.0078431373, - 0.1960784314, - 0.7450980392, - 0.28627450980392155, - 0.0078431373, - 0.1960784314, - 0.7294117647, - 0.2901960784313726, - 0.0078431373, - 0.1882352941, - 0.7215686275, - 0.29411764705882354, - 0.0078431373, - 0.1882352941, - 0.7058823529, - 0.2980392156862745, - 0.0078431373, - 0.1803921569, - 0.6980392157, - 0.30196078431372547, - 0.0078431373, - 0.1803921569, - 0.6823529412, - 0.3058823529411765, - 0.0078431373, - 0.1725490196, - 0.6745098039, - 0.30980392156862746, - 0.0078431373, - 0.1647058824, - 0.6666666667, - 0.3137254901960784, - 0.0078431373, - 0.1647058824, - 0.6509803922, - 0.3176470588235294, - 0.0078431373, - 0.1568627451, - 0.6431372549, - 0.3215686274509804, - 0.0078431373, - 0.1568627451, - 0.6274509804, - 0.3254901960784314, - 0.0078431373, - 0.1490196078, - 0.6196078431, - 0.32941176470588235, - 0.0078431373, - 0.1490196078, - 0.6039215686, - 0.3333333333333333, - 0.0078431373, - 0.1411764706, - 0.5960784314, - 0.33725490196078434, - 0.0078431373, - 0.1333333333, - 0.5882352941, - 0.3411764705882353, - 0.0078431373, - 0.1333333333, - 0.5725490196, - 0.34509803921568627, - 0.0078431373, - 0.1254901961, - 0.5647058824, - 0.34901960784313724, - 0.0078431373, - 0.1254901961, - 0.5490196078, - 0.35294117647058826, - 0.0078431373, - 0.1176470588, - 0.5411764706, - 0.3568627450980392, - 0.0078431373, - 0.1176470588, - 0.5254901961, - 0.3607843137254902, - 0.0078431373, - 0.1098039216, - 0.5176470588, - 0.36470588235294116, - 0.0078431373, - 0.1019607843, - 0.5098039216, - 0.3686274509803922, - 0.0078431373, - 0.1019607843, - 0.4941176471, - 0.37254901960784315, - 0.0078431373, - 0.0941176471, - 0.4862745098, - 0.3764705882352941, - 0.0078431373, - 0.0941176471, - 0.4705882353, - 0.3803921568627451, - 0.0078431373, - 0.0862745098, - 0.462745098, - 0.3843137254901961, - 0.0078431373, - 0.0862745098, - 0.4470588235, - 0.38823529411764707, - 0.0078431373, - 0.0784313725, - 0.4392156863, - 0.39215686274509803, - 0.0078431373, - 0.0705882353, - 0.431372549, - 0.396078431372549, - 0.0078431373, - 0.0705882353, - 0.4156862745, - 0.4, - 0.0078431373, - 0.062745098, - 0.4078431373, - 0.403921568627451, - 0.0078431373, - 0.062745098, - 0.3921568627, - 0.40784313725490196, - 0.0078431373, - 0.0549019608, - 0.3843137255, - 0.4117647058823529, - 0.0078431373, - 0.0549019608, - 0.368627451, - 0.41568627450980394, - 0.0078431373, - 0.0470588235, - 0.3607843137, - 0.4196078431372549, - 0.0078431373, - 0.0470588235, - 0.3529411765, - 0.4235294117647059, - 0.0078431373, - 0.0392156863, - 0.337254902, - 0.42745098039215684, - 0.0078431373, - 0.031372549, - 0.3294117647, - 0.43137254901960786, - 0.0078431373, - 0.031372549, - 0.3137254902, - 0.43529411764705883, - 0.0078431373, - 0.0235294118, - 0.3058823529, - 0.4392156862745098, - 0.0078431373, - 0.0235294118, - 0.2901960784, - 0.44313725490196076, - 0.0078431373, - 0.0156862745, - 0.2823529412, - 0.4470588235294118, - 0.0078431373, - 0.0156862745, - 0.2745098039, - 0.45098039215686275, - 0.0078431373, - 0.0078431373, - 0.2588235294, - 0.4549019607843137, - 0.0235294118, - 0.0078431373, - 0.2509803922, - 0.4588235294117647, - 0.0078431373, - 0.0078431373, - 0.2352941176, - 0.4627450980392157, - 0.0078431373, - 0.0078431373, - 0.2274509804, - 0.4666666666666667, - 0.0078431373, - 0.0078431373, - 0.2117647059, - 0.4705882352941177, - 0.0078431373, - 0.0078431373, - 0.2039215686, - 0.4745098039215686, - 0.0078431373, - 0.0078431373, - 0.1960784314, - 0.4784313725490197, - 0.0078431373, - 0.0078431373, - 0.1803921569, - 0.48235294117647065, - 0.0078431373, - 0.0078431373, - 0.1725490196, - 0.48627450980392156, - 0.0078431373, - 0.0078431373, - 0.1568627451, - 0.49019607843137253, - 0.0078431373, - 0.0078431373, - 0.1490196078, - 0.49411764705882355, - 0.0078431373, - 0.0078431373, - 0.1333333333, - 0.4980392156862745, - 0.0078431373, - 0.0078431373, - 0.1254901961, - 0.5019607843137255, - 0.0078431373, - 0.0078431373, - 0.1176470588, - 0.5058823529411764, - 0.0078431373, - 0.0078431373, - 0.1019607843, - 0.5098039215686274, - 0.0078431373, - 0.0078431373, - 0.0941176471, - 0.5137254901960784, - 0.0078431373, - 0.0078431373, - 0.0784313725, - 0.5176470588235295, - 0.0078431373, - 0.0078431373, - 0.0705882353, - 0.5215686274509804, - 0.0078431373, - 0.0078431373, - 0.0549019608, - 0.5254901960784314, - 0.0078431373, - 0.0078431373, - 0.0470588235, - 0.5294117647058824, - 0.0235294118, - 0.0078431373, - 0.0392156863, - 0.5333333333333333, - 0.031372549, - 0.0078431373, - 0.0235294118, - 0.5372549019607843, - 0.0392156863, - 0.0078431373, - 0.0156862745, - 0.5411764705882353, - 0.0549019608, - 0.0078431373, - 0.0, - 0.5450980392156862, - 0.062745098, - 0.0078431373, - 0.0, - 0.5490196078431373, - 0.0705882353, - 0.0078431373, - 0.0, - 0.5529411764705883, - 0.0862745098, - 0.0078431373, - 0.0, - 0.5568627450980392, - 0.0941176471, - 0.0078431373, - 0.0, - 0.5607843137254902, - 0.1019607843, - 0.0078431373, - 0.0, - 0.5647058823529412, - 0.1098039216, - 0.0078431373, - 0.0, - 0.5686274509803921, - 0.1254901961, - 0.0078431373, - 0.0, - 0.5725490196078431, - 0.1333333333, - 0.0078431373, - 0.0, - 0.5764705882352941, - 0.1411764706, - 0.0078431373, - 0.0, - 0.5803921568627451, - 0.1568627451, - 0.0078431373, - 0.0, - 0.5843137254901961, - 0.1647058824, - 0.0078431373, - 0.0, - 0.5882352941176471, - 0.1725490196, - 0.0078431373, - 0.0, - 0.592156862745098, - 0.1882352941, - 0.0078431373, - 0.0, - 0.596078431372549, - 0.1960784314, - 0.0078431373, - 0.0, - 0.6, - 0.2039215686, - 0.0078431373, - 0.0, - 0.6039215686274509, - 0.2117647059, - 0.0078431373, - 0.0, - 0.6078431372549019, - 0.2274509804, - 0.0078431373, - 0.0, - 0.611764705882353, - 0.2352941176, - 0.0078431373, - 0.0, - 0.615686274509804, - 0.2431372549, - 0.0078431373, - 0.0, - 0.6196078431372549, - 0.2588235294, - 0.0078431373, - 0.0, - 0.6235294117647059, - 0.2666666667, - 0.0078431373, - 0.0, - 0.6274509803921569, - 0.2745098039, - 0.0, - 0.0, - 0.6313725490196078, - 0.2901960784, - 0.0156862745, - 0.0, - 0.6352941176470588, - 0.2980392157, - 0.0235294118, - 0.0, - 0.6392156862745098, - 0.3058823529, - 0.0392156863, - 0.0, - 0.6431372549019608, - 0.3137254902, - 0.0470588235, - 0.0, - 0.6470588235294118, - 0.3294117647, - 0.0549019608, - 0.0, - 0.6509803921568628, - 0.337254902, - 0.0705882353, - 0.0, - 0.6549019607843137, - 0.3450980392, - 0.0784313725, - 0.0, - 0.6588235294117647, - 0.3607843137, - 0.0862745098, - 0.0, - 0.6627450980392157, - 0.368627451, - 0.1019607843, - 0.0, - 0.6666666666666666, - 0.3764705882, - 0.1098039216, - 0.0, - 0.6705882352941176, - 0.3843137255, - 0.1176470588, - 0.0, - 0.6745098039215687, - 0.4, - 0.1333333333, - 0.0, - 0.6784313725490196, - 0.4078431373, - 0.1411764706, - 0.0, - 0.6823529411764706, - 0.4156862745, - 0.1490196078, - 0.0, - 0.6862745098039216, - 0.431372549, - 0.1647058824, - 0.0, - 0.6901960784313725, - 0.4392156863, - 0.1725490196, - 0.0, - 0.6941176470588235, - 0.4470588235, - 0.1803921569, - 0.0, - 0.6980392156862745, - 0.462745098, - 0.1960784314, - 0.0, - 0.7019607843137254, - 0.4705882353, - 0.2039215686, - 0.0, - 0.7058823529411765, - 0.4784313725, - 0.2117647059, - 0.0, - 0.7098039215686275, - 0.4862745098, - 0.2274509804, - 0.0, - 0.7137254901960784, - 0.5019607843, - 0.2352941176, - 0.0, - 0.7176470588235294, - 0.5098039216, - 0.2431372549, - 0.0, - 0.7215686274509804, - 0.5176470588, - 0.2588235294, - 0.0, - 0.7254901960784313, - 0.5333333333, - 0.2666666667, - 0.0, - 0.7294117647058823, - 0.5411764706, - 0.2745098039, - 0.0, - 0.7333333333333333, - 0.5490196078, - 0.2901960784, - 0.0, - 0.7372549019607844, - 0.5647058824, - 0.2980392157, - 0.0, - 0.7411764705882353, - 0.5725490196, - 0.3058823529, - 0.0, - 0.7450980392156863, - 0.5803921569, - 0.3215686275, - 0.0, - 0.7490196078431373, - 0.5882352941, - 0.3294117647, - 0.0, - 0.7529411764705882, - 0.6039215686, - 0.337254902, - 0.0, - 0.7568627450980392, - 0.6117647059, - 0.3529411765, - 0.0, - 0.7607843137254902, - 0.6196078431, - 0.3607843137, - 0.0, - 0.7647058823529411, - 0.6352941176, - 0.368627451, - 0.0, - 0.7686274509803922, - 0.6431372549, - 0.3843137255, - 0.0, - 0.7725490196078432, - 0.6509803922, - 0.3921568627, - 0.0, - 0.7764705882352941, - 0.6588235294, - 0.4, - 0.0, - 0.7803921568627451, - 0.6745098039, - 0.4156862745, - 0.0, - 0.7843137254901961, - 0.6823529412, - 0.4235294118, - 0.0, - 0.788235294117647, - 0.6901960784, - 0.431372549, - 0.0, - 0.792156862745098, - 0.7058823529, - 0.4470588235, - 0.0, - 0.796078431372549, - 0.7137254902, - 0.4549019608, - 0.0, - 0.8, - 0.7215686275, - 0.462745098, - 0.0, - 0.803921568627451, - 0.737254902, - 0.4784313725, - 0.0, - 0.807843137254902, - 0.7450980392, - 0.4862745098, - 0.0, - 0.8117647058823529, - 0.7529411765, - 0.4941176471, - 0.0, - 0.8156862745098039, - 0.7607843137, - 0.5098039216, - 0.0, - 0.8196078431372549, - 0.7764705882, - 0.5176470588, - 0.0, - 0.8235294117647058, - 0.7843137255, - 0.5254901961, - 0.0, - 0.8274509803921568, - 0.7921568627, - 0.5411764706, - 0.0, - 0.8313725490196079, - 0.8078431373, - 0.5490196078, - 0.0, - 0.8352941176470589, - 0.8156862745, - 0.5568627451, - 0.0, - 0.8392156862745098, - 0.8235294118, - 0.5725490196, - 0.0, - 0.8431372549019608, - 0.8392156863, - 0.5803921569, - 0.0, - 0.8470588235294118, - 0.8470588235, - 0.5882352941, - 0.0, - 0.8509803921568627, - 0.8549019608, - 0.6039215686, - 0.0, - 0.8549019607843137, - 0.862745098, - 0.6117647059, - 0.0, - 0.8588235294117647, - 0.8784313725, - 0.6196078431, - 0.0, - 0.8627450980392157, - 0.8862745098, - 0.6352941176, - 0.0, - 0.8666666666666667, - 0.8941176471, - 0.6431372549, - 0.0, - 0.8705882352941177, - 0.9098039216, - 0.6509803922, - 0.0, - 0.8745098039215686, - 0.9176470588, - 0.6666666667, - 0.0, - 0.8784313725490196, - 0.9254901961, - 0.6745098039, - 0.0, - 0.8823529411764706, - 0.9411764706, - 0.6823529412, - 0.0, - 0.8862745098039215, - 0.9490196078, - 0.6980392157, - 0.0, - 0.8901960784313725, - 0.9568627451, - 0.7058823529, - 0.0, - 0.8941176470588236, - 0.9647058824, - 0.7137254902, - 0.0, - 0.8980392156862745, - 0.9803921569, - 0.7294117647, - 0.0, - 0.9019607843137255, - 0.9882352941, - 0.737254902, - 0.0, - 0.9058823529411765, - 0.9960784314, - 0.7450980392, - 0.0, - 0.9098039215686274, - 0.9960784314, - 0.7607843137, - 0.0, - 0.9137254901960784, - 0.9960784314, - 0.768627451, - 0.0, - 0.9176470588235294, - 0.9960784314, - 0.7764705882, - 0.0, - 0.9215686274509803, - 0.9960784314, - 0.7921568627, - 0.0, - 0.9254901960784314, - 0.9960784314, - 0.8, - 0.0, - 0.9294117647058824, - 0.9960784314, - 0.8078431373, - 0.0, - 0.9333333333333333, - 0.9960784314, - 0.8235294118, - 0.0, - 0.9372549019607843, - 0.9960784314, - 0.831372549, - 0.0, - 0.9411764705882354, - 0.9960784314, - 0.8392156863, - 0.0, - 0.9450980392156864, - 0.9960784314, - 0.8549019608, - 0.0, - 0.9490196078431372, - 0.9960784314, - 0.862745098, - 0.0549019608, - 0.9529411764705882, - 0.9960784314, - 0.8705882353, - 0.1098039216, - 0.9568627450980394, - 0.9960784314, - 0.8862745098, - 0.1647058824, - 0.9607843137254903, - 0.9960784314, - 0.8941176471, - 0.2196078431, - 0.9647058823529413, - 0.9960784314, - 0.9019607843, - 0.2666666667, - 0.9686274509803922, - 0.9960784314, - 0.9176470588, - 0.3215686275, - 0.9725490196078431, - 0.9960784314, - 0.9254901961, - 0.3764705882, - 0.9764705882352941, - 0.9960784314, - 0.9333333333, - 0.431372549, - 0.9803921568627451, - 0.9960784314, - 0.9490196078, - 0.4862745098, - 0.984313725490196, - 0.9960784314, - 0.9568627451, - 0.5333333333, - 0.9882352941176471, - 0.9960784314, - 0.9647058824, - 0.5882352941, - 0.9921568627450981, - 0.9960784314, - 0.9803921569, - 0.6431372549, - 0.996078431372549, - 0.9960784314, - 0.9882352941, - 0.6980392157, - 1.0, - 0.9960784314, - 0.9960784314, - 0.7450980392, + 0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0078431373, 0.0235294118, 0.0235294118, + 0.00784313725490196, 0.0078431373, 0.031372549, 0.0470588235, 0.011764705882352941, + 0.0078431373, 0.0392156863, 0.062745098, 0.01568627450980392, 0.0078431373, 0.0470588235, + 0.0862745098, 0.0196078431372549, 0.0078431373, 0.0549019608, 0.1019607843, + 0.023529411764705882, 0.0078431373, 0.0549019608, 0.1254901961, 0.027450980392156862, + 0.0078431373, 0.062745098, 0.1411764706, 0.03137254901960784, 0.0078431373, 0.0705882353, + 0.1647058824, 0.03529411764705882, 0.0078431373, 0.0784313725, 0.1803921569, + 0.0392156862745098, 0.0078431373, 0.0862745098, 0.2039215686, 0.043137254901960784, + 0.0078431373, 0.0862745098, 0.2196078431, 0.047058823529411764, 0.0078431373, 0.0941176471, + 0.2431372549, 0.050980392156862744, 0.0078431373, 0.1019607843, 0.2666666667, + 0.054901960784313725, 0.0078431373, 0.1098039216, 0.2823529412, 0.05882352941176471, + 0.0078431373, 0.1176470588, 0.3058823529, 0.06274509803921569, 0.0078431373, 0.1176470588, + 0.3215686275, 0.06666666666666667, 0.0078431373, 0.1254901961, 0.3450980392, + 0.07058823529411765, 0.0078431373, 0.1333333333, 0.3607843137, 0.07450980392156863, + 0.0078431373, 0.1411764706, 0.3843137255, 0.0784313725490196, 0.0078431373, 0.1490196078, 0.4, + 0.08235294117647059, 0.0078431373, 0.1490196078, 0.4235294118, 0.08627450980392157, + 0.0078431373, 0.1568627451, 0.4392156863, 0.09019607843137255, 0.0078431373, 0.1647058824, + 0.462745098, 0.09411764705882353, 0.0078431373, 0.1725490196, 0.4784313725, + 0.09803921568627451, 0.0078431373, 0.1803921569, 0.5019607843, 0.10196078431372549, + 0.0078431373, 0.1803921569, 0.5254901961, 0.10588235294117647, 0.0078431373, 0.1882352941, + 0.5411764706, 0.10980392156862745, 0.0078431373, 0.1960784314, 0.5647058824, + 0.11372549019607843, 0.0078431373, 0.2039215686, 0.5803921569, 0.11764705882352942, + 0.0078431373, 0.2117647059, 0.6039215686, 0.12156862745098039, 0.0078431373, 0.2117647059, + 0.6196078431, 0.12549019607843137, 0.0078431373, 0.2196078431, 0.6431372549, + 0.12941176470588237, 0.0078431373, 0.2274509804, 0.6588235294, 0.13333333333333333, + 0.0078431373, 0.2352941176, 0.6823529412, 0.13725490196078433, 0.0078431373, 0.2431372549, + 0.6980392157, 0.1411764705882353, 0.0078431373, 0.2431372549, 0.7215686275, + 0.1450980392156863, 0.0078431373, 0.2509803922, 0.737254902, 0.14901960784313725, + 0.0078431373, 0.2588235294, 0.7607843137, 0.15294117647058825, 0.0078431373, 0.2666666667, + 0.7843137255, 0.1568627450980392, 0.0078431373, 0.2745098039, 0.8, 0.1607843137254902, + 0.0078431373, 0.2745098039, 0.8235294118, 0.16470588235294117, 0.0078431373, 0.2823529412, + 0.8392156863, 0.16862745098039217, 0.0078431373, 0.2901960784, 0.862745098, + 0.17254901960784313, 0.0078431373, 0.2980392157, 0.8784313725, 0.17647058823529413, + 0.0078431373, 0.3058823529, 0.9019607843, 0.1803921568627451, 0.0078431373, 0.3058823529, + 0.9176470588, 0.1843137254901961, 0.0078431373, 0.2980392157, 0.9411764706, + 0.18823529411764706, 0.0078431373, 0.3058823529, 0.9568627451, 0.19215686274509805, + 0.0078431373, 0.2980392157, 0.9803921569, 0.19607843137254902, 0.0078431373, 0.2980392157, + 0.9882352941, 0.2, 0.0078431373, 0.2901960784, 0.9803921569, 0.20392156862745098, + 0.0078431373, 0.2901960784, 0.9647058824, 0.20784313725490197, 0.0078431373, 0.2823529412, + 0.9568627451, 0.21176470588235294, 0.0078431373, 0.2823529412, 0.9411764706, + 0.21568627450980393, 0.0078431373, 0.2745098039, 0.9333333333, 0.2196078431372549, + 0.0078431373, 0.2666666667, 0.9176470588, 0.2235294117647059, 0.0078431373, 0.2666666667, + 0.9098039216, 0.22745098039215686, 0.0078431373, 0.2588235294, 0.9019607843, + 0.23137254901960785, 0.0078431373, 0.2588235294, 0.8862745098, 0.23529411764705885, + 0.0078431373, 0.2509803922, 0.8784313725, 0.23921568627450984, 0.0078431373, 0.2509803922, + 0.862745098, 0.24313725490196078, 0.0078431373, 0.2431372549, 0.8549019608, + 0.24705882352941178, 0.0078431373, 0.2352941176, 0.8392156863, 0.25098039215686274, + 0.0078431373, 0.2352941176, 0.831372549, 0.2549019607843137, 0.0078431373, 0.2274509804, + 0.8235294118, 0.25882352941176473, 0.0078431373, 0.2274509804, 0.8078431373, + 0.2627450980392157, 0.0078431373, 0.2196078431, 0.8, 0.26666666666666666, 0.0078431373, + 0.2196078431, 0.7843137255, 0.27058823529411763, 0.0078431373, 0.2117647059, 0.7764705882, + 0.27450980392156865, 0.0078431373, 0.2039215686, 0.7607843137, 0.2784313725490196, + 0.0078431373, 0.2039215686, 0.7529411765, 0.2823529411764706, 0.0078431373, 0.1960784314, + 0.7450980392, 0.28627450980392155, 0.0078431373, 0.1960784314, 0.7294117647, + 0.2901960784313726, 0.0078431373, 0.1882352941, 0.7215686275, 0.29411764705882354, + 0.0078431373, 0.1882352941, 0.7058823529, 0.2980392156862745, 0.0078431373, 0.1803921569, + 0.6980392157, 0.30196078431372547, 0.0078431373, 0.1803921569, 0.6823529412, + 0.3058823529411765, 0.0078431373, 0.1725490196, 0.6745098039, 0.30980392156862746, + 0.0078431373, 0.1647058824, 0.6666666667, 0.3137254901960784, 0.0078431373, 0.1647058824, + 0.6509803922, 0.3176470588235294, 0.0078431373, 0.1568627451, 0.6431372549, + 0.3215686274509804, 0.0078431373, 0.1568627451, 0.6274509804, 0.3254901960784314, + 0.0078431373, 0.1490196078, 0.6196078431, 0.32941176470588235, 0.0078431373, 0.1490196078, + 0.6039215686, 0.3333333333333333, 0.0078431373, 0.1411764706, 0.5960784314, + 0.33725490196078434, 0.0078431373, 0.1333333333, 0.5882352941, 0.3411764705882353, + 0.0078431373, 0.1333333333, 0.5725490196, 0.34509803921568627, 0.0078431373, 0.1254901961, + 0.5647058824, 0.34901960784313724, 0.0078431373, 0.1254901961, 0.5490196078, + 0.35294117647058826, 0.0078431373, 0.1176470588, 0.5411764706, 0.3568627450980392, + 0.0078431373, 0.1176470588, 0.5254901961, 0.3607843137254902, 0.0078431373, 0.1098039216, + 0.5176470588, 0.36470588235294116, 0.0078431373, 0.1019607843, 0.5098039216, + 0.3686274509803922, 0.0078431373, 0.1019607843, 0.4941176471, 0.37254901960784315, + 0.0078431373, 0.0941176471, 0.4862745098, 0.3764705882352941, 0.0078431373, 0.0941176471, + 0.4705882353, 0.3803921568627451, 0.0078431373, 0.0862745098, 0.462745098, 0.3843137254901961, + 0.0078431373, 0.0862745098, 0.4470588235, 0.38823529411764707, 0.0078431373, 0.0784313725, + 0.4392156863, 0.39215686274509803, 0.0078431373, 0.0705882353, 0.431372549, 0.396078431372549, + 0.0078431373, 0.0705882353, 0.4156862745, 0.4, 0.0078431373, 0.062745098, 0.4078431373, + 0.403921568627451, 0.0078431373, 0.062745098, 0.3921568627, 0.40784313725490196, 0.0078431373, + 0.0549019608, 0.3843137255, 0.4117647058823529, 0.0078431373, 0.0549019608, 0.368627451, + 0.41568627450980394, 0.0078431373, 0.0470588235, 0.3607843137, 0.4196078431372549, + 0.0078431373, 0.0470588235, 0.3529411765, 0.4235294117647059, 0.0078431373, 0.0392156863, + 0.337254902, 0.42745098039215684, 0.0078431373, 0.031372549, 0.3294117647, + 0.43137254901960786, 0.0078431373, 0.031372549, 0.3137254902, 0.43529411764705883, + 0.0078431373, 0.0235294118, 0.3058823529, 0.4392156862745098, 0.0078431373, 0.0235294118, + 0.2901960784, 0.44313725490196076, 0.0078431373, 0.0156862745, 0.2823529412, + 0.4470588235294118, 0.0078431373, 0.0156862745, 0.2745098039, 0.45098039215686275, + 0.0078431373, 0.0078431373, 0.2588235294, 0.4549019607843137, 0.0235294118, 0.0078431373, + 0.2509803922, 0.4588235294117647, 0.0078431373, 0.0078431373, 0.2352941176, + 0.4627450980392157, 0.0078431373, 0.0078431373, 0.2274509804, 0.4666666666666667, + 0.0078431373, 0.0078431373, 0.2117647059, 0.4705882352941177, 0.0078431373, 0.0078431373, + 0.2039215686, 0.4745098039215686, 0.0078431373, 0.0078431373, 0.1960784314, + 0.4784313725490197, 0.0078431373, 0.0078431373, 0.1803921569, 0.48235294117647065, + 0.0078431373, 0.0078431373, 0.1725490196, 0.48627450980392156, 0.0078431373, 0.0078431373, + 0.1568627451, 0.49019607843137253, 0.0078431373, 0.0078431373, 0.1490196078, + 0.49411764705882355, 0.0078431373, 0.0078431373, 0.1333333333, 0.4980392156862745, + 0.0078431373, 0.0078431373, 0.1254901961, 0.5019607843137255, 0.0078431373, 0.0078431373, + 0.1176470588, 0.5058823529411764, 0.0078431373, 0.0078431373, 0.1019607843, + 0.5098039215686274, 0.0078431373, 0.0078431373, 0.0941176471, 0.5137254901960784, + 0.0078431373, 0.0078431373, 0.0784313725, 0.5176470588235295, 0.0078431373, 0.0078431373, + 0.0705882353, 0.5215686274509804, 0.0078431373, 0.0078431373, 0.0549019608, + 0.5254901960784314, 0.0078431373, 0.0078431373, 0.0470588235, 0.5294117647058824, + 0.0235294118, 0.0078431373, 0.0392156863, 0.5333333333333333, 0.031372549, 0.0078431373, + 0.0235294118, 0.5372549019607843, 0.0392156863, 0.0078431373, 0.0156862745, + 0.5411764705882353, 0.0549019608, 0.0078431373, 0.0, 0.5450980392156862, 0.062745098, + 0.0078431373, 0.0, 0.5490196078431373, 0.0705882353, 0.0078431373, 0.0, 0.5529411764705883, + 0.0862745098, 0.0078431373, 0.0, 0.5568627450980392, 0.0941176471, 0.0078431373, 0.0, + 0.5607843137254902, 0.1019607843, 0.0078431373, 0.0, 0.5647058823529412, 0.1098039216, + 0.0078431373, 0.0, 0.5686274509803921, 0.1254901961, 0.0078431373, 0.0, 0.5725490196078431, + 0.1333333333, 0.0078431373, 0.0, 0.5764705882352941, 0.1411764706, 0.0078431373, 0.0, + 0.5803921568627451, 0.1568627451, 0.0078431373, 0.0, 0.5843137254901961, 0.1647058824, + 0.0078431373, 0.0, 0.5882352941176471, 0.1725490196, 0.0078431373, 0.0, 0.592156862745098, + 0.1882352941, 0.0078431373, 0.0, 0.596078431372549, 0.1960784314, 0.0078431373, 0.0, 0.6, + 0.2039215686, 0.0078431373, 0.0, 0.6039215686274509, 0.2117647059, 0.0078431373, 0.0, + 0.6078431372549019, 0.2274509804, 0.0078431373, 0.0, 0.611764705882353, 0.2352941176, + 0.0078431373, 0.0, 0.615686274509804, 0.2431372549, 0.0078431373, 0.0, 0.6196078431372549, + 0.2588235294, 0.0078431373, 0.0, 0.6235294117647059, 0.2666666667, 0.0078431373, 0.0, + 0.6274509803921569, 0.2745098039, 0.0, 0.0, 0.6313725490196078, 0.2901960784, 0.0156862745, + 0.0, 0.6352941176470588, 0.2980392157, 0.0235294118, 0.0, 0.6392156862745098, 0.3058823529, + 0.0392156863, 0.0, 0.6431372549019608, 0.3137254902, 0.0470588235, 0.0, 0.6470588235294118, + 0.3294117647, 0.0549019608, 0.0, 0.6509803921568628, 0.337254902, 0.0705882353, 0.0, + 0.6549019607843137, 0.3450980392, 0.0784313725, 0.0, 0.6588235294117647, 0.3607843137, + 0.0862745098, 0.0, 0.6627450980392157, 0.368627451, 0.1019607843, 0.0, 0.6666666666666666, + 0.3764705882, 0.1098039216, 0.0, 0.6705882352941176, 0.3843137255, 0.1176470588, 0.0, + 0.6745098039215687, 0.4, 0.1333333333, 0.0, 0.6784313725490196, 0.4078431373, 0.1411764706, + 0.0, 0.6823529411764706, 0.4156862745, 0.1490196078, 0.0, 0.6862745098039216, 0.431372549, + 0.1647058824, 0.0, 0.6901960784313725, 0.4392156863, 0.1725490196, 0.0, 0.6941176470588235, + 0.4470588235, 0.1803921569, 0.0, 0.6980392156862745, 0.462745098, 0.1960784314, 0.0, + 0.7019607843137254, 0.4705882353, 0.2039215686, 0.0, 0.7058823529411765, 0.4784313725, + 0.2117647059, 0.0, 0.7098039215686275, 0.4862745098, 0.2274509804, 0.0, 0.7137254901960784, + 0.5019607843, 0.2352941176, 0.0, 0.7176470588235294, 0.5098039216, 0.2431372549, 0.0, + 0.7215686274509804, 0.5176470588, 0.2588235294, 0.0, 0.7254901960784313, 0.5333333333, + 0.2666666667, 0.0, 0.7294117647058823, 0.5411764706, 0.2745098039, 0.0, 0.7333333333333333, + 0.5490196078, 0.2901960784, 0.0, 0.7372549019607844, 0.5647058824, 0.2980392157, 0.0, + 0.7411764705882353, 0.5725490196, 0.3058823529, 0.0, 0.7450980392156863, 0.5803921569, + 0.3215686275, 0.0, 0.7490196078431373, 0.5882352941, 0.3294117647, 0.0, 0.7529411764705882, + 0.6039215686, 0.337254902, 0.0, 0.7568627450980392, 0.6117647059, 0.3529411765, 0.0, + 0.7607843137254902, 0.6196078431, 0.3607843137, 0.0, 0.7647058823529411, 0.6352941176, + 0.368627451, 0.0, 0.7686274509803922, 0.6431372549, 0.3843137255, 0.0, 0.7725490196078432, + 0.6509803922, 0.3921568627, 0.0, 0.7764705882352941, 0.6588235294, 0.4, 0.0, + 0.7803921568627451, 0.6745098039, 0.4156862745, 0.0, 0.7843137254901961, 0.6823529412, + 0.4235294118, 0.0, 0.788235294117647, 0.6901960784, 0.431372549, 0.0, 0.792156862745098, + 0.7058823529, 0.4470588235, 0.0, 0.796078431372549, 0.7137254902, 0.4549019608, 0.0, 0.8, + 0.7215686275, 0.462745098, 0.0, 0.803921568627451, 0.737254902, 0.4784313725, 0.0, + 0.807843137254902, 0.7450980392, 0.4862745098, 0.0, 0.8117647058823529, 0.7529411765, + 0.4941176471, 0.0, 0.8156862745098039, 0.7607843137, 0.5098039216, 0.0, 0.8196078431372549, + 0.7764705882, 0.5176470588, 0.0, 0.8235294117647058, 0.7843137255, 0.5254901961, 0.0, + 0.8274509803921568, 0.7921568627, 0.5411764706, 0.0, 0.8313725490196079, 0.8078431373, + 0.5490196078, 0.0, 0.8352941176470589, 0.8156862745, 0.5568627451, 0.0, 0.8392156862745098, + 0.8235294118, 0.5725490196, 0.0, 0.8431372549019608, 0.8392156863, 0.5803921569, 0.0, + 0.8470588235294118, 0.8470588235, 0.5882352941, 0.0, 0.8509803921568627, 0.8549019608, + 0.6039215686, 0.0, 0.8549019607843137, 0.862745098, 0.6117647059, 0.0, 0.8588235294117647, + 0.8784313725, 0.6196078431, 0.0, 0.8627450980392157, 0.8862745098, 0.6352941176, 0.0, + 0.8666666666666667, 0.8941176471, 0.6431372549, 0.0, 0.8705882352941177, 0.9098039216, + 0.6509803922, 0.0, 0.8745098039215686, 0.9176470588, 0.6666666667, 0.0, 0.8784313725490196, + 0.9254901961, 0.6745098039, 0.0, 0.8823529411764706, 0.9411764706, 0.6823529412, 0.0, + 0.8862745098039215, 0.9490196078, 0.6980392157, 0.0, 0.8901960784313725, 0.9568627451, + 0.7058823529, 0.0, 0.8941176470588236, 0.9647058824, 0.7137254902, 0.0, 0.8980392156862745, + 0.9803921569, 0.7294117647, 0.0, 0.9019607843137255, 0.9882352941, 0.737254902, 0.0, + 0.9058823529411765, 0.9960784314, 0.7450980392, 0.0, 0.9098039215686274, 0.9960784314, + 0.7607843137, 0.0, 0.9137254901960784, 0.9960784314, 0.768627451, 0.0, 0.9176470588235294, + 0.9960784314, 0.7764705882, 0.0, 0.9215686274509803, 0.9960784314, 0.7921568627, 0.0, + 0.9254901960784314, 0.9960784314, 0.8, 0.0, 0.9294117647058824, 0.9960784314, 0.8078431373, + 0.0, 0.9333333333333333, 0.9960784314, 0.8235294118, 0.0, 0.9372549019607843, 0.9960784314, + 0.831372549, 0.0, 0.9411764705882354, 0.9960784314, 0.8392156863, 0.0, 0.9450980392156864, + 0.9960784314, 0.8549019608, 0.0, 0.9490196078431372, 0.9960784314, 0.862745098, 0.0549019608, + 0.9529411764705882, 0.9960784314, 0.8705882353, 0.1098039216, 0.9568627450980394, + 0.9960784314, 0.8862745098, 0.1647058824, 0.9607843137254903, 0.9960784314, 0.8941176471, + 0.2196078431, 0.9647058823529413, 0.9960784314, 0.9019607843, 0.2666666667, + 0.9686274509803922, 0.9960784314, 0.9176470588, 0.3215686275, 0.9725490196078431, + 0.9960784314, 0.9254901961, 0.3764705882, 0.9764705882352941, 0.9960784314, 0.9333333333, + 0.431372549, 0.9803921568627451, 0.9960784314, 0.9490196078, 0.4862745098, 0.984313725490196, + 0.9960784314, 0.9568627451, 0.5333333333, 0.9882352941176471, 0.9960784314, 0.9647058824, + 0.5882352941, 0.9921568627450981, 0.9960784314, 0.9803921569, 0.6431372549, 0.996078431372549, + 0.9960784314, 0.9882352941, 0.6980392157, 1.0, 0.9960784314, 0.9960784314, 0.7450980392, ], }, { ColorSpace: 'RGB', Name: 'rainbow_2', RGBPoints: [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.00392156862745098, - 0.0156862745, - 0.0, - 0.0117647059, - 0.00784313725490196, - 0.0352941176, - 0.0, - 0.0274509804, - 0.011764705882352941, - 0.0509803922, - 0.0, - 0.0392156863, - 0.01568627450980392, - 0.0705882353, - 0.0, - 0.0549019608, - 0.0196078431372549, - 0.0862745098, - 0.0, - 0.0745098039, - 0.023529411764705882, - 0.1058823529, - 0.0, - 0.0901960784, - 0.027450980392156862, - 0.1215686275, - 0.0, - 0.1098039216, - 0.03137254901960784, - 0.1411764706, - 0.0, - 0.1254901961, - 0.03529411764705882, - 0.1568627451, - 0.0, - 0.1490196078, - 0.0392156862745098, - 0.1764705882, - 0.0, - 0.168627451, - 0.043137254901960784, - 0.1960784314, - 0.0, - 0.1882352941, - 0.047058823529411764, - 0.2117647059, - 0.0, - 0.2078431373, - 0.050980392156862744, - 0.2274509804, - 0.0, - 0.231372549, - 0.054901960784313725, - 0.2392156863, - 0.0, - 0.2470588235, - 0.05882352941176471, - 0.2509803922, - 0.0, - 0.2666666667, - 0.06274509803921569, - 0.2666666667, - 0.0, - 0.2823529412, - 0.06666666666666667, - 0.2705882353, - 0.0, - 0.3019607843, - 0.07058823529411765, - 0.2823529412, - 0.0, - 0.3176470588, - 0.07450980392156863, - 0.2901960784, - 0.0, - 0.337254902, - 0.0784313725490196, - 0.3019607843, - 0.0, - 0.3568627451, - 0.08235294117647059, - 0.3098039216, - 0.0, - 0.3725490196, - 0.08627450980392157, - 0.3137254902, - 0.0, - 0.3921568627, - 0.09019607843137255, - 0.3215686275, - 0.0, - 0.4078431373, - 0.09411764705882353, - 0.3254901961, - 0.0, - 0.4274509804, - 0.09803921568627451, - 0.3333333333, - 0.0, - 0.4431372549, - 0.10196078431372549, - 0.3294117647, - 0.0, - 0.462745098, - 0.10588235294117647, - 0.337254902, - 0.0, - 0.4784313725, - 0.10980392156862745, - 0.3411764706, - 0.0, - 0.4980392157, - 0.11372549019607843, - 0.3450980392, - 0.0, - 0.5176470588, - 0.11764705882352942, - 0.337254902, - 0.0, - 0.5333333333, - 0.12156862745098039, - 0.3411764706, - 0.0, - 0.5529411765, - 0.12549019607843137, - 0.3411764706, - 0.0, - 0.568627451, - 0.12941176470588237, - 0.3411764706, - 0.0, - 0.5882352941, - 0.13333333333333333, - 0.3333333333, - 0.0, - 0.6039215686, - 0.13725490196078433, - 0.3294117647, - 0.0, - 0.6235294118, - 0.1411764705882353, - 0.3294117647, - 0.0, - 0.6392156863, - 0.1450980392156863, - 0.3294117647, - 0.0, - 0.6588235294, - 0.14901960784313725, - 0.3254901961, - 0.0, - 0.6784313725, - 0.15294117647058825, - 0.3098039216, - 0.0, - 0.6941176471, - 0.1568627450980392, - 0.3058823529, - 0.0, - 0.7137254902, - 0.1607843137254902, - 0.3019607843, - 0.0, - 0.7294117647, - 0.16470588235294117, - 0.2980392157, - 0.0, - 0.7490196078, - 0.16862745098039217, - 0.2784313725, - 0.0, - 0.7647058824, - 0.17254901960784313, - 0.2745098039, - 0.0, - 0.7843137255, - 0.17647058823529413, - 0.2666666667, - 0.0, - 0.8, - 0.1803921568627451, - 0.2588235294, - 0.0, - 0.8196078431, - 0.1843137254901961, - 0.2352941176, - 0.0, - 0.8392156863, - 0.18823529411764706, - 0.2274509804, - 0.0, - 0.8549019608, - 0.19215686274509805, - 0.2156862745, - 0.0, - 0.8745098039, - 0.19607843137254902, - 0.2078431373, - 0.0, - 0.8901960784, - 0.2, - 0.1803921569, - 0.0, - 0.9098039216, - 0.20392156862745098, - 0.168627451, - 0.0, - 0.9254901961, - 0.20784313725490197, - 0.1568627451, - 0.0, - 0.9450980392, - 0.21176470588235294, - 0.1411764706, - 0.0, - 0.9607843137, - 0.21568627450980393, - 0.1294117647, - 0.0, - 0.9803921569, - 0.2196078431372549, - 0.0980392157, - 0.0, - 1.0, - 0.2235294117647059, - 0.0823529412, - 0.0, - 1.0, - 0.22745098039215686, - 0.062745098, - 0.0, - 1.0, - 0.23137254901960785, - 0.0470588235, - 0.0, - 1.0, - 0.23529411764705885, - 0.0156862745, - 0.0, - 1.0, - 0.23921568627450984, - 0.0, - 0.0, - 1.0, - 0.24313725490196078, - 0.0, - 0.0156862745, - 1.0, - 0.24705882352941178, - 0.0, - 0.031372549, - 1.0, - 0.25098039215686274, - 0.0, - 0.062745098, - 1.0, - 0.2549019607843137, - 0.0, - 0.0823529412, - 1.0, - 0.25882352941176473, - 0.0, - 0.0980392157, - 1.0, - 0.2627450980392157, - 0.0, - 0.1137254902, - 1.0, - 0.26666666666666666, - 0.0, - 0.1490196078, - 1.0, - 0.27058823529411763, - 0.0, - 0.1647058824, - 1.0, - 0.27450980392156865, - 0.0, - 0.1803921569, - 1.0, - 0.2784313725490196, - 0.0, - 0.2, - 1.0, - 0.2823529411764706, - 0.0, - 0.2156862745, - 1.0, - 0.28627450980392155, - 0.0, - 0.2470588235, - 1.0, - 0.2901960784313726, - 0.0, - 0.262745098, - 1.0, - 0.29411764705882354, - 0.0, - 0.2823529412, - 1.0, - 0.2980392156862745, - 0.0, - 0.2980392157, - 1.0, - 0.30196078431372547, - 0.0, - 0.3294117647, - 1.0, - 0.3058823529411765, - 0.0, - 0.3490196078, - 1.0, - 0.30980392156862746, - 0.0, - 0.3647058824, - 1.0, - 0.3137254901960784, - 0.0, - 0.3803921569, - 1.0, - 0.3176470588235294, - 0.0, - 0.4156862745, - 1.0, - 0.3215686274509804, - 0.0, - 0.431372549, - 1.0, - 0.3254901960784314, - 0.0, - 0.4470588235, - 1.0, - 0.32941176470588235, - 0.0, - 0.4666666667, - 1.0, - 0.3333333333333333, - 0.0, - 0.4980392157, - 1.0, - 0.33725490196078434, - 0.0, - 0.5137254902, - 1.0, - 0.3411764705882353, - 0.0, - 0.5294117647, - 1.0, - 0.34509803921568627, - 0.0, - 0.5490196078, - 1.0, - 0.34901960784313724, - 0.0, - 0.5647058824, - 1.0, - 0.35294117647058826, - 0.0, - 0.5960784314, - 1.0, - 0.3568627450980392, - 0.0, - 0.6156862745, - 1.0, - 0.3607843137254902, - 0.0, - 0.631372549, - 1.0, - 0.36470588235294116, - 0.0, - 0.6470588235, - 1.0, - 0.3686274509803922, - 0.0, - 0.6823529412, - 1.0, - 0.37254901960784315, - 0.0, - 0.6980392157, - 1.0, - 0.3764705882352941, - 0.0, - 0.7137254902, - 1.0, - 0.3803921568627451, - 0.0, - 0.7333333333, - 1.0, - 0.3843137254901961, - 0.0, - 0.7647058824, - 1.0, - 0.38823529411764707, - 0.0, - 0.7803921569, - 1.0, - 0.39215686274509803, - 0.0, - 0.7960784314, - 1.0, - 0.396078431372549, - 0.0, - 0.8156862745, - 1.0, - 0.4, - 0.0, - 0.8470588235, - 1.0, - 0.403921568627451, - 0.0, - 0.862745098, - 1.0, - 0.40784313725490196, - 0.0, - 0.8823529412, - 1.0, - 0.4117647058823529, - 0.0, - 0.8980392157, - 1.0, - 0.41568627450980394, - 0.0, - 0.9137254902, - 1.0, - 0.4196078431372549, - 0.0, - 0.9490196078, - 1.0, - 0.4235294117647059, - 0.0, - 0.9647058824, - 1.0, - 0.42745098039215684, - 0.0, - 0.9803921569, - 1.0, - 0.43137254901960786, - 0.0, - 1.0, - 1.0, - 0.43529411764705883, - 0.0, - 1.0, - 0.9647058824, - 0.4392156862745098, - 0.0, - 1.0, - 0.9490196078, - 0.44313725490196076, - 0.0, - 1.0, - 0.9333333333, - 0.4470588235294118, - 0.0, - 1.0, - 0.9137254902, - 0.45098039215686275, - 0.0, - 1.0, - 0.8823529412, - 0.4549019607843137, - 0.0, - 1.0, - 0.862745098, - 0.4588235294117647, - 0.0, - 1.0, - 0.8470588235, - 0.4627450980392157, - 0.0, - 1.0, - 0.831372549, - 0.4666666666666667, - 0.0, - 1.0, - 0.7960784314, - 0.4705882352941177, - 0.0, - 1.0, - 0.7803921569, - 0.4745098039215686, - 0.0, - 1.0, - 0.7647058824, - 0.4784313725490197, - 0.0, - 1.0, - 0.7490196078, - 0.48235294117647065, - 0.0, - 1.0, - 0.7333333333, - 0.48627450980392156, - 0.0, - 1.0, - 0.6980392157, - 0.49019607843137253, - 0.0, - 1.0, - 0.6823529412, - 0.49411764705882355, - 0.0, - 1.0, - 0.6666666667, - 0.4980392156862745, - 0.0, - 1.0, - 0.6470588235, - 0.5019607843137255, - 0.0, - 1.0, - 0.6156862745, - 0.5058823529411764, - 0.0, - 1.0, - 0.5960784314, - 0.5098039215686274, - 0.0, - 1.0, - 0.5803921569, - 0.5137254901960784, - 0.0, - 1.0, - 0.5647058824, - 0.5176470588235295, - 0.0, - 1.0, - 0.5294117647, - 0.5215686274509804, - 0.0, - 1.0, - 0.5137254902, - 0.5254901960784314, - 0.0, - 1.0, - 0.4980392157, - 0.5294117647058824, - 0.0, - 1.0, - 0.4823529412, - 0.5333333333333333, - 0.0, - 1.0, - 0.4470588235, - 0.5372549019607843, - 0.0, - 1.0, - 0.431372549, - 0.5411764705882353, - 0.0, - 1.0, - 0.4156862745, - 0.5450980392156862, - 0.0, - 1.0, - 0.4, - 0.5490196078431373, - 0.0, - 1.0, - 0.3803921569, - 0.5529411764705883, - 0.0, - 1.0, - 0.3490196078, - 0.5568627450980392, - 0.0, - 1.0, - 0.3294117647, - 0.5607843137254902, - 0.0, - 1.0, - 0.3137254902, - 0.5647058823529412, - 0.0, - 1.0, - 0.2980392157, - 0.5686274509803921, - 0.0, - 1.0, - 0.262745098, - 0.5725490196078431, - 0.0, - 1.0, - 0.2470588235, - 0.5764705882352941, - 0.0, - 1.0, - 0.231372549, - 0.5803921568627451, - 0.0, - 1.0, - 0.2156862745, - 0.5843137254901961, - 0.0, - 1.0, - 0.1803921569, - 0.5882352941176471, - 0.0, - 1.0, - 0.1647058824, - 0.592156862745098, - 0.0, - 1.0, - 0.1490196078, - 0.596078431372549, - 0.0, - 1.0, - 0.1333333333, - 0.6, - 0.0, - 1.0, - 0.0980392157, - 0.6039215686274509, - 0.0, - 1.0, - 0.0823529412, - 0.6078431372549019, - 0.0, - 1.0, - 0.062745098, - 0.611764705882353, - 0.0, - 1.0, - 0.0470588235, - 0.615686274509804, - 0.0, - 1.0, - 0.031372549, - 0.6196078431372549, - 0.0, - 1.0, - 0.0, - 0.6235294117647059, - 0.0156862745, - 1.0, - 0.0, - 0.6274509803921569, - 0.031372549, - 1.0, - 0.0, - 0.6313725490196078, - 0.0470588235, - 1.0, - 0.0, - 0.6352941176470588, - 0.0823529412, - 1.0, - 0.0, - 0.6392156862745098, - 0.0980392157, - 1.0, - 0.0, - 0.6431372549019608, - 0.1137254902, - 1.0, - 0.0, - 0.6470588235294118, - 0.1294117647, - 1.0, - 0.0, - 0.6509803921568628, - 0.1647058824, - 1.0, - 0.0, - 0.6549019607843137, - 0.1803921569, - 1.0, - 0.0, - 0.6588235294117647, - 0.2, - 1.0, - 0.0, - 0.6627450980392157, - 0.2156862745, - 1.0, - 0.0, - 0.6666666666666666, - 0.2470588235, - 1.0, - 0.0, - 0.6705882352941176, - 0.262745098, - 1.0, - 0.0, - 0.6745098039215687, - 0.2823529412, - 1.0, - 0.0, - 0.6784313725490196, - 0.2980392157, - 1.0, - 0.0, - 0.6823529411764706, - 0.3137254902, - 1.0, - 0.0, - 0.6862745098039216, - 0.3490196078, - 1.0, - 0.0, - 0.6901960784313725, - 0.3647058824, - 1.0, - 0.0, - 0.6941176470588235, - 0.3803921569, - 1.0, - 0.0, - 0.6980392156862745, - 0.3960784314, - 1.0, - 0.0, - 0.7019607843137254, - 0.431372549, - 1.0, - 0.0, - 0.7058823529411765, - 0.4470588235, - 1.0, - 0.0, - 0.7098039215686275, - 0.4666666667, - 1.0, - 0.0, - 0.7137254901960784, - 0.4823529412, - 1.0, - 0.0, - 0.7176470588235294, - 0.5137254902, - 1.0, - 0.0, - 0.7215686274509804, - 0.5294117647, - 1.0, - 0.0, - 0.7254901960784313, - 0.5490196078, - 1.0, - 0.0, - 0.7294117647058823, - 0.5647058824, - 1.0, - 0.0, - 0.7333333333333333, - 0.6, - 1.0, - 0.0, - 0.7372549019607844, - 0.6156862745, - 1.0, - 0.0, - 0.7411764705882353, - 0.631372549, - 1.0, - 0.0, - 0.7450980392156863, - 0.6470588235, - 1.0, - 0.0, - 0.7490196078431373, - 0.662745098, - 1.0, - 0.0, - 0.7529411764705882, - 0.6980392157, - 1.0, - 0.0, - 0.7568627450980392, - 0.7137254902, - 1.0, - 0.0, - 0.7607843137254902, - 0.7333333333, - 1.0, - 0.0, - 0.7647058823529411, - 0.7490196078, - 1.0, - 0.0, - 0.7686274509803922, - 0.7803921569, - 1.0, - 0.0, - 0.7725490196078432, - 0.7960784314, - 1.0, - 0.0, - 0.7764705882352941, - 0.8156862745, - 1.0, - 0.0, - 0.7803921568627451, - 0.831372549, - 1.0, - 0.0, - 0.7843137254901961, - 0.8666666667, - 1.0, - 0.0, - 0.788235294117647, - 0.8823529412, - 1.0, - 0.0, - 0.792156862745098, - 0.8980392157, - 1.0, - 0.0, - 0.796078431372549, - 0.9137254902, - 1.0, - 0.0, - 0.8, - 0.9490196078, - 1.0, - 0.0, - 0.803921568627451, - 0.9647058824, - 1.0, - 0.0, - 0.807843137254902, - 0.9803921569, - 1.0, - 0.0, - 0.8117647058823529, - 1.0, - 1.0, - 0.0, - 0.8156862745098039, - 1.0, - 0.9803921569, - 0.0, - 0.8196078431372549, - 1.0, - 0.9490196078, - 0.0, - 0.8235294117647058, - 1.0, - 0.9333333333, - 0.0, - 0.8274509803921568, - 1.0, - 0.9137254902, - 0.0, - 0.8313725490196079, - 1.0, - 0.8980392157, - 0.0, - 0.8352941176470589, - 1.0, - 0.8666666667, - 0.0, - 0.8392156862745098, - 1.0, - 0.8470588235, - 0.0, - 0.8431372549019608, - 1.0, - 0.831372549, - 0.0, - 0.8470588235294118, - 1.0, - 0.8156862745, - 0.0, - 0.8509803921568627, - 1.0, - 0.7803921569, - 0.0, - 0.8549019607843137, - 1.0, - 0.7647058824, - 0.0, - 0.8588235294117647, - 1.0, - 0.7490196078, - 0.0, - 0.8627450980392157, - 1.0, - 0.7333333333, - 0.0, - 0.8666666666666667, - 1.0, - 0.6980392157, - 0.0, - 0.8705882352941177, - 1.0, - 0.6823529412, - 0.0, - 0.8745098039215686, - 1.0, - 0.6666666667, - 0.0, - 0.8784313725490196, - 1.0, - 0.6470588235, - 0.0, - 0.8823529411764706, - 1.0, - 0.631372549, - 0.0, - 0.8862745098039215, - 1.0, - 0.6, - 0.0, - 0.8901960784313725, - 1.0, - 0.5803921569, - 0.0, - 0.8941176470588236, - 1.0, - 0.5647058824, - 0.0, - 0.8980392156862745, - 1.0, - 0.5490196078, - 0.0, - 0.9019607843137255, - 1.0, - 0.5137254902, - 0.0, - 0.9058823529411765, - 1.0, - 0.4980392157, - 0.0, - 0.9098039215686274, - 1.0, - 0.4823529412, - 0.0, - 0.9137254901960784, - 1.0, - 0.4666666667, - 0.0, - 0.9176470588235294, - 1.0, - 0.431372549, - 0.0, - 0.9215686274509803, - 1.0, - 0.4156862745, - 0.0, - 0.9254901960784314, - 1.0, - 0.4, - 0.0, - 0.9294117647058824, - 1.0, - 0.3803921569, - 0.0, - 0.9333333333333333, - 1.0, - 0.3490196078, - 0.0, - 0.9372549019607843, - 1.0, - 0.3333333333, - 0.0, - 0.9411764705882354, - 1.0, - 0.3137254902, - 0.0, - 0.9450980392156864, - 1.0, - 0.2980392157, - 0.0, - 0.9490196078431372, - 1.0, - 0.2823529412, - 0.0, - 0.9529411764705882, - 1.0, - 0.2470588235, - 0.0, - 0.9568627450980394, - 1.0, - 0.231372549, - 0.0, - 0.9607843137254903, - 1.0, - 0.2156862745, - 0.0, - 0.9647058823529413, - 1.0, - 0.2, - 0.0, - 0.9686274509803922, - 1.0, - 0.1647058824, - 0.0, - 0.9725490196078431, - 1.0, - 0.1490196078, - 0.0, - 0.9764705882352941, - 1.0, - 0.1333333333, - 0.0, - 0.9803921568627451, - 1.0, - 0.1137254902, - 0.0, - 0.984313725490196, - 1.0, - 0.0823529412, - 0.0, - 0.9882352941176471, - 1.0, - 0.0666666667, - 0.0, - 0.9921568627450981, - 1.0, - 0.0470588235, - 0.0, - 0.996078431372549, - 1.0, - 0.031372549, - 0.0, - 1.0, - 1.0, - 0.0, - 0.0, + 0.0, 0.0, 0.0, 0.0, 0.00392156862745098, 0.0156862745, 0.0, 0.0117647059, 0.00784313725490196, + 0.0352941176, 0.0, 0.0274509804, 0.011764705882352941, 0.0509803922, 0.0, 0.0392156863, + 0.01568627450980392, 0.0705882353, 0.0, 0.0549019608, 0.0196078431372549, 0.0862745098, 0.0, + 0.0745098039, 0.023529411764705882, 0.1058823529, 0.0, 0.0901960784, 0.027450980392156862, + 0.1215686275, 0.0, 0.1098039216, 0.03137254901960784, 0.1411764706, 0.0, 0.1254901961, + 0.03529411764705882, 0.1568627451, 0.0, 0.1490196078, 0.0392156862745098, 0.1764705882, 0.0, + 0.168627451, 0.043137254901960784, 0.1960784314, 0.0, 0.1882352941, 0.047058823529411764, + 0.2117647059, 0.0, 0.2078431373, 0.050980392156862744, 0.2274509804, 0.0, 0.231372549, + 0.054901960784313725, 0.2392156863, 0.0, 0.2470588235, 0.05882352941176471, 0.2509803922, 0.0, + 0.2666666667, 0.06274509803921569, 0.2666666667, 0.0, 0.2823529412, 0.06666666666666667, + 0.2705882353, 0.0, 0.3019607843, 0.07058823529411765, 0.2823529412, 0.0, 0.3176470588, + 0.07450980392156863, 0.2901960784, 0.0, 0.337254902, 0.0784313725490196, 0.3019607843, 0.0, + 0.3568627451, 0.08235294117647059, 0.3098039216, 0.0, 0.3725490196, 0.08627450980392157, + 0.3137254902, 0.0, 0.3921568627, 0.09019607843137255, 0.3215686275, 0.0, 0.4078431373, + 0.09411764705882353, 0.3254901961, 0.0, 0.4274509804, 0.09803921568627451, 0.3333333333, 0.0, + 0.4431372549, 0.10196078431372549, 0.3294117647, 0.0, 0.462745098, 0.10588235294117647, + 0.337254902, 0.0, 0.4784313725, 0.10980392156862745, 0.3411764706, 0.0, 0.4980392157, + 0.11372549019607843, 0.3450980392, 0.0, 0.5176470588, 0.11764705882352942, 0.337254902, 0.0, + 0.5333333333, 0.12156862745098039, 0.3411764706, 0.0, 0.5529411765, 0.12549019607843137, + 0.3411764706, 0.0, 0.568627451, 0.12941176470588237, 0.3411764706, 0.0, 0.5882352941, + 0.13333333333333333, 0.3333333333, 0.0, 0.6039215686, 0.13725490196078433, 0.3294117647, 0.0, + 0.6235294118, 0.1411764705882353, 0.3294117647, 0.0, 0.6392156863, 0.1450980392156863, + 0.3294117647, 0.0, 0.6588235294, 0.14901960784313725, 0.3254901961, 0.0, 0.6784313725, + 0.15294117647058825, 0.3098039216, 0.0, 0.6941176471, 0.1568627450980392, 0.3058823529, 0.0, + 0.7137254902, 0.1607843137254902, 0.3019607843, 0.0, 0.7294117647, 0.16470588235294117, + 0.2980392157, 0.0, 0.7490196078, 0.16862745098039217, 0.2784313725, 0.0, 0.7647058824, + 0.17254901960784313, 0.2745098039, 0.0, 0.7843137255, 0.17647058823529413, 0.2666666667, 0.0, + 0.8, 0.1803921568627451, 0.2588235294, 0.0, 0.8196078431, 0.1843137254901961, 0.2352941176, + 0.0, 0.8392156863, 0.18823529411764706, 0.2274509804, 0.0, 0.8549019608, 0.19215686274509805, + 0.2156862745, 0.0, 0.8745098039, 0.19607843137254902, 0.2078431373, 0.0, 0.8901960784, 0.2, + 0.1803921569, 0.0, 0.9098039216, 0.20392156862745098, 0.168627451, 0.0, 0.9254901961, + 0.20784313725490197, 0.1568627451, 0.0, 0.9450980392, 0.21176470588235294, 0.1411764706, 0.0, + 0.9607843137, 0.21568627450980393, 0.1294117647, 0.0, 0.9803921569, 0.2196078431372549, + 0.0980392157, 0.0, 1.0, 0.2235294117647059, 0.0823529412, 0.0, 1.0, 0.22745098039215686, + 0.062745098, 0.0, 1.0, 0.23137254901960785, 0.0470588235, 0.0, 1.0, 0.23529411764705885, + 0.0156862745, 0.0, 1.0, 0.23921568627450984, 0.0, 0.0, 1.0, 0.24313725490196078, 0.0, + 0.0156862745, 1.0, 0.24705882352941178, 0.0, 0.031372549, 1.0, 0.25098039215686274, 0.0, + 0.062745098, 1.0, 0.2549019607843137, 0.0, 0.0823529412, 1.0, 0.25882352941176473, 0.0, + 0.0980392157, 1.0, 0.2627450980392157, 0.0, 0.1137254902, 1.0, 0.26666666666666666, 0.0, + 0.1490196078, 1.0, 0.27058823529411763, 0.0, 0.1647058824, 1.0, 0.27450980392156865, 0.0, + 0.1803921569, 1.0, 0.2784313725490196, 0.0, 0.2, 1.0, 0.2823529411764706, 0.0, 0.2156862745, + 1.0, 0.28627450980392155, 0.0, 0.2470588235, 1.0, 0.2901960784313726, 0.0, 0.262745098, 1.0, + 0.29411764705882354, 0.0, 0.2823529412, 1.0, 0.2980392156862745, 0.0, 0.2980392157, 1.0, + 0.30196078431372547, 0.0, 0.3294117647, 1.0, 0.3058823529411765, 0.0, 0.3490196078, 1.0, + 0.30980392156862746, 0.0, 0.3647058824, 1.0, 0.3137254901960784, 0.0, 0.3803921569, 1.0, + 0.3176470588235294, 0.0, 0.4156862745, 1.0, 0.3215686274509804, 0.0, 0.431372549, 1.0, + 0.3254901960784314, 0.0, 0.4470588235, 1.0, 0.32941176470588235, 0.0, 0.4666666667, 1.0, + 0.3333333333333333, 0.0, 0.4980392157, 1.0, 0.33725490196078434, 0.0, 0.5137254902, 1.0, + 0.3411764705882353, 0.0, 0.5294117647, 1.0, 0.34509803921568627, 0.0, 0.5490196078, 1.0, + 0.34901960784313724, 0.0, 0.5647058824, 1.0, 0.35294117647058826, 0.0, 0.5960784314, 1.0, + 0.3568627450980392, 0.0, 0.6156862745, 1.0, 0.3607843137254902, 0.0, 0.631372549, 1.0, + 0.36470588235294116, 0.0, 0.6470588235, 1.0, 0.3686274509803922, 0.0, 0.6823529412, 1.0, + 0.37254901960784315, 0.0, 0.6980392157, 1.0, 0.3764705882352941, 0.0, 0.7137254902, 1.0, + 0.3803921568627451, 0.0, 0.7333333333, 1.0, 0.3843137254901961, 0.0, 0.7647058824, 1.0, + 0.38823529411764707, 0.0, 0.7803921569, 1.0, 0.39215686274509803, 0.0, 0.7960784314, 1.0, + 0.396078431372549, 0.0, 0.8156862745, 1.0, 0.4, 0.0, 0.8470588235, 1.0, 0.403921568627451, + 0.0, 0.862745098, 1.0, 0.40784313725490196, 0.0, 0.8823529412, 1.0, 0.4117647058823529, 0.0, + 0.8980392157, 1.0, 0.41568627450980394, 0.0, 0.9137254902, 1.0, 0.4196078431372549, 0.0, + 0.9490196078, 1.0, 0.4235294117647059, 0.0, 0.9647058824, 1.0, 0.42745098039215684, 0.0, + 0.9803921569, 1.0, 0.43137254901960786, 0.0, 1.0, 1.0, 0.43529411764705883, 0.0, 1.0, + 0.9647058824, 0.4392156862745098, 0.0, 1.0, 0.9490196078, 0.44313725490196076, 0.0, 1.0, + 0.9333333333, 0.4470588235294118, 0.0, 1.0, 0.9137254902, 0.45098039215686275, 0.0, 1.0, + 0.8823529412, 0.4549019607843137, 0.0, 1.0, 0.862745098, 0.4588235294117647, 0.0, 1.0, + 0.8470588235, 0.4627450980392157, 0.0, 1.0, 0.831372549, 0.4666666666666667, 0.0, 1.0, + 0.7960784314, 0.4705882352941177, 0.0, 1.0, 0.7803921569, 0.4745098039215686, 0.0, 1.0, + 0.7647058824, 0.4784313725490197, 0.0, 1.0, 0.7490196078, 0.48235294117647065, 0.0, 1.0, + 0.7333333333, 0.48627450980392156, 0.0, 1.0, 0.6980392157, 0.49019607843137253, 0.0, 1.0, + 0.6823529412, 0.49411764705882355, 0.0, 1.0, 0.6666666667, 0.4980392156862745, 0.0, 1.0, + 0.6470588235, 0.5019607843137255, 0.0, 1.0, 0.6156862745, 0.5058823529411764, 0.0, 1.0, + 0.5960784314, 0.5098039215686274, 0.0, 1.0, 0.5803921569, 0.5137254901960784, 0.0, 1.0, + 0.5647058824, 0.5176470588235295, 0.0, 1.0, 0.5294117647, 0.5215686274509804, 0.0, 1.0, + 0.5137254902, 0.5254901960784314, 0.0, 1.0, 0.4980392157, 0.5294117647058824, 0.0, 1.0, + 0.4823529412, 0.5333333333333333, 0.0, 1.0, 0.4470588235, 0.5372549019607843, 0.0, 1.0, + 0.431372549, 0.5411764705882353, 0.0, 1.0, 0.4156862745, 0.5450980392156862, 0.0, 1.0, 0.4, + 0.5490196078431373, 0.0, 1.0, 0.3803921569, 0.5529411764705883, 0.0, 1.0, 0.3490196078, + 0.5568627450980392, 0.0, 1.0, 0.3294117647, 0.5607843137254902, 0.0, 1.0, 0.3137254902, + 0.5647058823529412, 0.0, 1.0, 0.2980392157, 0.5686274509803921, 0.0, 1.0, 0.262745098, + 0.5725490196078431, 0.0, 1.0, 0.2470588235, 0.5764705882352941, 0.0, 1.0, 0.231372549, + 0.5803921568627451, 0.0, 1.0, 0.2156862745, 0.5843137254901961, 0.0, 1.0, 0.1803921569, + 0.5882352941176471, 0.0, 1.0, 0.1647058824, 0.592156862745098, 0.0, 1.0, 0.1490196078, + 0.596078431372549, 0.0, 1.0, 0.1333333333, 0.6, 0.0, 1.0, 0.0980392157, 0.6039215686274509, + 0.0, 1.0, 0.0823529412, 0.6078431372549019, 0.0, 1.0, 0.062745098, 0.611764705882353, 0.0, + 1.0, 0.0470588235, 0.615686274509804, 0.0, 1.0, 0.031372549, 0.6196078431372549, 0.0, 1.0, + 0.0, 0.6235294117647059, 0.0156862745, 1.0, 0.0, 0.6274509803921569, 0.031372549, 1.0, 0.0, + 0.6313725490196078, 0.0470588235, 1.0, 0.0, 0.6352941176470588, 0.0823529412, 1.0, 0.0, + 0.6392156862745098, 0.0980392157, 1.0, 0.0, 0.6431372549019608, 0.1137254902, 1.0, 0.0, + 0.6470588235294118, 0.1294117647, 1.0, 0.0, 0.6509803921568628, 0.1647058824, 1.0, 0.0, + 0.6549019607843137, 0.1803921569, 1.0, 0.0, 0.6588235294117647, 0.2, 1.0, 0.0, + 0.6627450980392157, 0.2156862745, 1.0, 0.0, 0.6666666666666666, 0.2470588235, 1.0, 0.0, + 0.6705882352941176, 0.262745098, 1.0, 0.0, 0.6745098039215687, 0.2823529412, 1.0, 0.0, + 0.6784313725490196, 0.2980392157, 1.0, 0.0, 0.6823529411764706, 0.3137254902, 1.0, 0.0, + 0.6862745098039216, 0.3490196078, 1.0, 0.0, 0.6901960784313725, 0.3647058824, 1.0, 0.0, + 0.6941176470588235, 0.3803921569, 1.0, 0.0, 0.6980392156862745, 0.3960784314, 1.0, 0.0, + 0.7019607843137254, 0.431372549, 1.0, 0.0, 0.7058823529411765, 0.4470588235, 1.0, 0.0, + 0.7098039215686275, 0.4666666667, 1.0, 0.0, 0.7137254901960784, 0.4823529412, 1.0, 0.0, + 0.7176470588235294, 0.5137254902, 1.0, 0.0, 0.7215686274509804, 0.5294117647, 1.0, 0.0, + 0.7254901960784313, 0.5490196078, 1.0, 0.0, 0.7294117647058823, 0.5647058824, 1.0, 0.0, + 0.7333333333333333, 0.6, 1.0, 0.0, 0.7372549019607844, 0.6156862745, 1.0, 0.0, + 0.7411764705882353, 0.631372549, 1.0, 0.0, 0.7450980392156863, 0.6470588235, 1.0, 0.0, + 0.7490196078431373, 0.662745098, 1.0, 0.0, 0.7529411764705882, 0.6980392157, 1.0, 0.0, + 0.7568627450980392, 0.7137254902, 1.0, 0.0, 0.7607843137254902, 0.7333333333, 1.0, 0.0, + 0.7647058823529411, 0.7490196078, 1.0, 0.0, 0.7686274509803922, 0.7803921569, 1.0, 0.0, + 0.7725490196078432, 0.7960784314, 1.0, 0.0, 0.7764705882352941, 0.8156862745, 1.0, 0.0, + 0.7803921568627451, 0.831372549, 1.0, 0.0, 0.7843137254901961, 0.8666666667, 1.0, 0.0, + 0.788235294117647, 0.8823529412, 1.0, 0.0, 0.792156862745098, 0.8980392157, 1.0, 0.0, + 0.796078431372549, 0.9137254902, 1.0, 0.0, 0.8, 0.9490196078, 1.0, 0.0, 0.803921568627451, + 0.9647058824, 1.0, 0.0, 0.807843137254902, 0.9803921569, 1.0, 0.0, 0.8117647058823529, 1.0, + 1.0, 0.0, 0.8156862745098039, 1.0, 0.9803921569, 0.0, 0.8196078431372549, 1.0, 0.9490196078, + 0.0, 0.8235294117647058, 1.0, 0.9333333333, 0.0, 0.8274509803921568, 1.0, 0.9137254902, 0.0, + 0.8313725490196079, 1.0, 0.8980392157, 0.0, 0.8352941176470589, 1.0, 0.8666666667, 0.0, + 0.8392156862745098, 1.0, 0.8470588235, 0.0, 0.8431372549019608, 1.0, 0.831372549, 0.0, + 0.8470588235294118, 1.0, 0.8156862745, 0.0, 0.8509803921568627, 1.0, 0.7803921569, 0.0, + 0.8549019607843137, 1.0, 0.7647058824, 0.0, 0.8588235294117647, 1.0, 0.7490196078, 0.0, + 0.8627450980392157, 1.0, 0.7333333333, 0.0, 0.8666666666666667, 1.0, 0.6980392157, 0.0, + 0.8705882352941177, 1.0, 0.6823529412, 0.0, 0.8745098039215686, 1.0, 0.6666666667, 0.0, + 0.8784313725490196, 1.0, 0.6470588235, 0.0, 0.8823529411764706, 1.0, 0.631372549, 0.0, + 0.8862745098039215, 1.0, 0.6, 0.0, 0.8901960784313725, 1.0, 0.5803921569, 0.0, + 0.8941176470588236, 1.0, 0.5647058824, 0.0, 0.8980392156862745, 1.0, 0.5490196078, 0.0, + 0.9019607843137255, 1.0, 0.5137254902, 0.0, 0.9058823529411765, 1.0, 0.4980392157, 0.0, + 0.9098039215686274, 1.0, 0.4823529412, 0.0, 0.9137254901960784, 1.0, 0.4666666667, 0.0, + 0.9176470588235294, 1.0, 0.431372549, 0.0, 0.9215686274509803, 1.0, 0.4156862745, 0.0, + 0.9254901960784314, 1.0, 0.4, 0.0, 0.9294117647058824, 1.0, 0.3803921569, 0.0, + 0.9333333333333333, 1.0, 0.3490196078, 0.0, 0.9372549019607843, 1.0, 0.3333333333, 0.0, + 0.9411764705882354, 1.0, 0.3137254902, 0.0, 0.9450980392156864, 1.0, 0.2980392157, 0.0, + 0.9490196078431372, 1.0, 0.2823529412, 0.0, 0.9529411764705882, 1.0, 0.2470588235, 0.0, + 0.9568627450980394, 1.0, 0.231372549, 0.0, 0.9607843137254903, 1.0, 0.2156862745, 0.0, + 0.9647058823529413, 1.0, 0.2, 0.0, 0.9686274509803922, 1.0, 0.1647058824, 0.0, + 0.9725490196078431, 1.0, 0.1490196078, 0.0, 0.9764705882352941, 1.0, 0.1333333333, 0.0, + 0.9803921568627451, 1.0, 0.1137254902, 0.0, 0.984313725490196, 1.0, 0.0823529412, 0.0, + 0.9882352941176471, 1.0, 0.0666666667, 0.0, 0.9921568627450981, 1.0, 0.0470588235, 0.0, + 0.996078431372549, 1.0, 0.031372549, 0.0, 1.0, 1.0, 0.0, 0.0, ], }, { ColorSpace: 'RGB', Name: 'suv', RGBPoints: [ - 0.0, - 1.0, - 1.0, - 1.0, - 0.00392156862745098, - 1.0, - 1.0, - 1.0, - 0.00784313725490196, - 1.0, - 1.0, - 1.0, - 0.011764705882352941, - 1.0, - 1.0, - 1.0, - 0.01568627450980392, - 1.0, - 1.0, - 1.0, - 0.0196078431372549, - 1.0, - 1.0, - 1.0, - 0.023529411764705882, - 1.0, - 1.0, - 1.0, - 0.027450980392156862, - 1.0, - 1.0, - 1.0, - 0.03137254901960784, - 1.0, - 1.0, - 1.0, - 0.03529411764705882, - 1.0, - 1.0, - 1.0, - 0.0392156862745098, - 1.0, - 1.0, - 1.0, - 0.043137254901960784, - 1.0, - 1.0, - 1.0, - 0.047058823529411764, - 1.0, - 1.0, - 1.0, - 0.050980392156862744, - 1.0, - 1.0, - 1.0, - 0.054901960784313725, - 1.0, - 1.0, - 1.0, - 0.05882352941176471, - 1.0, - 1.0, - 1.0, - 0.06274509803921569, - 1.0, - 1.0, - 1.0, - 0.06666666666666667, - 1.0, - 1.0, - 1.0, - 0.07058823529411765, - 1.0, - 1.0, - 1.0, - 0.07450980392156863, - 1.0, - 1.0, - 1.0, - 0.0784313725490196, - 1.0, - 1.0, - 1.0, - 0.08235294117647059, - 1.0, - 1.0, - 1.0, - 0.08627450980392157, - 1.0, - 1.0, - 1.0, - 0.09019607843137255, - 1.0, - 1.0, - 1.0, - 0.09411764705882353, - 1.0, - 1.0, - 1.0, - 0.09803921568627451, - 1.0, - 1.0, - 1.0, - 0.10196078431372549, - 0.737254902, - 0.737254902, - 0.737254902, - 0.10588235294117647, - 0.737254902, - 0.737254902, - 0.737254902, - 0.10980392156862745, - 0.737254902, - 0.737254902, - 0.737254902, - 0.11372549019607843, - 0.737254902, - 0.737254902, - 0.737254902, - 0.11764705882352942, - 0.737254902, - 0.737254902, - 0.737254902, - 0.12156862745098039, - 0.737254902, - 0.737254902, - 0.737254902, - 0.12549019607843137, - 0.737254902, - 0.737254902, - 0.737254902, - 0.12941176470588237, - 0.737254902, - 0.737254902, - 0.737254902, - 0.13333333333333333, - 0.737254902, - 0.737254902, - 0.737254902, - 0.13725490196078433, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1411764705882353, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1450980392156863, - 0.737254902, - 0.737254902, - 0.737254902, - 0.14901960784313725, - 0.737254902, - 0.737254902, - 0.737254902, - 0.15294117647058825, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1568627450980392, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1607843137254902, - 0.737254902, - 0.737254902, - 0.737254902, - 0.16470588235294117, - 0.737254902, - 0.737254902, - 0.737254902, - 0.16862745098039217, - 0.737254902, - 0.737254902, - 0.737254902, - 0.17254901960784313, - 0.737254902, - 0.737254902, - 0.737254902, - 0.17647058823529413, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1803921568627451, - 0.737254902, - 0.737254902, - 0.737254902, - 0.1843137254901961, - 0.737254902, - 0.737254902, - 0.737254902, - 0.18823529411764706, - 0.737254902, - 0.737254902, - 0.737254902, - 0.19215686274509805, - 0.737254902, - 0.737254902, - 0.737254902, - 0.19607843137254902, - 0.737254902, - 0.737254902, - 0.737254902, - 0.2, - 0.737254902, - 0.737254902, - 0.737254902, - 0.20392156862745098, - 0.431372549, - 0.0, - 0.568627451, - 0.20784313725490197, - 0.431372549, - 0.0, - 0.568627451, - 0.21176470588235294, - 0.431372549, - 0.0, - 0.568627451, - 0.21568627450980393, - 0.431372549, - 0.0, - 0.568627451, - 0.2196078431372549, - 0.431372549, - 0.0, - 0.568627451, - 0.2235294117647059, - 0.431372549, - 0.0, - 0.568627451, - 0.22745098039215686, - 0.431372549, - 0.0, - 0.568627451, - 0.23137254901960785, - 0.431372549, - 0.0, - 0.568627451, - 0.23529411764705885, - 0.431372549, - 0.0, - 0.568627451, - 0.23921568627450984, - 0.431372549, - 0.0, - 0.568627451, - 0.24313725490196078, - 0.431372549, - 0.0, - 0.568627451, - 0.24705882352941178, - 0.431372549, - 0.0, - 0.568627451, - 0.25098039215686274, - 0.431372549, - 0.0, - 0.568627451, - 0.2549019607843137, - 0.431372549, - 0.0, - 0.568627451, - 0.25882352941176473, - 0.431372549, - 0.0, - 0.568627451, - 0.2627450980392157, - 0.431372549, - 0.0, - 0.568627451, - 0.26666666666666666, - 0.431372549, - 0.0, - 0.568627451, - 0.27058823529411763, - 0.431372549, - 0.0, - 0.568627451, - 0.27450980392156865, - 0.431372549, - 0.0, - 0.568627451, - 0.2784313725490196, - 0.431372549, - 0.0, - 0.568627451, - 0.2823529411764706, - 0.431372549, - 0.0, - 0.568627451, - 0.28627450980392155, - 0.431372549, - 0.0, - 0.568627451, - 0.2901960784313726, - 0.431372549, - 0.0, - 0.568627451, - 0.29411764705882354, - 0.431372549, - 0.0, - 0.568627451, - 0.2980392156862745, - 0.431372549, - 0.0, - 0.568627451, - 0.30196078431372547, - 0.431372549, - 0.0, - 0.568627451, - 0.3058823529411765, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.30980392156862746, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3137254901960784, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3176470588235294, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3215686274509804, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3254901960784314, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.32941176470588235, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3333333333333333, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.33725490196078434, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3411764705882353, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.34509803921568627, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.34901960784313724, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.35294117647058826, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3568627450980392, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3607843137254902, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.36470588235294116, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3686274509803922, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.37254901960784315, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3764705882352941, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3803921568627451, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.3843137254901961, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.38823529411764707, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.39215686274509803, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.396078431372549, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.4, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.403921568627451, - 0.2509803922, - 0.3333333333, - 0.6509803922, - 0.40784313725490196, - 0.0, - 0.8, - 1.0, - 0.4117647058823529, - 0.0, - 0.8, - 1.0, - 0.41568627450980394, - 0.0, - 0.8, - 1.0, - 0.4196078431372549, - 0.0, - 0.8, - 1.0, - 0.4235294117647059, - 0.0, - 0.8, - 1.0, - 0.42745098039215684, - 0.0, - 0.8, - 1.0, - 0.43137254901960786, - 0.0, - 0.8, - 1.0, - 0.43529411764705883, - 0.0, - 0.8, - 1.0, - 0.4392156862745098, - 0.0, - 0.8, - 1.0, - 0.44313725490196076, - 0.0, - 0.8, - 1.0, - 0.4470588235294118, - 0.0, - 0.8, - 1.0, - 0.45098039215686275, - 0.0, - 0.8, - 1.0, - 0.4549019607843137, - 0.0, - 0.8, - 1.0, - 0.4588235294117647, - 0.0, - 0.8, - 1.0, - 0.4627450980392157, - 0.0, - 0.8, - 1.0, - 0.4666666666666667, - 0.0, - 0.8, - 1.0, - 0.4705882352941177, - 0.0, - 0.8, - 1.0, - 0.4745098039215686, - 0.0, - 0.8, - 1.0, - 0.4784313725490197, - 0.0, - 0.8, - 1.0, - 0.48235294117647065, - 0.0, - 0.8, - 1.0, - 0.48627450980392156, - 0.0, - 0.8, - 1.0, - 0.49019607843137253, - 0.0, - 0.8, - 1.0, - 0.49411764705882355, - 0.0, - 0.8, - 1.0, - 0.4980392156862745, - 0.0, - 0.8, - 1.0, - 0.5019607843137255, - 0.0, - 0.8, - 1.0, - 0.5058823529411764, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5098039215686274, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5137254901960784, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5176470588235295, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5215686274509804, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5254901960784314, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5294117647058824, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5333333333333333, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5372549019607843, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5411764705882353, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5450980392156862, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5490196078431373, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5529411764705883, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5568627450980392, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5607843137254902, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5647058823529412, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5686274509803921, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5725490196078431, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5764705882352941, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5803921568627451, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5843137254901961, - 0.0, - 0.6666666667, - 0.5333333333, - 0.5882352941176471, - 0.0, - 0.6666666667, - 0.5333333333, - 0.592156862745098, - 0.0, - 0.6666666667, - 0.5333333333, - 0.596078431372549, - 0.0, - 0.6666666667, - 0.5333333333, - 0.6, - 0.0, - 0.6666666667, - 0.5333333333, - 0.6039215686274509, - 0.0, - 0.6666666667, - 0.5333333333, - 0.6078431372549019, - 0.4, - 1.0, - 0.4, - 0.611764705882353, - 0.4, - 1.0, - 0.4, - 0.615686274509804, - 0.4, - 1.0, - 0.4, - 0.6196078431372549, - 0.4, - 1.0, - 0.4, - 0.6235294117647059, - 0.4, - 1.0, - 0.4, - 0.6274509803921569, - 0.4, - 1.0, - 0.4, - 0.6313725490196078, - 0.4, - 1.0, - 0.4, - 0.6352941176470588, - 0.4, - 1.0, - 0.4, - 0.6392156862745098, - 0.4, - 1.0, - 0.4, - 0.6431372549019608, - 0.4, - 1.0, - 0.4, - 0.6470588235294118, - 0.4, - 1.0, - 0.4, - 0.6509803921568628, - 0.4, - 1.0, - 0.4, - 0.6549019607843137, - 0.4, - 1.0, - 0.4, - 0.6588235294117647, - 0.4, - 1.0, - 0.4, - 0.6627450980392157, - 0.4, - 1.0, - 0.4, - 0.6666666666666666, - 0.4, - 1.0, - 0.4, - 0.6705882352941176, - 0.4, - 1.0, - 0.4, - 0.6745098039215687, - 0.4, - 1.0, - 0.4, - 0.6784313725490196, - 0.4, - 1.0, - 0.4, - 0.6823529411764706, - 0.4, - 1.0, - 0.4, - 0.6862745098039216, - 0.4, - 1.0, - 0.4, - 0.6901960784313725, - 0.4, - 1.0, - 0.4, - 0.6941176470588235, - 0.4, - 1.0, - 0.4, - 0.6980392156862745, - 0.4, - 1.0, - 0.4, - 0.7019607843137254, - 0.4, - 1.0, - 0.4, - 0.7058823529411765, - 1.0, - 0.9490196078, - 0.0, - 0.7098039215686275, - 1.0, - 0.9490196078, - 0.0, - 0.7137254901960784, - 1.0, - 0.9490196078, - 0.0, - 0.7176470588235294, - 1.0, - 0.9490196078, - 0.0, - 0.7215686274509804, - 1.0, - 0.9490196078, - 0.0, - 0.7254901960784313, - 1.0, - 0.9490196078, - 0.0, - 0.7294117647058823, - 1.0, - 0.9490196078, - 0.0, - 0.7333333333333333, - 1.0, - 0.9490196078, - 0.0, - 0.7372549019607844, - 1.0, - 0.9490196078, - 0.0, - 0.7411764705882353, - 1.0, - 0.9490196078, - 0.0, - 0.7450980392156863, - 1.0, - 0.9490196078, - 0.0, - 0.7490196078431373, - 1.0, - 0.9490196078, - 0.0, - 0.7529411764705882, - 1.0, - 0.9490196078, - 0.0, - 0.7568627450980392, - 1.0, - 0.9490196078, - 0.0, - 0.7607843137254902, - 1.0, - 0.9490196078, - 0.0, - 0.7647058823529411, - 1.0, - 0.9490196078, - 0.0, - 0.7686274509803922, - 1.0, - 0.9490196078, - 0.0, - 0.7725490196078432, - 1.0, - 0.9490196078, - 0.0, - 0.7764705882352941, - 1.0, - 0.9490196078, - 0.0, - 0.7803921568627451, - 1.0, - 0.9490196078, - 0.0, - 0.7843137254901961, - 1.0, - 0.9490196078, - 0.0, - 0.788235294117647, - 1.0, - 0.9490196078, - 0.0, - 0.792156862745098, - 1.0, - 0.9490196078, - 0.0, - 0.796078431372549, - 1.0, - 0.9490196078, - 0.0, - 0.8, - 1.0, - 0.9490196078, - 0.0, - 0.803921568627451, - 1.0, - 0.9490196078, - 0.0, - 0.807843137254902, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8117647058823529, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8156862745098039, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8196078431372549, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8235294117647058, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8274509803921568, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8313725490196079, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8352941176470589, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8392156862745098, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8431372549019608, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8470588235294118, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8509803921568627, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8549019607843137, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8588235294117647, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8627450980392157, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8666666666666667, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8705882352941177, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8745098039215686, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8784313725490196, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8823529411764706, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8862745098039215, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8901960784313725, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8941176470588236, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.8980392156862745, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.9019607843137255, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.9058823529411765, - 0.9490196078, - 0.6509803922, - 0.2509803922, - 0.9098039215686274, - 1.0, - 0.0, - 0.0, - 0.9137254901960784, - 1.0, - 0.0, - 0.0, - 0.9176470588235294, - 1.0, - 0.0, - 0.0, - 0.9215686274509803, - 1.0, - 0.0, - 0.0, - 0.9254901960784314, - 1.0, - 0.0, - 0.0, - 0.9294117647058824, - 1.0, - 0.0, - 0.0, - 0.9333333333333333, - 1.0, - 0.0, - 0.0, - 0.9372549019607843, - 1.0, - 0.0, - 0.0, - 0.9411764705882354, - 1.0, - 0.0, - 0.0, - 0.9450980392156864, - 1.0, - 0.0, - 0.0, - 0.9490196078431372, - 1.0, - 0.0, - 0.0, - 0.9529411764705882, - 1.0, - 0.0, - 0.0, - 0.9568627450980394, - 1.0, - 0.0, - 0.0, - 0.9607843137254903, - 1.0, - 0.0, - 0.0, - 0.9647058823529413, - 1.0, - 0.0, - 0.0, - 0.9686274509803922, - 1.0, - 0.0, - 0.0, - 0.9725490196078431, - 1.0, - 0.0, - 0.0, - 0.9764705882352941, - 1.0, - 0.0, - 0.0, - 0.9803921568627451, - 1.0, - 0.0, - 0.0, - 0.984313725490196, - 1.0, - 0.0, - 0.0, - 0.9882352941176471, - 1.0, - 0.0, - 0.0, - 0.9921568627450981, - 1.0, - 0.0, - 0.0, - 0.996078431372549, - 1.0, - 0.0, - 0.0, - 1.0, - 1.0, - 0.0, - 0.0, + 0.0, 1.0, 1.0, 1.0, 0.00392156862745098, 1.0, 1.0, 1.0, 0.00784313725490196, 1.0, 1.0, 1.0, + 0.011764705882352941, 1.0, 1.0, 1.0, 0.01568627450980392, 1.0, 1.0, 1.0, 0.0196078431372549, + 1.0, 1.0, 1.0, 0.023529411764705882, 1.0, 1.0, 1.0, 0.027450980392156862, 1.0, 1.0, 1.0, + 0.03137254901960784, 1.0, 1.0, 1.0, 0.03529411764705882, 1.0, 1.0, 1.0, 0.0392156862745098, + 1.0, 1.0, 1.0, 0.043137254901960784, 1.0, 1.0, 1.0, 0.047058823529411764, 1.0, 1.0, 1.0, + 0.050980392156862744, 1.0, 1.0, 1.0, 0.054901960784313725, 1.0, 1.0, 1.0, 0.05882352941176471, + 1.0, 1.0, 1.0, 0.06274509803921569, 1.0, 1.0, 1.0, 0.06666666666666667, 1.0, 1.0, 1.0, + 0.07058823529411765, 1.0, 1.0, 1.0, 0.07450980392156863, 1.0, 1.0, 1.0, 0.0784313725490196, + 1.0, 1.0, 1.0, 0.08235294117647059, 1.0, 1.0, 1.0, 0.08627450980392157, 1.0, 1.0, 1.0, + 0.09019607843137255, 1.0, 1.0, 1.0, 0.09411764705882353, 1.0, 1.0, 1.0, 0.09803921568627451, + 1.0, 1.0, 1.0, 0.10196078431372549, 0.737254902, 0.737254902, 0.737254902, + 0.10588235294117647, 0.737254902, 0.737254902, 0.737254902, 0.10980392156862745, 0.737254902, + 0.737254902, 0.737254902, 0.11372549019607843, 0.737254902, 0.737254902, 0.737254902, + 0.11764705882352942, 0.737254902, 0.737254902, 0.737254902, 0.12156862745098039, 0.737254902, + 0.737254902, 0.737254902, 0.12549019607843137, 0.737254902, 0.737254902, 0.737254902, + 0.12941176470588237, 0.737254902, 0.737254902, 0.737254902, 0.13333333333333333, 0.737254902, + 0.737254902, 0.737254902, 0.13725490196078433, 0.737254902, 0.737254902, 0.737254902, + 0.1411764705882353, 0.737254902, 0.737254902, 0.737254902, 0.1450980392156863, 0.737254902, + 0.737254902, 0.737254902, 0.14901960784313725, 0.737254902, 0.737254902, 0.737254902, + 0.15294117647058825, 0.737254902, 0.737254902, 0.737254902, 0.1568627450980392, 0.737254902, + 0.737254902, 0.737254902, 0.1607843137254902, 0.737254902, 0.737254902, 0.737254902, + 0.16470588235294117, 0.737254902, 0.737254902, 0.737254902, 0.16862745098039217, 0.737254902, + 0.737254902, 0.737254902, 0.17254901960784313, 0.737254902, 0.737254902, 0.737254902, + 0.17647058823529413, 0.737254902, 0.737254902, 0.737254902, 0.1803921568627451, 0.737254902, + 0.737254902, 0.737254902, 0.1843137254901961, 0.737254902, 0.737254902, 0.737254902, + 0.18823529411764706, 0.737254902, 0.737254902, 0.737254902, 0.19215686274509805, 0.737254902, + 0.737254902, 0.737254902, 0.19607843137254902, 0.737254902, 0.737254902, 0.737254902, 0.2, + 0.737254902, 0.737254902, 0.737254902, 0.20392156862745098, 0.431372549, 0.0, 0.568627451, + 0.20784313725490197, 0.431372549, 0.0, 0.568627451, 0.21176470588235294, 0.431372549, 0.0, + 0.568627451, 0.21568627450980393, 0.431372549, 0.0, 0.568627451, 0.2196078431372549, + 0.431372549, 0.0, 0.568627451, 0.2235294117647059, 0.431372549, 0.0, 0.568627451, + 0.22745098039215686, 0.431372549, 0.0, 0.568627451, 0.23137254901960785, 0.431372549, 0.0, + 0.568627451, 0.23529411764705885, 0.431372549, 0.0, 0.568627451, 0.23921568627450984, + 0.431372549, 0.0, 0.568627451, 0.24313725490196078, 0.431372549, 0.0, 0.568627451, + 0.24705882352941178, 0.431372549, 0.0, 0.568627451, 0.25098039215686274, 0.431372549, 0.0, + 0.568627451, 0.2549019607843137, 0.431372549, 0.0, 0.568627451, 0.25882352941176473, + 0.431372549, 0.0, 0.568627451, 0.2627450980392157, 0.431372549, 0.0, 0.568627451, + 0.26666666666666666, 0.431372549, 0.0, 0.568627451, 0.27058823529411763, 0.431372549, 0.0, + 0.568627451, 0.27450980392156865, 0.431372549, 0.0, 0.568627451, 0.2784313725490196, + 0.431372549, 0.0, 0.568627451, 0.2823529411764706, 0.431372549, 0.0, 0.568627451, + 0.28627450980392155, 0.431372549, 0.0, 0.568627451, 0.2901960784313726, 0.431372549, 0.0, + 0.568627451, 0.29411764705882354, 0.431372549, 0.0, 0.568627451, 0.2980392156862745, + 0.431372549, 0.0, 0.568627451, 0.30196078431372547, 0.431372549, 0.0, 0.568627451, + 0.3058823529411765, 0.2509803922, 0.3333333333, 0.6509803922, 0.30980392156862746, + 0.2509803922, 0.3333333333, 0.6509803922, 0.3137254901960784, 0.2509803922, 0.3333333333, + 0.6509803922, 0.3176470588235294, 0.2509803922, 0.3333333333, 0.6509803922, + 0.3215686274509804, 0.2509803922, 0.3333333333, 0.6509803922, 0.3254901960784314, + 0.2509803922, 0.3333333333, 0.6509803922, 0.32941176470588235, 0.2509803922, 0.3333333333, + 0.6509803922, 0.3333333333333333, 0.2509803922, 0.3333333333, 0.6509803922, + 0.33725490196078434, 0.2509803922, 0.3333333333, 0.6509803922, 0.3411764705882353, + 0.2509803922, 0.3333333333, 0.6509803922, 0.34509803921568627, 0.2509803922, 0.3333333333, + 0.6509803922, 0.34901960784313724, 0.2509803922, 0.3333333333, 0.6509803922, + 0.35294117647058826, 0.2509803922, 0.3333333333, 0.6509803922, 0.3568627450980392, + 0.2509803922, 0.3333333333, 0.6509803922, 0.3607843137254902, 0.2509803922, 0.3333333333, + 0.6509803922, 0.36470588235294116, 0.2509803922, 0.3333333333, 0.6509803922, + 0.3686274509803922, 0.2509803922, 0.3333333333, 0.6509803922, 0.37254901960784315, + 0.2509803922, 0.3333333333, 0.6509803922, 0.3764705882352941, 0.2509803922, 0.3333333333, + 0.6509803922, 0.3803921568627451, 0.2509803922, 0.3333333333, 0.6509803922, + 0.3843137254901961, 0.2509803922, 0.3333333333, 0.6509803922, 0.38823529411764707, + 0.2509803922, 0.3333333333, 0.6509803922, 0.39215686274509803, 0.2509803922, 0.3333333333, + 0.6509803922, 0.396078431372549, 0.2509803922, 0.3333333333, 0.6509803922, 0.4, 0.2509803922, + 0.3333333333, 0.6509803922, 0.403921568627451, 0.2509803922, 0.3333333333, 0.6509803922, + 0.40784313725490196, 0.0, 0.8, 1.0, 0.4117647058823529, 0.0, 0.8, 1.0, 0.41568627450980394, + 0.0, 0.8, 1.0, 0.4196078431372549, 0.0, 0.8, 1.0, 0.4235294117647059, 0.0, 0.8, 1.0, + 0.42745098039215684, 0.0, 0.8, 1.0, 0.43137254901960786, 0.0, 0.8, 1.0, 0.43529411764705883, + 0.0, 0.8, 1.0, 0.4392156862745098, 0.0, 0.8, 1.0, 0.44313725490196076, 0.0, 0.8, 1.0, + 0.4470588235294118, 0.0, 0.8, 1.0, 0.45098039215686275, 0.0, 0.8, 1.0, 0.4549019607843137, + 0.0, 0.8, 1.0, 0.4588235294117647, 0.0, 0.8, 1.0, 0.4627450980392157, 0.0, 0.8, 1.0, + 0.4666666666666667, 0.0, 0.8, 1.0, 0.4705882352941177, 0.0, 0.8, 1.0, 0.4745098039215686, 0.0, + 0.8, 1.0, 0.4784313725490197, 0.0, 0.8, 1.0, 0.48235294117647065, 0.0, 0.8, 1.0, + 0.48627450980392156, 0.0, 0.8, 1.0, 0.49019607843137253, 0.0, 0.8, 1.0, 0.49411764705882355, + 0.0, 0.8, 1.0, 0.4980392156862745, 0.0, 0.8, 1.0, 0.5019607843137255, 0.0, 0.8, 1.0, + 0.5058823529411764, 0.0, 0.6666666667, 0.5333333333, 0.5098039215686274, 0.0, 0.6666666667, + 0.5333333333, 0.5137254901960784, 0.0, 0.6666666667, 0.5333333333, 0.5176470588235295, 0.0, + 0.6666666667, 0.5333333333, 0.5215686274509804, 0.0, 0.6666666667, 0.5333333333, + 0.5254901960784314, 0.0, 0.6666666667, 0.5333333333, 0.5294117647058824, 0.0, 0.6666666667, + 0.5333333333, 0.5333333333333333, 0.0, 0.6666666667, 0.5333333333, 0.5372549019607843, 0.0, + 0.6666666667, 0.5333333333, 0.5411764705882353, 0.0, 0.6666666667, 0.5333333333, + 0.5450980392156862, 0.0, 0.6666666667, 0.5333333333, 0.5490196078431373, 0.0, 0.6666666667, + 0.5333333333, 0.5529411764705883, 0.0, 0.6666666667, 0.5333333333, 0.5568627450980392, 0.0, + 0.6666666667, 0.5333333333, 0.5607843137254902, 0.0, 0.6666666667, 0.5333333333, + 0.5647058823529412, 0.0, 0.6666666667, 0.5333333333, 0.5686274509803921, 0.0, 0.6666666667, + 0.5333333333, 0.5725490196078431, 0.0, 0.6666666667, 0.5333333333, 0.5764705882352941, 0.0, + 0.6666666667, 0.5333333333, 0.5803921568627451, 0.0, 0.6666666667, 0.5333333333, + 0.5843137254901961, 0.0, 0.6666666667, 0.5333333333, 0.5882352941176471, 0.0, 0.6666666667, + 0.5333333333, 0.592156862745098, 0.0, 0.6666666667, 0.5333333333, 0.596078431372549, 0.0, + 0.6666666667, 0.5333333333, 0.6, 0.0, 0.6666666667, 0.5333333333, 0.6039215686274509, 0.0, + 0.6666666667, 0.5333333333, 0.6078431372549019, 0.4, 1.0, 0.4, 0.611764705882353, 0.4, 1.0, + 0.4, 0.615686274509804, 0.4, 1.0, 0.4, 0.6196078431372549, 0.4, 1.0, 0.4, 0.6235294117647059, + 0.4, 1.0, 0.4, 0.6274509803921569, 0.4, 1.0, 0.4, 0.6313725490196078, 0.4, 1.0, 0.4, + 0.6352941176470588, 0.4, 1.0, 0.4, 0.6392156862745098, 0.4, 1.0, 0.4, 0.6431372549019608, 0.4, + 1.0, 0.4, 0.6470588235294118, 0.4, 1.0, 0.4, 0.6509803921568628, 0.4, 1.0, 0.4, + 0.6549019607843137, 0.4, 1.0, 0.4, 0.6588235294117647, 0.4, 1.0, 0.4, 0.6627450980392157, 0.4, + 1.0, 0.4, 0.6666666666666666, 0.4, 1.0, 0.4, 0.6705882352941176, 0.4, 1.0, 0.4, + 0.6745098039215687, 0.4, 1.0, 0.4, 0.6784313725490196, 0.4, 1.0, 0.4, 0.6823529411764706, 0.4, + 1.0, 0.4, 0.6862745098039216, 0.4, 1.0, 0.4, 0.6901960784313725, 0.4, 1.0, 0.4, + 0.6941176470588235, 0.4, 1.0, 0.4, 0.6980392156862745, 0.4, 1.0, 0.4, 0.7019607843137254, 0.4, + 1.0, 0.4, 0.7058823529411765, 1.0, 0.9490196078, 0.0, 0.7098039215686275, 1.0, 0.9490196078, + 0.0, 0.7137254901960784, 1.0, 0.9490196078, 0.0, 0.7176470588235294, 1.0, 0.9490196078, 0.0, + 0.7215686274509804, 1.0, 0.9490196078, 0.0, 0.7254901960784313, 1.0, 0.9490196078, 0.0, + 0.7294117647058823, 1.0, 0.9490196078, 0.0, 0.7333333333333333, 1.0, 0.9490196078, 0.0, + 0.7372549019607844, 1.0, 0.9490196078, 0.0, 0.7411764705882353, 1.0, 0.9490196078, 0.0, + 0.7450980392156863, 1.0, 0.9490196078, 0.0, 0.7490196078431373, 1.0, 0.9490196078, 0.0, + 0.7529411764705882, 1.0, 0.9490196078, 0.0, 0.7568627450980392, 1.0, 0.9490196078, 0.0, + 0.7607843137254902, 1.0, 0.9490196078, 0.0, 0.7647058823529411, 1.0, 0.9490196078, 0.0, + 0.7686274509803922, 1.0, 0.9490196078, 0.0, 0.7725490196078432, 1.0, 0.9490196078, 0.0, + 0.7764705882352941, 1.0, 0.9490196078, 0.0, 0.7803921568627451, 1.0, 0.9490196078, 0.0, + 0.7843137254901961, 1.0, 0.9490196078, 0.0, 0.788235294117647, 1.0, 0.9490196078, 0.0, + 0.792156862745098, 1.0, 0.9490196078, 0.0, 0.796078431372549, 1.0, 0.9490196078, 0.0, 0.8, + 1.0, 0.9490196078, 0.0, 0.803921568627451, 1.0, 0.9490196078, 0.0, 0.807843137254902, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8117647058823529, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8156862745098039, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8196078431372549, 0.9490196078, 0.6509803922, 0.2509803922, 0.8235294117647058, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8274509803921568, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8313725490196079, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8352941176470589, 0.9490196078, 0.6509803922, 0.2509803922, 0.8392156862745098, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8431372549019608, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8470588235294118, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8509803921568627, 0.9490196078, 0.6509803922, 0.2509803922, 0.8549019607843137, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8588235294117647, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8627450980392157, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8666666666666667, 0.9490196078, 0.6509803922, 0.2509803922, 0.8705882352941177, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8745098039215686, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8784313725490196, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8823529411764706, 0.9490196078, 0.6509803922, 0.2509803922, 0.8862745098039215, + 0.9490196078, 0.6509803922, 0.2509803922, 0.8901960784313725, 0.9490196078, 0.6509803922, + 0.2509803922, 0.8941176470588236, 0.9490196078, 0.6509803922, 0.2509803922, + 0.8980392156862745, 0.9490196078, 0.6509803922, 0.2509803922, 0.9019607843137255, + 0.9490196078, 0.6509803922, 0.2509803922, 0.9058823529411765, 0.9490196078, 0.6509803922, + 0.2509803922, 0.9098039215686274, 1.0, 0.0, 0.0, 0.9137254901960784, 1.0, 0.0, 0.0, + 0.9176470588235294, 1.0, 0.0, 0.0, 0.9215686274509803, 1.0, 0.0, 0.0, 0.9254901960784314, 1.0, + 0.0, 0.0, 0.9294117647058824, 1.0, 0.0, 0.0, 0.9333333333333333, 1.0, 0.0, 0.0, + 0.9372549019607843, 1.0, 0.0, 0.0, 0.9411764705882354, 1.0, 0.0, 0.0, 0.9450980392156864, 1.0, + 0.0, 0.0, 0.9490196078431372, 1.0, 0.0, 0.0, 0.9529411764705882, 1.0, 0.0, 0.0, + 0.9568627450980394, 1.0, 0.0, 0.0, 0.9607843137254903, 1.0, 0.0, 0.0, 0.9647058823529413, 1.0, + 0.0, 0.0, 0.9686274509803922, 1.0, 0.0, 0.0, 0.9725490196078431, 1.0, 0.0, 0.0, + 0.9764705882352941, 1.0, 0.0, 0.0, 0.9803921568627451, 1.0, 0.0, 0.0, 0.984313725490196, 1.0, + 0.0, 0.0, 0.9882352941176471, 1.0, 0.0, 0.0, 0.9921568627450981, 1.0, 0.0, 0.0, + 0.996078431372549, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, ], }, { ColorSpace: 'RGB', Name: 'ge_256', RGBPoints: [ - 0.0, - 0.0039215686, - 0.0078431373, - 0.0078431373, - 0.00392156862745098, - 0.0039215686, - 0.0078431373, - 0.0078431373, - 0.00784313725490196, - 0.0039215686, - 0.0078431373, - 0.0117647059, - 0.011764705882352941, - 0.0039215686, - 0.0117647059, - 0.0156862745, - 0.01568627450980392, - 0.0039215686, - 0.0117647059, - 0.0196078431, - 0.0196078431372549, - 0.0039215686, - 0.0156862745, - 0.0235294118, - 0.023529411764705882, - 0.0039215686, - 0.0156862745, - 0.0274509804, - 0.027450980392156862, - 0.0039215686, - 0.0196078431, - 0.031372549, - 0.03137254901960784, - 0.0039215686, - 0.0196078431, - 0.0352941176, - 0.03529411764705882, - 0.0039215686, - 0.0235294118, - 0.0392156863, - 0.0392156862745098, - 0.0039215686, - 0.0235294118, - 0.0431372549, - 0.043137254901960784, - 0.0039215686, - 0.0274509804, - 0.0470588235, - 0.047058823529411764, - 0.0039215686, - 0.0274509804, - 0.0509803922, - 0.050980392156862744, - 0.0039215686, - 0.031372549, - 0.0549019608, - 0.054901960784313725, - 0.0039215686, - 0.031372549, - 0.0588235294, - 0.05882352941176471, - 0.0039215686, - 0.0352941176, - 0.062745098, - 0.06274509803921569, - 0.0039215686, - 0.0352941176, - 0.0666666667, - 0.06666666666666667, - 0.0039215686, - 0.0392156863, - 0.0705882353, - 0.07058823529411765, - 0.0039215686, - 0.0392156863, - 0.0745098039, - 0.07450980392156863, - 0.0039215686, - 0.0431372549, - 0.0784313725, - 0.0784313725490196, - 0.0039215686, - 0.0431372549, - 0.0823529412, - 0.08235294117647059, - 0.0039215686, - 0.0470588235, - 0.0862745098, - 0.08627450980392157, - 0.0039215686, - 0.0470588235, - 0.0901960784, - 0.09019607843137255, - 0.0039215686, - 0.0509803922, - 0.0941176471, - 0.09411764705882353, - 0.0039215686, - 0.0509803922, - 0.0980392157, - 0.09803921568627451, - 0.0039215686, - 0.0549019608, - 0.1019607843, - 0.10196078431372549, - 0.0039215686, - 0.0549019608, - 0.1058823529, - 0.10588235294117647, - 0.0039215686, - 0.0588235294, - 0.1098039216, - 0.10980392156862745, - 0.0039215686, - 0.0588235294, - 0.1137254902, - 0.11372549019607843, - 0.0039215686, - 0.062745098, - 0.1176470588, - 0.11764705882352942, - 0.0039215686, - 0.062745098, - 0.1215686275, - 0.12156862745098039, - 0.0039215686, - 0.0666666667, - 0.1254901961, - 0.12549019607843137, - 0.0039215686, - 0.0666666667, - 0.1294117647, - 0.12941176470588237, - 0.0039215686, - 0.0705882353, - 0.1333333333, - 0.13333333333333333, - 0.0039215686, - 0.0705882353, - 0.137254902, - 0.13725490196078433, - 0.0039215686, - 0.0745098039, - 0.1411764706, - 0.1411764705882353, - 0.0039215686, - 0.0745098039, - 0.1450980392, - 0.1450980392156863, - 0.0039215686, - 0.0784313725, - 0.1490196078, - 0.14901960784313725, - 0.0039215686, - 0.0784313725, - 0.1529411765, - 0.15294117647058825, - 0.0039215686, - 0.0823529412, - 0.1568627451, - 0.1568627450980392, - 0.0039215686, - 0.0823529412, - 0.1607843137, - 0.1607843137254902, - 0.0039215686, - 0.0862745098, - 0.1647058824, - 0.16470588235294117, - 0.0039215686, - 0.0862745098, - 0.168627451, - 0.16862745098039217, - 0.0039215686, - 0.0901960784, - 0.1725490196, - 0.17254901960784313, - 0.0039215686, - 0.0901960784, - 0.1764705882, - 0.17647058823529413, - 0.0039215686, - 0.0941176471, - 0.1803921569, - 0.1803921568627451, - 0.0039215686, - 0.0941176471, - 0.1843137255, - 0.1843137254901961, - 0.0039215686, - 0.0980392157, - 0.1882352941, - 0.18823529411764706, - 0.0039215686, - 0.0980392157, - 0.1921568627, - 0.19215686274509805, - 0.0039215686, - 0.1019607843, - 0.1960784314, - 0.19607843137254902, - 0.0039215686, - 0.1019607843, - 0.2, - 0.2, - 0.0039215686, - 0.1058823529, - 0.2039215686, - 0.20392156862745098, - 0.0039215686, - 0.1058823529, - 0.2078431373, - 0.20784313725490197, - 0.0039215686, - 0.1098039216, - 0.2117647059, - 0.21176470588235294, - 0.0039215686, - 0.1098039216, - 0.2156862745, - 0.21568627450980393, - 0.0039215686, - 0.1137254902, - 0.2196078431, - 0.2196078431372549, - 0.0039215686, - 0.1137254902, - 0.2235294118, - 0.2235294117647059, - 0.0039215686, - 0.1176470588, - 0.2274509804, - 0.22745098039215686, - 0.0039215686, - 0.1176470588, - 0.231372549, - 0.23137254901960785, - 0.0039215686, - 0.1215686275, - 0.2352941176, - 0.23529411764705885, - 0.0039215686, - 0.1215686275, - 0.2392156863, - 0.23921568627450984, - 0.0039215686, - 0.1254901961, - 0.2431372549, - 0.24313725490196078, - 0.0039215686, - 0.1254901961, - 0.2470588235, - 0.24705882352941178, - 0.0039215686, - 0.1294117647, - 0.2509803922, - 0.25098039215686274, - 0.0039215686, - 0.1294117647, - 0.2509803922, - 0.2549019607843137, - 0.0078431373, - 0.1254901961, - 0.2549019608, - 0.25882352941176473, - 0.0156862745, - 0.1254901961, - 0.2588235294, - 0.2627450980392157, - 0.0235294118, - 0.1215686275, - 0.262745098, - 0.26666666666666666, - 0.031372549, - 0.1215686275, - 0.2666666667, - 0.27058823529411763, - 0.0392156863, - 0.1176470588, - 0.2705882353, - 0.27450980392156865, - 0.0470588235, - 0.1176470588, - 0.2745098039, - 0.2784313725490196, - 0.0549019608, - 0.1137254902, - 0.2784313725, - 0.2823529411764706, - 0.062745098, - 0.1137254902, - 0.2823529412, - 0.28627450980392155, - 0.0705882353, - 0.1098039216, - 0.2862745098, - 0.2901960784313726, - 0.0784313725, - 0.1098039216, - 0.2901960784, - 0.29411764705882354, - 0.0862745098, - 0.1058823529, - 0.2941176471, - 0.2980392156862745, - 0.0941176471, - 0.1058823529, - 0.2980392157, - 0.30196078431372547, - 0.1019607843, - 0.1019607843, - 0.3019607843, - 0.3058823529411765, - 0.1098039216, - 0.1019607843, - 0.3058823529, - 0.30980392156862746, - 0.1176470588, - 0.0980392157, - 0.3098039216, - 0.3137254901960784, - 0.1254901961, - 0.0980392157, - 0.3137254902, - 0.3176470588235294, - 0.1333333333, - 0.0941176471, - 0.3176470588, - 0.3215686274509804, - 0.1411764706, - 0.0941176471, - 0.3215686275, - 0.3254901960784314, - 0.1490196078, - 0.0901960784, - 0.3254901961, - 0.32941176470588235, - 0.1568627451, - 0.0901960784, - 0.3294117647, - 0.3333333333333333, - 0.1647058824, - 0.0862745098, - 0.3333333333, - 0.33725490196078434, - 0.1725490196, - 0.0862745098, - 0.337254902, - 0.3411764705882353, - 0.1803921569, - 0.0823529412, - 0.3411764706, - 0.34509803921568627, - 0.1882352941, - 0.0823529412, - 0.3450980392, - 0.34901960784313724, - 0.1960784314, - 0.0784313725, - 0.3490196078, - 0.35294117647058826, - 0.2039215686, - 0.0784313725, - 0.3529411765, - 0.3568627450980392, - 0.2117647059, - 0.0745098039, - 0.3568627451, - 0.3607843137254902, - 0.2196078431, - 0.0745098039, - 0.3607843137, - 0.36470588235294116, - 0.2274509804, - 0.0705882353, - 0.3647058824, - 0.3686274509803922, - 0.2352941176, - 0.0705882353, - 0.368627451, - 0.37254901960784315, - 0.2431372549, - 0.0666666667, - 0.3725490196, - 0.3764705882352941, - 0.2509803922, - 0.0666666667, - 0.3764705882, - 0.3803921568627451, - 0.2549019608, - 0.062745098, - 0.3803921569, - 0.3843137254901961, - 0.262745098, - 0.062745098, - 0.3843137255, - 0.38823529411764707, - 0.2705882353, - 0.0588235294, - 0.3882352941, - 0.39215686274509803, - 0.2784313725, - 0.0588235294, - 0.3921568627, - 0.396078431372549, - 0.2862745098, - 0.0549019608, - 0.3960784314, - 0.4, - 0.2941176471, - 0.0549019608, - 0.4, - 0.403921568627451, - 0.3019607843, - 0.0509803922, - 0.4039215686, - 0.40784313725490196, - 0.3098039216, - 0.0509803922, - 0.4078431373, - 0.4117647058823529, - 0.3176470588, - 0.0470588235, - 0.4117647059, - 0.41568627450980394, - 0.3254901961, - 0.0470588235, - 0.4156862745, - 0.4196078431372549, - 0.3333333333, - 0.0431372549, - 0.4196078431, - 0.4235294117647059, - 0.3411764706, - 0.0431372549, - 0.4235294118, - 0.42745098039215684, - 0.3490196078, - 0.0392156863, - 0.4274509804, - 0.43137254901960786, - 0.3568627451, - 0.0392156863, - 0.431372549, - 0.43529411764705883, - 0.3647058824, - 0.0352941176, - 0.4352941176, - 0.4392156862745098, - 0.3725490196, - 0.0352941176, - 0.4392156863, - 0.44313725490196076, - 0.3803921569, - 0.031372549, - 0.4431372549, - 0.4470588235294118, - 0.3882352941, - 0.031372549, - 0.4470588235, - 0.45098039215686275, - 0.3960784314, - 0.0274509804, - 0.4509803922, - 0.4549019607843137, - 0.4039215686, - 0.0274509804, - 0.4549019608, - 0.4588235294117647, - 0.4117647059, - 0.0235294118, - 0.4588235294, - 0.4627450980392157, - 0.4196078431, - 0.0235294118, - 0.462745098, - 0.4666666666666667, - 0.4274509804, - 0.0196078431, - 0.4666666667, - 0.4705882352941177, - 0.4352941176, - 0.0196078431, - 0.4705882353, - 0.4745098039215686, - 0.4431372549, - 0.0156862745, - 0.4745098039, - 0.4784313725490197, - 0.4509803922, - 0.0156862745, - 0.4784313725, - 0.48235294117647065, - 0.4588235294, - 0.0117647059, - 0.4823529412, - 0.48627450980392156, - 0.4666666667, - 0.0117647059, - 0.4862745098, - 0.49019607843137253, - 0.4745098039, - 0.0078431373, - 0.4901960784, - 0.49411764705882355, - 0.4823529412, - 0.0078431373, - 0.4941176471, - 0.4980392156862745, - 0.4901960784, - 0.0039215686, - 0.4980392157, - 0.5019607843137255, - 0.4980392157, - 0.0117647059, - 0.4980392157, - 0.5058823529411764, - 0.5058823529, - 0.0156862745, - 0.4901960784, - 0.5098039215686274, - 0.5137254902, - 0.0235294118, - 0.4823529412, - 0.5137254901960784, - 0.5215686275, - 0.0274509804, - 0.4745098039, - 0.5176470588235295, - 0.5294117647, - 0.0352941176, - 0.4666666667, - 0.5215686274509804, - 0.537254902, - 0.0392156863, - 0.4588235294, - 0.5254901960784314, - 0.5450980392, - 0.0470588235, - 0.4509803922, - 0.5294117647058824, - 0.5529411765, - 0.0509803922, - 0.4431372549, - 0.5333333333333333, - 0.5607843137, - 0.0588235294, - 0.4352941176, - 0.5372549019607843, - 0.568627451, - 0.062745098, - 0.4274509804, - 0.5411764705882353, - 0.5764705882, - 0.0705882353, - 0.4196078431, - 0.5450980392156862, - 0.5843137255, - 0.0745098039, - 0.4117647059, - 0.5490196078431373, - 0.5921568627, - 0.0823529412, - 0.4039215686, - 0.5529411764705883, - 0.6, - 0.0862745098, - 0.3960784314, - 0.5568627450980392, - 0.6078431373, - 0.0941176471, - 0.3882352941, - 0.5607843137254902, - 0.6156862745, - 0.0980392157, - 0.3803921569, - 0.5647058823529412, - 0.6235294118, - 0.1058823529, - 0.3725490196, - 0.5686274509803921, - 0.631372549, - 0.1098039216, - 0.3647058824, - 0.5725490196078431, - 0.6392156863, - 0.1176470588, - 0.3568627451, - 0.5764705882352941, - 0.6470588235, - 0.1215686275, - 0.3490196078, - 0.5803921568627451, - 0.6549019608, - 0.1294117647, - 0.3411764706, - 0.5843137254901961, - 0.662745098, - 0.1333333333, - 0.3333333333, - 0.5882352941176471, - 0.6705882353, - 0.1411764706, - 0.3254901961, - 0.592156862745098, - 0.6784313725, - 0.1450980392, - 0.3176470588, - 0.596078431372549, - 0.6862745098, - 0.1529411765, - 0.3098039216, - 0.6, - 0.6941176471, - 0.1568627451, - 0.3019607843, - 0.6039215686274509, - 0.7019607843, - 0.1647058824, - 0.2941176471, - 0.6078431372549019, - 0.7098039216, - 0.168627451, - 0.2862745098, - 0.611764705882353, - 0.7176470588, - 0.1764705882, - 0.2784313725, - 0.615686274509804, - 0.7254901961, - 0.1803921569, - 0.2705882353, - 0.6196078431372549, - 0.7333333333, - 0.1882352941, - 0.262745098, - 0.6235294117647059, - 0.7411764706, - 0.1921568627, - 0.2549019608, - 0.6274509803921569, - 0.7490196078, - 0.2, - 0.2509803922, - 0.6313725490196078, - 0.7529411765, - 0.2039215686, - 0.2431372549, - 0.6352941176470588, - 0.7607843137, - 0.2117647059, - 0.2352941176, - 0.6392156862745098, - 0.768627451, - 0.2156862745, - 0.2274509804, - 0.6431372549019608, - 0.7764705882, - 0.2235294118, - 0.2196078431, - 0.6470588235294118, - 0.7843137255, - 0.2274509804, - 0.2117647059, - 0.6509803921568628, - 0.7921568627, - 0.2352941176, - 0.2039215686, - 0.6549019607843137, - 0.8, - 0.2392156863, - 0.1960784314, - 0.6588235294117647, - 0.8078431373, - 0.2470588235, - 0.1882352941, - 0.6627450980392157, - 0.8156862745, - 0.2509803922, - 0.1803921569, - 0.6666666666666666, - 0.8235294118, - 0.2549019608, - 0.1725490196, - 0.6705882352941176, - 0.831372549, - 0.2588235294, - 0.1647058824, - 0.6745098039215687, - 0.8392156863, - 0.2666666667, - 0.1568627451, - 0.6784313725490196, - 0.8470588235, - 0.2705882353, - 0.1490196078, - 0.6823529411764706, - 0.8549019608, - 0.2784313725, - 0.1411764706, - 0.6862745098039216, - 0.862745098, - 0.2823529412, - 0.1333333333, - 0.6901960784313725, - 0.8705882353, - 0.2901960784, - 0.1254901961, - 0.6941176470588235, - 0.8784313725, - 0.2941176471, - 0.1176470588, - 0.6980392156862745, - 0.8862745098, - 0.3019607843, - 0.1098039216, - 0.7019607843137254, - 0.8941176471, - 0.3058823529, - 0.1019607843, - 0.7058823529411765, - 0.9019607843, - 0.3137254902, - 0.0941176471, - 0.7098039215686275, - 0.9098039216, - 0.3176470588, - 0.0862745098, - 0.7137254901960784, - 0.9176470588, - 0.3254901961, - 0.0784313725, - 0.7176470588235294, - 0.9254901961, - 0.3294117647, - 0.0705882353, - 0.7215686274509804, - 0.9333333333, - 0.337254902, - 0.062745098, - 0.7254901960784313, - 0.9411764706, - 0.3411764706, - 0.0549019608, - 0.7294117647058823, - 0.9490196078, - 0.3490196078, - 0.0470588235, - 0.7333333333333333, - 0.9568627451, - 0.3529411765, - 0.0392156863, - 0.7372549019607844, - 0.9647058824, - 0.3607843137, - 0.031372549, - 0.7411764705882353, - 0.9725490196, - 0.3647058824, - 0.0235294118, - 0.7450980392156863, - 0.9803921569, - 0.3725490196, - 0.0156862745, - 0.7490196078431373, - 0.9882352941, - 0.3725490196, - 0.0039215686, - 0.7529411764705882, - 0.9960784314, - 0.3843137255, - 0.0156862745, - 0.7568627450980392, - 0.9960784314, - 0.3921568627, - 0.031372549, - 0.7607843137254902, - 0.9960784314, - 0.4039215686, - 0.0470588235, - 0.7647058823529411, - 0.9960784314, - 0.4117647059, - 0.062745098, - 0.7686274509803922, - 0.9960784314, - 0.4235294118, - 0.0784313725, - 0.7725490196078432, - 0.9960784314, - 0.431372549, - 0.0941176471, - 0.7764705882352941, - 0.9960784314, - 0.4431372549, - 0.1098039216, - 0.7803921568627451, - 0.9960784314, - 0.4509803922, - 0.1254901961, - 0.7843137254901961, - 0.9960784314, - 0.462745098, - 0.1411764706, - 0.788235294117647, - 0.9960784314, - 0.4705882353, - 0.1568627451, - 0.792156862745098, - 0.9960784314, - 0.4823529412, - 0.1725490196, - 0.796078431372549, - 0.9960784314, - 0.4901960784, - 0.1882352941, - 0.8, - 0.9960784314, - 0.5019607843, - 0.2039215686, - 0.803921568627451, - 0.9960784314, - 0.5098039216, - 0.2196078431, - 0.807843137254902, - 0.9960784314, - 0.5215686275, - 0.2352941176, - 0.8117647058823529, - 0.9960784314, - 0.5294117647, - 0.2509803922, - 0.8156862745098039, - 0.9960784314, - 0.5411764706, - 0.262745098, - 0.8196078431372549, - 0.9960784314, - 0.5490196078, - 0.2784313725, - 0.8235294117647058, - 0.9960784314, - 0.5607843137, - 0.2941176471, - 0.8274509803921568, - 0.9960784314, - 0.568627451, - 0.3098039216, - 0.8313725490196079, - 0.9960784314, - 0.5803921569, - 0.3254901961, - 0.8352941176470589, - 0.9960784314, - 0.5882352941, - 0.3411764706, - 0.8392156862745098, - 0.9960784314, - 0.6, - 0.3568627451, - 0.8431372549019608, - 0.9960784314, - 0.6078431373, - 0.3725490196, - 0.8470588235294118, - 0.9960784314, - 0.6196078431, - 0.3882352941, - 0.8509803921568627, - 0.9960784314, - 0.6274509804, - 0.4039215686, - 0.8549019607843137, - 0.9960784314, - 0.6392156863, - 0.4196078431, - 0.8588235294117647, - 0.9960784314, - 0.6470588235, - 0.4352941176, - 0.8627450980392157, - 0.9960784314, - 0.6588235294, - 0.4509803922, - 0.8666666666666667, - 0.9960784314, - 0.6666666667, - 0.4666666667, - 0.8705882352941177, - 0.9960784314, - 0.6784313725, - 0.4823529412, - 0.8745098039215686, - 0.9960784314, - 0.6862745098, - 0.4980392157, - 0.8784313725490196, - 0.9960784314, - 0.6980392157, - 0.5137254902, - 0.8823529411764706, - 0.9960784314, - 0.7058823529, - 0.5294117647, - 0.8862745098039215, - 0.9960784314, - 0.7176470588, - 0.5450980392, - 0.8901960784313725, - 0.9960784314, - 0.7254901961, - 0.5607843137, - 0.8941176470588236, - 0.9960784314, - 0.737254902, - 0.5764705882, - 0.8980392156862745, - 0.9960784314, - 0.7450980392, - 0.5921568627, - 0.9019607843137255, - 0.9960784314, - 0.7529411765, - 0.6078431373, - 0.9058823529411765, - 0.9960784314, - 0.7607843137, - 0.6235294118, - 0.9098039215686274, - 0.9960784314, - 0.7725490196, - 0.6392156863, - 0.9137254901960784, - 0.9960784314, - 0.7803921569, - 0.6549019608, - 0.9176470588235294, - 0.9960784314, - 0.7921568627, - 0.6705882353, - 0.9215686274509803, - 0.9960784314, - 0.8, - 0.6862745098, - 0.9254901960784314, - 0.9960784314, - 0.8117647059, - 0.7019607843, - 0.9294117647058824, - 0.9960784314, - 0.8196078431, - 0.7176470588, - 0.9333333333333333, - 0.9960784314, - 0.831372549, - 0.7333333333, - 0.9372549019607843, - 0.9960784314, - 0.8392156863, - 0.7490196078, - 0.9411764705882354, - 0.9960784314, - 0.8509803922, - 0.7607843137, - 0.9450980392156864, - 0.9960784314, - 0.8588235294, - 0.7764705882, - 0.9490196078431372, - 0.9960784314, - 0.8705882353, - 0.7921568627, - 0.9529411764705882, - 0.9960784314, - 0.8784313725, - 0.8078431373, - 0.9568627450980394, - 0.9960784314, - 0.8901960784, - 0.8235294118, - 0.9607843137254903, - 0.9960784314, - 0.8980392157, - 0.8392156863, - 0.9647058823529413, - 0.9960784314, - 0.9098039216, - 0.8549019608, - 0.9686274509803922, - 0.9960784314, - 0.9176470588, - 0.8705882353, - 0.9725490196078431, - 0.9960784314, - 0.9294117647, - 0.8862745098, - 0.9764705882352941, - 0.9960784314, - 0.937254902, - 0.9019607843, - 0.9803921568627451, - 0.9960784314, - 0.9490196078, - 0.9176470588, - 0.984313725490196, - 0.9960784314, - 0.9568627451, - 0.9333333333, - 0.9882352941176471, - 0.9960784314, - 0.968627451, - 0.9490196078, - 0.9921568627450981, - 0.9960784314, - 0.9764705882, - 0.9647058824, - 0.996078431372549, - 0.9960784314, - 0.9882352941, - 0.9803921569, - 1.0, - 0.9960784314, - 0.9882352941, - 0.9803921569, + 0.0, 0.0039215686, 0.0078431373, 0.0078431373, 0.00392156862745098, 0.0039215686, + 0.0078431373, 0.0078431373, 0.00784313725490196, 0.0039215686, 0.0078431373, 0.0117647059, + 0.011764705882352941, 0.0039215686, 0.0117647059, 0.0156862745, 0.01568627450980392, + 0.0039215686, 0.0117647059, 0.0196078431, 0.0196078431372549, 0.0039215686, 0.0156862745, + 0.0235294118, 0.023529411764705882, 0.0039215686, 0.0156862745, 0.0274509804, + 0.027450980392156862, 0.0039215686, 0.0196078431, 0.031372549, 0.03137254901960784, + 0.0039215686, 0.0196078431, 0.0352941176, 0.03529411764705882, 0.0039215686, 0.0235294118, + 0.0392156863, 0.0392156862745098, 0.0039215686, 0.0235294118, 0.0431372549, + 0.043137254901960784, 0.0039215686, 0.0274509804, 0.0470588235, 0.047058823529411764, + 0.0039215686, 0.0274509804, 0.0509803922, 0.050980392156862744, 0.0039215686, 0.031372549, + 0.0549019608, 0.054901960784313725, 0.0039215686, 0.031372549, 0.0588235294, + 0.05882352941176471, 0.0039215686, 0.0352941176, 0.062745098, 0.06274509803921569, + 0.0039215686, 0.0352941176, 0.0666666667, 0.06666666666666667, 0.0039215686, 0.0392156863, + 0.0705882353, 0.07058823529411765, 0.0039215686, 0.0392156863, 0.0745098039, + 0.07450980392156863, 0.0039215686, 0.0431372549, 0.0784313725, 0.0784313725490196, + 0.0039215686, 0.0431372549, 0.0823529412, 0.08235294117647059, 0.0039215686, 0.0470588235, + 0.0862745098, 0.08627450980392157, 0.0039215686, 0.0470588235, 0.0901960784, + 0.09019607843137255, 0.0039215686, 0.0509803922, 0.0941176471, 0.09411764705882353, + 0.0039215686, 0.0509803922, 0.0980392157, 0.09803921568627451, 0.0039215686, 0.0549019608, + 0.1019607843, 0.10196078431372549, 0.0039215686, 0.0549019608, 0.1058823529, + 0.10588235294117647, 0.0039215686, 0.0588235294, 0.1098039216, 0.10980392156862745, + 0.0039215686, 0.0588235294, 0.1137254902, 0.11372549019607843, 0.0039215686, 0.062745098, + 0.1176470588, 0.11764705882352942, 0.0039215686, 0.062745098, 0.1215686275, + 0.12156862745098039, 0.0039215686, 0.0666666667, 0.1254901961, 0.12549019607843137, + 0.0039215686, 0.0666666667, 0.1294117647, 0.12941176470588237, 0.0039215686, 0.0705882353, + 0.1333333333, 0.13333333333333333, 0.0039215686, 0.0705882353, 0.137254902, + 0.13725490196078433, 0.0039215686, 0.0745098039, 0.1411764706, 0.1411764705882353, + 0.0039215686, 0.0745098039, 0.1450980392, 0.1450980392156863, 0.0039215686, 0.0784313725, + 0.1490196078, 0.14901960784313725, 0.0039215686, 0.0784313725, 0.1529411765, + 0.15294117647058825, 0.0039215686, 0.0823529412, 0.1568627451, 0.1568627450980392, + 0.0039215686, 0.0823529412, 0.1607843137, 0.1607843137254902, 0.0039215686, 0.0862745098, + 0.1647058824, 0.16470588235294117, 0.0039215686, 0.0862745098, 0.168627451, + 0.16862745098039217, 0.0039215686, 0.0901960784, 0.1725490196, 0.17254901960784313, + 0.0039215686, 0.0901960784, 0.1764705882, 0.17647058823529413, 0.0039215686, 0.0941176471, + 0.1803921569, 0.1803921568627451, 0.0039215686, 0.0941176471, 0.1843137255, + 0.1843137254901961, 0.0039215686, 0.0980392157, 0.1882352941, 0.18823529411764706, + 0.0039215686, 0.0980392157, 0.1921568627, 0.19215686274509805, 0.0039215686, 0.1019607843, + 0.1960784314, 0.19607843137254902, 0.0039215686, 0.1019607843, 0.2, 0.2, 0.0039215686, + 0.1058823529, 0.2039215686, 0.20392156862745098, 0.0039215686, 0.1058823529, 0.2078431373, + 0.20784313725490197, 0.0039215686, 0.1098039216, 0.2117647059, 0.21176470588235294, + 0.0039215686, 0.1098039216, 0.2156862745, 0.21568627450980393, 0.0039215686, 0.1137254902, + 0.2196078431, 0.2196078431372549, 0.0039215686, 0.1137254902, 0.2235294118, + 0.2235294117647059, 0.0039215686, 0.1176470588, 0.2274509804, 0.22745098039215686, + 0.0039215686, 0.1176470588, 0.231372549, 0.23137254901960785, 0.0039215686, 0.1215686275, + 0.2352941176, 0.23529411764705885, 0.0039215686, 0.1215686275, 0.2392156863, + 0.23921568627450984, 0.0039215686, 0.1254901961, 0.2431372549, 0.24313725490196078, + 0.0039215686, 0.1254901961, 0.2470588235, 0.24705882352941178, 0.0039215686, 0.1294117647, + 0.2509803922, 0.25098039215686274, 0.0039215686, 0.1294117647, 0.2509803922, + 0.2549019607843137, 0.0078431373, 0.1254901961, 0.2549019608, 0.25882352941176473, + 0.0156862745, 0.1254901961, 0.2588235294, 0.2627450980392157, 0.0235294118, 0.1215686275, + 0.262745098, 0.26666666666666666, 0.031372549, 0.1215686275, 0.2666666667, + 0.27058823529411763, 0.0392156863, 0.1176470588, 0.2705882353, 0.27450980392156865, + 0.0470588235, 0.1176470588, 0.2745098039, 0.2784313725490196, 0.0549019608, 0.1137254902, + 0.2784313725, 0.2823529411764706, 0.062745098, 0.1137254902, 0.2823529412, + 0.28627450980392155, 0.0705882353, 0.1098039216, 0.2862745098, 0.2901960784313726, + 0.0784313725, 0.1098039216, 0.2901960784, 0.29411764705882354, 0.0862745098, 0.1058823529, + 0.2941176471, 0.2980392156862745, 0.0941176471, 0.1058823529, 0.2980392157, + 0.30196078431372547, 0.1019607843, 0.1019607843, 0.3019607843, 0.3058823529411765, + 0.1098039216, 0.1019607843, 0.3058823529, 0.30980392156862746, 0.1176470588, 0.0980392157, + 0.3098039216, 0.3137254901960784, 0.1254901961, 0.0980392157, 0.3137254902, + 0.3176470588235294, 0.1333333333, 0.0941176471, 0.3176470588, 0.3215686274509804, + 0.1411764706, 0.0941176471, 0.3215686275, 0.3254901960784314, 0.1490196078, 0.0901960784, + 0.3254901961, 0.32941176470588235, 0.1568627451, 0.0901960784, 0.3294117647, + 0.3333333333333333, 0.1647058824, 0.0862745098, 0.3333333333, 0.33725490196078434, + 0.1725490196, 0.0862745098, 0.337254902, 0.3411764705882353, 0.1803921569, 0.0823529412, + 0.3411764706, 0.34509803921568627, 0.1882352941, 0.0823529412, 0.3450980392, + 0.34901960784313724, 0.1960784314, 0.0784313725, 0.3490196078, 0.35294117647058826, + 0.2039215686, 0.0784313725, 0.3529411765, 0.3568627450980392, 0.2117647059, 0.0745098039, + 0.3568627451, 0.3607843137254902, 0.2196078431, 0.0745098039, 0.3607843137, + 0.36470588235294116, 0.2274509804, 0.0705882353, 0.3647058824, 0.3686274509803922, + 0.2352941176, 0.0705882353, 0.368627451, 0.37254901960784315, 0.2431372549, 0.0666666667, + 0.3725490196, 0.3764705882352941, 0.2509803922, 0.0666666667, 0.3764705882, + 0.3803921568627451, 0.2549019608, 0.062745098, 0.3803921569, 0.3843137254901961, 0.262745098, + 0.062745098, 0.3843137255, 0.38823529411764707, 0.2705882353, 0.0588235294, 0.3882352941, + 0.39215686274509803, 0.2784313725, 0.0588235294, 0.3921568627, 0.396078431372549, + 0.2862745098, 0.0549019608, 0.3960784314, 0.4, 0.2941176471, 0.0549019608, 0.4, + 0.403921568627451, 0.3019607843, 0.0509803922, 0.4039215686, 0.40784313725490196, + 0.3098039216, 0.0509803922, 0.4078431373, 0.4117647058823529, 0.3176470588, 0.0470588235, + 0.4117647059, 0.41568627450980394, 0.3254901961, 0.0470588235, 0.4156862745, + 0.4196078431372549, 0.3333333333, 0.0431372549, 0.4196078431, 0.4235294117647059, + 0.3411764706, 0.0431372549, 0.4235294118, 0.42745098039215684, 0.3490196078, 0.0392156863, + 0.4274509804, 0.43137254901960786, 0.3568627451, 0.0392156863, 0.431372549, + 0.43529411764705883, 0.3647058824, 0.0352941176, 0.4352941176, 0.4392156862745098, + 0.3725490196, 0.0352941176, 0.4392156863, 0.44313725490196076, 0.3803921569, 0.031372549, + 0.4431372549, 0.4470588235294118, 0.3882352941, 0.031372549, 0.4470588235, + 0.45098039215686275, 0.3960784314, 0.0274509804, 0.4509803922, 0.4549019607843137, + 0.4039215686, 0.0274509804, 0.4549019608, 0.4588235294117647, 0.4117647059, 0.0235294118, + 0.4588235294, 0.4627450980392157, 0.4196078431, 0.0235294118, 0.462745098, 0.4666666666666667, + 0.4274509804, 0.0196078431, 0.4666666667, 0.4705882352941177, 0.4352941176, 0.0196078431, + 0.4705882353, 0.4745098039215686, 0.4431372549, 0.0156862745, 0.4745098039, + 0.4784313725490197, 0.4509803922, 0.0156862745, 0.4784313725, 0.48235294117647065, + 0.4588235294, 0.0117647059, 0.4823529412, 0.48627450980392156, 0.4666666667, 0.0117647059, + 0.4862745098, 0.49019607843137253, 0.4745098039, 0.0078431373, 0.4901960784, + 0.49411764705882355, 0.4823529412, 0.0078431373, 0.4941176471, 0.4980392156862745, + 0.4901960784, 0.0039215686, 0.4980392157, 0.5019607843137255, 0.4980392157, 0.0117647059, + 0.4980392157, 0.5058823529411764, 0.5058823529, 0.0156862745, 0.4901960784, + 0.5098039215686274, 0.5137254902, 0.0235294118, 0.4823529412, 0.5137254901960784, + 0.5215686275, 0.0274509804, 0.4745098039, 0.5176470588235295, 0.5294117647, 0.0352941176, + 0.4666666667, 0.5215686274509804, 0.537254902, 0.0392156863, 0.4588235294, 0.5254901960784314, + 0.5450980392, 0.0470588235, 0.4509803922, 0.5294117647058824, 0.5529411765, 0.0509803922, + 0.4431372549, 0.5333333333333333, 0.5607843137, 0.0588235294, 0.4352941176, + 0.5372549019607843, 0.568627451, 0.062745098, 0.4274509804, 0.5411764705882353, 0.5764705882, + 0.0705882353, 0.4196078431, 0.5450980392156862, 0.5843137255, 0.0745098039, 0.4117647059, + 0.5490196078431373, 0.5921568627, 0.0823529412, 0.4039215686, 0.5529411764705883, 0.6, + 0.0862745098, 0.3960784314, 0.5568627450980392, 0.6078431373, 0.0941176471, 0.3882352941, + 0.5607843137254902, 0.6156862745, 0.0980392157, 0.3803921569, 0.5647058823529412, + 0.6235294118, 0.1058823529, 0.3725490196, 0.5686274509803921, 0.631372549, 0.1098039216, + 0.3647058824, 0.5725490196078431, 0.6392156863, 0.1176470588, 0.3568627451, + 0.5764705882352941, 0.6470588235, 0.1215686275, 0.3490196078, 0.5803921568627451, + 0.6549019608, 0.1294117647, 0.3411764706, 0.5843137254901961, 0.662745098, 0.1333333333, + 0.3333333333, 0.5882352941176471, 0.6705882353, 0.1411764706, 0.3254901961, 0.592156862745098, + 0.6784313725, 0.1450980392, 0.3176470588, 0.596078431372549, 0.6862745098, 0.1529411765, + 0.3098039216, 0.6, 0.6941176471, 0.1568627451, 0.3019607843, 0.6039215686274509, 0.7019607843, + 0.1647058824, 0.2941176471, 0.6078431372549019, 0.7098039216, 0.168627451, 0.2862745098, + 0.611764705882353, 0.7176470588, 0.1764705882, 0.2784313725, 0.615686274509804, 0.7254901961, + 0.1803921569, 0.2705882353, 0.6196078431372549, 0.7333333333, 0.1882352941, 0.262745098, + 0.6235294117647059, 0.7411764706, 0.1921568627, 0.2549019608, 0.6274509803921569, + 0.7490196078, 0.2, 0.2509803922, 0.6313725490196078, 0.7529411765, 0.2039215686, 0.2431372549, + 0.6352941176470588, 0.7607843137, 0.2117647059, 0.2352941176, 0.6392156862745098, 0.768627451, + 0.2156862745, 0.2274509804, 0.6431372549019608, 0.7764705882, 0.2235294118, 0.2196078431, + 0.6470588235294118, 0.7843137255, 0.2274509804, 0.2117647059, 0.6509803921568628, + 0.7921568627, 0.2352941176, 0.2039215686, 0.6549019607843137, 0.8, 0.2392156863, 0.1960784314, + 0.6588235294117647, 0.8078431373, 0.2470588235, 0.1882352941, 0.6627450980392157, + 0.8156862745, 0.2509803922, 0.1803921569, 0.6666666666666666, 0.8235294118, 0.2549019608, + 0.1725490196, 0.6705882352941176, 0.831372549, 0.2588235294, 0.1647058824, 0.6745098039215687, + 0.8392156863, 0.2666666667, 0.1568627451, 0.6784313725490196, 0.8470588235, 0.2705882353, + 0.1490196078, 0.6823529411764706, 0.8549019608, 0.2784313725, 0.1411764706, + 0.6862745098039216, 0.862745098, 0.2823529412, 0.1333333333, 0.6901960784313725, 0.8705882353, + 0.2901960784, 0.1254901961, 0.6941176470588235, 0.8784313725, 0.2941176471, 0.1176470588, + 0.6980392156862745, 0.8862745098, 0.3019607843, 0.1098039216, 0.7019607843137254, + 0.8941176471, 0.3058823529, 0.1019607843, 0.7058823529411765, 0.9019607843, 0.3137254902, + 0.0941176471, 0.7098039215686275, 0.9098039216, 0.3176470588, 0.0862745098, + 0.7137254901960784, 0.9176470588, 0.3254901961, 0.0784313725, 0.7176470588235294, + 0.9254901961, 0.3294117647, 0.0705882353, 0.7215686274509804, 0.9333333333, 0.337254902, + 0.062745098, 0.7254901960784313, 0.9411764706, 0.3411764706, 0.0549019608, 0.7294117647058823, + 0.9490196078, 0.3490196078, 0.0470588235, 0.7333333333333333, 0.9568627451, 0.3529411765, + 0.0392156863, 0.7372549019607844, 0.9647058824, 0.3607843137, 0.031372549, 0.7411764705882353, + 0.9725490196, 0.3647058824, 0.0235294118, 0.7450980392156863, 0.9803921569, 0.3725490196, + 0.0156862745, 0.7490196078431373, 0.9882352941, 0.3725490196, 0.0039215686, + 0.7529411764705882, 0.9960784314, 0.3843137255, 0.0156862745, 0.7568627450980392, + 0.9960784314, 0.3921568627, 0.031372549, 0.7607843137254902, 0.9960784314, 0.4039215686, + 0.0470588235, 0.7647058823529411, 0.9960784314, 0.4117647059, 0.062745098, 0.7686274509803922, + 0.9960784314, 0.4235294118, 0.0784313725, 0.7725490196078432, 0.9960784314, 0.431372549, + 0.0941176471, 0.7764705882352941, 0.9960784314, 0.4431372549, 0.1098039216, + 0.7803921568627451, 0.9960784314, 0.4509803922, 0.1254901961, 0.7843137254901961, + 0.9960784314, 0.462745098, 0.1411764706, 0.788235294117647, 0.9960784314, 0.4705882353, + 0.1568627451, 0.792156862745098, 0.9960784314, 0.4823529412, 0.1725490196, 0.796078431372549, + 0.9960784314, 0.4901960784, 0.1882352941, 0.8, 0.9960784314, 0.5019607843, 0.2039215686, + 0.803921568627451, 0.9960784314, 0.5098039216, 0.2196078431, 0.807843137254902, 0.9960784314, + 0.5215686275, 0.2352941176, 0.8117647058823529, 0.9960784314, 0.5294117647, 0.2509803922, + 0.8156862745098039, 0.9960784314, 0.5411764706, 0.262745098, 0.8196078431372549, 0.9960784314, + 0.5490196078, 0.2784313725, 0.8235294117647058, 0.9960784314, 0.5607843137, 0.2941176471, + 0.8274509803921568, 0.9960784314, 0.568627451, 0.3098039216, 0.8313725490196079, 0.9960784314, + 0.5803921569, 0.3254901961, 0.8352941176470589, 0.9960784314, 0.5882352941, 0.3411764706, + 0.8392156862745098, 0.9960784314, 0.6, 0.3568627451, 0.8431372549019608, 0.9960784314, + 0.6078431373, 0.3725490196, 0.8470588235294118, 0.9960784314, 0.6196078431, 0.3882352941, + 0.8509803921568627, 0.9960784314, 0.6274509804, 0.4039215686, 0.8549019607843137, + 0.9960784314, 0.6392156863, 0.4196078431, 0.8588235294117647, 0.9960784314, 0.6470588235, + 0.4352941176, 0.8627450980392157, 0.9960784314, 0.6588235294, 0.4509803922, + 0.8666666666666667, 0.9960784314, 0.6666666667, 0.4666666667, 0.8705882352941177, + 0.9960784314, 0.6784313725, 0.4823529412, 0.8745098039215686, 0.9960784314, 0.6862745098, + 0.4980392157, 0.8784313725490196, 0.9960784314, 0.6980392157, 0.5137254902, + 0.8823529411764706, 0.9960784314, 0.7058823529, 0.5294117647, 0.8862745098039215, + 0.9960784314, 0.7176470588, 0.5450980392, 0.8901960784313725, 0.9960784314, 0.7254901961, + 0.5607843137, 0.8941176470588236, 0.9960784314, 0.737254902, 0.5764705882, 0.8980392156862745, + 0.9960784314, 0.7450980392, 0.5921568627, 0.9019607843137255, 0.9960784314, 0.7529411765, + 0.6078431373, 0.9058823529411765, 0.9960784314, 0.7607843137, 0.6235294118, + 0.9098039215686274, 0.9960784314, 0.7725490196, 0.6392156863, 0.9137254901960784, + 0.9960784314, 0.7803921569, 0.6549019608, 0.9176470588235294, 0.9960784314, 0.7921568627, + 0.6705882353, 0.9215686274509803, 0.9960784314, 0.8, 0.6862745098, 0.9254901960784314, + 0.9960784314, 0.8117647059, 0.7019607843, 0.9294117647058824, 0.9960784314, 0.8196078431, + 0.7176470588, 0.9333333333333333, 0.9960784314, 0.831372549, 0.7333333333, 0.9372549019607843, + 0.9960784314, 0.8392156863, 0.7490196078, 0.9411764705882354, 0.9960784314, 0.8509803922, + 0.7607843137, 0.9450980392156864, 0.9960784314, 0.8588235294, 0.7764705882, + 0.9490196078431372, 0.9960784314, 0.8705882353, 0.7921568627, 0.9529411764705882, + 0.9960784314, 0.8784313725, 0.8078431373, 0.9568627450980394, 0.9960784314, 0.8901960784, + 0.8235294118, 0.9607843137254903, 0.9960784314, 0.8980392157, 0.8392156863, + 0.9647058823529413, 0.9960784314, 0.9098039216, 0.8549019608, 0.9686274509803922, + 0.9960784314, 0.9176470588, 0.8705882353, 0.9725490196078431, 0.9960784314, 0.9294117647, + 0.8862745098, 0.9764705882352941, 0.9960784314, 0.937254902, 0.9019607843, 0.9803921568627451, + 0.9960784314, 0.9490196078, 0.9176470588, 0.984313725490196, 0.9960784314, 0.9568627451, + 0.9333333333, 0.9882352941176471, 0.9960784314, 0.968627451, 0.9490196078, 0.9921568627450981, + 0.9960784314, 0.9764705882, 0.9647058824, 0.996078431372549, 0.9960784314, 0.9882352941, + 0.9803921569, 1.0, 0.9960784314, 0.9882352941, 0.9803921569, ], }, { ColorSpace: 'RGB', Name: 'ge', RGBPoints: [ - 0.0, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.00392156862745098, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.00784313725490196, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.011764705882352941, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.01568627450980392, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.0196078431372549, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.023529411764705882, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.027450980392156862, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.03137254901960784, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.03529411764705882, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.0392156862745098, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.043137254901960784, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.047058823529411764, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.050980392156862744, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.054901960784313725, - 0.0078431373, - 0.0078431373, - 0.0078431373, - 0.05882352941176471, - 0.0117647059, - 0.0078431373, - 0.0078431373, - 0.06274509803921569, - 0.0078431373, - 0.0156862745, - 0.0156862745, - 0.06666666666666667, - 0.0078431373, - 0.0235294118, - 0.0235294118, - 0.07058823529411765, - 0.0078431373, - 0.031372549, - 0.031372549, - 0.07450980392156863, - 0.0078431373, - 0.0392156863, - 0.0392156863, - 0.0784313725490196, - 0.0078431373, - 0.0470588235, - 0.0470588235, - 0.08235294117647059, - 0.0078431373, - 0.0549019608, - 0.0549019608, - 0.08627450980392157, - 0.0078431373, - 0.062745098, - 0.062745098, - 0.09019607843137255, - 0.0078431373, - 0.0705882353, - 0.0705882353, - 0.09411764705882353, - 0.0078431373, - 0.0784313725, - 0.0784313725, - 0.09803921568627451, - 0.0078431373, - 0.0901960784, - 0.0862745098, - 0.10196078431372549, - 0.0078431373, - 0.0980392157, - 0.0941176471, - 0.10588235294117647, - 0.0078431373, - 0.1058823529, - 0.1019607843, - 0.10980392156862745, - 0.0078431373, - 0.1137254902, - 0.1098039216, - 0.11372549019607843, - 0.0078431373, - 0.1215686275, - 0.1176470588, - 0.11764705882352942, - 0.0078431373, - 0.1294117647, - 0.1254901961, - 0.12156862745098039, - 0.0078431373, - 0.137254902, - 0.1333333333, - 0.12549019607843137, - 0.0078431373, - 0.1450980392, - 0.1411764706, - 0.12941176470588237, - 0.0078431373, - 0.1529411765, - 0.1490196078, - 0.13333333333333333, - 0.0078431373, - 0.1647058824, - 0.1568627451, - 0.13725490196078433, - 0.0078431373, - 0.1725490196, - 0.1647058824, - 0.1411764705882353, - 0.0078431373, - 0.1803921569, - 0.1725490196, - 0.1450980392156863, - 0.0078431373, - 0.1882352941, - 0.1803921569, - 0.14901960784313725, - 0.0078431373, - 0.1960784314, - 0.1882352941, - 0.15294117647058825, - 0.0078431373, - 0.2039215686, - 0.1960784314, - 0.1568627450980392, - 0.0078431373, - 0.2117647059, - 0.2039215686, - 0.1607843137254902, - 0.0078431373, - 0.2196078431, - 0.2117647059, - 0.16470588235294117, - 0.0078431373, - 0.2274509804, - 0.2196078431, - 0.16862745098039217, - 0.0078431373, - 0.2352941176, - 0.2274509804, - 0.17254901960784313, - 0.0078431373, - 0.2470588235, - 0.2352941176, - 0.17647058823529413, - 0.0078431373, - 0.2509803922, - 0.2431372549, - 0.1803921568627451, - 0.0078431373, - 0.2549019608, - 0.2509803922, - 0.1843137254901961, - 0.0078431373, - 0.262745098, - 0.2509803922, - 0.18823529411764706, - 0.0078431373, - 0.2705882353, - 0.2588235294, - 0.19215686274509805, - 0.0078431373, - 0.2784313725, - 0.2666666667, - 0.19607843137254902, - 0.0078431373, - 0.2862745098, - 0.2745098039, - 0.2, - 0.0078431373, - 0.2941176471, - 0.2823529412, - 0.20392156862745098, - 0.0078431373, - 0.3019607843, - 0.2901960784, - 0.20784313725490197, - 0.0078431373, - 0.3137254902, - 0.2980392157, - 0.21176470588235294, - 0.0078431373, - 0.3215686275, - 0.3058823529, - 0.21568627450980393, - 0.0078431373, - 0.3294117647, - 0.3137254902, - 0.2196078431372549, - 0.0078431373, - 0.337254902, - 0.3215686275, - 0.2235294117647059, - 0.0078431373, - 0.3450980392, - 0.3294117647, - 0.22745098039215686, - 0.0078431373, - 0.3529411765, - 0.337254902, - 0.23137254901960785, - 0.0078431373, - 0.3607843137, - 0.3450980392, - 0.23529411764705885, - 0.0078431373, - 0.368627451, - 0.3529411765, - 0.23921568627450984, - 0.0078431373, - 0.3764705882, - 0.3607843137, - 0.24313725490196078, - 0.0078431373, - 0.3843137255, - 0.368627451, - 0.24705882352941178, - 0.0078431373, - 0.3960784314, - 0.3764705882, - 0.25098039215686274, - 0.0078431373, - 0.4039215686, - 0.3843137255, - 0.2549019607843137, - 0.0078431373, - 0.4117647059, - 0.3921568627, - 0.25882352941176473, - 0.0078431373, - 0.4196078431, - 0.4, - 0.2627450980392157, - 0.0078431373, - 0.4274509804, - 0.4078431373, - 0.26666666666666666, - 0.0078431373, - 0.4352941176, - 0.4156862745, - 0.27058823529411763, - 0.0078431373, - 0.4431372549, - 0.4235294118, - 0.27450980392156865, - 0.0078431373, - 0.4509803922, - 0.431372549, - 0.2784313725490196, - 0.0078431373, - 0.4588235294, - 0.4392156863, - 0.2823529411764706, - 0.0078431373, - 0.4705882353, - 0.4470588235, - 0.28627450980392155, - 0.0078431373, - 0.4784313725, - 0.4549019608, - 0.2901960784313726, - 0.0078431373, - 0.4862745098, - 0.462745098, - 0.29411764705882354, - 0.0078431373, - 0.4941176471, - 0.4705882353, - 0.2980392156862745, - 0.0078431373, - 0.5019607843, - 0.4784313725, - 0.30196078431372547, - 0.0117647059, - 0.5098039216, - 0.4862745098, - 0.3058823529411765, - 0.0196078431, - 0.5019607843, - 0.4941176471, - 0.30980392156862746, - 0.0274509804, - 0.4941176471, - 0.5058823529, - 0.3137254901960784, - 0.0352941176, - 0.4862745098, - 0.5137254902, - 0.3176470588235294, - 0.0431372549, - 0.4784313725, - 0.5215686275, - 0.3215686274509804, - 0.0509803922, - 0.4705882353, - 0.5294117647, - 0.3254901960784314, - 0.0588235294, - 0.462745098, - 0.537254902, - 0.32941176470588235, - 0.0666666667, - 0.4549019608, - 0.5450980392, - 0.3333333333333333, - 0.0745098039, - 0.4470588235, - 0.5529411765, - 0.33725490196078434, - 0.0823529412, - 0.4392156863, - 0.5607843137, - 0.3411764705882353, - 0.0901960784, - 0.431372549, - 0.568627451, - 0.34509803921568627, - 0.0980392157, - 0.4235294118, - 0.5764705882, - 0.34901960784313724, - 0.1058823529, - 0.4156862745, - 0.5843137255, - 0.35294117647058826, - 0.1137254902, - 0.4078431373, - 0.5921568627, - 0.3568627450980392, - 0.1215686275, - 0.4, - 0.6, - 0.3607843137254902, - 0.1294117647, - 0.3921568627, - 0.6078431373, - 0.36470588235294116, - 0.137254902, - 0.3843137255, - 0.6156862745, - 0.3686274509803922, - 0.1450980392, - 0.3764705882, - 0.6235294118, - 0.37254901960784315, - 0.1529411765, - 0.368627451, - 0.631372549, - 0.3764705882352941, - 0.1607843137, - 0.3607843137, - 0.6392156863, - 0.3803921568627451, - 0.168627451, - 0.3529411765, - 0.6470588235, - 0.3843137254901961, - 0.1764705882, - 0.3450980392, - 0.6549019608, - 0.38823529411764707, - 0.1843137255, - 0.337254902, - 0.662745098, - 0.39215686274509803, - 0.1921568627, - 0.3294117647, - 0.6705882353, - 0.396078431372549, - 0.2, - 0.3215686275, - 0.6784313725, - 0.4, - 0.2078431373, - 0.3137254902, - 0.6862745098, - 0.403921568627451, - 0.2156862745, - 0.3058823529, - 0.6941176471, - 0.40784313725490196, - 0.2235294118, - 0.2980392157, - 0.7019607843, - 0.4117647058823529, - 0.231372549, - 0.2901960784, - 0.7098039216, - 0.41568627450980394, - 0.2392156863, - 0.2823529412, - 0.7176470588, - 0.4196078431372549, - 0.2470588235, - 0.2745098039, - 0.7254901961, - 0.4235294117647059, - 0.2509803922, - 0.2666666667, - 0.7333333333, - 0.42745098039215684, - 0.2509803922, - 0.2588235294, - 0.7411764706, - 0.43137254901960786, - 0.2588235294, - 0.2509803922, - 0.7490196078, - 0.43529411764705883, - 0.2666666667, - 0.2509803922, - 0.7490196078, - 0.4392156862745098, - 0.2745098039, - 0.2431372549, - 0.7568627451, - 0.44313725490196076, - 0.2823529412, - 0.2352941176, - 0.7647058824, - 0.4470588235294118, - 0.2901960784, - 0.2274509804, - 0.7725490196, - 0.45098039215686275, - 0.2980392157, - 0.2196078431, - 0.7803921569, - 0.4549019607843137, - 0.3058823529, - 0.2117647059, - 0.7882352941, - 0.4588235294117647, - 0.3137254902, - 0.2039215686, - 0.7960784314, - 0.4627450980392157, - 0.3215686275, - 0.1960784314, - 0.8039215686, - 0.4666666666666667, - 0.3294117647, - 0.1882352941, - 0.8117647059, - 0.4705882352941177, - 0.337254902, - 0.1803921569, - 0.8196078431, - 0.4745098039215686, - 0.3450980392, - 0.1725490196, - 0.8274509804, - 0.4784313725490197, - 0.3529411765, - 0.1647058824, - 0.8352941176, - 0.48235294117647065, - 0.3607843137, - 0.1568627451, - 0.8431372549, - 0.48627450980392156, - 0.368627451, - 0.1490196078, - 0.8509803922, - 0.49019607843137253, - 0.3764705882, - 0.1411764706, - 0.8588235294, - 0.49411764705882355, - 0.3843137255, - 0.1333333333, - 0.8666666667, - 0.4980392156862745, - 0.3921568627, - 0.1254901961, - 0.8745098039, - 0.5019607843137255, - 0.4, - 0.1176470588, - 0.8823529412, - 0.5058823529411764, - 0.4078431373, - 0.1098039216, - 0.8901960784, - 0.5098039215686274, - 0.4156862745, - 0.1019607843, - 0.8980392157, - 0.5137254901960784, - 0.4235294118, - 0.0941176471, - 0.9058823529, - 0.5176470588235295, - 0.431372549, - 0.0862745098, - 0.9137254902, - 0.5215686274509804, - 0.4392156863, - 0.0784313725, - 0.9215686275, - 0.5254901960784314, - 0.4470588235, - 0.0705882353, - 0.9294117647, - 0.5294117647058824, - 0.4549019608, - 0.062745098, - 0.937254902, - 0.5333333333333333, - 0.462745098, - 0.0549019608, - 0.9450980392, - 0.5372549019607843, - 0.4705882353, - 0.0470588235, - 0.9529411765, - 0.5411764705882353, - 0.4784313725, - 0.0392156863, - 0.9607843137, - 0.5450980392156862, - 0.4862745098, - 0.031372549, - 0.968627451, - 0.5490196078431373, - 0.4941176471, - 0.0235294118, - 0.9764705882, - 0.5529411764705883, - 0.4980392157, - 0.0156862745, - 0.9843137255, - 0.5568627450980392, - 0.5058823529, - 0.0078431373, - 0.9921568627, - 0.5607843137254902, - 0.5137254902, - 0.0156862745, - 0.9803921569, - 0.5647058823529412, - 0.5215686275, - 0.0235294118, - 0.9647058824, - 0.5686274509803921, - 0.5294117647, - 0.0352941176, - 0.9490196078, - 0.5725490196078431, - 0.537254902, - 0.0431372549, - 0.9333333333, - 0.5764705882352941, - 0.5450980392, - 0.0509803922, - 0.9176470588, - 0.5803921568627451, - 0.5529411765, - 0.062745098, - 0.9019607843, - 0.5843137254901961, - 0.5607843137, - 0.0705882353, - 0.8862745098, - 0.5882352941176471, - 0.568627451, - 0.0784313725, - 0.8705882353, - 0.592156862745098, - 0.5764705882, - 0.0901960784, - 0.8549019608, - 0.596078431372549, - 0.5843137255, - 0.0980392157, - 0.8392156863, - 0.6, - 0.5921568627, - 0.1098039216, - 0.8235294118, - 0.6039215686274509, - 0.6, - 0.1176470588, - 0.8078431373, - 0.6078431372549019, - 0.6078431373, - 0.1254901961, - 0.7921568627, - 0.611764705882353, - 0.6156862745, - 0.137254902, - 0.7764705882, - 0.615686274509804, - 0.6235294118, - 0.1450980392, - 0.7607843137, - 0.6196078431372549, - 0.631372549, - 0.1529411765, - 0.7490196078, - 0.6235294117647059, - 0.6392156863, - 0.1647058824, - 0.737254902, - 0.6274509803921569, - 0.6470588235, - 0.1725490196, - 0.7215686275, - 0.6313725490196078, - 0.6549019608, - 0.1843137255, - 0.7058823529, - 0.6352941176470588, - 0.662745098, - 0.1921568627, - 0.6901960784, - 0.6392156862745098, - 0.6705882353, - 0.2, - 0.6745098039, - 0.6431372549019608, - 0.6784313725, - 0.2117647059, - 0.6588235294, - 0.6470588235294118, - 0.6862745098, - 0.2196078431, - 0.6431372549, - 0.6509803921568628, - 0.6941176471, - 0.2274509804, - 0.6274509804, - 0.6549019607843137, - 0.7019607843, - 0.2392156863, - 0.6117647059, - 0.6588235294117647, - 0.7098039216, - 0.2470588235, - 0.5960784314, - 0.6627450980392157, - 0.7176470588, - 0.2509803922, - 0.5803921569, - 0.6666666666666666, - 0.7254901961, - 0.2588235294, - 0.5647058824, - 0.6705882352941176, - 0.7333333333, - 0.2666666667, - 0.5490196078, - 0.6745098039215687, - 0.7411764706, - 0.2784313725, - 0.5333333333, - 0.6784313725490196, - 0.7490196078, - 0.2862745098, - 0.5176470588, - 0.6823529411764706, - 0.7490196078, - 0.2941176471, - 0.5019607843, - 0.6862745098039216, - 0.7529411765, - 0.3058823529, - 0.4862745098, - 0.6901960784313725, - 0.7607843137, - 0.3137254902, - 0.4705882353, - 0.6941176470588235, - 0.768627451, - 0.3215686275, - 0.4549019608, - 0.6980392156862745, - 0.7764705882, - 0.3333333333, - 0.4392156863, - 0.7019607843137254, - 0.7843137255, - 0.3411764706, - 0.4235294118, - 0.7058823529411765, - 0.7921568627, - 0.3529411765, - 0.4078431373, - 0.7098039215686275, - 0.8, - 0.3607843137, - 0.3921568627, - 0.7137254901960784, - 0.8078431373, - 0.368627451, - 0.3764705882, - 0.7176470588235294, - 0.8156862745, - 0.3803921569, - 0.3607843137, - 0.7215686274509804, - 0.8235294118, - 0.3882352941, - 0.3450980392, - 0.7254901960784313, - 0.831372549, - 0.3960784314, - 0.3294117647, - 0.7294117647058823, - 0.8392156863, - 0.4078431373, - 0.3137254902, - 0.7333333333333333, - 0.8470588235, - 0.4156862745, - 0.2980392157, - 0.7372549019607844, - 0.8549019608, - 0.4274509804, - 0.2823529412, - 0.7411764705882353, - 0.862745098, - 0.4352941176, - 0.2666666667, - 0.7450980392156863, - 0.8705882353, - 0.4431372549, - 0.2509803922, - 0.7490196078431373, - 0.8784313725, - 0.4549019608, - 0.2431372549, - 0.7529411764705882, - 0.8862745098, - 0.462745098, - 0.2274509804, - 0.7568627450980392, - 0.8941176471, - 0.4705882353, - 0.2117647059, - 0.7607843137254902, - 0.9019607843, - 0.4823529412, - 0.1960784314, - 0.7647058823529411, - 0.9098039216, - 0.4901960784, - 0.1803921569, - 0.7686274509803922, - 0.9176470588, - 0.4980392157, - 0.1647058824, - 0.7725490196078432, - 0.9254901961, - 0.5098039216, - 0.1490196078, - 0.7764705882352941, - 0.9333333333, - 0.5176470588, - 0.1333333333, - 0.7803921568627451, - 0.9411764706, - 0.5294117647, - 0.1176470588, - 0.7843137254901961, - 0.9490196078, - 0.537254902, - 0.1019607843, - 0.788235294117647, - 0.9568627451, - 0.5450980392, - 0.0862745098, - 0.792156862745098, - 0.9647058824, - 0.5568627451, - 0.0705882353, - 0.796078431372549, - 0.9725490196, - 0.5647058824, - 0.0549019608, - 0.8, - 0.9803921569, - 0.5725490196, - 0.0392156863, - 0.803921568627451, - 0.9882352941, - 0.5843137255, - 0.0235294118, - 0.807843137254902, - 0.9921568627, - 0.5921568627, - 0.0078431373, - 0.8117647058823529, - 0.9921568627, - 0.6039215686, - 0.0274509804, - 0.8156862745098039, - 0.9921568627, - 0.6117647059, - 0.0509803922, - 0.8196078431372549, - 0.9921568627, - 0.6196078431, - 0.0745098039, - 0.8235294117647058, - 0.9921568627, - 0.631372549, - 0.0980392157, - 0.8274509803921568, - 0.9921568627, - 0.6392156863, - 0.1215686275, - 0.8313725490196079, - 0.9921568627, - 0.6470588235, - 0.1411764706, - 0.8352941176470589, - 0.9921568627, - 0.6588235294, - 0.1647058824, - 0.8392156862745098, - 0.9921568627, - 0.6666666667, - 0.1882352941, - 0.8431372549019608, - 0.9921568627, - 0.6784313725, - 0.2117647059, - 0.8470588235294118, - 0.9921568627, - 0.6862745098, - 0.2352941176, - 0.8509803921568627, - 0.9921568627, - 0.6941176471, - 0.2509803922, - 0.8549019607843137, - 0.9921568627, - 0.7058823529, - 0.2705882353, - 0.8588235294117647, - 0.9921568627, - 0.7137254902, - 0.2941176471, - 0.8627450980392157, - 0.9921568627, - 0.7215686275, - 0.3176470588, - 0.8666666666666667, - 0.9921568627, - 0.7333333333, - 0.3411764706, - 0.8705882352941177, - 0.9921568627, - 0.7411764706, - 0.3647058824, - 0.8745098039215686, - 0.9921568627, - 0.7490196078, - 0.3843137255, - 0.8784313725490196, - 0.9921568627, - 0.7529411765, - 0.4078431373, - 0.8823529411764706, - 0.9921568627, - 0.7607843137, - 0.431372549, - 0.8862745098039215, - 0.9921568627, - 0.7725490196, - 0.4549019608, - 0.8901960784313725, - 0.9921568627, - 0.7803921569, - 0.4784313725, - 0.8941176470588236, - 0.9921568627, - 0.7882352941, - 0.4980392157, - 0.8980392156862745, - 0.9921568627, - 0.8, - 0.5215686275, - 0.9019607843137255, - 0.9921568627, - 0.8078431373, - 0.5450980392, - 0.9058823529411765, - 0.9921568627, - 0.8156862745, - 0.568627451, - 0.9098039215686274, - 0.9921568627, - 0.8274509804, - 0.5921568627, - 0.9137254901960784, - 0.9921568627, - 0.8352941176, - 0.6156862745, - 0.9176470588235294, - 0.9921568627, - 0.8470588235, - 0.6352941176, - 0.9215686274509803, - 0.9921568627, - 0.8549019608, - 0.6588235294, - 0.9254901960784314, - 0.9921568627, - 0.862745098, - 0.6823529412, - 0.9294117647058824, - 0.9921568627, - 0.8745098039, - 0.7058823529, - 0.9333333333333333, - 0.9921568627, - 0.8823529412, - 0.7294117647, - 0.9372549019607843, - 0.9921568627, - 0.8901960784, - 0.7490196078, - 0.9411764705882354, - 0.9921568627, - 0.9019607843, - 0.7647058824, - 0.9450980392156864, - 0.9921568627, - 0.9098039216, - 0.7882352941, - 0.9490196078431372, - 0.9921568627, - 0.9215686275, - 0.8117647059, - 0.9529411764705882, - 0.9921568627, - 0.9294117647, - 0.8352941176, - 0.9568627450980394, - 0.9921568627, - 0.937254902, - 0.8588235294, - 0.9607843137254903, - 0.9921568627, - 0.9490196078, - 0.8784313725, - 0.9647058823529413, - 0.9921568627, - 0.9568627451, - 0.9019607843, - 0.9686274509803922, - 0.9921568627, - 0.9647058824, - 0.9254901961, - 0.9725490196078431, - 0.9921568627, - 0.9764705882, - 0.9490196078, - 0.9764705882352941, - 0.9921568627, - 0.9843137255, - 0.9725490196, - 0.9803921568627451, - 0.9921568627, - 0.9921568627, - 0.9921568627, - 0.984313725490196, - 0.9921568627, - 0.9921568627, - 0.9921568627, - 0.9882352941176471, - 0.9921568627, - 0.9921568627, - 0.9921568627, - 0.9921568627450981, - 0.9921568627, - 0.9921568627, - 0.9921568627, - 0.996078431372549, - 0.9921568627, - 0.9921568627, - 0.9921568627, - 1.0, - 0.9921568627, - 0.9921568627, - 0.9921568627, + 0.0, 0.0078431373, 0.0078431373, 0.0078431373, 0.00392156862745098, 0.0078431373, + 0.0078431373, 0.0078431373, 0.00784313725490196, 0.0078431373, 0.0078431373, 0.0078431373, + 0.011764705882352941, 0.0078431373, 0.0078431373, 0.0078431373, 0.01568627450980392, + 0.0078431373, 0.0078431373, 0.0078431373, 0.0196078431372549, 0.0078431373, 0.0078431373, + 0.0078431373, 0.023529411764705882, 0.0078431373, 0.0078431373, 0.0078431373, + 0.027450980392156862, 0.0078431373, 0.0078431373, 0.0078431373, 0.03137254901960784, + 0.0078431373, 0.0078431373, 0.0078431373, 0.03529411764705882, 0.0078431373, 0.0078431373, + 0.0078431373, 0.0392156862745098, 0.0078431373, 0.0078431373, 0.0078431373, + 0.043137254901960784, 0.0078431373, 0.0078431373, 0.0078431373, 0.047058823529411764, + 0.0078431373, 0.0078431373, 0.0078431373, 0.050980392156862744, 0.0078431373, 0.0078431373, + 0.0078431373, 0.054901960784313725, 0.0078431373, 0.0078431373, 0.0078431373, + 0.05882352941176471, 0.0117647059, 0.0078431373, 0.0078431373, 0.06274509803921569, + 0.0078431373, 0.0156862745, 0.0156862745, 0.06666666666666667, 0.0078431373, 0.0235294118, + 0.0235294118, 0.07058823529411765, 0.0078431373, 0.031372549, 0.031372549, + 0.07450980392156863, 0.0078431373, 0.0392156863, 0.0392156863, 0.0784313725490196, + 0.0078431373, 0.0470588235, 0.0470588235, 0.08235294117647059, 0.0078431373, 0.0549019608, + 0.0549019608, 0.08627450980392157, 0.0078431373, 0.062745098, 0.062745098, + 0.09019607843137255, 0.0078431373, 0.0705882353, 0.0705882353, 0.09411764705882353, + 0.0078431373, 0.0784313725, 0.0784313725, 0.09803921568627451, 0.0078431373, 0.0901960784, + 0.0862745098, 0.10196078431372549, 0.0078431373, 0.0980392157, 0.0941176471, + 0.10588235294117647, 0.0078431373, 0.1058823529, 0.1019607843, 0.10980392156862745, + 0.0078431373, 0.1137254902, 0.1098039216, 0.11372549019607843, 0.0078431373, 0.1215686275, + 0.1176470588, 0.11764705882352942, 0.0078431373, 0.1294117647, 0.1254901961, + 0.12156862745098039, 0.0078431373, 0.137254902, 0.1333333333, 0.12549019607843137, + 0.0078431373, 0.1450980392, 0.1411764706, 0.12941176470588237, 0.0078431373, 0.1529411765, + 0.1490196078, 0.13333333333333333, 0.0078431373, 0.1647058824, 0.1568627451, + 0.13725490196078433, 0.0078431373, 0.1725490196, 0.1647058824, 0.1411764705882353, + 0.0078431373, 0.1803921569, 0.1725490196, 0.1450980392156863, 0.0078431373, 0.1882352941, + 0.1803921569, 0.14901960784313725, 0.0078431373, 0.1960784314, 0.1882352941, + 0.15294117647058825, 0.0078431373, 0.2039215686, 0.1960784314, 0.1568627450980392, + 0.0078431373, 0.2117647059, 0.2039215686, 0.1607843137254902, 0.0078431373, 0.2196078431, + 0.2117647059, 0.16470588235294117, 0.0078431373, 0.2274509804, 0.2196078431, + 0.16862745098039217, 0.0078431373, 0.2352941176, 0.2274509804, 0.17254901960784313, + 0.0078431373, 0.2470588235, 0.2352941176, 0.17647058823529413, 0.0078431373, 0.2509803922, + 0.2431372549, 0.1803921568627451, 0.0078431373, 0.2549019608, 0.2509803922, + 0.1843137254901961, 0.0078431373, 0.262745098, 0.2509803922, 0.18823529411764706, + 0.0078431373, 0.2705882353, 0.2588235294, 0.19215686274509805, 0.0078431373, 0.2784313725, + 0.2666666667, 0.19607843137254902, 0.0078431373, 0.2862745098, 0.2745098039, 0.2, + 0.0078431373, 0.2941176471, 0.2823529412, 0.20392156862745098, 0.0078431373, 0.3019607843, + 0.2901960784, 0.20784313725490197, 0.0078431373, 0.3137254902, 0.2980392157, + 0.21176470588235294, 0.0078431373, 0.3215686275, 0.3058823529, 0.21568627450980393, + 0.0078431373, 0.3294117647, 0.3137254902, 0.2196078431372549, 0.0078431373, 0.337254902, + 0.3215686275, 0.2235294117647059, 0.0078431373, 0.3450980392, 0.3294117647, + 0.22745098039215686, 0.0078431373, 0.3529411765, 0.337254902, 0.23137254901960785, + 0.0078431373, 0.3607843137, 0.3450980392, 0.23529411764705885, 0.0078431373, 0.368627451, + 0.3529411765, 0.23921568627450984, 0.0078431373, 0.3764705882, 0.3607843137, + 0.24313725490196078, 0.0078431373, 0.3843137255, 0.368627451, 0.24705882352941178, + 0.0078431373, 0.3960784314, 0.3764705882, 0.25098039215686274, 0.0078431373, 0.4039215686, + 0.3843137255, 0.2549019607843137, 0.0078431373, 0.4117647059, 0.3921568627, + 0.25882352941176473, 0.0078431373, 0.4196078431, 0.4, 0.2627450980392157, 0.0078431373, + 0.4274509804, 0.4078431373, 0.26666666666666666, 0.0078431373, 0.4352941176, 0.4156862745, + 0.27058823529411763, 0.0078431373, 0.4431372549, 0.4235294118, 0.27450980392156865, + 0.0078431373, 0.4509803922, 0.431372549, 0.2784313725490196, 0.0078431373, 0.4588235294, + 0.4392156863, 0.2823529411764706, 0.0078431373, 0.4705882353, 0.4470588235, + 0.28627450980392155, 0.0078431373, 0.4784313725, 0.4549019608, 0.2901960784313726, + 0.0078431373, 0.4862745098, 0.462745098, 0.29411764705882354, 0.0078431373, 0.4941176471, + 0.4705882353, 0.2980392156862745, 0.0078431373, 0.5019607843, 0.4784313725, + 0.30196078431372547, 0.0117647059, 0.5098039216, 0.4862745098, 0.3058823529411765, + 0.0196078431, 0.5019607843, 0.4941176471, 0.30980392156862746, 0.0274509804, 0.4941176471, + 0.5058823529, 0.3137254901960784, 0.0352941176, 0.4862745098, 0.5137254902, + 0.3176470588235294, 0.0431372549, 0.4784313725, 0.5215686275, 0.3215686274509804, + 0.0509803922, 0.4705882353, 0.5294117647, 0.3254901960784314, 0.0588235294, 0.462745098, + 0.537254902, 0.32941176470588235, 0.0666666667, 0.4549019608, 0.5450980392, + 0.3333333333333333, 0.0745098039, 0.4470588235, 0.5529411765, 0.33725490196078434, + 0.0823529412, 0.4392156863, 0.5607843137, 0.3411764705882353, 0.0901960784, 0.431372549, + 0.568627451, 0.34509803921568627, 0.0980392157, 0.4235294118, 0.5764705882, + 0.34901960784313724, 0.1058823529, 0.4156862745, 0.5843137255, 0.35294117647058826, + 0.1137254902, 0.4078431373, 0.5921568627, 0.3568627450980392, 0.1215686275, 0.4, 0.6, + 0.3607843137254902, 0.1294117647, 0.3921568627, 0.6078431373, 0.36470588235294116, + 0.137254902, 0.3843137255, 0.6156862745, 0.3686274509803922, 0.1450980392, 0.3764705882, + 0.6235294118, 0.37254901960784315, 0.1529411765, 0.368627451, 0.631372549, 0.3764705882352941, + 0.1607843137, 0.3607843137, 0.6392156863, 0.3803921568627451, 0.168627451, 0.3529411765, + 0.6470588235, 0.3843137254901961, 0.1764705882, 0.3450980392, 0.6549019608, + 0.38823529411764707, 0.1843137255, 0.337254902, 0.662745098, 0.39215686274509803, + 0.1921568627, 0.3294117647, 0.6705882353, 0.396078431372549, 0.2, 0.3215686275, 0.6784313725, + 0.4, 0.2078431373, 0.3137254902, 0.6862745098, 0.403921568627451, 0.2156862745, 0.3058823529, + 0.6941176471, 0.40784313725490196, 0.2235294118, 0.2980392157, 0.7019607843, + 0.4117647058823529, 0.231372549, 0.2901960784, 0.7098039216, 0.41568627450980394, + 0.2392156863, 0.2823529412, 0.7176470588, 0.4196078431372549, 0.2470588235, 0.2745098039, + 0.7254901961, 0.4235294117647059, 0.2509803922, 0.2666666667, 0.7333333333, + 0.42745098039215684, 0.2509803922, 0.2588235294, 0.7411764706, 0.43137254901960786, + 0.2588235294, 0.2509803922, 0.7490196078, 0.43529411764705883, 0.2666666667, 0.2509803922, + 0.7490196078, 0.4392156862745098, 0.2745098039, 0.2431372549, 0.7568627451, + 0.44313725490196076, 0.2823529412, 0.2352941176, 0.7647058824, 0.4470588235294118, + 0.2901960784, 0.2274509804, 0.7725490196, 0.45098039215686275, 0.2980392157, 0.2196078431, + 0.7803921569, 0.4549019607843137, 0.3058823529, 0.2117647059, 0.7882352941, + 0.4588235294117647, 0.3137254902, 0.2039215686, 0.7960784314, 0.4627450980392157, + 0.3215686275, 0.1960784314, 0.8039215686, 0.4666666666666667, 0.3294117647, 0.1882352941, + 0.8117647059, 0.4705882352941177, 0.337254902, 0.1803921569, 0.8196078431, 0.4745098039215686, + 0.3450980392, 0.1725490196, 0.8274509804, 0.4784313725490197, 0.3529411765, 0.1647058824, + 0.8352941176, 0.48235294117647065, 0.3607843137, 0.1568627451, 0.8431372549, + 0.48627450980392156, 0.368627451, 0.1490196078, 0.8509803922, 0.49019607843137253, + 0.3764705882, 0.1411764706, 0.8588235294, 0.49411764705882355, 0.3843137255, 0.1333333333, + 0.8666666667, 0.4980392156862745, 0.3921568627, 0.1254901961, 0.8745098039, + 0.5019607843137255, 0.4, 0.1176470588, 0.8823529412, 0.5058823529411764, 0.4078431373, + 0.1098039216, 0.8901960784, 0.5098039215686274, 0.4156862745, 0.1019607843, 0.8980392157, + 0.5137254901960784, 0.4235294118, 0.0941176471, 0.9058823529, 0.5176470588235295, 0.431372549, + 0.0862745098, 0.9137254902, 0.5215686274509804, 0.4392156863, 0.0784313725, 0.9215686275, + 0.5254901960784314, 0.4470588235, 0.0705882353, 0.9294117647, 0.5294117647058824, + 0.4549019608, 0.062745098, 0.937254902, 0.5333333333333333, 0.462745098, 0.0549019608, + 0.9450980392, 0.5372549019607843, 0.4705882353, 0.0470588235, 0.9529411765, + 0.5411764705882353, 0.4784313725, 0.0392156863, 0.9607843137, 0.5450980392156862, + 0.4862745098, 0.031372549, 0.968627451, 0.5490196078431373, 0.4941176471, 0.0235294118, + 0.9764705882, 0.5529411764705883, 0.4980392157, 0.0156862745, 0.9843137255, + 0.5568627450980392, 0.5058823529, 0.0078431373, 0.9921568627, 0.5607843137254902, + 0.5137254902, 0.0156862745, 0.9803921569, 0.5647058823529412, 0.5215686275, 0.0235294118, + 0.9647058824, 0.5686274509803921, 0.5294117647, 0.0352941176, 0.9490196078, + 0.5725490196078431, 0.537254902, 0.0431372549, 0.9333333333, 0.5764705882352941, 0.5450980392, + 0.0509803922, 0.9176470588, 0.5803921568627451, 0.5529411765, 0.062745098, 0.9019607843, + 0.5843137254901961, 0.5607843137, 0.0705882353, 0.8862745098, 0.5882352941176471, 0.568627451, + 0.0784313725, 0.8705882353, 0.592156862745098, 0.5764705882, 0.0901960784, 0.8549019608, + 0.596078431372549, 0.5843137255, 0.0980392157, 0.8392156863, 0.6, 0.5921568627, 0.1098039216, + 0.8235294118, 0.6039215686274509, 0.6, 0.1176470588, 0.8078431373, 0.6078431372549019, + 0.6078431373, 0.1254901961, 0.7921568627, 0.611764705882353, 0.6156862745, 0.137254902, + 0.7764705882, 0.615686274509804, 0.6235294118, 0.1450980392, 0.7607843137, 0.6196078431372549, + 0.631372549, 0.1529411765, 0.7490196078, 0.6235294117647059, 0.6392156863, 0.1647058824, + 0.737254902, 0.6274509803921569, 0.6470588235, 0.1725490196, 0.7215686275, 0.6313725490196078, + 0.6549019608, 0.1843137255, 0.7058823529, 0.6352941176470588, 0.662745098, 0.1921568627, + 0.6901960784, 0.6392156862745098, 0.6705882353, 0.2, 0.6745098039, 0.6431372549019608, + 0.6784313725, 0.2117647059, 0.6588235294, 0.6470588235294118, 0.6862745098, 0.2196078431, + 0.6431372549, 0.6509803921568628, 0.6941176471, 0.2274509804, 0.6274509804, + 0.6549019607843137, 0.7019607843, 0.2392156863, 0.6117647059, 0.6588235294117647, + 0.7098039216, 0.2470588235, 0.5960784314, 0.6627450980392157, 0.7176470588, 0.2509803922, + 0.5803921569, 0.6666666666666666, 0.7254901961, 0.2588235294, 0.5647058824, + 0.6705882352941176, 0.7333333333, 0.2666666667, 0.5490196078, 0.6745098039215687, + 0.7411764706, 0.2784313725, 0.5333333333, 0.6784313725490196, 0.7490196078, 0.2862745098, + 0.5176470588, 0.6823529411764706, 0.7490196078, 0.2941176471, 0.5019607843, + 0.6862745098039216, 0.7529411765, 0.3058823529, 0.4862745098, 0.6901960784313725, + 0.7607843137, 0.3137254902, 0.4705882353, 0.6941176470588235, 0.768627451, 0.3215686275, + 0.4549019608, 0.6980392156862745, 0.7764705882, 0.3333333333, 0.4392156863, + 0.7019607843137254, 0.7843137255, 0.3411764706, 0.4235294118, 0.7058823529411765, + 0.7921568627, 0.3529411765, 0.4078431373, 0.7098039215686275, 0.8, 0.3607843137, 0.3921568627, + 0.7137254901960784, 0.8078431373, 0.368627451, 0.3764705882, 0.7176470588235294, 0.8156862745, + 0.3803921569, 0.3607843137, 0.7215686274509804, 0.8235294118, 0.3882352941, 0.3450980392, + 0.7254901960784313, 0.831372549, 0.3960784314, 0.3294117647, 0.7294117647058823, 0.8392156863, + 0.4078431373, 0.3137254902, 0.7333333333333333, 0.8470588235, 0.4156862745, 0.2980392157, + 0.7372549019607844, 0.8549019608, 0.4274509804, 0.2823529412, 0.7411764705882353, 0.862745098, + 0.4352941176, 0.2666666667, 0.7450980392156863, 0.8705882353, 0.4431372549, 0.2509803922, + 0.7490196078431373, 0.8784313725, 0.4549019608, 0.2431372549, 0.7529411764705882, + 0.8862745098, 0.462745098, 0.2274509804, 0.7568627450980392, 0.8941176471, 0.4705882353, + 0.2117647059, 0.7607843137254902, 0.9019607843, 0.4823529412, 0.1960784314, + 0.7647058823529411, 0.9098039216, 0.4901960784, 0.1803921569, 0.7686274509803922, + 0.9176470588, 0.4980392157, 0.1647058824, 0.7725490196078432, 0.9254901961, 0.5098039216, + 0.1490196078, 0.7764705882352941, 0.9333333333, 0.5176470588, 0.1333333333, + 0.7803921568627451, 0.9411764706, 0.5294117647, 0.1176470588, 0.7843137254901961, + 0.9490196078, 0.537254902, 0.1019607843, 0.788235294117647, 0.9568627451, 0.5450980392, + 0.0862745098, 0.792156862745098, 0.9647058824, 0.5568627451, 0.0705882353, 0.796078431372549, + 0.9725490196, 0.5647058824, 0.0549019608, 0.8, 0.9803921569, 0.5725490196, 0.0392156863, + 0.803921568627451, 0.9882352941, 0.5843137255, 0.0235294118, 0.807843137254902, 0.9921568627, + 0.5921568627, 0.0078431373, 0.8117647058823529, 0.9921568627, 0.6039215686, 0.0274509804, + 0.8156862745098039, 0.9921568627, 0.6117647059, 0.0509803922, 0.8196078431372549, + 0.9921568627, 0.6196078431, 0.0745098039, 0.8235294117647058, 0.9921568627, 0.631372549, + 0.0980392157, 0.8274509803921568, 0.9921568627, 0.6392156863, 0.1215686275, + 0.8313725490196079, 0.9921568627, 0.6470588235, 0.1411764706, 0.8352941176470589, + 0.9921568627, 0.6588235294, 0.1647058824, 0.8392156862745098, 0.9921568627, 0.6666666667, + 0.1882352941, 0.8431372549019608, 0.9921568627, 0.6784313725, 0.2117647059, + 0.8470588235294118, 0.9921568627, 0.6862745098, 0.2352941176, 0.8509803921568627, + 0.9921568627, 0.6941176471, 0.2509803922, 0.8549019607843137, 0.9921568627, 0.7058823529, + 0.2705882353, 0.8588235294117647, 0.9921568627, 0.7137254902, 0.2941176471, + 0.8627450980392157, 0.9921568627, 0.7215686275, 0.3176470588, 0.8666666666666667, + 0.9921568627, 0.7333333333, 0.3411764706, 0.8705882352941177, 0.9921568627, 0.7411764706, + 0.3647058824, 0.8745098039215686, 0.9921568627, 0.7490196078, 0.3843137255, + 0.8784313725490196, 0.9921568627, 0.7529411765, 0.4078431373, 0.8823529411764706, + 0.9921568627, 0.7607843137, 0.431372549, 0.8862745098039215, 0.9921568627, 0.7725490196, + 0.4549019608, 0.8901960784313725, 0.9921568627, 0.7803921569, 0.4784313725, + 0.8941176470588236, 0.9921568627, 0.7882352941, 0.4980392157, 0.8980392156862745, + 0.9921568627, 0.8, 0.5215686275, 0.9019607843137255, 0.9921568627, 0.8078431373, 0.5450980392, + 0.9058823529411765, 0.9921568627, 0.8156862745, 0.568627451, 0.9098039215686274, 0.9921568627, + 0.8274509804, 0.5921568627, 0.9137254901960784, 0.9921568627, 0.8352941176, 0.6156862745, + 0.9176470588235294, 0.9921568627, 0.8470588235, 0.6352941176, 0.9215686274509803, + 0.9921568627, 0.8549019608, 0.6588235294, 0.9254901960784314, 0.9921568627, 0.862745098, + 0.6823529412, 0.9294117647058824, 0.9921568627, 0.8745098039, 0.7058823529, + 0.9333333333333333, 0.9921568627, 0.8823529412, 0.7294117647, 0.9372549019607843, + 0.9921568627, 0.8901960784, 0.7490196078, 0.9411764705882354, 0.9921568627, 0.9019607843, + 0.7647058824, 0.9450980392156864, 0.9921568627, 0.9098039216, 0.7882352941, + 0.9490196078431372, 0.9921568627, 0.9215686275, 0.8117647059, 0.9529411764705882, + 0.9921568627, 0.9294117647, 0.8352941176, 0.9568627450980394, 0.9921568627, 0.937254902, + 0.8588235294, 0.9607843137254903, 0.9921568627, 0.9490196078, 0.8784313725, + 0.9647058823529413, 0.9921568627, 0.9568627451, 0.9019607843, 0.9686274509803922, + 0.9921568627, 0.9647058824, 0.9254901961, 0.9725490196078431, 0.9921568627, 0.9764705882, + 0.9490196078, 0.9764705882352941, 0.9921568627, 0.9843137255, 0.9725490196, + 0.9803921568627451, 0.9921568627, 0.9921568627, 0.9921568627, 0.984313725490196, 0.9921568627, + 0.9921568627, 0.9921568627, 0.9882352941176471, 0.9921568627, 0.9921568627, 0.9921568627, + 0.9921568627450981, 0.9921568627, 0.9921568627, 0.9921568627, 0.996078431372549, 0.9921568627, + 0.9921568627, 0.9921568627, 1.0, 0.9921568627, 0.9921568627, 0.9921568627, ], }, { ColorSpace: 'RGB', Name: 'siemens', RGBPoints: [ - 0.0, - 0.0078431373, - 0.0039215686, - 0.1254901961, - 0.00392156862745098, - 0.0078431373, - 0.0039215686, - 0.1254901961, - 0.00784313725490196, - 0.0078431373, - 0.0039215686, - 0.1882352941, - 0.011764705882352941, - 0.0117647059, - 0.0039215686, - 0.2509803922, - 0.01568627450980392, - 0.0117647059, - 0.0039215686, - 0.3098039216, - 0.0196078431372549, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.023529411764705882, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.027450980392156862, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.03137254901960784, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.03529411764705882, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.0392156862745098, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.043137254901960784, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.047058823529411764, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.050980392156862744, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.054901960784313725, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.05882352941176471, - 0.0156862745, - 0.0039215686, - 0.3725490196, - 0.06274509803921569, - 0.0156862745, - 0.0039215686, - 0.3882352941, - 0.06666666666666667, - 0.0156862745, - 0.0039215686, - 0.4078431373, - 0.07058823529411765, - 0.0156862745, - 0.0039215686, - 0.4235294118, - 0.07450980392156863, - 0.0156862745, - 0.0039215686, - 0.4431372549, - 0.0784313725490196, - 0.0156862745, - 0.0039215686, - 0.462745098, - 0.08235294117647059, - 0.0156862745, - 0.0039215686, - 0.4784313725, - 0.08627450980392157, - 0.0156862745, - 0.0039215686, - 0.4980392157, - 0.09019607843137255, - 0.0196078431, - 0.0039215686, - 0.5137254902, - 0.09411764705882353, - 0.0196078431, - 0.0039215686, - 0.5333333333, - 0.09803921568627451, - 0.0196078431, - 0.0039215686, - 0.5529411765, - 0.10196078431372549, - 0.0196078431, - 0.0039215686, - 0.568627451, - 0.10588235294117647, - 0.0196078431, - 0.0039215686, - 0.5882352941, - 0.10980392156862745, - 0.0196078431, - 0.0039215686, - 0.6039215686, - 0.11372549019607843, - 0.0196078431, - 0.0039215686, - 0.6235294118, - 0.11764705882352942, - 0.0196078431, - 0.0039215686, - 0.6431372549, - 0.12156862745098039, - 0.0235294118, - 0.0039215686, - 0.6588235294, - 0.12549019607843137, - 0.0235294118, - 0.0039215686, - 0.6784313725, - 0.12941176470588237, - 0.0235294118, - 0.0039215686, - 0.6980392157, - 0.13333333333333333, - 0.0235294118, - 0.0039215686, - 0.7137254902, - 0.13725490196078433, - 0.0235294118, - 0.0039215686, - 0.7333333333, - 0.1411764705882353, - 0.0235294118, - 0.0039215686, - 0.7490196078, - 0.1450980392156863, - 0.0235294118, - 0.0039215686, - 0.7647058824, - 0.14901960784313725, - 0.0235294118, - 0.0039215686, - 0.7843137255, - 0.15294117647058825, - 0.0274509804, - 0.0039215686, - 0.8, - 0.1568627450980392, - 0.0274509804, - 0.0039215686, - 0.8196078431, - 0.1607843137254902, - 0.0274509804, - 0.0039215686, - 0.8352941176, - 0.16470588235294117, - 0.0274509804, - 0.0039215686, - 0.8549019608, - 0.16862745098039217, - 0.0274509804, - 0.0039215686, - 0.8745098039, - 0.17254901960784313, - 0.0274509804, - 0.0039215686, - 0.8901960784, - 0.17647058823529413, - 0.0274509804, - 0.0039215686, - 0.9098039216, - 0.1803921568627451, - 0.031372549, - 0.0039215686, - 0.9294117647, - 0.1843137254901961, - 0.031372549, - 0.0039215686, - 0.9254901961, - 0.18823529411764706, - 0.0509803922, - 0.0039215686, - 0.9098039216, - 0.19215686274509805, - 0.0705882353, - 0.0039215686, - 0.8901960784, - 0.19607843137254902, - 0.0901960784, - 0.0039215686, - 0.8705882353, - 0.2, - 0.1137254902, - 0.0039215686, - 0.8509803922, - 0.20392156862745098, - 0.1333333333, - 0.0039215686, - 0.831372549, - 0.20784313725490197, - 0.1529411765, - 0.0039215686, - 0.8117647059, - 0.21176470588235294, - 0.1725490196, - 0.0039215686, - 0.7921568627, - 0.21568627450980393, - 0.1960784314, - 0.0039215686, - 0.7725490196, - 0.2196078431372549, - 0.2156862745, - 0.0039215686, - 0.7529411765, - 0.2235294117647059, - 0.2352941176, - 0.0039215686, - 0.737254902, - 0.22745098039215686, - 0.2509803922, - 0.0039215686, - 0.7176470588, - 0.23137254901960785, - 0.2745098039, - 0.0039215686, - 0.6980392157, - 0.23529411764705885, - 0.2941176471, - 0.0039215686, - 0.6784313725, - 0.23921568627450984, - 0.3137254902, - 0.0039215686, - 0.6588235294, - 0.24313725490196078, - 0.3333333333, - 0.0039215686, - 0.6392156863, - 0.24705882352941178, - 0.3568627451, - 0.0039215686, - 0.6196078431, - 0.25098039215686274, - 0.3764705882, - 0.0039215686, - 0.6, - 0.2549019607843137, - 0.3960784314, - 0.0039215686, - 0.5803921569, - 0.25882352941176473, - 0.4156862745, - 0.0039215686, - 0.5607843137, - 0.2627450980392157, - 0.4392156863, - 0.0039215686, - 0.5411764706, - 0.26666666666666666, - 0.4588235294, - 0.0039215686, - 0.5215686275, - 0.27058823529411763, - 0.4784313725, - 0.0039215686, - 0.5019607843, - 0.27450980392156865, - 0.4980392157, - 0.0039215686, - 0.4823529412, - 0.2784313725490196, - 0.5215686275, - 0.0039215686, - 0.4666666667, - 0.2823529411764706, - 0.5411764706, - 0.0039215686, - 0.4470588235, - 0.28627450980392155, - 0.5607843137, - 0.0039215686, - 0.4274509804, - 0.2901960784313726, - 0.5803921569, - 0.0039215686, - 0.4078431373, - 0.29411764705882354, - 0.6039215686, - 0.0039215686, - 0.3882352941, - 0.2980392156862745, - 0.6235294118, - 0.0039215686, - 0.368627451, - 0.30196078431372547, - 0.6431372549, - 0.0039215686, - 0.3490196078, - 0.3058823529411765, - 0.662745098, - 0.0039215686, - 0.3294117647, - 0.30980392156862746, - 0.6862745098, - 0.0039215686, - 0.3098039216, - 0.3137254901960784, - 0.7058823529, - 0.0039215686, - 0.2901960784, - 0.3176470588235294, - 0.7254901961, - 0.0039215686, - 0.2705882353, - 0.3215686274509804, - 0.7450980392, - 0.0039215686, - 0.2509803922, - 0.3254901960784314, - 0.7647058824, - 0.0039215686, - 0.2352941176, - 0.32941176470588235, - 0.7843137255, - 0.0039215686, - 0.2156862745, - 0.3333333333333333, - 0.8039215686, - 0.0039215686, - 0.1960784314, - 0.33725490196078434, - 0.8235294118, - 0.0039215686, - 0.1764705882, - 0.3411764705882353, - 0.8470588235, - 0.0039215686, - 0.1568627451, - 0.34509803921568627, - 0.8666666667, - 0.0039215686, - 0.137254902, - 0.34901960784313724, - 0.8862745098, - 0.0039215686, - 0.1176470588, - 0.35294117647058826, - 0.9058823529, - 0.0039215686, - 0.0980392157, - 0.3568627450980392, - 0.9294117647, - 0.0039215686, - 0.0784313725, - 0.3607843137254902, - 0.9490196078, - 0.0039215686, - 0.0588235294, - 0.36470588235294116, - 0.968627451, - 0.0039215686, - 0.0392156863, - 0.3686274509803922, - 0.9921568627, - 0.0039215686, - 0.0235294118, - 0.37254901960784315, - 0.9529411765, - 0.0039215686, - 0.0588235294, - 0.3764705882352941, - 0.9529411765, - 0.0078431373, - 0.0549019608, - 0.3803921568627451, - 0.9529411765, - 0.0156862745, - 0.0549019608, - 0.3843137254901961, - 0.9529411765, - 0.0235294118, - 0.0549019608, - 0.38823529411764707, - 0.9529411765, - 0.031372549, - 0.0549019608, - 0.39215686274509803, - 0.9529411765, - 0.0352941176, - 0.0549019608, - 0.396078431372549, - 0.9529411765, - 0.0431372549, - 0.0549019608, - 0.4, - 0.9529411765, - 0.0509803922, - 0.0549019608, - 0.403921568627451, - 0.9529411765, - 0.0588235294, - 0.0549019608, - 0.40784313725490196, - 0.9529411765, - 0.062745098, - 0.0549019608, - 0.4117647058823529, - 0.9529411765, - 0.0705882353, - 0.0549019608, - 0.41568627450980394, - 0.9529411765, - 0.0784313725, - 0.0509803922, - 0.4196078431372549, - 0.9529411765, - 0.0862745098, - 0.0509803922, - 0.4235294117647059, - 0.9568627451, - 0.0941176471, - 0.0509803922, - 0.42745098039215684, - 0.9568627451, - 0.0980392157, - 0.0509803922, - 0.43137254901960786, - 0.9568627451, - 0.1058823529, - 0.0509803922, - 0.43529411764705883, - 0.9568627451, - 0.1137254902, - 0.0509803922, - 0.4392156862745098, - 0.9568627451, - 0.1215686275, - 0.0509803922, - 0.44313725490196076, - 0.9568627451, - 0.1254901961, - 0.0509803922, - 0.4470588235294118, - 0.9568627451, - 0.1333333333, - 0.0509803922, - 0.45098039215686275, - 0.9568627451, - 0.1411764706, - 0.0509803922, - 0.4549019607843137, - 0.9568627451, - 0.1490196078, - 0.0470588235, - 0.4588235294117647, - 0.9568627451, - 0.1568627451, - 0.0470588235, - 0.4627450980392157, - 0.9568627451, - 0.1607843137, - 0.0470588235, - 0.4666666666666667, - 0.9568627451, - 0.168627451, - 0.0470588235, - 0.4705882352941177, - 0.9607843137, - 0.1764705882, - 0.0470588235, - 0.4745098039215686, - 0.9607843137, - 0.1843137255, - 0.0470588235, - 0.4784313725490197, - 0.9607843137, - 0.1882352941, - 0.0470588235, - 0.48235294117647065, - 0.9607843137, - 0.1960784314, - 0.0470588235, - 0.48627450980392156, - 0.9607843137, - 0.2039215686, - 0.0470588235, - 0.49019607843137253, - 0.9607843137, - 0.2117647059, - 0.0470588235, - 0.49411764705882355, - 0.9607843137, - 0.2196078431, - 0.0431372549, - 0.4980392156862745, - 0.9607843137, - 0.2235294118, - 0.0431372549, - 0.5019607843137255, - 0.9607843137, - 0.231372549, - 0.0431372549, - 0.5058823529411764, - 0.9607843137, - 0.2392156863, - 0.0431372549, - 0.5098039215686274, - 0.9607843137, - 0.2470588235, - 0.0431372549, - 0.5137254901960784, - 0.9607843137, - 0.2509803922, - 0.0431372549, - 0.5176470588235295, - 0.9647058824, - 0.2549019608, - 0.0431372549, - 0.5215686274509804, - 0.9647058824, - 0.262745098, - 0.0431372549, - 0.5254901960784314, - 0.9647058824, - 0.2705882353, - 0.0431372549, - 0.5294117647058824, - 0.9647058824, - 0.2745098039, - 0.0431372549, - 0.5333333333333333, - 0.9647058824, - 0.2823529412, - 0.0392156863, - 0.5372549019607843, - 0.9647058824, - 0.2901960784, - 0.0392156863, - 0.5411764705882353, - 0.9647058824, - 0.2980392157, - 0.0392156863, - 0.5450980392156862, - 0.9647058824, - 0.3058823529, - 0.0392156863, - 0.5490196078431373, - 0.9647058824, - 0.3098039216, - 0.0392156863, - 0.5529411764705883, - 0.9647058824, - 0.3176470588, - 0.0392156863, - 0.5568627450980392, - 0.9647058824, - 0.3254901961, - 0.0392156863, - 0.5607843137254902, - 0.9647058824, - 0.3333333333, - 0.0392156863, - 0.5647058823529412, - 0.9647058824, - 0.337254902, - 0.0392156863, - 0.5686274509803921, - 0.968627451, - 0.3450980392, - 0.0392156863, - 0.5725490196078431, - 0.968627451, - 0.3529411765, - 0.0352941176, - 0.5764705882352941, - 0.968627451, - 0.3607843137, - 0.0352941176, - 0.5803921568627451, - 0.968627451, - 0.368627451, - 0.0352941176, - 0.5843137254901961, - 0.968627451, - 0.3725490196, - 0.0352941176, - 0.5882352941176471, - 0.968627451, - 0.3803921569, - 0.0352941176, - 0.592156862745098, - 0.968627451, - 0.3882352941, - 0.0352941176, - 0.596078431372549, - 0.968627451, - 0.3960784314, - 0.0352941176, - 0.6, - 0.968627451, - 0.4, - 0.0352941176, - 0.6039215686274509, - 0.968627451, - 0.4078431373, - 0.0352941176, - 0.6078431372549019, - 0.968627451, - 0.4156862745, - 0.0352941176, - 0.611764705882353, - 0.968627451, - 0.4235294118, - 0.031372549, - 0.615686274509804, - 0.9725490196, - 0.431372549, - 0.031372549, - 0.6196078431372549, - 0.9725490196, - 0.4352941176, - 0.031372549, - 0.6235294117647059, - 0.9725490196, - 0.4431372549, - 0.031372549, - 0.6274509803921569, - 0.9725490196, - 0.4509803922, - 0.031372549, - 0.6313725490196078, - 0.9725490196, - 0.4588235294, - 0.031372549, - 0.6352941176470588, - 0.9725490196, - 0.462745098, - 0.031372549, - 0.6392156862745098, - 0.9725490196, - 0.4705882353, - 0.031372549, - 0.6431372549019608, - 0.9725490196, - 0.4784313725, - 0.031372549, - 0.6470588235294118, - 0.9725490196, - 0.4862745098, - 0.031372549, - 0.6509803921568628, - 0.9725490196, - 0.4941176471, - 0.0274509804, - 0.6549019607843137, - 0.9725490196, - 0.4980392157, - 0.0274509804, - 0.6588235294117647, - 0.9725490196, - 0.5058823529, - 0.0274509804, - 0.6627450980392157, - 0.9764705882, - 0.5137254902, - 0.0274509804, - 0.6666666666666666, - 0.9764705882, - 0.5215686275, - 0.0274509804, - 0.6705882352941176, - 0.9764705882, - 0.5254901961, - 0.0274509804, - 0.6745098039215687, - 0.9764705882, - 0.5333333333, - 0.0274509804, - 0.6784313725490196, - 0.9764705882, - 0.5411764706, - 0.0274509804, - 0.6823529411764706, - 0.9764705882, - 0.5490196078, - 0.0274509804, - 0.6862745098039216, - 0.9764705882, - 0.5529411765, - 0.0274509804, - 0.6901960784313725, - 0.9764705882, - 0.5607843137, - 0.0235294118, - 0.6941176470588235, - 0.9764705882, - 0.568627451, - 0.0235294118, - 0.6980392156862745, - 0.9764705882, - 0.5764705882, - 0.0235294118, - 0.7019607843137254, - 0.9764705882, - 0.5843137255, - 0.0235294118, - 0.7058823529411765, - 0.9764705882, - 0.5882352941, - 0.0235294118, - 0.7098039215686275, - 0.9764705882, - 0.5960784314, - 0.0235294118, - 0.7137254901960784, - 0.9803921569, - 0.6039215686, - 0.0235294118, - 0.7176470588235294, - 0.9803921569, - 0.6117647059, - 0.0235294118, - 0.7215686274509804, - 0.9803921569, - 0.6156862745, - 0.0235294118, - 0.7254901960784313, - 0.9803921569, - 0.6235294118, - 0.0235294118, - 0.7294117647058823, - 0.9803921569, - 0.631372549, - 0.0196078431, - 0.7333333333333333, - 0.9803921569, - 0.6392156863, - 0.0196078431, - 0.7372549019607844, - 0.9803921569, - 0.6470588235, - 0.0196078431, - 0.7411764705882353, - 0.9803921569, - 0.6509803922, - 0.0196078431, - 0.7450980392156863, - 0.9803921569, - 0.6588235294, - 0.0196078431, - 0.7490196078431373, - 0.9803921569, - 0.6666666667, - 0.0196078431, - 0.7529411764705882, - 0.9803921569, - 0.6745098039, - 0.0196078431, - 0.7568627450980392, - 0.9803921569, - 0.6784313725, - 0.0196078431, - 0.7607843137254902, - 0.9843137255, - 0.6862745098, - 0.0196078431, - 0.7647058823529411, - 0.9843137255, - 0.6941176471, - 0.0196078431, - 0.7686274509803922, - 0.9843137255, - 0.7019607843, - 0.0156862745, - 0.7725490196078432, - 0.9843137255, - 0.7098039216, - 0.0156862745, - 0.7764705882352941, - 0.9843137255, - 0.7137254902, - 0.0156862745, - 0.7803921568627451, - 0.9843137255, - 0.7215686275, - 0.0156862745, - 0.7843137254901961, - 0.9843137255, - 0.7294117647, - 0.0156862745, - 0.788235294117647, - 0.9843137255, - 0.737254902, - 0.0156862745, - 0.792156862745098, - 0.9843137255, - 0.7411764706, - 0.0156862745, - 0.796078431372549, - 0.9843137255, - 0.7490196078, - 0.0156862745, - 0.8, - 0.9843137255, - 0.7529411765, - 0.0156862745, - 0.803921568627451, - 0.9843137255, - 0.7607843137, - 0.0156862745, - 0.807843137254902, - 0.9882352941, - 0.768627451, - 0.0156862745, - 0.8117647058823529, - 0.9882352941, - 0.768627451, - 0.0156862745, - 0.8156862745098039, - 0.9843137255, - 0.7843137255, - 0.0117647059, - 0.8196078431372549, - 0.9843137255, - 0.8, - 0.0117647059, - 0.8235294117647058, - 0.9843137255, - 0.8156862745, - 0.0117647059, - 0.8274509803921568, - 0.9803921569, - 0.831372549, - 0.0117647059, - 0.8313725490196079, - 0.9803921569, - 0.8431372549, - 0.0117647059, - 0.8352941176470589, - 0.9803921569, - 0.8588235294, - 0.0078431373, - 0.8392156862745098, - 0.9803921569, - 0.8745098039, - 0.0078431373, - 0.8431372549019608, - 0.9764705882, - 0.8901960784, - 0.0078431373, - 0.8470588235294118, - 0.9764705882, - 0.9058823529, - 0.0078431373, - 0.8509803921568627, - 0.9764705882, - 0.9176470588, - 0.0078431373, - 0.8549019607843137, - 0.9764705882, - 0.9333333333, - 0.0039215686, - 0.8588235294117647, - 0.9725490196, - 0.9490196078, - 0.0039215686, - 0.8627450980392157, - 0.9725490196, - 0.9647058824, - 0.0039215686, - 0.8666666666666667, - 0.9725490196, - 0.9803921569, - 0.0039215686, - 0.8705882352941177, - 0.9725490196, - 0.9960784314, - 0.0039215686, - 0.8745098039215686, - 0.9725490196, - 0.9960784314, - 0.0039215686, - 0.8784313725490196, - 0.9725490196, - 0.9960784314, - 0.0352941176, - 0.8823529411764706, - 0.9725490196, - 0.9960784314, - 0.0666666667, - 0.8862745098039215, - 0.9725490196, - 0.9960784314, - 0.0980392157, - 0.8901960784313725, - 0.9725490196, - 0.9960784314, - 0.1294117647, - 0.8941176470588236, - 0.9725490196, - 0.9960784314, - 0.1647058824, - 0.8980392156862745, - 0.9764705882, - 0.9960784314, - 0.1960784314, - 0.9019607843137255, - 0.9764705882, - 0.9960784314, - 0.2274509804, - 0.9058823529411765, - 0.9764705882, - 0.9960784314, - 0.2549019608, - 0.9098039215686274, - 0.9764705882, - 0.9960784314, - 0.2901960784, - 0.9137254901960784, - 0.9764705882, - 0.9960784314, - 0.3215686275, - 0.9176470588235294, - 0.9803921569, - 0.9960784314, - 0.3529411765, - 0.9215686274509803, - 0.9803921569, - 0.9960784314, - 0.3843137255, - 0.9254901960784314, - 0.9803921569, - 0.9960784314, - 0.4156862745, - 0.9294117647058824, - 0.9803921569, - 0.9960784314, - 0.4509803922, - 0.9333333333333333, - 0.9803921569, - 0.9960784314, - 0.4823529412, - 0.9372549019607843, - 0.9843137255, - 0.9960784314, - 0.5137254902, - 0.9411764705882354, - 0.9843137255, - 0.9960784314, - 0.5450980392, - 0.9450980392156864, - 0.9843137255, - 0.9960784314, - 0.5803921569, - 0.9490196078431372, - 0.9843137255, - 0.9960784314, - 0.6117647059, - 0.9529411764705882, - 0.9843137255, - 0.9960784314, - 0.6431372549, - 0.9568627450980394, - 0.9882352941, - 0.9960784314, - 0.6745098039, - 0.9607843137254903, - 0.9882352941, - 0.9960784314, - 0.7058823529, - 0.9647058823529413, - 0.9882352941, - 0.9960784314, - 0.7411764706, - 0.9686274509803922, - 0.9882352941, - 0.9960784314, - 0.768627451, - 0.9725490196078431, - 0.9882352941, - 0.9960784314, - 0.8, - 0.9764705882352941, - 0.9921568627, - 0.9960784314, - 0.831372549, - 0.9803921568627451, - 0.9921568627, - 0.9960784314, - 0.8666666667, - 0.984313725490196, - 0.9921568627, - 0.9960784314, - 0.8980392157, - 0.9882352941176471, - 0.9921568627, - 0.9960784314, - 0.9294117647, - 0.9921568627450981, - 0.9921568627, - 0.9960784314, - 0.9607843137, - 0.996078431372549, - 0.9960784314, - 0.9960784314, - 0.9607843137, - 1.0, - 0.9960784314, - 0.9960784314, - 0.9607843137, + 0.0, 0.0078431373, 0.0039215686, 0.1254901961, 0.00392156862745098, 0.0078431373, + 0.0039215686, 0.1254901961, 0.00784313725490196, 0.0078431373, 0.0039215686, 0.1882352941, + 0.011764705882352941, 0.0117647059, 0.0039215686, 0.2509803922, 0.01568627450980392, + 0.0117647059, 0.0039215686, 0.3098039216, 0.0196078431372549, 0.0156862745, 0.0039215686, + 0.3725490196, 0.023529411764705882, 0.0156862745, 0.0039215686, 0.3725490196, + 0.027450980392156862, 0.0156862745, 0.0039215686, 0.3725490196, 0.03137254901960784, + 0.0156862745, 0.0039215686, 0.3725490196, 0.03529411764705882, 0.0156862745, 0.0039215686, + 0.3725490196, 0.0392156862745098, 0.0156862745, 0.0039215686, 0.3725490196, + 0.043137254901960784, 0.0156862745, 0.0039215686, 0.3725490196, 0.047058823529411764, + 0.0156862745, 0.0039215686, 0.3725490196, 0.050980392156862744, 0.0156862745, 0.0039215686, + 0.3725490196, 0.054901960784313725, 0.0156862745, 0.0039215686, 0.3725490196, + 0.05882352941176471, 0.0156862745, 0.0039215686, 0.3725490196, 0.06274509803921569, + 0.0156862745, 0.0039215686, 0.3882352941, 0.06666666666666667, 0.0156862745, 0.0039215686, + 0.4078431373, 0.07058823529411765, 0.0156862745, 0.0039215686, 0.4235294118, + 0.07450980392156863, 0.0156862745, 0.0039215686, 0.4431372549, 0.0784313725490196, + 0.0156862745, 0.0039215686, 0.462745098, 0.08235294117647059, 0.0156862745, 0.0039215686, + 0.4784313725, 0.08627450980392157, 0.0156862745, 0.0039215686, 0.4980392157, + 0.09019607843137255, 0.0196078431, 0.0039215686, 0.5137254902, 0.09411764705882353, + 0.0196078431, 0.0039215686, 0.5333333333, 0.09803921568627451, 0.0196078431, 0.0039215686, + 0.5529411765, 0.10196078431372549, 0.0196078431, 0.0039215686, 0.568627451, + 0.10588235294117647, 0.0196078431, 0.0039215686, 0.5882352941, 0.10980392156862745, + 0.0196078431, 0.0039215686, 0.6039215686, 0.11372549019607843, 0.0196078431, 0.0039215686, + 0.6235294118, 0.11764705882352942, 0.0196078431, 0.0039215686, 0.6431372549, + 0.12156862745098039, 0.0235294118, 0.0039215686, 0.6588235294, 0.12549019607843137, + 0.0235294118, 0.0039215686, 0.6784313725, 0.12941176470588237, 0.0235294118, 0.0039215686, + 0.6980392157, 0.13333333333333333, 0.0235294118, 0.0039215686, 0.7137254902, + 0.13725490196078433, 0.0235294118, 0.0039215686, 0.7333333333, 0.1411764705882353, + 0.0235294118, 0.0039215686, 0.7490196078, 0.1450980392156863, 0.0235294118, 0.0039215686, + 0.7647058824, 0.14901960784313725, 0.0235294118, 0.0039215686, 0.7843137255, + 0.15294117647058825, 0.0274509804, 0.0039215686, 0.8, 0.1568627450980392, 0.0274509804, + 0.0039215686, 0.8196078431, 0.1607843137254902, 0.0274509804, 0.0039215686, 0.8352941176, + 0.16470588235294117, 0.0274509804, 0.0039215686, 0.8549019608, 0.16862745098039217, + 0.0274509804, 0.0039215686, 0.8745098039, 0.17254901960784313, 0.0274509804, 0.0039215686, + 0.8901960784, 0.17647058823529413, 0.0274509804, 0.0039215686, 0.9098039216, + 0.1803921568627451, 0.031372549, 0.0039215686, 0.9294117647, 0.1843137254901961, 0.031372549, + 0.0039215686, 0.9254901961, 0.18823529411764706, 0.0509803922, 0.0039215686, 0.9098039216, + 0.19215686274509805, 0.0705882353, 0.0039215686, 0.8901960784, 0.19607843137254902, + 0.0901960784, 0.0039215686, 0.8705882353, 0.2, 0.1137254902, 0.0039215686, 0.8509803922, + 0.20392156862745098, 0.1333333333, 0.0039215686, 0.831372549, 0.20784313725490197, + 0.1529411765, 0.0039215686, 0.8117647059, 0.21176470588235294, 0.1725490196, 0.0039215686, + 0.7921568627, 0.21568627450980393, 0.1960784314, 0.0039215686, 0.7725490196, + 0.2196078431372549, 0.2156862745, 0.0039215686, 0.7529411765, 0.2235294117647059, + 0.2352941176, 0.0039215686, 0.737254902, 0.22745098039215686, 0.2509803922, 0.0039215686, + 0.7176470588, 0.23137254901960785, 0.2745098039, 0.0039215686, 0.6980392157, + 0.23529411764705885, 0.2941176471, 0.0039215686, 0.6784313725, 0.23921568627450984, + 0.3137254902, 0.0039215686, 0.6588235294, 0.24313725490196078, 0.3333333333, 0.0039215686, + 0.6392156863, 0.24705882352941178, 0.3568627451, 0.0039215686, 0.6196078431, + 0.25098039215686274, 0.3764705882, 0.0039215686, 0.6, 0.2549019607843137, 0.3960784314, + 0.0039215686, 0.5803921569, 0.25882352941176473, 0.4156862745, 0.0039215686, 0.5607843137, + 0.2627450980392157, 0.4392156863, 0.0039215686, 0.5411764706, 0.26666666666666666, + 0.4588235294, 0.0039215686, 0.5215686275, 0.27058823529411763, 0.4784313725, 0.0039215686, + 0.5019607843, 0.27450980392156865, 0.4980392157, 0.0039215686, 0.4823529412, + 0.2784313725490196, 0.5215686275, 0.0039215686, 0.4666666667, 0.2823529411764706, + 0.5411764706, 0.0039215686, 0.4470588235, 0.28627450980392155, 0.5607843137, 0.0039215686, + 0.4274509804, 0.2901960784313726, 0.5803921569, 0.0039215686, 0.4078431373, + 0.29411764705882354, 0.6039215686, 0.0039215686, 0.3882352941, 0.2980392156862745, + 0.6235294118, 0.0039215686, 0.368627451, 0.30196078431372547, 0.6431372549, 0.0039215686, + 0.3490196078, 0.3058823529411765, 0.662745098, 0.0039215686, 0.3294117647, + 0.30980392156862746, 0.6862745098, 0.0039215686, 0.3098039216, 0.3137254901960784, + 0.7058823529, 0.0039215686, 0.2901960784, 0.3176470588235294, 0.7254901961, 0.0039215686, + 0.2705882353, 0.3215686274509804, 0.7450980392, 0.0039215686, 0.2509803922, + 0.3254901960784314, 0.7647058824, 0.0039215686, 0.2352941176, 0.32941176470588235, + 0.7843137255, 0.0039215686, 0.2156862745, 0.3333333333333333, 0.8039215686, 0.0039215686, + 0.1960784314, 0.33725490196078434, 0.8235294118, 0.0039215686, 0.1764705882, + 0.3411764705882353, 0.8470588235, 0.0039215686, 0.1568627451, 0.34509803921568627, + 0.8666666667, 0.0039215686, 0.137254902, 0.34901960784313724, 0.8862745098, 0.0039215686, + 0.1176470588, 0.35294117647058826, 0.9058823529, 0.0039215686, 0.0980392157, + 0.3568627450980392, 0.9294117647, 0.0039215686, 0.0784313725, 0.3607843137254902, + 0.9490196078, 0.0039215686, 0.0588235294, 0.36470588235294116, 0.968627451, 0.0039215686, + 0.0392156863, 0.3686274509803922, 0.9921568627, 0.0039215686, 0.0235294118, + 0.37254901960784315, 0.9529411765, 0.0039215686, 0.0588235294, 0.3764705882352941, + 0.9529411765, 0.0078431373, 0.0549019608, 0.3803921568627451, 0.9529411765, 0.0156862745, + 0.0549019608, 0.3843137254901961, 0.9529411765, 0.0235294118, 0.0549019608, + 0.38823529411764707, 0.9529411765, 0.031372549, 0.0549019608, 0.39215686274509803, + 0.9529411765, 0.0352941176, 0.0549019608, 0.396078431372549, 0.9529411765, 0.0431372549, + 0.0549019608, 0.4, 0.9529411765, 0.0509803922, 0.0549019608, 0.403921568627451, 0.9529411765, + 0.0588235294, 0.0549019608, 0.40784313725490196, 0.9529411765, 0.062745098, 0.0549019608, + 0.4117647058823529, 0.9529411765, 0.0705882353, 0.0549019608, 0.41568627450980394, + 0.9529411765, 0.0784313725, 0.0509803922, 0.4196078431372549, 0.9529411765, 0.0862745098, + 0.0509803922, 0.4235294117647059, 0.9568627451, 0.0941176471, 0.0509803922, + 0.42745098039215684, 0.9568627451, 0.0980392157, 0.0509803922, 0.43137254901960786, + 0.9568627451, 0.1058823529, 0.0509803922, 0.43529411764705883, 0.9568627451, 0.1137254902, + 0.0509803922, 0.4392156862745098, 0.9568627451, 0.1215686275, 0.0509803922, + 0.44313725490196076, 0.9568627451, 0.1254901961, 0.0509803922, 0.4470588235294118, + 0.9568627451, 0.1333333333, 0.0509803922, 0.45098039215686275, 0.9568627451, 0.1411764706, + 0.0509803922, 0.4549019607843137, 0.9568627451, 0.1490196078, 0.0470588235, + 0.4588235294117647, 0.9568627451, 0.1568627451, 0.0470588235, 0.4627450980392157, + 0.9568627451, 0.1607843137, 0.0470588235, 0.4666666666666667, 0.9568627451, 0.168627451, + 0.0470588235, 0.4705882352941177, 0.9607843137, 0.1764705882, 0.0470588235, + 0.4745098039215686, 0.9607843137, 0.1843137255, 0.0470588235, 0.4784313725490197, + 0.9607843137, 0.1882352941, 0.0470588235, 0.48235294117647065, 0.9607843137, 0.1960784314, + 0.0470588235, 0.48627450980392156, 0.9607843137, 0.2039215686, 0.0470588235, + 0.49019607843137253, 0.9607843137, 0.2117647059, 0.0470588235, 0.49411764705882355, + 0.9607843137, 0.2196078431, 0.0431372549, 0.4980392156862745, 0.9607843137, 0.2235294118, + 0.0431372549, 0.5019607843137255, 0.9607843137, 0.231372549, 0.0431372549, 0.5058823529411764, + 0.9607843137, 0.2392156863, 0.0431372549, 0.5098039215686274, 0.9607843137, 0.2470588235, + 0.0431372549, 0.5137254901960784, 0.9607843137, 0.2509803922, 0.0431372549, + 0.5176470588235295, 0.9647058824, 0.2549019608, 0.0431372549, 0.5215686274509804, + 0.9647058824, 0.262745098, 0.0431372549, 0.5254901960784314, 0.9647058824, 0.2705882353, + 0.0431372549, 0.5294117647058824, 0.9647058824, 0.2745098039, 0.0431372549, + 0.5333333333333333, 0.9647058824, 0.2823529412, 0.0392156863, 0.5372549019607843, + 0.9647058824, 0.2901960784, 0.0392156863, 0.5411764705882353, 0.9647058824, 0.2980392157, + 0.0392156863, 0.5450980392156862, 0.9647058824, 0.3058823529, 0.0392156863, + 0.5490196078431373, 0.9647058824, 0.3098039216, 0.0392156863, 0.5529411764705883, + 0.9647058824, 0.3176470588, 0.0392156863, 0.5568627450980392, 0.9647058824, 0.3254901961, + 0.0392156863, 0.5607843137254902, 0.9647058824, 0.3333333333, 0.0392156863, + 0.5647058823529412, 0.9647058824, 0.337254902, 0.0392156863, 0.5686274509803921, 0.968627451, + 0.3450980392, 0.0392156863, 0.5725490196078431, 0.968627451, 0.3529411765, 0.0352941176, + 0.5764705882352941, 0.968627451, 0.3607843137, 0.0352941176, 0.5803921568627451, 0.968627451, + 0.368627451, 0.0352941176, 0.5843137254901961, 0.968627451, 0.3725490196, 0.0352941176, + 0.5882352941176471, 0.968627451, 0.3803921569, 0.0352941176, 0.592156862745098, 0.968627451, + 0.3882352941, 0.0352941176, 0.596078431372549, 0.968627451, 0.3960784314, 0.0352941176, 0.6, + 0.968627451, 0.4, 0.0352941176, 0.6039215686274509, 0.968627451, 0.4078431373, 0.0352941176, + 0.6078431372549019, 0.968627451, 0.4156862745, 0.0352941176, 0.611764705882353, 0.968627451, + 0.4235294118, 0.031372549, 0.615686274509804, 0.9725490196, 0.431372549, 0.031372549, + 0.6196078431372549, 0.9725490196, 0.4352941176, 0.031372549, 0.6235294117647059, 0.9725490196, + 0.4431372549, 0.031372549, 0.6274509803921569, 0.9725490196, 0.4509803922, 0.031372549, + 0.6313725490196078, 0.9725490196, 0.4588235294, 0.031372549, 0.6352941176470588, 0.9725490196, + 0.462745098, 0.031372549, 0.6392156862745098, 0.9725490196, 0.4705882353, 0.031372549, + 0.6431372549019608, 0.9725490196, 0.4784313725, 0.031372549, 0.6470588235294118, 0.9725490196, + 0.4862745098, 0.031372549, 0.6509803921568628, 0.9725490196, 0.4941176471, 0.0274509804, + 0.6549019607843137, 0.9725490196, 0.4980392157, 0.0274509804, 0.6588235294117647, + 0.9725490196, 0.5058823529, 0.0274509804, 0.6627450980392157, 0.9764705882, 0.5137254902, + 0.0274509804, 0.6666666666666666, 0.9764705882, 0.5215686275, 0.0274509804, + 0.6705882352941176, 0.9764705882, 0.5254901961, 0.0274509804, 0.6745098039215687, + 0.9764705882, 0.5333333333, 0.0274509804, 0.6784313725490196, 0.9764705882, 0.5411764706, + 0.0274509804, 0.6823529411764706, 0.9764705882, 0.5490196078, 0.0274509804, + 0.6862745098039216, 0.9764705882, 0.5529411765, 0.0274509804, 0.6901960784313725, + 0.9764705882, 0.5607843137, 0.0235294118, 0.6941176470588235, 0.9764705882, 0.568627451, + 0.0235294118, 0.6980392156862745, 0.9764705882, 0.5764705882, 0.0235294118, + 0.7019607843137254, 0.9764705882, 0.5843137255, 0.0235294118, 0.7058823529411765, + 0.9764705882, 0.5882352941, 0.0235294118, 0.7098039215686275, 0.9764705882, 0.5960784314, + 0.0235294118, 0.7137254901960784, 0.9803921569, 0.6039215686, 0.0235294118, + 0.7176470588235294, 0.9803921569, 0.6117647059, 0.0235294118, 0.7215686274509804, + 0.9803921569, 0.6156862745, 0.0235294118, 0.7254901960784313, 0.9803921569, 0.6235294118, + 0.0235294118, 0.7294117647058823, 0.9803921569, 0.631372549, 0.0196078431, 0.7333333333333333, + 0.9803921569, 0.6392156863, 0.0196078431, 0.7372549019607844, 0.9803921569, 0.6470588235, + 0.0196078431, 0.7411764705882353, 0.9803921569, 0.6509803922, 0.0196078431, + 0.7450980392156863, 0.9803921569, 0.6588235294, 0.0196078431, 0.7490196078431373, + 0.9803921569, 0.6666666667, 0.0196078431, 0.7529411764705882, 0.9803921569, 0.6745098039, + 0.0196078431, 0.7568627450980392, 0.9803921569, 0.6784313725, 0.0196078431, + 0.7607843137254902, 0.9843137255, 0.6862745098, 0.0196078431, 0.7647058823529411, + 0.9843137255, 0.6941176471, 0.0196078431, 0.7686274509803922, 0.9843137255, 0.7019607843, + 0.0156862745, 0.7725490196078432, 0.9843137255, 0.7098039216, 0.0156862745, + 0.7764705882352941, 0.9843137255, 0.7137254902, 0.0156862745, 0.7803921568627451, + 0.9843137255, 0.7215686275, 0.0156862745, 0.7843137254901961, 0.9843137255, 0.7294117647, + 0.0156862745, 0.788235294117647, 0.9843137255, 0.737254902, 0.0156862745, 0.792156862745098, + 0.9843137255, 0.7411764706, 0.0156862745, 0.796078431372549, 0.9843137255, 0.7490196078, + 0.0156862745, 0.8, 0.9843137255, 0.7529411765, 0.0156862745, 0.803921568627451, 0.9843137255, + 0.7607843137, 0.0156862745, 0.807843137254902, 0.9882352941, 0.768627451, 0.0156862745, + 0.8117647058823529, 0.9882352941, 0.768627451, 0.0156862745, 0.8156862745098039, 0.9843137255, + 0.7843137255, 0.0117647059, 0.8196078431372549, 0.9843137255, 0.8, 0.0117647059, + 0.8235294117647058, 0.9843137255, 0.8156862745, 0.0117647059, 0.8274509803921568, + 0.9803921569, 0.831372549, 0.0117647059, 0.8313725490196079, 0.9803921569, 0.8431372549, + 0.0117647059, 0.8352941176470589, 0.9803921569, 0.8588235294, 0.0078431373, + 0.8392156862745098, 0.9803921569, 0.8745098039, 0.0078431373, 0.8431372549019608, + 0.9764705882, 0.8901960784, 0.0078431373, 0.8470588235294118, 0.9764705882, 0.9058823529, + 0.0078431373, 0.8509803921568627, 0.9764705882, 0.9176470588, 0.0078431373, + 0.8549019607843137, 0.9764705882, 0.9333333333, 0.0039215686, 0.8588235294117647, + 0.9725490196, 0.9490196078, 0.0039215686, 0.8627450980392157, 0.9725490196, 0.9647058824, + 0.0039215686, 0.8666666666666667, 0.9725490196, 0.9803921569, 0.0039215686, + 0.8705882352941177, 0.9725490196, 0.9960784314, 0.0039215686, 0.8745098039215686, + 0.9725490196, 0.9960784314, 0.0039215686, 0.8784313725490196, 0.9725490196, 0.9960784314, + 0.0352941176, 0.8823529411764706, 0.9725490196, 0.9960784314, 0.0666666667, + 0.8862745098039215, 0.9725490196, 0.9960784314, 0.0980392157, 0.8901960784313725, + 0.9725490196, 0.9960784314, 0.1294117647, 0.8941176470588236, 0.9725490196, 0.9960784314, + 0.1647058824, 0.8980392156862745, 0.9764705882, 0.9960784314, 0.1960784314, + 0.9019607843137255, 0.9764705882, 0.9960784314, 0.2274509804, 0.9058823529411765, + 0.9764705882, 0.9960784314, 0.2549019608, 0.9098039215686274, 0.9764705882, 0.9960784314, + 0.2901960784, 0.9137254901960784, 0.9764705882, 0.9960784314, 0.3215686275, + 0.9176470588235294, 0.9803921569, 0.9960784314, 0.3529411765, 0.9215686274509803, + 0.9803921569, 0.9960784314, 0.3843137255, 0.9254901960784314, 0.9803921569, 0.9960784314, + 0.4156862745, 0.9294117647058824, 0.9803921569, 0.9960784314, 0.4509803922, + 0.9333333333333333, 0.9803921569, 0.9960784314, 0.4823529412, 0.9372549019607843, + 0.9843137255, 0.9960784314, 0.5137254902, 0.9411764705882354, 0.9843137255, 0.9960784314, + 0.5450980392, 0.9450980392156864, 0.9843137255, 0.9960784314, 0.5803921569, + 0.9490196078431372, 0.9843137255, 0.9960784314, 0.6117647059, 0.9529411764705882, + 0.9843137255, 0.9960784314, 0.6431372549, 0.9568627450980394, 0.9882352941, 0.9960784314, + 0.6745098039, 0.9607843137254903, 0.9882352941, 0.9960784314, 0.7058823529, + 0.9647058823529413, 0.9882352941, 0.9960784314, 0.7411764706, 0.9686274509803922, + 0.9882352941, 0.9960784314, 0.768627451, 0.9725490196078431, 0.9882352941, 0.9960784314, 0.8, + 0.9764705882352941, 0.9921568627, 0.9960784314, 0.831372549, 0.9803921568627451, 0.9921568627, + 0.9960784314, 0.8666666667, 0.984313725490196, 0.9921568627, 0.9960784314, 0.8980392157, + 0.9882352941176471, 0.9921568627, 0.9960784314, 0.9294117647, 0.9921568627450981, + 0.9921568627, 0.9960784314, 0.9607843137, 0.996078431372549, 0.9960784314, 0.9960784314, + 0.9607843137, 1.0, 0.9960784314, 0.9960784314, 0.9607843137, ], }, ]; diff --git a/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js b/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js index 996ebd0f50..b367872931 100644 --- a/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js +++ b/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js @@ -1,7 +1,4 @@ -export default function createAndDownloadTMTVReport( - segReport, - additionalReportRows -) { +export default function createAndDownloadTMTVReport(segReport, additionalReportRows) { const firstReport = segReport[Object.keys(segReport)[0]]; const columns = Object.keys(firstReport); const csv = [columns.join(',')]; @@ -11,9 +8,7 @@ export default function createAndDownloadTMTVReport( columns.forEach(column => { // if it is array then we need to replace , with space to avoid csv parsing error row.push( - Array.isArray(segmentation[column]) - ? segmentation[column].join(' ') - : segmentation[column] + Array.isArray(segmentation[column]) ? segmentation[column].join(' ') : segmentation[column] ); }); csv.push(row.join(',')); diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js index 147b0cbf60..afcab5604a 100644 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js +++ b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js @@ -98,15 +98,9 @@ function initializeDataset(annotations, metadataProvider) { const rtSOPInstanceUID = DicomMetaDictionary.uid(); // get the first annotation data - const { - referencedImageId: imageId, - FrameOfReferenceUID, - } = annotations[0].metadata; + const { referencedImageId: imageId, FrameOfReferenceUID } = annotations[0].metadata; - const { studyInstanceUID } = metadataProvider.get( - 'generalSeriesModule', - imageId - ); + const { studyInstanceUID } = metadataProvider.get('generalSeriesModule', imageId); const patientModule = getPatientModule(imageId, metadataProvider); const rtSeriesModule = getRTSeriesModule(imageId, metadataProvider); @@ -136,23 +130,11 @@ function initializeDataset(annotations, metadataProvider) { } function getPatientModule(imageId, metadataProvider) { - const generalSeriesModule = metadataProvider.get( - 'generalSeriesModule', - imageId - ); - const generalStudyModule = metadataProvider.get( - 'generalStudyModule', - imageId - ); - const patientStudyModule = metadataProvider.get( - 'patientStudyModule', - imageId - ); + const generalSeriesModule = metadataProvider.get('generalSeriesModule', imageId); + const generalStudyModule = metadataProvider.get('generalStudyModule', imageId); + const patientStudyModule = metadataProvider.get('patientStudyModule', imageId); const patientModule = metadataProvider.get('patientModule', imageId); - const patientDemographicModule = metadataProvider.get( - 'patientDemographicModule', - imageId - ); + const patientDemographicModule = metadataProvider.get('patientDemographicModule', imageId); return { Modality: generalSeriesModule.modality, @@ -169,11 +151,7 @@ function getPatientModule(imageId, metadataProvider) { }; } -function getReferencedFrameOfReferenceSequence( - toolData, - metadataProvider, - dataset -) { +function getReferencedFrameOfReferenceSequence(toolData, metadataProvider, dataset) { const { referencedImageId: imageId, FrameOfReferenceUID } = toolData.metadata; const instance = metadataProvider.get('instance', imageId); const { SeriesInstanceUID } = instance; @@ -190,9 +168,7 @@ function getReferencedFrameOfReferenceSequence( RTReferencedSeriesSequence: [ { SeriesInstanceUID, - ContourImageSequence: [ - ...ReferencedSeriesSequence[0].ReferencedInstanceSequence, - ], + ContourImageSequence: [...ReferencedSeriesSequence[0].ReferencedInstanceSequence], }, ], }, @@ -209,10 +185,7 @@ function getReferencedSeriesSequence(toolData, index, metadataProvider) { const ReferencedSeriesSequence = []; if (SeriesInstanceUID) { - const series = DicomMetadataStore.getSeries( - StudyInstanceUID, - SeriesInstanceUID - ); + const series = DicomMetadataStore.getSeries(StudyInstanceUID, SeriesInstanceUID); const ReferencedSeries = { SeriesInstanceUID, diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js index f74fdc72ae..d1d67cebf8 100644 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js +++ b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js @@ -20,18 +20,13 @@ class AnnotationToPointData { const toolClass = AnnotationToPointData.TOOL_NAMES[toolName]; if (!toolClass) { - throw new Error( - `Unknown tool type: ${toolName}, cannot convert to RTSSReport` - ); + throw new Error(`Unknown tool type: ${toolName}, cannot convert to RTSSReport`); } // Each toolData should become a list of contours, ContourSequence // contains a list of contours with their pointData, their geometry // type and their length. - const ContourSequence = toolClass.getContourSequence( - annotation, - metadataProvider - ); + const ContourSequence = toolClass.getContourSequence(annotation, metadataProvider); // Todo: random rgb color for now, options should be passed in const color = [ diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js index 803bef365d..c3b86a627a 100644 --- a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js +++ b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js @@ -29,12 +29,7 @@ function getPointData(points) { // Since this is a closed contour, the order of the points is important. // re-order the points to be in the correct order clockwise // Spread to make sure Float32Arrays are converted to arrays - const orderedPoints = [ - ...points[0], - ...points[1], - ...points[3], - ...points[2], - ]; + const orderedPoints = [...points[0], ...points[1], ...points[3], ...points[2]]; const pointsArray = orderedPoints.flat(); // reduce the precision of the points to 2 decimal places diff --git a/extensions/tmtv/src/utils/getThresholdValue.ts b/extensions/tmtv/src/utils/getThresholdValue.ts index d11872dd0f..137b5b9a6d 100644 --- a/extensions/tmtv/src/utils/getThresholdValue.ts +++ b/extensions/tmtv/src/utils/getThresholdValue.ts @@ -3,10 +3,7 @@ import * as csTools from '@cornerstonejs/tools'; function getRoiStats(referencedVolume, annotations) { // roiStats const { imageData } = referencedVolume; - const values = imageData - .getPointData() - .getScalars() - .getData(); + const values = imageData.getPointData().getScalars().getData(); // Todo: add support for other strategies const { fn, baseValue } = _getStrategyFn('max'); @@ -59,9 +56,10 @@ function getThresholdValues( }; } -function _getStrategyFn( - statistic -): { fn: (a: number, b: number) => number; baseValue: number } { +function _getStrategyFn(statistic): { + fn: (a: number, b: number) => number; + baseValue: number; +} { const baseValue = -Infinity; const fn = (number, maxValue) => { if (number > maxValue) { diff --git a/extensions/tmtv/src/utils/hpViewports.ts b/extensions/tmtv/src/utils/hpViewports.ts index af2b760d43..b716a9ef10 100644 --- a/extensions/tmtv/src/utils/hpViewports.ts +++ b/extensions/tmtv/src/utils/hpViewports.ts @@ -114,6 +114,9 @@ const ptAXIAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], }, @@ -155,6 +158,9 @@ const ptSAGITTAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], }, @@ -196,6 +202,9 @@ const ptCORONAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], }, @@ -246,6 +255,9 @@ const fusionAXIAL = { id: 'ptFusionWLSync', source: false, target: true, + options: { + syncInvertState: false, + }, }, ], }, @@ -254,16 +266,20 @@ const fusionAXIAL = { id: 'ctDisplaySet', }, { + id: 'ptDisplaySet', options: { colormap: { name: 'hsv', - opacityMapping: [{ value: 0.1, opacity: 0.9 }], + opacity: [ + { value: 0, opacity: 0 }, + { value: 0.1, opacity: 0.9 }, + { value: 1, opacity: 0.95 }, + ], }, voi: { custom: 'getPTVOIRange', }, }, - id: 'ptDisplaySet', }, ], }; @@ -302,6 +318,9 @@ const fusionSAGITTAL = { id: 'ptFusionWLSync', source: false, target: true, + options: { + syncInvertState: false, + }, }, ], }, @@ -310,16 +329,20 @@ const fusionSAGITTAL = { id: 'ctDisplaySet', }, { + id: 'ptDisplaySet', options: { colormap: { name: 'hsv', - opacityMapping: [{ value: 0.1, opacity: 0.9 }], + opacity: [ + { value: 0, opacity: 0 }, + { value: 0.1, opacity: 0.9 }, + { value: 1, opacity: 0.95 }, + ], }, voi: { custom: 'getPTVOIRange', }, }, - id: 'ptDisplaySet', }, ], }; @@ -358,6 +381,9 @@ const fusionCORONAL = { id: 'ptFusionWLSync', source: false, target: true, + options: { + syncInvertState: false, + }, }, ], }, @@ -366,16 +392,20 @@ const fusionCORONAL = { id: 'ctDisplaySet', }, { + id: 'ptDisplaySet', options: { colormap: { name: 'hsv', - opacityMapping: [{ value: 0.1, opacity: 0.9 }], + opacity: [ + { value: 0, opacity: 0 }, + { value: 0.1, opacity: 0.9 }, + { value: 1, opacity: 0.95 }, + ], }, voi: { custom: 'getPTVOIRange', }, }, - id: 'ptDisplaySet', }, ], }; @@ -399,6 +429,9 @@ const mipSAGITTAL = { id: 'ptFusionWLSync', source: true, target: false, + options: { + syncInvertState: false, + }, }, ], diff --git a/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js b/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js index 5d2100aff8..a6131e2ac5 100644 --- a/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js +++ b/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js @@ -10,11 +10,7 @@ const RectangleROIStartEndThreshold = { * @param {Object} cornerstone Cornerstone event data * @return {Measurement} Measurement instance */ - toMeasurement: ( - csToolsEventDetail, - displaySetService, - cornerstoneViewportService - ) => { + toMeasurement: (csToolsEventDetail, displaySetService, cornerstoneViewportService) => { const { annotation, viewportId } = csToolsEventDetail; const { metadata, data, annotationUID } = annotation; @@ -30,11 +26,7 @@ const RectangleROIStartEndThreshold = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, cornerstoneViewportService, viewportId diff --git a/jest.config.js b/jest.config.js index 22b0ac8f1e..c19684dbde 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,5 +13,5 @@ module.exports = { '/extensions/*/jest.config.js', //'/modes/*/jest.config.js' // Enable if any mode definitions start including tests ], - coverageDirectory: "/coverage/" + coverageDirectory: '/coverage/', }; diff --git a/lerna.json b/lerna.json index 09786c5715..b343d95db2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,5 @@ { - "version": "3.6.0", + "version": "3.7.0-beta.85", "packages": ["extensions/*", "platform/*", "modes/*"], - "npmClient": "yarn", - "useWorkspaces": true + "npmClient": "yarn" } diff --git a/modes/basic-dev-mode/.webpack/webpack.dev.js b/modes/basic-dev-mode/.webpack/webpack.dev.js index 2bc3ced0b9..4bf848b6c5 100644 --- a/modes/basic-dev-mode/.webpack/webpack.dev.js +++ b/modes/basic-dev-mode/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/modes/basic-dev-mode/.webpack/webpack.prod.js b/modes/basic-dev-mode/.webpack/webpack.prod.js index 7147d3b06d..26c62c7d4a 100644 --- a/modes/basic-dev-mode/.webpack/webpack.prod.js +++ b/modes/basic-dev-mode/.webpack/webpack.prod.js @@ -12,7 +12,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); @@ -38,13 +37,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/modes/basic-dev-mode/CHANGELOG.md b/modes/basic-dev-mode/CHANGELOG.md new file mode 100644 index 0000000000..5aee83658a --- /dev/null +++ b/modes/basic-dev-mode/CHANGELOG.md @@ -0,0 +1,238 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/mode-basic-dev-mode diff --git a/modes/basic-dev-mode/package.json b/modes/basic-dev-mode/package.json index 6c6b1504c2..ef62f3e7ae 100644 --- a/modes/basic-dev-mode/package.json +++ b/modes/basic-dev-mode/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-basic-dev-mode", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "Basic OHIF Viewer Using Cornerstone", "author": "OHIF", "license": "MIT", @@ -29,12 +29,12 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-cornerstone-dicom-sr": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/extension-dicom-pdf": "3.6.0", - "@ohif/extension-dicom-video": "3.6.0" + "@ohif/core": "3.7.0-beta.85", + "@ohif/extension-cornerstone": "3.7.0-beta.85", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.85", + "@ohif/extension-default": "3.7.0-beta.85", + "@ohif/extension-dicom-pdf": "3.7.0-beta.85", + "@ohif/extension-dicom-video": "3.7.0-beta.85" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/basic-dev-mode/src/index.js b/modes/basic-dev-mode/src/index.js index 0f769854ec..411409e1f4 100644 --- a/modes/basic-dev-mode/src/index.js +++ b/modes/basic-dev-mode/src/index.js @@ -19,14 +19,12 @@ const cs3d = { }; const dicomsr = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', + sopClassHandler: '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', }; const dicomvideo = { - sopClassHandler: - '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', + sopClassHandler: '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video', }; @@ -86,18 +84,18 @@ function modeFactory({ modeConfiguration }) { { toolName: toolNames.CalibrationLine }, ], // enabled + enabled: [{ toolName: toolNames.ImageOverlayViewer }], // disabled }; const toolGroupId = 'default'; - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, configs); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); let unsubscribe; const activateTool = () => { toolbarService.recordInteraction({ groupId: 'WindowLevel', - itemId: 'WindowLevel', interactionType: 'tool', commands: [ { @@ -134,11 +132,7 @@ function modeFactory({ modeConfiguration }) { ]); }, onModeExit: ({ servicesManager }) => { - const { - toolGroupService, - measurementService, - toolbarService, - } = servicesManager.services; + const { toolGroupService, measurementService, toolbarService } = servicesManager.services; toolGroupService.destroy(); }, diff --git a/modes/basic-test-mode/.webpack/webpack.dev.js b/modes/basic-test-mode/.webpack/webpack.dev.js index 2bc3ced0b9..4bf848b6c5 100644 --- a/modes/basic-test-mode/.webpack/webpack.dev.js +++ b/modes/basic-test-mode/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/modes/basic-test-mode/.webpack/webpack.prod.js b/modes/basic-test-mode/.webpack/webpack.prod.js index 677f478813..25e5a75dd2 100644 --- a/modes/basic-test-mode/.webpack/webpack.prod.js +++ b/modes/basic-test-mode/.webpack/webpack.prod.js @@ -40,13 +40,7 @@ module.exports = (env, argv) => { libraryExport: 'default', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/modes/basic-test-mode/CHANGELOG.md b/modes/basic-test-mode/CHANGELOG.md new file mode 100644 index 0000000000..b18342f042 --- /dev/null +++ b/modes/basic-test-mode/CHANGELOG.md @@ -0,0 +1,235 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/mode-test + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/mode-test diff --git a/modes/basic-test-mode/package.json b/modes/basic-test-mode/package.json index c832649cfd..008bfee2cb 100644 --- a/modes/basic-test-mode/package.json +++ b/modes/basic-test-mode/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-test", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "Basic mode for testing", "author": "OHIF", "license": "MIT", @@ -32,14 +32,14 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-cornerstone-dicom-sr": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/extension-dicom-pdf": "3.6.0", - "@ohif/extension-dicom-video": "3.6.0", - "@ohif/extension-measurement-tracking": "3.6.0", - "@ohif/extension-test": "3.6.0" + "@ohif/core": "3.7.0-beta.85", + "@ohif/extension-cornerstone": "3.7.0-beta.85", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.85", + "@ohif/extension-default": "3.7.0-beta.85", + "@ohif/extension-dicom-pdf": "3.7.0-beta.85", + "@ohif/extension-dicom-video": "3.7.0-beta.85", + "@ohif/extension-measurement-tracking": "3.7.0-beta.85", + "@ohif/extension-test": "3.7.0-beta.85" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/basic-test-mode/src/index.js b/modes/basic-test-mode/src/index.js index d465b7e099..ab68c94699 100644 --- a/modes/basic-test-mode/src/index.js +++ b/modes/basic-test-mode/src/index.js @@ -14,22 +14,18 @@ const ohif = { }; const tracked = { - measurements: - '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', + measurements: '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', thumbnailList: '@ohif/extension-measurement-tracking.panelModule.seriesList', - viewport: - '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', + viewport: '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', }; const dicomsr = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', + sopClassHandler: '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', }; const dicomvideo = { - sopClassHandler: - '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', + sopClassHandler: '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video', }; @@ -39,8 +35,7 @@ const dicompdf = { }; const dicomSeg = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', + sopClassHandler: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', viewport: '@ohif/extension-cornerstone-dicom-seg.viewportModule.dicom-seg', panel: '@ohif/extension-cornerstone-dicom-seg.panelModule.panelSegmentation', }; @@ -68,12 +63,8 @@ function modeFactory() { * Lifecycle hooks */ onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { - const { - measurementService, - toolbarService, - toolGroupService, - customizationService, - } = servicesManager.services; + const { measurementService, toolbarService, toolGroupService, customizationService } = + servicesManager.services; measurementService.clearMeasurements(); @@ -90,7 +81,6 @@ function modeFactory() { const activateTool = () => { toolbarService.recordInteraction({ groupId: 'WindowLevel', - itemId: 'WindowLevel', interactionType: 'tool', commands: [ { @@ -147,13 +137,12 @@ function modeFactory() { series: [], }, - isValidMode: function({ modalities }) { + isValidMode: function ({ modalities }) { const modalities_list = modalities.split('\\'); // Exclude non-image modalities - return !!modalities_list.filter( - modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1 - ).length; + return !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1) + .length; }, routes: [ { diff --git a/modes/basic-test-mode/src/initToolGroups.js b/modes/basic-test-mode/src/initToolGroups.js index ccc5c1a6df..3110f24f88 100644 --- a/modes/basic-test-mode/src/initToolGroups.js +++ b/modes/basic-test-mode/src/initToolGroups.js @@ -1,9 +1,4 @@ -function initDefaultToolGroup( - extensionManager, - toolGroupService, - commandsManager, - toolGroupId -) { +function initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, toolGroupId) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' ); @@ -28,7 +23,23 @@ function initDefaultToolGroup( ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, @@ -44,24 +55,7 @@ function initDefaultToolGroup( disabled: [{ toolName: toolNames.ReferenceLines }], }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { @@ -122,25 +116,8 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - const toolGroupId = 'SRToolGroup'; - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { @@ -168,7 +145,23 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, @@ -179,7 +172,16 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { { toolName: toolNames.SegmentationDisplay }, ], disabled: [ - { toolName: toolNames.Crosshairs }, + { + toolName: toolNames.Crosshairs, + configuration: { + viewportIndicators: false, + autoPan: { + enabled: false, + panSize: 10, + }, + }, + }, { toolName: toolNames.ReferenceLines }, ], @@ -187,40 +189,11 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.Crosshairs]: { - viewportIndicators: false, - autoPan: { - enabled: false, - panSize: 10, - }, - }, - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - toolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools('mpr', tools); } function initToolGroups(extensionManager, toolGroupService, commandsManager) { - initDefaultToolGroup( - extensionManager, - toolGroupService, - commandsManager, - 'default' - ); + initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, 'default'); initSRToolGroup(extensionManager, toolGroupService, commandsManager); initMPRToolGroup(extensionManager, toolGroupService, commandsManager); } diff --git a/modes/longitudinal/.webpack/webpack.dev.js b/modes/longitudinal/.webpack/webpack.dev.js index 2bc3ced0b9..4bf848b6c5 100644 --- a/modes/longitudinal/.webpack/webpack.dev.js +++ b/modes/longitudinal/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/modes/longitudinal/.webpack/webpack.prod.js b/modes/longitudinal/.webpack/webpack.prod.js index 98ab53e924..6b84361f24 100644 --- a/modes/longitudinal/.webpack/webpack.prod.js +++ b/modes/longitudinal/.webpack/webpack.prod.js @@ -39,13 +39,7 @@ module.exports = (env, argv) => { libraryExport: 'default', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/modes/longitudinal/CHANGELOG.md b/modes/longitudinal/CHANGELOG.md new file mode 100644 index 0000000000..304c05e44c --- /dev/null +++ b/modes/longitudinal/CHANGELOG.md @@ -0,0 +1,238 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/mode-longitudinal + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/mode-longitudinal diff --git a/modes/longitudinal/package.json b/modes/longitudinal/package.json index ca4c55779b..8c481a9e20 100644 --- a/modes/longitudinal/package.json +++ b/modes/longitudinal/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-longitudinal", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "Longitudinal Workflow", "author": "OHIF", "license": "MIT", @@ -32,15 +32,15 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-cornerstone-dicom-rt": "3.6.0", - "@ohif/extension-cornerstone-dicom-seg": "3.6.0", - "@ohif/extension-cornerstone-dicom-sr": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/extension-dicom-pdf": "3.6.0", - "@ohif/extension-dicom-video": "3.6.0", - "@ohif/extension-measurement-tracking": "3.6.0" + "@ohif/core": "3.7.0-beta.85", + "@ohif/extension-cornerstone": "3.7.0-beta.85", + "@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.85", + "@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.85", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.85", + "@ohif/extension-default": "3.7.0-beta.85", + "@ohif/extension-dicom-pdf": "3.7.0-beta.85", + "@ohif/extension-dicom-video": "3.7.0-beta.85", + "@ohif/extension-measurement-tracking": "3.7.0-beta.85" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/longitudinal/src/index.js b/modes/longitudinal/src/index.js index 0c3e3d4d42..288d0787c0 100644 --- a/modes/longitudinal/src/index.js +++ b/modes/longitudinal/src/index.js @@ -14,22 +14,18 @@ const ohif = { }; const tracked = { - measurements: - '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', + measurements: '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', thumbnailList: '@ohif/extension-measurement-tracking.panelModule.seriesList', - viewport: - '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', + viewport: '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', }; const dicomsr = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', + sopClassHandler: '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', }; const dicomvideo = { - sopClassHandler: - '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', + sopClassHandler: '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video', }; @@ -39,16 +35,14 @@ const dicompdf = { }; const dicomSeg = { - sopClassHandler: - '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', + sopClassHandler: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', viewport: '@ohif/extension-cornerstone-dicom-seg.viewportModule.dicom-seg', panel: '@ohif/extension-cornerstone-dicom-seg.panelModule.panelSegmentation', }; const dicomRt = { viewport: '@ohif/extension-cornerstone-dicom-rt.viewportModule.dicom-rt', - sopClassHandler: - '@ohif/extension-cornerstone-dicom-rt.sopClassHandlerModule.dicom-rt', + sopClassHandler: '@ohif/extension-cornerstone-dicom-rt.sopClassHandlerModule.dicom-rt', }; const extensionDependencies = { @@ -63,7 +57,7 @@ const extensionDependencies = { '@ohif/extension-dicom-video': '^3.0.1', }; -function modeFactory() { +function modeFactory({ modeConfiguration }) { let _activatePanelTriggersSubscriptions = []; return { // TODO: We're using this as a route segment @@ -80,7 +74,7 @@ function modeFactory() { toolbarService, toolGroupService, panelService, - segmentationService, + customizationService, } = servicesManager.services; measurementService.clearMeasurements(); @@ -93,7 +87,6 @@ function modeFactory() { const activateTool = () => { toolbarService.recordInteraction({ groupId: 'WindowLevel', - itemId: 'WindowLevel', interactionType: 'tool', commands: [ { @@ -132,6 +125,13 @@ function modeFactory() { 'MoreTools', ]); + customizationService.addModeCustomizations([ + { + id: 'segmentation.disableEditing', + value: true, + }, + ]); + // // ActivatePanel event trigger for when a segmentation or measurement is added. // // Do not force activation so as to respect the state the user may have left the UI in. // _activatePanelTriggersSubscriptions = [ @@ -176,13 +176,12 @@ function modeFactory() { series: [], }, - isValidMode: function({ modalities }) { + isValidMode: function ({ modalities }) { const modalities_list = modalities.split('\\'); // Exclude non-image modalities - return !!modalities_list.filter( - modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1 - ).length; + return !!modalities_list.filter(modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1) + .length; }, routes: [ { @@ -244,6 +243,7 @@ function modeFactory() { dicomRt.sopClassHandler, ], hotkeys: [...hotkeys.defaults.hotkeyBindings], + ...modeConfiguration, }; } diff --git a/modes/longitudinal/src/initToolGroups.js b/modes/longitudinal/src/initToolGroups.js index b8e88db909..e7ea816db3 100644 --- a/modes/longitudinal/src/initToolGroups.js +++ b/modes/longitudinal/src/initToolGroups.js @@ -1,9 +1,4 @@ -function initDefaultToolGroup( - extensionManager, - toolGroupService, - commandsManager, - toolGroupId -) { +function initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, toolGroupId) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' ); @@ -28,7 +23,23 @@ function initDefaultToolGroup( ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, @@ -43,28 +54,12 @@ function initDefaultToolGroup( { toolName: toolNames.CalibrationLine }, ], // enabled + enabled: [{ toolName: toolNames.ImageOverlayViewer }], // disabled disabled: [{ toolName: toolNames.ReferenceLines }], }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { @@ -72,6 +67,10 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { '@ohif/extension-cornerstone-dicom-sr.utilityModule.tools' ); + if (!SRUtilityModule) { + return; + } + const CS3DUtilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' ); @@ -125,25 +124,8 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - const toolGroupId = 'SRToolGroup'; - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { @@ -171,7 +153,23 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, @@ -184,7 +182,16 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { { toolName: toolNames.SegmentationDisplay }, ], disabled: [ - { toolName: toolNames.Crosshairs }, + { + toolName: toolNames.Crosshairs, + configuration: { + viewportIndicators: false, + autoPan: { + enabled: false, + panSize: 10, + }, + }, + }, { toolName: toolNames.ReferenceLines }, ], @@ -192,31 +199,7 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.Crosshairs]: { - viewportIndicators: false, - autoPan: { - enabled: false, - panSize: 10, - }, - }, - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - toolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools('mpr', tools); } function initVolume3DToolGroup(extensionManager, toolGroupService) { const utilityModule = extensionManager.getModuleEntry( @@ -246,12 +229,7 @@ function initVolume3DToolGroup(extensionManager, toolGroupService) { } function initToolGroups(extensionManager, toolGroupService, commandsManager) { - initDefaultToolGroup( - extensionManager, - toolGroupService, - commandsManager, - 'default' - ); + initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, 'default'); initSRToolGroup(extensionManager, toolGroupService, commandsManager); initMPRToolGroup(extensionManager, toolGroupService, commandsManager); initVolume3DToolGroup(extensionManager, toolGroupService); diff --git a/modes/longitudinal/src/toolbarButtons.js b/modes/longitudinal/src/toolbarButtons.js index 92de37e89d..821d59c6bd 100644 --- a/modes/longitudinal/src/toolbarButtons.js +++ b/modes/longitudinal/src/toolbarButtons.js @@ -15,7 +15,7 @@ const { windowLevelPresets } = defaults; * @param {*} icon * @param {*} label */ -function _createButton(type, id, icon, label, commands, tooltip, uiType) { +function _createButton(type, id, icon, label, commands, tooltip, uiType, isActive) { return { id, icon, @@ -24,6 +24,7 @@ function _createButton(type, id, icon, label, commands, tooltip, uiType) { commands, tooltip, uiType, + isActive, }; } @@ -432,14 +433,41 @@ const toolbarButtons = [ 'ReferenceLines', 'tool-referenceLines', // change this with the new icon 'Reference Lines', + // two commands for the reference lines tool: + // - the first to set the source viewport for the tool when it is enabled + // - the second to toggle the tool [ { - commandName: 'toggleReferenceLines', + commandName: 'setSourceViewportForReferenceLinesTool', commandOptions: {}, context: 'CORNERSTONE', }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'ReferenceLines', + }, + context: 'CORNERSTONE', + }, ] ), + _createToggleButton( + 'ImageOverlayViewer', + 'toggle-dicom-overlay', + 'Image Overlay', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'ImageOverlayViewer', + }, + context: 'CORNERSTONE', + }, + ], + 'Image Overlay', + null, + true + ), _createToolButton( 'StackScroll', 'tool-stack-scroll', diff --git a/modes/microscopy/.prettierrc b/modes/microscopy/.prettierrc index b80ec6b347..914020ac70 100644 --- a/modes/microscopy/.prettierrc +++ b/modes/microscopy/.prettierrc @@ -1,8 +1,10 @@ { + "plugins": ["prettier-plugin-tailwindcss"], "trailingComma": "es5", - "printWidth": 80, + "printWidth": 100, "proseWrap": "always", "tabWidth": 2, "semi": true, - "singleQuote": true + "singleQuote": true, + "arrowParens": "avoid" } diff --git a/modes/microscopy/.webpack/webpack.dev.js b/modes/microscopy/.webpack/webpack.dev.js index cf62de6516..4bf848b6c5 100644 --- a/modes/microscopy/.webpack/webpack.dev.js +++ b/modes/microscopy/.webpack/webpack.dev.js @@ -3,13 +3,10 @@ const webpackCommon = require('./../../../.webpack/webpack.base.js'); const SRC_DIR = path.join(__dirname, '../src'); const DIST_DIR = path.join(__dirname, '../dist'); - const ENTRY = { app: `${SRC_DIR}/index.js`, }; - - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/modes/microscopy/.webpack/webpack.prod.js b/modes/microscopy/.webpack/webpack.prod.js index 6379fa8e53..f578122945 100644 --- a/modes/microscopy/.webpack/webpack.prod.js +++ b/modes/microscopy/.webpack/webpack.prod.js @@ -39,13 +39,7 @@ module.exports = (env, argv) => { libraryExport: 'default', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/modes/microscopy/CHANGELOG.md b/modes/microscopy/CHANGELOG.md new file mode 100644 index 0000000000..b2f0f3c36b --- /dev/null +++ b/modes/microscopy/CHANGELOG.md @@ -0,0 +1,232 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/mode-microscopy + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/mode-microscopy diff --git a/modes/microscopy/babel.config.js b/modes/microscopy/babel.config.js index 92fbbdeaf9..a38ddda212 100644 --- a/modes/microscopy/babel.config.js +++ b/modes/microscopy/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/modes/microscopy/package.json b/modes/microscopy/package.json index 654f0b88be..37d5bd94e5 100644 --- a/modes/microscopy/package.json +++ b/modes/microscopy/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-microscopy", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "OHIF mode for DICOM microscopy", "author": "OHIF", "license": "MIT", @@ -33,8 +33,8 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-dicom-microscopy": "3.6.0" + "@ohif/core": "3.7.0-beta.85", + "@ohif/extension-dicom-microscopy": "3.7.0-beta.85" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/microscopy/src/index.tsx b/modes/microscopy/src/index.tsx index 9044ad6eb3..5f170ca265 100644 --- a/modes/microscopy/src/index.tsx +++ b/modes/microscopy/src/index.tsx @@ -16,8 +16,7 @@ export const cornerstone = { }; const dicomvideo = { - sopClassHandler: - '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', + sopClassHandler: '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video', }; @@ -36,7 +35,7 @@ const extensionDependencies = { '@ohif/extension-dicom-microscopy': '^3.0.0', }; -function modeFactory() { +function modeFactory({ modeConfiguration }) { return { // TODO: We're using this as a route segment // We should not be. @@ -52,10 +51,7 @@ function modeFactory() { toolbarService.init(extensionManager); toolbarService.addButtons(toolbarButtons); - toolbarService.createButtonSection('primary', [ - 'MeasurementTools', - 'dragPan', - ]); + toolbarService.createButtonSection('primary', ['MeasurementTools', 'dragPan']); }, onModeExit: ({ servicesManager }) => { @@ -89,13 +85,10 @@ function modeFactory() { leftPanels: [ohif.leftPanel], leftPanelDefaultClosed: true, // we have problem with rendering thumbnails for microscopy images rightPanelDefaultClosed: true, // we do not have the save microscopy measurements yet - rightPanels: [ - '@ohif/extension-dicom-microscopy.panelModule.measure', - ], + rightPanels: ['@ohif/extension-dicom-microscopy.panelModule.measure'], viewports: [ { - namespace: - '@ohif/extension-dicom-microscopy.viewportModule.microscopy-dicom', + namespace: '@ohif/extension-dicom-microscopy.viewportModule.microscopy-dicom', displaySetsToDisplay: [ '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler', '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler', @@ -130,6 +123,7 @@ function modeFactory() { dicompdf.sopClassHandler, ], hotkeys: [...hotkeys.defaults.hotkeyBindings], + ...modeConfiguration, }; } diff --git a/modes/segmentation/.gitignore b/modes/segmentation/.gitignore new file mode 100644 index 0000000000..67045665db --- /dev/null +++ b/modes/segmentation/.gitignore @@ -0,0 +1,104 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port diff --git a/modes/segmentation/.webpack/webpack.prod.js b/modes/segmentation/.webpack/webpack.prod.js new file mode 100644 index 0000000000..163392a699 --- /dev/null +++ b/modes/segmentation/.webpack/webpack.prod.js @@ -0,0 +1,62 @@ +const path = require('path'); +const pkg = require('../package.json'); + +const outputFile = 'index.umd.js'; +const rootDir = path.resolve(__dirname, '../'); +const outputFolder = path.join(__dirname, `../dist/umd/${pkg.name}/`); + +// Todo: add ESM build for the mode in addition to umd build +const config = { + mode: 'production', + entry: rootDir + '/' + pkg.module, + devtool: 'source-map', + output: { + path: outputFolder, + filename: outputFile, + library: pkg.name, + libraryTarget: 'umd', + chunkFilename: '[name].chunk.js', + umdNamedDefine: true, + globalObject: "typeof self !== 'undefined' ? self : this", + }, + externals: [ + { + react: { + root: 'React', + commonjs2: 'react', + commonjs: 'react', + amd: 'react', + }, + '@ohif/core': { + commonjs2: '@ohif/core', + commonjs: '@ohif/core', + amd: '@ohif/core', + root: '@ohif/core', + }, + '@ohif/ui': { + commonjs2: '@ohif/ui', + commonjs: '@ohif/ui', + amd: '@ohif/ui', + root: '@ohif/ui', + }, + }, + ], + module: { + rules: [ + { + test: /(\.jsx|\.js|\.tsx|\.ts)$/, + loader: 'babel-loader', + exclude: /(node_modules|bower_components)/, + resolve: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + }, + ], + }, + resolve: { + modules: [path.resolve('./node_modules'), path.resolve('./src')], + extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'], + }, +}; + +module.exports = config; diff --git a/modes/segmentation/CHANGELOG.md b/modes/segmentation/CHANGELOG.md new file mode 100644 index 0000000000..32fd291704 --- /dev/null +++ b/modes/segmentation/CHANGELOG.md @@ -0,0 +1,51 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-segmentation + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) diff --git a/modes/segmentation/LICENSE b/modes/segmentation/LICENSE new file mode 100644 index 0000000000..c58f05915f --- /dev/null +++ b/modes/segmentation/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2023 @ohif-segmentation-mode (contact@ohif.org) + +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. \ No newline at end of file diff --git a/modes/segmentation/README.md b/modes/segmentation/README.md new file mode 100644 index 0000000000..5bf905d927 --- /dev/null +++ b/modes/segmentation/README.md @@ -0,0 +1,7 @@ +# @ohif-segmentation-mode +## Description +OHIF segmentation mode which enables labelmap segmentation read/edit/export +## Author +@ohif +## License +MIT \ No newline at end of file diff --git a/modes/segmentation/babel.config.js b/modes/segmentation/babel.config.js new file mode 100644 index 0000000000..a38ddda212 --- /dev/null +++ b/modes/segmentation/babel.config.js @@ -0,0 +1,44 @@ +module.exports = { + plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'], + env: { + test: { + presets: [ + [ + // TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2 + '@babel/preset-env', + { + modules: 'commonjs', + debug: false, + }, + '@babel/preset-typescript', + ], + '@babel/preset-react', + ], + plugins: [ + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-syntax-dynamic-import', + '@babel/plugin-transform-regenerator', + '@babel/plugin-transform-runtime', + ], + }, + production: { + presets: [ + // WebPack handles ES6 --> Target Syntax + ['@babel/preset-env', { modules: false }], + '@babel/preset-react', + '@babel/preset-typescript', + ], + ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], + }, + development: { + presets: [ + // WebPack handles ES6 --> Target Syntax + ['@babel/preset-env', { modules: false }], + '@babel/preset-react', + '@babel/preset-typescript', + ], + plugins: ['react-hot-loader/babel'], + ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], + }, + }, +}; diff --git a/modes/segmentation/package.json b/modes/segmentation/package.json new file mode 100644 index 0000000000..add1e6d6ce --- /dev/null +++ b/modes/segmentation/package.json @@ -0,0 +1,67 @@ +{ + "name": "@ohif/mode-segmentation", + "version": "3.7.0-beta.85", + "description": "OHIF segmentation mode which enables labelmap segmentation read/edit/export", + "author": "@ohif", + "license": "MIT", + "main": "dist/umd/@ohif/mode-segmentation/index.umd.js", + "files": [ + "dist/**", + "public/**", + "README.md" + ], + "repository": "OHIF/Viewers", + "keywords": [ + "ohif-mode" + ], + "publishConfig": { + "access": "public" + }, + "module": "src/index.tsx", + "engines": { + "node": ">=14", + "npm": ">=7", + "yarn": ">=1.16.0" + }, + "scripts": { + "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", + "dev:cornerstone": "yarn run dev", + "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", + "build:package": "yarn run build", + "start": "yarn run dev", + "test:unit": "jest --watchAll", + "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" + }, + "peerDependencies": { + "@ohif/core": "3.7.0-beta.85" + }, + "dependencies": { + "@babel/runtime": "^7.20.13" + }, + "devDependencies": { + "@babel/core": "^7.21.4", + "@babel/plugin-proposal-class-properties": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.17.3", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.16.7", + "@babel/plugin-transform-runtime": "^7.17.0", + "@babel/plugin-transform-typescript": "^7.13.0", + "@babel/preset-env": "^7.16.11", + "@babel/preset-react": "^7.16.7", + "@babel/preset-typescript": "^7.13.0", + "babel-eslint": "^8.0.3", + "babel-loader": "^8.0.0-beta.4", + "babel-plugin-inline-react-svg": "^2.0.1", + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^10.2.0", + "cross-env": "^7.0.3", + "dotenv": "^14.1.0", + "eslint": "^5.0.1", + "eslint-loader": "^2.0.0", + "webpack": "^5.50.0", + "webpack-cli": "^4.7.2", + "webpack-merge": "^5.7.3" + } +} diff --git a/modes/segmentation/src/id.js b/modes/segmentation/src/id.js new file mode 100644 index 0000000000..ebe5acd98a --- /dev/null +++ b/modes/segmentation/src/id.js @@ -0,0 +1,5 @@ +import packageJson from '../package.json'; + +const id = packageJson.name; + +export { id }; diff --git a/modes/segmentation/src/index.tsx b/modes/segmentation/src/index.tsx new file mode 100644 index 0000000000..a6f552c5ae --- /dev/null +++ b/modes/segmentation/src/index.tsx @@ -0,0 +1,179 @@ +import { hotkeys } from '@ohif/core'; +import { id } from './id'; +import toolbarButtons from './toolbarButtons'; +import initToolGroups from './initToolGroups'; + +const ohif = { + layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout', + sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack', + hangingProtocol: '@ohif/extension-default.hangingProtocolModule.default', + leftPanel: '@ohif/extension-default.panelModule.seriesList', + rightPanel: '@ohif/extension-default.panelModule.measure', +}; + +const cornerstone = { + viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone', +}; + +const segmentation = { + panel: '@ohif/extension-cornerstone-dicom-seg.panelModule.panelSegmentation', + panelTool: '@ohif/extension-cornerstone-dicom-seg.panelModule.panelSegmentationWithTools', + sopClassHandler: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg', + viewport: '@ohif/extension-cornerstone-dicom-seg.viewportModule.dicom-seg', +}; + +/** + * Just two dependencies to be able to render a viewport with panels in order + * to make sure that the mode is working. + */ +const extensionDependencies = { + '@ohif/extension-default': '^3.0.0', + '@ohif/extension-cornerstone': '^3.0.0', + '@ohif/extension-cornerstone-dicom-seg': '^3.0.0', +}; + +function modeFactory({ modeConfiguration }) { + return { + /** + * Mode ID, which should be unique among modes used by the viewer. This ID + * is used to identify the mode in the viewer's state. + */ + id, + routeName: 'segmentation', + /** + * Mode name, which is displayed in the viewer's UI in the workList, for the + * user to select the mode. + */ + displayName: 'Segmentation', + /** + * Runs when the Mode Route is mounted to the DOM. Usually used to initialize + * Services and other resources. + */ + onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { + const { measurementService, toolbarService, toolGroupService } = servicesManager.services; + + measurementService.clearMeasurements(); + + // Init Default and SR ToolGroups + initToolGroups(extensionManager, toolGroupService, commandsManager); + + let unsubscribe; + + const activateTool = () => { + toolbarService.recordInteraction({ + groupId: 'WindowLevel', + interactionType: 'tool', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], + }); + + // We don't need to reset the active tool whenever a viewport is getting + // added to the toolGroup. + unsubscribe(); + }; + + // Since we only have one viewport for the basic cs3d mode and it has + // only one hanging protocol, we can just use the first viewport + ({ unsubscribe } = toolGroupService.subscribe( + toolGroupService.EVENTS.VIEWPORT_ADDED, + activateTool + )); + + toolbarService.init(extensionManager); + toolbarService.addButtons(toolbarButtons); + toolbarService.createButtonSection('primary', [ + 'Zoom', + 'WindowLevel', + 'Pan', + 'Capture', + 'Layout', + 'MPR', + 'Crosshairs', + 'MoreTools', + ]); + }, + onModeExit: ({ servicesManager }) => { + const { + toolGroupService, + syncGroupService, + toolbarService, + segmentationService, + cornerstoneViewportService, + } = servicesManager.services; + + toolGroupService.destroy(); + syncGroupService.destroy(); + segmentationService.destroy(); + cornerstoneViewportService.destroy(); + }, + /** */ + validationTags: { + study: [], + series: [], + }, + /** + * A boolean return value that indicates whether the mode is valid for the + * modalities of the selected studies. For instance a PET/CT mode should be + */ + isValidMode: ({ modalities }) => true, + /** + * Mode Routes are used to define the mode's behavior. A list of Mode Route + * that includes the mode's path and the layout to be used. The layout will + * include the components that are used in the layout. For instance, if the + * default layoutTemplate is used (id: '@ohif/extension-default.layoutTemplateModule.viewerLayout') + * it will include the leftPanels, rightPanels, and viewports. However, if + * you define another layoutTemplate that includes a Footer for instance, + * you should provide the Footer component here too. Note: We use Strings + * to reference the component's ID as they are registered in the internal + * ExtensionManager. The template for the string is: + * `${extensionId}.{moduleType}.${componentId}`. + */ + routes: [ + { + path: 'template', + layoutTemplate: ({ location, servicesManager }) => { + return { + id: ohif.layout, + props: { + leftPanels: [ohif.leftPanel], + rightPanels: [segmentation.panelTool], + viewports: [ + { + namespace: cornerstone.viewport, + displaySetsToDisplay: [ohif.sopClassHandler], + }, + { + namespace: segmentation.viewport, + displaySetsToDisplay: [segmentation.sopClassHandler], + }, + ], + }, + }; + }, + }, + ], + /** List of extensions that are used by the mode */ + extensions: extensionDependencies, + /** HangingProtocol used by the mode */ + // hangingProtocol: [''], + /** SopClassHandlers used by the mode */ + sopClassHandlers: [ohif.sopClassHandler, segmentation.sopClassHandler], + /** hotkeys for mode */ + hotkeys: [...hotkeys.defaults.hotkeyBindings], + }; +} + +const mode = { + id, + modeFactory, + extensionDependencies, +}; + +export default mode; diff --git a/modes/segmentation/src/initToolGroups.ts b/modes/segmentation/src/initToolGroups.ts new file mode 100644 index 0000000000..58204839ee --- /dev/null +++ b/modes/segmentation/src/initToolGroups.ts @@ -0,0 +1,82 @@ +const brushInstanceNames = { + CircularBrush: 'CircularBrush', + CircularEraser: 'CircularEraser', + SphereBrush: 'SphereBrush', + SphereEraser: 'SphereEraser', + ThresholdCircularBrush: 'ThresholdCircularBrush', + ThresholdSphereBrush: 'ThresholdSphereBrush', +}; + +const brushStrategies = { + CircularBrush: 'FILL_INSIDE_CIRCLE', + CircularEraser: 'ERASE_INSIDE_CIRCLE', + SphereBrush: 'FILL_INSIDE_SPHERE', + SphereEraser: 'ERASE_INSIDE_SPHERE', + ThresholdCircularBrush: 'THRESHOLD_INSIDE_CIRCLE', + ThresholdSphereBrush: 'THRESHOLD_INSIDE_SPHERE', +}; + +function createTools(utilityModule) { + const { toolNames, Enums } = utilityModule.exports; + return { + active: [ + { toolName: toolNames.WindowLevel, bindings: [{ mouseButton: Enums.MouseBindings.Primary }] }, + { toolName: toolNames.Pan, bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }] }, + { toolName: toolNames.Zoom, bindings: [{ mouseButton: Enums.MouseBindings.Secondary }] }, + { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, + ], + passive: Object.keys(brushInstanceNames) + .map(brushName => ({ + toolName: brushName, + parentTool: 'Brush', + configuration: { + activeStrategy: brushStrategies[brushName], + }, + })) + .concat([ + { toolName: toolNames.CircleScissors }, + { toolName: toolNames.RectangleScissors }, + { toolName: toolNames.SphereScissors }, + { toolName: toolNames.StackScroll }, + { toolName: toolNames.Magnify }, + { toolName: toolNames.SegmentationDisplay }, + ]), + disabled: [{ toolName: toolNames.ReferenceLines }], + }; +} + +function initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, toolGroupId) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + const tools = createTools(utilityModule); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); +} + +function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + const tools = createTools(utilityModule); + tools.disabled.push( + { + toolName: utilityModule.exports.toolNames.Crosshairs, + configuration: { + viewportIndicators: false, + autoPan: { + enabled: false, + panSize: 10, + }, + }, + }, + { toolName: utilityModule.exports.toolNames.ReferenceLines } + ); + toolGroupService.createToolGroupAndAddTools('mpr', tools); +} + +function initToolGroups(extensionManager, toolGroupService, commandsManager) { + initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, 'default'); + initMPRToolGroup(extensionManager, toolGroupService, commandsManager); +} + +export default initToolGroups; diff --git a/modes/segmentation/src/toolbarButtons.ts b/modes/segmentation/src/toolbarButtons.ts new file mode 100644 index 0000000000..b17a33deae --- /dev/null +++ b/modes/segmentation/src/toolbarButtons.ts @@ -0,0 +1,356 @@ +import { + // ExpandableToolbarButton, + // ListMenu, + WindowLevelMenuItem, +} from '@ohif/ui'; +import { defaults } from '@ohif/core'; + +const { windowLevelPresets } = defaults; +/** + * + * @param {*} type - 'tool' | 'action' | 'toggle' + * @param {*} id + * @param {*} icon + * @param {*} label + */ +function _createButton(type, id, icon, label, commands, tooltip, uiType) { + return { + id, + icon, + label, + type, + commands, + tooltip, + uiType, + }; +} + +const _createActionButton = _createButton.bind(null, 'action'); +const _createToggleButton = _createButton.bind(null, 'toggle'); +const _createToolButton = _createButton.bind(null, 'tool'); + +/** + * + * @param {*} preset - preset number (from above import) + * @param {*} title + * @param {*} subtitle + */ +function _createWwwcPreset(preset, title, subtitle) { + return { + id: preset.toString(), + title, + subtitle, + type: 'action', + commands: [ + { + commandName: 'setWindowLevel', + commandOptions: { + ...windowLevelPresets[preset], + }, + context: 'CORNERSTONE', + }, + ], + }; +} + +const toolGroupIds = ['default', 'mpr', 'SRToolGroup']; + +/** + * Creates an array of 'setToolActive' commands for the given toolName - one for + * each toolGroupId specified in toolGroupIds. + * @param {string} toolName + * @returns {Array} an array of 'setToolActive' commands + */ +function _createSetToolActiveCommands(toolName) { + const temp = toolGroupIds.map(toolGroupId => ({ + commandName: 'setToolActive', + commandOptions: { + toolGroupId, + toolName, + }, + context: 'CORNERSTONE', + })); + return temp; +} + +const toolbarButtons = [ + // Zoom.. + { + id: 'Zoom', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-zoom', + label: 'Zoom', + commands: _createSetToolActiveCommands('Zoom'), + }, + }, + // Window Level + Presets... + { + id: 'WindowLevel', + type: 'ohif.splitButton', + props: { + groupId: 'WindowLevel', + primary: _createToolButton( + 'WindowLevel', + 'tool-window-level', + 'Window Level', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], + 'Window Level' + ), + secondary: { + icon: 'chevron-down', + label: 'W/L Manual', + isActive: true, + tooltip: 'W/L Presets', + }, + isAction: true, // ? + renderer: WindowLevelMenuItem, + items: [ + _createWwwcPreset(1, 'Soft tissue', '400 / 40'), + _createWwwcPreset(2, 'Lung', '1500 / -600'), + _createWwwcPreset(3, 'Liver', '150 / 90'), + _createWwwcPreset(4, 'Bone', '2500 / 480'), + _createWwwcPreset(5, 'Brain', '80 / 40'), + ], + }, + }, + // Pan... + { + id: 'Pan', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-move', + label: 'Pan', + commands: _createSetToolActiveCommands('Pan'), + }, + }, + { + id: 'Capture', + type: 'ohif.action', + props: { + icon: 'tool-capture', + label: 'Capture', + type: 'action', + commands: [ + { + commandName: 'showDownloadViewportModal', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + }, + }, + { + id: 'Layout', + type: 'ohif.layoutSelector', + props: { + rows: 3, + columns: 3, + }, + }, + { + id: 'MPR', + type: 'ohif.action', + props: { + type: 'toggle', + icon: 'icon-mpr', + label: 'MPR', + commands: [ + { + commandName: 'toggleHangingProtocol', + commandOptions: { + protocolId: 'mpr', + }, + context: 'DEFAULT', + }, + ], + }, + }, + { + id: 'Crosshairs', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-crosshair', + label: 'Crosshairs', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Crosshairs', + toolGroupId: 'mpr', + }, + context: 'CORNERSTONE', + }, + ], + }, + }, + // More... + { + id: 'MoreTools', + type: 'ohif.splitButton', + props: { + isRadio: true, // ? + groupId: 'MoreTools', + primary: _createActionButton( + 'Reset', + 'tool-reset', + 'Reset View', + [ + { + commandName: 'resetViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Reset' + ), + secondary: { + icon: 'chevron-down', + label: '', + isActive: true, + tooltip: 'More Tools', + }, + items: [ + _createActionButton( + 'Reset', + 'tool-reset', + 'Reset View', + [ + { + commandName: 'resetViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Reset' + ), + _createActionButton( + 'rotate-right', + 'tool-rotate-right', + 'Rotate Right', + [ + { + commandName: 'rotateViewportCW', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Rotate +90' + ), + _createActionButton( + 'flip-horizontal', + 'tool-flip-horizontal', + 'Flip Horizontally', + [ + { + commandName: 'flipViewportHorizontal', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Flip Horizontal' + ), + _createToggleButton('StackImageSync', 'link', 'Stack Image Sync', [ + { + commandName: 'toggleStackImageSync', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ]), + _createToggleButton( + 'ReferenceLines', + 'tool-referenceLines', // change this with the new icon + 'Reference Lines', + [ + { + commandName: 'toggleReferenceLines', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ] + ), + _createToolButton( + 'StackScroll', + 'tool-stack-scroll', + 'Stack Scroll', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'StackScroll', + }, + context: 'CORNERSTONE', + }, + ], + 'Stack Scroll' + ), + _createActionButton( + 'invert', + 'tool-invert', + 'Invert', + [ + { + commandName: 'invertViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Invert Colors' + ), + _createToggleButton( + 'cine', + 'tool-cine', + 'Cine', + [ + { + commandName: 'toggleCine', + context: 'CORNERSTONE', + }, + ], + 'Cine' + ), + _createToolButton( + 'Magnify', + 'tool-magnify', + 'Magnify', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Magnify', + }, + context: 'CORNERSTONE', + }, + ], + 'Magnify' + ), + _createActionButton( + 'TagBrowser', + 'list-bullets', + 'Dicom Tag Browser', + [ + { + commandName: 'openDICOMTagViewer', + commandOptions: {}, + context: 'DEFAULT', + }, + ], + 'Dicom Tag Browser' + ), + ], + }, + }, +]; + +export default toolbarButtons; diff --git a/modes/tmtv/.webpack/webpack.dev.js b/modes/tmtv/.webpack/webpack.dev.js index 2bc3ced0b9..4bf848b6c5 100644 --- a/modes/tmtv/.webpack/webpack.dev.js +++ b/modes/tmtv/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/modes/tmtv/.webpack/webpack.prod.js b/modes/tmtv/.webpack/webpack.prod.js index 385ed85ced..018db058d3 100644 --- a/modes/tmtv/.webpack/webpack.prod.js +++ b/modes/tmtv/.webpack/webpack.prod.js @@ -14,7 +14,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); @@ -41,13 +40,7 @@ module.exports = (env, argv) => { libraryExport: 'default', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/modes/tmtv/CHANGELOG.md b/modes/tmtv/CHANGELOG.md new file mode 100644 index 0000000000..960d37295c --- /dev/null +++ b/modes/tmtv/CHANGELOG.md @@ -0,0 +1,235 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/mode-tmtv + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/mode-tmtv diff --git a/modes/tmtv/package.json b/modes/tmtv/package.json index dcd6936744..9adbd25ccd 100644 --- a/modes/tmtv/package.json +++ b/modes/tmtv/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-tmtv", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "Total Metabolic Tumor Volume Workflow", "author": "OHIF", "license": "MIT", @@ -32,13 +32,13 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-cornerstone-dicom-sr": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/extension-dicom-pdf": "3.6.0", - "@ohif/extension-dicom-video": "3.6.0", - "@ohif/extension-measurement-tracking": "3.6.0" + "@ohif/core": "3.7.0-beta.85", + "@ohif/extension-cornerstone": "3.7.0-beta.85", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.85", + "@ohif/extension-default": "3.7.0-beta.85", + "@ohif/extension-dicom-pdf": "3.7.0-beta.85", + "@ohif/extension-dicom-video": "3.7.0-beta.85", + "@ohif/extension-measurement-tracking": "3.7.0-beta.85" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/tmtv/src/index.js b/modes/tmtv/src/index.js index aae993bbdc..110fb0fb75 100644 --- a/modes/tmtv/src/index.js +++ b/modes/tmtv/src/index.js @@ -43,12 +43,8 @@ function modeFactory({ modeConfiguration }) { * Lifecycle hooks */ onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { - const { - toolbarService, - toolGroupService, - hangingProtocolService, - displaySetService, - } = servicesManager.services; + const { toolbarService, toolGroupService, hangingProtocolService, displaySetService } = + servicesManager.services; const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' @@ -62,7 +58,6 @@ function modeFactory({ modeConfiguration }) { const setWindowLevelActive = () => { toolbarService.recordInteraction({ groupId: 'WindowLevel', - itemId: 'WindowLevel', interactionType: 'tool', commands: [ { @@ -99,9 +94,7 @@ function modeFactory({ modeConfiguration }) { // For fusion toolGroup we need to add the volumeIds for the crosshairs // since in the fusion viewport we don't want both PT and CT to render MIP // when slabThickness is modified - const { - displaySetMatchDetails, - } = hangingProtocolService.getMatchDetails(); + const { displaySetMatchDetails } = hangingProtocolService.getMatchDetails(); setCrosshairsConfiguration( displaySetMatchDetails, @@ -143,22 +136,16 @@ function modeFactory({ modeConfiguration }) { 'getPTVOIRange', 'get PT VOI based on corrected or not', props => { - const ptDisplaySet = props.find( - imageSet => imageSet.Modality === 'PT' - ); + const ptDisplaySet = props.find(imageSet => imageSet.Modality === 'PT'); if (!ptDisplaySet) { return; } const { imageId } = ptDisplaySet.images[0]; - const imageIdScalingFactor = MetadataProvider.get( - 'scalingModule', - imageId - ); + const imageIdScalingFactor = MetadataProvider.get('scalingModule', imageId); - const isSUVAvailable = - imageIdScalingFactor && imageIdScalingFactor.suvbw; + const isSUVAvailable = imageIdScalingFactor && imageIdScalingFactor.suvbw; if (isSUVAvailable) { return { @@ -196,16 +183,13 @@ function modeFactory({ modeConfiguration }) { const isValid = modalities_list.includes('CT') && modalities_list.includes('PT') && - !invalidModalities.some(modality => - modalities_list.includes(modality) - ) && + !invalidModalities.some(modality => modalities_list.includes(modality)) && // This is study is a 4D study with PT and CT and not a 3D study for the tmtv // mode, until we have a better way to identify 4D studies we will use the // StudyInstanceUID to identify the study // Todo: when we add the 4D mode which comes with a mechanism to identify // 4D studies we can use that - study.studyInstanceUid !== - '1.3.6.1.4.1.12842.1.1.14.3.20220915.105557.468.2963630849'; + study.studyInstanceUid !== '1.3.6.1.4.1.12842.1.1.14.3.20220915.105557.468.2963630849'; // there should be both CT and PT modalities and the modality should not be SM return isValid; @@ -220,7 +204,8 @@ function modeFactory({ modeConfiguration }) { return { id: ohif.layout, props: { - // leftPanels: [ohif.thumbnailList], + leftPanels: [ohif.thumbnailList], + leftPanelDefaultClosed: true, rightPanels: [tmtv.ROIThresholdPanel, tmtv.petSUV], viewports: [ { @@ -237,6 +222,7 @@ function modeFactory({ modeConfiguration }) { hangingProtocol: tmtv.hangingProtocol, sopClassHandlers: [ohif.sopClassHandler], hotkeys: [...hotkeys.defaults.hotkeyBindings], + ...modeConfiguration, }; } diff --git a/modes/tmtv/src/initToolGroups.js b/modes/tmtv/src/initToolGroups.js index 990b70058a..a8f4fcf31b 100644 --- a/modes/tmtv/src/initToolGroups.js +++ b/modes/tmtv/src/initToolGroups.js @@ -26,7 +26,24 @@ function _initToolGroups(toolNames, Enums, toolGroupService, commandsManager) { ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => { + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }); + }, + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.Probe }, @@ -38,157 +55,54 @@ function _initToolGroups(toolNames, Enums, toolGroupService, commandsManager) { { toolName: toolNames.Magnify }, ], enabled: [{ toolName: toolNames.SegmentationDisplay }], - disabled: [{ toolName: toolNames.Crosshairs }], - }; - - const toolsConfig = { - [toolNames.Crosshairs]: { - viewportIndicators: false, - autoPan: { - enabled: false, - panSize: 10, - }, - }, - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => { - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }); + disabled: [ + { + toolName: toolNames.Crosshairs, + configuration: { + viewportIndicators: false, + autoPan: { + enabled: false, + panSize: 10, + }, + }, }, - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, + ], }; - toolGroupService.createToolGroupAndAddTools( - toolGroupIds.CT, - tools, - toolsConfig - ); - toolGroupService.createToolGroupAndAddTools( - toolGroupIds.PT, - { - active: tools.active, - passive: [ - ...tools.passive, - { toolName: 'RectangleROIStartEndThreshold' }, - ], - enabled: tools.enabled, - disabled: tools.disabled, - }, - toolsConfig - ); - toolGroupService.createToolGroupAndAddTools( - toolGroupIds.Fusion, - tools, - toolsConfig - ); - toolGroupService.createToolGroupAndAddTools( - toolGroupIds.default, - tools, - toolsConfig - ); + toolGroupService.createToolGroupAndAddTools(toolGroupIds.CT, tools); + toolGroupService.createToolGroupAndAddTools(toolGroupIds.PT, { + active: tools.active, + passive: [...tools.passive, { toolName: 'RectangleROIStartEndThreshold' }], + enabled: tools.enabled, + disabled: tools.disabled, + }); + toolGroupService.createToolGroupAndAddTools(toolGroupIds.Fusion, tools); + toolGroupService.createToolGroupAndAddTools(toolGroupIds.default, tools); const mipTools = { active: [ { toolName: toolNames.VolumeRotateMouseWheel, + configuration: { + rotateIncrementDegrees: 0.1, + }, }, { toolName: toolNames.MipJumpToClick, + configuration: { + toolGroupId: toolGroupIds.PT, + }, bindings: [{ mouseButton: Enums.MouseBindings.Primary }], }, ], enabled: [{ toolName: toolNames.SegmentationDisplay }], }; - const mipToolsConfig = { - [toolNames.VolumeRotateMouseWheel]: { - rotateIncrementDegrees: 0.1, - }, - [toolNames.MipJumpToClick]: { - targetViewportIds: ['ptAXIAL', 'ptCORONAL', 'ptSAGITTAL'], - }, - }; - - toolGroupService.createToolGroupAndAddTools( - toolGroupIds.MIP, - mipTools, - mipToolsConfig - ); -} - -function initMPRToolGroup(toolNames, Enums, toolGroupService, commandsManager) { - const tools = { - active: [ - { - toolName: toolNames.WindowLevel, - bindings: [{ mouseButton: Enums.MouseBindings.Primary }], - }, - { - toolName: toolNames.Pan, - bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }], - }, - { - toolName: toolNames.Zoom, - bindings: [{ mouseButton: Enums.MouseBindings.Secondary }], - }, - { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, - ], - passive: [ - { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, - { toolName: toolNames.Bidirectional }, - { toolName: toolNames.DragProbe }, - { toolName: toolNames.EllipticalROI }, - { toolName: toolNames.RectangleROI }, - { toolName: toolNames.StackScroll }, - { toolName: toolNames.Angle }, - { toolName: toolNames.CobbAngle }, - { toolName: toolNames.SegmentationDisplay }, - ], - disabled: [{ toolName: toolNames.Crosshairs }], - - // enabled - // disabled - }; - - const toolsConfig = { - [toolNames.Crosshairs]: { - viewportIndicators: false, - autoPan: { - enabled: false, - panSize: 10, - }, - }, - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - toolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupIds.MIP, mipTools); } function initToolGroups(toolNames, Enums, toolGroupService, commandsManager) { _initToolGroups(toolNames, Enums, toolGroupService, commandsManager); - // initMPRToolGroup(toolNames, Enums, toolGroupService, commandsManager); } export default initToolGroups; diff --git a/modes/tmtv/src/toolbarButtons.js b/modes/tmtv/src/toolbarButtons.js index f6f7072213..6e336d4f83 100644 --- a/modes/tmtv/src/toolbarButtons.js +++ b/modes/tmtv/src/toolbarButtons.js @@ -24,9 +24,8 @@ function _createButton(type, id, icon, label, commands, tooltip) { function _createColormap(label, colormap) { return { - id: label.toString(), - title: label, - subtitle: label, + id: label, + label, type: 'action', commands: [ { @@ -36,13 +35,6 @@ function _createColormap(label, colormap) { colormap, }, }, - { - commandName: 'setFusionPTColormap', - commandOptions: { - toolGroupId: toolGroupIds.Fusion, - colormap, - }, - }, ], }; } @@ -306,9 +298,7 @@ const toolbarButtons = [ icon: 'tool-create-threshold', label: 'Rectangle ROI Threshold', commands: [ - ..._createCommands('setToolActive', 'RectangleROIStartEndThreshold', [ - toolGroupIds.PT, - ]), + ..._createCommands('setToolActive', 'RectangleROIStartEndThreshold', [toolGroupIds.PT]), { commandName: 'displayNotification', commandOptions: { @@ -345,7 +335,6 @@ const toolbarButtons = [ tooltip: 'PET Image Colormap', }, isAction: true, // ? - renderer: WindowLevelMenuItem, items: [ _createColormap('HSV', 'hsv'), _createColormap('Hot Iron', 'hot_iron'), diff --git a/modes/tmtv/src/utils/setCrosshairsConfiguration.js b/modes/tmtv/src/utils/setCrosshairsConfiguration.js index cc6b4f57da..072d9443e5 100644 --- a/modes/tmtv/src/utils/setCrosshairsConfiguration.js +++ b/modes/tmtv/src/utils/setCrosshairsConfiguration.js @@ -13,9 +13,7 @@ export default function setCrosshairsConfiguration( } const { SeriesInstanceUID } = matchDetails; - const displaySets = displaySetService.getDisplaySetsForSeries( - SeriesInstanceUID - ); + const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); const toolConfig = toolGroupService.getToolConfiguration( toolGroupIds.Fusion, diff --git a/modes/tmtv/src/utils/setFusionActiveVolume.js b/modes/tmtv/src/utils/setFusionActiveVolume.js index ed7b1f6a98..30d42455c7 100644 --- a/modes/tmtv/src/utils/setFusionActiveVolume.js +++ b/modes/tmtv/src/utils/setFusionActiveVolume.js @@ -14,9 +14,7 @@ export default function setFusionActiveVolume( const { SeriesInstanceUID } = matchDetails; - const displaySets = displaySetService.getDisplaySetsForSeries( - SeriesInstanceUID - ); + const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); if (!displaySets || displaySets.length === 0) { return; diff --git a/package.json b/package.json index 3d663c75a3..910a458211 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ] }, "engines": { - "node": ">=14", + "node": ">=16", "npm": ">=6", "yarn": ">=1.16.0" }, @@ -32,7 +32,7 @@ "dev:orthanc": "lerna run dev:orthanc --stream", "dev:dcm4chee": "lerna run dev:dcm4chee --stream", "dev:static": "lerna run dev:static --stream", - "orthanc:up": "docker-compose -f .docker/Nginx-Orthanc/docker-compose.yml up", + "orthanc:up": "docker-compose -f platform/app/.recipes/Nginx-Orthanc/docker-compose.yml up", "preinstall": "node preinstall.js", "start": "yarn run dev", "test": "yarn run test:unit", @@ -74,11 +74,12 @@ "@babel/plugin-transform-runtime": "^7.17.0", "@babel/plugin-transform-typescript": "^7.13.0", "@babel/preset-env": "^7.16.11", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.13.0", "@types/jest": "^27.5.0", - "@typescript-eslint/eslint-plugin": "^4.19.0", - "@typescript-eslint/parser": "^4.19.0", + "@typescript-eslint/eslint-plugin": "^6.3.0", + "@typescript-eslint/parser": "^6.3.0", "autoprefixer": "^10.4.4", "babel-eslint": "9.x", "babel-loader": "^8.2.4", @@ -97,13 +98,13 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^5.2.0", "eslint-plugin-react": "^7.29.4", "eslint-plugin-react-hooks": "^4.4.0", "eslint-plugin-tsdoc": "^0.2.11", "eslint-webpack-plugin": "^2.5.3", - "execa": "^7.1.1", + "execa": "^8.0.1", "extract-css-chunks-webpack-plugin": "^4.5.4", "html-webpack-plugin": "^5.3.2", "husky": "^3.0.0", @@ -111,7 +112,7 @@ "jest-environment-jsdom": "^29.5.0", "jest-canvas-mock": "^2.1.0", "jest-junit": "^6.4.0", - "lerna": "^6.6.1", + "lerna": "^7.2.0", "lint-staged": "^9.0.2", "mini-css-extract-plugin": "^2.1.0", "optimize-css-assets-webpack-plugin": "^6.0.1", @@ -119,11 +120,13 @@ "postcss-import": "^14.0.2", "postcss-loader": "^6.1.1", "postcss-preset-env": "^7.4.3", - "prettier": "^1.18.2", + "prettier": "^3.0.3", + "prettier-plugin-tailwindcss": "^0.5.4", "react-hot-loader": "^4.13.0", "semver": "^7.5.1", "serve": "^14.2.0", "shader-loader": "^1.3.1", + "source-map-loader": "^4.0.1", "start-server-and-test": "^1.10.0", "style-loader": "^1.0.0", "terser-webpack-plugin": "^5.1.4", @@ -149,7 +152,6 @@ ] }, "resolutions": { - "@cornerstonejs/core": "^1.1.0", "**/@babel/runtime": "^7.20.13", "commander": "8.3.0", "nth-check": "^2.1.1", diff --git a/platform/app/.all-contributorsrc b/platform/app/.all-contributorsrc index ea8f6c9fdc..b12263a8c7 100644 --- a/platform/app/.all-contributorsrc +++ b/platform/app/.all-contributorsrc @@ -1,7 +1,5 @@ { - "files": [ - "README.md" - ], + "files": ["README.md"], "imageSize": 100, "commit": false, "contributors": [ @@ -10,92 +8,70 @@ "name": "Erik Ziegler", "avatar_url": "https://avatars3.githubusercontent.com/u/607793?v=4", "profile": "https://github.com/swederik", - "contributions": [ - "code", - "infra" - ] + "contributions": ["code", "infra"] }, { "login": "evren217", "name": "Evren Ozkan", "avatar_url": "https://avatars1.githubusercontent.com/u/4920551?v=4", "profile": "https://github.com/evren217", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "galelis", "name": "Gustavo André Lelis", "avatar_url": "https://avatars3.githubusercontent.com/u/2378326?v=4", "profile": "https://github.com/galelis", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "dannyrb", "name": "Danny Brown", "avatar_url": "https://avatars1.githubusercontent.com/u/5797588?v=4", "profile": "http://dannyrb.com/", - "contributions": [ - "code", - "infra" - ] + "contributions": ["code", "infra"] }, { "login": "allcontributors", "name": "allcontributors[bot]", "avatar_url": "https://avatars3.githubusercontent.com/u/46843839?v=4", "profile": "https://github.com/all-contributors/all-contributors-bot", - "contributions": [ - "doc" - ] + "contributions": ["doc"] }, { "login": "EsrefDurna", "name": "Esref Durna", "avatar_url": "https://avatars0.githubusercontent.com/u/1230575?v=4", "profile": "https://www.linkedin.com/in/siliconvalleynextgeneration/", - "contributions": [ - "question" - ] + "contributions": ["question"] }, { "login": "diego0020", "name": "diego0020", "avatar_url": "https://avatars3.githubusercontent.com/u/7297450?v=4", "profile": "https://github.com/diego0020", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "dlwire", "name": "David Wire", "avatar_url": "https://avatars3.githubusercontent.com/u/1167291?v=4", "profile": "https://github.com/dlwire", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "jfmedeiros1820", "name": "João Felipe de Medeiros Moreira", "avatar_url": "https://avatars1.githubusercontent.com/u/2211708?v=4", "profile": "https://github.com/jfmedeiros1820", - "contributions": [ - "test" - ] + "contributions": ["test"] }, { "login": "pavertomato", "name": "Egor Lezhnin", "avatar_url": "https://avatars0.githubusercontent.com/u/878990?v=4", "profile": "http://egor.lezhn.in", - "contributions": [ - "code" - ] + "contributions": ["code"] } ], "contributorsPerLine": 7, diff --git a/platform/app/.gitignore b/platform/app/.gitignore new file mode 100644 index 0000000000..e985853ed8 --- /dev/null +++ b/platform/app/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/platform/app/.recipes/Nginx-Dcm4che/docker-compose-dcm4che.env b/platform/app/.recipes/Nginx-Dcm4chee/docker-compose-dcm4che.env similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4che/docker-compose-dcm4che.env rename to platform/app/.recipes/Nginx-Dcm4chee/docker-compose-dcm4che.env diff --git a/platform/app/.recipes/Nginx-Dcm4che/docker-compose.yml b/platform/app/.recipes/Nginx-Dcm4chee/docker-compose.yml similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4che/docker-compose.yml rename to platform/app/.recipes/Nginx-Dcm4chee/docker-compose.yml diff --git a/platform/app/.recipes/Nginx-Dcm4che/etc/localtime b/platform/app/.recipes/Nginx-Dcm4chee/etc/localtime similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4che/etc/localtime rename to platform/app/.recipes/Nginx-Dcm4chee/etc/localtime diff --git a/platform/app/.recipes/Nginx-Dcm4che/etc/timezone b/platform/app/.recipes/Nginx-Dcm4chee/etc/timezone similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4che/etc/timezone rename to platform/app/.recipes/Nginx-Dcm4chee/etc/timezone diff --git a/platform/app/.recipes/Nginx-Dcm4che/nginx-proxy/conf/nginx.conf b/platform/app/.recipes/Nginx-Dcm4chee/nginx-proxy/conf/nginx.conf similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4che/nginx-proxy/conf/nginx.conf rename to platform/app/.recipes/Nginx-Dcm4chee/nginx-proxy/conf/nginx.conf diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/.dockerignore b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/.dockerignore deleted file mode 100644 index 67f1b2e985..0000000000 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/.dockerignore +++ /dev/null @@ -1,16 +0,0 @@ -# Output -dist/ - -# Dependencies -node_modules/ - -# Root -README.md -Dockerfile - -# Misc. Config -.git -.DS_Store -.gitignore -.vscode -.circleci diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf index 7360c33610..9e496b79d9 100644 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf +++ b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf @@ -22,7 +22,7 @@ http { # lua_ settings # - lua_package_path '/usr/local/openresty/lualib/?.lua;;'; + lua_package_path '/usr/local/openresty/lualib/?.lua;;/usr/local/share/lua/5.4/?.lua;;'; lua_shared_dict discovery 1m; # cache for discovery metadata documents lua_shared_dict jwks 1m; # cache for JWKs # lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; @@ -182,6 +182,8 @@ http { index index.html; try_files $uri $uri/ /index.html; add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header 'Cross-Origin-Opener-Policy' 'same-origin' always; + add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always; } # EXAMPLE: Reverse Proxy, no auth diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json index 3f0e6118a6..4716e1592c 100644 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json +++ b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json @@ -368,19 +368,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "84e7b84d-ab74-452e-8a24-a0e657f100ea", @@ -406,19 +395,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "f7dd8587-4035-4590-a1c0-ebf576c4dfe3", @@ -462,19 +440,8 @@ } } ], - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "f7f76add-411b-420d-9be1-bd120ed99918", @@ -500,19 +467,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "3785434d-2af8-478c-b135-f0b11d1d3205", @@ -598,19 +554,8 @@ } } ], - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ], + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"], "authorizationSettings": { "allowRemoteResourceManagement": true, "policyEnforcementMode": "ENFORCING", @@ -678,19 +623,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] }, { "id": "53d51818-e1bf-4fc2-aa20-5541f2646f12", @@ -733,19 +667,8 @@ "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": true, "nodeReRegistrationTimeout": -1, - "defaultClientScopes": [ - "web-origins", - "role_list", - "profile", - "roles", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "defaultClientScopes": ["web-origins", "role_list", "profile", "roles", "email"], + "optionalClientScopes": ["address", "phone", "offline_access", "microprofile-jwt"] } ], "clientScopes": [ @@ -1233,19 +1156,8 @@ } } ], - "defaultDefaultClientScopes": [ - "role_list", - "profile", - "email", - "roles", - "web-origins" - ], - "defaultOptionalClientScopes": [ - "offline_access", - "address", - "phone", - "microprofile-jwt" - ], + "defaultDefaultClientScopes": ["role_list", "profile", "email", "roles", "web-origins"], + "defaultOptionalClientScopes": ["offline_access", "address", "phone", "microprofile-jwt"], "browserSecurityHeaders": { "contentSecurityPolicyReportOnly": "", "xContentTypeOptions": "nosniff", diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml index 771737f3cf..f3c0da2652 100644 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml +++ b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml @@ -10,9 +10,9 @@ services: ohif_viewer: build: # Project root - context: ./../../ + context: ./../../../../ # Relative to context - dockerfile: ./docker/OpenResty-Orthanc-Keycloak/dockerfile + dockerfile: ./platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile image: webapp:latest container_name: webapp volumes: @@ -35,7 +35,7 @@ services: # TODO: Update to use Postgres # https://github.com/mrts/docker-postgresql-multiple-databases orthanc: - image: jodogne/orthanc-plugins:1.5.6 + image: jodogne/orthanc-plugins hostname: orthanc container_name: orthanc volumes: diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile index 73090e8ae1..fa066291e3 100644 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile +++ b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile @@ -23,43 +23,63 @@ # Stage 1: Build the application -FROM node:11.2.0-slim as builder +FROM node:18.16.1-slim as builder RUN mkdir /usr/src/app WORKDIR /usr/src/app +RUN apt-get update && apt-get install -y build-essential python3 + ENV APP_CONFIG=config/docker_openresty-orthanc-keycloak.js ENV PATH /usr/src/app/node_modules/.bin:$PATH -COPY package.json /usr/src/app/package.json -COPY yarn.lock /usr/src/app/yarn.lock +# Copy all files from the root of the OHIF source and note +# that the Docker ignore file at the root (i.e. ./dockerignore) will filter +# out files and directories that are not needed. +COPY ./ /usr/src/app/ ADD . /usr/src/app/ +RUN yarn config set workspaces-experimental true RUN yarn install -RUN yarn run build:web +RUN yarn run build # Stage 2: Bundle the built application into a Docker container # which runs openresty (nginx) using Alpine Linux # LINK: https://hub.docker.com/r/openresty/openresty -FROM openresty/openresty:1.15.8.1rc1-0-alpine-fat +FROM openresty/openresty:1.21.4.2-0-bullseye-fat RUN mkdir /var/log/nginx -RUN apk add --no-cache openssl -RUN apk add --no-cache openssl-dev -RUN apk add --no-cache git -RUN apk add --no-cache gcc +RUN apt-get update && \ + apt-get install -y openssl libssl-dev git gcc wget unzip make&& \ + apt-get clean + +RUN apt-get install --assume-yes lua5.4 libzmq3-dev lua5.4-dev +RUN cd /tmp && \ + wget http://luarocks.org/releases/luarocks-3.9.2.tar.gz && \ + tar zxpf luarocks-3.9.2.tar.gz && \ + cd luarocks-3.9.2 && \ + ./configure && \ + make && \ + make install + # !!! +RUN luarocks install lua-resty-http +# RUN luarocks install lua-nginx-module +RUN luarocks install lua-cjson +RUN luarocks install lua-resty-string +RUN luarocks install lua-resty-session +RUN luarocks install lua-resty-jwt RUN luarocks install lua-resty-openidc +RUN apt-get clean && rm -rf /var/lib/apt/lists/* + # -RUN luarocks install lua-resty-jwt -RUN luarocks install lua-resty-session RUN luarocks install lua-resty-http # !!! -RUN luarocks install lua-resty-openidc -RUN luarocks install luacrypto +RUN luarocks install lua-resty-auto-ssl + # Copy build output to image -COPY --from=builder /usr/src/app/build /var/www/html +COPY --from=builder /usr/src/app/platform/app/dist /var/www/html ENTRYPOINT ["/usr/local/openresty/nginx/sbin/nginx", "-g", "daemon off;"] diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css index 5b5f0b9bee..ed1423365c 100644 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css +++ b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css @@ -71,26 +71,10 @@ input[type='password']:hover { input[type='submit'] { border: none; - background: -webkit-linear-gradient( - top, - rgba(255, 255, 255, 0.8), - rgba(255, 255, 255, 0.1) - ); - background: -moz-linear-gradient( - top, - rgba(255, 255, 255, 0.8), - rgba(255, 255, 255, 0.1) - ); - background: -ms-linear-gradient( - top, - rgba(255, 255, 255, 0.8), - rgba(255, 255, 255, 0.1) - ); - background: -o-linear-gradient( - top, - rgba(255, 255, 255, 0.8), - rgba(255, 255, 255, 0.1) - ); + background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)); + background: -moz-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)); + background: -ms-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)); + background: -o-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)); box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.5); diff --git a/platform/app/.recipes/OpenResty-Orthanc/.dockerignore b/platform/app/.recipes/OpenResty-Orthanc/.dockerignore deleted file mode 100644 index 67f1b2e985..0000000000 --- a/platform/app/.recipes/OpenResty-Orthanc/.dockerignore +++ /dev/null @@ -1,16 +0,0 @@ -# Output -dist/ - -# Dependencies -node_modules/ - -# Root -README.md -Dockerfile - -# Misc. Config -.git -.DS_Store -.gitignore -.vscode -.circleci diff --git a/platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf b/platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf index 777e497036..aff59a1302 100644 --- a/platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf +++ b/platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf @@ -118,6 +118,8 @@ http { index index.html; try_files $uri $uri/ /index.html; add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header 'Cross-Origin-Opener-Policy' 'same-origin' always; + add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always; } # EXAMPLE: Redirect server error pages to the static page /40x.html diff --git a/platform/app/.recipes/OpenResty-Orthanc/docker-compose.yml b/platform/app/.recipes/OpenResty-Orthanc/docker-compose.yml index 824ae5a3e1..16ec1ada70 100644 --- a/platform/app/.recipes/OpenResty-Orthanc/docker-compose.yml +++ b/platform/app/.recipes/OpenResty-Orthanc/docker-compose.yml @@ -34,7 +34,7 @@ services: # TODO: Update to use Postgres # https://github.com/mrts/docker-postgresql-multiple-databases orthanc: - image: jodogne/orthanc-plugins:1.5.6 + image: jodogne/orthanc-plugins hostname: orthanc container_name: orthanc volumes: diff --git a/platform/app/.recipes/OpenResty-Orthanc/dockerfile b/platform/app/.recipes/OpenResty-Orthanc/dockerfile index 768cbc576e..643c52a57d 100644 --- a/platform/app/.recipes/OpenResty-Orthanc/dockerfile +++ b/platform/app/.recipes/OpenResty-Orthanc/dockerfile @@ -23,24 +23,19 @@ # Stage 1: Build the application -FROM node:12.22.1-slim as builder +FROM node:18.16.1-slim as builder RUN mkdir /usr/src/app WORKDIR /usr/src/app -# Copy Files -COPY .docker /usr/src/app/.docker -COPY .webpack /usr/src/app/.webpack -COPY extensions /usr/src/app/extensions -COPY modes /usr/src/app/modes -COPY platform /usr/src/app/platform -COPY .browserslistrc /usr/src/app/.browserslistrc -COPY aliases.config.js /usr/src/app/aliases.config.js -COPY babel.config.js /usr/src/app/babel.config.js -COPY lerna.json /usr/src/app/lerna.json -COPY package.json /usr/src/app/package.json -COPY postcss.config.js /usr/src/app/postcss.config.js -COPY yarn.lock /usr/src/app/yarn.lock +# Copy all files from the root of the OHIF source and note +# that the Docker ignore file at the root (i.e. ./dockerignore) will filter +# out files and directories that are not needed. +COPY ./ /usr/src/app/ + +# For arm builds since parcel doesn't have prebuilt binaries for arm yet +RUN apt-get update && apt-get install -y build-essential python3 + # ADD . /usr/src/app/ RUN yarn config set workspaces-experimental true diff --git a/platform/app/.webpack/rules/extractStyleChunks.js b/platform/app/.webpack/rules/extractStyleChunks.js index 10e790e676..94388a353d 100644 --- a/platform/app/.webpack/rules/extractStyleChunks.js +++ b/platform/app/.webpack/rules/extractStyleChunks.js @@ -12,7 +12,7 @@ function extractStyleChunks(isProdBuild) { }, }, 'css-loader', - 'postcss-loader' + 'postcss-loader', ], }, ]; diff --git a/platform/app/.webpack/webpack.pwa.js b/platform/app/.webpack/webpack.pwa.js index 0da6104f5e..167f44a6f8 100644 --- a/platform/app/.webpack/webpack.pwa.js +++ b/platform/app/.webpack/webpack.pwa.js @@ -54,7 +54,7 @@ module.exports = (env, argv) => { path: DIST_DIR, filename: isProdBuild ? '[name].bundle.[chunkhash].js' : '[name].js', publicPath: PUBLIC_URL, // Used by HtmlWebPackPlugin for asset prefix - devtoolModuleFilenameTemplate: function(info) { + devtoolModuleFilenameTemplate: function (info) { if (isProdBuild) { return `webpack:///${info.resourcePath}`; } else { @@ -102,8 +102,7 @@ module.exports = (env, argv) => { }, // Copy Dicom Microscopy Viewer build files { - from: - '../../../node_modules/dicom-microscopy-viewer/dist/dynamic-import', + from: '../../../node_modules/dicom-microscopy-viewer/dist/dynamic-import', to: DIST_DIR, globOptions: { ignore: ['**/*.min.js.map'], @@ -111,8 +110,7 @@ module.exports = (env, argv) => { }, // Copy dicom-image-loader build files { - from: - '../../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import', + from: '../../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import', to: DIST_DIR, }, ], @@ -186,8 +184,6 @@ module.exports = (env, argv) => { chunkFilename: '[id].css', }) ); - } else { - mergedConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); } return mergedConfig; diff --git a/platform/app/.webpack/writePluginImportsFile.js b/platform/app/.webpack/writePluginImportsFile.js index 41caf84b61..b9151a32c4 100644 --- a/platform/app/.webpack/writePluginImportsFile.js +++ b/platform/app/.webpack/writePluginImportsFile.js @@ -7,7 +7,7 @@ const autogenerationDisclaimer = ` // THIS FILE IS AUTOGENERATED AS PART OF THE EXTENSION AND MODE PLUGIN PROCESS. // IT SHOULD NOT BE MODIFIED MANUALLY \n`; -const extractName = (val) => (typeof val === 'string') ? val : val.packageName; +const extractName = val => (typeof val === 'string' ? val : val.packageName); function constructLines(input, categoryName) { let pluginCount = 0; @@ -24,9 +24,7 @@ function constructLines(input, categoryName) { const packageName = extractName(entry); - lines.addToWindowLines.push( - `${categoryName}.push("${packageName}");\n` - ); + lines.addToWindowLines.push(`${categoryName}.push("${packageName}");\n`); pluginCount++; }); @@ -64,7 +62,8 @@ function getRuntimeLoadModesExtensions(modules) { dynamicLoad.push( '\n\n// Add a dynamic runtime loader', 'async function loadModule(module) {', - ' if (typeof module !== \'string\') return module;'); + " if (typeof module !== 'string') return module;" + ); modules.forEach(module => { const packageName = extractName(module); dynamicLoad.push( @@ -73,7 +72,7 @@ function getRuntimeLoadModesExtensions(modules) { ' return imported.default;', ' }' ); - }) + }); dynamicLoad.push( ' return (await import(module)).default;', '}\n', @@ -82,7 +81,8 @@ function getRuntimeLoadModesExtensions(modules) { 'export default function importItems(modules) {', ' return Promise.all(modules.map(loadModule));', '}\n', - 'export { loadModule, modes, extensions, importItems };\n\n'); + 'export { loadModule, modes, extensions, importItems };\n\n' + ); return dynamicLoad.join('\n'); } @@ -91,14 +91,9 @@ const fromDirectory = (srcDir, path) => { if (path[0] === '.') return srcDir + '/../../..' + path.substring(1); if (path[0] === '~') return os.homedir() + path.substring(1); return path; -} +}; -const createCopyPluginToDistForLink = ( - srcDir, - distDir, - plugins, - folderName -) => { +const createCopyPluginToDistForLink = (srcDir, distDir, plugins, folderName) => { return plugins .map(plugin => { const fromDir = fromDirectory(srcDir, plugin.directory); @@ -107,7 +102,7 @@ const createCopyPluginToDistForLink = ( return exists ? { from, - to: distDir, + to: distDir, toType: 'dir', } : undefined; @@ -115,12 +110,7 @@ const createCopyPluginToDistForLink = ( .filter(x => !!x); }; -const createCopyPluginToDistForBuild = ( - SRC_DIR, - DIST_DIR, - plugins, - folderName -) => { +const createCopyPluginToDistForBuild = (SRC_DIR, DIST_DIR, plugins, folderName) => { return plugins .map(plugin => { const from = `${SRC_DIR}/../../../node_modules/${plugin.packageName}/${folderName}/`; @@ -156,17 +146,12 @@ function writePluginImportsFile(SRC_DIR, DIST_DIR) { ...pluginConfig.modes, ]); - fs.writeFileSync( - `${SRC_DIR}/pluginImports.js`, - pluginImportsJsContent, - { flag: 'w+' }, - err => { - if (err) { - console.error(err); - return; - } + fs.writeFileSync(`${SRC_DIR}/pluginImports.js`, pluginImportsJsContent, { flag: 'w+' }, err => { + if (err) { + console.error(err); + return; } - ); + }); // Build packages using cli add-mode and add-extension // will get added to the root node_modules, but the linked packages @@ -175,21 +160,14 @@ function writePluginImportsFile(SRC_DIR, DIST_DIR) { const copyPluginPublicToDistBuild = createCopyPluginToDistForBuild( SRC_DIR, DIST_DIR, - [ - ...pluginConfig.modes, - ...pluginConfig.extensions, - ], + [...pluginConfig.modes, ...pluginConfig.extensions], 'public' ); const copyPluginPublicToDistLink = createCopyPluginToDistForLink( SRC_DIR, DIST_DIR, - [ - ...pluginConfig.modes, - ...pluginConfig.extensions, - ...pluginConfig.public, - ], + [...pluginConfig.modes, ...pluginConfig.extensions, ...pluginConfig.public], 'public' ); @@ -198,20 +176,14 @@ function writePluginImportsFile(SRC_DIR, DIST_DIR) { const copyPluginDistToDistBuild = createCopyPluginToDistForBuild( SRC_DIR, DIST_DIR, - [ - ...pluginConfig.modes, - ...pluginConfig.extensions, - ], + [...pluginConfig.modes, ...pluginConfig.extensions], 'dist' ); const copyPluginDistToDistLink = createCopyPluginToDistForLink( SRC_DIR, DIST_DIR, - [ - ...pluginConfig.modes, - ...pluginConfig.extensions, - ], + [...pluginConfig.modes, ...pluginConfig.extensions], 'dist' ); diff --git a/platform/app/CHANGELOG.md b/platform/app/CHANGELOG.md index ca7b98371d..e40fea3afa 100644 --- a/platform/app/CHANGELOG.md +++ b/platform/app/CHANGELOG.md @@ -3,6 +3,280 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Bug Fixes + +* **react-select:** update react select package ([#3622](https://github.com/OHIF/Viewers/issues/3622)) ([04ca10d](https://github.com/OHIF/Viewers/commit/04ca10d8779dd15454920002f3d48afa8830de8a)) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) +* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + + +### Bug Fixes + +* **keyCloak:** fix openresty keycloak deployment recipe ([#3655](https://github.com/OHIF/Viewers/issues/3655)) ([2d7721c](https://github.com/OHIF/Viewers/commit/2d7721cb581f55dc49e3baeca2411b18dd78ad74)) + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + + +### Bug Fixes + +* **health imaging:** studies not loading from healthimaging if imagepositionpatient is missing ([#3646](https://github.com/OHIF/Viewers/issues/3646)) ([74e62a1](https://github.com/OHIF/Viewers/commit/74e62a176374f720080d4e777972f70e7f2d8b2b)) + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + + +### Bug Fixes + +* **hotkeys:** preserve hotkeys if changed, and reduce re-rendering ([#3635](https://github.com/OHIF/Viewers/issues/3635)) ([94f7cfb](https://github.com/OHIF/Viewers/commit/94f7cfb08e3490488394efc42ef089ebe55e86be)) + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + + +### Bug Fixes + +* **nginx archive recipe:** Fixes to various configuration files. ([#3624](https://github.com/OHIF/Viewers/issues/3624)) ([3ce7225](https://github.com/OHIF/Viewers/commit/3ce72254b390f32c9aa207a0589e688805e2659d)) + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + + +### Bug Fixes + +* **OpenIdConnectRoutes:** fix handleUnauthenticated ([#3617](https://github.com/OHIF/Viewers/issues/3617)) ([35fc30c](https://github.com/OHIF/Viewers/commit/35fc30c5359d8199cc38ffa670c08687d2672f11)) + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/app + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) + + + + + # [4.0.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.11.11...@ohif/viewer@4.0.0) (2020-05-14) diff --git a/platform/app/babel.config.js b/platform/app/babel.config.js index fed6f05fec..325ca2a8ee 100644 --- a/platform/app/babel.config.js +++ b/platform/app/babel.config.js @@ -1 +1 @@ -module.exports = require("../../babel.config.js"); +module.exports = require('../../babel.config.js'); diff --git a/platform/app/cypress.config.ts b/platform/app/cypress.config.ts index 7cf5ff77f9..c575e7c56c 100644 --- a/platform/app/cypress.config.ts +++ b/platform/app/cypress.config.ts @@ -30,7 +30,7 @@ export default defineConfig({ responseTimeout: 10000, specPattern: 'cypress/integration/**/*.spec.[jt]s', projectId: '4oe38f', - video: false, + video: true, reporter: 'junit', reporterOptions: { mochaFile: 'cypress/results/test-output.xml', diff --git a/platform/app/cypress/fixtures/example.json b/platform/app/cypress/fixtures/example.json index da18d9352a..02e4254378 100644 --- a/platform/app/cypress/fixtures/example.json +++ b/platform/app/cypress/fixtures/example.json @@ -2,4 +2,4 @@ "name": "Using fixtures to represent data", "email": "hello@cypress.io", "body": "Fixtures are a great way to mock data for responses to routes" -} \ No newline at end of file +} diff --git a/platform/app/cypress/integration/MultiStudy.spec.js b/platform/app/cypress/integration/MultiStudy.spec.js new file mode 100644 index 0000000000..1df0a98070 --- /dev/null +++ b/platform/app/cypress/integration/MultiStudy.spec.js @@ -0,0 +1,27 @@ +describe('OHIF Multi Study', () => { + const beforeSetup = () => { + cy.checkStudyRouteInViewer( + '1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1,1.2.840.113619.2.5.1762583153.215519.978957063.78', + '&hangingProtocolId=@ohif/hpCompare' + ); + cy.expectMinimumThumbnails(4); + cy.initCornerstoneToolsAliases(); + cy.initCommonElementsAliases(); + cy.waitDicomImage(); + }; + + it('Should display 2 comparison up', () => { + beforeSetup(); + + cy.get('[data-cy="viewport-pane"]').as('viewportPane'); + cy.get('@viewportPane').its('length').should('be.eq', 4); + + cy.get('[data-cy="studyDate"]').as('studyDate'); + + cy.get('@studyDate').should(studyDate => { + expect(studyDate.length).to.be.eq(4); + expect(studyDate.text()).to.contain('2014').contain('2001'); + expect(studyDate.text().indexOf('2014')).to.be.lessThan(studyDate.text().indexOf('2001')); + }); + }); +}); diff --git a/platform/app/cypress/integration/OHIFPdfDisplay.spec.js b/platform/app/cypress/integration/OHIFPdfDisplay.spec.js index 8027380f66..2ef2f020ef 100644 --- a/platform/app/cypress/integration/OHIFPdfDisplay.spec.js +++ b/platform/app/cypress/integration/OHIFPdfDisplay.spec.js @@ -1,13 +1,9 @@ -describe('OHIF PDF Display', function() { - beforeEach(function() { +describe('OHIF PDF Display', function () { + beforeEach(function () { cy.openStudyInViewer('2.25.317377619501274872606137091638706705333'); - - cy.resetViewport().wait(50); }); - it('checks if series thumbnails are being displayed', function() { - cy.get('[data-cy="study-browser-thumbnail-no-image"]') - .its('length') - .should('be.gt', 0); + it('checks if series thumbnails are being displayed', function () { + cy.get('[data-cy="study-browser-thumbnail-no-image"]').its('length').should('be.gt', 0); }); }); diff --git a/platform/app/cypress/integration/OHIFVideoDisplay.spec.js b/platform/app/cypress/integration/OHIFVideoDisplay.spec.js index e312144ee9..ff877034af 100644 --- a/platform/app/cypress/integration/OHIFVideoDisplay.spec.js +++ b/platform/app/cypress/integration/OHIFVideoDisplay.spec.js @@ -1,19 +1,14 @@ -describe('OHIF Video Display', function() { - beforeEach(function() { +describe('OHIF Video Display', function () { + beforeEach(function () { cy.openStudyInViewer('2.25.96975534054447904995905761963464388233'); - cy.resetViewport().wait(50); }); - it('checks if series thumbnails are being displayed', function() { - cy.get('[data-cy="study-browser-thumbnail-no-image"]') - .its('length') - .should('be.gt', 1); + it('checks if series thumbnails are being displayed', function () { + cy.get('[data-cy="study-browser-thumbnail-no-image"]').its('length').should('be.gt', 1); }); it('performs double-click to load thumbnail in active viewport', () => { - cy.get( - '[data-cy="study-browser-thumbnail-no-image"]:nth-child(2)' - ).dblclick(); + cy.get('[data-cy="study-browser-thumbnail-no-image"]:nth-child(2)').dblclick(); //const expectedText = 'Ser: 3'; //cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText); diff --git a/platform/app/cypress/integration/customization/HangingProtocol.spec.js b/platform/app/cypress/integration/customization/HangingProtocol.spec.js index e57fd7d038..4f55e620b5 100644 --- a/platform/app/cypress/integration/customization/HangingProtocol.spec.js +++ b/platform/app/cypress/integration/customization/HangingProtocol.spec.js @@ -1,5 +1,5 @@ describe('OHIF HP', () => { - const beforeSetup = () => { + beforeEach(() => { cy.checkStudyRouteInViewer( '1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1', '&hangingProtocolId=@ohif/mnGrid' @@ -7,30 +7,21 @@ describe('OHIF HP', () => { cy.expectMinimumThumbnails(3); cy.initCornerstoneToolsAliases(); cy.initCommonElementsAliases(); - }; + cy.waitDicomImage(); + }); it('Should display 3 up', () => { - beforeSetup(); - - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', 3); + cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', 3); }); it('Should navigate next/previous stage', () => { - beforeSetup(); - cy.get('body').type(','); cy.wait(250); - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', 4); + cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', 4); cy.get('body').type('..'); cy.wait(250); - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', 2); + cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', 2); }); it('Should navigate to display set specified', () => { diff --git a/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js b/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js index 7b46387faf..431ff8b4ec 100644 --- a/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js +++ b/platform/app/cypress/integration/customization/OHIFDoubleClick.spec.js @@ -11,17 +11,21 @@ describe('OHIF Double Click', () => { it('Should double click each viewport to one up and back', () => { const numExpectedViewports = 3; - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', numExpectedViewports); + cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', numExpectedViewports); for (let i = 0; i < numExpectedViewports; i += 1) { + cy.wait(2000); + // For whatever reason, with Cypress tests, we have to activate the // viewport we are double clicking first. cy.get('[data-cy="viewport-pane"]') .eq(i) - .trigger('mousedown', 'center', { force: true }) - .trigger('mouseup', 'center', { force: true }); + .trigger('mousedown', 'center', { + force: true, + }) + .trigger('mouseup', 'center', { + force: true, + }); // Wait for the viewport to be 'active'. // TODO Is there a better way to do this? @@ -32,21 +36,19 @@ describe('OHIF Double Click', () => { .not('.pointer-events-none'); // The actual double click. - cy.get('[data-cy="viewport-pane"]') - .eq(i) - .trigger('dblclick', 'center'); + cy.get('[data-cy="viewport-pane"]').eq(i).trigger('dblclick', 'center'); - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', 1); + cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', 1); cy.get('[data-cy="viewport-pane"]') - .eq(0) - .trigger('dblclick', 'center'); + .trigger('mousedown', 'center', { + force: true, + }) + .trigger('mouseup', 'center', { + force: true, + }); - cy.get('[data-cy="viewport-pane"]') - .its('length') - .should('be.eq', numExpectedViewports); + cy.get('[data-cy="viewport-pane"]').eq(0).trigger('dblclick', 'center'); } }); }); diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFContextMenuCustomization.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFContextMenuCustomization.spec.js index e87f1bb80c..7e6806413f 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFContextMenuCustomization.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFContextMenuCustomization.spec.js @@ -1,20 +1,18 @@ describe('OHIF Context Menu', function () { beforeEach(function () { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.initCommonElementsAliases(); cy.initCornerstoneToolsAliases(); - cy.resetViewport().wait(50); + cy.waitDicomImage(); }); it('checks context menu customization', function () { // Add length measurement cy.addLengthMeasurement(); - cy.get('[data-cy="prompt-begin-tracking-yes"]').click(); - cy.get('[data-cy="measurement-item"]').click(); + cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').as('yesBtn').click(); + cy.get('[data-cy="measurement-item"]').as('measurementItem').click(); const [x1, y1] = [150, 100]; cy.get('@viewport') @@ -26,17 +24,12 @@ describe('OHIF Context Menu', function () { }); // Contextmenu is visible - cy.get('[data-cy="context-menu"]').should('be.visible'); - + cy.get('[data-cy="context-menu"]').as('contextMenu').should('be.visible'); // Click "Finding" subMenu - cy.get('[data-cy="context-menu-item"]') - .contains('Finding') - .click(); + cy.get('[data-cy="context-menu-item"]').as('item').contains('Finding').click(); // Click "Finding" subMenu - cy.get('[data-cy="context-menu-item"]') - .contains('Aortic insufficiency') - .click(); - cy.get('[data-cy="measurement-item"]').contains('Aortic insufficiency'); + cy.get('[data-cy="context-menu-item"]').as('item').contains('Aortic insufficiency').click(); + cy.get('[data-cy="measurement-item"]').as('measure-item').contains('Aortic insufficiency'); }); }); diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js index ddee809cac..984b038bc0 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js @@ -1,26 +1,22 @@ describe('OHIF Cornerstone Hotkeys', () => { beforeEach(() => { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.window() .its('cornerstone') .then(cornerstone => { // For debugging issues where tests pass locally but fail on CI // - Sometimes Cypress orb seems to use CPU rendering pathway - cy.log( - `Cornerstone using CPU Rendering?: ${cornerstone.getShouldUseCPURendering()}` - ); + cy.log(`Cornerstone using CPU Rendering?: ${cornerstone.getShouldUseCPURendering()}`); }); cy.expectMinimumThumbnails(3); cy.initCornerstoneToolsAliases(); cy.initCommonElementsAliases(); + cy.waitDicomImage(); }); it('checks if hotkeys "R" and "L" can rotate the image', () => { - // Hotkey R cy.get('body').type('R'); cy.get('@viewportInfoMidLeft').should('contains.text', 'P'); cy.get('@viewportInfoMidTop').should('contains.text', 'R'); diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js index c20aa75c63..117bdea388 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js @@ -1,18 +1,15 @@ describe('OHIF Cornerstone Toolbar', () => { beforeEach(() => { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.initCornerstoneToolsAliases(); cy.initCommonElementsAliases(); - cy.get('[data-cy="study-browser-thumbnail"]') - .eq(1) - .click(); + cy.get('[data-cy="study-browser-thumbnail"]').eq(1).click(); //const expectedText = 'Ser: 1'; //cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText); + cy.waitDicomImage(); }); it('checks if all primary buttons are being displayed', () => { @@ -70,12 +67,12 @@ describe('OHIF Cornerstone Toolbar', () => { // }); it('checks if Levels tool will change the window width and center of an image', () => { - //Click on button and verify if icon is active on toolbar - cy.get('@wwwcBtnPrimary') - .click() - .then($wwwcBtn => { - cy.wrap($wwwcBtn).should('have.class', 'active'); - }); + // Wait for the DICOM image to load + + // Assign an alias to the button element + cy.get('@wwwcBtnPrimary').as('wwwcButton'); + cy.get('@wwwcButton').click(); + cy.get('@wwwcButton').should('have.class', 'active'); //drags the mouse inside the viewport to be able to interact with series cy.get('@viewport') @@ -88,20 +85,21 @@ describe('OHIF Cornerstone Toolbar', () => { // The exact text is slightly dependent on the viewport resolution, so leave a range cy.get('@viewportInfoTopLeft').should($txt => { const text = $txt.text(); - expect(text) - .to.include('W:193') - .include('L:479'); + expect(text).to.include('W:193').include('L:479'); }); }); it('checks if Pan tool will move the image inside the viewport', () => { - //Click on button and verify if icon is active on toolbar - cy.get('@panBtn') - .click() - .then($panBtn => { - cy.wrap($panBtn).should('have.class', 'active'); - }); + // Assign an alias to the button element + cy.get('@panBtn').as('panButton'); + + // Click on the button + cy.get('@panButton').click(); + // Assert that the button has the 'active' class + cy.get('@panButton').should('have.class', 'active'); + + // Trigger the pan actions on the viewport cy.get('@viewport') .trigger('mousedown', 'center', { buttons: 1 }) .trigger('mousemove', 'bottom', { buttons: 1 }) @@ -111,16 +109,14 @@ describe('OHIF Cornerstone Toolbar', () => { it('checks if Length annotation can be added to viewport and shows up in the measurements panel', () => { //Click on button and verify if icon is active on toolbar cy.addLengthMeasurement(); - cy.get('[data-cy="viewport-notification"]').should('exist'); - cy.get('[data-cy="viewport-notification"]').should('be.visible'); - cy.get('[data-cy="prompt-begin-tracking-yes"]').click(); + cy.get('[data-cy="viewport-notification"]').as('notif').should('exist'); + cy.get('[data-cy="viewport-notification"]').as('notif').should('be.visible'); + cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').as('yesBtn').click(); //Verify the measurement exists in the table cy.get('@measurementsPanel').should('be.visible'); - cy.get('[data-cy="measurement-item"]') - .its('length') - .should('be.at.least', 1); + cy.get('[data-cy="measurement-item"]').as('measure').its('length').should('be.at.least', 1); }); /*it('checks if angle annotation can be added on viewport without causing any errors', () => { diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js index 9c3752e765..c8dc00d62c 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js @@ -1,13 +1,11 @@ describe('OHIF Download Snapshot File', () => { beforeEach(() => { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.openDownloadImageModal(); }); - it('checks displayed information for Desktop experience', function() { + it('checks displayed information for Desktop experience', function () { // Set Desktop resolution // cy.viewport(1750, 720); // Visual comparison @@ -36,12 +34,8 @@ describe('OHIF Download Snapshot File', () => { // .and('include', 'data:image'); // Check buttons - cy.get('[data-cy="cancel-btn"]') - .scrollIntoView() - .should('be.visible'); - cy.get('[data-cy="download-btn"]') - .scrollIntoView() - .should('be.visible'); + cy.get('[data-cy="cancel-btn"]').scrollIntoView().should('be.visible'); + cy.get('[data-cy="download-btn"]').scrollIntoView().should('be.visible'); }); /*it('cancel changes on download modal', function() { diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFGeneralViewer.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFGeneralViewer.spec.js index 67c1597d90..0d01bd546e 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFGeneralViewer.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFGeneralViewer.spec.js @@ -1,21 +1,19 @@ -describe('OHIF Study Viewer Page', function() { - beforeEach(function() { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); +describe('OHIF Study Viewer Page', function () { + beforeEach(function () { + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.initCommonElementsAliases(); cy.initCornerstoneToolsAliases(); }); - it('scrolls series stack using scrollbar', function() { + it('scrolls series stack using scrollbar', function () { cy.scrollToIndex(13); cy.get('@viewportInfoTopRight').should('contains.text', '14'); }); - it('performs right click to zoom', function() { + it('performs right click to zoom', function () { // This is not used to activate the tool, it is used to ensure the // top left viewport info shows the zoom values (it only shows up // when the zoom tool is active) @@ -25,11 +23,9 @@ describe('OHIF Study Viewer Page', function() { cy.wrap($zoomBtn).should('have.class', 'active'); }); - const zoomLevelInitial = cy - .get('@viewportInfoTopLeft') - .then($viewportInfo => { - return $viewportInfo.text().substring(6, 9); - }); + const zoomLevelInitial = cy.get('@viewportInfoTopLeft').then($viewportInfo => { + return $viewportInfo.text().substring(6, 9); + }); //Right click on viewport cy.get('@viewport') diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFMeasurementPanel.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFMeasurementPanel.spec.js index c96d777010..9f0e8b0c8d 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFMeasurementPanel.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFMeasurementPanel.spec.js @@ -1,16 +1,14 @@ -describe('OHIF Measurement Panel', function() { - beforeEach(function() { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); +describe('OHIF Measurement Panel', function () { + beforeEach(function () { + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.initCommonElementsAliases(); cy.initCornerstoneToolsAliases(); - cy.resetViewport().wait(50); + cy.waitDicomImage(); }); - it('checks if Measurements right panel can be hidden/displayed', function() { + it('checks if Measurements right panel can be hidden/displayed', function () { cy.get('@measurementsPanel').should('exist'); cy.get('@measurementsPanel').should('be.visible'); @@ -22,31 +20,31 @@ describe('OHIF Measurement Panel', function() { cy.get('@measurementsPanel').should('be.visible'); }); - it('checks if measurement item can be Relabeled under Measurements panel', function() { + it('checks if measurement item can be Relabeled under Measurements panel', function () { // Add length measurement cy.addLengthMeasurement(); - cy.get('[data-cy="viewport-notification"]').should('exist'); - cy.get('[data-cy="viewport-notification"]').should('be.visible'); - cy.get('[data-cy="prompt-begin-tracking-yes"]').click(); - cy.get('[data-cy="measurement-item"]').click(); - cy.get('[data-cy="measurement-item"]') - .find('svg') - .click(); + cy.get('[data-cy="viewport-notification"]').as('viewportNotification').should('exist'); + cy.get('[data-cy="viewport-notification"]').as('viewportNotification').should('be.visible'); + + cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').as('yesBtn').click(); + + cy.get('[data-cy="measurement-item"]').as('measurementItem').click(); + + cy.get('[data-cy="measurement-item"]').find('svg').as('measurementItemSvg').click(); // enter Bone label cy.get('[data-cy="input-annotation"]').should('exist'); cy.get('[data-cy="input-annotation"]').should('be.visible'); cy.get('[data-cy="input-annotation"]').type('Bone{enter}'); - // Verify if 'Bone' label was added - cy.get('[data-cy="measurement-item"]').should('contain.text', 'Bone'); + cy.get('[data-cy="measurement-item"]').as('measurementItem').should('contain.text', 'Bone'); }); - it('checks if image would jump when clicked on a measurement item', function() { + it('checks if image would jump when clicked on a measurement item', function () { // Add length measurement - cy.addLengthMeasurement(); - cy.get('[data-cy="prompt-begin-tracking-yes"]').click(); + cy.addLengthMeasurement().wait(250); + cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').as('yesBtn').click(); cy.scrollToIndex(13); @@ -55,9 +53,7 @@ describe('OHIF Measurement Panel', function() { cy.get('@viewportInfoTopRight').should('contains.text', '(14/'); // Click on first measurement item - cy.get('[data-cy="measurement-item"]') - .eq(0) - .click(); + cy.get('[data-cy="measurement-item"]').eq(0).click(); cy.get('@viewportInfoTopRight').should('contains.text', '(1/'); cy.get('@viewportInfoTopRight').should('not.contains.text', '(14/'); diff --git a/platform/app/cypress/integration/measurement-tracking/OHIFStudyBrowser.spec.js b/platform/app/cypress/integration/measurement-tracking/OHIFStudyBrowser.spec.js index e9c70f2eb3..e92e75e5c0 100644 --- a/platform/app/cypress/integration/measurement-tracking/OHIFStudyBrowser.spec.js +++ b/platform/app/cypress/integration/measurement-tracking/OHIFStudyBrowser.spec.js @@ -1,22 +1,17 @@ -describe('OHIF Study Viewer Page', function() { - beforeEach(function() { - cy.checkStudyRouteInViewer( - '1.2.840.113619.2.5.1762583153.215519.978957063.78' - ); +describe('OHIF Study Viewer Page', function () { + beforeEach(function () { + cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78'); cy.expectMinimumThumbnails(3); cy.initCommonElementsAliases(); cy.initCornerstoneToolsAliases(); - cy.resetViewport().wait(50); }); - it('checks if series thumbnails are being displayed', function() { - cy.get('[data-cy="study-browser-thumbnail"]') - .its('length') - .should('be.gt', 1); + it('checks if series thumbnails are being displayed', function () { + cy.get('[data-cy="study-browser-thumbnail"]').its('length').should('be.gt', 1); }); - it('drags and drop a series thumbnail into viewport', function() { + it('drags and drop a series thumbnail into viewport', function () { // Can't use the native drag version as the element should be rerendered // cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)') //element to be dragged // .drag('.cornerstone-canvas'); //dropzone element @@ -38,7 +33,7 @@ describe('OHIF Study Viewer Page', function() { //cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText); }); - it('checks if Series left panel can be hidden/displayed', function() { + it('checks if Series left panel can be hidden/displayed', function () { cy.get('@seriesPanel').should('exist'); cy.get('@seriesPanel').should('be.visible'); diff --git a/platform/app/cypress/integration/study-list/OHIFStudyList.spec.js b/platform/app/cypress/integration/study-list/OHIFStudyList.spec.js index 0d07b52a52..9809a1b7c5 100644 --- a/platform/app/cypress/integration/study-list/OHIFStudyList.spec.js +++ b/platform/app/cypress/integration/study-list/OHIFStudyList.spec.js @@ -1,8 +1,8 @@ //We are keeping the hardcoded results values for the study list tests -//this is intended to be running in a controled docker environment with test data. -describe('OHIF Study List', function() { - context('Desktop resolution', function() { - beforeEach(function() { +//this is intended to be running in a controlled docker environment with test data. +describe('OHIF Study List', function () { + context('Desktop resolution', function () { + beforeEach(function () { cy.openStudyList(); cy.viewport(1750, 720); @@ -14,7 +14,7 @@ describe('OHIF Study List', function() { cy.get('@StudyDescription').clear(); }); - it('Displays several studies initially', function() { + it('Displays several studies initially', function () { cy.waitStudyList(); cy.get('@searchResult2').should($list => { expect($list.length).to.be.greaterThan(1); @@ -23,7 +23,7 @@ describe('OHIF Study List', function() { }); }); - it('searches Patient Name with exact string', function() { + it('searches Patient Name with exact string', function () { cy.get('@PatientName').type('Juno'); //Wait result list to be displayed cy.waitStudyList(); @@ -33,7 +33,7 @@ describe('OHIF Study List', function() { }); }); - it('searches MRN with exact string', function() { + it('searches MRN with exact string', function () { cy.get('@MRN').type('0000003'); //Wait result list to be displayed cy.waitStudyList(); @@ -43,7 +43,7 @@ describe('OHIF Study List', function() { }); }); - it('searches Accession with exact string', function() { + it('searches Accession with exact string', function () { cy.get('@AccessionNumber').type('321'); //Wait result list to be displayed cy.waitStudyList(); @@ -53,7 +53,7 @@ describe('OHIF Study List', function() { }); }); - it('searches Description with exact string', function() { + it('searches Description with exact string', function () { cy.get('@StudyDescription').type('PETCT'); //Wait result list to be displayed cy.waitStudyList(); diff --git a/platform/app/cypress/integration/volume/MPR.spec.js b/platform/app/cypress/integration/volume/MPR.spec.js index 080f8100a0..c2f8d94ccd 100644 --- a/platform/app/cypress/integration/volume/MPR.spec.js +++ b/platform/app/cypress/integration/volume/MPR.spec.js @@ -1,8 +1,6 @@ describe('OHIF MPR', () => { beforeEach(() => { - cy.checkStudyRouteInViewer( - '1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1' - ); + cy.checkStudyRouteInViewer('1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1'); cy.expectMinimumThumbnails(3); cy.initCornerstoneToolsAliases(); cy.initCommonElementsAliases(); @@ -66,19 +64,17 @@ describe('OHIF MPR', () => { .then(cornerstone => { const viewports = cornerstone.getRenderingEngines()[0].getViewports(); - const imageData1 = viewports[0].getImageData(); - const imageData2 = viewports[1].getImageData(); - const imageData3 = viewports[2].getImageData(); + // The stack viewport still exists after the changes to viewportId and inde + const imageData1 = viewports[1].getImageData(); + const imageData2 = viewports[2].getImageData(); + const imageData3 = viewports[3].getImageData(); // for some reason map doesn't work here cy.wrap(imageData1).should('not.be', undefined); cy.wrap(imageData2).should('not.be', undefined); cy.wrap(imageData3).should('not.be', undefined); - cy.wrap(imageData1.dimensions).should( - 'deep.equal', - imageData2.dimensions - ); + cy.wrap(imageData1.dimensions).should('deep.equal', imageData2.dimensions); cy.wrap(imageData1.origin).should('deep.equal', imageData2.origin); }); @@ -88,10 +84,7 @@ describe('OHIF MPR', () => { cy.get('.cornerstone-canvas').should('have.length', 1); // should not have any div under it - cy.get('[data-cy="thumbnail-viewport-labels"]') - .eq(2) - .find('div') - .should('have.length', 0); + cy.get('[data-cy="thumbnail-viewport-labels"]').eq(2).find('div').should('have.length', 0); }); it('should correctly render Crosshairs for MPR', () => { diff --git a/platform/app/cypress/plugins/index.js b/platform/app/cypress/plugins/index.js index aa9918d215..8dd144a6c1 100644 --- a/platform/app/cypress/plugins/index.js +++ b/platform/app/cypress/plugins/index.js @@ -18,4 +18,4 @@ module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config -} +}; diff --git a/platform/app/cypress/support/DragSimulator.js b/platform/app/cypress/support/DragSimulator.js index 673a4668f7..c6a8e5f943 100644 --- a/platform/app/cypress/support/DragSimulator.js +++ b/platform/app/cypress/support/DragSimulator.js @@ -6,18 +6,12 @@ export const DragSimulator = { counter: 0, rectsEqual(r1, r2) { return ( - r1.top === r2.top && - r1.right === r2.right && - r1.bottom === r2.bottom && - r1.left === r2.left + r1.top === r2.top && r1.right === r2.right && r1.bottom === r2.bottom && r1.left === r2.left ); }, get dropped() { const currentSourcePosition = this.source.getBoundingClientRect(); - return !this.rectsEqual( - this.initialSourcePosition, - currentSourcePosition - ); + return !this.rectsEqual(this.initialSourcePosition, currentSourcePosition); }, get hasTriesLeft() { return this.counter < this.MAX_TRIES; @@ -71,8 +65,6 @@ export const DragSimulator = { simulate(sourceWrapper, targetSelector, position = 'center') { return cy .get(targetSelector) - .then(targetWrapper => - this.init(sourceWrapper.get(0), targetWrapper.get(0), position) - ); + .then(targetWrapper => this.init(sourceWrapper.get(0), targetWrapper.get(0), position)); }, }; diff --git a/platform/app/cypress/support/aliases.js b/platform/app/cypress/support/aliases.js index 52dac3ae52..1bda29b36a 100644 --- a/platform/app/cypress/support/aliases.js +++ b/platform/app/cypress/support/aliases.js @@ -3,16 +3,10 @@ export function initCornerstoneToolsAliases() { cy.get('[data-cy="StackScroll"]').as('stackScrollBtn'); cy.get('[data-cy="Zoom"]').as('zoomBtn'); cy.get('[data-cy="WindowLevel-split-button-primary"]').as('wwwcBtnPrimary'); - cy.get('[data-cy="WindowLevel-split-button-secondary"]').as( - 'wwwcBtnSecondary' - ); + cy.get('[data-cy="WindowLevel-split-button-secondary"]').as('wwwcBtnSecondary'); cy.get('[data-cy="Pan"]').as('panBtn'); - cy.get('[data-cy="MeasurementTools-split-button-primary"]').as( - 'measurementToolsBtnPrimary' - ); - cy.get('[data-cy="MeasurementTools-split-button-secondary"]').as( - 'measurementToolsBtnSecondary' - ); + cy.get('[data-cy="MeasurementTools-split-button-primary"]').as('measurementToolsBtnPrimary'); + cy.get('[data-cy="MeasurementTools-split-button-secondary"]').as('measurementToolsBtnSecondary'); // cy.get('[data-cy="Angle"]').as('angleBtn'); cy.get('[data-cy="MoreTools-split-button-primary"]').as('moreBtnPrimary'); cy.get('[data-cy="MoreTools-split-button-secondary"]').as('moreBtnSecondary'); @@ -37,12 +31,8 @@ export function initCommonElementsAliases() { cy.get('[data-cy="studyBrowser-panel"]').as('seriesPanel'); cy.get('[data-cy="viewport-overlay-top-right"]').as('viewportInfoTopRight'); cy.get('[data-cy="viewport-overlay-top-left"]').as('viewportInfoTopLeft'); - cy.get('[data-cy="viewport-overlay-bottom-right"]').as( - 'viewportInfoBottomRight' - ); - cy.get('[data-cy="viewport-overlay-bottom-left"]').as( - 'viewportInfoBottomLeft' - ); + cy.get('[data-cy="viewport-overlay-bottom-right"]').as('viewportInfoBottomRight'); + cy.get('[data-cy="viewport-overlay-bottom-left"]').as('viewportInfoBottomLeft'); cy.get('.left-mid.orientation-marker').as('viewportInfoMidLeft'); cy.get('.top-mid.orientation-marker').as('viewportInfoMidTop'); @@ -50,9 +40,7 @@ export function initCommonElementsAliases() { //Creating aliases for Routes export function initRouteAliases() { - cy.intercept('GET', '**/series**', { statusCode: 200, body: [] }).as( - 'getStudySeries' - ); + cy.intercept('GET', '**/series**', { statusCode: 200, body: [] }).as('getStudySeries'); // Todo: for some reason cypress does not redirect to the correct url // so we intercept the request and redirect it to the correct url @@ -73,7 +61,7 @@ export function initStudyListAliasesOnDesktop() { cy.get('[data-cy="study-list-results"] > tr').as('searchResult2'); // We can't use data attributes (e.g. data--cy) for these since - // they are using third party libraires (i.e. react-dates, react-select) + // they are using third party libraries (i.e. react-dates, react-select) cy.get('#date-range-studyDate-start-date').as('studyListStartDate'); cy.get('#date-range-studyDate-end-date').as('studyListEndDate'); cy.get('#input-modalities').as('modalities'); diff --git a/platform/app/cypress/support/commands.js b/platform/app/cypress/support/commands.js index df760db103..70a5cfe814 100644 --- a/platform/app/cypress/support/commands.js +++ b/platform/app/cypress/support/commands.js @@ -46,7 +46,7 @@ Cypress.Commands.add('openStudy', PatientName => { cy.openStudyList(); cy.get('#filter-patientNameOrId').type(PatientName); // cy.get('@getStudies').then(() => { - cy.wait(1000); + cy.waitQueryList(); cy.get('[data-cy="study-list-results"]', { timeout: 5000 }) .contains(PatientName) @@ -56,16 +56,14 @@ Cypress.Commands.add('openStudy', PatientName => { Cypress.Commands.add( 'checkStudyRouteInViewer', - (StudyInstanceUID, otherParams = '') => { + (StudyInstanceUID, otherParams = '', mode = '/basic-test') => { cy.location('pathname').then($url => { cy.log($url); - if ( - $url == 'blank' || - !$url.includes(`/basic-test/${StudyInstanceUID}${otherParams}`) - ) { - cy.openStudyInViewer(StudyInstanceUID, otherParams); + if ($url === 'blank' || !$url.includes(`${mode}/${StudyInstanceUID}${otherParams}`)) { + cy.openStudyInViewer(StudyInstanceUID, otherParams, mode); cy.waitDicomImage(); - cy.wait(2000); + // Very short wait to ensure pending updates are handled + cy.wait(25); } }); } @@ -73,11 +71,14 @@ Cypress.Commands.add( Cypress.Commands.add( 'openStudyInViewer', - (StudyInstanceUID, otherParams = '') => { - cy.visit(`/basic-test?StudyInstanceUIDs=${StudyInstanceUID}${otherParams}`); + (StudyInstanceUID, otherParams = '', mode = '/basic-test') => { + cy.visit(`${mode}?StudyInstanceUIDs=${StudyInstanceUID}${otherParams}`); } ); +Cypress.Commands.add('waitQueryList', () => { + cy.get('[data-querying="false"]'); +}); /** * Command to search for a Modality and open the study. * @@ -87,14 +88,9 @@ Cypress.Commands.add('openStudyModality', Modality => { cy.initRouteAliases(); cy.visit('/'); - cy.get('#filter-accessionOrModalityOrDescription') - .type(Modality) - .wait(2000); + cy.get('#filter-accessionOrModalityOrDescription').type(Modality).waitQueryList(); - cy.get('[data-cy="study-list-results"]') - .contains(Modality) - .first() - .click(); + cy.get('[data-cy="study-list-results"]').contains(Modality).first().click(); }); /** @@ -113,7 +109,7 @@ Cypress.Commands.add('openStudyList', () => { // For some reason cypress 12.x does not like to stub the network request // so we just wait herer for 1 second // cy.wait('@getStudies'); - cy.wait(1000); + cy.waitQueryList(); }); Cypress.Commands.add('waitStudyList', () => { @@ -148,18 +144,21 @@ Cypress.Commands.add('drag', { prevSubject: 'element' }, (...args) => * @param {number[]} secondClick - Click position [x, y] */ Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => { - cy.get(viewport).then($viewport => { - const [x1, y1] = firstClick; - const [x2, y2] = secondClick; + const performClick = (alias, x, y) => { + cy.get(alias).as(`axu-${alias}`).click(x, y, { force: true, multiple: true }).wait(1000); + }; - // TODO: Added a wait which appears necessary in Cornerstone Tools >4? - cy.wrap($viewport) - .click(x1, y1) - .wait(100) - .trigger('mousemove', { clientX: x2, clientY: y2 }) - .click(x2, y2) - .wait(100); - }); + cy.get(viewport).as('viewportAlias'); + const [x1, y1] = firstClick; + const [x2, y2] = secondClick; + + // First click + performClick('@viewportAlias', x1, y1); + + // Second click + performClick('@viewportAlias', x2, y2); + + cy.wait(1000); }); /** @@ -171,95 +170,64 @@ Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => { * @param {number[]} secondClick - Click position [x, y] * @param {number[]} thirdClick - Click position [x, y] */ -Cypress.Commands.add( - 'addAngle', - (viewport, firstClick, secondClick, thirdClick) => { - cy.get(viewport).then($viewport => { - const [x1, y1] = firstClick; - const [x2, y2] = secondClick; - const [x3, y3] = thirdClick; - - cy.wrap($viewport) - .click(x1, y1, { force: true }) - .trigger('mousemove', { clientX: x2, clientY: y2 }) - .click(x2, y2, { force: true }) - .trigger('mousemove', { clientX: x3, clientY: y3 }) - .click(x3, y3, { force: true }); - }); - } -); +Cypress.Commands.add('addAngle', (viewport, firstClick, secondClick, thirdClick) => { + cy.get(viewport).then($viewport => { + const [x1, y1] = firstClick; + const [x2, y2] = secondClick; + const [x3, y3] = thirdClick; + + cy.wrap($viewport) + .click(x1, y1, { force: true }) + .trigger('mousemove', { clientX: x2, clientY: y2 }) + .click(x2, y2, { force: true }) + .trigger('mousemove', { clientX: x3, clientY: y3 }) + .click(x3, y3, { force: true }); + }); +}); Cypress.Commands.add('expectMinimumThumbnails', (seriesToWait = 1) => { cy.get('[data-cy="study-browser-thumbnail"]', { timeout: 50000 }).should( - $itemList => { - expect($itemList.length >= seriesToWait).to.be.true; - } + 'have.length.gte', + seriesToWait ); }); //Command to wait DICOM image to load into the viewport -Cypress.Commands.add('waitDicomImage', (timeout = 50000) => { - const loaded = cy.isPageLoaded(); - - if (loaded) { - cy.window() - .its('cornerstone') - .then({ timeout }, $cornerstone => { - return new Cypress.Promise(resolve => { - const onEvent = renderedEvt => { - const element = renderedEvt.detail.element; - - element.removeEventListener( - $cornerstone.Enums.Events.IMAGE_RENDERED, - onEvent - ); - $cornerstone.eventTarget.removeEventListener( - $cornerstone.Enums.Events.IMAGE_RENDERED, - onEvent - ); - resolve(); - }; - const onEnabled = enabledEvt => { - const element = enabledEvt.detail.element; - - element.addEventListener( - $cornerstone.Enums.Events.IMAGE_RENDERED, - onEvent - ); - - $cornerstone.eventTarget.removeEventListener( - $cornerstone.Enums.Events.ELEMENT_ENABLED, - onEnabled - ); - }; - const enabledElements = $cornerstone.getEnabledElements(); - if (enabledElements && enabledElements.length) { - // Sometimes the page finishes rendering before this gets run, - // if so, just resolve immediately. - resolve(); - } else { - $cornerstone.eventTarget.addEventListener( - $cornerstone.Enums.Events.ELEMENT_ENABLED, - onEnabled - ); +Cypress.Commands.add('waitDicomImage', (mode = '/basic-test', timeout = 50000) => { + cy.window() + .its('cornerstone') + .should($cornerstone => { + const enabled = $cornerstone.getEnabledElements(); + if (enabled?.length) { + enabled.forEach((item, i) => { + if (item.viewport.viewportStatus !== $cornerstone.Enums.ViewportStatus.RENDERED) { + throw new Error(`Viewport ${i} in state ${item.viewport.viewportStatus}`); } }); - }); - } + } else { + throw new Error('No enabled elements'); + } + }); + // This shouldn't be necessary, but seems to be. + cy.wait(250); + cy.log('DICOM image loaded'); }); //Command to reset and clear all the changes made to the viewport Cypress.Commands.add('resetViewport', () => { - //Click on More button + // Assign an alias to the More button cy.get('[data-cy="MoreTools-split-button-primary"]') .should('have.attr', 'data-tool', 'Reset') - .as('moreBtn') - .click(); + .as('moreBtn'); + + // Use the alias to click on the More button + cy.get('@moreBtn').click(); }); Cypress.Commands.add('imageZoomIn', () => { cy.initCornerstoneToolsAliases(); cy.get('@zoomBtn').click(); + cy.wait(25); //drags the mouse inside the viewport to be able to interact with series cy.get('@viewport') @@ -271,6 +239,7 @@ Cypress.Commands.add('imageZoomIn', () => { Cypress.Commands.add('imageContrast', () => { cy.initCornerstoneToolsAliases(); cy.get('@wwwcBtnPrimary').click(); + cy.wait(25); //drags the mouse inside the viewport to be able to interact with series cy.get('@viewport') @@ -303,14 +272,15 @@ Cypress.Commands.add('initStudyListAliasesOnDesktop', () => { Cypress.Commands.add( 'addLengthMeasurement', (firstClick = [150, 100], secondClick = [130, 170]) => { - cy.get('@measurementToolsBtnPrimary') - .should('have.attr', 'data-tool', 'Length') - .click() - .then($lengthBtn => { - cy.wrap($lengthBtn).should('have.class', 'active'); - }); - - cy.addLine('.viewport-element', firstClick, secondClick); + // Assign an alias to the button element + cy.get('@measurementToolsBtnPrimary').as('lengthButton'); + + cy.get('@lengthButton').should('have.attr', 'data-tool', 'Length'); + cy.get('@lengthButton').click(); + + cy.get('@lengthButton').should('have.class', 'active'); + + cy.addLine('.cornerstone-canvas', firstClick, secondClick); } ); @@ -318,8 +288,10 @@ Cypress.Commands.add( Cypress.Commands.add( 'addAngleMeasurement', (initPos = [180, 390], midPos = [300, 410], finalPos = [180, 450]) => { + cy.get('[data-cy="MeasurementTools-split-button-secondary"]').click(); cy.get('[data-cy="Angle"]').click(); - cy.addAngle('.viewport-element', initPos, midPos, finalPos); + + cy.addAngle('.cornerstone-canvas', initPos, midPos, finalPos); } ); @@ -401,22 +373,19 @@ Cypress.Commands.add('setLayout', (columns = 1, rows = 1) => { .eq(columns - 1) .click(); - cy.wait(1000); + cy.wait(10); + cy.waitDicomImage(); }); function convertCanvas(documentClone) { - documentClone - .querySelectorAll('canvas') - .forEach(selector => canvasToImage(selector)); + documentClone.querySelectorAll('canvas').forEach(selector => canvasToImage(selector)); return documentClone; } function unconvertCanvas(documentClone) { // Remove previously generated images - documentClone - .querySelectorAll('[data-percy-image]') - .forEach(selector => selector.remove()); + documentClone.querySelectorAll('[data-percy-image]').forEach(selector => selector.remove()); // Restore canvas visibility documentClone.querySelectorAll('[data-percy-canvas]').forEach(selector => { selector.removeAttribute('data-percy-canvas'); @@ -426,9 +395,7 @@ function unconvertCanvas(documentClone) { function canvasToImage(selectorOrEl) { let canvas = - typeof selectorOrEl === 'object' - ? selectorOrEl - : document.querySelector(selectorOrEl); + typeof selectorOrEl === 'object' ? selectorOrEl : document.querySelector(selectorOrEl); let image = document.createElement('img'); let canvasImageBase64 = canvas.toDataURL('image/png'); @@ -456,10 +423,7 @@ Cypress.Commands.add('openPreferences', () => { .scrollIntoView() .click() .then(() => { - cy.get('[data-cy="options-dropdown"]') - .last() - .click() - .wait(200); + cy.get('[data-cy="options-dropdown"]').last().click().wait(200); }); } }); @@ -494,9 +458,7 @@ Cypress.Commands.add('closePreferences', () => { cy.get('body').then(body => { // Close notification if displayed if (body.find('.sb-closeIcon').length > 0) { - cy.get('.sb-closeIcon') - .first() - .click({ force: true }); + cy.get('.sb-closeIcon').first().click({ force: true }); } // Close User Preferences Modal (if displayed) @@ -508,9 +470,11 @@ Cypress.Commands.add('closePreferences', () => { Cypress.Commands.add('selectPreferencesTab', tabAlias => { cy.initPreferencesModalAliases(); - cy.get(tabAlias) - .click() - .should('have.class', 'active'); + + cy.get(tabAlias).as('selectedTab'); + cy.get('@selectedTab').click(); + cy.get('@selectedTab').should('have.class', 'active'); + initPreferencesModalFooterBtnAliases(); }); @@ -526,9 +490,7 @@ Cypress.Commands.add('resetUserHotkeyPreferences', () => { // Close Success Message overlay (if displayed) cy.get('body').then(body => { if (body.find('.sb-closeIcon').length > 0) { - cy.get('.sb-closeIcon') - .first() - .click({ force: true }); + cy.get('.sb-closeIcon').first().click({ force: true }); } // Click on Save Button cy.get('@saveBtn').click(); @@ -547,28 +509,23 @@ Cypress.Commands.add('resetUserGeneralPreferences', () => { // Close Success Message overlay (if displayed) cy.get('body').then(body => { if (body.find('.sb-closeIcon').length > 0) { - cy.get('.sb-closeIcon') - .first() - .click({ force: true }); + cy.get('.sb-closeIcon').first().click({ force: true }); } // Click on Save Button cy.get('@saveBtn').click(); }); }); -Cypress.Commands.add( - 'setNewHotkeyShortcutOnUserPreferencesModal', - (function_label, shortcut) => { - // Within scopes all `.get` and `.contains` to within the matched elements - // dom instead of checking from document - cy.get('.HotkeysPreferences').within(() => { - cy.contains(function_label) // label we're looking for - .parent() - .find('input') // closest input to that label - .type(shortcut, { force: true }); // Set new shortcut for that function - }); - } -); +Cypress.Commands.add('setNewHotkeyShortcutOnUserPreferencesModal', (function_label, shortcut) => { + // Within scopes all `.get` and `.contains` to within the matched elements + // dom instead of checking from document + cy.get('.HotkeysPreferences').within(() => { + cy.contains(function_label) // label we're looking for + .parent() + .find('input') // closest input to that label + .type(shortcut, { force: true }); // Set new shortcut for that function + }); +}); Cypress.Commands.add( 'setWindowLevelPreset', @@ -603,9 +560,7 @@ Cypress.Commands.add( Cypress.Commands.add('openDownloadImageModal', () => { // Click on More button - cy.get('[data-cy="Capture"]') - .as('captureBtn') - .click(); + cy.get('[data-cy="Capture"]').as('captureBtn').click(); }); Cypress.Commands.add('setLanguage', (language, save = true) => { @@ -622,16 +577,12 @@ Cypress.Commands.add('setLanguage', (language, save = true) => { // Close Success Message overlay (if displayed) cy.get('body').then(body => { if (body.find('.sb-closeIcon').length > 0) { - cy.get('.sb-closeIcon') - .first() - .click({ force: true }); + cy.get('.sb-closeIcon').first().click({ force: true }); } //Click on Save/Cancel button const toClick = save ? '@saveBtn' : '@cancelBtn'; - cy.get(toClick) - .scrollIntoView() - .click(); + cy.get(toClick).scrollIntoView().click(); }); }); @@ -639,7 +590,7 @@ Cypress.Commands.add('setLanguage', (language, save = true) => { // https://github.com/cypress-io/cypress/issues/7362 // uncomment this if you really need the network logs const origLog = Cypress.log; -Cypress.log = function(opts, ...other) { +Cypress.log = function (opts, ...other) { if (opts.displayName === 'script' || opts.name === 'request') { return; } diff --git a/platform/app/jestBabelTransform.js b/platform/app/jestBabelTransform.js index d1b645bc78..81c0a5cc96 100644 --- a/platform/app/jestBabelTransform.js +++ b/platform/app/jestBabelTransform.js @@ -1,5 +1,5 @@ -const babelJest = require("babel-jest"); +const babelJest = require('babel-jest'); module.exports = babelJest.createTransformer({ - rootMode: "upward" + rootMode: 'upward', }); diff --git a/platform/app/netlify.toml b/platform/app/netlify.toml index 4af3dd5977..bc07d914e5 100644 --- a/platform/app/netlify.toml +++ b/platform/app/netlify.toml @@ -19,7 +19,7 @@ [build.environment] # If 'production', `yarn install` does not install devDependencies NODE_ENV = "development" - NODE_VERSION = "16.14.0" + NODE_VERSION = "18.16.1" YARN_VERSION = "1.22.5" RUBY_VERSION = "2.6.2" YARN_FLAGS = "--no-ignore-optional --pure-lockfile" @@ -43,3 +43,5 @@ # COMMENT: For sharedArrayBuffer, see https://developer.chrome.com/blog/enabling-shared-array-buffer/ Cross-Origin-Embedder-Policy = "require-corp" Cross-Origin-Opener-Policy = "same-origin" + # set CORP to cross-origin for anyone who wants to use the viewer in an iframe + Cross-Origin-Resource-Policy = "cross-origin" diff --git a/platform/app/package.json b/platform/app/package.json index a95f7e0d3a..49ffe0b881 100644 --- a/platform/app/package.json +++ b/platform/app/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/app", - "version": "3.6.0", + "version": "3.7.0-beta.85", "productVersion": "3.4.0", "description": "OHIF Viewer", "author": "OHIF Contributors", @@ -30,7 +30,6 @@ "dev:dcm4chee": "cross-env NODE_ENV=development APP_CONFIG=config/local_dcm4chee.js webpack serve --config .webpack/webpack.pwa.js", "dev:static": "cross-env NODE_ENV=development APP_CONFIG=config/local_static.js webpack serve --config .webpack/webpack.pwa.js", "dev:viewer": "yarn run dev", - "preinstall": "node preinstall.js", "start": "yarn run dev", "test:e2e": "cypress open", "test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'", @@ -51,23 +50,23 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", - "@cornerstonejs/dicom-image-loader": "^0.6.8", - "@ohif/core": "3.6.0", - "@ohif/extension-cornerstone": "3.6.0", - "@ohif/extension-cornerstone-dicom-rt": "3.6.0", - "@ohif/extension-cornerstone-dicom-seg": "3.6.0", - "@ohif/extension-cornerstone-dicom-sr": "3.6.0", - "@ohif/extension-default": "3.6.0", - "@ohif/extension-dicom-microscopy": "3.6.0", - "@ohif/extension-dicom-pdf": "3.6.0", - "@ohif/extension-dicom-video": "3.6.0", - "@ohif/extension-test": "3.6.0", - "@ohif/i18n": "3.6.0", - "@ohif/mode-basic-dev-mode": "3.6.0", - "@ohif/mode-longitudinal": "3.6.0", - "@ohif/mode-microscopy": "3.6.0", - "@ohif/mode-test": "3.6.0", - "@ohif/ui": "3.6.0", + "@cornerstonejs/dicom-image-loader": "^1.16.5", + "@ohif/core": "3.7.0-beta.85", + "@ohif/extension-cornerstone": "3.7.0-beta.85", + "@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.85", + "@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.85", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.85", + "@ohif/extension-default": "3.7.0-beta.85", + "@ohif/extension-dicom-microscopy": "3.7.0-beta.85", + "@ohif/extension-dicom-pdf": "3.7.0-beta.85", + "@ohif/extension-dicom-video": "3.7.0-beta.85", + "@ohif/extension-test": "3.7.0-beta.85", + "@ohif/i18n": "3.7.0-beta.85", + "@ohif/mode-basic-dev-mode": "3.7.0-beta.85", + "@ohif/mode-longitudinal": "3.7.0-beta.85", + "@ohif/mode-microscopy": "3.7.0-beta.85", + "@ohif/mode-test": "3.7.0-beta.85", + "@ohif/ui": "3.7.0-beta.85", "@types/react": "^17.0.38", "classnames": "^2.3.2", "core-js": "^3.16.1", @@ -95,7 +94,7 @@ "devDependencies": { "@babel/plugin-proposal-private-methods": "^7.18.6", "@percy/cypress": "^3.1.1", - "cypress": "^12.6.0", + "cypress": "^13.2.0", "cypress-file-upload": "^3.5.3", "glob": "^8.0.3", "identity-obj-proxy": "3.0.x", diff --git a/platform/app/pluginConfig.json b/platform/app/pluginConfig.json index 2f892db6c8..08a42deb0f 100644 --- a/platform/app/pluginConfig.json +++ b/platform/app/pluginConfig.json @@ -57,6 +57,9 @@ { "packageName": "@ohif/mode-longitudinal" }, + { + "packageName": "@ohif/mode-segmentation" + }, { "packageName": "@ohif/mode-tmtv" }, diff --git a/platform/app/postcss.config.js b/platform/app/postcss.config.js index 6402436e4a..a0daaed081 100644 --- a/platform/app/postcss.config.js +++ b/platform/app/postcss.config.js @@ -1 +1 @@ -module.exports = require("../../postcss.config.js"); +module.exports = require('../../postcss.config.js'); diff --git a/platform/app/public/_redirects b/platform/app/public/_redirects index 9730bd0d3c..c3f7726c34 100644 --- a/platform/app/public/_redirects +++ b/platform/app/public/_redirects @@ -1,6 +1,6 @@ # Specific to our deploy-preview # Our docs are published using CircleCI + GitBook -# Confgure redirects using netlify.toml +# Configure redirects using netlify.toml # Spa /* /index.html 200 diff --git a/platform/app/public/assets/yandex-browser-manifest.json b/platform/app/public/assets/yandex-browser-manifest.json index ce9b1c12ec..846829c994 100644 --- a/platform/app/public/assets/yandex-browser-manifest.json +++ b/platform/app/public/assets/yandex-browser-manifest.json @@ -6,4 +6,4 @@ "color": "#fff", "show_title": true } -} \ No newline at end of file +} diff --git a/platform/app/public/config/aws.js b/platform/app/public/config/aws.js index 35ffbde5ca..f7dd9a0698 100644 --- a/platform/app/public/config/aws.js +++ b/platform/app/public/config/aws.js @@ -4,7 +4,7 @@ window.config = { modes: [], showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, + showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -13,38 +13,39 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'DCM4CHEE', // Something here to check build wadoUriRoot: 'https://myserver.com/dicomweb', qidoRoot: 'https://myserver.com/dicomweb', wadoRoot: 'https://myserver.com/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, supportsFuzzyMatching: false, supportsWildcard: false, staticWado: true, + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/default.js b/platform/app/public/config/default.js index 8ff91875b8..00013529ea 100644 --- a/platform/app/public/config/default.js +++ b/platform/app/public/config/default.js @@ -3,15 +3,11 @@ window.config = { // whiteLabeling: {}, extensions: [], modes: [], - customizationService: { - // Shows a custom route -access via http://localhost:3000/custom - // helloPage: '@ohif/extension-default.customizationModule.helloPage', - }, + customizationService: {}, showStudyList: true, // some windows systems have issues with more than 3 web workers maxNumberOfWebWorkers: 3, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -37,21 +33,41 @@ window.config = { // }, dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'AWS S3 Static wado server', name: 'aws', - // old server - // wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - // qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - // wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - - // new server wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', - + qidoSupportsIncludeField: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: true, + staticWado: true, + singlepart: 'bulkdata,video', + // whether the data source should use retrieveBulkData to grab metadata, + // and in case of relative path, what would it be relative to, options + // are in the series level or study level (some servers like series some study) + bulkDataURI: { + enabled: true, + relativeResolution: 'studies', + }, + omitQuotationForMultipartRequest: true, + }, + }, + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb2', + configuration: { + friendlyName: 'AWS S3 Static wado secondary server', + name: 'aws', + wadoUriRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', + qidoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', + wadoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', qidoSupportsIncludeField: false, supportsReject: false, imageRendering: 'wadors', @@ -68,29 +84,31 @@ window.config = { enabled: true, relativeResolution: 'studies', }, + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicomweb delegating proxy', namespace: '@ohif/extension-default.dataSourcesModule.dicomwebproxy', sourceName: 'dicomwebproxy', configuration: { + friendlyName: 'dicomweb delegating proxy', name: 'dicomwebproxy', }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/default_16bit.js b/platform/app/public/config/default_16bit.js new file mode 100644 index 0000000000..20b14d6b9b --- /dev/null +++ b/platform/app/public/config/default_16bit.js @@ -0,0 +1,189 @@ +window.config = { + routerBasename: '/', + // whiteLabeling: {}, + extensions: [], + modes: [], + customizationService: { + // Shows a custom route -access via http://localhost:3000/custom + // helloPage: '@ohif/extension-default.customizationModule.helloPage', + }, + showStudyList: true, + // some windows systems have issues with more than 3 web workers + maxNumberOfWebWorkers: 3, + // below flag is for performance reasons, but it might not work for all servers + omitQuotationForMultipartRequest: true, + showWarningMessageForCrossOrigin: false, + showCPUFallbackMessage: true, + showLoadingIndicator: true, + use16BitDataType: true, + useSharedArrayBuffer: 'AUTO', + maxNumRequests: { + interaction: 100, + thumbnail: 75, + // Prefetch number is dependent on the http protocol. For http 2 or + // above, the number of requests can be go a lot higher. + prefetch: 25, + }, + // filterQueryParam: false, + dataSources: [ + { + friendlyName: 'dcmjs DICOMWeb Server', + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + name: 'aws', + // old server + // wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', + // qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + // wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + // new server + wadoUriRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb', + qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb', + wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb', + qidoSupportsIncludeField: false, + supportsReject: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: true, + staticWado: true, + singlepart: 'bulkdata,video,pdf', + }, + }, + { + friendlyName: 'dicom json', + namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', + sourceName: 'dicomjson', + configuration: { + name: 'json', + }, + }, + { + friendlyName: 'dicom local', + namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', + sourceName: 'dicomlocal', + configuration: {}, + }, + ], + httpErrorHandler: error => { + // This is 429 when rejected from the public idc sandbox too often. + console.warn(error.status); + + // Could use services manager here to bring up a dialog/modal if needed. + console.warn('test, navigate to https://ohif.org/'); + }, + // whiteLabeling: { + // /* Optional: Should return a React component to be rendered in the "Logo" section of the application's Top Navigation bar */ + // createLogoComponentFn: function (React) { + // return React.createElement( + // 'a', + // { + // target: '_self', + // rel: 'noopener noreferrer', + // className: 'text-purple-600 line-through', + // href: '/', + // }, + // React.createElement('img', + // { + // src: './customLogo.svg', + // className: 'w-8 h-8', + // } + // )) + // }, + // }, + defaultDataSourceName: 'dicomweb', + hotkeys: [ + { + commandName: 'incrementActiveViewport', + label: 'Next Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Viewport', + keys: ['left'], + }, + { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, + { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, + { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, + { + commandName: 'flipViewportHorizontal', + label: 'Flip Horizontally', + keys: ['h'], + }, + { + commandName: 'flipViewportVertical', + label: 'Flip Vertically', + keys: ['v'], + }, + { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] }, + { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] }, + { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] }, + { commandName: 'resetViewport', label: 'Reset', keys: ['space'] }, + { commandName: 'nextImage', label: 'Next Image', keys: ['down'] }, + { commandName: 'previousImage', label: 'Previous Image', keys: ['up'] }, + // { + // commandName: 'previousViewportDisplaySet', + // label: 'Previous Series', + // keys: ['pagedown'], + // }, + // { + // commandName: 'nextViewportDisplaySet', + // label: 'Next Series', + // keys: ['pageup'], + // }, + { + commandName: 'setToolActive', + commandOptions: { toolName: 'Zoom' }, + label: 'Zoom', + keys: ['z'], + }, + // ~ Window level presets + { + commandName: 'windowLevelPreset1', + label: 'W/L Preset 1', + keys: ['1'], + }, + { + commandName: 'windowLevelPreset2', + label: 'W/L Preset 2', + keys: ['2'], + }, + { + commandName: 'windowLevelPreset3', + label: 'W/L Preset 3', + keys: ['3'], + }, + { + commandName: 'windowLevelPreset4', + label: 'W/L Preset 4', + keys: ['4'], + }, + { + commandName: 'windowLevelPreset5', + label: 'W/L Preset 5', + keys: ['5'], + }, + { + commandName: 'windowLevelPreset6', + label: 'W/L Preset 6', + keys: ['6'], + }, + { + commandName: 'windowLevelPreset7', + label: 'W/L Preset 7', + keys: ['7'], + }, + { + commandName: 'windowLevelPreset8', + label: 'W/L Preset 8', + keys: ['8'], + }, + { + commandName: 'windowLevelPreset9', + label: 'W/L Preset 9', + keys: ['9'], + }, + ], +}; diff --git a/platform/app/public/config/demo.js b/platform/app/public/config/demo.js index b4d43b8619..5dbd96ab15 100644 --- a/platform/app/public/config/demo.js +++ b/platform/app/public/config/demo.js @@ -4,17 +4,16 @@ window.config = { extensions: [], showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, strictZSpacingForVolumeViewport: true, showCPUFallbackMessage: true, defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'DCM4CHEE Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'DCM4CHEE Server', name: 'DCM4CHEE', wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', @@ -25,6 +24,7 @@ window.config = { bulkDataURI: { enabled: false, }, + omitQuotationForMultipartRequest: true, }, }, ], diff --git a/platform/app/public/config/dicomweb-server.js b/platform/app/public/config/dicomweb-server.js index 4db9904176..a3522e587c 100644 --- a/platform/app/public/config/dicomweb-server.js +++ b/platform/app/public/config/dicomweb-server.js @@ -4,7 +4,6 @@ window.config = { modes: [], showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -13,10 +12,10 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'DCM4CHEE', wadoUriRoot: 'http://localhost:5985', qidoRoot: 'http://localhost:5985', @@ -28,21 +27,23 @@ window.config = { enableStudyLazyLoad: true, supportsFuzzyMatching: false, supportsWildcard: false, + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], }; diff --git a/platform/app/public/config/dicomweb_relative.js b/platform/app/public/config/dicomweb_relative.js index af024abd70..bfa51c34ae 100644 --- a/platform/app/public/config/dicomweb_relative.js +++ b/platform/app/public/config/dicomweb_relative.js @@ -5,7 +5,6 @@ window.config = { showStudyList: true, maxNumberOfWebWorkers: 3, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -14,16 +13,15 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'Static WADO Local Data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'Static WADO Local Data', name: 'DCM4CHEE', wadoUriRoot: '/dicomweb', qidoRoot: '/dicomweb', wadoRoot: '/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, @@ -31,21 +29,23 @@ window.config = { supportsWildcard: true, staticWado: true, singlepart: 'bulkdata,video,pdf', + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/docker_nginx-orthanc.js b/platform/app/public/config/docker_nginx-orthanc.js index 60d7f083ce..97316bc8ad 100644 --- a/platform/app/public/config/docker_nginx-orthanc.js +++ b/platform/app/public/config/docker_nginx-orthanc.js @@ -4,7 +4,6 @@ window.config = { extensions: [], modes: [], // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -12,10 +11,10 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'Orthanc Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'Orthanc Server', name: 'Orthanc', wadoUriRoot: '/wado', qidoRoot: '/dicom-web', @@ -23,21 +22,23 @@ window.config = { qidoSupportsIncludeField: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], }; diff --git a/platform/app/public/config/docker_openresty-orthanc-keycloak.js b/platform/app/public/config/docker_openresty-orthanc-keycloak.js index 07709a7ebe..5e9f7854f3 100644 --- a/platform/app/public/config/docker_openresty-orthanc-keycloak.js +++ b/platform/app/public/config/docker_openresty-orthanc-keycloak.js @@ -1,32 +1,39 @@ window.config = { routerBasename: '/', showStudyList: true, + extensions: [], + modes: [], // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, + showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, strictZSpacingForVolumeViewport: true, - servers: { - // This is an array, but we'll only use the first entry for now - dicomWeb: [ - { + defaultDataSourceName: 'dicomweb', + dataSources: [ + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + friendlyName: 'Orthanc Server', name: 'Orthanc', - wadoUriRoot: 'http://127.0.0.1/pacs/wado', + wadoUriRoot: 'http://127.0.0.1/pacs/dicom-web', qidoRoot: 'http://127.0.0.1/pacs/dicom-web', wadoRoot: 'http://127.0.0.1/pacs/dicom-web', - qidoSupportsIncludeField: false, + qidoSupportsIncludeField: true, + supportsReject: true, imageRendering: 'wadors', thumbnailRendering: 'wadors', - // REQUIRED TAG: - // TODO: Remove tag after https://github.com/OHIF/ohif-core/pull/19 is merged and we bump version - // requestOptions: { - // undefined to use JWT + Bearer auth - // auth: 'orthanc:orthanc', - // }, + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: true, + dicomUploadEnabled: true, + bulkDataURI: { + enabled: true, + }, }, - ], - }, + }, + ], // This is an array, but we'll only use the first entry for now oidc: [ { diff --git a/platform/app/public/config/docker_openresty-orthanc.js b/platform/app/public/config/docker_openresty-orthanc.js index 33e4138779..2500760bf2 100644 --- a/platform/app/public/config/docker_openresty-orthanc.js +++ b/platform/app/public/config/docker_openresty-orthanc.js @@ -4,7 +4,7 @@ window.config = { extensions: [], modes: [], // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, + showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -12,32 +12,41 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'Orthanc Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'Orthanc Server', name: 'Orthanc', - wadoUriRoot: 'http://127.0.0.1/pacs/wado', + wadoUriRoot: 'http://127.0.0.1/pacs/dicom-web', qidoRoot: 'http://127.0.0.1/pacs/dicom-web', wadoRoot: 'http://127.0.0.1/pacs/dicom-web', - qidoSupportsIncludeField: false, + qidoSupportsIncludeField: true, + supportsReject: true, imageRendering: 'wadors', thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: true, + dicomUploadEnabled: true, + bulkDataURI: { + enabled: true, + }, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], }; diff --git a/platform/app/public/config/e2e.js b/platform/app/public/config/e2e.js index 47903374cb..8b5f174a1e 100644 --- a/platform/app/public/config/e2e.js +++ b/platform/app/public/config/e2e.js @@ -4,7 +4,6 @@ window.config = { modes: ['@ohif/mode-test'], showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, maxNumberOfWebWorkers: 3, showWarningMessageForCrossOrigin: false, showCPUFallbackMessage: false, @@ -13,10 +12,10 @@ window.config = { defaultDataSourceName: 'e2e', dataSources: [ { - friendlyName: 'StaticWado test data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'e2e', configuration: { + friendlyName: 'StaticWado test data', // The most important field to set for static WADO staticWado: true, name: 'StaticWADO', @@ -24,20 +23,20 @@ window.config = { qidoRoot: '/viewer-testdata', wadoRoot: '/viewer-testdata', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, supportsFuzzyMatching: false, supportsWildcard: true, singlepart: 'video,thumbnail,pdf', + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'Static WADO Local Data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'local', configuration: { + friendlyName: 'Static WADO Local Data', name: 'DCM4CHEE', qidoRoot: '/dicomweb', wadoRoot: '/dicomweb', @@ -54,21 +53,15 @@ window.config = { }, }, { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'ohif', configuration: { + friendlyName: 'AWS S3 Static wado server', name: 'aws', - // old server - // wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - // qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - // wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - // new server wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, @@ -98,18 +91,19 @@ window.config = { // }, // }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/google.js b/platform/app/public/config/google.js index 847e08ebbd..b380a21a72 100644 --- a/platform/app/public/config/google.js +++ b/platform/app/public/config/google.js @@ -6,7 +6,6 @@ window.config = { }, enableGoogleCloudAdapter: false, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -17,8 +16,7 @@ window.config = { // ~ REQUIRED // Authorization Server URL authority: 'https://accounts.google.com', - client_id: - '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', + client_id: '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', redirect_uri: '/callback', response_type: 'id_token token', scope: @@ -37,10 +35,10 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'GCP', wadoUriRoot: 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', @@ -55,21 +53,24 @@ window.config = { supportsFuzzyMatching: true, supportsWildcard: false, dicomUploadEnabled: true, + omitQuotationForMultipartRequest: true, + configurationAPI: 'ohif.dataSourceConfigurationAPI.google', }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], }; diff --git a/platform/app/public/config/idc.js b/platform/app/public/config/idc.js index 6056962bd7..0089182ee9 100644 --- a/platform/app/public/config/idc.js +++ b/platform/app/public/config/idc.js @@ -2,7 +2,6 @@ window.config = { routerBasename: '/', enableGoogleCloudAdapter: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -17,8 +16,7 @@ window.config = { // ~ REQUIRED // Authorization Server URL authority: 'https://accounts.google.com', - client_id: - '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', + client_id: '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', redirect_uri: '/callback', // `OHIFStandaloneViewer.js` response_type: 'id_token token', scope: diff --git a/platform/app/public/config/local_dcm4chee.js b/platform/app/public/config/local_dcm4chee.js index 763604ffdf..a521bfef06 100644 --- a/platform/app/public/config/local_dcm4chee.js +++ b/platform/app/public/config/local_dcm4chee.js @@ -8,7 +8,6 @@ window.config = { extensions: [], modes: [], // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -16,10 +15,10 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'DCM4CHEE Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'DCM4CHEE Server', name: 'DCM4CHEE', wadoUriRoot: 'http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/wado', qidoRoot: 'http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs', @@ -39,21 +38,23 @@ window.config = { bulkDataURI: { enabled: true, }, + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], studyListFunctionsEnabled: true, diff --git a/platform/app/public/config/local_orthanc.js b/platform/app/public/config/local_orthanc.js index 3153408b08..eabb22f21a 100644 --- a/platform/app/public/config/local_orthanc.js +++ b/platform/app/public/config/local_orthanc.js @@ -16,10 +16,10 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'local Orthanc DICOMWeb Server', name: 'DCM4CHEE', wadoUriRoot: 'http://localhost/dicom-web', qidoRoot: 'http://localhost/dicom-web', @@ -38,18 +38,19 @@ window.config = { }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/local_static.js b/platform/app/public/config/local_static.js index 6774e1b9a0..89f205d011 100644 --- a/platform/app/public/config/local_static.js +++ b/platform/app/public/config/local_static.js @@ -1,14 +1,11 @@ window.config = { routerBasename: '/', - customizationService: [ - '@ohif/extension-default.customizationModule.helloPage', - ], + customizationService: ['@ohif/extension-default.customizationModule.helloPage'], extensions: [], modes: [], showStudyList: true, maxNumberOfWebWorkers: 4, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -17,15 +14,14 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'Static WADO Local Data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'Static WADO Local Data', name: 'DCM4CHEE', qidoRoot: '/dicomweb', wadoRoot: '/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, @@ -33,21 +29,23 @@ window.config = { supportsWildcard: true, staticWado: true, singlepart: 'bulkdata,video,pdf', + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/multiple.js b/platform/app/public/config/multiple.js index 07a2842644..57684a9ef5 100644 --- a/platform/app/public/config/multiple.js +++ b/platform/app/public/config/multiple.js @@ -16,7 +16,6 @@ window.config = { showStudyList: true, maxNumberOfWebWorkers: 4, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -25,10 +24,10 @@ window.config = { defaultDataSourceName: 'default', dataSources: [ { - friendlyName: 'Static WADO Local Data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'default', configuration: { + friendlyName: 'Static WADO Local Data', name: 'DCM4CHEE', qidoRoot: '/dicomweb', wadoRoot: '/dicomweb', @@ -42,13 +41,14 @@ window.config = { supportsWildcard: true, staticWado: true, singlepart: 'bulkdata,video,pdf', + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'ohif', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'aws', // old server // wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', @@ -59,7 +59,6 @@ window.config = { qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, @@ -70,15 +69,14 @@ window.config = { }, }, { - friendlyName: 'AWS S3 OHIF', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'aws', configuration: { + friendlyName: 'AWS S3 OHIF', name: 'aws', qidoRoot: 'https://dd32w2rfebxel.cloudfront.net/dicomweb', wadoRoot: 'https://dd32w2rfebxel.cloudfront.net/dicomweb', qidoSupportsIncludeField: false, - supportsReject: false, supportsStow: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', @@ -90,16 +88,15 @@ window.config = { }, }, { - friendlyName: 'E2E Test Data', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'e2e', configuration: { + friendlyName: 'E2E Test Data', name: 'DCM4CHEE', wadoUriRoot: '/viewer-testdata', qidoRoot: '/viewer-testdata', wadoRoot: '/viewer-testdata', qidoSupportsIncludeField: false, - supportsReject: false, supportsStow: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', @@ -111,18 +108,19 @@ window.config = { }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/netlify.js b/platform/app/public/config/netlify.js index 1bc23c049d..a729d1c8d5 100644 --- a/platform/app/public/config/netlify.js +++ b/platform/app/public/config/netlify.js @@ -4,7 +4,6 @@ window.config = { modes: [], showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -13,15 +12,42 @@ window.config = { defaultDataSourceName: 'dicomweb', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'AWS S3 Static wado server', name: 'aws', wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb', + qidoSupportsIncludeField: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: true, + staticWado: true, + singlepart: 'bulkdata,video', + // whether the data source should use retrieveBulkData to grab metadata, + // and in case of relative path, what would it be relative to, options + // are in the series level or study level (some servers like series some study) + bulkDataURI: { + enabled: true, + relativeResolution: 'studies', + }, + omitQuotationForMultipartRequest: true, + }, + }, + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb2', + configuration: { + friendlyName: 'AWS S3 Static wado secondary server', + name: 'aws', + wadoUriRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', + qidoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', + wadoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb', qidoSupportsIncludeField: false, supportsReject: false, imageRendering: 'wadors', @@ -38,21 +64,23 @@ window.config = { enabled: true, relativeResolution: 'studies', }, + omitQuotationForMultipartRequest: true, }, }, { - friendlyName: 'dicom json', namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', sourceName: 'dicomjson', configuration: { + friendlyName: 'dicom json', name: 'json', }, }, { - friendlyName: 'dicom local', namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', sourceName: 'dicomlocal', - configuration: {}, + configuration: { + friendlyName: 'dicom local', + }, }, ], httpErrorHandler: error => { diff --git a/platform/app/public/config/public_dicomweb.js b/platform/app/public/config/public_dicomweb.js index 322d896846..2dc7503744 100644 --- a/platform/app/public/config/public_dicomweb.js +++ b/platform/app/public/config/public_dicomweb.js @@ -2,7 +2,6 @@ window.config = { routerBasename: '/', showStudyList: true, // below flag is for performance reasons, but it might not work for all servers - omitQuotationForMultipartRequest: true, showWarningMessageForCrossOrigin: true, showCPUFallbackMessage: true, showLoadingIndicator: true, @@ -18,6 +17,7 @@ window.config = { imageRendering: 'wadors', thumbnailRendering: 'wadors', supportsFuzzyMatching: true, + omitQuotationForMultipartRequest: true, }, ], }, diff --git a/platform/app/public/es6-shim.min.js b/platform/app/public/es6-shim.min.js index f8ef71c73a..c1447d6fc7 100644 --- a/platform/app/public/es6-shim.min.js +++ b/platform/app/public/es6-shim.min.js @@ -7,6 +7,3573 @@ * Details and documentation: * https://github.com/paulmillr/es6-shim/ */ -(function(e,t){if(typeof define==="function"&&define.amd){define(t)}else if(typeof exports==="object"){module.exports=t()}else{e.returnExports=t()}})(this,function(){"use strict";var e=Function.call.bind(Function.apply);var t=Function.call.bind(Function.call);var r=Array.isArray;var n=Object.keys;var o=function notThunker(t){return function notThunk(){return!e(t,this,arguments)}};var i=function(e){try{e();return false}catch(t){return true}};var a=function valueOrFalseIfThrows(e){try{return e()}catch(t){return false}};var u=o(i);var f=function(){return!i(function(){return Object.defineProperty({},"x",{get:function(){}})})};var s=!!Object.defineProperty&&f();var c=function foo(){}.name==="foo";var l=Function.call.bind(Array.prototype.forEach);var p=Function.call.bind(Array.prototype.reduce);var v=Function.call.bind(Array.prototype.filter);var y=Function.call.bind(Array.prototype.some);var h=function(e,t,r,n){if(!n&&t in e){return}if(s){Object.defineProperty(e,t,{configurable:true,enumerable:false,writable:true,value:r})}else{e[t]=r}};var b=function(e,t,r){l(n(t),function(n){var o=t[n];h(e,n,o,!!r)})};var g=Function.call.bind(Object.prototype.toString);var d=typeof/abc/==="function"?function IsCallableSlow(e){return typeof e==="function"&&g(e)==="[object Function]"}:function IsCallableFast(e){return typeof e==="function"};var m={getter:function(e,t,r){if(!s){throw new TypeError("getters require true ES5 support")}Object.defineProperty(e,t,{configurable:true,enumerable:false,get:r})},proxy:function(e,t,r){if(!s){throw new TypeError("getters require true ES5 support")}var n=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(r,t,{configurable:n.configurable,enumerable:n.enumerable,get:function getKey(){return e[t]},set:function setKey(r){e[t]=r}})},redefine:function(e,t,r){if(s){var n=Object.getOwnPropertyDescriptor(e,t);n.value=r;Object.defineProperty(e,t,n)}else{e[t]=r}},defineByDescriptor:function(e,t,r){if(s){Object.defineProperty(e,t,r)}else if("value"in r){e[t]=r.value}},preserveToString:function(e,t){if(t&&d(t.toString)){h(e,"toString",t.toString.bind(t),true)}}};var O=Object.create||function(e,t){var r=function Prototype(){};r.prototype=e;var o=new r;if(typeof t!=="undefined"){n(t).forEach(function(e){m.defineByDescriptor(o,e,t[e])})}return o};var w=function(e,t){if(!Object.setPrototypeOf){return false}return a(function(){var r=function Subclass(t){var r=new e(t);Object.setPrototypeOf(r,Subclass.prototype);return r};Object.setPrototypeOf(r,e);r.prototype=O(e.prototype,{constructor:{value:r}});return t(r)})};var j=function(){if(typeof self!=="undefined"){return self}if(typeof window!=="undefined"){return window}if(typeof global!=="undefined"){return global}throw new Error("unable to locate global object")};var S=j();var T=S.isFinite;var I=Function.call.bind(String.prototype.indexOf);var E=Function.apply.bind(Array.prototype.indexOf);var P=Function.call.bind(Array.prototype.concat);var C=Function.call.bind(String.prototype.slice);var M=Function.call.bind(Array.prototype.push);var x=Function.apply.bind(Array.prototype.push);var N=Function.call.bind(Array.prototype.shift);var A=Math.max;var R=Math.min;var _=Math.floor;var k=Math.abs;var L=Math.exp;var F=Math.log;var D=Math.sqrt;var z=Function.call.bind(Object.prototype.hasOwnProperty);var q;var W=function(){};var G=S.Map;var H=G&&G.prototype["delete"];var V=G&&G.prototype.get;var B=G&&G.prototype.has;var U=G&&G.prototype.set;var $=S.Symbol||{};var J=$.species||"@@species";var X=Number.isNaN||function isNaN(e){return e!==e};var K=Number.isFinite||function isFinite(e){return typeof e==="number"&&T(e)};var Z=d(Math.sign)?Math.sign:function sign(e){var t=Number(e);if(t===0){return t}if(X(t)){return t}return t<0?-1:1};var Y=function log1p(e){var t=Number(e);if(t<-1||X(t)){return NaN}if(t===0||t===Infinity){return t}if(t===-1){return-Infinity}return 1+t-1===0?t:t*(F(1+t)/(1+t-1))};var Q=function isArguments(e){return g(e)==="[object Arguments]"};var ee=function isArguments(e){return e!==null&&typeof e==="object"&&typeof e.length==="number"&&e.length>=0&&g(e)!=="[object Array]"&&g(e.callee)==="[object Function]"};var te=Q(arguments)?Q:ee;var re={primitive:function(e){return e===null||typeof e!=="function"&&typeof e!=="object"},string:function(e){return g(e)==="[object String]"},regex:function(e){return g(e)==="[object RegExp]"},symbol:function(e){return typeof S.Symbol==="function"&&typeof e==="symbol"}};var ne=function overrideNative(e,t,r){var n=e[t];h(e,t,r,true);m.preserveToString(e[t],n)};var oe=typeof $==="function"&&typeof $["for"]==="function"&&re.symbol($());var ie=re.symbol($.iterator)?$.iterator:"_es6-shim iterator_";if(S.Set&&typeof(new S.Set)["@@iterator"]==="function"){ie="@@iterator"}if(!S.Reflect){h(S,"Reflect",{},true)}var ae=S.Reflect;var ue=String;var fe=typeof document==="undefined"||!document?null:document.all;var se=fe==null?function isNullOrUndefined(e){return e==null}:function isNullOrUndefinedAndNotDocumentAll(e){return e==null&&e!==fe};var ce={Call:function Call(t,r){var n=arguments.length>2?arguments[2]:[];if(!ce.IsCallable(t)){throw new TypeError(t+" is not a function")}return e(t,r,n)},RequireObjectCoercible:function(e,t){if(se(e)){throw new TypeError(t||"Cannot call method on "+e)}return e},TypeIsObject:function(e){if(e===void 0||e===null||e===true||e===false){return false}return typeof e==="function"||typeof e==="object"||e===fe},ToObject:function(e,t){return Object(ce.RequireObjectCoercible(e,t))},IsCallable:d,IsConstructor:function(e){return ce.IsCallable(e)},ToInt32:function(e){return ce.ToNumber(e)>>0},ToUint32:function(e){return ce.ToNumber(e)>>>0},ToNumber:function(e){if(g(e)==="[object Symbol]"){throw new TypeError("Cannot convert a Symbol value to a number")}return+e},ToInteger:function(e){var t=ce.ToNumber(e);if(X(t)){return 0}if(t===0||!K(t)){return t}return(t>0?1:-1)*_(k(t))},ToLength:function(e){var t=ce.ToInteger(e);if(t<=0){return 0}if(t>Number.MAX_SAFE_INTEGER){return Number.MAX_SAFE_INTEGER}return t},SameValue:function(e,t){if(e===t){if(e===0){return 1/e===1/t}return true}return X(e)&&X(t)},SameValueZero:function(e,t){return e===t||X(e)&&X(t)},IsIterable:function(e){return ce.TypeIsObject(e)&&(typeof e[ie]!=="undefined"||te(e))},GetIterator:function(e){if(te(e)){return new q(e,"value")}var t=ce.GetMethod(e,ie);if(!ce.IsCallable(t)){throw new TypeError("value is not an iterable")}var r=ce.Call(t,e);if(!ce.TypeIsObject(r)){throw new TypeError("bad iterator")}return r},GetMethod:function(e,t){var r=ce.ToObject(e)[t];if(se(r)){return void 0}if(!ce.IsCallable(r)){throw new TypeError("Method not callable: "+t)}return r},IteratorComplete:function(e){return!!e.done},IteratorClose:function(e,t){var r=ce.GetMethod(e,"return");if(r===void 0){return}var n,o;try{n=ce.Call(r,e)}catch(i){o=i}if(t){return}if(o){throw o}if(!ce.TypeIsObject(n)){throw new TypeError("Iterator's return method returned a non-object.")}},IteratorNext:function(e){var t=arguments.length>1?e.next(arguments[1]):e.next();if(!ce.TypeIsObject(t)){throw new TypeError("bad iterator")}return t},IteratorStep:function(e){var t=ce.IteratorNext(e);var r=ce.IteratorComplete(t);return r?false:t},Construct:function(e,t,r,n){var o=typeof r==="undefined"?e:r;if(!n&&ae.construct){return ae.construct(e,t,o)}var i=o.prototype;if(!ce.TypeIsObject(i)){i=Object.prototype}var a=O(i);var u=ce.Call(e,a,t);return ce.TypeIsObject(u)?u:a},SpeciesConstructor:function(e,t){var r=e.constructor;if(r===void 0){return t}if(!ce.TypeIsObject(r)){throw new TypeError("Bad constructor")}var n=r[J];if(se(n)){return t}if(!ce.IsConstructor(n)){throw new TypeError("Bad @@species")}return n},CreateHTML:function(e,t,r,n){var o=ce.ToString(e);var i="<"+t;if(r!==""){var a=ce.ToString(n);var u=a.replace(/"/g,""");i+=" "+r+'="'+u+'"'}var f=i+">";var s=f+o;return s+""},IsRegExp:function IsRegExp(e){if(!ce.TypeIsObject(e)){return false}var t=e[$.match];if(typeof t!=="undefined"){return!!t}return re.regex(e)},ToString:function ToString(e){return ue(e)}};if(s&&oe){var le=function defineWellKnownSymbol(e){if(re.symbol($[e])){return $[e]}var t=$["for"]("Symbol."+e);Object.defineProperty($,e,{configurable:false,enumerable:false,writable:false,value:t});return t};if(!re.symbol($.search)){var pe=le("search");var ve=String.prototype.search;h(RegExp.prototype,pe,function search(e){return ce.Call(ve,e,[this])});var ye=function search(e){var t=ce.RequireObjectCoercible(this);if(!se(e)){var r=ce.GetMethod(e,pe);if(typeof r!=="undefined"){return ce.Call(r,e,[t])}}return ce.Call(ve,t,[ce.ToString(e)])};ne(String.prototype,"search",ye)}if(!re.symbol($.replace)){var he=le("replace");var be=String.prototype.replace;h(RegExp.prototype,he,function replace(e,t){return ce.Call(be,e,[this,t])});var ge=function replace(e,t){var r=ce.RequireObjectCoercible(this);if(!se(e)){var n=ce.GetMethod(e,he);if(typeof n!=="undefined"){return ce.Call(n,e,[r,t])}}return ce.Call(be,r,[ce.ToString(e),t])};ne(String.prototype,"replace",ge)}if(!re.symbol($.split)){var de=le("split");var me=String.prototype.split;h(RegExp.prototype,de,function split(e,t){return ce.Call(me,e,[this,t])});var Oe=function split(e,t){var r=ce.RequireObjectCoercible(this);if(!se(e)){var n=ce.GetMethod(e,de);if(typeof n!=="undefined"){return ce.Call(n,e,[r,t])}}return ce.Call(me,r,[ce.ToString(e),t])};ne(String.prototype,"split",Oe)}var we=re.symbol($.match);var je=we&&function(){var e={};e[$.match]=function(){return 42};return"a".match(e)!==42}();if(!we||je){var Se=le("match");var Te=String.prototype.match;h(RegExp.prototype,Se,function match(e){return ce.Call(Te,e,[this])});var Ie=function match(e){var t=ce.RequireObjectCoercible(this);if(!se(e)){var r=ce.GetMethod(e,Se);if(typeof r!=="undefined"){return ce.Call(r,e,[t])}}return ce.Call(Te,t,[ce.ToString(e)])};ne(String.prototype,"match",Ie)}}var Ee=function wrapConstructor(e,t,r){m.preserveToString(t,e);if(Object.setPrototypeOf){Object.setPrototypeOf(e,t)}if(s){l(Object.getOwnPropertyNames(e),function(n){if(n in W||r[n]){return}m.proxy(e,n,t)})}else{l(Object.keys(e),function(n){if(n in W||r[n]){return}t[n]=e[n]})}t.prototype=e.prototype;m.redefine(e.prototype,"constructor",t)};var Pe=function(){return this};var Ce=function(e){if(s&&!z(e,J)){m.getter(e,J,Pe)}};var Me=function(e,t){var r=t||function iterator(){return this};h(e,ie,r);if(!e[ie]&&re.symbol(ie)){e[ie]=r}};var xe=function createDataProperty(e,t,r){if(s){Object.defineProperty(e,t,{configurable:true,enumerable:true,writable:true,value:r})}else{e[t]=r}};var Ne=function createDataPropertyOrThrow(e,t,r){xe(e,t,r);if(!ce.SameValue(e[t],r)){throw new TypeError("property is nonconfigurable")}};var Ae=function(e,t,r,n){if(!ce.TypeIsObject(e)){throw new TypeError("Constructor requires `new`: "+t.name)}var o=t.prototype;if(!ce.TypeIsObject(o)){o=r}var i=O(o);for(var a in n){if(z(n,a)){var u=n[a];h(i,a,u,true)}}return i};if(String.fromCodePoint&&String.fromCodePoint.length!==1){var Re=String.fromCodePoint;ne(String,"fromCodePoint",function fromCodePoint(e){return ce.Call(Re,this,arguments)})}var _e={fromCodePoint:function fromCodePoint(e){var t=[];var r;for(var n=0,o=arguments.length;n1114111){throw new RangeError("Invalid code point "+r)}if(r<65536){M(t,String.fromCharCode(r))}else{r-=65536;M(t,String.fromCharCode((r>>10)+55296));M(t,String.fromCharCode(r%1024+56320))}}return t.join("")},raw:function raw(e){var t=ce.ToObject(e,"bad callSite");var r=ce.ToObject(t.raw,"bad raw value");var n=r.length;var o=ce.ToLength(n);if(o<=0){return""}var i=[];var a=0;var u,f,s,c;while(a=o){break}f=a+1=Le){throw new RangeError("repeat count must be less than infinity and not overflow maximum string size")}return ke(t,r)},startsWith:function startsWith(e){var t=ce.ToString(ce.RequireObjectCoercible(this));if(ce.IsRegExp(e)){throw new TypeError('Cannot call method "startsWith" with a regex')}var r=ce.ToString(e);var n;if(arguments.length>1){n=arguments[1]}var o=A(ce.ToInteger(n),0);return C(t,o,o+r.length)===r},endsWith:function endsWith(e){var t=ce.ToString(ce.RequireObjectCoercible(this));if(ce.IsRegExp(e)){throw new TypeError('Cannot call method "endsWith" with a regex')}var r=ce.ToString(e);var n=t.length;var o;if(arguments.length>1){o=arguments[1]}var i=typeof o==="undefined"?n:ce.ToInteger(o);var a=R(A(i,0),n);return C(t,a-r.length,a)===r},includes:function includes(e){if(ce.IsRegExp(e)){throw new TypeError('"includes" does not accept a RegExp')}var t=ce.ToString(e);var r;if(arguments.length>1){r=arguments[1]}return I(this,t,r)!==-1},codePointAt:function codePointAt(e){var t=ce.ToString(ce.RequireObjectCoercible(this));var r=ce.ToInteger(e);var n=t.length;if(r>=0&&r56319||i){return o}var a=t.charCodeAt(r+1);if(a<56320||a>57343){return o}return(o-55296)*1024+(a-56320)+65536}}};if(String.prototype.includes&&"a".includes("a",Infinity)!==false){ne(String.prototype,"includes",Fe.includes)}if(String.prototype.startsWith&&String.prototype.endsWith){var De=i(function(){return"/a/".startsWith(/a/)});var ze=a(function(){return"abc".startsWith("a",Infinity)===false});if(!De||!ze){ne(String.prototype,"startsWith",Fe.startsWith);ne(String.prototype,"endsWith",Fe.endsWith)}}if(oe){var qe=a(function(){var e=/a/;e[$.match]=false;return"/a/".startsWith(e)});if(!qe){ne(String.prototype,"startsWith",Fe.startsWith)}var We=a(function(){var e=/a/;e[$.match]=false;return"/a/".endsWith(e)});if(!We){ne(String.prototype,"endsWith",Fe.endsWith)}var Ge=a(function(){var e=/a/;e[$.match]=false;return"/a/".includes(e)});if(!Ge){ne(String.prototype,"includes",Fe.includes)}}b(String.prototype,Fe);var He=["\t\n\x0B\f\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003","\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028","\u2029\ufeff"].join("");var Ve=new RegExp("(^["+He+"]+)|(["+He+"]+$)","g");var Be=function trim(){return ce.ToString(ce.RequireObjectCoercible(this)).replace(Ve,"")};var Ue=["\x85","\u200b","\ufffe"].join("");var $e=new RegExp("["+Ue+"]","g");var Je=/^[-+]0x[0-9a-f]+$/i;var Xe=Ue.trim().length!==Ue.length;h(String.prototype,"trim",Be,Xe);var Ke=function(e){return{value:e,done:arguments.length===0}};var Ze=function(e){ce.RequireObjectCoercible(e);this._s=ce.ToString(e);this._i=0};Ze.prototype.next=function(){var e=this._s;var t=this._i;if(typeof e==="undefined"||t>=e.length){this._s=void 0;return Ke()}var r=e.charCodeAt(t);var n,o;if(r<55296||r>56319||t+1===e.length){o=1}else{n=e.charCodeAt(t+1);o=n<56320||n>57343?1:2}this._i=t+o;return Ke(e.substr(t,o))};Me(Ze.prototype);Me(String.prototype,function(){return new Ze(this)});var Ye={from:function from(e){var r=this;var n;if(arguments.length>1){n=arguments[1]}var o,i;if(typeof n==="undefined"){o=false}else{if(!ce.IsCallable(n)){throw new TypeError("Array.from: when provided, the second argument must be a function")}if(arguments.length>2){i=arguments[2]}o=true}var a=typeof(te(e)||ce.GetMethod(e,ie))!=="undefined";var u,f,s;if(a){f=ce.IsConstructor(r)?Object(new r):[];var c=ce.GetIterator(e);var l,p;s=0;while(true){l=ce.IteratorStep(c);if(l===false){break}p=l.value;try{if(o){p=typeof i==="undefined"?n(p,s):t(n,i,p,s)}f[s]=p}catch(v){ce.IteratorClose(c,true);throw v}s+=1}u=s}else{var y=ce.ToObject(e);u=ce.ToLength(y.length);f=ce.IsConstructor(r)?Object(new r(u)):new Array(u);var h;for(s=0;s2){f=arguments[2]}var s=typeof f==="undefined"?n:ce.ToInteger(f);var c=s<0?A(n+s,0):R(s,n);var l=R(c-u,n-a);var p=1;if(u0){if(u in r){r[a]=r[u]}else{delete r[a]}u+=p;a+=p;l-=1}return r},fill:function fill(e){var t;if(arguments.length>1){t=arguments[1]}var r;if(arguments.length>2){r=arguments[2]}var n=ce.ToObject(this);var o=ce.ToLength(n.length);t=ce.ToInteger(typeof t==="undefined"?0:t);r=ce.ToInteger(typeof r==="undefined"?o:r);var i=t<0?A(o+t,0):R(t,o);var a=r<0?o+r:r;for(var u=i;u1?arguments[1]:null;for(var i=0,a;i1?arguments[1]:null;for(var i=0;i1&&typeof arguments[1]!=="undefined"){return ce.Call(it,this,arguments)}else{return t(it,this,e)}})}var at=-(Math.pow(2,32)-1);var ut=function(e,r){var n={length:at};n[r?(n.length>>>0)-1:0]=true;return a(function(){t(e,n,function(){throw new RangeError("should not reach here")},[]);return true})};if(!ut(Array.prototype.forEach)){var ft=Array.prototype.forEach;ne(Array.prototype,"forEach",function forEach(e){return ce.Call(ft,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.map)){var st=Array.prototype.map;ne(Array.prototype,"map",function map(e){return ce.Call(st,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.filter)){var ct=Array.prototype.filter;ne(Array.prototype,"filter",function filter(e){return ce.Call(ct,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.some)){var lt=Array.prototype.some;ne(Array.prototype,"some",function some(e){return ce.Call(lt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.every)){var pt=Array.prototype.every;ne(Array.prototype,"every",function every(e){return ce.Call(pt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.reduce)){var vt=Array.prototype.reduce;ne(Array.prototype,"reduce",function reduce(e){return ce.Call(vt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.reduceRight,true)){var yt=Array.prototype.reduceRight;ne(Array.prototype,"reduceRight",function reduceRight(e){return ce.Call(yt,this.length>=0?this:[],arguments)},true)}var ht=Number("0o10")!==8;var bt=Number("0b10")!==2;var gt=y(Ue,function(e){return Number(e+0+e)===0});if(ht||bt||gt){var dt=Number;var mt=/^0b[01]+$/i;var Ot=/^0o[0-7]+$/i;var wt=mt.test.bind(mt);var jt=Ot.test.bind(Ot);var St=function(e){var t;if(typeof e.valueOf==="function"){t=e.valueOf();if(re.primitive(t)){return t}}if(typeof e.toString==="function"){t=e.toString();if(re.primitive(t)){return t}}throw new TypeError("No default value")};var Tt=$e.test.bind($e);var It=Je.test.bind(Je);var Et=function(){var e=function Number(t){var r;if(arguments.length>0){r=re.primitive(t)?t:St(t,"number")}else{r=0}if(typeof r==="string"){r=ce.Call(Be,r);if(wt(r)){r=parseInt(C(r,2),2)}else if(jt(r)){r=parseInt(C(r,2),8)}else if(Tt(r)||It(r)){r=NaN}}var n=this;var o=a(function(){dt.prototype.valueOf.call(n);return true});if(n instanceof e&&!o){return new dt(r)}return dt(r)};return e}();Ee(dt,Et,{});b(Et,{NaN:dt.NaN,MAX_VALUE:dt.MAX_VALUE,MIN_VALUE:dt.MIN_VALUE,NEGATIVE_INFINITY:dt.NEGATIVE_INFINITY,POSITIVE_INFINITY:dt.POSITIVE_INFINITY});Number=Et;m.redefine(S,"Number",Et)}var Pt=Math.pow(2,53)-1;b(Number,{MAX_SAFE_INTEGER:Pt,MIN_SAFE_INTEGER:-Pt,EPSILON:2.220446049250313e-16,parseInt:S.parseInt,parseFloat:S.parseFloat,isFinite:K,isInteger:function isInteger(e){return K(e)&&ce.ToInteger(e)===e},isSafeInteger:function isSafeInteger(e){return Number.isInteger(e)&&k(e)<=Number.MAX_SAFE_INTEGER},isNaN:X});h(Number,"parseInt",S.parseInt,Number.parseInt!==S.parseInt);if([,1].find(function(){return true})===1){ne(Array.prototype,"find",et.find)}if([,1].findIndex(function(){return true})!==0){ne(Array.prototype,"findIndex",et.findIndex)}var Ct=Function.bind.call(Function.bind,Object.prototype.propertyIsEnumerable);var Mt=function ensureEnumerable(e,t){if(s&&Ct(e,t)){Object.defineProperty(e,t,{enumerable:false})}};var xt=function sliceArgs(){var e=Number(this);var t=arguments.length;var r=t-e;var n=new Array(r<0?0:r);for(var o=e;o1){return NaN}var r=k(t);return Z(t)*Y(2*r/(1-r))/2},cbrt:function cbrt(e){var t=Number(e);if(t===0){return t}var r=t<0;var n;if(r){t=-t}if(t===Infinity){n=Infinity}else{n=L(F(t)/3);n=(t/(n*n)+2*n)/3}return r?-n:n},clz32:function clz32(e){var t=Number(e);var r=ce.ToUint32(t);if(r===0){return 32}return Pr?ce.Call(Pr,r):31-_(F(r+.5)*Ir)},cosh:function cosh(e){var t=Number(e);if(t===0){return 1}if(X(t)){return NaN}if(!T(t)){return Infinity}var r=L(k(t)-1);return(r+1/(r*Tr*Tr))*(Tr/2)},expm1:function expm1(e){var t=Number(e);if(t===-Infinity){return-1}if(!T(t)||t===0){return t}if(k(t)>.5){return L(t)-1}var r=t;var n=0;var o=1;while(n+r!==n){n+=r;o+=1;r*=t/o}return n},hypot:function hypot(e,t){var r=0;var n=0;for(var o=0;o0?i/n*(i/n):i}}return n===Infinity?Infinity:n*D(r)},log2:function log2(e){return F(e)*Ir},log10:function log10(e){return F(e)*Er},log1p:Y,sign:Z,sinh:function sinh(e){var t=Number(e);if(!T(t)||t===0){return t}var r=k(t);if(r<1){var n=Math.expm1(r);return Z(t)*n*(1+1/(n+1))/2}var o=L(r-1);return Z(t)*(o-1/(o*Tr*Tr))*(Tr/2)},tanh:function tanh(e){var t=Number(e);if(X(t)||t===0){return t}if(t>=20){return 1}if(t<=-20){return-1}return(Math.expm1(t)-Math.expm1(-t))/(L(t)+L(-t))},trunc:function trunc(e){var t=Number(e);return t<0?-_(-t):_(t)},imul:function imul(e,t){var r=ce.ToUint32(e);var n=ce.ToUint32(t);var o=r>>>16&65535;var i=r&65535;var a=n>>>16&65535;var u=n&65535;return i*u+(o*u+i*a<<16>>>0)|0},fround:function fround(e){var t=Number(e);if(t===0||t===Infinity||t===-Infinity||X(t)){return t}var r=Z(t);var n=k(t);if(njr||X(i)){return r*Infinity}return r*i}};var Mr=function withinULPDistance(e,t,r){return k(1-e/t)/Number.EPSILON<(r||8)};b(Math,Cr);h(Math,"sinh",Cr.sinh,Math.sinh(710)===Infinity);h(Math,"cosh",Cr.cosh,Math.cosh(710)===Infinity);h(Math,"log1p",Cr.log1p,Math.log1p(-1e-17)!==-1e-17);h(Math,"asinh",Cr.asinh,Math.asinh(-1e7)!==-Math.asinh(1e7));h(Math,"asinh",Cr.asinh,Math.asinh(1e300)===Infinity);h(Math,"atanh",Cr.atanh,Math.atanh(1e-300)===0);h(Math,"tanh",Cr.tanh,Math.tanh(-2e-17)!==-2e-17); - h(Math,"acosh",Cr.acosh,Math.acosh(Number.MAX_VALUE)===Infinity);h(Math,"acosh",Cr.acosh,!Mr(Math.acosh(1+Number.EPSILON),Math.sqrt(2*Number.EPSILON)));h(Math,"cbrt",Cr.cbrt,!Mr(Math.cbrt(1e-300),1e-100));h(Math,"sinh",Cr.sinh,Math.sinh(-2e-17)!==-2e-17);var xr=Math.expm1(10);h(Math,"expm1",Cr.expm1,xr>22025.465794806718||xr<22025.465794806718);var Nr=Math.round;var Ar=Math.round(.5-Number.EPSILON/4)===0&&Math.round(-.5+Number.EPSILON/3.99)===1;var Rr=mr+1;var _r=2*mr-1;var kr=[Rr,_r].every(function(e){return Math.round(e)===e});h(Math,"round",function round(e){var t=_(e);var r=t===-1?-0:t+1;return e-t<.5?t:r},!Ar||!kr);m.preserveToString(Math.round,Nr);var Lr=Math.imul;if(Math.imul(4294967295,5)!==-5){Math.imul=Cr.imul;m.preserveToString(Math.imul,Lr)}if(Math.imul.length!==2){ne(Math,"imul",function imul(e,t){return ce.Call(Lr,Math,arguments)})}var Fr=function(){var e=S.setTimeout;if(typeof e!=="function"&&typeof e!=="object"){return}ce.IsPromise=function(e){if(!ce.TypeIsObject(e)){return false}if(typeof e._promise==="undefined"){return false}return true};var r=function(e){if(!ce.IsConstructor(e)){throw new TypeError("Bad promise constructor")}var t=this;var r=function(e,r){if(t.resolve!==void 0||t.reject!==void 0){throw new TypeError("Bad Promise implementation!")}t.resolve=e;t.reject=r};t.resolve=void 0;t.reject=void 0;t.promise=new e(r);if(!(ce.IsCallable(t.resolve)&&ce.IsCallable(t.reject))){throw new TypeError("Bad promise constructor")}};var n;if(typeof window!=="undefined"&&ce.IsCallable(window.postMessage)){n=function(){var e=[];var t="zero-timeout-message";var r=function(r){M(e,r);window.postMessage(t,"*")};var n=function(r){if(r.source===window&&r.data===t){r.stopPropagation();if(e.length===0){return}var n=N(e);n()}};window.addEventListener("message",n,true);return r}}var o=function(){var e=S.Promise;var t=e&&e.resolve&&e.resolve();return t&&function(e){return t.then(e)}};var i=ce.IsCallable(S.setImmediate)?S.setImmediate:typeof process==="object"&&process.nextTick?process.nextTick:o()||(ce.IsCallable(n)?n():function(t){e(t,0)});var a=function(e){return e};var u=function(e){throw e};var f=0;var s=1;var c=2;var l=0;var p=1;var v=2;var y={};var h=function(e,t,r){i(function(){g(e,t,r)})};var g=function(e,t,r){var n,o;if(t===y){return e(r)}try{n=e(r);o=t.resolve}catch(i){n=i;o=t.reject}o(n)};var d=function(e,t){var r=e._promise;var n=r.reactionLength;if(n>0){h(r.fulfillReactionHandler0,r.reactionCapability0,t);r.fulfillReactionHandler0=void 0;r.rejectReactions0=void 0;r.reactionCapability0=void 0;if(n>1){for(var o=1,i=0;o0){h(r.rejectReactionHandler0,r.reactionCapability0,t);r.fulfillReactionHandler0=void 0;r.rejectReactions0=void 0;r.reactionCapability0=void 0;if(n>1){for(var o=1,i=0;o2&&arguments[2]===y;if(b&&o===E){i=y}else{i=new r(o)}var g=ce.IsCallable(e)?e:a;var d=ce.IsCallable(t)?t:u;var m=n._promise;var O;if(m.state===f){if(m.reactionLength===0){m.fulfillReactionHandler0=g;m.rejectReactionHandler0=d;m.reactionCapability0=i}else{var w=3*(m.reactionLength-1);m[w+l]=g;m[w+p]=d;m[w+v]=i}m.reactionLength+=1}else if(m.state===s){O=m.result;h(g,i,O)}else if(m.state===c){O=m.result;h(d,i,O)}else{throw new TypeError("unexpected Promise state")}return i.promise}});y=new r(E);I=T.then;return E}();if(S.Promise){delete S.Promise.accept;delete S.Promise.defer;delete S.Promise.prototype.chain}if(typeof Fr==="function"){b(S,{Promise:Fr});var Dr=w(S.Promise,function(e){return e.resolve(42).then(function(){})instanceof e});var zr=!i(function(){return S.Promise.reject(42).then(null,5).then(null,W)});var qr=i(function(){return S.Promise.call(3,W)});var Wr=function(e){var t=e.resolve(5);t.constructor={};var r=e.resolve(t);try{r.then(null,W).then(null,W)}catch(n){return true}return t===r}(S.Promise);var Gr=s&&function(){var e=0;var t=Object.defineProperty({},"then",{get:function(){e+=1}});Promise.resolve(t);return e===1}();var Hr=function BadResolverPromise(e){var t=new Promise(e);e(3,function(){});this.then=t.then;this.constructor=BadResolverPromise};Hr.prototype=Promise.prototype;Hr.all=Promise.all;var Vr=a(function(){return!!Hr.all([1,2])});if(!Dr||!zr||!qr||Wr||!Gr||Vr){Promise=Fr;ne(S,"Promise",Fr)}if(Promise.all.length!==1){var Br=Promise.all;ne(Promise,"all",function all(e){return ce.Call(Br,this,arguments)})}if(Promise.race.length!==1){var Ur=Promise.race;ne(Promise,"race",function race(e){return ce.Call(Ur,this,arguments)})}if(Promise.resolve.length!==1){var $r=Promise.resolve;ne(Promise,"resolve",function resolve(e){return ce.Call($r,this,arguments)})}if(Promise.reject.length!==1){var Jr=Promise.reject;ne(Promise,"reject",function reject(e){return ce.Call(Jr,this,arguments)})}Mt(Promise,"all");Mt(Promise,"race");Mt(Promise,"resolve");Mt(Promise,"reject");Ce(Promise)}var Xr=function(e){var t=n(p(e,function(e,t){e[t]=true;return e},{}));return e.join(":")===t.join(":")};var Kr=Xr(["z","a","bb"]);var Zr=Xr(["z",1,"a","3",2]);if(s){var Yr=function fastkey(e,t){if(!t&&!Kr){return null}if(se(e)){return"^"+ce.ToString(e)}else if(typeof e==="string"){return"$"+e}else if(typeof e==="number"){if(!Zr){return"n"+e}return e}else if(typeof e==="boolean"){return"b"+e}return null};var Qr=function emptyObject(){return Object.create?Object.create(null):{}};var en=function addIterableToMap(e,n,o){if(r(o)||re.string(o)){l(o,function(e){if(!ce.TypeIsObject(e)){throw new TypeError("Iterator value "+e+" is not an entry object")}n.set(e[0],e[1])})}else if(o instanceof e){t(e.prototype.forEach,o,function(e,t){n.set(t,e)})}else{var i,a;if(!se(o)){a=n.set;if(!ce.IsCallable(a)){throw new TypeError("bad map")}i=ce.GetIterator(o)}if(typeof i!=="undefined"){while(true){var u=ce.IteratorStep(i);if(u===false){break}var f=u.value;try{if(!ce.TypeIsObject(f)){throw new TypeError("Iterator value "+f+" is not an entry object")}t(a,n,f[0],f[1])}catch(s){ce.IteratorClose(i,true);throw s}}}}};var tn=function addIterableToSet(e,n,o){if(r(o)||re.string(o)){l(o,function(e){n.add(e)})}else if(o instanceof e){t(e.prototype.forEach,o,function(e){n.add(e)})}else{var i,a;if(!se(o)){a=n.add;if(!ce.IsCallable(a)){throw new TypeError("bad set")}i=ce.GetIterator(o)}if(typeof i!=="undefined"){while(true){var u=ce.IteratorStep(i);if(u===false){break}var f=u.value;try{t(a,n,f)}catch(s){ce.IteratorClose(i,true);throw s}}}}};var rn={Map:function(){var e={};var r=function MapEntry(e,t){this.key=e;this.value=t;this.next=null;this.prev=null};r.prototype.isRemoved=function isRemoved(){return this.key===e};var n=function isMap(e){return!!e._es6map};var o=function requireMapSlot(e,t){if(!ce.TypeIsObject(e)||!n(e)){throw new TypeError("Method Map.prototype."+t+" called on incompatible receiver "+ce.ToString(e))}};var i=function MapIterator(e,t){o(e,"[[MapIterator]]");this.head=e._head;this.i=this.head;this.kind=t};i.prototype={isMapIterator:true,next:function next(){if(!this.isMapIterator){throw new TypeError("Not a MapIterator")}var e=this.i;var t=this.kind;var r=this.head;if(typeof this.i==="undefined"){return Ke()}while(e.isRemoved()&&e!==r){e=e.prev}var n;while(e.next!==r){e=e.next;if(!e.isRemoved()){if(t==="key"){n=e.key}else if(t==="value"){n=e.value}else{n=[e.key,e.value]}this.i=e;return Ke(n)}}this.i=void 0;return Ke()}};Me(i.prototype);var a;var u=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires "new"')}if(this&&this._es6map){throw new TypeError("Bad construction")}var e=Ae(this,Map,a,{_es6map:true,_head:null,_map:G?new G:null,_size:0,_storage:Qr()});var t=new r(null,null);t.next=t.prev=t;e._head=t;if(arguments.length>0){en(Map,e,arguments[0])}return e};a=u.prototype;m.getter(a,"size",function(){if(typeof this._size==="undefined"){throw new TypeError("size method called on incompatible Map")}return this._size});b(a,{get:function get(e){o(this,"get");var t;var r=Yr(e,true);if(r!==null){t=this._storage[r];if(t){return t.value}else{return}}if(this._map){t=V.call(this._map,e);if(t){return t.value}else{return}}var n=this._head;var i=n;while((i=i.next)!==n){if(ce.SameValueZero(i.key,e)){return i.value}}},has:function has(e){o(this,"has");var t=Yr(e,true);if(t!==null){return typeof this._storage[t]!=="undefined"}if(this._map){return B.call(this._map,e)}var r=this._head;var n=r;while((n=n.next)!==r){if(ce.SameValueZero(n.key,e)){return true}}return false},set:function set(e,t){o(this,"set");var n=this._head;var i=n;var a;var u=Yr(e,true);if(u!==null){if(typeof this._storage[u]!=="undefined"){this._storage[u].value=t;return this}else{a=this._storage[u]=new r(e,t);i=n.prev}}else if(this._map){if(B.call(this._map,e)){V.call(this._map,e).value=t}else{a=new r(e,t);U.call(this._map,e,a);i=n.prev}}while((i=i.next)!==n){if(ce.SameValueZero(i.key,e)){i.value=t;return this}}a=a||new r(e,t);if(ce.SameValue(-0,e)){a.key=+0}a.next=this._head;a.prev=this._head.prev;a.prev.next=a;a.next.prev=a;this._size+=1;return this},"delete":function(t){o(this,"delete");var r=this._head;var n=r;var i=Yr(t,true);if(i!==null){if(typeof this._storage[i]==="undefined"){return false}n=this._storage[i].prev;delete this._storage[i]}else if(this._map){if(!B.call(this._map,t)){return false}n=V.call(this._map,t).prev;H.call(this._map,t)}while((n=n.next)!==r){if(ce.SameValueZero(n.key,t)){n.key=e;n.value=e;n.prev.next=n.next;n.next.prev=n.prev;this._size-=1;return true}}return false},clear:function clear(){o(this,"clear");this._map=G?new G:null;this._size=0;this._storage=Qr();var t=this._head;var r=t;var n=r.next;while((r=n)!==t){r.key=e;r.value=e;n=r.next;r.next=r.prev=t}t.next=t.prev=t},keys:function keys(){o(this,"keys");return new i(this,"key")},values:function values(){o(this,"values");return new i(this,"value")},entries:function entries(){o(this,"entries");return new i(this,"key+value")},forEach:function forEach(e){o(this,"forEach");var r=arguments.length>1?arguments[1]:null;var n=this.entries();for(var i=n.next();!i.done;i=n.next()){if(r){t(e,r,i.value[1],i.value[0],this)}else{e(i.value[1],i.value[0],this)}}}});Me(a,a.entries);return u}(),Set:function(){var e=function isSet(e){return e._es6set&&typeof e._storage!=="undefined"};var r=function requireSetSlot(t,r){if(!ce.TypeIsObject(t)||!e(t)){throw new TypeError("Set.prototype."+r+" called on incompatible receiver "+ce.ToString(t))}};var o;var i=function Set(){if(!(this instanceof Set)){throw new TypeError('Constructor Set requires "new"')}if(this&&this._es6set){throw new TypeError("Bad construction")}var e=Ae(this,Set,o,{_es6set:true,"[[SetData]]":null,_storage:Qr()});if(!e._es6set){throw new TypeError("bad set")}if(arguments.length>0){tn(Set,e,arguments[0])}return e};o=i.prototype;var a=function(e){var t=e;if(t==="^null"){return null}else if(t==="^undefined"){return void 0}else{var r=t.charAt(0);if(r==="$"){return C(t,1)}else if(r==="n"){return+C(t,1)}else if(r==="b"){return t==="btrue"}}return+t};var u=function ensureMap(e){if(!e["[[SetData]]"]){var t=new rn.Map;e["[[SetData]]"]=t;l(n(e._storage),function(e){var r=a(e);t.set(r,r)});e["[[SetData]]"]=t}e._storage=null};m.getter(i.prototype,"size",function(){r(this,"size");if(this._storage){return n(this._storage).length}u(this);return this["[[SetData]]"].size});b(i.prototype,{has:function has(e){r(this,"has");var t;if(this._storage&&(t=Yr(e))!==null){return!!this._storage[t]}u(this);return this["[[SetData]]"].has(e)},add:function add(e){r(this,"add");var t;if(this._storage&&(t=Yr(e))!==null){this._storage[t]=true;return this}u(this);this["[[SetData]]"].set(e,e);return this},"delete":function(e){r(this,"delete");var t;if(this._storage&&(t=Yr(e))!==null){var n=z(this._storage,t);return delete this._storage[t]&&n}u(this);return this["[[SetData]]"]["delete"](e)},clear:function clear(){r(this,"clear");if(this._storage){this._storage=Qr()}if(this["[[SetData]]"]){this["[[SetData]]"].clear()}},values:function values(){r(this,"values");u(this);return new f(this["[[SetData]]"].values())},entries:function entries(){r(this,"entries");u(this);return new f(this["[[SetData]]"].entries())},forEach:function forEach(e){r(this,"forEach");var n=arguments.length>1?arguments[1]:null;var o=this;u(o);this["[[SetData]]"].forEach(function(r,i){if(n){t(e,n,i,i,o)}else{e(i,i,o)}})}});h(i.prototype,"keys",i.prototype.values,true);Me(i.prototype,i.prototype.values);var f=function SetIterator(e){this.it=e};f.prototype={isSetIterator:true,next:function next(){if(!this.isSetIterator){throw new TypeError("Not a SetIterator")}return this.it.next()}};Me(f.prototype);return i}()};var nn=S.Set&&!Set.prototype["delete"]&&Set.prototype.remove&&Set.prototype.items&&Set.prototype.map&&Array.isArray((new Set).keys);if(nn){S.Set=rn.Set}if(S.Map||S.Set){var on=a(function(){return new Map([[1,2]]).get(1)===2});if(!on){S.Map=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires "new"')}var e=new G;if(arguments.length>0){en(Map,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,S.Map.prototype);return e};S.Map.prototype=O(G.prototype);h(S.Map.prototype,"constructor",S.Map,true);m.preserveToString(S.Map,G)}var an=new Map;var un=function(){var e=new Map([[1,0],[2,0],[3,0],[4,0]]);e.set(-0,e);return e.get(0)===e&&e.get(-0)===e&&e.has(0)&&e.has(-0)}();var fn=an.set(1,2)===an;if(!un||!fn){ne(Map.prototype,"set",function set(e,r){t(U,this,e===0?0:e,r);return this})}if(!un){b(Map.prototype,{get:function get(e){return t(V,this,e===0?0:e)},has:function has(e){return t(B,this,e===0?0:e)}},true);m.preserveToString(Map.prototype.get,V);m.preserveToString(Map.prototype.has,B)}var sn=new Set;var cn=Set.prototype["delete"]&&Set.prototype.add&&Set.prototype.has&&function(e){e["delete"](0);e.add(-0);return!e.has(0)}(sn);var ln=sn.add(1)===sn;if(!cn||!ln){var pn=Set.prototype.add;Set.prototype.add=function add(e){t(pn,this,e===0?0:e);return this};m.preserveToString(Set.prototype.add,pn)}if(!cn){var vn=Set.prototype.has;Set.prototype.has=function has(e){return t(vn,this,e===0?0:e)};m.preserveToString(Set.prototype.has,vn);var yn=Set.prototype["delete"];Set.prototype["delete"]=function SetDelete(e){return t(yn,this,e===0?0:e)};m.preserveToString(Set.prototype["delete"],yn)}var hn=w(S.Map,function(e){var t=new e([]);t.set(42,42);return t instanceof e});var bn=Object.setPrototypeOf&&!hn;var gn=function(){try{return!(S.Map()instanceof S.Map)}catch(e){return e instanceof TypeError}}();if(S.Map.length!==0||bn||!gn){S.Map=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires "new"')}var e=new G;if(arguments.length>0){en(Map,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,Map.prototype);return e};S.Map.prototype=G.prototype;h(S.Map.prototype,"constructor",S.Map,true);m.preserveToString(S.Map,G)}var dn=w(S.Set,function(e){var t=new e([]);t.add(42,42);return t instanceof e});var mn=Object.setPrototypeOf&&!dn;var On=function(){try{return!(S.Set()instanceof S.Set)}catch(e){return e instanceof TypeError}}();if(S.Set.length!==0||mn||!On){var wn=S.Set;S.Set=function Set(){if(!(this instanceof Set)){throw new TypeError('Constructor Set requires "new"')}var e=new wn;if(arguments.length>0){tn(Set,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,Set.prototype);return e};S.Set.prototype=wn.prototype;h(S.Set.prototype,"constructor",S.Set,true);m.preserveToString(S.Set,wn)}var jn=new S.Map;var Sn=!a(function(){return jn.keys().next().done});if(typeof S.Map.prototype.clear!=="function"||(new S.Set).size!==0||jn.size!==0||typeof S.Map.prototype.keys!=="function"||typeof S.Set.prototype.keys!=="function"||typeof S.Map.prototype.forEach!=="function"||typeof S.Set.prototype.forEach!=="function"||u(S.Map)||u(S.Set)||typeof jn.keys().next!=="function"||Sn||!hn){b(S,{Map:rn.Map,Set:rn.Set},true)}if(S.Set.prototype.keys!==S.Set.prototype.values){h(S.Set.prototype,"keys",S.Set.prototype.values,true)}Me(Object.getPrototypeOf((new S.Map).keys()));Me(Object.getPrototypeOf((new S.Set).keys()));if(c&&S.Set.prototype.has.name!=="has"){var Tn=S.Set.prototype.has;ne(S.Set.prototype,"has",function has(e){return t(Tn,this,e)})}}b(S,rn);Ce(S.Map);Ce(S.Set)}var In=function throwUnlessTargetIsObject(e){if(!ce.TypeIsObject(e)){throw new TypeError("target must be an object")}};var En={apply:function apply(){return ce.Call(ce.Call,null,arguments)},construct:function construct(e,t){if(!ce.IsConstructor(e)){throw new TypeError("First argument must be a constructor.")}var r=arguments.length>2?arguments[2]:e;if(!ce.IsConstructor(r)){throw new TypeError("new.target must be a constructor.")}return ce.Construct(e,t,r,"internal")},deleteProperty:function deleteProperty(e,t){In(e);if(s){var r=Object.getOwnPropertyDescriptor(e,t);if(r&&!r.configurable){return false}}return delete e[t]},has:function has(e,t){In(e);return t in e}};if(Object.getOwnPropertyNames){Object.assign(En,{ownKeys:function ownKeys(e){In(e);var t=Object.getOwnPropertyNames(e);if(ce.IsCallable(Object.getOwnPropertySymbols)){x(t,Object.getOwnPropertySymbols(e))}return t}})}var Pn=function ConvertExceptionToBoolean(e){return!i(e)};if(Object.preventExtensions){Object.assign(En,{isExtensible:function isExtensible(e){In(e);return Object.isExtensible(e)},preventExtensions:function preventExtensions(e){In(e);return Pn(function(){return Object.preventExtensions(e)})}})}if(s){var Cn=function get(e,t,r){var n=Object.getOwnPropertyDescriptor(e,t);if(!n){var o=Object.getPrototypeOf(e);if(o===null){return void 0}return Cn(o,t,r)}if("value"in n){return n.value}if(n.get){return ce.Call(n.get,r)}return void 0};var Mn=function set(e,r,n,o){var i=Object.getOwnPropertyDescriptor(e,r);if(!i){var a=Object.getPrototypeOf(e);if(a!==null){return Mn(a,r,n,o)}i={value:void 0,writable:true,enumerable:true,configurable:true}}if("value"in i){if(!i.writable){return false}if(!ce.TypeIsObject(o)){return false}var u=Object.getOwnPropertyDescriptor(o,r);if(u){return ae.defineProperty(o,r,{value:n})}else{return ae.defineProperty(o,r,{value:n,writable:true,enumerable:true,configurable:true})}}if(i.set){t(i.set,o,n);return true}return false};Object.assign(En,{defineProperty:function defineProperty(e,t,r){In(e);return Pn(function(){return Object.defineProperty(e,t,r)})},getOwnPropertyDescriptor:function getOwnPropertyDescriptor(e,t){In(e);return Object.getOwnPropertyDescriptor(e,t)},get:function get(e,t){In(e);var r=arguments.length>2?arguments[2]:e;return Cn(e,t,r)},set:function set(e,t,r){In(e);var n=arguments.length>3?arguments[3]:e;return Mn(e,t,r,n)}})}if(Object.getPrototypeOf){var xn=Object.getPrototypeOf;En.getPrototypeOf=function getPrototypeOf(e){In(e);return xn(e)}}if(Object.setPrototypeOf&&En.getPrototypeOf){var Nn=function(e,t){var r=t;while(r){if(e===r){return true}r=En.getPrototypeOf(r)}return false};Object.assign(En,{setPrototypeOf:function setPrototypeOf(e,t){In(e);if(t!==null&&!ce.TypeIsObject(t)){throw new TypeError("proto must be an object or null")}if(t===ae.getPrototypeOf(e)){return true}if(ae.isExtensible&&!ae.isExtensible(e)){return false}if(Nn(e,t)){return false}Object.setPrototypeOf(e,t);return true}})}var An=function(e,t){if(!ce.IsCallable(S.Reflect[e])){h(S.Reflect,e,t)}else{var r=a(function(){S.Reflect[e](1);S.Reflect[e](NaN);S.Reflect[e](true);return true});if(r){ne(S.Reflect,e,t)}}};Object.keys(En).forEach(function(e){An(e,En[e])});var Rn=S.Reflect.getPrototypeOf;if(c&&Rn&&Rn.name!=="getPrototypeOf"){ne(S.Reflect,"getPrototypeOf",function getPrototypeOf(e){return t(Rn,S.Reflect,e)})}if(S.Reflect.setPrototypeOf){if(a(function(){S.Reflect.setPrototypeOf(1,{});return true})){ne(S.Reflect,"setPrototypeOf",En.setPrototypeOf)}}if(S.Reflect.defineProperty){if(!a(function(){var e=!S.Reflect.defineProperty(1,"test",{value:1});var t=typeof Object.preventExtensions!=="function"||!S.Reflect.defineProperty(Object.preventExtensions({}),"test",{});return e&&t})){ne(S.Reflect,"defineProperty",En.defineProperty)}}if(S.Reflect.construct){if(!a(function(){var e=function F(){};return S.Reflect.construct(function(){},[],e)instanceof e})){ne(S.Reflect,"construct",En.construct)}}if(String(new Date(NaN))!=="Invalid Date"){var _n=Date.prototype.toString;var kn=function toString(){var e=+this;if(e!==e){return"Invalid Date"}return ce.Call(_n,this)};ne(Date.prototype,"toString",kn)}var Ln={anchor:function anchor(e){return ce.CreateHTML(this,"a","name",e)},big:function big(){return ce.CreateHTML(this,"big","","")},blink:function blink(){return ce.CreateHTML(this,"blink","","")},bold:function bold(){return ce.CreateHTML(this,"b","","")},fixed:function fixed(){return ce.CreateHTML(this,"tt","","")},fontcolor:function fontcolor(e){return ce.CreateHTML(this,"font","color",e)},fontsize:function fontsize(e){return ce.CreateHTML(this,"font","size",e)},italics:function italics(){return ce.CreateHTML(this,"i","","")},link:function link(e){return ce.CreateHTML(this,"a","href",e)},small:function small(){return ce.CreateHTML(this,"small","","")},strike:function strike(){return ce.CreateHTML(this,"strike","","")},sub:function sub(){return ce.CreateHTML(this,"sub","","")},sup:function sub(){return ce.CreateHTML(this,"sup","","")}};l(Object.keys(Ln),function(e){var r=String.prototype[e];var n=false;if(ce.IsCallable(r)){var o=t(r,"",' " ');var i=P([],o.match(/"/g)).length;n=o!==o.toLowerCase()||i>2}else{n=true}if(n){ne(String.prototype,e,Ln[e])}});var Fn=function(){if(!oe){return false}var e=typeof JSON==="object"&&typeof JSON.stringify==="function"?JSON.stringify:null;if(!e){return false}if(typeof e($())!=="undefined"){return true}if(e([$()])!=="[null]"){return true}var t={a:$()};t[$()]=true;if(e(t)!=="{}"){return true}return false}();var Dn=a(function(){if(!oe){return true}return JSON.stringify(Object($()))==="{}"&&JSON.stringify([Object($())])==="[{}]"});if(Fn||!Dn){var zn=JSON.stringify;ne(JSON,"stringify",function stringify(e){if(typeof e==="symbol"){return}var n;if(arguments.length>1){n=arguments[1]}var o=[e];if(!r(n)){var i=ce.IsCallable(n)?n:null;var a=function(e,r){var n=i?t(i,this,e,r):r;if(typeof n!=="symbol"){if(re.symbol(n)){return Nt({})(n)}else{return n}}};o.push(a)}else{o.push(n)}if(arguments.length>2){o.push(arguments[2])}return zn.apply(this,o)})}return S}); +(function (e, t) { + if (typeof define === 'function' && define.amd) { + define(t); + } else if (typeof exports === 'object') { + module.exports = t(); + } else { + e.returnExports = t(); + } +})(this, function () { + 'use strict'; + var e = Function.call.bind(Function.apply); + var t = Function.call.bind(Function.call); + var r = Array.isArray; + var n = Object.keys; + var o = function notThunker(t) { + return function notThunk() { + return !e(t, this, arguments); + }; + }; + var i = function (e) { + try { + e(); + return false; + } catch (t) { + return true; + } + }; + var a = function valueOrFalseIfThrows(e) { + try { + return e(); + } catch (t) { + return false; + } + }; + var u = o(i); + var f = function () { + return !i(function () { + return Object.defineProperty({}, 'x', { get: function () {} }); + }); + }; + var s = !!Object.defineProperty && f(); + var c = function foo() {}.name === 'foo'; + var l = Function.call.bind(Array.prototype.forEach); + var p = Function.call.bind(Array.prototype.reduce); + var v = Function.call.bind(Array.prototype.filter); + var y = Function.call.bind(Array.prototype.some); + var h = function (e, t, r, n) { + if (!n && t in e) { + return; + } + if (s) { + Object.defineProperty(e, t, { + configurable: true, + enumerable: false, + writable: true, + value: r, + }); + } else { + e[t] = r; + } + }; + var b = function (e, t, r) { + l(n(t), function (n) { + var o = t[n]; + h(e, n, o, !!r); + }); + }; + var g = Function.call.bind(Object.prototype.toString); + var d = + typeof /abc/ === 'function' + ? function IsCallableSlow(e) { + return typeof e === 'function' && g(e) === '[object Function]'; + } + : function IsCallableFast(e) { + return typeof e === 'function'; + }; + var m = { + getter: function (e, t, r) { + if (!s) { + throw new TypeError('getters require true ES5 support'); + } + Object.defineProperty(e, t, { + configurable: true, + enumerable: false, + get: r, + }); + }, + proxy: function (e, t, r) { + if (!s) { + throw new TypeError('getters require true ES5 support'); + } + var n = Object.getOwnPropertyDescriptor(e, t); + Object.defineProperty(r, t, { + configurable: n.configurable, + enumerable: n.enumerable, + get: function getKey() { + return e[t]; + }, + set: function setKey(r) { + e[t] = r; + }, + }); + }, + redefine: function (e, t, r) { + if (s) { + var n = Object.getOwnPropertyDescriptor(e, t); + n.value = r; + Object.defineProperty(e, t, n); + } else { + e[t] = r; + } + }, + defineByDescriptor: function (e, t, r) { + if (s) { + Object.defineProperty(e, t, r); + } else if ('value' in r) { + e[t] = r.value; + } + }, + preserveToString: function (e, t) { + if (t && d(t.toString)) { + h(e, 'toString', t.toString.bind(t), true); + } + }, + }; + var O = + Object.create || + function (e, t) { + var r = function Prototype() {}; + r.prototype = e; + var o = new r(); + if (typeof t !== 'undefined') { + n(t).forEach(function (e) { + m.defineByDescriptor(o, e, t[e]); + }); + } + return o; + }; + var w = function (e, t) { + if (!Object.setPrototypeOf) { + return false; + } + return a(function () { + var r = function Subclass(t) { + var r = new e(t); + Object.setPrototypeOf(r, Subclass.prototype); + return r; + }; + Object.setPrototypeOf(r, e); + r.prototype = O(e.prototype, { constructor: { value: r } }); + return t(r); + }); + }; + var j = function () { + if (typeof self !== 'undefined') { + return self; + } + if (typeof window !== 'undefined') { + return window; + } + if (typeof global !== 'undefined') { + return global; + } + throw new Error('unable to locate global object'); + }; + var S = j(); + var T = S.isFinite; + var I = Function.call.bind(String.prototype.indexOf); + var E = Function.apply.bind(Array.prototype.indexOf); + var P = Function.call.bind(Array.prototype.concat); + var C = Function.call.bind(String.prototype.slice); + var M = Function.call.bind(Array.prototype.push); + var x = Function.apply.bind(Array.prototype.push); + var N = Function.call.bind(Array.prototype.shift); + var A = Math.max; + var R = Math.min; + var _ = Math.floor; + var k = Math.abs; + var L = Math.exp; + var F = Math.log; + var D = Math.sqrt; + var z = Function.call.bind(Object.prototype.hasOwnProperty); + var q; + var W = function () {}; + var G = S.Map; + var H = G && G.prototype['delete']; + var V = G && G.prototype.get; + var B = G && G.prototype.has; + var U = G && G.prototype.set; + var $ = S.Symbol || {}; + var J = $.species || '@@species'; + var X = + Number.isNaN || + function isNaN(e) { + return e !== e; + }; + var K = + Number.isFinite || + function isFinite(e) { + return typeof e === 'number' && T(e); + }; + var Z = d(Math.sign) + ? Math.sign + : function sign(e) { + var t = Number(e); + if (t === 0) { + return t; + } + if (X(t)) { + return t; + } + return t < 0 ? -1 : 1; + }; + var Y = function log1p(e) { + var t = Number(e); + if (t < -1 || X(t)) { + return NaN; + } + if (t === 0 || t === Infinity) { + return t; + } + if (t === -1) { + return -Infinity; + } + return 1 + t - 1 === 0 ? t : t * (F(1 + t) / (1 + t - 1)); + }; + var Q = function isArguments(e) { + return g(e) === '[object Arguments]'; + }; + var ee = function isArguments(e) { + return ( + e !== null && + typeof e === 'object' && + typeof e.length === 'number' && + e.length >= 0 && + g(e) !== '[object Array]' && + g(e.callee) === '[object Function]' + ); + }; + var te = Q(arguments) ? Q : ee; + var re = { + primitive: function (e) { + return e === null || (typeof e !== 'function' && typeof e !== 'object'); + }, + string: function (e) { + return g(e) === '[object String]'; + }, + regex: function (e) { + return g(e) === '[object RegExp]'; + }, + symbol: function (e) { + return typeof S.Symbol === 'function' && typeof e === 'symbol'; + }, + }; + var ne = function overrideNative(e, t, r) { + var n = e[t]; + h(e, t, r, true); + m.preserveToString(e[t], n); + }; + var oe = typeof $ === 'function' && typeof $['for'] === 'function' && re.symbol($()); + var ie = re.symbol($.iterator) ? $.iterator : '_es6-shim iterator_'; + if (S.Set && typeof new S.Set()['@@iterator'] === 'function') { + ie = '@@iterator'; + } + if (!S.Reflect) { + h(S, 'Reflect', {}, true); + } + var ae = S.Reflect; + var ue = String; + var fe = typeof document === 'undefined' || !document ? null : document.all; + var se = + fe == null + ? function isNullOrUndefined(e) { + return e == null; + } + : function isNullOrUndefinedAndNotDocumentAll(e) { + return e == null && e !== fe; + }; + var ce = { + Call: function Call(t, r) { + var n = arguments.length > 2 ? arguments[2] : []; + if (!ce.IsCallable(t)) { + throw new TypeError(t + ' is not a function'); + } + return e(t, r, n); + }, + RequireObjectCoercible: function (e, t) { + if (se(e)) { + throw new TypeError(t || 'Cannot call method on ' + e); + } + return e; + }, + TypeIsObject: function (e) { + if (e === void 0 || e === null || e === true || e === false) { + return false; + } + return typeof e === 'function' || typeof e === 'object' || e === fe; + }, + ToObject: function (e, t) { + return Object(ce.RequireObjectCoercible(e, t)); + }, + IsCallable: d, + IsConstructor: function (e) { + return ce.IsCallable(e); + }, + ToInt32: function (e) { + return ce.ToNumber(e) >> 0; + }, + ToUint32: function (e) { + return ce.ToNumber(e) >>> 0; + }, + ToNumber: function (e) { + if (g(e) === '[object Symbol]') { + throw new TypeError('Cannot convert a Symbol value to a number'); + } + return +e; + }, + ToInteger: function (e) { + var t = ce.ToNumber(e); + if (X(t)) { + return 0; + } + if (t === 0 || !K(t)) { + return t; + } + return (t > 0 ? 1 : -1) * _(k(t)); + }, + ToLength: function (e) { + var t = ce.ToInteger(e); + if (t <= 0) { + return 0; + } + if (t > Number.MAX_SAFE_INTEGER) { + return Number.MAX_SAFE_INTEGER; + } + return t; + }, + SameValue: function (e, t) { + if (e === t) { + if (e === 0) { + return 1 / e === 1 / t; + } + return true; + } + return X(e) && X(t); + }, + SameValueZero: function (e, t) { + return e === t || (X(e) && X(t)); + }, + IsIterable: function (e) { + return ce.TypeIsObject(e) && (typeof e[ie] !== 'undefined' || te(e)); + }, + GetIterator: function (e) { + if (te(e)) { + return new q(e, 'value'); + } + var t = ce.GetMethod(e, ie); + if (!ce.IsCallable(t)) { + throw new TypeError('value is not an iterable'); + } + var r = ce.Call(t, e); + if (!ce.TypeIsObject(r)) { + throw new TypeError('bad iterator'); + } + return r; + }, + GetMethod: function (e, t) { + var r = ce.ToObject(e)[t]; + if (se(r)) { + return void 0; + } + if (!ce.IsCallable(r)) { + throw new TypeError('Method not callable: ' + t); + } + return r; + }, + IteratorComplete: function (e) { + return !!e.done; + }, + IteratorClose: function (e, t) { + var r = ce.GetMethod(e, 'return'); + if (r === void 0) { + return; + } + var n, o; + try { + n = ce.Call(r, e); + } catch (i) { + o = i; + } + if (t) { + return; + } + if (o) { + throw o; + } + if (!ce.TypeIsObject(n)) { + throw new TypeError("Iterator's return method returned a non-object."); + } + }, + IteratorNext: function (e) { + var t = arguments.length > 1 ? e.next(arguments[1]) : e.next(); + if (!ce.TypeIsObject(t)) { + throw new TypeError('bad iterator'); + } + return t; + }, + IteratorStep: function (e) { + var t = ce.IteratorNext(e); + var r = ce.IteratorComplete(t); + return r ? false : t; + }, + Construct: function (e, t, r, n) { + var o = typeof r === 'undefined' ? e : r; + if (!n && ae.construct) { + return ae.construct(e, t, o); + } + var i = o.prototype; + if (!ce.TypeIsObject(i)) { + i = Object.prototype; + } + var a = O(i); + var u = ce.Call(e, a, t); + return ce.TypeIsObject(u) ? u : a; + }, + SpeciesConstructor: function (e, t) { + var r = e.constructor; + if (r === void 0) { + return t; + } + if (!ce.TypeIsObject(r)) { + throw new TypeError('Bad constructor'); + } + var n = r[J]; + if (se(n)) { + return t; + } + if (!ce.IsConstructor(n)) { + throw new TypeError('Bad @@species'); + } + return n; + }, + CreateHTML: function (e, t, r, n) { + var o = ce.ToString(e); + var i = '<' + t; + if (r !== '') { + var a = ce.ToString(n); + var u = a.replace(/"/g, '"'); + i += ' ' + r + '="' + u + '"'; + } + var f = i + '>'; + var s = f + o; + return s + ''; + }, + IsRegExp: function IsRegExp(e) { + if (!ce.TypeIsObject(e)) { + return false; + } + var t = e[$.match]; + if (typeof t !== 'undefined') { + return !!t; + } + return re.regex(e); + }, + ToString: function ToString(e) { + return ue(e); + }, + }; + if (s && oe) { + var le = function defineWellKnownSymbol(e) { + if (re.symbol($[e])) { + return $[e]; + } + var t = $['for']('Symbol.' + e); + Object.defineProperty($, e, { + configurable: false, + enumerable: false, + writable: false, + value: t, + }); + return t; + }; + if (!re.symbol($.search)) { + var pe = le('search'); + var ve = String.prototype.search; + h(RegExp.prototype, pe, function search(e) { + return ce.Call(ve, e, [this]); + }); + var ye = function search(e) { + var t = ce.RequireObjectCoercible(this); + if (!se(e)) { + var r = ce.GetMethod(e, pe); + if (typeof r !== 'undefined') { + return ce.Call(r, e, [t]); + } + } + return ce.Call(ve, t, [ce.ToString(e)]); + }; + ne(String.prototype, 'search', ye); + } + if (!re.symbol($.replace)) { + var he = le('replace'); + var be = String.prototype.replace; + h(RegExp.prototype, he, function replace(e, t) { + return ce.Call(be, e, [this, t]); + }); + var ge = function replace(e, t) { + var r = ce.RequireObjectCoercible(this); + if (!se(e)) { + var n = ce.GetMethod(e, he); + if (typeof n !== 'undefined') { + return ce.Call(n, e, [r, t]); + } + } + return ce.Call(be, r, [ce.ToString(e), t]); + }; + ne(String.prototype, 'replace', ge); + } + if (!re.symbol($.split)) { + var de = le('split'); + var me = String.prototype.split; + h(RegExp.prototype, de, function split(e, t) { + return ce.Call(me, e, [this, t]); + }); + var Oe = function split(e, t) { + var r = ce.RequireObjectCoercible(this); + if (!se(e)) { + var n = ce.GetMethod(e, de); + if (typeof n !== 'undefined') { + return ce.Call(n, e, [r, t]); + } + } + return ce.Call(me, r, [ce.ToString(e), t]); + }; + ne(String.prototype, 'split', Oe); + } + var we = re.symbol($.match); + var je = + we && + (function () { + var e = {}; + e[$.match] = function () { + return 42; + }; + return 'a'.match(e) !== 42; + })(); + if (!we || je) { + var Se = le('match'); + var Te = String.prototype.match; + h(RegExp.prototype, Se, function match(e) { + return ce.Call(Te, e, [this]); + }); + var Ie = function match(e) { + var t = ce.RequireObjectCoercible(this); + if (!se(e)) { + var r = ce.GetMethod(e, Se); + if (typeof r !== 'undefined') { + return ce.Call(r, e, [t]); + } + } + return ce.Call(Te, t, [ce.ToString(e)]); + }; + ne(String.prototype, 'match', Ie); + } + } + var Ee = function wrapConstructor(e, t, r) { + m.preserveToString(t, e); + if (Object.setPrototypeOf) { + Object.setPrototypeOf(e, t); + } + if (s) { + l(Object.getOwnPropertyNames(e), function (n) { + if (n in W || r[n]) { + return; + } + m.proxy(e, n, t); + }); + } else { + l(Object.keys(e), function (n) { + if (n in W || r[n]) { + return; + } + t[n] = e[n]; + }); + } + t.prototype = e.prototype; + m.redefine(e.prototype, 'constructor', t); + }; + var Pe = function () { + return this; + }; + var Ce = function (e) { + if (s && !z(e, J)) { + m.getter(e, J, Pe); + } + }; + var Me = function (e, t) { + var r = + t || + function iterator() { + return this; + }; + h(e, ie, r); + if (!e[ie] && re.symbol(ie)) { + e[ie] = r; + } + }; + var xe = function createDataProperty(e, t, r) { + if (s) { + Object.defineProperty(e, t, { + configurable: true, + enumerable: true, + writable: true, + value: r, + }); + } else { + e[t] = r; + } + }; + var Ne = function createDataPropertyOrThrow(e, t, r) { + xe(e, t, r); + if (!ce.SameValue(e[t], r)) { + throw new TypeError('property is nonconfigurable'); + } + }; + var Ae = function (e, t, r, n) { + if (!ce.TypeIsObject(e)) { + throw new TypeError('Constructor requires `new`: ' + t.name); + } + var o = t.prototype; + if (!ce.TypeIsObject(o)) { + o = r; + } + var i = O(o); + for (var a in n) { + if (z(n, a)) { + var u = n[a]; + h(i, a, u, true); + } + } + return i; + }; + if (String.fromCodePoint && String.fromCodePoint.length !== 1) { + var Re = String.fromCodePoint; + ne(String, 'fromCodePoint', function fromCodePoint(e) { + return ce.Call(Re, this, arguments); + }); + } + var _e = { + fromCodePoint: function fromCodePoint(e) { + var t = []; + var r; + for (var n = 0, o = arguments.length; n < o; n++) { + r = Number(arguments[n]); + if (!ce.SameValue(r, ce.ToInteger(r)) || r < 0 || r > 1114111) { + throw new RangeError('Invalid code point ' + r); + } + if (r < 65536) { + M(t, String.fromCharCode(r)); + } else { + r -= 65536; + M(t, String.fromCharCode((r >> 10) + 55296)); + M(t, String.fromCharCode((r % 1024) + 56320)); + } + } + return t.join(''); + }, + raw: function raw(e) { + var t = ce.ToObject(e, 'bad callSite'); + var r = ce.ToObject(t.raw, 'bad raw value'); + var n = r.length; + var o = ce.ToLength(n); + if (o <= 0) { + return ''; + } + var i = []; + var a = 0; + var u, f, s, c; + while (a < o) { + u = ce.ToString(a); + s = ce.ToString(r[u]); + M(i, s); + if (a + 1 >= o) { + break; + } + f = a + 1 < arguments.length ? arguments[a + 1] : ''; + c = ce.ToString(f); + M(i, c); + a += 1; + } + return i.join(''); + }, + }; + if (String.raw && String.raw({ raw: { 0: 'x', 1: 'y', length: 2 } }) !== 'xy') { + ne(String, 'raw', _e.raw); + } + b(String, _e); + var ke = function repeat(e, t) { + if (t < 1) { + return ''; + } + if (t % 2) { + return repeat(e, t - 1) + e; + } + var r = repeat(e, t / 2); + return r + r; + }; + var Le = Infinity; + var Fe = { + repeat: function repeat(e) { + var t = ce.ToString(ce.RequireObjectCoercible(this)); + var r = ce.ToInteger(e); + if (r < 0 || r >= Le) { + throw new RangeError( + 'repeat count must be less than infinity and not overflow maximum string size' + ); + } + return ke(t, r); + }, + startsWith: function startsWith(e) { + var t = ce.ToString(ce.RequireObjectCoercible(this)); + if (ce.IsRegExp(e)) { + throw new TypeError('Cannot call method "startsWith" with a regex'); + } + var r = ce.ToString(e); + var n; + if (arguments.length > 1) { + n = arguments[1]; + } + var o = A(ce.ToInteger(n), 0); + return C(t, o, o + r.length) === r; + }, + endsWith: function endsWith(e) { + var t = ce.ToString(ce.RequireObjectCoercible(this)); + if (ce.IsRegExp(e)) { + throw new TypeError('Cannot call method "endsWith" with a regex'); + } + var r = ce.ToString(e); + var n = t.length; + var o; + if (arguments.length > 1) { + o = arguments[1]; + } + var i = typeof o === 'undefined' ? n : ce.ToInteger(o); + var a = R(A(i, 0), n); + return C(t, a - r.length, a) === r; + }, + includes: function includes(e) { + if (ce.IsRegExp(e)) { + throw new TypeError('"includes" does not accept a RegExp'); + } + var t = ce.ToString(e); + var r; + if (arguments.length > 1) { + r = arguments[1]; + } + return I(this, t, r) !== -1; + }, + codePointAt: function codePointAt(e) { + var t = ce.ToString(ce.RequireObjectCoercible(this)); + var r = ce.ToInteger(e); + var n = t.length; + if (r >= 0 && r < n) { + var o = t.charCodeAt(r); + var i = r + 1 === n; + if (o < 55296 || o > 56319 || i) { + return o; + } + var a = t.charCodeAt(r + 1); + if (a < 56320 || a > 57343) { + return o; + } + return (o - 55296) * 1024 + (a - 56320) + 65536; + } + }, + }; + if (String.prototype.includes && 'a'.includes('a', Infinity) !== false) { + ne(String.prototype, 'includes', Fe.includes); + } + if (String.prototype.startsWith && String.prototype.endsWith) { + var De = i(function () { + return '/a/'.startsWith(/a/); + }); + var ze = a(function () { + return 'abc'.startsWith('a', Infinity) === false; + }); + if (!De || !ze) { + ne(String.prototype, 'startsWith', Fe.startsWith); + ne(String.prototype, 'endsWith', Fe.endsWith); + } + } + if (oe) { + var qe = a(function () { + var e = /a/; + e[$.match] = false; + return '/a/'.startsWith(e); + }); + if (!qe) { + ne(String.prototype, 'startsWith', Fe.startsWith); + } + var We = a(function () { + var e = /a/; + e[$.match] = false; + return '/a/'.endsWith(e); + }); + if (!We) { + ne(String.prototype, 'endsWith', Fe.endsWith); + } + var Ge = a(function () { + var e = /a/; + e[$.match] = false; + return '/a/'.includes(e); + }); + if (!Ge) { + ne(String.prototype, 'includes', Fe.includes); + } + } + b(String.prototype, Fe); + var He = [ + '\t\n\x0B\f\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003', + '\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028', + '\u2029\ufeff', + ].join(''); + var Ve = new RegExp('(^[' + He + ']+)|([' + He + ']+$)', 'g'); + var Be = function trim() { + return ce.ToString(ce.RequireObjectCoercible(this)).replace(Ve, ''); + }; + var Ue = ['\x85', '\u200b', '\ufffe'].join(''); + var $e = new RegExp('[' + Ue + ']', 'g'); + var Je = /^[-+]0x[0-9a-f]+$/i; + var Xe = Ue.trim().length !== Ue.length; + h(String.prototype, 'trim', Be, Xe); + var Ke = function (e) { + return { value: e, done: arguments.length === 0 }; + }; + var Ze = function (e) { + ce.RequireObjectCoercible(e); + this._s = ce.ToString(e); + this._i = 0; + }; + Ze.prototype.next = function () { + var e = this._s; + var t = this._i; + if (typeof e === 'undefined' || t >= e.length) { + this._s = void 0; + return Ke(); + } + var r = e.charCodeAt(t); + var n, o; + if (r < 55296 || r > 56319 || t + 1 === e.length) { + o = 1; + } else { + n = e.charCodeAt(t + 1); + o = n < 56320 || n > 57343 ? 1 : 2; + } + this._i = t + o; + return Ke(e.substr(t, o)); + }; + Me(Ze.prototype); + Me(String.prototype, function () { + return new Ze(this); + }); + var Ye = { + from: function from(e) { + var r = this; + var n; + if (arguments.length > 1) { + n = arguments[1]; + } + var o, i; + if (typeof n === 'undefined') { + o = false; + } else { + if (!ce.IsCallable(n)) { + throw new TypeError('Array.from: when provided, the second argument must be a function'); + } + if (arguments.length > 2) { + i = arguments[2]; + } + o = true; + } + var a = typeof (te(e) || ce.GetMethod(e, ie)) !== 'undefined'; + var u, f, s; + if (a) { + f = ce.IsConstructor(r) ? Object(new r()) : []; + var c = ce.GetIterator(e); + var l, p; + s = 0; + while (true) { + l = ce.IteratorStep(c); + if (l === false) { + break; + } + p = l.value; + try { + if (o) { + p = typeof i === 'undefined' ? n(p, s) : t(n, i, p, s); + } + f[s] = p; + } catch (v) { + ce.IteratorClose(c, true); + throw v; + } + s += 1; + } + u = s; + } else { + var y = ce.ToObject(e); + u = ce.ToLength(y.length); + f = ce.IsConstructor(r) ? Object(new r(u)) : new Array(u); + var h; + for (s = 0; s < u; ++s) { + h = y[s]; + if (o) { + h = typeof i === 'undefined' ? n(h, s) : t(n, i, h, s); + } + Ne(f, s, h); + } + } + f.length = u; + return f; + }, + of: function of() { + var e = arguments.length; + var t = this; + var n = r(t) || !ce.IsCallable(t) ? new Array(e) : ce.Construct(t, [e]); + for (var o = 0; o < e; ++o) { + Ne(n, o, arguments[o]); + } + n.length = e; + return n; + }, + }; + b(Array, Ye); + Ce(Array); + q = function (e, t) { + this.i = 0; + this.array = e; + this.kind = t; + }; + b(q.prototype, { + next: function () { + var e = this.i; + var t = this.array; + if (!(this instanceof q)) { + throw new TypeError('Not an ArrayIterator'); + } + if (typeof t !== 'undefined') { + var r = ce.ToLength(t.length); + for (; e < r; e++) { + var n = this.kind; + var o; + if (n === 'key') { + o = e; + } else if (n === 'value') { + o = t[e]; + } else if (n === 'entry') { + o = [e, t[e]]; + } + this.i = e + 1; + return Ke(o); + } + } + this.array = void 0; + return Ke(); + }, + }); + Me(q.prototype); + var Qe = + Array.of === Ye.of || + (function () { + var e = function Foo(e) { + this.length = e; + }; + e.prototype = []; + var t = Array.of.apply(e, [1, 2]); + return t instanceof e && t.length === 2; + })(); + if (!Qe) { + ne(Array, 'of', Ye.of); + } + var et = { + copyWithin: function copyWithin(e, t) { + var r = ce.ToObject(this); + var n = ce.ToLength(r.length); + var o = ce.ToInteger(e); + var i = ce.ToInteger(t); + var a = o < 0 ? A(n + o, 0) : R(o, n); + var u = i < 0 ? A(n + i, 0) : R(i, n); + var f; + if (arguments.length > 2) { + f = arguments[2]; + } + var s = typeof f === 'undefined' ? n : ce.ToInteger(f); + var c = s < 0 ? A(n + s, 0) : R(s, n); + var l = R(c - u, n - a); + var p = 1; + if (u < a && a < u + l) { + p = -1; + u += l - 1; + a += l - 1; + } + while (l > 0) { + if (u in r) { + r[a] = r[u]; + } else { + delete r[a]; + } + u += p; + a += p; + l -= 1; + } + return r; + }, + fill: function fill(e) { + var t; + if (arguments.length > 1) { + t = arguments[1]; + } + var r; + if (arguments.length > 2) { + r = arguments[2]; + } + var n = ce.ToObject(this); + var o = ce.ToLength(n.length); + t = ce.ToInteger(typeof t === 'undefined' ? 0 : t); + r = ce.ToInteger(typeof r === 'undefined' ? o : r); + var i = t < 0 ? A(o + t, 0) : R(t, o); + var a = r < 0 ? o + r : r; + for (var u = i; u < o && u < a; ++u) { + n[u] = e; + } + return n; + }, + find: function find(e) { + var r = ce.ToObject(this); + var n = ce.ToLength(r.length); + if (!ce.IsCallable(e)) { + throw new TypeError('Array#find: predicate must be a function'); + } + var o = arguments.length > 1 ? arguments[1] : null; + for (var i = 0, a; i < n; i++) { + a = r[i]; + if (o) { + if (t(e, o, a, i, r)) { + return a; + } + } else if (e(a, i, r)) { + return a; + } + } + }, + findIndex: function findIndex(e) { + var r = ce.ToObject(this); + var n = ce.ToLength(r.length); + if (!ce.IsCallable(e)) { + throw new TypeError('Array#findIndex: predicate must be a function'); + } + var o = arguments.length > 1 ? arguments[1] : null; + for (var i = 0; i < n; i++) { + if (o) { + if (t(e, o, r[i], i, r)) { + return i; + } + } else if (e(r[i], i, r)) { + return i; + } + } + return -1; + }, + keys: function keys() { + return new q(this, 'key'); + }, + values: function values() { + return new q(this, 'value'); + }, + entries: function entries() { + return new q(this, 'entry'); + }, + }; + if (Array.prototype.keys && !ce.IsCallable([1].keys().next)) { + delete Array.prototype.keys; + } + if (Array.prototype.entries && !ce.IsCallable([1].entries().next)) { + delete Array.prototype.entries; + } + if ( + Array.prototype.keys && + Array.prototype.entries && + !Array.prototype.values && + Array.prototype[ie] + ) { + b(Array.prototype, { values: Array.prototype[ie] }); + if (re.symbol($.unscopables)) { + Array.prototype[$.unscopables].values = true; + } + } + if (c && Array.prototype.values && Array.prototype.values.name !== 'values') { + var tt = Array.prototype.values; + ne(Array.prototype, 'values', function values() { + return ce.Call(tt, this, arguments); + }); + h(Array.prototype, ie, Array.prototype.values, true); + } + b(Array.prototype, et); + if (1 / [true].indexOf(true, -0) < 0) { + h( + Array.prototype, + 'indexOf', + function indexOf(e) { + var t = E(this, arguments); + if (t === 0 && 1 / t < 0) { + return 0; + } + return t; + }, + true + ); + } + Me(Array.prototype, function () { + return this.values(); + }); + if (Object.getPrototypeOf) { + Me(Object.getPrototypeOf([].values())); + } + var rt = (function () { + return a(function () { + return Array.from({ length: -1 }).length === 0; + }); + })(); + var nt = (function () { + var e = Array.from([0].entries()); + return e.length === 1 && r(e[0]) && e[0][0] === 0 && e[0][1] === 0; + })(); + if (!rt || !nt) { + ne(Array, 'from', Ye.from); + } + var ot = (function () { + return a(function () { + return Array.from([0], void 0); + }); + })(); + if (!ot) { + var it = Array.from; + ne(Array, 'from', function from(e) { + if (arguments.length > 1 && typeof arguments[1] !== 'undefined') { + return ce.Call(it, this, arguments); + } else { + return t(it, this, e); + } + }); + } + var at = -(Math.pow(2, 32) - 1); + var ut = function (e, r) { + var n = { length: at }; + n[r ? (n.length >>> 0) - 1 : 0] = true; + return a(function () { + t( + e, + n, + function () { + throw new RangeError('should not reach here'); + }, + [] + ); + return true; + }); + }; + if (!ut(Array.prototype.forEach)) { + var ft = Array.prototype.forEach; + ne( + Array.prototype, + 'forEach', + function forEach(e) { + return ce.Call(ft, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.map)) { + var st = Array.prototype.map; + ne( + Array.prototype, + 'map', + function map(e) { + return ce.Call(st, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.filter)) { + var ct = Array.prototype.filter; + ne( + Array.prototype, + 'filter', + function filter(e) { + return ce.Call(ct, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.some)) { + var lt = Array.prototype.some; + ne( + Array.prototype, + 'some', + function some(e) { + return ce.Call(lt, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.every)) { + var pt = Array.prototype.every; + ne( + Array.prototype, + 'every', + function every(e) { + return ce.Call(pt, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.reduce)) { + var vt = Array.prototype.reduce; + ne( + Array.prototype, + 'reduce', + function reduce(e) { + return ce.Call(vt, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + if (!ut(Array.prototype.reduceRight, true)) { + var yt = Array.prototype.reduceRight; + ne( + Array.prototype, + 'reduceRight', + function reduceRight(e) { + return ce.Call(yt, this.length >= 0 ? this : [], arguments); + }, + true + ); + } + var ht = Number('0o10') !== 8; + var bt = Number('0b10') !== 2; + var gt = y(Ue, function (e) { + return Number(e + 0 + e) === 0; + }); + if (ht || bt || gt) { + var dt = Number; + var mt = /^0b[01]+$/i; + var Ot = /^0o[0-7]+$/i; + var wt = mt.test.bind(mt); + var jt = Ot.test.bind(Ot); + var St = function (e) { + var t; + if (typeof e.valueOf === 'function') { + t = e.valueOf(); + if (re.primitive(t)) { + return t; + } + } + if (typeof e.toString === 'function') { + t = e.toString(); + if (re.primitive(t)) { + return t; + } + } + throw new TypeError('No default value'); + }; + var Tt = $e.test.bind($e); + var It = Je.test.bind(Je); + var Et = (function () { + var e = function Number(t) { + var r; + if (arguments.length > 0) { + r = re.primitive(t) ? t : St(t, 'number'); + } else { + r = 0; + } + if (typeof r === 'string') { + r = ce.Call(Be, r); + if (wt(r)) { + r = parseInt(C(r, 2), 2); + } else if (jt(r)) { + r = parseInt(C(r, 2), 8); + } else if (Tt(r) || It(r)) { + r = NaN; + } + } + var n = this; + var o = a(function () { + dt.prototype.valueOf.call(n); + return true; + }); + if (n instanceof e && !o) { + return new dt(r); + } + return dt(r); + }; + return e; + })(); + Ee(dt, Et, {}); + b(Et, { + NaN: dt.NaN, + MAX_VALUE: dt.MAX_VALUE, + MIN_VALUE: dt.MIN_VALUE, + NEGATIVE_INFINITY: dt.NEGATIVE_INFINITY, + POSITIVE_INFINITY: dt.POSITIVE_INFINITY, + }); + Number = Et; + m.redefine(S, 'Number', Et); + } + var Pt = Math.pow(2, 53) - 1; + b(Number, { + MAX_SAFE_INTEGER: Pt, + MIN_SAFE_INTEGER: -Pt, + EPSILON: 2.220446049250313e-16, + parseInt: S.parseInt, + parseFloat: S.parseFloat, + isFinite: K, + isInteger: function isInteger(e) { + return K(e) && ce.ToInteger(e) === e; + }, + isSafeInteger: function isSafeInteger(e) { + return Number.isInteger(e) && k(e) <= Number.MAX_SAFE_INTEGER; + }, + isNaN: X, + }); + h(Number, 'parseInt', S.parseInt, Number.parseInt !== S.parseInt); + if ( + [, 1].find(function () { + return true; + }) === 1 + ) { + ne(Array.prototype, 'find', et.find); + } + if ( + [, 1].findIndex(function () { + return true; + }) !== 0 + ) { + ne(Array.prototype, 'findIndex', et.findIndex); + } + var Ct = Function.bind.call(Function.bind, Object.prototype.propertyIsEnumerable); + var Mt = function ensureEnumerable(e, t) { + if (s && Ct(e, t)) { + Object.defineProperty(e, t, { enumerable: false }); + } + }; + var xt = function sliceArgs() { + var e = Number(this); + var t = arguments.length; + var r = t - e; + var n = new Array(r < 0 ? 0 : r); + for (var o = e; o < t; ++o) { + n[o - e] = arguments[o]; + } + return n; + }; + var Nt = function assignTo(e) { + return function assignToSource(t, r) { + t[r] = e[r]; + return t; + }; + }; + var At = function (e, t) { + var r = n(Object(t)); + var o; + if (ce.IsCallable(Object.getOwnPropertySymbols)) { + o = v(Object.getOwnPropertySymbols(Object(t)), Ct(t)); + } + return p(P(r, o || []), Nt(t), e); + }; + var Rt = { + assign: function (e, t) { + var r = ce.ToObject(e, 'Cannot convert undefined or null to object'); + return p(ce.Call(xt, 1, arguments), At, r); + }, + is: function is(e, t) { + return ce.SameValue(e, t); + }, + }; + var _t = + Object.assign && + Object.preventExtensions && + (function () { + var e = Object.preventExtensions({ 1: 2 }); + try { + Object.assign(e, 'xy'); + } catch (t) { + return e[1] === 'y'; + } + })(); + if (_t) { + ne(Object, 'assign', Rt.assign); + } + b(Object, Rt); + if (s) { + var kt = { + setPrototypeOf: (function (e, r) { + var n; + var o = function (e, t) { + if (!ce.TypeIsObject(e)) { + throw new TypeError('cannot set prototype on a non-object'); + } + if (!(t === null || ce.TypeIsObject(t))) { + throw new TypeError('can only set prototype to an object or null' + t); + } + }; + var i = function (e, r) { + o(e, r); + t(n, e, r); + return e; + }; + try { + n = e.getOwnPropertyDescriptor(e.prototype, r).set; + t(n, {}, null); + } catch (a) { + if (e.prototype !== {}[r]) { + return; + } + n = function (e) { + this[r] = e; + }; + i.polyfill = i(i({}, null), e.prototype) instanceof e; + } + return i; + })(Object, '__proto__'), + }; + b(Object, kt); + } + if ( + Object.setPrototypeOf && + Object.getPrototypeOf && + Object.getPrototypeOf(Object.setPrototypeOf({}, null)) !== null && + Object.getPrototypeOf(Object.create(null)) === null + ) { + (function () { + var e = Object.create(null); + var t = Object.getPrototypeOf; + var r = Object.setPrototypeOf; + Object.getPrototypeOf = function (r) { + var n = t(r); + return n === e ? null : n; + }; + Object.setPrototypeOf = function (t, n) { + var o = n === null ? e : n; + return r(t, o); + }; + Object.setPrototypeOf.polyfill = false; + })(); + } + var Lt = !i(function () { + return Object.keys('foo'); + }); + if (!Lt) { + var Ft = Object.keys; + ne(Object, 'keys', function keys(e) { + return Ft(ce.ToObject(e)); + }); + n = Object.keys; + } + var Dt = i(function () { + return Object.keys(/a/g); + }); + if (Dt) { + var zt = Object.keys; + ne(Object, 'keys', function keys(e) { + if (re.regex(e)) { + var t = []; + for (var r in e) { + if (z(e, r)) { + M(t, r); + } + } + return t; + } + return zt(e); + }); + n = Object.keys; + } + if (Object.getOwnPropertyNames) { + var qt = !i(function () { + return Object.getOwnPropertyNames('foo'); + }); + if (!qt) { + var Wt = typeof window === 'object' ? Object.getOwnPropertyNames(window) : []; + var Gt = Object.getOwnPropertyNames; + ne(Object, 'getOwnPropertyNames', function getOwnPropertyNames(e) { + var t = ce.ToObject(e); + if (g(t) === '[object Window]') { + try { + return Gt(t); + } catch (r) { + return P([], Wt); + } + } + return Gt(t); + }); + } + } + if (Object.getOwnPropertyDescriptor) { + var Ht = !i(function () { + return Object.getOwnPropertyDescriptor('foo', 'bar'); + }); + if (!Ht) { + var Vt = Object.getOwnPropertyDescriptor; + ne(Object, 'getOwnPropertyDescriptor', function getOwnPropertyDescriptor(e, t) { + return Vt(ce.ToObject(e), t); + }); + } + } + if (Object.seal) { + var Bt = !i(function () { + return Object.seal('foo'); + }); + if (!Bt) { + var Ut = Object.seal; + ne(Object, 'seal', function seal(e) { + if (!ce.TypeIsObject(e)) { + return e; + } + return Ut(e); + }); + } + } + if (Object.isSealed) { + var $t = !i(function () { + return Object.isSealed('foo'); + }); + if (!$t) { + var Jt = Object.isSealed; + ne(Object, 'isSealed', function isSealed(e) { + if (!ce.TypeIsObject(e)) { + return true; + } + return Jt(e); + }); + } + } + if (Object.freeze) { + var Xt = !i(function () { + return Object.freeze('foo'); + }); + if (!Xt) { + var Kt = Object.freeze; + ne(Object, 'freeze', function freeze(e) { + if (!ce.TypeIsObject(e)) { + return e; + } + return Kt(e); + }); + } + } + if (Object.isFrozen) { + var Zt = !i(function () { + return Object.isFrozen('foo'); + }); + if (!Zt) { + var Yt = Object.isFrozen; + ne(Object, 'isFrozen', function isFrozen(e) { + if (!ce.TypeIsObject(e)) { + return true; + } + return Yt(e); + }); + } + } + if (Object.preventExtensions) { + var Qt = !i(function () { + return Object.preventExtensions('foo'); + }); + if (!Qt) { + var er = Object.preventExtensions; + ne(Object, 'preventExtensions', function preventExtensions(e) { + if (!ce.TypeIsObject(e)) { + return e; + } + return er(e); + }); + } + } + if (Object.isExtensible) { + var tr = !i(function () { + return Object.isExtensible('foo'); + }); + if (!tr) { + var rr = Object.isExtensible; + ne(Object, 'isExtensible', function isExtensible(e) { + if (!ce.TypeIsObject(e)) { + return false; + } + return rr(e); + }); + } + } + if (Object.getPrototypeOf) { + var nr = !i(function () { + return Object.getPrototypeOf('foo'); + }); + if (!nr) { + var or = Object.getPrototypeOf; + ne(Object, 'getPrototypeOf', function getPrototypeOf(e) { + return or(ce.ToObject(e)); + }); + } + } + var ir = + s && + (function () { + var e = Object.getOwnPropertyDescriptor(RegExp.prototype, 'flags'); + return e && ce.IsCallable(e.get); + })(); + if (s && !ir) { + var ar = function flags() { + if (!ce.TypeIsObject(this)) { + throw new TypeError('Method called on incompatible type: must be an object.'); + } + var e = ''; + if (this.global) { + e += 'g'; + } + if (this.ignoreCase) { + e += 'i'; + } + if (this.multiline) { + e += 'm'; + } + if (this.unicode) { + e += 'u'; + } + if (this.sticky) { + e += 'y'; + } + return e; + }; + m.getter(RegExp.prototype, 'flags', ar); + } + var ur = + s && + a(function () { + return String(new RegExp(/a/g, 'i')) === '/a/i'; + }); + var fr = + oe && + s && + (function () { + var e = /./; + e[$.match] = false; + return RegExp(e) === e; + })(); + var sr = a(function () { + return RegExp.prototype.toString.call({ source: 'abc' }) === '/abc/'; + }); + var cr = + sr && + a(function () { + return RegExp.prototype.toString.call({ source: 'a', flags: 'b' }) === '/a/b'; + }); + if (!sr || !cr) { + var lr = RegExp.prototype.toString; + h( + RegExp.prototype, + 'toString', + function toString() { + var e = ce.RequireObjectCoercible(this); + if (re.regex(e)) { + return t(lr, e); + } + var r = ue(e.source); + var n = ue(e.flags); + return '/' + r + '/' + n; + }, + true + ); + m.preserveToString(RegExp.prototype.toString, lr); + } + if (s && (!ur || fr)) { + var pr = Object.getOwnPropertyDescriptor(RegExp.prototype, 'flags').get; + var vr = Object.getOwnPropertyDescriptor(RegExp.prototype, 'source') || {}; + var yr = function () { + return this.source; + }; + var hr = ce.IsCallable(vr.get) ? vr.get : yr; + var br = RegExp; + var gr = (function () { + return function RegExp(e, t) { + var r = ce.IsRegExp(e); + var n = this instanceof RegExp; + if (!n && r && typeof t === 'undefined' && e.constructor === RegExp) { + return e; + } + var o = e; + var i = t; + if (re.regex(e)) { + o = ce.Call(hr, e); + i = typeof t === 'undefined' ? ce.Call(pr, e) : t; + return new RegExp(o, i); + } else if (r) { + o = e.source; + i = typeof t === 'undefined' ? e.flags : t; + } + return new br(e, t); + }; + })(); + Ee(br, gr, { $input: true }); + RegExp = gr; + m.redefine(S, 'RegExp', gr); + } + if (s) { + var dr = { + input: '$_', + lastMatch: '$&', + lastParen: '$+', + leftContext: '$`', + rightContext: "$'", + }; + l(n(dr), function (e) { + if (e in RegExp && !(dr[e] in RegExp)) { + m.getter(RegExp, dr[e], function get() { + return RegExp[e]; + }); + } + }); + } + Ce(RegExp); + var mr = 1 / Number.EPSILON; + var Or = function roundTiesToEven(e) { + return e + mr - mr; + }; + var wr = Math.pow(2, -23); + var jr = Math.pow(2, 127) * (2 - wr); + var Sr = Math.pow(2, -126); + var Tr = Math.E; + var Ir = Math.LOG2E; + var Er = Math.LOG10E; + var Pr = Number.prototype.clz; + delete Number.prototype.clz; + var Cr = { + acosh: function acosh(e) { + var t = Number(e); + if (X(t) || e < 1) { + return NaN; + } + if (t === 1) { + return 0; + } + if (t === Infinity) { + return t; + } + var r = 1 / (t * t); + if (t < 2) { + return Y(t - 1 + D(1 - r) * t); + } + var n = t / 2; + return Y(n + D(1 - r) * n - 1) + 1 / Ir; + }, + asinh: function asinh(e) { + var t = Number(e); + if (t === 0 || !T(t)) { + return t; + } + var r = k(t); + var n = r * r; + var o = Z(t); + if (r < 1) { + return o * Y(r + n / (D(n + 1) + 1)); + } + return o * (Y(r / 2 + (D(1 + 1 / n) * r) / 2 - 1) + 1 / Ir); + }, + atanh: function atanh(e) { + var t = Number(e); + if (t === 0) { + return t; + } + if (t === -1) { + return -Infinity; + } + if (t === 1) { + return Infinity; + } + if (X(t) || t < -1 || t > 1) { + return NaN; + } + var r = k(t); + return (Z(t) * Y((2 * r) / (1 - r))) / 2; + }, + cbrt: function cbrt(e) { + var t = Number(e); + if (t === 0) { + return t; + } + var r = t < 0; + var n; + if (r) { + t = -t; + } + if (t === Infinity) { + n = Infinity; + } else { + n = L(F(t) / 3); + n = (t / (n * n) + 2 * n) / 3; + } + return r ? -n : n; + }, + clz32: function clz32(e) { + var t = Number(e); + var r = ce.ToUint32(t); + if (r === 0) { + return 32; + } + return Pr ? ce.Call(Pr, r) : 31 - _(F(r + 0.5) * Ir); + }, + cosh: function cosh(e) { + var t = Number(e); + if (t === 0) { + return 1; + } + if (X(t)) { + return NaN; + } + if (!T(t)) { + return Infinity; + } + var r = L(k(t) - 1); + return (r + 1 / (r * Tr * Tr)) * (Tr / 2); + }, + expm1: function expm1(e) { + var t = Number(e); + if (t === -Infinity) { + return -1; + } + if (!T(t) || t === 0) { + return t; + } + if (k(t) > 0.5) { + return L(t) - 1; + } + var r = t; + var n = 0; + var o = 1; + while (n + r !== n) { + n += r; + o += 1; + r *= t / o; + } + return n; + }, + hypot: function hypot(e, t) { + var r = 0; + var n = 0; + for (var o = 0; o < arguments.length; ++o) { + var i = k(Number(arguments[o])); + if (n < i) { + r *= (n / i) * (n / i); + r += 1; + n = i; + } else { + r += i > 0 ? (i / n) * (i / n) : i; + } + } + return n === Infinity ? Infinity : n * D(r); + }, + log2: function log2(e) { + return F(e) * Ir; + }, + log10: function log10(e) { + return F(e) * Er; + }, + log1p: Y, + sign: Z, + sinh: function sinh(e) { + var t = Number(e); + if (!T(t) || t === 0) { + return t; + } + var r = k(t); + if (r < 1) { + var n = Math.expm1(r); + return (Z(t) * n * (1 + 1 / (n + 1))) / 2; + } + var o = L(r - 1); + return Z(t) * (o - 1 / (o * Tr * Tr)) * (Tr / 2); + }, + tanh: function tanh(e) { + var t = Number(e); + if (X(t) || t === 0) { + return t; + } + if (t >= 20) { + return 1; + } + if (t <= -20) { + return -1; + } + return (Math.expm1(t) - Math.expm1(-t)) / (L(t) + L(-t)); + }, + trunc: function trunc(e) { + var t = Number(e); + return t < 0 ? -_(-t) : _(t); + }, + imul: function imul(e, t) { + var r = ce.ToUint32(e); + var n = ce.ToUint32(t); + var o = (r >>> 16) & 65535; + var i = r & 65535; + var a = (n >>> 16) & 65535; + var u = n & 65535; + return (i * u + (((o * u + i * a) << 16) >>> 0)) | 0; + }, + fround: function fround(e) { + var t = Number(e); + if (t === 0 || t === Infinity || t === -Infinity || X(t)) { + return t; + } + var r = Z(t); + var n = k(t); + if (n < Sr) { + return r * Or(n / Sr / wr) * Sr * wr; + } + var o = (1 + wr / Number.EPSILON) * n; + var i = o - (o - n); + if (i > jr || X(i)) { + return r * Infinity; + } + return r * i; + }, + }; + var Mr = function withinULPDistance(e, t, r) { + return k(1 - e / t) / Number.EPSILON < (r || 8); + }; + b(Math, Cr); + h(Math, 'sinh', Cr.sinh, Math.sinh(710) === Infinity); + h(Math, 'cosh', Cr.cosh, Math.cosh(710) === Infinity); + h(Math, 'log1p', Cr.log1p, Math.log1p(-1e-17) !== -1e-17); + h(Math, 'asinh', Cr.asinh, Math.asinh(-1e7) !== -Math.asinh(1e7)); + h(Math, 'asinh', Cr.asinh, Math.asinh(1e300) === Infinity); + h(Math, 'atanh', Cr.atanh, Math.atanh(1e-300) === 0); + h(Math, 'tanh', Cr.tanh, Math.tanh(-2e-17) !== -2e-17); + h(Math, 'acosh', Cr.acosh, Math.acosh(Number.MAX_VALUE) === Infinity); + h(Math, 'acosh', Cr.acosh, !Mr(Math.acosh(1 + Number.EPSILON), Math.sqrt(2 * Number.EPSILON))); + h(Math, 'cbrt', Cr.cbrt, !Mr(Math.cbrt(1e-300), 1e-100)); + h(Math, 'sinh', Cr.sinh, Math.sinh(-2e-17) !== -2e-17); + var xr = Math.expm1(10); + h(Math, 'expm1', Cr.expm1, xr > 22025.465794806718 || xr < 22025.465794806718); + var Nr = Math.round; + var Ar = + Math.round(0.5 - Number.EPSILON / 4) === 0 && Math.round(-0.5 + Number.EPSILON / 3.99) === 1; + var Rr = mr + 1; + var _r = 2 * mr - 1; + var kr = [Rr, _r].every(function (e) { + return Math.round(e) === e; + }); + h( + Math, + 'round', + function round(e) { + var t = _(e); + var r = t === -1 ? -0 : t + 1; + return e - t < 0.5 ? t : r; + }, + !Ar || !kr + ); + m.preserveToString(Math.round, Nr); + var Lr = Math.imul; + if (Math.imul(4294967295, 5) !== -5) { + Math.imul = Cr.imul; + m.preserveToString(Math.imul, Lr); + } + if (Math.imul.length !== 2) { + ne(Math, 'imul', function imul(e, t) { + return ce.Call(Lr, Math, arguments); + }); + } + var Fr = (function () { + var e = S.setTimeout; + if (typeof e !== 'function' && typeof e !== 'object') { + return; + } + ce.IsPromise = function (e) { + if (!ce.TypeIsObject(e)) { + return false; + } + if (typeof e._promise === 'undefined') { + return false; + } + return true; + }; + var r = function (e) { + if (!ce.IsConstructor(e)) { + throw new TypeError('Bad promise constructor'); + } + var t = this; + var r = function (e, r) { + if (t.resolve !== void 0 || t.reject !== void 0) { + throw new TypeError('Bad Promise implementation!'); + } + t.resolve = e; + t.reject = r; + }; + t.resolve = void 0; + t.reject = void 0; + t.promise = new e(r); + if (!(ce.IsCallable(t.resolve) && ce.IsCallable(t.reject))) { + throw new TypeError('Bad promise constructor'); + } + }; + var n; + if (typeof window !== 'undefined' && ce.IsCallable(window.postMessage)) { + n = function () { + var e = []; + var t = 'zero-timeout-message'; + var r = function (r) { + M(e, r); + window.postMessage(t, '*'); + }; + var n = function (r) { + if (r.source === window && r.data === t) { + r.stopPropagation(); + if (e.length === 0) { + return; + } + var n = N(e); + n(); + } + }; + window.addEventListener('message', n, true); + return r; + }; + } + var o = function () { + var e = S.Promise; + var t = e && e.resolve && e.resolve(); + return ( + t && + function (e) { + return t.then(e); + } + ); + }; + var i = ce.IsCallable(S.setImmediate) + ? S.setImmediate + : typeof process === 'object' && process.nextTick + ? process.nextTick + : o() || + (ce.IsCallable(n) + ? n() + : function (t) { + e(t, 0); + }); + var a = function (e) { + return e; + }; + var u = function (e) { + throw e; + }; + var f = 0; + var s = 1; + var c = 2; + var l = 0; + var p = 1; + var v = 2; + var y = {}; + var h = function (e, t, r) { + i(function () { + g(e, t, r); + }); + }; + var g = function (e, t, r) { + var n, o; + if (t === y) { + return e(r); + } + try { + n = e(r); + o = t.resolve; + } catch (i) { + n = i; + o = t.reject; + } + o(n); + }; + var d = function (e, t) { + var r = e._promise; + var n = r.reactionLength; + if (n > 0) { + h(r.fulfillReactionHandler0, r.reactionCapability0, t); + r.fulfillReactionHandler0 = void 0; + r.rejectReactions0 = void 0; + r.reactionCapability0 = void 0; + if (n > 1) { + for (var o = 1, i = 0; o < n; o++, i += 3) { + h(r[i + l], r[i + v], t); + e[i + l] = void 0; + e[i + p] = void 0; + e[i + v] = void 0; + } + } + } + r.result = t; + r.state = s; + r.reactionLength = 0; + }; + var m = function (e, t) { + var r = e._promise; + var n = r.reactionLength; + if (n > 0) { + h(r.rejectReactionHandler0, r.reactionCapability0, t); + r.fulfillReactionHandler0 = void 0; + r.rejectReactions0 = void 0; + r.reactionCapability0 = void 0; + if (n > 1) { + for (var o = 1, i = 0; o < n; o++, i += 3) { + h(r[i + p], r[i + v], t); + e[i + l] = void 0; + e[i + p] = void 0; + e[i + v] = void 0; + } + } + } + r.result = t; + r.state = c; + r.reactionLength = 0; + }; + var O = function (e) { + var t = false; + var r = function (r) { + var n; + if (t) { + return; + } + t = true; + if (r === e) { + return m(e, new TypeError('Self resolution')); + } + if (!ce.TypeIsObject(r)) { + return d(e, r); + } + try { + n = r.then; + } catch (o) { + return m(e, o); + } + if (!ce.IsCallable(n)) { + return d(e, r); + } + i(function () { + j(e, r, n); + }); + }; + var n = function (r) { + if (t) { + return; + } + t = true; + return m(e, r); + }; + return { resolve: r, reject: n }; + }; + var w = function (e, r, n, o) { + if (e === I) { + t(e, r, n, o, y); + } else { + t(e, r, n, o); + } + }; + var j = function (e, t, r) { + var n = O(e); + var o = n.resolve; + var i = n.reject; + try { + w(r, t, o, i); + } catch (a) { + i(a); + } + }; + var T, I; + var E = (function () { + var e = function Promise(t) { + if (!(this instanceof e)) { + throw new TypeError('Constructor Promise requires "new"'); + } + if (this && this._promise) { + throw new TypeError('Bad construction'); + } + if (!ce.IsCallable(t)) { + throw new TypeError('not a valid resolver'); + } + var r = Ae(this, e, T, { + _promise: { + result: void 0, + state: f, + reactionLength: 0, + fulfillReactionHandler0: void 0, + rejectReactionHandler0: void 0, + reactionCapability0: void 0, + }, + }); + var n = O(r); + var o = n.reject; + try { + t(n.resolve, o); + } catch (i) { + o(i); + } + return r; + }; + return e; + })(); + T = E.prototype; + var P = function (e, t, r, n) { + var o = false; + return function (i) { + if (o) { + return; + } + o = true; + t[e] = i; + if (--n.count === 0) { + var a = r.resolve; + a(t); + } + }; + }; + var C = function (e, t, r) { + var n = e.iterator; + var o = []; + var i = { count: 1 }; + var a, u; + var f = 0; + while (true) { + try { + a = ce.IteratorStep(n); + if (a === false) { + e.done = true; + break; + } + u = a.value; + } catch (s) { + e.done = true; + throw s; + } + o[f] = void 0; + var c = t.resolve(u); + var l = P(f, o, r, i); + i.count += 1; + w(c.then, c, l, r.reject); + f += 1; + } + if (--i.count === 0) { + var p = r.resolve; + p(o); + } + return r.promise; + }; + var x = function (e, t, r) { + var n = e.iterator; + var o, i, a; + while (true) { + try { + o = ce.IteratorStep(n); + if (o === false) { + e.done = true; + break; + } + i = o.value; + } catch (u) { + e.done = true; + throw u; + } + a = t.resolve(i); + w(a.then, a, r.resolve, r.reject); + } + return r.promise; + }; + b(E, { + all: function all(e) { + var t = this; + if (!ce.TypeIsObject(t)) { + throw new TypeError('Promise is not object'); + } + var n = new r(t); + var o, i; + try { + o = ce.GetIterator(e); + i = { iterator: o, done: false }; + return C(i, t, n); + } catch (a) { + var u = a; + if (i && !i.done) { + try { + ce.IteratorClose(o, true); + } catch (f) { + u = f; + } + } + var s = n.reject; + s(u); + return n.promise; + } + }, + race: function race(e) { + var t = this; + if (!ce.TypeIsObject(t)) { + throw new TypeError('Promise is not object'); + } + var n = new r(t); + var o, i; + try { + o = ce.GetIterator(e); + i = { iterator: o, done: false }; + return x(i, t, n); + } catch (a) { + var u = a; + if (i && !i.done) { + try { + ce.IteratorClose(o, true); + } catch (f) { + u = f; + } + } + var s = n.reject; + s(u); + return n.promise; + } + }, + reject: function reject(e) { + var t = this; + if (!ce.TypeIsObject(t)) { + throw new TypeError('Bad promise constructor'); + } + var n = new r(t); + var o = n.reject; + o(e); + return n.promise; + }, + resolve: function resolve(e) { + var t = this; + if (!ce.TypeIsObject(t)) { + throw new TypeError('Bad promise constructor'); + } + if (ce.IsPromise(e)) { + var n = e.constructor; + if (n === t) { + return e; + } + } + var o = new r(t); + var i = o.resolve; + i(e); + return o.promise; + }, + }); + b(T, { + catch: function (e) { + return this.then(null, e); + }, + then: function then(e, t) { + var n = this; + if (!ce.IsPromise(n)) { + throw new TypeError('not a promise'); + } + var o = ce.SpeciesConstructor(n, E); + var i; + var b = arguments.length > 2 && arguments[2] === y; + if (b && o === E) { + i = y; + } else { + i = new r(o); + } + var g = ce.IsCallable(e) ? e : a; + var d = ce.IsCallable(t) ? t : u; + var m = n._promise; + var O; + if (m.state === f) { + if (m.reactionLength === 0) { + m.fulfillReactionHandler0 = g; + m.rejectReactionHandler0 = d; + m.reactionCapability0 = i; + } else { + var w = 3 * (m.reactionLength - 1); + m[w + l] = g; + m[w + p] = d; + m[w + v] = i; + } + m.reactionLength += 1; + } else if (m.state === s) { + O = m.result; + h(g, i, O); + } else if (m.state === c) { + O = m.result; + h(d, i, O); + } else { + throw new TypeError('unexpected Promise state'); + } + return i.promise; + }, + }); + y = new r(E); + I = T.then; + return E; + })(); + if (S.Promise) { + delete S.Promise.accept; + delete S.Promise.defer; + delete S.Promise.prototype.chain; + } + if (typeof Fr === 'function') { + b(S, { Promise: Fr }); + var Dr = w(S.Promise, function (e) { + return e.resolve(42).then(function () {}) instanceof e; + }); + var zr = !i(function () { + return S.Promise.reject(42).then(null, 5).then(null, W); + }); + var qr = i(function () { + return S.Promise.call(3, W); + }); + var Wr = (function (e) { + var t = e.resolve(5); + t.constructor = {}; + var r = e.resolve(t); + try { + r.then(null, W).then(null, W); + } catch (n) { + return true; + } + return t === r; + })(S.Promise); + var Gr = + s && + (function () { + var e = 0; + var t = Object.defineProperty({}, 'then', { + get: function () { + e += 1; + }, + }); + Promise.resolve(t); + return e === 1; + })(); + var Hr = function BadResolverPromise(e) { + var t = new Promise(e); + e(3, function () {}); + this.then = t.then; + this.constructor = BadResolverPromise; + }; + Hr.prototype = Promise.prototype; + Hr.all = Promise.all; + var Vr = a(function () { + return !!Hr.all([1, 2]); + }); + if (!Dr || !zr || !qr || Wr || !Gr || Vr) { + Promise = Fr; + ne(S, 'Promise', Fr); + } + if (Promise.all.length !== 1) { + var Br = Promise.all; + ne(Promise, 'all', function all(e) { + return ce.Call(Br, this, arguments); + }); + } + if (Promise.race.length !== 1) { + var Ur = Promise.race; + ne(Promise, 'race', function race(e) { + return ce.Call(Ur, this, arguments); + }); + } + if (Promise.resolve.length !== 1) { + var $r = Promise.resolve; + ne(Promise, 'resolve', function resolve(e) { + return ce.Call($r, this, arguments); + }); + } + if (Promise.reject.length !== 1) { + var Jr = Promise.reject; + ne(Promise, 'reject', function reject(e) { + return ce.Call(Jr, this, arguments); + }); + } + Mt(Promise, 'all'); + Mt(Promise, 'race'); + Mt(Promise, 'resolve'); + Mt(Promise, 'reject'); + Ce(Promise); + } + var Xr = function (e) { + var t = n( + p( + e, + function (e, t) { + e[t] = true; + return e; + }, + {} + ) + ); + return e.join(':') === t.join(':'); + }; + var Kr = Xr(['z', 'a', 'bb']); + var Zr = Xr(['z', 1, 'a', '3', 2]); + if (s) { + var Yr = function fastkey(e, t) { + if (!t && !Kr) { + return null; + } + if (se(e)) { + return '^' + ce.ToString(e); + } else if (typeof e === 'string') { + return '$' + e; + } else if (typeof e === 'number') { + if (!Zr) { + return 'n' + e; + } + return e; + } else if (typeof e === 'boolean') { + return 'b' + e; + } + return null; + }; + var Qr = function emptyObject() { + return Object.create ? Object.create(null) : {}; + }; + var en = function addIterableToMap(e, n, o) { + if (r(o) || re.string(o)) { + l(o, function (e) { + if (!ce.TypeIsObject(e)) { + throw new TypeError('Iterator value ' + e + ' is not an entry object'); + } + n.set(e[0], e[1]); + }); + } else if (o instanceof e) { + t(e.prototype.forEach, o, function (e, t) { + n.set(t, e); + }); + } else { + var i, a; + if (!se(o)) { + a = n.set; + if (!ce.IsCallable(a)) { + throw new TypeError('bad map'); + } + i = ce.GetIterator(o); + } + if (typeof i !== 'undefined') { + while (true) { + var u = ce.IteratorStep(i); + if (u === false) { + break; + } + var f = u.value; + try { + if (!ce.TypeIsObject(f)) { + throw new TypeError('Iterator value ' + f + ' is not an entry object'); + } + t(a, n, f[0], f[1]); + } catch (s) { + ce.IteratorClose(i, true); + throw s; + } + } + } + } + }; + var tn = function addIterableToSet(e, n, o) { + if (r(o) || re.string(o)) { + l(o, function (e) { + n.add(e); + }); + } else if (o instanceof e) { + t(e.prototype.forEach, o, function (e) { + n.add(e); + }); + } else { + var i, a; + if (!se(o)) { + a = n.add; + if (!ce.IsCallable(a)) { + throw new TypeError('bad set'); + } + i = ce.GetIterator(o); + } + if (typeof i !== 'undefined') { + while (true) { + var u = ce.IteratorStep(i); + if (u === false) { + break; + } + var f = u.value; + try { + t(a, n, f); + } catch (s) { + ce.IteratorClose(i, true); + throw s; + } + } + } + } + }; + var rn = { + Map: (function () { + var e = {}; + var r = function MapEntry(e, t) { + this.key = e; + this.value = t; + this.next = null; + this.prev = null; + }; + r.prototype.isRemoved = function isRemoved() { + return this.key === e; + }; + var n = function isMap(e) { + return !!e._es6map; + }; + var o = function requireMapSlot(e, t) { + if (!ce.TypeIsObject(e) || !n(e)) { + throw new TypeError( + 'Method Map.prototype.' + t + ' called on incompatible receiver ' + ce.ToString(e) + ); + } + }; + var i = function MapIterator(e, t) { + o(e, '[[MapIterator]]'); + this.head = e._head; + this.i = this.head; + this.kind = t; + }; + i.prototype = { + isMapIterator: true, + next: function next() { + if (!this.isMapIterator) { + throw new TypeError('Not a MapIterator'); + } + var e = this.i; + var t = this.kind; + var r = this.head; + if (typeof this.i === 'undefined') { + return Ke(); + } + while (e.isRemoved() && e !== r) { + e = e.prev; + } + var n; + while (e.next !== r) { + e = e.next; + if (!e.isRemoved()) { + if (t === 'key') { + n = e.key; + } else if (t === 'value') { + n = e.value; + } else { + n = [e.key, e.value]; + } + this.i = e; + return Ke(n); + } + } + this.i = void 0; + return Ke(); + }, + }; + Me(i.prototype); + var a; + var u = function Map() { + if (!(this instanceof Map)) { + throw new TypeError('Constructor Map requires "new"'); + } + if (this && this._es6map) { + throw new TypeError('Bad construction'); + } + var e = Ae(this, Map, a, { + _es6map: true, + _head: null, + _map: G ? new G() : null, + _size: 0, + _storage: Qr(), + }); + var t = new r(null, null); + t.next = t.prev = t; + e._head = t; + if (arguments.length > 0) { + en(Map, e, arguments[0]); + } + return e; + }; + a = u.prototype; + m.getter(a, 'size', function () { + if (typeof this._size === 'undefined') { + throw new TypeError('size method called on incompatible Map'); + } + return this._size; + }); + b(a, { + get: function get(e) { + o(this, 'get'); + var t; + var r = Yr(e, true); + if (r !== null) { + t = this._storage[r]; + if (t) { + return t.value; + } else { + return; + } + } + if (this._map) { + t = V.call(this._map, e); + if (t) { + return t.value; + } else { + return; + } + } + var n = this._head; + var i = n; + while ((i = i.next) !== n) { + if (ce.SameValueZero(i.key, e)) { + return i.value; + } + } + }, + has: function has(e) { + o(this, 'has'); + var t = Yr(e, true); + if (t !== null) { + return typeof this._storage[t] !== 'undefined'; + } + if (this._map) { + return B.call(this._map, e); + } + var r = this._head; + var n = r; + while ((n = n.next) !== r) { + if (ce.SameValueZero(n.key, e)) { + return true; + } + } + return false; + }, + set: function set(e, t) { + o(this, 'set'); + var n = this._head; + var i = n; + var a; + var u = Yr(e, true); + if (u !== null) { + if (typeof this._storage[u] !== 'undefined') { + this._storage[u].value = t; + return this; + } else { + a = this._storage[u] = new r(e, t); + i = n.prev; + } + } else if (this._map) { + if (B.call(this._map, e)) { + V.call(this._map, e).value = t; + } else { + a = new r(e, t); + U.call(this._map, e, a); + i = n.prev; + } + } + while ((i = i.next) !== n) { + if (ce.SameValueZero(i.key, e)) { + i.value = t; + return this; + } + } + a = a || new r(e, t); + if (ce.SameValue(-0, e)) { + a.key = +0; + } + a.next = this._head; + a.prev = this._head.prev; + a.prev.next = a; + a.next.prev = a; + this._size += 1; + return this; + }, + delete: function (t) { + o(this, 'delete'); + var r = this._head; + var n = r; + var i = Yr(t, true); + if (i !== null) { + if (typeof this._storage[i] === 'undefined') { + return false; + } + n = this._storage[i].prev; + delete this._storage[i]; + } else if (this._map) { + if (!B.call(this._map, t)) { + return false; + } + n = V.call(this._map, t).prev; + H.call(this._map, t); + } + while ((n = n.next) !== r) { + if (ce.SameValueZero(n.key, t)) { + n.key = e; + n.value = e; + n.prev.next = n.next; + n.next.prev = n.prev; + this._size -= 1; + return true; + } + } + return false; + }, + clear: function clear() { + o(this, 'clear'); + this._map = G ? new G() : null; + this._size = 0; + this._storage = Qr(); + var t = this._head; + var r = t; + var n = r.next; + while ((r = n) !== t) { + r.key = e; + r.value = e; + n = r.next; + r.next = r.prev = t; + } + t.next = t.prev = t; + }, + keys: function keys() { + o(this, 'keys'); + return new i(this, 'key'); + }, + values: function values() { + o(this, 'values'); + return new i(this, 'value'); + }, + entries: function entries() { + o(this, 'entries'); + return new i(this, 'key+value'); + }, + forEach: function forEach(e) { + o(this, 'forEach'); + var r = arguments.length > 1 ? arguments[1] : null; + var n = this.entries(); + for (var i = n.next(); !i.done; i = n.next()) { + if (r) { + t(e, r, i.value[1], i.value[0], this); + } else { + e(i.value[1], i.value[0], this); + } + } + }, + }); + Me(a, a.entries); + return u; + })(), + Set: (function () { + var e = function isSet(e) { + return e._es6set && typeof e._storage !== 'undefined'; + }; + var r = function requireSetSlot(t, r) { + if (!ce.TypeIsObject(t) || !e(t)) { + throw new TypeError( + 'Set.prototype.' + r + ' called on incompatible receiver ' + ce.ToString(t) + ); + } + }; + var o; + var i = function Set() { + if (!(this instanceof Set)) { + throw new TypeError('Constructor Set requires "new"'); + } + if (this && this._es6set) { + throw new TypeError('Bad construction'); + } + var e = Ae(this, Set, o, { + _es6set: true, + '[[SetData]]': null, + _storage: Qr(), + }); + if (!e._es6set) { + throw new TypeError('bad set'); + } + if (arguments.length > 0) { + tn(Set, e, arguments[0]); + } + return e; + }; + o = i.prototype; + var a = function (e) { + var t = e; + if (t === '^null') { + return null; + } else if (t === '^undefined') { + return void 0; + } else { + var r = t.charAt(0); + if (r === '$') { + return C(t, 1); + } else if (r === 'n') { + return +C(t, 1); + } else if (r === 'b') { + return t === 'btrue'; + } + } + return +t; + }; + var u = function ensureMap(e) { + if (!e['[[SetData]]']) { + var t = new rn.Map(); + e['[[SetData]]'] = t; + l(n(e._storage), function (e) { + var r = a(e); + t.set(r, r); + }); + e['[[SetData]]'] = t; + } + e._storage = null; + }; + m.getter(i.prototype, 'size', function () { + r(this, 'size'); + if (this._storage) { + return n(this._storage).length; + } + u(this); + return this['[[SetData]]'].size; + }); + b(i.prototype, { + has: function has(e) { + r(this, 'has'); + var t; + if (this._storage && (t = Yr(e)) !== null) { + return !!this._storage[t]; + } + u(this); + return this['[[SetData]]'].has(e); + }, + add: function add(e) { + r(this, 'add'); + var t; + if (this._storage && (t = Yr(e)) !== null) { + this._storage[t] = true; + return this; + } + u(this); + this['[[SetData]]'].set(e, e); + return this; + }, + delete: function (e) { + r(this, 'delete'); + var t; + if (this._storage && (t = Yr(e)) !== null) { + var n = z(this._storage, t); + return delete this._storage[t] && n; + } + u(this); + return this['[[SetData]]']['delete'](e); + }, + clear: function clear() { + r(this, 'clear'); + if (this._storage) { + this._storage = Qr(); + } + if (this['[[SetData]]']) { + this['[[SetData]]'].clear(); + } + }, + values: function values() { + r(this, 'values'); + u(this); + return new f(this['[[SetData]]'].values()); + }, + entries: function entries() { + r(this, 'entries'); + u(this); + return new f(this['[[SetData]]'].entries()); + }, + forEach: function forEach(e) { + r(this, 'forEach'); + var n = arguments.length > 1 ? arguments[1] : null; + var o = this; + u(o); + this['[[SetData]]'].forEach(function (r, i) { + if (n) { + t(e, n, i, i, o); + } else { + e(i, i, o); + } + }); + }, + }); + h(i.prototype, 'keys', i.prototype.values, true); + Me(i.prototype, i.prototype.values); + var f = function SetIterator(e) { + this.it = e; + }; + f.prototype = { + isSetIterator: true, + next: function next() { + if (!this.isSetIterator) { + throw new TypeError('Not a SetIterator'); + } + return this.it.next(); + }, + }; + Me(f.prototype); + return i; + })(), + }; + var nn = + S.Set && + !Set.prototype['delete'] && + Set.prototype.remove && + Set.prototype.items && + Set.prototype.map && + Array.isArray(new Set().keys); + if (nn) { + S.Set = rn.Set; + } + if (S.Map || S.Set) { + var on = a(function () { + return new Map([[1, 2]]).get(1) === 2; + }); + if (!on) { + S.Map = function Map() { + if (!(this instanceof Map)) { + throw new TypeError('Constructor Map requires "new"'); + } + var e = new G(); + if (arguments.length > 0) { + en(Map, e, arguments[0]); + } + delete e.constructor; + Object.setPrototypeOf(e, S.Map.prototype); + return e; + }; + S.Map.prototype = O(G.prototype); + h(S.Map.prototype, 'constructor', S.Map, true); + m.preserveToString(S.Map, G); + } + var an = new Map(); + var un = (function () { + var e = new Map([ + [1, 0], + [2, 0], + [3, 0], + [4, 0], + ]); + e.set(-0, e); + return e.get(0) === e && e.get(-0) === e && e.has(0) && e.has(-0); + })(); + var fn = an.set(1, 2) === an; + if (!un || !fn) { + ne(Map.prototype, 'set', function set(e, r) { + t(U, this, e === 0 ? 0 : e, r); + return this; + }); + } + if (!un) { + b( + Map.prototype, + { + get: function get(e) { + return t(V, this, e === 0 ? 0 : e); + }, + has: function has(e) { + return t(B, this, e === 0 ? 0 : e); + }, + }, + true + ); + m.preserveToString(Map.prototype.get, V); + m.preserveToString(Map.prototype.has, B); + } + var sn = new Set(); + var cn = + Set.prototype['delete'] && + Set.prototype.add && + Set.prototype.has && + (function (e) { + e['delete'](0); + e.add(-0); + return !e.has(0); + })(sn); + var ln = sn.add(1) === sn; + if (!cn || !ln) { + var pn = Set.prototype.add; + Set.prototype.add = function add(e) { + t(pn, this, e === 0 ? 0 : e); + return this; + }; + m.preserveToString(Set.prototype.add, pn); + } + if (!cn) { + var vn = Set.prototype.has; + Set.prototype.has = function has(e) { + return t(vn, this, e === 0 ? 0 : e); + }; + m.preserveToString(Set.prototype.has, vn); + var yn = Set.prototype['delete']; + Set.prototype['delete'] = function SetDelete(e) { + return t(yn, this, e === 0 ? 0 : e); + }; + m.preserveToString(Set.prototype['delete'], yn); + } + var hn = w(S.Map, function (e) { + var t = new e([]); + t.set(42, 42); + return t instanceof e; + }); + var bn = Object.setPrototypeOf && !hn; + var gn = (function () { + try { + return !(S.Map() instanceof S.Map); + } catch (e) { + return e instanceof TypeError; + } + })(); + if (S.Map.length !== 0 || bn || !gn) { + S.Map = function Map() { + if (!(this instanceof Map)) { + throw new TypeError('Constructor Map requires "new"'); + } + var e = new G(); + if (arguments.length > 0) { + en(Map, e, arguments[0]); + } + delete e.constructor; + Object.setPrototypeOf(e, Map.prototype); + return e; + }; + S.Map.prototype = G.prototype; + h(S.Map.prototype, 'constructor', S.Map, true); + m.preserveToString(S.Map, G); + } + var dn = w(S.Set, function (e) { + var t = new e([]); + t.add(42, 42); + return t instanceof e; + }); + var mn = Object.setPrototypeOf && !dn; + var On = (function () { + try { + return !(S.Set() instanceof S.Set); + } catch (e) { + return e instanceof TypeError; + } + })(); + if (S.Set.length !== 0 || mn || !On) { + var wn = S.Set; + S.Set = function Set() { + if (!(this instanceof Set)) { + throw new TypeError('Constructor Set requires "new"'); + } + var e = new wn(); + if (arguments.length > 0) { + tn(Set, e, arguments[0]); + } + delete e.constructor; + Object.setPrototypeOf(e, Set.prototype); + return e; + }; + S.Set.prototype = wn.prototype; + h(S.Set.prototype, 'constructor', S.Set, true); + m.preserveToString(S.Set, wn); + } + var jn = new S.Map(); + var Sn = !a(function () { + return jn.keys().next().done; + }); + if ( + typeof S.Map.prototype.clear !== 'function' || + new S.Set().size !== 0 || + jn.size !== 0 || + typeof S.Map.prototype.keys !== 'function' || + typeof S.Set.prototype.keys !== 'function' || + typeof S.Map.prototype.forEach !== 'function' || + typeof S.Set.prototype.forEach !== 'function' || + u(S.Map) || + u(S.Set) || + typeof jn.keys().next !== 'function' || + Sn || + !hn + ) { + b(S, { Map: rn.Map, Set: rn.Set }, true); + } + if (S.Set.prototype.keys !== S.Set.prototype.values) { + h(S.Set.prototype, 'keys', S.Set.prototype.values, true); + } + Me(Object.getPrototypeOf(new S.Map().keys())); + Me(Object.getPrototypeOf(new S.Set().keys())); + if (c && S.Set.prototype.has.name !== 'has') { + var Tn = S.Set.prototype.has; + ne(S.Set.prototype, 'has', function has(e) { + return t(Tn, this, e); + }); + } + } + b(S, rn); + Ce(S.Map); + Ce(S.Set); + } + var In = function throwUnlessTargetIsObject(e) { + if (!ce.TypeIsObject(e)) { + throw new TypeError('target must be an object'); + } + }; + var En = { + apply: function apply() { + return ce.Call(ce.Call, null, arguments); + }, + construct: function construct(e, t) { + if (!ce.IsConstructor(e)) { + throw new TypeError('First argument must be a constructor.'); + } + var r = arguments.length > 2 ? arguments[2] : e; + if (!ce.IsConstructor(r)) { + throw new TypeError('new.target must be a constructor.'); + } + return ce.Construct(e, t, r, 'internal'); + }, + deleteProperty: function deleteProperty(e, t) { + In(e); + if (s) { + var r = Object.getOwnPropertyDescriptor(e, t); + if (r && !r.configurable) { + return false; + } + } + return delete e[t]; + }, + has: function has(e, t) { + In(e); + return t in e; + }, + }; + if (Object.getOwnPropertyNames) { + Object.assign(En, { + ownKeys: function ownKeys(e) { + In(e); + var t = Object.getOwnPropertyNames(e); + if (ce.IsCallable(Object.getOwnPropertySymbols)) { + x(t, Object.getOwnPropertySymbols(e)); + } + return t; + }, + }); + } + var Pn = function ConvertExceptionToBoolean(e) { + return !i(e); + }; + if (Object.preventExtensions) { + Object.assign(En, { + isExtensible: function isExtensible(e) { + In(e); + return Object.isExtensible(e); + }, + preventExtensions: function preventExtensions(e) { + In(e); + return Pn(function () { + return Object.preventExtensions(e); + }); + }, + }); + } + if (s) { + var Cn = function get(e, t, r) { + var n = Object.getOwnPropertyDescriptor(e, t); + if (!n) { + var o = Object.getPrototypeOf(e); + if (o === null) { + return void 0; + } + return Cn(o, t, r); + } + if ('value' in n) { + return n.value; + } + if (n.get) { + return ce.Call(n.get, r); + } + return void 0; + }; + var Mn = function set(e, r, n, o) { + var i = Object.getOwnPropertyDescriptor(e, r); + if (!i) { + var a = Object.getPrototypeOf(e); + if (a !== null) { + return Mn(a, r, n, o); + } + i = { + value: void 0, + writable: true, + enumerable: true, + configurable: true, + }; + } + if ('value' in i) { + if (!i.writable) { + return false; + } + if (!ce.TypeIsObject(o)) { + return false; + } + var u = Object.getOwnPropertyDescriptor(o, r); + if (u) { + return ae.defineProperty(o, r, { value: n }); + } else { + return ae.defineProperty(o, r, { + value: n, + writable: true, + enumerable: true, + configurable: true, + }); + } + } + if (i.set) { + t(i.set, o, n); + return true; + } + return false; + }; + Object.assign(En, { + defineProperty: function defineProperty(e, t, r) { + In(e); + return Pn(function () { + return Object.defineProperty(e, t, r); + }); + }, + getOwnPropertyDescriptor: function getOwnPropertyDescriptor(e, t) { + In(e); + return Object.getOwnPropertyDescriptor(e, t); + }, + get: function get(e, t) { + In(e); + var r = arguments.length > 2 ? arguments[2] : e; + return Cn(e, t, r); + }, + set: function set(e, t, r) { + In(e); + var n = arguments.length > 3 ? arguments[3] : e; + return Mn(e, t, r, n); + }, + }); + } + if (Object.getPrototypeOf) { + var xn = Object.getPrototypeOf; + En.getPrototypeOf = function getPrototypeOf(e) { + In(e); + return xn(e); + }; + } + if (Object.setPrototypeOf && En.getPrototypeOf) { + var Nn = function (e, t) { + var r = t; + while (r) { + if (e === r) { + return true; + } + r = En.getPrototypeOf(r); + } + return false; + }; + Object.assign(En, { + setPrototypeOf: function setPrototypeOf(e, t) { + In(e); + if (t !== null && !ce.TypeIsObject(t)) { + throw new TypeError('proto must be an object or null'); + } + if (t === ae.getPrototypeOf(e)) { + return true; + } + if (ae.isExtensible && !ae.isExtensible(e)) { + return false; + } + if (Nn(e, t)) { + return false; + } + Object.setPrototypeOf(e, t); + return true; + }, + }); + } + var An = function (e, t) { + if (!ce.IsCallable(S.Reflect[e])) { + h(S.Reflect, e, t); + } else { + var r = a(function () { + S.Reflect[e](1); + S.Reflect[e](NaN); + S.Reflect[e](true); + return true; + }); + if (r) { + ne(S.Reflect, e, t); + } + } + }; + Object.keys(En).forEach(function (e) { + An(e, En[e]); + }); + var Rn = S.Reflect.getPrototypeOf; + if (c && Rn && Rn.name !== 'getPrototypeOf') { + ne(S.Reflect, 'getPrototypeOf', function getPrototypeOf(e) { + return t(Rn, S.Reflect, e); + }); + } + if (S.Reflect.setPrototypeOf) { + if ( + a(function () { + S.Reflect.setPrototypeOf(1, {}); + return true; + }) + ) { + ne(S.Reflect, 'setPrototypeOf', En.setPrototypeOf); + } + } + if (S.Reflect.defineProperty) { + if ( + !a(function () { + var e = !S.Reflect.defineProperty(1, 'test', { value: 1 }); + var t = + typeof Object.preventExtensions !== 'function' || + !S.Reflect.defineProperty(Object.preventExtensions({}), 'test', {}); + return e && t; + }) + ) { + ne(S.Reflect, 'defineProperty', En.defineProperty); + } + } + if (S.Reflect.construct) { + if ( + !a(function () { + var e = function F() {}; + return S.Reflect.construct(function () {}, [], e) instanceof e; + }) + ) { + ne(S.Reflect, 'construct', En.construct); + } + } + if (String(new Date(NaN)) !== 'Invalid Date') { + var _n = Date.prototype.toString; + var kn = function toString() { + var e = +this; + if (e !== e) { + return 'Invalid Date'; + } + return ce.Call(_n, this); + }; + ne(Date.prototype, 'toString', kn); + } + var Ln = { + anchor: function anchor(e) { + return ce.CreateHTML(this, 'a', 'name', e); + }, + big: function big() { + return ce.CreateHTML(this, 'big', '', ''); + }, + blink: function blink() { + return ce.CreateHTML(this, 'blink', '', ''); + }, + bold: function bold() { + return ce.CreateHTML(this, 'b', '', ''); + }, + fixed: function fixed() { + return ce.CreateHTML(this, 'tt', '', ''); + }, + fontcolor: function fontcolor(e) { + return ce.CreateHTML(this, 'font', 'color', e); + }, + fontsize: function fontsize(e) { + return ce.CreateHTML(this, 'font', 'size', e); + }, + italics: function italics() { + return ce.CreateHTML(this, 'i', '', ''); + }, + link: function link(e) { + return ce.CreateHTML(this, 'a', 'href', e); + }, + small: function small() { + return ce.CreateHTML(this, 'small', '', ''); + }, + strike: function strike() { + return ce.CreateHTML(this, 'strike', '', ''); + }, + sub: function sub() { + return ce.CreateHTML(this, 'sub', '', ''); + }, + sup: function sub() { + return ce.CreateHTML(this, 'sup', '', ''); + }, + }; + l(Object.keys(Ln), function (e) { + var r = String.prototype[e]; + var n = false; + if (ce.IsCallable(r)) { + var o = t(r, '', ' " '); + var i = P([], o.match(/"/g)).length; + n = o !== o.toLowerCase() || i > 2; + } else { + n = true; + } + if (n) { + ne(String.prototype, e, Ln[e]); + } + }); + var Fn = (function () { + if (!oe) { + return false; + } + var e = + typeof JSON === 'object' && typeof JSON.stringify === 'function' ? JSON.stringify : null; + if (!e) { + return false; + } + if (typeof e($()) !== 'undefined') { + return true; + } + if (e([$()]) !== '[null]') { + return true; + } + var t = { a: $() }; + t[$()] = true; + if (e(t) !== '{}') { + return true; + } + return false; + })(); + var Dn = a(function () { + if (!oe) { + return true; + } + return JSON.stringify(Object($())) === '{}' && JSON.stringify([Object($())]) === '[{}]'; + }); + if (Fn || !Dn) { + var zn = JSON.stringify; + ne(JSON, 'stringify', function stringify(e) { + if (typeof e === 'symbol') { + return; + } + var n; + if (arguments.length > 1) { + n = arguments[1]; + } + var o = [e]; + if (!r(n)) { + var i = ce.IsCallable(n) ? n : null; + var a = function (e, r) { + var n = i ? t(i, this, e, r) : r; + if (typeof n !== 'symbol') { + if (re.symbol(n)) { + return Nt({})(n); + } else { + return n; + } + } + }; + o.push(a); + } else { + o.push(n); + } + if (arguments.length > 2) { + o.push(arguments[2]); + } + return zn.apply(this, o); + }); + } + return S; +}); //# sourceMappingURL=es6-shim.map diff --git a/platform/app/public/html-templates/index.html b/platform/app/public/html-templates/index.html index 9ff1c3e692..9853c121a6 100644 --- a/platform/app/public/html-templates/index.html +++ b/platform/app/public/html-templates/index.html @@ -1,4 +1,4 @@ - + @@ -6,16 +6,34 @@ name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - - - - + + + + - - + + - + - + OHIF Viewer - + + @@ -6,16 +6,34 @@ name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - - - - + + + + - - + + - + - + { // customize the UI prompt accordingly. - const isFirstTimeUpdatedServiceWorkerIsWaiting = - event.wasWaitingBeforeRegister === false; + const isFirstTimeUpdatedServiceWorkerIsWaiting = event.wasWaitingBeforeRegister === false; console.log( 'isFirstTimeUpdatedServiceWorkerIsWaiting', isFirstTimeUpdatedServiceWorkerIsWaiting diff --git a/platform/app/public/oidc-client.min.js b/platform/app/public/oidc-client.min.js index 082fc8d529..1e82a13d4b 100644 --- a/platform/app/public/oidc-client.min.js +++ b/platform/app/public/oidc-client.min.js @@ -1,46 +1,10864 @@ -!function webpackUniversalModuleDefinition(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var r=t();for(var n in r)("object"==typeof exports?exports:e)[n]=r[n]}}(window,function(){return function(e){var t={};function __webpack_require__(r){if(t[r])return t[r].exports;var n=t[r]={i:r,l:!1,exports:{}};return e[r].call(n.exports,n,n.exports,__webpack_require__),n.l=!0,n.exports}return __webpack_require__.m=e,__webpack_require__.c=t,__webpack_require__.d=function(e,t,r){__webpack_require__.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},__webpack_require__.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.t=function(e,t){if(1&t&&(e=__webpack_require__(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(__webpack_require__.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)__webpack_require__.d(r,n,function(t){return e[t]}.bind(null,n));return r},__webpack_require__.n=function(e){var t=e&&e.__esModule?function getDefault(){return e.default}:function getModuleExports(){return e};return __webpack_require__.d(t,"a",t),t},__webpack_require__.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},__webpack_require__.p="",__webpack_require__(__webpack_require__.s=45)}([function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function defineProperties(e,t){for(var r=0;r=4){for(var e=arguments.length,t=Array(e),r=0;r=3){for(var e=arguments.length,t=Array(e),r=0;r=2){for(var e=arguments.length,t=Array(e),r=0;r=1){for(var e=arguments.length,t=Array(e),r=0;r1&&void 0!==arguments[1]?arguments[1]:"#",r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:i.Global;"string"!=typeof e&&(e=r.location.href);var o=e.lastIndexOf(t);o>=0&&(e=e.substr(o+1));for(var s,a={},u=/([^&=]+)=([^&]*)/g,c=0;s=u.exec(e);)if(a[decodeURIComponent(s[1])]=decodeURIComponent(s[2]),c++>50)return n.Log.error("UrlUtility.parseUrlFragment: response exceeded expected number of parameters",e),{error:"Response exceeded expected number of parameters"};for(var h in a)return a;return{}},UrlUtility}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.MetadataService=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:o.JsonService;if(function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,MetadataService),!e)throw i.Log.error("MetadataService: No settings passed to MetadataService"),new Error("settings");this._settings=e,this._jsonService=new t(["application/jwk-set+json"])}return MetadataService.prototype.getMetadata=function getMetadata(){var e=this;return this._settings.metadata?(i.Log.debug("MetadataService.getMetadata: Returning metadata from settings"),Promise.resolve(this._settings.metadata)):this.metadataUrl?(i.Log.debug("MetadataService.getMetadata: getting metadata from",this.metadataUrl),this._jsonService.getJson(this.metadataUrl).then(function(t){return i.Log.debug("MetadataService.getMetadata: json received"),e._settings.metadata=t,t})):(i.Log.error("MetadataService.getMetadata: No authority or metadataUrl configured on settings"),Promise.reject(new Error("No authority or metadataUrl configured on settings")))},MetadataService.prototype.getIssuer=function getIssuer(){return this._getMetadataProperty("issuer")},MetadataService.prototype.getAuthorizationEndpoint=function getAuthorizationEndpoint(){return this._getMetadataProperty("authorization_endpoint")},MetadataService.prototype.getUserInfoEndpoint=function getUserInfoEndpoint(){return this._getMetadataProperty("userinfo_endpoint")},MetadataService.prototype.getTokenEndpoint=function getTokenEndpoint(){return this._getMetadataProperty("token_endpoint",!0)},MetadataService.prototype.getCheckSessionIframe=function getCheckSessionIframe(){return this._getMetadataProperty("check_session_iframe",!0)},MetadataService.prototype.getEndSessionEndpoint=function getEndSessionEndpoint(){return this._getMetadataProperty("end_session_endpoint",!0)},MetadataService.prototype.getRevocationEndpoint=function getRevocationEndpoint(){return this._getMetadataProperty("revocation_endpoint",!0)},MetadataService.prototype._getMetadataProperty=function _getMetadataProperty(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return i.Log.debug("MetadataService.getMetadataProperty for: "+e),this.getMetadata().then(function(r){if(i.Log.debug("MetadataService.getMetadataProperty: metadata recieved"),void 0===r[e]){if(!0===t)return void i.Log.warn("MetadataService.getMetadataProperty: Metadata does not contain optional property "+e);throw i.Log.error("MetadataService.getMetadataProperty: Metadata does not contain property "+e),new Error("Metadata does not contain property "+e)}return r[e]})},MetadataService.prototype.getSigningKeys=function getSigningKeys(){var e=this;return this._settings.signingKeys?(i.Log.debug("MetadataService.getSigningKeys: Returning signingKeys from settings"),Promise.resolve(this._settings.signingKeys)):this._getMetadataProperty("jwks_uri").then(function(t){return i.Log.debug("MetadataService.getSigningKeys: jwks_uri received",t),e._jsonService.getJson(t).then(function(t){if(i.Log.debug("MetadataService.getSigningKeys: key set received",t),!t.keys)throw i.Log.error("MetadataService.getSigningKeys: Missing keys on keyset"),new Error("Missing keys on keyset");return e._settings.signingKeys=t.keys,e._settings.signingKeys})})},n(MetadataService,[{key:"metadataUrl",get:function get(){return this._metadataUrl||(this._settings.metadataUrl?this._metadataUrl=this._settings.metadataUrl:(this._metadataUrl=this._settings.authority,this._metadataUrl&&this._metadataUrl.indexOf(".well-known/openid-configuration")<0&&("/"!==this._metadataUrl[this._metadataUrl.length-1]&&(this._metadataUrl+="/"),this._metadataUrl+=".well-known/openid-configuration"))),this._metadataUrl}}]),MetadataService}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.State=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},t=e.id,r=e.data,n=e.created;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,State),this._id=t||(0,o.default)(),this._data=r,this._created="number"==typeof n&&n>0?n:parseInt(Date.now()/1e3)}return State.prototype.toStorageString=function toStorageString(){return i.Log.debug("State.toStorageString"),JSON.stringify({id:this.id,data:this.data,created:this.created})},State.fromStorageString=function fromStorageString(e){return i.Log.debug("State.fromStorageString"),new State(JSON.parse(e))},State.clearStaleState=function clearStaleState(e,t){var r=Date.now()/1e3-t;return e.getAllKeys().then(function(t){i.Log.debug("State.clearStaleState: got keys",t);for(var n=[],o=function _loop(o){var s=t[o];a=e.get(s).then(function(t){var n=!1;if(t)try{var o=State.fromStorageString(t);i.Log.debug("State.clearStaleState: got item from key: ",s,o.created),o.created<=r&&(n=!0)}catch(e){i.Log.error("State.clearStaleState: Error parsing state for key",s,e.message),n=!0}else i.Log.debug("State.clearStaleState: no item in storage for key: ",s),n=!0;if(n)return i.Log.debug("State.clearStaleState: removed item for key: ",s),e.remove(s)}),n.push(a)},s=0;s0&&void 0!==arguments[0]?arguments[0]:{},t=e.prefix,r=void 0===t?"oidc.":t,n=e.store,o=void 0===n?i.Global.localStorage:n;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,WebStorageStateStore),this._store=o,this._prefix=r}return WebStorageStateStore.prototype.set=function set(e,t){return n.Log.debug("WebStorageStateStore.set",e),e=this._prefix+e,this._store.setItem(e,t),Promise.resolve()},WebStorageStateStore.prototype.get=function get(e){n.Log.debug("WebStorageStateStore.get",e),e=this._prefix+e;var t=this._store.getItem(e);return Promise.resolve(t)},WebStorageStateStore.prototype.remove=function remove(e){n.Log.debug("WebStorageStateStore.remove",e),e=this._prefix+e;var t=this._store.getItem(e);return this._store.removeItem(e),Promise.resolve(t)},WebStorageStateStore.prototype.getAllKeys=function getAllKeys(){n.Log.debug("WebStorageStateStore.getAllKeys");for(var e=[],t=0;t0&&void 0!==arguments[0]?arguments[0]:{},t=e.authority,r=e.metadataUrl,i=e.metadata,o=e.signingKeys,g=e.client_id,p=e.client_secret,d=e.response_type,v=void 0===d?c:d,y=e.scope,m=void 0===y?h:y,S=e.redirect_uri,F=e.post_logout_redirect_uri,b=e.prompt,_=e.display,w=e.max_age,E=e.ui_locales,x=e.acr_values,C=e.resource,P=e.filterProtocolClaims,A=void 0===P||P,k=e.loadUserInfo,I=void 0===k||k,B=e.staleStateAge,R=void 0===B?f:B,T=e.clockSkew,U=void 0===T?l:T,M=e.stateStore,L=void 0===M?new s.WebStorageStateStore:M,D=e.ResponseValidatorCtor,N=void 0===D?a.ResponseValidator:D,O=e.MetadataServiceCtor,H=void 0===O?u.MetadataService:O,j=e.extraQueryParams,K=void 0===j?{}:j;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,OidcClientSettings),this._authority=t,this._metadataUrl=r,this._metadata=i,this._signingKeys=o,this._client_id=g,this._client_secret=p,this._response_type=v,this._scope=m,this._redirect_uri=S,this._post_logout_redirect_uri=F,this._prompt=b,this._display=_,this._max_age=w,this._ui_locales=E,this._acr_values=x,this._resource=C,this._filterProtocolClaims=!!A,this._loadUserInfo=!!I,this._staleStateAge=R,this._clockSkew=U,this._stateStore=L,this._validator=new N(this),this._metadataService=new H(this),this._extraQueryParams="object"===(void 0===K?"undefined":n(K))?K:{}}return i(OidcClientSettings,[{key:"client_id",get:function get(){return this._client_id},set:function set(e){if(this._client_id)throw o.Log.error("OidcClientSettings.set_client_id: client_id has already been assigned."),new Error("client_id has already been assigned.");this._client_id=e}},{key:"client_secret",get:function get(){return this._client_secret}},{key:"response_type",get:function get(){return this._response_type}},{key:"scope",get:function get(){return this._scope}},{key:"redirect_uri",get:function get(){return this._redirect_uri}},{key:"post_logout_redirect_uri",get:function get(){return this._post_logout_redirect_uri}},{key:"prompt",get:function get(){return this._prompt}},{key:"display",get:function get(){return this._display}},{key:"max_age",get:function get(){return this._max_age}},{key:"ui_locales",get:function get(){return this._ui_locales}},{key:"acr_values",get:function get(){return this._acr_values}},{key:"resource",get:function get(){return this._resource}},{key:"authority",get:function get(){return this._authority},set:function set(e){if(this._authority)throw o.Log.error("OidcClientSettings.set_authority: authority has already been assigned."),new Error("authority has already been assigned.");this._authority=e}},{key:"metadataUrl",get:function get(){return this._metadataUrl||(this._metadataUrl=this.authority,this._metadataUrl&&this._metadataUrl.indexOf(".well-known/openid-configuration")<0&&("/"!==this._metadataUrl[this._metadataUrl.length-1]&&(this._metadataUrl+="/"),this._metadataUrl+=".well-known/openid-configuration")),this._metadataUrl}},{key:"metadata",get:function get(){return this._metadata},set:function set(e){this._metadata=e}},{key:"signingKeys",get:function get(){return this._signingKeys},set:function set(e){this._signingKeys=e}},{key:"filterProtocolClaims",get:function get(){return this._filterProtocolClaims}},{key:"loadUserInfo",get:function get(){return this._loadUserInfo}},{key:"staleStateAge",get:function get(){return this._staleStateAge}},{key:"clockSkew",get:function get(){return this._clockSkew}},{key:"stateStore",get:function get(){return this._stateStore}},{key:"validator",get:function get(){return this._validator}},{key:"metadataService",get:function get(){return this._metadataService}},{key:"extraQueryParams",get:function get(){return this._extraQueryParams},set:function set(e){"object"===(void 0===e?"undefined":n(e))?this._extraQueryParams=e:this._extraQueryParams={}}}]),OidcClientSettings}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CordovaPopupWindow=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:o.Global.XMLHttpRequest,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:i.MetadataService;if(function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,TokenRevocationClient),!e)throw n.Log.error("TokenRevocationClient.ctor: No settings provided"),new Error("No settings provided.");this._settings=e,this._XMLHttpRequestCtor=t,this._metadataService=new r(this._settings)}return TokenRevocationClient.prototype.revoke=function revoke(e,t){var r=this;if(!e)throw n.Log.error("TokenRevocationClient.revoke: No accessToken provided"),new Error("No accessToken provided.");return this._metadataService.getRevocationEndpoint().then(function(i){if(i){n.Log.error("TokenRevocationClient.revoke: Revoking access token");var o=r._settings.client_id,s=r._settings.client_secret;return r._revoke(i,o,s,e)}if(t)throw n.Log.error("TokenRevocationClient.revoke: Revocation not supported"),new Error("Revocation not supported")})},TokenRevocationClient.prototype._revoke=function _revoke(e,t,r,i){var o=this;return new Promise(function(s,a){var u=new o._XMLHttpRequestCtor;u.open("POST",e),u.onload=function(){n.Log.debug("TokenRevocationClient.revoke: HTTP response received, status",u.status),200===u.status?s():a(Error(u.statusText+" ("+u.status+")"))};var c="client_id="+encodeURIComponent(t);r&&(c+="&client_secret="+encodeURIComponent(r)),c+="&token_type_hint="+encodeURIComponent("access_token"),c+="&token="+encodeURIComponent(i),u.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),u.send(c)})},TokenRevocationClient}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CheckSessionIFrame=void 0;var n=r(0);var i=2e3;t.CheckSessionIFrame=function(){function CheckSessionIFrame(e,t,r,n){var o=!(arguments.length>4&&void 0!==arguments[4])||arguments[4];!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,CheckSessionIFrame),this._callback=e,this._client_id=t,this._url=r,this._interval=n||i,this._stopOnError=o;var s=r.indexOf("/",r.indexOf("//")+2);this._frame_origin=r.substr(0,s),this._frame=window.document.createElement("iframe"),this._frame.style.visibility="hidden",this._frame.style.position="absolute",this._frame.style.display="none",this._frame.style.width=0,this._frame.style.height=0,this._frame.src=r}return CheckSessionIFrame.prototype.load=function load(){var e=this;return new Promise(function(t){e._frame.onload=function(){t()},window.document.body.appendChild(e._frame),e._boundMessageEvent=e._message.bind(e),window.addEventListener("message",e._boundMessageEvent,!1)})},CheckSessionIFrame.prototype._message=function _message(e){e.origin===this._frame_origin&&e.source===this._frame.contentWindow&&("error"===e.data?(n.Log.error("CheckSessionIFrame: error message from check session op iframe"),this._stopOnError&&this.stop()):"changed"===e.data?(n.Log.debug("CheckSessionIFrame: changed message from check session op iframe"),this.stop(),this._callback()):n.Log.debug("CheckSessionIFrame: "+e.data+" message from check session op iframe"))},CheckSessionIFrame.prototype.start=function start(e){var t=this;if(this._session_state!==e){n.Log.debug("CheckSessionIFrame.start"),this.stop(),this._session_state=e;var r=function send(){t._frame.contentWindow.postMessage(t._client_id+" "+t._session_state,t._frame_origin)};r(),this._timer=window.setInterval(r,this._interval)}},CheckSessionIFrame.prototype.stop=function stop(){this._session_state=null,this._timer&&(n.Log.debug("CheckSessionIFrame.stop"),window.clearInterval(this._timer),this._timer=null)},CheckSessionIFrame}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SessionMonitor=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:o.CheckSessionIFrame;if(function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,SessionMonitor),!e)throw i.Log.error("SessionMonitor.ctor: No user manager passed to SessionMonitor"),new Error("userManager");this._userManager=e,this._CheckSessionIFrameCtor=r,this._userManager.events.addUserLoaded(this._start.bind(this)),this._userManager.events.addUserUnloaded(this._stop.bind(this)),this._userManager.getUser().then(function(e){e&&t._start(e)}).catch(function(e){i.Log.error("SessionMonitor ctor: error from getUser:",e.message)})}return SessionMonitor.prototype._start=function _start(e){var t=this,r=e.session_state;r&&(this._sub=e.profile.sub,this._sid=e.profile.sid,i.Log.debug("SessionMonitor._start: session_state:",r,", sub:",this._sub),this._checkSessionIFrame?this._checkSessionIFrame.start(r):this._metadataService.getCheckSessionIframe().then(function(e){if(e){i.Log.debug("SessionMonitor._start: Initializing check session iframe");var n=t._client_id,o=t._checkSessionInterval,s=t._stopCheckSessionOnError;t._checkSessionIFrame=new t._CheckSessionIFrameCtor(t._callback.bind(t),n,e,o,s),t._checkSessionIFrame.load().then(function(){t._checkSessionIFrame.start(r)})}else i.Log.warn("SessionMonitor._start: No check session iframe found in the metadata")}).catch(function(e){i.Log.error("SessionMonitor._start: Error from getCheckSessionIframe:",e.message)}))},SessionMonitor.prototype._stop=function _stop(){this._sub=null,this._sid=null,this._checkSessionIFrame&&(i.Log.debug("SessionMonitor._stop"),this._checkSessionIFrame.stop())},SessionMonitor.prototype._callback=function _callback(){var e=this;this._userManager.querySessionStatus().then(function(t){var r=!0;t?t.sub===e._sub?(r=!1,e._checkSessionIFrame.start(t.session_state),t.sid===e._sid?i.Log.debug("SessionMonitor._callback: Same sub still logged in at OP, restarting check session iframe; session_state:",t.session_state):(i.Log.debug("SessionMonitor._callback: Same sub still logged in at OP, session state has changed, restarting check session iframe; session_state:",t.session_state),e._userManager.events._raiseUserSessionChanged())):i.Log.debug("SessionMonitor._callback: Different subject signed into OP:",t.sub):i.Log.debug("SessionMonitor._callback: Subject no longer signed into OP"),r&&(i.Log.debug("SessionMonitor._callback: SessionMonitor._callback; raising signed out event"),e._userManager.events._raiseUserSignedOut())}).catch(function(t){i.Log.debug("SessionMonitor._callback: Error calling queryCurrentSigninSession; raising signed out event",t.message),e._userManager.events._raiseUserSignedOut()})},n(SessionMonitor,[{key:"_settings",get:function get(){return this._userManager.settings}},{key:"_metadataService",get:function get(){return this._userManager.metadataService}},{key:"_client_id",get:function get(){return this._settings.client_id}},{key:"_checkSessionInterval",get:function get(){return this._settings.checkSessionInterval}},{key:"_stopCheckSessionOnError",get:function get(){return this._settings.stopCheckSessionOnError}}]),SessionMonitor}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Event=void 0;var n=r(0);t.Event=function(){function Event(e){!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,Event),this._name=e,this._callbacks=[]}return Event.prototype.addHandler=function addHandler(e){this._callbacks.push(e)},Event.prototype.removeHandler=function removeHandler(e){var t=this._callbacks.findIndex(function(t){return t===e});t>=0&&this._callbacks.splice(t,1)},Event.prototype.raise=function raise(){n.Log.debug("Event: Raising event: "+this._name);for(var e=0;e0&&void 0!==arguments[0]?arguments[0]:{},t=e.accessTokenExpiringNotificationTime,r=void 0===t?o:t,n=e.accessTokenExpiringTimer,s=void 0===n?new i.Timer("Access token expiring"):n,a=e.accessTokenExpiredTimer,u=void 0===a?new i.Timer("Access token expired"):a;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,AccessTokenEvents),this._accessTokenExpiringNotificationTime=r,this._accessTokenExpiring=s,this._accessTokenExpired=u}return AccessTokenEvents.prototype.load=function load(e){if(e.access_token&&void 0!==e.expires_in){var t=e.expires_in;if(n.Log.debug("AccessTokenEvents.load: access token present, remaining duration:",t),t>0){var r=t-this._accessTokenExpiringNotificationTime;r<=0&&(r=1),n.Log.debug("AccessTokenEvents.load: registering expiring timer in:",r),this._accessTokenExpiring.init(r)}else n.Log.debug("AccessTokenEvents.load: canceling existing expiring timer becase we're past expiration."),this._accessTokenExpiring.cancel();var i=t+1;n.Log.debug("AccessTokenEvents.load: registering expired timer in:",i),this._accessTokenExpired.init(i)}else this._accessTokenExpiring.cancel(),this._accessTokenExpired.cancel()},AccessTokenEvents.prototype.unload=function unload(){n.Log.debug("AccessTokenEvents.unload: canceling existing access token timers"),this._accessTokenExpiring.cancel(),this._accessTokenExpired.cancel()},AccessTokenEvents.prototype.addAccessTokenExpiring=function addAccessTokenExpiring(e){this._accessTokenExpiring.addHandler(e)},AccessTokenEvents.prototype.removeAccessTokenExpiring=function removeAccessTokenExpiring(e){this._accessTokenExpiring.removeHandler(e)},AccessTokenEvents.prototype.addAccessTokenExpired=function addAccessTokenExpired(e){this._accessTokenExpired.addHandler(e)},AccessTokenEvents.prototype.removeAccessTokenExpired=function removeAccessTokenExpired(e){this._accessTokenExpired.removeHandler(e)},AccessTokenEvents}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.User=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},r=t.nonce,n=t.authority,i=t.client_id;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,SigninState);var o=function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this,arguments[0]));return!0===r?o._nonce=(0,s.default)():r&&(o._nonce=r),o._authority=n,o._client_id=i,o}return function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(SigninState,e),SigninState.prototype.toStorageString=function toStorageString(){return i.Log.debug("SigninState.toStorageString"),JSON.stringify({id:this.id,data:this.data,created:this.created,nonce:this.nonce,authority:this.authority,client_id:this.client_id})},SigninState.fromStorageString=function fromStorageString(e){return i.Log.debug("SigninState.fromStorageString"),new SigninState(JSON.parse(e))},n(SigninState,[{key:"nonce",get:function get(){return this._nonce}},{key:"authority",get:function get(){return this._authority}},{key:"client_id",get:function get(){return this._client_id}}]),SigninState}(o.State)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ErrorResponse=void 0;var n=r(0);t.ErrorResponse=function(e){function ErrorResponse(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=t.error,i=t.error_description,o=t.error_uri,s=t.state;if(function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,ErrorResponse),!r)throw n.Log.error("No error passed to ErrorResponse"),new Error("error");var a=function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this,i||r));return a.name="ErrorResponse",a.error=r,a.error_description=i,a.error_uri=o,a.state=s,a}return function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(ErrorResponse,e),ErrorResponse}(Error)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.JsonService=void 0;var n=r(0),i=r(1);t.JsonService=function(){function JsonService(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:i.Global.XMLHttpRequest;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,JsonService),e&&Array.isArray(e)?this._contentTypes=e.slice():this._contentTypes=[],this._contentTypes.push("application/json"),this._XMLHttpRequest=t}return JsonService.prototype.getJson=function getJson(e,t){var r=this;if(!e)throw n.Log.error("JsonService.getJson: No url passed"),new Error("url");return n.Log.debug("JsonService.getJson, url: ",e),new Promise(function(i,o){var s=new r._XMLHttpRequest;s.open("GET",e);var a=r._contentTypes;s.onload=function(){if(n.Log.debug("JsonService.getJson: HTTP response received, status",s.status),200===s.status){var t=s.getResponseHeader("Content-Type");if(t)if(a.find(function(e){if(t.startsWith(e))return!0}))try{return void i(JSON.parse(s.responseText))}catch(e){return n.Log.error("JsonService.getJson: Error parsing JSON response",e.message),void o(e)}o(Error("Invalid response Content-Type: "+t+", from URL: "+e))}else o(Error(s.statusText+" ("+s.status+")"))},s.onerror=function(){n.Log.error("JsonService.getJson: network error"),o(Error("Network Error"))},t&&(n.Log.debug("JsonService.getJson: token passed, setting Authorization header"),s.setRequestHeader("Authorization","Bearer "+t)),s.send()})},JsonService}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OidcClient=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{};!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,OidcClient),e instanceof o.OidcClientSettings?this._settings=e:this._settings=new o.OidcClientSettings(e)}return OidcClient.prototype.createSigninRequest=function createSigninRequest(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=t.response_type,n=t.scope,o=t.redirect_uri,s=t.data,u=t.state,c=t.prompt,h=t.display,f=t.max_age,l=t.ui_locales,g=t.id_token_hint,p=t.login_hint,d=t.acr_values,v=t.resource,y=t.request,m=t.request_uri,S=t.extraQueryParams,F=arguments[1];i.Log.debug("OidcClient.createSigninRequest");var b=this._settings.client_id;r=r||this._settings.response_type,n=n||this._settings.scope,o=o||this._settings.redirect_uri,c=c||this._settings.prompt,h=h||this._settings.display,f=f||this._settings.max_age,l=l||this._settings.ui_locales,d=d||this._settings.acr_values,v=v||this._settings.resource,S=S||this._settings.extraQueryParams;var _=this._settings.authority;return this._metadataService.getAuthorizationEndpoint().then(function(t){i.Log.debug("OidcClient.createSigninRequest: Received authorization endpoint",t);var w=new a.SigninRequest({url:t,client_id:b,redirect_uri:o,response_type:r,scope:n,data:s||u,authority:_,prompt:c,display:h,max_age:f,ui_locales:l,id_token_hint:g,login_hint:p,acr_values:d,resource:v,request:y,request_uri:m,extraQueryParams:S}),E=w.state;return(F=F||e._stateStore).set(E.id,E.toStorageString()).then(function(){return w})})},OidcClient.prototype.processSigninResponse=function processSigninResponse(e,t){var r=this;i.Log.debug("OidcClient.processSigninResponse");var n=new u.SigninResponse(e);return n.state?(t=t||this._stateStore).remove(n.state).then(function(e){if(!e)throw i.Log.error("OidcClient.processSigninResponse: No matching state found in storage"),new Error("No matching state found in storage");var t=f.SigninState.fromStorageString(e);return i.Log.debug("OidcClient.processSigninResponse: Received state from storage; validating response"),r._validator.validateSigninResponse(t,n)}):(i.Log.error("OidcClient.processSigninResponse: No state in response"),Promise.reject(new Error("No state in response")))},OidcClient.prototype.createSignoutRequest=function createSignoutRequest(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=t.id_token_hint,n=t.data,o=t.state,s=t.post_logout_redirect_uri,a=arguments[1];return i.Log.debug("OidcClient.createSignoutRequest"),s=s||this._settings.post_logout_redirect_uri,this._metadataService.getEndSessionEndpoint().then(function(t){if(!t)throw i.Log.error("OidcClient.createSignoutRequest: No end session endpoint url returned"),new Error("no end session endpoint");i.Log.debug("OidcClient.createSignoutRequest: Received end session endpoint",t);var u=new c.SignoutRequest({url:t,id_token_hint:r,post_logout_redirect_uri:s,data:n||o}),h=u.state;return h&&(i.Log.debug("OidcClient.createSignoutRequest: Signout request has state to persist"),(a=a||e._stateStore).set(h.id,h.toStorageString())),u})},OidcClient.prototype.processSignoutResponse=function processSignoutResponse(e,t){var r=this;i.Log.debug("OidcClient.processSignoutResponse");var n=new h.SignoutResponse(e);if(!n.state)return i.Log.debug("OidcClient.processSignoutResponse: No state in response"),n.error?(i.Log.warn("OidcClient.processSignoutResponse: Response was error: ",n.error),Promise.reject(new s.ErrorResponse(n))):Promise.resolve(n);var o=n.state;return(t=t||this._stateStore).remove(o).then(function(e){if(!e)throw i.Log.error("OidcClient.processSignoutResponse: No matching state found in storage"),new Error("No matching state found in storage");var t=l.State.fromStorageString(e);return i.Log.debug("OidcClient.processSignoutResponse: Received state from storage; validating response"),r._validator.validateSignoutResponse(t,n)})},OidcClient.prototype.clearStaleState=function clearStaleState(e){return i.Log.debug("OidcClient.clearStaleState"),e=e||this._stateStore,l.State.clearStaleState(e,this.settings.staleStateAge)},n(OidcClient,[{key:"_stateStore",get:function get(){return this.settings.stateStore}},{key:"_validator",get:function get(){return this.settings.validator}},{key:"_metadataService",get:function get(){return this.settings.metadataService}},{key:"settings",get:function get(){return this._settings}},{key:"metadataService",get:function get(){return this._metadataService}}]),OidcClient}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CordovaIFrameNavigator=void 0;var n=r(7);t.CordovaIFrameNavigator=function(){function CordovaIFrameNavigator(){!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,CordovaIFrameNavigator)}return CordovaIFrameNavigator.prototype.prepare=function prepare(e){e.popupWindowFeatures="hidden=yes";var t=new n.CordovaPopupWindow(e);return Promise.resolve(t)},CordovaIFrameNavigator}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CordovaPopupNavigator=void 0;var n=r(7);t.CordovaPopupNavigator=function(){function CordovaPopupNavigator(){!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,CordovaPopupNavigator)}return CordovaPopupNavigator.prototype.prepare=function prepare(e){var t=new n.CordovaPopupWindow(e);return Promise.resolve(t)},CordovaPopupNavigator}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SilentRenewService=void 0;var n=r(0);t.SilentRenewService=function(){function SilentRenewService(e){!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,SilentRenewService),this._userManager=e}return SilentRenewService.prototype.start=function start(){this._callback||(this._callback=this._tokenExpiring.bind(this),this._userManager.events.addAccessTokenExpiring(this._callback),this._userManager.getUser().then(function(e){}).catch(function(e){n.Log.error("SilentRenewService.start: Error from getUser:",e.message)}))},SilentRenewService.prototype.stop=function stop(){this._callback&&(this._userManager.events.removeAccessTokenExpiring(this._callback),delete this._callback)},SilentRenewService.prototype._tokenExpiring=function _tokenExpiring(){var e=this;this._userManager.signinSilent().then(function(e){n.Log.debug("SilentRenewService._tokenExpiring: Silent token renewal successful")},function(t){n.Log.error("SilentRenewService._tokenExpiring: Error from signinSilent:",t.message),e._userManager.events._raiseSilentRenewError(t)})},SilentRenewService}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Timer=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:o.Global.timer,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,Timer);var i=function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this,t));return i._timer=r,i._nowFunc=n||function(){return Date.now()/1e3},i}return function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(Timer,e),Timer.prototype.init=function init(e){e<=0&&(e=1),e=parseInt(e);var t=this.now+e;if(this.expiration===t&&this._timerHandle)i.Log.debug("Timer.init timer "+this._name+" skipping initialization since already initialized for expiration:",this.expiration);else{this.cancel(),i.Log.debug("Timer.init timer "+this._name+" for duration:",e),this._expiration=t;var r=5;e1&&void 0!==arguments[1])||arguments[1];n.Log.debug("UserManagerEvents.load"),e.prototype.load.call(this,t),r&&this._userLoaded.raise(t)},UserManagerEvents.prototype.unload=function unload(){n.Log.debug("UserManagerEvents.unload"),e.prototype.unload.call(this),this._userUnloaded.raise()},UserManagerEvents.prototype.addUserLoaded=function addUserLoaded(e){this._userLoaded.addHandler(e)},UserManagerEvents.prototype.removeUserLoaded=function removeUserLoaded(e){this._userLoaded.removeHandler(e)},UserManagerEvents.prototype.addUserUnloaded=function addUserUnloaded(e){this._userUnloaded.addHandler(e)},UserManagerEvents.prototype.removeUserUnloaded=function removeUserUnloaded(e){this._userUnloaded.removeHandler(e)},UserManagerEvents.prototype.addSilentRenewError=function addSilentRenewError(e){this._silentRenewError.addHandler(e)},UserManagerEvents.prototype.removeSilentRenewError=function removeSilentRenewError(e){this._silentRenewError.removeHandler(e)},UserManagerEvents.prototype._raiseSilentRenewError=function _raiseSilentRenewError(e){n.Log.debug("UserManagerEvents._raiseSilentRenewError",e.message),this._silentRenewError.raise(e)},UserManagerEvents.prototype.addUserSignedOut=function addUserSignedOut(e){this._userSignedOut.addHandler(e)},UserManagerEvents.prototype.removeUserSignedOut=function removeUserSignedOut(e){this._userSignedOut.removeHandler(e)},UserManagerEvents.prototype._raiseUserSignedOut=function _raiseUserSignedOut(e){n.Log.debug("UserManagerEvents._raiseUserSignedOut"),this._userSignedOut.raise(e)},UserManagerEvents.prototype.addUserSessionChanged=function addUserSessionChanged(e){this._userSessionChanged.addHandler(e)},UserManagerEvents.prototype.removeUserSessionChanged=function removeUserSessionChanged(e){this._userSessionChanged.removeHandler(e)},UserManagerEvents.prototype._raiseUserSessionChanged=function _raiseUserSessionChanged(e){n.Log.debug("UserManagerEvents._raiseUserSessionChanged"),this._userSessionChanged.raise(e)},UserManagerEvents}(i.AccessTokenEvents)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.IFrameWindow=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},r=t.popup_redirect_uri,n=t.popup_post_logout_redirect_uri,i=t.popupWindowFeatures,l=t.popupWindowTarget,g=t.silent_redirect_uri,p=t.silentRequestTimeout,d=t.automaticSilentRenew,v=void 0!==d&&d,y=t.includeIdTokenInSilentRenew,m=void 0===y||y,S=t.monitorSession,F=void 0===S||S,b=t.checkSessionInterval,_=void 0===b?f:b,w=t.stopCheckSessionOnError,E=void 0===w||w,x=t.revokeAccessTokenOnSignout,C=void 0!==x&&x,P=t.accessTokenExpiringNotificationTime,A=void 0===P?h:P,k=t.redirectNavigator,I=void 0===k?new o.RedirectNavigator:k,B=t.popupNavigator,R=void 0===B?new s.PopupNavigator:B,T=t.iframeNavigator,U=void 0===T?new a.IFrameNavigator:T,M=t.userStore,L=void 0===M?new u.WebStorageStateStore({store:c.Global.sessionStorage}):M;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,UserManagerSettings);var D=function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this,arguments[0]));return D._popup_redirect_uri=r,D._popup_post_logout_redirect_uri=n,D._popupWindowFeatures=i,D._popupWindowTarget=l,D._silent_redirect_uri=g,D._silentRequestTimeout=p,D._automaticSilentRenew=!!v,D._includeIdTokenInSilentRenew=m,D._accessTokenExpiringNotificationTime=A,D._monitorSession=F,D._checkSessionInterval=_,D._stopCheckSessionOnError=E,D._revokeAccessTokenOnSignout=C,D._redirectNavigator=I,D._popupNavigator=R,D._iframeNavigator=U,D._userStore=L,D}return function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(UserManagerSettings,e),n(UserManagerSettings,[{key:"popup_redirect_uri",get:function get(){return this._popup_redirect_uri}},{key:"popup_post_logout_redirect_uri",get:function get(){return this._popup_post_logout_redirect_uri}},{key:"popupWindowFeatures",get:function get(){return this._popupWindowFeatures}},{key:"popupWindowTarget",get:function get(){return this._popupWindowTarget}},{key:"silent_redirect_uri",get:function get(){return this._silent_redirect_uri}},{key:"silentRequestTimeout",get:function get(){return this._silentRequestTimeout}},{key:"automaticSilentRenew",get:function get(){return!(!this.silent_redirect_uri||!this._automaticSilentRenew)}},{key:"includeIdTokenInSilentRenew",get:function get(){return this._includeIdTokenInSilentRenew}},{key:"accessTokenExpiringNotificationTime",get:function get(){return this._accessTokenExpiringNotificationTime}},{key:"monitorSession",get:function get(){return this._monitorSession}},{key:"checkSessionInterval",get:function get(){return this._checkSessionInterval}},{key:"stopCheckSessionOnError",get:function get(){return this._stopCheckSessionOnError}},{key:"revokeAccessTokenOnSignout",get:function get(){return this._revokeAccessTokenOnSignout}},{key:"redirectNavigator",get:function get(){return this._redirectNavigator}},{key:"popupNavigator",get:function get(){return this._popupNavigator}},{key:"iframeNavigator",get:function get(){return this._iframeNavigator}},{key:"userStore",get:function get(){return this._userStore}}]),UserManagerSettings}(i.OidcClientSettings)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.UserManager=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:c.SilentRenewService,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:h.SessionMonitor,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:f.TokenRevocationClient;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,UserManager),t instanceof s.UserManagerSettings||(t=new s.UserManagerSettings(t));var a=function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this,t));return a._events=new u.UserManagerEvents(t),a._silentRenewService=new r(a),a.settings.automaticSilentRenew&&(i.Log.debug("UserManager.ctor: automaticSilentRenew is configured, setting up silent renew"),a.startSilentRenew()),a.settings.monitorSession&&(i.Log.debug("UserManager.ctor: monitorSession is configured, setting up session monitor"),a._sessionMonitor=new n(a)),a._tokenRevocationClient=new o(a._settings),a}return function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(UserManager,e),UserManager.prototype.getUser=function getUser(){var e=this;return this._loadUser().then(function(t){return t?(i.Log.info("UserManager.getUser: user loaded"),e._events.load(t,!1),t):(i.Log.info("UserManager.getUser: user not found in storage"),null)})},UserManager.prototype.removeUser=function removeUser(){var e=this;return this.storeUser(null).then(function(){i.Log.info("UserManager.removeUser: user removed from storage"),e._events.unload()})},UserManager.prototype.signinRedirect=function signinRedirect(e){return this._signinStart(e,this._redirectNavigator).then(function(){i.Log.info("UserManager.signinRedirect: successful")})},UserManager.prototype.signinRedirectCallback=function signinRedirectCallback(e){return this._signinEnd(e||this._redirectNavigator.url).then(function(e){return e&&(e.profile&&e.profile.sub?i.Log.info("UserManager.signinRedirectCallback: successful, signed in sub: ",e.profile.sub):i.Log.info("UserManager.signinRedirectCallback: no sub")),e})},UserManager.prototype.signinPopup=function signinPopup(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.redirect_uri||this.settings.popup_redirect_uri||this.settings.redirect_uri;return t?(e.redirect_uri=t,e.display="popup",this._signin(e,this._popupNavigator,{startUrl:t,popupWindowFeatures:e.popupWindowFeatures||this.settings.popupWindowFeatures,popupWindowTarget:e.popupWindowTarget||this.settings.popupWindowTarget}).then(function(e){return e&&(e.profile&&e.profile.sub?i.Log.info("UserManager.signinPopup: signinPopup successful, signed in sub: ",e.profile.sub):i.Log.info("UserManager.signinPopup: no sub")),e})):(i.Log.error("UserManager.signinPopup: No popup_redirect_uri or redirect_uri configured"),Promise.reject(new Error("No popup_redirect_uri or redirect_uri configured")))},UserManager.prototype.signinPopupCallback=function signinPopupCallback(e){return this._signinCallback(e,this._popupNavigator).then(function(e){return e&&(e.profile&&e.profile.sub?i.Log.info("UserManager.signinPopupCallback: successful, signed in sub: ",e.profile.sub):i.Log.info("UserManager.signinPopupCallback: no sub")),e}).catch(function(e){i.Log.error("UserManager.signinPopupCallback error: "+e&&e.message)})},UserManager.prototype.signinSilent=function signinSilent(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=t.redirect_uri||this.settings.silent_redirect_uri;if(!r)return i.Log.error("UserManager.signinSilent: No silent_redirect_uri configured"),Promise.reject(new Error("No silent_redirect_uri configured"));t.redirect_uri=r,t.prompt="none";return(t.id_token_hint||!this.settings.includeIdTokenInSilentRenew?Promise.resolve():this._loadUser().then(function(e){t.id_token_hint=e&&e.id_token})).then(function(){return e._signin(t,e._iframeNavigator,{startUrl:r,silentRequestTimeout:t.silentRequestTimeout||e.settings.silentRequestTimeout})}).then(function(e){return e&&(e.profile&&e.profile.sub?i.Log.info("UserManager.signinSilent: successful, signed in sub: ",e.profile.sub):i.Log.info("UserManager.signinSilent: no sub")),e})},UserManager.prototype.signinSilentCallback=function signinSilentCallback(e){return this._signinCallback(e,this._iframeNavigator).then(function(e){return e&&(e.profile&&e.profile.sub?i.Log.info("UserManager.signinSilentCallback: successful, signed in sub: ",e.profile.sub):i.Log.info("UserManager.signinSilentCallback: no sub")),e})},UserManager.prototype.querySessionStatus=function querySessionStatus(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=t.redirect_uri||this.settings.silent_redirect_uri;return r?(t.redirect_uri=r,t.prompt="none",t.response_type="id_token",t.scope="openid",this._signinStart(t,this._iframeNavigator,{startUrl:r,silentRequestTimeout:t.silentRequestTimeout||this.settings.silentRequestTimeout}).then(function(t){return e.processSigninResponse(t.url).then(function(e){if(i.Log.debug("UserManager.querySessionStatus: got signin response"),e.session_state&&e.profile.sub&&e.profile.sid)return i.Log.info("UserManager.querySessionStatus: querySessionStatus success for sub: ",e.profile.sub),{session_state:e.session_state,sub:e.profile.sub,sid:e.profile.sid};i.Log.info("querySessionStatus successful, user not authenticated")})})):(i.Log.error("UserManager.querySessionStatus: No silent_redirect_uri configured"),Promise.reject(new Error("No silent_redirect_uri configured")))},UserManager.prototype._signin=function _signin(e,t){var r=this,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this._signinStart(e,t,n).then(function(e){return r._signinEnd(e.url)})},UserManager.prototype._signinStart=function _signinStart(e,t){var r=this,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return t.prepare(n).then(function(t){return i.Log.debug("UserManager._signinStart: got navigator window handle"),r.createSigninRequest(e).then(function(e){return i.Log.debug("UserManager._signinStart: got signin request"),n.url=e.url,n.id=e.state.id,t.navigate(n)}).catch(function(e){throw t.close&&(i.Log.debug("UserManager._signinStart: Error after preparing navigator, closing navigator window"),t.close()),e})})},UserManager.prototype._signinEnd=function _signinEnd(e){var t=this;return this.processSigninResponse(e).then(function(e){i.Log.debug("UserManager._signinEnd: got signin response");var r=new a.User(e);return t.storeUser(r).then(function(){return i.Log.debug("UserManager._signinEnd: user stored"),t._events.load(r),r})})},UserManager.prototype._signinCallback=function _signinCallback(e,t){return i.Log.debug("UserManager._signinCallback"),t.callback(e)},UserManager.prototype.signoutRedirect=function signoutRedirect(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.post_logout_redirect_uri||this.settings.post_logout_redirect_uri;return t&&(e.post_logout_redirect_uri=t),this._signoutStart(e,this._redirectNavigator).then(function(){i.Log.info("UserManager.signoutRedirect: successful")})},UserManager.prototype.signoutRedirectCallback=function signoutRedirectCallback(e){return this._signoutEnd(e||this._redirectNavigator.url).then(function(e){return i.Log.info("UserManager.signoutRedirectCallback: successful"),e})},UserManager.prototype.signoutPopup=function signoutPopup(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.post_logout_redirect_uri||this.settings.popup_post_logout_redirect_uri||this.settings.post_logout_redirect_uri;return e.post_logout_redirect_uri=t,e.display="popup",e.post_logout_redirect_uri&&(e.state=e.state||{}),this._signout(e,this._popupNavigator,{startUrl:t,popupWindowFeatures:e.popupWindowFeatures||this.settings.popupWindowFeatures,popupWindowTarget:e.popupWindowTarget||this.settings.popupWindowTarget}).then(function(){i.Log.info("UserManager.signinPopup: successful")})},UserManager.prototype.signoutPopupCallback=function signoutPopupCallback(e,t){void 0===t&&"boolean"==typeof e&&(e=null,t=!0);return this._popupNavigator.callback(e,t,"?").then(function(){i.Log.info("UserManager.signoutPopupCallback: successful")})},UserManager.prototype._signout=function _signout(e,t){var r=this,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this._signoutStart(e,t,n).then(function(e){return r._signoutEnd(e.url)})},UserManager.prototype._signoutStart=function _signoutStart(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this,r=arguments[1],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return r.prepare(n).then(function(r){return i.Log.debug("UserManager._signoutStart: got navigator window handle"),t._loadUser().then(function(o){return i.Log.debug("UserManager._signoutStart: loaded current user from storage"),(t._settings.revokeAccessTokenOnSignout?t._revokeInternal(o):Promise.resolve()).then(function(){var s=e.id_token_hint||o&&o.id_token;return s&&(i.Log.debug("UserManager._signoutStart: Setting id_token into signout request"),e.id_token_hint=s),t.removeUser().then(function(){return i.Log.debug("UserManager._signoutStart: user removed, creating signout request"),t.createSignoutRequest(e).then(function(e){return i.Log.debug("UserManager._signoutStart: got signout request"),n.url=e.url,e.state&&(n.id=e.state.id),r.navigate(n)})})})}).catch(function(e){throw r.close&&(i.Log.debug("UserManager._signoutStart: Error after preparing navigator, closing navigator window"),r.close()),e})})},UserManager.prototype._signoutEnd=function _signoutEnd(e){return this.processSignoutResponse(e).then(function(e){return i.Log.debug("UserManager._signoutEnd: got signout response"),e})},UserManager.prototype.revokeAccessToken=function revokeAccessToken(){var e=this;return this._loadUser().then(function(t){return e._revokeInternal(t,!0).then(function(r){if(r)return i.Log.debug("UserManager.revokeAccessToken: removing token properties from user and re-storing"),t.access_token=null,t.expires_at=null,t.token_type=null,e.storeUser(t).then(function(){i.Log.debug("UserManager.revokeAccessToken: user stored"),e._events.load(t)})})}).then(function(){i.Log.info("UserManager.revokeAccessToken: access token revoked successfully")})},UserManager.prototype._revokeInternal=function _revokeInternal(e,t){var r=e&&e.access_token;return!r||r.indexOf(".")>=0?(i.Log.debug("UserManager.revokeAccessToken: no need to revoke due to no user, token, or JWT format"),Promise.resolve(!1)):this._tokenRevocationClient.revoke(r,t).then(function(){return!0})},UserManager.prototype.startSilentRenew=function startSilentRenew(){this._silentRenewService.start()},UserManager.prototype.stopSilentRenew=function stopSilentRenew(){this._silentRenewService.stop()},UserManager.prototype._loadUser=function _loadUser(){return this._userStore.get(this._userStoreKey).then(function(e){return e?(i.Log.debug("UserManager._loadUser: user storageString loaded"),a.User.fromStorageString(e)):(i.Log.debug("UserManager._loadUser: no user storageString"),null)})},UserManager.prototype.storeUser=function storeUser(e){if(e){i.Log.debug("UserManager.storeUser: storing user");var t=e.toStorageString();return this._userStore.set(this._userStoreKey,t)}return i.Log.debug("storeUser.storeUser: removing user"),this._userStore.remove(this._userStoreKey)},n(UserManager,[{key:"_redirectNavigator",get:function get(){return this.settings.redirectNavigator}},{key:"_popupNavigator",get:function get(){return this.settings.popupNavigator}},{key:"_iframeNavigator",get:function get(){return this.settings.iframeNavigator}},{key:"_userStore",get:function get(){return this.settings.userStore}},{key:"events",get:function get(){return this._events}},{key:"_userStoreKey",get:function get(){return"user:"+this.settings.authority+":"+this.settings.client_id}}]),UserManager}(o.OidcClient)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InMemoryWebStorage=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r0){var n=parseInt(Date.now()/1e3);this.expires_at=n+r}}return n(SigninResponse,[{key:"expires_in",get:function get(){if(this.expires_at){var e=parseInt(Date.now()/1e3);return this.expires_at-e}}},{key:"expired",get:function get(){var e=this.expires_in;if(void 0!==e)return e<=0}},{key:"scopes",get:function get(){return(this.scope||"").split(" ")}},{key:"isOpenIdConnect",get:function get(){return this.scopes.indexOf("openid")>=0||!!this.id_token}}]),SigninResponse}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SigninRequest=void 0;var n=r(0),i=r(2),o=r(15);t.SigninRequest=function(){function SigninRequest(e){var t=e.url,r=e.client_id,s=e.redirect_uri,a=e.response_type,u=e.scope,c=e.authority,h=e.data,f=e.prompt,l=e.display,g=e.max_age,p=e.ui_locales,d=e.id_token_hint,v=e.login_hint,y=e.acr_values,m=e.resource,S=e.request,F=e.request_uri,b=e.extraQueryParams;if(function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,SigninRequest),!t)throw n.Log.error("SigninRequest.ctor: No url passed"),new Error("url");if(!r)throw n.Log.error("SigninRequest.ctor: No client_id passed"),new Error("client_id");if(!s)throw n.Log.error("SigninRequest.ctor: No redirect_uri passed"),new Error("redirect_uri");if(!a)throw n.Log.error("SigninRequest.ctor: No response_type passed"),new Error("response_type");if(!u)throw n.Log.error("SigninRequest.ctor: No scope passed"),new Error("scope");if(!c)throw n.Log.error("SigninRequest.ctor: No authority passed"),new Error("authority");var _=SigninRequest.isOidc(a);this.state=new o.SigninState({nonce:_,data:h,client_id:r,authority:c}),t=i.UrlUtility.addQueryParam(t,"client_id",r),t=i.UrlUtility.addQueryParam(t,"redirect_uri",s),t=i.UrlUtility.addQueryParam(t,"response_type",a),t=i.UrlUtility.addQueryParam(t,"scope",u),t=i.UrlUtility.addQueryParam(t,"state",this.state.id),_&&(t=i.UrlUtility.addQueryParam(t,"nonce",this.state.nonce));var w={prompt:f,display:l,max_age:g,ui_locales:p,id_token_hint:d,login_hint:v,acr_values:y,resource:m,request:S,request_uri:F};for(var E in w)w[E]&&(t=i.UrlUtility.addQueryParam(t,E,w[E]));for(var x in b)t=i.UrlUtility.addQueryParam(t,x,b[x]);this.url=t}return SigninRequest.isOidc=function isOidc(e){return!!e.split(/\s+/g).filter(function(e){return"id_token"===e})[0]},SigninRequest.isOAuth=function isOAuth(e){return!!e.split(/\s+/g).filter(function(e){return"token"===e})[0]},SigninRequest}()},function(e,t){var r={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==r.call(e)}},function(e,t){t.read=function(e,t,r,n,i){var o,s,a=8*i-n-1,u=(1<>1,h=-7,f=r?i-1:0,l=r?-1:1,g=e[t+f];for(f+=l,o=g&(1<<-h)-1,g>>=-h,h+=a;h>0;o=256*o+e[t+f],f+=l,h-=8);for(s=o&(1<<-h)-1,o>>=-h,h+=n;h>0;s=256*s+e[t+f],f+=l,h-=8);if(0===o)o=1-c;else{if(o===u)return s?NaN:1/0*(g?-1:1);s+=Math.pow(2,n),o-=c}return(g?-1:1)*s*Math.pow(2,o-n)},t.write=function(e,t,r,n,i,o){var s,a,u,c=8*o-i-1,h=(1<>1,l=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,g=n?0:o-1,p=n?1:-1,d=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,s=h):(s=Math.floor(Math.log(t)/Math.LN2),t*(u=Math.pow(2,-s))<1&&(s--,u*=2),(t+=s+f>=1?l/u:l*Math.pow(2,1-f))*u>=2&&(s++,u/=2),s+f>=h?(a=0,s=h):s+f>=1?(a=(t*u-1)*Math.pow(2,i),s+=f):(a=t*Math.pow(2,f-1)*Math.pow(2,i),s=0));i>=8;e[r+g]=255&a,g+=p,a/=256,i-=8);for(s=s<0;e[r+g]=255&s,g+=p,s/=256,c-=8);e[r+g-p]|=128*d}},function(e,t,r){"use strict";t.byteLength=function byteLength(e){var t=getLens(e),r=t[0],n=t[1];return 3*(r+n)/4-n},t.toByteArray=function toByteArray(e){for(var t,r=getLens(e),n=r[0],s=r[1],a=new o(function _byteLength(e,t,r){return 3*(t+r)/4-r}(0,n,s)),u=0,c=s>0?n-4:n,h=0;h>16&255,a[u++]=t>>8&255,a[u++]=255&t;2===s&&(t=i[e.charCodeAt(h)]<<2|i[e.charCodeAt(h+1)]>>4,a[u++]=255&t);1===s&&(t=i[e.charCodeAt(h)]<<10|i[e.charCodeAt(h+1)]<<4|i[e.charCodeAt(h+2)]>>2,a[u++]=t>>8&255,a[u++]=255&t);return a},t.fromByteArray=function fromByteArray(e){for(var t,r=e.length,i=r%3,o=[],s=0,a=r-i;sa?a:s+16383));1===i?(t=e[r-1],o.push(n[t>>2]+n[t<<4&63]+"==")):2===i&&(t=(e[r-2]<<8)+e[r-1],o.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"="));return o.join("")};for(var n=[],i=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,u=s.length;a0)throw new Error("Invalid string. Length must be a multiple of 4");var r=e.indexOf("=");return-1===r&&(r=t),[r,r===t?0:4-r%4]}function tripletToBase64(e){return n[e>>18&63]+n[e>>12&63]+n[e>>6&63]+n[63&e]}function encodeChunk(e,t,r){for(var n,i=[],o=t;o - * @license MIT - */ - var n=r(38),i=r(37),o=r(36);function kMaxLength(){return Buffer.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function createBuffer(e,t){if(kMaxLength()=kMaxLength())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+kMaxLength().toString(16)+" bytes");return 0|e}function byteLength(e,t){if(Buffer.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var r=e.length;if(0===r)return 0;for(var n=!1;;)switch(t){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":case void 0:return utf8ToBytes(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return base64ToBytes(e).length;default:if(n)return utf8ToBytes(e).length;t=(""+t).toLowerCase(),n=!0}}function swap(e,t,r){var n=e[t];e[t]=e[r],e[r]=n}function bidirectionalIndexOf(e,t,r,n,i){if(0===e.length)return-1;if("string"==typeof r?(n=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),r=+r,isNaN(r)&&(r=i?0:e.length-1),r<0&&(r=e.length+r),r>=e.length){if(i)return-1;r=e.length-1}else if(r<0){if(!i)return-1;r=0}if("string"==typeof t&&(t=Buffer.from(t,n)),Buffer.isBuffer(t))return 0===t.length?-1:arrayIndexOf(e,t,r,n,i);if("number"==typeof t)return t&=255,Buffer.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,r):Uint8Array.prototype.lastIndexOf.call(e,t,r):arrayIndexOf(e,[t],r,n,i);throw new TypeError("val must be string, number or Buffer")}function arrayIndexOf(e,t,r,n,i){var o,s=1,a=e.length,u=t.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(e.length<2||t.length<2)return-1;s=2,a/=2,u/=2,r/=2}function read(e,t){return 1===s?e[t]:e.readUInt16BE(t*s)}if(i){var c=-1;for(o=r;oa&&(r=a-u),o=r;o>=0;o--){for(var h=!0,f=0;fi&&(n=i):n=i;var o=t.length;if(o%2!=0)throw new TypeError("Invalid hex string");n>o/2&&(n=o/2);for(var s=0;s>8,i=r%256,o.push(i),o.push(n);return o}(t,e.length-r),e,r,n)}function base64Slice(e,t,r){return 0===t&&r===e.length?n.fromByteArray(e):n.fromByteArray(e.slice(t,r))}function utf8Slice(e,t,r){r=Math.min(e.length,r);for(var n=[],i=t;i239?4:h>223?3:h>191?2:1;if(i+l<=r)switch(l){case 1:h<128&&(f=h);break;case 2:128==(192&(o=e[i+1]))&&(c=(31&h)<<6|63&o)>127&&(f=c);break;case 3:o=e[i+1],a=e[i+2],128==(192&o)&&128==(192&a)&&(c=(15&h)<<12|(63&o)<<6|63&a)>2047&&(c<55296||c>57343)&&(f=c);break;case 4:o=e[i+1],a=e[i+2],u=e[i+3],128==(192&o)&&128==(192&a)&&128==(192&u)&&(c=(15&h)<<18|(63&o)<<12|(63&a)<<6|63&u)>65535&&c<1114112&&(f=c)}null===f?(f=65533,l=1):f>65535&&(f-=65536,n.push(f>>>10&1023|55296),f=56320|1023&f),n.push(f),i+=l}return function decodeCodePointsArray(e){var t=e.length;if(t<=s)return String.fromCharCode.apply(String,e);var r="",n=0;for(;nthis.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return hexSlice(this,t,r);case"utf8":case"utf-8":return utf8Slice(this,t,r);case"ascii":return asciiSlice(this,t,r);case"latin1":case"binary":return latin1Slice(this,t,r);case"base64":return base64Slice(this,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return utf16leSlice(this,t,r);default:if(n)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),n=!0}}.apply(this,arguments)},Buffer.prototype.equals=function equals(e){if(!Buffer.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===Buffer.compare(this,e)},Buffer.prototype.inspect=function inspect(){var e="",r=t.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,r).match(/.{2}/g).join(" "),this.length>r&&(e+=" ... ")),""},Buffer.prototype.compare=function compare(e,t,r,n,i){if(!Buffer.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===n&&(n=0),void 0===i&&(i=this.length),t<0||r>e.length||n<0||i>this.length)throw new RangeError("out of range index");if(n>=i&&t>=r)return 0;if(n>=i)return-1;if(t>=r)return 1;if(t>>>=0,r>>>=0,n>>>=0,i>>>=0,this===e)return 0;for(var o=i-n,s=r-t,a=Math.min(o,s),u=this.slice(n,i),c=e.slice(t,r),h=0;hi)&&(r=i),e.length>0&&(r<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var o=!1;;)switch(n){case"hex":return hexWrite(this,e,t,r);case"utf8":case"utf-8":return utf8Write(this,e,t,r);case"ascii":return asciiWrite(this,e,t,r);case"latin1":case"binary":return latin1Write(this,e,t,r);case"base64":return base64Write(this,e,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return ucs2Write(this,e,t,r);default:if(o)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),o=!0}},Buffer.prototype.toJSON=function toJSON(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var s=4096;function asciiSlice(e,t,r){var n="";r=Math.min(e.length,r);for(var i=t;in)&&(r=n);for(var i="",o=t;or)throw new RangeError("Trying to access beyond buffer length")}function checkInt(e,t,r,n,i,o){if(!Buffer.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>i||te.length)throw new RangeError("Index out of range")}function objectWriteUInt16(e,t,r,n){t<0&&(t=65535+t+1);for(var i=0,o=Math.min(e.length-r,2);i>>8*(n?i:1-i)}function objectWriteUInt32(e,t,r,n){t<0&&(t=4294967295+t+1);for(var i=0,o=Math.min(e.length-r,4);i>>8*(n?i:3-i)&255}function checkIEEE754(e,t,r,n,i,o){if(r+n>e.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function writeFloat(e,t,r,n,o){return o||checkIEEE754(e,0,r,4),i.write(e,t,r,n,23,4),r+4}function writeDouble(e,t,r,n,o){return o||checkIEEE754(e,0,r,8),i.write(e,t,r,n,52,8),r+8}Buffer.prototype.slice=function slice(e,t){var r,n=this.length;if(e=~~e,t=void 0===t?n:~~t,e<0?(e+=n)<0&&(e=0):e>n&&(e=n),t<0?(t+=n)<0&&(t=0):t>n&&(t=n),t0&&(i*=256);)n+=this[e+--t]*i;return n},Buffer.prototype.readUInt8=function readUInt8(e,t){return t||checkOffset(e,1,this.length),this[e]},Buffer.prototype.readUInt16LE=function readUInt16LE(e,t){return t||checkOffset(e,2,this.length),this[e]|this[e+1]<<8},Buffer.prototype.readUInt16BE=function readUInt16BE(e,t){return t||checkOffset(e,2,this.length),this[e]<<8|this[e+1]},Buffer.prototype.readUInt32LE=function readUInt32LE(e,t){return t||checkOffset(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},Buffer.prototype.readUInt32BE=function readUInt32BE(e,t){return t||checkOffset(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},Buffer.prototype.readIntLE=function readIntLE(e,t,r){e|=0,t|=0,r||checkOffset(e,t,this.length);for(var n=this[e],i=1,o=0;++o=(i*=128)&&(n-=Math.pow(2,8*t)),n},Buffer.prototype.readIntBE=function readIntBE(e,t,r){e|=0,t|=0,r||checkOffset(e,t,this.length);for(var n=t,i=1,o=this[e+--n];n>0&&(i*=256);)o+=this[e+--n]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*t)),o},Buffer.prototype.readInt8=function readInt8(e,t){return t||checkOffset(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},Buffer.prototype.readInt16LE=function readInt16LE(e,t){t||checkOffset(e,2,this.length);var r=this[e]|this[e+1]<<8;return 32768&r?4294901760|r:r},Buffer.prototype.readInt16BE=function readInt16BE(e,t){t||checkOffset(e,2,this.length);var r=this[e+1]|this[e]<<8;return 32768&r?4294901760|r:r},Buffer.prototype.readInt32LE=function readInt32LE(e,t){return t||checkOffset(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},Buffer.prototype.readInt32BE=function readInt32BE(e,t){return t||checkOffset(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},Buffer.prototype.readFloatLE=function readFloatLE(e,t){return t||checkOffset(e,4,this.length),i.read(this,e,!0,23,4)},Buffer.prototype.readFloatBE=function readFloatBE(e,t){return t||checkOffset(e,4,this.length),i.read(this,e,!1,23,4)},Buffer.prototype.readDoubleLE=function readDoubleLE(e,t){return t||checkOffset(e,8,this.length),i.read(this,e,!0,52,8)},Buffer.prototype.readDoubleBE=function readDoubleBE(e,t){return t||checkOffset(e,8,this.length),i.read(this,e,!1,52,8)},Buffer.prototype.writeUIntLE=function writeUIntLE(e,t,r,n){(e=+e,t|=0,r|=0,n)||checkInt(this,e,t,r,Math.pow(2,8*r)-1,0);var i=1,o=0;for(this[t]=255&e;++o=0&&(o*=256);)this[t+i]=e/o&255;return t+r},Buffer.prototype.writeUInt8=function writeUInt8(e,t,r){return e=+e,t|=0,r||checkInt(this,e,t,1,255,0),Buffer.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},Buffer.prototype.writeUInt16LE=function writeUInt16LE(e,t,r){return e=+e,t|=0,r||checkInt(this,e,t,2,65535,0),Buffer.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):objectWriteUInt16(this,e,t,!0),t+2},Buffer.prototype.writeUInt16BE=function writeUInt16BE(e,t,r){return e=+e,t|=0,r||checkInt(this,e,t,2,65535,0),Buffer.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):objectWriteUInt16(this,e,t,!1),t+2},Buffer.prototype.writeUInt32LE=function writeUInt32LE(e,t,r){return e=+e,t|=0,r||checkInt(this,e,t,4,4294967295,0),Buffer.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):objectWriteUInt32(this,e,t,!0),t+4},Buffer.prototype.writeUInt32BE=function writeUInt32BE(e,t,r){return e=+e,t|=0,r||checkInt(this,e,t,4,4294967295,0),Buffer.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):objectWriteUInt32(this,e,t,!1),t+4},Buffer.prototype.writeIntLE=function writeIntLE(e,t,r,n){if(e=+e,t|=0,!n){var i=Math.pow(2,8*r-1);checkInt(this,e,t,r,i-1,-i)}var o=0,s=1,a=0;for(this[t]=255&e;++o>0)-a&255;return t+r},Buffer.prototype.writeIntBE=function writeIntBE(e,t,r,n){if(e=+e,t|=0,!n){var i=Math.pow(2,8*r-1);checkInt(this,e,t,r,i-1,-i)}var o=r-1,s=1,a=0;for(this[t+o]=255&e;--o>=0&&(s*=256);)e<0&&0===a&&0!==this[t+o+1]&&(a=1),this[t+o]=(e/s>>0)-a&255;return t+r},Buffer.prototype.writeInt8=function writeInt8(e,t,r){return e=+e,t|=0,r||checkInt(this,e,t,1,127,-128),Buffer.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},Buffer.prototype.writeInt16LE=function writeInt16LE(e,t,r){return e=+e,t|=0,r||checkInt(this,e,t,2,32767,-32768),Buffer.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):objectWriteUInt16(this,e,t,!0),t+2},Buffer.prototype.writeInt16BE=function writeInt16BE(e,t,r){return e=+e,t|=0,r||checkInt(this,e,t,2,32767,-32768),Buffer.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):objectWriteUInt16(this,e,t,!1),t+2},Buffer.prototype.writeInt32LE=function writeInt32LE(e,t,r){return e=+e,t|=0,r||checkInt(this,e,t,4,2147483647,-2147483648),Buffer.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):objectWriteUInt32(this,e,t,!0),t+4},Buffer.prototype.writeInt32BE=function writeInt32BE(e,t,r){return e=+e,t|=0,r||checkInt(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),Buffer.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):objectWriteUInt32(this,e,t,!1),t+4},Buffer.prototype.writeFloatLE=function writeFloatLE(e,t,r){return writeFloat(this,e,t,!0,r)},Buffer.prototype.writeFloatBE=function writeFloatBE(e,t,r){return writeFloat(this,e,t,!1,r)},Buffer.prototype.writeDoubleLE=function writeDoubleLE(e,t,r){return writeDouble(this,e,t,!0,r)},Buffer.prototype.writeDoubleBE=function writeDoubleBE(e,t,r){return writeDouble(this,e,t,!1,r)},Buffer.prototype.copy=function copy(e,t,r,n){if(r||(r=0),n||0===n||(n=this.length),t>=e.length&&(t=e.length),t||(t=0),n>0&&n=this.length)throw new RangeError("sourceStart out of bounds");if(n<0)throw new RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),e.length-t=0;--i)e[i+t]=this[i+r];else if(o<1e3||!Buffer.TYPED_ARRAY_SUPPORT)for(i=0;i>>=0,r=void 0===r?this.length:r>>>0,e||(e=0),"number"==typeof e)for(o=t;o55295&&r<57344){if(!i){if(r>56319){(t-=3)>-1&&o.push(239,191,189);continue}if(s+1===n){(t-=3)>-1&&o.push(239,191,189);continue}i=r;continue}if(r<56320){(t-=3)>-1&&o.push(239,191,189),i=r;continue}r=65536+(i-55296<<10|r-56320)}else i&&(t-=3)>-1&&o.push(239,191,189);if(i=null,r<128){if((t-=1)<0)break;o.push(r)}else if(r<2048){if((t-=2)<0)break;o.push(r>>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;o.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return o}function base64ToBytes(e){return n.toByteArray(function base64clean(e){if((e=function stringtrim(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(a,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function blitBuffer(e,t,r,n){for(var i=0;i=t.length||i>=e.length);++i)t[i+r]=e[i];return i}}).call(this,r(39))},function(e,t,r){"use strict";(function(n){var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},u={userAgent:!1},p={}; - /*! +!(function webpackUniversalModuleDefinition(e, t) { + if ('object' == typeof exports && 'object' == typeof module) module.exports = t(); + else if ('function' == typeof define && define.amd) define([], t); + else { + var r = t(); + for (var n in r) ('object' == typeof exports ? exports : e)[n] = r[n]; + } +})(window, function () { + return (function (e) { + var t = {}; + function __webpack_require__(r) { + if (t[r]) return t[r].exports; + var n = (t[r] = { i: r, l: !1, exports: {} }); + return e[r].call(n.exports, n, n.exports, __webpack_require__), (n.l = !0), n.exports; + } + return ( + (__webpack_require__.m = e), + (__webpack_require__.c = t), + (__webpack_require__.d = function (e, t, r) { + __webpack_require__.o(e, t) || Object.defineProperty(e, t, { enumerable: !0, get: r }); + }), + (__webpack_require__.r = function (e) { + 'undefined' != typeof Symbol && + Symbol.toStringTag && + Object.defineProperty(e, Symbol.toStringTag, { value: 'Module' }), + Object.defineProperty(e, '__esModule', { value: !0 }); + }), + (__webpack_require__.t = function (e, t) { + if ((1 & t && (e = __webpack_require__(e)), 8 & t)) return e; + if (4 & t && 'object' == typeof e && e && e.__esModule) return e; + var r = Object.create(null); + if ( + (__webpack_require__.r(r), + Object.defineProperty(r, 'default', { enumerable: !0, value: e }), + 2 & t && 'string' != typeof e) + ) + for (var n in e) + __webpack_require__.d( + r, + n, + function (t) { + return e[t]; + }.bind(null, n) + ); + return r; + }), + (__webpack_require__.n = function (e) { + var t = + e && e.__esModule + ? function getDefault() { + return e.default; + } + : function getModuleExports() { + return e; + }; + return __webpack_require__.d(t, 'a', t), t; + }), + (__webpack_require__.o = function (e, t) { + return Object.prototype.hasOwnProperty.call(e, t); + }), + (__webpack_require__.p = ''), + __webpack_require__((__webpack_require__.s = 45)) + ); + })([ + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(); + var i = { + debug: function debug() {}, + info: function info() {}, + warn: function warn() {}, + error: function error() {}, + }, + o = void 0, + s = void 0; + (t.Log = (function () { + function Log() { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, Log); + } + return ( + (Log.reset = function reset() { + (s = 3), (o = i); + }), + (Log.debug = function debug() { + if (s >= 4) { + for (var e = arguments.length, t = Array(e), r = 0; r < e; r++) t[r] = arguments[r]; + o.debug.apply(o, Array.from(t)); + } + }), + (Log.info = function info() { + if (s >= 3) { + for (var e = arguments.length, t = Array(e), r = 0; r < e; r++) t[r] = arguments[r]; + o.info.apply(o, Array.from(t)); + } + }), + (Log.warn = function warn() { + if (s >= 2) { + for (var e = arguments.length, t = Array(e), r = 0; r < e; r++) t[r] = arguments[r]; + o.warn.apply(o, Array.from(t)); + } + }), + (Log.error = function error() { + if (s >= 1) { + for (var e = arguments.length, t = Array(e), r = 0; r < e; r++) t[r] = arguments[r]; + o.error.apply(o, Array.from(t)); + } + }), + n(Log, null, [ + { + key: 'NONE', + get: function get() { + return 0; + }, + }, + { + key: 'ERROR', + get: function get() { + return 1; + }, + }, + { + key: 'WARN', + get: function get() { + return 2; + }, + }, + { + key: 'INFO', + get: function get() { + return 3; + }, + }, + { + key: 'DEBUG', + get: function get() { + return 4; + }, + }, + { + key: 'level', + get: function get() { + return s; + }, + set: function set(e) { + if (!(0 <= e && e <= 4)) throw new Error('Invalid log level'); + s = e; + }, + }, + { + key: 'logger', + get: function get() { + return o; + }, + set: function set(e) { + if ( + (!e.debug && e.info && (e.debug = e.info), + !(e.debug && e.info && e.warn && e.error)) + ) + throw new Error('Invalid logger'); + o = e; + }, + }, + ]), + Log + ); + })()).reset(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(); + var i = { + setInterval: (function (e) { + function setInterval(t, r) { + return e.apply(this, arguments); + } + return ( + (setInterval.toString = function () { + return e.toString(); + }), + setInterval + ); + })(function (e, t) { + return setInterval(e, t); + }), + clearInterval: (function (e) { + function clearInterval(t) { + return e.apply(this, arguments); + } + return ( + (clearInterval.toString = function () { + return e.toString(); + }), + clearInterval + ); + })(function (e) { + return clearInterval(e); + }), + }, + o = !1, + s = null; + t.Global = (function () { + function Global() { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, Global); + } + return ( + (Global._testing = function _testing() { + o = !0; + }), + (Global.setXMLHttpRequest = function setXMLHttpRequest(e) { + s = e; + }), + n(Global, null, [ + { + key: 'location', + get: function get() { + if (!o) return location; + }, + }, + { + key: 'localStorage', + get: function get() { + if (!o && 'undefined' != typeof window) return localStorage; + }, + }, + { + key: 'sessionStorage', + get: function get() { + if (!o && 'undefined' != typeof window) return sessionStorage; + }, + }, + { + key: 'XMLHttpRequest', + get: function get() { + if (!o && 'undefined' != typeof window) return s || XMLHttpRequest; + }, + }, + { + key: 'timer', + get: function get() { + if (!o) return i; + }, + }, + ]), + Global + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.UrlUtility = void 0); + var n = r(0), + i = r(1); + t.UrlUtility = (function () { + function UrlUtility() { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, UrlUtility); + } + return ( + (UrlUtility.addQueryParam = function addQueryParam(e, t, r) { + return ( + e.indexOf('?') < 0 && (e += '?'), + '?' !== e[e.length - 1] && (e += '&'), + (e += encodeURIComponent(t)), + (e += '='), + (e += encodeURIComponent(r)) + ); + }), + (UrlUtility.parseUrlFragment = function parseUrlFragment(e) { + var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : '#', + r = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : i.Global; + 'string' != typeof e && (e = r.location.href); + var o = e.lastIndexOf(t); + o >= 0 && (e = e.substr(o + 1)); + for (var s, a = {}, u = /([^&=]+)=([^&]*)/g, c = 0; (s = u.exec(e)); ) + if (((a[decodeURIComponent(s[1])] = decodeURIComponent(s[2])), c++ > 50)) + return ( + n.Log.error( + 'UrlUtility.parseUrlFragment: response exceeded expected number of parameters', + e + ), + { error: 'Response exceeded expected number of parameters' } + ); + for (var h in a) return a; + return {}; + }), + UrlUtility + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.MetadataService = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0), + o = r(17); + t.MetadataService = (function () { + function MetadataService(e) { + var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : o.JsonService; + if ( + ((function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, MetadataService), + !e) + ) + throw ( + (i.Log.error('MetadataService: No settings passed to MetadataService'), + new Error('settings')) + ); + (this._settings = e), (this._jsonService = new t(['application/jwk-set+json'])); + } + return ( + (MetadataService.prototype.getMetadata = function getMetadata() { + var e = this; + return this._settings.metadata + ? (i.Log.debug('MetadataService.getMetadata: Returning metadata from settings'), + Promise.resolve(this._settings.metadata)) + : this.metadataUrl + ? (i.Log.debug( + 'MetadataService.getMetadata: getting metadata from', + this.metadataUrl + ), + this._jsonService.getJson(this.metadataUrl).then(function (t) { + return ( + i.Log.debug('MetadataService.getMetadata: json received'), + (e._settings.metadata = t), + t + ); + })) + : (i.Log.error( + 'MetadataService.getMetadata: No authority or metadataUrl configured on settings' + ), + Promise.reject(new Error('No authority or metadataUrl configured on settings'))); + }), + (MetadataService.prototype.getIssuer = function getIssuer() { + return this._getMetadataProperty('issuer'); + }), + (MetadataService.prototype.getAuthorizationEndpoint = + function getAuthorizationEndpoint() { + return this._getMetadataProperty('authorization_endpoint'); + }), + (MetadataService.prototype.getUserInfoEndpoint = function getUserInfoEndpoint() { + return this._getMetadataProperty('userinfo_endpoint'); + }), + (MetadataService.prototype.getTokenEndpoint = function getTokenEndpoint() { + return this._getMetadataProperty('token_endpoint', !0); + }), + (MetadataService.prototype.getCheckSessionIframe = function getCheckSessionIframe() { + return this._getMetadataProperty('check_session_iframe', !0); + }), + (MetadataService.prototype.getEndSessionEndpoint = function getEndSessionEndpoint() { + return this._getMetadataProperty('end_session_endpoint', !0); + }), + (MetadataService.prototype.getRevocationEndpoint = function getRevocationEndpoint() { + return this._getMetadataProperty('revocation_endpoint', !0); + }), + (MetadataService.prototype._getMetadataProperty = function _getMetadataProperty(e) { + var t = arguments.length > 1 && void 0 !== arguments[1] && arguments[1]; + return ( + i.Log.debug('MetadataService.getMetadataProperty for: ' + e), + this.getMetadata().then(function (r) { + if ( + (i.Log.debug('MetadataService.getMetadataProperty: metadata recieved'), + void 0 === r[e]) + ) { + if (!0 === t) + return void i.Log.warn( + 'MetadataService.getMetadataProperty: Metadata does not contain optional property ' + + e + ); + throw ( + (i.Log.error( + 'MetadataService.getMetadataProperty: Metadata does not contain property ' + e + ), + new Error('Metadata does not contain property ' + e)) + ); + } + return r[e]; + }) + ); + }), + (MetadataService.prototype.getSigningKeys = function getSigningKeys() { + var e = this; + return this._settings.signingKeys + ? (i.Log.debug('MetadataService.getSigningKeys: Returning signingKeys from settings'), + Promise.resolve(this._settings.signingKeys)) + : this._getMetadataProperty('jwks_uri').then(function (t) { + return ( + i.Log.debug('MetadataService.getSigningKeys: jwks_uri received', t), + e._jsonService.getJson(t).then(function (t) { + if ( + (i.Log.debug('MetadataService.getSigningKeys: key set received', t), + !t.keys) + ) + throw ( + (i.Log.error('MetadataService.getSigningKeys: Missing keys on keyset'), + new Error('Missing keys on keyset')) + ); + return (e._settings.signingKeys = t.keys), e._settings.signingKeys; + }) + ); + }); + }), + n(MetadataService, [ + { + key: 'metadataUrl', + get: function get() { + return ( + this._metadataUrl || + (this._settings.metadataUrl + ? (this._metadataUrl = this._settings.metadataUrl) + : ((this._metadataUrl = this._settings.authority), + this._metadataUrl && + this._metadataUrl.indexOf('.well-known/openid-configuration') < 0 && + ('/' !== this._metadataUrl[this._metadataUrl.length - 1] && + (this._metadataUrl += '/'), + (this._metadataUrl += '.well-known/openid-configuration')))), + this._metadataUrl + ); + }, + }, + ]), + MetadataService + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.State = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0), + o = (function _interopRequireDefault(e) { + return e && e.__esModule ? e : { default: e }; + })(r(14)); + t.State = (function () { + function State() { + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + t = e.id, + r = e.data, + n = e.created; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, State), + (this._id = t || (0, o.default)()), + (this._data = r), + (this._created = 'number' == typeof n && n > 0 ? n : parseInt(Date.now() / 1e3)); + } + return ( + (State.prototype.toStorageString = function toStorageString() { + return ( + i.Log.debug('State.toStorageString'), + JSON.stringify({ + id: this.id, + data: this.data, + created: this.created, + }) + ); + }), + (State.fromStorageString = function fromStorageString(e) { + return i.Log.debug('State.fromStorageString'), new State(JSON.parse(e)); + }), + (State.clearStaleState = function clearStaleState(e, t) { + var r = Date.now() / 1e3 - t; + return e.getAllKeys().then(function (t) { + i.Log.debug('State.clearStaleState: got keys', t); + for ( + var n = [], + o = function _loop(o) { + var s = t[o]; + (a = e.get(s).then(function (t) { + var n = !1; + if (t) + try { + var o = State.fromStorageString(t); + i.Log.debug('State.clearStaleState: got item from key: ', s, o.created), + o.created <= r && (n = !0); + } catch (e) { + i.Log.error( + 'State.clearStaleState: Error parsing state for key', + s, + e.message + ), + (n = !0); + } + else + i.Log.debug('State.clearStaleState: no item in storage for key: ', s), + (n = !0); + if (n) + return ( + i.Log.debug('State.clearStaleState: removed item for key: ', s), + e.remove(s) + ); + })), + n.push(a); + }, + s = 0; + s < t.length; + s++ + ) { + var a; + o(s); + } + return ( + i.Log.debug('State.clearStaleState: waiting on promise count:', n.length), + Promise.all(n) + ); + }); + }), + n(State, [ + { + key: 'id', + get: function get() { + return this._id; + }, + }, + { + key: 'data', + get: function get() { + return this._data; + }, + }, + { + key: 'created', + get: function get() { + return this._created; + }, + }, + ]), + State + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.WebStorageStateStore = void 0); + var n = r(0), + i = r(1); + t.WebStorageStateStore = (function () { + function WebStorageStateStore() { + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + t = e.prefix, + r = void 0 === t ? 'oidc.' : t, + n = e.store, + o = void 0 === n ? i.Global.localStorage : n; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, WebStorageStateStore), + (this._store = o), + (this._prefix = r); + } + return ( + (WebStorageStateStore.prototype.set = function set(e, t) { + return ( + n.Log.debug('WebStorageStateStore.set', e), + (e = this._prefix + e), + this._store.setItem(e, t), + Promise.resolve() + ); + }), + (WebStorageStateStore.prototype.get = function get(e) { + n.Log.debug('WebStorageStateStore.get', e), (e = this._prefix + e); + var t = this._store.getItem(e); + return Promise.resolve(t); + }), + (WebStorageStateStore.prototype.remove = function remove(e) { + n.Log.debug('WebStorageStateStore.remove', e), (e = this._prefix + e); + var t = this._store.getItem(e); + return this._store.removeItem(e), Promise.resolve(t); + }), + (WebStorageStateStore.prototype.getAllKeys = function getAllKeys() { + n.Log.debug('WebStorageStateStore.getAllKeys'); + for (var e = [], t = 0; t < this._store.length; t++) { + var r = this._store.key(t); + 0 === r.indexOf(this._prefix) && e.push(r.substr(this._prefix.length)); + } + return Promise.resolve(e); + }), + WebStorageStateStore + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.OidcClientSettings = void 0); + var n = + 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator + ? function (e) { + return typeof e; + } + : function (e) { + return e && + 'function' == typeof Symbol && + e.constructor === Symbol && + e !== Symbol.prototype + ? 'symbol' + : typeof e; + }, + i = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + o = r(0), + s = r(5), + a = r(44), + u = r(3); + var c = 'id_token', + h = 'openid', + f = 900, + l = 300; + t.OidcClientSettings = (function () { + function OidcClientSettings() { + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + t = e.authority, + r = e.metadataUrl, + i = e.metadata, + o = e.signingKeys, + g = e.client_id, + p = e.client_secret, + d = e.response_type, + v = void 0 === d ? c : d, + y = e.scope, + m = void 0 === y ? h : y, + S = e.redirect_uri, + F = e.post_logout_redirect_uri, + b = e.prompt, + _ = e.display, + w = e.max_age, + E = e.ui_locales, + x = e.acr_values, + C = e.resource, + P = e.filterProtocolClaims, + A = void 0 === P || P, + k = e.loadUserInfo, + I = void 0 === k || k, + B = e.staleStateAge, + R = void 0 === B ? f : B, + T = e.clockSkew, + U = void 0 === T ? l : T, + M = e.stateStore, + L = void 0 === M ? new s.WebStorageStateStore() : M, + D = e.ResponseValidatorCtor, + N = void 0 === D ? a.ResponseValidator : D, + O = e.MetadataServiceCtor, + H = void 0 === O ? u.MetadataService : O, + j = e.extraQueryParams, + K = void 0 === j ? {} : j; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, OidcClientSettings), + (this._authority = t), + (this._metadataUrl = r), + (this._metadata = i), + (this._signingKeys = o), + (this._client_id = g), + (this._client_secret = p), + (this._response_type = v), + (this._scope = m), + (this._redirect_uri = S), + (this._post_logout_redirect_uri = F), + (this._prompt = b), + (this._display = _), + (this._max_age = w), + (this._ui_locales = E), + (this._acr_values = x), + (this._resource = C), + (this._filterProtocolClaims = !!A), + (this._loadUserInfo = !!I), + (this._staleStateAge = R), + (this._clockSkew = U), + (this._stateStore = L), + (this._validator = new N(this)), + (this._metadataService = new H(this)), + (this._extraQueryParams = 'object' === (void 0 === K ? 'undefined' : n(K)) ? K : {}); + } + return ( + i(OidcClientSettings, [ + { + key: 'client_id', + get: function get() { + return this._client_id; + }, + set: function set(e) { + if (this._client_id) + throw ( + (o.Log.error( + 'OidcClientSettings.set_client_id: client_id has already been assigned.' + ), + new Error('client_id has already been assigned.')) + ); + this._client_id = e; + }, + }, + { + key: 'client_secret', + get: function get() { + return this._client_secret; + }, + }, + { + key: 'response_type', + get: function get() { + return this._response_type; + }, + }, + { + key: 'scope', + get: function get() { + return this._scope; + }, + }, + { + key: 'redirect_uri', + get: function get() { + return this._redirect_uri; + }, + }, + { + key: 'post_logout_redirect_uri', + get: function get() { + return this._post_logout_redirect_uri; + }, + }, + { + key: 'prompt', + get: function get() { + return this._prompt; + }, + }, + { + key: 'display', + get: function get() { + return this._display; + }, + }, + { + key: 'max_age', + get: function get() { + return this._max_age; + }, + }, + { + key: 'ui_locales', + get: function get() { + return this._ui_locales; + }, + }, + { + key: 'acr_values', + get: function get() { + return this._acr_values; + }, + }, + { + key: 'resource', + get: function get() { + return this._resource; + }, + }, + { + key: 'authority', + get: function get() { + return this._authority; + }, + set: function set(e) { + if (this._authority) + throw ( + (o.Log.error( + 'OidcClientSettings.set_authority: authority has already been assigned.' + ), + new Error('authority has already been assigned.')) + ); + this._authority = e; + }, + }, + { + key: 'metadataUrl', + get: function get() { + return ( + this._metadataUrl || + ((this._metadataUrl = this.authority), + this._metadataUrl && + this._metadataUrl.indexOf('.well-known/openid-configuration') < 0 && + ('/' !== this._metadataUrl[this._metadataUrl.length - 1] && + (this._metadataUrl += '/'), + (this._metadataUrl += '.well-known/openid-configuration'))), + this._metadataUrl + ); + }, + }, + { + key: 'metadata', + get: function get() { + return this._metadata; + }, + set: function set(e) { + this._metadata = e; + }, + }, + { + key: 'signingKeys', + get: function get() { + return this._signingKeys; + }, + set: function set(e) { + this._signingKeys = e; + }, + }, + { + key: 'filterProtocolClaims', + get: function get() { + return this._filterProtocolClaims; + }, + }, + { + key: 'loadUserInfo', + get: function get() { + return this._loadUserInfo; + }, + }, + { + key: 'staleStateAge', + get: function get() { + return this._staleStateAge; + }, + }, + { + key: 'clockSkew', + get: function get() { + return this._clockSkew; + }, + }, + { + key: 'stateStore', + get: function get() { + return this._stateStore; + }, + }, + { + key: 'validator', + get: function get() { + return this._validator; + }, + }, + { + key: 'metadataService', + get: function get() { + return this._metadataService; + }, + }, + { + key: 'extraQueryParams', + get: function get() { + return this._extraQueryParams; + }, + set: function set(e) { + 'object' === (void 0 === e ? 'undefined' : n(e)) + ? (this._extraQueryParams = e) + : (this._extraQueryParams = {}); + }, + }, + ]), + OidcClientSettings + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.CordovaPopupWindow = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0); + var o = 'location=no,toolbar=no,zoom=no', + s = '_blank'; + t.CordovaPopupWindow = (function () { + function CordovaPopupWindow(e) { + var t = this; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, CordovaPopupWindow), + (this._promise = new Promise(function (e, r) { + (t._resolve = e), (t._reject = r); + })), + (this.features = e.popupWindowFeatures || o), + (this.target = e.popupWindowTarget || s), + (this.redirect_uri = e.startUrl), + i.Log.debug('CordovaPopupWindow.ctor: redirect_uri: ' + this.redirect_uri); + } + return ( + (CordovaPopupWindow.prototype._isInAppBrowserInstalled = + function _isInAppBrowserInstalled(e) { + return [ + 'cordova-plugin-inappbrowser', + 'cordova-plugin-inappbrowser.inappbrowser', + 'org.apache.cordova.inappbrowser', + ].some(function (t) { + return e.hasOwnProperty(t); + }); + }), + (CordovaPopupWindow.prototype.navigate = function navigate(e) { + if (e && e.url) { + if (!window.cordova) return this._error('cordova is undefined'); + var t = window.cordova.require('cordova/plugin_list').metadata; + if (!1 === this._isInAppBrowserInstalled(t)) + return this._error('InAppBrowser plugin not found'); + (this._popup = cordova.InAppBrowser.open(e.url, this.target, this.features)), + this._popup + ? (i.Log.debug('CordovaPopupWindow.navigate: popup successfully created'), + (this._exitCallbackEvent = this._exitCallback.bind(this)), + (this._loadStartCallbackEvent = this._loadStartCallback.bind(this)), + this._popup.addEventListener('exit', this._exitCallbackEvent, !1), + this._popup.addEventListener('loadstart', this._loadStartCallbackEvent, !1)) + : this._error('Error opening popup window'); + } else this._error('No url provided'); + return this.promise; + }), + (CordovaPopupWindow.prototype._loadStartCallback = function _loadStartCallback(e) { + 0 === e.url.indexOf(this.redirect_uri) && this._success({ url: e.url }); + }), + (CordovaPopupWindow.prototype._exitCallback = function _exitCallback(e) { + this._error(e); + }), + (CordovaPopupWindow.prototype._success = function _success(e) { + this._cleanup(), + i.Log.debug('CordovaPopupWindow: Successful response from cordova popup window'), + this._resolve(e); + }), + (CordovaPopupWindow.prototype._error = function _error(e) { + this._cleanup(), i.Log.error(e), this._reject(new Error(e)); + }), + (CordovaPopupWindow.prototype.close = function close() { + this._cleanup(); + }), + (CordovaPopupWindow.prototype._cleanup = function _cleanup() { + this._popup && + (i.Log.debug('CordovaPopupWindow: cleaning up popup'), + this._popup.removeEventListener('exit', this._exitCallbackEvent, !1), + this._popup.removeEventListener('loadstart', this._loadStartCallbackEvent, !1), + this._popup.close()), + (this._popup = null); + }), + n(CordovaPopupWindow, [ + { + key: 'promise', + get: function get() { + return this._promise; + }, + }, + ]), + CordovaPopupWindow + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.TokenRevocationClient = void 0); + var n = r(0), + i = r(3), + o = r(1); + t.TokenRevocationClient = (function () { + function TokenRevocationClient(e) { + var t = + arguments.length > 1 && void 0 !== arguments[1] + ? arguments[1] + : o.Global.XMLHttpRequest, + r = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : i.MetadataService; + if ( + ((function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, TokenRevocationClient), + !e) + ) + throw ( + (n.Log.error('TokenRevocationClient.ctor: No settings provided'), + new Error('No settings provided.')) + ); + (this._settings = e), + (this._XMLHttpRequestCtor = t), + (this._metadataService = new r(this._settings)); + } + return ( + (TokenRevocationClient.prototype.revoke = function revoke(e, t) { + var r = this; + if (!e) + throw ( + (n.Log.error('TokenRevocationClient.revoke: No accessToken provided'), + new Error('No accessToken provided.')) + ); + return this._metadataService.getRevocationEndpoint().then(function (i) { + if (i) { + n.Log.error('TokenRevocationClient.revoke: Revoking access token'); + var o = r._settings.client_id, + s = r._settings.client_secret; + return r._revoke(i, o, s, e); + } + if (t) + throw ( + (n.Log.error('TokenRevocationClient.revoke: Revocation not supported'), + new Error('Revocation not supported')) + ); + }); + }), + (TokenRevocationClient.prototype._revoke = function _revoke(e, t, r, i) { + var o = this; + return new Promise(function (s, a) { + var u = new o._XMLHttpRequestCtor(); + u.open('POST', e), + (u.onload = function () { + n.Log.debug( + 'TokenRevocationClient.revoke: HTTP response received, status', + u.status + ), + 200 === u.status ? s() : a(Error(u.statusText + ' (' + u.status + ')')); + }); + var c = 'client_id=' + encodeURIComponent(t); + r && (c += '&client_secret=' + encodeURIComponent(r)), + (c += '&token_type_hint=' + encodeURIComponent('access_token')), + (c += '&token=' + encodeURIComponent(i)), + u.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'), + u.send(c); + }); + }), + TokenRevocationClient + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.CheckSessionIFrame = void 0); + var n = r(0); + var i = 2e3; + t.CheckSessionIFrame = (function () { + function CheckSessionIFrame(e, t, r, n) { + var o = !(arguments.length > 4 && void 0 !== arguments[4]) || arguments[4]; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, CheckSessionIFrame), + (this._callback = e), + (this._client_id = t), + (this._url = r), + (this._interval = n || i), + (this._stopOnError = o); + var s = r.indexOf('/', r.indexOf('//') + 2); + (this._frame_origin = r.substr(0, s)), + (this._frame = window.document.createElement('iframe')), + (this._frame.style.visibility = 'hidden'), + (this._frame.style.position = 'absolute'), + (this._frame.style.display = 'none'), + (this._frame.style.width = 0), + (this._frame.style.height = 0), + (this._frame.src = r); + } + return ( + (CheckSessionIFrame.prototype.load = function load() { + var e = this; + return new Promise(function (t) { + (e._frame.onload = function () { + t(); + }), + window.document.body.appendChild(e._frame), + (e._boundMessageEvent = e._message.bind(e)), + window.addEventListener('message', e._boundMessageEvent, !1); + }); + }), + (CheckSessionIFrame.prototype._message = function _message(e) { + e.origin === this._frame_origin && + e.source === this._frame.contentWindow && + ('error' === e.data + ? (n.Log.error('CheckSessionIFrame: error message from check session op iframe'), + this._stopOnError && this.stop()) + : 'changed' === e.data + ? (n.Log.debug('CheckSessionIFrame: changed message from check session op iframe'), + this.stop(), + this._callback()) + : n.Log.debug( + 'CheckSessionIFrame: ' + e.data + ' message from check session op iframe' + )); + }), + (CheckSessionIFrame.prototype.start = function start(e) { + var t = this; + if (this._session_state !== e) { + n.Log.debug('CheckSessionIFrame.start'), this.stop(), (this._session_state = e); + var r = function send() { + t._frame.contentWindow.postMessage( + t._client_id + ' ' + t._session_state, + t._frame_origin + ); + }; + r(), (this._timer = window.setInterval(r, this._interval)); + } + }), + (CheckSessionIFrame.prototype.stop = function stop() { + (this._session_state = null), + this._timer && + (n.Log.debug('CheckSessionIFrame.stop'), + window.clearInterval(this._timer), + (this._timer = null)); + }), + CheckSessionIFrame + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.SessionMonitor = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0), + o = r(9); + t.SessionMonitor = (function () { + function SessionMonitor(e) { + var t = this, + r = + arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : o.CheckSessionIFrame; + if ( + ((function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, SessionMonitor), + !e) + ) + throw ( + (i.Log.error('SessionMonitor.ctor: No user manager passed to SessionMonitor'), + new Error('userManager')) + ); + (this._userManager = e), + (this._CheckSessionIFrameCtor = r), + this._userManager.events.addUserLoaded(this._start.bind(this)), + this._userManager.events.addUserUnloaded(this._stop.bind(this)), + this._userManager + .getUser() + .then(function (e) { + e && t._start(e); + }) + .catch(function (e) { + i.Log.error('SessionMonitor ctor: error from getUser:', e.message); + }); + } + return ( + (SessionMonitor.prototype._start = function _start(e) { + var t = this, + r = e.session_state; + r && + ((this._sub = e.profile.sub), + (this._sid = e.profile.sid), + i.Log.debug('SessionMonitor._start: session_state:', r, ', sub:', this._sub), + this._checkSessionIFrame + ? this._checkSessionIFrame.start(r) + : this._metadataService + .getCheckSessionIframe() + .then(function (e) { + if (e) { + i.Log.debug('SessionMonitor._start: Initializing check session iframe'); + var n = t._client_id, + o = t._checkSessionInterval, + s = t._stopCheckSessionOnError; + (t._checkSessionIFrame = new t._CheckSessionIFrameCtor( + t._callback.bind(t), + n, + e, + o, + s + )), + t._checkSessionIFrame.load().then(function () { + t._checkSessionIFrame.start(r); + }); + } else + i.Log.warn( + 'SessionMonitor._start: No check session iframe found in the metadata' + ); + }) + .catch(function (e) { + i.Log.error( + 'SessionMonitor._start: Error from getCheckSessionIframe:', + e.message + ); + })); + }), + (SessionMonitor.prototype._stop = function _stop() { + (this._sub = null), + (this._sid = null), + this._checkSessionIFrame && + (i.Log.debug('SessionMonitor._stop'), this._checkSessionIFrame.stop()); + }), + (SessionMonitor.prototype._callback = function _callback() { + var e = this; + this._userManager + .querySessionStatus() + .then(function (t) { + var r = !0; + t + ? t.sub === e._sub + ? ((r = !1), + e._checkSessionIFrame.start(t.session_state), + t.sid === e._sid + ? i.Log.debug( + 'SessionMonitor._callback: Same sub still logged in at OP, restarting check session iframe; session_state:', + t.session_state + ) + : (i.Log.debug( + 'SessionMonitor._callback: Same sub still logged in at OP, session state has changed, restarting check session iframe; session_state:', + t.session_state + ), + e._userManager.events._raiseUserSessionChanged())) + : i.Log.debug( + 'SessionMonitor._callback: Different subject signed into OP:', + t.sub + ) + : i.Log.debug('SessionMonitor._callback: Subject no longer signed into OP'), + r && + (i.Log.debug( + 'SessionMonitor._callback: SessionMonitor._callback; raising signed out event' + ), + e._userManager.events._raiseUserSignedOut()); + }) + .catch(function (t) { + i.Log.debug( + 'SessionMonitor._callback: Error calling queryCurrentSigninSession; raising signed out event', + t.message + ), + e._userManager.events._raiseUserSignedOut(); + }); + }), + n(SessionMonitor, [ + { + key: '_settings', + get: function get() { + return this._userManager.settings; + }, + }, + { + key: '_metadataService', + get: function get() { + return this._userManager.metadataService; + }, + }, + { + key: '_client_id', + get: function get() { + return this._settings.client_id; + }, + }, + { + key: '_checkSessionInterval', + get: function get() { + return this._settings.checkSessionInterval; + }, + }, + { + key: '_stopCheckSessionOnError', + get: function get() { + return this._settings.stopCheckSessionOnError; + }, + }, + ]), + SessionMonitor + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.Event = void 0); + var n = r(0); + t.Event = (function () { + function Event(e) { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, Event), + (this._name = e), + (this._callbacks = []); + } + return ( + (Event.prototype.addHandler = function addHandler(e) { + this._callbacks.push(e); + }), + (Event.prototype.removeHandler = function removeHandler(e) { + var t = this._callbacks.findIndex(function (t) { + return t === e; + }); + t >= 0 && this._callbacks.splice(t, 1); + }), + (Event.prototype.raise = function raise() { + n.Log.debug('Event: Raising event: ' + this._name); + for (var e = 0; e < this._callbacks.length; e++) { + var t; + (t = this._callbacks)[e].apply(t, arguments); + } + }), + Event + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.AccessTokenEvents = void 0); + var n = r(0), + i = r(22); + var o = 60; + t.AccessTokenEvents = (function () { + function AccessTokenEvents() { + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + t = e.accessTokenExpiringNotificationTime, + r = void 0 === t ? o : t, + n = e.accessTokenExpiringTimer, + s = void 0 === n ? new i.Timer('Access token expiring') : n, + a = e.accessTokenExpiredTimer, + u = void 0 === a ? new i.Timer('Access token expired') : a; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, AccessTokenEvents), + (this._accessTokenExpiringNotificationTime = r), + (this._accessTokenExpiring = s), + (this._accessTokenExpired = u); + } + return ( + (AccessTokenEvents.prototype.load = function load(e) { + if (e.access_token && void 0 !== e.expires_in) { + var t = e.expires_in; + if ( + (n.Log.debug( + 'AccessTokenEvents.load: access token present, remaining duration:', + t + ), + t > 0) + ) { + var r = t - this._accessTokenExpiringNotificationTime; + r <= 0 && (r = 1), + n.Log.debug('AccessTokenEvents.load: registering expiring timer in:', r), + this._accessTokenExpiring.init(r); + } else + n.Log.debug( + "AccessTokenEvents.load: canceling existing expiring timer becase we're past expiration." + ), + this._accessTokenExpiring.cancel(); + var i = t + 1; + n.Log.debug('AccessTokenEvents.load: registering expired timer in:', i), + this._accessTokenExpired.init(i); + } else this._accessTokenExpiring.cancel(), this._accessTokenExpired.cancel(); + }), + (AccessTokenEvents.prototype.unload = function unload() { + n.Log.debug('AccessTokenEvents.unload: canceling existing access token timers'), + this._accessTokenExpiring.cancel(), + this._accessTokenExpired.cancel(); + }), + (AccessTokenEvents.prototype.addAccessTokenExpiring = function addAccessTokenExpiring(e) { + this._accessTokenExpiring.addHandler(e); + }), + (AccessTokenEvents.prototype.removeAccessTokenExpiring = + function removeAccessTokenExpiring(e) { + this._accessTokenExpiring.removeHandler(e); + }), + (AccessTokenEvents.prototype.addAccessTokenExpired = function addAccessTokenExpired(e) { + this._accessTokenExpired.addHandler(e); + }), + (AccessTokenEvents.prototype.removeAccessTokenExpired = function removeAccessTokenExpired( + e + ) { + this._accessTokenExpired.removeHandler(e); + }), + AccessTokenEvents + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.User = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0); + t.User = (function () { + function User(e) { + var t = e.id_token, + r = e.session_state, + n = e.access_token, + i = e.token_type, + o = e.scope, + s = e.profile, + a = e.expires_at, + u = e.state; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, User), + (this.id_token = t), + (this.session_state = r), + (this.access_token = n), + (this.token_type = i), + (this.scope = o), + (this.profile = s), + (this.expires_at = a), + (this.state = u); + } + return ( + (User.prototype.toStorageString = function toStorageString() { + return ( + i.Log.debug('User.toStorageString'), + JSON.stringify({ + id_token: this.id_token, + session_state: this.session_state, + access_token: this.access_token, + token_type: this.token_type, + scope: this.scope, + profile: this.profile, + expires_at: this.expires_at, + }) + ); + }), + (User.fromStorageString = function fromStorageString(e) { + return i.Log.debug('User.fromStorageString'), new User(JSON.parse(e)); + }), + n(User, [ + { + key: 'expires_in', + get: function get() { + if (this.expires_at) { + var e = parseInt(Date.now() / 1e3); + return this.expires_at - e; + } + }, + }, + { + key: 'expired', + get: function get() { + var e = this.expires_in; + if (void 0 !== e) return e <= 0; + }, + }, + { + key: 'scopes', + get: function get() { + return (this.scope || '').split(' '); + }, + }, + ]), + User + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), + (t.default = + // @preserve Copyright (c) Microsoft Open Technologies, Inc. + function random() { + for ( + var e = 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx', + t = '0123456789abcdef', + r = 0, + n = '', + i = 0; + i < e.length; + i++ + ) + '-' !== e[i] && '4' !== e[i] && (r = (16 * Math.random()) | 0), + 'x' === e[i] + ? (n += t[r]) + : 'y' === e[i] + ? ((r &= 3), (n += t[(r |= 8)])) + : (n += e[i]); + return n; + }), + (e.exports = t.default); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.SigninState = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0), + o = r(4), + s = (function _interopRequireDefault(e) { + return e && e.__esModule ? e : { default: e }; + })(r(14)); + t.SigninState = (function (e) { + function SigninState() { + var t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + r = t.nonce, + n = t.authority, + i = t.client_id; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, SigninState); + var o = (function _possibleConstructorReturn(e, t) { + if (!e) + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + return !t || ('object' != typeof t && 'function' != typeof t) ? e : t; + })(this, e.call(this, arguments[0])); + return ( + !0 === r ? (o._nonce = (0, s.default)()) : r && (o._nonce = r), + (o._authority = n), + (o._client_id = i), + o + ); + } + return ( + (function _inherits(e, t) { + if ('function' != typeof t && null !== t) + throw new TypeError( + 'Super expression must either be null or a function, not ' + typeof t + ); + (e.prototype = Object.create(t && t.prototype, { + constructor: { + value: e, + enumerable: !1, + writable: !0, + configurable: !0, + }, + })), + t && (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : (e.__proto__ = t)); + })(SigninState, e), + (SigninState.prototype.toStorageString = function toStorageString() { + return ( + i.Log.debug('SigninState.toStorageString'), + JSON.stringify({ + id: this.id, + data: this.data, + created: this.created, + nonce: this.nonce, + authority: this.authority, + client_id: this.client_id, + }) + ); + }), + (SigninState.fromStorageString = function fromStorageString(e) { + return i.Log.debug('SigninState.fromStorageString'), new SigninState(JSON.parse(e)); + }), + n(SigninState, [ + { + key: 'nonce', + get: function get() { + return this._nonce; + }, + }, + { + key: 'authority', + get: function get() { + return this._authority; + }, + }, + { + key: 'client_id', + get: function get() { + return this._client_id; + }, + }, + ]), + SigninState + ); + })(o.State); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.ErrorResponse = void 0); + var n = r(0); + t.ErrorResponse = (function (e) { + function ErrorResponse() { + var t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + r = t.error, + i = t.error_description, + o = t.error_uri, + s = t.state; + if ( + ((function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, ErrorResponse), + !r) + ) + throw (n.Log.error('No error passed to ErrorResponse'), new Error('error')); + var a = (function _possibleConstructorReturn(e, t) { + if (!e) + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + return !t || ('object' != typeof t && 'function' != typeof t) ? e : t; + })(this, e.call(this, i || r)); + return ( + (a.name = 'ErrorResponse'), + (a.error = r), + (a.error_description = i), + (a.error_uri = o), + (a.state = s), + a + ); + } + return ( + (function _inherits(e, t) { + if ('function' != typeof t && null !== t) + throw new TypeError( + 'Super expression must either be null or a function, not ' + typeof t + ); + (e.prototype = Object.create(t && t.prototype, { + constructor: { + value: e, + enumerable: !1, + writable: !0, + configurable: !0, + }, + })), + t && (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : (e.__proto__ = t)); + })(ErrorResponse, e), + ErrorResponse + ); + })(Error); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.JsonService = void 0); + var n = r(0), + i = r(1); + t.JsonService = (function () { + function JsonService() { + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : null, + t = + arguments.length > 1 && void 0 !== arguments[1] + ? arguments[1] + : i.Global.XMLHttpRequest; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, JsonService), + e && Array.isArray(e) ? (this._contentTypes = e.slice()) : (this._contentTypes = []), + this._contentTypes.push('application/json'), + (this._XMLHttpRequest = t); + } + return ( + (JsonService.prototype.getJson = function getJson(e, t) { + var r = this; + if (!e) throw (n.Log.error('JsonService.getJson: No url passed'), new Error('url')); + return ( + n.Log.debug('JsonService.getJson, url: ', e), + new Promise(function (i, o) { + var s = new r._XMLHttpRequest(); + s.open('GET', e); + var a = r._contentTypes; + (s.onload = function () { + if ( + (n.Log.debug('JsonService.getJson: HTTP response received, status', s.status), + 200 === s.status) + ) { + var t = s.getResponseHeader('Content-Type'); + if (t) + if ( + a.find(function (e) { + if (t.startsWith(e)) return !0; + }) + ) + try { + return void i(JSON.parse(s.responseText)); + } catch (e) { + return ( + n.Log.error( + 'JsonService.getJson: Error parsing JSON response', + e.message + ), + void o(e) + ); + } + o(Error('Invalid response Content-Type: ' + t + ', from URL: ' + e)); + } else o(Error(s.statusText + ' (' + s.status + ')')); + }), + (s.onerror = function () { + n.Log.error('JsonService.getJson: network error'), o(Error('Network Error')); + }), + t && + (n.Log.debug('JsonService.getJson: token passed, setting Authorization header'), + s.setRequestHeader('Authorization', 'Bearer ' + t)), + s.send(); + }) + ); + }), + JsonService + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.OidcClient = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0), + o = r(6), + s = r(16), + a = r(35), + u = r(34), + c = r(33), + h = r(32), + f = r(15), + l = r(4); + t.OidcClient = (function () { + function OidcClient() { + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, OidcClient), + e instanceof o.OidcClientSettings + ? (this._settings = e) + : (this._settings = new o.OidcClientSettings(e)); + } + return ( + (OidcClient.prototype.createSigninRequest = function createSigninRequest() { + var e = this, + t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + r = t.response_type, + n = t.scope, + o = t.redirect_uri, + s = t.data, + u = t.state, + c = t.prompt, + h = t.display, + f = t.max_age, + l = t.ui_locales, + g = t.id_token_hint, + p = t.login_hint, + d = t.acr_values, + v = t.resource, + y = t.request, + m = t.request_uri, + S = t.extraQueryParams, + F = arguments[1]; + i.Log.debug('OidcClient.createSigninRequest'); + var b = this._settings.client_id; + (r = r || this._settings.response_type), + (n = n || this._settings.scope), + (o = o || this._settings.redirect_uri), + (c = c || this._settings.prompt), + (h = h || this._settings.display), + (f = f || this._settings.max_age), + (l = l || this._settings.ui_locales), + (d = d || this._settings.acr_values), + (v = v || this._settings.resource), + (S = S || this._settings.extraQueryParams); + var _ = this._settings.authority; + return this._metadataService.getAuthorizationEndpoint().then(function (t) { + i.Log.debug('OidcClient.createSigninRequest: Received authorization endpoint', t); + var w = new a.SigninRequest({ + url: t, + client_id: b, + redirect_uri: o, + response_type: r, + scope: n, + data: s || u, + authority: _, + prompt: c, + display: h, + max_age: f, + ui_locales: l, + id_token_hint: g, + login_hint: p, + acr_values: d, + resource: v, + request: y, + request_uri: m, + extraQueryParams: S, + }), + E = w.state; + return (F = F || e._stateStore).set(E.id, E.toStorageString()).then(function () { + return w; + }); + }); + }), + (OidcClient.prototype.processSigninResponse = function processSigninResponse(e, t) { + var r = this; + i.Log.debug('OidcClient.processSigninResponse'); + var n = new u.SigninResponse(e); + return n.state + ? (t = t || this._stateStore).remove(n.state).then(function (e) { + if (!e) + throw ( + (i.Log.error( + 'OidcClient.processSigninResponse: No matching state found in storage' + ), + new Error('No matching state found in storage')) + ); + var t = f.SigninState.fromStorageString(e); + return ( + i.Log.debug( + 'OidcClient.processSigninResponse: Received state from storage; validating response' + ), + r._validator.validateSigninResponse(t, n) + ); + }) + : (i.Log.error('OidcClient.processSigninResponse: No state in response'), + Promise.reject(new Error('No state in response'))); + }), + (OidcClient.prototype.createSignoutRequest = function createSignoutRequest() { + var e = this, + t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + r = t.id_token_hint, + n = t.data, + o = t.state, + s = t.post_logout_redirect_uri, + a = arguments[1]; + return ( + i.Log.debug('OidcClient.createSignoutRequest'), + (s = s || this._settings.post_logout_redirect_uri), + this._metadataService.getEndSessionEndpoint().then(function (t) { + if (!t) + throw ( + (i.Log.error( + 'OidcClient.createSignoutRequest: No end session endpoint url returned' + ), + new Error('no end session endpoint')) + ); + i.Log.debug('OidcClient.createSignoutRequest: Received end session endpoint', t); + var u = new c.SignoutRequest({ + url: t, + id_token_hint: r, + post_logout_redirect_uri: s, + data: n || o, + }), + h = u.state; + return ( + h && + (i.Log.debug( + 'OidcClient.createSignoutRequest: Signout request has state to persist' + ), + (a = a || e._stateStore).set(h.id, h.toStorageString())), + u + ); + }) + ); + }), + (OidcClient.prototype.processSignoutResponse = function processSignoutResponse(e, t) { + var r = this; + i.Log.debug('OidcClient.processSignoutResponse'); + var n = new h.SignoutResponse(e); + if (!n.state) + return ( + i.Log.debug('OidcClient.processSignoutResponse: No state in response'), + n.error + ? (i.Log.warn('OidcClient.processSignoutResponse: Response was error: ', n.error), + Promise.reject(new s.ErrorResponse(n))) + : Promise.resolve(n) + ); + var o = n.state; + return (t = t || this._stateStore).remove(o).then(function (e) { + if (!e) + throw ( + (i.Log.error( + 'OidcClient.processSignoutResponse: No matching state found in storage' + ), + new Error('No matching state found in storage')) + ); + var t = l.State.fromStorageString(e); + return ( + i.Log.debug( + 'OidcClient.processSignoutResponse: Received state from storage; validating response' + ), + r._validator.validateSignoutResponse(t, n) + ); + }); + }), + (OidcClient.prototype.clearStaleState = function clearStaleState(e) { + return ( + i.Log.debug('OidcClient.clearStaleState'), + (e = e || this._stateStore), + l.State.clearStaleState(e, this.settings.staleStateAge) + ); + }), + n(OidcClient, [ + { + key: '_stateStore', + get: function get() { + return this.settings.stateStore; + }, + }, + { + key: '_validator', + get: function get() { + return this.settings.validator; + }, + }, + { + key: '_metadataService', + get: function get() { + return this.settings.metadataService; + }, + }, + { + key: 'settings', + get: function get() { + return this._settings; + }, + }, + { + key: 'metadataService', + get: function get() { + return this._metadataService; + }, + }, + ]), + OidcClient + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.CordovaIFrameNavigator = void 0); + var n = r(7); + t.CordovaIFrameNavigator = (function () { + function CordovaIFrameNavigator() { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, CordovaIFrameNavigator); + } + return ( + (CordovaIFrameNavigator.prototype.prepare = function prepare(e) { + e.popupWindowFeatures = 'hidden=yes'; + var t = new n.CordovaPopupWindow(e); + return Promise.resolve(t); + }), + CordovaIFrameNavigator + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.CordovaPopupNavigator = void 0); + var n = r(7); + t.CordovaPopupNavigator = (function () { + function CordovaPopupNavigator() { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, CordovaPopupNavigator); + } + return ( + (CordovaPopupNavigator.prototype.prepare = function prepare(e) { + var t = new n.CordovaPopupWindow(e); + return Promise.resolve(t); + }), + CordovaPopupNavigator + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.SilentRenewService = void 0); + var n = r(0); + t.SilentRenewService = (function () { + function SilentRenewService(e) { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, SilentRenewService), + (this._userManager = e); + } + return ( + (SilentRenewService.prototype.start = function start() { + this._callback || + ((this._callback = this._tokenExpiring.bind(this)), + this._userManager.events.addAccessTokenExpiring(this._callback), + this._userManager + .getUser() + .then(function (e) {}) + .catch(function (e) { + n.Log.error('SilentRenewService.start: Error from getUser:', e.message); + })); + }), + (SilentRenewService.prototype.stop = function stop() { + this._callback && + (this._userManager.events.removeAccessTokenExpiring(this._callback), + delete this._callback); + }), + (SilentRenewService.prototype._tokenExpiring = function _tokenExpiring() { + var e = this; + this._userManager.signinSilent().then( + function (e) { + n.Log.debug('SilentRenewService._tokenExpiring: Silent token renewal successful'); + }, + function (t) { + n.Log.error( + 'SilentRenewService._tokenExpiring: Error from signinSilent:', + t.message + ), + e._userManager.events._raiseSilentRenewError(t); + } + ); + }), + SilentRenewService + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.Timer = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0), + o = r(1), + s = r(11); + t.Timer = (function (e) { + function Timer(t) { + var r = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : o.Global.timer, + n = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : void 0; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, Timer); + var i = (function _possibleConstructorReturn(e, t) { + if (!e) + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + return !t || ('object' != typeof t && 'function' != typeof t) ? e : t; + })(this, e.call(this, t)); + return ( + (i._timer = r), + (i._nowFunc = + n || + function () { + return Date.now() / 1e3; + }), + i + ); + } + return ( + (function _inherits(e, t) { + if ('function' != typeof t && null !== t) + throw new TypeError( + 'Super expression must either be null or a function, not ' + typeof t + ); + (e.prototype = Object.create(t && t.prototype, { + constructor: { + value: e, + enumerable: !1, + writable: !0, + configurable: !0, + }, + })), + t && (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : (e.__proto__ = t)); + })(Timer, e), + (Timer.prototype.init = function init(e) { + e <= 0 && (e = 1), (e = parseInt(e)); + var t = this.now + e; + if (this.expiration === t && this._timerHandle) + i.Log.debug( + 'Timer.init timer ' + + this._name + + ' skipping initialization since already initialized for expiration:', + this.expiration + ); + else { + this.cancel(), + i.Log.debug('Timer.init timer ' + this._name + ' for duration:', e), + (this._expiration = t); + var r = 5; + e < r && (r = e), + (this._timerHandle = this._timer.setInterval(this._callback.bind(this), 1e3 * r)); + } + }), + (Timer.prototype.cancel = function cancel() { + this._timerHandle && + (i.Log.debug('Timer.cancel: ', this._name), + this._timer.clearInterval(this._timerHandle), + (this._timerHandle = null)); + }), + (Timer.prototype._callback = function _callback() { + var t = this._expiration - this.now; + i.Log.debug('Timer.callback; ' + this._name + ' timer expires in:', t), + this._expiration <= this.now && (this.cancel(), e.prototype.raise.call(this)); + }), + n(Timer, [ + { + key: 'now', + get: function get() { + return parseInt(this._nowFunc()); + }, + }, + { + key: 'expiration', + get: function get() { + return this._expiration; + }, + }, + ]), + Timer + ); + })(s.Event); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.UserManagerEvents = void 0); + var n = r(0), + i = r(12), + o = r(11); + t.UserManagerEvents = (function (e) { + function UserManagerEvents(t) { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, UserManagerEvents); + var r = (function _possibleConstructorReturn(e, t) { + if (!e) + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + return !t || ('object' != typeof t && 'function' != typeof t) ? e : t; + })(this, e.call(this, t)); + return ( + (r._userLoaded = new o.Event('User loaded')), + (r._userUnloaded = new o.Event('User unloaded')), + (r._silentRenewError = new o.Event('Silent renew error')), + (r._userSignedOut = new o.Event('User signed out')), + (r._userSessionChanged = new o.Event('User session changed')), + r + ); + } + return ( + (function _inherits(e, t) { + if ('function' != typeof t && null !== t) + throw new TypeError( + 'Super expression must either be null or a function, not ' + typeof t + ); + (e.prototype = Object.create(t && t.prototype, { + constructor: { + value: e, + enumerable: !1, + writable: !0, + configurable: !0, + }, + })), + t && (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : (e.__proto__ = t)); + })(UserManagerEvents, e), + (UserManagerEvents.prototype.load = function load(t) { + var r = !(arguments.length > 1 && void 0 !== arguments[1]) || arguments[1]; + n.Log.debug('UserManagerEvents.load'), + e.prototype.load.call(this, t), + r && this._userLoaded.raise(t); + }), + (UserManagerEvents.prototype.unload = function unload() { + n.Log.debug('UserManagerEvents.unload'), + e.prototype.unload.call(this), + this._userUnloaded.raise(); + }), + (UserManagerEvents.prototype.addUserLoaded = function addUserLoaded(e) { + this._userLoaded.addHandler(e); + }), + (UserManagerEvents.prototype.removeUserLoaded = function removeUserLoaded(e) { + this._userLoaded.removeHandler(e); + }), + (UserManagerEvents.prototype.addUserUnloaded = function addUserUnloaded(e) { + this._userUnloaded.addHandler(e); + }), + (UserManagerEvents.prototype.removeUserUnloaded = function removeUserUnloaded(e) { + this._userUnloaded.removeHandler(e); + }), + (UserManagerEvents.prototype.addSilentRenewError = function addSilentRenewError(e) { + this._silentRenewError.addHandler(e); + }), + (UserManagerEvents.prototype.removeSilentRenewError = function removeSilentRenewError(e) { + this._silentRenewError.removeHandler(e); + }), + (UserManagerEvents.prototype._raiseSilentRenewError = function _raiseSilentRenewError(e) { + n.Log.debug('UserManagerEvents._raiseSilentRenewError', e.message), + this._silentRenewError.raise(e); + }), + (UserManagerEvents.prototype.addUserSignedOut = function addUserSignedOut(e) { + this._userSignedOut.addHandler(e); + }), + (UserManagerEvents.prototype.removeUserSignedOut = function removeUserSignedOut(e) { + this._userSignedOut.removeHandler(e); + }), + (UserManagerEvents.prototype._raiseUserSignedOut = function _raiseUserSignedOut(e) { + n.Log.debug('UserManagerEvents._raiseUserSignedOut'), this._userSignedOut.raise(e); + }), + (UserManagerEvents.prototype.addUserSessionChanged = function addUserSessionChanged(e) { + this._userSessionChanged.addHandler(e); + }), + (UserManagerEvents.prototype.removeUserSessionChanged = function removeUserSessionChanged( + e + ) { + this._userSessionChanged.removeHandler(e); + }), + (UserManagerEvents.prototype._raiseUserSessionChanged = function _raiseUserSessionChanged( + e + ) { + n.Log.debug('UserManagerEvents._raiseUserSessionChanged'), + this._userSessionChanged.raise(e); + }), + UserManagerEvents + ); + })(i.AccessTokenEvents); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.IFrameWindow = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0); + t.IFrameWindow = (function () { + function IFrameWindow(e) { + var t = this; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, IFrameWindow), + (this._promise = new Promise(function (e, r) { + (t._resolve = e), (t._reject = r); + })), + (this._boundMessageEvent = this._message.bind(this)), + window.addEventListener('message', this._boundMessageEvent, !1), + (this._frame = window.document.createElement('iframe')), + (this._frame.style.visibility = 'hidden'), + (this._frame.style.position = 'absolute'), + (this._frame.style.display = 'none'), + (this._frame.style.width = 0), + (this._frame.style.height = 0), + window.document.body.appendChild(this._frame); + } + return ( + (IFrameWindow.prototype.navigate = function navigate(e) { + if (e && e.url) { + var t = e.silentRequestTimeout || 1e4; + i.Log.debug('IFrameWindow.navigate: Using timeout of:', t), + (this._timer = window.setTimeout(this._timeout.bind(this), t)), + (this._frame.src = e.url); + } else this._error('No url provided'); + return this.promise; + }), + (IFrameWindow.prototype._success = function _success(e) { + this._cleanup(), + i.Log.debug('IFrameWindow: Successful response from frame window'), + this._resolve(e); + }), + (IFrameWindow.prototype._error = function _error(e) { + this._cleanup(), i.Log.error(e), this._reject(new Error(e)); + }), + (IFrameWindow.prototype.close = function close() { + this._cleanup(); + }), + (IFrameWindow.prototype._cleanup = function _cleanup() { + this._frame && + (i.Log.debug('IFrameWindow: cleanup'), + window.removeEventListener('message', this._boundMessageEvent, !1), + window.clearTimeout(this._timer), + window.document.body.removeChild(this._frame), + (this._timer = null), + (this._frame = null), + (this._boundMessageEvent = null)); + }), + (IFrameWindow.prototype._timeout = function _timeout() { + i.Log.debug('IFrameWindow.timeout'), this._error('Frame window timed out'); + }), + (IFrameWindow.prototype._message = function _message(e) { + if ( + (i.Log.debug('IFrameWindow.message'), + this._timer && e.origin === this._origin && e.source === this._frame.contentWindow) + ) { + var t = e.data; + t ? this._success({ url: t }) : this._error('Invalid response from frame'); + } + }), + (IFrameWindow.notifyParent = function notifyParent(e) { + i.Log.debug('IFrameWindow.notifyParent'), + window.parent && + window !== window.parent && + (e = e || window.location.href) && + (i.Log.debug('IFrameWindow.notifyParent: posting url message to parent'), + window.parent.postMessage(e, location.protocol + '//' + location.host)); + }), + n(IFrameWindow, [ + { + key: 'promise', + get: function get() { + return this._promise; + }, + }, + { + key: '_origin', + get: function get() { + return location.protocol + '//' + location.host; + }, + }, + ]), + IFrameWindow + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.IFrameNavigator = void 0); + var n = r(0), + i = r(24); + t.IFrameNavigator = (function () { + function IFrameNavigator() { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, IFrameNavigator); + } + return ( + (IFrameNavigator.prototype.prepare = function prepare(e) { + var t = new i.IFrameWindow(e); + return Promise.resolve(t); + }), + (IFrameNavigator.prototype.callback = function callback(e) { + n.Log.debug('IFrameNavigator.callback'); + try { + return i.IFrameWindow.notifyParent(e), Promise.resolve(); + } catch (e) { + return Promise.reject(e); + } + }), + IFrameNavigator + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.PopupWindow = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0), + o = r(2); + var s = 500, + a = 'location=no,toolbar=no,width=500,height=500,left=100,top=100;', + u = '_blank'; + t.PopupWindow = (function () { + function PopupWindow(e) { + var t = this; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, PopupWindow), + (this._promise = new Promise(function (e, r) { + (t._resolve = e), (t._reject = r); + })); + var r = e.popupWindowTarget || u, + n = e.popupWindowFeatures || a; + (this._popup = window.open('', r, n)), + this._popup && + (i.Log.debug('PopupWindow.ctor: popup successfully created'), + (this._checkForPopupClosedTimer = window.setInterval( + this._checkForPopupClosed.bind(this), + s + ))); + } + return ( + (PopupWindow.prototype.navigate = function navigate(e) { + return ( + this._popup + ? e && e.url + ? (i.Log.debug('PopupWindow.navigate: Setting URL in popup'), + (this._id = e.id), + this._id && (window['popupCallback_' + e.id] = this._callback.bind(this)), + this._popup.focus(), + (this._popup.window.location = e.url)) + : (this._error('PopupWindow.navigate: no url provided'), + this._error('No url provided')) + : this._error('PopupWindow.navigate: Error opening popup window'), + this.promise + ); + }), + (PopupWindow.prototype._success = function _success(e) { + i.Log.debug('PopupWindow.callback: Successful response from popup window'), + this._cleanup(), + this._resolve(e); + }), + (PopupWindow.prototype._error = function _error(e) { + i.Log.error('PopupWindow.error: ', e), this._cleanup(), this._reject(new Error(e)); + }), + (PopupWindow.prototype.close = function close() { + this._cleanup(!1); + }), + (PopupWindow.prototype._cleanup = function _cleanup(e) { + i.Log.debug('PopupWindow.cleanup'), + window.clearInterval(this._checkForPopupClosedTimer), + (this._checkForPopupClosedTimer = null), + delete window['popupCallback_' + this._id], + this._popup && !e && this._popup.close(), + (this._popup = null); + }), + (PopupWindow.prototype._checkForPopupClosed = function _checkForPopupClosed() { + (this._popup && !this._popup.closed) || this._error('Popup window closed'); + }), + (PopupWindow.prototype._callback = function _callback(e, t) { + this._cleanup(t), + e + ? (i.Log.debug('PopupWindow.callback success'), this._success({ url: e })) + : (i.Log.debug('PopupWindow.callback: Invalid response from popup'), + this._error('Invalid response from popup')); + }), + (PopupWindow.notifyOpener = function notifyOpener(e, t, r) { + if (window.opener) { + if ((e = e || window.location.href)) { + var n = o.UrlUtility.parseUrlFragment(e, r); + if (n.state) { + var s = 'popupCallback_' + n.state, + a = window.opener[s]; + a + ? (i.Log.debug('PopupWindow.notifyOpener: passing url message to opener'), + a(e, t)) + : i.Log.warn('PopupWindow.notifyOpener: no matching callback found on opener'); + } else i.Log.warn('PopupWindow.notifyOpener: no state found in response url'); + } + } else + i.Log.warn( + "PopupWindow.notifyOpener: no window.opener. Can't complete notification." + ); + }), + n(PopupWindow, [ + { + key: 'promise', + get: function get() { + return this._promise; + }, + }, + ]), + PopupWindow + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.PopupNavigator = void 0); + var n = r(0), + i = r(26); + t.PopupNavigator = (function () { + function PopupNavigator() { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, PopupNavigator); + } + return ( + (PopupNavigator.prototype.prepare = function prepare(e) { + var t = new i.PopupWindow(e); + return Promise.resolve(t); + }), + (PopupNavigator.prototype.callback = function callback(e, t, r) { + n.Log.debug('PopupNavigator.callback'); + try { + return i.PopupWindow.notifyOpener(e, t, r), Promise.resolve(); + } catch (e) { + return Promise.reject(e); + } + }), + PopupNavigator + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.RedirectNavigator = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0); + t.RedirectNavigator = (function () { + function RedirectNavigator() { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, RedirectNavigator); + } + return ( + (RedirectNavigator.prototype.prepare = function prepare() { + return Promise.resolve(this); + }), + (RedirectNavigator.prototype.navigate = function navigate(e) { + return e && e.url + ? ((window.location = e.url), Promise.resolve()) + : (i.Log.error('RedirectNavigator.navigate: No url provided'), + Promise.reject(new Error('No url provided'))); + }), + n(RedirectNavigator, [ + { + key: 'url', + get: function get() { + return window.location.href; + }, + }, + ]), + RedirectNavigator + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.UserManagerSettings = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = (r(0), r(6)), + o = r(28), + s = r(27), + a = r(25), + u = r(5), + c = r(1); + var h = 60, + f = 2e3; + t.UserManagerSettings = (function (e) { + function UserManagerSettings() { + var t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + r = t.popup_redirect_uri, + n = t.popup_post_logout_redirect_uri, + i = t.popupWindowFeatures, + l = t.popupWindowTarget, + g = t.silent_redirect_uri, + p = t.silentRequestTimeout, + d = t.automaticSilentRenew, + v = void 0 !== d && d, + y = t.includeIdTokenInSilentRenew, + m = void 0 === y || y, + S = t.monitorSession, + F = void 0 === S || S, + b = t.checkSessionInterval, + _ = void 0 === b ? f : b, + w = t.stopCheckSessionOnError, + E = void 0 === w || w, + x = t.revokeAccessTokenOnSignout, + C = void 0 !== x && x, + P = t.accessTokenExpiringNotificationTime, + A = void 0 === P ? h : P, + k = t.redirectNavigator, + I = void 0 === k ? new o.RedirectNavigator() : k, + B = t.popupNavigator, + R = void 0 === B ? new s.PopupNavigator() : B, + T = t.iframeNavigator, + U = void 0 === T ? new a.IFrameNavigator() : T, + M = t.userStore, + L = void 0 === M ? new u.WebStorageStateStore({ store: c.Global.sessionStorage }) : M; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, UserManagerSettings); + var D = (function _possibleConstructorReturn(e, t) { + if (!e) + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + return !t || ('object' != typeof t && 'function' != typeof t) ? e : t; + })(this, e.call(this, arguments[0])); + return ( + (D._popup_redirect_uri = r), + (D._popup_post_logout_redirect_uri = n), + (D._popupWindowFeatures = i), + (D._popupWindowTarget = l), + (D._silent_redirect_uri = g), + (D._silentRequestTimeout = p), + (D._automaticSilentRenew = !!v), + (D._includeIdTokenInSilentRenew = m), + (D._accessTokenExpiringNotificationTime = A), + (D._monitorSession = F), + (D._checkSessionInterval = _), + (D._stopCheckSessionOnError = E), + (D._revokeAccessTokenOnSignout = C), + (D._redirectNavigator = I), + (D._popupNavigator = R), + (D._iframeNavigator = U), + (D._userStore = L), + D + ); + } + return ( + (function _inherits(e, t) { + if ('function' != typeof t && null !== t) + throw new TypeError( + 'Super expression must either be null or a function, not ' + typeof t + ); + (e.prototype = Object.create(t && t.prototype, { + constructor: { + value: e, + enumerable: !1, + writable: !0, + configurable: !0, + }, + })), + t && (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : (e.__proto__ = t)); + })(UserManagerSettings, e), + n(UserManagerSettings, [ + { + key: 'popup_redirect_uri', + get: function get() { + return this._popup_redirect_uri; + }, + }, + { + key: 'popup_post_logout_redirect_uri', + get: function get() { + return this._popup_post_logout_redirect_uri; + }, + }, + { + key: 'popupWindowFeatures', + get: function get() { + return this._popupWindowFeatures; + }, + }, + { + key: 'popupWindowTarget', + get: function get() { + return this._popupWindowTarget; + }, + }, + { + key: 'silent_redirect_uri', + get: function get() { + return this._silent_redirect_uri; + }, + }, + { + key: 'silentRequestTimeout', + get: function get() { + return this._silentRequestTimeout; + }, + }, + { + key: 'automaticSilentRenew', + get: function get() { + return !(!this.silent_redirect_uri || !this._automaticSilentRenew); + }, + }, + { + key: 'includeIdTokenInSilentRenew', + get: function get() { + return this._includeIdTokenInSilentRenew; + }, + }, + { + key: 'accessTokenExpiringNotificationTime', + get: function get() { + return this._accessTokenExpiringNotificationTime; + }, + }, + { + key: 'monitorSession', + get: function get() { + return this._monitorSession; + }, + }, + { + key: 'checkSessionInterval', + get: function get() { + return this._checkSessionInterval; + }, + }, + { + key: 'stopCheckSessionOnError', + get: function get() { + return this._stopCheckSessionOnError; + }, + }, + { + key: 'revokeAccessTokenOnSignout', + get: function get() { + return this._revokeAccessTokenOnSignout; + }, + }, + { + key: 'redirectNavigator', + get: function get() { + return this._redirectNavigator; + }, + }, + { + key: 'popupNavigator', + get: function get() { + return this._popupNavigator; + }, + }, + { + key: 'iframeNavigator', + get: function get() { + return this._iframeNavigator; + }, + }, + { + key: 'userStore', + get: function get() { + return this._userStore; + }, + }, + ]), + UserManagerSettings + ); + })(i.OidcClientSettings); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.UserManager = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0), + o = r(18), + s = r(29), + a = r(13), + u = r(23), + c = r(21), + h = r(10), + f = r(8); + t.UserManager = (function (e) { + function UserManager() { + var t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + r = + arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : c.SilentRenewService, + n = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : h.SessionMonitor, + o = + arguments.length > 3 && void 0 !== arguments[3] + ? arguments[3] + : f.TokenRevocationClient; + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, UserManager), + t instanceof s.UserManagerSettings || (t = new s.UserManagerSettings(t)); + var a = (function _possibleConstructorReturn(e, t) { + if (!e) + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + return !t || ('object' != typeof t && 'function' != typeof t) ? e : t; + })(this, e.call(this, t)); + return ( + (a._events = new u.UserManagerEvents(t)), + (a._silentRenewService = new r(a)), + a.settings.automaticSilentRenew && + (i.Log.debug( + 'UserManager.ctor: automaticSilentRenew is configured, setting up silent renew' + ), + a.startSilentRenew()), + a.settings.monitorSession && + (i.Log.debug( + 'UserManager.ctor: monitorSession is configured, setting up session monitor' + ), + (a._sessionMonitor = new n(a))), + (a._tokenRevocationClient = new o(a._settings)), + a + ); + } + return ( + (function _inherits(e, t) { + if ('function' != typeof t && null !== t) + throw new TypeError( + 'Super expression must either be null or a function, not ' + typeof t + ); + (e.prototype = Object.create(t && t.prototype, { + constructor: { + value: e, + enumerable: !1, + writable: !0, + configurable: !0, + }, + })), + t && (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : (e.__proto__ = t)); + })(UserManager, e), + (UserManager.prototype.getUser = function getUser() { + var e = this; + return this._loadUser().then(function (t) { + return t + ? (i.Log.info('UserManager.getUser: user loaded'), e._events.load(t, !1), t) + : (i.Log.info('UserManager.getUser: user not found in storage'), null); + }); + }), + (UserManager.prototype.removeUser = function removeUser() { + var e = this; + return this.storeUser(null).then(function () { + i.Log.info('UserManager.removeUser: user removed from storage'), e._events.unload(); + }); + }), + (UserManager.prototype.signinRedirect = function signinRedirect(e) { + return this._signinStart(e, this._redirectNavigator).then(function () { + i.Log.info('UserManager.signinRedirect: successful'); + }); + }), + (UserManager.prototype.signinRedirectCallback = function signinRedirectCallback(e) { + return this._signinEnd(e || this._redirectNavigator.url).then(function (e) { + return ( + e && + (e.profile && e.profile.sub + ? i.Log.info( + 'UserManager.signinRedirectCallback: successful, signed in sub: ', + e.profile.sub + ) + : i.Log.info('UserManager.signinRedirectCallback: no sub')), + e + ); + }); + }), + (UserManager.prototype.signinPopup = function signinPopup() { + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + t = e.redirect_uri || this.settings.popup_redirect_uri || this.settings.redirect_uri; + return t + ? ((e.redirect_uri = t), + (e.display = 'popup'), + this._signin(e, this._popupNavigator, { + startUrl: t, + popupWindowFeatures: e.popupWindowFeatures || this.settings.popupWindowFeatures, + popupWindowTarget: e.popupWindowTarget || this.settings.popupWindowTarget, + }).then(function (e) { + return ( + e && + (e.profile && e.profile.sub + ? i.Log.info( + 'UserManager.signinPopup: signinPopup successful, signed in sub: ', + e.profile.sub + ) + : i.Log.info('UserManager.signinPopup: no sub')), + e + ); + })) + : (i.Log.error( + 'UserManager.signinPopup: No popup_redirect_uri or redirect_uri configured' + ), + Promise.reject(new Error('No popup_redirect_uri or redirect_uri configured'))); + }), + (UserManager.prototype.signinPopupCallback = function signinPopupCallback(e) { + return this._signinCallback(e, this._popupNavigator) + .then(function (e) { + return ( + e && + (e.profile && e.profile.sub + ? i.Log.info( + 'UserManager.signinPopupCallback: successful, signed in sub: ', + e.profile.sub + ) + : i.Log.info('UserManager.signinPopupCallback: no sub')), + e + ); + }) + .catch(function (e) { + i.Log.error('UserManager.signinPopupCallback error: ' + e && e.message); + }); + }), + (UserManager.prototype.signinSilent = function signinSilent() { + var e = this, + t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + r = t.redirect_uri || this.settings.silent_redirect_uri; + if (!r) + return ( + i.Log.error('UserManager.signinSilent: No silent_redirect_uri configured'), + Promise.reject(new Error('No silent_redirect_uri configured')) + ); + (t.redirect_uri = r), (t.prompt = 'none'); + return ( + t.id_token_hint || !this.settings.includeIdTokenInSilentRenew + ? Promise.resolve() + : this._loadUser().then(function (e) { + t.id_token_hint = e && e.id_token; + }) + ) + .then(function () { + return e._signin(t, e._iframeNavigator, { + startUrl: r, + silentRequestTimeout: t.silentRequestTimeout || e.settings.silentRequestTimeout, + }); + }) + .then(function (e) { + return ( + e && + (e.profile && e.profile.sub + ? i.Log.info( + 'UserManager.signinSilent: successful, signed in sub: ', + e.profile.sub + ) + : i.Log.info('UserManager.signinSilent: no sub')), + e + ); + }); + }), + (UserManager.prototype.signinSilentCallback = function signinSilentCallback(e) { + return this._signinCallback(e, this._iframeNavigator).then(function (e) { + return ( + e && + (e.profile && e.profile.sub + ? i.Log.info( + 'UserManager.signinSilentCallback: successful, signed in sub: ', + e.profile.sub + ) + : i.Log.info('UserManager.signinSilentCallback: no sub')), + e + ); + }); + }), + (UserManager.prototype.querySessionStatus = function querySessionStatus() { + var e = this, + t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + r = t.redirect_uri || this.settings.silent_redirect_uri; + return r + ? ((t.redirect_uri = r), + (t.prompt = 'none'), + (t.response_type = 'id_token'), + (t.scope = 'openid'), + this._signinStart(t, this._iframeNavigator, { + startUrl: r, + silentRequestTimeout: + t.silentRequestTimeout || this.settings.silentRequestTimeout, + }).then(function (t) { + return e.processSigninResponse(t.url).then(function (e) { + if ( + (i.Log.debug('UserManager.querySessionStatus: got signin response'), + e.session_state && e.profile.sub && e.profile.sid) + ) + return ( + i.Log.info( + 'UserManager.querySessionStatus: querySessionStatus success for sub: ', + e.profile.sub + ), + { + session_state: e.session_state, + sub: e.profile.sub, + sid: e.profile.sid, + } + ); + i.Log.info('querySessionStatus successful, user not authenticated'); + }); + })) + : (i.Log.error('UserManager.querySessionStatus: No silent_redirect_uri configured'), + Promise.reject(new Error('No silent_redirect_uri configured'))); + }), + (UserManager.prototype._signin = function _signin(e, t) { + var r = this, + n = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : {}; + return this._signinStart(e, t, n).then(function (e) { + return r._signinEnd(e.url); + }); + }), + (UserManager.prototype._signinStart = function _signinStart(e, t) { + var r = this, + n = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : {}; + return t.prepare(n).then(function (t) { + return ( + i.Log.debug('UserManager._signinStart: got navigator window handle'), + r + .createSigninRequest(e) + .then(function (e) { + return ( + i.Log.debug('UserManager._signinStart: got signin request'), + (n.url = e.url), + (n.id = e.state.id), + t.navigate(n) + ); + }) + .catch(function (e) { + throw ( + (t.close && + (i.Log.debug( + 'UserManager._signinStart: Error after preparing navigator, closing navigator window' + ), + t.close()), + e) + ); + }) + ); + }); + }), + (UserManager.prototype._signinEnd = function _signinEnd(e) { + var t = this; + return this.processSigninResponse(e).then(function (e) { + i.Log.debug('UserManager._signinEnd: got signin response'); + var r = new a.User(e); + return t.storeUser(r).then(function () { + return i.Log.debug('UserManager._signinEnd: user stored'), t._events.load(r), r; + }); + }); + }), + (UserManager.prototype._signinCallback = function _signinCallback(e, t) { + return i.Log.debug('UserManager._signinCallback'), t.callback(e); + }), + (UserManager.prototype.signoutRedirect = function signoutRedirect() { + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + t = e.post_logout_redirect_uri || this.settings.post_logout_redirect_uri; + return ( + t && (e.post_logout_redirect_uri = t), + this._signoutStart(e, this._redirectNavigator).then(function () { + i.Log.info('UserManager.signoutRedirect: successful'); + }) + ); + }), + (UserManager.prototype.signoutRedirectCallback = function signoutRedirectCallback(e) { + return this._signoutEnd(e || this._redirectNavigator.url).then(function (e) { + return i.Log.info('UserManager.signoutRedirectCallback: successful'), e; + }); + }), + (UserManager.prototype.signoutPopup = function signoutPopup() { + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + t = + e.post_logout_redirect_uri || + this.settings.popup_post_logout_redirect_uri || + this.settings.post_logout_redirect_uri; + return ( + (e.post_logout_redirect_uri = t), + (e.display = 'popup'), + e.post_logout_redirect_uri && (e.state = e.state || {}), + this._signout(e, this._popupNavigator, { + startUrl: t, + popupWindowFeatures: e.popupWindowFeatures || this.settings.popupWindowFeatures, + popupWindowTarget: e.popupWindowTarget || this.settings.popupWindowTarget, + }).then(function () { + i.Log.info('UserManager.signinPopup: successful'); + }) + ); + }), + (UserManager.prototype.signoutPopupCallback = function signoutPopupCallback(e, t) { + void 0 === t && 'boolean' == typeof e && ((e = null), (t = !0)); + return this._popupNavigator.callback(e, t, '?').then(function () { + i.Log.info('UserManager.signoutPopupCallback: successful'); + }); + }), + (UserManager.prototype._signout = function _signout(e, t) { + var r = this, + n = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : {}; + return this._signoutStart(e, t, n).then(function (e) { + return r._signoutEnd(e.url); + }); + }), + (UserManager.prototype._signoutStart = function _signoutStart() { + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + t = this, + r = arguments[1], + n = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : {}; + return r.prepare(n).then(function (r) { + return ( + i.Log.debug('UserManager._signoutStart: got navigator window handle'), + t + ._loadUser() + .then(function (o) { + return ( + i.Log.debug('UserManager._signoutStart: loaded current user from storage'), + (t._settings.revokeAccessTokenOnSignout + ? t._revokeInternal(o) + : Promise.resolve() + ).then(function () { + var s = e.id_token_hint || (o && o.id_token); + return ( + s && + (i.Log.debug( + 'UserManager._signoutStart: Setting id_token into signout request' + ), + (e.id_token_hint = s)), + t.removeUser().then(function () { + return ( + i.Log.debug( + 'UserManager._signoutStart: user removed, creating signout request' + ), + t.createSignoutRequest(e).then(function (e) { + return ( + i.Log.debug('UserManager._signoutStart: got signout request'), + (n.url = e.url), + e.state && (n.id = e.state.id), + r.navigate(n) + ); + }) + ); + }) + ); + }) + ); + }) + .catch(function (e) { + throw ( + (r.close && + (i.Log.debug( + 'UserManager._signoutStart: Error after preparing navigator, closing navigator window' + ), + r.close()), + e) + ); + }) + ); + }); + }), + (UserManager.prototype._signoutEnd = function _signoutEnd(e) { + return this.processSignoutResponse(e).then(function (e) { + return i.Log.debug('UserManager._signoutEnd: got signout response'), e; + }); + }), + (UserManager.prototype.revokeAccessToken = function revokeAccessToken() { + var e = this; + return this._loadUser() + .then(function (t) { + return e._revokeInternal(t, !0).then(function (r) { + if (r) + return ( + i.Log.debug( + 'UserManager.revokeAccessToken: removing token properties from user and re-storing' + ), + (t.access_token = null), + (t.expires_at = null), + (t.token_type = null), + e.storeUser(t).then(function () { + i.Log.debug('UserManager.revokeAccessToken: user stored'), + e._events.load(t); + }) + ); + }); + }) + .then(function () { + i.Log.info('UserManager.revokeAccessToken: access token revoked successfully'); + }); + }), + (UserManager.prototype._revokeInternal = function _revokeInternal(e, t) { + var r = e && e.access_token; + return !r || r.indexOf('.') >= 0 + ? (i.Log.debug( + 'UserManager.revokeAccessToken: no need to revoke due to no user, token, or JWT format' + ), + Promise.resolve(!1)) + : this._tokenRevocationClient.revoke(r, t).then(function () { + return !0; + }); + }), + (UserManager.prototype.startSilentRenew = function startSilentRenew() { + this._silentRenewService.start(); + }), + (UserManager.prototype.stopSilentRenew = function stopSilentRenew() { + this._silentRenewService.stop(); + }), + (UserManager.prototype._loadUser = function _loadUser() { + return this._userStore.get(this._userStoreKey).then(function (e) { + return e + ? (i.Log.debug('UserManager._loadUser: user storageString loaded'), + a.User.fromStorageString(e)) + : (i.Log.debug('UserManager._loadUser: no user storageString'), null); + }); + }), + (UserManager.prototype.storeUser = function storeUser(e) { + if (e) { + i.Log.debug('UserManager.storeUser: storing user'); + var t = e.toStorageString(); + return this._userStore.set(this._userStoreKey, t); + } + return ( + i.Log.debug('storeUser.storeUser: removing user'), + this._userStore.remove(this._userStoreKey) + ); + }), + n(UserManager, [ + { + key: '_redirectNavigator', + get: function get() { + return this.settings.redirectNavigator; + }, + }, + { + key: '_popupNavigator', + get: function get() { + return this.settings.popupNavigator; + }, + }, + { + key: '_iframeNavigator', + get: function get() { + return this.settings.iframeNavigator; + }, + }, + { + key: '_userStore', + get: function get() { + return this.settings.userStore; + }, + }, + { + key: 'events', + get: function get() { + return this._events; + }, + }, + { + key: '_userStoreKey', + get: function get() { + return 'user:' + this.settings.authority + ':' + this.settings.client_id; + }, + }, + ]), + UserManager + ); + })(o.OidcClient); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.InMemoryWebStorage = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(0); + t.InMemoryWebStorage = (function () { + function InMemoryWebStorage() { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, InMemoryWebStorage), + (this._data = {}); + } + return ( + (InMemoryWebStorage.prototype.getItem = function getItem(e) { + return i.Log.debug('InMemoryWebStorage.getItem', e), this._data[e]; + }), + (InMemoryWebStorage.prototype.setItem = function setItem(e, t) { + i.Log.debug('InMemoryWebStorage.setItem', e), (this._data[e] = t); + }), + (InMemoryWebStorage.prototype.removeItem = function removeItem(e) { + i.Log.debug('InMemoryWebStorage.removeItem', e), delete this._data[e]; + }), + (InMemoryWebStorage.prototype.key = function key(e) { + return Object.getOwnPropertyNames(this._data)[e]; + }), + n(InMemoryWebStorage, [ + { + key: 'length', + get: function get() { + return Object.getOwnPropertyNames(this._data).length; + }, + }, + ]), + InMemoryWebStorage + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.SignoutResponse = void 0); + var n = r(2); + t.SignoutResponse = function SignoutResponse(e) { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, SignoutResponse); + var t = n.UrlUtility.parseUrlFragment(e, '?'); + (this.error = t.error), + (this.error_description = t.error_description), + (this.error_uri = t.error_uri), + (this.state = t.state); + }; + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.SignoutRequest = void 0); + var n = r(0), + i = r(2), + o = r(4); + t.SignoutRequest = function SignoutRequest(e) { + var t = e.url, + r = e.id_token_hint, + s = e.post_logout_redirect_uri, + a = e.data; + if ( + ((function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, SignoutRequest), + !t) + ) + throw (n.Log.error('SignoutRequest.ctor: No url passed'), new Error('url')); + r && (t = i.UrlUtility.addQueryParam(t, 'id_token_hint', r)), + s && + ((t = i.UrlUtility.addQueryParam(t, 'post_logout_redirect_uri', s)), + a && + ((this.state = new o.State({ data: a })), + (t = i.UrlUtility.addQueryParam(t, 'state', this.state.id)))), + (this.url = t); + }; + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.SigninResponse = void 0); + var n = (function () { + function defineProperties(e, t) { + for (var r = 0; r < t.length; r++) { + var n = t[r]; + (n.enumerable = n.enumerable || !1), + (n.configurable = !0), + 'value' in n && (n.writable = !0), + Object.defineProperty(e, n.key, n); + } + } + return function (e, t, r) { + return t && defineProperties(e.prototype, t), r && defineProperties(e, r), e; + }; + })(), + i = r(2); + t.SigninResponse = (function () { + function SigninResponse(e) { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, SigninResponse); + var t = i.UrlUtility.parseUrlFragment(e, '#'); + (this.error = t.error), + (this.error_description = t.error_description), + (this.error_uri = t.error_uri), + (this.state = t.state), + (this.id_token = t.id_token), + (this.session_state = t.session_state), + (this.access_token = t.access_token), + (this.token_type = t.token_type), + (this.scope = t.scope), + (this.profile = void 0); + var r = parseInt(t.expires_in); + if ('number' == typeof r && r > 0) { + var n = parseInt(Date.now() / 1e3); + this.expires_at = n + r; + } + } + return ( + n(SigninResponse, [ + { + key: 'expires_in', + get: function get() { + if (this.expires_at) { + var e = parseInt(Date.now() / 1e3); + return this.expires_at - e; + } + }, + }, + { + key: 'expired', + get: function get() { + var e = this.expires_in; + if (void 0 !== e) return e <= 0; + }, + }, + { + key: 'scopes', + get: function get() { + return (this.scope || '').split(' '); + }, + }, + { + key: 'isOpenIdConnect', + get: function get() { + return this.scopes.indexOf('openid') >= 0 || !!this.id_token; + }, + }, + ]), + SigninResponse + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.SigninRequest = void 0); + var n = r(0), + i = r(2), + o = r(15); + t.SigninRequest = (function () { + function SigninRequest(e) { + var t = e.url, + r = e.client_id, + s = e.redirect_uri, + a = e.response_type, + u = e.scope, + c = e.authority, + h = e.data, + f = e.prompt, + l = e.display, + g = e.max_age, + p = e.ui_locales, + d = e.id_token_hint, + v = e.login_hint, + y = e.acr_values, + m = e.resource, + S = e.request, + F = e.request_uri, + b = e.extraQueryParams; + if ( + ((function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, SigninRequest), + !t) + ) + throw (n.Log.error('SigninRequest.ctor: No url passed'), new Error('url')); + if (!r) + throw (n.Log.error('SigninRequest.ctor: No client_id passed'), new Error('client_id')); + if (!s) + throw ( + (n.Log.error('SigninRequest.ctor: No redirect_uri passed'), new Error('redirect_uri')) + ); + if (!a) + throw ( + (n.Log.error('SigninRequest.ctor: No response_type passed'), + new Error('response_type')) + ); + if (!u) throw (n.Log.error('SigninRequest.ctor: No scope passed'), new Error('scope')); + if (!c) + throw (n.Log.error('SigninRequest.ctor: No authority passed'), new Error('authority')); + var _ = SigninRequest.isOidc(a); + (this.state = new o.SigninState({ + nonce: _, + data: h, + client_id: r, + authority: c, + })), + (t = i.UrlUtility.addQueryParam(t, 'client_id', r)), + (t = i.UrlUtility.addQueryParam(t, 'redirect_uri', s)), + (t = i.UrlUtility.addQueryParam(t, 'response_type', a)), + (t = i.UrlUtility.addQueryParam(t, 'scope', u)), + (t = i.UrlUtility.addQueryParam(t, 'state', this.state.id)), + _ && (t = i.UrlUtility.addQueryParam(t, 'nonce', this.state.nonce)); + var w = { + prompt: f, + display: l, + max_age: g, + ui_locales: p, + id_token_hint: d, + login_hint: v, + acr_values: y, + resource: m, + request: S, + request_uri: F, + }; + for (var E in w) w[E] && (t = i.UrlUtility.addQueryParam(t, E, w[E])); + for (var x in b) t = i.UrlUtility.addQueryParam(t, x, b[x]); + this.url = t; + } + return ( + (SigninRequest.isOidc = function isOidc(e) { + return !!e.split(/\s+/g).filter(function (e) { + return 'id_token' === e; + })[0]; + }), + (SigninRequest.isOAuth = function isOAuth(e) { + return !!e.split(/\s+/g).filter(function (e) { + return 'token' === e; + })[0]; + }), + SigninRequest + ); + })(); + }, + function (e, t) { + var r = {}.toString; + e.exports = + Array.isArray || + function (e) { + return '[object Array]' == r.call(e); + }; + }, + function (e, t) { + (t.read = function (e, t, r, n, i) { + var o, + s, + a = 8 * i - n - 1, + u = (1 << a) - 1, + c = u >> 1, + h = -7, + f = r ? i - 1 : 0, + l = r ? -1 : 1, + g = e[t + f]; + for ( + f += l, o = g & ((1 << -h) - 1), g >>= -h, h += a; + h > 0; + o = 256 * o + e[t + f], f += l, h -= 8 + ); + for ( + s = o & ((1 << -h) - 1), o >>= -h, h += n; + h > 0; + s = 256 * s + e[t + f], f += l, h -= 8 + ); + if (0 === o) o = 1 - c; + else { + if (o === u) return s ? NaN : (1 / 0) * (g ? -1 : 1); + (s += Math.pow(2, n)), (o -= c); + } + return (g ? -1 : 1) * s * Math.pow(2, o - n); + }), + (t.write = function (e, t, r, n, i, o) { + var s, + a, + u, + c = 8 * o - i - 1, + h = (1 << c) - 1, + f = h >> 1, + l = 23 === i ? Math.pow(2, -24) - Math.pow(2, -77) : 0, + g = n ? 0 : o - 1, + p = n ? 1 : -1, + d = t < 0 || (0 === t && 1 / t < 0) ? 1 : 0; + for ( + t = Math.abs(t), + isNaN(t) || t === 1 / 0 + ? ((a = isNaN(t) ? 1 : 0), (s = h)) + : ((s = Math.floor(Math.log(t) / Math.LN2)), + t * (u = Math.pow(2, -s)) < 1 && (s--, (u *= 2)), + (t += s + f >= 1 ? l / u : l * Math.pow(2, 1 - f)) * u >= 2 && (s++, (u /= 2)), + s + f >= h + ? ((a = 0), (s = h)) + : s + f >= 1 + ? ((a = (t * u - 1) * Math.pow(2, i)), (s += f)) + : ((a = t * Math.pow(2, f - 1) * Math.pow(2, i)), (s = 0))); + i >= 8; + e[r + g] = 255 & a, g += p, a /= 256, i -= 8 + ); + for (s = (s << i) | a, c += i; c > 0; e[r + g] = 255 & s, g += p, s /= 256, c -= 8); + e[r + g - p] |= 128 * d; + }); + }, + function (e, t, r) { + 'use strict'; + (t.byteLength = function byteLength(e) { + var t = getLens(e), + r = t[0], + n = t[1]; + return (3 * (r + n)) / 4 - n; + }), + (t.toByteArray = function toByteArray(e) { + for ( + var t, + r = getLens(e), + n = r[0], + s = r[1], + a = new o( + (function _byteLength(e, t, r) { + return (3 * (t + r)) / 4 - r; + })(0, n, s) + ), + u = 0, + c = s > 0 ? n - 4 : n, + h = 0; + h < c; + h += 4 + ) + (t = + (i[e.charCodeAt(h)] << 18) | + (i[e.charCodeAt(h + 1)] << 12) | + (i[e.charCodeAt(h + 2)] << 6) | + i[e.charCodeAt(h + 3)]), + (a[u++] = (t >> 16) & 255), + (a[u++] = (t >> 8) & 255), + (a[u++] = 255 & t); + 2 === s && + ((t = (i[e.charCodeAt(h)] << 2) | (i[e.charCodeAt(h + 1)] >> 4)), (a[u++] = 255 & t)); + 1 === s && + ((t = + (i[e.charCodeAt(h)] << 10) | + (i[e.charCodeAt(h + 1)] << 4) | + (i[e.charCodeAt(h + 2)] >> 2)), + (a[u++] = (t >> 8) & 255), + (a[u++] = 255 & t)); + return a; + }), + (t.fromByteArray = function fromByteArray(e) { + for (var t, r = e.length, i = r % 3, o = [], s = 0, a = r - i; s < a; s += 16383) + o.push(encodeChunk(e, s, s + 16383 > a ? a : s + 16383)); + 1 === i + ? ((t = e[r - 1]), o.push(n[t >> 2] + n[(t << 4) & 63] + '==')) + : 2 === i && + ((t = (e[r - 2] << 8) + e[r - 1]), + o.push(n[t >> 10] + n[(t >> 4) & 63] + n[(t << 2) & 63] + '=')); + return o.join(''); + }); + for ( + var n = [], + i = [], + o = 'undefined' != typeof Uint8Array ? Uint8Array : Array, + s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', + a = 0, + u = s.length; + a < u; + ++a + ) + (n[a] = s[a]), (i[s.charCodeAt(a)] = a); + function getLens(e) { + var t = e.length; + if (t % 4 > 0) throw new Error('Invalid string. Length must be a multiple of 4'); + var r = e.indexOf('='); + return -1 === r && (r = t), [r, r === t ? 0 : 4 - (r % 4)]; + } + function tripletToBase64(e) { + return n[(e >> 18) & 63] + n[(e >> 12) & 63] + n[(e >> 6) & 63] + n[63 & e]; + } + function encodeChunk(e, t, r) { + for (var n, i = [], o = t; o < r; o += 3) + (n = ((e[o] << 16) & 16711680) + ((e[o + 1] << 8) & 65280) + (255 & e[o + 2])), + i.push(tripletToBase64(n)); + return i.join(''); + } + (i['-'.charCodeAt(0)] = 62), (i['_'.charCodeAt(0)] = 63); + }, + function (e, t) { + var r; + r = (function () { + return this; + })(); + try { + r = r || Function('return this')() || (0, eval)('this'); + } catch (e) { + 'object' == typeof window && (r = window); + } + e.exports = r; + }, + function (e, t, r) { + 'use strict'; + (function (e) { + /*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ + var n = r(38), + i = r(37), + o = r(36); + function kMaxLength() { + return Buffer.TYPED_ARRAY_SUPPORT ? 2147483647 : 1073741823; + } + function createBuffer(e, t) { + if (kMaxLength() < t) throw new RangeError('Invalid typed array length'); + return ( + Buffer.TYPED_ARRAY_SUPPORT + ? ((e = new Uint8Array(t)).__proto__ = Buffer.prototype) + : (null === e && (e = new Buffer(t)), (e.length = t)), + e + ); + } + function Buffer(e, t, r) { + if (!(Buffer.TYPED_ARRAY_SUPPORT || this instanceof Buffer)) return new Buffer(e, t, r); + if ('number' == typeof e) { + if ('string' == typeof t) + throw new Error('If encoding is specified then the first argument must be a string'); + return allocUnsafe(this, e); + } + return from(this, e, t, r); + } + function from(e, t, r, n) { + if ('number' == typeof t) throw new TypeError('"value" argument must not be a number'); + return 'undefined' != typeof ArrayBuffer && t instanceof ArrayBuffer + ? (function fromArrayBuffer(e, t, r, n) { + if ((t.byteLength, r < 0 || t.byteLength < r)) + throw new RangeError("'offset' is out of bounds"); + if (t.byteLength < r + (n || 0)) throw new RangeError("'length' is out of bounds"); + t = + void 0 === r && void 0 === n + ? new Uint8Array(t) + : void 0 === n + ? new Uint8Array(t, r) + : new Uint8Array(t, r, n); + Buffer.TYPED_ARRAY_SUPPORT + ? ((e = t).__proto__ = Buffer.prototype) + : (e = fromArrayLike(e, t)); + return e; + })(e, t, r, n) + : 'string' == typeof t + ? (function fromString(e, t, r) { + ('string' == typeof r && '' !== r) || (r = 'utf8'); + if (!Buffer.isEncoding(r)) + throw new TypeError('"encoding" must be a valid string encoding'); + var n = 0 | byteLength(t, r), + i = (e = createBuffer(e, n)).write(t, r); + i !== n && (e = e.slice(0, i)); + return e; + })(e, t, r) + : (function fromObject(e, t) { + if (Buffer.isBuffer(t)) { + var r = 0 | checked(t.length); + return 0 === (e = createBuffer(e, r)).length ? e : (t.copy(e, 0, 0, r), e); + } + if (t) { + if ( + ('undefined' != typeof ArrayBuffer && t.buffer instanceof ArrayBuffer) || + 'length' in t + ) + return 'number' != typeof t.length || + (function isnan(e) { + return e != e; + })(t.length) + ? createBuffer(e, 0) + : fromArrayLike(e, t); + if ('Buffer' === t.type && o(t.data)) return fromArrayLike(e, t.data); + } + throw new TypeError( + 'First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.' + ); + })(e, t); + } + function assertSize(e) { + if ('number' != typeof e) throw new TypeError('"size" argument must be a number'); + if (e < 0) throw new RangeError('"size" argument must not be negative'); + } + function allocUnsafe(e, t) { + if ( + (assertSize(t), + (e = createBuffer(e, t < 0 ? 0 : 0 | checked(t))), + !Buffer.TYPED_ARRAY_SUPPORT) + ) + for (var r = 0; r < t; ++r) e[r] = 0; + return e; + } + function fromArrayLike(e, t) { + var r = t.length < 0 ? 0 : 0 | checked(t.length); + e = createBuffer(e, r); + for (var n = 0; n < r; n += 1) e[n] = 255 & t[n]; + return e; + } + function checked(e) { + if (e >= kMaxLength()) + throw new RangeError( + 'Attempt to allocate Buffer larger than maximum size: 0x' + + kMaxLength().toString(16) + + ' bytes' + ); + return 0 | e; + } + function byteLength(e, t) { + if (Buffer.isBuffer(e)) return e.length; + if ( + 'undefined' != typeof ArrayBuffer && + 'function' == typeof ArrayBuffer.isView && + (ArrayBuffer.isView(e) || e instanceof ArrayBuffer) + ) + return e.byteLength; + 'string' != typeof e && (e = '' + e); + var r = e.length; + if (0 === r) return 0; + for (var n = !1; ; ) + switch (t) { + case 'ascii': + case 'latin1': + case 'binary': + return r; + case 'utf8': + case 'utf-8': + case void 0: + return utf8ToBytes(e).length; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return 2 * r; + case 'hex': + return r >>> 1; + case 'base64': + return base64ToBytes(e).length; + default: + if (n) return utf8ToBytes(e).length; + (t = ('' + t).toLowerCase()), (n = !0); + } + } + function swap(e, t, r) { + var n = e[t]; + (e[t] = e[r]), (e[r] = n); + } + function bidirectionalIndexOf(e, t, r, n, i) { + if (0 === e.length) return -1; + if ( + ('string' == typeof r + ? ((n = r), (r = 0)) + : r > 2147483647 + ? (r = 2147483647) + : r < -2147483648 && (r = -2147483648), + (r = +r), + isNaN(r) && (r = i ? 0 : e.length - 1), + r < 0 && (r = e.length + r), + r >= e.length) + ) { + if (i) return -1; + r = e.length - 1; + } else if (r < 0) { + if (!i) return -1; + r = 0; + } + if (('string' == typeof t && (t = Buffer.from(t, n)), Buffer.isBuffer(t))) + return 0 === t.length ? -1 : arrayIndexOf(e, t, r, n, i); + if ('number' == typeof t) + return ( + (t &= 255), + Buffer.TYPED_ARRAY_SUPPORT && 'function' == typeof Uint8Array.prototype.indexOf + ? i + ? Uint8Array.prototype.indexOf.call(e, t, r) + : Uint8Array.prototype.lastIndexOf.call(e, t, r) + : arrayIndexOf(e, [t], r, n, i) + ); + throw new TypeError('val must be string, number or Buffer'); + } + function arrayIndexOf(e, t, r, n, i) { + var o, + s = 1, + a = e.length, + u = t.length; + if ( + void 0 !== n && + ('ucs2' === (n = String(n).toLowerCase()) || + 'ucs-2' === n || + 'utf16le' === n || + 'utf-16le' === n) + ) { + if (e.length < 2 || t.length < 2) return -1; + (s = 2), (a /= 2), (u /= 2), (r /= 2); + } + function read(e, t) { + return 1 === s ? e[t] : e.readUInt16BE(t * s); + } + if (i) { + var c = -1; + for (o = r; o < a; o++) + if (read(e, o) === read(t, -1 === c ? 0 : o - c)) { + if ((-1 === c && (c = o), o - c + 1 === u)) return c * s; + } else -1 !== c && (o -= o - c), (c = -1); + } else + for (r + u > a && (r = a - u), o = r; o >= 0; o--) { + for (var h = !0, f = 0; f < u; f++) + if (read(e, o + f) !== read(t, f)) { + h = !1; + break; + } + if (h) return o; + } + return -1; + } + function hexWrite(e, t, r, n) { + r = Number(r) || 0; + var i = e.length - r; + n ? (n = Number(n)) > i && (n = i) : (n = i); + var o = t.length; + if (o % 2 != 0) throw new TypeError('Invalid hex string'); + n > o / 2 && (n = o / 2); + for (var s = 0; s < n; ++s) { + var a = parseInt(t.substr(2 * s, 2), 16); + if (isNaN(a)) return s; + e[r + s] = a; + } + return s; + } + function utf8Write(e, t, r, n) { + return blitBuffer(utf8ToBytes(t, e.length - r), e, r, n); + } + function asciiWrite(e, t, r, n) { + return blitBuffer( + (function asciiToBytes(e) { + for (var t = [], r = 0; r < e.length; ++r) t.push(255 & e.charCodeAt(r)); + return t; + })(t), + e, + r, + n + ); + } + function latin1Write(e, t, r, n) { + return asciiWrite(e, t, r, n); + } + function base64Write(e, t, r, n) { + return blitBuffer(base64ToBytes(t), e, r, n); + } + function ucs2Write(e, t, r, n) { + return blitBuffer( + (function utf16leToBytes(e, t) { + for (var r, n, i, o = [], s = 0; s < e.length && !((t -= 2) < 0); ++s) + (r = e.charCodeAt(s)), (n = r >> 8), (i = r % 256), o.push(i), o.push(n); + return o; + })(t, e.length - r), + e, + r, + n + ); + } + function base64Slice(e, t, r) { + return 0 === t && r === e.length ? n.fromByteArray(e) : n.fromByteArray(e.slice(t, r)); + } + function utf8Slice(e, t, r) { + r = Math.min(e.length, r); + for (var n = [], i = t; i < r; ) { + var o, + a, + u, + c, + h = e[i], + f = null, + l = h > 239 ? 4 : h > 223 ? 3 : h > 191 ? 2 : 1; + if (i + l <= r) + switch (l) { + case 1: + h < 128 && (f = h); + break; + case 2: + 128 == (192 & (o = e[i + 1])) && + (c = ((31 & h) << 6) | (63 & o)) > 127 && + (f = c); + break; + case 3: + (o = e[i + 1]), + (a = e[i + 2]), + 128 == (192 & o) && + 128 == (192 & a) && + (c = ((15 & h) << 12) | ((63 & o) << 6) | (63 & a)) > 2047 && + (c < 55296 || c > 57343) && + (f = c); + break; + case 4: + (o = e[i + 1]), + (a = e[i + 2]), + (u = e[i + 3]), + 128 == (192 & o) && + 128 == (192 & a) && + 128 == (192 & u) && + (c = ((15 & h) << 18) | ((63 & o) << 12) | ((63 & a) << 6) | (63 & u)) > + 65535 && + c < 1114112 && + (f = c); + } + null === f + ? ((f = 65533), (l = 1)) + : f > 65535 && + ((f -= 65536), n.push(((f >>> 10) & 1023) | 55296), (f = 56320 | (1023 & f))), + n.push(f), + (i += l); + } + return (function decodeCodePointsArray(e) { + var t = e.length; + if (t <= s) return String.fromCharCode.apply(String, e); + var r = '', + n = 0; + for (; n < t; ) r += String.fromCharCode.apply(String, e.slice(n, (n += s))); + return r; + })(n); + } + (t.Buffer = Buffer), + (t.SlowBuffer = function SlowBuffer(e) { + +e != e && (e = 0); + return Buffer.alloc(+e); + }), + (t.INSPECT_MAX_BYTES = 50), + (Buffer.TYPED_ARRAY_SUPPORT = + void 0 !== e.TYPED_ARRAY_SUPPORT + ? e.TYPED_ARRAY_SUPPORT + : (function typedArraySupport() { + try { + var e = new Uint8Array(1); + return ( + (e.__proto__ = { + __proto__: Uint8Array.prototype, + foo: function () { + return 42; + }, + }), + 42 === e.foo() && + 'function' == typeof e.subarray && + 0 === e.subarray(1, 1).byteLength + ); + } catch (e) { + return !1; + } + })()), + (t.kMaxLength = kMaxLength()), + (Buffer.poolSize = 8192), + (Buffer._augment = function (e) { + return (e.__proto__ = Buffer.prototype), e; + }), + (Buffer.from = function (e, t, r) { + return from(null, e, t, r); + }), + Buffer.TYPED_ARRAY_SUPPORT && + ((Buffer.prototype.__proto__ = Uint8Array.prototype), + (Buffer.__proto__ = Uint8Array), + 'undefined' != typeof Symbol && + Symbol.species && + Buffer[Symbol.species] === Buffer && + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: !0, + })), + (Buffer.alloc = function (e, t, r) { + return (function alloc(e, t, r, n) { + return ( + assertSize(t), + t <= 0 + ? createBuffer(e, t) + : void 0 !== r + ? 'string' == typeof n + ? createBuffer(e, t).fill(r, n) + : createBuffer(e, t).fill(r) + : createBuffer(e, t) + ); + })(null, e, t, r); + }), + (Buffer.allocUnsafe = function (e) { + return allocUnsafe(null, e); + }), + (Buffer.allocUnsafeSlow = function (e) { + return allocUnsafe(null, e); + }), + (Buffer.isBuffer = function isBuffer(e) { + return !(null == e || !e._isBuffer); + }), + (Buffer.compare = function compare(e, t) { + if (!Buffer.isBuffer(e) || !Buffer.isBuffer(t)) + throw new TypeError('Arguments must be Buffers'); + if (e === t) return 0; + for (var r = e.length, n = t.length, i = 0, o = Math.min(r, n); i < o; ++i) + if (e[i] !== t[i]) { + (r = e[i]), (n = t[i]); + break; + } + return r < n ? -1 : n < r ? 1 : 0; + }), + (Buffer.isEncoding = function isEncoding(e) { + switch (String(e).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return !0; + default: + return !1; + } + }), + (Buffer.concat = function concat(e, t) { + if (!o(e)) throw new TypeError('"list" argument must be an Array of Buffers'); + if (0 === e.length) return Buffer.alloc(0); + var r; + if (void 0 === t) for (t = 0, r = 0; r < e.length; ++r) t += e[r].length; + var n = Buffer.allocUnsafe(t), + i = 0; + for (r = 0; r < e.length; ++r) { + var s = e[r]; + if (!Buffer.isBuffer(s)) + throw new TypeError('"list" argument must be an Array of Buffers'); + s.copy(n, i), (i += s.length); + } + return n; + }), + (Buffer.byteLength = byteLength), + (Buffer.prototype._isBuffer = !0), + (Buffer.prototype.swap16 = function swap16() { + var e = this.length; + if (e % 2 != 0) throw new RangeError('Buffer size must be a multiple of 16-bits'); + for (var t = 0; t < e; t += 2) swap(this, t, t + 1); + return this; + }), + (Buffer.prototype.swap32 = function swap32() { + var e = this.length; + if (e % 4 != 0) throw new RangeError('Buffer size must be a multiple of 32-bits'); + for (var t = 0; t < e; t += 4) swap(this, t, t + 3), swap(this, t + 1, t + 2); + return this; + }), + (Buffer.prototype.swap64 = function swap64() { + var e = this.length; + if (e % 8 != 0) throw new RangeError('Buffer size must be a multiple of 64-bits'); + for (var t = 0; t < e; t += 8) + swap(this, t, t + 7), + swap(this, t + 1, t + 6), + swap(this, t + 2, t + 5), + swap(this, t + 3, t + 4); + return this; + }), + (Buffer.prototype.toString = function toString() { + var e = 0 | this.length; + return 0 === e + ? '' + : 0 === arguments.length + ? utf8Slice(this, 0, e) + : function slowToString(e, t, r) { + var n = !1; + if (((void 0 === t || t < 0) && (t = 0), t > this.length)) return ''; + if (((void 0 === r || r > this.length) && (r = this.length), r <= 0)) return ''; + if ((r >>>= 0) <= (t >>>= 0)) return ''; + for (e || (e = 'utf8'); ; ) + switch (e) { + case 'hex': + return hexSlice(this, t, r); + case 'utf8': + case 'utf-8': + return utf8Slice(this, t, r); + case 'ascii': + return asciiSlice(this, t, r); + case 'latin1': + case 'binary': + return latin1Slice(this, t, r); + case 'base64': + return base64Slice(this, t, r); + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, t, r); + default: + if (n) throw new TypeError('Unknown encoding: ' + e); + (e = (e + '').toLowerCase()), (n = !0); + } + }.apply(this, arguments); + }), + (Buffer.prototype.equals = function equals(e) { + if (!Buffer.isBuffer(e)) throw new TypeError('Argument must be a Buffer'); + return this === e || 0 === Buffer.compare(this, e); + }), + (Buffer.prototype.inspect = function inspect() { + var e = '', + r = t.INSPECT_MAX_BYTES; + return ( + this.length > 0 && + ((e = this.toString('hex', 0, r).match(/.{2}/g).join(' ')), + this.length > r && (e += ' ... ')), + '' + ); + }), + (Buffer.prototype.compare = function compare(e, t, r, n, i) { + if (!Buffer.isBuffer(e)) throw new TypeError('Argument must be a Buffer'); + if ( + (void 0 === t && (t = 0), + void 0 === r && (r = e ? e.length : 0), + void 0 === n && (n = 0), + void 0 === i && (i = this.length), + t < 0 || r > e.length || n < 0 || i > this.length) + ) + throw new RangeError('out of range index'); + if (n >= i && t >= r) return 0; + if (n >= i) return -1; + if (t >= r) return 1; + if (((t >>>= 0), (r >>>= 0), (n >>>= 0), (i >>>= 0), this === e)) return 0; + for ( + var o = i - n, + s = r - t, + a = Math.min(o, s), + u = this.slice(n, i), + c = e.slice(t, r), + h = 0; + h < a; + ++h + ) + if (u[h] !== c[h]) { + (o = u[h]), (s = c[h]); + break; + } + return o < s ? -1 : s < o ? 1 : 0; + }), + (Buffer.prototype.includes = function includes(e, t, r) { + return -1 !== this.indexOf(e, t, r); + }), + (Buffer.prototype.indexOf = function indexOf(e, t, r) { + return bidirectionalIndexOf(this, e, t, r, !0); + }), + (Buffer.prototype.lastIndexOf = function lastIndexOf(e, t, r) { + return bidirectionalIndexOf(this, e, t, r, !1); + }), + (Buffer.prototype.write = function write(e, t, r, n) { + if (void 0 === t) (n = 'utf8'), (r = this.length), (t = 0); + else if (void 0 === r && 'string' == typeof t) (n = t), (r = this.length), (t = 0); + else { + if (!isFinite(t)) + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ); + (t |= 0), + isFinite(r) ? ((r |= 0), void 0 === n && (n = 'utf8')) : ((n = r), (r = void 0)); + } + var i = this.length - t; + if ( + ((void 0 === r || r > i) && (r = i), + (e.length > 0 && (r < 0 || t < 0)) || t > this.length) + ) + throw new RangeError('Attempt to write outside buffer bounds'); + n || (n = 'utf8'); + for (var o = !1; ; ) + switch (n) { + case 'hex': + return hexWrite(this, e, t, r); + case 'utf8': + case 'utf-8': + return utf8Write(this, e, t, r); + case 'ascii': + return asciiWrite(this, e, t, r); + case 'latin1': + case 'binary': + return latin1Write(this, e, t, r); + case 'base64': + return base64Write(this, e, t, r); + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, e, t, r); + default: + if (o) throw new TypeError('Unknown encoding: ' + n); + (n = ('' + n).toLowerCase()), (o = !0); + } + }), + (Buffer.prototype.toJSON = function toJSON() { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0), + }; + }); + var s = 4096; + function asciiSlice(e, t, r) { + var n = ''; + r = Math.min(e.length, r); + for (var i = t; i < r; ++i) n += String.fromCharCode(127 & e[i]); + return n; + } + function latin1Slice(e, t, r) { + var n = ''; + r = Math.min(e.length, r); + for (var i = t; i < r; ++i) n += String.fromCharCode(e[i]); + return n; + } + function hexSlice(e, t, r) { + var n = e.length; + (!t || t < 0) && (t = 0), (!r || r < 0 || r > n) && (r = n); + for (var i = '', o = t; o < r; ++o) i += toHex(e[o]); + return i; + } + function utf16leSlice(e, t, r) { + for (var n = e.slice(t, r), i = '', o = 0; o < n.length; o += 2) + i += String.fromCharCode(n[o] + 256 * n[o + 1]); + return i; + } + function checkOffset(e, t, r) { + if (e % 1 != 0 || e < 0) throw new RangeError('offset is not uint'); + if (e + t > r) throw new RangeError('Trying to access beyond buffer length'); + } + function checkInt(e, t, r, n, i, o) { + if (!Buffer.isBuffer(e)) + throw new TypeError('"buffer" argument must be a Buffer instance'); + if (t > i || t < o) throw new RangeError('"value" argument is out of bounds'); + if (r + n > e.length) throw new RangeError('Index out of range'); + } + function objectWriteUInt16(e, t, r, n) { + t < 0 && (t = 65535 + t + 1); + for (var i = 0, o = Math.min(e.length - r, 2); i < o; ++i) + e[r + i] = (t & (255 << (8 * (n ? i : 1 - i)))) >>> (8 * (n ? i : 1 - i)); + } + function objectWriteUInt32(e, t, r, n) { + t < 0 && (t = 4294967295 + t + 1); + for (var i = 0, o = Math.min(e.length - r, 4); i < o; ++i) + e[r + i] = (t >>> (8 * (n ? i : 3 - i))) & 255; + } + function checkIEEE754(e, t, r, n, i, o) { + if (r + n > e.length) throw new RangeError('Index out of range'); + if (r < 0) throw new RangeError('Index out of range'); + } + function writeFloat(e, t, r, n, o) { + return o || checkIEEE754(e, 0, r, 4), i.write(e, t, r, n, 23, 4), r + 4; + } + function writeDouble(e, t, r, n, o) { + return o || checkIEEE754(e, 0, r, 8), i.write(e, t, r, n, 52, 8), r + 8; + } + (Buffer.prototype.slice = function slice(e, t) { + var r, + n = this.length; + if ( + ((e = ~~e), + (t = void 0 === t ? n : ~~t), + e < 0 ? (e += n) < 0 && (e = 0) : e > n && (e = n), + t < 0 ? (t += n) < 0 && (t = 0) : t > n && (t = n), + t < e && (t = e), + Buffer.TYPED_ARRAY_SUPPORT) + ) + (r = this.subarray(e, t)).__proto__ = Buffer.prototype; + else { + var i = t - e; + r = new Buffer(i, void 0); + for (var o = 0; o < i; ++o) r[o] = this[o + e]; + } + return r; + }), + (Buffer.prototype.readUIntLE = function readUIntLE(e, t, r) { + (e |= 0), (t |= 0), r || checkOffset(e, t, this.length); + for (var n = this[e], i = 1, o = 0; ++o < t && (i *= 256); ) n += this[e + o] * i; + return n; + }), + (Buffer.prototype.readUIntBE = function readUIntBE(e, t, r) { + (e |= 0), (t |= 0), r || checkOffset(e, t, this.length); + for (var n = this[e + --t], i = 1; t > 0 && (i *= 256); ) n += this[e + --t] * i; + return n; + }), + (Buffer.prototype.readUInt8 = function readUInt8(e, t) { + return t || checkOffset(e, 1, this.length), this[e]; + }), + (Buffer.prototype.readUInt16LE = function readUInt16LE(e, t) { + return t || checkOffset(e, 2, this.length), this[e] | (this[e + 1] << 8); + }), + (Buffer.prototype.readUInt16BE = function readUInt16BE(e, t) { + return t || checkOffset(e, 2, this.length), (this[e] << 8) | this[e + 1]; + }), + (Buffer.prototype.readUInt32LE = function readUInt32LE(e, t) { + return ( + t || checkOffset(e, 4, this.length), + (this[e] | (this[e + 1] << 8) | (this[e + 2] << 16)) + 16777216 * this[e + 3] + ); + }), + (Buffer.prototype.readUInt32BE = function readUInt32BE(e, t) { + return ( + t || checkOffset(e, 4, this.length), + 16777216 * this[e] + ((this[e + 1] << 16) | (this[e + 2] << 8) | this[e + 3]) + ); + }), + (Buffer.prototype.readIntLE = function readIntLE(e, t, r) { + (e |= 0), (t |= 0), r || checkOffset(e, t, this.length); + for (var n = this[e], i = 1, o = 0; ++o < t && (i *= 256); ) n += this[e + o] * i; + return n >= (i *= 128) && (n -= Math.pow(2, 8 * t)), n; + }), + (Buffer.prototype.readIntBE = function readIntBE(e, t, r) { + (e |= 0), (t |= 0), r || checkOffset(e, t, this.length); + for (var n = t, i = 1, o = this[e + --n]; n > 0 && (i *= 256); ) o += this[e + --n] * i; + return o >= (i *= 128) && (o -= Math.pow(2, 8 * t)), o; + }), + (Buffer.prototype.readInt8 = function readInt8(e, t) { + return ( + t || checkOffset(e, 1, this.length), + 128 & this[e] ? -1 * (255 - this[e] + 1) : this[e] + ); + }), + (Buffer.prototype.readInt16LE = function readInt16LE(e, t) { + t || checkOffset(e, 2, this.length); + var r = this[e] | (this[e + 1] << 8); + return 32768 & r ? 4294901760 | r : r; + }), + (Buffer.prototype.readInt16BE = function readInt16BE(e, t) { + t || checkOffset(e, 2, this.length); + var r = this[e + 1] | (this[e] << 8); + return 32768 & r ? 4294901760 | r : r; + }), + (Buffer.prototype.readInt32LE = function readInt32LE(e, t) { + return ( + t || checkOffset(e, 4, this.length), + this[e] | (this[e + 1] << 8) | (this[e + 2] << 16) | (this[e + 3] << 24) + ); + }), + (Buffer.prototype.readInt32BE = function readInt32BE(e, t) { + return ( + t || checkOffset(e, 4, this.length), + (this[e] << 24) | (this[e + 1] << 16) | (this[e + 2] << 8) | this[e + 3] + ); + }), + (Buffer.prototype.readFloatLE = function readFloatLE(e, t) { + return t || checkOffset(e, 4, this.length), i.read(this, e, !0, 23, 4); + }), + (Buffer.prototype.readFloatBE = function readFloatBE(e, t) { + return t || checkOffset(e, 4, this.length), i.read(this, e, !1, 23, 4); + }), + (Buffer.prototype.readDoubleLE = function readDoubleLE(e, t) { + return t || checkOffset(e, 8, this.length), i.read(this, e, !0, 52, 8); + }), + (Buffer.prototype.readDoubleBE = function readDoubleBE(e, t) { + return t || checkOffset(e, 8, this.length), i.read(this, e, !1, 52, 8); + }), + (Buffer.prototype.writeUIntLE = function writeUIntLE(e, t, r, n) { + ((e = +e), (t |= 0), (r |= 0), n) || checkInt(this, e, t, r, Math.pow(2, 8 * r) - 1, 0); + var i = 1, + o = 0; + for (this[t] = 255 & e; ++o < r && (i *= 256); ) this[t + o] = (e / i) & 255; + return t + r; + }), + (Buffer.prototype.writeUIntBE = function writeUIntBE(e, t, r, n) { + ((e = +e), (t |= 0), (r |= 0), n) || checkInt(this, e, t, r, Math.pow(2, 8 * r) - 1, 0); + var i = r - 1, + o = 1; + for (this[t + i] = 255 & e; --i >= 0 && (o *= 256); ) this[t + i] = (e / o) & 255; + return t + r; + }), + (Buffer.prototype.writeUInt8 = function writeUInt8(e, t, r) { + return ( + (e = +e), + (t |= 0), + r || checkInt(this, e, t, 1, 255, 0), + Buffer.TYPED_ARRAY_SUPPORT || (e = Math.floor(e)), + (this[t] = 255 & e), + t + 1 + ); + }), + (Buffer.prototype.writeUInt16LE = function writeUInt16LE(e, t, r) { + return ( + (e = +e), + (t |= 0), + r || checkInt(this, e, t, 2, 65535, 0), + Buffer.TYPED_ARRAY_SUPPORT + ? ((this[t] = 255 & e), (this[t + 1] = e >>> 8)) + : objectWriteUInt16(this, e, t, !0), + t + 2 + ); + }), + (Buffer.prototype.writeUInt16BE = function writeUInt16BE(e, t, r) { + return ( + (e = +e), + (t |= 0), + r || checkInt(this, e, t, 2, 65535, 0), + Buffer.TYPED_ARRAY_SUPPORT + ? ((this[t] = e >>> 8), (this[t + 1] = 255 & e)) + : objectWriteUInt16(this, e, t, !1), + t + 2 + ); + }), + (Buffer.prototype.writeUInt32LE = function writeUInt32LE(e, t, r) { + return ( + (e = +e), + (t |= 0), + r || checkInt(this, e, t, 4, 4294967295, 0), + Buffer.TYPED_ARRAY_SUPPORT + ? ((this[t + 3] = e >>> 24), + (this[t + 2] = e >>> 16), + (this[t + 1] = e >>> 8), + (this[t] = 255 & e)) + : objectWriteUInt32(this, e, t, !0), + t + 4 + ); + }), + (Buffer.prototype.writeUInt32BE = function writeUInt32BE(e, t, r) { + return ( + (e = +e), + (t |= 0), + r || checkInt(this, e, t, 4, 4294967295, 0), + Buffer.TYPED_ARRAY_SUPPORT + ? ((this[t] = e >>> 24), + (this[t + 1] = e >>> 16), + (this[t + 2] = e >>> 8), + (this[t + 3] = 255 & e)) + : objectWriteUInt32(this, e, t, !1), + t + 4 + ); + }), + (Buffer.prototype.writeIntLE = function writeIntLE(e, t, r, n) { + if (((e = +e), (t |= 0), !n)) { + var i = Math.pow(2, 8 * r - 1); + checkInt(this, e, t, r, i - 1, -i); + } + var o = 0, + s = 1, + a = 0; + for (this[t] = 255 & e; ++o < r && (s *= 256); ) + e < 0 && 0 === a && 0 !== this[t + o - 1] && (a = 1), + (this[t + o] = (((e / s) >> 0) - a) & 255); + return t + r; + }), + (Buffer.prototype.writeIntBE = function writeIntBE(e, t, r, n) { + if (((e = +e), (t |= 0), !n)) { + var i = Math.pow(2, 8 * r - 1); + checkInt(this, e, t, r, i - 1, -i); + } + var o = r - 1, + s = 1, + a = 0; + for (this[t + o] = 255 & e; --o >= 0 && (s *= 256); ) + e < 0 && 0 === a && 0 !== this[t + o + 1] && (a = 1), + (this[t + o] = (((e / s) >> 0) - a) & 255); + return t + r; + }), + (Buffer.prototype.writeInt8 = function writeInt8(e, t, r) { + return ( + (e = +e), + (t |= 0), + r || checkInt(this, e, t, 1, 127, -128), + Buffer.TYPED_ARRAY_SUPPORT || (e = Math.floor(e)), + e < 0 && (e = 255 + e + 1), + (this[t] = 255 & e), + t + 1 + ); + }), + (Buffer.prototype.writeInt16LE = function writeInt16LE(e, t, r) { + return ( + (e = +e), + (t |= 0), + r || checkInt(this, e, t, 2, 32767, -32768), + Buffer.TYPED_ARRAY_SUPPORT + ? ((this[t] = 255 & e), (this[t + 1] = e >>> 8)) + : objectWriteUInt16(this, e, t, !0), + t + 2 + ); + }), + (Buffer.prototype.writeInt16BE = function writeInt16BE(e, t, r) { + return ( + (e = +e), + (t |= 0), + r || checkInt(this, e, t, 2, 32767, -32768), + Buffer.TYPED_ARRAY_SUPPORT + ? ((this[t] = e >>> 8), (this[t + 1] = 255 & e)) + : objectWriteUInt16(this, e, t, !1), + t + 2 + ); + }), + (Buffer.prototype.writeInt32LE = function writeInt32LE(e, t, r) { + return ( + (e = +e), + (t |= 0), + r || checkInt(this, e, t, 4, 2147483647, -2147483648), + Buffer.TYPED_ARRAY_SUPPORT + ? ((this[t] = 255 & e), + (this[t + 1] = e >>> 8), + (this[t + 2] = e >>> 16), + (this[t + 3] = e >>> 24)) + : objectWriteUInt32(this, e, t, !0), + t + 4 + ); + }), + (Buffer.prototype.writeInt32BE = function writeInt32BE(e, t, r) { + return ( + (e = +e), + (t |= 0), + r || checkInt(this, e, t, 4, 2147483647, -2147483648), + e < 0 && (e = 4294967295 + e + 1), + Buffer.TYPED_ARRAY_SUPPORT + ? ((this[t] = e >>> 24), + (this[t + 1] = e >>> 16), + (this[t + 2] = e >>> 8), + (this[t + 3] = 255 & e)) + : objectWriteUInt32(this, e, t, !1), + t + 4 + ); + }), + (Buffer.prototype.writeFloatLE = function writeFloatLE(e, t, r) { + return writeFloat(this, e, t, !0, r); + }), + (Buffer.prototype.writeFloatBE = function writeFloatBE(e, t, r) { + return writeFloat(this, e, t, !1, r); + }), + (Buffer.prototype.writeDoubleLE = function writeDoubleLE(e, t, r) { + return writeDouble(this, e, t, !0, r); + }), + (Buffer.prototype.writeDoubleBE = function writeDoubleBE(e, t, r) { + return writeDouble(this, e, t, !1, r); + }), + (Buffer.prototype.copy = function copy(e, t, r, n) { + if ( + (r || (r = 0), + n || 0 === n || (n = this.length), + t >= e.length && (t = e.length), + t || (t = 0), + n > 0 && n < r && (n = r), + n === r) + ) + return 0; + if (0 === e.length || 0 === this.length) return 0; + if (t < 0) throw new RangeError('targetStart out of bounds'); + if (r < 0 || r >= this.length) throw new RangeError('sourceStart out of bounds'); + if (n < 0) throw new RangeError('sourceEnd out of bounds'); + n > this.length && (n = this.length), e.length - t < n - r && (n = e.length - t + r); + var i, + o = n - r; + if (this === e && r < t && t < n) for (i = o - 1; i >= 0; --i) e[i + t] = this[i + r]; + else if (o < 1e3 || !Buffer.TYPED_ARRAY_SUPPORT) + for (i = 0; i < o; ++i) e[i + t] = this[i + r]; + else Uint8Array.prototype.set.call(e, this.subarray(r, r + o), t); + return o; + }), + (Buffer.prototype.fill = function fill(e, t, r, n) { + if ('string' == typeof e) { + if ( + ('string' == typeof t + ? ((n = t), (t = 0), (r = this.length)) + : 'string' == typeof r && ((n = r), (r = this.length)), + 1 === e.length) + ) { + var i = e.charCodeAt(0); + i < 256 && (e = i); + } + if (void 0 !== n && 'string' != typeof n) + throw new TypeError('encoding must be a string'); + if ('string' == typeof n && !Buffer.isEncoding(n)) + throw new TypeError('Unknown encoding: ' + n); + } else 'number' == typeof e && (e &= 255); + if (t < 0 || this.length < t || this.length < r) + throw new RangeError('Out of range index'); + if (r <= t) return this; + var o; + if ( + ((t >>>= 0), + (r = void 0 === r ? this.length : r >>> 0), + e || (e = 0), + 'number' == typeof e) + ) + for (o = t; o < r; ++o) this[o] = e; + else { + var s = Buffer.isBuffer(e) ? e : utf8ToBytes(new Buffer(e, n).toString()), + a = s.length; + for (o = 0; o < r - t; ++o) this[o + t] = s[o % a]; + } + return this; + }); + var a = /[^+\/0-9A-Za-z-_]/g; + function toHex(e) { + return e < 16 ? '0' + e.toString(16) : e.toString(16); + } + function utf8ToBytes(e, t) { + var r; + t = t || 1 / 0; + for (var n = e.length, i = null, o = [], s = 0; s < n; ++s) { + if ((r = e.charCodeAt(s)) > 55295 && r < 57344) { + if (!i) { + if (r > 56319) { + (t -= 3) > -1 && o.push(239, 191, 189); + continue; + } + if (s + 1 === n) { + (t -= 3) > -1 && o.push(239, 191, 189); + continue; + } + i = r; + continue; + } + if (r < 56320) { + (t -= 3) > -1 && o.push(239, 191, 189), (i = r); + continue; + } + r = 65536 + (((i - 55296) << 10) | (r - 56320)); + } else i && (t -= 3) > -1 && o.push(239, 191, 189); + if (((i = null), r < 128)) { + if ((t -= 1) < 0) break; + o.push(r); + } else if (r < 2048) { + if ((t -= 2) < 0) break; + o.push((r >> 6) | 192, (63 & r) | 128); + } else if (r < 65536) { + if ((t -= 3) < 0) break; + o.push((r >> 12) | 224, ((r >> 6) & 63) | 128, (63 & r) | 128); + } else { + if (!(r < 1114112)) throw new Error('Invalid code point'); + if ((t -= 4) < 0) break; + o.push( + (r >> 18) | 240, + ((r >> 12) & 63) | 128, + ((r >> 6) & 63) | 128, + (63 & r) | 128 + ); + } + } + return o; + } + function base64ToBytes(e) { + return n.toByteArray( + (function base64clean(e) { + if ( + (e = (function stringtrim(e) { + return e.trim ? e.trim() : e.replace(/^\s+|\s+$/g, ''); + })(e).replace(a, '')).length < 2 + ) + return ''; + for (; e.length % 4 != 0; ) e += '='; + return e; + })(e) + ); + } + function blitBuffer(e, t, r, n) { + for (var i = 0; i < n && !(i + r >= t.length || i >= e.length); ++i) t[i + r] = e[i]; + return i; + } + }).call(this, r(39)); + }, + function (e, t, r) { + 'use strict'; + (function (n) { + var i = + 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator + ? function (e) { + return typeof e; + } + : function (e) { + return e && + 'function' == typeof Symbol && + e.constructor === Symbol && + e !== Symbol.prototype + ? 'symbol' + : typeof e; + }, + u = { userAgent: !1 }, + p = {}; + /*! Copyright (c) 2011, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 2.9.0 */ - if(void 0===v)var v={};v.lang={extend:function extend(t,r,n){if(!r||!t)throw new Error("YAHOO.lang.extend failed, please check that all dependencies are included.");var i=function d(){};if(i.prototype=r.prototype,t.prototype=new i,t.prototype.constructor=t,t.superclass=r.prototype,r.prototype.constructor==Object.prototype.constructor&&(r.prototype.constructor=r),n){var o;for(o in n)t.prototype[o]=n[o];var s=function e(){},a=["toString","valueOf"];try{/MSIE/.test(u.userAgent)&&(s=function e(t,r){for(o=0;o>>2]>>>24-o%4*8&255;t[n+o>>>2]|=s<<24-(n+o)%4*8}else for(o=0;o>>2]=r[o>>>2];return this.sigBytes+=i,this},clamp:function clamp(){var t=this.words,r=this.sigBytes;t[r>>>2]&=4294967295<<32-r%4*8,t.length=e.ceil(r/4)},clone:function clone(){var e=i.clone.call(this);return e.words=this.words.slice(0),e},random:function random(t){for(var r=[],n=0;n>>2]>>>24-i%4*8&255;n.push((o>>>4).toString(16)),n.push((15&o).toString(16))}return n.join("")},parse:function parse(e){for(var t=e.length,r=[],n=0;n>>3]|=parseInt(e.substr(n,2),16)<<24-n%8*4;return new o.init(r,t/2)}},u=s.Latin1={stringify:function stringify(e){for(var t=e.words,r=e.sigBytes,n=[],i=0;i>>2]>>>24-i%4*8&255;n.push(String.fromCharCode(o))}return n.join("")},parse:function parse(e){for(var t=e.length,r=[],n=0;n>>2]|=(255&e.charCodeAt(n))<<24-n%4*8;return new o.init(r,t)}},c=s.Utf8={stringify:function stringify(e){try{return decodeURIComponent(escape(u.stringify(e)))}catch(e){throw new Error("Malformed UTF-8 data")}},parse:function parse(e){return u.parse(unescape(encodeURIComponent(e)))}},h=n.BufferedBlockAlgorithm=i.extend({reset:function reset(){this._data=new o.init,this._nDataBytes=0},_append:function _append(e){"string"==typeof e&&(e=c.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function _process(t){var r=this._data,n=r.words,i=r.sigBytes,s=this.blockSize,a=i/(4*s),u=(a=t?e.ceil(a):e.max((0|a)-this._minBufferSize,0))*s,c=e.min(4*u,i);if(u){for(var h=0;h>>2]>>>24-i%4*8&255)<<16|(t[i+1>>>2]>>>24-(i+1)%4*8&255)<<8|t[i+2>>>2]>>>24-(i+2)%4*8&255,s=0;4>s&&i+.75*s>>6*(3-s)&63));if(t=n.charAt(64))for(;e.length%4;)e.push(t);return e.join("")},parse:function parse(e){var r=e.length,n=this._map;(i=n.charAt(64))&&(-1!=(i=e.indexOf(i))&&(r=i));for(var i=[],o=0,s=0;s>>6-s%4*2;i[o>>>2]|=(a|u)<<24-o%4*8,o++}return t.create(i,o)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}}(),function(e){for(var t=y,r=(i=t.lib).WordArray,n=i.Hasher,i=t.algo,o=[],s=[],a=function u(e){return 4294967296*(e-(0|e))|0},u=2,c=0;64>c;){var h;e:{h=u;for(var f=e.sqrt(h),l=2;l<=f;l++)if(!(h%l)){h=!1;break e}h=!0}h&&(8>c&&(o[c]=a(e.pow(u,.5))),s[c]=a(e.pow(u,1/3)),c++),u++}var g=[];i=i.SHA256=n.extend({_doReset:function _doReset(){this._hash=new r.init(o.slice(0))},_doProcessBlock:function _doProcessBlock(e,t){for(var r=this._hash.words,n=r[0],i=r[1],o=r[2],a=r[3],u=r[4],c=r[5],h=r[6],f=r[7],l=0;64>l;l++){if(16>l)g[l]=0|e[t+l];else{var p=g[l-15],d=g[l-2];g[l]=((p<<25|p>>>7)^(p<<14|p>>>18)^p>>>3)+g[l-7]+((d<<15|d>>>17)^(d<<13|d>>>19)^d>>>10)+g[l-16]}p=f+((u<<26|u>>>6)^(u<<21|u>>>11)^(u<<7|u>>>25))+(u&c^~u&h)+s[l]+g[l],d=((n<<30|n>>>2)^(n<<19|n>>>13)^(n<<10|n>>>22))+(n&i^n&o^i&o),f=h,h=c,c=u,u=a+p|0,a=o,o=i,i=n,n=p+d|0}r[0]=r[0]+n|0,r[1]=r[1]+i|0,r[2]=r[2]+o|0,r[3]=r[3]+a|0,r[4]=r[4]+u|0,r[5]=r[5]+c|0,r[6]=r[6]+h|0,r[7]=r[7]+f|0},_doFinalize:function _doFinalize(){var t=this._data,r=t.words,n=8*this._nDataBytes,i=8*t.sigBytes;return r[i>>>5]|=128<<24-i%32,r[14+(i+64>>>9<<4)]=e.floor(n/4294967296),r[15+(i+64>>>9<<4)]=n,t.sigBytes=4*r.length,this._process(),this._hash},clone:function clone(){var e=n.clone.call(this);return e._hash=this._hash.clone(),e}});t.SHA256=n._createHelper(i),t.HmacSHA256=n._createHmacHelper(i)}(Math),function(){function a(){return r.create.apply(r,arguments)}for(var e=y,t=e.lib.Hasher,r=(i=e.x64).Word,n=i.WordArray,i=e.algo,o=[a(1116352408,3609767458),a(1899447441,602891725),a(3049323471,3964484399),a(3921009573,2173295548),a(961987163,4081628472),a(1508970993,3053834265),a(2453635748,2937671579),a(2870763221,3664609560),a(3624381080,2734883394),a(310598401,1164996542),a(607225278,1323610764),a(1426881987,3590304994),a(1925078388,4068182383),a(2162078206,991336113),a(2614888103,633803317),a(3248222580,3479774868),a(3835390401,2666613458),a(4022224774,944711139),a(264347078,2341262773),a(604807628,2007800933),a(770255983,1495990901),a(1249150122,1856431235),a(1555081692,3175218132),a(1996064986,2198950837),a(2554220882,3999719339),a(2821834349,766784016),a(2952996808,2566594879),a(3210313671,3203337956),a(3336571891,1034457026),a(3584528711,2466948901),a(113926993,3758326383),a(338241895,168717936),a(666307205,1188179964),a(773529912,1546045734),a(1294757372,1522805485),a(1396182291,2643833823),a(1695183700,2343527390),a(1986661051,1014477480),a(2177026350,1206759142),a(2456956037,344077627),a(2730485921,1290863460),a(2820302411,3158454273),a(3259730800,3505952657),a(3345764771,106217008),a(3516065817,3606008344),a(3600352804,1432725776),a(4094571909,1467031594),a(275423344,851169720),a(430227734,3100823752),a(506948616,1363258195),a(659060556,3750685593),a(883997877,3785050280),a(958139571,3318307427),a(1322822218,3812723403),a(1537002063,2003034995),a(1747873779,3602036899),a(1955562222,1575990012),a(2024104815,1125592928),a(2227730452,2716904306),a(2361852424,442776044),a(2428436474,593698344),a(2756734187,3733110249),a(3204031479,2999351573),a(3329325298,3815920427),a(3391569614,3928383900),a(3515267271,566280711),a(3940187606,3454069534),a(4118630271,4000239992),a(116418474,1914138554),a(174292421,2731055270),a(289380356,3203993006),a(460393269,320620315),a(685471733,587496836),a(852142971,1086792851),a(1017036298,365543100),a(1126000580,2618297676),a(1288033470,3409855158),a(1501505948,4234509866),a(1607167915,987167468),a(1816402316,1246189591)],s=[],u=0;80>u;u++)s[u]=a();i=i.SHA512=t.extend({_doReset:function _doReset(){this._hash=new n.init([new r.init(1779033703,4089235720),new r.init(3144134277,2227873595),new r.init(1013904242,4271175723),new r.init(2773480762,1595750129),new r.init(1359893119,2917565137),new r.init(2600822924,725511199),new r.init(528734635,4215389547),new r.init(1541459225,327033209)])},_doProcessBlock:function _doProcessBlock(e,t){for(var r=(f=this._hash.words)[0],n=f[1],i=f[2],a=f[3],u=f[4],c=f[5],h=f[6],f=f[7],l=r.high,g=r.low,p=n.high,d=n.low,v=i.high,y=i.low,m=a.high,S=a.low,F=u.high,b=u.low,_=c.high,w=c.low,E=h.high,x=h.low,C=f.high,P=f.low,A=l,k=g,I=p,B=d,R=v,T=y,U=m,M=S,L=F,D=b,N=_,O=w,H=E,j=x,K=C,q=P,W=0;80>W;W++){var V=s[W];if(16>W)var J=V.high=0|e[t+2*W],z=V.low=0|e[t+2*W+1];else{J=((z=(J=s[W-15]).high)>>>1|(Y=J.low)<<31)^(z>>>8|Y<<24)^z>>>7;var Y=(Y>>>1|z<<31)^(Y>>>8|z<<24)^(Y>>>7|z<<25),G=((z=(G=s[W-2]).high)>>>19|(X=G.low)<<13)^(z<<3|X>>>29)^z>>>6,X=(X>>>19|z<<13)^(X<<3|z>>>29)^(X>>>6|z<<26),Q=(z=s[W-7]).high,Z=($=s[W-16]).high,$=$.low;J=(J=(J=J+Q+((z=Y+z.low)>>>0>>0?1:0))+G+((z=z+X)>>>0>>0?1:0))+Z+((z=z+$)>>>0<$>>>0?1:0);V.high=J,V.low=z}Q=L&N^~L&H,$=D&O^~D&j,V=A&I^A&R^I&R;var ee=k&B^k&T^B&T,te=(Y=(A>>>28|k<<4)^(A<<30|k>>>2)^(A<<25|k>>>7),G=(k>>>28|A<<4)^(k<<30|A>>>2)^(k<<25|A>>>7),(X=o[W]).high),re=X.low;Z=(Z=(Z=(Z=K+((L>>>14|D<<18)^(L>>>18|D<<14)^(L<<23|D>>>9))+((X=q+((D>>>14|L<<18)^(D>>>18|L<<14)^(D<<23|L>>>9)))>>>0>>0?1:0))+Q+((X=X+$)>>>0<$>>>0?1:0))+te+((X=X+re)>>>0>>0?1:0))+J+((X=X+z)>>>0>>0?1:0),V=Y+V+((z=G+ee)>>>0>>0?1:0),K=H,q=j,H=N,j=O,N=L,O=D,L=U+Z+((D=M+X|0)>>>0>>0?1:0)|0,U=R,M=T,R=I,T=B,I=A,B=k,A=Z+V+((k=X+z|0)>>>0>>0?1:0)|0}g=r.low=g+k,r.high=l+A+(g>>>0>>0?1:0),d=n.low=d+B,n.high=p+I+(d>>>0>>0?1:0),y=i.low=y+T,i.high=v+R+(y>>>0>>0?1:0),S=a.low=S+M,a.high=m+U+(S>>>0>>0?1:0),b=u.low=b+D,u.high=F+L+(b>>>0>>0?1:0),w=c.low=w+O,c.high=_+N+(w>>>0>>0?1:0),x=h.low=x+j,h.high=E+H+(x>>>0>>0?1:0),P=f.low=P+q,f.high=C+K+(P>>>0>>0?1:0)},_doFinalize:function _doFinalize(){var e=this._data,t=e.words,r=8*this._nDataBytes,n=8*e.sigBytes;return t[n>>>5]|=128<<24-n%32,t[30+(n+128>>>10<<5)]=Math.floor(r/4294967296),t[31+(n+128>>>10<<5)]=r,e.sigBytes=4*t.length,this._process(),this._hash.toX32()},clone:function clone(){var e=t.clone.call(this);return e._hash=this._hash.clone(),e},blockSize:32}),e.SHA512=t._createHelper(i),e.HmacSHA512=t._createHmacHelper(i)}(),function(){var e=y,t=(i=e.x64).Word,r=i.WordArray,n=(i=e.algo).SHA512,i=i.SHA384=n.extend({_doReset:function _doReset(){this._hash=new r.init([new t.init(3418070365,3238371032),new t.init(1654270250,914150663),new t.init(2438529370,812702999),new t.init(355462360,4144912697),new t.init(1731405415,4290775857),new t.init(2394180231,1750603025),new t.init(3675008525,1694076839),new t.init(1203062813,3204075428)])},_doFinalize:function _doFinalize(){var e=n._doFinalize.call(this);return e.sigBytes-=16,e}});e.SHA384=n._createHelper(i),e.HmacSHA384=n._createHmacHelper(i)}(); - /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ - var S,F="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",_="=";function hex2b64(e){var t,r,n="";for(t=0;t+3<=e.length;t+=3)r=parseInt(e.substring(t,t+3),16),n+=F.charAt(r>>6)+F.charAt(63&r);if(t+1==e.length?(r=parseInt(e.substring(t,t+1),16),n+=F.charAt(r<<2)):t+2==e.length&&(r=parseInt(e.substring(t,t+2),16),n+=F.charAt(r>>2)+F.charAt((3&r)<<4)),_)for(;(3&n.length)>0;)n+=_;return n}function b64tohex(e){var t,r,n,i="",o=0;for(t=0;t>2),r=3&n,o=1):1==o?(i+=int2char(r<<2|n>>4),r=15&n,o=2):2==o?(i+=int2char(r),i+=int2char(n>>2),r=3&n,o=3):(i+=int2char(r<<2|n>>4),i+=int2char(15&n),o=0));return 1==o&&(i+=int2char(r<<2)),i}function b64toBA(e){var t,r=b64tohex(e),n=new Array;for(t=0;2*t>15;--o>=0;){var u=32767&this[e],c=this[e++]>>15,h=a*u+c*s;i=((u=s*u+((32767&h)<<15)+r[n]+(1073741823&i))>>>30)+(h>>>15)+a*c+(i>>>30),r[n++]=1073741823&u}return i},S=30):"Netscape"!=u.appName?(BigInteger.prototype.am=function am1(e,t,r,n,i,o){for(;--o>=0;){var s=t*this[e++]+r[n]+i;i=Math.floor(s/67108864),r[n++]=67108863&s}return i},S=26):(BigInteger.prototype.am=function am3(e,t,r,n,i,o){for(var s=16383&t,a=t>>14;--o>=0;){var u=16383&this[e],c=this[e++]>>14,h=a*u+c*s;i=((u=s*u+((16383&h)<<14)+r[n]+i)>>28)+(h>>14)+a*c,r[n++]=268435455&u}return i},S=28),BigInteger.prototype.DB=S,BigInteger.prototype.DM=(1<>>16)&&(e=t,r+=16),0!=(t=e>>8)&&(e=t,r+=8),0!=(t=e>>4)&&(e=t,r+=4),0!=(t=e>>2)&&(e=t,r+=2),0!=(t=e>>1)&&(e=t,r+=1),r}function Classic(e){this.m=e}function Montgomery(e){this.m=e,this.mp=e.invDigit(),this.mpl=32767&this.mp,this.mph=this.mp>>15,this.um=(1<>=16,t+=16),0==(255&e)&&(e>>=8,t+=8),0==(15&e)&&(e>>=4,t+=4),0==(3&e)&&(e>>=2,t+=2),0==(1&e)&&++t,t}function cbit(e){for(var t=0;0!=e;)e&=e-1,++t;return t}function NullExp(){}function nNop(e){return e}function Barrett(e){this.r2=nbi(),this.q3=nbi(),BigInteger.ONE.dlShiftTo(2*e.t,this.r2),this.mu=this.r2.divide(e),this.m=e}Classic.prototype.convert=function cConvert(e){return e.s<0||e.compareTo(this.m)>=0?e.mod(this.m):e},Classic.prototype.revert=function cRevert(e){return e},Classic.prototype.reduce=function cReduce(e){e.divRemTo(this.m,null,e)},Classic.prototype.mulTo=function cMulTo(e,t,r){e.multiplyTo(t,r),this.reduce(r)},Classic.prototype.sqrTo=function cSqrTo(e,t){e.squareTo(t),this.reduce(t)},Montgomery.prototype.convert=function montConvert(e){var t=nbi();return e.abs().dlShiftTo(this.m.t,t),t.divRemTo(this.m,null,t),e.s<0&&t.compareTo(BigInteger.ZERO)>0&&this.m.subTo(t,t),t},Montgomery.prototype.revert=function montRevert(e){var t=nbi();return e.copyTo(t),this.reduce(t),t},Montgomery.prototype.reduce=function montReduce(e){for(;e.t<=this.mt2;)e[e.t++]=0;for(var t=0;t>15)*this.mpl&this.um)<<15)&e.DM;for(e[r=t+this.m.t]+=this.m.am(0,n,e,t,0,this.m.t);e[r]>=e.DV;)e[r]-=e.DV,e[++r]++}e.clamp(),e.drShiftTo(this.m.t,e),e.compareTo(this.m)>=0&&e.subTo(this.m,e)},Montgomery.prototype.mulTo=function montMulTo(e,t,r){e.multiplyTo(t,r),this.reduce(r)},Montgomery.prototype.sqrTo=function montSqrTo(e,t){e.squareTo(t),this.reduce(t)},BigInteger.prototype.copyTo=function bnpCopyTo(e){for(var t=this.t-1;t>=0;--t)e[t]=this[t];e.t=this.t,e.s=this.s},BigInteger.prototype.fromInt=function bnpFromInt(e){this.t=1,this.s=e<0?-1:0,e>0?this[0]=e:e<-1?this[0]=e+this.DV:this.t=0},BigInteger.prototype.fromString=function bnpFromString(e,t){var r;if(16==t)r=4;else if(8==t)r=3;else if(256==t)r=8;else if(2==t)r=1;else if(32==t)r=5;else{if(4!=t)return void this.fromRadix(e,t);r=2}this.t=0,this.s=0;for(var n=e.length,i=!1,o=0;--n>=0;){var s=8==r?255&e[n]:intAt(e,n);s<0?"-"==e.charAt(n)&&(i=!0):(i=!1,0==o?this[this.t++]=s:o+r>this.DB?(this[this.t-1]|=(s&(1<>this.DB-o):this[this.t-1]|=s<=this.DB&&(o-=this.DB))}8==r&&0!=(128&e[0])&&(this.s=-1,o>0&&(this[this.t-1]|=(1<0&&this[this.t-1]==e;)--this.t},BigInteger.prototype.dlShiftTo=function bnpDLShiftTo(e,t){var r;for(r=this.t-1;r>=0;--r)t[r+e]=this[r];for(r=e-1;r>=0;--r)t[r]=0;t.t=this.t+e,t.s=this.s},BigInteger.prototype.drShiftTo=function bnpDRShiftTo(e,t){for(var r=e;r=0;--r)t[r+s+1]=this[r]>>i|a,a=(this[r]&o)<=0;--r)t[r]=0;t[s]=a,t.t=this.t+s+1,t.s=this.s,t.clamp()},BigInteger.prototype.rShiftTo=function bnpRShiftTo(e,t){t.s=this.s;var r=Math.floor(e/this.DB);if(r>=this.t)t.t=0;else{var n=e%this.DB,i=this.DB-n,o=(1<>n;for(var s=r+1;s>n;n>0&&(t[this.t-r-1]|=(this.s&o)<>=this.DB;if(e.t>=this.DB;n+=this.s}else{for(n+=this.s;r>=this.DB;n-=e.s}t.s=n<0?-1:0,n<-1?t[r++]=this.DV+n:n>0&&(t[r++]=n),t.t=r,t.clamp()},BigInteger.prototype.multiplyTo=function bnpMultiplyTo(e,t){var r=this.abs(),n=e.abs(),i=r.t;for(t.t=i+n.t;--i>=0;)t[i]=0;for(i=0;i=0;)e[r]=0;for(r=0;r=t.DV&&(e[r+t.t]-=t.DV,e[r+t.t+1]=1)}e.t>0&&(e[e.t-1]+=t.am(r,t[r],e,2*r,0,1)),e.s=0,e.clamp()},BigInteger.prototype.divRemTo=function bnpDivRemTo(e,t,r){var n=e.abs();if(!(n.t<=0)){var i=this.abs();if(i.t0?(n.lShiftTo(u,o),i.lShiftTo(u,r)):(n.copyTo(o),i.copyTo(r));var c=o.t,h=o[c-1];if(0!=h){var f=h*(1<1?o[c-2]>>this.F2:0),l=this.FV/f,g=(1<=0&&(r[r.t++]=1,r.subTo(y,r)),BigInteger.ONE.dlShiftTo(c,y),y.subTo(o,o);o.t=0;){var m=r[--d]==h?this.DM:Math.floor(r[d]*l+(r[d-1]+p)*g);if((r[d]+=o.am(0,m,r,v,0,c))0&&r.rShiftTo(u,r),s<0&&BigInteger.ZERO.subTo(r,r)}}},BigInteger.prototype.invDigit=function bnpInvDigit(){if(this.t<1)return 0;var e=this[0];if(0==(1&e))return 0;var t=3&e;return(t=(t=(t=(t=t*(2-(15&e)*t)&15)*(2-(255&e)*t)&255)*(2-((65535&e)*t&65535))&65535)*(2-e*t%this.DV)%this.DV)>0?this.DV-t:-t},BigInteger.prototype.isEven=function bnpIsEven(){return 0==(this.t>0?1&this[0]:this.s)},BigInteger.prototype.exp=function bnpExp(e,t){if(e>4294967295||e<1)return BigInteger.ONE;var r=nbi(),n=nbi(),i=t.convert(this),o=nbits(e)-1;for(i.copyTo(r);--o>=0;)if(t.sqrTo(r,n),(e&1<0)t.mulTo(n,i,r);else{var s=r;r=n,n=s}return t.revert(r)},BigInteger.prototype.toString=function bnToString(e){if(this.s<0)return"-"+this.negate().toString(e);var t;if(16==e)t=4;else if(8==e)t=3;else if(2==e)t=1;else if(32==e)t=5;else{if(4!=e)return this.toRadix(e);t=2}var r,n=(1<0)for(a>a)>0&&(i=!0,o=int2char(r));s>=0;)a>(a+=this.DB-t)):(r=this[s]>>(a-=t)&n,a<=0&&(a+=this.DB,--s)),r>0&&(i=!0),i&&(o+=int2char(r));return i?o:"0"},BigInteger.prototype.negate=function bnNegate(){var e=nbi();return BigInteger.ZERO.subTo(this,e),e},BigInteger.prototype.abs=function bnAbs(){return this.s<0?this.negate():this},BigInteger.prototype.compareTo=function bnCompareTo(e){var t=this.s-e.s;if(0!=t)return t;var r=this.t;if(0!=(t=r-e.t))return this.s<0?-t:t;for(;--r>=0;)if(0!=(t=this[r]-e[r]))return t;return 0},BigInteger.prototype.bitLength=function bnBitLength(){return this.t<=0?0:this.DB*(this.t-1)+nbits(this[this.t-1]^this.s&this.DM)},BigInteger.prototype.mod=function bnMod(e){var t=nbi();return this.abs().divRemTo(e,null,t),this.s<0&&t.compareTo(BigInteger.ZERO)>0&&e.subTo(t,t),t},BigInteger.prototype.modPowInt=function bnModPowInt(e,t){var r;return r=e<256||t.isEven()?new Classic(t):new Montgomery(t),this.exp(e,r)},BigInteger.ZERO=nbv(0),BigInteger.ONE=nbv(1),NullExp.prototype.convert=nNop,NullExp.prototype.revert=nNop,NullExp.prototype.mulTo=function nMulTo(e,t,r){e.multiplyTo(t,r)},NullExp.prototype.sqrTo=function nSqrTo(e,t){e.squareTo(t)},Barrett.prototype.convert=function barrettConvert(e){if(e.s<0||e.t>2*this.m.t)return e.mod(this.m);if(e.compareTo(this.m)<0)return e;var t=nbi();return e.copyTo(t),this.reduce(t),t},Barrett.prototype.revert=function barrettRevert(e){return e},Barrett.prototype.reduce=function barrettReduce(e){for(e.drShiftTo(this.m.t-1,this.r2),e.t>this.m.t+1&&(e.t=this.m.t+1,e.clamp()),this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3),this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);e.compareTo(this.r2)<0;)e.dAddOffset(1,this.m.t+1);for(e.subTo(this.r2,e);e.compareTo(this.m)>=0;)e.subTo(this.m,e)},Barrett.prototype.mulTo=function barrettMulTo(e,t,r){e.multiplyTo(t,r),this.reduce(r)},Barrett.prototype.sqrTo=function barrettSqrTo(e,t){e.squareTo(t),this.reduce(t)};var I=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997],R=(1<<26)/I[I.length-1]; - /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ - function Arcfour(){this.i=0,this.j=0,this.S=new Array}BigInteger.prototype.chunkSize=function bnpChunkSize(e){return Math.floor(Math.LN2*this.DB/Math.log(e))},BigInteger.prototype.toRadix=function bnpToRadix(e){if(null==e&&(e=10),0==this.signum()||e<2||e>36)return"0";var t=this.chunkSize(e),r=Math.pow(e,t),n=nbv(r),i=nbi(),o=nbi(),s="";for(this.divRemTo(n,i,o);i.signum()>0;)s=(r+o.intValue()).toString(e).substr(1)+s,i.divRemTo(n,i,o);return o.intValue().toString(e)+s},BigInteger.prototype.fromRadix=function bnpFromRadix(e,t){this.fromInt(0),null==t&&(t=10);for(var r=this.chunkSize(t),n=Math.pow(t,r),i=!1,o=0,s=0,a=0;a=r&&(this.dMultiply(n),this.dAddOffset(s,0),o=0,s=0))}o>0&&(this.dMultiply(Math.pow(t,o)),this.dAddOffset(s,0)),i&&BigInteger.ZERO.subTo(this,this)},BigInteger.prototype.fromNumber=function bnpFromNumber(e,t,r){if("number"==typeof t)if(e<2)this.fromInt(1);else for(this.fromNumber(e,r),this.testBit(e-1)||this.bitwiseTo(BigInteger.ONE.shiftLeft(e-1),op_or,this),this.isEven()&&this.dAddOffset(1,0);!this.isProbablePrime(t);)this.dAddOffset(2,0),this.bitLength()>e&&this.subTo(BigInteger.ONE.shiftLeft(e-1),this);else{var n=new Array,i=7&e;n.length=1+(e>>3),t.nextBytes(n),i>0?n[0]&=(1<>=this.DB;if(e.t>=this.DB;n+=this.s}else{for(n+=this.s;r>=this.DB;n+=e.s}t.s=n<0?-1:0,n>0?t[r++]=n:n<-1&&(t[r++]=this.DV+n),t.t=r,t.clamp()},BigInteger.prototype.dMultiply=function bnpDMultiply(e){this[this.t]=this.am(0,e-1,this,0,0,this.t),++this.t,this.clamp()},BigInteger.prototype.dAddOffset=function bnpDAddOffset(e,t){if(0!=e){for(;this.t<=t;)this[this.t++]=0;for(this[t]+=e;this[t]>=this.DV;)this[t]-=this.DV,++t>=this.t&&(this[this.t++]=0),++this[t]}},BigInteger.prototype.multiplyLowerTo=function bnpMultiplyLowerTo(e,t,r){var n,i=Math.min(this.t+e.t,t);for(r.s=0,r.t=i;i>0;)r[--i]=0;for(n=r.t-this.t;i=0;)r[n]=0;for(n=Math.max(t-this.t,0);n0)if(0==t)r=this[0]%e;else for(var n=this.t-1;n>=0;--n)r=(t*r+this[n])%e;return r},BigInteger.prototype.millerRabin=function bnpMillerRabin(e){var t=this.subtract(BigInteger.ONE),r=t.getLowestSetBit();if(r<=0)return!1;var n=t.shiftRight(r);(e=e+1>>1)>I.length&&(e=I.length);for(var i=nbi(),o=0;o>> 2] >>> (24 - (o % 4) * 8)) & 255; + t[(n + o) >>> 2] |= s << (24 - ((n + o) % 4) * 8); + } + else for (o = 0; o < i; o += 4) t[(n + o) >>> 2] = r[o >>> 2]; + return (this.sigBytes += i), this; + }, + clamp: function clamp() { + var t = this.words, + r = this.sigBytes; + (t[r >>> 2] &= 4294967295 << (32 - (r % 4) * 8)), (t.length = e.ceil(r / 4)); + }, + clone: function clone() { + var e = i.clone.call(this); + return (e.words = this.words.slice(0)), e; + }, + random: function random(t) { + for (var r = [], n = 0; n < t; n += 4) r.push((4294967296 * e.random()) | 0); + return new o.init(r, t); + }, + })), + s = (r.enc = {}), + a = (s.Hex = { + stringify: function stringify(e) { + for (var t = e.words, r = e.sigBytes, n = [], i = 0; i < r; i++) { + var o = (t[i >>> 2] >>> (24 - (i % 4) * 8)) & 255; + n.push((o >>> 4).toString(16)), n.push((15 & o).toString(16)); + } + return n.join(''); + }, + parse: function parse(e) { + for (var t = e.length, r = [], n = 0; n < t; n += 2) + r[n >>> 3] |= parseInt(e.substr(n, 2), 16) << (24 - (n % 8) * 4); + return new o.init(r, t / 2); + }, + }), + u = (s.Latin1 = { + stringify: function stringify(e) { + for (var t = e.words, r = e.sigBytes, n = [], i = 0; i < r; i++) { + var o = (t[i >>> 2] >>> (24 - (i % 4) * 8)) & 255; + n.push(String.fromCharCode(o)); + } + return n.join(''); + }, + parse: function parse(e) { + for (var t = e.length, r = [], n = 0; n < t; n++) + r[n >>> 2] |= (255 & e.charCodeAt(n)) << (24 - (n % 4) * 8); + return new o.init(r, t); + }, + }), + c = (s.Utf8 = { + stringify: function stringify(e) { + try { + return decodeURIComponent(escape(u.stringify(e))); + } catch (e) { + throw new Error('Malformed UTF-8 data'); + } + }, + parse: function parse(e) { + return u.parse(unescape(encodeURIComponent(e))); + }, + }), + h = (n.BufferedBlockAlgorithm = i.extend({ + reset: function reset() { + (this._data = new o.init()), (this._nDataBytes = 0); + }, + _append: function _append(e) { + 'string' == typeof e && (e = c.parse(e)), + this._data.concat(e), + (this._nDataBytes += e.sigBytes); + }, + _process: function _process(t) { + var r = this._data, + n = r.words, + i = r.sigBytes, + s = this.blockSize, + a = i / (4 * s), + u = (a = t ? e.ceil(a) : e.max((0 | a) - this._minBufferSize, 0)) * s, + c = e.min(4 * u, i); + if (u) { + for (var h = 0; h < u; h += s) this._doProcessBlock(n, h); + var f = n.splice(0, u); + r.sigBytes -= c; + } + return new o.init(f, c); + }, + clone: function clone() { + var e = i.clone.call(this); + return (e._data = this._data.clone()), e; + }, + _minBufferSize: 0, + })), + f = + ((n.Hasher = h.extend({ + cfg: i.extend(), + init: function init(e) { + (this.cfg = this.cfg.extend(e)), this.reset(); + }, + reset: function reset() { + h.reset.call(this), this._doReset(); + }, + update: function update(e) { + return this._append(e), this._process(), this; + }, + finalize: function finalize(e) { + return e && this._append(e), this._doFinalize(); + }, + blockSize: 16, + _createHelper: function _createHelper(e) { + return function (t, r) { + return new e.init(r).finalize(t); + }; + }, + _createHmacHelper: function _createHmacHelper(e) { + return function (t, r) { + return new f.HMAC.init(e, r).finalize(t); + }; + }, + })), + (r.algo = {})); + return r; + })(Math); + !(function (e) { + var t, + r = (t = y).lib, + n = r.Base, + i = r.WordArray; + ((t = t.x64 = {}).Word = n.extend({ + init: function init(e, t) { + (this.high = e), (this.low = t); + }, + })), + (t.WordArray = n.extend({ + init: function init(e, t) { + (e = this.words = e || []), (this.sigBytes = void 0 != t ? t : 8 * e.length); + }, + toX32: function toX32() { + for (var e = this.words, t = e.length, r = [], n = 0; n < t; n++) { + var o = e[n]; + r.push(o.high), r.push(o.low); + } + return i.create(r, this.sigBytes); + }, + clone: function clone() { + for ( + var e = n.clone.call(this), + t = (e.words = this.words.slice(0)), + r = t.length, + i = 0; + i < r; + i++ + ) + t[i] = t[i].clone(); + return e; + }, + })); + })(), + (function () { + var e = y, + t = e.lib.WordArray; + e.enc.Base64 = { + stringify: function stringify(e) { + var t = e.words, + r = e.sigBytes, + n = this._map; + e.clamp(), (e = []); + for (var i = 0; i < r; i += 3) + for ( + var o = + (((t[i >>> 2] >>> (24 - (i % 4) * 8)) & 255) << 16) | + (((t[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 255) << 8) | + ((t[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 255), + s = 0; + 4 > s && i + 0.75 * s < r; + s++ + ) + e.push(n.charAt((o >>> (6 * (3 - s))) & 63)); + if ((t = n.charAt(64))) for (; e.length % 4; ) e.push(t); + return e.join(''); + }, + parse: function parse(e) { + var r = e.length, + n = this._map; + (i = n.charAt(64)) && -1 != (i = e.indexOf(i)) && (r = i); + for (var i = [], o = 0, s = 0; s < r; s++) + if (s % 4) { + var a = n.indexOf(e.charAt(s - 1)) << ((s % 4) * 2), + u = n.indexOf(e.charAt(s)) >>> (6 - (s % 4) * 2); + (i[o >>> 2] |= (a | u) << (24 - (o % 4) * 8)), o++; + } + return t.create(i, o); + }, + _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', + }; + })(), + (function (e) { + for ( + var t = y, + r = (i = t.lib).WordArray, + n = i.Hasher, + i = t.algo, + o = [], + s = [], + a = function u(e) { + return (4294967296 * (e - (0 | e))) | 0; + }, + u = 2, + c = 0; + 64 > c; + + ) { + var h; + e: { + h = u; + for (var f = e.sqrt(h), l = 2; l <= f; l++) + if (!(h % l)) { + h = !1; + break e; + } + h = !0; + } + h && (8 > c && (o[c] = a(e.pow(u, 0.5))), (s[c] = a(e.pow(u, 1 / 3))), c++), u++; + } + var g = []; + i = i.SHA256 = n.extend({ + _doReset: function _doReset() { + this._hash = new r.init(o.slice(0)); + }, + _doProcessBlock: function _doProcessBlock(e, t) { + for ( + var r = this._hash.words, + n = r[0], + i = r[1], + o = r[2], + a = r[3], + u = r[4], + c = r[5], + h = r[6], + f = r[7], + l = 0; + 64 > l; + l++ + ) { + if (16 > l) g[l] = 0 | e[t + l]; + else { + var p = g[l - 15], + d = g[l - 2]; + g[l] = + (((p << 25) | (p >>> 7)) ^ ((p << 14) | (p >>> 18)) ^ (p >>> 3)) + + g[l - 7] + + (((d << 15) | (d >>> 17)) ^ ((d << 13) | (d >>> 19)) ^ (d >>> 10)) + + g[l - 16]; + } + (p = + f + + (((u << 26) | (u >>> 6)) ^ ((u << 21) | (u >>> 11)) ^ ((u << 7) | (u >>> 25))) + + ((u & c) ^ (~u & h)) + + s[l] + + g[l]), + (d = + (((n << 30) | (n >>> 2)) ^ + ((n << 19) | (n >>> 13)) ^ + ((n << 10) | (n >>> 22))) + + ((n & i) ^ (n & o) ^ (i & o))), + (f = h), + (h = c), + (c = u), + (u = (a + p) | 0), + (a = o), + (o = i), + (i = n), + (n = (p + d) | 0); + } + (r[0] = (r[0] + n) | 0), + (r[1] = (r[1] + i) | 0), + (r[2] = (r[2] + o) | 0), + (r[3] = (r[3] + a) | 0), + (r[4] = (r[4] + u) | 0), + (r[5] = (r[5] + c) | 0), + (r[6] = (r[6] + h) | 0), + (r[7] = (r[7] + f) | 0); + }, + _doFinalize: function _doFinalize() { + var t = this._data, + r = t.words, + n = 8 * this._nDataBytes, + i = 8 * t.sigBytes; + return ( + (r[i >>> 5] |= 128 << (24 - (i % 32))), + (r[14 + (((i + 64) >>> 9) << 4)] = e.floor(n / 4294967296)), + (r[15 + (((i + 64) >>> 9) << 4)] = n), + (t.sigBytes = 4 * r.length), + this._process(), + this._hash + ); + }, + clone: function clone() { + var e = n.clone.call(this); + return (e._hash = this._hash.clone()), e; + }, + }); + (t.SHA256 = n._createHelper(i)), (t.HmacSHA256 = n._createHmacHelper(i)); + })(Math), + (function () { + function a() { + return r.create.apply(r, arguments); + } + for ( + var e = y, + t = e.lib.Hasher, + r = (i = e.x64).Word, + n = i.WordArray, + i = e.algo, + o = [ + a(1116352408, 3609767458), + a(1899447441, 602891725), + a(3049323471, 3964484399), + a(3921009573, 2173295548), + a(961987163, 4081628472), + a(1508970993, 3053834265), + a(2453635748, 2937671579), + a(2870763221, 3664609560), + a(3624381080, 2734883394), + a(310598401, 1164996542), + a(607225278, 1323610764), + a(1426881987, 3590304994), + a(1925078388, 4068182383), + a(2162078206, 991336113), + a(2614888103, 633803317), + a(3248222580, 3479774868), + a(3835390401, 2666613458), + a(4022224774, 944711139), + a(264347078, 2341262773), + a(604807628, 2007800933), + a(770255983, 1495990901), + a(1249150122, 1856431235), + a(1555081692, 3175218132), + a(1996064986, 2198950837), + a(2554220882, 3999719339), + a(2821834349, 766784016), + a(2952996808, 2566594879), + a(3210313671, 3203337956), + a(3336571891, 1034457026), + a(3584528711, 2466948901), + a(113926993, 3758326383), + a(338241895, 168717936), + a(666307205, 1188179964), + a(773529912, 1546045734), + a(1294757372, 1522805485), + a(1396182291, 2643833823), + a(1695183700, 2343527390), + a(1986661051, 1014477480), + a(2177026350, 1206759142), + a(2456956037, 344077627), + a(2730485921, 1290863460), + a(2820302411, 3158454273), + a(3259730800, 3505952657), + a(3345764771, 106217008), + a(3516065817, 3606008344), + a(3600352804, 1432725776), + a(4094571909, 1467031594), + a(275423344, 851169720), + a(430227734, 3100823752), + a(506948616, 1363258195), + a(659060556, 3750685593), + a(883997877, 3785050280), + a(958139571, 3318307427), + a(1322822218, 3812723403), + a(1537002063, 2003034995), + a(1747873779, 3602036899), + a(1955562222, 1575990012), + a(2024104815, 1125592928), + a(2227730452, 2716904306), + a(2361852424, 442776044), + a(2428436474, 593698344), + a(2756734187, 3733110249), + a(3204031479, 2999351573), + a(3329325298, 3815920427), + a(3391569614, 3928383900), + a(3515267271, 566280711), + a(3940187606, 3454069534), + a(4118630271, 4000239992), + a(116418474, 1914138554), + a(174292421, 2731055270), + a(289380356, 3203993006), + a(460393269, 320620315), + a(685471733, 587496836), + a(852142971, 1086792851), + a(1017036298, 365543100), + a(1126000580, 2618297676), + a(1288033470, 3409855158), + a(1501505948, 4234509866), + a(1607167915, 987167468), + a(1816402316, 1246189591), + ], + s = [], + u = 0; + 80 > u; + u++ + ) + s[u] = a(); + (i = i.SHA512 = + t.extend({ + _doReset: function _doReset() { + this._hash = new n.init([ + new r.init(1779033703, 4089235720), + new r.init(3144134277, 2227873595), + new r.init(1013904242, 4271175723), + new r.init(2773480762, 1595750129), + new r.init(1359893119, 2917565137), + new r.init(2600822924, 725511199), + new r.init(528734635, 4215389547), + new r.init(1541459225, 327033209), + ]); + }, + _doProcessBlock: function _doProcessBlock(e, t) { + for ( + var r = (f = this._hash.words)[0], + n = f[1], + i = f[2], + a = f[3], + u = f[4], + c = f[5], + h = f[6], + f = f[7], + l = r.high, + g = r.low, + p = n.high, + d = n.low, + v = i.high, + y = i.low, + m = a.high, + S = a.low, + F = u.high, + b = u.low, + _ = c.high, + w = c.low, + E = h.high, + x = h.low, + C = f.high, + P = f.low, + A = l, + k = g, + I = p, + B = d, + R = v, + T = y, + U = m, + M = S, + L = F, + D = b, + N = _, + O = w, + H = E, + j = x, + K = C, + q = P, + W = 0; + 80 > W; + W++ + ) { + var V = s[W]; + if (16 > W) + var J = (V.high = 0 | e[t + 2 * W]), + z = (V.low = 0 | e[t + 2 * W + 1]); + else { + J = + (((z = (J = s[W - 15]).high) >>> 1) | ((Y = J.low) << 31)) ^ + ((z >>> 8) | (Y << 24)) ^ + (z >>> 7); + var Y = + ((Y >>> 1) | (z << 31)) ^ + ((Y >>> 8) | (z << 24)) ^ + ((Y >>> 7) | (z << 25)), + G = + (((z = (G = s[W - 2]).high) >>> 19) | ((X = G.low) << 13)) ^ + ((z << 3) | (X >>> 29)) ^ + (z >>> 6), + X = + ((X >>> 19) | (z << 13)) ^ + ((X << 3) | (z >>> 29)) ^ + ((X >>> 6) | (z << 26)), + Q = (z = s[W - 7]).high, + Z = ($ = s[W - 16]).high, + $ = $.low; + J = + (J = + (J = J + Q + ((z = Y + z.low) >>> 0 < Y >>> 0 ? 1 : 0)) + + G + + ((z = z + X) >>> 0 < X >>> 0 ? 1 : 0)) + + Z + + ((z = z + $) >>> 0 < $ >>> 0 ? 1 : 0); + (V.high = J), (V.low = z); + } + (Q = (L & N) ^ (~L & H)), + ($ = (D & O) ^ (~D & j)), + (V = (A & I) ^ (A & R) ^ (I & R)); + var ee = (k & B) ^ (k & T) ^ (B & T), + te = + ((Y = + ((A >>> 28) | (k << 4)) ^ + ((A << 30) | (k >>> 2)) ^ + ((A << 25) | (k >>> 7))), + (G = + ((k >>> 28) | (A << 4)) ^ + ((k << 30) | (A >>> 2)) ^ + ((k << 25) | (A >>> 7))), + (X = o[W]).high), + re = X.low; + (Z = + (Z = + (Z = + (Z = + K + + (((L >>> 14) | (D << 18)) ^ + ((L >>> 18) | (D << 14)) ^ + ((L << 23) | (D >>> 9))) + + ((X = + q + + (((D >>> 14) | (L << 18)) ^ + ((D >>> 18) | (L << 14)) ^ + ((D << 23) | (L >>> 9)))) >>> + 0 < + q >>> 0 + ? 1 + : 0)) + + Q + + ((X = X + $) >>> 0 < $ >>> 0 ? 1 : 0)) + + te + + ((X = X + re) >>> 0 < re >>> 0 ? 1 : 0)) + + J + + ((X = X + z) >>> 0 < z >>> 0 ? 1 : 0)), + (V = Y + V + ((z = G + ee) >>> 0 < G >>> 0 ? 1 : 0)), + (K = H), + (q = j), + (H = N), + (j = O), + (N = L), + (O = D), + (L = (U + Z + ((D = (M + X) | 0) >>> 0 < M >>> 0 ? 1 : 0)) | 0), + (U = R), + (M = T), + (R = I), + (T = B), + (I = A), + (B = k), + (A = (Z + V + ((k = (X + z) | 0) >>> 0 < X >>> 0 ? 1 : 0)) | 0); + } + (g = r.low = g + k), + (r.high = l + A + (g >>> 0 < k >>> 0 ? 1 : 0)), + (d = n.low = d + B), + (n.high = p + I + (d >>> 0 < B >>> 0 ? 1 : 0)), + (y = i.low = y + T), + (i.high = v + R + (y >>> 0 < T >>> 0 ? 1 : 0)), + (S = a.low = S + M), + (a.high = m + U + (S >>> 0 < M >>> 0 ? 1 : 0)), + (b = u.low = b + D), + (u.high = F + L + (b >>> 0 < D >>> 0 ? 1 : 0)), + (w = c.low = w + O), + (c.high = _ + N + (w >>> 0 < O >>> 0 ? 1 : 0)), + (x = h.low = x + j), + (h.high = E + H + (x >>> 0 < j >>> 0 ? 1 : 0)), + (P = f.low = P + q), + (f.high = C + K + (P >>> 0 < q >>> 0 ? 1 : 0)); + }, + _doFinalize: function _doFinalize() { + var e = this._data, + t = e.words, + r = 8 * this._nDataBytes, + n = 8 * e.sigBytes; + return ( + (t[n >>> 5] |= 128 << (24 - (n % 32))), + (t[30 + (((n + 128) >>> 10) << 5)] = Math.floor(r / 4294967296)), + (t[31 + (((n + 128) >>> 10) << 5)] = r), + (e.sigBytes = 4 * t.length), + this._process(), + this._hash.toX32() + ); + }, + clone: function clone() { + var e = t.clone.call(this); + return (e._hash = this._hash.clone()), e; + }, + blockSize: 32, + })), + (e.SHA512 = t._createHelper(i)), + (e.HmacSHA512 = t._createHmacHelper(i)); + })(), + (function () { + var e = y, + t = (i = e.x64).Word, + r = i.WordArray, + n = (i = e.algo).SHA512, + i = (i.SHA384 = n.extend({ + _doReset: function _doReset() { + this._hash = new r.init([ + new t.init(3418070365, 3238371032), + new t.init(1654270250, 914150663), + new t.init(2438529370, 812702999), + new t.init(355462360, 4144912697), + new t.init(1731405415, 4290775857), + new t.init(2394180231, 1750603025), + new t.init(3675008525, 1694076839), + new t.init(1203062813, 3204075428), + ]); + }, + _doFinalize: function _doFinalize() { + var e = n._doFinalize.call(this); + return (e.sigBytes -= 16), e; + }, + })); + (e.SHA384 = n._createHelper(i)), (e.HmacSHA384 = n._createHmacHelper(i)); + })(); + /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ + */ + var S, + F = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', + _ = '='; + function hex2b64(e) { + var t, + r, + n = ''; + for (t = 0; t + 3 <= e.length; t += 3) + (r = parseInt(e.substring(t, t + 3), 16)), (n += F.charAt(r >> 6) + F.charAt(63 & r)); + if ( + (t + 1 == e.length + ? ((r = parseInt(e.substring(t, t + 1), 16)), (n += F.charAt(r << 2))) + : t + 2 == e.length && + ((r = parseInt(e.substring(t, t + 2), 16)), + (n += F.charAt(r >> 2) + F.charAt((3 & r) << 4))), + _) + ) + for (; (3 & n.length) > 0; ) n += _; + return n; + } + function b64tohex(e) { + var t, + r, + n, + i = '', + o = 0; + for (t = 0; t < e.length && e.charAt(t) != _; ++t) + (n = F.indexOf(e.charAt(t))) < 0 || + (0 == o + ? ((i += int2char(n >> 2)), (r = 3 & n), (o = 1)) + : 1 == o + ? ((i += int2char((r << 2) | (n >> 4))), (r = 15 & n), (o = 2)) + : 2 == o + ? ((i += int2char(r)), (i += int2char(n >> 2)), (r = 3 & n), (o = 3)) + : ((i += int2char((r << 2) | (n >> 4))), (i += int2char(15 & n)), (o = 0))); + return 1 == o && (i += int2char(r << 2)), i; + } + function b64toBA(e) { + var t, + r = b64tohex(e), + n = new Array(); + for (t = 0; 2 * t < r.length; ++t) n[t] = parseInt(r.substring(2 * t, 2 * t + 2), 16); + return n; + } + function BigInteger(e, t, r) { + null != e && + ('number' == typeof e + ? this.fromNumber(e, t, r) + : null == t && 'string' != typeof e + ? this.fromString(e, 256) + : this.fromString(e, t)); + } + function nbi() { + return new BigInteger(null); + } + 'Microsoft Internet Explorer' == u.appName + ? ((BigInteger.prototype.am = function am2(e, t, r, n, i, o) { + for (var s = 32767 & t, a = t >> 15; --o >= 0; ) { + var u = 32767 & this[e], + c = this[e++] >> 15, + h = a * u + c * s; + (i = + ((u = s * u + ((32767 & h) << 15) + r[n] + (1073741823 & i)) >>> 30) + + (h >>> 15) + + a * c + + (i >>> 30)), + (r[n++] = 1073741823 & u); + } + return i; + }), + (S = 30)) + : 'Netscape' != u.appName + ? ((BigInteger.prototype.am = function am1(e, t, r, n, i, o) { + for (; --o >= 0; ) { + var s = t * this[e++] + r[n] + i; + (i = Math.floor(s / 67108864)), (r[n++] = 67108863 & s); + } + return i; + }), + (S = 26)) + : ((BigInteger.prototype.am = function am3(e, t, r, n, i, o) { + for (var s = 16383 & t, a = t >> 14; --o >= 0; ) { + var u = 16383 & this[e], + c = this[e++] >> 14, + h = a * u + c * s; + (i = ((u = s * u + ((16383 & h) << 14) + r[n] + i) >> 28) + (h >> 14) + a * c), + (r[n++] = 268435455 & u); + } + return i; + }), + (S = 28)), + (BigInteger.prototype.DB = S), + (BigInteger.prototype.DM = (1 << S) - 1), + (BigInteger.prototype.DV = 1 << S); + (BigInteger.prototype.FV = Math.pow(2, 52)), + (BigInteger.prototype.F1 = 52 - S), + (BigInteger.prototype.F2 = 2 * S - 52); + var w, + E, + C = '0123456789abcdefghijklmnopqrstuvwxyz', + P = new Array(); + for (w = '0'.charCodeAt(0), E = 0; E <= 9; ++E) P[w++] = E; + for (w = 'a'.charCodeAt(0), E = 10; E < 36; ++E) P[w++] = E; + for (w = 'A'.charCodeAt(0), E = 10; E < 36; ++E) P[w++] = E; + function int2char(e) { + return C.charAt(e); + } + function intAt(e, t) { + var r = P[e.charCodeAt(t)]; + return null == r ? -1 : r; + } + function nbv(e) { + var t = nbi(); + return t.fromInt(e), t; + } + function nbits(e) { + var t, + r = 1; + return ( + 0 != (t = e >>> 16) && ((e = t), (r += 16)), + 0 != (t = e >> 8) && ((e = t), (r += 8)), + 0 != (t = e >> 4) && ((e = t), (r += 4)), + 0 != (t = e >> 2) && ((e = t), (r += 2)), + 0 != (t = e >> 1) && ((e = t), (r += 1)), + r + ); + } + function Classic(e) { + this.m = e; + } + function Montgomery(e) { + (this.m = e), + (this.mp = e.invDigit()), + (this.mpl = 32767 & this.mp), + (this.mph = this.mp >> 15), + (this.um = (1 << (e.DB - 15)) - 1), + (this.mt2 = 2 * e.t); + } + function op_and(e, t) { + return e & t; + } + function op_or(e, t) { + return e | t; + } + function op_xor(e, t) { + return e ^ t; + } + function op_andnot(e, t) { + return e & ~t; + } + function lbit(e) { + if (0 == e) return -1; + var t = 0; + return ( + 0 == (65535 & e) && ((e >>= 16), (t += 16)), + 0 == (255 & e) && ((e >>= 8), (t += 8)), + 0 == (15 & e) && ((e >>= 4), (t += 4)), + 0 == (3 & e) && ((e >>= 2), (t += 2)), + 0 == (1 & e) && ++t, + t + ); + } + function cbit(e) { + for (var t = 0; 0 != e; ) (e &= e - 1), ++t; + return t; + } + function NullExp() {} + function nNop(e) { + return e; + } + function Barrett(e) { + (this.r2 = nbi()), + (this.q3 = nbi()), + BigInteger.ONE.dlShiftTo(2 * e.t, this.r2), + (this.mu = this.r2.divide(e)), + (this.m = e); + } + (Classic.prototype.convert = function cConvert(e) { + return e.s < 0 || e.compareTo(this.m) >= 0 ? e.mod(this.m) : e; + }), + (Classic.prototype.revert = function cRevert(e) { + return e; + }), + (Classic.prototype.reduce = function cReduce(e) { + e.divRemTo(this.m, null, e); + }), + (Classic.prototype.mulTo = function cMulTo(e, t, r) { + e.multiplyTo(t, r), this.reduce(r); + }), + (Classic.prototype.sqrTo = function cSqrTo(e, t) { + e.squareTo(t), this.reduce(t); + }), + (Montgomery.prototype.convert = function montConvert(e) { + var t = nbi(); + return ( + e.abs().dlShiftTo(this.m.t, t), + t.divRemTo(this.m, null, t), + e.s < 0 && t.compareTo(BigInteger.ZERO) > 0 && this.m.subTo(t, t), + t + ); + }), + (Montgomery.prototype.revert = function montRevert(e) { + var t = nbi(); + return e.copyTo(t), this.reduce(t), t; + }), + (Montgomery.prototype.reduce = function montReduce(e) { + for (; e.t <= this.mt2; ) e[e.t++] = 0; + for (var t = 0; t < this.m.t; ++t) { + var r = 32767 & e[t], + n = + (r * this.mpl + (((r * this.mph + (e[t] >> 15) * this.mpl) & this.um) << 15)) & + e.DM; + for (e[(r = t + this.m.t)] += this.m.am(0, n, e, t, 0, this.m.t); e[r] >= e.DV; ) + (e[r] -= e.DV), e[++r]++; + } + e.clamp(), e.drShiftTo(this.m.t, e), e.compareTo(this.m) >= 0 && e.subTo(this.m, e); + }), + (Montgomery.prototype.mulTo = function montMulTo(e, t, r) { + e.multiplyTo(t, r), this.reduce(r); + }), + (Montgomery.prototype.sqrTo = function montSqrTo(e, t) { + e.squareTo(t), this.reduce(t); + }), + (BigInteger.prototype.copyTo = function bnpCopyTo(e) { + for (var t = this.t - 1; t >= 0; --t) e[t] = this[t]; + (e.t = this.t), (e.s = this.s); + }), + (BigInteger.prototype.fromInt = function bnpFromInt(e) { + (this.t = 1), + (this.s = e < 0 ? -1 : 0), + e > 0 ? (this[0] = e) : e < -1 ? (this[0] = e + this.DV) : (this.t = 0); + }), + (BigInteger.prototype.fromString = function bnpFromString(e, t) { + var r; + if (16 == t) r = 4; + else if (8 == t) r = 3; + else if (256 == t) r = 8; + else if (2 == t) r = 1; + else if (32 == t) r = 5; + else { + if (4 != t) return void this.fromRadix(e, t); + r = 2; + } + (this.t = 0), (this.s = 0); + for (var n = e.length, i = !1, o = 0; --n >= 0; ) { + var s = 8 == r ? 255 & e[n] : intAt(e, n); + s < 0 + ? '-' == e.charAt(n) && (i = !0) + : ((i = !1), + 0 == o + ? (this[this.t++] = s) + : o + r > this.DB + ? ((this[this.t - 1] |= (s & ((1 << (this.DB - o)) - 1)) << o), + (this[this.t++] = s >> (this.DB - o))) + : (this[this.t - 1] |= s << o), + (o += r) >= this.DB && (o -= this.DB)); + } + 8 == r && + 0 != (128 & e[0]) && + ((this.s = -1), o > 0 && (this[this.t - 1] |= ((1 << (this.DB - o)) - 1) << o)), + this.clamp(), + i && BigInteger.ZERO.subTo(this, this); + }), + (BigInteger.prototype.clamp = function bnpClamp() { + for (var e = this.s & this.DM; this.t > 0 && this[this.t - 1] == e; ) --this.t; + }), + (BigInteger.prototype.dlShiftTo = function bnpDLShiftTo(e, t) { + var r; + for (r = this.t - 1; r >= 0; --r) t[r + e] = this[r]; + for (r = e - 1; r >= 0; --r) t[r] = 0; + (t.t = this.t + e), (t.s = this.s); + }), + (BigInteger.prototype.drShiftTo = function bnpDRShiftTo(e, t) { + for (var r = e; r < this.t; ++r) t[r - e] = this[r]; + (t.t = Math.max(this.t - e, 0)), (t.s = this.s); + }), + (BigInteger.prototype.lShiftTo = function bnpLShiftTo(e, t) { + var r, + n = e % this.DB, + i = this.DB - n, + o = (1 << i) - 1, + s = Math.floor(e / this.DB), + a = (this.s << n) & this.DM; + for (r = this.t - 1; r >= 0; --r) + (t[r + s + 1] = (this[r] >> i) | a), (a = (this[r] & o) << n); + for (r = s - 1; r >= 0; --r) t[r] = 0; + (t[s] = a), (t.t = this.t + s + 1), (t.s = this.s), t.clamp(); + }), + (BigInteger.prototype.rShiftTo = function bnpRShiftTo(e, t) { + t.s = this.s; + var r = Math.floor(e / this.DB); + if (r >= this.t) t.t = 0; + else { + var n = e % this.DB, + i = this.DB - n, + o = (1 << n) - 1; + t[0] = this[r] >> n; + for (var s = r + 1; s < this.t; ++s) + (t[s - r - 1] |= (this[s] & o) << i), (t[s - r] = this[s] >> n); + n > 0 && (t[this.t - r - 1] |= (this.s & o) << i), (t.t = this.t - r), t.clamp(); + } + }), + (BigInteger.prototype.subTo = function bnpSubTo(e, t) { + for (var r = 0, n = 0, i = Math.min(e.t, this.t); r < i; ) + (n += this[r] - e[r]), (t[r++] = n & this.DM), (n >>= this.DB); + if (e.t < this.t) { + for (n -= e.s; r < this.t; ) (n += this[r]), (t[r++] = n & this.DM), (n >>= this.DB); + n += this.s; + } else { + for (n += this.s; r < e.t; ) (n -= e[r]), (t[r++] = n & this.DM), (n >>= this.DB); + n -= e.s; + } + (t.s = n < 0 ? -1 : 0), + n < -1 ? (t[r++] = this.DV + n) : n > 0 && (t[r++] = n), + (t.t = r), + t.clamp(); + }), + (BigInteger.prototype.multiplyTo = function bnpMultiplyTo(e, t) { + var r = this.abs(), + n = e.abs(), + i = r.t; + for (t.t = i + n.t; --i >= 0; ) t[i] = 0; + for (i = 0; i < n.t; ++i) t[i + r.t] = r.am(0, n[i], t, i, 0, r.t); + (t.s = 0), t.clamp(), this.s != e.s && BigInteger.ZERO.subTo(t, t); + }), + (BigInteger.prototype.squareTo = function bnpSquareTo(e) { + for (var t = this.abs(), r = (e.t = 2 * t.t); --r >= 0; ) e[r] = 0; + for (r = 0; r < t.t - 1; ++r) { + var n = t.am(r, t[r], e, 2 * r, 0, 1); + (e[r + t.t] += t.am(r + 1, 2 * t[r], e, 2 * r + 1, n, t.t - r - 1)) >= t.DV && + ((e[r + t.t] -= t.DV), (e[r + t.t + 1] = 1)); + } + e.t > 0 && (e[e.t - 1] += t.am(r, t[r], e, 2 * r, 0, 1)), (e.s = 0), e.clamp(); + }), + (BigInteger.prototype.divRemTo = function bnpDivRemTo(e, t, r) { + var n = e.abs(); + if (!(n.t <= 0)) { + var i = this.abs(); + if (i.t < n.t) return null != t && t.fromInt(0), void (null != r && this.copyTo(r)); + null == r && (r = nbi()); + var o = nbi(), + s = this.s, + a = e.s, + u = this.DB - nbits(n[n.t - 1]); + u > 0 ? (n.lShiftTo(u, o), i.lShiftTo(u, r)) : (n.copyTo(o), i.copyTo(r)); + var c = o.t, + h = o[c - 1]; + if (0 != h) { + var f = h * (1 << this.F1) + (c > 1 ? o[c - 2] >> this.F2 : 0), + l = this.FV / f, + g = (1 << this.F1) / f, + p = 1 << this.F2, + d = r.t, + v = d - c, + y = null == t ? nbi() : t; + for ( + o.dlShiftTo(v, y), + r.compareTo(y) >= 0 && ((r[r.t++] = 1), r.subTo(y, r)), + BigInteger.ONE.dlShiftTo(c, y), + y.subTo(o, o); + o.t < c; + + ) + o[o.t++] = 0; + for (; --v >= 0; ) { + var m = r[--d] == h ? this.DM : Math.floor(r[d] * l + (r[d - 1] + p) * g); + if ((r[d] += o.am(0, m, r, v, 0, c)) < m) + for (o.dlShiftTo(v, y), r.subTo(y, r); r[d] < --m; ) r.subTo(y, r); + } + null != t && (r.drShiftTo(c, t), s != a && BigInteger.ZERO.subTo(t, t)), + (r.t = c), + r.clamp(), + u > 0 && r.rShiftTo(u, r), + s < 0 && BigInteger.ZERO.subTo(r, r); + } + } + }), + (BigInteger.prototype.invDigit = function bnpInvDigit() { + if (this.t < 1) return 0; + var e = this[0]; + if (0 == (1 & e)) return 0; + var t = 3 & e; + return (t = + ((t = + ((t = ((t = (t * (2 - (15 & e) * t)) & 15) * (2 - (255 & e) * t)) & 255) * + (2 - (((65535 & e) * t) & 65535))) & + 65535) * + (2 - ((e * t) % this.DV))) % + this.DV) > 0 + ? this.DV - t + : -t; + }), + (BigInteger.prototype.isEven = function bnpIsEven() { + return 0 == (this.t > 0 ? 1 & this[0] : this.s); + }), + (BigInteger.prototype.exp = function bnpExp(e, t) { + if (e > 4294967295 || e < 1) return BigInteger.ONE; + var r = nbi(), + n = nbi(), + i = t.convert(this), + o = nbits(e) - 1; + for (i.copyTo(r); --o >= 0; ) + if ((t.sqrTo(r, n), (e & (1 << o)) > 0)) t.mulTo(n, i, r); + else { + var s = r; + (r = n), (n = s); + } + return t.revert(r); + }), + (BigInteger.prototype.toString = function bnToString(e) { + if (this.s < 0) return '-' + this.negate().toString(e); + var t; + if (16 == e) t = 4; + else if (8 == e) t = 3; + else if (2 == e) t = 1; + else if (32 == e) t = 5; + else { + if (4 != e) return this.toRadix(e); + t = 2; + } + var r, + n = (1 << t) - 1, + i = !1, + o = '', + s = this.t, + a = this.DB - ((s * this.DB) % t); + if (s-- > 0) + for (a < this.DB && (r = this[s] >> a) > 0 && ((i = !0), (o = int2char(r))); s >= 0; ) + a < t + ? ((r = (this[s] & ((1 << a) - 1)) << (t - a)), + (r |= this[--s] >> (a += this.DB - t))) + : ((r = (this[s] >> (a -= t)) & n), a <= 0 && ((a += this.DB), --s)), + r > 0 && (i = !0), + i && (o += int2char(r)); + return i ? o : '0'; + }), + (BigInteger.prototype.negate = function bnNegate() { + var e = nbi(); + return BigInteger.ZERO.subTo(this, e), e; + }), + (BigInteger.prototype.abs = function bnAbs() { + return this.s < 0 ? this.negate() : this; + }), + (BigInteger.prototype.compareTo = function bnCompareTo(e) { + var t = this.s - e.s; + if (0 != t) return t; + var r = this.t; + if (0 != (t = r - e.t)) return this.s < 0 ? -t : t; + for (; --r >= 0; ) if (0 != (t = this[r] - e[r])) return t; + return 0; + }), + (BigInteger.prototype.bitLength = function bnBitLength() { + return this.t <= 0 + ? 0 + : this.DB * (this.t - 1) + nbits(this[this.t - 1] ^ (this.s & this.DM)); + }), + (BigInteger.prototype.mod = function bnMod(e) { + var t = nbi(); + return ( + this.abs().divRemTo(e, null, t), + this.s < 0 && t.compareTo(BigInteger.ZERO) > 0 && e.subTo(t, t), + t + ); + }), + (BigInteger.prototype.modPowInt = function bnModPowInt(e, t) { + var r; + return (r = e < 256 || t.isEven() ? new Classic(t) : new Montgomery(t)), this.exp(e, r); + }), + (BigInteger.ZERO = nbv(0)), + (BigInteger.ONE = nbv(1)), + (NullExp.prototype.convert = nNop), + (NullExp.prototype.revert = nNop), + (NullExp.prototype.mulTo = function nMulTo(e, t, r) { + e.multiplyTo(t, r); + }), + (NullExp.prototype.sqrTo = function nSqrTo(e, t) { + e.squareTo(t); + }), + (Barrett.prototype.convert = function barrettConvert(e) { + if (e.s < 0 || e.t > 2 * this.m.t) return e.mod(this.m); + if (e.compareTo(this.m) < 0) return e; + var t = nbi(); + return e.copyTo(t), this.reduce(t), t; + }), + (Barrett.prototype.revert = function barrettRevert(e) { + return e; + }), + (Barrett.prototype.reduce = function barrettReduce(e) { + for ( + e.drShiftTo(this.m.t - 1, this.r2), + e.t > this.m.t + 1 && ((e.t = this.m.t + 1), e.clamp()), + this.mu.multiplyUpperTo(this.r2, this.m.t + 1, this.q3), + this.m.multiplyLowerTo(this.q3, this.m.t + 1, this.r2); + e.compareTo(this.r2) < 0; + + ) + e.dAddOffset(1, this.m.t + 1); + for (e.subTo(this.r2, e); e.compareTo(this.m) >= 0; ) e.subTo(this.m, e); + }), + (Barrett.prototype.mulTo = function barrettMulTo(e, t, r) { + e.multiplyTo(t, r), this.reduce(r); + }), + (Barrett.prototype.sqrTo = function barrettSqrTo(e, t) { + e.squareTo(t), this.reduce(t); + }); + var I = [ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, + 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, + 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, + 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, + 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, + 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, + 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, + 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, + 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, + 947, 953, 967, 971, 977, 983, 991, 997, + ], + R = (1 << 26) / I[I.length - 1]; + /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ + */ + function Arcfour() { + (this.i = 0), (this.j = 0), (this.S = new Array()); + } + (BigInteger.prototype.chunkSize = function bnpChunkSize(e) { + return Math.floor((Math.LN2 * this.DB) / Math.log(e)); + }), + (BigInteger.prototype.toRadix = function bnpToRadix(e) { + if ((null == e && (e = 10), 0 == this.signum() || e < 2 || e > 36)) return '0'; + var t = this.chunkSize(e), + r = Math.pow(e, t), + n = nbv(r), + i = nbi(), + o = nbi(), + s = ''; + for (this.divRemTo(n, i, o); i.signum() > 0; ) + (s = (r + o.intValue()).toString(e).substr(1) + s), i.divRemTo(n, i, o); + return o.intValue().toString(e) + s; + }), + (BigInteger.prototype.fromRadix = function bnpFromRadix(e, t) { + this.fromInt(0), null == t && (t = 10); + for ( + var r = this.chunkSize(t), n = Math.pow(t, r), i = !1, o = 0, s = 0, a = 0; + a < e.length; + ++a + ) { + var u = intAt(e, a); + u < 0 + ? '-' == e.charAt(a) && 0 == this.signum() && (i = !0) + : ((s = t * s + u), + ++o >= r && (this.dMultiply(n), this.dAddOffset(s, 0), (o = 0), (s = 0))); + } + o > 0 && (this.dMultiply(Math.pow(t, o)), this.dAddOffset(s, 0)), + i && BigInteger.ZERO.subTo(this, this); + }), + (BigInteger.prototype.fromNumber = function bnpFromNumber(e, t, r) { + if ('number' == typeof t) + if (e < 2) this.fromInt(1); + else + for ( + this.fromNumber(e, r), + this.testBit(e - 1) || + this.bitwiseTo(BigInteger.ONE.shiftLeft(e - 1), op_or, this), + this.isEven() && this.dAddOffset(1, 0); + !this.isProbablePrime(t); + + ) + this.dAddOffset(2, 0), + this.bitLength() > e && this.subTo(BigInteger.ONE.shiftLeft(e - 1), this); + else { + var n = new Array(), + i = 7 & e; + (n.length = 1 + (e >> 3)), + t.nextBytes(n), + i > 0 ? (n[0] &= (1 << i) - 1) : (n[0] = 0), + this.fromString(n, 256); + } + }), + (BigInteger.prototype.bitwiseTo = function bnpBitwiseTo(e, t, r) { + var n, + i, + o = Math.min(e.t, this.t); + for (n = 0; n < o; ++n) r[n] = t(this[n], e[n]); + if (e.t < this.t) { + for (i = e.s & this.DM, n = o; n < this.t; ++n) r[n] = t(this[n], i); + r.t = this.t; + } else { + for (i = this.s & this.DM, n = o; n < e.t; ++n) r[n] = t(i, e[n]); + r.t = e.t; + } + (r.s = t(this.s, e.s)), r.clamp(); + }), + (BigInteger.prototype.changeBit = function bnpChangeBit(e, t) { + var r = BigInteger.ONE.shiftLeft(e); + return this.bitwiseTo(r, t, r), r; + }), + (BigInteger.prototype.addTo = function bnpAddTo(e, t) { + for (var r = 0, n = 0, i = Math.min(e.t, this.t); r < i; ) + (n += this[r] + e[r]), (t[r++] = n & this.DM), (n >>= this.DB); + if (e.t < this.t) { + for (n += e.s; r < this.t; ) (n += this[r]), (t[r++] = n & this.DM), (n >>= this.DB); + n += this.s; + } else { + for (n += this.s; r < e.t; ) (n += e[r]), (t[r++] = n & this.DM), (n >>= this.DB); + n += e.s; + } + (t.s = n < 0 ? -1 : 0), + n > 0 ? (t[r++] = n) : n < -1 && (t[r++] = this.DV + n), + (t.t = r), + t.clamp(); + }), + (BigInteger.prototype.dMultiply = function bnpDMultiply(e) { + (this[this.t] = this.am(0, e - 1, this, 0, 0, this.t)), ++this.t, this.clamp(); + }), + (BigInteger.prototype.dAddOffset = function bnpDAddOffset(e, t) { + if (0 != e) { + for (; this.t <= t; ) this[this.t++] = 0; + for (this[t] += e; this[t] >= this.DV; ) + (this[t] -= this.DV), ++t >= this.t && (this[this.t++] = 0), ++this[t]; + } + }), + (BigInteger.prototype.multiplyLowerTo = function bnpMultiplyLowerTo(e, t, r) { + var n, + i = Math.min(this.t + e.t, t); + for (r.s = 0, r.t = i; i > 0; ) r[--i] = 0; + for (n = r.t - this.t; i < n; ++i) r[i + this.t] = this.am(0, e[i], r, i, 0, this.t); + for (n = Math.min(e.t, t); i < n; ++i) this.am(0, e[i], r, i, 0, t - i); + r.clamp(); + }), + (BigInteger.prototype.multiplyUpperTo = function bnpMultiplyUpperTo(e, t, r) { + --t; + var n = (r.t = this.t + e.t - t); + for (r.s = 0; --n >= 0; ) r[n] = 0; + for (n = Math.max(t - this.t, 0); n < e.t; ++n) + r[this.t + n - t] = this.am(t - n, e[n], r, 0, 0, this.t + n - t); + r.clamp(), r.drShiftTo(1, r); + }), + (BigInteger.prototype.modInt = function bnpModInt(e) { + if (e <= 0) return 0; + var t = this.DV % e, + r = this.s < 0 ? e - 1 : 0; + if (this.t > 0) + if (0 == t) r = this[0] % e; + else for (var n = this.t - 1; n >= 0; --n) r = (t * r + this[n]) % e; + return r; + }), + (BigInteger.prototype.millerRabin = function bnpMillerRabin(e) { + var t = this.subtract(BigInteger.ONE), + r = t.getLowestSetBit(); + if (r <= 0) return !1; + var n = t.shiftRight(r); + (e = (e + 1) >> 1) > I.length && (e = I.length); + for (var i = nbi(), o = 0; o < e; ++o) { + i.fromInt(I[Math.floor(Math.random() * I.length)]); + var s = i.modPow(n, this); + if (0 != s.compareTo(BigInteger.ONE) && 0 != s.compareTo(t)) { + for (var a = 1; a++ < r && 0 != s.compareTo(t); ) + if (0 == (s = s.modPowInt(2, this)).compareTo(BigInteger.ONE)) return !1; + if (0 != s.compareTo(t)) return !1; + } + } + return !0; + }), + (BigInteger.prototype.clone = + /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ + */ + function bnClone() { + var e = nbi(); + return this.copyTo(e), e; + }), + (BigInteger.prototype.intValue = function bnIntValue() { + if (this.s < 0) { + if (1 == this.t) return this[0] - this.DV; + if (0 == this.t) return -1; + } else { + if (1 == this.t) return this[0]; + if (0 == this.t) return 0; + } + return ((this[1] & ((1 << (32 - this.DB)) - 1)) << this.DB) | this[0]; + }), + (BigInteger.prototype.byteValue = function bnByteValue() { + return 0 == this.t ? this.s : (this[0] << 24) >> 24; + }), + (BigInteger.prototype.shortValue = function bnShortValue() { + return 0 == this.t ? this.s : (this[0] << 16) >> 16; + }), + (BigInteger.prototype.signum = function bnSigNum() { + return this.s < 0 ? -1 : this.t <= 0 || (1 == this.t && this[0] <= 0) ? 0 : 1; + }), + (BigInteger.prototype.toByteArray = function bnToByteArray() { + var e = this.t, + t = new Array(); + t[0] = this.s; + var r, + n = this.DB - ((e * this.DB) % 8), + i = 0; + if (e-- > 0) + for ( + n < this.DB && + (r = this[e] >> n) != (this.s & this.DM) >> n && + (t[i++] = r | (this.s << (this.DB - n))); + e >= 0; + + ) + n < 8 + ? ((r = (this[e] & ((1 << n) - 1)) << (8 - n)), + (r |= this[--e] >> (n += this.DB - 8))) + : ((r = (this[e] >> (n -= 8)) & 255), n <= 0 && ((n += this.DB), --e)), + 0 != (128 & r) && (r |= -256), + 0 == i && (128 & this.s) != (128 & r) && ++i, + (i > 0 || r != this.s) && (t[i++] = r); + return t; + }), + (BigInteger.prototype.equals = function bnEquals(e) { + return 0 == this.compareTo(e); + }), + (BigInteger.prototype.min = function bnMin(e) { + return this.compareTo(e) < 0 ? this : e; + }), + (BigInteger.prototype.max = function bnMax(e) { + return this.compareTo(e) > 0 ? this : e; + }), + (BigInteger.prototype.and = function bnAnd(e) { + var t = nbi(); + return this.bitwiseTo(e, op_and, t), t; + }), + (BigInteger.prototype.or = function bnOr(e) { + var t = nbi(); + return this.bitwiseTo(e, op_or, t), t; + }), + (BigInteger.prototype.xor = function bnXor(e) { + var t = nbi(); + return this.bitwiseTo(e, op_xor, t), t; + }), + (BigInteger.prototype.andNot = function bnAndNot(e) { + var t = nbi(); + return this.bitwiseTo(e, op_andnot, t), t; + }), + (BigInteger.prototype.not = function bnNot() { + for (var e = nbi(), t = 0; t < this.t; ++t) e[t] = this.DM & ~this[t]; + return (e.t = this.t), (e.s = ~this.s), e; + }), + (BigInteger.prototype.shiftLeft = function bnShiftLeft(e) { + var t = nbi(); + return e < 0 ? this.rShiftTo(-e, t) : this.lShiftTo(e, t), t; + }), + (BigInteger.prototype.shiftRight = function bnShiftRight(e) { + var t = nbi(); + return e < 0 ? this.lShiftTo(-e, t) : this.rShiftTo(e, t), t; + }), + (BigInteger.prototype.getLowestSetBit = function bnGetLowestSetBit() { + for (var e = 0; e < this.t; ++e) if (0 != this[e]) return e * this.DB + lbit(this[e]); + return this.s < 0 ? this.t * this.DB : -1; + }), + (BigInteger.prototype.bitCount = function bnBitCount() { + for (var e = 0, t = this.s & this.DM, r = 0; r < this.t; ++r) e += cbit(this[r] ^ t); + return e; + }), + (BigInteger.prototype.testBit = function bnTestBit(e) { + var t = Math.floor(e / this.DB); + return t >= this.t ? 0 != this.s : 0 != (this[t] & (1 << e % this.DB)); + }), + (BigInteger.prototype.setBit = function bnSetBit(e) { + return this.changeBit(e, op_or); + }), + (BigInteger.prototype.clearBit = function bnClearBit(e) { + return this.changeBit(e, op_andnot); + }), + (BigInteger.prototype.flipBit = function bnFlipBit(e) { + return this.changeBit(e, op_xor); + }), + (BigInteger.prototype.add = function bnAdd(e) { + var t = nbi(); + return this.addTo(e, t), t; + }), + (BigInteger.prototype.subtract = function bnSubtract(e) { + var t = nbi(); + return this.subTo(e, t), t; + }), + (BigInteger.prototype.multiply = function bnMultiply(e) { + var t = nbi(); + return this.multiplyTo(e, t), t; + }), + (BigInteger.prototype.divide = function bnDivide(e) { + var t = nbi(); + return this.divRemTo(e, t, null), t; + }), + (BigInteger.prototype.remainder = function bnRemainder(e) { + var t = nbi(); + return this.divRemTo(e, null, t), t; + }), + (BigInteger.prototype.divideAndRemainder = function bnDivideAndRemainder(e) { + var t = nbi(), + r = nbi(); + return this.divRemTo(e, t, r), new Array(t, r); + }), + (BigInteger.prototype.modPow = function bnModPow(e, t) { + var r, + n, + i = e.bitLength(), + o = nbv(1); + if (i <= 0) return o; + (r = i < 18 ? 1 : i < 48 ? 3 : i < 144 ? 4 : i < 768 ? 5 : 6), + (n = i < 8 ? new Classic(t) : t.isEven() ? new Barrett(t) : new Montgomery(t)); + var s = new Array(), + a = 3, + u = r - 1, + c = (1 << r) - 1; + if (((s[1] = n.convert(this)), r > 1)) { + var h = nbi(); + for (n.sqrTo(s[1], h); a <= c; ) (s[a] = nbi()), n.mulTo(h, s[a - 2], s[a]), (a += 2); + } + var f, + l, + g = e.t - 1, + p = !0, + d = nbi(); + for (i = nbits(e[g]) - 1; g >= 0; ) { + for ( + i >= u + ? (f = (e[g] >> (i - u)) & c) + : ((f = (e[g] & ((1 << (i + 1)) - 1)) << (u - i)), + g > 0 && (f |= e[g - 1] >> (this.DB + i - u))), + a = r; + 0 == (1 & f); + + ) + (f >>= 1), --a; + if (((i -= a) < 0 && ((i += this.DB), --g), p)) s[f].copyTo(o), (p = !1); + else { + for (; a > 1; ) n.sqrTo(o, d), n.sqrTo(d, o), (a -= 2); + a > 0 ? n.sqrTo(o, d) : ((l = o), (o = d), (d = l)), n.mulTo(d, s[f], o); + } + for (; g >= 0 && 0 == (e[g] & (1 << i)); ) + n.sqrTo(o, d), (l = o), (o = d), (d = l), --i < 0 && ((i = this.DB - 1), --g); + } + return n.revert(o); + }), + (BigInteger.prototype.modInverse = function bnModInverse(e) { + var t = e.isEven(); + if ((this.isEven() && t) || 0 == e.signum()) return BigInteger.ZERO; + for ( + var r = e.clone(), n = this.clone(), i = nbv(1), o = nbv(0), s = nbv(0), a = nbv(1); + 0 != r.signum(); + + ) { + for (; r.isEven(); ) + r.rShiftTo(1, r), + t + ? ((i.isEven() && o.isEven()) || (i.addTo(this, i), o.subTo(e, o)), + i.rShiftTo(1, i)) + : o.isEven() || o.subTo(e, o), + o.rShiftTo(1, o); + for (; n.isEven(); ) + n.rShiftTo(1, n), + t + ? ((s.isEven() && a.isEven()) || (s.addTo(this, s), a.subTo(e, a)), + s.rShiftTo(1, s)) + : a.isEven() || a.subTo(e, a), + a.rShiftTo(1, a); + r.compareTo(n) >= 0 + ? (r.subTo(n, r), t && i.subTo(s, i), o.subTo(a, o)) + : (n.subTo(r, n), t && s.subTo(i, s), a.subTo(o, a)); + } + return 0 != n.compareTo(BigInteger.ONE) + ? BigInteger.ZERO + : a.compareTo(e) >= 0 + ? a.subtract(e) + : a.signum() < 0 + ? (a.addTo(e, a), a.signum() < 0 ? a.add(e) : a) + : a; + }), + (BigInteger.prototype.pow = function bnPow(e) { + return this.exp(e, new NullExp()); + }), + (BigInteger.prototype.gcd = function bnGCD(e) { + var t = this.s < 0 ? this.negate() : this.clone(), + r = e.s < 0 ? e.negate() : e.clone(); + if (t.compareTo(r) < 0) { + var n = t; + (t = r), (r = n); + } + var i = t.getLowestSetBit(), + o = r.getLowestSetBit(); + if (o < 0) return t; + for (i < o && (o = i), o > 0 && (t.rShiftTo(o, t), r.rShiftTo(o, r)); t.signum() > 0; ) + (i = t.getLowestSetBit()) > 0 && t.rShiftTo(i, t), + (i = r.getLowestSetBit()) > 0 && r.rShiftTo(i, r), + t.compareTo(r) >= 0 + ? (t.subTo(r, t), t.rShiftTo(1, t)) + : (r.subTo(t, r), r.rShiftTo(1, r)); + return o > 0 && r.lShiftTo(o, r), r; + }), + (BigInteger.prototype.isProbablePrime = function bnIsProbablePrime(e) { + var t, + r = this.abs(); + if (1 == r.t && r[0] <= I[I.length - 1]) { + for (t = 0; t < I.length; ++t) if (r[0] == I[t]) return !0; + return !1; + } + if (r.isEven()) return !1; + for (t = 1; t < I.length; ) { + for (var n = I[t], i = t + 1; i < I.length && n < R; ) n *= I[i++]; + for (n = r.modInt(n); t < i; ) if (n % I[t++] == 0) return !1; + } + return r.millerRabin(e); + }), + (BigInteger.prototype.square = function bnSquare() { + var e = nbi(); + return this.squareTo(e), e; + }), + (Arcfour.prototype.init = function ARC4init(e) { + var t, r, n; + for (t = 0; t < 256; ++t) this.S[t] = t; + for (r = 0, t = 0; t < 256; ++t) + (r = (r + this.S[t] + e[t % e.length]) & 255), + (n = this.S[t]), + (this.S[t] = this.S[r]), + (this.S[r] = n); + (this.i = 0), (this.j = 0); + }), + (Arcfour.prototype.next = function ARC4next() { + var e; + return ( + (this.i = (this.i + 1) & 255), + (this.j = (this.j + this.S[this.i]) & 255), + (e = this.S[this.i]), + (this.S[this.i] = this.S[this.j]), + (this.S[this.j] = e), + this.S[(e + this.S[this.i]) & 255] + ); + }); + var T, + U, + M, + L = 256; + /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ + */ function rng_seed_time() { + !(function rng_seed_int(e) { + (U[M++] ^= 255 & e), + (U[M++] ^= (e >> 8) & 255), + (U[M++] ^= (e >> 16) & 255), + (U[M++] ^= (e >> 24) & 255), + M >= L && (M -= L); + })(new Date().getTime()); + } + if (null == U) { + var D; + if ( + ((U = new Array()), + (M = 0), + void 0 !== p && (void 0 !== p.crypto || void 0 !== p.msCrypto)) + ) { + var N = p.crypto || p.msCrypto; + if (N.getRandomValues) { + var O = new Uint8Array(32); + for (N.getRandomValues(O), D = 0; D < 32; ++D) U[M++] = O[D]; + } else if ('Netscape' == u.appName && u.appVersion < '5') { + var H = p.crypto.random(32); + for (D = 0; D < H.length; ++D) U[M++] = 255 & H.charCodeAt(D); + } + } + for (; M < L; ) + (D = Math.floor(65536 * Math.random())), (U[M++] = D >>> 8), (U[M++] = 255 & D); + (M = 0), rng_seed_time(); + } + function rng_get_byte() { + if (null == T) { + for ( + rng_seed_time(), + (T = (function prng_newstate() { + return new Arcfour(); + })()).init(U), + M = 0; + M < U.length; + ++M + ) + U[M] = 0; + M = 0; + } + return T.next(); + } + function SecureRandom() {} /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ */ - function bnClone(){var e=nbi();return this.copyTo(e),e},BigInteger.prototype.intValue=function bnIntValue(){if(this.s<0){if(1==this.t)return this[0]-this.DV;if(0==this.t)return-1}else{if(1==this.t)return this[0];if(0==this.t)return 0}return(this[1]&(1<<32-this.DB)-1)<>24},BigInteger.prototype.shortValue=function bnShortValue(){return 0==this.t?this.s:this[0]<<16>>16},BigInteger.prototype.signum=function bnSigNum(){return this.s<0?-1:this.t<=0||1==this.t&&this[0]<=0?0:1},BigInteger.prototype.toByteArray=function bnToByteArray(){var e=this.t,t=new Array;t[0]=this.s;var r,n=this.DB-e*this.DB%8,i=0;if(e-- >0)for(n>n)!=(this.s&this.DM)>>n&&(t[i++]=r|this.s<=0;)n<8?(r=(this[e]&(1<>(n+=this.DB-8)):(r=this[e]>>(n-=8)&255,n<=0&&(n+=this.DB,--e)),0!=(128&r)&&(r|=-256),0==i&&(128&this.s)!=(128&r)&&++i,(i>0||r!=this.s)&&(t[i++]=r);return t},BigInteger.prototype.equals=function bnEquals(e){return 0==this.compareTo(e)},BigInteger.prototype.min=function bnMin(e){return this.compareTo(e)<0?this:e},BigInteger.prototype.max=function bnMax(e){return this.compareTo(e)>0?this:e},BigInteger.prototype.and=function bnAnd(e){var t=nbi();return this.bitwiseTo(e,op_and,t),t},BigInteger.prototype.or=function bnOr(e){var t=nbi();return this.bitwiseTo(e,op_or,t),t},BigInteger.prototype.xor=function bnXor(e){var t=nbi();return this.bitwiseTo(e,op_xor,t),t},BigInteger.prototype.andNot=function bnAndNot(e){var t=nbi();return this.bitwiseTo(e,op_andnot,t),t},BigInteger.prototype.not=function bnNot(){for(var e=nbi(),t=0;t=this.t?0!=this.s:0!=(this[t]&1<1){var h=nbi();for(n.sqrTo(s[1],h);a<=c;)s[a]=nbi(),n.mulTo(h,s[a-2],s[a]),a+=2}var f,l,g=e.t-1,p=!0,d=nbi();for(i=nbits(e[g])-1;g>=0;){for(i>=u?f=e[g]>>i-u&c:(f=(e[g]&(1<0&&(f|=e[g-1]>>this.DB+i-u)),a=r;0==(1&f);)f>>=1,--a;if((i-=a)<0&&(i+=this.DB,--g),p)s[f].copyTo(o),p=!1;else{for(;a>1;)n.sqrTo(o,d),n.sqrTo(d,o),a-=2;a>0?n.sqrTo(o,d):(l=o,o=d,d=l),n.mulTo(d,s[f],o)}for(;g>=0&&0==(e[g]&1<=0?(r.subTo(n,r),t&&i.subTo(s,i),o.subTo(a,o)):(n.subTo(r,n),t&&s.subTo(i,s),a.subTo(o,a))}return 0!=n.compareTo(BigInteger.ONE)?BigInteger.ZERO:a.compareTo(e)>=0?a.subtract(e):a.signum()<0?(a.addTo(e,a),a.signum()<0?a.add(e):a):a},BigInteger.prototype.pow=function bnPow(e){return this.exp(e,new NullExp)},BigInteger.prototype.gcd=function bnGCD(e){var t=this.s<0?this.negate():this.clone(),r=e.s<0?e.negate():e.clone();if(t.compareTo(r)<0){var n=t;t=r,r=n}var i=t.getLowestSetBit(),o=r.getLowestSetBit();if(o<0)return t;for(i0&&(t.rShiftTo(o,t),r.rShiftTo(o,r));t.signum()>0;)(i=t.getLowestSetBit())>0&&t.rShiftTo(i,t),(i=r.getLowestSetBit())>0&&r.rShiftTo(i,r),t.compareTo(r)>=0?(t.subTo(r,t),t.rShiftTo(1,t)):(r.subTo(t,r),r.rShiftTo(1,r));return o>0&&r.lShiftTo(o,r),r},BigInteger.prototype.isProbablePrime=function bnIsProbablePrime(e){var t,r=this.abs();if(1==r.t&&r[0]<=I[I.length-1]){for(t=0;t>8&255,U[M++]^=e>>16&255,U[M++]^=e>>24&255,M>=L&&(M-=L)}((new Date).getTime())}if(null==U){var D;if(U=new Array,M=0,void 0!==p&&(void 0!==p.crypto||void 0!==p.msCrypto)){var N=p.crypto||p.msCrypto;if(N.getRandomValues){var O=new Uint8Array(32);for(N.getRandomValues(O),D=0;D<32;++D)U[M++]=O[D]}else if("Netscape"==u.appName&&u.appVersion<"5"){var H=p.crypto.random(32);for(D=0;D>>8,U[M++]=255&D;M=0,rng_seed_time()}function rng_get_byte(){if(null==T){for(rng_seed_time(),(T=function prng_newstate(){return new Arcfour}()).init(U),M=0;M>24,(16711680&i)>>16,(65280&i)>>8,255&i]))),i+=1;return n}function RSAKey(){this.n=null,this.e=0,this.d=null,this.p=null,this.q=null,this.dmp1=null,this.dmq1=null,this.coeff=null} - /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ - */ - function ECFieldElementFp(e,t){this.x=t,this.q=e}function ECPointFp(e,t,r,n){this.curve=e,this.x=t,this.y=r,this.z=null==n?BigInteger.ONE:n,this.zinv=null}function ECCurveFp(e,t,r){this.q=e,this.a=this.fromBigInteger(t),this.b=this.fromBigInteger(r),this.infinity=new ECPointFp(this,null,null)}SecureRandom.prototype.nextBytes=function rng_get_bytes(e){var t;for(t=0;t0&&t.length>0))throw"Invalid RSA public key";this.n=parseBigInt(e,16),this.e=parseInt(t,16)}},RSAKey.prototype.encrypt=function RSAEncrypt(e){var t=function pkcs1pad2(e,t){if(t=0&&t>0;){var i=e.charCodeAt(n--);i<128?r[--t]=i:i>127&&i<2048?(r[--t]=63&i|128,r[--t]=i>>6|192):(r[--t]=63&i|128,r[--t]=i>>6&63|128,r[--t]=i>>12|224)}r[--t]=0;for(var o=new SecureRandom,s=new Array;t>2;){for(s[0]=0;0==s[0];)o.nextBytes(s);r[--t]=s[0]}return r[--t]=2,r[--t]=0,new BigInteger(r)}(e,this.n.bitLength()+7>>3);if(null==t)return null;var r=this.doPublic(t);if(null==r)return null;var n=r.toString(16);return 0==(1&n.length)?n:"0"+n},RSAKey.prototype.encryptOAEP=function RSAEncryptOAEP(e,t,r){var n=function oaep_pad(e,t,r,n){var i=K.crypto.MessageDigest,o=K.crypto.Util,s=null;if(r||(r="sha1"),"string"==typeof r&&(s=i.getCanonicalAlgName(r),n=i.getHashLength(s),r=function f(e){return hextorstr(o.hashHex(rstrtohex(e),s))}),e.length+2*n+2>t)throw"Message too long for RSA";var a,u="";for(a=0;a>3,t,r);if(null==n)return null;var i=this.doPublic(n);if(null==i)return null;var o=i.toString(16);return 0==(1&o.length)?o:"0"+o},RSAKey.prototype.type="RSA",ECFieldElementFp.prototype.equals=function feFpEquals(e){return e==this||this.q.equals(e.q)&&this.x.equals(e.x)},ECFieldElementFp.prototype.toBigInteger=function feFpToBigInteger(){return this.x},ECFieldElementFp.prototype.negate=function feFpNegate(){return new ECFieldElementFp(this.q,this.x.negate().mod(this.q))},ECFieldElementFp.prototype.add=function feFpAdd(e){return new ECFieldElementFp(this.q,this.x.add(e.toBigInteger()).mod(this.q))},ECFieldElementFp.prototype.subtract=function feFpSubtract(e){return new ECFieldElementFp(this.q,this.x.subtract(e.toBigInteger()).mod(this.q))},ECFieldElementFp.prototype.multiply=function feFpMultiply(e){return new ECFieldElementFp(this.q,this.x.multiply(e.toBigInteger()).mod(this.q))},ECFieldElementFp.prototype.square=function feFpSquare(){return new ECFieldElementFp(this.q,this.x.square().mod(this.q))},ECFieldElementFp.prototype.divide=function feFpDivide(e){return new ECFieldElementFp(this.q,this.x.multiply(e.toBigInteger().modInverse(this.q)).mod(this.q))},ECPointFp.prototype.getX=function pointFpGetX(){return null==this.zinv&&(this.zinv=this.z.modInverse(this.curve.q)),this.curve.fromBigInteger(this.x.toBigInteger().multiply(this.zinv).mod(this.curve.q))},ECPointFp.prototype.getY=function pointFpGetY(){return null==this.zinv&&(this.zinv=this.z.modInverse(this.curve.q)),this.curve.fromBigInteger(this.y.toBigInteger().multiply(this.zinv).mod(this.curve.q))},ECPointFp.prototype.equals=function pointFpEquals(e){return e==this||(this.isInfinity()?e.isInfinity():e.isInfinity()?this.isInfinity():!!e.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(e.z)).mod(this.curve.q).equals(BigInteger.ZERO)&&e.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(e.z)).mod(this.curve.q).equals(BigInteger.ZERO))},ECPointFp.prototype.isInfinity=function pointFpIsInfinity(){return null==this.x&&null==this.y||this.z.equals(BigInteger.ZERO)&&!this.y.toBigInteger().equals(BigInteger.ZERO)},ECPointFp.prototype.negate=function pointFpNegate(){return new ECPointFp(this.curve,this.x,this.y.negate(),this.z)},ECPointFp.prototype.add=function pointFpAdd(e){if(this.isInfinity())return e;if(e.isInfinity())return this;var t=e.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(e.z)).mod(this.curve.q),r=e.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(e.z)).mod(this.curve.q);if(BigInteger.ZERO.equals(r))return BigInteger.ZERO.equals(t)?this.twice():this.curve.getInfinity();var n=new BigInteger("3"),i=this.x.toBigInteger(),o=this.y.toBigInteger(),s=(e.x.toBigInteger(),e.y.toBigInteger(),r.square()),a=s.multiply(r),u=i.multiply(s),c=t.square().multiply(this.z),h=c.subtract(u.shiftLeft(1)).multiply(e.z).subtract(a).multiply(r).mod(this.curve.q),f=u.multiply(n).multiply(t).subtract(o.multiply(a)).subtract(c.multiply(t)).multiply(e.z).add(t.multiply(a)).mod(this.curve.q),l=a.multiply(this.z).multiply(e.z).mod(this.curve.q);return new ECPointFp(this.curve,this.curve.fromBigInteger(h),this.curve.fromBigInteger(f),l)},ECPointFp.prototype.twice=function pointFpTwice(){if(this.isInfinity())return this;if(0==this.y.toBigInteger().signum())return this.curve.getInfinity();var e=new BigInteger("3"),t=this.x.toBigInteger(),r=this.y.toBigInteger(),n=r.multiply(this.z),i=n.multiply(r).mod(this.curve.q),o=this.curve.a.toBigInteger(),s=t.square().multiply(e);BigInteger.ZERO.equals(o)||(s=s.add(this.z.square().multiply(o)));var a=(s=s.mod(this.curve.q)).square().subtract(t.shiftLeft(3).multiply(i)).shiftLeft(1).multiply(n).mod(this.curve.q),u=s.multiply(e).multiply(t).subtract(i.shiftLeft(1)).shiftLeft(2).multiply(i).subtract(s.square().multiply(s)).mod(this.curve.q),c=n.square().multiply(n).shiftLeft(3).mod(this.curve.q);return new ECPointFp(this.curve,this.curve.fromBigInteger(a),this.curve.fromBigInteger(u),c)},ECPointFp.prototype.multiply=function pointFpMultiply(e){if(this.isInfinity())return this;if(0==e.signum())return this.curve.getInfinity();var t,r=e,n=r.multiply(new BigInteger("3")),i=this.negate(),o=this;for(t=n.bitLength()-2;t>0;--t){o=o.twice();var s=n.testBit(t);s!=r.testBit(t)&&(o=o.add(s?this:i))}return o},ECPointFp.prototype.multiplyTwo=function pointFpMultiplyTwo(e,t,r){var n;n=e.bitLength()>r.bitLength()?e.bitLength()-1:r.bitLength()-1;for(var i=this.curve.getInfinity(),o=this.add(t);n>=0;)i=i.twice(),e.testBit(n)?i=r.testBit(n)?i.add(o):i.add(this):r.testBit(n)&&(i=i.add(t)),--n;return i},ECCurveFp.prototype.getQ=function curveFpGetQ(){return this.q},ECCurveFp.prototype.getA=function curveFpGetA(){return this.a},ECCurveFp.prototype.getB=function curveFpGetB(){return this.b},ECCurveFp.prototype.equals=function curveFpEquals(e){return e==this||this.q.equals(e.q)&&this.a.equals(e.a)&&this.b.equals(e.b)},ECCurveFp.prototype.getInfinity=function curveFpGetInfinity(){return this.infinity},ECCurveFp.prototype.fromBigInteger=function curveFpFromBigInteger(e){return new ECFieldElementFp(this.q,e)},ECCurveFp.prototype.decodePointHex=function curveFpDecodePointHex(e){switch(parseInt(e.substr(0,2),16)){case 0:return this.infinity;case 2:case 3:return null;case 4:case 6:case 7:var t=(e.length-2)/2,r=e.substr(2,t),n=e.substr(t+2,t);return new ECPointFp(this,this.fromBigInteger(new BigInteger(r,16)),this.fromBigInteger(new BigInteger(n,16)));default:return null}}; - /*! Mike Samuel (c) 2009 | code.google.com/p/json-sans-eval - */ - var K,q,W,V=function(){var e=new RegExp('(?:false|true|null|[\\{\\}\\[\\]]|(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)|(?:"(?:[^\\0-\\x08\\x0a-\\x1f"\\\\]|\\\\(?:["/\\\\bfnrt]|u[0-9A-Fa-f]{4}))*"))',"g"),t=new RegExp("\\\\(?:([^u])|u(.{4}))","g"),r={'"':'"',"/":"/","\\":"\\",b:"\b",f:"\f",n:"\n",r:"\r",t:"\t"};function h(e,t,n){return t?r[t]:String.fromCharCode(parseInt(n,16))}var n=new String(""),o=(Object,Array,Object.hasOwnProperty);return function(r,a){var u,c,f=r.match(e),l=f[0],g=!1;"{"===l?u={}:"["===l?u=[]:(u=[],g=!0);for(var p=[u],d=1-g,v=f.length;d=0;)delete r[n[h]]}return a.call(e,t,r)}({"":u},"")}return u}}(),J=new function(){};function stoBA(e){for(var t=new Array,r=0;ri.length&&(i=n[r]);return(e=e.replace(i,"::")).slice(1,-1)}function hextoip(e){var t="malformed hex value";if(!e.match(/^([0-9A-Fa-f][0-9A-Fa-f]){1,}$/))throw t;if(8!=e.length)return 32==e.length?hextoipv6(e):e;try{return parseInt(e.substr(0,2),16)+"."+parseInt(e.substr(2,2),16)+"."+parseInt(e.substr(4,2),16)+"."+parseInt(e.substr(6,2),16)}catch(e){throw t}}function encodeURIComponentAll(e){for(var t=encodeURIComponent(e),r="",n=0;n"7"?"00"+e:e}J.getLblen=function(e,t){if("8"!=e.substr(t+2,1))return 1;var r=parseInt(e.substr(t+3,1));return 0==r?-1:0=2*o)break;if(a>=200)break;n.push(u),s=u,a++}return n},J.getNthChildIdx=function(e,t,r){return J.getChildIdx(e,t)[r]},J.getIdxbyList=function(e,t,r,n){var i,o,s=J;if(0==r.length){if(void 0!==n&&e.substr(t,2)!==n)throw"checking tag doesn't match: "+e.substr(t,2)+"!="+n;return t}return i=r.shift(),o=s.getChildIdx(e,t),s.getIdxbyList(e,o[i],r,n)},J.getTLVbyList=function(e,t,r,n){var i=J,o=i.getIdxbyList(e,t,r);if(void 0===o)throw"can't find nthList object";if(void 0!==n&&e.substr(o,2)!=n)throw"checking tag doesn't match: "+e.substr(o,2)+"!="+n;return i.getTLV(e,o)},J.getVbyList=function(e,t,r,n,i){var o,s,a=J;if(void 0===(o=a.getIdxbyList(e,t,r,n)))throw"can't find nthList object";return s=a.getV(e,o),!0===i&&(s=s.substr(2)),s},J.hextooidstr=function(e){var t=function h(e,t){return e.length>=t?e:new Array(t-e.length+1).join("0")+e},r=[],n=e.substr(0,2),i=parseInt(n,16);r[0]=new String(Math.floor(i/40)),r[1]=new String(i%40);for(var o=e.substr(2),s=[],a=0;a0&&(h=h+"."+u.join(".")),h},J.dump=function(e,t,r,n){var i=J,o=i.getV,s=i.dump,a=i.getChildIdx,u=e;e instanceof K.asn1.ASN1Object&&(u=e.getEncodedHex());var c=function q(e,t){return e.length<=2*t?e:e.substr(0,t)+"..(total "+e.length/2+"bytes).."+e.substr(e.length-t,t)};void 0===t&&(t={ommit_long_octet:32}),void 0===r&&(r=0),void 0===n&&(n="");var h=t.ommit_long_octet;if("01"==u.substr(r,2))return"00"==(f=o(u,r))?n+"BOOLEAN FALSE\n":n+"BOOLEAN TRUE\n";if("02"==u.substr(r,2))return n+"INTEGER "+c(f=o(u,r),h)+"\n";if("03"==u.substr(r,2))return n+"BITSTRING "+c(f=o(u,r),h)+"\n";if("04"==u.substr(r,2)){var f=o(u,r);if(i.isASN1HEX(f)){var l=n+"OCTETSTRING, encapsulates\n";return l+=s(f,t,0,n+" ")}return n+"OCTETSTRING "+c(f,h)+"\n"}if("05"==u.substr(r,2))return n+"NULL\n";if("06"==u.substr(r,2)){var g=o(u,r),p=K.asn1.ASN1Util.oidHexToInt(g),d=K.asn1.x509.OID.oid2name(p),v=p.replace(/\./g," ");return""!=d?n+"ObjectIdentifier "+d+" ("+v+")\n":n+"ObjectIdentifier ("+v+")\n"}if("0c"==u.substr(r,2))return n+"UTF8String '"+hextoutf8(o(u,r))+"'\n";if("13"==u.substr(r,2))return n+"PrintableString '"+hextoutf8(o(u,r))+"'\n";if("14"==u.substr(r,2))return n+"TeletexString '"+hextoutf8(o(u,r))+"'\n";if("16"==u.substr(r,2))return n+"IA5String '"+hextoutf8(o(u,r))+"'\n";if("17"==u.substr(r,2))return n+"UTCTime "+hextoutf8(o(u,r))+"\n";if("18"==u.substr(r,2))return n+"GeneralizedTime "+hextoutf8(o(u,r))+"\n";if("30"==u.substr(r,2)){if("3000"==u.substr(r,4))return n+"SEQUENCE {}\n";l=n+"SEQUENCE\n";var y=t;if((2==(F=a(u,r)).length||3==F.length)&&"06"==u.substr(F[0],2)&&"04"==u.substr(F[F.length-1],2)){d=i.oidname(o(u,F[0]));var m=JSON.parse(JSON.stringify(t));m.x509ExtName=d,y=m}for(var S=0;Si)throw"key is too short for SigAlg: keylen="+r+","+t;for(var o="0001",s="00"+n,a="",u=i-o.length-s.length,c=0;c=0)return!1;if(r.compareTo(BigInteger.ONE)<0||r.compareTo(i)>=0)return!1;var s=r.modInverse(i),a=e.multiply(s).mod(i),u=t.multiply(s).mod(i);return o.multiply(a).add(n.multiply(u)).getX().toBigInteger().mod(i).equals(t)},this.serializeSig=function(e,t){var r=e.toByteArraySigned(),n=t.toByteArraySigned(),i=[];return i.push(2),i.push(r.length),(i=i.concat(r)).push(2),i.push(n.length),(i=i.concat(n)).unshift(i.length),i.unshift(48),i},this.parseSig=function(e){var t;if(48!=e[0])throw new Error("Signature not a valid DERSequence");if(2!=e[t=2])throw new Error("First element in signature must be a DERInteger");var r=e.slice(t+2,t+2+e[t+1]);if(2!=e[t+=2+e[t+1]])throw new Error("Second element in signature must be a DERInteger");var n=e.slice(t+2,t+2+e[t+1]);return t+=2+e[t+1],{r:BigInteger.fromByteArrayUnsigned(r),s:BigInteger.fromByteArrayUnsigned(n)}},this.parseSigCompact=function(e){if(65!==e.length)throw"Signature has the wrong length";var t=e[0]-27;if(t<0||t>7)throw"Invalid signature type";var r=this.ecparams.n;return{r:BigInteger.fromByteArrayUnsigned(e.slice(1,33)).mod(r),s:BigInteger.fromByteArrayUnsigned(e.slice(33,65)).mod(r),i:t}},this.readPKCS5PrvKeyHex=function(e){var t,r,n,i=J,o=K.crypto.ECDSA.getName,s=i.getVbyList;if(!1===i.isASN1HEX(e))throw"not ASN.1 hex string";try{t=s(e,0,[2,0],"06"),r=s(e,0,[1],"04");try{n=s(e,0,[3,0],"03").substr(2)}catch(e){}}catch(e){throw"malformed PKCS#1/5 plain ECC private key"}if(this.curveName=o(t),void 0===this.curveName)throw"unsupported curve name";this.setNamedCurve(this.curveName),this.setPublicKeyHex(n),this.setPrivateKeyHex(r),this.isPublic=!1},this.readPKCS8PrvKeyHex=function(e){var t,r,n,i=J,o=K.crypto.ECDSA.getName,s=i.getVbyList;if(!1===i.isASN1HEX(e))throw"not ASN.1 hex string";try{s(e,0,[1,0],"06"),t=s(e,0,[1,1],"06"),r=s(e,0,[2,0,1],"04");try{n=s(e,0,[2,0,2,0],"03").substr(2)}catch(e){}}catch(e){throw"malformed PKCS#8 plain ECC private key"}if(this.curveName=o(t),void 0===this.curveName)throw"unsupported curve name";this.setNamedCurve(this.curveName),this.setPublicKeyHex(n),this.setPrivateKeyHex(r),this.isPublic=!1},this.readPKCS8PubKeyHex=function(e){var t,r,n=J,i=K.crypto.ECDSA.getName,o=n.getVbyList;if(!1===n.isASN1HEX(e))throw"not ASN.1 hex string";try{o(e,0,[0,0],"06"),t=o(e,0,[0,1],"06"),r=o(e,0,[1],"03").substr(2)}catch(e){throw"malformed PKCS#8 ECC public key"}if(this.curveName=i(t),null===this.curveName)throw"unsupported curve name";this.setNamedCurve(this.curveName),this.setPublicKeyHex(r)},this.readCertPubKeyHex=function(e,t){5!==t&&(t=6);var r,n,i=J,o=K.crypto.ECDSA.getName,s=i.getVbyList;if(!1===i.isASN1HEX(e))throw"not ASN.1 hex string";try{r=s(e,0,[0,t,0,1],"06"),n=s(e,0,[0,t,1],"03").substr(2)}catch(e){throw"malformed X.509 certificate ECC public key"}if(this.curveName=o(r),null===this.curveName)throw"unsupported curve name";this.setNamedCurve(this.curveName),this.setPublicKeyHex(n)},void 0!==e&&void 0!==e.curve&&(this.curveName=e.curve),void 0===this.curveName&&(this.curveName="secp256r1"),this.setNamedCurve(this.curveName),void 0!==e&&(void 0!==e.prv&&this.setPrivateKeyHex(e.prv),void 0!==e.pub&&this.setPublicKeyHex(e.pub))},K.crypto.ECDSA.parseSigHex=function(e){var t=K.crypto.ECDSA.parseSigHexInHexRS(e);return{r:new BigInteger(t.r,16),s:new BigInteger(t.s,16)}},K.crypto.ECDSA.parseSigHexInHexRS=function(e){var t=J,r=t.getChildIdx,n=t.getV;if("30"!=e.substr(0,2))throw"signature is not a ASN.1 sequence";var i=r(e,0);if(2!=i.length)throw"number of signature ASN.1 sequence elements seem wrong";var o=i[0],s=i[1];if("02"!=e.substr(o,2))throw"1st item of sequene of signature is not ASN.1 integer";if("02"!=e.substr(s,2))throw"2nd item of sequene of signature is not ASN.1 integer";return{r:n(e,o),s:n(e,s)}},K.crypto.ECDSA.asn1SigToConcatSig=function(e){var t=K.crypto.ECDSA.parseSigHexInHexRS(e),r=t.r,n=t.s;if("00"==r.substr(0,2)&&r.length%32==2&&(r=r.substr(2)),"00"==n.substr(0,2)&&n.length%32==2&&(n=n.substr(2)),r.length%32==30&&(r="00"+r),n.length%32==30&&(n="00"+n),r.length%32!=0)throw"unknown ECDSA sig r length error";if(n.length%32!=0)throw"unknown ECDSA sig s length error";return r+n},K.crypto.ECDSA.concatSigToASN1Sig=function(e){if(e.length/2*8%128!=0)throw"unknown ECDSA concatinated r-s sig length error";var t=e.substr(0,e.length/2),r=e.substr(e.length/2);return K.crypto.ECDSA.hexRSSigToASN1Sig(t,r)},K.crypto.ECDSA.hexRSSigToASN1Sig=function(e,t){var r=new BigInteger(e,16),n=new BigInteger(t,16);return K.crypto.ECDSA.biRSSigToASN1Sig(r,n)},K.crypto.ECDSA.biRSSigToASN1Sig=function(e,t){var r=K.asn1,n=new r.DERInteger({bigint:e}),i=new r.DERInteger({bigint:t});return new r.DERSequence({array:[n,i]}).getEncodedHex()},K.crypto.ECDSA.getName=function(e){return"2a8648ce3d030107"===e?"secp256r1":"2b8104000a"===e?"secp256k1":"2b81040022"===e?"secp384r1":-1!=="|secp256r1|NIST P-256|P-256|prime256v1|".indexOf(e)?"secp256r1":-1!=="|secp256k1|".indexOf(e)?"secp256k1":-1!=="|secp384r1|NIST P-384|P-384|".indexOf(e)?"secp384r1":null},void 0!==K&&K||(K={}),void 0!==K.crypto&&K.crypto||(K.crypto={}),K.crypto.ECParameterDB=new function(){var e={},t={};function a(e){return new BigInteger(e,16)}this.getByName=function(r){var n=r;if(void 0!==t[n]&&(n=t[r]),void 0!==e[n])return e[n];throw"unregistered EC curve name: "+n},this.regist=function(r,n,i,o,s,u,c,h,f,l,g,p){e[r]={};var d=a(i),v=a(o),y=a(s),m=a(u),S=a(c),F=new ECCurveFp(d,v,y),b=F.decodePointHex("04"+h+f);e[r].name=r,e[r].keylen=n,e[r].curve=F,e[r].G=b,e[r].n=m,e[r].h=S,e[r].oid=g,e[r].info=p;for(var _=0;_=2*a)break}var f={};return f.keyhex=u.substr(0,2*s[e].keylen),f.ivhex=u.substr(2*s[e].keylen,2*s[e].ivlen),f},g=function b(e,t,r,n){var i=y.enc.Base64.parse(e),o=y.enc.Hex.stringify(i);return(0,s[t].proc)(o,r,n)};return{version:"1.0.0",parsePKCS5PEM:function parsePKCS5PEM(e){return u(e)},getKeyAndUnusedIvByPasscodeAndIvsalt:function getKeyAndUnusedIvByPasscodeAndIvsalt(e,t,r){return c(e,t,r)},decryptKeyB64:function decryptKeyB64(e,t,r,n){return g(e,t,r,n)},getDecryptedKeyHex:function getDecryptedKeyHex(e,t){var r=u(e),n=(r.type,r.cipher),i=r.ivsalt,o=r.data,s=c(n,t,i).keyhex;return g(o,n,s,i)},getEncryptedPKCS5PEMFromPrvKeyHex:function getEncryptedPKCS5PEMFromPrvKeyHex(e,t,r,n,i){var o="";if(void 0!==n&&null!=n||(n="AES-256-CBC"),void 0===s[n])throw"KEYUTIL unsupported algorithm: "+n;void 0!==i&&null!=i||(i=function m(e){var t=y.lib.WordArray.random(e);return y.enc.Hex.stringify(t)}(s[n].ivlen).toUpperCase());var a=function h(e,t,r,n){return(0,s[t].eproc)(e,r,n)}(t,n,c(n,r,i).keyhex,i);o="-----BEGIN "+e+" PRIVATE KEY-----\r\n";return o+="Proc-Type: 4,ENCRYPTED\r\n",o+="DEK-Info: "+n+","+i+"\r\n",o+="\r\n",o+=a.replace(/(.{64})/g,"$1\r\n"),o+="\r\n-----END "+e+" PRIVATE KEY-----\r\n"},parseHexOfEncryptedPKCS8:function parseHexOfEncryptedPKCS8(e){var t=J,r=t.getChildIdx,n=t.getV,i={},o=r(e,0);if(2!=o.length)throw"malformed format: SEQUENCE(0).items != 2: "+o.length;i.ciphertext=n(e,o[1]);var s=r(e,o[0]);if(2!=s.length)throw"malformed format: SEQUENCE(0.0).items != 2: "+s.length;if("2a864886f70d01050d"!=n(e,s[0]))throw"this only supports pkcs5PBES2";var a=r(e,s[1]);if(2!=s.length)throw"malformed format: SEQUENCE(0.0.1).items != 2: "+a.length;var u=r(e,a[1]);if(2!=u.length)throw"malformed format: SEQUENCE(0.0.1.1).items != 2: "+u.length;if("2a864886f70d0307"!=n(e,u[0]))throw"this only supports TripleDES";i.encryptionSchemeAlg="TripleDES",i.encryptionSchemeIV=n(e,u[1]);var c=r(e,a[0]);if(2!=c.length)throw"malformed format: SEQUENCE(0.0.1.0).items != 2: "+c.length;if("2a864886f70d01050c"!=n(e,c[0]))throw"this only supports pkcs5PBKDF2";var h=r(e,c[1]);if(h.length<2)throw"malformed format: SEQUENCE(0.0.1.0.1).items < 2: "+h.length;i.pbkdf2Salt=n(e,h[0]);var f=n(e,h[1]);try{i.pbkdf2Iter=parseInt(f,16)}catch(e){throw"malformed format pbkdf2Iter: "+f}return i},getPBKDF2KeyHexFromParam:function getPBKDF2KeyHexFromParam(e,t){var r=y.enc.Hex.parse(e.pbkdf2Salt),n=e.pbkdf2Iter,i=y.PBKDF2(t,r,{keySize:6,iterations:n});return y.enc.Hex.stringify(i)},_getPlainPKCS8HexFromEncryptedPKCS8PEM:function _getPlainPKCS8HexFromEncryptedPKCS8PEM(e,t){var r=pemtohex(e,"ENCRYPTED PRIVATE KEY"),n=this.parseHexOfEncryptedPKCS8(r),i=z.getPBKDF2KeyHexFromParam(n,t),o={};o.ciphertext=y.enc.Hex.parse(n.ciphertext);var s=y.enc.Hex.parse(i),a=y.enc.Hex.parse(n.encryptionSchemeIV),u=y.TripleDES.decrypt(o,s,{iv:a});return y.enc.Hex.stringify(u)},getKeyFromEncryptedPKCS8PEM:function getKeyFromEncryptedPKCS8PEM(e,t){var r=this._getPlainPKCS8HexFromEncryptedPKCS8PEM(e,t);return this.getKeyFromPlainPrivatePKCS8Hex(r)},parsePlainPrivatePKCS8Hex:function parsePlainPrivatePKCS8Hex(e){var t=J,r=t.getChildIdx,n=t.getV,i={algparam:null};if("30"!=e.substr(0,2))throw"malformed plain PKCS8 private key(code:001)";var o=r(e,0);if(3!=o.length)throw"malformed plain PKCS8 private key(code:002)";if("30"!=e.substr(o[1],2))throw"malformed PKCS8 private key(code:003)";var s=r(e,o[1]);if(2!=s.length)throw"malformed PKCS8 private key(code:004)";if("06"!=e.substr(s[0],2))throw"malformed PKCS8 private key(code:005)";if(i.algoid=n(e,s[0]),"06"==e.substr(s[1],2)&&(i.algparam=n(e,s[1])),"04"!=e.substr(o[2],2))throw"malformed PKCS8 private key(code:006)";return i.keyidx=t.getVidx(e,o[2]),i},getKeyFromPlainPrivatePKCS8PEM:function getKeyFromPlainPrivatePKCS8PEM(e){var t=pemtohex(e,"PRIVATE KEY");return this.getKeyFromPlainPrivatePKCS8Hex(t)},getKeyFromPlainPrivatePKCS8Hex:function getKeyFromPlainPrivatePKCS8Hex(e){var t,r=this.parsePlainPrivatePKCS8Hex(e);if("2a864886f70d010101"==r.algoid)t=new RSAKey;else if("2a8648ce380401"==r.algoid)t=new K.crypto.DSA;else{if("2a8648ce3d0201"!=r.algoid)throw"unsupported private key algorithm";t=new K.crypto.ECDSA}return t.readPKCS8PrvKeyHex(e),t},_getKeyFromPublicPKCS8Hex:function _getKeyFromPublicPKCS8Hex(e){var t,r=J.getVbyList(e,0,[0,0],"06");if("2a864886f70d010101"===r)t=new RSAKey;else if("2a8648ce380401"===r)t=new K.crypto.DSA;else{if("2a8648ce3d0201"!==r)throw"unsupported PKCS#8 public key hex";t=new K.crypto.ECDSA}return t.readPKCS8PubKeyHex(e),t},parsePublicRawRSAKeyHex:function parsePublicRawRSAKeyHex(e){var t=J,r=t.getChildIdx,n=t.getV,i={};if("30"!=e.substr(0,2))throw"malformed RSA key(code:001)";var o=r(e,0);if(2!=o.length)throw"malformed RSA key(code:002)";if("02"!=e.substr(o[0],2))throw"malformed RSA key(code:003)";if(i.n=n(e,o[0]),"02"!=e.substr(o[1],2))throw"malformed RSA key(code:004)";return i.e=n(e,o[1]),i},parsePublicPKCS8Hex:function parsePublicPKCS8Hex(e){var t=J,r=t.getChildIdx,n=t.getV,i={algparam:null},o=r(e,0);if(2!=o.length)throw"outer DERSequence shall have 2 elements: "+o.length;var s=o[0];if("30"!=e.substr(s,2))throw"malformed PKCS8 public key(code:001)";var a=r(e,s);if(2!=a.length)throw"malformed PKCS8 public key(code:002)";if("06"!=e.substr(a[0],2))throw"malformed PKCS8 public key(code:003)";if(i.algoid=n(e,a[0]),"06"==e.substr(a[1],2)?i.algparam=n(e,a[1]):"30"==e.substr(a[1],2)&&(i.algparam={},i.algparam.p=t.getVbyList(e,a[1],[0],"02"),i.algparam.q=t.getVbyList(e,a[1],[1],"02"),i.algparam.g=t.getVbyList(e,a[1],[2],"02")),"03"!=e.substr(o[1],2))throw"malformed PKCS8 public key(code:004)";return i.key=n(e,o[1]).substr(2),i}}}();z.getKey=function(e,t,r){var n=(v=J).getChildIdx,i=(v.getV,v.getVbyList),o=K.crypto,s=o.ECDSA,a=o.DSA,u=RSAKey,c=pemtohex,h=z;if(void 0!==u&&e instanceof u)return e;if(void 0!==s&&e instanceof s)return e;if(void 0!==a&&e instanceof a)return e;if(void 0!==e.curve&&void 0!==e.xy&&void 0===e.d)return new s({pub:e.xy,curve:e.curve});if(void 0!==e.curve&&void 0!==e.d)return new s({prv:e.d,curve:e.curve});if(void 0===e.kty&&void 0!==e.n&&void 0!==e.e&&void 0===e.d)return(P=new u).setPublic(e.n,e.e),P;if(void 0===e.kty&&void 0!==e.n&&void 0!==e.e&&void 0!==e.d&&void 0!==e.p&&void 0!==e.q&&void 0!==e.dp&&void 0!==e.dq&&void 0!==e.co&&void 0===e.qi)return(P=new u).setPrivateEx(e.n,e.e,e.d,e.p,e.q,e.dp,e.dq,e.co),P;if(void 0===e.kty&&void 0!==e.n&&void 0!==e.e&&void 0!==e.d&&void 0===e.p)return(P=new u).setPrivate(e.n,e.e,e.d),P;if(void 0!==e.p&&void 0!==e.q&&void 0!==e.g&&void 0!==e.y&&void 0===e.x)return(P=new a).setPublic(e.p,e.q,e.g,e.y),P;if(void 0!==e.p&&void 0!==e.q&&void 0!==e.g&&void 0!==e.y&&void 0!==e.x)return(P=new a).setPrivate(e.p,e.q,e.g,e.y,e.x),P;if("RSA"===e.kty&&void 0!==e.n&&void 0!==e.e&&void 0===e.d)return(P=new u).setPublic(b64utohex(e.n),b64utohex(e.e)),P;if("RSA"===e.kty&&void 0!==e.n&&void 0!==e.e&&void 0!==e.d&&void 0!==e.p&&void 0!==e.q&&void 0!==e.dp&&void 0!==e.dq&&void 0!==e.qi)return(P=new u).setPrivateEx(b64utohex(e.n),b64utohex(e.e),b64utohex(e.d),b64utohex(e.p),b64utohex(e.q),b64utohex(e.dp),b64utohex(e.dq),b64utohex(e.qi)),P;if("RSA"===e.kty&&void 0!==e.n&&void 0!==e.e&&void 0!==e.d)return(P=new u).setPrivate(b64utohex(e.n),b64utohex(e.e),b64utohex(e.d)),P;if("EC"===e.kty&&void 0!==e.crv&&void 0!==e.x&&void 0!==e.y&&void 0===e.d){var f=(C=new s({curve:e.crv})).ecparams.keylen/4,l="04"+("0000000000"+b64utohex(e.x)).slice(-f)+("0000000000"+b64utohex(e.y)).slice(-f);return C.setPublicKeyHex(l),C}if("EC"===e.kty&&void 0!==e.crv&&void 0!==e.x&&void 0!==e.y&&void 0!==e.d){f=(C=new s({curve:e.crv})).ecparams.keylen/4,l="04"+("0000000000"+b64utohex(e.x)).slice(-f)+("0000000000"+b64utohex(e.y)).slice(-f);var g=("0000000000"+b64utohex(e.d)).slice(-f);return C.setPublicKeyHex(l),C.setPrivateKeyHex(g),C}if("pkcs5prv"===r){var p,d=e,v=J;if(9===(p=n(d,0)).length)(P=new u).readPKCS5PrvKeyHex(d);else if(6===p.length)(P=new a).readPKCS5PrvKeyHex(d);else{if(!(p.length>2&&"04"===d.substr(p[1],2)))throw"unsupported PKCS#1/5 hexadecimal key";(P=new s).readPKCS5PrvKeyHex(d)}return P}if("pkcs8prv"===r)return P=h.getKeyFromPlainPrivatePKCS8Hex(e);if("pkcs8pub"===r)return h._getKeyFromPublicPKCS8Hex(e);if("x509pub"===r)return X509.getPublicKeyFromCertHex(e);if(-1!=e.indexOf("-END CERTIFICATE-",0)||-1!=e.indexOf("-END X509 CERTIFICATE-",0)||-1!=e.indexOf("-END TRUSTED CERTIFICATE-",0))return X509.getPublicKeyFromCertPEM(e);if(-1!=e.indexOf("-END PUBLIC KEY-")){var y=pemtohex(e,"PUBLIC KEY");return h._getKeyFromPublicPKCS8Hex(y)}if(-1!=e.indexOf("-END RSA PRIVATE KEY-")&&-1==e.indexOf("4,ENCRYPTED")){var m=c(e,"RSA PRIVATE KEY");return h.getKey(m,null,"pkcs5prv")}if(-1!=e.indexOf("-END DSA PRIVATE KEY-")&&-1==e.indexOf("4,ENCRYPTED")){var S=i(I=c(e,"DSA PRIVATE KEY"),0,[1],"02"),F=i(I,0,[2],"02"),b=i(I,0,[3],"02"),_=i(I,0,[4],"02"),w=i(I,0,[5],"02");return(P=new a).setPrivate(new BigInteger(S,16),new BigInteger(F,16),new BigInteger(b,16),new BigInteger(_,16),new BigInteger(w,16)),P}if(-1!=e.indexOf("-END PRIVATE KEY-"))return h.getKeyFromPlainPrivatePKCS8PEM(e);if(-1!=e.indexOf("-END RSA PRIVATE KEY-")&&-1!=e.indexOf("4,ENCRYPTED")){var E=h.getDecryptedKeyHex(e,t),x=new RSAKey;return x.readPKCS5PrvKeyHex(E),x}if(-1!=e.indexOf("-END EC PRIVATE KEY-")&&-1!=e.indexOf("4,ENCRYPTED")){var C,P=i(I=h.getDecryptedKeyHex(e,t),0,[1],"04"),A=i(I,0,[2,0],"06"),k=i(I,0,[3,0],"03").substr(2);if(void 0===K.crypto.OID.oidhex2name[A])throw"undefined OID(hex) in KJUR.crypto.OID: "+A;return(C=new s({curve:K.crypto.OID.oidhex2name[A]})).setPublicKeyHex(k),C.setPrivateKeyHex(P),C.isPublic=!1,C}if(-1!=e.indexOf("-END DSA PRIVATE KEY-")&&-1!=e.indexOf("4,ENCRYPTED")){var I;S=i(I=h.getDecryptedKeyHex(e,t),0,[1],"02"),F=i(I,0,[2],"02"),b=i(I,0,[3],"02"),_=i(I,0,[4],"02"),w=i(I,0,[5],"02");return(P=new a).setPrivate(new BigInteger(S,16),new BigInteger(F,16),new BigInteger(b,16),new BigInteger(_,16),new BigInteger(w,16)),P}if(-1!=e.indexOf("-END ENCRYPTED PRIVATE KEY-"))return h.getKeyFromEncryptedPKCS8PEM(e,t);throw"not supported argument"},z.generateKeypair=function(e,t){if("RSA"==e){var r=t;(s=new RSAKey).generate(r,"10001"),s.isPrivate=!0,s.isPublic=!0;var n=new RSAKey,i=s.n.toString(16),o=s.e.toString(16);return n.setPublic(i,o),n.isPrivate=!1,n.isPublic=!0,(a={}).prvKeyObj=s,a.pubKeyObj=n,a}if("EC"==e){var s,a,u=t,c=new K.crypto.ECDSA({curve:u}).generateKeyPairHex();return(s=new K.crypto.ECDSA({curve:u})).setPublicKeyHex(c.ecpubhex),s.setPrivateKeyHex(c.ecprvhex),s.isPrivate=!0,s.isPublic=!1,(n=new K.crypto.ECDSA({curve:u})).setPublicKeyHex(c.ecpubhex),n.isPrivate=!1,n.isPublic=!0,(a={}).prvKeyObj=s,a.pubKeyObj=n,a}throw"unknown algorithm: "+e},z.getPEM=function(e,t,r,n,i,s){var a=K,u=a.asn1,c=u.DERObjectIdentifier,h=u.DERInteger,f=u.ASN1Util.newObject,l=u.x509.SubjectPublicKeyInfo,g=a.crypto,p=g.DSA,d=g.ECDSA,v=RSAKey;function A(e){return f({seq:[{int:0},{int:{bigint:e.n}},{int:e.e},{int:{bigint:e.d}},{int:{bigint:e.p}},{int:{bigint:e.q}},{int:{bigint:e.dmp1}},{int:{bigint:e.dmq1}},{int:{bigint:e.coeff}}]})}function B(e){return f({seq:[{int:1},{octstr:{hex:e.prvKeyHex}},{tag:["a0",!0,{oid:{name:e.curveName}}]},{tag:["a1",!0,{bitstr:{hex:"00"+e.pubKeyHex}}]}]})}function x(e){return f({seq:[{int:0},{int:{bigint:e.p}},{int:{bigint:e.q}},{int:{bigint:e.g}},{int:{bigint:e.y}},{int:{bigint:e.x}}]})}if((void 0!==v&&e instanceof v||void 0!==p&&e instanceof p||void 0!==d&&e instanceof d)&&1==e.isPublic&&(void 0===t||"PKCS8PUB"==t))return hextopem(b=new l(e).getEncodedHex(),"PUBLIC KEY");if("PKCS1PRV"==t&&void 0!==v&&e instanceof v&&(void 0===r||null==r)&&1==e.isPrivate)return hextopem(b=A(e).getEncodedHex(),"RSA PRIVATE KEY");if("PKCS1PRV"==t&&void 0!==d&&e instanceof d&&(void 0===r||null==r)&&1==e.isPrivate){var m=new c({name:e.curveName}).getEncodedHex(),S=B(e).getEncodedHex(),F="";return F+=hextopem(m,"EC PARAMETERS"),F+=hextopem(S,"EC PRIVATE KEY")}if("PKCS1PRV"==t&&void 0!==p&&e instanceof p&&(void 0===r||null==r)&&1==e.isPrivate)return hextopem(b=x(e).getEncodedHex(),"DSA PRIVATE KEY");if("PKCS5PRV"==t&&void 0!==v&&e instanceof v&&void 0!==r&&null!=r&&1==e.isPrivate){var b=A(e).getEncodedHex();return void 0===n&&(n="DES-EDE3-CBC"),this.getEncryptedPKCS5PEMFromPrvKeyHex("RSA",b,r,n,s)}if("PKCS5PRV"==t&&void 0!==d&&e instanceof d&&void 0!==r&&null!=r&&1==e.isPrivate){b=B(e).getEncodedHex();return void 0===n&&(n="DES-EDE3-CBC"),this.getEncryptedPKCS5PEMFromPrvKeyHex("EC",b,r,n,s)}if("PKCS5PRV"==t&&void 0!==p&&e instanceof p&&void 0!==r&&null!=r&&1==e.isPrivate){b=x(e).getEncodedHex();return void 0===n&&(n="DES-EDE3-CBC"),this.getEncryptedPKCS5PEMFromPrvKeyHex("DSA",b,r,n,s)}var _=function o(e,t){var r=w(e,t);return new f({seq:[{seq:[{oid:{name:"pkcs5PBES2"}},{seq:[{seq:[{oid:{name:"pkcs5PBKDF2"}},{seq:[{octstr:{hex:r.pbkdf2Salt}},{int:r.pbkdf2Iter}]}]},{seq:[{oid:{name:"des-EDE3-CBC"}},{octstr:{hex:r.encryptionSchemeIV}}]}]}]},{octstr:{hex:r.ciphertext}}]}).getEncodedHex()},w=function c(e,t){var r=y.lib.WordArray.random(8),n=y.lib.WordArray.random(8),i=y.PBKDF2(t,r,{keySize:6,iterations:100}),o=y.enc.Hex.parse(e),s=y.TripleDES.encrypt(o,i,{iv:n})+"",a={};return a.ciphertext=s,a.pbkdf2Salt=y.enc.Hex.stringify(r),a.pbkdf2Iter=100,a.encryptionSchemeAlg="DES-EDE3-CBC",a.encryptionSchemeIV=y.enc.Hex.stringify(n),a};if("PKCS8PRV"==t&&void 0!=v&&e instanceof v&&1==e.isPrivate){var E=A(e).getEncodedHex();b=f({seq:[{int:0},{seq:[{oid:{name:"rsaEncryption"}},{null:!0}]},{octstr:{hex:E}}]}).getEncodedHex();return void 0===r||null==r?hextopem(b,"PRIVATE KEY"):hextopem(S=_(b,r),"ENCRYPTED PRIVATE KEY")}if("PKCS8PRV"==t&&void 0!==d&&e instanceof d&&1==e.isPrivate){E=new f({seq:[{int:1},{octstr:{hex:e.prvKeyHex}},{tag:["a1",!0,{bitstr:{hex:"00"+e.pubKeyHex}}]}]}).getEncodedHex(),b=f({seq:[{int:0},{seq:[{oid:{name:"ecPublicKey"}},{oid:{name:e.curveName}}]},{octstr:{hex:E}}]}).getEncodedHex();return void 0===r||null==r?hextopem(b,"PRIVATE KEY"):hextopem(S=_(b,r),"ENCRYPTED PRIVATE KEY")}if("PKCS8PRV"==t&&void 0!==p&&e instanceof p&&1==e.isPrivate){E=new h({bigint:e.x}).getEncodedHex(),b=f({seq:[{int:0},{seq:[{oid:{name:"dsa"}},{seq:[{int:{bigint:e.p}},{int:{bigint:e.q}},{int:{bigint:e.g}}]}]},{octstr:{hex:E}}]}).getEncodedHex();return void 0===r||null==r?hextopem(b,"PRIVATE KEY"):hextopem(S=_(b,r),"ENCRYPTED PRIVATE KEY")}throw"unsupported object nor format"},z.getKeyFromCSRPEM=function(e){var t=pemtohex(e,"CERTIFICATE REQUEST");return z.getKeyFromCSRHex(t)},z.getKeyFromCSRHex=function(e){var t=z.parseCSRHex(e);return z.getKey(t.p8pubkeyhex,null,"pkcs8pub")},z.parseCSRHex=function(e){var t=J,r=t.getChildIdx,n=t.getTLV,i={},o=e;if("30"!=o.substr(0,2))throw"malformed CSR(code:001)";var s=r(o,0);if(s.length<1)throw"malformed CSR(code:002)";if("30"!=o.substr(s[0],2))throw"malformed CSR(code:003)";var a=r(o,s[0]);if(a.length<3)throw"malformed CSR(code:004)";return i.p8pubkeyhex=n(o,a[2]),i},z.getJWKFromKey=function(e){var t={};if(e instanceof RSAKey&&e.isPrivate)return t.kty="RSA",t.n=hextob64u(e.n.toString(16)),t.e=hextob64u(e.e.toString(16)),t.d=hextob64u(e.d.toString(16)),t.p=hextob64u(e.p.toString(16)),t.q=hextob64u(e.q.toString(16)),t.dp=hextob64u(e.dmp1.toString(16)),t.dq=hextob64u(e.dmq1.toString(16)),t.qi=hextob64u(e.coeff.toString(16)),t;if(e instanceof RSAKey&&e.isPublic)return t.kty="RSA",t.n=hextob64u(e.n.toString(16)),t.e=hextob64u(e.e.toString(16)),t;if(e instanceof K.crypto.ECDSA&&e.isPrivate){if("P-256"!==(n=e.getShortNISTPCurveName())&&"P-384"!==n)throw"unsupported curve name for JWT: "+n;var r=e.getPublicKeyXYHex();return t.kty="EC",t.crv=n,t.x=hextob64u(r.x),t.y=hextob64u(r.y),t.d=hextob64u(e.prvKeyHex),t}if(e instanceof K.crypto.ECDSA&&e.isPublic){var n;if("P-256"!==(n=e.getShortNISTPCurveName())&&"P-384"!==n)throw"unsupported curve name for JWT: "+n;r=e.getPublicKeyXYHex();return t.kty="EC",t.crv=n,t.x=hextob64u(r.x),t.y=hextob64u(r.y),t}throw"not supported key object"},RSAKey.getPosArrayOfChildrenFromHex=function(e){return J.getChildIdx(e,0)},RSAKey.getHexValueArrayOfChildrenFromHex=function(e){var t,r=J.getV,n=r(e,(t=RSAKey.getPosArrayOfChildrenFromHex(e))[0]),i=r(e,t[1]),o=r(e,t[2]),s=r(e,t[3]),a=r(e,t[4]),u=r(e,t[5]),c=r(e,t[6]),h=r(e,t[7]),f=r(e,t[8]);return(t=new Array).push(n,i,o,s,a,u,c,h,f),t},RSAKey.prototype.readPrivateKeyFromPEMString=function(e){var t=pemtohex(e),r=RSAKey.getHexValueArrayOfChildrenFromHex(t);this.setPrivateEx(r[1],r[2],r[3],r[4],r[5],r[6],r[7],r[8])},RSAKey.prototype.readPKCS5PrvKeyHex=function(e){var t=RSAKey.getHexValueArrayOfChildrenFromHex(e);this.setPrivateEx(t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8])},RSAKey.prototype.readPKCS8PrvKeyHex=function(e){var t,r,n,i,o,s,a,u,c=J,h=c.getVbyList;if(!1===c.isASN1HEX(e))throw"not ASN.1 hex string";try{t=h(e,0,[2,0,1],"02"),r=h(e,0,[2,0,2],"02"),n=h(e,0,[2,0,3],"02"),i=h(e,0,[2,0,4],"02"),o=h(e,0,[2,0,5],"02"),s=h(e,0,[2,0,6],"02"),a=h(e,0,[2,0,7],"02"),u=h(e,0,[2,0,8],"02")}catch(e){throw"malformed PKCS#8 plain RSA private key"}this.setPrivateEx(t,r,n,i,o,s,a,u)},RSAKey.prototype.readPKCS5PubKeyHex=function(e){var t=J,r=t.getV;if(!1===t.isASN1HEX(e))throw"keyHex is not ASN.1 hex string";var n=t.getChildIdx(e,0);if(2!==n.length||"02"!==e.substr(n[0],2)||"02"!==e.substr(n[1],2))throw"wrong hex for PKCS#5 public key";var i=r(e,n[0]),o=r(e,n[1]);this.setPublic(i,o)},RSAKey.prototype.readPKCS8PubKeyHex=function(e){var t=J;if(!1===t.isASN1HEX(e))throw"not ASN.1 hex string";if("06092a864886f70d010101"!==t.getTLVbyList(e,0,[0,0]))throw"not PKCS8 RSA public key";var r=t.getTLVbyList(e,0,[1,0]);this.readPKCS5PubKeyHex(r)},RSAKey.prototype.readCertPubKeyHex=function(e,t){var r,n;(r=new X509).readCertHex(e),n=r.getPublicKeyHex(),this.readPKCS8PubKeyHex(n)};var Y=new RegExp("");function _zeroPaddingOfSignature(e,t){for(var r="",n=t/4-e.length,i=0;i>24,(16711680&i)>>16,(65280&i)>>8,255&i])))),i+=1;return n}function _rsasign_getAlgNameAndHashFromHexDisgestInfo(e){for(var t in K.crypto.Util.DIGESTINFOHEAD){var r=K.crypto.Util.DIGESTINFOHEAD[t],n=r.length;if(e.substring(0,n)==r)return[t,e.substring(n)]}return[]}function X509(){var e=J,t=e.getChildIdx,r=e.getV,n=e.getTLV,i=e.getVbyList,o=e.getTLVbyList,s=e.getIdxbyList,a=e.getVidx,u=e.oidname,c=X509,h=pemtohex;this.hex=null,this.version=0,this.foffset=0,this.aExtInfo=null,this.getVersion=function(){return null===this.hex||0!==this.version?this.version:"a003020102"!==o(this.hex,0,[0,0])?(this.version=1,this.foffset=-1,1):(this.version=3,3)},this.getSerialNumberHex=function(){return i(this.hex,0,[0,1+this.foffset],"02")},this.getSignatureAlgorithmField=function(){return u(i(this.hex,0,[0,2+this.foffset,0],"06"))},this.getIssuerHex=function(){return o(this.hex,0,[0,3+this.foffset],"30")},this.getIssuerString=function(){return c.hex2dn(this.getIssuerHex())},this.getSubjectHex=function(){return o(this.hex,0,[0,5+this.foffset],"30")},this.getSubjectString=function(){return c.hex2dn(this.getSubjectHex())},this.getNotBefore=function(){var e=i(this.hex,0,[0,4+this.foffset,0]);return e=e.replace(/(..)/g,"%$1"),e=decodeURIComponent(e)},this.getNotAfter=function(){var e=i(this.hex,0,[0,4+this.foffset,1]);return e=e.replace(/(..)/g,"%$1"),e=decodeURIComponent(e)},this.getPublicKeyHex=function(){return e.getTLVbyList(this.hex,0,[0,6+this.foffset],"30")},this.getPublicKeyIdx=function(){return s(this.hex,0,[0,6+this.foffset],"30")},this.getPublicKeyContentIdx=function(){var e=this.getPublicKeyIdx();return s(this.hex,e,[1,0],"30")},this.getPublicKey=function(){return z.getKey(this.getPublicKeyHex(),null,"pkcs8pub")},this.getSignatureAlgorithmName=function(){return u(i(this.hex,0,[1,0],"06"))},this.getSignatureValueHex=function(){return i(this.hex,0,[2],"03",!0)},this.verifySignature=function(e){var t=this.getSignatureAlgorithmName(),r=this.getSignatureValueHex(),n=o(this.hex,0,[0],"30"),i=new K.crypto.Signature({alg:t});return i.init(e),i.updateHex(n),i.verify(r)},this.parseExt=function(){if(3!==this.version)return-1;var r=s(this.hex,0,[0,7,0],"30"),n=t(this.hex,r);this.aExtInfo=new Array;for(var o=0;o0&&(h=new Array(r),(new SecureRandom).nextBytes(h),h=String.fromCharCode.apply(String,h));var f=hextorstr(c(rstrtohex("\0\0\0\0\0\0\0\0"+i+h))),l=[];for(n=0;n>8*u-a&255;for(d[0]&=~v,n=0;nthis.n.bitLength())return 0;var n=_rsasign_getAlgNameAndHashFromHexDisgestInfo(this.doPublic(r).toString(16).replace(/^1f+00/,""));if(0==n.length)return!1;var i=n[0];return n[1]==function a(e){return K.crypto.Util.hashString(e,i)}(e)},RSAKey.prototype.verifyWithMessageHash=function(e,t){var r=parseBigInt(t=(t=t.replace(Y,"")).replace(/[ \n]+/g,""),16);if(r.bitLength()>this.n.bitLength())return 0;var n=_rsasign_getAlgNameAndHashFromHexDisgestInfo(this.doPublic(r).toString(16).replace(/^1f+00/,""));if(0==n.length)return!1;n[0];return n[1]==e},RSAKey.prototype.verifyPSS=function(t,r,n,i){var o=function e(t){return K.crypto.Util.hashHex(t,n)}(rstrtohex(t));return void 0===i&&(i=-1),this.verifyWithMessageHashPSS(o,r,n,i)},RSAKey.prototype.verifyWithMessageHashPSS=function(e,t,n,i){var o=new BigInteger(t,16);if(o.bitLength()>this.n.bitLength())return!1;var s,a=function r(e){return K.crypto.Util.hashHex(e,n)},u=hextorstr(e),c=u.length,h=this.n.bitLength()-1,f=Math.ceil(h/8);if(-1===i||void 0===i)i=c;else if(-2===i)i=f-c-2;else if(i<-2)throw"invalid salt length";if(f>8*f-h&255;if(0!=(g.charCodeAt(0)&d))throw"bits beyond keysize not zero";var v=pss_mgf1_str(p,g.length,a),y=[];for(s=0;s0)&&-1==(":"+r.join(":")+":").indexOf(":"+v+":"))throw"algorithm '"+v+"' not accepted in the list";if("none"!=v&&null===t)throw"key shall be specified to verify.";if("string"==typeof t&&-1!=t.indexOf("-----BEGIN ")&&(t=z.getKey(t)),!("RS"!=y&&"PS"!=y||t instanceof n))throw"key shall be a RSAKey obj for RS* and PS* algs";if("ES"==y&&!(t instanceof c))throw"key shall be a ECDSA obj for ES* algs";var m=null;if(void 0===s.jwsalg2sigalg[d.alg])throw"unsupported alg name: "+v;if("none"==(m=s.jwsalg2sigalg[v]))throw"not supported";if("Hmac"==m.substr(0,4)){if(void 0===t)throw"hexadecimal key shall be specified for HMAC";var S=new h({alg:m,pass:t});return S.updateString(g),p==S.doFinal()}if(-1!=m.indexOf("withECDSA")){var F,b=null;try{b=c.concatSigToASN1Sig(p)}catch(e){return!1}return(F=new f({alg:m})).init(t),F.updateString(g),F.verify(b)}return(F=new f({alg:m})).init(t),F.updateString(g),F.verify(p)},K.jws.JWS.parse=function(e){var t,r,n,i=e.split("."),o={};if(2!=i.length&&3!=i.length)throw"malformed sJWS: wrong number of '.' splitted elements";return t=i[0],r=i[1],3==i.length&&(n=i[2]),o.headerObj=K.jws.JWS.readSafeJSONString(W(t)),o.payloadObj=K.jws.JWS.readSafeJSONString(W(r)),o.headerPP=JSON.stringify(o.headerObj,null," "),null==o.payloadObj?o.payloadPP=W(r):o.payloadPP=JSON.stringify(o.payloadObj,null," "),void 0!==n&&(o.sigHex=b64utohex(n)),o},K.jws.JWS.verifyJWT=function(e,t,r){var n=K.jws,o=n.JWS,s=o.readSafeJSONString,a=o.inArray,u=o.includedArray,c=e.split("."),h=c[0],f=c[1],l=(b64utohex(c[2]),s(W(h))),g=s(W(f));if(void 0===l.alg)return!1;if(void 0===r.alg)throw"acceptField.alg shall be specified";if(!a(l.alg,r.alg))return!1;if(void 0!==g.iss&&"object"===i(r.iss)&&!a(g.iss,r.iss))return!1;if(void 0!==g.sub&&"object"===i(r.sub)&&!a(g.sub,r.sub))return!1;if(void 0!==g.aud&&"object"===i(r.aud))if("string"==typeof g.aud){if(!a(g.aud,r.aud))return!1}else if("object"==i(g.aud)&&!u(g.aud,r.aud))return!1;var p=n.IntDate.getNow();return void 0!==r.verifyAt&&"number"==typeof r.verifyAt&&(p=r.verifyAt),void 0!==r.gracePeriod&&"number"==typeof r.gracePeriod||(r.gracePeriod=0),!(void 0!==g.exp&&"number"==typeof g.exp&&g.exp+r.gracePeriodt.length&&(r=t.length);for(var n=0;n=0))return i.Log.error("JoseUtil._validateJwt: Invalid audience in token",c.aud),Promise.reject(new Error("Invalid audience in token: "+c.aud));var h=u+a,f=u-a;if(!c.iat)return i.Log.error("JoseUtil._validateJwt: iat was not provided"),Promise.reject(new Error("iat was not provided"));if(h1&&void 0!==arguments[1]?arguments[1]:n.JsonService,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:i.MetadataService;if(function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,UserInfoService),!e)throw o.Log.error("UserInfoService.ctor: No settings passed"),new Error("settings");this._settings=e,this._jsonService=new t,this._metadataService=new r(this._settings)}return UserInfoService.prototype.getClaims=function getClaims(e){var t=this;return e?this._metadataService.getUserInfoEndpoint().then(function(r){return o.Log.debug("UserInfoService.getClaims: received userinfo url",r),t._jsonService.getJson(r,e).then(function(e){return o.Log.debug("UserInfoService.getClaims: claims received",e),e})}):(o.Log.error("UserInfoService.getClaims: No token passed"),Promise.reject(new Error("A token is required")))},UserInfoService}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseValidator=void 0;var n=r(0),i=r(3),o=r(43),s=r(16),a=r(42);var u=["nonce","at_hash","iat","nbf","exp","aud","iss","c_hash"];t.ResponseValidator=function(){function ResponseValidator(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:i.MetadataService,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:o.UserInfoService,s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:a.JoseUtil;if(function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,ResponseValidator),!e)throw n.Log.error("ResponseValidator.ctor: No settings passed to ResponseValidator"),new Error("settings");this._settings=e,this._metadataService=new t(this._settings),this._userInfoService=new r(this._settings),this._joseUtil=s}return ResponseValidator.prototype.validateSigninResponse=function validateSigninResponse(e,t){var r=this;return n.Log.debug("ResponseValidator.validateSigninResponse"),this._processSigninParams(e,t).then(function(t){return n.Log.debug("ResponseValidator.validateSigninResponse: state processed"),r._validateTokens(e,t).then(function(e){return n.Log.debug("ResponseValidator.validateSigninResponse: tokens validated"),r._processClaims(e).then(function(e){return n.Log.debug("ResponseValidator.validateSigninResponse: claims processed"),e})})})},ResponseValidator.prototype.validateSignoutResponse=function validateSignoutResponse(e,t){return e.id!==t.state?(n.Log.error("ResponseValidator.validateSignoutResponse: State does not match"),Promise.reject(new Error("State does not match"))):(n.Log.debug("ResponseValidator.validateSignoutResponse: state validated"),t.state=e.data,t.error?(n.Log.warn("ResponseValidator.validateSignoutResponse: Response was error",t.error),Promise.reject(new s.ErrorResponse(t))):Promise.resolve(t))},ResponseValidator.prototype._processSigninParams=function _processSigninParams(e,t){if(e.id!==t.state)return n.Log.error("ResponseValidator._processSigninParams: State does not match"),Promise.reject(new Error("State does not match"));if(!e.client_id)return n.Log.error("ResponseValidator._processSigninParams: No client_id on state"),Promise.reject(new Error("No client_id on state"));if(!e.authority)return n.Log.error("ResponseValidator._processSigninParams: No authority on state"),Promise.reject(new Error("No authority on state"));if(this._settings.authority){if(this._settings.authority&&this._settings.authority!==e.authority)return n.Log.error("ResponseValidator._processSigninParams: authority mismatch on settings vs. signin state"),Promise.reject(new Error("authority mismatch on settings vs. signin state"))}else this._settings.authority=e.authority;if(this._settings.client_id){if(this._settings.client_id&&this._settings.client_id!==e.client_id)return n.Log.error("ResponseValidator._processSigninParams: client_id mismatch on settings vs. signin state"),Promise.reject(new Error("client_id mismatch on settings vs. signin state"))}else this._settings.client_id=e.client_id;return n.Log.debug("ResponseValidator._processSigninParams: state validated"),t.state=e.data,t.error?(n.Log.warn("ResponseValidator._processSigninParams: Response was error",t.error),Promise.reject(new s.ErrorResponse(t))):e.nonce&&!t.id_token?(n.Log.error("ResponseValidator._processSigninParams: Expecting id_token in response"),Promise.reject(new Error("No id_token in response"))):!e.nonce&&t.id_token?(n.Log.error("ResponseValidator._processSigninParams: Not expecting id_token in response"),Promise.reject(new Error("Unexpected id_token in response"))):Promise.resolve(t)},ResponseValidator.prototype._processClaims=function _processClaims(e){var t=this;if(e.isOpenIdConnect){if(n.Log.debug("ResponseValidator._processClaims: response is OIDC, processing claims"),e.profile=this._filterProtocolClaims(e.profile),this._settings.loadUserInfo&&e.access_token)return n.Log.debug("ResponseValidator._processClaims: loading user info"),this._userInfoService.getClaims(e.access_token).then(function(r){return n.Log.debug("ResponseValidator._processClaims: user info claims received from user info endpoint"),r.sub!==e.profile.sub?(n.Log.error("ResponseValidator._processClaims: sub from user info endpoint does not match sub in access_token"),Promise.reject(new Error("sub from user info endpoint does not match sub in access_token"))):(e.profile=t._mergeClaims(e.profile,r),n.Log.debug("ResponseValidator._processClaims: user info claims received, updated profile:",e.profile),e)});n.Log.debug("ResponseValidator._processClaims: not loading user info")}else n.Log.debug("ResponseValidator._processClaims: response is not OIDC, not processing claims");return Promise.resolve(e)},ResponseValidator.prototype._mergeClaims=function _mergeClaims(e,t){var r=Object.assign({},e);for(var n in t){var i=t[n];Array.isArray(i)||(i=[i]);for(var o=0;o1)return n.Log.error("ResponseValidator._validateIdToken: No kid found in id_token and more than one key found in metadata"),Promise.reject(new Error("No kid found in id_token and more than one key found in metadata"));u=a[0]}if(!u)return n.Log.error("ResponseValidator._validateIdToken: No key matching kid or alg found in signing keys"),Promise.reject(new Error("No key matching kid or alg found in signing keys"));var c=e.client_id,h=r._settings.clockSkew;return n.Log.debug("ResponseValidator._validateIdToken: Validaing JWT; using clock skew (in seconds) of: ",h),r._joseUtil.validateJwt(t.id_token,u,s,c,h).then(function(){return n.Log.debug("ResponseValidator._validateIdToken: JWT validation successful"),i.payload.sub?(t.profile=i.payload,t):(n.Log.error("ResponseValidator._validateIdToken: No sub present in id_token"),Promise.reject(new Error("No sub present in id_token")))})})})},ResponseValidator.prototype._filterByAlg=function _filterByAlg(e,t){var r=null;if(t.startsWith("RS"))r="RSA";else if(t.startsWith("PS"))r="PS";else{if(!t.startsWith("ES"))return n.Log.debug("ResponseValidator._filterByAlg: alg not supported: ",t),[];r="EC"}return n.Log.debug("ResponseValidator._filterByAlg: Looking for keys that match kty: ",r),e=e.filter(function(e){return e.kty===r}),n.Log.debug("ResponseValidator._filterByAlg: Number of keys that match kty: ",r,e.length),e},ResponseValidator.prototype._validateAccessToken=function _validateAccessToken(e){if(!e.profile)return n.Log.error("ResponseValidator._validateAccessToken: No profile loaded from id_token"),Promise.reject(new Error("No profile loaded from id_token"));if(!e.profile.at_hash)return n.Log.error("ResponseValidator._validateAccessToken: No at_hash in id_token"),Promise.reject(new Error("No at_hash in id_token"));if(!e.id_token)return n.Log.error("ResponseValidator._validateAccessToken: No id_token"),Promise.reject(new Error("No id_token"));var t=this._joseUtil.parseJwt(e.id_token);if(!t||!t.header)return n.Log.error("ResponseValidator._validateAccessToken: Failed to parse id_token",t),Promise.reject(new Error("Failed to parse id_token"));var r=t.header.alg;if(!r||5!==r.length)return n.Log.error("ResponseValidator._validateAccessToken: Unsupported alg:",r),Promise.reject(new Error("Unsupported alg: "+r));var i=r.substr(2,3);if(!i)return n.Log.error("ResponseValidator._validateAccessToken: Unsupported alg:",r,i),Promise.reject(new Error("Unsupported alg: "+r));if(256!==(i=parseInt(i))&&384!==i&&512!==i)return n.Log.error("ResponseValidator._validateAccessToken: Unsupported alg:",r,i),Promise.reject(new Error("Unsupported alg: "+r));var o="sha"+i,s=this._joseUtil.hashString(e.access_token,o);if(!s)return n.Log.error("ResponseValidator._validateAccessToken: access_token hash failed:",o),Promise.reject(new Error("Failed to validate at_hash"));var a=s.substr(0,s.length/2),u=this._joseUtil.hexToBase64Url(a);return u!==e.profile.at_hash?(n.Log.error("ResponseValidator._validateAccessToken: Failed to validate at_hash",u,e.profile.at_hash),Promise.reject(new Error("Failed to validate at_hash"))):(n.Log.debug("ResponseValidator._validateAccessToken: success"),Promise.resolve(e))},ResponseValidator}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=r(0),i=r(18),o=r(6),s=r(5),a=r(31),u=r(30),c=r(12),h=r(3),f=r(20),l=r(19),g=r(9),p=r(8),d=r(10),v=r(1),y=r(13);t.default={Log:n.Log,OidcClient:i.OidcClient,OidcClientSettings:o.OidcClientSettings,WebStorageStateStore:s.WebStorageStateStore,InMemoryWebStorage:a.InMemoryWebStorage,UserManager:u.UserManager,AccessTokenEvents:c.AccessTokenEvents,MetadataService:h.MetadataService,CordovaPopupNavigator:f.CordovaPopupNavigator,CordovaIFrameNavigator:l.CordovaIFrameNavigator,CheckSessionIFrame:g.CheckSessionIFrame,TokenRevocationClient:p.TokenRevocationClient,SessionMonitor:d.SessionMonitor,Global:v.Global,User:y.User},e.exports=t.default}])}); + function parseBigInt(e, t) { + return new BigInteger(e, t); + } + function oaep_mgf1_arr(e, t, r) { + for (var n = '', i = 0; n.length < t; ) + (n += r( + String.fromCharCode.apply( + String, + e.concat([(4278190080 & i) >> 24, (16711680 & i) >> 16, (65280 & i) >> 8, 255 & i]) + ) + )), + (i += 1); + return n; + } + function RSAKey() { + (this.n = null), + (this.e = 0), + (this.d = null), + (this.p = null), + (this.q = null), + (this.dmp1 = null), + (this.dmq1 = null), + (this.coeff = null); + } + /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ + */ + function ECFieldElementFp(e, t) { + (this.x = t), (this.q = e); + } + function ECPointFp(e, t, r, n) { + (this.curve = e), + (this.x = t), + (this.y = r), + (this.z = null == n ? BigInteger.ONE : n), + (this.zinv = null); + } + function ECCurveFp(e, t, r) { + (this.q = e), + (this.a = this.fromBigInteger(t)), + (this.b = this.fromBigInteger(r)), + (this.infinity = new ECPointFp(this, null, null)); + } + (SecureRandom.prototype.nextBytes = function rng_get_bytes(e) { + var t; + for (t = 0; t < e.length; ++t) e[t] = rng_get_byte(); + }), + (RSAKey.prototype.doPublic = function RSADoPublic(e) { + return e.modPowInt(this.e, this.n); + }), + (RSAKey.prototype.setPublic = function RSASetPublic(e, t) { + if (((this.isPublic = !0), (this.isPrivate = !1), 'string' != typeof e)) + (this.n = e), (this.e = t); + else { + if (!(null != e && null != t && e.length > 0 && t.length > 0)) + throw 'Invalid RSA public key'; + (this.n = parseBigInt(e, 16)), (this.e = parseInt(t, 16)); + } + }), + (RSAKey.prototype.encrypt = function RSAEncrypt(e) { + var t = (function pkcs1pad2(e, t) { + if (t < e.length + 11) throw 'Message too long for RSA'; + for (var r = new Array(), n = e.length - 1; n >= 0 && t > 0; ) { + var i = e.charCodeAt(n--); + i < 128 + ? (r[--t] = i) + : i > 127 && i < 2048 + ? ((r[--t] = (63 & i) | 128), (r[--t] = (i >> 6) | 192)) + : ((r[--t] = (63 & i) | 128), + (r[--t] = ((i >> 6) & 63) | 128), + (r[--t] = (i >> 12) | 224)); + } + r[--t] = 0; + for (var o = new SecureRandom(), s = new Array(); t > 2; ) { + for (s[0] = 0; 0 == s[0]; ) o.nextBytes(s); + r[--t] = s[0]; + } + return (r[--t] = 2), (r[--t] = 0), new BigInteger(r); + })(e, (this.n.bitLength() + 7) >> 3); + if (null == t) return null; + var r = this.doPublic(t); + if (null == r) return null; + var n = r.toString(16); + return 0 == (1 & n.length) ? n : '0' + n; + }), + (RSAKey.prototype.encryptOAEP = function RSAEncryptOAEP(e, t, r) { + var n = (function oaep_pad(e, t, r, n) { + var i = K.crypto.MessageDigest, + o = K.crypto.Util, + s = null; + if ( + (r || (r = 'sha1'), + 'string' == typeof r && + ((s = i.getCanonicalAlgName(r)), + (n = i.getHashLength(s)), + (r = function f(e) { + return hextorstr(o.hashHex(rstrtohex(e), s)); + })), + e.length + 2 * n + 2 > t) + ) + throw 'Message too long for RSA'; + var a, + u = ''; + for (a = 0; a < t - e.length - 2 * n - 2; a += 1) u += '\0'; + var c = r('') + u + '' + e, + h = new Array(n); + new SecureRandom().nextBytes(h); + var l = oaep_mgf1_arr(h, c.length, r), + g = []; + for (a = 0; a < c.length; a += 1) g[a] = c.charCodeAt(a) ^ l.charCodeAt(a); + var p = oaep_mgf1_arr(g, h.length, r), + d = [0]; + for (a = 0; a < h.length; a += 1) d[a + 1] = h[a] ^ p.charCodeAt(a); + return new BigInteger(d.concat(g)); + })(e, (this.n.bitLength() + 7) >> 3, t, r); + if (null == n) return null; + var i = this.doPublic(n); + if (null == i) return null; + var o = i.toString(16); + return 0 == (1 & o.length) ? o : '0' + o; + }), + (RSAKey.prototype.type = 'RSA'), + (ECFieldElementFp.prototype.equals = function feFpEquals(e) { + return e == this || (this.q.equals(e.q) && this.x.equals(e.x)); + }), + (ECFieldElementFp.prototype.toBigInteger = function feFpToBigInteger() { + return this.x; + }), + (ECFieldElementFp.prototype.negate = function feFpNegate() { + return new ECFieldElementFp(this.q, this.x.negate().mod(this.q)); + }), + (ECFieldElementFp.prototype.add = function feFpAdd(e) { + return new ECFieldElementFp(this.q, this.x.add(e.toBigInteger()).mod(this.q)); + }), + (ECFieldElementFp.prototype.subtract = function feFpSubtract(e) { + return new ECFieldElementFp(this.q, this.x.subtract(e.toBigInteger()).mod(this.q)); + }), + (ECFieldElementFp.prototype.multiply = function feFpMultiply(e) { + return new ECFieldElementFp(this.q, this.x.multiply(e.toBigInteger()).mod(this.q)); + }), + (ECFieldElementFp.prototype.square = function feFpSquare() { + return new ECFieldElementFp(this.q, this.x.square().mod(this.q)); + }), + (ECFieldElementFp.prototype.divide = function feFpDivide(e) { + return new ECFieldElementFp( + this.q, + this.x.multiply(e.toBigInteger().modInverse(this.q)).mod(this.q) + ); + }), + (ECPointFp.prototype.getX = function pointFpGetX() { + return ( + null == this.zinv && (this.zinv = this.z.modInverse(this.curve.q)), + this.curve.fromBigInteger(this.x.toBigInteger().multiply(this.zinv).mod(this.curve.q)) + ); + }), + (ECPointFp.prototype.getY = function pointFpGetY() { + return ( + null == this.zinv && (this.zinv = this.z.modInverse(this.curve.q)), + this.curve.fromBigInteger(this.y.toBigInteger().multiply(this.zinv).mod(this.curve.q)) + ); + }), + (ECPointFp.prototype.equals = function pointFpEquals(e) { + return ( + e == this || + (this.isInfinity() + ? e.isInfinity() + : e.isInfinity() + ? this.isInfinity() + : !!e.y + .toBigInteger() + .multiply(this.z) + .subtract(this.y.toBigInteger().multiply(e.z)) + .mod(this.curve.q) + .equals(BigInteger.ZERO) && + e.x + .toBigInteger() + .multiply(this.z) + .subtract(this.x.toBigInteger().multiply(e.z)) + .mod(this.curve.q) + .equals(BigInteger.ZERO)) + ); + }), + (ECPointFp.prototype.isInfinity = function pointFpIsInfinity() { + return ( + (null == this.x && null == this.y) || + (this.z.equals(BigInteger.ZERO) && !this.y.toBigInteger().equals(BigInteger.ZERO)) + ); + }), + (ECPointFp.prototype.negate = function pointFpNegate() { + return new ECPointFp(this.curve, this.x, this.y.negate(), this.z); + }), + (ECPointFp.prototype.add = function pointFpAdd(e) { + if (this.isInfinity()) return e; + if (e.isInfinity()) return this; + var t = e.y + .toBigInteger() + .multiply(this.z) + .subtract(this.y.toBigInteger().multiply(e.z)) + .mod(this.curve.q), + r = e.x + .toBigInteger() + .multiply(this.z) + .subtract(this.x.toBigInteger().multiply(e.z)) + .mod(this.curve.q); + if (BigInteger.ZERO.equals(r)) + return BigInteger.ZERO.equals(t) ? this.twice() : this.curve.getInfinity(); + var n = new BigInteger('3'), + i = this.x.toBigInteger(), + o = this.y.toBigInteger(), + s = (e.x.toBigInteger(), e.y.toBigInteger(), r.square()), + a = s.multiply(r), + u = i.multiply(s), + c = t.square().multiply(this.z), + h = c + .subtract(u.shiftLeft(1)) + .multiply(e.z) + .subtract(a) + .multiply(r) + .mod(this.curve.q), + f = u + .multiply(n) + .multiply(t) + .subtract(o.multiply(a)) + .subtract(c.multiply(t)) + .multiply(e.z) + .add(t.multiply(a)) + .mod(this.curve.q), + l = a.multiply(this.z).multiply(e.z).mod(this.curve.q); + return new ECPointFp( + this.curve, + this.curve.fromBigInteger(h), + this.curve.fromBigInteger(f), + l + ); + }), + (ECPointFp.prototype.twice = function pointFpTwice() { + if (this.isInfinity()) return this; + if (0 == this.y.toBigInteger().signum()) return this.curve.getInfinity(); + var e = new BigInteger('3'), + t = this.x.toBigInteger(), + r = this.y.toBigInteger(), + n = r.multiply(this.z), + i = n.multiply(r).mod(this.curve.q), + o = this.curve.a.toBigInteger(), + s = t.square().multiply(e); + BigInteger.ZERO.equals(o) || (s = s.add(this.z.square().multiply(o))); + var a = (s = s.mod(this.curve.q)) + .square() + .subtract(t.shiftLeft(3).multiply(i)) + .shiftLeft(1) + .multiply(n) + .mod(this.curve.q), + u = s + .multiply(e) + .multiply(t) + .subtract(i.shiftLeft(1)) + .shiftLeft(2) + .multiply(i) + .subtract(s.square().multiply(s)) + .mod(this.curve.q), + c = n.square().multiply(n).shiftLeft(3).mod(this.curve.q); + return new ECPointFp( + this.curve, + this.curve.fromBigInteger(a), + this.curve.fromBigInteger(u), + c + ); + }), + (ECPointFp.prototype.multiply = function pointFpMultiply(e) { + if (this.isInfinity()) return this; + if (0 == e.signum()) return this.curve.getInfinity(); + var t, + r = e, + n = r.multiply(new BigInteger('3')), + i = this.negate(), + o = this; + for (t = n.bitLength() - 2; t > 0; --t) { + o = o.twice(); + var s = n.testBit(t); + s != r.testBit(t) && (o = o.add(s ? this : i)); + } + return o; + }), + (ECPointFp.prototype.multiplyTwo = function pointFpMultiplyTwo(e, t, r) { + var n; + n = e.bitLength() > r.bitLength() ? e.bitLength() - 1 : r.bitLength() - 1; + for (var i = this.curve.getInfinity(), o = this.add(t); n >= 0; ) + (i = i.twice()), + e.testBit(n) + ? (i = r.testBit(n) ? i.add(o) : i.add(this)) + : r.testBit(n) && (i = i.add(t)), + --n; + return i; + }), + (ECCurveFp.prototype.getQ = function curveFpGetQ() { + return this.q; + }), + (ECCurveFp.prototype.getA = function curveFpGetA() { + return this.a; + }), + (ECCurveFp.prototype.getB = function curveFpGetB() { + return this.b; + }), + (ECCurveFp.prototype.equals = function curveFpEquals(e) { + return e == this || (this.q.equals(e.q) && this.a.equals(e.a) && this.b.equals(e.b)); + }), + (ECCurveFp.prototype.getInfinity = function curveFpGetInfinity() { + return this.infinity; + }), + (ECCurveFp.prototype.fromBigInteger = function curveFpFromBigInteger(e) { + return new ECFieldElementFp(this.q, e); + }), + (ECCurveFp.prototype.decodePointHex = function curveFpDecodePointHex(e) { + switch (parseInt(e.substr(0, 2), 16)) { + case 0: + return this.infinity; + case 2: + case 3: + return null; + case 4: + case 6: + case 7: + var t = (e.length - 2) / 2, + r = e.substr(2, t), + n = e.substr(t + 2, t); + return new ECPointFp( + this, + this.fromBigInteger(new BigInteger(r, 16)), + this.fromBigInteger(new BigInteger(n, 16)) + ); + default: + return null; + } + }); + /*! Mike Samuel (c) 2009 | code.google.com/p/json-sans-eval + */ + var K, + q, + W, + V = (function () { + var e = new RegExp( + '(?:false|true|null|[\\{\\}\\[\\]]|(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)|(?:"(?:[^\\0-\\x08\\x0a-\\x1f"\\\\]|\\\\(?:["/\\\\bfnrt]|u[0-9A-Fa-f]{4}))*"))', + 'g' + ), + t = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g'), + r = { + '"': '"', + '/': '/', + '\\': '\\', + b: '\b', + f: '\f', + n: '\n', + r: '\r', + t: '\t', + }; + function h(e, t, n) { + return t ? r[t] : String.fromCharCode(parseInt(n, 16)); + } + var n = new String(''), + o = (Object, Array, Object.hasOwnProperty); + return function (r, a) { + var u, + c, + f = r.match(e), + l = f[0], + g = !1; + '{' === l ? (u = {}) : '[' === l ? (u = []) : ((u = []), (g = !0)); + for (var p = [u], d = 1 - g, v = f.length; d < v; ++d) { + var y; + switch ((l = f[d]).charCodeAt(0)) { + default: + ((y = p[0])[c || y.length] = +l), (c = void 0); + break; + case 34: + if ( + (-1 !== (l = l.substring(1, l.length - 1)).indexOf('\\') && + (l = l.replace(t, h)), + (y = p[0]), + !c) + ) { + if (!(y instanceof Array)) { + c = l || n; + break; + } + c = y.length; + } + (y[c] = l), (c = void 0); + break; + case 91: + (y = p[0]), p.unshift((y[c || y.length] = [])), (c = void 0); + break; + case 93: + p.shift(); + break; + case 102: + ((y = p[0])[c || y.length] = !1), (c = void 0); + break; + case 110: + ((y = p[0])[c || y.length] = null), (c = void 0); + break; + case 116: + ((y = p[0])[c || y.length] = !0), (c = void 0); + break; + case 123: + (y = p[0]), p.unshift((y[c || y.length] = {})), (c = void 0); + break; + case 125: + p.shift(); + } + } + if (g) { + if (1 !== p.length) throw new Error(); + u = u[0]; + } else if (p.length) throw new Error(); + if (a) { + u = (function s(e, t) { + var r = e[t]; + if (r && 'object' === (void 0 === r ? 'undefined' : i(r))) { + var n = null; + for (var u in r) + if (o.call(r, u) && r !== e) { + var c = s(r, u); + void 0 !== c ? (r[u] = c) : (n || (n = []), n.push(u)); + } + if (n) for (var h = n.length; --h >= 0; ) delete r[n[h]]; + } + return a.call(e, t, r); + })({ '': u }, ''); + } + return u; + }; + })(), + J = new (function () {})(); + function stoBA(e) { + for (var t = new Array(), r = 0; r < e.length; r++) t[r] = e.charCodeAt(r); + return t; + } + function BAtos(e) { + for (var t = '', r = 0; r < e.length; r++) t += String.fromCharCode(e[r]); + return t; + } + function BAtohex(e) { + for (var t = '', r = 0; r < e.length; r++) { + var n = e[r].toString(16); + 1 == n.length && (n = '0' + n), (t += n); + } + return t; + } + function stohex(e) { + return BAtohex(stoBA(e)); + } + function b64tob64u(e) { + return (e = (e = (e = e.replace(/\=/g, '')).replace(/\+/g, '-')).replace(/\//g, '_')); + } + function b64utob64(e) { + return ( + e.length % 4 == 2 ? (e += '==') : e.length % 4 == 3 && (e += '='), + (e = (e = e.replace(/-/g, '+')).replace(/_/g, '/')) + ); + } + function hextob64u(e) { + return e.length % 2 == 1 && (e = '0' + e), b64tob64u(hex2b64(e)); + } + function b64utohex(e) { + return b64tohex(b64utob64(e)); + } + function utf8tohex(e) { + return uricmptohex(encodeURIComponentAll(e)); + } + function hextoutf8(e) { + return decodeURIComponent(hextouricmp(e)); + } + function hextorstr(e) { + for (var t = '', r = 0; r < e.length - 1; r += 2) + t += String.fromCharCode(parseInt(e.substr(r, 2), 16)); + return t; + } + function rstrtohex(e) { + for (var t = '', r = 0; r < e.length; r++) + t += ('0' + e.charCodeAt(r).toString(16)).slice(-2); + return t; + } + function hextob64(e) { + return hex2b64(e); + } + function hextob64nl(e) { + var t = hextob64(e).replace(/(.{64})/g, '$1\r\n'); + return (t = t.replace(/\r\n$/, '')); + } + function b64nltohex(e) { + return b64tohex(e.replace(/[^0-9A-Za-z\/+=]*/g, '')); + } + function hextopem(e, t) { + return ( + '-----BEGIN ' + t + '-----\r\n' + hextob64nl(e) + '\r\n-----END ' + t + '-----\r\n' + ); + } + function pemtohex(e, t) { + if (-1 == e.indexOf('-----BEGIN ')) throw "can't find PEM header: " + t; + return b64nltohex( + (e = + void 0 !== t + ? (e = e.replace('-----BEGIN ' + t + '-----', '')).replace( + '-----END ' + t + '-----', + '' + ) + : (e = e.replace(/-----BEGIN [^-]+-----/, '')).replace(/-----END [^-]+-----/, '')) + ); + } + function zulutomsec(e) { + var t, r, n, i, o, s, a, u, c, h, f; + if ((f = e.match(/^(\d{2}|\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(|\.\d+)Z$/))) + return ( + (u = f[1]), + (t = parseInt(u)), + 2 === u.length && + (50 <= t && t < 100 ? (t = 1900 + t) : 0 <= t && t < 50 && (t = 2e3 + t)), + (r = parseInt(f[2]) - 1), + (n = parseInt(f[3])), + (i = parseInt(f[4])), + (o = parseInt(f[5])), + (s = parseInt(f[6])), + (a = 0), + '' !== (c = f[7]) && ((h = (c.substr(1) + '00').substr(0, 3)), (a = parseInt(h))), + Date.UTC(t, r, n, i, o, s, a) + ); + throw 'unsupported zulu format: ' + e; + } + function zulutosec(e) { + return ~~(zulutomsec(e) / 1e3); + } + function uricmptohex(e) { + return e.replace(/%/g, ''); + } + function hextouricmp(e) { + return e.replace(/(..)/g, '%$1'); + } + function ipv6tohex(e) { + var t = 'malformed IPv6 address'; + if (!e.match(/^[0-9A-Fa-f:]+$/)) throw t; + var r = (e = e.toLowerCase()).split(':').length - 1; + if (r < 2) throw t; + var n = ':'.repeat(7 - r + 2), + i = (e = e.replace('::', n)).split(':'); + if (8 != i.length) throw t; + for (var o = 0; o < 8; o++) i[o] = ('0000' + i[o]).slice(-4); + return i.join(''); + } + function hextoipv6(e) { + if (!e.match(/^[0-9A-Fa-f]{32}$/)) throw 'malformed IPv6 address octet'; + for (var t = (e = e.toLowerCase()).match(/.{1,4}/g), r = 0; r < 8; r++) + (t[r] = t[r].replace(/^0+/, '')), '' == t[r] && (t[r] = '0'); + var n = (e = ':' + t.join(':') + ':').match(/:(0:){2,}/g); + if (null === n) return e.slice(1, -1); + var i = ''; + for (r = 0; r < n.length; r++) n[r].length > i.length && (i = n[r]); + return (e = e.replace(i, '::')).slice(1, -1); + } + function hextoip(e) { + var t = 'malformed hex value'; + if (!e.match(/^([0-9A-Fa-f][0-9A-Fa-f]){1,}$/)) throw t; + if (8 != e.length) return 32 == e.length ? hextoipv6(e) : e; + try { + return ( + parseInt(e.substr(0, 2), 16) + + '.' + + parseInt(e.substr(2, 2), 16) + + '.' + + parseInt(e.substr(4, 2), 16) + + '.' + + parseInt(e.substr(6, 2), 16) + ); + } catch (e) { + throw t; + } + } + function encodeURIComponentAll(e) { + for (var t = encodeURIComponent(e), r = '', n = 0; n < t.length; n++) + '%' == t[n] ? ((r += t.substr(n, 3)), (n += 2)) : (r = r + '%' + stohex(t[n])); + return r; + } + function hextoposhex(e) { + return e.length % 2 == 1 ? '0' + e : e.substr(0, 1) > '7' ? '00' + e : e; + } + (J.getLblen = function (e, t) { + if ('8' != e.substr(t + 2, 1)) return 1; + var r = parseInt(e.substr(t + 3, 1)); + return 0 == r ? -1 : 0 < r && r < 10 ? r + 1 : -2; + }), + (J.getL = function (e, t) { + var r = J.getLblen(e, t); + return r < 1 ? '' : e.substr(t + 2, 2 * r); + }), + (J.getVblen = function (e, t) { + var r; + return '' == (r = J.getL(e, t)) + ? -1 + : ('8' === r.substr(0, 1) + ? new BigInteger(r.substr(2), 16) + : new BigInteger(r, 16) + ).intValue(); + }), + (J.getVidx = function (e, t) { + var r = J.getLblen(e, t); + return r < 0 ? r : t + 2 * (r + 1); + }), + (J.getV = function (e, t) { + var r = J.getVidx(e, t), + n = J.getVblen(e, t); + return e.substr(r, 2 * n); + }), + (J.getTLV = function (e, t) { + return e.substr(t, 2) + J.getL(e, t) + J.getV(e, t); + }), + (J.getNextSiblingIdx = function (e, t) { + return J.getVidx(e, t) + 2 * J.getVblen(e, t); + }), + (J.getChildIdx = function (e, t) { + var r = J, + n = new Array(), + i = r.getVidx(e, t); + '03' == e.substr(t, 2) ? n.push(i + 2) : n.push(i); + for (var o = r.getVblen(e, t), s = i, a = 0; ; ) { + var u = r.getNextSiblingIdx(e, s); + if (null == u || u - i >= 2 * o) break; + if (a >= 200) break; + n.push(u), (s = u), a++; + } + return n; + }), + (J.getNthChildIdx = function (e, t, r) { + return J.getChildIdx(e, t)[r]; + }), + (J.getIdxbyList = function (e, t, r, n) { + var i, + o, + s = J; + if (0 == r.length) { + if (void 0 !== n && e.substr(t, 2) !== n) + throw "checking tag doesn't match: " + e.substr(t, 2) + '!=' + n; + return t; + } + return (i = r.shift()), (o = s.getChildIdx(e, t)), s.getIdxbyList(e, o[i], r, n); + }), + (J.getTLVbyList = function (e, t, r, n) { + var i = J, + o = i.getIdxbyList(e, t, r); + if (void 0 === o) throw "can't find nthList object"; + if (void 0 !== n && e.substr(o, 2) != n) + throw "checking tag doesn't match: " + e.substr(o, 2) + '!=' + n; + return i.getTLV(e, o); + }), + (J.getVbyList = function (e, t, r, n, i) { + var o, + s, + a = J; + if (void 0 === (o = a.getIdxbyList(e, t, r, n))) throw "can't find nthList object"; + return (s = a.getV(e, o)), !0 === i && (s = s.substr(2)), s; + }), + (J.hextooidstr = function (e) { + var t = function h(e, t) { + return e.length >= t ? e : new Array(t - e.length + 1).join('0') + e; + }, + r = [], + n = e.substr(0, 2), + i = parseInt(n, 16); + (r[0] = new String(Math.floor(i / 40))), (r[1] = new String(i % 40)); + for (var o = e.substr(2), s = [], a = 0; a < o.length / 2; a++) + s.push(parseInt(o.substr(2 * a, 2), 16)); + var u = [], + c = ''; + for (a = 0; a < s.length; a++) + 128 & s[a] + ? (c += t((127 & s[a]).toString(2), 7)) + : ((c += t((127 & s[a]).toString(2), 7)), + u.push(new String(parseInt(c, 2))), + (c = '')); + var h = r.join('.'); + return u.length > 0 && (h = h + '.' + u.join('.')), h; + }), + (J.dump = function (e, t, r, n) { + var i = J, + o = i.getV, + s = i.dump, + a = i.getChildIdx, + u = e; + e instanceof K.asn1.ASN1Object && (u = e.getEncodedHex()); + var c = function q(e, t) { + return e.length <= 2 * t + ? e + : e.substr(0, t) + + '..(total ' + + e.length / 2 + + 'bytes)..' + + e.substr(e.length - t, t); + }; + void 0 === t && (t = { ommit_long_octet: 32 }), + void 0 === r && (r = 0), + void 0 === n && (n = ''); + var h = t.ommit_long_octet; + if ('01' == u.substr(r, 2)) + return '00' == (f = o(u, r)) ? n + 'BOOLEAN FALSE\n' : n + 'BOOLEAN TRUE\n'; + if ('02' == u.substr(r, 2)) return n + 'INTEGER ' + c((f = o(u, r)), h) + '\n'; + if ('03' == u.substr(r, 2)) return n + 'BITSTRING ' + c((f = o(u, r)), h) + '\n'; + if ('04' == u.substr(r, 2)) { + var f = o(u, r); + if (i.isASN1HEX(f)) { + var l = n + 'OCTETSTRING, encapsulates\n'; + return (l += s(f, t, 0, n + ' ')); + } + return n + 'OCTETSTRING ' + c(f, h) + '\n'; + } + if ('05' == u.substr(r, 2)) return n + 'NULL\n'; + if ('06' == u.substr(r, 2)) { + var g = o(u, r), + p = K.asn1.ASN1Util.oidHexToInt(g), + d = K.asn1.x509.OID.oid2name(p), + v = p.replace(/\./g, ' '); + return '' != d + ? n + 'ObjectIdentifier ' + d + ' (' + v + ')\n' + : n + 'ObjectIdentifier (' + v + ')\n'; + } + if ('0c' == u.substr(r, 2)) return n + "UTF8String '" + hextoutf8(o(u, r)) + "'\n"; + if ('13' == u.substr(r, 2)) return n + "PrintableString '" + hextoutf8(o(u, r)) + "'\n"; + if ('14' == u.substr(r, 2)) return n + "TeletexString '" + hextoutf8(o(u, r)) + "'\n"; + if ('16' == u.substr(r, 2)) return n + "IA5String '" + hextoutf8(o(u, r)) + "'\n"; + if ('17' == u.substr(r, 2)) return n + 'UTCTime ' + hextoutf8(o(u, r)) + '\n'; + if ('18' == u.substr(r, 2)) return n + 'GeneralizedTime ' + hextoutf8(o(u, r)) + '\n'; + if ('30' == u.substr(r, 2)) { + if ('3000' == u.substr(r, 4)) return n + 'SEQUENCE {}\n'; + l = n + 'SEQUENCE\n'; + var y = t; + if ( + (2 == (F = a(u, r)).length || 3 == F.length) && + '06' == u.substr(F[0], 2) && + '04' == u.substr(F[F.length - 1], 2) + ) { + d = i.oidname(o(u, F[0])); + var m = JSON.parse(JSON.stringify(t)); + (m.x509ExtName = d), (y = m); + } + for (var S = 0; S < F.length; S++) l += s(u, y, F[S], n + ' '); + return l; + } + if ('31' == u.substr(r, 2)) { + l = n + 'SET\n'; + var F = a(u, r); + for (S = 0; S < F.length; S++) l += s(u, t, F[S], n + ' '); + return l; + } + var b = parseInt(u.substr(r, 2), 16); + if (0 != (128 & b)) { + var _ = 31 & b; + if (0 != (32 & b)) { + var l = n + '[' + _ + ']\n'; + for (F = a(u, r), S = 0; S < F.length; S++) l += s(u, t, F[S], n + ' '); + return l; + } + return ( + '68747470' == (f = o(u, r)).substr(0, 8) && (f = hextoutf8(f)), + 'subjectAltName' === t.x509ExtName && 2 == _ && (f = hextoutf8(f)), + (l = n + '[' + _ + '] ' + f + '\n') + ); + } + return n + 'UNKNOWN(' + u.substr(r, 2) + ') ' + o(u, r) + '\n'; + }), + (J.isASN1HEX = function (e) { + var t = J; + if (e.length % 2 == 1) return !1; + var r = t.getVblen(e, 0), + n = e.substr(0, 2), + i = t.getL(e, 0); + return e.length - n.length - i.length == 2 * r; + }), + (J.oidname = function (e) { + var t = K.asn1; + K.lang.String.isHex(e) && (e = t.ASN1Util.oidHexToInt(e)); + var r = t.x509.OID.oid2name(e); + return '' === r && (r = e), r; + }), + (void 0 !== K && K) || (K = {}), + (void 0 !== K.lang && K.lang) || (K.lang = {}), + (K.lang.String = function () {}), + 'function' == typeof n + ? ((q = function utf8tob64u(e) { + return b64tob64u(new n(e, 'utf8').toString('base64')); + }), + (W = function b64utoutf8(e) { + return new n(b64utob64(e), 'base64').toString('utf8'); + })) + : ((q = function utf8tob64u(e) { + return hextob64u(uricmptohex(encodeURIComponentAll(e))); + }), + (W = function b64utoutf8(e) { + return decodeURIComponent(hextouricmp(b64utohex(e))); + })), + (K.lang.String.isInteger = function (e) { + return !!e.match(/^[0-9]+$/) || !!e.match(/^-[0-9]+$/); + }), + (K.lang.String.isHex = function (e) { + return !(e.length % 2 != 0 || (!e.match(/^[0-9a-f]+$/) && !e.match(/^[0-9A-F]+$/))); + }), + (K.lang.String.isBase64 = function (e) { + return !( + !(e = e.replace(/\s+/g, '')).match(/^[0-9A-Za-z+\/]+={0,3}$/) || e.length % 4 != 0 + ); + }), + (K.lang.String.isBase64URL = function (e) { + return !e.match(/[+/=]/) && ((e = b64utob64(e)), K.lang.String.isBase64(e)); + }), + (K.lang.String.isIntegerArray = function (e) { + return !!(e = e.replace(/\s+/g, '')).match(/^\[[0-9,]+\]$/); + }); + (void 0 !== K && K) || (K = {}), + (void 0 !== K.crypto && K.crypto) || (K.crypto = {}), + (K.crypto.Util = new (function () { + (this.DIGESTINFOHEAD = { + sha1: '3021300906052b0e03021a05000414', + sha224: '302d300d06096086480165030402040500041c', + sha256: '3031300d060960864801650304020105000420', + sha384: '3041300d060960864801650304020205000430', + sha512: '3051300d060960864801650304020305000440', + md2: '3020300c06082a864886f70d020205000410', + md5: '3020300c06082a864886f70d020505000410', + ripemd160: '3021300906052b2403020105000414', + }), + (this.DEFAULTPROVIDER = { + md5: 'cryptojs', + sha1: 'cryptojs', + sha224: 'cryptojs', + sha256: 'cryptojs', + sha384: 'cryptojs', + sha512: 'cryptojs', + ripemd160: 'cryptojs', + hmacmd5: 'cryptojs', + hmacsha1: 'cryptojs', + hmacsha224: 'cryptojs', + hmacsha256: 'cryptojs', + hmacsha384: 'cryptojs', + hmacsha512: 'cryptojs', + hmacripemd160: 'cryptojs', + MD5withRSA: 'cryptojs/jsrsa', + SHA1withRSA: 'cryptojs/jsrsa', + SHA224withRSA: 'cryptojs/jsrsa', + SHA256withRSA: 'cryptojs/jsrsa', + SHA384withRSA: 'cryptojs/jsrsa', + SHA512withRSA: 'cryptojs/jsrsa', + RIPEMD160withRSA: 'cryptojs/jsrsa', + MD5withECDSA: 'cryptojs/jsrsa', + SHA1withECDSA: 'cryptojs/jsrsa', + SHA224withECDSA: 'cryptojs/jsrsa', + SHA256withECDSA: 'cryptojs/jsrsa', + SHA384withECDSA: 'cryptojs/jsrsa', + SHA512withECDSA: 'cryptojs/jsrsa', + RIPEMD160withECDSA: 'cryptojs/jsrsa', + SHA1withDSA: 'cryptojs/jsrsa', + SHA224withDSA: 'cryptojs/jsrsa', + SHA256withDSA: 'cryptojs/jsrsa', + MD5withRSAandMGF1: 'cryptojs/jsrsa', + SHA1withRSAandMGF1: 'cryptojs/jsrsa', + SHA224withRSAandMGF1: 'cryptojs/jsrsa', + SHA256withRSAandMGF1: 'cryptojs/jsrsa', + SHA384withRSAandMGF1: 'cryptojs/jsrsa', + SHA512withRSAandMGF1: 'cryptojs/jsrsa', + RIPEMD160withRSAandMGF1: 'cryptojs/jsrsa', + }), + (this.CRYPTOJSMESSAGEDIGESTNAME = { + md5: y.algo.MD5, + sha1: y.algo.SHA1, + sha224: y.algo.SHA224, + sha256: y.algo.SHA256, + sha384: y.algo.SHA384, + sha512: y.algo.SHA512, + ripemd160: y.algo.RIPEMD160, + }), + (this.getDigestInfoHex = function (e, t) { + if (void 0 === this.DIGESTINFOHEAD[t]) + throw 'alg not supported in Util.DIGESTINFOHEAD: ' + t; + return this.DIGESTINFOHEAD[t] + e; + }), + (this.getPaddedDigestInfoHex = function (e, t, r) { + var n = this.getDigestInfoHex(e, t), + i = r / 4; + if (n.length + 22 > i) throw 'key is too short for SigAlg: keylen=' + r + ',' + t; + for ( + var o = '0001', s = '00' + n, a = '', u = i - o.length - s.length, c = 0; + c < u; + c += 2 + ) + a += 'ff'; + return o + a + s; + }), + (this.hashString = function (e, t) { + return new K.crypto.MessageDigest({ alg: t }).digestString(e); + }), + (this.hashHex = function (e, t) { + return new K.crypto.MessageDigest({ alg: t }).digestHex(e); + }), + (this.sha1 = function (e) { + return new K.crypto.MessageDigest({ + alg: 'sha1', + prov: 'cryptojs', + }).digestString(e); + }), + (this.sha256 = function (e) { + return new K.crypto.MessageDigest({ + alg: 'sha256', + prov: 'cryptojs', + }).digestString(e); + }), + (this.sha256Hex = function (e) { + return new K.crypto.MessageDigest({ + alg: 'sha256', + prov: 'cryptojs', + }).digestHex(e); + }), + (this.sha512 = function (e) { + return new K.crypto.MessageDigest({ + alg: 'sha512', + prov: 'cryptojs', + }).digestString(e); + }), + (this.sha512Hex = function (e) { + return new K.crypto.MessageDigest({ + alg: 'sha512', + prov: 'cryptojs', + }).digestHex(e); + }); + })()), + (K.crypto.Util.md5 = function (e) { + return new K.crypto.MessageDigest({ + alg: 'md5', + prov: 'cryptojs', + }).digestString(e); + }), + (K.crypto.Util.ripemd160 = function (e) { + return new K.crypto.MessageDigest({ + alg: 'ripemd160', + prov: 'cryptojs', + }).digestString(e); + }), + (K.crypto.Util.SECURERANDOMGEN = new SecureRandom()), + (K.crypto.Util.getRandomHexOfNbytes = function (e) { + var t = new Array(e); + return K.crypto.Util.SECURERANDOMGEN.nextBytes(t), BAtohex(t); + }), + (K.crypto.Util.getRandomBigIntegerOfNbytes = function (e) { + return new BigInteger(K.crypto.Util.getRandomHexOfNbytes(e), 16); + }), + (K.crypto.Util.getRandomHexOfNbits = function (e) { + var t = e % 8, + r = new Array((e - t) / 8 + 1); + return ( + K.crypto.Util.SECURERANDOMGEN.nextBytes(r), + (r[0] = (((255 << t) & 255) ^ 255) & r[0]), + BAtohex(r) + ); + }), + (K.crypto.Util.getRandomBigIntegerOfNbits = function (e) { + return new BigInteger(K.crypto.Util.getRandomHexOfNbits(e), 16); + }), + (K.crypto.Util.getRandomBigIntegerZeroToMax = function (e) { + for (var t = e.bitLength(); ; ) { + var r = K.crypto.Util.getRandomBigIntegerOfNbits(t); + if (-1 != e.compareTo(r)) return r; + } + }), + (K.crypto.Util.getRandomBigIntegerMinToMax = function (e, t) { + var r = e.compareTo(t); + if (1 == r) throw 'biMin is greater than biMax'; + if (0 == r) return e; + var n = t.subtract(e); + return K.crypto.Util.getRandomBigIntegerZeroToMax(n).add(e); + }), + (K.crypto.MessageDigest = function (e) { + (this.setAlgAndProvider = function (e, t) { + if ( + (null !== (e = K.crypto.MessageDigest.getCanonicalAlgName(e)) && + void 0 === t && + (t = K.crypto.Util.DEFAULTPROVIDER[e]), + -1 != ':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(e) && + 'cryptojs' == t) + ) { + try { + this.md = K.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME[e].create(); + } catch (t) { + throw 'setAlgAndProvider hash alg set fail alg=' + e + '/' + t; + } + (this.updateString = function (e) { + this.md.update(e); + }), + (this.updateHex = function (e) { + var t = y.enc.Hex.parse(e); + this.md.update(t); + }), + (this.digest = function () { + return this.md.finalize().toString(y.enc.Hex); + }), + (this.digestString = function (e) { + return this.updateString(e), this.digest(); + }), + (this.digestHex = function (e) { + return this.updateHex(e), this.digest(); + }); + } + if (-1 != ':sha256:'.indexOf(e) && 'sjcl' == t) { + try { + this.md = new sjcl.hash.sha256(); + } catch (t) { + throw 'setAlgAndProvider hash alg set fail alg=' + e + '/' + t; + } + (this.updateString = function (e) { + this.md.update(e); + }), + (this.updateHex = function (e) { + var t = sjcl.codec.hex.toBits(e); + this.md.update(t); + }), + (this.digest = function () { + var e = this.md.finalize(); + return sjcl.codec.hex.fromBits(e); + }), + (this.digestString = function (e) { + return this.updateString(e), this.digest(); + }), + (this.digestHex = function (e) { + return this.updateHex(e), this.digest(); + }); + } + }), + (this.updateString = function (e) { + throw ( + 'updateString(str) not supported for this alg/prov: ' + + this.algName + + '/' + + this.provName + ); + }), + (this.updateHex = function (e) { + throw ( + 'updateHex(hex) not supported for this alg/prov: ' + + this.algName + + '/' + + this.provName + ); + }), + (this.digest = function () { + throw ( + 'digest() not supported for this alg/prov: ' + this.algName + '/' + this.provName + ); + }), + (this.digestString = function (e) { + throw ( + 'digestString(str) not supported for this alg/prov: ' + + this.algName + + '/' + + this.provName + ); + }), + (this.digestHex = function (e) { + throw ( + 'digestHex(hex) not supported for this alg/prov: ' + + this.algName + + '/' + + this.provName + ); + }), + void 0 !== e && + void 0 !== e.alg && + ((this.algName = e.alg), + void 0 === e.prov && (this.provName = K.crypto.Util.DEFAULTPROVIDER[this.algName]), + this.setAlgAndProvider(this.algName, this.provName)); + }), + (K.crypto.MessageDigest.getCanonicalAlgName = function (e) { + return 'string' == typeof e && (e = (e = e.toLowerCase()).replace(/-/, '')), e; + }), + (K.crypto.MessageDigest.getHashLength = function (e) { + var t = K.crypto.MessageDigest, + r = t.getCanonicalAlgName(e); + if (void 0 === t.HASHLENGTH[r]) throw 'not supported algorithm: ' + e; + return t.HASHLENGTH[r]; + }), + (K.crypto.MessageDigest.HASHLENGTH = { + md5: 16, + sha1: 20, + sha224: 28, + sha256: 32, + sha384: 48, + sha512: 64, + ripemd160: 20, + }), + (K.crypto.Mac = function (e) { + (this.setAlgAndProvider = function (e, t) { + if ( + (null == (e = e.toLowerCase()) && (e = 'hmacsha1'), + 'hmac' != (e = e.toLowerCase()).substr(0, 4)) + ) + throw 'setAlgAndProvider unsupported HMAC alg: ' + e; + void 0 === t && (t = K.crypto.Util.DEFAULTPROVIDER[e]), (this.algProv = e + '/' + t); + var r = e.substr(4); + if ( + -1 != ':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(r) && + 'cryptojs' == t + ) { + try { + var n = K.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME[r]; + this.mac = y.algo.HMAC.create(n, this.pass); + } catch (e) { + throw 'setAlgAndProvider hash alg set fail hashAlg=' + r + '/' + e; + } + (this.updateString = function (e) { + this.mac.update(e); + }), + (this.updateHex = function (e) { + var t = y.enc.Hex.parse(e); + this.mac.update(t); + }), + (this.doFinal = function () { + return this.mac.finalize().toString(y.enc.Hex); + }), + (this.doFinalString = function (e) { + return this.updateString(e), this.doFinal(); + }), + (this.doFinalHex = function (e) { + return this.updateHex(e), this.doFinal(); + }); + } + }), + (this.updateString = function (e) { + throw 'updateString(str) not supported for this alg/prov: ' + this.algProv; + }), + (this.updateHex = function (e) { + throw 'updateHex(hex) not supported for this alg/prov: ' + this.algProv; + }), + (this.doFinal = function () { + throw 'digest() not supported for this alg/prov: ' + this.algProv; + }), + (this.doFinalString = function (e) { + throw 'digestString(str) not supported for this alg/prov: ' + this.algProv; + }), + (this.doFinalHex = function (e) { + throw 'digestHex(hex) not supported for this alg/prov: ' + this.algProv; + }), + (this.setPassword = function (e) { + if ('string' == typeof e) { + var t = e; + return ( + (e.length % 2 != 1 && e.match(/^[0-9A-Fa-f]+$/)) || (t = rstrtohex(e)), + void (this.pass = y.enc.Hex.parse(t)) + ); + } + if ('object' != (void 0 === e ? 'undefined' : i(e))) + throw 'KJUR.crypto.Mac unsupported password type: ' + e; + t = null; + if (void 0 !== e.hex) { + if (e.hex.length % 2 != 0 || !e.hex.match(/^[0-9A-Fa-f]+$/)) + throw 'Mac: wrong hex password: ' + e.hex; + t = e.hex; + } + if ( + (void 0 !== e.utf8 && (t = utf8tohex(e.utf8)), + void 0 !== e.rstr && (t = rstrtohex(e.rstr)), + void 0 !== e.b64 && (t = b64tohex(e.b64)), + void 0 !== e.b64u && (t = b64utohex(e.b64u)), + null == t) + ) + throw 'KJUR.crypto.Mac unsupported password type: ' + e; + this.pass = y.enc.Hex.parse(t); + }), + void 0 !== e && + (void 0 !== e.pass && this.setPassword(e.pass), + void 0 !== e.alg && + ((this.algName = e.alg), + void 0 === e.prov && + (this.provName = K.crypto.Util.DEFAULTPROVIDER[this.algName]), + this.setAlgAndProvider(this.algName, this.provName))); + }), + (K.crypto.Signature = function (e) { + var t = null; + if ( + ((this._setAlgNames = function () { + var e = this.algName.match(/^(.+)with(.+)$/); + e && + ((this.mdAlgName = e[1].toLowerCase()), + (this.pubkeyAlgName = e[2].toLowerCase())); + }), + (this._zeroPaddingOfSignature = function (e, t) { + for (var r = '', n = t / 4 - e.length, i = 0; i < n; i++) r += '0'; + return r + e; + }), + (this.setAlgAndProvider = function (e, t) { + if ((this._setAlgNames(), 'cryptojs/jsrsa' != t)) + throw 'provider not supported: ' + t; + if ( + -1 != ':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(this.mdAlgName) + ) { + try { + this.md = new K.crypto.MessageDigest({ + alg: this.mdAlgName, + }); + } catch (e) { + throw 'setAlgAndProvider hash alg set fail alg=' + this.mdAlgName + '/' + e; + } + (this.init = function (e, t) { + var r = null; + try { + r = void 0 === t ? z.getKey(e) : z.getKey(e, t); + } catch (e) { + throw 'init failed:' + e; + } + if (!0 === r.isPrivate) (this.prvKey = r), (this.state = 'SIGN'); + else { + if (!0 !== r.isPublic) throw 'init failed.:' + r; + (this.pubKey = r), (this.state = 'VERIFY'); + } + }), + (this.updateString = function (e) { + this.md.updateString(e); + }), + (this.updateHex = function (e) { + this.md.updateHex(e); + }), + (this.sign = function () { + if ( + ((this.sHashHex = this.md.digest()), + void 0 !== this.ecprvhex && void 0 !== this.eccurvename) + ) { + var e = new K.crypto.ECDSA({ curve: this.eccurvename }); + this.hSign = e.signHex(this.sHashHex, this.ecprvhex); + } else if ( + this.prvKey instanceof RSAKey && + 'rsaandmgf1' === this.pubkeyAlgName + ) + this.hSign = this.prvKey.signWithMessageHashPSS( + this.sHashHex, + this.mdAlgName, + this.pssSaltLen + ); + else if (this.prvKey instanceof RSAKey && 'rsa' === this.pubkeyAlgName) + this.hSign = this.prvKey.signWithMessageHash(this.sHashHex, this.mdAlgName); + else if (this.prvKey instanceof K.crypto.ECDSA) + this.hSign = this.prvKey.signWithMessageHash(this.sHashHex); + else { + if (!(this.prvKey instanceof K.crypto.DSA)) + throw 'Signature: unsupported private key alg: ' + this.pubkeyAlgName; + this.hSign = this.prvKey.signWithMessageHash(this.sHashHex); + } + return this.hSign; + }), + (this.signString = function (e) { + return this.updateString(e), this.sign(); + }), + (this.signHex = function (e) { + return this.updateHex(e), this.sign(); + }), + (this.verify = function (e) { + if ( + ((this.sHashHex = this.md.digest()), + void 0 !== this.ecpubhex && void 0 !== this.eccurvename) + ) + return new K.crypto.ECDSA({ + curve: this.eccurvename, + }).verifyHex(this.sHashHex, e, this.ecpubhex); + if (this.pubKey instanceof RSAKey && 'rsaandmgf1' === this.pubkeyAlgName) + return this.pubKey.verifyWithMessageHashPSS( + this.sHashHex, + e, + this.mdAlgName, + this.pssSaltLen + ); + if (this.pubKey instanceof RSAKey && 'rsa' === this.pubkeyAlgName) + return this.pubKey.verifyWithMessageHash(this.sHashHex, e); + if (void 0 !== K.crypto.ECDSA && this.pubKey instanceof K.crypto.ECDSA) + return this.pubKey.verifyWithMessageHash(this.sHashHex, e); + if (void 0 !== K.crypto.DSA && this.pubKey instanceof K.crypto.DSA) + return this.pubKey.verifyWithMessageHash(this.sHashHex, e); + throw 'Signature: unsupported public key alg: ' + this.pubkeyAlgName; + }); + } + }), + (this.init = function (e, t) { + throw 'init(key, pass) not supported for this alg:prov=' + this.algProvName; + }), + (this.updateString = function (e) { + throw 'updateString(str) not supported for this alg:prov=' + this.algProvName; + }), + (this.updateHex = function (e) { + throw 'updateHex(hex) not supported for this alg:prov=' + this.algProvName; + }), + (this.sign = function () { + throw 'sign() not supported for this alg:prov=' + this.algProvName; + }), + (this.signString = function (e) { + throw 'digestString(str) not supported for this alg:prov=' + this.algProvName; + }), + (this.signHex = function (e) { + throw 'digestHex(hex) not supported for this alg:prov=' + this.algProvName; + }), + (this.verify = function (e) { + throw 'verify(hSigVal) not supported for this alg:prov=' + this.algProvName; + }), + (this.initParams = e), + void 0 !== e && + (void 0 !== e.alg && + ((this.algName = e.alg), + void 0 === e.prov + ? (this.provName = K.crypto.Util.DEFAULTPROVIDER[this.algName]) + : (this.provName = e.prov), + (this.algProvName = this.algName + ':' + this.provName), + this.setAlgAndProvider(this.algName, this.provName), + this._setAlgNames()), + void 0 !== e.psssaltlen && (this.pssSaltLen = e.psssaltlen), + void 0 !== e.prvkeypem)) + ) { + if (void 0 !== e.prvkeypas) + throw 'both prvkeypem and prvkeypas parameters not supported'; + try { + t = z.getKey(e.prvkeypem); + this.init(t); + } catch (e) { + throw 'fatal error to load pem private key: ' + e; + } + } + }), + (K.crypto.Cipher = function (e) {}), + (K.crypto.Cipher.encrypt = function (e, t, r) { + if (t instanceof RSAKey && t.isPublic) { + var n = K.crypto.Cipher.getAlgByKeyAndName(t, r); + if ('RSA' === n) return t.encrypt(e); + if ('RSAOAEP' === n) return t.encryptOAEP(e, 'sha1'); + var i = n.match(/^RSAOAEP(\d+)$/); + if (null !== i) return t.encryptOAEP(e, 'sha' + i[1]); + throw 'Cipher.encrypt: unsupported algorithm for RSAKey: ' + r; + } + throw 'Cipher.encrypt: unsupported key or algorithm'; + }), + (K.crypto.Cipher.decrypt = function (e, t, r) { + if (t instanceof RSAKey && t.isPrivate) { + var n = K.crypto.Cipher.getAlgByKeyAndName(t, r); + if ('RSA' === n) return t.decrypt(e); + if ('RSAOAEP' === n) return t.decryptOAEP(e, 'sha1'); + var i = n.match(/^RSAOAEP(\d+)$/); + if (null !== i) return t.decryptOAEP(e, 'sha' + i[1]); + throw 'Cipher.decrypt: unsupported algorithm for RSAKey: ' + r; + } + throw 'Cipher.decrypt: unsupported key or algorithm'; + }), + (K.crypto.Cipher.getAlgByKeyAndName = function (e, t) { + if (e instanceof RSAKey) { + if (-1 != ':RSA:RSAOAEP:RSAOAEP224:RSAOAEP256:RSAOAEP384:RSAOAEP512:'.indexOf(t)) + return t; + if (null === t || void 0 === t) return 'RSA'; + throw 'getAlgByKeyAndName: not supported algorithm name for RSAKey: ' + t; + } + throw 'getAlgByKeyAndName: not supported algorithm name: ' + t; + }), + (K.crypto.OID = new (function () { + this.oidhex2name = { + '2a864886f70d010101': 'rsaEncryption', + '2a8648ce3d0201': 'ecPublicKey', + '2a8648ce380401': 'dsa', + '2a8648ce3d030107': 'secp256r1', + '2b8104001f': 'secp192k1', + '2b81040021': 'secp224r1', + '2b8104000a': 'secp256k1', + '2b81040023': 'secp521r1', + '2b81040022': 'secp384r1', + '2a8648ce380403': 'SHA1withDSA', + '608648016503040301': 'SHA224withDSA', + '608648016503040302': 'SHA256withDSA', + }; + })()), + (void 0 !== K && K) || (K = {}), + (void 0 !== K.crypto && K.crypto) || (K.crypto = {}), + (K.crypto.ECDSA = function (e) { + var t = new SecureRandom(); + (this.type = 'EC'), + (this.isPrivate = !1), + (this.isPublic = !1), + (this.getBigRandom = function (e) { + return new BigInteger(e.bitLength(), t) + .mod(e.subtract(BigInteger.ONE)) + .add(BigInteger.ONE); + }), + (this.setNamedCurve = function (e) { + (this.ecparams = K.crypto.ECParameterDB.getByName(e)), + (this.prvKeyHex = null), + (this.pubKeyHex = null), + (this.curveName = e); + }), + (this.setPrivateKeyHex = function (e) { + (this.isPrivate = !0), (this.prvKeyHex = e); + }), + (this.setPublicKeyHex = function (e) { + (this.isPublic = !0), (this.pubKeyHex = e); + }), + (this.getPublicKeyXYHex = function () { + var e = this.pubKeyHex; + if ('04' !== e.substr(0, 2)) + throw 'this method supports uncompressed format(04) only'; + var t = this.ecparams.keylen / 4; + if (e.length !== 2 + 2 * t) throw 'malformed public key hex length'; + var r = {}; + return (r.x = e.substr(2, t)), (r.y = e.substr(2 + t)), r; + }), + (this.getShortNISTPCurveName = function () { + var e = this.curveName; + return 'secp256r1' === e || + 'NIST P-256' === e || + 'P-256' === e || + 'prime256v1' === e + ? 'P-256' + : 'secp384r1' === e || 'NIST P-384' === e || 'P-384' === e + ? 'P-384' + : null; + }), + (this.generateKeyPairHex = function () { + var e = this.ecparams.n, + t = this.getBigRandom(e), + r = this.ecparams.G.multiply(t), + n = r.getX().toBigInteger(), + i = r.getY().toBigInteger(), + o = this.ecparams.keylen / 4, + s = ('0000000000' + t.toString(16)).slice(-o), + a = + '04' + + ('0000000000' + n.toString(16)).slice(-o) + + ('0000000000' + i.toString(16)).slice(-o); + return ( + this.setPrivateKeyHex(s), this.setPublicKeyHex(a), { ecprvhex: s, ecpubhex: a } + ); + }), + (this.signWithMessageHash = function (e) { + return this.signHex(e, this.prvKeyHex); + }), + (this.signHex = function (e, t) { + var r = new BigInteger(t, 16), + n = this.ecparams.n, + i = new BigInteger(e, 16); + do { + var o = this.getBigRandom(n), + s = this.ecparams.G.multiply(o).getX().toBigInteger().mod(n); + } while (s.compareTo(BigInteger.ZERO) <= 0); + var a = o + .modInverse(n) + .multiply(i.add(r.multiply(s))) + .mod(n); + return K.crypto.ECDSA.biRSSigToASN1Sig(s, a); + }), + (this.sign = function (e, t) { + var r = t, + n = this.ecparams.n, + i = BigInteger.fromByteArrayUnsigned(e); + do { + var o = this.getBigRandom(n), + s = this.ecparams.G.multiply(o).getX().toBigInteger().mod(n); + } while (s.compareTo(BigInteger.ZERO) <= 0); + var a = o + .modInverse(n) + .multiply(i.add(r.multiply(s))) + .mod(n); + return this.serializeSig(s, a); + }), + (this.verifyWithMessageHash = function (e, t) { + return this.verifyHex(e, t, this.pubKeyHex); + }), + (this.verifyHex = function (e, t, r) { + var n, + i, + o, + s = K.crypto.ECDSA.parseSigHex(t); + (n = s.r), (i = s.s), (o = ECPointFp.decodeFromHex(this.ecparams.curve, r)); + var a = new BigInteger(e, 16); + return this.verifyRaw(a, n, i, o); + }), + (this.verify = function (e, t, r) { + var n, o, s; + if (Bitcoin.Util.isArray(t)) { + var a = this.parseSig(t); + (n = a.r), (o = a.s); + } else { + if ('object' !== (void 0 === t ? 'undefined' : i(t)) || !t.r || !t.s) + throw 'Invalid value for signature'; + (n = t.r), (o = t.s); + } + if (r instanceof ECPointFp) s = r; + else { + if (!Bitcoin.Util.isArray(r)) + throw 'Invalid format for pubkey value, must be byte array or ECPointFp'; + s = ECPointFp.decodeFrom(this.ecparams.curve, r); + } + var u = BigInteger.fromByteArrayUnsigned(e); + return this.verifyRaw(u, n, o, s); + }), + (this.verifyRaw = function (e, t, r, n) { + var i = this.ecparams.n, + o = this.ecparams.G; + if (t.compareTo(BigInteger.ONE) < 0 || t.compareTo(i) >= 0) return !1; + if (r.compareTo(BigInteger.ONE) < 0 || r.compareTo(i) >= 0) return !1; + var s = r.modInverse(i), + a = e.multiply(s).mod(i), + u = t.multiply(s).mod(i); + return o.multiply(a).add(n.multiply(u)).getX().toBigInteger().mod(i).equals(t); + }), + (this.serializeSig = function (e, t) { + var r = e.toByteArraySigned(), + n = t.toByteArraySigned(), + i = []; + return ( + i.push(2), + i.push(r.length), + (i = i.concat(r)).push(2), + i.push(n.length), + (i = i.concat(n)).unshift(i.length), + i.unshift(48), + i + ); + }), + (this.parseSig = function (e) { + var t; + if (48 != e[0]) throw new Error('Signature not a valid DERSequence'); + if (2 != e[(t = 2)]) + throw new Error('First element in signature must be a DERInteger'); + var r = e.slice(t + 2, t + 2 + e[t + 1]); + if (2 != e[(t += 2 + e[t + 1])]) + throw new Error('Second element in signature must be a DERInteger'); + var n = e.slice(t + 2, t + 2 + e[t + 1]); + return ( + (t += 2 + e[t + 1]), + { + r: BigInteger.fromByteArrayUnsigned(r), + s: BigInteger.fromByteArrayUnsigned(n), + } + ); + }), + (this.parseSigCompact = function (e) { + if (65 !== e.length) throw 'Signature has the wrong length'; + var t = e[0] - 27; + if (t < 0 || t > 7) throw 'Invalid signature type'; + var r = this.ecparams.n; + return { + r: BigInteger.fromByteArrayUnsigned(e.slice(1, 33)).mod(r), + s: BigInteger.fromByteArrayUnsigned(e.slice(33, 65)).mod(r), + i: t, + }; + }), + (this.readPKCS5PrvKeyHex = function (e) { + var t, + r, + n, + i = J, + o = K.crypto.ECDSA.getName, + s = i.getVbyList; + if (!1 === i.isASN1HEX(e)) throw 'not ASN.1 hex string'; + try { + (t = s(e, 0, [2, 0], '06')), (r = s(e, 0, [1], '04')); + try { + n = s(e, 0, [3, 0], '03').substr(2); + } catch (e) {} + } catch (e) { + throw 'malformed PKCS#1/5 plain ECC private key'; + } + if (((this.curveName = o(t)), void 0 === this.curveName)) + throw 'unsupported curve name'; + this.setNamedCurve(this.curveName), + this.setPublicKeyHex(n), + this.setPrivateKeyHex(r), + (this.isPublic = !1); + }), + (this.readPKCS8PrvKeyHex = function (e) { + var t, + r, + n, + i = J, + o = K.crypto.ECDSA.getName, + s = i.getVbyList; + if (!1 === i.isASN1HEX(e)) throw 'not ASN.1 hex string'; + try { + s(e, 0, [1, 0], '06'), + (t = s(e, 0, [1, 1], '06')), + (r = s(e, 0, [2, 0, 1], '04')); + try { + n = s(e, 0, [2, 0, 2, 0], '03').substr(2); + } catch (e) {} + } catch (e) { + throw 'malformed PKCS#8 plain ECC private key'; + } + if (((this.curveName = o(t)), void 0 === this.curveName)) + throw 'unsupported curve name'; + this.setNamedCurve(this.curveName), + this.setPublicKeyHex(n), + this.setPrivateKeyHex(r), + (this.isPublic = !1); + }), + (this.readPKCS8PubKeyHex = function (e) { + var t, + r, + n = J, + i = K.crypto.ECDSA.getName, + o = n.getVbyList; + if (!1 === n.isASN1HEX(e)) throw 'not ASN.1 hex string'; + try { + o(e, 0, [0, 0], '06'), + (t = o(e, 0, [0, 1], '06')), + (r = o(e, 0, [1], '03').substr(2)); + } catch (e) { + throw 'malformed PKCS#8 ECC public key'; + } + if (((this.curveName = i(t)), null === this.curveName)) + throw 'unsupported curve name'; + this.setNamedCurve(this.curveName), this.setPublicKeyHex(r); + }), + (this.readCertPubKeyHex = function (e, t) { + 5 !== t && (t = 6); + var r, + n, + i = J, + o = K.crypto.ECDSA.getName, + s = i.getVbyList; + if (!1 === i.isASN1HEX(e)) throw 'not ASN.1 hex string'; + try { + (r = s(e, 0, [0, t, 0, 1], '06')), (n = s(e, 0, [0, t, 1], '03').substr(2)); + } catch (e) { + throw 'malformed X.509 certificate ECC public key'; + } + if (((this.curveName = o(r)), null === this.curveName)) + throw 'unsupported curve name'; + this.setNamedCurve(this.curveName), this.setPublicKeyHex(n); + }), + void 0 !== e && void 0 !== e.curve && (this.curveName = e.curve), + void 0 === this.curveName && (this.curveName = 'secp256r1'), + this.setNamedCurve(this.curveName), + void 0 !== e && + (void 0 !== e.prv && this.setPrivateKeyHex(e.prv), + void 0 !== e.pub && this.setPublicKeyHex(e.pub)); + }), + (K.crypto.ECDSA.parseSigHex = function (e) { + var t = K.crypto.ECDSA.parseSigHexInHexRS(e); + return { r: new BigInteger(t.r, 16), s: new BigInteger(t.s, 16) }; + }), + (K.crypto.ECDSA.parseSigHexInHexRS = function (e) { + var t = J, + r = t.getChildIdx, + n = t.getV; + if ('30' != e.substr(0, 2)) throw 'signature is not a ASN.1 sequence'; + var i = r(e, 0); + if (2 != i.length) throw 'number of signature ASN.1 sequence elements seem wrong'; + var o = i[0], + s = i[1]; + if ('02' != e.substr(o, 2)) + throw '1st item of sequene of signature is not ASN.1 integer'; + if ('02' != e.substr(s, 2)) + throw '2nd item of sequene of signature is not ASN.1 integer'; + return { r: n(e, o), s: n(e, s) }; + }), + (K.crypto.ECDSA.asn1SigToConcatSig = function (e) { + var t = K.crypto.ECDSA.parseSigHexInHexRS(e), + r = t.r, + n = t.s; + if ( + ('00' == r.substr(0, 2) && r.length % 32 == 2 && (r = r.substr(2)), + '00' == n.substr(0, 2) && n.length % 32 == 2 && (n = n.substr(2)), + r.length % 32 == 30 && (r = '00' + r), + n.length % 32 == 30 && (n = '00' + n), + r.length % 32 != 0) + ) + throw 'unknown ECDSA sig r length error'; + if (n.length % 32 != 0) throw 'unknown ECDSA sig s length error'; + return r + n; + }), + (K.crypto.ECDSA.concatSigToASN1Sig = function (e) { + if (((e.length / 2) * 8) % 128 != 0) + throw 'unknown ECDSA concatinated r-s sig length error'; + var t = e.substr(0, e.length / 2), + r = e.substr(e.length / 2); + return K.crypto.ECDSA.hexRSSigToASN1Sig(t, r); + }), + (K.crypto.ECDSA.hexRSSigToASN1Sig = function (e, t) { + var r = new BigInteger(e, 16), + n = new BigInteger(t, 16); + return K.crypto.ECDSA.biRSSigToASN1Sig(r, n); + }), + (K.crypto.ECDSA.biRSSigToASN1Sig = function (e, t) { + var r = K.asn1, + n = new r.DERInteger({ bigint: e }), + i = new r.DERInteger({ bigint: t }); + return new r.DERSequence({ array: [n, i] }).getEncodedHex(); + }), + (K.crypto.ECDSA.getName = function (e) { + return '2a8648ce3d030107' === e + ? 'secp256r1' + : '2b8104000a' === e + ? 'secp256k1' + : '2b81040022' === e + ? 'secp384r1' + : -1 !== '|secp256r1|NIST P-256|P-256|prime256v1|'.indexOf(e) + ? 'secp256r1' + : -1 !== '|secp256k1|'.indexOf(e) + ? 'secp256k1' + : -1 !== '|secp384r1|NIST P-384|P-384|'.indexOf(e) + ? 'secp384r1' + : null; + }), + (void 0 !== K && K) || (K = {}), + (void 0 !== K.crypto && K.crypto) || (K.crypto = {}), + (K.crypto.ECParameterDB = new (function () { + var e = {}, + t = {}; + function a(e) { + return new BigInteger(e, 16); + } + (this.getByName = function (r) { + var n = r; + if ((void 0 !== t[n] && (n = t[r]), void 0 !== e[n])) return e[n]; + throw 'unregistered EC curve name: ' + n; + }), + (this.regist = function (r, n, i, o, s, u, c, h, f, l, g, p) { + e[r] = {}; + var d = a(i), + v = a(o), + y = a(s), + m = a(u), + S = a(c), + F = new ECCurveFp(d, v, y), + b = F.decodePointHex('04' + h + f); + (e[r].name = r), + (e[r].keylen = n), + (e[r].curve = F), + (e[r].G = b), + (e[r].n = m), + (e[r].h = S), + (e[r].oid = g), + (e[r].info = p); + for (var _ = 0; _ < l.length; _++) t[l[_]] = r; + }); + })()), + K.crypto.ECParameterDB.regist( + 'secp128r1', + 128, + 'FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF', + 'FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFC', + 'E87579C11079F43DD824993C2CEE5ED3', + 'FFFFFFFE0000000075A30D1B9038A115', + '1', + '161FF7528B899B2D0C28607CA52C5B86', + 'CF5AC8395BAFEB13C02DA292DDED7A83', + [], + '', + 'secp128r1 : SECG curve over a 128 bit prime field' + ), + K.crypto.ECParameterDB.regist( + 'secp160k1', + 160, + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73', + '0', + '7', + '0100000000000000000001B8FA16DFAB9ACA16B6B3', + '1', + '3B4C382CE37AA192A4019E763036F4F5DD4D7EBB', + '938CF935318FDCED6BC28286531733C3F03C4FEE', + [], + '', + 'secp160k1 : SECG curve over a 160 bit prime field' + ), + K.crypto.ECParameterDB.regist( + 'secp160r1', + 160, + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC', + '1C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45', + '0100000000000000000001F4C8F927AED3CA752257', + '1', + '4A96B5688EF573284664698968C38BB913CBFC82', + '23A628553168947D59DCC912042351377AC5FB32', + [], + '', + 'secp160r1 : SECG curve over a 160 bit prime field' + ), + K.crypto.ECParameterDB.regist( + 'secp192k1', + 192, + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37', + '0', + '3', + 'FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D', + '1', + 'DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D', + '9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D', + [] + ), + K.crypto.ECParameterDB.regist( + 'secp192r1', + 192, + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC', + '64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1', + 'FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831', + '1', + '188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012', + '07192B95FFC8DA78631011ED6B24CDD573F977A11E794811', + [] + ), + K.crypto.ECParameterDB.regist( + 'secp224r1', + 224, + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE', + 'B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D', + '1', + 'B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21', + 'BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34', + [] + ), + K.crypto.ECParameterDB.regist( + 'secp256k1', + 256, + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', + '0', + '7', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', + '1', + '79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', + '483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8', + [] + ), + K.crypto.ECParameterDB.regist( + 'secp256r1', + 256, + 'FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF', + 'FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC', + '5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B', + 'FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551', + '1', + '6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296', + '4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5', + ['NIST P-256', 'P-256', 'prime256v1'] + ), + K.crypto.ECParameterDB.regist( + 'secp384r1', + 384, + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC', + 'B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973', + '1', + 'AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7', + '3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f', + ['NIST P-384', 'P-384'] + ), + K.crypto.ECParameterDB.regist( + 'secp521r1', + 521, + '1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', + '1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC', + '051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00', + '1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409', + '1', + 'C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66', + '011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650', + ['NIST P-521', 'P-521'] + ); + var z = (function () { + var t = function d(e, t, n) { + return r(y.AES, e, t, n); + }, + r = function k(e, t, r, n) { + var i = y.enc.Hex.parse(t), + o = y.enc.Hex.parse(r), + s = y.enc.Hex.parse(n), + a = {}; + (a.key = o), (a.iv = s), (a.ciphertext = i); + var u = e.decrypt(a, o, { iv: s }); + return y.enc.Hex.stringify(u); + }, + n = function l(e, t, r) { + return i(y.AES, e, t, r); + }, + i = function g(e, t, r, n) { + var i = y.enc.Hex.parse(t), + o = y.enc.Hex.parse(r), + s = y.enc.Hex.parse(n), + a = e.encrypt(i, o, { iv: s }), + u = y.enc.Hex.parse(a.toString()); + return y.enc.Base64.stringify(u); + }, + s = { + 'AES-256-CBC': { proc: t, eproc: n, keylen: 32, ivlen: 16 }, + 'AES-192-CBC': { proc: t, eproc: n, keylen: 24, ivlen: 16 }, + 'AES-128-CBC': { proc: t, eproc: n, keylen: 16, ivlen: 16 }, + 'DES-EDE3-CBC': { + proc: function e(t, n, i) { + return r(y.TripleDES, t, n, i); + }, + eproc: function o(e, t, r) { + return i(y.TripleDES, e, t, r); + }, + keylen: 24, + ivlen: 8, + }, + 'DES-CBC': { + proc: function a(e, t, n) { + return r(y.DES, e, t, n); + }, + eproc: function f(e, t, r) { + return i(y.DES, e, t, r); + }, + keylen: 8, + ivlen: 8, + }, + }, + u = function n(e) { + var t = {}, + r = e.match(new RegExp('DEK-Info: ([^,]+),([0-9A-Fa-f]+)', 'm')); + r && ((t.cipher = r[1]), (t.ivsalt = r[2])); + var i = e.match(new RegExp('-----BEGIN ([A-Z]+) PRIVATE KEY-----')); + i && (t.type = i[1]); + var o = -1, + s = 0; + -1 != e.indexOf('\r\n\r\n') && ((o = e.indexOf('\r\n\r\n')), (s = 2)), + -1 != e.indexOf('\n\n') && ((o = e.indexOf('\n\n')), (s = 1)); + var a = e.indexOf('-----END'); + if (-1 != o && -1 != a) { + var u = e.substring(o + 2 * s, a - s); + (u = u.replace(/\s+/g, '')), (t.data = u); + } + return t; + }, + c = function j(e, t, r) { + for ( + var n = r.substring(0, 16), + i = y.enc.Hex.parse(n), + o = y.enc.Utf8.parse(t), + a = s[e].keylen + s[e].ivlen, + u = '', + c = null; + ; + + ) { + var h = y.algo.MD5.create(); + if ( + (null != c && h.update(c), + h.update(o), + h.update(i), + (c = h.finalize()), + (u += y.enc.Hex.stringify(c)).length >= 2 * a) + ) + break; + } + var f = {}; + return ( + (f.keyhex = u.substr(0, 2 * s[e].keylen)), + (f.ivhex = u.substr(2 * s[e].keylen, 2 * s[e].ivlen)), + f + ); + }, + g = function b(e, t, r, n) { + var i = y.enc.Base64.parse(e), + o = y.enc.Hex.stringify(i); + return (0, s[t].proc)(o, r, n); + }; + return { + version: '1.0.0', + parsePKCS5PEM: function parsePKCS5PEM(e) { + return u(e); + }, + getKeyAndUnusedIvByPasscodeAndIvsalt: function getKeyAndUnusedIvByPasscodeAndIvsalt( + e, + t, + r + ) { + return c(e, t, r); + }, + decryptKeyB64: function decryptKeyB64(e, t, r, n) { + return g(e, t, r, n); + }, + getDecryptedKeyHex: function getDecryptedKeyHex(e, t) { + var r = u(e), + n = (r.type, r.cipher), + i = r.ivsalt, + o = r.data, + s = c(n, t, i).keyhex; + return g(o, n, s, i); + }, + getEncryptedPKCS5PEMFromPrvKeyHex: function getEncryptedPKCS5PEMFromPrvKeyHex( + e, + t, + r, + n, + i + ) { + var o = ''; + if (((void 0 !== n && null != n) || (n = 'AES-256-CBC'), void 0 === s[n])) + throw 'KEYUTIL unsupported algorithm: ' + n; + (void 0 !== i && null != i) || + (i = (function m(e) { + var t = y.lib.WordArray.random(e); + return y.enc.Hex.stringify(t); + })(s[n].ivlen).toUpperCase()); + var a = (function h(e, t, r, n) { + return (0, s[t].eproc)(e, r, n); + })(t, n, c(n, r, i).keyhex, i); + o = '-----BEGIN ' + e + ' PRIVATE KEY-----\r\n'; + return ( + (o += 'Proc-Type: 4,ENCRYPTED\r\n'), + (o += 'DEK-Info: ' + n + ',' + i + '\r\n'), + (o += '\r\n'), + (o += a.replace(/(.{64})/g, '$1\r\n')), + (o += '\r\n-----END ' + e + ' PRIVATE KEY-----\r\n') + ); + }, + parseHexOfEncryptedPKCS8: function parseHexOfEncryptedPKCS8(e) { + var t = J, + r = t.getChildIdx, + n = t.getV, + i = {}, + o = r(e, 0); + if (2 != o.length) throw 'malformed format: SEQUENCE(0).items != 2: ' + o.length; + i.ciphertext = n(e, o[1]); + var s = r(e, o[0]); + if (2 != s.length) throw 'malformed format: SEQUENCE(0.0).items != 2: ' + s.length; + if ('2a864886f70d01050d' != n(e, s[0])) throw 'this only supports pkcs5PBES2'; + var a = r(e, s[1]); + if (2 != s.length) throw 'malformed format: SEQUENCE(0.0.1).items != 2: ' + a.length; + var u = r(e, a[1]); + if (2 != u.length) + throw 'malformed format: SEQUENCE(0.0.1.1).items != 2: ' + u.length; + if ('2a864886f70d0307' != n(e, u[0])) throw 'this only supports TripleDES'; + (i.encryptionSchemeAlg = 'TripleDES'), (i.encryptionSchemeIV = n(e, u[1])); + var c = r(e, a[0]); + if (2 != c.length) + throw 'malformed format: SEQUENCE(0.0.1.0).items != 2: ' + c.length; + if ('2a864886f70d01050c' != n(e, c[0])) throw 'this only supports pkcs5PBKDF2'; + var h = r(e, c[1]); + if (h.length < 2) + throw 'malformed format: SEQUENCE(0.0.1.0.1).items < 2: ' + h.length; + i.pbkdf2Salt = n(e, h[0]); + var f = n(e, h[1]); + try { + i.pbkdf2Iter = parseInt(f, 16); + } catch (e) { + throw 'malformed format pbkdf2Iter: ' + f; + } + return i; + }, + getPBKDF2KeyHexFromParam: function getPBKDF2KeyHexFromParam(e, t) { + var r = y.enc.Hex.parse(e.pbkdf2Salt), + n = e.pbkdf2Iter, + i = y.PBKDF2(t, r, { keySize: 6, iterations: n }); + return y.enc.Hex.stringify(i); + }, + _getPlainPKCS8HexFromEncryptedPKCS8PEM: function _getPlainPKCS8HexFromEncryptedPKCS8PEM( + e, + t + ) { + var r = pemtohex(e, 'ENCRYPTED PRIVATE KEY'), + n = this.parseHexOfEncryptedPKCS8(r), + i = z.getPBKDF2KeyHexFromParam(n, t), + o = {}; + o.ciphertext = y.enc.Hex.parse(n.ciphertext); + var s = y.enc.Hex.parse(i), + a = y.enc.Hex.parse(n.encryptionSchemeIV), + u = y.TripleDES.decrypt(o, s, { iv: a }); + return y.enc.Hex.stringify(u); + }, + getKeyFromEncryptedPKCS8PEM: function getKeyFromEncryptedPKCS8PEM(e, t) { + var r = this._getPlainPKCS8HexFromEncryptedPKCS8PEM(e, t); + return this.getKeyFromPlainPrivatePKCS8Hex(r); + }, + parsePlainPrivatePKCS8Hex: function parsePlainPrivatePKCS8Hex(e) { + var t = J, + r = t.getChildIdx, + n = t.getV, + i = { algparam: null }; + if ('30' != e.substr(0, 2)) throw 'malformed plain PKCS8 private key(code:001)'; + var o = r(e, 0); + if (3 != o.length) throw 'malformed plain PKCS8 private key(code:002)'; + if ('30' != e.substr(o[1], 2)) throw 'malformed PKCS8 private key(code:003)'; + var s = r(e, o[1]); + if (2 != s.length) throw 'malformed PKCS8 private key(code:004)'; + if ('06' != e.substr(s[0], 2)) throw 'malformed PKCS8 private key(code:005)'; + if ( + ((i.algoid = n(e, s[0])), + '06' == e.substr(s[1], 2) && (i.algparam = n(e, s[1])), + '04' != e.substr(o[2], 2)) + ) + throw 'malformed PKCS8 private key(code:006)'; + return (i.keyidx = t.getVidx(e, o[2])), i; + }, + getKeyFromPlainPrivatePKCS8PEM: function getKeyFromPlainPrivatePKCS8PEM(e) { + var t = pemtohex(e, 'PRIVATE KEY'); + return this.getKeyFromPlainPrivatePKCS8Hex(t); + }, + getKeyFromPlainPrivatePKCS8Hex: function getKeyFromPlainPrivatePKCS8Hex(e) { + var t, + r = this.parsePlainPrivatePKCS8Hex(e); + if ('2a864886f70d010101' == r.algoid) t = new RSAKey(); + else if ('2a8648ce380401' == r.algoid) t = new K.crypto.DSA(); + else { + if ('2a8648ce3d0201' != r.algoid) throw 'unsupported private key algorithm'; + t = new K.crypto.ECDSA(); + } + return t.readPKCS8PrvKeyHex(e), t; + }, + _getKeyFromPublicPKCS8Hex: function _getKeyFromPublicPKCS8Hex(e) { + var t, + r = J.getVbyList(e, 0, [0, 0], '06'); + if ('2a864886f70d010101' === r) t = new RSAKey(); + else if ('2a8648ce380401' === r) t = new K.crypto.DSA(); + else { + if ('2a8648ce3d0201' !== r) throw 'unsupported PKCS#8 public key hex'; + t = new K.crypto.ECDSA(); + } + return t.readPKCS8PubKeyHex(e), t; + }, + parsePublicRawRSAKeyHex: function parsePublicRawRSAKeyHex(e) { + var t = J, + r = t.getChildIdx, + n = t.getV, + i = {}; + if ('30' != e.substr(0, 2)) throw 'malformed RSA key(code:001)'; + var o = r(e, 0); + if (2 != o.length) throw 'malformed RSA key(code:002)'; + if ('02' != e.substr(o[0], 2)) throw 'malformed RSA key(code:003)'; + if (((i.n = n(e, o[0])), '02' != e.substr(o[1], 2))) + throw 'malformed RSA key(code:004)'; + return (i.e = n(e, o[1])), i; + }, + parsePublicPKCS8Hex: function parsePublicPKCS8Hex(e) { + var t = J, + r = t.getChildIdx, + n = t.getV, + i = { algparam: null }, + o = r(e, 0); + if (2 != o.length) throw 'outer DERSequence shall have 2 elements: ' + o.length; + var s = o[0]; + if ('30' != e.substr(s, 2)) throw 'malformed PKCS8 public key(code:001)'; + var a = r(e, s); + if (2 != a.length) throw 'malformed PKCS8 public key(code:002)'; + if ('06' != e.substr(a[0], 2)) throw 'malformed PKCS8 public key(code:003)'; + if ( + ((i.algoid = n(e, a[0])), + '06' == e.substr(a[1], 2) + ? (i.algparam = n(e, a[1])) + : '30' == e.substr(a[1], 2) && + ((i.algparam = {}), + (i.algparam.p = t.getVbyList(e, a[1], [0], '02')), + (i.algparam.q = t.getVbyList(e, a[1], [1], '02')), + (i.algparam.g = t.getVbyList(e, a[1], [2], '02'))), + '03' != e.substr(o[1], 2)) + ) + throw 'malformed PKCS8 public key(code:004)'; + return (i.key = n(e, o[1]).substr(2)), i; + }, + }; + })(); + (z.getKey = function (e, t, r) { + var n = (v = J).getChildIdx, + i = (v.getV, v.getVbyList), + o = K.crypto, + s = o.ECDSA, + a = o.DSA, + u = RSAKey, + c = pemtohex, + h = z; + if (void 0 !== u && e instanceof u) return e; + if (void 0 !== s && e instanceof s) return e; + if (void 0 !== a && e instanceof a) return e; + if (void 0 !== e.curve && void 0 !== e.xy && void 0 === e.d) + return new s({ pub: e.xy, curve: e.curve }); + if (void 0 !== e.curve && void 0 !== e.d) return new s({ prv: e.d, curve: e.curve }); + if (void 0 === e.kty && void 0 !== e.n && void 0 !== e.e && void 0 === e.d) + return (P = new u()).setPublic(e.n, e.e), P; + if ( + void 0 === e.kty && + void 0 !== e.n && + void 0 !== e.e && + void 0 !== e.d && + void 0 !== e.p && + void 0 !== e.q && + void 0 !== e.dp && + void 0 !== e.dq && + void 0 !== e.co && + void 0 === e.qi + ) + return (P = new u()).setPrivateEx(e.n, e.e, e.d, e.p, e.q, e.dp, e.dq, e.co), P; + if ( + void 0 === e.kty && + void 0 !== e.n && + void 0 !== e.e && + void 0 !== e.d && + void 0 === e.p + ) + return (P = new u()).setPrivate(e.n, e.e, e.d), P; + if ( + void 0 !== e.p && + void 0 !== e.q && + void 0 !== e.g && + void 0 !== e.y && + void 0 === e.x + ) + return (P = new a()).setPublic(e.p, e.q, e.g, e.y), P; + if ( + void 0 !== e.p && + void 0 !== e.q && + void 0 !== e.g && + void 0 !== e.y && + void 0 !== e.x + ) + return (P = new a()).setPrivate(e.p, e.q, e.g, e.y, e.x), P; + if ('RSA' === e.kty && void 0 !== e.n && void 0 !== e.e && void 0 === e.d) + return (P = new u()).setPublic(b64utohex(e.n), b64utohex(e.e)), P; + if ( + 'RSA' === e.kty && + void 0 !== e.n && + void 0 !== e.e && + void 0 !== e.d && + void 0 !== e.p && + void 0 !== e.q && + void 0 !== e.dp && + void 0 !== e.dq && + void 0 !== e.qi + ) + return ( + (P = new u()).setPrivateEx( + b64utohex(e.n), + b64utohex(e.e), + b64utohex(e.d), + b64utohex(e.p), + b64utohex(e.q), + b64utohex(e.dp), + b64utohex(e.dq), + b64utohex(e.qi) + ), + P + ); + if ('RSA' === e.kty && void 0 !== e.n && void 0 !== e.e && void 0 !== e.d) + return (P = new u()).setPrivate(b64utohex(e.n), b64utohex(e.e), b64utohex(e.d)), P; + if ( + 'EC' === e.kty && + void 0 !== e.crv && + void 0 !== e.x && + void 0 !== e.y && + void 0 === e.d + ) { + var f = (C = new s({ curve: e.crv })).ecparams.keylen / 4, + l = + '04' + + ('0000000000' + b64utohex(e.x)).slice(-f) + + ('0000000000' + b64utohex(e.y)).slice(-f); + return C.setPublicKeyHex(l), C; + } + if ( + 'EC' === e.kty && + void 0 !== e.crv && + void 0 !== e.x && + void 0 !== e.y && + void 0 !== e.d + ) { + (f = (C = new s({ curve: e.crv })).ecparams.keylen / 4), + (l = + '04' + + ('0000000000' + b64utohex(e.x)).slice(-f) + + ('0000000000' + b64utohex(e.y)).slice(-f)); + var g = ('0000000000' + b64utohex(e.d)).slice(-f); + return C.setPublicKeyHex(l), C.setPrivateKeyHex(g), C; + } + if ('pkcs5prv' === r) { + var p, + d = e, + v = J; + if (9 === (p = n(d, 0)).length) (P = new u()).readPKCS5PrvKeyHex(d); + else if (6 === p.length) (P = new a()).readPKCS5PrvKeyHex(d); + else { + if (!(p.length > 2 && '04' === d.substr(p[1], 2))) + throw 'unsupported PKCS#1/5 hexadecimal key'; + (P = new s()).readPKCS5PrvKeyHex(d); + } + return P; + } + if ('pkcs8prv' === r) return (P = h.getKeyFromPlainPrivatePKCS8Hex(e)); + if ('pkcs8pub' === r) return h._getKeyFromPublicPKCS8Hex(e); + if ('x509pub' === r) return X509.getPublicKeyFromCertHex(e); + if ( + -1 != e.indexOf('-END CERTIFICATE-', 0) || + -1 != e.indexOf('-END X509 CERTIFICATE-', 0) || + -1 != e.indexOf('-END TRUSTED CERTIFICATE-', 0) + ) + return X509.getPublicKeyFromCertPEM(e); + if (-1 != e.indexOf('-END PUBLIC KEY-')) { + var y = pemtohex(e, 'PUBLIC KEY'); + return h._getKeyFromPublicPKCS8Hex(y); + } + if (-1 != e.indexOf('-END RSA PRIVATE KEY-') && -1 == e.indexOf('4,ENCRYPTED')) { + var m = c(e, 'RSA PRIVATE KEY'); + return h.getKey(m, null, 'pkcs5prv'); + } + if (-1 != e.indexOf('-END DSA PRIVATE KEY-') && -1 == e.indexOf('4,ENCRYPTED')) { + var S = i((I = c(e, 'DSA PRIVATE KEY')), 0, [1], '02'), + F = i(I, 0, [2], '02'), + b = i(I, 0, [3], '02'), + _ = i(I, 0, [4], '02'), + w = i(I, 0, [5], '02'); + return ( + (P = new a()).setPrivate( + new BigInteger(S, 16), + new BigInteger(F, 16), + new BigInteger(b, 16), + new BigInteger(_, 16), + new BigInteger(w, 16) + ), + P + ); + } + if (-1 != e.indexOf('-END PRIVATE KEY-')) return h.getKeyFromPlainPrivatePKCS8PEM(e); + if (-1 != e.indexOf('-END RSA PRIVATE KEY-') && -1 != e.indexOf('4,ENCRYPTED')) { + var E = h.getDecryptedKeyHex(e, t), + x = new RSAKey(); + return x.readPKCS5PrvKeyHex(E), x; + } + if (-1 != e.indexOf('-END EC PRIVATE KEY-') && -1 != e.indexOf('4,ENCRYPTED')) { + var C, + P = i((I = h.getDecryptedKeyHex(e, t)), 0, [1], '04'), + A = i(I, 0, [2, 0], '06'), + k = i(I, 0, [3, 0], '03').substr(2); + if (void 0 === K.crypto.OID.oidhex2name[A]) + throw 'undefined OID(hex) in KJUR.crypto.OID: ' + A; + return ( + (C = new s({ + curve: K.crypto.OID.oidhex2name[A], + })).setPublicKeyHex(k), + C.setPrivateKeyHex(P), + (C.isPublic = !1), + C + ); + } + if (-1 != e.indexOf('-END DSA PRIVATE KEY-') && -1 != e.indexOf('4,ENCRYPTED')) { + var I; + (S = i((I = h.getDecryptedKeyHex(e, t)), 0, [1], '02')), + (F = i(I, 0, [2], '02')), + (b = i(I, 0, [3], '02')), + (_ = i(I, 0, [4], '02')), + (w = i(I, 0, [5], '02')); + return ( + (P = new a()).setPrivate( + new BigInteger(S, 16), + new BigInteger(F, 16), + new BigInteger(b, 16), + new BigInteger(_, 16), + new BigInteger(w, 16) + ), + P + ); + } + if (-1 != e.indexOf('-END ENCRYPTED PRIVATE KEY-')) + return h.getKeyFromEncryptedPKCS8PEM(e, t); + throw 'not supported argument'; + }), + (z.generateKeypair = function (e, t) { + if ('RSA' == e) { + var r = t; + (s = new RSAKey()).generate(r, '10001'), (s.isPrivate = !0), (s.isPublic = !0); + var n = new RSAKey(), + i = s.n.toString(16), + o = s.e.toString(16); + return ( + n.setPublic(i, o), + (n.isPrivate = !1), + (n.isPublic = !0), + ((a = {}).prvKeyObj = s), + (a.pubKeyObj = n), + a + ); + } + if ('EC' == e) { + var s, + a, + u = t, + c = new K.crypto.ECDSA({ curve: u }).generateKeyPairHex(); + return ( + (s = new K.crypto.ECDSA({ curve: u })).setPublicKeyHex(c.ecpubhex), + s.setPrivateKeyHex(c.ecprvhex), + (s.isPrivate = !0), + (s.isPublic = !1), + (n = new K.crypto.ECDSA({ curve: u })).setPublicKeyHex(c.ecpubhex), + (n.isPrivate = !1), + (n.isPublic = !0), + ((a = {}).prvKeyObj = s), + (a.pubKeyObj = n), + a + ); + } + throw 'unknown algorithm: ' + e; + }), + (z.getPEM = function (e, t, r, n, i, s) { + var a = K, + u = a.asn1, + c = u.DERObjectIdentifier, + h = u.DERInteger, + f = u.ASN1Util.newObject, + l = u.x509.SubjectPublicKeyInfo, + g = a.crypto, + p = g.DSA, + d = g.ECDSA, + v = RSAKey; + function A(e) { + return f({ + seq: [ + { int: 0 }, + { int: { bigint: e.n } }, + { int: e.e }, + { int: { bigint: e.d } }, + { int: { bigint: e.p } }, + { int: { bigint: e.q } }, + { int: { bigint: e.dmp1 } }, + { int: { bigint: e.dmq1 } }, + { int: { bigint: e.coeff } }, + ], + }); + } + function B(e) { + return f({ + seq: [ + { int: 1 }, + { octstr: { hex: e.prvKeyHex } }, + { tag: ['a0', !0, { oid: { name: e.curveName } }] }, + { tag: ['a1', !0, { bitstr: { hex: '00' + e.pubKeyHex } }] }, + ], + }); + } + function x(e) { + return f({ + seq: [ + { int: 0 }, + { int: { bigint: e.p } }, + { int: { bigint: e.q } }, + { int: { bigint: e.g } }, + { int: { bigint: e.y } }, + { int: { bigint: e.x } }, + ], + }); + } + if ( + ((void 0 !== v && e instanceof v) || + (void 0 !== p && e instanceof p) || + (void 0 !== d && e instanceof d)) && + 1 == e.isPublic && + (void 0 === t || 'PKCS8PUB' == t) + ) + return hextopem((b = new l(e).getEncodedHex()), 'PUBLIC KEY'); + if ( + 'PKCS1PRV' == t && + void 0 !== v && + e instanceof v && + (void 0 === r || null == r) && + 1 == e.isPrivate + ) + return hextopem((b = A(e).getEncodedHex()), 'RSA PRIVATE KEY'); + if ( + 'PKCS1PRV' == t && + void 0 !== d && + e instanceof d && + (void 0 === r || null == r) && + 1 == e.isPrivate + ) { + var m = new c({ name: e.curveName }).getEncodedHex(), + S = B(e).getEncodedHex(), + F = ''; + return (F += hextopem(m, 'EC PARAMETERS')), (F += hextopem(S, 'EC PRIVATE KEY')); + } + if ( + 'PKCS1PRV' == t && + void 0 !== p && + e instanceof p && + (void 0 === r || null == r) && + 1 == e.isPrivate + ) + return hextopem((b = x(e).getEncodedHex()), 'DSA PRIVATE KEY'); + if ( + 'PKCS5PRV' == t && + void 0 !== v && + e instanceof v && + void 0 !== r && + null != r && + 1 == e.isPrivate + ) { + var b = A(e).getEncodedHex(); + return ( + void 0 === n && (n = 'DES-EDE3-CBC'), + this.getEncryptedPKCS5PEMFromPrvKeyHex('RSA', b, r, n, s) + ); + } + if ( + 'PKCS5PRV' == t && + void 0 !== d && + e instanceof d && + void 0 !== r && + null != r && + 1 == e.isPrivate + ) { + b = B(e).getEncodedHex(); + return ( + void 0 === n && (n = 'DES-EDE3-CBC'), + this.getEncryptedPKCS5PEMFromPrvKeyHex('EC', b, r, n, s) + ); + } + if ( + 'PKCS5PRV' == t && + void 0 !== p && + e instanceof p && + void 0 !== r && + null != r && + 1 == e.isPrivate + ) { + b = x(e).getEncodedHex(); + return ( + void 0 === n && (n = 'DES-EDE3-CBC'), + this.getEncryptedPKCS5PEMFromPrvKeyHex('DSA', b, r, n, s) + ); + } + var _ = function o(e, t) { + var r = w(e, t); + return new f({ + seq: [ + { + seq: [ + { oid: { name: 'pkcs5PBES2' } }, + { + seq: [ + { + seq: [ + { oid: { name: 'pkcs5PBKDF2' } }, + { + seq: [{ octstr: { hex: r.pbkdf2Salt } }, { int: r.pbkdf2Iter }], + }, + ], + }, + { + seq: [ + { oid: { name: 'des-EDE3-CBC' } }, + { octstr: { hex: r.encryptionSchemeIV } }, + ], + }, + ], + }, + ], + }, + { octstr: { hex: r.ciphertext } }, + ], + }).getEncodedHex(); + }, + w = function c(e, t) { + var r = y.lib.WordArray.random(8), + n = y.lib.WordArray.random(8), + i = y.PBKDF2(t, r, { keySize: 6, iterations: 100 }), + o = y.enc.Hex.parse(e), + s = y.TripleDES.encrypt(o, i, { iv: n }) + '', + a = {}; + return ( + (a.ciphertext = s), + (a.pbkdf2Salt = y.enc.Hex.stringify(r)), + (a.pbkdf2Iter = 100), + (a.encryptionSchemeAlg = 'DES-EDE3-CBC'), + (a.encryptionSchemeIV = y.enc.Hex.stringify(n)), + a + ); + }; + if ('PKCS8PRV' == t && void 0 != v && e instanceof v && 1 == e.isPrivate) { + var E = A(e).getEncodedHex(); + b = f({ + seq: [ + { int: 0 }, + { seq: [{ oid: { name: 'rsaEncryption' } }, { null: !0 }] }, + { octstr: { hex: E } }, + ], + }).getEncodedHex(); + return void 0 === r || null == r + ? hextopem(b, 'PRIVATE KEY') + : hextopem((S = _(b, r)), 'ENCRYPTED PRIVATE KEY'); + } + if ('PKCS8PRV' == t && void 0 !== d && e instanceof d && 1 == e.isPrivate) { + (E = new f({ + seq: [ + { int: 1 }, + { octstr: { hex: e.prvKeyHex } }, + { tag: ['a1', !0, { bitstr: { hex: '00' + e.pubKeyHex } }] }, + ], + }).getEncodedHex()), + (b = f({ + seq: [ + { int: 0 }, + { + seq: [{ oid: { name: 'ecPublicKey' } }, { oid: { name: e.curveName } }], + }, + { octstr: { hex: E } }, + ], + }).getEncodedHex()); + return void 0 === r || null == r + ? hextopem(b, 'PRIVATE KEY') + : hextopem((S = _(b, r)), 'ENCRYPTED PRIVATE KEY'); + } + if ('PKCS8PRV' == t && void 0 !== p && e instanceof p && 1 == e.isPrivate) { + (E = new h({ bigint: e.x }).getEncodedHex()), + (b = f({ + seq: [ + { int: 0 }, + { + seq: [ + { oid: { name: 'dsa' } }, + { + seq: [ + { int: { bigint: e.p } }, + { int: { bigint: e.q } }, + { int: { bigint: e.g } }, + ], + }, + ], + }, + { octstr: { hex: E } }, + ], + }).getEncodedHex()); + return void 0 === r || null == r + ? hextopem(b, 'PRIVATE KEY') + : hextopem((S = _(b, r)), 'ENCRYPTED PRIVATE KEY'); + } + throw 'unsupported object nor format'; + }), + (z.getKeyFromCSRPEM = function (e) { + var t = pemtohex(e, 'CERTIFICATE REQUEST'); + return z.getKeyFromCSRHex(t); + }), + (z.getKeyFromCSRHex = function (e) { + var t = z.parseCSRHex(e); + return z.getKey(t.p8pubkeyhex, null, 'pkcs8pub'); + }), + (z.parseCSRHex = function (e) { + var t = J, + r = t.getChildIdx, + n = t.getTLV, + i = {}, + o = e; + if ('30' != o.substr(0, 2)) throw 'malformed CSR(code:001)'; + var s = r(o, 0); + if (s.length < 1) throw 'malformed CSR(code:002)'; + if ('30' != o.substr(s[0], 2)) throw 'malformed CSR(code:003)'; + var a = r(o, s[0]); + if (a.length < 3) throw 'malformed CSR(code:004)'; + return (i.p8pubkeyhex = n(o, a[2])), i; + }), + (z.getJWKFromKey = function (e) { + var t = {}; + if (e instanceof RSAKey && e.isPrivate) + return ( + (t.kty = 'RSA'), + (t.n = hextob64u(e.n.toString(16))), + (t.e = hextob64u(e.e.toString(16))), + (t.d = hextob64u(e.d.toString(16))), + (t.p = hextob64u(e.p.toString(16))), + (t.q = hextob64u(e.q.toString(16))), + (t.dp = hextob64u(e.dmp1.toString(16))), + (t.dq = hextob64u(e.dmq1.toString(16))), + (t.qi = hextob64u(e.coeff.toString(16))), + t + ); + if (e instanceof RSAKey && e.isPublic) + return ( + (t.kty = 'RSA'), + (t.n = hextob64u(e.n.toString(16))), + (t.e = hextob64u(e.e.toString(16))), + t + ); + if (e instanceof K.crypto.ECDSA && e.isPrivate) { + if ('P-256' !== (n = e.getShortNISTPCurveName()) && 'P-384' !== n) + throw 'unsupported curve name for JWT: ' + n; + var r = e.getPublicKeyXYHex(); + return ( + (t.kty = 'EC'), + (t.crv = n), + (t.x = hextob64u(r.x)), + (t.y = hextob64u(r.y)), + (t.d = hextob64u(e.prvKeyHex)), + t + ); + } + if (e instanceof K.crypto.ECDSA && e.isPublic) { + var n; + if ('P-256' !== (n = e.getShortNISTPCurveName()) && 'P-384' !== n) + throw 'unsupported curve name for JWT: ' + n; + r = e.getPublicKeyXYHex(); + return (t.kty = 'EC'), (t.crv = n), (t.x = hextob64u(r.x)), (t.y = hextob64u(r.y)), t; + } + throw 'not supported key object'; + }), + (RSAKey.getPosArrayOfChildrenFromHex = function (e) { + return J.getChildIdx(e, 0); + }), + (RSAKey.getHexValueArrayOfChildrenFromHex = function (e) { + var t, + r = J.getV, + n = r(e, (t = RSAKey.getPosArrayOfChildrenFromHex(e))[0]), + i = r(e, t[1]), + o = r(e, t[2]), + s = r(e, t[3]), + a = r(e, t[4]), + u = r(e, t[5]), + c = r(e, t[6]), + h = r(e, t[7]), + f = r(e, t[8]); + return (t = new Array()).push(n, i, o, s, a, u, c, h, f), t; + }), + (RSAKey.prototype.readPrivateKeyFromPEMString = function (e) { + var t = pemtohex(e), + r = RSAKey.getHexValueArrayOfChildrenFromHex(t); + this.setPrivateEx(r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8]); + }), + (RSAKey.prototype.readPKCS5PrvKeyHex = function (e) { + var t = RSAKey.getHexValueArrayOfChildrenFromHex(e); + this.setPrivateEx(t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8]); + }), + (RSAKey.prototype.readPKCS8PrvKeyHex = function (e) { + var t, + r, + n, + i, + o, + s, + a, + u, + c = J, + h = c.getVbyList; + if (!1 === c.isASN1HEX(e)) throw 'not ASN.1 hex string'; + try { + (t = h(e, 0, [2, 0, 1], '02')), + (r = h(e, 0, [2, 0, 2], '02')), + (n = h(e, 0, [2, 0, 3], '02')), + (i = h(e, 0, [2, 0, 4], '02')), + (o = h(e, 0, [2, 0, 5], '02')), + (s = h(e, 0, [2, 0, 6], '02')), + (a = h(e, 0, [2, 0, 7], '02')), + (u = h(e, 0, [2, 0, 8], '02')); + } catch (e) { + throw 'malformed PKCS#8 plain RSA private key'; + } + this.setPrivateEx(t, r, n, i, o, s, a, u); + }), + (RSAKey.prototype.readPKCS5PubKeyHex = function (e) { + var t = J, + r = t.getV; + if (!1 === t.isASN1HEX(e)) throw 'keyHex is not ASN.1 hex string'; + var n = t.getChildIdx(e, 0); + if (2 !== n.length || '02' !== e.substr(n[0], 2) || '02' !== e.substr(n[1], 2)) + throw 'wrong hex for PKCS#5 public key'; + var i = r(e, n[0]), + o = r(e, n[1]); + this.setPublic(i, o); + }), + (RSAKey.prototype.readPKCS8PubKeyHex = function (e) { + var t = J; + if (!1 === t.isASN1HEX(e)) throw 'not ASN.1 hex string'; + if ('06092a864886f70d010101' !== t.getTLVbyList(e, 0, [0, 0])) + throw 'not PKCS8 RSA public key'; + var r = t.getTLVbyList(e, 0, [1, 0]); + this.readPKCS5PubKeyHex(r); + }), + (RSAKey.prototype.readCertPubKeyHex = function (e, t) { + var r, n; + (r = new X509()).readCertHex(e), (n = r.getPublicKeyHex()), this.readPKCS8PubKeyHex(n); + }); + var Y = new RegExp(''); + function _zeroPaddingOfSignature(e, t) { + for (var r = '', n = t / 4 - e.length, i = 0; i < n; i++) r += '0'; + return r + e; + } + function pss_mgf1_str(e, t, r) { + for (var n = '', i = 0; n.length < t; ) + (n += hextorstr( + r( + rstrtohex( + e + + String.fromCharCode.apply(String, [ + (4278190080 & i) >> 24, + (16711680 & i) >> 16, + (65280 & i) >> 8, + 255 & i, + ]) + ) + ) + )), + (i += 1); + return n; + } + function _rsasign_getAlgNameAndHashFromHexDisgestInfo(e) { + for (var t in K.crypto.Util.DIGESTINFOHEAD) { + var r = K.crypto.Util.DIGESTINFOHEAD[t], + n = r.length; + if (e.substring(0, n) == r) return [t, e.substring(n)]; + } + return []; + } + function X509() { + var e = J, + t = e.getChildIdx, + r = e.getV, + n = e.getTLV, + i = e.getVbyList, + o = e.getTLVbyList, + s = e.getIdxbyList, + a = e.getVidx, + u = e.oidname, + c = X509, + h = pemtohex; + (this.hex = null), + (this.version = 0), + (this.foffset = 0), + (this.aExtInfo = null), + (this.getVersion = function () { + return null === this.hex || 0 !== this.version + ? this.version + : 'a003020102' !== o(this.hex, 0, [0, 0]) + ? ((this.version = 1), (this.foffset = -1), 1) + : ((this.version = 3), 3); + }), + (this.getSerialNumberHex = function () { + return i(this.hex, 0, [0, 1 + this.foffset], '02'); + }), + (this.getSignatureAlgorithmField = function () { + return u(i(this.hex, 0, [0, 2 + this.foffset, 0], '06')); + }), + (this.getIssuerHex = function () { + return o(this.hex, 0, [0, 3 + this.foffset], '30'); + }), + (this.getIssuerString = function () { + return c.hex2dn(this.getIssuerHex()); + }), + (this.getSubjectHex = function () { + return o(this.hex, 0, [0, 5 + this.foffset], '30'); + }), + (this.getSubjectString = function () { + return c.hex2dn(this.getSubjectHex()); + }), + (this.getNotBefore = function () { + var e = i(this.hex, 0, [0, 4 + this.foffset, 0]); + return (e = e.replace(/(..)/g, '%$1')), (e = decodeURIComponent(e)); + }), + (this.getNotAfter = function () { + var e = i(this.hex, 0, [0, 4 + this.foffset, 1]); + return (e = e.replace(/(..)/g, '%$1')), (e = decodeURIComponent(e)); + }), + (this.getPublicKeyHex = function () { + return e.getTLVbyList(this.hex, 0, [0, 6 + this.foffset], '30'); + }), + (this.getPublicKeyIdx = function () { + return s(this.hex, 0, [0, 6 + this.foffset], '30'); + }), + (this.getPublicKeyContentIdx = function () { + var e = this.getPublicKeyIdx(); + return s(this.hex, e, [1, 0], '30'); + }), + (this.getPublicKey = function () { + return z.getKey(this.getPublicKeyHex(), null, 'pkcs8pub'); + }), + (this.getSignatureAlgorithmName = function () { + return u(i(this.hex, 0, [1, 0], '06')); + }), + (this.getSignatureValueHex = function () { + return i(this.hex, 0, [2], '03', !0); + }), + (this.verifySignature = function (e) { + var t = this.getSignatureAlgorithmName(), + r = this.getSignatureValueHex(), + n = o(this.hex, 0, [0], '30'), + i = new K.crypto.Signature({ alg: t }); + return i.init(e), i.updateHex(n), i.verify(r); + }), + (this.parseExt = function () { + if (3 !== this.version) return -1; + var r = s(this.hex, 0, [0, 7, 0], '30'), + n = t(this.hex, r); + this.aExtInfo = new Array(); + for (var o = 0; o < n.length; o++) { + var u = { critical: !1 }, + c = 0; + 3 === t(this.hex, n[o]).length && ((u.critical = !0), (c = 1)), + (u.oid = e.hextooidstr(i(this.hex, n[o], [0], '06'))); + var h = s(this.hex, n[o], [1 + c]); + (u.vidx = a(this.hex, h)), this.aExtInfo.push(u); + } + }), + (this.getExtInfo = function (e) { + var t = this.aExtInfo, + r = e; + if ((e.match(/^[0-9.]+$/) || (r = K.asn1.x509.OID.name2oid(e)), '' !== r)) + for (var n = 0; n < t.length; n++) if (t[n].oid === r) return t[n]; + }), + (this.getExtBasicConstraints = function () { + var e = this.getExtInfo('basicConstraints'); + if (void 0 === e) return e; + var t = r(this.hex, e.vidx); + if ('' === t) return {}; + if ('0101ff' === t) return { cA: !0 }; + if ('0101ff02' === t.substr(0, 8)) { + var n = r(t, 6); + return { cA: !0, pathLen: parseInt(n, 16) }; + } + throw 'basicConstraints parse error'; + }), + (this.getExtKeyUsageBin = function () { + var e = this.getExtInfo('keyUsage'); + if (void 0 === e) return ''; + var t = r(this.hex, e.vidx); + if (t.length % 2 != 0 || t.length <= 2) throw 'malformed key usage value'; + var n = parseInt(t.substr(0, 2)), + i = parseInt(t.substr(2), 16).toString(2); + return i.substr(0, i.length - n); + }), + (this.getExtKeyUsageString = function () { + for (var e = this.getExtKeyUsageBin(), t = new Array(), r = 0; r < e.length; r++) + '1' == e.substr(r, 1) && t.push(X509.KEYUSAGE_NAME[r]); + return t.join(','); + }), + (this.getExtSubjectKeyIdentifier = function () { + var e = this.getExtInfo('subjectKeyIdentifier'); + return void 0 === e ? e : r(this.hex, e.vidx); + }), + (this.getExtAuthorityKeyIdentifier = function () { + var e = this.getExtInfo('authorityKeyIdentifier'); + if (void 0 === e) return e; + for (var i = {}, o = n(this.hex, e.vidx), s = t(o, 0), a = 0; a < s.length; a++) + '80' === o.substr(s[a], 2) && (i.kid = r(o, s[a])); + return i; + }), + (this.getExtExtKeyUsageName = function () { + var e = this.getExtInfo('extKeyUsage'); + if (void 0 === e) return e; + var i = new Array(), + o = n(this.hex, e.vidx); + if ('' === o) return i; + for (var s = t(o, 0), a = 0; a < s.length; a++) i.push(u(r(o, s[a]))); + return i; + }), + (this.getExtSubjectAltName = function () { + for (var e = this.getExtSubjectAltName2(), t = new Array(), r = 0; r < e.length; r++) + 'DNS' === e[r][0] && t.push(e[r][1]); + return t; + }), + (this.getExtSubjectAltName2 = function () { + var e, + i, + o, + s = this.getExtInfo('subjectAltName'); + if (void 0 === s) return s; + for ( + var a = new Array(), u = n(this.hex, s.vidx), c = t(u, 0), h = 0; + h < c.length; + h++ + ) + (o = u.substr(c[h], 2)), + (e = r(u, c[h])), + '81' === o && ((i = hextoutf8(e)), a.push(['MAIL', i])), + '82' === o && ((i = hextoutf8(e)), a.push(['DNS', i])), + '84' === o && ((i = X509.hex2dn(e, 0)), a.push(['DN', i])), + '86' === o && ((i = hextoutf8(e)), a.push(['URI', i])), + '87' === o && ((i = hextoip(e)), a.push(['IP', i])); + return a; + }), + (this.getExtCRLDistributionPointsURI = function () { + var e = this.getExtInfo('cRLDistributionPoints'); + if (void 0 === e) return e; + for (var r = new Array(), n = t(this.hex, e.vidx), o = 0; o < n.length; o++) + try { + var s = hextoutf8(i(this.hex, n[o], [0, 0, 0], '86')); + r.push(s); + } catch (e) {} + return r; + }), + (this.getExtAIAInfo = function () { + var e = this.getExtInfo('authorityInfoAccess'); + if (void 0 === e) return e; + for ( + var r = { ocsp: [], caissuer: [] }, n = t(this.hex, e.vidx), o = 0; + o < n.length; + o++ + ) { + var s = i(this.hex, n[o], [0], '06'), + a = i(this.hex, n[o], [1], '86'); + '2b06010505073001' === s && r.ocsp.push(hextoutf8(a)), + '2b06010505073002' === s && r.caissuer.push(hextoutf8(a)); + } + return r; + }), + (this.getExtCertificatePolicies = function () { + var e = this.getExtInfo('certificatePolicies'); + if (void 0 === e) return e; + for (var o = n(this.hex, e.vidx), s = [], a = t(o, 0), c = 0; c < a.length; c++) { + var h = {}, + f = t(o, a[c]); + if (((h.id = u(r(o, f[0]))), 2 === f.length)) + for (var l = t(o, f[1]), g = 0; g < l.length; g++) { + var p = i(o, l[g], [0], '06'); + '2b06010505070201' === p + ? (h.cps = hextoutf8(i(o, l[g], [1]))) + : '2b06010505070202' === p && (h.unotice = hextoutf8(i(o, l[g], [1, 0]))); + } + s.push(h); + } + return s; + }), + (this.readCertPEM = function (e) { + this.readCertHex(h(e)); + }), + (this.readCertHex = function (e) { + (this.hex = e), this.getVersion(); + try { + s(this.hex, 0, [0, 7], 'a3'), this.parseExt(); + } catch (e) {} + }), + (this.getInfo = function () { + var e, t, r; + if ( + ((e = 'Basic Fields\n'), + (e += ' serial number: ' + this.getSerialNumberHex() + '\n'), + (e += ' signature algorithm: ' + this.getSignatureAlgorithmField() + '\n'), + (e += ' issuer: ' + this.getIssuerString() + '\n'), + (e += ' notBefore: ' + this.getNotBefore() + '\n'), + (e += ' notAfter: ' + this.getNotAfter() + '\n'), + (e += ' subject: ' + this.getSubjectString() + '\n'), + (e += ' subject public key info: \n'), + (e += ' key algorithm: ' + (t = this.getPublicKey()).type + '\n'), + 'RSA' === t.type && + ((e += ' n=' + hextoposhex(t.n.toString(16)).substr(0, 16) + '...\n'), + (e += ' e=' + hextoposhex(t.e.toString(16)) + '\n')), + void 0 !== (r = this.aExtInfo) && null !== r) + ) { + e += 'X509v3 Extensions:\n'; + for (var n = 0; n < r.length; n++) { + var i = r[n], + o = K.asn1.x509.OID.oid2name(i.oid); + '' === o && (o = i.oid); + var s = ''; + if ( + (!0 === i.critical && (s = 'CRITICAL'), + (e += ' ' + o + ' ' + s + ':\n'), + 'basicConstraints' === o) + ) { + var a = this.getExtBasicConstraints(); + void 0 === a.cA + ? (e += ' {}\n') + : ((e += ' cA=true'), + void 0 !== a.pathLen && (e += ', pathLen=' + a.pathLen), + (e += '\n')); + } else if ('keyUsage' === o) e += ' ' + this.getExtKeyUsageString() + '\n'; + else if ('subjectKeyIdentifier' === o) + e += ' ' + this.getExtSubjectKeyIdentifier() + '\n'; + else if ('authorityKeyIdentifier' === o) { + var u = this.getExtAuthorityKeyIdentifier(); + void 0 !== u.kid && (e += ' kid=' + u.kid + '\n'); + } else { + if ('extKeyUsage' === o) + e += ' ' + this.getExtExtKeyUsageName().join(', ') + '\n'; + else if ('subjectAltName' === o) + e += ' ' + this.getExtSubjectAltName2() + '\n'; + else if ('cRLDistributionPoints' === o) + e += ' ' + this.getExtCRLDistributionPointsURI() + '\n'; + else if ('authorityInfoAccess' === o) { + var c = this.getExtAIAInfo(); + void 0 !== c.ocsp && (e += ' ocsp: ' + c.ocsp.join(',') + '\n'), + void 0 !== c.caissuer && + (e += ' caissuer: ' + c.caissuer.join(',') + '\n'); + } else if ('certificatePolicies' === o) + for (var h = this.getExtCertificatePolicies(), f = 0; f < h.length; f++) + void 0 !== h[f].id && (e += ' policy oid: ' + h[f].id + '\n'), + void 0 !== h[f].cps && (e += ' cps: ' + h[f].cps + '\n'); + } + } + } + return ( + (e += 'signature algorithm: ' + this.getSignatureAlgorithmName() + '\n'), + (e += 'signature: ' + this.getSignatureValueHex().substr(0, 16) + '...\n') + ); + }); + } + Y.compile('[^0-9a-f]', 'gi'), + (RSAKey.prototype.sign = function (e, t) { + var r = (function b(e) { + return K.crypto.Util.hashString(e, t); + })(e); + return this.signWithMessageHash(r, t); + }), + (RSAKey.prototype.signWithMessageHash = function (e, t) { + var r = parseBigInt(K.crypto.Util.getPaddedDigestInfoHex(e, t, this.n.bitLength()), 16); + return _zeroPaddingOfSignature(this.doPrivate(r).toString(16), this.n.bitLength()); + }), + (RSAKey.prototype.signPSS = function (e, t, r) { + var n = (function c(e) { + return K.crypto.Util.hashHex(e, t); + })(rstrtohex(e)); + return void 0 === r && (r = -1), this.signWithMessageHashPSS(n, t, r); + }), + (RSAKey.prototype.signWithMessageHashPSS = function (e, t, r) { + var n, + i = hextorstr(e), + s = i.length, + a = this.n.bitLength() - 1, + u = Math.ceil(a / 8), + c = function o(e) { + return K.crypto.Util.hashHex(e, t); + }; + if (-1 === r || void 0 === r) r = s; + else if (-2 === r) r = u - s - 2; + else if (r < -2) throw 'invalid salt length'; + if (u < s + r + 2) throw 'data too long'; + var h = ''; + r > 0 && + ((h = new Array(r)), + new SecureRandom().nextBytes(h), + (h = String.fromCharCode.apply(String, h))); + var f = hextorstr(c(rstrtohex('\0\0\0\0\0\0\0\0' + i + h))), + l = []; + for (n = 0; n < u - r - s - 2; n += 1) l[n] = 0; + var g = String.fromCharCode.apply(String, l) + '' + h, + p = pss_mgf1_str(f, g.length, c), + d = []; + for (n = 0; n < g.length; n += 1) d[n] = g.charCodeAt(n) ^ p.charCodeAt(n); + var v = (65280 >> (8 * u - a)) & 255; + for (d[0] &= ~v, n = 0; n < s; n++) d.push(f.charCodeAt(n)); + return ( + d.push(188), + _zeroPaddingOfSignature( + this.doPrivate(new BigInteger(d)).toString(16), + this.n.bitLength() + ) + ); + }), + (RSAKey.prototype.verify = function (e, t) { + var r = parseBigInt((t = (t = t.replace(Y, '')).replace(/[ \n]+/g, '')), 16); + if (r.bitLength() > this.n.bitLength()) return 0; + var n = _rsasign_getAlgNameAndHashFromHexDisgestInfo( + this.doPublic(r) + .toString(16) + .replace(/^1f+00/, '') + ); + if (0 == n.length) return !1; + var i = n[0]; + return ( + n[1] == + (function a(e) { + return K.crypto.Util.hashString(e, i); + })(e) + ); + }), + (RSAKey.prototype.verifyWithMessageHash = function (e, t) { + var r = parseBigInt((t = (t = t.replace(Y, '')).replace(/[ \n]+/g, '')), 16); + if (r.bitLength() > this.n.bitLength()) return 0; + var n = _rsasign_getAlgNameAndHashFromHexDisgestInfo( + this.doPublic(r) + .toString(16) + .replace(/^1f+00/, '') + ); + if (0 == n.length) return !1; + n[0]; + return n[1] == e; + }), + (RSAKey.prototype.verifyPSS = function (t, r, n, i) { + var o = (function e(t) { + return K.crypto.Util.hashHex(t, n); + })(rstrtohex(t)); + return void 0 === i && (i = -1), this.verifyWithMessageHashPSS(o, r, n, i); + }), + (RSAKey.prototype.verifyWithMessageHashPSS = function (e, t, n, i) { + var o = new BigInteger(t, 16); + if (o.bitLength() > this.n.bitLength()) return !1; + var s, + a = function r(e) { + return K.crypto.Util.hashHex(e, n); + }, + u = hextorstr(e), + c = u.length, + h = this.n.bitLength() - 1, + f = Math.ceil(h / 8); + if (-1 === i || void 0 === i) i = c; + else if (-2 === i) i = f - c - 2; + else if (i < -2) throw 'invalid salt length'; + if (f < c + i + 2) throw 'data too long'; + var l = this.doPublic(o).toByteArray(); + for (s = 0; s < l.length; s += 1) l[s] &= 255; + for (; l.length < f; ) l.unshift(0); + if (188 !== l[f - 1]) throw 'encoded message does not end in 0xbc'; + var g = (l = String.fromCharCode.apply(String, l)).substr(0, f - c - 1), + p = l.substr(g.length, c), + d = (65280 >> (8 * f - h)) & 255; + if (0 != (g.charCodeAt(0) & d)) throw 'bits beyond keysize not zero'; + var v = pss_mgf1_str(p, g.length, a), + y = []; + for (s = 0; s < g.length; s += 1) y[s] = g.charCodeAt(s) ^ v.charCodeAt(s); + y[0] &= ~d; + var m = f - c - i - 2; + for (s = 0; s < m; s += 1) if (0 !== y[s]) throw 'leftmost octets not zero'; + if (1 !== y[m]) throw '0x01 marker not found'; + return ( + p === + hextorstr( + a( + rstrtohex('\0\0\0\0\0\0\0\0' + u + String.fromCharCode.apply(String, y.slice(-i))) + ) + ) + ); + }), + (RSAKey.SALT_LEN_HLEN = -1), + (RSAKey.SALT_LEN_MAX = -2), + (RSAKey.SALT_LEN_RECOVER = -2), + (X509.hex2dn = function (e, t) { + if ((void 0 === t && (t = 0), '30' !== e.substr(t, 2))) throw 'malformed DN'; + for (var r = new Array(), n = J.getChildIdx(e, t), i = 0; i < n.length; i++) + r.push(X509.hex2rdn(e, n[i])); + return ( + '/' + + (r = r.map(function (e) { + return e.replace('/', '\\/'); + })).join('/') + ); + }), + (X509.hex2rdn = function (e, t) { + if ((void 0 === t && (t = 0), '31' !== e.substr(t, 2))) throw 'malformed RDN'; + for (var r = new Array(), n = J.getChildIdx(e, t), i = 0; i < n.length; i++) + r.push(X509.hex2attrTypeValue(e, n[i])); + return (r = r.map(function (e) { + return e.replace('+', '\\+'); + })).join('+'); + }), + (X509.hex2attrTypeValue = function (e, t) { + var r = J, + n = r.getV; + if ((void 0 === t && (t = 0), '30' !== e.substr(t, 2))) + throw 'malformed attribute type and value'; + var i = r.getChildIdx(e, t); + 2 !== i.length || e.substr(i[0], 2); + var o = n(e, i[0]), + s = K.asn1.ASN1Util.oidHexToInt(o); + return K.asn1.x509.OID.oid2atype(s) + '=' + hextorstr(n(e, i[1])); + }), + (X509.getPublicKeyFromCertHex = function (e) { + var t = new X509(); + return t.readCertHex(e), t.getPublicKey(); + }), + (X509.getPublicKeyFromCertPEM = function (e) { + var t = new X509(); + return t.readCertPEM(e), t.getPublicKey(); + }), + (X509.getPublicKeyInfoPropOfCertPEM = function (e) { + var t, + r, + n = J.getVbyList, + i = {}; + return ( + (i.algparam = null), + (t = new X509()).readCertPEM(e), + (r = t.getPublicKeyHex()), + (i.keyhex = n(r, 0, [1], '03').substr(2)), + (i.algoid = n(r, 0, [0, 0], '06')), + '2a8648ce3d0201' === i.algoid && (i.algparam = n(r, 0, [0, 1], '06')), + i + ); + }), + (X509.KEYUSAGE_NAME = [ + 'digitalSignature', + 'nonRepudiation', + 'keyEncipherment', + 'dataEncipherment', + 'keyAgreement', + 'keyCertSign', + 'cRLSign', + 'encipherOnly', + 'decipherOnly', + ]), + (void 0 !== K && K) || (K = {}), + (void 0 !== K.jws && K.jws) || (K.jws = {}), + (K.jws.JWS = function () { + var e = K.jws.JWS.isSafeJSONString; + this.parseJWS = function (t, r) { + if (void 0 === this.parsedJWS || (!r && void 0 === this.parsedJWS.sigvalH)) { + var n = t.match(/^([^.]+)\.([^.]+)\.([^.]+)$/); + if (null == n) throw "JWS signature is not a form of 'Head.Payload.SigValue'."; + var i = n[1], + o = n[2], + s = n[3], + a = i + '.' + o; + if ( + ((this.parsedJWS = {}), + (this.parsedJWS.headB64U = i), + (this.parsedJWS.payloadB64U = o), + (this.parsedJWS.sigvalB64U = s), + (this.parsedJWS.si = a), + !r) + ) { + var u = b64utohex(s), + c = parseBigInt(u, 16); + (this.parsedJWS.sigvalH = u), (this.parsedJWS.sigvalBI = c); + } + var h = W(i), + f = W(o); + if ( + ((this.parsedJWS.headS = h), + (this.parsedJWS.payloadS = f), + !e(h, this.parsedJWS, 'headP')) + ) + throw 'malformed JSON string for JWS Head: ' + h; + } + }; + }), + (K.jws.JWS.sign = function (e, t, r, n, o) { + var s, + a, + u, + c = K, + h = c.jws.JWS, + f = h.readSafeJSONString, + l = h.isSafeJSONString, + g = c.crypto, + p = (g.ECDSA, g.Mac), + d = g.Signature, + v = JSON; + if ('string' != typeof t && 'object' != (void 0 === t ? 'undefined' : i(t))) + throw 'spHeader must be JSON string or object: ' + t; + if ( + ('object' == (void 0 === t ? 'undefined' : i(t)) && ((a = t), (s = v.stringify(a))), + 'string' == typeof t) + ) { + if (!l((s = t))) throw 'JWS Head is not safe JSON string: ' + s; + a = f(s); + } + if ( + ((u = r), + 'object' == (void 0 === r ? 'undefined' : i(r)) && (u = v.stringify(r)), + ('' != e && null != e) || void 0 === a.alg || (e = a.alg), + '' != e && null != e && void 0 === a.alg && ((a.alg = e), (s = v.stringify(a))), + e !== a.alg) + ) + throw "alg and sHeader.alg doesn't match: " + e + '!=' + a.alg; + var y = null; + if (void 0 === h.jwsalg2sigalg[e]) throw 'unsupported alg name: ' + e; + y = h.jwsalg2sigalg[e]; + var m = q(s) + '.' + q(u), + S = ''; + if ('Hmac' == y.substr(0, 4)) { + if (void 0 === n) throw 'mac key shall be specified for HS* alg'; + var F = new p({ alg: y, prov: 'cryptojs', pass: n }); + F.updateString(m), (S = F.doFinal()); + } else { + var b; + if (-1 != y.indexOf('withECDSA')) + (b = new d({ alg: y })).init(n, o), + b.updateString(m), + (hASN1Sig = b.sign()), + (S = K.crypto.ECDSA.asn1SigToConcatSig(hASN1Sig)); + else if ('none' != y) + (b = new d({ alg: y })).init(n, o), b.updateString(m), (S = b.sign()); + } + return m + '.' + hextob64u(S); + }), + (K.jws.JWS.verify = function (e, t, r) { + var n, + o = K, + s = o.jws.JWS, + a = s.readSafeJSONString, + u = o.crypto, + c = u.ECDSA, + h = u.Mac, + f = u.Signature; + void 0 !== i(RSAKey) && (n = RSAKey); + var l = e.split('.'); + if (3 !== l.length) return !1; + var g = l[0] + '.' + l[1], + p = b64utohex(l[2]), + d = a(W(l[0])), + v = null, + y = null; + if (void 0 === d.alg) throw 'algorithm not specified in header'; + if ( + ((y = (v = d.alg).substr(0, 2)), + null != r && + '[object Array]' === Object.prototype.toString.call(r) && + r.length > 0) && + -1 == (':' + r.join(':') + ':').indexOf(':' + v + ':') + ) + throw "algorithm '" + v + "' not accepted in the list"; + if ('none' != v && null === t) throw 'key shall be specified to verify.'; + if ( + ('string' == typeof t && -1 != t.indexOf('-----BEGIN ') && (t = z.getKey(t)), + !(('RS' != y && 'PS' != y) || t instanceof n)) + ) + throw 'key shall be a RSAKey obj for RS* and PS* algs'; + if ('ES' == y && !(t instanceof c)) throw 'key shall be a ECDSA obj for ES* algs'; + var m = null; + if (void 0 === s.jwsalg2sigalg[d.alg]) throw 'unsupported alg name: ' + v; + if ('none' == (m = s.jwsalg2sigalg[v])) throw 'not supported'; + if ('Hmac' == m.substr(0, 4)) { + if (void 0 === t) throw 'hexadecimal key shall be specified for HMAC'; + var S = new h({ alg: m, pass: t }); + return S.updateString(g), p == S.doFinal(); + } + if (-1 != m.indexOf('withECDSA')) { + var F, + b = null; + try { + b = c.concatSigToASN1Sig(p); + } catch (e) { + return !1; + } + return (F = new f({ alg: m })).init(t), F.updateString(g), F.verify(b); + } + return (F = new f({ alg: m })).init(t), F.updateString(g), F.verify(p); + }), + (K.jws.JWS.parse = function (e) { + var t, + r, + n, + i = e.split('.'), + o = {}; + if (2 != i.length && 3 != i.length) + throw "malformed sJWS: wrong number of '.' splitted elements"; + return ( + (t = i[0]), + (r = i[1]), + 3 == i.length && (n = i[2]), + (o.headerObj = K.jws.JWS.readSafeJSONString(W(t))), + (o.payloadObj = K.jws.JWS.readSafeJSONString(W(r))), + (o.headerPP = JSON.stringify(o.headerObj, null, ' ')), + null == o.payloadObj + ? (o.payloadPP = W(r)) + : (o.payloadPP = JSON.stringify(o.payloadObj, null, ' ')), + void 0 !== n && (o.sigHex = b64utohex(n)), + o + ); + }), + (K.jws.JWS.verifyJWT = function (e, t, r) { + var n = K.jws, + o = n.JWS, + s = o.readSafeJSONString, + a = o.inArray, + u = o.includedArray, + c = e.split('.'), + h = c[0], + f = c[1], + l = (b64utohex(c[2]), s(W(h))), + g = s(W(f)); + if (void 0 === l.alg) return !1; + if (void 0 === r.alg) throw 'acceptField.alg shall be specified'; + if (!a(l.alg, r.alg)) return !1; + if (void 0 !== g.iss && 'object' === i(r.iss) && !a(g.iss, r.iss)) return !1; + if (void 0 !== g.sub && 'object' === i(r.sub) && !a(g.sub, r.sub)) return !1; + if (void 0 !== g.aud && 'object' === i(r.aud)) + if ('string' == typeof g.aud) { + if (!a(g.aud, r.aud)) return !1; + } else if ('object' == i(g.aud) && !u(g.aud, r.aud)) return !1; + var p = n.IntDate.getNow(); + return ( + void 0 !== r.verifyAt && 'number' == typeof r.verifyAt && (p = r.verifyAt), + (void 0 !== r.gracePeriod && 'number' == typeof r.gracePeriod) || (r.gracePeriod = 0), + !(void 0 !== g.exp && 'number' == typeof g.exp && g.exp + r.gracePeriod < p) && + !(void 0 !== g.nbf && 'number' == typeof g.nbf && p < g.nbf - r.gracePeriod) && + !(void 0 !== g.iat && 'number' == typeof g.iat && p < g.iat - r.gracePeriod) && + (void 0 === g.jti || void 0 === r.jti || g.jti === r.jti) && + !!o.verify(e, t, r.alg) + ); + }), + (K.jws.JWS.includedArray = function (e, t) { + var r = K.jws.JWS.inArray; + if (null === e) return !1; + if ('object' !== (void 0 === e ? 'undefined' : i(e))) return !1; + if ('number' != typeof e.length) return !1; + for (var n = 0; n < e.length; n++) if (!r(e[n], t)) return !1; + return !0; + }), + (K.jws.JWS.inArray = function (e, t) { + if (null === t) return !1; + if ('object' !== (void 0 === t ? 'undefined' : i(t))) return !1; + if ('number' != typeof t.length) return !1; + for (var r = 0; r < t.length; r++) if (t[r] == e) return !0; + return !1; + }), + (K.jws.JWS.jwsalg2sigalg = { + HS256: 'HmacSHA256', + HS384: 'HmacSHA384', + HS512: 'HmacSHA512', + RS256: 'SHA256withRSA', + RS384: 'SHA384withRSA', + RS512: 'SHA512withRSA', + ES256: 'SHA256withECDSA', + ES384: 'SHA384withECDSA', + PS256: 'SHA256withRSAandMGF1', + PS384: 'SHA384withRSAandMGF1', + PS512: 'SHA512withRSAandMGF1', + none: 'none', + }), + (K.jws.JWS.isSafeJSONString = function (e, t, r) { + var n = null; + try { + return 'object' != (void 0 === (n = V(e)) ? 'undefined' : i(n)) + ? 0 + : n.constructor === Array + ? 0 + : (t && (t[r] = n), 1); + } catch (e) { + return 0; + } + }), + (K.jws.JWS.readSafeJSONString = function (e) { + var t = null; + try { + return 'object' != (void 0 === (t = V(e)) ? 'undefined' : i(t)) + ? null + : t.constructor === Array + ? null + : t; + } catch (e) { + return null; + } + }), + (K.jws.JWS.getEncodedSignatureValueFromJWS = function (e) { + var t = e.match(/^[^.]+\.[^.]+\.([^.]+)$/); + if (null == t) throw "JWS signature is not a form of 'Head.Payload.SigValue'."; + return t[1]; + }), + (K.jws.JWS.getJWKthumbprint = function (e) { + if ('RSA' !== e.kty && 'EC' !== e.kty && 'oct' !== e.kty) + throw 'unsupported algorithm for JWK Thumprint'; + var t = '{'; + if ('RSA' === e.kty) { + if ('string' != typeof e.n || 'string' != typeof e.e) + throw 'wrong n and e value for RSA key'; + (t += '"e":"' + e.e + '",'), + (t += '"kty":"' + e.kty + '",'), + (t += '"n":"' + e.n + '"}'); + } else if ('EC' === e.kty) { + if ('string' != typeof e.crv || 'string' != typeof e.x || 'string' != typeof e.y) + throw 'wrong crv, x and y value for EC key'; + (t += '"crv":"' + e.crv + '",'), + (t += '"kty":"' + e.kty + '",'), + (t += '"x":"' + e.x + '",'), + (t += '"y":"' + e.y + '"}'); + } else if ('oct' === e.kty) { + if ('string' != typeof e.k) throw 'wrong k value for oct(symmetric) key'; + (t += '"kty":"' + e.kty + '",'), (t += '"k":"' + e.k + '"}'); + } + var r = rstrtohex(t); + return hextob64u(K.crypto.Util.hashHex(r, 'sha256')); + }), + (K.jws.IntDate = {}), + (K.jws.IntDate.get = function (e) { + var t = K.jws.IntDate, + r = t.getNow, + n = t.getZulu; + if ('now' == e) return r(); + if ('now + 1hour' == e) return r() + 3600; + if ('now + 1day' == e) return r() + 86400; + if ('now + 1month' == e) return r() + 2592e3; + if ('now + 1year' == e) return r() + 31536e3; + if (e.match(/Z$/)) return n(e); + if (e.match(/^[0-9]+$/)) return parseInt(e); + throw 'unsupported format: ' + e; + }), + (K.jws.IntDate.getZulu = function (e) { + return zulutosec(e); + }), + (K.jws.IntDate.getNow = function () { + return ~~(new Date() / 1e3); + }), + (K.jws.IntDate.intDate2UTCString = function (e) { + return new Date(1e3 * e).toUTCString(); + }), + (K.jws.IntDate.intDate2Zulu = function (e) { + var t = new Date(1e3 * e); + return ( + ('0000' + t.getUTCFullYear()).slice(-4) + + ('00' + (t.getUTCMonth() + 1)).slice(-2) + + ('00' + t.getUTCDate()).slice(-2) + + ('00' + t.getUTCHours()).slice(-2) + + ('00' + t.getUTCMinutes()).slice(-2) + + ('00' + t.getUTCSeconds()).slice(-2) + + 'Z' + ); + }), + (t.SecureRandom = SecureRandom), + (t.rng_seed_time = rng_seed_time), + (t.BigInteger = BigInteger), + (t.RSAKey = RSAKey), + (t.ECDSA = K.crypto.ECDSA), + (t.DSA = K.crypto.DSA), + (t.Signature = K.crypto.Signature), + (t.MessageDigest = K.crypto.MessageDigest), + (t.Mac = K.crypto.Mac), + (t.Cipher = K.crypto.Cipher), + (t.KEYUTIL = z), + (t.ASN1HEX = J), + (t.X509 = X509), + (t.CryptoJS = y), + (t.b64tohex = b64tohex), + (t.b64toBA = b64toBA), + (t.stoBA = stoBA), + (t.BAtos = BAtos), + (t.BAtohex = BAtohex), + (t.stohex = stohex), + (t.stob64 = function stob64(e) { + return hex2b64(stohex(e)); + }), + (t.stob64u = function stob64u(e) { + return b64tob64u(hex2b64(stohex(e))); + }), + (t.b64utos = function b64utos(e) { + return BAtos(b64toBA(b64utob64(e))); + }), + (t.b64tob64u = b64tob64u), + (t.b64utob64 = b64utob64), + (t.hex2b64 = hex2b64), + (t.hextob64u = hextob64u), + (t.b64utohex = b64utohex), + (t.utf8tob64u = q), + (t.b64utoutf8 = W), + (t.utf8tob64 = function utf8tob64(e) { + return hex2b64(uricmptohex(encodeURIComponentAll(e))); + }), + (t.b64toutf8 = function b64toutf8(e) { + return decodeURIComponent(hextouricmp(b64tohex(e))); + }), + (t.utf8tohex = utf8tohex), + (t.hextoutf8 = hextoutf8), + (t.hextorstr = hextorstr), + (t.rstrtohex = rstrtohex), + (t.hextob64 = hextob64), + (t.hextob64nl = hextob64nl), + (t.b64nltohex = b64nltohex), + (t.hextopem = hextopem), + (t.pemtohex = pemtohex), + (t.hextoArrayBuffer = function hextoArrayBuffer(e) { + if (e.length % 2 != 0) throw 'input is not even length'; + if (null == e.match(/^[0-9A-Fa-f]+$/)) throw 'input is not hexadecimal'; + for ( + var t = new ArrayBuffer(e.length / 2), r = new DataView(t), n = 0; + n < e.length / 2; + n++ + ) + r.setUint8(n, parseInt(e.substr(2 * n, 2), 16)); + return t; + }), + (t.ArrayBuffertohex = function ArrayBuffertohex(e) { + for (var t = '', r = new DataView(e), n = 0; n < e.byteLength; n++) + t += ('00' + r.getUint8(n).toString(16)).slice(-2); + return t; + }), + (t.zulutomsec = zulutomsec), + (t.zulutosec = zulutosec), + (t.zulutodate = function zulutodate(e) { + return new Date(zulutomsec(e)); + }), + (t.datetozulu = function datetozulu(e, t, r) { + var n, + i = e.getUTCFullYear(); + if (t) { + if (i < 1950 || 2049 < i) throw 'not proper year for UTCTime: ' + i; + n = ('' + i).slice(-2); + } else n = ('000' + i).slice(-4); + if ( + ((n += ('0' + (e.getUTCMonth() + 1)).slice(-2)), + (n += ('0' + e.getUTCDate()).slice(-2)), + (n += ('0' + e.getUTCHours()).slice(-2)), + (n += ('0' + e.getUTCMinutes()).slice(-2)), + (n += ('0' + e.getUTCSeconds()).slice(-2)), + r) + ) { + var o = e.getUTCMilliseconds(); + 0 !== o && (n += '.' + (o = (o = ('00' + o).slice(-3)).replace(/0+$/g, ''))); + } + return (n += 'Z'); + }), + (t.uricmptohex = uricmptohex), + (t.hextouricmp = hextouricmp), + (t.ipv6tohex = ipv6tohex), + (t.hextoipv6 = hextoipv6), + (t.hextoip = hextoip), + (t.iptohex = function iptohex(e) { + var t = 'malformed IP address'; + if (!(e = e.toLowerCase(e)).match(/^[0-9.]+$/)) { + if (e.match(/^[0-9a-f:]+$/) && -1 !== e.indexOf(':')) return ipv6tohex(e); + throw t; + } + var r = e.split('.'); + if (4 !== r.length) throw t; + var n = ''; + try { + for (var i = 0; i < 4; i++) n += ('0' + parseInt(r[i]).toString(16)).slice(-2); + return n; + } catch (e) { + throw t; + } + }), + (t.encodeURIComponentAll = encodeURIComponentAll), + (t.newline_toUnix = function newline_toUnix(e) { + return (e = e.replace(/\r\n/gm, '\n')); + }), + (t.newline_toDos = function newline_toDos(e) { + return (e = (e = e.replace(/\r\n/gm, '\n')).replace(/\n/gm, '\r\n')); + }), + (t.hextoposhex = hextoposhex), + (t.intarystrtohex = function intarystrtohex(e) { + e = (e = (e = e.replace(/^\s*\[\s*/, '')).replace(/\s*\]\s*$/, '')).replace(/\s*/g, ''); + try { + return e + .split(/,/) + .map(function (e, t, r) { + var n = parseInt(e); + if (n < 0 || 255 < n) throw 'integer not in range 0-255'; + return ('00' + n.toString(16)).slice(-2); + }) + .join(''); + } catch (e) { + throw 'malformed integer array string: ' + e; + } + }), + (t.strdiffidx = function strdiffidx(e, t) { + var r = e.length; + e.length > t.length && (r = t.length); + for (var n = 0; n < r; n++) if (e.charCodeAt(n) != t.charCodeAt(n)) return n; + return e.length != t.length ? r : -1; + }), + (t.KJUR = K), + (t.crypto = K.crypto), + (t.asn1 = K.asn1), + (t.jws = K.jws), + (t.lang = K.lang); + }).call(this, r(40).Buffer); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.JoseUtil = void 0); + var n = r(41), + i = r(0); + var o = ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512', 'ES256', 'ES384', 'ES512']; + t.JoseUtil = (function () { + function JoseUtil() { + !(function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, JoseUtil); + } + return ( + (JoseUtil.parseJwt = function parseJwt(e) { + i.Log.debug('JoseUtil.parseJwt'); + try { + var t = n.jws.JWS.parse(e); + return { header: t.headerObj, payload: t.payloadObj }; + } catch (e) { + i.Log.error(e); + } + }), + (JoseUtil.validateJwt = function validateJwt(e, t, r, o, s, a) { + i.Log.debug('JoseUtil.validateJwt'); + try { + if ('RSA' === t.kty) + if (t.e && t.n) t = n.KEYUTIL.getKey(t); + else { + if (!t.x5c || !t.x5c.length) + return ( + i.Log.error('JoseUtil.validateJwt: RSA key missing key material', t), + Promise.reject(new Error('RSA key missing key material')) + ); + var u = (0, n.b64tohex)(t.x5c[0]); + t = n.X509.getPublicKeyFromCertHex(u); + } + else { + if ('EC' !== t.kty) + return ( + i.Log.error('JoseUtil.validateJwt: Unsupported key type', t && t.kty), + Promise.reject(new Error('Unsupported key type: ' + t && t.kty)) + ); + if (!(t.crv && t.x && t.y)) + return ( + i.Log.error('JoseUtil.validateJwt: EC key missing key material', t), + Promise.reject(new Error('EC key missing key material')) + ); + t = n.KEYUTIL.getKey(t); + } + return JoseUtil._validateJwt(e, t, r, o, s, a); + } catch (e) { + return i.Log.error((e && e.message) || e), Promise.reject('JWT validation failed'); + } + }), + (JoseUtil._validateJwt = function _validateJwt(e, t, r, s, a, u) { + a || (a = 0), u || (u = parseInt(Date.now() / 1e3)); + var c = JoseUtil.parseJwt(e).payload; + if (!c.iss) + return ( + i.Log.error('JoseUtil._validateJwt: issuer was not provided'), + Promise.reject(new Error('issuer was not provided')) + ); + if (c.iss !== r) + return ( + i.Log.error('JoseUtil._validateJwt: Invalid issuer in token', c.iss), + Promise.reject(new Error('Invalid issuer in token: ' + c.iss)) + ); + if (!c.aud) + return ( + i.Log.error('JoseUtil._validateJwt: aud was not provided'), + Promise.reject(new Error('aud was not provided')) + ); + if (!(c.aud === s || (Array.isArray(c.aud) && c.aud.indexOf(s) >= 0))) + return ( + i.Log.error('JoseUtil._validateJwt: Invalid audience in token', c.aud), + Promise.reject(new Error('Invalid audience in token: ' + c.aud)) + ); + var h = u + a, + f = u - a; + if (!c.iat) + return ( + i.Log.error('JoseUtil._validateJwt: iat was not provided'), + Promise.reject(new Error('iat was not provided')) + ); + if (h < c.iat) + return ( + i.Log.error('JoseUtil._validateJwt: iat is in the future', c.iat), + Promise.reject(new Error('iat is in the future: ' + c.iat)) + ); + if (c.nbf && h < c.nbf) + return ( + i.Log.error('JoseUtil._validateJwt: nbf is in the future', c.nbf), + Promise.reject(new Error('nbf is in the future: ' + c.nbf)) + ); + if (!c.exp) + return ( + i.Log.error('JoseUtil._validateJwt: exp was not provided'), + Promise.reject(new Error('exp was not provided')) + ); + if (c.exp < f) + return ( + i.Log.error('JoseUtil._validateJwt: exp is in the past', c.exp), + Promise.reject(new Error('exp is in the past:' + c.exp)) + ); + try { + if (!n.jws.JWS.verify(e, t, o)) + return ( + i.Log.error('JoseUtil._validateJwt: signature validation failed'), + Promise.reject(new Error('signature validation failed')) + ); + } catch (e) { + return ( + i.Log.error((e && e.message) || e), + Promise.reject(new Error('signature validation failed')) + ); + } + return Promise.resolve(); + }), + (JoseUtil.hashString = function hashString(e, t) { + try { + return n.crypto.Util.hashString(e, t); + } catch (e) { + i.Log.error(e); + } + }), + (JoseUtil.hexToBase64Url = function hexToBase64Url(e) { + try { + return (0, n.hextob64u)(e); + } catch (e) { + i.Log.error(e); + } + }), + JoseUtil + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.UserInfoService = void 0); + var n = r(17), + i = r(3), + o = r(0); + t.UserInfoService = (function () { + function UserInfoService(e) { + var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : n.JsonService, + r = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : i.MetadataService; + if ( + ((function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, UserInfoService), + !e) + ) + throw (o.Log.error('UserInfoService.ctor: No settings passed'), new Error('settings')); + (this._settings = e), + (this._jsonService = new t()), + (this._metadataService = new r(this._settings)); + } + return ( + (UserInfoService.prototype.getClaims = function getClaims(e) { + var t = this; + return e + ? this._metadataService.getUserInfoEndpoint().then(function (r) { + return ( + o.Log.debug('UserInfoService.getClaims: received userinfo url', r), + t._jsonService.getJson(r, e).then(function (e) { + return o.Log.debug('UserInfoService.getClaims: claims received', e), e; + }) + ); + }) + : (o.Log.error('UserInfoService.getClaims: No token passed'), + Promise.reject(new Error('A token is required'))); + }), + UserInfoService + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }), (t.ResponseValidator = void 0); + var n = r(0), + i = r(3), + o = r(43), + s = r(16), + a = r(42); + var u = ['nonce', 'at_hash', 'iat', 'nbf', 'exp', 'aud', 'iss', 'c_hash']; + t.ResponseValidator = (function () { + function ResponseValidator(e) { + var t = + arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : i.MetadataService, + r = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : o.UserInfoService, + s = arguments.length > 3 && void 0 !== arguments[3] ? arguments[3] : a.JoseUtil; + if ( + ((function _classCallCheck(e, t) { + if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function'); + })(this, ResponseValidator), + !e) + ) + throw ( + (n.Log.error('ResponseValidator.ctor: No settings passed to ResponseValidator'), + new Error('settings')) + ); + (this._settings = e), + (this._metadataService = new t(this._settings)), + (this._userInfoService = new r(this._settings)), + (this._joseUtil = s); + } + return ( + (ResponseValidator.prototype.validateSigninResponse = function validateSigninResponse( + e, + t + ) { + var r = this; + return ( + n.Log.debug('ResponseValidator.validateSigninResponse'), + this._processSigninParams(e, t).then(function (t) { + return ( + n.Log.debug('ResponseValidator.validateSigninResponse: state processed'), + r._validateTokens(e, t).then(function (e) { + return ( + n.Log.debug('ResponseValidator.validateSigninResponse: tokens validated'), + r._processClaims(e).then(function (e) { + return ( + n.Log.debug('ResponseValidator.validateSigninResponse: claims processed'), + e + ); + }) + ); + }) + ); + }) + ); + }), + (ResponseValidator.prototype.validateSignoutResponse = function validateSignoutResponse( + e, + t + ) { + return e.id !== t.state + ? (n.Log.error('ResponseValidator.validateSignoutResponse: State does not match'), + Promise.reject(new Error('State does not match'))) + : (n.Log.debug('ResponseValidator.validateSignoutResponse: state validated'), + (t.state = e.data), + t.error + ? (n.Log.warn( + 'ResponseValidator.validateSignoutResponse: Response was error', + t.error + ), + Promise.reject(new s.ErrorResponse(t))) + : Promise.resolve(t)); + }), + (ResponseValidator.prototype._processSigninParams = function _processSigninParams(e, t) { + if (e.id !== t.state) + return ( + n.Log.error('ResponseValidator._processSigninParams: State does not match'), + Promise.reject(new Error('State does not match')) + ); + if (!e.client_id) + return ( + n.Log.error('ResponseValidator._processSigninParams: No client_id on state'), + Promise.reject(new Error('No client_id on state')) + ); + if (!e.authority) + return ( + n.Log.error('ResponseValidator._processSigninParams: No authority on state'), + Promise.reject(new Error('No authority on state')) + ); + if (this._settings.authority) { + if (this._settings.authority && this._settings.authority !== e.authority) + return ( + n.Log.error( + 'ResponseValidator._processSigninParams: authority mismatch on settings vs. signin state' + ), + Promise.reject(new Error('authority mismatch on settings vs. signin state')) + ); + } else this._settings.authority = e.authority; + if (this._settings.client_id) { + if (this._settings.client_id && this._settings.client_id !== e.client_id) + return ( + n.Log.error( + 'ResponseValidator._processSigninParams: client_id mismatch on settings vs. signin state' + ), + Promise.reject(new Error('client_id mismatch on settings vs. signin state')) + ); + } else this._settings.client_id = e.client_id; + return ( + n.Log.debug('ResponseValidator._processSigninParams: state validated'), + (t.state = e.data), + t.error + ? (n.Log.warn( + 'ResponseValidator._processSigninParams: Response was error', + t.error + ), + Promise.reject(new s.ErrorResponse(t))) + : e.nonce && !t.id_token + ? (n.Log.error( + 'ResponseValidator._processSigninParams: Expecting id_token in response' + ), + Promise.reject(new Error('No id_token in response'))) + : !e.nonce && t.id_token + ? (n.Log.error( + 'ResponseValidator._processSigninParams: Not expecting id_token in response' + ), + Promise.reject(new Error('Unexpected id_token in response'))) + : Promise.resolve(t) + ); + }), + (ResponseValidator.prototype._processClaims = function _processClaims(e) { + var t = this; + if (e.isOpenIdConnect) { + if ( + (n.Log.debug( + 'ResponseValidator._processClaims: response is OIDC, processing claims' + ), + (e.profile = this._filterProtocolClaims(e.profile)), + this._settings.loadUserInfo && e.access_token) + ) + return ( + n.Log.debug('ResponseValidator._processClaims: loading user info'), + this._userInfoService.getClaims(e.access_token).then(function (r) { + return ( + n.Log.debug( + 'ResponseValidator._processClaims: user info claims received from user info endpoint' + ), + r.sub !== e.profile.sub + ? (n.Log.error( + 'ResponseValidator._processClaims: sub from user info endpoint does not match sub in access_token' + ), + Promise.reject( + new Error( + 'sub from user info endpoint does not match sub in access_token' + ) + )) + : ((e.profile = t._mergeClaims(e.profile, r)), + n.Log.debug( + 'ResponseValidator._processClaims: user info claims received, updated profile:', + e.profile + ), + e) + ); + }) + ); + n.Log.debug('ResponseValidator._processClaims: not loading user info'); + } else + n.Log.debug( + 'ResponseValidator._processClaims: response is not OIDC, not processing claims' + ); + return Promise.resolve(e); + }), + (ResponseValidator.prototype._mergeClaims = function _mergeClaims(e, t) { + var r = Object.assign({}, e); + for (var n in t) { + var i = t[n]; + Array.isArray(i) || (i = [i]); + for (var o = 0; o < i.length; o++) { + var s = i[o]; + r[n] + ? Array.isArray(r[n]) + ? r[n].indexOf(s) < 0 && r[n].push(s) + : r[n] !== s && (r[n] = [r[n], s]) + : (r[n] = s); + } + } + return r; + }), + (ResponseValidator.prototype._filterProtocolClaims = function _filterProtocolClaims(e) { + n.Log.debug('ResponseValidator._filterProtocolClaims, incoming claims:', e); + var t = Object.assign({}, e); + return ( + this._settings._filterProtocolClaims + ? (u.forEach(function (e) { + delete t[e]; + }), + n.Log.debug( + 'ResponseValidator._filterProtocolClaims: protocol claims filtered', + t + )) + : n.Log.debug( + 'ResponseValidator._filterProtocolClaims: protocol claims not filtered' + ), + t + ); + }), + (ResponseValidator.prototype._validateTokens = function _validateTokens(e, t) { + return t.id_token + ? t.access_token + ? (n.Log.debug( + 'ResponseValidator._validateTokens: Validating id_token and access_token' + ), + this._validateIdTokenAndAccessToken(e, t)) + : (n.Log.debug('ResponseValidator._validateTokens: Validating id_token'), + this._validateIdToken(e, t)) + : (n.Log.debug('ResponseValidator._validateTokens: No id_token to validate'), + Promise.resolve(t)); + }), + (ResponseValidator.prototype._validateIdTokenAndAccessToken = + function _validateIdTokenAndAccessToken(e, t) { + var r = this; + return this._validateIdToken(e, t).then(function (e) { + return r._validateAccessToken(e); + }); + }), + (ResponseValidator.prototype._validateIdToken = function _validateIdToken(e, t) { + var r = this; + if (!e.nonce) + return ( + n.Log.error('ResponseValidator._validateIdToken: No nonce on state'), + Promise.reject(new Error('No nonce on state')) + ); + var i = this._joseUtil.parseJwt(t.id_token); + if (!i || !i.header || !i.payload) + return ( + n.Log.error('ResponseValidator._validateIdToken: Failed to parse id_token', i), + Promise.reject(new Error('Failed to parse id_token')) + ); + if (e.nonce !== i.payload.nonce) + return ( + n.Log.error('ResponseValidator._validateIdToken: Invalid nonce in id_token'), + Promise.reject(new Error('Invalid nonce in id_token')) + ); + var o = i.header.kid; + return this._metadataService.getIssuer().then(function (s) { + return ( + n.Log.debug('ResponseValidator._validateIdToken: Received issuer'), + r._metadataService.getSigningKeys().then(function (a) { + if (!a) + return ( + n.Log.error( + 'ResponseValidator._validateIdToken: No signing keys from metadata' + ), + Promise.reject(new Error('No signing keys from metadata')) + ); + n.Log.debug('ResponseValidator._validateIdToken: Received signing keys'); + var u = void 0; + if (o) + u = a.filter(function (e) { + return e.kid === o; + })[0]; + else { + if ((a = r._filterByAlg(a, i.header.alg)).length > 1) + return ( + n.Log.error( + 'ResponseValidator._validateIdToken: No kid found in id_token and more than one key found in metadata' + ), + Promise.reject( + new Error( + 'No kid found in id_token and more than one key found in metadata' + ) + ) + ); + u = a[0]; + } + if (!u) + return ( + n.Log.error( + 'ResponseValidator._validateIdToken: No key matching kid or alg found in signing keys' + ), + Promise.reject(new Error('No key matching kid or alg found in signing keys')) + ); + var c = e.client_id, + h = r._settings.clockSkew; + return ( + n.Log.debug( + 'ResponseValidator._validateIdToken: Validaing JWT; using clock skew (in seconds) of: ', + h + ), + r._joseUtil.validateJwt(t.id_token, u, s, c, h).then(function () { + return ( + n.Log.debug( + 'ResponseValidator._validateIdToken: JWT validation successful' + ), + i.payload.sub + ? ((t.profile = i.payload), t) + : (n.Log.error( + 'ResponseValidator._validateIdToken: No sub present in id_token' + ), + Promise.reject(new Error('No sub present in id_token'))) + ); + }) + ); + }) + ); + }); + }), + (ResponseValidator.prototype._filterByAlg = function _filterByAlg(e, t) { + var r = null; + if (t.startsWith('RS')) r = 'RSA'; + else if (t.startsWith('PS')) r = 'PS'; + else { + if (!t.startsWith('ES')) + return n.Log.debug('ResponseValidator._filterByAlg: alg not supported: ', t), []; + r = 'EC'; + } + return ( + n.Log.debug('ResponseValidator._filterByAlg: Looking for keys that match kty: ', r), + (e = e.filter(function (e) { + return e.kty === r; + })), + n.Log.debug( + 'ResponseValidator._filterByAlg: Number of keys that match kty: ', + r, + e.length + ), + e + ); + }), + (ResponseValidator.prototype._validateAccessToken = function _validateAccessToken(e) { + if (!e.profile) + return ( + n.Log.error( + 'ResponseValidator._validateAccessToken: No profile loaded from id_token' + ), + Promise.reject(new Error('No profile loaded from id_token')) + ); + if (!e.profile.at_hash) + return ( + n.Log.error('ResponseValidator._validateAccessToken: No at_hash in id_token'), + Promise.reject(new Error('No at_hash in id_token')) + ); + if (!e.id_token) + return ( + n.Log.error('ResponseValidator._validateAccessToken: No id_token'), + Promise.reject(new Error('No id_token')) + ); + var t = this._joseUtil.parseJwt(e.id_token); + if (!t || !t.header) + return ( + n.Log.error('ResponseValidator._validateAccessToken: Failed to parse id_token', t), + Promise.reject(new Error('Failed to parse id_token')) + ); + var r = t.header.alg; + if (!r || 5 !== r.length) + return ( + n.Log.error('ResponseValidator._validateAccessToken: Unsupported alg:', r), + Promise.reject(new Error('Unsupported alg: ' + r)) + ); + var i = r.substr(2, 3); + if (!i) + return ( + n.Log.error('ResponseValidator._validateAccessToken: Unsupported alg:', r, i), + Promise.reject(new Error('Unsupported alg: ' + r)) + ); + if (256 !== (i = parseInt(i)) && 384 !== i && 512 !== i) + return ( + n.Log.error('ResponseValidator._validateAccessToken: Unsupported alg:', r, i), + Promise.reject(new Error('Unsupported alg: ' + r)) + ); + var o = 'sha' + i, + s = this._joseUtil.hashString(e.access_token, o); + if (!s) + return ( + n.Log.error('ResponseValidator._validateAccessToken: access_token hash failed:', o), + Promise.reject(new Error('Failed to validate at_hash')) + ); + var a = s.substr(0, s.length / 2), + u = this._joseUtil.hexToBase64Url(a); + return u !== e.profile.at_hash + ? (n.Log.error( + 'ResponseValidator._validateAccessToken: Failed to validate at_hash', + u, + e.profile.at_hash + ), + Promise.reject(new Error('Failed to validate at_hash'))) + : (n.Log.debug('ResponseValidator._validateAccessToken: success'), + Promise.resolve(e)); + }), + ResponseValidator + ); + })(); + }, + function (e, t, r) { + 'use strict'; + Object.defineProperty(t, '__esModule', { value: !0 }); + var n = r(0), + i = r(18), + o = r(6), + s = r(5), + a = r(31), + u = r(30), + c = r(12), + h = r(3), + f = r(20), + l = r(19), + g = r(9), + p = r(8), + d = r(10), + v = r(1), + y = r(13); + (t.default = { + Log: n.Log, + OidcClient: i.OidcClient, + OidcClientSettings: o.OidcClientSettings, + WebStorageStateStore: s.WebStorageStateStore, + InMemoryWebStorage: a.InMemoryWebStorage, + UserManager: u.UserManager, + AccessTokenEvents: c.AccessTokenEvents, + MetadataService: h.MetadataService, + CordovaPopupNavigator: f.CordovaPopupNavigator, + CordovaIFrameNavigator: l.CordovaIFrameNavigator, + CheckSessionIFrame: g.CheckSessionIFrame, + TokenRevocationClient: p.TokenRevocationClient, + SessionMonitor: d.SessionMonitor, + Global: v.Global, + User: y.User, + }), + (e.exports = t.default); + }, + ]); +}); diff --git a/platform/app/public/polyfill.min.js b/platform/app/public/polyfill.min.js index 425c164d04..0786328dc6 100644 --- a/platform/app/public/polyfill.min.js +++ b/platform/app/public/polyfill.min.js @@ -1 +1,184 @@ -!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n()}(0,function(){"use strict";function e(e){var n=this.constructor;return this.then(function(t){return n.resolve(e()).then(function(){return t})},function(t){return n.resolve(e()).then(function(){return n.reject(t)})})}function n(){}function t(e){if(!(this instanceof t))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=undefined,this._deferreds=[],u(e,this)}function o(e,n){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,t._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null!==t){var o;try{o=t(e._value)}catch(f){return void i(n.promise,f)}r(n.promise,o)}else(1===e._state?r:i)(n.promise,e._value)})):e._deferreds.push(n)}function r(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var o=n.then;if(n instanceof t)return e._state=3,e._value=n,void f(e);if("function"==typeof o)return void u(function(e,n){return function(){e.apply(n,arguments)}}(o,n),e)}e._state=1,e._value=n,f(e)}catch(r){i(e,r)}}function i(e,n){e._state=2,e._value=n,f(e)}function f(e){2===e._state&&0===e._deferreds.length&&t._immediateFn(function(){e._handled||t._unhandledRejectionFn(e._value)});for(var n=0,r=e._deferreds.length;r>n;n++)o(e,e._deferreds[n]);e._deferreds=null}function u(e,n){var t=!1;try{e(function(e){t||(t=!0,r(n,e))},function(e){t||(t=!0,i(n,e))})}catch(o){if(t)return;t=!0,i(n,o)}}var c=setTimeout;t.prototype["catch"]=function(e){return this.then(null,e)},t.prototype.then=function(e,t){var r=new this.constructor(n);return o(this,new function(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}(e,t,r)),r},t.prototype["finally"]=e,t.all=function(e){return new t(function(n,t){function o(e,f){try{if(f&&("object"==typeof f||"function"==typeof f)){var u=f.then;if("function"==typeof u)return void u.call(f,function(n){o(e,n)},t)}r[e]=f,0==--i&&n(r)}catch(c){t(c)}}if(!e||"undefined"==typeof e.length)throw new TypeError("Promise.all accepts an array");var r=Array.prototype.slice.call(e);if(0===r.length)return n([]);for(var i=r.length,f=0;r.length>f;f++)o(f,r[f])})},t.resolve=function(e){return e&&"object"==typeof e&&e.constructor===t?e:new t(function(n){n(e)})},t.reject=function(e){return new t(function(n,t){t(e)})},t.race=function(e){return new t(function(n,t){for(var o=0,r=e.length;r>o;o++)e[o].then(n,t)})},t._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){c(e,0)},t._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var l=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"Promise"in l?l.Promise.prototype["finally"]||(l.Promise.prototype["finally"]=e):l.Promise=t}); +!(function (e, n) { + 'object' == typeof exports && 'undefined' != typeof module + ? n() + : 'function' == typeof define && define.amd + ? define(n) + : n(); +})(0, function () { + 'use strict'; + function e(e) { + var n = this.constructor; + return this.then( + function (t) { + return n.resolve(e()).then(function () { + return t; + }); + }, + function (t) { + return n.resolve(e()).then(function () { + return n.reject(t); + }); + } + ); + } + function n() {} + function t(e) { + if (!(this instanceof t)) throw new TypeError('Promises must be constructed via new'); + if ('function' != typeof e) throw new TypeError('not a function'); + (this._state = 0), + (this._handled = !1), + (this._value = undefined), + (this._deferreds = []), + u(e, this); + } + function o(e, n) { + for (; 3 === e._state; ) e = e._value; + 0 !== e._state + ? ((e._handled = !0), + t._immediateFn(function () { + var t = 1 === e._state ? n.onFulfilled : n.onRejected; + if (null !== t) { + var o; + try { + o = t(e._value); + } catch (f) { + return void i(n.promise, f); + } + r(n.promise, o); + } else (1 === e._state ? r : i)(n.promise, e._value); + })) + : e._deferreds.push(n); + } + function r(e, n) { + try { + if (n === e) throw new TypeError('A promise cannot be resolved with itself.'); + if (n && ('object' == typeof n || 'function' == typeof n)) { + var o = n.then; + if (n instanceof t) return (e._state = 3), (e._value = n), void f(e); + if ('function' == typeof o) + return void u( + (function (e, n) { + return function () { + e.apply(n, arguments); + }; + })(o, n), + e + ); + } + (e._state = 1), (e._value = n), f(e); + } catch (r) { + i(e, r); + } + } + function i(e, n) { + (e._state = 2), (e._value = n), f(e); + } + function f(e) { + 2 === e._state && + 0 === e._deferreds.length && + t._immediateFn(function () { + e._handled || t._unhandledRejectionFn(e._value); + }); + for (var n = 0, r = e._deferreds.length; r > n; n++) o(e, e._deferreds[n]); + e._deferreds = null; + } + function u(e, n) { + var t = !1; + try { + e( + function (e) { + t || ((t = !0), r(n, e)); + }, + function (e) { + t || ((t = !0), i(n, e)); + } + ); + } catch (o) { + if (t) return; + (t = !0), i(n, o); + } + } + var c = setTimeout; + (t.prototype['catch'] = function (e) { + return this.then(null, e); + }), + (t.prototype.then = function (e, t) { + var r = new this.constructor(n); + return ( + o( + this, + new (function (e, n, t) { + (this.onFulfilled = 'function' == typeof e ? e : null), + (this.onRejected = 'function' == typeof n ? n : null), + (this.promise = t); + })(e, t, r) + ), + r + ); + }), + (t.prototype['finally'] = e), + (t.all = function (e) { + return new t(function (n, t) { + function o(e, f) { + try { + if (f && ('object' == typeof f || 'function' == typeof f)) { + var u = f.then; + if ('function' == typeof u) + return void u.call( + f, + function (n) { + o(e, n); + }, + t + ); + } + (r[e] = f), 0 == --i && n(r); + } catch (c) { + t(c); + } + } + if (!e || 'undefined' == typeof e.length) + throw new TypeError('Promise.all accepts an array'); + var r = Array.prototype.slice.call(e); + if (0 === r.length) return n([]); + for (var i = r.length, f = 0; r.length > f; f++) o(f, r[f]); + }); + }), + (t.resolve = function (e) { + return e && 'object' == typeof e && e.constructor === t + ? e + : new t(function (n) { + n(e); + }); + }), + (t.reject = function (e) { + return new t(function (n, t) { + t(e); + }); + }), + (t.race = function (e) { + return new t(function (n, t) { + for (var o = 0, r = e.length; r > o; o++) e[o].then(n, t); + }); + }), + (t._immediateFn = + ('function' == typeof setImmediate && + function (e) { + setImmediate(e); + }) || + function (e) { + c(e, 0); + }), + (t._unhandledRejectionFn = function (e) { + void 0 !== console && console && console.warn('Possible Unhandled Promise Rejection:', e); + }); + var l = (function () { + if ('undefined' != typeof self) return self; + if ('undefined' != typeof window) return window; + if ('undefined' != typeof global) return global; + throw Error('unable to locate global object'); + })(); + 'Promise' in l + ? l.Promise.prototype['finally'] || (l.Promise.prototype['finally'] = e) + : (l.Promise = t); +}); diff --git a/platform/app/public/silent-refresh.html b/platform/app/public/silent-refresh.html index c4c0a5f134..089c94b9c8 100644 --- a/platform/app/public/silent-refresh.html +++ b/platform/app/public/silent-refresh.html @@ -1,16 +1,25 @@ - + - - + + Silent OpenID Connect Token Refresh Page - - + + - - - + + + - + diff --git a/platform/app/src/App.tsx b/platform/app/src/App.tsx index 44c3fd7546..cadc05e2e8 100644 --- a/platform/app/src/App.tsx +++ b/platform/app/src/App.tsx @@ -5,12 +5,7 @@ import i18n from '@ohif/i18n'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; import Compose from './routes/Mode/Compose'; -import { - ServicesManager, - ExtensionManager, - CommandsManager, - HotkeysManager, -} from '@ohif/core'; +import { ServicesManager, ExtensionManager, CommandsManager, HotkeysManager } from '@ohif/core'; import { DialogProvider, Modal, @@ -38,9 +33,7 @@ function App({ config, defaultExtensions, defaultModes }) { const [init, setInit] = useState(null); useEffect(() => { const run = async () => { - appInit(config, defaultExtensions, defaultModes) - .then(setInit) - .catch(console.error); + appInit(config, defaultExtensions, defaultModes).then(setInit).catch(console.error); }; run(); @@ -58,13 +51,7 @@ function App({ config, defaultExtensions, defaultModes }) { // Set appConfig const appConfigState = init.appConfig; - const { - routerBasename, - modes, - dataSources, - oidc, - showStudyList, - } = appConfigState; + const { routerBasename, modes, dataSources, oidc, showStudyList } = appConfigState; const { uiDialogService, @@ -89,8 +76,7 @@ function App({ config, defaultExtensions, defaultModes }) { [DialogProvider, { service: uiDialogService }], [ModalProvider, { service: uiModalService, modal: Modal }], ]; - const CombinedProviders = ({ children }) => - Compose({ components: providers, children }); + const CombinedProviders = ({ children }) => Compose({ components: providers, children }); let authRoutes = null; diff --git a/platform/app/src/__tests__/globalSetup.js b/platform/app/src/__tests__/globalSetup.js index 0fecec0801..0aae7f5846 100644 --- a/platform/app/src/__tests__/globalSetup.js +++ b/platform/app/src/__tests__/globalSetup.js @@ -2,8 +2,10 @@ const _ = require('lodash'); const originalConsoleError = console.error; // JSDom's CSS Parser has limited support for certain features -// This supresses error warnings caused by it -console.error = function(msg) { - if (_.startsWith(msg, 'Error: Could not parse CSS stylesheet')) return; +// This suppresses error warnings caused by it +console.error = function (msg) { + if (_.startsWith(msg, 'Error: Could not parse CSS stylesheet')) { + return; + } originalConsoleError(msg); }; diff --git a/platform/app/src/appInit.js b/platform/app/src/appInit.js index c6ddbf7ac6..168d05a045 100644 --- a/platform/app/src/appInit.js +++ b/platform/app/src/appInit.js @@ -76,14 +76,8 @@ async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) { * Example: [ext1, ext2, ext3] * Example2: [[ext1, config], ext2, [ext3, config]] */ - const loadedExtensions = await loadModules([ - ...defaultExtensions, - ...appConfig.extensions, - ]); - await extensionManager.registerExtensions( - loadedExtensions, - appConfig.dataSources - ); + const loadedExtensions = await loadModules([...defaultExtensions, ...appConfig.extensions]); + await extensionManager.registerExtensions(loadedExtensions, appConfig.dataSources); // TODO: We no longer use `utils.addServer` // TODO: We no longer init webWorkers at app level @@ -93,33 +87,36 @@ async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) { throw new Error('No modes are defined! Check your app-config.js'); } - const loadedModes = await loadModules([ - ...(appConfig.modes || []), - ...defaultModes, - ]); + const loadedModes = await loadModules([...(appConfig.modes || []), ...defaultModes]); - // This is the name for the loaded istance object + // This is the name for the loaded instance object appConfig.loadedModes = []; const modesById = new Set(); for (let i = 0; i < loadedModes.length; i++) { let mode = loadedModes[i]; - if (!mode) continue; + if (!mode) { + continue; + } const { id } = mode; if (mode.modeFactory) { // If the appConfig contains configuration for this mode, use it. - const modeConfig = - appConfig.modeConfig && appConfig.modeConfig[i] - ? appConfig.modeConfig[id] + const modeConfiguration = + appConfig.modesConfiguration && appConfig.modesConfiguration[id] + ? appConfig.modesConfiguration[id] : {}; - mode = mode.modeFactory(modeConfig); + mode = mode.modeFactory({ modeConfiguration }); } - if (modesById.has(id)) continue; + if (modesById.has(id)) { + continue; + } // Prevent duplication modesById.add(id); - if (!mode || typeof mode !== 'object') continue; + if (!mode || typeof mode !== 'object') { + continue; + } appConfig.loadedModes.push(mode); } // Hack alert - don't touch the original modes definition, diff --git a/platform/app/src/components/ViewportGrid.tsx b/platform/app/src/components/ViewportGrid.tsx index 67d3280412..e6e1c23947 100644 --- a/platform/app/src/components/ViewportGrid.tsx +++ b/platform/app/src/components/ViewportGrid.tsx @@ -5,21 +5,17 @@ import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui'; import EmptyViewport from './EmptyViewport'; import classNames from 'classnames'; - function ViewerViewportGrid(props) { const { servicesManager, viewportComponents, dataSource } = props; const [viewportGrid, viewportGridService] = useViewportGrid(); - const { layout, activeViewportIndex, viewports } = viewportGrid; + const { layout, activeViewportId, viewports } = viewportGrid; const { numCols, numRows } = layout; // TODO -> Need some way of selecting which displaySets hit the viewports. - const { - displaySetService, - measurementService, - hangingProtocolService, - uiNotificationService, - } = (servicesManager as ServicesManager).services; + const { displaySetService, measurementService, hangingProtocolService, uiNotificationService } = ( + servicesManager as ServicesManager + ).services; /** * This callback runs after the viewports structure has changed in any way. @@ -51,10 +47,11 @@ function ViewerViewportGrid(props) { * specify the viewport match details, which specifies the size and * setup of the various viewports. */ - const findOrCreateViewport = viewportIndex => { - const details = viewportMatchDetails.get(viewportIndex); + const findOrCreateViewport = pos => { + const viewportId = Array.from(viewportMatchDetails.keys())[pos]; + const details = viewportMatchDetails.get(viewportId); if (!details) { - console.log('No match details for viewport', viewportIndex); + console.log('No match details for viewport', viewportId); return; } @@ -62,15 +59,13 @@ function ViewerViewportGrid(props) { const displaySetUIDsToHang = []; const displaySetUIDsToHangOptions = []; - displaySetsInfo.forEach( - ({ displaySetInstanceUID, displaySetOptions }) => { - if (displaySetInstanceUID) { - displaySetUIDsToHang.push(displaySetInstanceUID); - } - - displaySetUIDsToHangOptions.push(displaySetOptions); + displaySetsInfo.forEach(({ displaySetInstanceUID, displaySetOptions }) => { + if (displaySetInstanceUID) { + displaySetUIDsToHang.push(displaySetInstanceUID); } - ); + + displaySetUIDsToHangOptions.push(displaySetOptions); + }); const computedViewportOptions = hangingProtocolService.getComputedOptions( viewportOptions, @@ -99,11 +94,11 @@ function ViewerViewportGrid(props) { }; const _getUpdatedViewports = useCallback( - (viewportIndex, displaySetInstanceUID) => { + (viewportId, displaySetInstanceUID) => { let updatedViewports = []; try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, + viewportId, displaySetInstanceUID ); } catch (error) { @@ -127,12 +122,7 @@ function ViewerViewportGrid(props) { const { unsubscribe } = hangingProtocolService.subscribe( hangingProtocolService.EVENTS.PROTOCOL_CHANGED, ({ protocol, stage, activeStudyUID, viewportMatchDetails }) => { - updateDisplaySetsFromProtocol( - protocol, - stage, - activeStudyUID, - viewportMatchDetails - ); + updateDisplaySetsFromProtocol(protocol, stage, activeStudyUID, viewportMatchDetails); } ); @@ -144,18 +134,16 @@ function ViewerViewportGrid(props) { useEffect(() => { const { unsubscribe } = measurementService.subscribe( MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT, - ({ viewportIndex, measurement, isConsumed }) => { - if (isConsumed) return; + ({ viewportId, measurement, isConsumed }) => { + if (isConsumed) { + return; + } // This occurs when no viewport has elected to consume the event // so we need to change layouts into a layout which can consume // the event. - const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = - measurement; + const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement; - const updatedViewports = _getUpdatedViewports( - viewportIndex, - referencedDisplaySetInstanceUID - ); + const updatedViewports = _getUpdatedViewports(viewportId, referencedDisplaySetInstanceUID); // Arbitrarily assign the viewport to element 0 const viewport = updatedViewports?.[0]; @@ -170,9 +158,7 @@ function ViewerViewportGrid(props) { viewport.viewportOptions ||= {}; viewport.viewportOptions.orientation = 'acquisition'; - const displaySet = displaySetService.getDisplaySetByUID( - referencedDisplaySetInstanceUID - ); + const displaySet = displaySetService.getDisplaySetByUID(referencedDisplaySetInstanceUID); // jump straight to the initial image index if we can if (displaySet.images && measurement.SOPInstanceUID) { for (let index = 0; index < displaySet.images.length; index++) { @@ -195,7 +181,7 @@ function ViewerViewportGrid(props) { }, [viewports]); /** - const onDoubleClick = viewportIndex => { + const onDoubleClick = viewportId => { // TODO -> Disabled for now. // onNewImage on a cornerstone viewport is firing setDisplaySetsForViewport. // Which it really really shouldn't. We need a larger fix for jump to @@ -204,7 +190,7 @@ function ViewerViewportGrid(props) { viewportGridService.set({ numCols: cachedLayout.numCols, numRows: cachedLayout.numRows, - activeViewportIndex: cachedLayout.activeViewportIndex, + activeViewportId: cachedLayout.activeViewportId, viewports: cachedLayout.viewports, cachedLayout: null, }); @@ -221,10 +207,10 @@ function ViewerViewportGrid(props) { viewportGridService.set({ numCols: 1, numRows: 1, - activeViewportIndex: 0, + activeViewportId: 0, viewports: [ { - displaySetInstanceUID: viewports[viewportIndex].displaySetInstanceUID, + displaySetInstanceUID: viewports[viewportId].displaySetInstanceUID, imageIndex: undefined, }, ], @@ -232,17 +218,14 @@ function ViewerViewportGrid(props) { numCols, numRows, viewports: cachedViewports, - activeViewportIndex: viewportIndex, + activeViewportId: viewportId, }, }); }; */ - const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => { - const updatedViewports = _getUpdatedViewports( - viewportIndex, - displaySetInstanceUID - ); + const onDropHandler = (viewportId, { displaySetInstanceUID }) => { + const updatedViewports = _getUpdatedViewports(viewportId, displaySetInstanceUID); viewportGridService.setDisplaySetsForViewports(updatedViewports); }; @@ -251,13 +234,7 @@ function ViewerViewportGrid(props) { const numViewportPanes = viewportGridService.getNumViewportPanes(); for (let i = 0; i < numViewportPanes; i++) { - const viewportIndex = i; - const isActive = activeViewportIndex === viewportIndex; - const paneMetadata = viewports[i] || {}; - const viewportId = paneMetadata.viewportId || `viewport-${i}`; - if (!paneMetadata.viewportId) { - paneMetadata.viewportId = viewportId; - } + const paneMetadata = Array.from(viewports.values())[i] || {}; const { displaySetInstanceUIDs, viewportOptions, @@ -269,16 +246,19 @@ function ViewerViewportGrid(props) { viewportLabel, } = paneMetadata; + const viewportId = viewportOptions.viewportId; + const isActive = activeViewportId === viewportId; + const displaySetInstanceUIDsToUse = displaySetInstanceUIDs || []; - // This is causing the viewport components re-render when the activeViewportIndex changes - const displaySets = displaySetInstanceUIDsToUse.map( - displaySetInstanceUID => { - return ( - displaySetService.getDisplaySetByUID(displaySetInstanceUID) || {} - ); - } - ); + // This is causing the viewport components re-render when the activeViewportId changes + const displaySets = displaySetInstanceUIDsToUse + .map(displaySetInstanceUID => { + return displaySetService.getDisplaySetByUID(displaySetInstanceUID) || {}; + }) + .filter(displaySet => { + return !displaySet?.unsupported; + }); const ViewportComponent = _getViewportComponent( displaySets, @@ -292,24 +272,32 @@ function ViewerViewportGrid(props) { }); const onInteractionHandler = event => { - if (isActive) return; + if (isActive) { + return; + } if (event) { event.preventDefault(); event.stopPropagation(); } - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); }; - // TEMP -> Double click disabled for now - // onDoubleClick={() => onDoubleClick(viewportIndex)} - viewportPanes[i] = (
1 ? viewportLabel : ''} + viewportLabel={viewports.size > 1 ? viewportLabel : ''} + viewportId={viewportId} dataSource={dataSource} viewportOptions={viewportOptions} displaySetOptions={displaySetOptions} @@ -341,7 +329,7 @@ function ViewerViewportGrid(props) { } return viewportPanes; - }, [viewports, activeViewportIndex, viewportComponents, dataSource]); + }, [viewports, activeViewportId, viewportComponents, dataSource]); /** * Loading indicator until numCols and numRows are gotten from the HangingProtocolService @@ -351,7 +339,10 @@ function ViewerViewportGrid(props) { } return ( - + {/* {ViewportPanes} */} {getViewportPanes()} @@ -367,11 +358,7 @@ ViewerViewportGrid.defaultProps = { viewportComponents: [], }; -function _getViewportComponent( - displaySets, - viewportComponents, - uiNotificationService -) { +function _getViewportComponent(displaySets, viewportComponents, uiNotificationService) { if (!displaySets || !displaySets.length) { return EmptyViewport; } @@ -386,9 +373,7 @@ function _getViewportComponent( if (!viewportComponents[i].displaySetsToDisplay) { throw new Error('displaySetsToDisplay is null'); } - if ( - viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId) - ) { + if (viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId)) { const { component } = viewportComponents[i]; return component; } @@ -397,7 +382,7 @@ function _getViewportComponent( console.log("Can't show displaySet", SOPClassHandlerId, displaySets[0]); uiNotificationService.show({ title: 'Viewport Not Supported Yet', - message: `Cannot display SOPClassId of ${displaySets[0].SOPClassUID} yet`, + message: `Cannot display SOPClassUID of ${displaySets[0].SOPClassUID} yet`, type: 'error', }); diff --git a/platform/app/src/hooks/index.js b/platform/app/src/hooks/index.js index 0660f2611f..b2e97562e1 100644 --- a/platform/app/src/hooks/index.js +++ b/platform/app/src/hooks/index.js @@ -1,5 +1,4 @@ import useDebounce from './useDebounce.js'; -import useQuery from './useQuery.js'; -import useSearchParams from './useSearchParams.js'; +import useSearchParams from './useSearchParams'; -export { useDebounce, useQuery, useSearchParams }; +export { useDebounce, useSearchParams }; diff --git a/platform/app/src/hooks/useQuery.js b/platform/app/src/hooks/useQuery.js deleted file mode 100644 index 53cd665954..0000000000 --- a/platform/app/src/hooks/useQuery.js +++ /dev/null @@ -1,5 +0,0 @@ -import { useLocation } from 'react-router-dom'; - -export default function useQuery() { - return new URLSearchParams(useLocation().search); -} diff --git a/platform/app/src/hooks/useSearchParams.js b/platform/app/src/hooks/useSearchParams.js deleted file mode 100644 index c7cd0aee84..0000000000 --- a/platform/app/src/hooks/useSearchParams.js +++ /dev/null @@ -1,17 +0,0 @@ -import useQuery from './useQuery'; - -/** - * It returns a Map of the query parameters in the URL, where the keys are - * lowercase - * @returns A function that returns a Map of the query parameters. - */ -export default function useSearchParams() { - const query = useQuery(); - // make query params case-insensitive - const searchParams = new Map(); - for (const [key, value] of query) { - searchParams.set(key.toLowerCase(), value); - } - - return searchParams; -} diff --git a/platform/app/src/hooks/useSearchParams.ts b/platform/app/src/hooks/useSearchParams.ts new file mode 100644 index 0000000000..3ceaebd9de --- /dev/null +++ b/platform/app/src/hooks/useSearchParams.ts @@ -0,0 +1,24 @@ +import { useLocation } from 'react-router'; + +/** + * It returns a URLSearchParams of the query parameters in the URL, where the keys are + * either lowercase or maintain their case based on the lowerCaseKeys parameter. + * @param {lowerCaseKeys:boolean} true to return lower case keys; false (default) to maintain casing; + * @returns {URLSearchParams} + */ +export default function useSearchParams(options = { lowerCaseKeys: false }) { + const { lowerCaseKeys } = options; + const searchParams = new URLSearchParams(useLocation().search); + + if (!lowerCaseKeys) { + return searchParams; + } + + const lowerCaseSearchParams = new URLSearchParams(); + + for (const [key, value] of searchParams) { + lowerCaseSearchParams.set(key.toLowerCase(), value); + } + + return lowerCaseSearchParams; +} diff --git a/platform/app/src/index.js b/platform/app/src/index.js index 6a6c064bf6..7b8d63324d 100644 --- a/platform/app/src/index.js +++ b/platform/app/src/index.js @@ -16,10 +16,7 @@ import { history } from './utils/history'; * pluginImports.js imports all of the modes and extensions and adds them * to the window for processing. */ -import { - modes as defaultModes, - extensions as defaultExtensions, -} from './pluginImports'; +import { modes as defaultModes, extensions as defaultExtensions } from './pluginImports'; import loadDynamicConfig from './loadDynamicConfig'; loadDynamicConfig(window.config).then(config_json => { diff --git a/platform/app/src/routes/DataSourceWrapper.tsx b/platform/app/src/routes/DataSourceWrapper.tsx index 026bba14c5..7426e270c0 100644 --- a/platform/app/src/routes/DataSourceWrapper.tsx +++ b/platform/app/src/routes/DataSourceWrapper.tsx @@ -1,10 +1,23 @@ /* eslint-disable react/jsx-props-no-spreading */ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { MODULE_TYPES } from '@ohif/core'; +import { ExtensionManager, MODULE_TYPES } from '@ohif/core'; // import { extensionManager } from '../App.tsx'; import { useParams, useLocation } from 'react-router'; +import { useNavigate } from 'react-router-dom'; +import useSearchParams from '../hooks/useSearchParams.ts'; + +/** + * Determines if two React Router location objects are the same. + */ +const areLocationsTheSame = (location0, location1) => { + return ( + location0.pathname === location1.pathname && + location0.search === location1.search && + location0.hash === location1.hash + ); +}; /** * Uses route properties to determine the data source that should be passed @@ -15,40 +28,12 @@ import { useParams, useLocation } from 'react-router'; * @param {function} props.children - Layout Template React Component */ function DataSourceWrapper(props) { + const navigate = useNavigate(); const { children: LayoutTemplate, ...rest } = props; const params = useParams(); const location = useLocation(); - - // TODO - get the variable from the props all the time... - let dataSourceName = new URLSearchParams(location.search).get('datasources'); - const dataPath = dataSourceName ? `/${dataSourceName}` : ''; - - if (!dataSourceName && window.config.defaultDataSourceName) { - dataSourceName = window.config.defaultDataSourceName; - } else if (!dataSourceName) { - // Gets the first defined datasource with the right name - // Mostly for historical reasons - new configs should use the defaultDataSourceName - const dataSourceModules = - extensionManager.modules[MODULE_TYPES.DATA_SOURCE]; - // TODO: Good usecase for flatmap? - const webApiDataSources = dataSourceModules.reduce((acc, curr) => { - const mods = []; - curr.module.forEach(mod => { - if (mod.type === 'webApi') { - mods.push(mod); - } - }); - return acc.concat(mods); - }, []); - dataSourceName = webApiDataSources - .map(ds => ds.name) - .find(it => extensionManager.getDataSources(it)?.[0] !== undefined); - } - const dataSource = extensionManager.getDataSources(dataSourceName)?.[0]; - if (!dataSource) { - throw new Error(`No data source found for ${dataSourceName}`); - } - + const lowerCaseSearchParams = useSearchParams({ lowerCaseKeys: true }); + const query = useSearchParams(); // Route props --> studies.mapParams // mapParams --> studies.search // studies.search --> studies.processResults @@ -63,18 +48,108 @@ function DataSourceWrapper(props) { pageNumber: 1, location: 'Not a valid location, causes first load to occur', }; + + const getInitialDataSourceName = useCallback(() => { + // TODO - get the variable from the props all the time... + let dataSourceName = lowerCaseSearchParams.get('datasources'); + + if (!dataSourceName && window.config.defaultDataSourceName) { + return ''; + } + + if (!dataSourceName) { + // Gets the first defined datasource with the right name + // Mostly for historical reasons - new configs should use the defaultDataSourceName + const dataSourceModules = extensionManager.modules[MODULE_TYPES.DATA_SOURCE]; + // TODO: Good usecase for flatmap? + const webApiDataSources = dataSourceModules.reduce((acc, curr) => { + const mods = []; + curr.module.forEach(mod => { + if (mod.type === 'webApi') { + mods.push(mod); + } + }); + return acc.concat(mods); + }, []); + dataSourceName = webApiDataSources + .map(ds => ds.name) + .find(it => extensionManager.getDataSources(it)?.[0] !== undefined); + } + + return dataSourceName; + }, []); + + const [isDataSourceInitialized, setIsDataSourceInitialized] = useState(false); + + // The path to the data source to be used in the URL for a mode (e.g. mode/dataSourcePath?StudyIntanceUIDs=1.2.3) + const [dataSourcePath, setDataSourcePath] = useState(() => { + const dataSourceName = getInitialDataSourceName(); + return dataSourceName ? `/${dataSourceName}` : ''; + }); + + const [dataSource, setDataSource] = useState(() => { + const dataSourceName = getInitialDataSourceName(); + + if (!dataSourceName) { + return extensionManager.getActiveDataSource()[0]; + } + + const dataSource = extensionManager.getDataSources(dataSourceName)?.[0]; + if (!dataSource) { + throw new Error(`No data source found for ${dataSourceName}`); + } + + return dataSource; + }); + const [data, setData] = useState(DEFAULT_DATA); const [isLoading, setIsLoading] = useState(false); + /** + * The effect to initialize the data source whenever it changes. Similar to + * whenever a different Mode is entered, the Mode's data source is initialized, so + * too this DataSourceWrapper must initialize its data source whenever a different + * data source is activated. Furthermore, a data source might be initialized + * several times as it gets activated/deactivated because the location URL + * might change and data sources initialize based on the URL. + */ + useEffect(() => { + const initializeDataSource = async () => { + await dataSource.initialize({ params, query }); + setIsDataSourceInitialized(true); + }; + + initializeDataSource(); + }, [dataSource]); + useEffect(() => { - const queryFilterValues = _getQueryFilterValues( - location.search, - STUDIES_LIMIT + const dataSourceChangedCallback = () => { + setIsLoading(false); + setIsDataSourceInitialized(false); + setDataSourcePath(''); + setDataSource(extensionManager.getActiveDataSource()[0]); + // Setting data to DEFAULT_DATA triggers a new query just like it does for the initial load. + setData(DEFAULT_DATA); + }; + + const sub = extensionManager.subscribe( + ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED, + dataSourceChangedCallback ); + return () => sub.unsubscribe(); + }, []); + + useEffect(() => { + if (!isDataSourceInitialized) { + return; + } + + const queryFilterValues = _getQueryFilterValues(location.search, STUDIES_LIMIT); // 204: no content async function getData() { setIsLoading(true); + const studies = await dataSource.query.studies.search(queryFilterValues); setData({ @@ -94,27 +169,37 @@ function DataSourceWrapper(props) { // - And we didn't cross a result offset range const isSamePage = data.pageNumber === queryFilterValues.pageNumber; const previousOffset = - Math.floor((data.pageNumber * data.resultsPerPage) / STUDIES_LIMIT) * - (STUDIES_LIMIT - 1); + Math.floor((data.pageNumber * data.resultsPerPage) / STUDIES_LIMIT) * (STUDIES_LIMIT - 1); const newOffset = Math.floor( - (queryFilterValues.pageNumber * queryFilterValues.resultsPerPage) / - STUDIES_LIMIT + (queryFilterValues.pageNumber * queryFilterValues.resultsPerPage) / STUDIES_LIMIT ) * (STUDIES_LIMIT - 1); - const isLocationUpdated = data.location !== location; + // Simply checking data.location !== location is not sufficient because even though the location href (i.e. entire URL) + // has not changed, the React Router still provides a new location reference and would result in two study queries + // on initial load. Alternatively, window.location.href could be used. + const isLocationUpdated = + typeof data.location === 'string' || !areLocationsTheSame(data.location, location); const isDataInvalid = - !isSamePage || - (!isLoading && (newOffset !== previousOffset || isLocationUpdated)); + !isSamePage || (!isLoading && (newOffset !== previousOffset || isLocationUpdated)); if (isDataInvalid) { - getData(); + getData().catch(() => { + // If there is a data source configuration API, then the Worklist will popup the dialog to attempt to configure it + // and attempt to resolve this issue. + if (dataSource.getConfig().configurationAPI) { + return; + } + + // No data source configuration API, so navigate to the not found server page. + navigate('/notfoundserver', '_self'); + }); } } catch (ex) { console.warn(ex); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data, location, params, isLoading, setIsLoading]); + }, [data, location, params, isLoading, setIsLoading, dataSource, isDataSourceInitialized]); // queryFilterValues // TODO: Better way to pass DataSource? @@ -122,7 +207,7 @@ function DataSourceWrapper(props) { -
-
+
+
OHIF -
-
-

- Debug Information -

-
-

- Cross Origin Isolated (COOP/COEP) -

+
+
+

Debug Information

+
+

Cross Origin Isolated (COOP/COEP)

{!window.crossOriginIsolated && ( -
- We use SharedArrayBuffer to render volume data (e.g., MPR). - If you are seeing this message, it means that your browser - has not enabled COOP/COEP. Please see the following link for - more information:{' '} +
+ We use SharedArrayBuffer to render volume data (e.g., MPR). If you are seeing + this message, it means that your browser has not enabled COOP/COEP. Please see + the following link for more information:{' '} { return ( - + {({ getRootProps, getInputProps }) => (
- - ); - })} +
+ {appConfig.loadedModes.map((mode, i) => { + const modalitiesToCheck = modalities.replaceAll('/', '\\'); + + const isValidMode = mode.isValidMode({ + modalities: modalitiesToCheck, + study, + }); + // TODO: Modes need a default/target route? We mostly support a single one for now. + // We should also be using the route path, but currently are not + // mode.routeName + // mode.routes[x].path + // Don't specify default data source, and it should just be picked up... (this may not currently be the case) + // How do we know which params to pass? Today, it's just StudyInstanceUIDs and configUrl if exists + const query = new URLSearchParams(); + if (filterValues.configUrl) { + query.append('configUrl', filterValues.configUrl); + } + query.append('StudyInstanceUIDs', studyInstanceUid); + return ( + mode.displayName && ( + { + // In case any event bubbles up for an invalid mode, prevent the navigation. + // For example, the event bubbles up when the icon embedded in the disabled button is clicked. + if (!isValidMode) { + event.preventDefault(); + } + }} + // to={`${mode.routeName}/dicomweb?StudyInstanceUIDs=${studyInstanceUid}`} + > + {/* TODO revisit the completely rounded style of buttons used for launching a mode from the worklist later - for now use LegacyButton*/} + } // launch-arrow | launch-info + onClick={() => {}} + > + {t(`Modes:${mode.displayName}`)} + + + ) + ); + })} +
), onClickRow: () => - setExpandedRows(s => - isExpanded ? s.filter(n => rowKey !== n) : [...s, rowKey] - ), + setExpandedRows(s => (isExpanded ? s.filter(n => rowKey !== n) : [...s, rowKey])), isExpanded, }; }); @@ -409,16 +416,16 @@ function WorkList({ title: t('UserPreferencesModal:User Preferences'), content: UserPreferences, contentProps: { - hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions( - hotkeyDefaults - ), + hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions(hotkeyDefaults), hotkeyDefinitions, onCancel: hide, currentLanguage: currentLanguage(), availableLanguages, defaultLanguage, onSubmit: state => { - i18n.changeLanguage(state.language.value); + if (state.language.value !== currentLanguage().value) { + i18n.changeLanguage(state.language.value); + } hotkeysManager.setHotkeys(state.hotkeyDefinitions); hide(); }, @@ -434,9 +441,7 @@ function WorkList({ icon: 'power-off', title: t('Header:Logout'), onClick: () => { - navigate( - `/logout?redirect_uri=${encodeURIComponent(window.location.href)}` - ); + navigate(`/logout?redirect_uri=${encodeURIComponent(window.location.href)}`); }, }); } @@ -445,7 +450,7 @@ function WorkList({ const { component: dicomUploadComponent } = customizationService.get('dicomUploadComponent') ?? {}; const uploadProps = - dicomUploadComponent && dataSource.getConfig().dicomUploadEnabled + dicomUploadComponent && dataSource.getConfig()?.dicomUploadEnabled ? { title: 'Upload files', closeButton: true, @@ -468,15 +473,18 @@ function WorkList({ } : undefined; + const { component: dataSourceConfigurationComponent } = + customizationService.get('ohif.dataSourceConfigurationComponent') ?? {}; + return ( -
+
-
+
100 ? 101 : numOfStudies} filtersMeta={filtersMeta} @@ -485,12 +493,16 @@ function WorkList({ clearFilters={() => setFilterValues(defaultFilterValues)} isFiltering={isFiltering(filterValues, defaultFilterValues)} onUploadClick={uploadProps ? () => show(uploadProps) : undefined} + getDataSourceConfigurationComponent={ + dataSourceConfigurationComponent ? () => dataSourceConfigurationComponent() : undefined + } /> {hasStudies ? ( -
+
@@ -505,7 +517,7 @@ function WorkList({ ) : (
{appConfig.showLoadingIndicator && isLoadingData ? ( - + ) : ( )} @@ -563,9 +575,7 @@ function _getQueryFilterValues(params) { endDate: params.get('enddate') || null, }, description: params.get('description'), - modalities: params.get('modalities') - ? params.get('modalities').split(',') - : [], + modalities: params.get('modalities') ? params.get('modalities').split(',') : [], accession: params.get('accession'), sortBy: params.get('sortby'), sortDirection: params.get('sortdirection'), @@ -589,9 +599,7 @@ function _sortStringDates(s1, s2, sortModifier) { const s2Date = moment(s2.date, ['YYYYMMDD', 'YYYY.MM.DD'], true); if (s1Date.isValid() && s2Date.isValid()) { - return ( - (s1Date.toISOString() > s2Date.toISOString() ? 1 : -1) * sortModifier - ); + return (s1Date.toISOString() > s2Date.toISOString() ? 1 : -1) * sortModifier; } else if (s1Date.isValid()) { return sortModifier; } else if (s2Date.isValid()) { diff --git a/platform/app/src/routes/buildModeRoutes.tsx b/platform/app/src/routes/buildModeRoutes.tsx index c3375788d5..be46177b68 100644 --- a/platform/app/src/routes/buildModeRoutes.tsx +++ b/platform/app/src/routes/buildModeRoutes.tsx @@ -63,16 +63,14 @@ export default function buildModeRoutes({ }); }); - const defaultDataSourceName = extensionManager.defaultDataSourceName; - - // Add default DataSource route. + // Add active DataSource route. + // This is the DataSource route for the active data source defined in ExtensionManager.getActiveDataSource const path = `/${mode.routeName}`; // TODO move up. const children = () => ( { + return ( +
+
+

{message}

+
+
+ ); +}; + +NotFoundServer.propTypes = { + message: PropTypes.string, +}; + +const NotFoundStudy = () => { + return ( +
+
+

+ One or more of the requested studies are not available at this time. Return to the{' '} + + study list + {' '} + to select a different study to view. +

+
+
+ ); +}; + +NotFoundStudy.propTypes = { + message: PropTypes.string, +}; // TODO: Include "routes" debug route if dev build const bakedInRoutes = [ + { + path: '/notfoundserver', + children: NotFoundServer, + }, + { + path: '/notfoundstudy', + children: NotFoundStudy, + }, { path: '/debug', children: Debug, @@ -56,12 +105,10 @@ const createRoutes = ({ path: '/', children: DataSourceWrapper, private: true, - props: { children: WorkList, servicesManager }, + props: { children: WorkList, servicesManager, extensionManager }, }; - const customRoutes = customizationService.getGlobalCustomization( - 'customRoutes' - ); + const customRoutes = customizationService.getGlobalCustomization('customRoutes'); const allRoutes = [ ...routes, ...(showStudyList ? [WorkListRoute] : []), @@ -73,7 +120,10 @@ const createRoutes = ({ function RouteWithErrorBoundary({ route, ...rest }) { // eslint-disable-next-line react/jsx-props-no-spreading return ( - + + } diff --git a/platform/app/src/service-worker.js b/platform/app/src/service-worker.js index e97198b410..a235b8c792 100644 --- a/platform/app/src/service-worker.js +++ b/platform/app/src/service-worker.js @@ -1,13 +1,11 @@ -navigator.serviceWorker.getRegistrations().then(function(registrations) { +navigator.serviceWorker.getRegistrations().then(function (registrations) { for (let registration of registrations) { registration.unregister(); } }); // https://developers.google.com/web/tools/workbox/guides/troubleshoot-and-debug -importScripts( - 'https://storage.googleapis.com/workbox-cdn/releases/5.0.0-beta.1/workbox-sw.js' -); +importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.0.0-beta.1/workbox-sw.js'); // Install newest // https://developers.google.com/web/tools/workbox/modules/workbox-core diff --git a/platform/app/src/utils/OpenIdConnectRoutes.tsx b/platform/app/src/utils/OpenIdConnectRoutes.tsx index 861bd770bb..4552b0527e 100644 --- a/platform/app/src/utils/OpenIdConnectRoutes.tsx +++ b/platform/app/src/utils/OpenIdConnectRoutes.tsx @@ -34,18 +34,13 @@ const initUserManager = (oidc, routerBasename) => { const baseUri = `${protocol}//${host}${routerBasename}`; const redirect_uri = firstOpenIdClient.redirect_uri || '/callback'; - const silent_redirect_uri = - firstOpenIdClient.silent_redirect_uri || '/silent-refresh.html'; - const post_logout_redirect_uri = - firstOpenIdClient.post_logout_redirect_uri || '/'; + const silent_redirect_uri = firstOpenIdClient.silent_redirect_uri || '/silent-refresh.html'; + const post_logout_redirect_uri = firstOpenIdClient.post_logout_redirect_uri || '/'; const openIdConnectConfiguration = Object.assign({}, firstOpenIdClient, { redirect_uri: _makeAbsoluteIfNecessary(redirect_uri, baseUri), silent_redirect_uri: _makeAbsoluteIfNecessary(silent_redirect_uri, baseUri), - post_logout_redirect_uri: _makeAbsoluteIfNecessary( - post_logout_redirect_uri, - baseUri - ), + post_logout_redirect_uri: _makeAbsoluteIfNecessary(post_logout_redirect_uri, baseUri), }); return getUserManagerForOpenIdConnectClient(openIdConnectConfiguration); @@ -77,18 +72,12 @@ function LoginComponent(userManager) { const ohifRedirectTo = { pathname: new URL(targetLinkUri).pathname, }; - sessionStorage.setItem( - 'ohif-redirect-to', - JSON.stringify(ohifRedirectTo) - ); + sessionStorage.setItem('ohif-redirect-to', JSON.stringify(ohifRedirectTo)); } else { const ohifRedirectTo = { pathname: '/', }; - sessionStorage.setItem( - 'ohif-redirect-to', - JSON.stringify(ohifRedirectTo) - ); + sessionStorage.setItem('ohif-redirect-to', JSON.stringify(ohifRedirectTo)); } if (loginHint !== null) { @@ -101,23 +90,25 @@ function LoginComponent(userManager) { return null; } -function OpenIdConnectRoutes({ - oidc, - routerBasename, - userAuthenticationService, -}) { +function OpenIdConnectRoutes({ oidc, routerBasename, userAuthenticationService }) { const userManager = initUserManager(oidc, routerBasename); const getAuthorizationHeader = () => { const user = userAuthenticationService.getUser(); + // if the user is null return early, next time + // we hit this function we will have a user + if (!user) { + return; + } + return { Authorization: `Bearer ${user.access_token}`, }; }; - const handleUnauthenticated = () => { - userManager.signinRedirect(); + const handleUnauthenticated = async () => { + await userManager.signinRedirect(); // return null because this is used in a react component return null; @@ -131,9 +122,7 @@ function OpenIdConnectRoutes({ const storageEventListener = event => { const signOutEvent = localStorage.getItem('signoutEvent'); if (signOutEvent) { - navigate( - `/logout?redirect_uri=${encodeURIComponent(window.location.href)}` - ); + navigate(`/logout?redirect_uri=${encodeURIComponent(window.location.href)}`); } }; @@ -159,24 +148,21 @@ function OpenIdConnectRoutes({ const { pathname, search } = location; const redirect_uri = new URL(userManager.settings._redirect_uri).pathname; //.replace(routerBasename,'') - const silent_refresh_uri = new URL(userManager.settings._silent_redirect_uri) - .pathname; //.replace(routerBasename,'') - const post_logout_redirect_uri = new URL( - userManager.settings._post_logout_redirect_uri - ).pathname; //.replace(routerBasename,''); + const silent_refresh_uri = new URL(userManager.settings._silent_redirect_uri).pathname; //.replace(routerBasename,'') + const post_logout_redirect_uri = new URL(userManager.settings._post_logout_redirect_uri).pathname; //.replace(routerBasename,''); // const pathnameRelative = pathname.replace(routerBasename,''); if (pathname !== redirect_uri) { - sessionStorage.setItem( - 'ohif-redirect-to', - JSON.stringify({ pathname, search }) - ); + sessionStorage.setItem('ohif-redirect-to', JSON.stringify({ pathname, search })); } return ( - + ({ ...theme('spacing'), - '0': '0', + 0: '0', auto: 'auto', full: '100%', viewport: '0.5rem', @@ -329,7 +325,7 @@ module.exports = { }), minHeight: theme => ({ ...theme('spacing'), - '0': '0', + 0: '0', full: '100%', screen: '100vh', }), @@ -348,14 +344,14 @@ module.exports = { normal: '1.5', relaxed: '1.625', loose: '2', - '3': '.75rem', - '4': '1rem', - '5': '1.25rem', - '6': '1.5rem', - '7': '1.75rem', - '8': '2rem', - '9': '2.25rem', - '10': '2.5rem', + 3: '.75rem', + 4: '1rem', + 5: '1.25rem', + 6: '1.5rem', + 7: '1.75rem', + 8: '2rem', + 9: '2.25rem', + 10: '2.5rem', }, listStyleType: { none: 'none', @@ -390,7 +386,7 @@ module.exports = { }), minWidth: theme => ({ ...theme('spacing'), - '0': '0', + 0: '0', xs: '2rem', sm: '4rem', md: '6rem', @@ -410,44 +406,44 @@ module.exports = { top: 'top', }, opacity: { - '0': '0', - '5': '.5', - '10': '.10', - '15': '.15', - '20': '.20', - '25': '.25', - '30': '.30', - '35': '.35', - '40': '.40', - '45': '.45', - '50': '.50', - '55': '.55', - '60': '.60', - '65': '.65', - '70': '.70', - '75': '.75', - '80': '.80', - '85': '.85', - '90': '.90', - '95': '.95', - '100': '1', + 0: '0', + 5: '.5', + 10: '.10', + 15: '.15', + 20: '.20', + 25: '.25', + 30: '.30', + 35: '.35', + 40: '.40', + 45: '.45', + 50: '.50', + 55: '.55', + 60: '.60', + 65: '.65', + 70: '.70', + 75: '.75', + 80: '.80', + 85: '.85', + 90: '.90', + 95: '.95', + 100: '1', }, order: { first: '-9999', last: '9999', none: '0', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', }, padding: theme => theme('spacing'), placeholderColor: theme => theme('colors'), @@ -456,9 +452,9 @@ module.exports = { current: 'currentColor', }), strokeWidth: { - '0': '0', - '1': '1', - '2': '2', + 0: '0', + 1: '1', + 2: '2', }, textColor: theme => theme('colors'), width: theme => ({ @@ -519,28 +515,28 @@ module.exports = { }), zIndex: { auto: 'auto', - '0': '0', - '10': '10', - '20': '20', - '30': '30', - '40': '40', - '50': '50', + 0: '0', + 10: '10', + 20: '20', + 30: '30', + 40: '40', + 50: '50', }, gap: theme => theme('spacing'), gridTemplateColumns: { none: 'none', - '1': 'repeat(1, minmax(0, 1fr))', - '2': 'repeat(2, minmax(0, 1fr))', - '3': 'repeat(3, minmax(0, 1fr))', - '4': 'repeat(4, minmax(0, 1fr))', - '5': 'repeat(5, minmax(0, 1fr))', - '6': 'repeat(6, minmax(0, 1fr))', - '7': 'repeat(7, minmax(0, 1fr))', - '8': 'repeat(8, minmax(0, 1fr))', - '9': 'repeat(9, minmax(0, 1fr))', - '10': 'repeat(10, minmax(0, 1fr))', - '11': 'repeat(11, minmax(0, 1fr))', - '12': 'repeat(12, minmax(0, 1fr))', + 1: 'repeat(1, minmax(0, 1fr))', + 2: 'repeat(2, minmax(0, 1fr))', + 3: 'repeat(3, minmax(0, 1fr))', + 4: 'repeat(4, minmax(0, 1fr))', + 5: 'repeat(5, minmax(0, 1fr))', + 6: 'repeat(6, minmax(0, 1fr))', + 7: 'repeat(7, minmax(0, 1fr))', + 8: 'repeat(8, minmax(0, 1fr))', + 9: 'repeat(9, minmax(0, 1fr))', + 10: 'repeat(10, minmax(0, 1fr))', + 11: 'repeat(11, minmax(0, 1fr))', + 12: 'repeat(12, minmax(0, 1fr))', }, gridColumn: { auto: 'auto', @@ -559,44 +555,44 @@ module.exports = { }, gridColumnStart: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', - '13': '13', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', }, gridColumnEnd: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', - '13': '13', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', }, gridTemplateRows: { none: 'none', - '1': 'repeat(1, minmax(0, 1fr))', - '2': 'repeat(2, minmax(0, 1fr))', - '3': 'repeat(3, minmax(0, 1fr))', - '4': 'repeat(4, minmax(0, 1fr))', - '5': 'repeat(5, minmax(0, 1fr))', - '6': 'repeat(6, minmax(0, 1fr))', + 1: 'repeat(1, minmax(0, 1fr))', + 2: 'repeat(2, minmax(0, 1fr))', + 3: 'repeat(3, minmax(0, 1fr))', + 4: 'repeat(4, minmax(0, 1fr))', + 5: 'repeat(5, minmax(0, 1fr))', + 6: 'repeat(6, minmax(0, 1fr))', }, gridRow: { auto: 'auto', @@ -609,23 +605,23 @@ module.exports = { }, gridRowStart: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', }, gridRowEnd: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', }, transformOrigin: { center: 'center', @@ -639,25 +635,25 @@ module.exports = { 'top-left': 'top left', }, scale: { - '0': '0', - '50': '.5', - '75': '.75', - '90': '.9', - '95': '.95', - '100': '1', - '105': '1.05', - '110': '1.1', - '125': '1.25', - '150': '1.5', + 0: '0', + 50: '.5', + 75: '.75', + 90: '.9', + 95: '.95', + 100: '1', + 105: '1.05', + 110: '1.1', + 125: '1.25', + 150: '1.5', }, rotate: { '-180': '-180deg', '-90': '-90deg', '-45': '-45deg', - '0': '0', - '45': '45deg', - '90': '90deg', - '180': '180deg', + 0: '0', + 45: '45deg', + 90: '90deg', + 180: '180deg', }, translate: (theme, { negative }) => ({ ...theme('spacing'), @@ -671,10 +667,10 @@ module.exports = { '-12': '-12deg', '-6': '-6deg', '-3': '-3deg', - '0': '0', - '3': '3deg', - '6': '6deg', - '12': '12deg', + 0: '0', + 3: '3deg', + 6: '6deg', + 12: '12deg', }, transitionProperty: { none: 'none', @@ -694,14 +690,14 @@ module.exports = { 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', }, transitionDuration: { - '75': '75ms', - '100': '100ms', - '150': '150ms', - '200': '200ms', - '300': '300ms', - '500': '500ms', - '700': '700ms', - '1000': '1000ms', + 75: '75ms', + 100: '100ms', + 150: '150ms', + 200: '200ms', + 300: '300ms', + 500: '500ms', + 700: '700ms', + 1000: '1000ms', }, }, variants: { @@ -711,26 +707,12 @@ module.exports = { alignSelf: ['responsive'], appearance: ['responsive'], backgroundAttachment: ['responsive'], - backgroundColor: [ - 'responsive', - 'hover', - 'focus', - 'active', - 'group-focus', - 'group-hover', - ], + backgroundColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'], backgroundPosition: ['responsive'], backgroundRepeat: ['responsive'], backgroundSize: ['responsive'], borderCollapse: ['responsive'], - borderColor: [ - 'responsive', - 'hover', - 'focus', - 'active', - 'group-focus', - 'group-hover', - ], + borderColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'], borderRadius: ['responsive', 'focus', 'first', 'last'], borderStyle: ['responsive', 'focus'], borderWidth: ['responsive', 'focus', 'first', 'last'], diff --git a/platform/cli/CHANGELOG.md b/platform/cli/CHANGELOG.md new file mode 100644 index 0000000000..6b90925f2a --- /dev/null +++ b/platform/cli/CHANGELOG.md @@ -0,0 +1,235 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/cli + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/cli diff --git a/platform/cli/package.json b/platform/cli/package.json index a677a87d5c..12297d3744 100644 --- a/platform/cli/package.json +++ b/platform/cli/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/cli", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "A CLI to bootstrap new OHIF extension or mode", "type": "module", "main": "src/index.js", diff --git a/platform/cli/src/commands/addExtension.js b/platform/cli/src/commands/addExtension.js index 62ca566efc..37e2c3d09c 100644 --- a/platform/cli/src/commands/addExtension.js +++ b/platform/cli/src/commands/addExtension.js @@ -44,9 +44,7 @@ export default async function addExtension(packageName, version) { .run() .then(ctx => { console.log( - `${chalk.green.bold( - `Added ohif-extension ${packageName}@${ctx.yarnInfo.version}` - )} ` + `${chalk.green.bold(`Added ohif-extension ${packageName}@${ctx.yarnInfo.version}`)} ` ); }) .catch(error => { diff --git a/platform/cli/src/commands/addExtensions.js b/platform/cli/src/commands/addExtensions.js index 5da1d07107..cf5252d13b 100644 --- a/platform/cli/src/commands/addExtensions.js +++ b/platform/cli/src/commands/addExtensions.js @@ -28,9 +28,7 @@ export default async function addExtensions(ohifExtensions) { extensonsString += ` ${packageName}@${version}`; }); - console.log( - `${chalk.green.bold(`Extensions added:${extensonsString}`)} ` - ); + console.log(`${chalk.green.bold(`Extensions added:${extensonsString}`)} `); }) .catch(error => { console.log(error.message); diff --git a/platform/cli/src/commands/addMode.js b/platform/cli/src/commands/addMode.js index 6769261389..827f2e8650 100644 --- a/platform/cli/src/commands/addMode.js +++ b/platform/cli/src/commands/addMode.js @@ -39,9 +39,7 @@ export default async function addMode(packageName, version) { { title: 'Detecting required ohif-extensions...', task: async ctx => { - ctx.ohifExtensions = await findRequiredOhifExtensionsForMode( - ctx.yarnInfo - ); + ctx.ohifExtensions = await findRequiredOhifExtensionsForMode(ctx.yarnInfo); }, }, ], @@ -53,11 +51,7 @@ export default async function addMode(packageName, version) { await tasks .run() .then(async ctx => { - console.log( - `${chalk.green.bold( - `Added ohif-mode ${packageName}@${ctx.yarnInfo.version}` - )} ` - ); + console.log(`${chalk.green.bold(`Added ohif-mode ${packageName}@${ctx.yarnInfo.version}`)} `); const ohifExtensions = ctx.ohifExtensions; diff --git a/platform/cli/src/commands/createPackage.js b/platform/cli/src/commands/createPackage.js index 84fa7d4549..3b1f20a5f5 100644 --- a/platform/cli/src/commands/createPackage.js +++ b/platform/cli/src/commands/createPackage.js @@ -10,7 +10,7 @@ import { initGit, } from './utils/index.js'; -const createPackage = async (options) => { +const createPackage = async options => { const { packageType } = options; // extension or mode if (fs.existsSync(options.targetDir)) { @@ -28,11 +28,7 @@ const createPackage = async (options) => { { title: 'Copying template files', task: () => - createDirectoryContents( - options.templateDir, - options.targetDir, - options.prettier - ), + createDirectoryContents(options.templateDir, options.targetDir, options.prettier), }, { title: 'Editing Package.json with provided information', @@ -59,36 +55,20 @@ const createPackage = async (options) => { await tasks.run(); console.log(); - console.log( - chalk.green(`Done: ${packageType} is ready at`, options.targetDir) - ); + console.log(chalk.green(`Done: ${packageType} is ready at`, options.targetDir)); console.log(); - console.log( - chalk.green(`NOTE: In order to use this ${packageType} for development,`) - ); - console.log( - chalk.green( - `run the following command inside the root of the OHIF monorepo` - ) - ); + console.log(chalk.green(`NOTE: In order to use this ${packageType} for development,`)); + console.log(chalk.green(`run the following command inside the root of the OHIF monorepo`)); console.log(); - console.log( - chalk.green.bold( - ` yarn run cli link-${packageType} ${options.targetDir}` - ) - ); + console.log(chalk.green.bold(` yarn run cli link-${packageType} ${options.targetDir}`)); console.log(); console.log( - chalk.yellow( - "and when you don't need it anymore, run the following command to unlink it" - ) + chalk.yellow("and when you don't need it anymore, run the following command to unlink it") ); console.log(); - console.log( - chalk.yellow(` yarn run cli unlink-${packageType} ${options.name}`) - ); + console.log(chalk.yellow(` yarn run cli unlink-${packageType} ${options.name}`)); console.log(); return true; diff --git a/platform/cli/src/commands/linkPackage.js b/platform/cli/src/commands/linkPackage.js index 23764db23d..527713e822 100644 --- a/platform/cli/src/commands/linkPackage.js +++ b/platform/cli/src/commands/linkPackage.js @@ -2,11 +2,7 @@ import fs from 'fs'; import path from 'path'; import { execa } from 'execa'; import { keywords } from './enums/index.js'; -import { - validateYarn, - addExtensionToConfig, - addModeToConfig, -} from './utils/index.js'; +import { validateYarn, addExtensionToConfig, addModeToConfig } from './utils/index.js'; async function linkPackage(packageDir, options, addToConfig, keyword) { const { viewerDirectory } = options; @@ -43,11 +39,7 @@ async function linkPackage(packageDir, options, addToConfig, keyword) { // Add the node_modules of the linked package so that webpack // can find the linked package externals if there are - const webpackPwaPath = path.join( - viewerDirectory, - '.webpack', - 'webpack.pwa.js' - ); + const webpackPwaPath = path.join(viewerDirectory, '.webpack', 'webpack.pwa.js'); async function updateWebpackConfig(webpackConfigPath, packageDir) { const packageNodeModules = path.join(packageDir, 'node_modules'); diff --git a/platform/cli/src/commands/removeExtension.js b/platform/cli/src/commands/removeExtension.js index e29ec135e8..e5345b2b59 100644 --- a/platform/cli/src/commands/removeExtension.js +++ b/platform/cli/src/commands/removeExtension.js @@ -19,8 +19,7 @@ export default async function removeExtension(packageName) { }, { title: `Checking if ${packageName} is in use by an installed mode`, - task: async () => - await throwIfExtensionUsedByInstalledMode(packageName), + task: async () => await throwIfExtensionUsedByInstalledMode(packageName), }, { title: `Uninstalling npm package: ${packageName}`, @@ -39,9 +38,7 @@ export default async function removeExtension(packageName) { await tasks .run() .then(() => { - console.log( - `${chalk.green.bold(`Removed ohif-extension ${packageName}`)} ` - ); + console.log(`${chalk.green.bold(`Removed ohif-extension ${packageName}`)} `); }) .catch(error => { console.log(error.message); diff --git a/platform/cli/src/commands/removeExtensions.js b/platform/cli/src/commands/removeExtensions.js index 2b29e1be80..951af30061 100644 --- a/platform/cli/src/commands/removeExtensions.js +++ b/platform/cli/src/commands/removeExtensions.js @@ -28,9 +28,7 @@ export default async function removeExtensions(ohifExtensionsToRemove) { extensonsString += ` ${packageName}`; }); - console.log( - `${chalk.green.bold(`Extensions removed:${extensonsString}`)} ` - ); + console.log(`${chalk.green.bold(`Extensions removed:${extensonsString}`)} `); }) .catch(error => { console.log(error.message); diff --git a/platform/cli/src/commands/unlinkPackage.js b/platform/cli/src/commands/unlinkPackage.js index 463e749646..8aaa9f871c 100644 --- a/platform/cli/src/commands/unlinkPackage.js +++ b/platform/cli/src/commands/unlinkPackage.js @@ -1,11 +1,7 @@ import { execa } from 'execa'; import fs from 'fs'; import path from 'path'; -import { - validateYarn, - removeExtensionFromConfig, - removeModeFromConfig, -} from './utils/index.js'; +import { validateYarn, removeExtensionFromConfig, removeModeFromConfig } from './utils/index.js'; const linkPackage = async (packageName, options, removeFromConfig) => { const { viewerDirectory } = options; @@ -19,11 +15,7 @@ const linkPackage = async (packageName, options, removeFromConfig) => { const results = await execa(`yarn`, ['unlink', packageName]); console.log(results.stdout); - const webpackPwaPath = path.join( - viewerDirectory, - '.webpack', - 'webpack.pwa.js' - ); + const webpackPwaPath = path.join(viewerDirectory, '.webpack', 'webpack.pwa.js'); await removePathFromWebpackConfig(webpackPwaPath, packageName); @@ -58,8 +50,7 @@ async function removePathFromWebpackConfig(webpackConfigPath, packageName) { endIndex++; } - const modifiedFileContent = - fileContent.slice(0, startIndex) + fileContent.slice(endIndex); + const modifiedFileContent = fileContent.slice(0, startIndex) + fileContent.slice(endIndex); await fs.promises.writeFile(webpackConfigPath, modifiedFileContent); } diff --git a/platform/cli/src/commands/utils/createDirectoryContents.js b/platform/cli/src/commands/utils/createDirectoryContents.js index 90a9329c64..4ea3d65e10 100644 --- a/platform/cli/src/commands/utils/createDirectoryContents.js +++ b/platform/cli/src/commands/utils/createDirectoryContents.js @@ -1,11 +1,7 @@ import fs from 'fs'; // https://github.dev/leoroese/template-cli/blob/628dd24db7df399ebb520edd0bc301bc7b5e8b66/index.js#L19 -const createDirectoryContents = ( - templatePath, - targetDirPath, - copyPrettierRules -) => { +const createDirectoryContents = (templatePath, targetDirPath, copyPrettierRules) => { const filesToCreate = fs.readdirSync(templatePath); filesToCreate.forEach(file => { @@ -22,7 +18,9 @@ const createDirectoryContents = ( const contents = fs.readFileSync(origFilePath, 'utf8'); // Rename - if (file === '.npmignore') file = '.gitignore'; + if (file === '.npmignore') { + file = '.gitignore'; + } const writePath = `${targetDirPath}/${file}`; fs.writeFileSync(writePath, contents, 'utf8'); diff --git a/platform/cli/src/commands/utils/findOhifExtensionsToRemoveAfterRemovingMode.js b/platform/cli/src/commands/utils/findOhifExtensionsToRemoveAfterRemovingMode.js index be69cc6d14..afb295cb69 100644 --- a/platform/cli/src/commands/utils/findOhifExtensionsToRemoveAfterRemovingMode.js +++ b/platform/cli/src/commands/utils/findOhifExtensionsToRemoveAfterRemovingMode.js @@ -1,9 +1,7 @@ import { readPluginConfigFile } from './private/index.js'; import getYarnInfo from './getYarnInfo.js'; -export default async function findOhifExtensionsToRemoveAfterRemovingMode( - removedModeYarnInfo -) { +export default async function findOhifExtensionsToRemoveAfterRemovingMode(removedModeYarnInfo) { const pluginConfig = readPluginConfigFile(); if (!pluginConfig) { @@ -13,27 +11,21 @@ export default async function findOhifExtensionsToRemoveAfterRemovingMode( const { modes, extensions } = pluginConfig; - const registeredExtensions = extensions.map( - extension => extension.packageName - ); + const registeredExtensions = extensions.map(extension => extension.packageName); // TODO this is not a function - const ohifExtensionsOfMode = Object.keys( - removedModeYarnInfo.peerDependencies - ).filter(peerDependency => registeredExtensions.includes(peerDependency)); - - const ohifExtensionsUsedInOtherModes = ohifExtensionsOfMode.map( - packageName => { - return { - packageName, - used: false, - }; - } + const ohifExtensionsOfMode = Object.keys(removedModeYarnInfo.peerDependencies).filter( + peerDependency => registeredExtensions.includes(peerDependency) ); + const ohifExtensionsUsedInOtherModes = ohifExtensionsOfMode.map(packageName => { + return { + packageName, + used: false, + }; + }); + // Check if other modes use each extension used by this mode - const otherModes = modes.filter( - mode => mode.packageName !== removedModeYarnInfo.name - ); + const otherModes = modes.filter(mode => mode.packageName !== removedModeYarnInfo.name); for (let i = 0; i < otherModes.length; i++) { const mode = otherModes[i]; diff --git a/platform/cli/src/commands/utils/findRequiredOhifExtensionsForMode.js b/platform/cli/src/commands/utils/findRequiredOhifExtensionsForMode.js index c907a56b0e..f895abdd05 100644 --- a/platform/cli/src/commands/utils/findRequiredOhifExtensionsForMode.js +++ b/platform/cli/src/commands/utils/findRequiredOhifExtensionsForMode.js @@ -11,7 +11,7 @@ export default async function findRequiredOhifExtensionsForMode(yarnInfo) { const dependencies = []; const ohifExtensions = []; - Object.keys(peerDependencies).forEach((packageName) => { + Object.keys(peerDependencies).forEach(packageName => { dependencies.push({ packageName, version: peerDependencies[packageName], diff --git a/platform/cli/src/commands/utils/index.js b/platform/cli/src/commands/utils/index.js index b053c73c93..ee32c1efbc 100644 --- a/platform/cli/src/commands/utils/index.js +++ b/platform/cli/src/commands/utils/index.js @@ -10,10 +10,7 @@ import { import getYarnInfo from './getYarnInfo.js'; import { addExtensionToConfig, addModeToConfig } from './addToConfig.js'; import findRequiredOhifExtensionsForMode from './findRequiredOhifExtensionsForMode.js'; -import { - removeExtensionFromConfig, - removeModeFromConfig, -} from './removeFromConfig.js'; +import { removeExtensionFromConfig, removeModeFromConfig } from './removeFromConfig.js'; import throwIfExtensionUsedByInstalledMode from './throwIfExtensionUsedByInstalledMode.js'; import findOhifExtensionsToRemoveAfterRemovingMode from './findOhifExtensionsToRemoveAfterRemovingMode.js'; import initGit from './initGit.js'; diff --git a/platform/cli/src/commands/utils/private/manipulatePluginConfigFile.js b/platform/cli/src/commands/utils/private/manipulatePluginConfigFile.js index 37afbda00c..119314d729 100644 --- a/platform/cli/src/commands/utils/private/manipulatePluginConfigFile.js +++ b/platform/cli/src/commands/utils/private/manipulatePluginConfigFile.js @@ -17,9 +17,7 @@ function removeModeFromConfigJson(pluginConfig, { packageName }) { function removeFromList(listName, pluginConfig, { packageName }) { const list = pluginConfig[listName]; - const indexOfExistingEntry = list.findIndex( - entry => entry.packageName === packageName - ); + const indexOfExistingEntry = list.findIndex(entry => entry.packageName === packageName); if (indexOfExistingEntry !== -1) { pluginConfig[listName].splice(indexOfExistingEntry, 1); diff --git a/platform/cli/src/commands/utils/validate.js b/platform/cli/src/commands/utils/validate.js index bd782a73ed..356dd901f2 100644 --- a/platform/cli/src/commands/utils/validate.js +++ b/platform/cli/src/commands/utils/validate.js @@ -25,9 +25,7 @@ async function validateExtensionYarnInfo(packageName) { function validateYarnInfo(packageName, keyword) { return new Promise(async (resolve, reject) => { function rejectIfNotFound() { - const error = new Error( - `${chalk.red.bold('Error')} extension ${packageName} not installed` - ); + const error = new Error(`${chalk.red.bold('Error')} extension ${packageName} not installed`); reject(error); } @@ -77,14 +75,14 @@ function getVersion(json, version) { const [majorVersion] = version .split('^')[1] .split('.') - .map((v) => parseInt(v)); + .map(v => parseInt(v)); // Find the version that matches the major version, but is the latest minor version versions - .filter((version) => parseInt(version.split('.')[0]) === majorVersion) + .filter(version => parseInt(version.split('.')[0]) === majorVersion) .sort((a, b) => { - const [majorA, minorA, patchA] = a.split('.').map((v) => parseInt(v)); - const [majorB, minorB, patchB] = b.split('.').map((v) => parseInt(v)); + const [majorA, minorA, patchA] = a.split('.').map(v => parseInt(v)); + const [majorB, minorB, patchB] = b.split('.').map(v => parseInt(v)); if (majorA === majorB) { if (minorA === minorB) { @@ -110,19 +108,17 @@ function validate(packageName, version, keyword) { // Gets the registry of the package. Scoped packages may not be using the global default. const registryUrlOfPackage = registryUrl(scope); - let options = {} - if (process.env.NPM_TOKEN){ + let options = {}; + if (process.env.NPM_TOKEN) { options['headers'] = { - 'Authorization': `Bearer ${process.env.NPM_TOKEN}`, - } + Authorization: `Bearer ${process.env.NPM_TOKEN}`, + }; } const response = await fetch(`${registryUrlOfPackage}${packageName}`, options); const json = await response.json(); if (json.error && json.error === NOT_FOUND) { - const error = new Error( - `${chalk.red.bold('Error')} package ${packageName} not found` - ); + const error = new Error(`${chalk.red.bold('Error')} package ${packageName} not found`); reject(error); return; } @@ -139,27 +135,18 @@ function validate(packageName, version, keyword) { resolve(true); } else { const error = new Error( - `${chalk.red.bold( - 'Error' - )} package ${packageName} is not an ${keyword}` + `${chalk.red.bold('Error')} package ${packageName} is not an ${keyword}` ); reject(error); } } else { // Particular version undefined const error = new Error( - `${chalk.red.bold( - 'Error' - )} version ${packageVersion} of package ${packageName} not found` + `${chalk.red.bold('Error')} version ${packageVersion} of package ${packageName} not found` ); reject(error); } }); } -export { - validateMode, - validateExtension, - validateModeYarnInfo, - validateExtensionYarnInfo, -}; +export { validateMode, validateExtension, validateModeYarnInfo, validateExtensionYarnInfo }; diff --git a/platform/cli/src/index.js b/platform/cli/src/index.js index e6b40563d8..d14445abb7 100755 --- a/platform/cli/src/index.js +++ b/platform/cli/src/index.js @@ -32,15 +32,11 @@ try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); if (packageJson.name !== 'ohif-monorepo-root') { console.log(packageJson); - console.log( - chalk.red('ohif-cli must run from the root of the OHIF platform') - ); + console.log(chalk.red('ohif-cli must run from the root of the OHIF platform')); process.exit(1); } } catch (error) { - console.log( - chalk.red('ohif-cli must run from the root of the OHIF platform') - ); + console.log(chalk.red('ohif-cli must run from the root of the OHIF platform')); process.exit(1); } @@ -142,15 +138,11 @@ program program .command('link-extension ') - .description( - 'Links a local OHIF Extension to the Viewer to be used for development' - ) + .description('Links a local OHIF Extension to the Viewer to be used for development') .action(packageDir => { if (!fs.existsSync(packageDir)) { console.log( - chalk.red( - 'The Extension directory does not exist, please provide a valid directory' - ) + chalk.red('The Extension directory does not exist, please provide a valid directory') ); process.exit(1); } @@ -171,16 +163,10 @@ program program .command('link-mode ') - .description( - 'Links a local OHIF Mode to the Viewer to be used for development' - ) + .description('Links a local OHIF Mode to the Viewer to be used for development') .action(packageDir => { if (!fs.existsSync(packageDir)) { - console.log( - chalk.red( - 'The Mode directory does not exist, please provide a valid directory' - ) - ); + console.log(chalk.red('The Mode directory does not exist, please provide a valid directory')); process.exit(1); } linkMode(packageDir, { viewerDirectory }); diff --git a/platform/cli/templates/extension/.prettierrc b/platform/cli/templates/extension/.prettierrc index b80ec6b347..914020ac70 100644 --- a/platform/cli/templates/extension/.prettierrc +++ b/platform/cli/templates/extension/.prettierrc @@ -1,8 +1,10 @@ { + "plugins": ["prettier-plugin-tailwindcss"], "trailingComma": "es5", - "printWidth": 80, + "printWidth": 100, "proseWrap": "always", "tabWidth": 2, "semi": true, - "singleQuote": true + "singleQuote": true, + "arrowParens": "avoid" } diff --git a/platform/cli/templates/extension/babel.config.js b/platform/cli/templates/extension/babel.config.js index 92fbbdeaf9..371f77fcf4 100644 --- a/platform/cli/templates/extension/babel.config.js +++ b/platform/cli/templates/extension/babel.config.js @@ -1,5 +1,26 @@ +// https://babeljs.io/docs/en/options#babelrcroots +const { extendDefaultPlugins } = require('svgo'); + module.exports = { - plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'], + plugins: [ + [ + 'inline-react-svg', + { + svgo: { + plugins: extendDefaultPlugins([ + { + name: 'removeViewBox', + active: false, + }, + ]), + }, + }, + ], + ['@babel/plugin-proposal-class-properties', { loose: true }], + '@babel/plugin-transform-typescript', + ['@babel/plugin-proposal-private-property-in-object', { loose: true }], + ['@babel/plugin-proposal-private-methods', { loose: true }], + ], env: { test: { presets: [ @@ -10,15 +31,16 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", ], '@babel/preset-react', + '@babel/preset-typescript', ], plugins: [ '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-syntax-dynamic-import', '@babel/plugin-transform-regenerator', '@babel/plugin-transform-runtime', + '@babel/plugin-transform-typescript', ], }, production: { @@ -26,7 +48,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +57,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/platform/cli/templates/extension/dependencies.json b/platform/cli/templates/extension/dependencies.json index fcbb4101b6..b9d3b9e1ec 100644 --- a/platform/cli/templates/extension/dependencies.json +++ b/platform/cli/templates/extension/dependencies.json @@ -9,7 +9,7 @@ }, "scripts": { "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", - "dev:dicom-pdf": "yarn run dev", + "dev:my-extension": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package": "yarn run build", "start": "yarn run dev" @@ -40,14 +40,14 @@ "@babel/plugin-transform-arrow-functions": "^7.16.7", "@babel/plugin-transform-regenerator": "^7.16.7", "@babel/plugin-transform-runtime": "^7.17.0", - "babel-plugin-inline-react-svg": "^2.0.1", "@babel/plugin-transform-typescript": "^7.13.0", "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.13.0", - - "babel-eslint": "^8.0.3", - "babel-loader": "^8.0.0-beta.4", + "babel-eslint": "9.x", + "babel-loader": "^8.2.4", + "babel-plugin-inline-react-svg": "^2.0.2", + "babel-plugin-module-resolver": "^5.0.0", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^10.2.0", "cross-env": "^7.0.3", diff --git a/platform/cli/templates/extension/src/index.tsx b/platform/cli/templates/extension/src/index.tsx index fa659bced1..24eff3fb89 100644 --- a/platform/cli/templates/extension/src/index.tsx +++ b/platform/cli/templates/extension/src/index.tsx @@ -16,44 +16,28 @@ export default { * (e.g. cornerstone, cornerstoneTools, ...) or registering any services that * this extension is providing. */ - preRegistration: ({ - servicesManager, - commandsManager, - configuration = {}, - }) => {}, + preRegistration: ({ servicesManager, commandsManager, configuration = {} }) => {}, /** * PanelModule should provide a list of panels that will be available in OHIF * for Modes to consume and render. Each panel is defined by a {name, * iconName, iconLabel, label, component} object. Example of a panel module * is the StudyBrowserPanel that is provided by the default extension in OHIF. */ - getPanelModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getPanelModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * ViewportModule should provide a list of viewports that will be available in OHIF * for Modes to consume and use in the viewports. Each viewport is defined by * {name, component} object. Example of a viewport module is the CornerstoneViewport * that is provided by the Cornerstone extension in OHIF. */ - getViewportModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getViewportModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * ToolbarModule should provide a list of tool buttons that will be available in OHIF * for Modes to consume and use in the toolbar. Each tool button is defined by * {name, defaultComponent, clickHandler }. Examples include radioGroupIcons and * splitButton toolButton that the default extension is providing. */ - getToolbarModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getToolbarModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * LayoutTemplateMOdule should provide a list of layout templates that will be * available in OHIF for Modes to consume and use to layout the viewer. @@ -62,22 +46,14 @@ export default { * a Header, left and right sidebars, and a viewport section in the middle * of the viewer. */ - getLayoutTemplateModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getLayoutTemplateModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * SopClassHandlerModule should provide a list of sop class handlers that will be * available in OHIF for Modes to consume and use to create displaySets from Series. * Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}. * Examples include the default sop class handler provided by the default extension */ - getSopClassHandlerModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getSopClassHandlerModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * HangingProtocolModule should provide a list of hanging protocols that will be * available in OHIF for Modes to use to decide on the structure of the viewports @@ -85,11 +61,7 @@ export default { * { name, protocols}. Examples include the default hanging protocol provided by * the default extension that shows 2x2 viewports. */ - getHangingProtocolModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getHangingProtocolModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * CommandsModule should provide a list of commands that will be available in OHIF * for Modes to consume and use in the viewports. Each command is defined by @@ -97,30 +69,18 @@ export default { * object of functions, definitions is an object of available commands, their * options, and defaultContext is the default context for the command to run against. */ - getCommandsModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getCommandsModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * ContextModule should provide a list of context that will be available in OHIF * and will be provided to the Modes. A context is a state that is shared OHIF. * Context is defined by an object of { name, context, provider }. Examples include * the measurementTracking context provided by the measurementTracking extension. */ - getContextModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getContextModule: ({ servicesManager, commandsManager, extensionManager }) => {}, /** * DataSourceModule should provide a list of data sources to be used in OHIF. * DataSources can be used to map the external data formats to the OHIF's * native format. DataSources are defined by an object of { name, type, createDataSource }. */ - getDataSourcesModule: ({ - servicesManager, - commandsManager, - extensionManager, - }) => {}, + getDataSourcesModule: ({ servicesManager, commandsManager, extensionManager }) => {}, }; diff --git a/platform/cli/templates/mode/.prettierrc b/platform/cli/templates/mode/.prettierrc index b80ec6b347..914020ac70 100644 --- a/platform/cli/templates/mode/.prettierrc +++ b/platform/cli/templates/mode/.prettierrc @@ -1,8 +1,10 @@ { + "plugins": ["prettier-plugin-tailwindcss"], "trailingComma": "es5", - "printWidth": 80, + "printWidth": 100, "proseWrap": "always", "tabWidth": 2, "semi": true, - "singleQuote": true + "singleQuote": true, + "arrowParens": "avoid" } diff --git a/platform/cli/templates/mode/babel.config.js b/platform/cli/templates/mode/babel.config.js index 92fbbdeaf9..a38ddda212 100644 --- a/platform/cli/templates/mode/babel.config.js +++ b/platform/cli/templates/mode/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/platform/cli/templates/mode/src/index.tsx b/platform/cli/templates/mode/src/index.tsx index 2086aa36b8..2d63d10618 100644 --- a/platform/cli/templates/mode/src/index.tsx +++ b/platform/cli/templates/mode/src/index.tsx @@ -41,11 +41,7 @@ function modeFactory({ modeConfiguration }) { * Services and other resources. */ onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { - const { - measurementService, - toolbarService, - toolGroupService, - } = servicesManager.services; + const { measurementService, toolbarService, toolGroupService } = servicesManager.services; measurementService.clearMeasurements(); @@ -57,7 +53,6 @@ function modeFactory({ modeConfiguration }) { const activateTool = () => { toolbarService.recordInteraction({ groupId: 'WindowLevel', - itemId: 'WindowLevel', interactionType: 'tool', commands: [ { diff --git a/platform/core/.all-contributorsrc b/platform/core/.all-contributorsrc index f8dc5c140a..9f6138f450 100644 --- a/platform/core/.all-contributorsrc +++ b/platform/core/.all-contributorsrc @@ -1,7 +1,5 @@ { - "files": [ - "README.md" - ], + "files": ["README.md"], "imageSize": 100, "commit": false, "contributors": [ @@ -10,83 +8,63 @@ "name": "Erik Ziegler", "avatar_url": "https://avatars3.githubusercontent.com/u/607793?v=4", "profile": "https://github.com/swederik", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "evren217", "name": "Evren Ozkan", "avatar_url": "https://avatars1.githubusercontent.com/u/4920551?v=4", "profile": "https://github.com/evren217", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "galelis", "name": "Gustavo André Lelis", "avatar_url": "https://avatars3.githubusercontent.com/u/2378326?v=4", "profile": "https://github.com/galelis", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "dannyrb", "name": "Danny Brown", "avatar_url": "https://avatars1.githubusercontent.com/u/5797588?v=4", "profile": "http://dannyrb.com/", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "allcontributors", "name": "allcontributors[bot]", "avatar_url": "https://avatars3.githubusercontent.com/u/46843839?v=4", "profile": "https://github.com/all-contributors/all-contributors-bot", - "contributions": [ - "doc" - ] + "contributions": ["doc"] }, { "login": "ivan-aksamentov", "name": "Ivan Aksamentov", "avatar_url": "https://avatars0.githubusercontent.com/u/9403403?v=4", "profile": "https://github.com/ivan-aksamentov", - "contributions": [ - "code", - "test" - ] + "contributions": ["code", "test"] }, { "login": "igoroctaviano", "name": "Igor Octaviano", "avatar_url": "https://avatars0.githubusercontent.com/u/13886933?v=4", "profile": "http://igoroctaviano.com", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "dlwire", "name": "David Wire", "avatar_url": "https://avatars3.githubusercontent.com/u/1167291?v=4", "profile": "https://github.com/dlwire", - "contributions": [ - "code", - "test" - ] + "contributions": ["code", "test"] }, { "login": "pavertomato", "name": "Egor Lezhnin", "avatar_url": "https://avatars0.githubusercontent.com/u/878990?v=4", "profile": "http://egor.lezhn.in", - "contributions": [ - "code" - ] + "contributions": ["code"] } ], "contributorsPerLine": 7, diff --git a/platform/core/.webpack/webpack.dev.js b/platform/core/.webpack/webpack.dev.js index c496972cdf..1b8e34cfd1 100644 --- a/platform/core/.webpack/webpack.dev.js +++ b/platform/core/.webpack/webpack.dev.js @@ -3,12 +3,10 @@ const webpackCommon = require('./../../../.webpack/webpack.base.js'); const SRC_DIR = path.join(__dirname, '../src'); const DIST_DIR = path.join(__dirname, '../dist'); - const ENTRY = { app: `${SRC_DIR}/index.ts`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/platform/core/.webpack/webpack.prod.js b/platform/core/.webpack/webpack.prod.js index 4b599bec2a..beff3bba3c 100644 --- a/platform/core/.webpack/webpack.prod.js +++ b/platform/core/.webpack/webpack.prod.js @@ -36,11 +36,6 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@cornerstonejs/], }); }; diff --git a/platform/core/CHANGELOG.md b/platform/core/CHANGELOG.md index 64bb01a5a6..6b5f145097 100644 --- a/platform/core/CHANGELOG.md +++ b/platform/core/CHANGELOG.md @@ -3,6 +3,265 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + + +### Bug Fixes + +* **mpr:** Return the original/raw hanging protocol when fetching and preserving the current active protocol. ([#3670](https://github.com/OHIF/Viewers/issues/3670)) ([221dedd](https://github.com/OHIF/Viewers/commit/221dedde5dd4df086276406a9fa2da1cc23b4eb1)) + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + + +### Bug Fixes + +* **measurements:** Update the calibration tool to match changes in CS3D ([#3505](https://github.com/OHIF/Viewers/issues/3505)) ([38af311](https://github.com/OHIF/Viewers/commit/38af3112ec1f94f36c0ef64ff1cf9d21c0981c81)) + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/core + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) + + + + + ## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.5...@ohif/core@2.9.6) (2020-05-14) diff --git a/platform/core/README.md b/platform/core/README.md index ac3b6c6bd9..ff8f555467 100644 --- a/platform/core/README.md +++ b/platform/core/README.md @@ -42,7 +42,7 @@ implementation][react-viewer]. ### Install -> This library is pre- v1.0. All realeases until a v1.0 have the possibility of +> This library is pre- v1.0. All releases until a v1.0 have the possibility of > introducing breaking changes. Please depend on an "exact" version in your > projects to prevent issues caused by loose versioning. diff --git a/platform/core/babel.config.js b/platform/core/babel.config.js index fed6f05fec..325ca2a8ee 100644 --- a/platform/core/babel.config.js +++ b/platform/core/babel.config.js @@ -1 +1 @@ -module.exports = require("../../babel.config.js"); +module.exports = require('../../babel.config.js'); diff --git a/platform/core/package.json b/platform/core/package.json index 36456d2754..e36f75f1b5 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/core", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "Generic business logic for web-based medical imaging applications", "author": "OHIF Core Team", "license": "MIT", @@ -35,8 +35,8 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", - "@cornerstonejs/dicom-image-loader": "^0.6.8", - "@ohif/ui": "3.6.0", + "@cornerstonejs/dicom-image-loader": "^1.16.5", + "@ohif/ui": "3.7.0-beta.85", "cornerstone-math": "0.1.9", "dicom-parser": "^1.8.21" }, diff --git a/platform/core/src/DICOMWeb/getAttribute.js b/platform/core/src/DICOMWeb/getAttribute.js index ae6b34b056..b07f3cd8cd 100644 --- a/platform/core/src/DICOMWeb/getAttribute.js +++ b/platform/core/src/DICOMWeb/getAttribute.js @@ -25,10 +25,18 @@ function convertToInt(input) { function padFour(input) { const l = input.length; - if (l == 0) return '0000'; - if (l == 1) return '000' + input; - if (l == 2) return '00' + input; - if (l == 3) return '0' + input; + if (l === 0) { + return '0000'; + } + if (l === 1) { + return '000' + input; + } + if (l === 2) { + return '00' + input; + } + if (l === 3) { + return '0' + input; + } return input; } diff --git a/platform/core/src/DICOMWeb/getAttribute.test.js b/platform/core/src/DICOMWeb/getAttribute.test.js index 8432c8dcaa..a0dcc6d3de 100644 --- a/platform/core/src/DICOMWeb/getAttribute.test.js +++ b/platform/core/src/DICOMWeb/getAttribute.test.js @@ -26,9 +26,7 @@ describe('getAttribute', () => { expect(getAttribute(nullElement, defaultValue)).toEqual(defaultValue); expect(getAttribute(undefinedElement, defaultValue)).toEqual(defaultValue); - expect(getAttribute(noValuePresentElement, defaultValue)).toEqual( - defaultValue - ); + expect(getAttribute(noValuePresentElement, defaultValue)).toEqual(defaultValue); }); it('should return 48 for element with value 0', () => { diff --git a/platform/core/src/DICOMWeb/getAuthorizationHeader.js b/platform/core/src/DICOMWeb/getAuthorizationHeader.js index a154b74627..4d996bb97d 100644 --- a/platform/core/src/DICOMWeb/getAuthorizationHeader.js +++ b/platform/core/src/DICOMWeb/getAuthorizationHeader.js @@ -1,4 +1,4 @@ -import 'isomorphic-base64' +import 'isomorphic-base64'; import user from '../user'; /** diff --git a/platform/core/src/DICOMWeb/getAuthorizationHeader.test.js b/platform/core/src/DICOMWeb/getAuthorizationHeader.test.js index dc593bd5f6..9bf0b4636b 100644 --- a/platform/core/src/DICOMWeb/getAuthorizationHeader.test.js +++ b/platform/core/src/DICOMWeb/getAuthorizationHeader.test.js @@ -28,9 +28,7 @@ describe('getAuthorizationHeader', () => { }; const expectedAuthorizationHeader = { - Authorization: `Basic ${btoa( - validServerWithoutPassword.requestOptions.auth - )}`, + Authorization: `Basic ${btoa(validServerWithoutPassword.requestOptions.auth)}`, }; const authentication = getAuthorizationHeader(validServerWithoutPassword); diff --git a/platform/core/src/DICOMWeb/getModalities.test.js b/platform/core/src/DICOMWeb/getModalities.test.js index debae322df..1ce13bb06f 100644 --- a/platform/core/src/DICOMWeb/getModalities.test.js +++ b/platform/core/src/DICOMWeb/getModalities.test.js @@ -23,9 +23,7 @@ describe('getModalities', () => { vr: 'MOCKED_VALUE', }; - expect(getModalities(Modality, ModalitiesInStudy)).toEqual( - ModalitiesInStudy - ); + expect(getModalities(Modality, ModalitiesInStudy)).toEqual(ModalitiesInStudy); }); test('should return only the modalitues that exists in ModalitiesInStudy', () => { @@ -67,8 +65,6 @@ describe('getModalities', () => { vr: 'ANOTHER_VR', }; - expect(getModalities(Modality, ModalitiesInStudy)).toEqual( - ModalitiesInStudy - ); + expect(getModalities(Modality, ModalitiesInStudy)).toEqual(ModalitiesInStudy); }); }); diff --git a/platform/core/src/DICOMWeb/getName.test.js b/platform/core/src/DICOMWeb/getName.test.js index b42a928c79..2003cb0ecc 100644 --- a/platform/core/src/DICOMWeb/getName.test.js +++ b/platform/core/src/DICOMWeb/getName.test.js @@ -32,12 +32,7 @@ describe('getName', () => { it('should return A for element when Alphabetic is [A, B, C, D]', () => { const returnValue = 'A'; const element = { - Value: [ - { Alphabetic: 'A' }, - { Alphabetic: 'B' }, - { Alphabetic: 'C' }, - { Alphabetic: 'D' }, - ], + Value: [{ Alphabetic: 'A' }, { Alphabetic: 'B' }, { Alphabetic: 'C' }, { Alphabetic: 'D' }], }; expect(getName(element, null)).toEqual(returnValue); }); diff --git a/platform/core/src/DICOMWeb/getNumber.test.js b/platform/core/src/DICOMWeb/getNumber.test.js index 18f10524e0..ab0cb0814b 100644 --- a/platform/core/src/DICOMWeb/getNumber.test.js +++ b/platform/core/src/DICOMWeb/getNumber.test.js @@ -26,9 +26,7 @@ describe('getNumber', () => { expect(getNumber(nullElement, defaultValue)).toEqual(defaultValue); expect(getNumber(undefinedElement, defaultValue)).toEqual(defaultValue); - expect(getNumber(noValuePresentElement, defaultValue)).toEqual( - defaultValue - ); + expect(getNumber(noValuePresentElement, defaultValue)).toEqual(defaultValue); }); it('should return 2.0 for element when element.Value[0] = 2', () => { diff --git a/platform/core/src/DICOMWeb/getString.test.js b/platform/core/src/DICOMWeb/getString.test.js index 34d0295536..727df7f9ef 100644 --- a/platform/core/src/DICOMWeb/getString.test.js +++ b/platform/core/src/DICOMWeb/getString.test.js @@ -26,9 +26,7 @@ describe('getString', () => { expect(getString(nullElement, defaultValue)).toEqual(defaultValue); expect(getString(undefinedElement, defaultValue)).toEqual(defaultValue); - expect(getString(noValuePresentElement, defaultValue)).toEqual( - defaultValue - ); + expect(getString(noValuePresentElement, defaultValue)).toEqual(defaultValue); }); it('should return A,B,C,D for element when element.Value[0] = [A, B, C, D]', () => { diff --git a/platform/core/src/DICOMWeb/index.js b/platform/core/src/DICOMWeb/index.js index 24ac21239a..0775d92749 100644 --- a/platform/core/src/DICOMWeb/index.js +++ b/platform/core/src/DICOMWeb/index.js @@ -14,13 +14,6 @@ const DICOMWeb = { getString, }; -export { - getAttribute, - getAuthorizationHeader, - getModalities, - getName, - getNumber, - getString, -}; +export { getAttribute, getAuthorizationHeader, getModalities, getName, getNumber, getString }; export default DICOMWeb; diff --git a/platform/core/src/DataSources/IWebApiDataSource.js b/platform/core/src/DataSources/IWebApiDataSource.js index a0b1a713f0..9d9a5581ee 100644 --- a/platform/core/src/DataSources/IWebApiDataSource.js +++ b/platform/core/src/DataSources/IWebApiDataSource.js @@ -22,6 +22,7 @@ function create({ getImageIdsForDisplaySet, getImageIdsForInstance, getConfig, + getStudyInstanceUIDs, }) { const defaultQuery = { studies: { @@ -73,6 +74,7 @@ function create({ getImageIdsForDisplaySet, getImageIdsForInstance, getConfig: getConfig || defaultGetConfig, + getStudyInstanceUIDs: getStudyInstanceUIDs, }; } diff --git a/platform/core/src/__mocks__/dicomweb-client.js b/platform/core/src/__mocks__/dicomweb-client.js index c076a1d721..b75c269179 100644 --- a/platform/core/src/__mocks__/dicomweb-client.js +++ b/platform/core/src/__mocks__/dicomweb-client.js @@ -1,9 +1,9 @@ // import { api } from 'dicomweb-client' const api = { - DICOMwebClient: jest.fn().mockImplementation(function() { + DICOMwebClient: jest.fn().mockImplementation(function () { this.retrieveStudyMetadata = jest.fn().mockResolvedValue([]); - this.retrieveSeriesMetadata = jest.fn(function(options) { + this.retrieveSeriesMetadata = jest.fn(function (options) { const { studyInstanceUID, seriesInstanceUID } = options; return Promise.resolve([{ studyInstanceUID, seriesInstanceUID }]); }); diff --git a/platform/core/src/classes/CommandsManager.test.js b/platform/core/src/classes/CommandsManager.test.js index 7b9fee3ea6..399003834f 100644 --- a/platform/core/src/classes/CommandsManager.test.js +++ b/platform/core/src/classes/CommandsManager.test.js @@ -94,10 +94,7 @@ describe('CommandsManager', () => { describe('getCommand()', () => { it('returns undefined if context does not exist', () => { - const result = commandsManager.getCommand( - 'TestCommand', - 'NonExistentContext' - ); + const result = commandsManager.getCommand('TestCommand', 'NonExistentContext'); expect(result).toBe(undefined); }); @@ -131,9 +128,7 @@ describe('CommandsManager', () => { describe('runCommand()', () => { it('Logs a warning if commandName not found in context', () => { - const result = commandsManager.runCommand( - 'CommandThatDoesNotExistInAnyContext' - ); + const result = commandsManager.runCommand('CommandThatDoesNotExistInAnyContext'); expect(result).toBe(undefined); expect(log.warn.mock.calls[0][0]).toEqual( @@ -149,16 +144,8 @@ describe('CommandsManager', () => { }; commandsManager.createContext(contextName); - commandsManager.registerCommand( - contextName, - 'TestCommand', - commandWithNoCommmandFn - ); - const result = commandsManager.runCommand( - 'TestCommand', - null, - contextName - ); + commandsManager.registerCommand(contextName, 'TestCommand', commandWithNoCommmandFn); + const result = commandsManager.runCommand('TestCommand', null, contextName); expect(result).toBe(undefined); expect(log.warn.mock.calls[0][0]).toEqual( @@ -192,9 +179,7 @@ describe('CommandsManager', () => { commandsManager.runCommand('TestCommand', runCommandOptions, 'VIEWER'); expect(command.commandFn.mock.calls.length).toBe(1); - expect(command.commandFn.mock.calls[0][0].test).toEqual( - runCommandOptions.test - ); + expect(command.commandFn.mock.calls[0][0].test).toEqual(runCommandOptions.test); }); it('Returns the result of commandFn', () => { diff --git a/platform/core/src/classes/CommandsManager.ts b/platform/core/src/classes/CommandsManager.ts index 34a825c4d3..4d4563a005 100644 --- a/platform/core/src/classes/CommandsManager.ts +++ b/platform/core/src/classes/CommandsManager.ts @@ -175,34 +175,33 @@ export class CommandsManager { toRun: Command | Commands | Command[] | undefined, options?: Record ): unknown { - if (!toRun) return; + if (!toRun) { + return; + } const commands = (Array.isArray(toRun) && toRun) || ((toRun as Command).commandName && [toRun]) || - (Array.isArray((toRun as Commands).commands) && - (toRun as Commands).commands); + (Array.isArray((toRun as Commands).commands) && (toRun as Commands).commands); if (!commands) { console.log("Command isn't runnable", toRun); return; } let result; - (commands as Command[]).forEach( - ({ commandName, commandOptions, context }) => { - if (commandName) { - result = this.runCommand( - commandName, - { - ...commandOptions, - ...options, - }, - context - ); - } else { - console.warn('No command name supplied in', toRun); - } + (commands as Command[]).forEach(({ commandName, commandOptions, context }) => { + if (commandName) { + result = this.runCommand( + commandName, + { + ...commandOptions, + ...options, + }, + context + ); + } else { + console.warn('No command name supplied in', toRun); } - ); + }); return result; } diff --git a/platform/core/src/classes/HotkeysManager.test.js b/platform/core/src/classes/HotkeysManager.test.js index 3cb68be179..2eb63def22 100644 --- a/platform/core/src/classes/HotkeysManager.test.js +++ b/platform/core/src/classes/HotkeysManager.test.js @@ -21,11 +21,7 @@ describe('HotkeysManager', () => { }); it('has expected properties', () => { const allProperties = Object.keys(hotkeysManager); - const expectedProperties = [ - 'hotkeyDefinitions', - 'hotkeyDefaults', - 'isEnabled', - ]; + const expectedProperties = ['hotkeyDefinitions', 'hotkeyDefaults', 'isEnabled']; const containsAllExpectedProperties = expectedProperties.every(expected => allProperties.includes(expected) @@ -134,21 +130,16 @@ describe('HotkeysManager', () => { hotkeysManager.registerHotkeys(definition); - const numOfHotkeyDefinitions = Object.keys( - hotkeysManager.hotkeyDefinitions - ).length; + const numOfHotkeyDefinitions = Object.keys(hotkeysManager.hotkeyDefinitions).length; const commandHash = objectHash({ commandName: definition.commandName, commandOptions: definition.commandOptions, }); - const hotkeyDefinitionForRegisteredCommand = - hotkeysManager.hotkeyDefinitions[commandHash]; + const hotkeyDefinitionForRegisteredCommand = hotkeysManager.hotkeyDefinitions[commandHash]; expect(numOfHotkeyDefinitions).toBe(1); - expect(Object.keys(hotkeysManager.hotkeyDefinitions)[0]).toEqual( - commandHash - ); + expect(Object.keys(hotkeysManager.hotkeyDefinitions)[0]).toEqual(commandHash); expect(hotkeyDefinitionForRegisteredCommand).toEqual(definition); }); it('calls hotkeys.bind for the group of keys', () => { @@ -182,9 +173,7 @@ describe('HotkeysManager', () => { hotkeysManager.restoreDefaultBindings(); - expect(hotkeysManager.setHotkeys.mock.calls[0][0]).toEqual( - hotkeysManager.hotkeyDefaults - ); + expect(hotkeysManager.setHotkeys.mock.calls[0][0]).toEqual(hotkeysManager.hotkeyDefaults); }); }); diff --git a/platform/core/src/classes/HotkeysManager.ts b/platform/core/src/classes/HotkeysManager.ts index 50ccc0fe51..886cafc2d0 100644 --- a/platform/core/src/classes/HotkeysManager.ts +++ b/platform/core/src/classes/HotkeysManager.ts @@ -174,14 +174,7 @@ export class HotkeysManager { * @returns {undefined} */ registerHotkeys( - { - commandName, - commandOptions = {}, - context, - keys, - label, - isEditable, - }: Hotkey = {}, + { commandName, commandOptions = {}, context, keys, label, isEditable }: Hotkey = {}, extension ) { if (!commandName) { @@ -189,9 +182,7 @@ export class HotkeysManager { } const commandHash = objectHash({ commandName, commandOptions }); - const options = Object.keys(commandOptions).length - ? JSON.stringify(commandOptions) - : 'no'; + const options = Object.keys(commandOptions).length ? JSON.stringify(commandOptions) : 'no'; const previouslyRegisteredDefinition = this.hotkeyDefinitions[commandHash]; if (previouslyRegisteredDefinition) { @@ -255,11 +246,7 @@ export class HotkeysManager { hotkeys.bind(combinedKeys, evt => { evt.preventDefault(); evt.stopPropagation(); - this._commandsManager.runCommand( - commandName, - { evt, ...commandOptions }, - context - ); + this._commandsManager.runCommand(commandName, { evt, ...commandOptions }, context); }); } diff --git a/platform/core/src/classes/ImageSet.ts b/platform/core/src/classes/ImageSet.ts index d7ac82bce9..d541a793e2 100644 --- a/platform/core/src/classes/ImageSet.ts +++ b/platform/core/src/classes/ImageSet.ts @@ -107,7 +107,7 @@ class ImageSet { ) ); - const distanceImagePairs = images.map(function(image: Image) { + const distanceImagePairs = images.map(function (image: Image) { const ippVec = new Vector3(..._getImagePositionPatient(image)); const positionVector = refIppVec.clone().sub(ippVec); const distance = positionVector.dot(scanAxisNormal); @@ -118,13 +118,13 @@ class ImageSet { }; }); - distanceImagePairs.sort(function(a, b) { + distanceImagePairs.sort(function (a, b) { return b.distance - a.distance; }); const sortedImages = distanceImagePairs.map(a => a.image); - images.sort(function(a, b) { + images.sort(function (a, b) { return sortedImages.indexOf(a) - sortedImages.indexOf(b); }); } diff --git a/platform/core/src/classes/MetadataProvider.js b/platform/core/src/classes/MetadataProvider.ts similarity index 81% rename from platform/core/src/classes/MetadataProvider.js rename to platform/core/src/classes/MetadataProvider.ts index 75f1ad9fe3..7b59c3ebd6 100644 --- a/platform/core/src/classes/MetadataProvider.js +++ b/platform/core/src/classes/MetadataProvider.ts @@ -58,21 +58,19 @@ class MetadataProvider { return; } - const { - StudyInstanceUID, - SeriesInstanceUID, - SOPInstanceUID, - frameNumber, - } = uids; + const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID, frameNumber } = uids; const instance = DicomMetadataStore.getInstance( StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID ); - return ( - (frameNumber && combineFrameInstance(frameNumber, instance)) || instance - ); + + if (!instance) { + return; + } + + return (frameNumber && combineFrameInstance(frameNumber, instance)) || instance; } get(query, imageId, options = { fallback: false }) { @@ -102,11 +100,7 @@ class MetadataProvider { return this.get(INSTANCE, imageId); } - getTagFromInstance( - naturalizedTagOrWADOImageLoaderTag, - instance, - options = { fallback: false } - ) { + getTagFromInstance(naturalizedTagOrWADOImageLoaderTag, instance, options = { fallback: false }) { if (!instance) { return; } @@ -117,14 +111,23 @@ class MetadataProvider { } // Maybe its a legacy dicomImageLoader tag then: - return this._getCornerstoneDICOMImageLoaderTag( - naturalizedTagOrWADOImageLoaderTag, - instance - ); + return this._getCornerstoneDICOMImageLoaderTag(naturalizedTagOrWADOImageLoaderTag, instance); + } + + /** + * Adds a new handler for the given tag. The handler will be provided an + * instance object that it can read values from. + */ + public addHandler( + wadoImageLoaderTag: string, + handler + ) { + WADO_IMAGE_LOADER[wadoImageLoaderTag] = handler; } _getCornerstoneDICOMImageLoaderTag(wadoImageLoaderTag, instance) { - let metadata; + let metadata = WADO_IMAGE_LOADER[wadoImageLoaderTag]?.(instance); + if (metadata) return metadata; switch (wadoImageLoaderTag) { case WADO_IMAGE_LOADER_TAGS.GENERAL_SERIES_MODULE: @@ -162,47 +165,6 @@ class MetadataProvider { patientSex: instance.PatientSex, }; break; - case WADO_IMAGE_LOADER_TAGS.IMAGE_PLANE_MODULE: - const { ImageOrientationPatient } = instance; - - // Fallback for DX images. - // TODO: We should use the rest of the results of this function - // to update the UI somehow - const { PixelSpacing } = getPixelSpacingInformation(instance); - - let rowPixelSpacing; - let columnPixelSpacing; - - let rowCosines; - let columnCosines; - - if (PixelSpacing) { - rowPixelSpacing = PixelSpacing[0]; - columnPixelSpacing = PixelSpacing[1]; - } - - if (ImageOrientationPatient) { - rowCosines = ImageOrientationPatient.slice(0, 3); - columnCosines = ImageOrientationPatient.slice(3, 6); - } - - metadata = { - frameOfReferenceUID: instance.FrameOfReferenceUID, - rows: toNumber(instance.Rows), - columns: toNumber(instance.Columns), - imageOrientationPatient: toNumber(ImageOrientationPatient), - rowCosines: toNumber(rowCosines || [0, 1, 0]), - columnCosines: toNumber(columnCosines || [0, 0, -1]), - imagePositionPatient: toNumber( - instance.ImagePositionPatient || [0, 0, 0] - ), - sliceThickness: toNumber(instance.SliceThickness), - sliceLocation: toNumber(instance.SliceLocation), - pixelSpacing: toNumber(PixelSpacing || 1), - rowPixelSpacing: toNumber(rowPixelSpacing || 1), - columnPixelSpacing: toNumber(columnPixelSpacing || 1), - }; - break; case WADO_IMAGE_LOADER_TAGS.IMAGE_PIXEL_MODULE: metadata = { samplesPerPixel: toNumber(instance.SamplesPerPixel), @@ -249,12 +211,8 @@ class MetadataProvider { if (WindowCenter === undefined || WindowWidth === undefined) { return; } - const windowCenter = Array.isArray(WindowCenter) - ? WindowCenter - : [WindowCenter]; - const windowWidth = Array.isArray(WindowWidth) - ? WindowWidth - : [WindowWidth]; + const windowCenter = Array.isArray(WindowCenter) ? WindowCenter : [WindowCenter]; + const windowWidth = Array.isArray(WindowWidth) ? WindowWidth : [WindowWidth]; metadata = { windowCenter: toNumber(windowCenter), @@ -291,16 +249,11 @@ class MetadataProvider { ? RadiopharmaceuticalInformationSequence[0] : RadiopharmaceuticalInformationSequence; - const { - RadiopharmaceuticalStartTime, - RadionuclideTotalDose, - RadionuclideHalfLife, - } = RadiopharmaceuticalInformation; + const { RadiopharmaceuticalStartTime, RadionuclideTotalDose, RadionuclideHalfLife } = + RadiopharmaceuticalInformation; const radiopharmaceuticalInfo = { - radiopharmaceuticalStartTime: dicomParser.parseTM( - RadiopharmaceuticalStartTime - ), + radiopharmaceuticalStartTime: dicomParser.parseTM(RadiopharmaceuticalStartTime), radionuclideTotalDose: RadionuclideTotalDose, radionuclideHalfLife: RadionuclideHalfLife, }; @@ -313,11 +266,7 @@ class MetadataProvider { case WADO_IMAGE_LOADER_TAGS.OVERLAY_PLANE_MODULE: const overlays = []; - for ( - let overlayGroup = 0x00; - overlayGroup <= 0x1e; - overlayGroup += 0x02 - ) { + for (let overlayGroup = 0x00; overlayGroup <= 0x1e; overlayGroup += 0x02) { let groupStr = `60${overlayGroup.toString(16)}`; if (groupStr.length === 3) { @@ -405,6 +354,14 @@ class MetadataProvider { }; break; + case WADO_IMAGE_LOADER_TAGS.PER_SERIES_MODULE: + metadata = { + correctedImage: instance.CorrectedImage, + units: instance.Units, + decayCorrection: instance.DecayCorrection, + }; + break; + default: return; } @@ -441,7 +398,9 @@ class MetadataProvider { } getUIDsFromImageID(imageId) { - if (!imageId) throw new Error('MetadataProvider::Empty imageId'); + if (!imageId) { + throw new Error('MetadataProvider::Empty imageId'); + } // TODO: adding csiv here is not really correct. Probably need to use // metadataProvider.addImageIdToUIDs(imageId, { // StudyInstanceUID, @@ -498,20 +457,64 @@ const metadataProvider = new MetadataProvider(); export default metadataProvider; +const WADO_IMAGE_LOADER = { + imagePlaneModule: instance => { + const { ImageOrientationPatient } = instance; + + // Fallback for DX images. + // TODO: We should use the rest of the results of this function + // to update the UI somehow + const { PixelSpacing } = getPixelSpacingInformation(instance); + + let rowPixelSpacing; + let columnPixelSpacing; + + let rowCosines; + let columnCosines; + + if (PixelSpacing) { + rowPixelSpacing = PixelSpacing[0]; + columnPixelSpacing = PixelSpacing[1]; + } + + if (ImageOrientationPatient) { + rowCosines = ImageOrientationPatient.slice(0, 3); + columnCosines = ImageOrientationPatient.slice(3, 6); + } + + return { + frameOfReferenceUID: instance.FrameOfReferenceUID, + rows: toNumber(instance.Rows), + columns: toNumber(instance.Columns), + imageOrientationPatient: toNumber(ImageOrientationPatient), + rowCosines: toNumber(rowCosines || [0, 1, 0]), + columnCosines: toNumber(columnCosines || [0, 0, -1]), + imagePositionPatient: toNumber( + instance.ImagePositionPatient || [0, 0, 0] + ), + sliceThickness: toNumber(instance.SliceThickness), + sliceLocation: toNumber(instance.SliceLocation), + pixelSpacing: toNumber(PixelSpacing || 1), + rowPixelSpacing: rowPixelSpacing ? toNumber(rowPixelSpacing) : null, + columnPixelSpacing: columnPixelSpacing ? toNumber(columnPixelSpacing) : null, + }; + }, +}; + const WADO_IMAGE_LOADER_TAGS = { // dicomImageLoader specific GENERAL_SERIES_MODULE: 'generalSeriesModule', PATIENT_STUDY_MODULE: 'patientStudyModule', - IMAGE_PLANE_MODULE: 'imagePlaneModule', IMAGE_PIXEL_MODULE: 'imagePixelModule', VOI_LUT_MODULE: 'voiLutModule', MODALITY_LUT_MODULE: 'modalityLutModule', SOP_COMMON_MODULE: 'sopCommonModule', PET_ISOTOPE_MODULE: 'petIsotopeModule', + PER_SERIES_MODULE: 'petSeriesModule', OVERLAY_PLANE_MODULE: 'overlayPlaneModule', PATIENT_DEMOGRAPHIC_MODULE: 'patientDemographicModule', - // react-cornerstone-viewport specifc + // react-cornerstone-viewport specific PATIENT_MODULE: 'patientModule', GENERAL_IMAGE_MODULE: 'generalImageModule', GENERAL_STUDY_MODULE: 'generalStudyModule', diff --git a/platform/core/src/defaults/hotkeyBindings.js b/platform/core/src/defaults/hotkeyBindings.js index 4c12e21791..b4c6098936 100644 --- a/platform/core/src/defaults/hotkeyBindings.js +++ b/platform/core/src/defaults/hotkeyBindings.js @@ -173,30 +173,31 @@ const bindings = [ label: 'W/L Preset 5', keys: ['5'], }, - { - commandName: 'setWindowLevel', - commandOptions: windowLevelPresets[6], - label: 'W/L Preset 6', - keys: ['6'], - }, - { - commandName: 'setWindowLevel', - commandOptions: windowLevelPresets[7], - label: 'W/L Preset 7', - keys: ['7'], - }, - { - commandName: 'setWindowLevel', - commandOptions: windowLevelPresets[8], - label: 'W/L Preset 8', - keys: ['8'], - }, - { - commandName: 'setWindowLevel', - commandOptions: windowLevelPresets[9], - label: 'W/L Preset 9', - keys: ['9'], - }, + // These don't exist, so don't try applying them.... + // { + // commandName: 'setWindowLevel', + // commandOptions: windowLevelPresets[6], + // label: 'W/L Preset 6', + // keys: ['6'], + // }, + // { + // commandName: 'setWindowLevel', + // commandOptions: windowLevelPresets[7], + // label: 'W/L Preset 7', + // keys: ['7'], + // }, + // { + // commandName: 'setWindowLevel', + // commandOptions: windowLevelPresets[8], + // label: 'W/L Preset 8', + // keys: ['8'], + // }, + // { + // commandName: 'setWindowLevel', + // commandOptions: windowLevelPresets[9], + // label: 'W/L Preset 9', + // keys: ['9'], + // }, ]; export default bindings; diff --git a/platform/core/src/extensions/ExtensionManager.test.js b/platform/core/src/extensions/ExtensionManager.test.js index 2af3ce52a0..5d5fed85be 100644 --- a/platform/core/src/extensions/ExtensionManager.test.js +++ b/platform/core/src/extensions/ExtensionManager.test.js @@ -59,17 +59,11 @@ describe('ExtensionManager.ts', () => { extensionManager.registerExtension = jest.fn(); // SUT - const fakeExtensions = [ - { one: '1' }, - [{ two: '2' }, fakeConfiguration], - { three: '3 ' }, - ]; + const fakeExtensions = [{ one: '1' }, [{ two: '2' }, fakeConfiguration], { three: '3 ' }]; await extensionManager.registerExtensions(fakeExtensions); // Assert - expect(extensionManager.registerExtension.mock.calls[1][1]).toEqual( - fakeConfiguration - ); + expect(extensionManager.registerExtension.mock.calls[1][1]).toEqual(fakeConfiguration); }); }); @@ -104,15 +98,11 @@ describe('ExtensionManager.ts', () => { const undefinedExtension = undefined; const nullExtension = null; - await expect( - extensionManager.registerExtension(undefinedExtension) - ).rejects.toThrow( + await expect(extensionManager.registerExtension(undefinedExtension)).rejects.toThrow( new Error('Attempting to register a null/undefined extension.') ); - await expect( - extensionManager.registerExtension(nullExtension) - ).rejects.toThrow( + await expect(extensionManager.registerExtension(nullExtension)).rejects.toThrow( new Error('Attempting to register a null/undefined extension.') ); }); @@ -120,9 +110,9 @@ describe('ExtensionManager.ts', () => { it('logs a warning if the extension does not have an id', async () => { const extensionWithoutId = {}; - await expect( - extensionManager.registerExtension(extensionWithoutId) - ).rejects.toThrow(new Error('Extension ID not set')); + await expect(extensionManager.registerExtension(extensionWithoutId)).rejects.toThrow( + new Error('Extension ID not set') + ); }); it('tracks which extensions have been registered', () => { @@ -156,9 +146,7 @@ describe('ExtensionManager.ts', () => { extensionManager.registerExtension(extensionWithBadModule); expect(log.warn.mock.calls.length).toBe(1); - expect(log.warn.mock.calls[0][0]).toContain( - 'Null or undefined returned when registering' - ); + expect(log.warn.mock.calls[0][0]).toContain('Null or undefined returned when registering'); }); it('logs an error if an exception is thrown while retrieving a module', async () => { @@ -169,9 +157,7 @@ describe('ExtensionManager.ts', () => { }, }; - await expect( - extensionManager.registerExtension(extensionWithBadModule) - ).rejects.toThrow(); + await expect(extensionManager.registerExtension(extensionWithBadModule)).rejects.toThrow(); }); it('successfully passes dependencies to each module along with extension configuration', () => { diff --git a/platform/core/src/extensions/ExtensionManager.ts b/platform/core/src/extensions/ExtensionManager.ts index c028fbab35..aaf6df8918 100644 --- a/platform/core/src/extensions/ExtensionManager.ts +++ b/platform/core/src/extensions/ExtensionManager.ts @@ -1,8 +1,9 @@ import MODULE_TYPES from './MODULE_TYPES'; import log from '../log'; import { AppConfig } from '../types/AppConfig'; -import { ServicesManager } from '../services'; +import { PubSubService, ServicesManager } from '../services'; import { HotkeysManager, CommandsManager } from '../classes'; +import { DataSourceDefinition } from '../types'; /** * This is the arguments given to create the extension. @@ -26,6 +27,7 @@ export type ExtensionConfiguration = Record; */ export interface ExtensionParams extends ExtensionConstructor { extensionManager: ExtensionManager; + servicesManager: ServicesManager; configuration?: ExtensionConfiguration; } @@ -42,6 +44,7 @@ export interface Extension { getViewportModule?: (p: ExtensionParams) => unknown; getUtilityModule?: (p: ExtensionParams) => unknown; getCustomizationModule?: (p: ExtensionParams) => unknown; + getSopClassHandlerModule?: (p: ExtensionParams) => unknown; onModeEnter?: () => void; onModeExit?: () => void; } @@ -57,7 +60,11 @@ export type CommandsModule = { defaultContext?: string; }; -export default class ExtensionManager { +export default class ExtensionManager extends PubSubService { + public static readonly EVENTS = { + ACTIVE_DATA_SOURCE_CHANGED: 'event::activedatasourcechanged', + }; + private _commandsManager: CommandsManager; private _servicesManager: ServicesManager; private _hotkeysManager: HotkeysManager; @@ -68,6 +75,7 @@ export default class ExtensionManager { hotkeysManager, appConfig = {}, }: ExtensionConstructor) { + super(ExtensionManager.EVENTS); this.modules = {}; this.registeredExtensionIds = []; this.moduleTypeNames = Object.values(MODULE_TYPES); @@ -85,11 +93,20 @@ export default class ExtensionManager { this.dataSourceMap = {}; this.dataSourceDefs = {}; this.defaultDataSourceName = appConfig.defaultDataSourceName; - this.activeDataSource = undefined; + this.activeDataSource = appConfig.defaultDataSourceName; } - public setActiveDataSource(dataSourceName: string): void { - this.activeDataSource = dataSourceName; + public setActiveDataSource(dataSource: string): void { + if (this.activeDataSource === dataSource) { + return; + } + + this.activeDataSource = dataSource; + + this._broadcastEvent( + ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED, + this.dataSourceDefs[this.activeDataSource] + ); } /** @@ -128,12 +145,8 @@ export default class ExtensionManager { } public onModeExit(): void { - const { - registeredExtensionIds, - _servicesManager, - _commandsManager, - _extensionLifeCycleHooks, - } = this; + const { registeredExtensionIds, _servicesManager, _commandsManager, _extensionLifeCycleHooks } = + this; registeredExtensionIds.forEach(extensionId => { const onModeExit = _extensionLifeCycleHooks.onModeExit[extensionId]; @@ -164,10 +177,7 @@ export default class ExtensionManager { * @param {Object[]} extensions - Array of extensions */ public registerExtensions = async ( - extensions: ( - | ExtensionRegister - | [ExtensionRegister, ExtensionConfiguration] - )[], + extensions: (ExtensionRegister | [ExtensionRegister, ExtensionConfiguration])[], dataSources: unknown[] = [] ): Promise => { // Todo: we ideally should be able to run registrations in parallel @@ -186,11 +196,7 @@ export default class ExtensionManager { // for (const extension of extensions) const ohifExtension = extension[0]; const configuration = extension[1]; - await this.registerExtension( - ohifExtension, - configuration, - dataSources - ); + await this.registerExtension(ohifExtension, configuration, dataSources); } else { await this.registerExtension(extension, {}, dataSources); } @@ -243,13 +249,11 @@ export default class ExtensionManager { } if (extension.onModeEnter) { - this._extensionLifeCycleHooks.onModeEnter[extensionId] = - extension.onModeEnter; + this._extensionLifeCycleHooks.onModeEnter[extensionId] = extension.onModeEnter; } if (extension.onModeExit) { - this._extensionLifeCycleHooks.onModeExit[extensionId] = - extension.onModeExit; + this._extensionLifeCycleHooks.onModeExit[extensionId] = extension.onModeExit; } // Register Modules @@ -267,11 +271,7 @@ export default class ExtensionManager { this._initCommandsModule(extensionModule); break; case MODULE_TYPES.DATA_SOURCE: - this._initDataSourcesModule( - extensionModule, - extensionId, - dataSources - ); + this._initDataSourcesModule(extensionModule, extensionId, dataSources); break; case MODULE_TYPES.HANGING_PROTOCOL: this._initHangingProtocolsModule(extensionModule, extensionId); @@ -330,8 +330,27 @@ export default class ExtensionManager { return this.dataSourceMap[this.activeDataSource]; }; - getDataSource = () => { - return this.dataSourceMap[this.activeDataSource]; + /** + * Gets the data source definition for the given data source name. + * If no data source name is provided, the active data source definition is + * returned. + * @param dataSourceName the data source name + * @returns the data source definition + */ + getDataSourceDefinition = dataSourceName => { + if (dataSourceName === undefined) { + // Default to the activeDataSource + dataSourceName = this.activeDataSource; + } + + return this.dataSourceDefs[dataSourceName]; + }; + + /** + * Gets the data source definition for the active data source. + */ + getActiveDataSourceDefinition = () => { + return this.getDataSourceDefinition(this.activeDataSource); }; /** @@ -383,10 +402,81 @@ export default class ExtensionManager { }); }; - _initDataSourcesModule(extensionModule, extensionId, dataSources = []) { + /** + * Adds the given data source and optionally sets it as the active data source. + * The method does this by first creating the data source. + * @param dataSourceDef the data source definition to be added + * @param activate flag to indicate if the added data source should be set to the active data source + */ + addDataSource(dataSourceDef: DataSourceDefinition, options = { activate: false }) { + const existingDataSource = this.getDataSources(dataSourceDef.sourceName); + if (existingDataSource?.[0]) { + // The data source already exists and cannot be added. + return; + } + + this._createDataSourceInstance(dataSourceDef); + + if (options.activate) { + this.setActiveDataSource(dataSourceDef.sourceName); + } + } + + /** + * Updates the configuration of the given data source name. It first creates a new data source with + * the existing definition and the new configuration passed in. + * @param dataSourceName the name of the data source to update + * @param dataSourceConfiguration the new configuration to update the data source with + */ + updateDataSourceConfiguration(dataSourceName: string, dataSourceConfiguration: any) { + const existingDataSource = this.getDataSources(dataSourceName); + if (!existingDataSource?.[0]) { + // Cannot update a non existent data source. + return; + } + + const dataSourceDef = this.dataSourceDefs[dataSourceName]; + // Update the configuration. + dataSourceDef.configuration = dataSourceConfiguration; + this._createDataSourceInstance(dataSourceDef); + + if (this.activeDataSource === dataSourceName) { + // When the active data source is changed/set, fire an event to indicate that its configuration has changed. + this._broadcastEvent(ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED, dataSourceDef); + } + } + + /** + * Creates a data source instance from the given definition. The definition is + * added to dataSourceDefs and the created instance is added to dataSourceMap. + * @param dataSourceDef + * @returns + */ + _createDataSourceInstance(dataSourceDef: DataSourceDefinition) { + const module = this.getModuleEntry(dataSourceDef.namespace); + + if (!module) { + return; + } + + this.dataSourceDefs[dataSourceDef.sourceName] = dataSourceDef; + const { userAuthenticationService } = this._servicesManager.services; - dataSources.forEach(dataSource => { - this.dataSourceDefs[dataSource.sourceName] = dataSource; + const dataSourceInstance = module.createDataSource( + dataSourceDef.configuration, + userAuthenticationService + ); + + this.dataSourceMap[dataSourceDef.sourceName] = [dataSourceInstance]; + } + + _initDataSourcesModule( + extensionModule, + extensionId, + dataSources: Array = [] + ): void { + extensionModule.forEach(element => { + this.modulesMap[`${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}`] = element; }); extensionModule.forEach(element => { @@ -394,25 +484,10 @@ export default class ExtensionManager { dataSources.forEach(dataSource => { if (dataSource.namespace === namespace) { - const dataSourceInstance = element.createDataSource( - dataSource.configuration, - userAuthenticationService - ); - - if (this.dataSourceMap[dataSource.sourceName]) { - this.dataSourceMap[dataSource.sourceName].push(dataSourceInstance); - } else { - this.dataSourceMap[dataSource.sourceName] = [dataSourceInstance]; - } + this.addDataSource(dataSource); } }); }); - - extensionModule.forEach(element => { - this.modulesMap[ - `${extensionId}.${MODULE_TYPES.DATA_SOURCE}.${element.name}` - ] = element; - }); } /** @@ -436,8 +511,7 @@ export default class ExtensionManager { Object.keys(definitions).forEach(commandName => { const commandDefinition = definitions[commandName]; const commandHasContextThatDoesNotExist = - commandDefinition.context && - !this._commandsManager.getContext(commandDefinition.context); + commandDefinition.context && !this._commandsManager.getContext(commandDefinition.context); if (commandHasContextThatDoesNotExist) { this._commandsManager.createContext(commandDefinition.context); diff --git a/platform/core/src/index.test.js b/platform/core/src/index.test.js index 3a62a5141d..85ee681764 100644 --- a/platform/core/src/index.test.js +++ b/platform/core/src/index.test.js @@ -39,6 +39,8 @@ describe('Top level exports', () => { 'UserAuthenticationService', 'IWebApiDataSource', 'DicomMetadataStore', + 'DisplaySetMessage', + 'DisplaySetMessageList', 'pubSubServiceInterface', 'PubSubService', 'PanelService', diff --git a/platform/core/src/index.ts b/platform/core/src/index.ts index 458c7a3ab8..67836da4d3 100644 --- a/platform/core/src/index.ts +++ b/platform/core/src/index.ts @@ -33,6 +33,8 @@ import { PanelService, } from './services'; +import { DisplaySetMessage, DisplaySetMessageList } from './services/DisplaySetService'; + import IWebApiDataSource from './DataSources/IWebApiDataSource'; const hotkeys = { @@ -107,6 +109,8 @@ export { UINotificationService, UIViewportDialogService, DisplaySetService, + DisplaySetMessage, + DisplaySetMessageList, MeasurementService, ToolbarService, ViewportGridService, diff --git a/platform/core/src/object.js b/platform/core/src/object.js index 030793f52b..6201b212ca 100644 --- a/platform/core/src/object.js +++ b/platform/core/src/object.js @@ -2,7 +2,9 @@ function getNestedObject(shallowObject) { const nestedObject = {}; for (let key in shallowObject) { - if (!shallowObject.hasOwnProperty(key)) continue; + if (!shallowObject.hasOwnProperty(key)) { + continue; + } const value = shallowObject[key]; const propertyArray = key.split('.'); let currentObject = nestedObject; @@ -28,7 +30,9 @@ function getShallowObject(nestedObject) { const shallowObject = {}; const putValues = (baseKey, nestedObject, resultObject) => { for (let key in nestedObject) { - if (!nestedObject.hasOwnProperty(key)) continue; + if (!nestedObject.hasOwnProperty(key)) { + continue; + } let currentKey = baseKey ? `${baseKey}.${key}` : key; const currentValue = nestedObject[key]; if (typeof currentValue === 'object') { diff --git a/platform/core/src/services/CustomizationService/CustomizationService.test.js b/platform/core/src/services/CustomizationService/CustomizationService.test.js index 959816ea57..763bd62e31 100644 --- a/platform/core/src/services/CustomizationService/CustomizationService.test.js +++ b/platform/core/src/services/CustomizationService/CustomizationService.test.js @@ -59,29 +59,27 @@ describe('CustomizationService.ts', () => { it('configurationRegistered', () => { configuration.testItem = testItem; customizationService.init(extensionManager); - expect(customizationService.getGlobalCustomization('testItem')).toBe( - testItem - ); + expect(customizationService.getGlobalCustomization('testItem')).toBe(testItem); }); it('defaultRegistered', () => { extensionManager.registeredExtensionIds.push('@testExtension'); - extensionManager.moduleEntries[ - '@testExtension.customizationModule.default' - ] = { name: 'default', value: [testItem] }; + extensionManager.moduleEntries['@testExtension.customizationModule.default'] = { + name: 'default', + value: [testItem], + }; customizationService.init(extensionManager); - expect(customizationService.getGlobalCustomization('testItem')).toBe( - testItem - ); + expect(customizationService.getGlobalCustomization('testItem')).toBe(testItem); }); }); describe('customizationType', () => { it('inherits type', () => { extensionManager.registeredExtensionIds.push('@testExtension'); - extensionManager.moduleEntries[ - '@testExtension.customizationModule.default' - ] = { name: 'default', value: [ohifOverlayItem] }; + extensionManager.moduleEntries['@testExtension.customizationModule.default'] = { + name: 'default', + value: [ohifOverlayItem], + }; configuration.testItem = testItem; customizationService.init(extensionManager); @@ -96,9 +94,10 @@ describe('CustomizationService.ts', () => { it('inline default inherits type', () => { extensionManager.registeredExtensionIds.push('@testExtension'); - extensionManager.moduleEntries[ - '@testExtension.customizationModule.default' - ] = { name: 'default', value: [ohifOverlayItem] }; + extensionManager.moduleEntries['@testExtension.customizationModule.default'] = { + name: 'default', + value: [ohifOverlayItem], + }; configuration.testItem = testItem; customizationService.init(extensionManager); @@ -121,20 +120,17 @@ describe('CustomizationService.ts', () => { describe('mode customization', () => { it('onModeEnter can add extensions', () => { extensionManager.registeredExtensionIds.push('@testExtension'); - extensionManager.moduleEntries[ - '@testExtension.customizationModule.default' - ] = { name: 'default', value: [ohifOverlayItem] }; + extensionManager.moduleEntries['@testExtension.customizationModule.default'] = { + name: 'default', + value: [ohifOverlayItem], + }; customizationService.init(extensionManager); - expect( - customizationService.getModeCustomization('testItem') - ).toBeUndefined(); + expect(customizationService.getModeCustomization('testItem')).toBeUndefined(); customizationService.addModeCustomizations([testItem]); - expect( - customizationService.getGlobalCustomization('testItem') - ).toBeUndefined(); + expect(customizationService.getGlobalCustomization('testItem')).toBeUndefined(); const item = customizationService.getModeCustomization('testItem'); @@ -147,16 +143,15 @@ describe('CustomizationService.ts', () => { it('global customizations override modes', () => { extensionManager.registeredExtensionIds.push('@testExtension'); - extensionManager.moduleEntries[ - '@testExtension.customizationModule.default' - ] = { name: 'default', value: [ohifOverlayItem] }; + extensionManager.moduleEntries['@testExtension.customizationModule.default'] = { + name: 'default', + value: [ohifOverlayItem], + }; configuration.testItem = testItem; customizationService.init(extensionManager); // Add a mode customization that would otherwise fail below - customizationService.addModeCustomizations([ - { ...testItem, label: 'other' }, - ]); + customizationService.addModeCustomizations([{ ...testItem, label: 'other' }]); const item = customizationService.getModeCustomization('testItem'); diff --git a/platform/core/src/services/CustomizationService/CustomizationService.ts b/platform/core/src/services/CustomizationService/CustomizationService.ts index cf3ee6a5f2..be8639d5e0 100644 --- a/platform/core/src/services/CustomizationService/CustomizationService.ts +++ b/platform/core/src/services/CustomizationService/CustomizationService.ts @@ -12,8 +12,12 @@ const flattenNestedStrings = ( strs: NestedStrings | string, ret?: Record ): Record => { - if (!ret) ret = {}; - if (!strs) return ret; + if (!ret) { + ret = {}; + } + if (!strs) { + return ret; + } if (Array.isArray(strs)) { for (const val of strs) { flattenNestedStrings(val, ret); @@ -81,7 +85,9 @@ export default class CustomizationService extends PubSubService { this.extensionManager.registeredExtensionIds.forEach(extensionId => { const key = `${extensionId}.customizationModule.default`; const defaultCustomizations = this.findExtensionValue(key); - if (!defaultCustomizations) return; + if (!defaultCustomizations) { + return; + } const { value } = defaultCustomizations; this.addReference(value, true); }); @@ -101,10 +107,7 @@ export default class CustomizationService extends PubSubService { return this.modeCustomizations; } - public setModeCustomization( - customizationId: string, - customization: Customization - ): void { + public setModeCustomization(customizationId: string, customization: Customization): void { this.modeCustomizations[customizationId] = merge( this.modeCustomizations[customizationId] || {}, customization @@ -149,10 +152,7 @@ export default class CustomizationService extends PubSubService { } public hasModeCustomization(customizationId: string) { - return ( - this.globalCustomizations[customizationId] || - this.modeCustomizations[customizationId] - ); + return this.globalCustomizations[customizationId] || this.modeCustomizations[customizationId]; } /** * get is an alias for getModeCustomization, as it is the generic getter @@ -172,13 +172,15 @@ export default class CustomizationService extends PubSubService { * type into the new type, allowing default behaviour to be configured. */ public transform(customization: Customization): Customization { - if (!customization) return customization; + if (!customization) { + return customization; + } const { customizationType } = customization; - if (!customizationType) return customization; + if (!customizationType) { + return customization; + } const parent = this.getCustomization(customizationType); - const result = parent - ? Object.assign(Object.create(parent), customization) - : customization; + const result = parent ? Object.assign(Object.create(parent), customization) : customization; // Execute an nested type information return result.transform?.(this) || result; } @@ -203,10 +205,7 @@ export default class CustomizationService extends PubSubService { * the modes. They include things like settings for the search screen. * Reset does NOT clear global customizations. */ - getGlobalCustomization( - id: string, - defaultValue?: Customization - ): Customization | void { + getGlobalCustomization(id: string, defaultValue?: Customization): Customization | void { return this.transform(this.globalCustomizations[id] ?? defaultValue); } @@ -215,15 +214,10 @@ export default class CustomizationService extends PubSubService { this._broadcastGlobalCustomizationModified(); } - protected setConfigGlobalCustomization( - configuration: AppConfigCustomization - ): void { + protected setConfigGlobalCustomization(configuration: AppConfigCustomization): void { this.globalCustomizations = {}; const keys = flattenNestedStrings(configuration.globalCustomizations); - this.readCustomizationTypes( - v => keys[v.name] && v.customization, - this.globalCustomizations - ); + this.readCustomizationTypes(v => keys[v.name] && v.customization, this.globalCustomizations); // TODO - iterate over customizations, loading them from the extension // manager. @@ -242,7 +236,9 @@ export default class CustomizationService extends PubSubService { * or a customization itself. */ addReference(value?: Obj | string, isGlobal = true, id?: string): void { - if (!value) return; + if (!value) { + return; + } if (typeof value === 'string') { const extensionValue = this.findExtensionValue(value); // The child of a reference is only a set of references when an array, @@ -252,10 +248,7 @@ export default class CustomizationService extends PubSubService { this.addReferences(value, isGlobal); } else { const useId = value.id || id; - this[isGlobal ? 'setGlobalCustomization' : 'setModeCustomization']( - useId as string, - value - ); + this[isGlobal ? 'setGlobalCustomization' : 'setModeCustomization'](useId as string, value); } } @@ -265,7 +258,9 @@ export default class CustomizationService extends PubSubService { * or customization. */ addReferences(references?: Obj | Obj[], isGlobal = true): void { - if (!references) return; + if (!references) { + return; + } if (Array.isArray(references)) { references.forEach(item => { this.addReference(item, isGlobal); diff --git a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts index f90dd2039a..f63508c975 100644 --- a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts +++ b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.ts @@ -54,9 +54,7 @@ function _getStudyInstanceUIDs() { } function _getStudy(StudyInstanceUID) { - return _model.studies.find( - aStudy => aStudy.StudyInstanceUID === StudyInstanceUID - ); + return _model.studies.find(aStudy => aStudy.StudyInstanceUID === StudyInstanceUID); } function _getSeries(StudyInstanceUID, SeriesInstanceUID) { @@ -66,9 +64,7 @@ function _getSeries(StudyInstanceUID, SeriesInstanceUID) { return; } - return study.series.find( - aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID - ); + return study.series.find(aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID); } function _getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) { @@ -78,9 +74,7 @@ function _getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) { return; } - return series.instances.find( - instance => instance.SOPInstanceUID === SOPInstanceUID - ); + return series.instances.find(instance => instance.SOPInstanceUID === SOPInstanceUID); } function _getInstanceByImageId(imageId) { @@ -102,20 +96,14 @@ function _getInstanceByImageId(imageId) { * @param {*} metadata metadata inform of key value pairs * @returns */ -function _updateMetadataForSeries( - StudyInstanceUID, - SeriesInstanceUID, - metadata -) { +function _updateMetadataForSeries(StudyInstanceUID, SeriesInstanceUID, metadata) { const study = _getStudy(StudyInstanceUID); if (!study) { return; } - const series = study.series.find( - aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID - ); + const series = study.series.find(aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID); const { instances } = series; // update all instances metadata for this series with the new metadata @@ -149,9 +137,7 @@ const BaseImplementation = { // If Arraybuffer, parse to DICOMJSON before naturalizing. if (dicomJSONDatasetOrP10ArrayBuffer instanceof ArrayBuffer) { - const dicomData = dcmjs.data.DicomMessage.readFile( - dicomJSONDatasetOrP10ArrayBuffer - ); + const dicomData = dcmjs.data.DicomMessage.readFile(dicomJSONDatasetOrP10ArrayBuffer); dicomJSONDataset = dicomData.dict; } else { @@ -161,18 +147,14 @@ const BaseImplementation = { let naturalizedDataset; if (dicomJSONDataset['SeriesInstanceUID'] === undefined) { - naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset( - dicomJSONDataset - ); + naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomJSONDataset); } else { naturalizedDataset = dicomJSONDataset; } const { StudyInstanceUID } = naturalizedDataset; - let study = _model.studies.find( - study => study.StudyInstanceUID === StudyInstanceUID - ); + let study = _model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID); if (!study) { _model.studies.push(createStudyMetadata(StudyInstanceUID)); @@ -184,9 +166,7 @@ const BaseImplementation = { addInstances(instances, madeInClient = false) { const { StudyInstanceUID, SeriesInstanceUID } = instances[0]; - let study = _model.studies.find( - study => study.StudyInstanceUID === StudyInstanceUID - ); + let study = _model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID); if (!study) { _model.studies.push(createStudyMetadata(StudyInstanceUID)); @@ -236,9 +216,7 @@ const BaseImplementation = { addStudy(study) { const { StudyInstanceUID } = study; - const existingStudy = _model.studies.find( - study => study.StudyInstanceUID === StudyInstanceUID - ); + const existingStudy = _model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID); if (!existingStudy) { const newStudy = createStudyMetadata(StudyInstanceUID); diff --git a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js index dc43aa503f..91b491afc3 100644 --- a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js +++ b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js @@ -12,14 +12,12 @@ function createStudyMetadata(StudyInstanceUID) { * @param {object} instance * @returns {bool} true if series were added; false if series already exist */ - addInstanceToSeries: function(instance) { + addInstanceToSeries: function (instance) { const { SeriesInstanceUID } = instance; if (!this.StudyDescription) { this.StudyDescription = instance.StudyDescription; } - const existingSeries = this.series.find( - s => s.SeriesInstanceUID === SeriesInstanceUID - ); + const existingSeries = this.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID); if (existingSeries) { existingSeries.instances.push(instance); @@ -39,23 +37,19 @@ function createStudyMetadata(StudyInstanceUID) { * @param {string} instances[].StudyDescription * @returns {bool} true if series were added; false if series already exist */ - addInstancesToSeries: function(instances) { + addInstancesToSeries: function (instances) { const { SeriesInstanceUID } = instances[0]; if (!this.StudyDescription) { this.StudyDescription = instances[0].StudyDescription; } - const existingSeries = this.series.find( - s => s.SeriesInstanceUID === SeriesInstanceUID - ); + const existingSeries = this.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID); if (existingSeries) { // Only add instances not already present, so generate a map // of existing instances and filter the to add by things // already present. const sopMap = {}; - existingSeries.instances.forEach( - it => (sopMap[it.SOPInstanceUID] = it) - ); + existingSeries.instances.forEach(it => (sopMap[it.SOPInstanceUID] = it)); const newInstances = instances.filter(it => !sopMap[it.SOPInstanceUID]); existingSeries.instances.push(...newInstances); } else { @@ -64,10 +58,8 @@ function createStudyMetadata(StudyInstanceUID) { } }, - setSeriesMetadata: function(SeriesInstanceUID, seriesMetadata) { - let existingSeries = this.series.find( - s => s.SeriesInstanceUID === SeriesInstanceUID - ); + setSeriesMetadata: function (SeriesInstanceUID, seriesMetadata) { + let existingSeries = this.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID); if (existingSeries) { existingSeries = Object.assign(existingSeries, seriesMetadata); diff --git a/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts b/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts new file mode 100644 index 0000000000..6a139daf63 --- /dev/null +++ b/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts @@ -0,0 +1,50 @@ +/** + * Defines a displaySet message, that could be any pf the potential problems of a displaySet + */ +class DisplaySetMessage { + id: number; + static CODES = { + NO_VALID_INSTANCES: 1, + NO_POSITION_INFORMATION: 2, + NOT_RECONSTRUCTABLE: 3, + MULTIFRAME_NO_PIXEL_MEASUREMENTS: 4, + MULTIFRAME_NO_ORIENTATION: 5, + MULTIFRAME_NO_POSITION_INFORMATION: 6, + MISSING_FRAMES: 7, + IRREGULAR_SPACING: 8, + INCONSISTENT_DIMENSIONS: 9, + INCONSISTENT_COMPONENTS: 10, + INCONSISTENT_ORIENTATIONS: 11, + INCONSISTENT_POSITION_INFORMATION: 12, + UNSUPPORTED_DISPLAYSET: 13, + }; + + constructor(id: number) { + this.id = id; + } +} +/** + * Defines a list of displaySet messages + */ +class DisplaySetMessageList { + messages = []; + + public addMessage(messageId: number): void { + const message = new DisplaySetMessage(messageId); + this.messages.push(message); + } + + public size(): number { + return this.messages.length; + } + + public includesMessage(messageId: number): boolean { + return this.messages.some(message => message.id === messageId); + } + + public includesAllMessages(messageIdList: number[]): boolean { + return messageIdList.every(messageId => this.include(messageId)); + } +} + +export { DisplaySetMessage, DisplaySetMessageList }; diff --git a/platform/core/src/services/DisplaySetService/DisplaySetService.ts b/platform/core/src/services/DisplaySetService/DisplaySetService.ts index 5796afa903..8d01a28dca 100644 --- a/platform/core/src/services/DisplaySetService/DisplaySetService.ts +++ b/platform/core/src/services/DisplaySetService/DisplaySetService.ts @@ -1,15 +1,8 @@ -import { InstanceMetadata } from '../../types'; +import { ExtensionManager } from '../../extensions'; +import { DisplaySet, InstanceMetadata } from '../../types'; import { PubSubService } from '../_shared/pubSubServiceInterface'; import EVENTS from './EVENTS'; -export type DisplaySet = { - displaySetInstanceUID: string; - instances: InstanceMetadata[]; - StudyInstanceUID: string; - SeriesInstanceUID?: string; - numImages?: number; -}; - const displaySetCache = new Map(); /** @@ -26,15 +19,11 @@ const filterInstances = ( if (!dsInstances) { console.warn('No instances in', ds); } else { - dsInstances.forEach(instance => - dsInstancesSOP.add(instance.SOPInstanceUID) - ); + dsInstances.forEach(instance => dsInstancesSOP.add(instance.SOPInstanceUID)); } }); - return instances.filter( - instance => !dsInstancesSOP.has(instance.SOPInstanceUID) - ); + return instances.filter(instance => !dsInstancesSOP.has(instance.SOPInstanceUID)); }; export default class DisplaySetService extends PubSubService { @@ -47,6 +36,8 @@ export default class DisplaySetService extends PubSubService { }; public activeDisplaySets = []; + public unsuportedSOPClassHandler; + extensionManager: ExtensionManager; protected activeDisplaySetsMap = new Map(); @@ -56,6 +47,8 @@ export default class DisplaySetService extends PubSubService { constructor() { super(EVENTS); + this.unsuportedSOPClassHandler = + '@ohif/extension-default.sopClassHandlerModule.not-supported-display-sets-handler'; } public init(extensionManager, SOPClassHandlerIds): void { @@ -83,6 +76,14 @@ export default class DisplaySetService extends PubSubService { }); } + /** + * Sets the handler for unsupported sop classes + * @param sopClassHandlerUID + */ + public setUnsuportedSOPClassHandler(sopClassHandler) { + this.unsuportedSOPClassHandler = sopClassHandler; + } + /** * Adds new display sets directly, as specified. * Use this function when the display sets are created externally directly @@ -114,9 +115,7 @@ export default class DisplaySetService extends PubSubService { return this.activeDisplaySets; } - public getDisplaySetsForSeries = ( - seriesInstanceUID: string - ): DisplaySet[] => { + public getDisplaySetsForSeries = (seriesInstanceUID: string): DisplaySet[] => { return [...displaySetCache.values()].filter( displaySet => displaySet.SeriesInstanceUID === seriesInstanceUID ); @@ -132,15 +131,16 @@ export default class DisplaySetService extends PubSubService { : [...this.getDisplaySetCache().values()]; const displaySet = displaySets.find(ds => { - return ( - ds.images && ds.images.some(i => i.SOPInstanceUID === sopInstanceUID) - ); + return ds.images && ds.images.some(i => i.SOPInstanceUID === sopInstanceUID); }); return displaySet; } - public setDisplaySetMetadataInvalidated(displaySetInstanceUID: string): void { + public setDisplaySetMetadataInvalidated( + displaySetInstanceUID: string, + invalidateData = true + ): void { const displaySet = this.getDisplaySetByUID(displaySetInstanceUID); if (!displaySet) { @@ -148,14 +148,16 @@ export default class DisplaySetService extends PubSubService { } // broadcast event to update listeners with the new displaySets - this._broadcastEvent( - EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, - displaySetInstanceUID - ); + this._broadcastEvent(EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, { + displaySetInstanceUID, + invalidateData, + }); } public deleteDisplaySet(displaySetInstanceUID) { - if (!displaySetInstanceUID) return; + if (!displaySetInstanceUID) { + return; + } const { activeDisplaySets, activeDisplaySetsMap } = this; const activeDisplaySetsIndex = activeDisplaySets.findIndex( @@ -176,8 +178,15 @@ export default class DisplaySetService extends PubSubService { * @param {string} displaySetInstanceUID * @returns {object} displaySet */ - public getDisplaySetByUID = (displaySetInstanceUid: string): DisplaySet => - displaySetCache.get(displaySetInstanceUid); + public getDisplaySetByUID = (displaySetInstanceUid: string): DisplaySet => { + if (typeof displaySetInstanceUid !== 'string') { + throw new Error( + `getDisplaySetByUID: displaySetInstanceUid must be a string, you passed ${displaySetInstanceUid}` + ); + } + + return displaySetCache.get(displaySetInstanceUid); + }; /** * @@ -185,18 +194,13 @@ export default class DisplaySetService extends PubSubService { * @param {*} param1: settings: initialViewportSettings by HP or callbacks after rendering * @returns {string[]} - added displaySetInstanceUIDs */ - makeDisplaySets = ( - input, - { batch = false, madeInClient = false, settings = {} } = {} - ) => { + makeDisplaySets = (input, { batch = false, madeInClient = false, settings = {} } = {}) => { if (!input || !input.length) { throw new Error('No instances were provided.'); } if (batch && !input[0].length) { - throw new Error( - 'Batch displaySet creation does not contain array of array of instances.' - ); + throw new Error('Batch displaySet creation does not contain array of array of instances.'); } // If array of instances => One instance. @@ -205,10 +209,7 @@ export default class DisplaySetService extends PubSubService { if (batch) { for (let i = 0; i < input.length; i++) { const instances = input[i]; - const displaySets = this.makeDisplaySetForInstances( - instances, - settings - ); + const displaySets = this.makeDisplaySetForInstances(instances, settings); displaySetsAdded.push(...displaySets); } @@ -252,6 +253,41 @@ export default class DisplaySetService extends PubSubService { this.activeDisplaySetsMap.clear(); } + /** + * This function hides the old makeDisplaySetForInstances function to first + * separate the instances by sopClassUID so each call have only instances + * with the same sopClassUID, to avoid a series composed by different + * sopClassUIDs be filtered inside one of the SOPClassHandler functions and + * didn't appear in the series list. + * @param instancesSrc + * @param settings + * @returns + */ + public makeDisplaySetForInstances(instancesSrc: InstanceMetadata[], settings): DisplaySet[] { + // creating a sopClassUID list and for each sopClass associate its respective + // instance list + const instancesForSetSOPClasses = instancesSrc.reduce((sopClassList, instance) => { + if (!(instance.SOPClassUID in sopClassList)) { + sopClassList[instance.SOPClassUID] = []; + } + sopClassList[instance.SOPClassUID].push(instance); + return sopClassList; + }, {}); + // for each sopClassUID, call the old makeDisplaySetForInstances with a + // instance list composed only by instances with the same sopClassUID and + // accumulate the displaySets in the variable allDisplaySets + const sopClasses = Object.keys(instancesForSetSOPClasses); + let allDisplaySets = []; + sopClasses.forEach(sopClass => { + const displaySets = this._makeDisplaySetForInstances( + instancesForSetSOPClasses[sopClass], + settings + ); + allDisplaySets = [...allDisplaySets, ...displaySets]; + }); + return allDisplaySets; + } + /** * Creates new display sets for the instances contained in instancesSrc * according to the sop class handlers registered. @@ -268,17 +304,13 @@ export default class DisplaySetService extends PubSubService { * @param settings are settings to add * @returns Array of the display sets added. */ - public makeDisplaySetForInstances( - instancesSrc: InstanceMetadata[], - settings - ): DisplaySet[] { + private _makeDisplaySetForInstances(instancesSrc: InstanceMetadata[], settings): DisplaySet[] { // Some of the sop class handlers take a direct reference to instances // so make sure it gets copied here so that they have their own ref let instances = [...instancesSrc]; const instance = instances[0]; - const existingDisplaySets = - this.getDisplaySetsForSeries(instance.SeriesInstanceUID) || []; + const existingDisplaySets = this.getDisplaySetsForSeries(instance.SeriesInstanceUID) || []; const SOPClassHandlerIds = this.SOPClassHandlerIds; const allDisplaySets = []; @@ -306,13 +338,13 @@ export default class DisplaySetService extends PubSubService { this.activeDisplaySetsChanged = true; instances = filterInstances(instances, [addedDs]); this._addActiveDisplaySets([addedDs]); - this.setDisplaySetMetadataInvalidated( - addedDs.displaySetInstanceUID - ); + this.setDisplaySetMetadataInvalidated(addedDs.displaySetInstanceUID); } // This means that all instances already existed or got added to // existing display sets, and had an invalidated event fired - if (!instances.length) return allDisplaySets; + if (!instances.length) { + return allDisplaySets; + } } if (!instances.length) { @@ -327,7 +359,9 @@ export default class DisplaySetService extends PubSubService { // creating additional display sets using the sop class handler displaySets = handler.getDisplaySetsFromSeries(instances); - if (!displaySets || !displaySets.length) continue; + if (!displaySets || !displaySets.length) { + continue; + } // applying hp-defined viewport settings to the displaysets displaySets.forEach(ds => { @@ -347,6 +381,24 @@ export default class DisplaySetService extends PubSubService { allDisplaySets.push(...displaySets); } } + // applying the default sopClassUID handler + if (allDisplaySets.length === 0) { + // applying hp-defined viewport settings to the displaysets + const handler = this.extensionManager.getModuleEntry(this.unsuportedSOPClassHandler); + const displaySets = handler.getDisplaySetsFromSeries(instances); + if (displaySets?.length) { + displaySets.forEach(ds => { + Object.keys(settings).forEach(key => { + ds[key] = settings[key]; + }); + }); + + this._addDisplaySetsToCache(displaySets); + this._addActiveDisplaySets(displaySets); + + allDisplaySets.push(...displaySets); + } + } return allDisplaySets; } diff --git a/platform/core/src/services/DisplaySetService/IDisplaySet.ts b/platform/core/src/services/DisplaySetService/IDisplaySet.ts index 9b81d6f891..f193e32221 100644 --- a/platform/core/src/services/DisplaySetService/IDisplaySet.ts +++ b/platform/core/src/services/DisplaySetService/IDisplaySet.ts @@ -3,6 +3,7 @@ interface IDisplaySet { StudyInstanceUID: string; SeriesInstanceUID?: string; SeriesNumber?: string; + unsupported?: boolean; } export default IDisplaySet; diff --git a/platform/core/src/services/DisplaySetService/index.ts b/platform/core/src/services/DisplaySetService/index.ts index 6039e648c3..5893d7beea 100644 --- a/platform/core/src/services/DisplaySetService/index.ts +++ b/platform/core/src/services/DisplaySetService/index.ts @@ -1,3 +1,5 @@ import DisplaySetService from './DisplaySetService'; +import { DisplaySetMessage, DisplaySetMessageList } from './DisplaySetMessage'; export default DisplaySetService; +export { DisplaySetMessage, DisplaySetMessageList }; diff --git a/platform/core/src/services/HangingProtocolService/HPMatcher.js b/platform/core/src/services/HangingProtocolService/HPMatcher.js index b87c31e84e..9d255cc9ca 100644 --- a/platform/core/src/services/HangingProtocolService/HPMatcher.js +++ b/platform/core/src/services/HangingProtocolService/HPMatcher.js @@ -9,12 +9,7 @@ import validate from './lib/validator'; * @param {object[]} options.displaySets is a list of the display sets * @return {Object} Matching Object with score and details (which rule passed or failed) */ -const match = ( - metadataInstance, - rules = [], - customAttributeRetrievalCallbacks, - options -) => { +const match = (metadataInstance, rules = [], customAttributeRetrievalCallbacks, options) => { const validateOptions = { format: 'grouped', }; @@ -46,12 +41,13 @@ const match = ( const { attribute, from = 'metadataInstance' } = rule; // Do not use the custom attribute from the metadataInstance since it is subject to change if (customAttributeRetrievalCallbacks.hasOwnProperty(attribute)) { - readValues[attribute] = customAttributeRetrievalCallbacks[ - attribute - ].callback.call(rule, metadataInstance, options); + readValues[attribute] = customAttributeRetrievalCallbacks[attribute].callback.call( + rule, + metadataInstance, + options + ); } else { - readValues[attribute] = - fromSrc[from]?.[attribute] ?? instance?.[attribute]; + readValues[attribute] = fromSrc[from]?.[attribute] ?? instance?.[attribute]; } // Format the constraint as required by Validate.js @@ -74,13 +70,14 @@ const match = ( errorMessages = ['Something went wrong during validation.', e]; } - console.log( - 'Test', - `${from}.${attribute}`, - readValues[attribute], - JSON.stringify(rule.constraint), - !errorMessages - ); + // TODO: move to a logger + // console.log( + // 'Test', + // `${from}.${attribute}`, + // readValues[attribute], + // JSON.stringify(rule.constraint), + // !errorMessages + // ); if (!errorMessages) { // If no errorMessages were returned, then validation passed. diff --git a/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js b/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js index 1af19950b1..aa9dfa169f 100644 --- a/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js +++ b/platform/core/src/services/HangingProtocolService/HangingProtocolService.test.js @@ -2,7 +2,6 @@ import HangingProtocolService from './HangingProtocolService'; const testProtocol = { id: 'test', - hasUpdatedPriorsInformation: false, name: 'Default', protocolMatchingRules: [ { @@ -119,7 +118,7 @@ function checkHpsBestMatch(hps) { hps.run({ studies: [studyMatch], displaySets: studyMatchDisplaySets }); const { viewportMatchDetails } = hps.getMatchDetails(); expect(viewportMatchDetails.size).toBe(1); - expect(viewportMatchDetails.get(0)).toMatchObject({ + expect(viewportMatchDetails.get('ctAXIAL')).toMatchObject({ viewportOptions: { viewportId: 'ctAXIAL', viewportType: 'volume', @@ -134,7 +133,7 @@ function checkHpsBestMatch(hps) { displaySetOptions: { id: 'displaySetSelector', options: {}, - }, + }, }, ], }); @@ -150,10 +149,7 @@ describe('HangingProtocolService', () => { }, }, }; - const hangingProtocolService = new HangingProtocolService( - commandsManager, - servicesManager - ); + const hangingProtocolService = new HangingProtocolService(commandsManager, servicesManager); let initialScaling; afterEach(() => { @@ -178,10 +174,7 @@ describe('HangingProtocolService', () => { describe('with protocol generator', () => { beforeAll(() => { - hangingProtocolService.addProtocol( - testProtocol.id, - testProtocolGenerator - ); + hangingProtocolService.addProtocol(testProtocol.id, testProtocolGenerator); }); it('has one protocol', () => { @@ -192,6 +185,6 @@ describe('HangingProtocolService', () => { it('matches best image match', () => { checkHpsBestMatch(hangingProtocolService); }); - }); }); }); +}); diff --git a/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts b/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts index 4d3f72df09..01fdd8f6ef 100644 --- a/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts +++ b/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts @@ -8,12 +8,10 @@ import IDisplaySet from '../DisplaySetService/IDisplaySet'; import { CommandsManager } from '../../classes'; import ServicesManager from '../ServicesManager'; import * as HangingProtocol from '../../types/HangingProtocol'; -import { - isDisplaySetFromUrl, - sopInstanceLocation, -} from './custom-attribute/isDisplaySetFromUrl'; +import { isDisplaySetFromUrl, sopInstanceLocation } from './custom-attribute/isDisplaySetFromUrl'; import numberOfDisplaySetsWithImages from './custom-attribute/numberOfDisplaySetsWithImages'; import seriesDescriptionsFromDisplaySets from './custom-attribute/seriesDescriptionsFromDisplaySets'; +import uuidv4 from '../../utils/uuidv4'; type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator; @@ -36,8 +34,7 @@ export default class HangingProtocolService extends PubSubService { // Fired when the stages within the current protocol are known to have // the status set - that is, they are activated (or deactivated). STAGE_ACTIVATION: 'event::hanging_protocol_stage_activation', - CUSTOM_IMAGE_LOAD_PERFORMED: - 'event::hanging_protocol_custom_image_load_performed', + CUSTOM_IMAGE_LOAD_PERFORMED: 'event::hanging_protocol_custom_image_load_performed', }; public static REGISTRATION = { @@ -73,8 +70,7 @@ export default class HangingProtocolService extends PubSubService { customAttributeRetrievalCallbacks = { NumberOfStudyRelatedSeries: { name: 'The number of series in the study', - callback: metadata => - metadata.NumberOfStudyRelatedSeries ?? metadata.series?.length, + callback: metadata => metadata.NumberOfStudyRelatedSeries ?? metadata.series?.length, }, NumberOfSeriesRelatedInstances: { name: 'The number of instances in the display set', @@ -86,7 +82,9 @@ export default class HangingProtocolService extends PubSubService { metadata.ModalitiesInStudy ?? (metadata.series || []).reduce((prev, curr) => { const { Modality } = curr; - if (Modality && prev.indexOf(Modality) == -1) prev.push(Modality); + if (Modality && prev.indexOf(Modality) == -1) { + prev.push(Modality); + } return prev; }, []), }, @@ -130,11 +128,11 @@ export default class HangingProtocolService extends PubSubService { > = new Map(); /** - * An array that contains for each viewport (viewportIndex) specified in the + * An array that contains for each viewport (viewportId) specified in the * hanging protocol, an object of the form */ viewportMatchDetails: Map< - number, // viewportIndex + string, // viewportId HangingProtocol.ViewportMatchDetails > = new Map(); @@ -159,6 +157,9 @@ export default class HangingProtocolService extends PubSubService { this.studies = []; this.viewportMatchDetails = new Map(); this.displaySetMatchDetails = new Map(); + this.protocol = undefined; + this.stageIndex = undefined; + this.protocolEngine = undefined; } /** Leave the hanging protocol in the initialized state */ @@ -177,15 +178,17 @@ export default class HangingProtocolService extends PubSubService { */ public getActiveProtocol(): { protocol: HangingProtocol.Protocol; + _originalProtocol: HangingProtocol.Protocol; stage: HangingProtocol.ProtocolStage; stageIndex: number; activeStudy?: StudyMetadata; - viewportMatchDetails: Map; + viewportMatchDetails: Map; displaySetMatchDetails: Map; activeImageLoadStrategyName: string; } { return { protocol: this.protocol, + _originalProtocol: this._originalProtocol, stage: this.protocol?.stages?.[this.stageIndex], stageIndex: this.stageIndex, activeStudy: this.activeStudy, @@ -200,7 +203,9 @@ export default class HangingProtocolService extends PubSubService { * protocolId, stageIndex, stageId and activeStudyUID */ public getState(): HangingProtocol.HPInfo { - if (!this.protocol) return; + if (!this.protocol) { + return; + } return { protocolId: this.protocol.id, stageIndex: this.stageIndex, @@ -257,8 +262,12 @@ export default class HangingProtocolService extends PubSubService { * @returns protocol - the protocol with the given id */ public getProtocolById(protocolId: string): HangingProtocol.Protocol { - if (!protocolId) return; - if (protocolId === this.protocol?.id) return this.protocol; + if (!protocolId) { + return; + } + if (protocolId === this.protocol?.id) { + return this.protocol; + } const protocol = this.protocols.get(protocolId); if (!protocol) { throw new Error(`No protocol ${protocolId} found`); @@ -266,9 +275,7 @@ export default class HangingProtocolService extends PubSubService { if (protocol instanceof Function) { try { - const { protocol: generatedProtocol } = this._getProtocolFromGenerator( - protocol - ); + const { protocol: generatedProtocol } = this._getProtocolFromGenerator(protocol); return generatedProtocol; } catch (error) { @@ -292,9 +299,7 @@ export default class HangingProtocolService extends PubSubService { */ public addProtocol(protocolId: string, protocol: Protocol): void { if (this.protocols.has(protocolId)) { - console.warn( - `A protocol with id ${protocolId} already exists. It will be overwritten.` - ); + console.warn(`A protocol with id ${protocolId} already exists. It will be overwritten.`); } if (!(protocol instanceof Function)) { @@ -344,9 +349,7 @@ export default class HangingProtocolService extends PubSubService { * specifically, but will show another study instead. */ public setActiveStudyUID(activeStudyUID: string): void { - this.activeStudy = this.studies.find( - it => it.StudyInstanceUID === activeStudyUID - ); + this.activeStudy = this.studies.find(it => it.StudyInstanceUID === activeStudyUID); } /** @@ -361,7 +364,7 @@ export default class HangingProtocolService extends PubSubService { * @param params is the dataset to run the hanging protocol on. * @param params.activeStudy is the "primary" study to hang This may or may * not be displayed by the actual viewports. - * @param params.studies is the list of studies to hang. If absent, will re-use the previous set. + * @param params.studies is the list of studies to hang. If absent, will reuse the previous set. * @param params.displaySets is the list of display sets associated with * the studies to display in viewports. * @param protocol is a specific protocol to apply. @@ -393,21 +396,38 @@ export default class HangingProtocolService extends PubSubService { /** * Returns true, if the hangingProtocol has a custom loading strategy for the images * and its callback has been added to the HangingProtocolService - * @returns {boolean} true + * @returns A boolean indicating whether a custom image load strategy has been added or not. */ public hasCustomImageLoadStrategy(): boolean { return ( this.activeImageLoadStrategyName !== null && - this.registeredImageLoadStrategies[ - this.activeImageLoadStrategyName - ] instanceof Function + this.registeredImageLoadStrategies[this.activeImageLoadStrategyName] instanceof Function ); } + /** + * Returns a boolean indicating whether a custom image load has been performed or not. + * A custom image load is performed when a custom image load strategy is used to load images. + * This method is used internally by the HangingProtocolService to determine whether to perform + * a custom image load or not. + * + * @returns A boolean indicating whether a custom image load has been performed or not. + */ public getCustomImageLoadPerformed(): boolean { return this.customImageLoadPerformed; } + /** + * Returns a boolean indicating whether a custom image load should be performed or not. + * A custom image load should be performed if a custom image load strategy has been added to the HangingProtocolService + * and it has not been performed yet. + * + * @returns A boolean indicating whether a custom image load should be performed or not. + */ + public getShouldPerformCustomImageLoad(): boolean { + return this.hasCustomImageLoadStrategy() && !this.getCustomImageLoadPerformed(); + } + /** * Set the strategy callback for loading images to the HangingProtocolService * @param {string} name strategy name @@ -431,10 +451,7 @@ export default class HangingProtocolService extends PubSubService { public addCustomAttribute( attributeId: string, attributeName: string, - callback: ( - metadata: Record, - extraData?: Record - ) => unknown, + callback: (metadata: Record, extraData?: Record) => unknown, options: Record = {} ): void { this.customAttributeRetrievalCallbacks[attributeId] = { @@ -450,9 +467,7 @@ export default class HangingProtocolService extends PubSubService { * if no strategy is set, the default strategy is used */ runImageLoadStrategy(data): boolean { - const loader = this.registeredImageLoadStrategies[ - this.activeImageLoadStrategyName - ]; + const loader = this.registeredImageLoadStrategies[this.activeImageLoadStrategyName]; const loadedData = loader({ data, displaySetsMatchDetails: this.displaySetMatchDetails, @@ -471,9 +486,7 @@ export default class HangingProtocolService extends PubSubService { return true; } - _validateProtocol( - protocol: HangingProtocol.Protocol - ): HangingProtocol.Protocol { + _validateProtocol(protocol: HangingProtocol.Protocol): HangingProtocol.Protocol { protocol.id = protocol.id || protocol.name; const defaultViewportOptions = { toolGroupId: 'default', @@ -508,21 +521,38 @@ export default class HangingProtocolService extends PubSubService { for (let i = 0; i < rows * columns; i++) { stage.viewports.push({ - viewportOptions: defaultViewportOptions, + viewportOptions: { + ...defaultViewportOptions, + // Use 'default' for the first viewport, and UUIDs for the rest. + viewportId: i === 0 ? 'default' : uuidv4(), + }, displaySets: [], }); } } else { + // Clone each viewport to ensure independent objects + stage.viewports = stage.viewports.map((viewport, index) => { + const existingViewportId = viewport.viewportOptions?.viewportId; + + return { + ...viewport, + viewportOptions: { + ...(viewport.viewportOptions || defaultViewportOptions), + // use provided viewportId when available, otherwise use default for first viewport + // and uuid for the rest + viewportId: existingViewportId + ? existingViewportId + : index === 0 + ? 'default' + : uuidv4(), + }, + displaySets: viewport.displaySets || [], + }; + }); stage.viewports.forEach(viewport => { - viewport.viewportOptions = - viewport.viewportOptions || defaultViewportOptions; - if (!viewport.displaySets) { - viewport.displaySets = []; - } else { - viewport.displaySets.forEach(displaySet => { - displaySet.options = displaySet.options || {}; - }); - } + viewport.displaySets.forEach(displaySet => { + displaySet.options = displaySet.options || {}; + }); }); } }); @@ -530,9 +560,7 @@ export default class HangingProtocolService extends PubSubService { return protocol; } - private _getProtocolFromGenerator( - protocolGenerator: HangingProtocol.ProtocolGenerator - ): { + private _getProtocolFromGenerator(protocolGenerator: HangingProtocol.ProtocolGenerator): { protocol: HangingProtocol.Protocol; } { const { protocol } = protocolGenerator({ @@ -547,20 +575,32 @@ export default class HangingProtocolService extends PubSubService { }; } - getViewportsRequireUpdate(viewportIndex, displaySetInstanceUID) { + getViewportsRequireUpdate(viewportId, displaySetInstanceUID) { + const { displaySetService } = this._servicesManager.services; + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + if (displaySet?.unsupported) { + throw new Error('Unsupported displaySet'); + } const newDisplaySetInstanceUID = displaySetInstanceUID; const protocol = this.protocol; const protocolStage = protocol.stages[this.stageIndex]; const protocolViewports = protocolStage.viewports; - const protocolViewport = protocolViewports[viewportIndex]; const defaultReturn = [ { - viewportIndex, + viewportId, displaySetInstanceUIDs: [newDisplaySetInstanceUID], }, ]; + if (!protocolViewports) { + return defaultReturn; + } + + const protocolViewport = protocolViewports.find( + pv => pv.viewportOptions.viewportId === viewportId + ); + // if no viewport, then we can assume there is no predefined set of // rules that should be applied to this viewport while matching if (!protocolViewport) { @@ -589,12 +629,9 @@ export default class HangingProtocolService extends PubSubService { // if the viewport is not empty, then we check the displaySets it is showing // currently, which means we need to check if the requested updated displaySet // follow the same rules as the current displaySets - const { - id: displaySetSelectorId, - matchedDisplaySetsIndex = 0, - } = protocolViewport.displaySets[0]; - const displaySetSelector = - protocol.displaySetSelectors[displaySetSelectorId]; + const { id: displaySetSelectorId, matchedDisplaySetsIndex = 0 } = + protocolViewport.displaySets[0]; + const displaySetSelector = protocol.displaySetSelectors[displaySetSelectorId]; if (!displaySetSelector) { return defaultReturn; @@ -626,13 +663,13 @@ export default class HangingProtocolService extends PubSubService { } // if we reach here, it means that the displaySetInstanceUIDs to be dropped - // in the viewportIndex are valid, and we can proceed with the update. However + // for the desired viewportId are valid, and we can proceed with the update. However // we need to check if the displaySets that the viewport were showing // was also referenced by other viewports, and if so, we need to update those // viewports as well // check if displaySetSelectors are used by other viewports, and - // store the viewportIndex and displaySetInstanceUIDs that need to be updated + // store the viewportId and displaySetInstanceUIDs that need to be updated const viewportsToUpdate = []; protocolViewports.forEach((viewport, index) => { const viewportNeedsUpdate = viewport.displaySets.some( @@ -643,22 +680,20 @@ export default class HangingProtocolService extends PubSubService { if (viewportNeedsUpdate) { // Try to recompute the viewport options based on the current - // viewportIndex that needs update but from its old/original un-computed + // viewportId that needs update but from its old/original un-computed // viewport & displaySet options if (originalProtocolStage) { const originalViewport = originalProtocolStage.viewports[index]; const originalViewportOptions = originalViewport.viewportOptions; const originalDisplaySetOptions = originalViewport.displaySets; - viewport.viewportOptions = this.getComputedOptions( - originalViewportOptions, - [newDisplaySetInstanceUID] - ); + viewport.viewportOptions = this.getComputedOptions(originalViewportOptions, [ + newDisplaySetInstanceUID, + ]); - viewport.displaySets = this.getComputedOptions( - originalDisplaySetOptions, - [newDisplaySetInstanceUID] - ); + viewport.displaySets = this.getComputedOptions(originalDisplaySetOptions, [ + newDisplaySetInstanceUID, + ]); } const displaySetInstanceUIDs = []; @@ -674,7 +709,7 @@ export default class HangingProtocolService extends PubSubService { ); viewportsToUpdate.push({ - viewportIndex: index, + viewportId: viewport.viewportOptions.viewportId, displaySetInstanceUIDs, viewportOptions: viewport.viewportOptions, displaySetOptions, @@ -695,15 +730,18 @@ export default class HangingProtocolService extends PubSubService { ) { viewport.displaySets.forEach(displaySet => { const { id } = displaySet; - const { - displaySetInstanceUID: oldDisplaySetInstanceUID, - } = displaySetMatchDetails.get(id); + const displaySetMatchDetail = displaySetMatchDetails.get(id); - displaySetInstanceUIDs.push( + const { displaySetInstanceUID: oldDisplaySetInstanceUID } = displaySetMatchDetail; + + const displaySetInstanceUID = displaySet.id === displaySetSelectorId ? newDisplaySetInstanceUID - : oldDisplaySetInstanceUID - ); + : oldDisplaySetInstanceUID; + + displaySetMatchDetail.displaySetInstanceUID = displaySetInstanceUID; + + displaySetInstanceUIDs.push(displaySetInstanceUID); displaySetOptions.push(displaySet); }); } @@ -722,13 +760,15 @@ export default class HangingProtocolService extends PubSubService { ): any { // Base case: if options is an array, map over the array and recursively call getComputedOptions if (Array.isArray(options)) { - return options.map(option => - this.getComputedOptions(option, displaySetUIDs) - ); + return options.map(option => this.getComputedOptions(option, displaySetUIDs)); } - if (options === null) return options; - if (typeof options !== 'object') return options; + if (options === null) { + return options; + } + if (typeof options !== 'object') { + return options; + } // If options is an object with a custom attribute, compute a new options object if (options.custom) { @@ -743,8 +783,7 @@ export default class HangingProtocolService extends PubSubService { ); } - const callback = this.customAttributeRetrievalCallbacks[customKey] - .callback; + const callback = this.customAttributeRetrievalCallbacks[customKey].callback; let newOptions = callback.call(options, displaySets); if (newOptions === undefined) { @@ -772,7 +811,7 @@ export default class HangingProtocolService extends PubSubService { * @param protocolId - name of the registered protocol to be set * @param options - options to be passed to the protocol, this is either an array * of the displaySetInstanceUIDs to be set on ALL VIEWPORTS OF THE PROTOCOL or an object - * that contains viewportIndex as the key and displaySetInstanceUIDs as the value + * that contains viewportId as the key and displaySetInstanceUIDs as the value * for each viewport that needs to be set. * @param errorCallback - callback to be called if there is an error * during the protocol application @@ -849,18 +888,12 @@ export default class HangingProtocolService extends PubSubService { * * @returns the stage number to apply initially, given the options. */ - private _updateStageStatus( - options = null as HangingProtocol.SetProtocolOptions - ) { + private _updateStageStatus(options = null as HangingProtocol.SetProtocolOptions) { const stages = this.protocol.stages; for (let i = 0; i < stages.length; i++) { const stage = stages[i]; - const { matchedViewports } = this._matchAllViewports( - stage, - options, - new Map() - ); + const { matchedViewports } = this._matchAllViewports(stage, options, new Map()); const activation = stage.stageActivation || {}; if (this.matchActivation(matchedViewports, activation.passive, 0)) { if (this.matchActivation(matchedViewports, activation.enabled, 1)) { @@ -879,9 +912,7 @@ export default class HangingProtocolService extends PubSubService { }); } - private _findStageIndex( - options = null as HangingProtocol.SetProtocolOptions - ): number | void { + private _findStageIndex(options = null as HangingProtocol.SetProtocolOptions): number | void { const stageId = options?.stageId; const protocol = this.protocol; const stages = protocol.stages; @@ -889,7 +920,9 @@ export default class HangingProtocolService extends PubSubService { if (stageId) { for (let i = 0; i < stages.length; i++) { const stage = stages[i]; - if (stage.id === stageId && stage.status !== 'disabled') return i; + if (stage.id === stageId && stage.status !== 'disabled') { + return i; + } } return; } @@ -902,7 +935,9 @@ export default class HangingProtocolService extends PubSubService { let firstNotDisabled: number; for (let i = 0; i < stages.length; i++) { - if (stages[i].status === 'enabled') return i; + if (stages[i].status === 'enabled') { + return i; + } if (firstNotDisabled === undefined && stages[i].status !== 'disabled') { firstNotDisabled = i; } @@ -926,10 +961,7 @@ export default class HangingProtocolService extends PubSubService { const { imageLoadStrategy } = protocol; if (imageLoadStrategy) { // check if the imageLoadStrategy is a valid strategy - if ( - this.registeredImageLoadStrategies[imageLoadStrategy] instanceof - Function - ) { + if (this.registeredImageLoadStrategies[imageLoadStrategy] instanceof Function) { this.activeImageLoadStrategyName = imageLoadStrategy; } } @@ -939,9 +971,7 @@ export default class HangingProtocolService extends PubSubService { const stage = this._findStageIndex(options); if (stage === undefined) { - throw new Error( - `Can't find applicable stage ${protocol.id} ${options?.stageIndex}` - ); + throw new Error(`Can't find applicable stage ${protocol.id} ${options?.stageIndex}`); } this.stageIndex = stage as number; this._updateViewports(options); @@ -976,7 +1006,9 @@ export default class HangingProtocolService extends PubSubService { if (stageId !== undefined) { return protocol.stages.findIndex(it => it.id === stageId); } - if (stageIndex !== undefined) return stageIndex; + if (stageIndex !== undefined) { + return stageIndex; + } return 0; } @@ -985,11 +1017,7 @@ export default class HangingProtocolService extends PubSubService { * undefined if no protocol or stages are set */ _getNumProtocolStages() { - if ( - !this.protocol || - !this.protocol.stages || - !this.protocol.stages.length - ) { + if (!this.protocol || !this.protocol.stages || !this.protocol.stages.length) { return; } @@ -1027,7 +1055,9 @@ export default class HangingProtocolService extends PubSubService { const protocol = this.protocol; const stage = protocol.stages[stageIdx]; const defaultViewport = stage.defaultViewport || protocol.defaultViewport; - if (!defaultViewport) return; + if (!defaultViewport) { + return; + } const useViewport = { ...defaultViewport }; return this._matchViewport(useViewport, options); @@ -1063,7 +1093,6 @@ export default class HangingProtocolService extends PubSubService { // matching applied this.viewportMatchDetails = new Map(); this.displaySetMatchDetails = new Map(); - this.customImageLoadPerformed = false; // Retrieve the current stage const stageModel = this._getCurrentStageModel(); @@ -1113,7 +1142,11 @@ export default class HangingProtocolService extends PubSubService { displaySetMatchDetails: Map; } { let matchedViewports = 0; - stageModel.viewports.forEach((viewport, viewportIndex) => { + stageModel.viewports.forEach(viewport => { + // Todo: we should probably assign a random viewportId if not defined + // below, but it feels odd since viewportGrid should handle this kind + // of thing + const viewportId = viewport.viewportOptions.viewportId; const matchDetails = this._matchViewport( viewport, options, @@ -1127,14 +1160,12 @@ export default class HangingProtocolService extends PubSubService { ) { matchedViewports++; } else { - console.log( - 'Adding an empty set of display sets for mapping purposes' - ); + console.log('Adding an empty set of display sets for mapping purposes'); matchDetails.displaySetsInfo = viewport.displaySets.map(it => ({ displaySetOptions: it, })); } - viewportMatchDetails.set(viewportIndex, matchDetails); + viewportMatchDetails.set(viewportId, matchDetails); } }); return { @@ -1149,18 +1180,20 @@ export default class HangingProtocolService extends PubSubService { offset: number, options: HangingProtocol.SetProtocolOptions = {} ): HangingProtocol.DisplaySetMatchDetails { - if (!matchDetails) return; - if (offset === 0) return matchDetails; + if (!matchDetails) { + return; + } + if (offset === 0) { + return matchDetails; + } const { matchingScores = [] } = matchDetails; if (offset === -1) { const { inDisplay } = options; - if (!inDisplay) return matchDetails; + if (!inDisplay) { + return matchDetails; + } for (let i = 0; i < matchDetails.matchingScores.length; i++) { - if ( - inDisplay.indexOf( - matchDetails.matchingScores[i].displaySetInstanceUID - ) === -1 - ) { + if (inDisplay.indexOf(matchDetails.matchingScores[i].displaySetInstanceUID) === -1) { const match = matchDetails.matchingScores[i]; return match.matchingScore > 0 ? { @@ -1181,16 +1214,18 @@ export default class HangingProtocolService extends PubSubService { id: string, displaySetUID: string ): void { - if (match.displaySetInstanceUID === displaySetUID) return; + if (match.displaySetInstanceUID === displaySetUID) { + return; + } if (!match.matchingScores) { throw new Error('No matchingScores found in ' + match); } for (const subMatch of match.matchingScores) { - if (subMatch.displaySetInstanceUID === displaySetUID) return; + if (subMatch.displaySetInstanceUID === displaySetUID) { + return; + } } - throw new Error( - `Reused viewport details ${id} with ds ${displaySetUID} not valid` - ); + throw new Error(`Reused viewport details ${id} with ds ${displaySetUID} not valid`); } protected _matchViewport( @@ -1212,9 +1247,7 @@ export default class HangingProtocolService extends PubSubService { console.warn('No display set selector for', displaySetId); continue; } - const { bestMatch, matchingScores } = this._matchImages( - displaySetSelector - ); + const { bestMatch, matchingScores } = this._matchImages(displaySetSelector); displaySetMatchDetails.set(displaySetId, bestMatch); if (bestMatch) { @@ -1231,10 +1264,7 @@ export default class HangingProtocolService extends PubSubService { viewport.displaySets.forEach(displaySetOptions => { const { id, matchedDisplaySetsIndex = 0 } = displaySetOptions; const reuseDisplaySetUID = - id && - displaySetSelectorMap[ - `${activeStudyUID}:${id}:${matchedDisplaySetsIndex || 0}` - ]; + id && displaySetSelectorMap[`${activeStudyUID}:${id}:${matchedDisplaySetsIndex || 0}`]; const viewportDisplaySetMain = this.displaySetMatchDetails.get(id); const viewportDisplaySet = this.findDeduplicatedMatchDetails( @@ -1246,11 +1276,7 @@ export default class HangingProtocolService extends PubSubService { // Use the display set provided instead if (reuseDisplaySetUID) { if (viewportOptions.allowUnmatchedView !== true) { - this.validateDisplaySetSelectMatch( - viewportDisplaySet, - id, - reuseDisplaySetUID - ); + this.validateDisplaySetSelectMatch(viewportDisplaySet, id, reuseDisplaySetUID); } const displaySetInfo: HangingProtocol.DisplaySetInfo = { displaySetInstanceUID: reuseDisplaySetUID, @@ -1294,8 +1320,7 @@ export default class HangingProtocolService extends PubSubService { ): void { const { displaySetService } = this._servicesManager.services; const protocolViewportDisplaySets = protocolViewport.displaySets; - const numDisplaySetsToSet = - displaySetAndViewportOptions.displaySetInstanceUIDs.length; + const numDisplaySetsToSet = displaySetAndViewportOptions.displaySetInstanceUIDs.length; if ( protocolViewportDisplaySets.length > 0 && @@ -1306,24 +1331,20 @@ export default class HangingProtocolService extends PubSubService { ); } - displaySetAndViewportOptions.displaySetInstanceUIDs.forEach( - displaySetInstanceUID => { - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + displaySetAndViewportOptions.displaySetInstanceUIDs.forEach(displaySetInstanceUID => { + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); - const { displaySets: displaySetsInfo } = protocolViewport; + const { displaySets: displaySetsInfo } = protocolViewport; - for (const displaySetInfo of displaySetsInfo) { - const displaySetSelector = displaySetSelectors[displaySetInfo.id]; + for (const displaySetInfo of displaySetsInfo) { + const displaySetSelector = displaySetSelectors[displaySetInfo.id]; - if (!displaySetSelector) { - continue; - } - this._validateRequiredSelectors(displaySetSelector, displaySet); + if (!displaySetSelector) { + continue; } + this._validateRequiredSelectors(displaySetSelector, displaySet); } - ); + }); } private _validateRequiredSelectors( @@ -1349,19 +1370,15 @@ export default class HangingProtocolService extends PubSubService { const { displaySetService } = this._servicesManager.services; const { displaySetSelectorMap } = options; if (displaySetSelectorMap) { - Object.entries(displaySetSelectorMap).forEach( - ([key, displaySetInstanceUID]) => { - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + Object.entries(displaySetSelectorMap).forEach(([key, displaySetInstanceUID]) => { + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); - if (!displaySet) { - throw new Error( - `The displaySetInstanceUID ${displaySetInstanceUID} is not found in the displaySetService` - ); - } + if (!displaySet) { + throw new Error( + `The displaySetInstanceUID ${displaySetInstanceUID} is not found in the displaySetService` + ); } - ); + }); } } @@ -1371,54 +1388,39 @@ export default class HangingProtocolService extends PubSubService { // level matching needs to be added in future // Todo: handle fusion viewports by not taking the first displaySet rule for the viewport - const { - id, - studyMatchingRules = [], - seriesMatchingRules, - } = displaySetRules; + const { id, studyMatchingRules = [], seriesMatchingRules } = displaySetRules; const matchingScores = []; let highestSeriesMatchingScore = 0; - console.log( - 'ProtocolEngine::matchImages', - studyMatchingRules, - seriesMatchingRules - ); + console.log('ProtocolEngine::matchImages', studyMatchingRules, seriesMatchingRules); const matchActiveOnly = this.protocol.numberOfPriorsReferenced === -1; - this.studies.forEach(study => { + this.studies.forEach((study, studyInstanceUIDsIndex) => { // Skip non-active if active only - if (matchActiveOnly && this.activeStudy !== study) return; + if (matchActiveOnly && this.activeStudy !== study) { + return; + } + const studyDisplaySets = this.displaySets.filter( - it => it.StudyInstanceUID === study.StudyInstanceUID - ); - const studyMatchDetails = this.protocolEngine.findMatch( - study, - studyMatchingRules, - { - studies: this.studies, - displaySets: studyDisplaySets, - displaySetMatchDetails: this.displaySetMatchDetails, - } + it => it.StudyInstanceUID === study.StudyInstanceUID && !it?.unsupported ); + const studyMatchDetails = this.protocolEngine.findMatch(study, studyMatchingRules, { + studies: this.studies, + displaySets: studyDisplaySets, + allDisplaySets: this.displaySets, + displaySetMatchDetails: this.displaySetMatchDetails, + studyInstanceUIDsIndex, + }); + // Prevent bestMatch from being updated if the matchDetails' required attribute check has failed if (studyMatchDetails.requiredFailed === true) { return; } - this.debug( - 'study', - study.StudyInstanceUID, - 'display sets #', - studyDisplaySets.length - ); + this.debug('study', study.StudyInstanceUID, 'display sets #', studyDisplaySets.length); studyDisplaySets.forEach(displaySet => { - const { - StudyInstanceUID, - SeriesInstanceUID, - displaySetInstanceUID, - } = displaySet; + const { StudyInstanceUID, SeriesInstanceUID, displaySetInstanceUID } = displaySet; const seriesMatchDetails = this.protocolEngine.findMatch( displaySet, seriesMatchingRules, @@ -1433,41 +1435,25 @@ export default class HangingProtocolService extends PubSubService { // Prevent bestMatch from being updated if the matchDetails' required attribute check has failed if (seriesMatchDetails.requiredFailed === true) { - this.debug( - 'Display set required failed', - displaySet, - seriesMatchingRules - ); + this.debug('Display set required failed', displaySet, seriesMatchingRules); return; } this.debug('Found displaySet for rules', displaySet); - highestSeriesMatchingScore = Math.max( - seriesMatchDetails.score, - highestSeriesMatchingScore - ); + highestSeriesMatchingScore = Math.max(seriesMatchDetails.score, highestSeriesMatchingScore); const matchDetails = { passed: [], failed: [], }; - matchDetails.passed = matchDetails.passed.concat( - seriesMatchDetails.details.passed - ); - matchDetails.passed = matchDetails.passed.concat( - studyMatchDetails.details.passed - ); + matchDetails.passed = matchDetails.passed.concat(seriesMatchDetails.details.passed); + matchDetails.passed = matchDetails.passed.concat(studyMatchDetails.details.passed); - matchDetails.failed = matchDetails.failed.concat( - seriesMatchDetails.details.failed - ); - matchDetails.failed = matchDetails.failed.concat( - studyMatchDetails.details.failed - ); + matchDetails.failed = matchDetails.failed.concat(seriesMatchDetails.details.failed); + matchDetails.failed = matchDetails.failed.concat(studyMatchDetails.details.failed); - const totalMatchScore = - seriesMatchDetails.score + studyMatchDetails.score; + const totalMatchScore = seriesMatchDetails.score + studyMatchDetails.score; const imageDetails = { StudyInstanceUID, @@ -1503,17 +1489,11 @@ export default class HangingProtocolService extends PubSubService { }, this._getSeriesFieldForDisplaySetSort() ); - matchingScores.sort((a, b) => - sortingFunction(a.sortingInfo, b.sortingInfo) - ); + matchingScores.sort((a, b) => sortingFunction(a.sortingInfo, b.sortingInfo)); const bestMatch = matchingScores[0]; - console.log( - 'ProtocolEngine::matchImages bestMatch', - bestMatch, - matchingScores - ); + console.log('ProtocolEngine::matchImages bestMatch', bestMatch, matchingScores); return { bestMatch, @@ -1582,9 +1562,7 @@ export default class HangingProtocolService extends PubSubService { this.stageIndex = i; // Log the new stage - this.debug( - `ProtocolEngine::setCurrentProtocolStage stage = ${this.stageIndex}` - ); + this.debug(`ProtocolEngine::setCurrentProtocolStage stage = ${this.stageIndex}`); // Since stage has changed, we need to update the viewports // and redo matchings diff --git a/platform/core/src/services/HangingProtocolService/ProtocolEngine.js b/platform/core/src/services/HangingProtocolService/ProtocolEngine.js index b3c18ab40c..40a2947674 100644 --- a/platform/core/src/services/HangingProtocolService/ProtocolEngine.js +++ b/platform/core/src/services/HangingProtocolService/ProtocolEngine.js @@ -92,12 +92,7 @@ export default class ProtocolEngine { * @returns */ findMatch(metaData, rules, options) { - return HPMatcher.match( - metaData, - rules, - this.customAttributeRetrievalCallbacks, - options - ); + return HPMatcher.match(metaData, rules, this.customAttributeRetrievalCallbacks, options); } /** @@ -142,8 +137,7 @@ export default class ProtocolEngine { // if not select the first protocol in the list if (!matched.length) { const protocol = - this.protocols.find(protocol => protocol.id === 'default') ?? - this.protocols[0]; + this.protocols.find(protocol => protocol.id === 'default') ?? this.protocols[0]; console.log('No protocol matches, defaulting to', protocol); return [ { @@ -174,9 +168,7 @@ export default class ProtocolEngine { if (!Object.keys(this.matchedProtocolScores).length) { return; } - const highestScoringProtocolId = this._largestKeyByValue( - this.matchedProtocolScores - ); + const highestScoringProtocolId = this._largestKeyByValue(this.matchedProtocolScores); return this.matchedProtocols.get(highestScoringProtocolId); } } diff --git a/platform/core/src/services/HangingProtocolService/custom-attribute/isDisplaySetFromUrl.ts b/platform/core/src/services/HangingProtocolService/custom-attribute/isDisplaySetFromUrl.ts index d1626919da..20ebb76fe4 100644 --- a/platform/core/src/services/HangingProtocolService/custom-attribute/isDisplaySetFromUrl.ts +++ b/platform/core/src/services/HangingProtocolService/custom-attribute/isDisplaySetFromUrl.ts @@ -8,17 +8,14 @@ import { getSplitParam } from '../../../utils'; */ const isDisplaySetFromUrl = (displaySet): boolean => { const params = new URLSearchParams(window.location.search); - const initialSeriesInstanceUID = getSplitParam( - 'initialseriesinstanceuid', - params - ); + const initialSeriesInstanceUID = getSplitParam('initialseriesinstanceuid', params); const initialSOPInstanceUID = getSplitParam('initialsopinstanceuid', params); - if (!initialSeriesInstanceUID && !initialSOPInstanceUID) return false; + if (!initialSeriesInstanceUID && !initialSOPInstanceUID) { + return false; + } const isSeriesMatch = !initialSeriesInstanceUID || - initialSeriesInstanceUID.some( - seriesUID => displaySet.SeriesInstanceUID === seriesUID - ); + initialSeriesInstanceUID.some(seriesUID => displaySet.SeriesInstanceUID === seriesUID); const isSopMatch = !initialSOPInstanceUID || displaySet.instances?.some?.(instance => @@ -32,9 +29,13 @@ const isDisplaySetFromUrl = (displaySet): boolean => { */ function sopInstanceLocation(displaySets) { const displaySet = displaySets?.[0]; - if (!displaySet) return; + if (!displaySet) { + return; + } const initialSOPInstanceUID = getSplitParam('initialsopinstanceuid'); - if (!initialSOPInstanceUID) return; + if (!initialSOPInstanceUID) { + return; + } const index = displaySet.instances.findIndex(instance => initialSOPInstanceUID.includes(instance.SOPInstanceUID) diff --git a/platform/core/src/services/HangingProtocolService/custom-attribute/numberOfDisplaySetsWithImages.ts b/platform/core/src/services/HangingProtocolService/custom-attribute/numberOfDisplaySetsWithImages.ts index 75c3d969c6..d61ff3c62c 100644 --- a/platform/core/src/services/HangingProtocolService/custom-attribute/numberOfDisplaySetsWithImages.ts +++ b/platform/core/src/services/HangingProtocolService/custom-attribute/numberOfDisplaySetsWithImages.ts @@ -1,5 +1,5 @@ export default (study, extraData) => { - const ret = extraData?.displaySets?.filter(ds => ds.numImageFrames>0)?.length; - console.log("number of display sets with images", ret); + const ret = extraData?.displaySets?.filter(ds => ds.numImageFrames > 0)?.length; + console.log('number of display sets with images', ret); return ret; -}; \ No newline at end of file +}; diff --git a/platform/core/src/services/HangingProtocolService/custom-attribute/seriesDescriptionsFromDisplaySets.ts b/platform/core/src/services/HangingProtocolService/custom-attribute/seriesDescriptionsFromDisplaySets.ts index 445fb6dd21..cb32f1bbcd 100644 --- a/platform/core/src/services/HangingProtocolService/custom-attribute/seriesDescriptionsFromDisplaySets.ts +++ b/platform/core/src/services/HangingProtocolService/custom-attribute/seriesDescriptionsFromDisplaySets.ts @@ -1 +1 @@ -export default (study, extraData) => extraData?.displaySets?.map(ds => ds.SeriesDescription); \ No newline at end of file +export default (study, extraData) => extraData?.displaySets?.map(ds => ds.SeriesDescription); diff --git a/platform/core/src/services/HangingProtocolService/lib/displayConstraint.js b/platform/core/src/services/HangingProtocolService/lib/displayConstraint.js index 17368c8772..97f85be63e 100644 --- a/platform/core/src/services/HangingProtocolService/lib/displayConstraint.js +++ b/platform/core/src/services/HangingProtocolService/lib/displayConstraint.js @@ -64,8 +64,7 @@ function displayConstraint(attributeId, constraint, attributes) { } const attributeText = getAttributeText(attributeId, attributes); - const constraintText = - attributeText + ' ' + humanize(comparator).toLowerCase() + ' ' + value; + const constraintText = attributeText + ' ' + humanize(comparator).toLowerCase() + ' ' + value; return constraintText; } diff --git a/platform/core/src/services/HangingProtocolService/lib/validator.js b/platform/core/src/services/HangingProtocolService/lib/validator.js index 498ae3201a..72c0a9549c 100644 --- a/platform/core/src/services/HangingProtocolService/lib/validator.js +++ b/platform/core/src/services/HangingProtocolService/lib/validator.js @@ -1,70 +1,224 @@ import validate from 'validate.js'; +/** + * check if the value is strictly equal to options + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = 'abc' (Fail) + * = ['abc'] (Fail) + * = ['abc', 'def', 'GHI'] (Valid) + * = ['abc', 'GHI', 'def'] (Fail) + * = ['abc', 'def'] (Fail) + * + * value = 'Attenuation Corrected' + * testValue = 'Attenuation Corrected' (Valid) + * testValue = 'Attenuation' (Fail) + * + * value = ['Attenuation Corrected'] + * testValue = ['Attenuation Corrected'] (Valid) + * = 'Attenuation Corrected' (Valid) + * = 'Attenuation' (Fail) + * + * */ +validate.validators.equals = function (value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); + + // If options is an array, then we need to validate each element in the array + if (Array.isArray(testValue)) { + // If the array has only one element, then we need to compare the value to that element + if (testValue.length !== dicomArrayValue.length) { + return `${key} must be an array of length ${testValue.length}`; + } else { + for (let i = 0; i < testValue.length; i++) { + if (testValue[i] !== dicomArrayValue[i]) { + return `${key} ${testValue[i]} must equal ${dicomArrayValue[i]}`; + } + } + } + } else if (testValue !== dicomArrayValue[0]) { + return `${key} must equal ${testValue}`; + } +}; +/** + * check if the value is not equal to options + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = 'abc' (Valid) + * = ['abc'] (Valid) + * = ['abc', 'def', 'GHI'] (Fail) + * = ['abc', 'GHI', 'def'] (Valid) + * = ['abc', 'def'] (Valid) + * + * value = 'Attenuation Corrected' + * = 'Attenuation Corrected' (Fail) + * = 'Attenuation' (Valid) + * + * value = ['Attenuation Corrected'] + * testValue = ['Attenuation Corrected'] (Fail) + * = 'Attenuation Corrected' (Fail) + * = 'Attenuation' (Fail) + * */ +validate.validators.doesNotEqual = function (value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); -validate.validators.equals = function(value, options, key, attributes) { - const testValue = options?.value ?? options; - if (value !== testValue) { - return key + 'must equal ' + testValue; + if (Array.isArray(testValue)) { + if (testValue.length === dicomArrayValue.length) { + let score = 0; + testValue.forEach((x, i) => { + if (x === dicomArrayValue[i]) { + score++; + } + }); + if (score === testValue.length) { + return `${key} must not equal to ${testValue}`; + } + } + } else if (testValue === dicomArrayValue[0]) { + console.log(dicomArrayValue, testValue); + return `${key} must not equal to ${testValue}`; } }; -validate.validators.doesNotEqual = function(value, options, key) { - const testValue = options?.value ?? options; - if (value === testValue) { - return key + 'cannot equal ' + testValue; +/** + * Check if a value includes one or more specified options. + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = ‘abc’ (Fail) + * = ‘dog’ (Fail) + * = [‘abc’] (Valid) + * = [‘att’, ‘abc’] (Valid) + * = ['abc', 'def', 'dog'] (Valid) + * = ['cat', 'dog'] (Fail) + * + * value = ['Attenuation Corrected'] + * testValue = 'Attenuation Corrected' (Fail) + * = ['Attenuation Corrected', 'Corrected'] (Valid) + * = ['Attenuation', 'Corrected'] (Fail) + * + * value = 'Attenuation Corrected' + * testValue = ['Attenuation Corrected', 'Corrected'] (Valid) + * = ['Attenuation', 'Corrected'] (Fail) + * */ +validate.validators.includes = function (value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); + + if (Array.isArray(testValue)) { + const includedValues = testValue.filter(el => dicomArrayValue.includes(el)); + if (includedValues.length === 0) { + return `${key} must include at least one of the following values: ${testValue.join(', ')}`; + } + } else { + return `${key} ${testValue} must be an array`; } + // else if (!value.includes(testValue)) { + // return `${key} ${value} must include ${testValue}`; + // } }; +/** + * Check if a value does not include one or more specified options. + * + * @example + * value = ['abc', 'def', 'GHI'] + * testValue = ['Corr'] (Valid) + * = 'abc' (Fail) + * = ['abc'] (Fail) + * = [‘att’, ‘cor’] (Valid) + * = ['abc', 'def', 'dog'] (Fail) + * + * value = ['Attenuation Corrected'] + * testValue = 'Attenuation Corrected' (Fail) + * = ['Attenuation Corrected', 'Corrected'] (Fail) + * = ['Attenuation', 'Corrected'] (Valid) + * + * value = 'Attenuation Corrected' + * testValue = ['Attenuation Corrected', 'Corrected'] (Fail) + * = ['Attenuation', 'Corrected'] (Valid) + * */ +validate.validators.doesNotInclude = function (value, options, key) { + const testValue = getTestValue(options); + const dicomArrayValue = dicomTagToArray(value); + // if (!Array.isArray(value) || value.length === 1) { + // return `${key} is not allowed as a single value`; + // } + if (Array.isArray(testValue)) { + const includedValues = testValue.filter(el => dicomArrayValue.includes(el)); + if (includedValues.length > 0) { + return `${key} must not include the following value: ${includedValues}`; + } + } else { + return `${key} ${testValue} must be an array`; + } +}; // Ignore case contains. // options testValue MUST be in lower case already, otherwise it won't match +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Valid) + * = ‘corr’ (Valid) + * = [‘att’, ‘cor’] (Valid) + * = [‘Att’, ‘Wall’] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'def' (Valid) + * = 'dog' (Fail) + * = ['gh', 'de'] (Valid) + * = ['cat', 'dog'] (Fail) + * + * */ validate.validators.containsI = function (value, options, key) { - const testValue = options?.value ?? options; + const testValue = getTestValue(options); if (Array.isArray(value)) { - if ( - value.some( - item => !validate.validators.containsI(item.toLowerCase(), options, key) - ) - ) { + if (value.some(item => !validate.validators.containsI(item.toLowerCase(), options, key))) { return undefined; } - return `No item of ${value.join(',')} contains ${JSON.stringify( - testValue - )}`; + return `No item of ${value.join(',')} contains ${JSON.stringify(testValue)}`; } if (Array.isArray(testValue)) { if ( - testValue.some( - subTest => !validate.validators.containsI(value, subTest, key) - ) + testValue.some(subTest => !validate.validators.containsI(value, subTest.toLowerCase(), key)) ) { return; } return `${key} must contain at least one of ${testValue.join(',')}`; } - if ( - testValue && - value.indexOf && - value.toLowerCase().indexOf(testValue) === -1 - ) { + if (testValue && value.indexOf && value.toLowerCase().indexOf(testValue.toLowerCase()) === -1) { return key + 'must contain any case of' + testValue; } }; - -validate.validators.contains = function(value, options, key) { - const testValue = options?.value ?? options; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Valid) + * = ‘corr’ (Fail) + * = [‘att’, ‘cor’] (Fail) + * = [‘Att’, ‘Wall’] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'def' (Valid) + * = 'dog' (Fail) + * = ['cat', 'de'] (Valid) + * = ['cat', 'dog'] (Fail) + * + * */ +validate.validators.contains = function (value, options, key) { + const testValue = getTestValue(options); if (Array.isArray(value)) { if (value.some(item => !validate.validators.contains(item, options, key))) { return undefined; } - return `No item of ${value.join(',')} contains ${JSON.stringify( - testValue - )}`; + return `No item of ${value.join(',')} contains ${JSON.stringify(testValue)}`; } if (Array.isArray(testValue)) { - if ( - testValue.some( - subTest => !validate.validators.contains(value, subTest, key) - ) - ) { + if (testValue.some(subTest => !validate.validators.contains(value, subTest, key))) { return; } return `${key} must contain at least one of ${testValue.join(',')}`; @@ -73,50 +227,237 @@ validate.validators.contains = function(value, options, key) { return key + 'must contain ' + testValue; } }; - -validate.validators.doesNotContain = function(value, options, key) { - if (options && value.indexOf && value.indexOf(options.value) !== -1) { - return key + 'cannot contain ' + options.value; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Fail) + * = ‘corr’ (Valid) + * = [‘att’, ‘cor’] (Valid) + * = [‘Att’, ‘Wall’] (Fail) + * = [‘cat’, ‘dog’] (Valid) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'def' (Fail) + * = 'dog' (Valid) + * = ['cat', 'de'] (Fail) + * = ['cat', 'dog'] (Valid) + * + * */ +validate.validators.doesNotContain = function (value, options, key) { + const containsResult = validate.validators.contains(value, options, key); + if (!containsResult) { + return `No item of ${value} should contain ${getTestValue(options)}`; } }; -validate.validators.startsWith = function(value, options, key) { - if (options && value.startsWith && !value.startsWith(options.value)) { - return key + 'must start with ' + options.value; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Fail) + * = ‘corr’ (Fail) + * = [‘att’, ‘cor’] (Fail) + * = [‘Att’, ‘Wall’] (Fail) + * = [‘cat’, ‘dog’] (Valid) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'DEF' (Fail) + * = 'dog' (Valid) + * = ['cat', 'gh'] (Fail) + * = ['cat', 'dog'] (Valid) + * + * */ +validate.validators.doesNotContainI = function (value, options, key) { + const containsResult = validate.validators.containsI(value, options, key); + if (!containsResult) { + return `No item of ${value} should not contain ${getTestValue(options)}`; } }; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘Corr’ (Fail) + * = ‘Att’ (Fail) + * = ['cat', 'dog', 'Att'] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'deg' (Valid) + * = ['cat', 'GH'] (Valid) + * = ['cat', 'gh'] (Fail) + * = ['cat', 'dog'] (Fail) + * + * */ +validate.validators.startsWith = function (value, options, key) { + let testValues = getTestValue(options); + + if (typeof testValues === 'string') { + testValues = [testValues]; + } -validate.validators.endsWith = function(value, options, key) { - if (options && value.endsWith && !value.endsWith(options.value)) { - return key + 'must end with ' + options.value; + if (typeof value === 'string') { + if (!testValues.some(testValue => value.startsWith(testValue))) { + return key + ' must start with any of these values: ' + testValues; + } + } else if (Array.isArray(value)) { + let valid = false; + for (let i = 0; i < value.length; i++) { + for (let j = 0; j < testValues.length; j++) { + if (value[i].startsWith(testValues[j])) { + valid = true; // set valid flag to true if a match is found + break; + } + } + if (valid) { + return undefined; // break out of loop if a match is found + } + } + + if (!valid) { + return key + ' must start with any of these values: ' + testValues; // return undefined if no match is found + } + } else { + return 'Value must be a string or an array'; } }; -const getTestValue = options => options?.value ?? options; +/** + * @example + * value = 'Attenuation Corrected' + * testValue = ‘TED’ (Fail) + * = ‘ted’ (Valid) + * = ['cat', 'dog', 'ted'] (Valid) + * = [‘cat’, ‘dog’] (Fail) + * + * value = ['abc', 'def', 'GHI'] + * testValue = 'deg' (Valid) + * = ['cat', 'HI'] (Valid) + * = ['cat', 'hi'] (Fail) + * = ['cat', 'dog'] (Fail) + * + * */ +validate.validators.endsWith = function (value, options, key) { + let testValues = getTestValue(options); + + if (typeof testValues === 'string') { + testValues = [testValues]; + } + + if (typeof value === 'string') { + if (!testValues.some(testValue => value.endsWith(testValue))) { + return key + ' must end with any of these values: ' + testValues; + } + } else if (Array.isArray(value)) { + let valid = false; + for (let i = 0; i < value.length; i++) { + for (let j = 0; j < testValues.length; j++) { + if (value[i].endsWith(testValues[j])) { + valid = true; // set valid flag to true if a match is found + break; + } + } + if (valid) { + return undefined; // break out of loop if a match is found + } + } -validate.validators.greaterThan = function(value, options, key) { + if (!valid) { + return key + ' must end with any of these values: ' + testValues; // return undefined if no match is found + } + } else { + return key + ' must be a string or an array'; + } +}; +/** + * @example + * value = 30 + * testValue = 20 (Valid) + * = 40 (Fail) + * + * */ +validate.validators.greaterThan = function (value, options, key) { + const testValue = getTestValue(options); + if (Array.isArray(value) || typeof value === 'string') { + return `${key} is not allowed as an array or string`; + } + if (Array.isArray(testValue)) { + if (testValue.length === 1) { + if (!(value >= testValue[0])) { + return `${key} must be greater than or equal to ${testValue[0]}, but was ${value}`; + } + } else if (testValue.length > 1) { + return key + ' must be an array of length 1'; + } + } else { + if (!(value >= testValue)) { + return key + ' must be greater than ' + testValue; + } + } +}; +/** + * @example + * value = 30 + * testValue = 40 (Valid) + * = 20 (Fail) + * + * */ +validate.validators.lessThan = function (value, options, key) { const testValue = getTestValue(options); - if (value === undefined || value === null || value <= testValue) { - return key + 'with value ' + value + ' must be greater than ' + testValue; + if (Array.isArray(testValue)) { + if (testValue.length === 1) { + if (!(value <= testValue[0])) { + return `${key} must be less than or equal to ${testValue[0]}, but was ${value}`; + } + } else if (testValue.length > 1) { + return key + ' must be an array of length 1'; + } + } else { + if (!(value <= testValue)) { + return key + ' must be less than ' + testValue; + } } }; +/** + * @example -validate.validators.range = function(value, options, key) { + * + * value = 50 + * testValue = [10,60] (Valid) + * = [60, 10] (Valid) + * = [0, 10] (Fail) + * = [70, 80] (Fail) + * = 45 (Fail) + * = [45] (Fail) + * + * */ +validate.validators.range = function (value, options, key) { const testValue = getTestValue(options); - if (value === undefined || value < testValue[0] || value > testValue[1]) { - return ( - key + - 'with value ' + - value + - ' must be between ' + - testValue[0] + - ' and ' + - testValue[1] - ); + if (Array.isArray(testValue) && testValue.length === 2) { + const min = Math.min(testValue[0], testValue[1]); + const max = Math.max(testValue[0], testValue[1]); + if (value === undefined || value < min || value > max) { + return `${key} with value ${value} must be between ${min} and ${max}`; + } + } else { + return `${key} must be an array of length 2`; } }; validate.validators.notNull = value => value === null || value === undefined ? 'Value is null' : undefined; - +const getTestValue = options => { + if (Array.isArray(options)) { + return options.map(option => option?.value ?? option); + } else { + return options?.value ?? options; + } +}; +const dicomTagToArray = value => { + let dicomArrayValue; + if (!Array.isArray(value)) { + dicomArrayValue = [value]; + } else { + dicomArrayValue = [...value]; + } + return dicomArrayValue; +}; export default validate; diff --git a/platform/core/src/services/HangingProtocolService/lib/validator.test.js b/platform/core/src/services/HangingProtocolService/lib/validator.test.js index 67b7ac52e5..4fa200177d 100644 --- a/platform/core/src/services/HangingProtocolService/lib/validator.test.js +++ b/platform/core/src/services/HangingProtocolService/lib/validator.test.js @@ -2,138 +2,357 @@ import validate from './validator.js'; describe('validator', () => { const attributeMap = { - str: 'string', + str: 'Attenuation Corrected', upper: 'UPPER', num: 3, nullValue: null, list: ['abc', 'def', 'GHI'], + listStr: ['Attenuation Corrected'], }; const options = { format: 'grouped', }; - describe('contains', () => { - it('returns match any list contains', () => { + describe('equals', () => { + it('returned undefined on strictly equals', () => { + expect( + validate(attributeMap, { listStr: { equals: ['Attenuation'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { listStr: { equals: 'Attenuation' } }, [options]) + ).not.toBeUndefined(); expect( - validate(attributeMap, { list: { contains: 'a' } }, [options]) + validate(attributeMap, { listStr: { equals: 'Attenuation Corrected' } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { str: { contains: 'i' } }, [options]) + validate(attributeMap, { listStr: { equals: ['Attenuation Corrected'] } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { str: { contains: ['i'] } }, [options]) + validate(attributeMap, { str: { equals: 'Attenuation Corrected' } }, [options]) + ).toBeUndefined(); + + expect( + validate(attributeMap, { str: { equals: { value: 'Attenuation Corrected' } } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { list: { contains: ['a'] } }, [options]) + validate(attributeMap, { str: { equals: ['Attenuation Corrected'] } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { list: { contains: ['z', 'd'] } }, [options]) + validate(attributeMap, { str: { equals: ['Attenuation'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { equals: ['abc', 'def', 'GHI'] } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { list: { contains: ['z'] } }, [options]) + validate(attributeMap, { list: { equals: ['abc', 'GHI', 'def'] } }, [options]) ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { equals: { value: ['abc', 'def', 'GHI'] } } }, [options]) + ).toBeUndefined(); }); }); - - describe('containsI', () => { - it('returns match any list contains case insensitive', () => { + describe('doesNotEqual', () => { + it('returns undefined if value does not equal ', () => { expect( - validate(attributeMap, { upper: { containsI: ['bye', 'pre'] } }, [ - options, - ]) + validate(attributeMap, { listStr: { doesNotEqual: 'Attenuation Corrected' } }, [options]) ).not.toBeUndefined(); expect( - validate(attributeMap, { list: { containsI: 'hi' } }, [options]) + validate(attributeMap, { listStr: { doesNotEqual: ['Attenuation Corrected'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { listStr: { doesNotEqual: 'Attenuation' } }, [options]) ).toBeUndefined(); + expect( - validate(attributeMap, { list: { containsI: ['hi', 'bye'] } }, [ - options, - ]) + validate(attributeMap, { str: { doesNotEqual: 'Attenuation' } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { list: { containsI: ['bye', 'hi'] } }, [ - options, - ]) + validate(attributeMap, { str: { doesNotEqual: { value: 'Attenuation' } } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { list: { containsI: ['ig', 'hi'] } }, [options]) + validate(attributeMap, { str: { doesNotEqual: ['Attenuation'] } }, [options]) ).toBeUndefined(); expect( - validate(attributeMap, { upper: { containsI: ['bye', 'per'] } }, [ - options, - ]) + validate(attributeMap, { str: { doesNotEqual: ['abc', 'def'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotEqual: ['abc', 'GHI', 'def'] } }, [options]) ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotEqual: ['abc', 'def', 'GHI'] } }, [options]) + ).not.toBeUndefined(); }); }); - - describe('equals', () => { - it('returned undefined on equals', () => { + describe('includes', () => { + it('returns match any list includes', () => { + expect( + validate(attributeMap, { listStr: { includes: 'Attenuation Corrected' } }, [options]) + ).not.toBeUndefined(); expect( - validate(attributeMap, { str: { equals: attributeMap.str } }, [options]) + validate(attributeMap, { listStr: { includes: ['Attenuation Corrected'] } }, [options]) ).toBeUndefined(); expect( - validate( - attributeMap, - { num: { equals: { value: attributeMap.num } } }, - [options] - ) + validate(attributeMap, { listStr: { includes: ['Attenuation Corrected', 'Corrected'] } }, [ + options, + ]) ).toBeUndefined(); - }); - - it('returns error on not equals', () => { expect( - validate(attributeMap, { str: { equals: 'abc' } }, [options]) + validate(attributeMap, { listStr: { includes: ['Attenuation', 'Corrected'] } }, [options]) ).not.toBeUndefined(); expect( - validate( - attributeMap, - { num: { equals: { value: 1 + attributeMap.num } } }, - [options] - ) + validate(attributeMap, { str: { includes: ['Attenuation Corrected', 'Corrected'] } }, [ + options, + ]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { includes: ['Attenuation', 'Corrected'] } }, [options]) + ).not.toBeUndefined(); + expect(validate(attributeMap, { list: { includes: ['abc'] } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { list: { includes: ['GHI', 'HI'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { includes: ['HI', 'bye'] } }, [options]) ).not.toBeUndefined(); }); }); - - describe('greaterThan', () => { - it('returns undefined on greaterThan', () => { + describe('doesNotInclude', () => { + it('returns undefined if list does not includes', () => { + expect( + validate(attributeMap, { listStr: { doesNotInclude: 'Attenuation Corrected' } }, [options]) + ).not.toBeUndefined(); expect( validate( attributeMap, - { num: { greaterThan: { value: attributeMap.num - 1 } } }, + { + listStr: { doesNotInclude: ['Attenuation Corrected', 'Corrected'] }, + }, [options] ) - ).toBeUndefined(); + ).not.toBeUndefined(); expect( - validate(attributeMap, { num: { greaterThan: attributeMap.num - 1 } }, [ + validate(attributeMap, { listStr: { doesNotInclude: ['Attenuation', 'Corrected'] } }, [ options, ]) ).toBeUndefined(); - }); - - it('returns error on not greater than', () => { expect( validate( attributeMap, - { num: { greaterThan: { value: attributeMap.num } } }, + { str: { doesNotInclude: ['Attenuation Corrected', 'Corrected'] } }, [options] ) ).not.toBeUndefined(); expect( - validate(attributeMap, { num: { greaterThan: attributeMap.num } }, [ + validate(attributeMap, { str: { doesNotInclude: ['Attenuation', 'Corrected'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: ['Corr'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: 'abc' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: { value: ['abc'] } } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: { value: ['att', 'cor'] } } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotInclude: { value: ['abc', 'def', 'dog'] } } }, [ options, ]) ).not.toBeUndefined(); }); + }); + describe('containsI', () => { + it('returns match any list contains case insensitive', () => { + expect( + validate(attributeMap, { upper: { containsI: ['hi', 'pre'] } }, [options]) + ).not.toBeUndefined(); + expect(validate(attributeMap, { list: { containsI: 'hi' } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { list: { containsI: ['ghi', 'bye'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { containsI: ['bye', 'hi'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { containsI: ['ig', 'hi'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { upper: { containsI: ['bye', 'per'] } }, [options]) + ).toBeUndefined(); + }); + }); + describe('contains', () => { + it('returns match any list contains', () => { + expect(validate(attributeMap, { str: { contains: 'Corr' } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { str: { contains: { value: 'Corr' } } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { str: { contains: ['Corr'] } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { str: { contains: ['corr'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { contains: ['Att', 'Wall'] } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { list: { contains: 'GH' } }, [options])).toBeUndefined(); + expect(validate(attributeMap, { list: { contains: ['ab'] } }, [options])).toBeUndefined(); + + expect( + validate(attributeMap, { list: { contains: ['z', 'bc'] } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { list: { contains: ['z'] } }, [options])).not.toBeUndefined(); + }); + }); - it('returns error on undefined value', () => { + describe('doesNotContain', () => { + it('returns undefined if string does not contain specified value', () => { expect( - validate( - attributeMap, - { numUndefined: { greaterThan: { value: 3 } } }, - [options] - ) + validate(attributeMap, { str: { doesNotContain: ['att', 'wall'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: 'Corr' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: 'corr' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: { value: 'corr' } } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: ['att', 'cor'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: ['Att', 'cor'] } }, [options]) ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContain: ['bye', 'hi'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContain: ['GHI', 'hi'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContain: ['hi'] } }, [options]) + ).toBeUndefined(); + }); + }); + describe('doesNotContainI', () => { + it('returns undefined if string does not contain specified value', () => { + expect( + validate(attributeMap, { str: { doesNotContainI: 'corr' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: 'Corr' } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: ['att', 'cor'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: ['Att', 'wall'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { doesNotContainI: ['bye', 'hi'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContainI: ['bye', 'ABC'] } }, [options]) + ).not.toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContainI: 'bye' } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { doesNotContainI: ['bye', 'ABC'] } }, [options]) + ).not.toBeUndefined(); + }); + }); + describe('startsWith', () => { + it('returns undefined if string starts with specified value', () => { + expect( + validate(attributeMap, { str: { startsWith: { value: 'Atte' } } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { str: { startsWith: 'Att' } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { str: { startsWith: ['cat', 'dog', 'Att'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { str: { startsWith: ['cat', 'dog'] } }, [options]) + ).not.toBeUndefined(); + expect(validate(attributeMap, { list: { startsWith: ['GH'] } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { list: { startsWith: ['de', 'bye'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { startsWith: ['hi', 'bye'] } }, [options]) + ).not.toBeUndefined(); + }); + }); + describe('endsWith', () => { + it('returns undefined if string ends with specified value', () => { + expect(validate(attributeMap, { str: { endsWith: 'ted' } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { str: { endsWith: { value: 'ted' } } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { str: { endsWith: ['ted'] } }, [options])).toBeUndefined(); + expect(validate(attributeMap, { str: { endsWith: ['Att'] } }, [options])).not.toBeUndefined(); + expect( + validate(attributeMap, { str: { endsWith: ['cat', 'dog', 'ted'] } }, [options]) + ).toBeUndefined(); + expect(validate(attributeMap, { list: { endsWith: ['HI'] } }, [options])).toBeUndefined(); + expect( + validate(attributeMap, { list: { endsWith: ['bc', 'dog', 'ted'] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { list: { endsWith: ['bye', 'dog'] } }, [options]) + ).not.toBeUndefined(); + }); + }); + + describe('greaterThan', () => { + it('returns undefined on greaterThan', () => { + expect( + validate(attributeMap, { num: { greaterThan: { value: attributeMap.num - 1 } } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { greaterThan: attributeMap.num - 1 } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { greaterThan: [attributeMap.num - 1] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { greaterThan: [attributeMap.num + 1] } }, [options]) + ).not.toBeUndefined(); + }); + }); + describe('lessThan', () => { + it('returns undefined on lessThan', () => { + expect( + validate(attributeMap, { num: { lessThan: { value: attributeMap.num + 1 } } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { lessThan: attributeMap.num + 1 } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { lessThan: [attributeMap.num + 1] } }, [options]) + ).toBeUndefined(); + expect( + validate(attributeMap, { num: { lessThan: [attributeMap.num - 1] } }, [options]) + ).not.toBeUndefined(); + }); + }); + describe('range', () => { + it('returns undefined if the value is between', () => { + expect( + validate(attributeMap, { num: { range: [attributeMap.num + 1, attributeMap.num - 1] } }, [ + options, + ]) + ).toBeUndefined(); + expect(validate(attributeMap, { num: { range: [1, 4] } }, [options])).toBeUndefined(); + expect(validate(attributeMap, { num: { range: [1, 2] } }, [options])).not.toBeUndefined(); + expect(validate(attributeMap, { num: { range: [4, 5] } }, [options])).not.toBeUndefined(); + expect(validate(attributeMap, { num: { range: [5] } }, [options])).not.toBeUndefined(); + expect(validate(attributeMap, { num: { range: 5 } }, [options])).not.toBeUndefined(); }); }); }); diff --git a/platform/core/src/services/MeasurementService/MeasurementService.test.js b/platform/core/src/services/MeasurementService/MeasurementService.test.js index 78c13c8370..93517d58c2 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.test.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.test.js @@ -126,13 +126,7 @@ describe('MeasurementService.js', () => { it('throws Error if no matching criteria provided', () => { expect(() => { - measurementService.addMapping( - source, - annotationType, - null, - toSourceSchema, - toMeasurement - ); + measurementService.addMapping(source, annotationType, null, toSourceSchema, toMeasurement); }).toThrow(new Error('Matching criteria not provided.')); }); @@ -194,34 +188,16 @@ describe('MeasurementService.js', () => { toSourceSchema, toMeasurement ); - const measurementId = source.annotationToMeasurement( - annotationType, - annotation - ); - const mappedAnnotation = source.getAnnotation( - annotationType, - measurementId - ); + const measurementId = source.annotationToMeasurement(annotationType, annotation); + const mappedAnnotation = source.getAnnotation(annotationType, measurementId); expect(annotation).toBe(mappedAnnotation); }); it('get annotation based on source and annotationType', () => { - measurementService.addMapping( - source, - annotationType, - {}, - toSourceSchema, - toMeasurement - ); - const measurementId = source.annotationToMeasurement( - annotationType, - annotation - ); - const mappedAnnotation = source.getAnnotation( - annotationType, - measurementId - ); + measurementService.addMapping(source, annotationType, {}, toSourceSchema, toMeasurement); + const measurementId = source.annotationToMeasurement(annotationType, annotation); + const mappedAnnotation = source.getAnnotation(annotationType, measurementId); expect(annotation).toBe(mappedAnnotation); }); @@ -323,9 +299,7 @@ describe('MeasurementService.js', () => { /* Add new measurement */ source.annotationToMeasurement(annotationType, newMeasurement); - const savedMeasurement = measurementService.getMeasurement( - newMeasurement.uid - ); + const savedMeasurement = measurementService.getMeasurement(newMeasurement.uid); /* Clear dynamic data */ delete newMeasurement.modifiedTimestamp; @@ -384,10 +358,7 @@ describe('MeasurementService.js', () => { let addCallbackWasCalled = false; /* Subscribe to add event */ - measurementService.subscribe( - MEASUREMENT_ADDED, - () => (addCallbackWasCalled = true) - ); + measurementService.subscribe(MEASUREMENT_ADDED, () => (addCallbackWasCalled = true)); /* Add new measurement - two calls needed for the start and the other for the completed*/ const uid = source.annotationToMeasurement(annotationType, measurement); @@ -409,20 +380,13 @@ describe('MeasurementService.js', () => { let updateCallbackWasCalled = false; /* Subscribe to update event */ - measurementService.subscribe( - MEASUREMENT_UPDATED, - () => (updateCallbackWasCalled = true) - ); + measurementService.subscribe(MEASUREMENT_UPDATED, () => (updateCallbackWasCalled = true)); /* Create measurement */ const uid = source.annotationToMeasurement(annotationType, measurement); /* Update measurement */ - source.annotationToMeasurement( - annotationType, - { uid, ...measurement }, - true - ); + source.annotationToMeasurement(annotationType, { uid, ...measurement }, true); expect(updateCallbackWasCalled).toBe(true); }); @@ -468,10 +432,7 @@ describe('MeasurementService.js', () => { let addCallbackWasCalled = false; /* Subscribe to add event */ - measurementService.subscribe( - MEASUREMENT_ADDED, - () => (addCallbackWasCalled = true) - ); + measurementService.subscribe(MEASUREMENT_ADDED, () => (addCallbackWasCalled = true)); /* Add new measurement - two calls needed for the start and the other for the completed*/ // expect exceptions for unmapped measurements @@ -502,10 +463,7 @@ describe('MeasurementService.js', () => { let removeCallbackWasCalled = false; /* Subscribe to add event */ - measurementService.subscribe( - MEASUREMENT_REMOVED, - () => (removeCallbackWasCalled = true) - ); + measurementService.subscribe(MEASUREMENT_REMOVED, () => (removeCallbackWasCalled = true)); /* Add new measurement - two calls needed for the start and the other for the completed*/ // expect exceptions for unmapped measurements diff --git a/platform/core/src/services/MeasurementService/MeasurementService.ts b/platform/core/src/services/MeasurementService/MeasurementService.ts index bd1ac8b843..dde9ff4d21 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.ts +++ b/platform/core/src/services/MeasurementService/MeasurementService.ts @@ -146,9 +146,7 @@ class MeasurementService extends PubSubService { // check if valuetype is valid , and if values are strings if (!valueType || typeof valueType !== 'object') { - console.warn( - `MeasurementService: addValueType: invalid valueType: ${valueType}` - ); + console.warn(`MeasurementService: addValueType: invalid valueType: ${valueType}`); return; } @@ -178,10 +176,7 @@ class MeasurementService extends PubSubService { return this.measurements.get(measurementUID); } - public setMeasurementSelected( - measurementUID: string, - selected: boolean - ): void { + public setMeasurementSelected(measurementUID: string, selected: boolean): void { const measurement = this.getMeasurement(measurementUID); if (!measurement) { return; @@ -230,17 +225,8 @@ class MeasurementService extends PubSubService { version, }; - source.annotationToMeasurement = ( - annotationType, - annotation, - isUpdate = false - ) => { - return this.annotationToMeasurement( - source, - annotationType, - annotation, - isUpdate - ); + source.annotationToMeasurement = (annotationType, annotation, isUpdate = false) => { + return this.annotationToMeasurement(source, annotationType, annotation, isUpdate); }; source.remove = (measurementUID, eventDetails) => { @@ -281,13 +267,7 @@ class MeasurementService extends PubSubService { * @param {Function} toMeasurementSchema Mapping function to measurement schema * @return void */ - addMapping( - source, - annotationType, - matchingCriteria, - toAnnotationSchema, - toMeasurementSchema - ) { + addMapping(source, annotationType, matchingCriteria, toAnnotationSchema, toMeasurementSchema) { if (!this._isValidSource(source)) { throw new Error('Invalid source.'); } @@ -321,11 +301,7 @@ class MeasurementService extends PubSubService { this.mappings[source.uid] = [mapping]; } - log.info( - `New measurement mapping added to source '${this._getSourceToString( - source - )}'.` - ); + log.info(`New measurement mapping added to source '${this._getSourceToString(source)}'.`); } /** @@ -348,20 +324,13 @@ class MeasurementService extends PubSubService { } const measurement = this.getMeasurement(measurementUID); - const mapping = this._getMappingByMeasurementSource( - measurement, - annotationType - ); + const mapping = this._getMappingByMeasurementSource(measurement, annotationType); if (mapping) { return mapping.toAnnotationSchema(measurement, annotationType); } - const matchingMapping = this._getMatchingMapping( - source, - annotationType, - measurement - ); + const matchingMapping = this._getMatchingMapping(source, annotationType, measurement); if (matchingMapping) { log.info('Matching mapping found:', matchingMapping); @@ -380,10 +349,7 @@ class MeasurementService extends PubSubService { modifiedTimestamp: Math.floor(Date.now() / 1000), }; - log.info( - `Updating internal measurement representation...`, - updatedMeasurement - ); + log.info(`Updating internal measurement representation...`, updatedMeasurement); this.measurements.set(measurementUID, updatedMeasurement); @@ -405,13 +371,7 @@ class MeasurementService extends PubSubService { * @param {object} data The data you wish to add to the source. * @param {function} toMeasurementSchema A function to get the `data` into the same shape as the source annotationType. */ - addRawMeasurement( - source, - annotationType, - data, - toMeasurementSchema, - dataSource = {} - ) { + addRawMeasurement(source, annotationType, data, toMeasurementSchema, dataSource = {}) { if (!this._isValidSource(source)) { log.warn('Invalid source. Exiting early.'); return; @@ -425,9 +385,7 @@ class MeasurementService extends PubSubService { } if (!this._sourceHasMappings(source)) { - log.warn( - `No measurement mappings found for '${sourceInfo}' source. Exiting early.` - ); + log.warn(`No measurement mappings found for '${sourceInfo}' source. Exiting early.`); return; } @@ -484,7 +442,7 @@ class MeasurementService extends PubSubService { }); } - return newMeasurement.id; + return newMeasurement.uid; } /** @@ -496,12 +454,7 @@ class MeasurementService extends PubSubService { * @param {boolean} isUpdate is this an update or an add/completed instead? * @return {string} A measurement uid */ - annotationToMeasurement( - source, - annotationType, - sourceAnnotationDetail, - isUpdate = false - ) { + annotationToMeasurement(source, annotationType, sourceAnnotationDetail, isUpdate = false) { if (!this._isValidSource(source)) { throw new Error('Invalid source.'); } @@ -512,9 +465,7 @@ class MeasurementService extends PubSubService { const sourceInfo = this._getSourceToString(source); if (!this._sourceHasMappings(source)) { - throw new Error( - `No measurement mappings found for '${sourceInfo}' source. Exiting early.` - ); + throw new Error(`No measurement mappings found for '${sourceInfo}' source. Exiting early.`); } let measurement = {}; @@ -566,7 +517,7 @@ class MeasurementService extends PubSubService { }; if (oldMeasurement) { - // TODO: Ultimately, each annotation should have a selected flag right from the soure. + // TODO: Ultimately, each annotation should have a selected flag right from the source. // For now, it is just added in OHIF here and in setMeasurementSelected. this.measurements.set(internalUID, newMeasurement); if (isUpdate) { @@ -599,8 +550,7 @@ class MeasurementService extends PubSubService { remove(measurementUID, source, eventDetails) { if ( !measurementUID || - (!this.measurements.has(measurementUID) && - !this.unmappedMeasurements.has(measurementUID)) + (!this.measurements.has(measurementUID) && !this.unmappedMeasurements.has(measurementUID)) ) { log.warn(`No uid provided, or unable to find measurement by uid.`); return; @@ -649,10 +599,7 @@ class MeasurementService extends PubSubService { * merely causes it to fire the event with the isConsumed set to true. */ - public jumpToMeasurement( - viewportIndex: number, - measurementUID: string - ): void { + public jumpToMeasurement(viewportId: string, measurementUID: string): void { const measurement = this.measurements.get(measurementUID); if (!measurement) { @@ -660,7 +607,7 @@ class MeasurementService extends PubSubService { return; } const consumableEvent = this.createConsumableEvent({ - viewportIndex, + viewportId, measurement, }); @@ -682,9 +629,7 @@ class MeasurementService extends PubSubService { _getMappingByMeasurementSource(measurement, annotationType) { if (this._isValidSource(measurement.source)) { - return this.mappings[measurement.source.uid].find( - m => m.annotationType === annotationType - ); + return this.mappings[measurement.source.uid].find(m => m.annotationType === annotationType); } } @@ -705,10 +650,7 @@ class MeasurementService extends PubSubService { /* Criteria Matching */ return sourceMappingsByDefinition.find(({ matchingCriteria }) => { - return ( - measurement.points && - measurement.points.length === matchingCriteria.points - ); + return measurement.points && measurement.points.length === matchingCriteria.points; }); } @@ -739,10 +681,7 @@ class MeasurementService extends PubSubService { * @return {boolean} Validation if source has mappings */ _sourceHasMappings(source) { - return ( - Array.isArray(this.mappings[source.uid]) && - this.mappings[source.uid].length - ); + return Array.isArray(this.mappings[source.uid]) && this.mappings[source.uid].length; } /** diff --git a/platform/core/src/services/ServicesManager.test.js b/platform/core/src/services/ServicesManager.test.js index 30b115975b..1a4337034e 100644 --- a/platform/core/src/services/ServicesManager.test.js +++ b/platform/core/src/services/ServicesManager.test.js @@ -38,9 +38,7 @@ describe('ServicesManager', () => { [{ name: 'UIModalTestService', create: jest.fn() }, fakeConfiguration], ]); - expect(servicesManager.registerService.mock.calls[1][1]).toEqual( - fakeConfiguration - ); + expect(servicesManager.registerService.mock.calls[1][1]).toEqual(fakeConfiguration); }); }); @@ -78,9 +76,7 @@ describe('ServicesManager', () => { it('tracks which services have been registered', () => { servicesManager.registerService(fakeService); - expect(servicesManager.registeredServiceNames).toContain( - fakeService.name - ); + expect(servicesManager.registeredServiceNames).toContain(fakeService.name); }); it('logs a warning if the service has an name that has already been registered', () => { @@ -95,9 +91,7 @@ describe('ServicesManager', () => { servicesManager.registerService(fakeService, configuration); - expect(fakeService.create.mock.calls[0][0].configuration.config).toBe( - configuration.config - ); + expect(fakeService.create.mock.calls[0][0].configuration.config).toBe(configuration.config); }); }); }); diff --git a/platform/core/src/services/ServicesManager.ts b/platform/core/src/services/ServicesManager.ts index 3d2843dbf9..445e5aec39 100644 --- a/platform/core/src/services/ServicesManager.ts +++ b/platform/core/src/services/ServicesManager.ts @@ -19,9 +19,7 @@ export default class ServicesManager { */ registerService(service, configuration = {}) { if (!service) { - log.warn( - 'Attempting to register a null/undefined service. Exiting early.' - ); + log.warn('Attempting to register a null/undefined service. Exiting early.'); return; } diff --git a/platform/core/src/services/StateSyncService/StateSyncService.ts b/platform/core/src/services/StateSyncService/StateSyncService.ts index 5a28bc0416..ef0d6a3624 100644 --- a/platform/core/src/services/StateSyncService/StateSyncService.ts +++ b/platform/core/src/services/StateSyncService/StateSyncService.ts @@ -6,7 +6,7 @@ const EVENTS = {}; type Obj = Record; type StateConfig = { - /** clearOnModeExit defines state configuraion that is cleared automatically on + /** clearOnModeExit defines state configuration that is cleared automatically on * exiting a mode. This clearing occurs after the mode onModeExit, * so it is possible to preserve desired state during exit to be restored * later. @@ -40,7 +40,7 @@ export default class StateSyncService extends PubSubService { this.configuration = configuration || {}; } - public init(extensionManager: ExtensionManager): void { } + public init(extensionManager: ExtensionManager): void {} /** Registers a new sync store called `id`. The state * defines how the state is stored, and any default clearing of the diff --git a/platform/core/src/services/ToolBarService/ToolbarService.ts b/platform/core/src/services/ToolBarService/ToolbarService.ts index 4a3cf2429e..919ee94635 100644 --- a/platform/core/src/services/ToolBarService/ToolbarService.ts +++ b/platform/core/src/services/ToolBarService/ToolbarService.ts @@ -66,7 +66,9 @@ export default class ToolbarService extends PubSubService { * called with {...commandOptions,...options} */ recordInteraction(interaction, options?: Record) { - if (!interaction) return; + if (!interaction) { + return; + } const commandsManager = this._commandsManager; const { groupId, itemId, interactionType, commands } = interaction; @@ -88,14 +90,14 @@ export default class ToolbarService extends PubSubService { } case 'tool': { try { - commands.forEach( - ({ commandName = 'setToolActive', commandOptions, context }) => { - commandsManager.runCommand(commandName, commandOptions, context); - } - ); - - // only set the primary tool if no error was thrown - this.state.primaryToolId = itemId; + commands.forEach(({ commandName = 'setToolActive', commandOptions, context }) => { + commandsManager.runCommand(commandName, commandOptions, context); + }); + + // only set the primary tool if no error was thrown. + // if the itemId is not undefined use it; otherwise, set the first tool in + // the commands as the primary tool + this.state.primaryToolId = itemId || commands[0].commandOptions?.toolName; } catch (error) { console.warn(error); } @@ -108,9 +110,7 @@ export default class ToolbarService extends PubSubService { // only toggle if a command was executed this.state.toggles[itemId] = - this.state.toggles[itemId] === undefined - ? true - : !this.state.toggles[itemId]; + this.state.toggles[itemId] === undefined ? true : !this.state.toggles[itemId]; if (!commands) { break; @@ -154,7 +154,7 @@ export default class ToolbarService extends PubSubService { // unsubscribe = commandsManager.runCommand(commandName, commandOptions); // } - // // Storing the unsubscribe for later reseting + // // Storing the unsubscribe for later resetting // if (unsubscribe && typeof unsubscribe === 'function') { // if (this.unsubscriptions.indexOf(unsubscribe) === -1) { // this.unsubscriptions.push(unsubscribe); @@ -166,7 +166,7 @@ export default class ToolbarService extends PubSubService { this.state.groups[groupId] = itemId; } - this._broadcastEvent(this.EVENTS.TOOL_BAR_STATE_MODIFIED, {}); + this._broadcastEvent(this.EVENTS.TOOL_BAR_STATE_MODIFIED, { ...this.state }); } getButtons() { @@ -211,14 +211,9 @@ export default class ToolbarService extends PubSubService { _buttonTypes() { const buttonTypes = {}; - const registeredToolbarModules = this.extensionManager.modules[ - 'toolbarModule' - ]; - - if ( - Array.isArray(registeredToolbarModules) && - registeredToolbarModules.length - ) { + const registeredToolbarModules = this.extensionManager.modules['toolbarModule']; + + if (Array.isArray(registeredToolbarModules) && registeredToolbarModules.length) { registeredToolbarModules.forEach(toolbarModule => toolbarModule.module.forEach(def => { buttonTypes[def.name] = def; @@ -272,11 +267,26 @@ export default class ToolbarService extends PubSubService { if (!this.buttons[button.id]) { this.buttons[button.id] = button; } + this._setTogglesForButtonItems(button.props?.items); }); this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {}); } + _setTogglesForButtonItems(buttonItems) { + if (!buttonItems) { + return; + } + + buttonItems.forEach(buttonItem => { + if (buttonItem.type === 'toggle') { + this.state.toggles[buttonItem.id] = buttonItem.isActive; + } else { + this._setTogglesForButtonItems(buttonItem.props?.items); + } + }); + } + /** * * @param {*} btn @@ -285,6 +295,10 @@ export default class ToolbarService extends PubSubService { * @param {*} props - Props set by the Viewer layer */ _mapButtonToDisplay(btn, btnSection, metadata, props) { + if (!btn) { + return; + } + const { id, type, component } = btn; const buttonType = this._buttonTypes()[type]; @@ -300,8 +314,6 @@ export default class ToolbarService extends PubSubService { } getButtonComponentForUIType(uiType: string) { - return uiType - ? this._buttonTypes()[uiType]?.defaultComponent ?? null - : null; + return uiType ? this._buttonTypes()[uiType]?.defaultComponent ?? null : null; } } diff --git a/platform/core/src/services/UIModalService/index.ts b/platform/core/src/services/UIModalService/index.ts index 3b33d38068..865a2f4b46 100644 --- a/platform/core/src/services/UIModalService/index.ts +++ b/platform/core/src/services/UIModalService/index.ts @@ -71,10 +71,7 @@ class UIModalService { * show: showImplementation, * } */ - setServiceImplementation({ - hide: hideImplementation, - show: showImplementation, - }) { + setServiceImplementation({ hide: hideImplementation, show: showImplementation }) { if (hideImplementation) { serviceImplementation._hide = hideImplementation; } diff --git a/platform/core/src/services/UINotificationService/index.ts b/platform/core/src/services/UINotificationService/index.ts index 78a2882afd..b49f46fa7d 100644 --- a/platform/core/src/services/UINotificationService/index.ts +++ b/platform/core/src/services/UINotificationService/index.ts @@ -38,10 +38,7 @@ class UINotificationService { * show: showImplementation, * } */ - public setServiceImplementation({ - hide: hideImplementation, - show: showImplementation, - }): void { + public setServiceImplementation({ hide: hideImplementation, show: showImplementation }): void { if (hideImplementation) { serviceImplementation._hide = hideImplementation; } diff --git a/platform/core/src/services/UIViewportDialogService/index.js b/platform/core/src/services/UIViewportDialogService/index.js index 770fccc05f..5c5053648f 100644 --- a/platform/core/src/services/UIViewportDialogService/index.js +++ b/platform/core/src/services/UIViewportDialogService/index.js @@ -4,7 +4,7 @@ * @typedef {Object} ViewportDialogProps * @property {ReactElement|HTMLElement} [content=null] Modal content. * @property {Object} [contentProps=null] Modal content props. - * @property {boolean} [viewportIndex=false] Modal is dismissible via the esc key. + * @property {boolean} [viewportId=false] Modal is dismissible via the esc key. */ const name = 'uiViewportDialogService'; @@ -22,21 +22,13 @@ const serviceImplementation = { }; /** - * Show a new UI viewport dialog on the specified viewportIndex; + * Show a new UI viewport dialog on the specified viewportId; * - * @param {ViewportDialogProps} props { content, contentProps, viewportIndex } + * @param {ViewportDialogProps} props { content, contentProps, viewportId } */ -function _show({ - viewportIndex, - id, - type, - message, - actions, - onSubmit, - onOutsideClick, -}) { +function _show({ viewportId, id, type, message, actions, onSubmit, onOutsideClick }) { return serviceImplementation._show({ - viewportIndex, + viewportId, id, type, message, @@ -59,13 +51,10 @@ function _hide() { * @param {*} { * hide: hideImplementation, * show: showImplementation, - * viewportIndex, + * viewportId, * } */ -function setServiceImplementation({ - hide: hideImplementation, - show: showImplementation, -}) { +function setServiceImplementation({ hide: hideImplementation, show: showImplementation }) { if (hideImplementation) { serviceImplementation._hide = hideImplementation; } diff --git a/platform/core/src/services/UserAuthenticationService/UserAuthenticationService.js b/platform/core/src/services/UserAuthenticationService/UserAuthenticationService.js index e112542e8a..5c1ec921b6 100644 --- a/platform/core/src/services/UserAuthenticationService/UserAuthenticationService.js +++ b/platform/core/src/services/UserAuthenticationService/UserAuthenticationService.js @@ -18,8 +18,7 @@ const serviceImplementation = { _getUser: () => console.warn('_setUser() NOT IMPLEMENTED'), _getAuthorizationHeader: () => {}, // TODO: have enabled/disabled state? //console.warn('_getAuthorizationHeader() NOT IMPLEMENTED'), - _handleUnauthenticated: () => - console.warn('_handleUnauthenticated() NOT IMPLEMENTED'), + _handleUnauthenticated: () => console.warn('_handleUnauthenticated() NOT IMPLEMENTED'), _reset: () => console.warn('reset() NOT IMPLEMENTED'), _set: () => console.warn('set() NOT IMPLEMENTED'), }; diff --git a/platform/core/src/services/ViewportGridService/ViewportGridService.ts b/platform/core/src/services/ViewportGridService/ViewportGridService.ts index a5fca6e6a9..ee430ccacb 100644 --- a/platform/core/src/services/ViewportGridService/ViewportGridService.ts +++ b/platform/core/src/services/ViewportGridService/ViewportGridService.ts @@ -3,7 +3,7 @@ import { getPresentationIds, PresentationIds } from './getPresentationIds'; class ViewportGridService extends PubSubService { public static readonly EVENTS = { - ACTIVE_VIEWPORT_INDEX_CHANGED: 'event::activeviewportindexchanged', + ACTIVE_VIEWPORT_ID_CHANGED: 'event::activeviewportidchanged', LAYOUT_CHANGED: 'event::layoutChanged', GRID_STATE_CHANGED: 'event::gridStateChanged', }; @@ -27,7 +27,7 @@ class ViewportGridService extends PubSubService { public setServiceImplementation({ getState: getStateImplementation, - setActiveViewportIndex: setActiveViewportIndexImplementation, + setActiveViewportId: setActiveViewportIdImplementation, setDisplaySetsForViewports: setDisplaySetsForViewportsImplementation, setLayout: setLayoutImplementation, reset: resetImplementation, @@ -38,9 +38,8 @@ class ViewportGridService extends PubSubService { if (getStateImplementation) { this.serviceImplementation._getState = getStateImplementation; } - if (setActiveViewportIndexImplementation) { - this.serviceImplementation._setActiveViewportIndex = - setActiveViewportIndexImplementation; + if (setActiveViewportIdImplementation) { + this.serviceImplementation._setActiveViewport = setActiveViewportIdImplementation; } if (setDisplaySetsForViewportsImplementation) { this.serviceImplementation._setDisplaySetsForViewports = @@ -59,18 +58,15 @@ class ViewportGridService extends PubSubService { this.serviceImplementation._set = setImplementation; } if (getNumViewportPanesImplementation) { - this.serviceImplementation._getNumViewportPanes = - getNumViewportPanesImplementation; + this.serviceImplementation._getNumViewportPanes = getNumViewportPanesImplementation; } } - public setActiveViewportIndex(index) { - this.serviceImplementation._setActiveViewportIndex(index); + public setActiveViewportId(id: string) { + this.serviceImplementation._setActiveViewport(id); const state = this.getState(); - const viewportId = state.viewports[index]?.viewportOptions?.viewportId; - this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED, { - viewportIndex: index, - viewportId, + this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, { + viewportId: id, }); } @@ -78,6 +74,11 @@ class ViewportGridService extends PubSubService { return this.serviceImplementation._getState(); } + public getActiveViewportId() { + const state = this.getState(); + return state.activeViewportId; + } + public setDisplaySetsForViewport(props) { // Just update a single viewport, but use the multi-viewport update for it. this.setDisplaySetsForViewports([props]); @@ -89,14 +90,11 @@ class ViewportGridService extends PubSubService { const viewports = []; for (const viewport of props) { - const updatedViewport = state.viewports[viewport.viewportIndex]; + const updatedViewport = state.viewports.get(viewport.viewportId); if (updatedViewport) { viewports.push(updatedViewport); } else { - console.warn( - "ViewportGridService::Didn't find updated viewport", - viewport - ); + console.warn("ViewportGridService::Didn't find updated viewport", viewport); } } this._broadcastEvent(ViewportGridService.EVENTS.GRID_STATE_CHANGED, { @@ -118,7 +116,7 @@ class ViewportGridService extends PubSubService { numRows, layoutOptions, layoutType = 'grid', - activeViewportIndex = undefined, + activeViewportId = undefined, findOrCreateViewport = undefined, }) { this.serviceImplementation._setLayout({ @@ -126,7 +124,7 @@ class ViewportGridService extends PubSubService { numRows, layoutOptions, layoutType, - activeViewportIndex, + activeViewportId, findOrCreateViewport, }); this._broadcastEvent(this.EVENTS.LAYOUT_CHANGED, { @@ -160,8 +158,10 @@ class ViewportGridService extends PubSubService { return this.serviceImplementation._getNumViewportPanes(); } - public getLayoutOptionsFromState(state) { - return state.viewports.map(viewport => { + public getLayoutOptionsFromState( + state: any + ): { x: number; y: number; width: number; height: number }[] { + return Array.from(state.viewports.entries()).map(([_, viewport]) => { return { x: viewport.x, y: viewport.y, diff --git a/platform/core/src/services/ViewportGridService/getPresentationIds.ts b/platform/core/src/services/ViewportGridService/getPresentationIds.ts index 4299175115..47771c300a 100644 --- a/platform/core/src/services/ViewportGridService/getPresentationIds.ts +++ b/platform/core/src/services/ViewportGridService/getPresentationIds.ts @@ -10,8 +10,14 @@ const DEFAULT = 'default'; // dragged and dropped the view in twice. For example, it allows displaying // bone, brain and soft tissue views of a single display set, and to still // remember the specific changes to each viewport. -const addUniqueIndex = (arr, key, viewports) => { +const addUniqueIndex = (arr, key, viewports, isUpdatingSameViewport) => { arr.push(0); + + // If we are updating the viewport, we should not increment the index + if (isUpdatingSameViewport) { + return; + } + // The 128 is just a value that is larger than how many viewports we // display at once, used as an upper bound on how many unique presentation // ID's might exist for a single display set at once. @@ -19,7 +25,7 @@ const addUniqueIndex = (arr, key, viewports) => { arr[arr.length - 1] = displayInstance; const testId = arr.join(JOIN_STR); if ( - !viewports.find( + !Array.from(viewports.values()).find( viewport => viewport.viewportOptions?.presentationIds?.[key] === testId ) ) { @@ -29,10 +35,16 @@ const addUniqueIndex = (arr, key, viewports) => { }; const getLutId = (ds): string => { - if (!ds || !ds.options) return DEFAULT; - if (ds.options.id) return ds.options.id; + if (!ds || !ds.options) { + return DEFAULT; + } + if (ds.options.id) { + return ds.options.id; + } const arr = Object.entries(ds.options).map(([key, val]) => `${key}=${val}`); - if (!arr.length) return DEFAULT; + if (!arr.length) { + return DEFAULT; + } return arr.join(JOIN_STR); }; @@ -87,12 +99,10 @@ export type PresentationIds = { * @returns PresentationIds */ const getPresentationIds = (viewport, viewports): PresentationIds => { - if (!viewport) return; - const { - viewportOptions, - displaySetInstanceUIDs, - displaySetOptions, - } = viewport; + if (!viewport) { + return; + } + const { viewportOptions, displaySetInstanceUIDs, displaySetOptions } = viewport; if (!viewportOptions || !displaySetInstanceUIDs?.length) { return; } @@ -102,15 +112,33 @@ const getPresentationIds = (viewport, viewports): PresentationIds => { const lutPresentationArr = [lutId]; const positionPresentationArr = [orientation || 'acquisition']; - if (id) positionPresentationArr.push(id); + if (id) { + positionPresentationArr.push(id); + } for (const uid of displaySetInstanceUIDs) { positionPresentationArr.push(uid); lutPresentationArr.push(uid); } - addUniqueIndex(positionPresentationArr, 'positionPresentationId', viewports); - addUniqueIndex(lutPresentationArr, 'lutPresentationId', viewports); + // only add unique index if the viewport is getting inserted and not updated + const isUpdatingSameViewport = Array.from(viewports.values()).some(v => { + return ( + v.displaySetInstanceUIDs.toString() === viewport.displaySetInstanceUIDs.toString() && + v.viewportId === viewport.viewportId + ); + }); + + // if it is updating the viewport we should not increment the index since + // it might be a layer on the fusion or a SEG layer that is added on + // top of the original display set + addUniqueIndex( + positionPresentationArr, + 'positionPresentationId', + viewports, + isUpdatingSameViewport + ); + addUniqueIndex(lutPresentationArr, 'lutPresentationId', viewports, isUpdatingSameViewport); const lutPresentationId = lutPresentationArr.join(JOIN_STR); const positionPresentationId = positionPresentationArr.join(JOIN_STR); diff --git a/platform/core/src/services/_shared/pubSubServiceInterface.js b/platform/core/src/services/_shared/pubSubServiceInterface.ts similarity index 99% rename from platform/core/src/services/_shared/pubSubServiceInterface.js rename to platform/core/src/services/_shared/pubSubServiceInterface.ts index 7d4460fda2..15bf7a4765 100644 --- a/platform/core/src/services/_shared/pubSubServiceInterface.js +++ b/platform/core/src/services/_shared/pubSubServiceInterface.ts @@ -117,6 +117,6 @@ export class PubSubService { consume: function Consume() { this.isConsumed = true; }, - } + }; } } diff --git a/platform/core/src/services/index.ts b/platform/core/src/services/index.ts index b34e028d8e..ab127fb974 100644 --- a/platform/core/src/services/index.ts +++ b/platform/core/src/services/index.ts @@ -10,9 +10,7 @@ import ToolbarService from './ToolBarService'; import ViewportGridService from './ViewportGridService'; import CineService from './CineService'; import HangingProtocolService from './HangingProtocolService'; -import pubSubServiceInterface, { - PubSubService, -} from './_shared/pubSubServiceInterface'; +import pubSubServiceInterface, { PubSubService } from './_shared/pubSubServiceInterface'; import UserAuthenticationService from './UserAuthenticationService'; import CustomizationService from './CustomizationService'; diff --git a/platform/core/src/string.js b/platform/core/src/string.js index b9ba46052f..0c841a9099 100644 --- a/platform/core/src/string.js +++ b/platform/core/src/string.js @@ -1,8 +1,5 @@ function isObject(subject) { - return ( - subject instanceof Object || - (typeof subject === 'object' && subject !== null) - ); + return subject instanceof Object || (typeof subject === 'object' && subject !== null); } function isString(subject) { diff --git a/platform/core/src/types/DataSource.ts b/platform/core/src/types/DataSource.ts new file mode 100644 index 0000000000..cc27e16302 --- /dev/null +++ b/platform/core/src/types/DataSource.ts @@ -0,0 +1,7 @@ +export type DataSourceDefinition = { + // TODO friendlyName to move to configuration; here now for legacy purposes + friendlyName: string; + namespace: string; + sourceName: string; + configuration: any; +}; diff --git a/platform/core/src/types/DataSourceConfigurationAPI.ts b/platform/core/src/types/DataSourceConfigurationAPI.ts new file mode 100644 index 0000000000..146f4a2851 --- /dev/null +++ b/platform/core/src/types/DataSourceConfigurationAPI.ts @@ -0,0 +1,68 @@ +export interface BaseDataSourceConfigurationAPIItem { + id: string; + name: string; +} + +/** + * The interface to use to configure an associated data source. Typically an + * instance of this interface is associated with a data source that the instance + * understands and can alter the data source's configuration. + */ +export interface BaseDataSourceConfigurationAPI { + /** + * Gets the i18n labels (i.e. the i18n lookup keys) for each of the configurable items + * of the data source configuration API. + * For example, for the Google Cloud Healthcare API, this would be + * ['Project', 'Location', 'Data set', 'DICOM store']. + * Besides the configurable item labels themselves, several other string look ups + * are used base on EACH of the labels returned by this method. + * For instance, for the label {itemLabel}, the following strings are fetched for + * translation... + * 1. `No {itemLabel} available` + * - used to indicate no such items are available + * - for example, for Google, `No Project available` would be 'No projects available' + * 2. `Select {itemLabel}` + * - used to direct selection of the item + * - for example, for Google, `Select Project` would be 'Select a project' + * 3. `Error fetching {itemLabel} list` + * - used to indicate an error occurred fetching the list of items + * - usually accompanied by the error itself + * - for example, for Google, `Error fetching Project list` would be 'Error fetching projects' + * 4. `Search {itemLabel} list` + * - used as the placeholder text for filtering a list of items + * - for example, for Google, `Search Project list` would be 'Search projects' + */ + getItemLabels(): Array; + + /** + * Initializes the data source configuration API and returns the top-level sub-items + * that can be chosen to begin the process of configuring the data source. + * For example, for the Google Cloud Healthcare API, this would perform the initial request + * to fetch the top level projects for the logged in user account. + */ + initialize(): Promise>; + + /** + * Sets the current path item and returns the sub-items of that item + * that can be further chosen to configure a data source. + * When setting the last configurable item of the data source (path), this method + * returns an empty list AND configures the active data source with the selected + * items path. + * For example, for the Google Cloud Healthcare API, this would take the current item + * (say a data set) and queries and returns its sub-items (i.e. all of the DICOM stores + * contained in that data set). Furthermore, whenever the item to set is a DICOM store, + * the Google Cloud Healthcare API implementation would update the OHIF data source + * associated with this instance to point to that DICOM store. + * @param item the item to set as current + */ + setCurrentItem( + item: BaseDataSourceConfigurationAPIItem + ): Promise>; + + /** + * Gets the list of items currently configured for the data source associated with + * this API instance. The resultant array must be the same length as the result of + * `getItemLabels`. + */ + getConfiguredItems(): Promise>; +} diff --git a/platform/core/src/types/DisplaySet.ts b/platform/core/src/types/DisplaySet.ts new file mode 100644 index 0000000000..3bc1cb5167 --- /dev/null +++ b/platform/core/src/types/DisplaySet.ts @@ -0,0 +1,15 @@ +import { InstanceMetadata } from './StudyMetadata'; + +export type DisplaySet = { + displaySetInstanceUID: string; + instances: InstanceMetadata[]; + StudyInstanceUID: string; + SeriesInstanceUID?: string; + numImages?: number; + unsupported?: boolean; +}; + +export type DisplaySetSeriesMetadataInvalidatedEvent = { + displaySetInstanceUID: string; + invalidateData: boolean; +}; diff --git a/platform/core/src/types/HangingProtocol.ts b/platform/core/src/types/HangingProtocol.ts index fec29f4896..797028f381 100644 --- a/platform/core/src/types/HangingProtocol.ts +++ b/platform/core/src/types/HangingProtocol.ts @@ -61,7 +61,7 @@ export type SetProtocolOptions = { export type HangingProtocolMatchDetails = { displaySetMatchDetails: Map; - viewportMatchDetails: Map; + viewportMatchDetails: Map; }; export type ConstraintValue = @@ -257,12 +257,14 @@ export type ProtocolNotifications = { * It is a set of rules about when the protocol can be applied at all, * as well as a set of stages that represent indivividual views. * Additionally, the display set selectors are used to choose from the existing - * display sets. The hanging protcol definition here does NOT allow + * display sets. The hanging protocol definition here does NOT allow * redefining the display sets to use, but only selects the views to show. */ export type Protocol = { // Mandatory id: string; + /** A description of this protocol. Used as a tool tip for the user. */ + description?: string; /** Maps ids to display set selectors to choose display sets */ displaySetSelectors: Record; /** A default viewport to use for any stage to select new viewport layouts. */ @@ -270,7 +272,6 @@ export type Protocol = { stages: ProtocolStage[]; // Optional locked?: boolean; - hasUpdatedPriorsInformation?: boolean; name?: string; createdDate?: string; modifiedDate?: string; @@ -284,7 +285,9 @@ export type Protocol = { /* The number of priors required for this hanging protocol. * -1 means that NO priors are referenced, and thus this HP matches * only the active study, whereas 0 means that an unknown number of - * priors is matched. + * priors is matched. Positive values mean at least that many priors are + * required. + * Replaces hasUpdatedPriors */ numberOfPriorsReferenced?: number; syncDataForViewports?: boolean; @@ -295,10 +298,7 @@ export type Protocol = { * to the GUI when this is used, and it can be expensive to apply. * Alternatives include using the custom attributes where possible. */ -export type ProtocolGenerator = ({ - servicesManager: any, - commandsManager: any, -}) => { +export type ProtocolGenerator = ({ servicesManager: any, commandsManager: any }) => { protocol: Protocol; }; diff --git a/platform/core/src/types/IPubSub.ts b/platform/core/src/types/IPubSub.ts index 06e918374d..733c98146e 100644 --- a/platform/core/src/types/IPubSub.ts +++ b/platform/core/src/types/IPubSub.ts @@ -2,10 +2,7 @@ import Consumer from './Consumer'; export default interface IPubSub { subscribe: (eventName: string, callback: Consumer) => void; - _broadcastEvent: ( - eventName: string, - callbackProps: Record - ) => void; + _broadcastEvent: (eventName: string, callbackProps: Record) => void; _unsubscribe: (eventName: string, listenerId: string) => void; _isValidEvent: (eventName: string) => boolean; } diff --git a/platform/core/src/types/PanelModule.ts b/platform/core/src/types/PanelModule.ts index fe3e501390..eb9d13e432 100644 --- a/platform/core/src/types/PanelModule.ts +++ b/platform/core/src/types/PanelModule.ts @@ -1,4 +1,4 @@ -import { PubSubService } from "../services"; +import { PubSubService } from '../services'; type Panel = { id?: string; @@ -11,8 +11,8 @@ type Panel = { type ActivatePanelTriggers = { sourcePubSubService: PubSubService; - sourceEvents: string[] -} + sourceEvents: string[]; +}; interface PanelEvent { panelId: string; @@ -22,9 +22,4 @@ interface ActivatePanelEvent extends PanelEvent { forceActive: boolean; } -export type { - ActivatePanelEvent, - ActivatePanelTriggers, - Panel, - PanelEvent, -}; +export type { ActivatePanelEvent, ActivatePanelTriggers, Panel, PanelEvent }; diff --git a/platform/core/src/types/Services.ts b/platform/core/src/types/Services.ts index abc6b09bea..ece05c32a9 100644 --- a/platform/core/src/types/Services.ts +++ b/platform/core/src/types/Services.ts @@ -14,23 +14,23 @@ import { * The interface for the services object */ export default interface Services { - userAuthenticationService?: Record; hangingProtocolService?: HangingProtocolService; customizationService?: CustomizationService; measurementService?: MeasurementService; displaySetService?: DisplaySetService; - cineService?: Record; toolbarService?: ToolbarService; - cornerstoneViewportService?: Record; - uiDialogService?: Record; - toolGroupService?: Record; - uiNotificationService?: UINotificationService; - uiModalService?: UIModalService; - uiViewportDialogService?: Record; viewportGridService?: ViewportGridService; - syncGroupService?: Record; - cornerstoneCacheService?: Record; - segmentationService?: Record; + uiModalService?: UIModalService; + uiNotificationService?: UINotificationService; stateSyncService?: StateSyncService; - panelService?: Record; + cineService?: unknown; + userAuthenticationService?: unknown; + cornerstoneViewportService?: unknown; + uiDialogService?: unknown; + toolGroupService?: unknown; + uiViewportDialogService?: unknown; + syncGroupService?: unknown; + cornerstoneCacheService?: unknown; + segmentationService?: unknown; + panelService?: unknown; } diff --git a/platform/core/src/types/index.ts b/platform/core/src/types/index.ts index 3c68b60c42..d2a39b83b9 100644 --- a/platform/core/src/types/index.ts +++ b/platform/core/src/types/index.ts @@ -2,13 +2,18 @@ import * as Extensions from '../extensions/ExtensionManager'; import * as HangingProtocol from './HangingProtocol'; import Services from './Services'; import Hotkey from '../classes/Hotkey'; -import { DisplaySet } from '../services/DisplaySetService/DisplaySetService'; +import { DataSourceDefinition } from './DataSource'; +import { + BaseDataSourceConfigurationAPI, + BaseDataSourceConfigurationAPIItem, +} from './DataSourceConfigurationAPI'; export * from '../services/CustomizationService/types'; // Separate out some generic types export * from './AppConfig'; export * from './Consumer'; export * from './Command'; +export * from './DisplaySet'; export * from './StudyMetadata'; export * from './PanelModule'; export * from './IPubSub'; @@ -18,4 +23,12 @@ export * from './Color'; * Export the types used within the various services and managers, but * not the services/managers themselves, which are exported at the top level. */ -export { Extensions, HangingProtocol, Services, Hotkey, DisplaySet }; +export { + Extensions, + HangingProtocol, + Services, + Hotkey, + DataSourceDefinition, + BaseDataSourceConfigurationAPI, + BaseDataSourceConfigurationAPIItem, +}; diff --git a/platform/core/src/utils/Queue.js b/platform/core/src/utils/Queue.js index afe64cc00a..34149bcfce 100644 --- a/platform/core/src/utils/Queue.js +++ b/platform/core/src/utils/Queue.js @@ -8,7 +8,7 @@ export default class Queue { /** * Creates a new "proxy" function associated with the current execution queue * instance. When the returned function is invoked, the queue limit is checked - * to make sure the limit of scheduled tasks is repected (throwing an + * to make sure the limit of scheduled tasks is respected (throwing an * exception when the limit has been reached and before calling the original * function). The original function is only invoked after all the previously * scheduled tasks have finished executing (their returned promises have diff --git a/platform/core/src/utils/absoluteUrl.js b/platform/core/src/utils/absoluteUrl.js index faf2369be7..7fb9fd9168 100644 --- a/platform/core/src/utils/absoluteUrl.js +++ b/platform/core/src/utils/absoluteUrl.js @@ -1,7 +1,9 @@ const absoluteUrl = path => { let absolutePath = '/'; - if (!path) return absolutePath; + if (!path) { + return absolutePath; + } // TODO: Find another way to get root url const absoluteUrl = window.location.origin; diff --git a/platform/core/src/utils/absoluteUrl.test.js b/platform/core/src/utils/absoluteUrl.test.js index fe923064d9..41cc64a1d3 100644 --- a/platform/core/src/utils/absoluteUrl.test.js +++ b/platform/core/src/utils/absoluteUrl.test.js @@ -13,9 +13,7 @@ describe('absoluteUrl', () => { writable: true, }); const absoluteUrlOutput = absoluteUrl('/path_3/path_to_destination'); - expect(absoluteUrlOutput).toEqual( - '/path_1/path_2/path_3/path_to_destination' - ); + expect(absoluteUrlOutput).toEqual('/path_1/path_2/path_3/path_to_destination'); }); test('should return / when the path is not defined', () => { diff --git a/platform/core/src/utils/combineFrameInstance.ts b/platform/core/src/utils/combineFrameInstance.ts index 933437a246..087f97b422 100644 --- a/platform/core/src/utils/combineFrameInstance.ts +++ b/platform/core/src/utils/combineFrameInstance.ts @@ -9,42 +9,34 @@ * single frame data. (eg frame is undefined is the same as frame===1). */ const combineFrameInstance = (frame, instance) => { - const { - PerFrameFunctionalGroupsSequence, - SharedFunctionalGroupsSequence, - NumberOfFrames, - } = instance; + const { PerFrameFunctionalGroupsSequence, SharedFunctionalGroupsSequence, NumberOfFrames } = + instance; if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) { const frameNumber = Number.parseInt(frame || 1); - const shared = (SharedFunctionalGroupsSequence - ? Object.values(SharedFunctionalGroupsSequence[0]) - : [] + const shared = ( + SharedFunctionalGroupsSequence ? Object.values(SharedFunctionalGroupsSequence[0]) : [] ) + .filter(it => !!it) .map(it => it[0]) .filter(it => it !== undefined && typeof it === 'object'); - const perFrame = (PerFrameFunctionalGroupsSequence - ? Object.values(PerFrameFunctionalGroupsSequence[frameNumber - 1]) - : [] + const perFrame = ( + PerFrameFunctionalGroupsSequence + ? Object.values(PerFrameFunctionalGroupsSequence[frameNumber - 1]) + : [] ) + .filter(it => !!it) .map(it => it[0]) .filter(it => it !== undefined && typeof it === 'object'); // this is to fix NM multiframe datasets with position and orientation // information inside DetectorInformationSequence - if ( - !instance.ImageOrientationPatient && - instance.DetectorInformationSequence - ) { + if (!instance.ImageOrientationPatient && instance.DetectorInformationSequence) { instance.ImageOrientationPatient = instance.DetectorInformationSequence[0].ImageOrientationPatient; } - if ( - !instance.ImagePositionPatient && - instance.DetectorInformationSequence - ) { - instance.ImagePositionPatient = - instance.DetectorInformationSequence[0].ImagePositionPatient; + if (!instance.ImagePositionPatient && instance.DetectorInformationSequence) { + instance.ImagePositionPatient = instance.DetectorInformationSequence[0].ImagePositionPatient; } const newInstance = Object.assign(instance, { frameNumber: frameNumber }); diff --git a/platform/core/src/utils/debounce.js b/platform/core/src/utils/debounce.js index 83a16b0990..7a5b2bdf3c 100644 --- a/platform/core/src/utils/debounce.js +++ b/platform/core/src/utils/debounce.js @@ -4,17 +4,21 @@ // leading edge, instead of the trailing. function debounce(func, wait, immediate) { var timeout; - return function() { + return function () { var context = this, args = arguments; - var later = function() { + var later = function () { timeout = null; - if (!immediate) func.apply(context, args); + if (!immediate) { + func.apply(context, args); + } }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); + if (callNow) { + func.apply(context, args); + } }; } diff --git a/platform/core/src/utils/downloadCSVReport.js b/platform/core/src/utils/downloadCSVReport.js index 12951a6a64..b2da98b612 100644 --- a/platform/core/src/utils/downloadCSVReport.js +++ b/platform/core/src/utils/downloadCSVReport.js @@ -17,22 +17,14 @@ export default function downloadCSVReport(measurementData) { const reportMap = {}; measurementData.forEach(measurement => { - const { - referenceStudyUID, - referenceSeriesUID, - getReport, - uid, - } = measurement; + const { referenceStudyUID, referenceSeriesUID, getReport, uid } = measurement; if (!getReport) { console.warn('Measurement does not have a getReport function'); return; } - const seriesMetadata = DicomMetadataStore.getSeries( - referenceStudyUID, - referenceSeriesUID - ); + const seriesMetadata = DicomMetadataStore.getSeries(referenceStudyUID, referenceSeriesUID); const commonRowItems = _getCommonRowItems(measurement, seriesMetadata); const report = getReport(measurement); @@ -57,9 +49,7 @@ export default function downloadCSVReport(measurementData) { const results = _mapReportsToRowArray(reportMap, columns); - let csvContent = - 'data:text/csv;charset=utf-8,' + - results.map(res => res.join(',')).join('\n'); + let csvContent = 'data:text/csv;charset=utf-8,' + results.map(res => res.join(',')).join('\n'); _createAndDownloadFile(csvContent); } diff --git a/platform/core/src/utils/formatDate.js b/platform/core/src/utils/formatDate.js index a2a9a9bab3..7b44277f20 100644 --- a/platform/core/src/utils/formatDate.js +++ b/platform/core/src/utils/formatDate.js @@ -8,5 +8,6 @@ import moment from 'moment'; * @returns {string} Formatted date */ export default (date, format = 'DD-MMM-YYYY') => { - return moment(date).format(format); + // moment(undefined) returns the current date, so return the empty string instead + return date ? moment(date).format(format) : ''; }; diff --git a/platform/core/src/utils/formatPN.js b/platform/core/src/utils/formatPN.js index 5e6f26a029..04270040a6 100644 --- a/platform/core/src/utils/formatPN.js +++ b/platform/core/src/utils/formatPN.js @@ -6,7 +6,10 @@ export default function formatPN(name) { return; } - const nameToUse = name.Alphabetic ?? name; + let nameToUse = name.Alphabetic ?? name; + if (typeof nameToUse === 'object') { + nameToUse = ''; + } // Convert the first ^ to a ', '. String.replace() only affects // the first appearance of the character. diff --git a/platform/core/src/utils/generateAcceptHeader.ts b/platform/core/src/utils/generateAcceptHeader.ts new file mode 100644 index 0000000000..a428be0f73 --- /dev/null +++ b/platform/core/src/utils/generateAcceptHeader.ts @@ -0,0 +1,57 @@ +const generateAcceptHeader = ( + configAcceptHeader = [], + requestTransferSyntaxUID = null, + omitQuotationForMultipartRequest = false +): string[] => { + //if acceptedHeader is passed by config use it as it. + if (configAcceptHeader.length > 0) { + return configAcceptHeader; + } + + let acceptHeader = ['multipart/related']; + if (requestTransferSyntaxUID && typeForTS[requestTransferSyntaxUID]) { + const type = typeForTS[requestTransferSyntaxUID]; + acceptHeader.push('type=' + type); + acceptHeader.push('transfer-syntax=' + requestTransferSyntaxUID); + } else { + acceptHeader.push('type=application/octet-stream'); + } + + if (!omitQuotationForMultipartRequest) { + //need to add quotation for each mime type of each accept entry + acceptHeader = acceptHeader.map(mime => { + if (mime.startsWith('type=')) { + const quotedParam = 'type="' + mime.substring(5, mime.length) + '"'; + return quotedParam; + } + if (mime.startsWith('transfer-syntax=')) { + const quotedParam = 'transfer-syntax="' + mime.substring(16, mime.length) + '"'; + return quotedParam; + } else { + return mime; + } + }); + } + + return [acceptHeader.join('; ')]; +}; + +const typeForTS = { + '*': 'application/octet-stream', + '1.2.840.10008.1.2.1': 'application/octet-stream', + '1.2.840.10008.1.2': 'application/octet-stream', + '1.2.840.10008.1.2.2': 'application/octet-stream', + '1.2.840.10008.1.2.4.70': 'image/jpeg', + '1.2.840.10008.1.2.4.50': 'image/jpeg', + '1.2.840.10008.1.2.4.51': 'image/dicom+jpeg', + '1.2.840.10008.1.2.4.57': 'image/jpeg', + '1.2.840.10008.1.2.5': 'image/dicom-rle', + '1.2.840.10008.1.2.4.80': 'image/jls', + '1.2.840.10008.1.2.4.81': 'image/jls', + '1.2.840.10008.1.2.4.90': 'image/jp2', + '1.2.840.10008.1.2.4.91': 'image/jp2', + '1.2.840.10008.1.2.4.92': 'image/jpx', + '1.2.840.10008.1.2.4.93': 'image/jpx', +}; + +export default generateAcceptHeader; diff --git a/platform/core/src/utils/getImageId.js b/platform/core/src/utils/getImageId.js index ccd5e39171..51e057b912 100644 --- a/platform/core/src/utils/getImageId.js +++ b/platform/core/src/utils/getImageId.js @@ -38,11 +38,7 @@ export default function getImageId(instance, frame, thumbnail = false) { const renderingAttr = thumbnail ? 'thumbnailRendering' : 'imageRendering'; - if ( - !instance[renderingAttr] || - instance[renderingAttr] === 'wadouri' || - !instance.wadorsuri - ) { + if (!instance[renderingAttr] || instance[renderingAttr] === 'wadouri' || !instance.wadorsuri) { let imageId = 'dicomweb:' + instance.wadouri; if (frame !== undefined) { imageId += '&frame=' + frame; diff --git a/platform/core/src/utils/hierarchicalListUtils.js b/platform/core/src/utils/hierarchicalListUtils.js index 5c3d5d3b28..ca12c7752c 100644 --- a/platform/core/src/utils/hierarchicalListUtils.js +++ b/platform/core/src/utils/hierarchicalListUtils.js @@ -36,11 +36,11 @@ function addToList(list, ...values) { * once for each leaf-node of the tree. The ancestors of the leaf-node being * visited are passed to the callback function along with the leaf-node in * the exact same order they appear on the tree (from root to leaf); - * @ For example, if the hierachy `a > b > c` appears on the tree ("a" being + * @ For example, if the hierarchy `a > b > c` appears on the tree ("a" being * the root and "c" being the leaf) the callback function will be called as: * callback('a', 'b', 'c'); * @param {Array} list The hierarchical list to be iterated - * @param {function} callback The callback which will be exected once for + * @param {function} callback The callback which will be executed once for * each leaf-node of the hierarchical list; * @returns {Array} Returns the provided list or null for bad arguments; */ @@ -103,7 +103,7 @@ function print(list) { let text = ''; if (Array.isArray(list)) { let prev = []; - forEachValue(list, function(...args) { + forEachValue(list, function (...args) { let prevLen = prev.length; for (let i = 0, l = args.length; i < l; ++i) { if (i < prevLen && args[i] === prev[i]) { diff --git a/platform/core/src/utils/hierarchicalListUtils.test.js b/platform/core/src/utils/hierarchicalListUtils.test.js index 4141783391..fbf41e9fa8 100644 --- a/platform/core/src/utils/hierarchicalListUtils.test.js +++ b/platform/core/src/utils/hierarchicalListUtils.test.js @@ -1,9 +1,9 @@ import { addToList, forEach, getItem, print } from './hierarchicalListUtils'; -describe('hierarchicalListUtils', function() { +describe('hierarchicalListUtils', function () { let sharedList; - beforeEach(function() { + beforeEach(function () { sharedList = [ ['1.2.3.1', ['1.2.3.1.1', '1.2.3.1.2']], '1.2.3.2', @@ -11,14 +11,14 @@ describe('hierarchicalListUtils', function() { ]; }); - describe('getItem', function() { - it('should retrieve elements from a list by index', function() { + describe('getItem', function () { + it('should retrieve elements from a list by index', function () { expect(getItem(sharedList, 0)).toBe('1.2.3.1'); expect(getItem(sharedList, 1)).toBe('1.2.3.2'); expect(getItem(sharedList, 2)).toBe('1.2.3.3'); expect(getItem(sharedList, 3)).toBeUndefined(); }); - it('should retrieve elements from a list by path', function() { + it('should retrieve elements from a list by path', function () { expect(getItem(sharedList, '0')).toBe('1.2.3.1'); expect(getItem(sharedList, '0/0')).toBe('1.2.3.1.1'); expect(getItem(sharedList, '0/1')).toBe('1.2.3.1.2'); @@ -35,8 +35,8 @@ describe('hierarchicalListUtils', function() { }); }); - describe('addToList', function() { - it('should support adding elements to a list hierarchically', function() { + describe('addToList', function () { + it('should support adding elements to a list hierarchically', function () { const list = []; addToList(list, '1.2.3.1', '1.2.3.1.1'); addToList(list, '1.2.3.1', '1.2.3.1.2'); @@ -46,7 +46,7 @@ describe('hierarchicalListUtils', function() { addToList(list, '1.2.3.3', '1.2.3.3.2', '1.2.3.3.2.2'); expect(list).toStrictEqual(sharedList); }); - it('should change leaf nodes into non-leaf nodes', function() { + it('should change leaf nodes into non-leaf nodes', function () { const listw = []; const listx = [['x.1', ['x.1.1', 'x.1.2']], 'x.2']; const listy = [ @@ -64,8 +64,8 @@ describe('hierarchicalListUtils', function() { }); }); - describe('forEach', function() { - it('should iterate through all leaf nodes of the tree', function() { + describe('forEach', function () { + it('should iterate through all leaf nodes of the tree', function () { const fn = jest.fn(); forEach(sharedList, fn); expect(fn).toHaveBeenCalledTimes(6); @@ -78,8 +78,8 @@ describe('hierarchicalListUtils', function() { }); }); - describe('print', function() { - it('should pretty-print the hierarchical list', function() { + describe('print', function () { + it('should pretty-print the hierarchical list', function () { expect(print(sharedList)).toBe( '1.2.3.1\n' + ' 1.2.3.1.1\n' + diff --git a/platform/core/src/utils/hotkeys/pausePlugin.js b/platform/core/src/utils/hotkeys/pausePlugin.js index 6774d760f7..82176e0619 100644 --- a/platform/core/src/utils/hotkeys/pausePlugin.js +++ b/platform/core/src/utils/hotkeys/pausePlugin.js @@ -8,7 +8,7 @@ export default function pausePlugin(Mousetrap) { var _originalStopCallback = Mousetrap.prototype.stopCallback; - Mousetrap.prototype.stopCallback = function(e, element, combo) { + Mousetrap.prototype.stopCallback = function (e, element, combo) { var self = this; if (self.paused) { @@ -18,12 +18,12 @@ export default function pausePlugin(Mousetrap) { return _originalStopCallback.call(self, e, element, combo); }; - Mousetrap.prototype.pause = function() { + Mousetrap.prototype.pause = function () { var self = this; self.paused = true; }; - Mousetrap.prototype.unpause = function() { + Mousetrap.prototype.unpause = function () { var self = this; self.paused = false; }; diff --git a/platform/core/src/utils/hotkeys/recordPlugin.js b/platform/core/src/utils/hotkeys/recordPlugin.js index e425ce5bfe..a2b757442d 100644 --- a/platform/core/src/utils/hotkeys/recordPlugin.js +++ b/platform/core/src/utils/hotkeys/recordPlugin.js @@ -122,7 +122,7 @@ export default function recordPlugin(Mousetrap, options = { timeout: 100 }) { */ function _normalizeSequence(sequence) { for (let i = 0; i < sequence.length; ++i) { - sequence[i].sort(function(x, y) { + sequence[i].sort(function (x, y) { // modifier keys always come first, in alphabetical order if (x.length > 1 && y.length === 1) { return -1; @@ -177,10 +177,10 @@ export default function recordPlugin(Mousetrap, options = { timeout: 100 }) { * @param {Function} callback * @returns void */ - Mousetrap.prototype.record = function(callback) { + Mousetrap.prototype.record = function (callback) { var self = this; self.recording = true; - _recordedSequenceCallback = function() { + _recordedSequenceCallback = function () { self.recording = false; callback.apply(self, arguments); }; @@ -192,7 +192,7 @@ export default function recordPlugin(Mousetrap, options = { timeout: 100 }) { * @param {Function} callback * @returns void */ - Mousetrap.prototype.stopRecord = function() { + Mousetrap.prototype.stopRecord = function () { var self = this; self.recording = false; }; @@ -203,11 +203,11 @@ export default function recordPlugin(Mousetrap, options = { timeout: 100 }) { * @param {Function} callback * @returns void */ - Mousetrap.prototype.startRecording = function() { + Mousetrap.prototype.startRecording = function () { var self = this; self.recording = true; }; - Mousetrap.prototype.handleKey = function() { + Mousetrap.prototype.handleKey = function () { var self = this; _handleKey.apply(self, arguments); }; diff --git a/platform/core/src/utils/index.js b/platform/core/src/utils/index.js index 06e7537f3b..3f5d66e413 100644 --- a/platform/core/src/utils/index.js +++ b/platform/core/src/utils/index.js @@ -1,6 +1,7 @@ import ObjectPath from './objectPath'; import absoluteUrl from './absoluteUrl'; import guid from './guid'; +import uuidv4 from './uuidv4'; import sortBy from './sortBy.js'; import writeScript from './writeScript.js'; import b64toBlob from './b64toBlob.js'; @@ -13,6 +14,7 @@ import Queue from './Queue'; import isDicomUid from './isDicomUid'; import formatDate from './formatDate'; import formatPN from './formatPN'; +import generateAcceptHeader from './generateAcceptHeader'; import resolveObjectPath from './resolveObjectPath'; import hierarchicalListUtils from './hierarchicalListUtils'; import progressTrackingUtils from './progressTrackingUtils'; @@ -40,6 +42,7 @@ import { splitComma, getSplitParam } from './splitComma'; const utils = { guid, + uuidv4, ObjectPath, absoluteUrl, sortBy, @@ -74,6 +77,7 @@ const utils = { subscribeToNextViewportGridChange, splitComma, getSplitParam, + generateAcceptHeader, }; export { @@ -105,6 +109,7 @@ export { downloadCSVReport, splitComma, getSplitParam, + generateAcceptHeader, }; export default utils; diff --git a/platform/core/src/utils/index.test.js b/platform/core/src/utils/index.test.js index b3c3e1e26e..5ac07149c3 100644 --- a/platform/core/src/utils/index.test.js +++ b/platform/core/src/utils/index.test.js @@ -24,6 +24,7 @@ describe('Top level exports', () => { 'b64toBlob', 'formatDate', 'formatPN', + 'generateAcceptHeader', 'isEqualWithin', //'loadAndCacheDerivedDisplaySets', 'isDisplaySetReconstructable', @@ -38,6 +39,7 @@ describe('Top level exports', () => { 'hierarchicalListUtils', 'progressTrackingUtils', 'subscribeToNextViewportGridChange', + 'uuidv4', ].sort(); const exports = Object.keys(utils.default).sort(); diff --git a/platform/core/src/utils/isDicomUid.test.js b/platform/core/src/utils/isDicomUid.test.js index 3a442823a9..279a2b4874 100644 --- a/platform/core/src/utils/isDicomUid.test.js +++ b/platform/core/src/utils/isDicomUid.test.js @@ -1,13 +1,13 @@ import isDicomUid from './isDicomUid'; -describe('isDicomUid', function() { - it('should return true for valid DICOM UIDs', function() { +describe('isDicomUid', function () { + it('should return true for valid DICOM UIDs', function () { expect(isDicomUid('1')).toBe(true); expect(isDicomUid('1.2')).toBe(true); expect(isDicomUid('1.2.3')).toBe(true); expect(isDicomUid('1.2.3.4')).toBe(true); }); - it('should return false for invalid DICOM UIDs', function() { + it('should return false for invalid DICOM UIDs', function () { expect(isDicomUid('x')).toBe(false); expect(isDicomUid('1.')).toBe(false); expect(isDicomUid('1. 2')).toBe(false); diff --git a/platform/core/src/utils/isDisplaySetReconstructable.js b/platform/core/src/utils/isDisplaySetReconstructable.js index 373da994af..8b348177d8 100644 --- a/platform/core/src/utils/isDisplaySetReconstructable.js +++ b/platform/core/src/utils/isDisplaySetReconstructable.js @@ -30,23 +30,17 @@ export default function isDisplaySetReconstructable(instances) { } // Can't reconstruct if all instances don't have the ImagePositionPatient. - if ( - !isMultiframe && - !instances.every(instance => instance.ImagePositionPatient) - ) { + if (!isMultiframe && !instances.every(instance => instance.ImagePositionPatient)) { return { value: false }; } const sortedInstances = sortInstancesByPosition(instances); - return isMultiframe - ? processMultiframe(sortedInstances[0]) - : processSingleframe(sortedInstances); + return isMultiframe ? processMultiframe(sortedInstances[0]) : processSingleframe(sortedInstances); } function hasPixelMeasurements(multiFrameInstance) { - const perFrameSequence = - multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; + const perFrameSequence = multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; const sharedSequence = multiFrameInstance.SharedFunctionalGroupsSequence; return ( @@ -54,39 +48,34 @@ function hasPixelMeasurements(multiFrameInstance) { Boolean(sharedSequence?.PixelMeasuresSequence) || Boolean( multiFrameInstance.PixelSpacing && - (multiFrameInstance.SliceThickness || - multiFrameInstance.SpacingBetweenFrames) + (multiFrameInstance.SliceThickness || multiFrameInstance.SpacingBetweenFrames) ) ); } function hasOrientation(multiFrameInstance) { const sharedSequence = multiFrameInstance.SharedFunctionalGroupsSequence; - const perFrameSequence = - multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; + const perFrameSequence = multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; return ( Boolean(sharedSequence?.PlaneOrientationSequence) || Boolean(perFrameSequence?.PlaneOrientationSequence) || Boolean( multiFrameInstance.ImageOrientationPatient || - multiFrameInstance.DetectorInformationSequence?.[0] - ?.ImageOrientationPatient + multiFrameInstance.DetectorInformationSequence?.[0]?.ImageOrientationPatient ) ); } function hasPosition(multiFrameInstance) { - const perFrameSequence = - multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; + const perFrameSequence = multiFrameInstance.PerFrameFunctionalGroupsSequence?.[0]; return ( Boolean(perFrameSequence?.PlanePositionSequence) || Boolean(perFrameSequence?.CTPositionSequence) || Boolean( multiFrameInstance.ImagePositionPatient || - multiFrameInstance.DetectorInformationSequence?.[0] - ?.ImagePositionPatient + multiFrameInstance.DetectorInformationSequence?.[0]?.ImagePositionPatient ) ); } @@ -114,10 +103,7 @@ function processMultiframe(multiFrameInstance) { return { value: false }; } - if ( - multiFrameInstance.Modality.includes('NM') && - !isNMReconstructable(multiFrameInstance) - ) { + if (multiFrameInstance.Modality.includes('NM') && !isNMReconstructable(multiFrameInstance)) { return { value: false }; } @@ -130,9 +116,7 @@ function processSingleframe(instances) { const firstImageRows = toNumber(firstImage.Rows); const firstImageColumns = toNumber(firstImage.Columns); const firstImageSamplesPerPixel = toNumber(firstImage.SamplesPerPixel); - const firstImageOrientationPatient = toNumber( - firstImage.ImageOrientationPatient - ); + const firstImageOrientationPatient = toNumber(firstImage.ImageOrientationPatient); const firstImagePositionPatient = toNumber(firstImage.ImagePositionPatient); // Can't reconstruct if we: @@ -141,12 +125,7 @@ function processSingleframe(instances) { // -- Have different orientations within a displaySet. for (let i = 1; i < instances.length; i++) { const instance = instances[i]; - const { - Rows, - Columns, - SamplesPerPixel, - ImageOrientationPatient, - } = instance; + const { Rows, Columns, SamplesPerPixel, ImageOrientationPatient } = instance; const imageOrientationPatient = toNumber(ImageOrientationPatient); @@ -161,23 +140,21 @@ function processSingleframe(instances) { } let missingFrames = 0; + let averageSpacingBetweenFrames; // Check if frame spacing is approximately equal within a spacingTolerance. // If spacing is on a uniform grid but we are missing frames, // Allow reconstruction, but pass back the number of missing frames. if (instances.length > 2) { - const lastIpp = toNumber( - instances[instances.length - 1].ImagePositionPatient - ); + const lastIpp = toNumber(instances[instances.length - 1].ImagePositionPatient); // We can't reconstruct if we are missing ImagePositionPatient values if (!firstImagePositionPatient || !lastIpp) { return { value: false }; } - const averageSpacingBetweenFrames = - _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / - (instances.length - 1); + averageSpacingBetweenFrames = + _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / (instances.length - 1); let previousImagePositionPatient = firstImagePositionPatient; @@ -190,10 +167,7 @@ function processSingleframe(instances) { imagePositionPatient, previousImagePositionPatient ); - const spacingIssue = _getSpacingIssue( - spacingBetweenFrames, - averageSpacingBetweenFrames - ); + const spacingIssue = _getSpacingIssue(spacingBetweenFrames, averageSpacingBetweenFrames); if (spacingIssue) { const issue = spacingIssue.issue; @@ -209,7 +183,7 @@ function processSingleframe(instances) { } } - return { value: true, missingFrames }; + return { value: true, averageSpacingBetweenFrames }; } function _isSameOrientation(iop1, iop2) { @@ -220,7 +194,10 @@ function _isSameOrientation(iop1, iop2) { return ( Math.abs(iop1[0] - iop2[0]) < iopTolerance && Math.abs(iop1[1] - iop2[1]) < iopTolerance && - Math.abs(iop1[2] - iop2[2]) < iopTolerance + Math.abs(iop1[2] - iop2[2]) < iopTolerance && + Math.abs(iop1[3] - iop2[3]) < iopTolerance && + Math.abs(iop1[4] - iop2[4]) < iopTolerance && + Math.abs(iop1[5] - iop2[5]) < iopTolerance ); } @@ -258,11 +235,7 @@ function _getSpacingIssue(spacing, averageSpacing) { } function _getPerpendicularDistance(a, b) { - return Math.sqrt( - Math.pow(a[0] - b[0], 2) + - Math.pow(a[1] - b[1], 2) + - Math.pow(a[2] - b[2], 2) - ); + return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)); } const constructableModalities = ['MR', 'CT', 'PT', 'NM']; @@ -270,3 +243,15 @@ const reconstructionIssues = { MISSING_FRAMES: 'missingframes', IRREGULAR_SPACING: 'irregularspacing', }; + +export { + hasPixelMeasurements, + hasOrientation, + hasPosition, + isNMReconstructable, + _isSameOrientation, + _getSpacingIssue, + _getPerpendicularDistance, + reconstructionIssues, + constructableModalities, +}; diff --git a/platform/core/src/utils/isImage.js b/platform/core/src/utils/isImage.js index 525e6ccd95..373abce995 100644 --- a/platform/core/src/utils/isImage.js +++ b/platform/core/src/utils/isImage.js @@ -58,6 +58,8 @@ const imagesTypes = [ * @returns {boolean} - true if it has image data */ export const isImage = SOPClassUID => { - if (!SOPClassUID) return false; + if (!SOPClassUID) { + return false; + } return imagesTypes.indexOf(SOPClassUID) !== -1; }; diff --git a/platform/core/src/utils/isImage.test.js b/platform/core/src/utils/isImage.test.js index 1f53ccea17..8826392008 100644 --- a/platform/core/src/utils/isImage.test.js +++ b/platform/core/src/utils/isImage.test.js @@ -3,23 +3,17 @@ import { isImage } from './isImage'; describe('isImage', () => { test('should return true when the image is of type sopClassDictionary.ComputedRadiographyImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.ComputedRadiographyImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.ComputedRadiographyImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.DigitalXRayImageStorageForPresentation', () => { - const isImageStatus = isImage( - sopClassDictionary.DigitalXRayImageStorageForPresentation - ); + const isImageStatus = isImage(sopClassDictionary.DigitalXRayImageStorageForPresentation); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.DigitalXRayImageStorageForProcessing', () => { - const isImageStatus = isImage( - sopClassDictionary.DigitalXRayImageStorageForProcessing - ); + const isImageStatus = isImage(sopClassDictionary.DigitalXRayImageStorageForProcessing); expect(isImageStatus).toBe(true); }); @@ -45,9 +39,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing', () => { - const isImageStatus = isImage( - sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing - ); + const isImageStatus = isImage(sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing); expect(isImageStatus).toBe(true); }); @@ -62,16 +54,12 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.LegacyConvertedEnhancedCTImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.LegacyConvertedEnhancedCTImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.LegacyConvertedEnhancedCTImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.UltrasoundMultiframeImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.UltrasoundMultiframeImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.UltrasoundMultiframeImageStorage); expect(isImageStatus).toBe(true); }); @@ -86,16 +74,12 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.EnhancedMRColorImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.EnhancedMRColorImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.EnhancedMRColorImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.LegacyConvertedEnhancedMRImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.LegacyConvertedEnhancedMRImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.LegacyConvertedEnhancedMRImageStorage); expect(isImageStatus).toBe(true); }); @@ -105,9 +89,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.SecondaryCaptureImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.SecondaryCaptureImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.SecondaryCaptureImageStorage); expect(isImageStatus).toBe(true); }); @@ -140,9 +122,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.XRayAngiographicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.XRayAngiographicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.XRayAngiographicImageStorage); expect(isImageStatus).toBe(true); }); @@ -152,9 +132,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.XRayRadiofluoroscopicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.XRayRadiofluoroscopicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.XRayRadiofluoroscopicImageStorage); expect(isImageStatus).toBe(true); }); @@ -164,23 +142,17 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.XRay3DAngiographicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.XRay3DAngiographicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.XRay3DAngiographicImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.XRay3DCraniofacialImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.XRay3DCraniofacialImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.XRay3DCraniofacialImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.BreastTomosynthesisImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.BreastTomosynthesisImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.BreastTomosynthesisImageStorage); expect(isImageStatus).toBe(true); }); @@ -192,9 +164,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.BreastProjectionXRayImageStorageForProcessing', () => { - const isImageStatus = isImage( - sopClassDictionary.BreastProjectionXRayImageStorageForProcessing - ); + const isImageStatus = isImage(sopClassDictionary.BreastProjectionXRayImageStorageForProcessing); expect(isImageStatus).toBe(true); }); @@ -213,9 +183,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.NuclearMedicineImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.NuclearMedicineImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.NuclearMedicineImageStorage); expect(isImageStatus).toBe(true); }); @@ -225,9 +193,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.VideoEndoscopicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VideoEndoscopicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VideoEndoscopicImageStorage); expect(isImageStatus).toBe(true); }); @@ -237,65 +203,47 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.VideoMicroscopicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VideoMicroscopicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VideoMicroscopicImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.VLPhotographicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VLPhotographicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VLPhotographicImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.VideoPhotographicImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VideoPhotographicImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VideoPhotographicImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.OphthalmicPhotography8BitImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.OphthalmicPhotography8BitImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.OphthalmicPhotography8BitImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.OphthalmicPhotography16BitImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.OphthalmicPhotography16BitImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.OphthalmicPhotography16BitImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.OphthalmicTomographyImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.OphthalmicTomographyImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.OphthalmicTomographyImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.VLWholeSlideMicroscopyImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.VLWholeSlideMicroscopyImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.VLWholeSlideMicroscopyImageStorage); expect(isImageStatus).toBe(true); }); test('should return true when the image is of type sopClassDictionary.PositronEmissionTomographyImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.PositronEmissionTomographyImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.PositronEmissionTomographyImageStorage); expect(isImageStatus).toBe(true); }); @@ -305,9 +253,7 @@ describe('isImage', () => { }); test('should return true when the image is of type sopClassDictionary.LegacyConvertedEnhancedPETImageStorage', () => { - const isImageStatus = isImage( - sopClassDictionary.LegacyConvertedEnhancedPETImageStorage - ); + const isImageStatus = isImage(sopClassDictionary.LegacyConvertedEnhancedPETImageStorage); expect(isImageStatus).toBe(true); }); diff --git a/platform/core/src/utils/isLowPriorityModality.ts b/platform/core/src/utils/isLowPriorityModality.ts index aebb450ffb..54024d5a05 100644 --- a/platform/core/src/utils/isLowPriorityModality.ts +++ b/platform/core/src/utils/isLowPriorityModality.ts @@ -1,10 +1,4 @@ -const LOW_PRIORITY_MODALITIES = Object.freeze([ - 'SEG', - 'KO', - 'PR', - 'SR', - 'RTSTRUCT', -]); +const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR', 'SR', 'RTSTRUCT']); export default function isLowPriorityModality(Modality) { return LOW_PRIORITY_MODALITIES.includes(Modality); diff --git a/platform/core/src/utils/makeCancelable.js b/platform/core/src/utils/makeCancelable.js index 11e507103a..f64af59680 100644 --- a/platform/core/src/utils/makeCancelable.js +++ b/platform/core/src/utils/makeCancelable.js @@ -1,12 +1,16 @@ export default function makeCancelable(thenable) { let isCanceled = false; const promise = Promise.resolve(thenable).then( - function(result) { - if (isCanceled) throw Object.freeze({ isCanceled }); + function (result) { + if (isCanceled) { + throw Object.freeze({ isCanceled }); + } return result; }, - function(error) { - if (isCanceled) throw Object.freeze({ isCanceled, error }); + function (error) { + if (isCanceled) { + throw Object.freeze({ isCanceled, error }); + } throw error; } ); diff --git a/platform/core/src/utils/makeDeferred.js b/platform/core/src/utils/makeDeferred.js index 8becf3295f..86a25ec17d 100644 --- a/platform/core/src/utils/makeDeferred.js +++ b/platform/core/src/utils/makeDeferred.js @@ -1,7 +1,7 @@ export default function makeDeferred() { let reject, resolve, - promise = new Promise(function(res, rej) { + promise = new Promise(function (res, rej) { resolve = res; reject = rej; }); diff --git a/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js b/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js index 1975b3942f..a3d5a7aeaf 100644 --- a/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js +++ b/platform/core/src/utils/metadataProvider/fetchPaletteColorLookupTableData.js @@ -11,15 +11,12 @@ * @returns Array view containing the palette data, or a promise to return one. * Returns undefined if the palette data is absent. */ -export default function fetchPaletteColorLookupTableData( - item, - tag, - descriptorTag -) { +export default function fetchPaletteColorLookupTableData(item, tag, descriptorTag) { const { PaletteColorLookupTableUID } = item; const paletteData = item[tag]; - if (paletteData === undefined && PaletteColorLookupTableUID === undefined) + if (paletteData === undefined && PaletteColorLookupTableUID === undefined) { return; + } // performance optimization - read UID and cache by UID return _getPaletteColor(item[tag], item[descriptorTag]); } @@ -28,7 +25,9 @@ function _getPaletteColor(paletteColorLookupTableData, lutDescriptor) { const numLutEntries = lutDescriptor[0]; const bits = lutDescriptor[2]; - if (!paletteColorLookupTableData) return undefined; + if (!paletteColorLookupTableData) { + return undefined; + } const arrayBufferToPaletteColorLUT = arraybuffer => { const lut = []; @@ -52,19 +51,12 @@ function _getPaletteColor(paletteColorLookupTableData, lutDescriptor) { if (paletteColorLookupTableData.InlineBinary) { try { - const arraybuffer = Uint8Array.from( - atob(paletteColorLookupTableData.InlineBinary), - c => c.charCodeAt(0) + const arraybuffer = Uint8Array.from(atob(paletteColorLookupTableData.InlineBinary), c => + c.charCodeAt(0) ); - return (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT( - arraybuffer - )); + return (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT(arraybuffer)); } catch (e) { - console.log( - "Couldn't decode", - paletteColorLookupTableData.InlineBinary, - e - ); + console.log("Couldn't decode", paletteColorLookupTableData.InlineBinary, e); return undefined; } } @@ -72,12 +64,7 @@ function _getPaletteColor(paletteColorLookupTableData, lutDescriptor) { if (paletteColorLookupTableData.retrieveBulkData) { return paletteColorLookupTableData .retrieveBulkData() - .then( - val => - (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT( - val - )) - ); + .then(val => (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT(val))); } console.error(`No data found for ${paletteColorLookupTableData} palette`); diff --git a/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js b/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js index edcd1cf894..f869467b9a 100644 --- a/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js +++ b/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js @@ -48,11 +48,7 @@ export default function getPixelSpacingInformation(instance) { type: TYPES.UNKNOWN, isProjection, }; - } else if ( - PixelSpacing && - ImagerPixelSpacing && - PixelSpacing === ImagerPixelSpacing - ) { + } else if (PixelSpacing && ImagerPixelSpacing && PixelSpacing === ImagerPixelSpacing) { // If Imager Pixel Spacing and Pixel Spacing are present and they have the same values, // then the user should be informed that the measurements are at the detector plane return { @@ -60,11 +56,7 @@ export default function getPixelSpacingInformation(instance) { type: TYPES.DETECTOR, isProjection, }; - } else if ( - PixelSpacing && - ImagerPixelSpacing && - PixelSpacing !== ImagerPixelSpacing - ) { + } else if (PixelSpacing && ImagerPixelSpacing && PixelSpacing !== ImagerPixelSpacing) { // If Imager Pixel Spacing and Pixel Spacing are present and they have different values, // then the user should be informed that these are "calibrated" // (in some unknown manner if Pixel Spacing Calibration Type and/or @@ -95,10 +87,7 @@ export default function getPixelSpacingInformation(instance) { PixelSpacing: CorrectedImagerPixelSpacing, isProjection, }; - } else if ( - SequenceOfUltrasoundRegions && - typeof SequenceOfUltrasoundRegions === 'object' - ) { + } else if (SequenceOfUltrasoundRegions && typeof SequenceOfUltrasoundRegions === 'object') { const { PhysicalDeltaX, PhysicalDeltaY } = SequenceOfUltrasoundRegions; const USPixelSpacing = [PhysicalDeltaX * 10, PhysicalDeltaY * 10]; diff --git a/platform/core/src/utils/metadataProvider/unpackOverlay.js b/platform/core/src/utils/metadataProvider/unpackOverlay.js index 78e39b2c2b..387687440b 100644 --- a/platform/core/src/utils/metadataProvider/unpackOverlay.js +++ b/platform/core/src/utils/metadataProvider/unpackOverlay.js @@ -5,8 +5,7 @@ export default function unpackOverlay(arrayBuffer) { for (let byteIndex = 0; byteIndex < byteArray.length; byteIndex++) { const bitIndex = byteIndex % 8; const bitByteIndex = Math.floor(byteIndex / 8); - byteArray[byteIndex] = - 1 * ((bitArray[bitByteIndex] & (1 << bitIndex)) >> bitIndex); + byteArray[byteIndex] = 1 * ((bitArray[bitByteIndex] & (1 << bitIndex)) >> bitIndex); } return byteArray; diff --git a/platform/core/src/utils/objectPath.js b/platform/core/src/utils/objectPath.js index 5727267d56..feae26db21 100644 --- a/platform/core/src/utils/objectPath.js +++ b/platform/core/src/utils/objectPath.js @@ -6,7 +6,7 @@ export class ObjectPath { * @param path {String} A string representing the property to be set, e.g. "user.study.series.timepoint". * @param value {Any} The value of the property that will be set. * @return {Boolean} Returns "true" on success, "false" if any intermediate component of the supplied path - * ... is not a valid Object, in which case the property cannot be set. No excpetions are thrown. + * ... is not a valid Object, in which case the property cannot be set. No exceptions are thrown. */ static set(object, path, value) { let components = ObjectPath.getPathComponents(path), @@ -85,9 +85,7 @@ export class ObjectPath { * @return {Boolean} Returns "true" if the object is a real Object instance and "false" otherwise. */ static isValidObject(object) { - return ( - typeof object === 'object' && object !== null && object instanceof Object - ); + return typeof object === 'object' && object !== null && object instanceof Object; } static getPathComponents(path) { diff --git a/platform/core/src/utils/progressTrackingUtils.js b/platform/core/src/utils/progressTrackingUtils.js index efc3447d4a..7ff2f82618 100644 --- a/platform/core/src/utils/progressTrackingUtils.js +++ b/platform/core/src/utils/progressTrackingUtils.js @@ -114,7 +114,7 @@ function finish(task) { /** * Generate a summarized snapshot of the current status of the given task List * @param {Object} list The List instance to be scanned - * @returns {Object} An obeject representing the summarized status of the list + * @returns {Object} An object representing the summarized status of the list */ function getOverallProgress(list) { const status = createStatus(); @@ -124,7 +124,9 @@ function getOverallProgress(list) { status.total++; if (isValidProgress(task.progress)) { status.partial += task.progress; - if (task.progress === 1.0 && task.failed) status.failures++; + if (task.progress === 1.0 && task.failed) { + status.failures++; + } } task = task.next; } @@ -147,10 +149,10 @@ function waitOn(list, thenable) { const task = increaseList(list); if (isTask(task)) { task.awaiting = Promise.resolve(thenable).then( - function() { + function () { finish(task); }, - function() { + function () { task.failed = true; finish(task); } @@ -221,14 +223,10 @@ function getTaskByName(list, name) { * @param {Object} list The List instance to which the observer will be appended * @param {Function} observer The observer (function) that will be executed * every time a change happens within the list - * @returns {boolean} Returns true on success and false otherewise + * @returns {boolean} Returns true on success and false otherwise */ function addObserver(list, observer) { - if ( - isList(list) && - Array.isArray(list.observers) && - typeof observer === 'function' - ) { + if (isList(list) && Array.isArray(list.observers) && typeof observer === 'function') { list.observers.push(observer); return true; } @@ -239,14 +237,10 @@ function addObserver(list, observer) { * Removes an observer (callback function) from a given List instance * @param {Object} list The instance List from which the observer will removed * @param {Function} observer The observer function to be removed - * @returns {boolean} Returns true on success and false otherewise + * @returns {boolean} Returns true on success and false otherwise */ function removeObserver(list, observer) { - if ( - isList(list) && - Array.isArray(list.observers) && - list.observers.length > 0 - ) { + if (isList(list) && Array.isArray(list.observers) && list.observers.length > 0) { const index = list.observers.indexOf(observer); if (index >= 0) { list.observers.splice(index, 1); @@ -274,9 +268,7 @@ function objectWithType(type, object) { } function isOfType(type, subject) { - return ( - subject !== null && typeof subject === 'object' && subject[TYPE] === type - ); + return subject !== null && typeof subject === 'object' && subject[TYPE] === type; } function isValidProgress(value) { @@ -297,12 +289,8 @@ function contains(list, task) { } function notify(list, data) { - if ( - isList(list) && - Array.isArray(list.observers) && - list.observers.length > 0 - ) { - list.observers.slice().forEach(function(observer) { + if (isList(list) && Array.isArray(list.observers) && list.observers.length > 0) { + list.observers.slice().forEach(function (observer) { if (typeof observer === 'function') { try { observer(data, list); diff --git a/platform/core/src/utils/progressTrackingUtils.test.js b/platform/core/src/utils/progressTrackingUtils.test.js index f9b098c584..3a74c8c95f 100644 --- a/platform/core/src/utils/progressTrackingUtils.test.js +++ b/platform/core/src/utils/progressTrackingUtils.test.js @@ -40,11 +40,7 @@ describe('progressTrackingUtils', () => { it('should call observer twice for each task', () => { const { list, observer } = context; - const promises = [ - Promise.resolve('A'), - Promise.resolve('B'), - Promise.resolve('C'), - ]; + const promises = [Promise.resolve('A'), Promise.resolve('B'), Promise.resolve('C')]; promises.forEach(promise => void utils.waitOn(list, promise)); return Promise.all(promises).then(() => { expect(observer).toBeCalledTimes(6); diff --git a/platform/core/src/utils/resolveObjectPath.js b/platform/core/src/utils/resolveObjectPath.js index d5e82babf5..c541732669 100644 --- a/platform/core/src/utils/resolveObjectPath.js +++ b/platform/core/src/utils/resolveObjectPath.js @@ -10,8 +10,6 @@ export default function resolveObjectPath(root, path, defaultValue) { ); } value = root[path]; - return value === undefined && defaultValue !== undefined - ? defaultValue - : value; + return value === undefined && defaultValue !== undefined ? defaultValue : value; } } diff --git a/platform/core/src/utils/resolveObjectPath.test.js b/platform/core/src/utils/resolveObjectPath.test.js index 6d019a5bc3..838341fba2 100644 --- a/platform/core/src/utils/resolveObjectPath.test.js +++ b/platform/core/src/utils/resolveObjectPath.test.js @@ -1,9 +1,9 @@ import resolveObjectPath from './resolveObjectPath'; -describe('resolveObjectPath', function() { +describe('resolveObjectPath', function () { let config; - beforeEach(function() { + beforeEach(function () { config = { active: { user: { @@ -21,13 +21,13 @@ describe('resolveObjectPath', function() { }; }); - it('should safely return deeply nested values from an object', function() { + it('should safely return deeply nested values from an object', function () { expect(resolveObjectPath(config, 'active.user.name.first')).toBe('John'); expect(resolveObjectPath(config, 'active.user.name.last')).toBe('Doe'); expect(resolveObjectPath(config, 'active.servers.0.ipv4')).toBe('10.0.0.1'); }); - it('should silently return undefined when intermediate values are not valid objects', function() { + it('should silently return undefined when intermediate values are not valid objects', function () { expect(resolveObjectPath(config, 'active.usr.name.first')).toBeUndefined(); expect(resolveObjectPath(config, 'active.name.last')).toBeUndefined(); expect(resolveObjectPath(config, 'active.servers.7.ipv4')).toBeUndefined(); diff --git a/platform/core/src/utils/roundNumber.js b/platform/core/src/utils/roundNumber.js index a441225da5..be76c7edea 100644 --- a/platform/core/src/utils/roundNumber.js +++ b/platform/core/src/utils/roundNumber.js @@ -1,9 +1,34 @@ /** - * @param {string | number} value - * @param {number} decimals + * Truncates decimal points to that there is at least 1+precision significant + * digits. + * + * For example, with the default precision 2 (3 significant digits) + * * Values larger than 100 show no information after the decimal point + * * Values between 10 and 99 show 1 decimal point + * * Values between 1 and 9 show 2 decimal points + * + * @param value - to return a fixed measurement value from + * @param precision - defining how many digits after 1..9 are desired */ -function _round(value, decimals) { - return Number(value).toFixed(decimals); +function roundNumber(value: string | number, precision = 2): string { + if (value === undefined || value === null || value === '') return 'NaN'; + value = Number(value); + if (value < 0.0001) return `${value}`; + const fixedPrecision = + value >= 100 + ? precision - 2 + : value >= 10 + ? precision - 1 + : value >= 1 + ? precision + : value >= 0.1 + ? precision + 1 + : value >= 0.01 + ? precision + 2 + : value >= 0.001 + ? precision + 3 + : precision + 4; + return value.toFixed(fixedPrecision); } -export default _round; +export default roundNumber; diff --git a/platform/core/src/utils/sopClassDictionary.js b/platform/core/src/utils/sopClassDictionary.js index 5e95576958..cb9712e910 100644 --- a/platform/core/src/utils/sopClassDictionary.js +++ b/platform/core/src/utils/sopClassDictionary.js @@ -3,14 +3,10 @@ export const sopClassDictionary = { ComputedRadiographyImageStorage: '1.2.840.10008.5.1.4.1.1.1', DigitalXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.1.1', DigitalXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.1.1.1', - DigitalMammographyXRayImageStorageForPresentation: - '1.2.840.10008.5.1.4.1.1.1.2', - DigitalMammographyXRayImageStorageForProcessing: - '1.2.840.10008.5.1.4.1.1.1.2.1', - DigitalIntraOralXRayImageStorageForPresentation: - '1.2.840.10008.5.1.4.1.1.1.3', - DigitalIntraOralXRayImageStorageForProcessing: - '1.2.840.10008.5.1.4.1.1.1.3.1', + DigitalMammographyXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.1.2', + DigitalMammographyXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.1.2.1', + DigitalIntraOralXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.1.3', + DigitalIntraOralXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.1.3.1', CTImageStorage: '1.2.840.10008.5.1.4.1.1.2', EnhancedCTImageStorage: '1.2.840.10008.5.1.4.1.1.2.1', LegacyConvertedEnhancedCTImageStorage: '1.2.840.10008.5.1.4.1.1.2.2', @@ -23,14 +19,10 @@ export const sopClassDictionary = { UltrasoundImageStorage: '1.2.840.10008.5.1.4.1.1.6.1', EnhancedUSVolumeStorage: '1.2.840.10008.5.1.4.1.1.6.2', SecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7', - MultiframeSingleBitSecondaryCaptureImageStorage: - '1.2.840.10008.5.1.4.1.1.7.1', - MultiframeGrayscaleByteSecondaryCaptureImageStorage: - '1.2.840.10008.5.1.4.1.1.7.2', - MultiframeGrayscaleWordSecondaryCaptureImageStorage: - '1.2.840.10008.5.1.4.1.1.7.3', - MultiframeTrueColorSecondaryCaptureImageStorage: - '1.2.840.10008.5.1.4.1.1.7.4', + MultiframeSingleBitSecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7.1', + MultiframeGrayscaleByteSecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7.2', + MultiframeGrayscaleWordSecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7.3', + MultiframeTrueColorSecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7.4', Sop12LeadECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.1', GeneralECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.2', AmbulatoryECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.3', @@ -44,8 +36,7 @@ export const sopClassDictionary = { ColorSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.2', PseudoColorSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.3', BlendingSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.4', - XAXRFGrayscaleSoftcopyPresentationStateStorage: - '1.2.840.10008.5.1.4.1.1.11.5', + XAXRFGrayscaleSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.5', XRayAngiographicImageStorage: '1.2.840.10008.5.1.4.1.1.12.1', EnhancedXAImageStorage: '1.2.840.10008.5.1.4.1.1.12.1.1', XRayRadiofluoroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.12.2', @@ -53,14 +44,11 @@ export const sopClassDictionary = { XRay3DAngiographicImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.1', XRay3DCraniofacialImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.2', BreastTomosynthesisImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.3', - BreastProjectionXRayImageStorageForPresentation: - '1.2.840.10008.5.1.4.1.1.13.1.4', - BreastProjectionXRayImageStorageForProcessing: - '1.2.840.10008.5.1.4.1.1.13.1.5', + BreastProjectionXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.13.1.4', + BreastProjectionXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.13.1.5', IntravascularOpticalCoherenceTomographyImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.14.1', - IntravascularOpticalCoherenceTomographyImageStorageForProcessing: - '1.2.840.10008.5.1.4.1.1.14.2', + IntravascularOpticalCoherenceTomographyImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.14.2', NuclearMedicineImageStorage: '1.2.840.10008.5.1.4.1.1.20', RawDataStorage: '1.2.840.10008.5.1.4.1.1.66', SpatialRegistrationStorage: '1.2.840.10008.5.1.4.1.1.66.1', @@ -92,8 +80,7 @@ export const sopClassDictionary = { OphthalmicAxialMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.7', IntraocularLensCalculationsStorage: '1.2.840.10008.5.1.4.1.1.78.8', MacularGridThicknessandVolumeReport: '1.2.840.10008.5.1.4.1.1.79.1', - OphthalmicVisualFieldStaticPerimetryMeasurementsStorage: - '1.2.840.10008.5.1.4.1.1.80.1', + OphthalmicVisualFieldStaticPerimetryMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.80.1', OphthalmicThicknessMapStorage: '1.2.840.10008.5.1.4.1.1.81.1', CornealTopographyMapStorage: '1.2.840.10008.5.1.4.1.1.82.1', BasicTextSR: '1.2.840.10008.5.1.4.1.1.88.11', diff --git a/platform/core/src/utils/sortBy.js b/platform/core/src/utils/sortBy.js index 39f21ef245..8809b0fbd1 100644 --- a/platform/core/src/utils/sortBy.js +++ b/platform/core/src/utils/sortBy.js @@ -3,7 +3,7 @@ export default function sortBy() { var fields = [].slice.call(arguments), n_fields = fields.length; - return function(A, B) { + return function (A, B) { var a, b, field, key, reverse, result, i; for (i = 0; i < n_fields; i++) { diff --git a/platform/core/src/utils/sortInstancesByPosition.ts b/platform/core/src/utils/sortInstancesByPosition.ts index a441796fbe..4fd248354f 100644 --- a/platform/core/src/utils/sortInstancesByPosition.ts +++ b/platform/core/src/utils/sortInstancesByPosition.ts @@ -15,10 +15,8 @@ export default function sortInstances(instances: Array) { return instances; } - const { - ImagePositionPatient: referenceImagePositionPatient, - ImageOrientationPatient, - } = instances[Math.floor(instances.length / 2)]; // this prevents getting scout image as test image + const { ImagePositionPatient: referenceImagePositionPatient, ImageOrientationPatient } = + instances[Math.floor(instances.length / 2)]; // this prevents getting scout image as test image if (!referenceImagePositionPatient || !ImageOrientationPatient) { return instances; @@ -49,11 +47,7 @@ export default function sortInstances(instances: Array) { const positionVector = vec3.create(); - vec3.sub( - positionVector, - referenceImagePositionPatient, - imagePositionPatient - ); + vec3.sub(positionVector, referenceImagePositionPatient, imagePositionPatient); const distance = vec3.dot(positionVector, scanAxisNormal); diff --git a/platform/core/src/utils/sortStudy.ts b/platform/core/src/utils/sortStudy.ts index a39c8a16a6..8928a2f348 100644 --- a/platform/core/src/utils/sortStudy.ts +++ b/platform/core/src/utils/sortStudy.ts @@ -1,19 +1,17 @@ import isLowPriorityModality from './isLowPriorityModality'; const compareSeriesDateTime = (a, b) => { - const seriesDateA = Date.parse( - `${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}` - ); - const seriesDateB = Date.parse( - `${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}` - ); + const seriesDateA = Date.parse(`${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}`); + const seriesDateB = Date.parse(`${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}`); return seriesDateA - seriesDateB; }; const defaultSeriesSort = (a, b) => { const seriesNumberA = a.SeriesNumber ?? a.seriesNumber; const seriesNumberB = b.SeriesNumber ?? b.seriesNumber; - if (seriesNumberA === seriesNumberB) return compareSeriesDateTime(a, b); + if (seriesNumberA === seriesNumberB) { + return compareSeriesDateTime(a, b); + } return seriesNumberA - seriesNumberB; }; @@ -24,12 +22,8 @@ const defaultSeriesSort = (a, b) => { * @param {Object} secondSeries */ function seriesInfoSortingCriteria(firstSeries, secondSeries) { - const aLowPriority = isLowPriorityModality( - firstSeries.Modality ?? firstSeries.modality - ); - const bLowPriority = isLowPriorityModality( - secondSeries.Modality ?? secondSeries.modality - ); + const aLowPriority = isLowPriorityModality(firstSeries.Modality ?? firstSeries.modality); + const bLowPriority = isLowPriorityModality(secondSeries.Modality ?? secondSeries.modality); if (aLowPriority) { return bLowPriority ? defaultSeriesSort(secondSeries, firstSeries) : 1; @@ -67,8 +61,11 @@ const sortStudySeries = ( seriesSortingCriteria = seriesSortCriteria.default, sortFunction = null ) => { - if (typeof sortFunction === 'function') return sortFunction(series); - else return series.sort(seriesSortingCriteria); + if (typeof sortFunction === 'function') { + return sortFunction(series); + } else { + return series.sort(seriesSortingCriteria); + } }; /** @@ -117,10 +114,4 @@ export default function sortStudy( return study; } -export { - sortStudy, - sortStudySeries, - sortStudyInstances, - sortingCriteria, - seriesSortCriteria, -}; +export { sortStudy, sortStudySeries, sortStudyInstances, sortingCriteria, seriesSortCriteria }; diff --git a/platform/core/src/utils/splitComma.ts b/platform/core/src/utils/splitComma.ts index 4354e0ce0b..922a944e1b 100644 --- a/platform/core/src/utils/splitComma.ts +++ b/platform/core/src/utils/splitComma.ts @@ -1,6 +1,8 @@ -/** Splits a list of stirngs by commas within the strings */ +/** Splits a list of strings by commas within the strings */ const splitComma = (strings: string[]): string[] => { - if (!strings) return null; + if (!strings) { + return null; + } for (let i = 0; i < strings.length; i++) { const comma = strings[i].indexOf(','); if (comma !== -1) { @@ -21,10 +23,10 @@ const getSplitParam = ( lowerCaseKey: string, params = new URLSearchParams(window.location.search) ): string[] => { - const sourceKey = [...params.keys()].find( - it => it.toLowerCase() === lowerCaseKey - ); - if (!sourceKey) return; + const sourceKey = [...params.keys()].find(it => it.toLowerCase() === lowerCaseKey); + if (!sourceKey) { + return; + } return splitComma(params.getAll(sourceKey)); }; diff --git a/platform/core/src/utils/subscribeToNextViewportGridChange.ts b/platform/core/src/utils/subscribeToNextViewportGridChange.ts index e6c72d0485..6ffbc4a1c6 100644 --- a/platform/core/src/utils/subscribeToNextViewportGridChange.ts +++ b/platform/core/src/utils/subscribeToNextViewportGridChange.ts @@ -1,7 +1,7 @@ import { ViewportGridService } from '../services'; /** - * Subscribes to the very next LAYOUT_CHANGED or GRID_STATE_CHANGED event that + * Subscribes to the very next LAYOUT_CHANGED event that * is not currently on the event queue. The subscriptions are made on a 'zero' * timeout so as to avoid responding to any of those events currently on the event queue. * The subscription persists only for a single invocation of either event. @@ -20,14 +20,7 @@ function subscribeToNextViewportGridChange( }; const subscriptions = [ - viewportGridService.subscribe( - viewportGridService.EVENTS.LAYOUT_CHANGED, - callback - ), - viewportGridService.subscribe( - viewportGridService.EVENTS.GRID_STATE_CHANGED, - callback - ), + viewportGridService.subscribe(viewportGridService.EVENTS.LAYOUT_CHANGED, callback), ]; }; diff --git a/platform/docs/CHANGELOG.md b/platform/docs/CHANGELOG.md new file mode 100644 index 0000000000..bd125c0e21 --- /dev/null +++ b/platform/docs/CHANGELOG.md @@ -0,0 +1,250 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + + +### Bug Fixes + +* **keyCloak:** fix openresty keycloak deployment recipe ([#3655](https://github.com/OHIF/Viewers/issues/3655)) ([2d7721c](https://github.com/OHIF/Viewers/commit/2d7721cb581f55dc49e3baeca2411b18dd78ad74)) + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + + +### Bug Fixes + +* **nginx archive recipe:** Fixes to various configuration files. ([#3624](https://github.com/OHIF/Viewers/issues/3624)) ([3ce7225](https://github.com/OHIF/Viewers/commit/3ce72254b390f32c9aa207a0589e688805e2659d)) + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package ohif-docs + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package ohif-docs diff --git a/platform/docs/docs/README.md b/platform/docs/docs/README.md index 05896daea4..3f6446dd13 100644 --- a/platform/docs/docs/README.md +++ b/platform/docs/docs/README.md @@ -18,12 +18,11 @@ Key features: Offers a Data Source API for communicating with archives over proprietary API formats. - Provides a plugin framework for creating task-based workflow modes which can - re-use core functionality. + reuse core functionality. - Beautiful user interface (UI) designed with extensibility in mind. UI components available in a reusable component library built with React.js and Tailwind CSS -![OHIF Viewer Screenshot](./assets/img/OHIF-Viewer.jpg)
@@ -32,6 +31,18 @@ Key features:
+
+
+ + + +| | | | +| :-: | :--- | :--- | +| Measurement tracking | Measurement Tracking | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5) | +| Segmentations | Labelmap Segmentations | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046) | +| Hanging Protocols | Fusion and Custom Hanging protocols | [Demo](https://viewer.ohif.org/tmtv?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463) | +| Microscopy | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.275741864483510678566144889372061815320) | +| Volume Rendering | Volume Rendering | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&hangingprotocolId=mprAnd3DVolumeViewport) | diff --git a/platform/docs/docs/assets/img/browser-console-non-secure-context.png b/platform/docs/docs/assets/img/browser-console-non-secure-context.png new file mode 100644 index 0000000000..3fb4f1bcdc Binary files /dev/null and b/platform/docs/docs/assets/img/browser-console-non-secure-context.png differ diff --git a/platform/docs/docs/assets/img/cors-browser-console-errors.png b/platform/docs/docs/assets/img/cors-browser-console-errors.png new file mode 100644 index 0000000000..0b8d062ca7 Binary files /dev/null and b/platform/docs/docs/assets/img/cors-browser-console-errors.png differ diff --git a/platform/docs/docs/assets/img/cors-network-panel-errors.png b/platform/docs/docs/assets/img/cors-network-panel-errors.png new file mode 100644 index 0000000000..3820b76431 Binary files /dev/null and b/platform/docs/docs/assets/img/cors-network-panel-errors.png differ diff --git a/platform/docs/docs/assets/img/data-source-configuration-ui.png b/platform/docs/docs/assets/img/data-source-configuration-ui.png new file mode 100644 index 0000000000..f04956e075 Binary files /dev/null and b/platform/docs/docs/assets/img/data-source-configuration-ui.png differ diff --git a/platform/docs/docs/assets/img/OHIF-Viewer.jpg b/platform/docs/docs/assets/img/demo-measurements.jpg similarity index 100% rename from platform/docs/docs/assets/img/OHIF-Viewer.jpg rename to platform/docs/docs/assets/img/demo-measurements.jpg diff --git a/platform/docs/docs/assets/img/demo-microscopy.png b/platform/docs/docs/assets/img/demo-microscopy.png new file mode 100644 index 0000000000..ad54801261 Binary files /dev/null and b/platform/docs/docs/assets/img/demo-microscopy.png differ diff --git a/platform/docs/docs/assets/img/demo-ptct.png b/platform/docs/docs/assets/img/demo-ptct.png new file mode 100644 index 0000000000..83472f0766 Binary files /dev/null and b/platform/docs/docs/assets/img/demo-ptct.png differ diff --git a/platform/docs/docs/assets/img/demo-segmentation.png b/platform/docs/docs/assets/img/demo-segmentation.png new file mode 100644 index 0000000000..3482a15c51 Binary files /dev/null and b/platform/docs/docs/assets/img/demo-segmentation.png differ diff --git a/platform/docs/docs/assets/img/demo-volumeRendering.png b/platform/docs/docs/assets/img/demo-volumeRendering.png new file mode 100644 index 0000000000..4c508ab6d3 Binary files /dev/null and b/platform/docs/docs/assets/img/demo-volumeRendering.png differ diff --git a/platform/docs/docs/assets/img/google-create-credentials.png b/platform/docs/docs/assets/img/google-create-credentials.png new file mode 100644 index 0000000000..e6534fe080 Binary files /dev/null and b/platform/docs/docs/assets/img/google-create-credentials.png differ diff --git a/platform/docs/docs/assets/img/google-enable-apis.png b/platform/docs/docs/assets/img/google-enable-apis.png new file mode 100644 index 0000000000..434bf77771 Binary files /dev/null and b/platform/docs/docs/assets/img/google-enable-apis.png differ diff --git a/platform/docs/docs/assets/img/google-healthcare-service-agent-warning.png b/platform/docs/docs/assets/img/google-healthcare-service-agent-warning.png new file mode 100644 index 0000000000..c98ddca97c Binary files /dev/null and b/platform/docs/docs/assets/img/google-healthcare-service-agent-warning.png differ diff --git a/platform/docs/docs/assets/img/google-manually-add-scopes.png b/platform/docs/docs/assets/img/google-manually-add-scopes.png new file mode 100644 index 0000000000..9500b8557d Binary files /dev/null and b/platform/docs/docs/assets/img/google-manually-add-scopes.png differ diff --git a/platform/docs/docs/assets/img/google-oauth-consent-steps.png b/platform/docs/docs/assets/img/google-oauth-consent-steps.png new file mode 100644 index 0000000000..67d4a42ad7 Binary files /dev/null and b/platform/docs/docs/assets/img/google-oauth-consent-steps.png differ diff --git a/platform/docs/docs/assets/img/google-projects-drop-down.png b/platform/docs/docs/assets/img/google-projects-drop-down.png new file mode 100644 index 0000000000..fb203105fc Binary files /dev/null and b/platform/docs/docs/assets/img/google-projects-drop-down.png differ diff --git a/platform/docs/docs/assets/img/google-provided-accounts-checkbox.png b/platform/docs/docs/assets/img/google-provided-accounts-checkbox.png new file mode 100644 index 0000000000..e129854517 Binary files /dev/null and b/platform/docs/docs/assets/img/google-provided-accounts-checkbox.png differ diff --git a/platform/docs/docs/assets/img/iframe-basic.png b/platform/docs/docs/assets/img/iframe-basic.png new file mode 100644 index 0000000000..25ea20708b Binary files /dev/null and b/platform/docs/docs/assets/img/iframe-basic.png differ diff --git a/platform/docs/docs/assets/img/iframe-headers.png b/platform/docs/docs/assets/img/iframe-headers.png new file mode 100644 index 0000000000..27d649d22f Binary files /dev/null and b/platform/docs/docs/assets/img/iframe-headers.png differ diff --git a/platform/docs/docs/assets/img/ohif-non-secure-context.png b/platform/docs/docs/assets/img/ohif-non-secure-context.png new file mode 100644 index 0000000000..b4ff6d0d35 Binary files /dev/null and b/platform/docs/docs/assets/img/ohif-non-secure-context.png differ diff --git a/platform/docs/docs/assets/img/self-signed-cert-advanced-warning.png b/platform/docs/docs/assets/img/self-signed-cert-advanced-warning.png new file mode 100644 index 0000000000..6f0c98b04e Binary files /dev/null and b/platform/docs/docs/assets/img/self-signed-cert-advanced-warning.png differ diff --git a/platform/docs/docs/assets/img/self-signed-cert-warning.png b/platform/docs/docs/assets/img/self-signed-cert-warning.png new file mode 100644 index 0000000000..41df65f914 Binary files /dev/null and b/platform/docs/docs/assets/img/self-signed-cert-warning.png differ diff --git a/platform/docs/docs/configuration/configurationFiles.md b/platform/docs/docs/configuration/configurationFiles.md index b195e46f4c..09d5d7d1e6 100644 --- a/platform/docs/docs/configuration/configurationFiles.md +++ b/platform/docs/docs/configuration/configurationFiles.md @@ -10,10 +10,11 @@ After following the steps outlined in OHIF Viewer has data for several studies and their images. You didn't add this data, so where is it coming from? -By default, the viewer is configured to connect to a remote server hosted by the -nice folks over at [dcmjs.org][dcmjs-org]. While convenient for getting started, -the time may come when you want to develop using your own data either locally or -remotely. +By default, the viewer is configured to connect to a Amazon S3 bucket that is hosting +a Static WADO server (see [Static WADO DICOMWeb](https://github.com/RadicalImaging/static-dicomweb)). +By default we use `default.js` for the configuration file. You can change this by setting the `APP_CONFIG` environment variable +and select other options such as `config/local_orthanc.js` or `config/google.js`. + ## Configuration Files @@ -33,10 +34,10 @@ window.config = { showStudyList: true, dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'DCM4CHEE', wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', @@ -48,6 +49,7 @@ window.config = { enableStudyLazyLoad: true, supportsFuzzyMatching: true, supportsWildcard: true, + omitQuotationForMultipartRequest: true, }, }, ], @@ -82,10 +84,10 @@ window.config = ({ servicesManager } = {}) => { routerBasename: '/', dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'DCM4CHEE', wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', @@ -97,6 +99,7 @@ window.config = ({ servicesManager } = {}) => { enableStudyLazyLoad: true, supportsFuzzyMatching: true, supportsWildcard: true, + omitQuotationForMultipartRequest: true, }, }, ], @@ -105,21 +108,86 @@ window.config = ({ servicesManager } = {}) => { }; ``` + + + + ## Configuration Options -Here are a list of some options available: +Here are a list of some options available: +- `disableEditing`: If true, it disables editing in OHIF, hiding edit buttons in segmentation + panel and locking already stored measurements. - `maxNumberOfWebWorkers`: The maximum number of web workers to use for decoding. Defaults to minimum of `navigator.hardwareConcurrency` and what is specified by `maxNumberOfWebWorkers`. Some windows machines require smaller values. +- `acceptHeader` : accept header to request specific dicom transfer syntax ex : [ 'multipart/related; type=image/jls; q=1', 'multipart/related; type=application/octet-stream; q=0.1' ] +- `requestTransferSyntaxUID` : Request a specific Tansfer syntax from dicom web server ex: 1.2.840.10008.1.2.4.80 (applied only if acceptHeader is not set) - `omitQuotationForMultipartRequest`: Some servers (e.g., .NET) require the `multipart/related` request to be sent without quotation marks. Defaults to `false`. If your server doesn't require this, then setting this flag to `true` might improve performance (by removing the need for preflight requests). Also note that if auth headers are used, a preflight request is required. - `maxNumRequests`: The maximum number of requests to allow in parallel. It is an object with keys of `interaction`, `thumbnail`, and `prefetch`. You can specify a specific number for each type. +- `modesConfiguration`: Allows overriding modes configuration. + - Example config: + ```js + modesConfiguration: { + '@ohif/mode-longitudinal': { + displayName: 'Custom Name', + routeName: 'customRouteName', + routes: [ + { + path: 'customPath', + layoutTemplate: () => { + /** Custom Layout */ + return { + id: ohif.layout, + props: { + leftPanels: [tracked.thumbnailList], + rightPanels: [dicomSeg.panel, tracked.measurements], + rightPanelDefaultClosed: true, + viewports: [ + { + namespace: tracked.viewport, + displaySetsToDisplay: [ohif.sopClassHandler], + }, + ], + }, + }; + }, + }, + ], + } + }, + ``` + Note: Although the mode configuration is passed to the mode factory function, it is up to the particular mode itself if its going to use it to allow overwriting its original configuration e.g. + ```js + function modeFactory({ modeConfiguration }) { + return { + id, + routeName: 'viewer', + displayName: 'Basic Viewer', + ... + onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { + ... + }, + /** + * This mode allows its configuration to be overwritten by + * destructuring the modeConfiguration value from the mode fatory function + * at the end of the mode configuration definition. + */ + ...modeConfiguration, + }; + } + ``` - `showLoadingIndicator`: (default to true), if set to false, the loading indicator will not be shown when navigating between studies. +- `use16BitDataType`: (default to false), if set to true, it will use 16 bit data type for the image data wherever possible which has + significant impact on reducing the memory usage. However, the 16Bit textures require EXT_texture_norm16 extension in webGL 2.0 (you can check if you have it here https://webglreport.com/?v=2). In addition to the extension, there are reported problems for Intel Macs that might cause the viewer to crash. In summary, it is great a configuration if you have support for it. +- `useSharedArrayBuffer` (default to true), for volume loading we use sharedArrayBuffer to be able to + load the volume progressively as the data arrives (each webworker has the shared buffer and can write to it). However, there might be certain environments that do not support sharedArrayBuffer. In that case, you can set this flag to false and the viewer will use the regular arrayBuffer which might be slower for large volume loading. +- `supportsWildcard`: (default to false), if set to true, the datasource will support wildcard matching for patient name and patient id. - `dangerouslyUseDynamicConfig`: Dynamic config allows user to pass `configUrl` query string. This allows to load config without recompiling application. If the `configUrl` query string is passed, the worklist and modes will load from the referenced json rather than the default .env config. If there is no `configUrl` path provided, the default behaviour is used and there should not be any deviation from current user experience.
Points to consider while using `dangerouslyUseDynamicConfig`:
- User have to enable this feature by setting `dangerouslyUseDynamicConfig.enabled:true`. By default it is `false`. - - Regex helps to avoid easy exploit. Dafault is `/.*/`. Setup your own regex to choose a specific source of configuration only. + - Regex helps to avoid easy exploit. Default is `/.*/`. Setup your own regex to choose a specific source of configuration only. - System administrators can return `cross-origin: same-origin` with OHIF files to disallow any loading from other origin. It will block read access to resources loaded from a different origin to avoid potential attack vector. - Example config: ```js @@ -134,7 +202,38 @@ Example 2, to restricts to either hosptial.com or othersite.com.
`regex: /(https:\/\/hospital.com(\/[0-9A-Za-z.]+)*)|(https:\/\/othersite.com(\/[0-9A-Za-z.]+)*)/`
Example usage:
`http://localhost:3000/?configUrl=http://localhost:3000/config/example.json`
- +- `onConfiguration`: Currently only available for DicomWebDataSource, this option allows the interception of the data source configuration for dynamic values e.g. values coming from url params or query params. Here is an example of building the dicomweb datasource configuration object with values that are based on the route url params: + ``` + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'gcpdicomweb', + configuration: { + friendlyName: 'GCP DICOMWeb Server', + name: 'gcpdicomweb', + qidoSupportsIncludeField: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: false, + singlepart: 'bulkdata,video,pdf', + useBulkDataURI: false, + onConfiguration: (dicomWebConfig, options) => { + const { params } = options; + const { project, location, dataset, dicomStore } = params; + const pathUrl = `https://healthcare.googleapis.com/v1/projects/${project}/locations/${location}/datasets/${dataset}/dicomStores/${dicomStore}/dicomWeb`; + return { + ...dicomWebConfig, + wadoRoot: pathUrl, + qidoRoot: pathUrl, + wadoUri: pathUrl, + wadoUriRoot: pathUrl, + }; + }, + }, + }, + ``` +This configuration would allow the user to build a dicomweb configuration from a GCP healthcare api path e.g. http://localhost:3000/projects/your-gcp-project/locations/us-central1/datasets/your-dataset/dicomStores/your-dicom-store/study/1.3.6.1.4.1.1234.5.2.1.1234.1234.123123123123123123123123123123 [dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light [dcm4chee-docker]: https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker diff --git a/platform/docs/docs/configuration/dataSources/configuration-ui.md b/platform/docs/docs/configuration/dataSources/configuration-ui.md new file mode 100644 index 0000000000..15f3b0bcba --- /dev/null +++ b/platform/docs/docs/configuration/dataSources/configuration-ui.md @@ -0,0 +1,172 @@ +--- +sidebar_position: 6 +sidebar_label: Configuration UI +--- + +# Configuration UI + +OHIF provides for a generic mechanism for configuring a data source. This is +most useful for those organizations with several data sources +that share common (path) hierarchies. For example, an organization may have several DICOM stores +in the Google Cloud Healthcare realm where each is organized into various projects, +location, data sets and DICOM stores. + +By implementing the `BaseDataSourceConfigurationAPI` and +`BaseDataSourceConfigurationAPIItem` in an [OHIF extension](../../platform/extensions/index.md), a data source can +be made configurable via the generic UI as is depicted below for a +Google Cloud Healthcare data source. + +![Data source configuration UI](../../assets/img/data-source-configuration-ui.png) + +## `BaseDataSourceConfigurationAPIItem` interface + +Each (path) item of a data source is represented by an instance of this interface. +At the very least each of these items must expose two properties: + +|Property |Description| +|---------|-----------| +|id|a string that uniquely identifies the item| +|name|a human readable name for the item| + +Note that information such as where in the path hierarchy the item exists +has been omitted, but can be added in any concrete class that might implement this +interface. For example, the the Google Cloud Healthcare implementation of this +interface (`GoogleCloudDataSourceConfigurationAPIItem`) adds an `itemType` +(i.e. projects, locations, datasets, or dicomStores) and `url`. + +## `BaseDataSourceConfigurationAPI` interface + +The implementation of this interface is at the heart of the configuration process. +It possesses several methods for building up a data source path based on various +`BaseDataSourceConfigurationAPIItem` objects that are set via calls to the `setCurrentItem` +method. + +The constructor for the concrete class implementation should accept whatever +parameters are necessary for configuring the data source. One argument +to the constructor must be the string identifying the name of the data source +to be configured. Furthermore, considering that the `ExtensionManager` possesses +API to configure and update data sources, it too will likely be an argument to +the constructor. See [Creation via Customization Module](#creation-via-customization-module) +for more information on how the constructor is invoked via a factory method. + +For an example implementation of this interface see `GoogleCloudDataSourceConfigurationAPI`. + +### Interface Methods + +Each of the following subsections lists a method of the interface with a description +detailing what the method should do. + +#### `getItemLabels` + +Gets the i18n labels (i.e. the i18n lookup keys) for each of the configurable items +of the data source configuration API. For example, for the Google Cloud Healthcare +API, this would be `['Project', 'Location', 'Data set', 'DICOM store']`. + +Besides the configurable item labels themselves, several other string look ups +are used base on EACH of the labels returned by this method. +For instance, for the label `{itemLabel}``, the following strings are fetched for +translation... +1. `No {itemLabel} available` + - used to indicate no such items are available + - for example, for Google, `No Project available` would be 'No projects available' +2. `Select {itemLabel}` + - used to direct selection of the item + - for example, for Google, `Select Project` would be 'Select a project' +3. `Error fetching {itemLabel} list` + - used to indicate an error occurred fetching the list of items + - usually accompanied by the error itself + - for example, for Google, `Error fetching Project list` would be 'Error fetching projects' +4. `Search {itemLabel} list` + - used as the placeholder text for filtering a list of items + - for example, for Google, `Search Project list` would be 'Search projects' + +#### `initialize` + +Initializes the cloud server API and returns the top-level sub-items +that can be chosen to begin the process of configuring a data source. +For example, for the Google Cloud Healthcare API, this would perform the initial request +to fetch the top level projects for the logged in user account. + +#### `setCurrentItem` + +Sets the current path item that is passed as an argument to the method and +returns the sub-items of that item +that can be further chosen to configure a data source. +When setting the last configurable item of the data source (path), this method +returns an empty list AND configures the active data source with the selected +items path. + +For example, for the Google Cloud Healthcare API, this would take the current item +(say a data set) and queries and returns its sub-items (i.e. all of the DICOM stores +contained in that data set). Furthermore, whenever the item to set is a DICOM store, +the Google Cloud Healthcare API implementation would update the OHIF data source +associated with this instance to point to that DICOM store. + +#### `getConfiguredItems` + +Gets the list of items currently configured for the data source associated with +this API instance. The resultant array must be the same length as the result of +`getItemLabels`. Furthermore the items returned should correspond (index-wise) +with the labels returned from `getItemLabels`. + +## Creation via Customization Module + +The generic UI (i.e. `DataSourceConfigurationComponent`) uses the +[OHIF UI customization service](../../platform/services/ui/customization-service.md) to +instantiate the `BaseDataSourceConfigurationAPI` instance to configure a data source. + +A UI configurable data source should have a `configurationAPI` field as part of +its `configuration` in the OHIF config file. The `configurationAPI` value is the +customization id of the customization module that provides the factory method +to instantiate the `BaseDataSourceConfigurationAPI` instance. + +For example, the following is a snippet of a Google Cloud Healthcare data source configuration. + +```js + dataSources: [ + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'google-dicomweb', + configuration: { + name: 'GCP', + wadoUriRoot: 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/...', + ... + configurationAPI: 'ohif.dataSourceConfigurationAPI.google', + ... + }, + }, + ] +``` + +This suggests that the factory method is provided by the `'ohif.dataSourceConfigurationAPI.google'` +customization module. That customization module is provided by the `default` extension's +`getCustomizationModule` and looks something like the following snippet of code. Notice that +the factory method's name MUST be `factory` and accept one argument - the data source name. +Furthermore note how the constructor is invoked with anything required by the concrete configuration +API class. + +```js +export default function getCustomizationModule({ + servicesManager, + extensionManager, +}) { + return [ + { + name: 'default', + value: [ + { + // The factory for creating an instance of a BaseDataSourceConfigurationAPI for Google Cloud Healthcare + id: 'ohif.dataSourceConfigurationAPI.google', + factory: (dataSourceName: string) => + new GoogleCloudDataSourceConfigurationAPI( + dataSourceName, + servicesManager, + extensionManager + ), + }, + ], + }, + ]; +} + +``` diff --git a/platform/docs/docs/configuration/dataSources/dicom-web.md b/platform/docs/docs/configuration/dataSources/dicom-web.md index dcf4749ca8..e5949c72be 100644 --- a/platform/docs/docs/configuration/dataSources/dicom-web.md +++ b/platform/docs/docs/configuration/dataSources/dicom-web.md @@ -74,7 +74,7 @@ _Upload your first Study:_ #### Orthanc: Learn More You can see the `docker-compose.yml` file this command runs at -[`/.docker/Nginx-Orthanc/`][orthanc-docker-compose], and more on +[`/platform/app/.recipes/Nginx-Orthanc`][orthanc-docker-compose], and more on Orthanc for Docker in [Orthanc's documentation][orthanc-docker]. #### Connecting to Orthanc @@ -131,10 +131,10 @@ window.config = { showStudyList: true, dataSources: [ { - friendlyName: 'dcmjs DICOMWeb Server', namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', sourceName: 'dicomweb', configuration: { + friendlyName: 'dcmjs DICOMWeb Server', name: 'DCM4CHEE', wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', @@ -160,6 +160,13 @@ The following properties can be added to the `configuration` property of each da ##### `dicomUploadEnabled` A boolean indicating if the DICOM upload to the data source is permitted/accepted or not. A value of true provides a link on the OHIF work list page that allows for DICOM files from the local file system to be uploaded to the data source +:::tip +The [OHIF plugin for Orthanc](https://book.orthanc-server.com/plugins/ohif.html) by default utilizes the DICOM JSON data +source and it has been discovered that only those studies uploaded to Orthanc AFTER the plugin has been installed are +available as DICOM JSON. As such, if the OHIF plugin for Orthanc is desired for studies uploaded prior to installing the plugin, +then consider switching to using [DICOMweb instead](https://book.orthanc-server.com/plugins/ohif.html#using-dicomweb). +::: + ![toolbarModule-layout](../../assets/img/uploader.gif) #### `singlepart` @@ -212,7 +219,7 @@ An overview of steps for running OHIF Viewer using a local DCM4CHEE is shown below:
- +
[dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light diff --git a/platform/docs/docs/configuration/dataSources/static-files.md b/platform/docs/docs/configuration/dataSources/static-files.md index 296a879965..820e20a1a2 100644 --- a/platform/docs/docs/configuration/dataSources/static-files.md +++ b/platform/docs/docs/configuration/dataSources/static-files.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 sidebar_label: Static Files --- diff --git a/platform/docs/docs/configuration/url.md b/platform/docs/docs/configuration/url.md index 444146a198..fc8eaa2e94 100644 --- a/platform/docs/docs/configuration/url.md +++ b/platform/docs/docs/configuration/url.md @@ -59,9 +59,11 @@ If you happen to have multiple data sources configured, you can filter the WorkList by adding the `dataSources` query parameter. ```js -/?dataSourcename=orthanc +/?dataSources=orthanc ``` +Note: you should pass the `sourceName` of the data source in the configuration file (not the friendly name nor the name) + :::tip You can add `sortBy` and `sortDirection` query parameters to sort the WorkList @@ -109,7 +111,7 @@ You can open more than one study in the Viewer by adding the `StudyInstanceUIDs` :::tip -You can ues this feature to open a current and prior study in the Viewer. +You can use this feature to open a current and prior study in the Viewer. Read more in the [Hanging Protocol Module](../platform/extensions/modules/hpModule.md#matching-on-prior-study-with-uid) section. You can also use commas to separate values. diff --git a/platform/docs/docs/deployment/authorization.md b/platform/docs/docs/deployment/authorization.md index daba7454e7..281b8f91bc 100644 --- a/platform/docs/docs/deployment/authorization.md +++ b/platform/docs/docs/deployment/authorization.md @@ -1,5 +1,5 @@ --- -sidebar_position: 8 +sidebar_position: 5 sidebar_label: Authorization --- diff --git a/platform/docs/docs/deployment/build-for-production.md b/platform/docs/docs/deployment/build-for-production.md index b1fa6999cc..217f38da28 100644 --- a/platform/docs/docs/deployment/build-for-production.md +++ b/platform/docs/docs/deployment/build-for-production.md @@ -70,7 +70,7 @@ and registered extension's features, are configured using this file. The easiest way to apply your own configuration is to modify the `default.js` file. For more advanced configuration options, check out our -[configuration essentials guide](../configuration/index.md). +[configuration essentials guide](../configuration/configurationFiles.md). ## Next Steps @@ -108,10 +108,34 @@ yarn global add http-server npx http-server ./dist ``` +:::caution +In the video below notice that there is `platform/viewer` which has been renamed to `platform/app` in the latest version +::: +
+### Build for non-root path + +If you would like to access the viewer from a non-root path (e.g., `/my-awesome-viewer` instead of `/`), +You can achieve so by using the `PUBLIC_URL` environment variable AND the `routerBasename` configuration option. + +1. use a config (e.g. config/myConfig.js) file that is using the `routerBasename` of your choice `/my-awesome-viewer` (note there is only one / - it is not /my-awesome-viewer/). +2. build the viewer with `PUBLIC_URL=/my-awesome-viewer/ APP_CONFIG=config/myConfig.js yarn build` (note there are two / - it is not /my-awesome-viewer). + + +:::tip +The PUBLIC_URL tells the application where to find the static assets and the routerBasename will tell the application how to handle the routes +::: + +:::tip +Testing, you can use `npx http-server` to serve the files in the generated `dist` folder and access the viewer from `http://localhost:8080/my-awesome-viewer`. To achieve +so, you should first rename the `dist` folder to `my-awesome-viewer` and then change the working directory +to the `platform/app` folder and run `npx http-server ./`. Then on the browser, you can access the viewer from `http://localhost:8080/my-awesome-viewer` +::: + + ### Automating Builds and Deployments If you found setting up your environment and running all of these steps to be a @@ -127,6 +151,6 @@ web application. For a starting point, check out this repository's own use of: [circleci]: https://circleci.com/gh/OHIF/Viewers [circleci-config]: https://github.com/OHIF/Viewers/blob/master/.circleci/config.yml [netlify]: https://app.netlify.com/sites/ohif/deploys -[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/netlify.toml +[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/platform/app/netlify.toml [build-deploy-preview.sh]: https://github.com/OHIF/Viewers/blob/master/.netlify/build-deploy-preview.sh diff --git a/platform/docs/docs/deployment/cors.md b/platform/docs/docs/deployment/cors.md new file mode 100644 index 0000000000..7cfb7558cc --- /dev/null +++ b/platform/docs/docs/deployment/cors.md @@ -0,0 +1,308 @@ +--- +sidebar_position: 6 +--- + +# Cross-Origin Information for OHIF + +This document describes various security configurations, settings and environments/contexts needed to fully leverage OHIF’s capabilities. One may need some configurations while others might need ALL of them - it all depends on the environment OHIF is expected to run in. + +In particular, three of OHIF’s features depend on these configurations: +- [OHIF’s use of SharedArrayBuffer](#sharedarraybuffer) +- [Embedding OHIF in an iframe](#embedding-ohif-in-an-iframe) +- [XMLHttpRequests to fetch data from data sources](#cors-in-ohif) + + +## SharedArrayBuffer +A `SharedArrayBuffer` is a JavaScript object that is similar to an `ArrayBuffer` but can be shared between web workers and the window that spawned them via the `postMessage` API. See [SharedArrayBuffer in MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) for more information. + +### Security Requirements + +In order to use `SharedArrayBuffer` objects in the browser, the following security conditions must be met: + +- The page must be served in a [secure context](#secure-context). +- The page must have [cross-origin isolation](#cross-origin-isolation) enabled. + +### `SharedArrayBuffer` in OHIF + +OHIF uses `SharedArrayBuffer` in its volume loader (from Cornerstone3D). It comes with the benefit of improved performance and optimization at the cost of some configuration to use it. + +As such, if the following popup is shown when launching OHIF then the OHIF server will have to be configured to permit the loading of volumetric images and data. Note that stack viewports are still available and functional even when this error is present. + +![OHIF in non-secure context](../assets/img/ohif-non-secure-context.png) + +To better determine which (if not all) of the [security requirements](#security-requirements) are lacking, have a look at the browser console. + +Output in the console similar to the following indicates that OHIF is not running in a [secure context](#secure-context). + +![browser console for non-secure context](../assets/img/browser-console-non-secure-context.png) + +Absence of the above error in the console together with the presence of the Cross Origin Isolation popup warning, likely indicates that either or both of the [COOP](#coop---cross-origin-opener-policy) and/or [COEP](#coep---cross-origin-embedder-policy) headers are not set for OHIF. + +## Embedding OHIF in an iframe + +As described [here](./iframe.md), there are cases where OHIF will be embedded in an iframe. The following links provide more information for setting up and configuring OHIF to work in an iframe: + +- [OHIF iframe documentation](./iframe.md#static-build) +- [OHIF as a Cross-origin Resource in an iframe](#ohif-as-a-cross-origin-resource-in-an-iframe) + +## Secure Context + +MDN defines a secure context as [“a Window or Worker for which certain minimum standards of authentication and confidentiality are met.“](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) + +Any local URL is considered secure. The following are some examples of local URLs that are considered secure… +- http://localhost +- http://127.0.0.1:3000 + +URLs that are NOT local must be delivered over `https://` or `wss://` (i.e. TLS) to be considered secure. See [When is a context considered secure](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure) in MDN for more information. + +### iframes + +A page embedded in an iframe is considered secure if it itself and every one of its embedding ancestors are delivered securely. Otherwise it is deemed insecure. + +### Why does OHIF require a secure context? + +Beyond all of the inherent benefits of a secure connection, OHIF requires a secure context so that it can utilize [SharedArrayBuffer](#sharedarraybuffer) objects for volume rendering. + +### Configuring/setting up a secure context + +[Local URLs are considered secure](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure), and as such whenever OHIF is accessed via a local URL (e.g. http://localhost:3000) it is running in a secure context. For example, in a development environment using the default webpack setup, OHIF can be deployed and accessed in a secure context at http://localhost:3000. + +The best alternative is to host OHIF over HTTPS. + +:::tip +OHIF can be served over HTTPS in a variety of ways (these are just some examples). +- Website hosting services that offer HTTPS deployment (e.g,. Netlify) or offer HTTPS load balancers (AWS, Google Cloud etc.) +- Setting up a reverse proxy (e.g. `nginx`) with a self-signed certificate that forwards requests to the OHIF server + - [An OHIF Docker image can be set up this way](./docker.md#ssl). +::: + +## Origin Definition + +According to [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Origin), a Web content’s origin is defined by the scheme (protocol), hostname (domain), and port of the URL used to access it. Two objects have the same origin only when the scheme, hostname, and port all match. + +## CORS - Cross-Origin Resource Sharing + +A cross-origin resource is a resource (e.g. image, JSON, etc) that is served by one origin and used/referenced by a different origin. + +CORS is the protocol utilized by web servers and browsers whereby a server of one origin identifies and/or restricts which of its resources that other origins (i.e. other than its own) a browser should allow access to. By default a browser does not permit cross-origin resource sharing. + +The CORS mechanism relies on the HTTP response headers from the server to indicate if a resource can be shared with a different origin. + +See the [MDN CORS article](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) for more information. + +### CORS HTTP Headers + +The header that mostly concerns OHIF is listed below and should be configured accordingly on the DICOMweb server or any data source that OHIF would make XMLHttpRequests to for its data. + +```http +Access-Control-Allow-Origin: `` | * +``` + +:::tip +The `Access-Control-Allow-Origin` header specifies which origins can access the served resource embedded in the response. + +Either a single, specific origin (i.e. ``) can be specified or ALL origins (i.e. *) + +See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#access-control-allow-origin) for more information. +::: + +### CORS in OHIF + +OHIF fetches and displays data and images from data sources. It invokes XMLHttpRequests to some data sources such as DICOMweb data sources to fetch the information to render. Typically, a DICOMweb server is hosted on a completely different origin than the one serving OHIF. As such, those XMLHttpRequests use CORS. + +### Troubleshooting CORS in OHIF + +The following is an example screenshot of the browser console when one of OHIF’s DICOMweb data source servers is not configured for CORS. + +![CORS browser console errors](../assets/img/cors-browser-console-errors.png) + +And the following is what is in the accompanying network tab. + +![CORS browser network panel errors](../assets/img/cors-network-panel-errors.png) + +:::info +Setting the appropriate CORS header varies per server or service that is hosting the data source. What follows below is just one example to remedy the problem. +::: + +:::tip +If Orthanc is the data source running in a Docker container composed with/behind nginx. And OHIF is being served at localhost:3000. The issue can be remedied by adding either of the following to Orthanc’s Docker container nginx.conf file. + +```nginx +add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always; +``` + +Or + +```nginx +add_header 'Access-Control-Allow-Origin' '*' always; +``` +::: + +## COOP - Cross-Origin Opener Policy + +The COOP HTTP response header restricts the global, root document of the page from being referenced and accessed by another cross-origin document that might open the page in a window. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy) for more information. + +### Header Values Pertinent to OHIF (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy#syntax) for more information) + +|Value|Description| +|-----|-----------| +|same-origin|Restricts the document to be referenced by openers of the same origin only.| + +### COOP in OHIF + +COOP is required for [SharedArrayBuffer](#sharedarraybuffer) usage in OHIF. See also [Troubleshooting Cross-origin Isolation in OHIF](#troubleshooting-cross-origin-isolation-in-ohif). + +## COEP - Cross-Origin Embedder Policy + +The COEP HTTP response header restricts cross-origin documents from being embedded into a document (e.g. in an iframe, video, image, etc). See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy) for more information. + +### Header Values Pertinent to OHIF (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy#syntax) for more information) + +|Value|Description| +|-----|-----------| +|require-corp|Permits the document to load either of the following embedded resources:
  • Those from the same origin
  • Cross-origin resources embedded by a DOM element that has the appropriate [crossorigin attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin) set
  • Cross-origin resources with the appropriate [CORP response header](#corp---cross-origin-resource-policy)
+|credentialless|See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy#syntax) for more information| + +### COEP in OHIF + +COEP is required for [SharedArrayBuffer](#sharedarraybuffer) usage in OHIF. See also [Troubleshooting Cross-origin Isolation in OHIF](#troubleshooting-cross-origin-isolation-in-ohif). + +## Cross-origin Isolation + +Cross-origin isolation is [enabled](https://web.dev/cross-origin-isolation-guide/#enable-cross-origin-isolation) for a web page when the following COOP and COEP headers are set. +- [COOP](#coop---cross-origin-opener-policy) with `same-origin` +- [COEP](#coep---cross-origin-embedder-policy) with `require-corp` or `credentialless` + +### iframe + +An iframe is considered to have cross-origin isolation enabled if it itself has the appropriate COOP and COEP headers set as well as every one of its embedding ancestors. + +### Troubleshooting Cross-origin Isolation in OHIF + +The [SharedArrayBuffer in OHIF](#sharedarraybuffer-in-ohif) section describes how to determine if there are problems with cross-origin isolation in OHIF. If it is determined that COOP and/or COEP is indeed an issue, then the COOP and COEP headers must be set for OHIF. How to accomplish this varies per server or service that is hosting OHIF. The following are just a few examples. + +:::tip +In the default dev environment, the following can be set in the webpack.pwa.js file… + +```javascript +devServer: { + headers: { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp" + } +} +``` +::: + +:::tip +If deploying OHIF using Netlify, the Netlify configuration [file](https://docs.netlify.com/configure-builds/file-based-configuration/) can be used to configure the headers as such… + +``` +[[headers]] + # Define which paths this specific [[headers]] block will cover. + for = "/*" + + [headers.values] + Cross-Origin-Opener-Policy = "same-origin" + Cross-Origin-Embedder-Policy = "require-corp" +``` +::: + +:::tip +If OHIF is served behind nginx, then the headers can be set in the nginx.conf file as follows. The [template nginx configuration file](https://github.com/OHIF/Viewers/blob/master/.docker/Viewer-v3.x/default.conf.template) for creating a [OHIF Docker image](./docker.md#building-the-docker-image) has an example of this too. +```nginx +server { + location / { + add_header Cross-Origin-Opener-Policy same-origin; + add_header Cross-Origin-Embedder-Policy require-corp; + } +} +``` +::: + +## CORP - Cross-Origin Resource Policy + +The CORP HTTP response header indicates which origins can read and use a resource. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy) for more information. + +### Header Values (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy#usage) for more information) + +|Value|Description| +|-----|-----------| +|same-site|Only requests from the same site can read the resource.| +|same-origin|Only requests from the same origin can read the resource.| +|cross-origin|Requests from any origin can read the resource. The value is useful and [exists](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy#relationship_to_cross-origin_embedder_policy_coep) primarily for letting documents with the [COEP require-corp value](#header-values-pertinent-to-ohif-see-mdn-for-more-information-1) know that the resource is ok to be embedded| + +### OHIF and CORP + +There are two scenarios where the CORP header is relevant to OHIF: + +- [PDF from a Cross Origin DICOMweb Data Source](#pdf-from-a-cross-origin-dicomweb-data-source) +- [OHIF as a Cross-origin Resource in an iframe](#ohif-as-a-cross-origin-resource-in-an-iframe) + +Both these scenarios stem from the fact that OHIF has to be served with the [COEP](#coep---cross-origin-embedder-policy) header to support [SharedArrayBuffer](#sharedarraybuffer). + +#### PDF from a Cross Origin DICOMweb Data Source + +There are some DICOMweb data sources (e.g. dcm4chee) whereby OHIF uses the data source’s `/rendered` endpoint to embed a DICOM PDF document in the OHIF DOM using an `` tag. + +As specified for the [COEP require-corp value](#header-values-pertinent-to-ohif-see-mdn-for-more-information-1), a page like OHIF with COEP header `require-corp` can embed cross-origin resources in DOM elements that have the [`crossorigin` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin) OR the resource is delivered with an appropriate CORP header. The `` tag does NOT support the `crossorigin` attribute. As such, the PDF must be delivered with a CORP header. + +:::tip +Setting the CORP header varies per server or service that is hosting the data source. The following is just one example. + +For a dcm4chee DICOMweb data source composed in Docker behind nginx, the CORP header can be configured in the nginx.conf file as such: + +```nginx +add_header 'Cross-Origin-Resource-Policy' 'cross-origin' always; +``` + +If the dcm4chee server and the OHIF server are hosted on the same site, then the following would also work: +```nginx +add_header 'Cross-Origin-Resource-Policy' 'same-site' always; +``` +::: + +#### OHIF as a Cross-origin Resource in an iframe + +There are cases where [OHIF is embedded in an iframe](./iframe.md) and the embedding page is from a different origin. Again due to the [security requirements for SharedArrayBuffer](#security-requirements), [both OHIF and the embedding page](#iframe) must have the appropriate COEP header. In this scenario, OHIF is the cross-origin resource and since the ` + + + + +[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration +[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc + +[config]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/src/config.js +[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/dockerfile +[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf +[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/orthanc.json +[config-keycloak]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json + diff --git a/platform/docs/docs/development/continous-integration.md b/platform/docs/docs/development/continuous-integration.md similarity index 100% rename from platform/docs/docs/development/continous-integration.md rename to platform/docs/docs/development/continuous-integration.md diff --git a/platform/docs/docs/development/getting-started.md b/platform/docs/docs/development/getting-started.md index 5f631a5d31..cafb0f538a 100644 --- a/platform/docs/docs/development/getting-started.md +++ b/platform/docs/docs/development/getting-started.md @@ -99,7 +99,7 @@ You should see the following output: ### 🎉 Celebrate 🎉
- +
### Building for Production diff --git a/platform/docs/docs/development/link.md b/platform/docs/docs/development/link.md new file mode 100644 index 0000000000..733ae5d0f2 --- /dev/null +++ b/platform/docs/docs/development/link.md @@ -0,0 +1,62 @@ +--- +sidebar_position: 8 +sidebar_label: Local Linking +--- + +# Introduction + +Local linking allows you to develop and test a library in the context of an application before it's published or when you encounter +a bug that you suspect is related to a library. With Yarn, this can be achieved through the yarn link command. + +The general procedure is as follows: + + +Link the Library: + +```sh +cd /path/to/library +yarn link +``` + +This command will create a symlink in a global directory for the library. + + +Link to the Application: + +```sh +cd /path/to/application +yarn link "library-name" +``` + +Creates a symlink from the global directory to the application's node_modules. + + +# Tutorial for linking Cornerstone3D to OHIF + +Below we demonstrate how to link Cornerstone3D to OHIF Viewer. This is useful for testing and debugging Cornerstone3D in the context of OHIF Viewer. + +
+ +
+ +::tip +Since `@cornerstonejs/tools` depends on `@cornerstonejs/core`, if you need the changes +you made in `@cornerstonejs/core` to be reflected in `@cornerstonejs/tools`, you need to +also link `@cornerstonejs/core` to `@cornerstonejs/tools`. + +```sh +cd /path/to/cornerstonejs-core +# for the core +yarn link + +cd /path/to/cornerstonejs-tools +yarn link "@cornerstonejs/core" + +# for the tools +yarn link + +# inside OHIF +cd /path/to/OHIFViewer +yarn link "@cornerstonejs/core" +yarn link "@cornerstonejs/tools" +``` diff --git a/platform/docs/docs/development/ohif-cli.md b/platform/docs/docs/development/ohif-cli.md index 7511e2bc73..0cd7ebc6aa 100644 --- a/platform/docs/docs/development/ohif-cli.md +++ b/platform/docs/docs/development/ohif-cli.md @@ -113,7 +113,7 @@ files: ### create-extension -Similar to the `create-extension` command, you can use the `create-extension` +Similar to the `create-mode` command, you can use the `create-extension` command to create a new extension template. This command will create a new extension template in the directory that you specify the path. diff --git a/platform/docs/docs/development/our-process.md b/platform/docs/docs/development/our-process.md index d8b8ed8621..aaac77533d 100644 --- a/platform/docs/docs/development/our-process.md +++ b/platform/docs/docs/development/our-process.md @@ -101,7 +101,7 @@ quality and test coverage must not be changed by a significant margin. For some repositories, visual screenshot-based tests are also included, and video recordings of end-to-end tests are stored for later review. -[You can read more about our continous integration efforts here](/development/continous-integration.md) +[You can read more about our continuous integration efforts here](/development/continuous-integration.md) ## Releases diff --git a/platform/docs/docs/faq.md b/platform/docs/docs/faq.md index 67b7d30b2e..ff859fc69d 100644 --- a/platform/docs/docs/faq.md +++ b/platform/docs/docs/faq.md @@ -37,11 +37,10 @@ collaborators. We are always happy to hear about new groups interested in using the OHIF framework, and may be able to provide development support if the proposed collaboration has an impact on cancer research. -### Does OHIF offer commercial support? +### Does OHIF offer support? + +yes, you can contact us for more information [here](https://ohif.org/get-support) -The Open Health Imaging Foundation does not offer commercial support, however, -some community members do offer consulting services. You can search our -[Community Forum](https://community.ohif.org/) for more information. ### Does The OHIF Viewer have [510(k) Clearance][501k-clearance] from the U.S. F.D.A or [CE Marking][ce-marking] from the European Commission? diff --git a/platform/docs/docs/migration-guide.md b/platform/docs/docs/migration-guide.md index ac1d2fce48..fcf45aadd3 100644 --- a/platform/docs/docs/migration-guide.md +++ b/platform/docs/docs/migration-guide.md @@ -1,6 +1,6 @@ --- sidebar_position: 10 -sidebar_label: 💥 Migration Guide (NEW)💥 +sidebar_label: Migration Guide (NEW) --- # Migration Guide @@ -89,6 +89,16 @@ Since the platform/viewer (@ohif/viewer) is already at v4.12.51, we opted to ren ## Configuration +:::tip +There are various configurations available to customize the viewer. Each configuration is represented by a custom-tailored object that should be used with the viewer to work effectively with a specific server. Here are some examples of configuration files found in the platform/app/public/config directory. Some server-specific configurations that you should be aware are: `supportsWildcard`, `bulkDataURI`, `omitQuotationForMultipartRequest`, `staticWado` (Read more about them [here](./configuration/configurationFiles.md)). + +- default.js: This is our default configuration designed for our main server, which uses a Static WADO datasource hosted on Amazon S3. +- local_orthanc.js: Use this configuration when working with our local Orthanc server. +- local_dcm4chee.js: This configuration is intended for our local dcm4chee server. +- netlify.js: This configuration is the same as default.js and is used for deployment on Netlify. +- google.js: Use this configuration to run the viewer against the Google Health API. +::: + OHIF v3 has a new configuration structure. The main difference is that the `servers` is renamed to `dataSources` and the configuration is now asynchronous. Datasources are more abstract and far more capable than servers. Read more about dataSources [here](./platform/extensions/modules/data-source.md). @@ -98,6 +108,7 @@ far more capable than servers. Read more about dataSources [here](./platform/ext - The maxConcurrentMetadataRequests property has been removed in favor of `maxNumRequests` - The hotkeys array has been updated with different command names and options, and some keys have been removed. - New properties have been added, including `maxNumberOfWebWorkers`, `omitQuotationForMultipartRequest`, `showWarningMessageForCrossOrigin`, `showCPUFallbackMessage`, `showLoadingIndicator`, `strictZSpacingForVolumeViewport`. +- you should see if `supportsWildcard` is supported in your server, some servers don't support it and you need to make it false. ## Modes @@ -924,7 +935,7 @@ We have gone through extensive re-design of each part of the UI, and we have als
-I have a huge complex styles using native CSS, how can I re-use them? +I have a huge complex styles using native CSS, how can I reuse them? You can leverage the power of Tailwind CSS (https://TailwindCSS.com/) in OHIF v3 to reuse your existing styles. Tailwind CSS is a utility-first approach, allowing you to create reusable CSS classes by composing utility classes together. You can migrate your existing styles to Tailwind CSS by breaking them down into utility classes and utilizing the extensive set of predefined utilities provided by Tailwind CSS. diff --git a/platform/docs/docs/platform/environment-variables.md b/platform/docs/docs/platform/environment-variables.md index 4fd2691a4a..1b08d54925 100644 --- a/platform/docs/docs/platform/environment-variables.md +++ b/platform/docs/docs/platform/environment-variables.md @@ -11,7 +11,7 @@ There are a number of environment variables we use at build time to influence th NODE_ENV=< production | development > DEBUG=< true | false > APP_CONFIG=< relative path to application configuration file > -PUBLIC_URL=<> +PUBLIC_URL=< relative path to application root - default / > VERSION_NUMBER= BUILD_NUM= # i18n diff --git a/platform/docs/docs/platform/extensions/index.md b/platform/docs/docs/platform/extensions/index.md index cf60ca5fef..ec8f48d18c 100644 --- a/platform/docs/docs/platform/extensions/index.md +++ b/platform/docs/docs/platform/extensions/index.md @@ -51,7 +51,7 @@ export default { */ id, - // Lifecyle + // Lifecycle preRegistration() { /* */ }, onModeEnter() { /* */ }, onModeExit() { /* */ }, @@ -182,7 +182,7 @@ Use the provided `cli` to add/remove/install/uninstall extensions. Read more [he The final registration and import of the extensions happen inside a non-tracked file `pluginImport.js` (this file is also for internal use only). -After an extension gets registered withing the `viewer`, +After an extension gets registered within the `viewer`, each [module](#modules) defined by the extension becomes available to the modes via the `ExtensionManager` by requesting it via its id. [Read more about Extension Manager](#extension-manager) @@ -206,7 +206,7 @@ used to initialize data. [`onModeExit`](./lifecycle#onModeExit): Similarly to onModeEnter, this hook is called when navigating away from a mode, or before a mode’s data or datasource -is changed. This can be used to cache data for re-use later, but since it +is changed. This can be used to cache data for reuse later, but since it isn't known which mode will be entered next, the state after exiting should be clean, that is, the same as the state on a clean start. This is called BEFORE service clean up, and after mode specific onModeExit handling. diff --git a/platform/docs/docs/platform/extensions/modules/data-source.md b/platform/docs/docs/platform/extensions/modules/data-source.md index 0d7d855616..6cbb741d36 100644 --- a/platform/docs/docs/platform/extensions/modules/data-source.md +++ b/platform/docs/docs/platform/extensions/modules/data-source.md @@ -61,11 +61,9 @@ You can add your custom datasource by creating the implementation using `IWebApiDataSource.create` from `@ohif/core`. This factory function creates a new "Web API" data source that fetches data over HTTP. -You need to make sure, you implement the following functions for the data -source. - ```js title="platform/core/src/DataSources/IWebApiDataSource.js" function create({ + initialize, query, retrieve, store, @@ -80,7 +78,18 @@ function create({ ``` You can take a look at `dicomweb` data source implementation to get an idea -`extensions/default/src/DicomWebDataSource/index.js` +`extensions/default/src/DicomWebDataSource/index.js` but here here are some +important api endpoints that you need to implement: + + +- `initialize`: This method is called when the data source is first created in the mode.tsx, it is used to initialize the data source and set the configuration. For instance, `dicomwebDatasource` uses this method to grab the StudyInstanceUID from the URL and set it as the active study, as opposed to `dicomJSONDatasource` which uses url in the browser to fetch the data and store it in a cache +- `query.studies.search`: This is used in the study panel on the left to fetch the prior studies for the same MRN which is then used to display on the `All` tab. it is also used in the Worklist to show all the studies from the server. +- `query.series.search`: This is used to fetch the series information for a given study that is expanded in the Worklist. +- `retrieve.bulkDataURI`: used to render RTSTUCTURESET in the viewport. +- `retrieve.series.metadata`: It is a crucial end point that is used to fetch series level metadata which for hanging displaySets and displaySet creation. +- `store.dicom`: If you don't need store functionality, you can skip this method. This is used to store the data in the backend. + + ## Static WADO Client @@ -100,3 +109,66 @@ In `OHIF-v3` we have a central location for the metadata of studies, and they ar located in `DicomMetadataStore`. Your custom datasource can communicate with `DicomMetadataStore` to store, and fetch Study/Series/Instance metadata. We will learn more about `DicomMetadataStore` in services. + +## Adding a Data Source Outside a Module + +A data source can be added outside a module via `ExtensionManager.addDataSource`. +The following snippet of code demonstrates how `addDataSource` can be used to add +a new DICOMWeb data source for the Google Cloud Healthcare API and set it as the +active data source. + +```js +extensionManager.addDataSource({ + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'google', + configuration: { + friendlyName: 'dcmjs DICOMWeb Server', + name: 'GCP', + wadoUriRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + qidoRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + wadoRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + qidoSupportsIncludeField: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: false, + dicomUploadEnabled: true, + omitQuotationForMultipartRequest: true, + }, + {activate:true} +}); +``` + +## Updating a Data Source's Configuration + +An existing data source can have its configuration updated using the +`ExtensionManager.updateDataSourceConfiguration` method. The following snippet of +code demonstrates how `updateDataSourceConfiguration` can be use to update the +configuration of an existing DICOMWeb data source (named `dicomweb`) with the +configuration for a Google Cloud Healthcare API data source. + +```js +extensionManager.updateDataSourceConfiguration( "dicomweb", + { + name: 'GCP', + wadoUriRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + qidoRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + wadoRoot: + 'https://healthcare.googleapis.com/v1/projects/ohif-cloud-healthcare/locations/us-east4/datasets/ohif-qa-dataset/dicomStores/ohif-qa-2/dicomWeb', + qidoSupportsIncludeField: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: false, + dicomUploadEnabled: true, + omitQuotationForMultipartRequest: true, + }, +); +``` diff --git a/platform/docs/docs/platform/extensions/modules/hpModule.md b/platform/docs/docs/platform/extensions/modules/hpModule.md index 67bbd7ec82..39d6408e50 100644 --- a/platform/docs/docs/platform/extensions/modules/hpModule.md +++ b/platform/docs/docs/platform/extensions/modules/hpModule.md @@ -36,7 +36,6 @@ Here is an example protocol which if used will hang a 1x3 layout with the first const oneByThreeProtocol = { id: 'oneByThreeProtocol', locked: true, - hasUpdatedPriorsInformation: false, name: 'Default', createdDate: '2021-02-23T19:22:08.894Z', modifiedDate: '2022-10-04T19:22:08.894Z', @@ -241,12 +240,31 @@ A list of criteria for the protocol along with the provided points for ranking. "StudyDescription", "ModalitiesInStudy", "NumberOfStudyRelatedSeries", "NumberOfSeriesRelatedInstances" In addition to these tags, you can also use a custom attribute that you have registered before. We will learn more about this later. + - `from`: Indicates the source of the attribute. This allows getting values + from other objects such as the `prior` instance object instead of from the + current one. - `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be [`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`] + - | Rule | Single Value | Array Value | Example | + |--- |--- |--- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | equals | === | All members are === in same order | value = ['abc', 'def', 'GHI']
testValue = 'abc' (Fail)

= ['abc'] (Fail)

= ['abc', 'def', 'GHI'] (Valid)

= ['abc', 'GHI', 'def'] (Fail)

= ['abc', 'def'] (Fail)

value = 'Attenuation Corrected'
testValue = 'Attenuation Corrected' (Valid)
= 'Attenuation' (Fail)

value = ['Attenuation Corrected']
testValue = ['Attenuation Corrected'] (Valid)
= 'Attenuation Corrected' (Valid)
= 'Attenuation' (Fail)
| + | doesNotEqual | !== | Any member is !== for the array, either in value, order, or length | value = ['abc', 'def', 'GHI']
testValue = 'abc' (Valid)
= ['abc'] (Valid)
= ['abc', 'def', 'GHI'] (Fail)
= ['abc', 'GHI', 'def'] (Valid)
= ['abc', 'def'] (Valid)

value = 'Attenuation Corrected'
testValue = 'Attenuation Corrected' (Fail)Valid

= 'Attenuation' (Valid)

value = ['Attenuation Corrected']
testValue = ['Attenuation Corrected'] (Fail)
= 'Attenuation Corrected' (Fail)
= 'Attenuation' (Fail) | + | includes | Not allowed | Value is equal to one of the values of the array | value = ['abc', 'def', 'GHI']
testValue = ['abc'] (Valid)

= ‘abc’ (Fail)

= [‘abc’] (Fail)

= ‘dog’ (Fail)

= = [‘att’, ‘abc’] (Valid)

= ['abc', 'def', 'dog'] (Valid)

= ['cat', 'dog'] (Fail)


value = 'Attenuation Corrected'
testValue = ['Attenuation Corrected', 'Corrected'] (Valid)
= ['Attenuation', 'Corrected'] (Fail)


value = ['Attenuation Corrected']
testValue = 'Attenuation Corrected' (Fail)
= ['Attenuation Corrected', 'Corrected'] (Valid)
= ['Attenuation', 'Corrected'] (Fail) | + | doesNotInclude | Not allowed | Value is not in one of the values of the array | value = ['abc', 'def', 'GHI']
testValue = ‘Corr’ (Valid)

= ‘abc’ (Fail)

= [‘att’, ‘cor’] (Valid)

= ['abc', 'def', 'dog'] (Fail)


value = 'Attenuation Corrected'
testValue = ['Attenuation Corrected', 'Corrected'] (Fail)
= ['Attenuation', 'Corrected'] (Valid)


value = ['Attenuation Corrected']
testValue = 'Attenuation' (Fail)
= ['Attenuation Corrected', 'Corrected'] (Fail)
= ['Attenuation', 'Corrected'] (Valid) | + | containsI | String containment (case insensitive) | String containment (case insensitive) is OK for one of the rule values | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Valid)
= ‘corr’ (Valid)

= [‘att’, ‘cor’] (Valid)

= [‘Att’, ‘Wall’] (Valid)

= [‘cat’, ‘dog’] (Fail)



value = ['abc', 'def', 'GHI']
testValue = 'def' (Valid)

= 'dog' (Fail)

= ['gh', 'de'] (Valid)

= ['cat', 'dog'] (Fail)
| + | contains | String containment (case sensitive) | String containment (case sensitive) is OK for one of the rule values | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Valid)
= ‘corr’ (Fail)
= [‘att’, ‘cor’] (Fail)
= [‘Att’, ‘Wall’] (Valid)

= [‘cat’, ‘dog’] (Fail)


value = ['abc', 'def', 'GHI']

testValue = 'def' (Valid)
= 'dog' (Fail)

= ['cat', 'de'] (Valid)

= ['cat', 'dog'] (Fail) | + | doesNotContain | String containment is false | String containment is false for all values of the array | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Fail)


= ‘corr’ (Valid)

= [‘att’, ‘cor’] (Valid)

= [‘Att’, ‘Wall’] (Fail)

= [‘cat’, ‘dog’] (Valid)

value = ['abc', 'def', 'GHI']
testValue = 'def' (Fail)


= 'dog' (Valid)

= ['cat', 'de'] (Fail)

= ['cat', 'dog'] (Valid) | + | doesNotContainI | String containment is false (case insensitive) | String containment (case insensitive) is false for all values of the array | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Fail)

= ‘corr’ (Fail)

= [‘att’, ‘cor’] (Fail)

= [‘Att’, ‘Wall’] (Fail)

= [‘cat’, ‘dog’] (Valid)


value = ['abc', 'def', 'GHI']
testValue = 'DEF' (Fail)

= 'dog' (Valid)

= ['cat', 'gh'] (Fail)

= ['cat', 'dog'] (Valid) | + | startsWith | Value begins with characters | Starts with one of the values of the array | value = 'Attenuation Corrected'
testValue = ‘Corr’ (Fail)

= ‘Att’ (Fail)

= ['cat', 'dog', 'Att'] (Valid)

= [‘cat’, ‘dog’] (Fail)


value = ['abc', 'def', 'GHI']
testValue = 'deg' (Valid)

= ['cat', 'GH'] (Valid)

= ['cat', 'gh'] (Fail)

= ['cat', 'dog'] (Fail) | + | endsWith | Value ends with characters | ends with one of the value of the array | value = 'Attenuation Corrected'
testValue = ‘TED’ (Fail)

= ‘ted’ (Valid)

= ['cat', 'dog', 'ted'] (Valid)

= [‘cat’, ‘dog’] (Fail)


value = ['abc', 'def', 'GHI']
testValue = 'deg' (Valid)

= ['cat', 'HI'] (Valid)

= ['cat', 'hi'] (Fail)

= ['cat', 'dog'] (Fail) | + | greaterThan | value is => to rule | Not applicable | value = 30

testValue = 20 (Valid)
= 40 (Fail)

| + | lessThan | value is <= to rule | Not applicable | value = 30

testValue = 40 (Valid)
= 20 (Fail)
| + | range | Not applicable | 2 value requested (min and max) | value = 50

testValue = [10,60] (Valid)
= [60, 10] (Valid)

= [0, 10] (Fail)

= [70, 80] (Fail)

= 45 (Fail)

= [45] (Fail) | + | notNull | Not Applicable | Not Applicable | No value | A sample of the matching rule is above which matches against the study description to be PETCT ```js @@ -263,6 +281,20 @@ A list of criteria for the protocol along with the provided points for ranking. }, ``` +### `from` attribute +The from attribute allows getting the attribute to test from some other object +such as the prior study, the list of studies overall or another module provided +value. Some of the possible attributes are: + +* `prior`: To get the value from the prior study. +* `activeStudy`: To match the active study +* `studies`: To match the list of studies to display +* `displaySets`: The display sets for the current study +* `allDisplaySets`: Alll available display sets +* `instance`: An instance from the current display set being tested +* `options`: Gets the options object itself, eg if you want a simple top level + value. + ### displaySetSelectors Defines the display sets that the protocol will use for arrangement. @@ -423,7 +455,7 @@ As you can see in the hanging protocol we defined three viewports (but only show - `options` (optional): options for the display set - voi: windowing options for the display set (optional: windowWidth, windowCenter) - voiInverted: whether the VOI is inverted or not (optional) - - colormap: colormap for the display set (optional, it is an object with `{ name }` and optional extra `opacityMapping` property) + - colormap: colormap for the display set (optional, it is an object with `{ name }` and optional extra `opacity` property) - displayPreset: display preset for the display set (optional, used for 3D volume rendering. e.g., 'CT-Bone') @@ -459,22 +491,70 @@ HangingProtocolService.addCustomAttribute( ``` - ## Matching on Prior Study with UID Often it is desired to match a new study to a prior study (e.g., follow up on a surgery). Since the hanging protocols run on displaySets we need to have a way to let OHIF knows that it needs to load the prior study as well. This can -be done by specifying both StudyInstanceUIDs in the URL. Below we are -running OHIF with two studies +be done by specifying both StudyInstanceUIDs in the URL. The additional studies +are then accessible to the hanging protocol. Below we are +running OHIF with two studies, and a comparison hanging protocol available by +default. ```bash -http://localhost:3000/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1 +http://localhost:3000/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1&hangingprotocolId=@ohif/hpCompare +``` + +The `&hangingProtocolId` option forces the specific hanging protocol to be +applied, but the mode can also add the hanging protocols to the default set, +and then the best matching hanging protocol will be applied by the run method. + +To match any other studies, it is required to enable the prior matching rules +capability using: + +```javascript + // Indicate number of priors used - 0 means any number, -1 means none. + numberOfPriorsReferenced: 1, ``` -Now you have access to both studies and you can use matchingRules to match -displaySets. +The matching rule that allows the hanging protocol to be runnable is: +```javascript + protocolMatchingRules: [ + { + id: 'Two Studies', + weight: 1000, + // This will generate 1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1 + // since that is study instance UID in the prior from instance. + attribute: 'StudyInstanceUID', + // The 'from' attribute says where to get the 'attribute' value from. In this case + // prior means the second study in the study list. + from: 'prior', + required: true, + constraint: { + notNull: true, + }, + }, + ], +``` +The display set selector selecting the specific study to display is included +in the studyMatchingRules. Note that this rule will cause ONLY the second study +to be matched, so it won't attempt to match anything in other studies. +Additional series level criteria, such as modality rules must be included at the +`seriesMatchingRules`. -Our roadmap includes enabling matching on prior studies without the UID (e.g., baseline, most recent and index). +```javascript + studyMatchingRules: [ + { + // The priorInstance is a study counter that indicates what position this study is in + // and the value comes from the options parameter. + attribute: 'studyInstanceUIDsIndex', + from: 'options', + required: true, + constraint: { + equals: { value: 1 }, + }, + }, + ], +``` diff --git a/platform/docs/docs/platform/extensions/modules/layout-template.md b/platform/docs/docs/platform/extensions/modules/layout-template.md index 66c839dae1..821444975b 100644 --- a/platform/docs/docs/platform/extensions/modules/layout-template.md +++ b/platform/docs/docs/platform/extensions/modules/layout-template.md @@ -120,7 +120,7 @@ function ViewerLayout({ commandsManager={commandsManager} /> - {/* Rigth SIDEPANELS */} + {/* Right SIDEPANELS */} { const wrappedViewport = props => { return ( @@ -43,20 +43,13 @@ const getViewportModule = () => { A simplified version of the tracked `OHIFCornerstoneViewport` is shown below, which creates a cornerstone viewport: -:::note Tip - -Not in OHIF version 3.1 we use `displaySets` in the props which is new compared to -the previous version (3.0) which uses `displaySet`. This is due to the fact that -we are moving to a new data model that can render fused images in a single viewport. - -::: ```jsx function TrackedCornerstoneViewport({ children, dataSource, displaySets, - viewportIndex, + viewportId, servicesManager, extensionManager, commandsManager, @@ -87,6 +80,45 @@ function TrackedCornerstoneViewport({ } ``` +### Viewport re-rendering optimizations + +We make use of the React memoization pattern to prevent unnecessary re-renders +for the viewport unless certain aspects of the Viewport props change. You can take +a look into the `areEqual` function in the `OHIFCornerstoneViewport` component to +see how this is done. + +```js +function areEqual(prevProps, nextProps) { + if (prevProps.displaySets.length !== nextProps.displaySets.length) { + return false; + } + + if ( + prevProps.viewportOptions.orientation !== + nextProps.viewportOptions.orientation + ) { + return false; + } + + // rest of the code +``` + +as you see, we check if the `needsRerendering` prop is true, and if so, we will +re-render the viewport if the `displaySets` prop changes or the orientation +changes. + + +We use viewportId to identify a viewport and we use it as a key in React +rendering. This is important because it allows us to keep track of the viewport +and its state, and also let React optimize and move the viewport around in the +grid without re-rendering it. However, there are some cases where we need to +force re-render the viewport, for example, when the viewport is hydrated +with a new Segmentation. For these cases, we use the `needsRerendering` prop +to force re-render the viewport. You can add it to the `viewportOptions` + + + + ### `@ohif/app` diff --git a/platform/docs/docs/platform/internationalization.md b/platform/docs/docs/platform/internationalization.md index 1bb32de5ba..8caf9a729e 100644 --- a/platform/docs/docs/platform/internationalization.md +++ b/platform/docs/docs/platform/internationalization.md @@ -182,7 +182,7 @@ the following file tree: index.js | UK |-- Buttons.js - indes.js + index.js | US |-- Buttons.js index.js @@ -347,7 +347,7 @@ https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78 Chinese: https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=zh -Portugese: +Portuguese: https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=pt-BR Here are some links you can use to sign up to help translate. All you have to do diff --git a/platform/docs/docs/platform/managers/extension.md b/platform/docs/docs/platform/managers/extension.md index 9fb5196f84..a31f224421 100644 --- a/platform/docs/docs/platform/managers/extension.md +++ b/platform/docs/docs/platform/managers/extension.md @@ -21,13 +21,23 @@ const extensionManager = new ExtensionManager({ appConfig, }); ``` +## Events +The following events get published by the `ExtensionManager`: -The `ExtensionManager` only has a few public members: +| Event | Description | +| ---------------------------- | ------------------------------------------------------ | +| ACTIVE_DATA_SOURCE_CHANGED | Fired when the active data source is changed - either replaced with an entirely different one or the existing active data source gets its definition changed via `updateDataSourceConfiguration`. | + +## API +The `ExtensionManager` only has the following public API: - `setActiveDataSource` - Sets the active data source for the application - `getDataSources` - Returns the registered data sources - `getActiveDataSource` - Returns the currently active data source - `getModuleEntry` - Returns the module entry by the give id. +- `addDataSource` - Dynamically adds a data source and optionally sets it as the active data source +- `updateDataSourceConfiguration` - Updates the configuration of a specified data source (name). +- `getDataSourceDef` - Gets the data source definition for a particular data source name. ## Accessing Modules diff --git a/platform/docs/docs/platform/modes/index.md b/platform/docs/docs/platform/modes/index.md index f2c32b91ce..35b046d67f 100644 --- a/platform/docs/docs/platform/modes/index.md +++ b/platform/docs/docs/platform/modes/index.md @@ -191,7 +191,7 @@ export default mode; ### Consuming Extensions As mentioned in the [Extensions](../extensions/index.md) section, in `OHIF-v3` -developers write their extensions to create re-usable functionalities that later +developers write their extensions to create reusable functionalities that later can be used by `modes`. Now, it is time to describe how the registered extensions will get utilized for a workflow mode via its `id`. diff --git a/platform/docs/docs/platform/services/data/DisplaySetService.md b/platform/docs/docs/platform/services/data/DisplaySetService.md index 12136e987c..8b8bcae671 100644 --- a/platform/docs/docs/platform/services/data/DisplaySetService.md +++ b/platform/docs/docs/platform/services/data/DisplaySetService.md @@ -8,6 +8,14 @@ sidebar_label: DisplaySet Service ## Overview `DisplaySetService` handles converting the `instanceMetadata` into `DisplaySet` that `OHIF` uses for the visualization. `DisplaySetService` gets initialized at service startup time, but is then cleared in the `Mode.jsx`. During the initialization `SOPClassHandlerIds` of the `modes` gets registered with the `DisplaySetService`. +:::tip + +DisplaySet is a general set of entities and contains links to bunch of displayable objects (images, etc.) Some series might get split up into different displaySets e.g., MG might have mixed views in a single series, but users might want to have separate LCC, RCC, etc. for hanging protocol usage. A viewport renders a display set into a displayable object. + +imageSet is a particular implementation of image displays. +::: + + > Based on the instanceMetadata's `SOPClassHandlerId`, the correct module from the registered extensions is found by `OHIF` and its `getDisplaySetsFromSeries` runs to create a DisplaySet for the Series. Note that this is an ordered operation, and consumes the instances as it proceeds, with the first registered handlers being able to consume instances first. @@ -33,6 +41,7 @@ There are three events that get broadcasted in `DisplaySetService`: | DISPLAY_SETS_ADDED | Fires a displayset is added to the displaysets cache | | DISPLAY_SETS_CHANGED | Fires when a displayset is changed | | DISPLAY_SETS_REMOVED | Fires when a displayset is removed | +| DISPLAY_SET_SERIES_METADATA_INVALIDATED | Fires when a displayset's series metadata has been altered. An object payload for the event is sent with properties: `displaySetInstanceUID` - the UID of the display set affected; `invalidateData` - boolean indicating if data should be invalidated ## API @@ -60,3 +69,5 @@ Let's find out about the public API for `DisplaySetService`. - `deleteDisplaySet`: Deletes the displaySets from the displaySets cache - `addActiveDisplaySets`: Adds a new display set independently of the make operation. + +- `setDisplaySetMetadataInvalidated`: Fires the `DISPLAY_SET_SERIES_METADATA_INVALIDATED` event. diff --git a/platform/docs/docs/platform/services/data/HangingProtocolService.md b/platform/docs/docs/platform/services/data/HangingProtocolService.md index a30118f3ed..f2e156b633 100644 --- a/platform/docs/docs/platform/services/data/HangingProtocolService.md +++ b/platform/docs/docs/platform/services/data/HangingProtocolService.md @@ -45,7 +45,7 @@ export default function getHangingProtocolModule() { } ``` -Within the protocol itself, the structure is layed out as described in the HangingProtocol.ts +Within the protocol itself, the structure is laid out as described in the HangingProtocol.ts type definition, starting with `Protocol`. See the type definition for more details. ## Events @@ -71,7 +71,7 @@ the stage activate. The status values are: * enabled - meaning that the stage is fully applicable * passive - meaning that the stage can be applied, but might be missing details -* disabled - meaning that the study has insuffient information for this stage +* disabled - meaning that the study has insufficient information for this stage The default values for no `stageActivation` are to assume that `enabled` has `minViewports` of 1, and `passive` has `minViewports=0`. That is, enable the stage if at least one @@ -118,7 +118,7 @@ stageActivation: { stable as to exactly what this returns, as internal details can change. - `getState`: Returns the currently applied protocol ID, stage index and active study UID. - This information is storable/useable as state information to be used elsewhere. + This information is storable/usable as state information to be used elsewhere. - `getDefaultProtocol`: Returns the default protocol to apply. diff --git a/platform/docs/docs/platform/services/data/MeasurementService.md b/platform/docs/docs/platform/services/data/MeasurementService.md index 21bd4589f9..f4ecd51f62 100644 --- a/platform/docs/docs/platform/services/data/MeasurementService.md +++ b/platform/docs/docs/platform/services/data/MeasurementService.md @@ -68,7 +68,7 @@ There are seven events that get publish in `MeasurementService`: - `toMeasurementSchema`: A function to get the `data` into the same shape as the source definition. -- `jumpToMeasurement(viewportIndex, id)`: calls the listeners who have +- `jumpToMeasurement(viewportId, id)`: calls the listeners who have subscribed to `JUMP_TO_MEASUREMENT`. ## Source / Mappers diff --git a/platform/docs/docs/platform/services/data/SegmentationService.md b/platform/docs/docs/platform/services/data/SegmentationService.md index 42e47c5530..bd99e12dee 100644 --- a/platform/docs/docs/platform/services/data/SegmentationService.md +++ b/platform/docs/docs/platform/services/data/SegmentationService.md @@ -45,8 +45,8 @@ There are seven events that get publish in `MeasurementService`: ### Segment Behavior -- setSegmentLockedForSegmentation, removeSegment, addSegment, setSegmentLockedForSegmentation, setSegmentLabel, setActiveSegmentForSegmentation, -setSegmentRGBAColorForSegmentation +- setSegmentLocked, removeSegment, addSegment, setSegmentLocked, setSegmentLabel, setActiveSegment, +setSegmentRGBAColor ### Segmentation Configuration diff --git a/platform/docs/docs/platform/services/data/StateSyncService.md b/platform/docs/docs/platform/services/data/StateSyncService.md index 4e6964d17d..6688c5eca8 100644 --- a/platform/docs/docs/platform/services/data/StateSyncService.md +++ b/platform/docs/docs/platform/services/data/StateSyncService.md @@ -47,7 +47,7 @@ grid store state as a modal state. ``` ### getState -The `getState` call returns an object containing all of the reigstered states, +The `getState` call returns an object containing all of the registered states, by id. The values can be read directly, but should not be modified. ### reduce diff --git a/platform/docs/docs/platform/services/data/index.md b/platform/docs/docs/platform/services/data/index.md index cdeb44c773..6b92fdde1d 100644 --- a/platform/docs/docs/platform/services/data/index.md +++ b/platform/docs/docs/platform/services/data/index.md @@ -17,9 +17,9 @@ We maintain the following non-ui Services: - [DicomMetadata Store](./../data/DicomMetadataStore.md) - [DisplaySet Service](./../data/DisplaySetService.md) - [Hanging Protocol Service](../data/HangingProtocolService.md) -- [Toolbar Service](../data/ToolBarService.md) +- [Toolbar Service](./ToolbarService.md) - [Measurement Service](../data/MeasurementService.md) -- [Customization Service](../data/customization-service.md) +- [Customization Service](./../ui/customization-service.md) - [State Sync Service](../data/StateSyncService.md) - [Panel Service](../data/PanelService.md) diff --git a/platform/docs/docs/platform/services/ui/customization-service.md b/platform/docs/docs/platform/services/ui/customization-service.md index 171548c20c..7fc5a60ee3 100644 --- a/platform/docs/docs/platform/services/ui/customization-service.md +++ b/platform/docs/docs/platform/services/ui/customization-service.md @@ -43,7 +43,8 @@ automatically when the extension or mode is loaded. In the `value` of each customizations, you will define customization prototype(s). These customization prototype(s) can be considered like "Prototype" in Javascript. These can be used to extend the customization definitions from configurations. -Default cutomizations will be often used to define all the customization prototypes, +Default customizations will be often used to define all the customization prototypes, +Default customizations will be often used to define all the customization prototypes, as they will be loaded automatically along with the defining extension or mode. @@ -58,7 +59,6 @@ For example, the `@ohif/extension-default` extension defines, value: [ { id: 'ohif.overlayItem', - uiType: 'uiType', content: function (props) { if (this.condition && !this.condition(props)) return null; @@ -92,8 +92,9 @@ For example, the `@ohif/extension-default` extension defines, ], ``` -And this `ohif.overlayItem` object will be used as a prototype to define items -to be displayed on `CustomizableViewportOverlay`. See the next section. +And this `ohif.overlayItem` object will be used as a prototype (and template) to define items +to be displayed on `CustomizableViewportOverlay`. See how we use the `ohif.overlayItem` in +the example below. ## Configuring customizations @@ -119,13 +120,16 @@ window.config = { customizationService: { cornerstoneOverlayTopRight: { id: 'cornerstoneOverlayTopRight', - customizationType: 'ohif.cornerstoneOverlay', items: [ { id: 'PatientNameOverlay', - // Note the overlayItem as a parent type - this provides the - // rendering functionality to read the attribute and use the label. + // Note below that here we are using the customization prototype of + // `ohif.overlayItem` which was registered to the customization module in + // `ohif/extension-default` extension. customizationType: 'ohif.overlayItem', + // the following props are passed to the `ohif.overlayItem` prototype + // which is used to render the overlay item based on the label, color, + // conditions, etc. attribute: 'PatientName', label: 'PN:', title: 'Patient Name', @@ -157,20 +161,19 @@ The `customizationType` field is simply the id of another customization object. ### Mode Customizations Mode-specific customizations are no different from the global ones, -except that the mode customizations are cleared before the mode `onModeEnter` -is called, and they can have new values registered in the `onModeEnter` +except that the mode customizations are specific to one mode and +are not globally applied. Mode-specific customizations are also cleared +before the mode `onModeEnter` is called, and they can have new values registered in the `onModeEnter` -In the mode customization, the overlay is then further customized -with a bottom-right overlay, which extends the customizationService configuration. +Following on our example above to customize the overlay, we can now add a mode customization +with a bottom-right overlay. ```js // Import the type from the extension itself -import OverlayUICustomization from '@ohif/cornerstone-extension'; - +import OverlayUICustomization from "@ohif/cornerstone-extension"; // In the mode itself, customizations can be registered: -onModeEnter() { - ... +onModeEnter: { // Note how the object can be strongly typed const bottomRight: OverlayUICustomization = { id: 'cornerstoneOverlayBottomRight', @@ -178,10 +181,11 @@ onModeEnter() { customizationType: 'ohif.cornerstoneOverlay', // The cornerstoneOverlay definition requires an items list here. items: [ - // Custom definitions for hte context menu here. + // Custom definitions for the context menu here. ], }; customizationService.addModeCustomizations(bottomRight); +} ``` The mode customizations are retrieved via the `getModeCustomization` function, @@ -198,9 +202,17 @@ can then be used in a way defined by the extension provided that customization point. ```ts - cornerstoneOverlay = uiConfigurationService.getModeCustomization("cornerstoneOverlay", {customizationType: "ohif.cornerstoneOverlay", ...}); - const { component: overlayComponent, props} = uiConfigurationService.getComponent(cornerstoneOverlay); - return (); +const cornerstoneOverlay = customizationService.getModeCustomization( + "cornerstoneOverlay", + { customizationType: "ohif.cornerstoneOverlay" }, +); + +const { component: overlayComponent, props } = + customizationService.getComponent(cornerstoneOverlay); + +return ( + +); ``` This example shows fetching the default component to render this object. The @@ -210,8 +222,11 @@ example (this example comes from the context menu customizations as that one uses commands lists): ```ts - cornerstoneContextMenu = uiConfigurationService.get("cornerstoneContextMenu", defaultMenu); - commandsManager.run(cornerstoneContextMenu, extraProps); +cornerstoneContextMenu = customizationService.get( + "cornerstoneContextMenu", + defaultMenu, +); +commandsManager.run(cornerstoneContextMenu, extraProps); ``` ### Global Customizations @@ -285,7 +300,7 @@ uses it's own internal class names. * Name: 'class:StudyBrowser' * Attributes: ** `true` for the is active true text color -** `false` fo rhte is active false text color. +** `false` for the is active false text color. ** Values are button colors, from the Button class, eg default, white, black ## customRoutes @@ -339,7 +354,6 @@ window.config = { customizationService: { cornerstoneOverlayTopLeft: { id: 'cornerstoneOverlayTopLeft', - customizationType: 'ohif.cornerstoneOverlay', items: [ { id: 'WindowLevel', @@ -398,7 +412,6 @@ window.config = { }, cornerstoneOverlayTopRight: { id: 'cornerstoneOverlayTopRight', - customizationType: 'ohif.cornerstoneOverlay', items: [ { @@ -438,7 +451,6 @@ window.config = { }, cornerstoneOverlayBottomLeft: { id: 'cornerstoneOverlayBottomLeft', - customizationType: 'ohif.cornerstoneOverlay', items: [ { diff --git a/platform/docs/docs/platform/services/ui/ui-modal-service.md b/platform/docs/docs/platform/services/ui/ui-modal-service.md index cb36d7e6cf..e78bc9c01b 100644 --- a/platform/docs/docs/platform/services/ui/ui-modal-service.md +++ b/platform/docs/docs/platform/services/ui/ui-modal-service.md @@ -17,7 +17,7 @@ article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article]
- +
## Interface diff --git a/platform/docs/docs/platform/services/ui/ui-notification-service.md b/platform/docs/docs/platform/services/ui/ui-notification-service.md index 9618594348..815ac6d8d0 100644 --- a/platform/docs/docs/platform/services/ui/ui-notification-service.md +++ b/platform/docs/docs/platform/services/ui/ui-notification-service.md @@ -19,7 +19,7 @@ article: ["How To Design Notifications For Better UX"][ux-article]
- +
diff --git a/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md b/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md index 7370428459..b50e3e7e47 100644 --- a/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md +++ b/platform/docs/docs/platform/services/ui/ui-viewport-dialog-service.md @@ -48,7 +48,7 @@ is expected to support, [check out it's interface in `@ohif/core`][interface] ```js const DEFAULT_STATE = { - viewportIndex: null, + viewportId: null, message: undefined, type: 'info', // "error" | "warning" | "info" | "success" actions: undefined, // array of { type, text, value } diff --git a/platform/docs/docs/platform/services/ui/viewport-grid-service.md b/platform/docs/docs/platform/services/ui/viewport-grid-service.md index d523a7415a..ee610a98c8 100644 --- a/platform/docs/docs/platform/services/ui/viewport-grid-service.md +++ b/platform/docs/docs/platform/services/ui/viewport-grid-service.md @@ -15,7 +15,7 @@ There are seven events that get publish in `ViewportGridService `: | Event | Description | | ----------------------------- | --------------------------------------------------| -| ACTIVE_VIEWPORT_INDEX_CHANGED | Fires the index of the active viewport is changed | +| ACTIVE_VIEWPORT_ID_CHANGED | Fires the Id of the active viewport is changed | | LAYOUT_CHANGED | Fires the layout is changed | | GRID_STATE_CHANGED | Fires when the entire grid state is changed | ## Interface @@ -25,13 +25,14 @@ is expected to support, [check out it's interface in `@ohif/core`][interface] | API Member | Description | | --------------------------------------------------------------------- | --------------------------------------------------- | -| `setActiveViewportIndex(index)` | Sets the active viewport index in the app | +| `setActiveViewportId(viewportId)` | Sets the active viewport Id in the app | | `getState()` | Gets the states of the viewport (see below) | -| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | +| `setDisplaySetsForViewport({ viewportId, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | | `setLayout({numCols, numRows, keepExtraViewports})` | Sets rows and columns. When the total number of viewports decreases, optionally keep the extra/offscreen viewports. | | `reset()` | Resets the default states | | `getNumViewportPanes()` | Gets the number of visible viewport panes | | `getLayoutOptionsFromState(gridState)` | Utility method that produces a `ViewportLayoutOptions` based on the passed in state| +| `getActiveViewportId()` | Returns the viewport Id of the active viewport in the grid| ## Implementations @@ -56,6 +57,6 @@ const DEFAULT_STATE = { * } */ ], - activeViewportIndex: 0, + activeViewportId: null, }; ``` diff --git a/platform/docs/docs/release-notes.md b/platform/docs/docs/release-notes.md index 2ab1ef1f2d..146f567b1d 100644 --- a/platform/docs/docs/release-notes.md +++ b/platform/docs/docs/release-notes.md @@ -5,34 +5,100 @@ sidebar_label: Release Notes # Release Notes -> New `OHIF-v3` architecture has made OHIF a general purpose extensible medical -> imaging **platform**, as opposed to a configurable viewer. -## What's new in `OHIF-v3` +## Current Release (master branch) -`OHIF-v3` is our second try for a React-based viewer, and is the third version -of our medical image web viewers from the start. The summary of changes includes: +### OHIF Viewer v3.6 - Official Version 3 Release (June 2023) +Check out the complete press announcement [here](https://ohif.org/newsletters/2023-06-08-ohif%20viewer%20v3%20official%20release%20&%20new%20nci%20funding--release). + +- Official OHIF v3 release: An important milestone achieved with OHIF v3 now at feature parity with v2 but with a more extensible and powerful framework. + +New Features: + +- DICOM Radiotherapy Structure Sets: Enhancement of DICOM RTSTRUCT rendering pipeline to better integrate with other segmentation types. +- Slide Microscopy: Slide microscopy code updated with the latest technologies from the DICOM Microscopy Library and SLIM Viewer. +- DICOM Uploader: New feature to easily upload DICOM files directly from the viewer to their PACS systems over DICOMWeb. +- Cornerstone DICOM Image Loader Migrated to TypeScript: Transition to the new TypeScript-based DICOM Image Loader library. +- Cornerstone3D 1.0 Release: Announcement of Cornerstone3D reaching version 1.0, indicating its readiness for production use. + +## Previous V3 Releases (on `v3-stable` branch, before merge to `master`) + +### OHIF Viewer v3.5 - Docker Build + +This update represents a minor release that is primarily focused on enhancing the development environment of the OHIF Viewer. It achieves this by integrating Docker build support, which is essential for streamlining the deployment process and ensuring consistent environments. Additionally, in an effort to optimize the development workflow, this release takes care of pushing changes to the master branch. Furthermore, it strategically splits the master branch from the release branch. This separation is crucial as it allows the developers to work more efficiently on the ongoing developments in the master branch, while simultaneously ensuring that the release branch remains stable and well-maintained. Such an approach underlines the commitment to both innovation and reliability. + + +### OHIF Viewer v3.4 - Segmentation Support (April 2023) +Check out the complete press announcement [here](https://ohif.org/newsletters/2023-04-03-new%20product%20features,%20grant%20updates%20and%20collaborations). + +- New Viewport State Preservation: Enhancements in state synchronization in OHIF Viewer for a seamless experience when switching between Multiplanar Reformatting (MPR) and other views. + +- Enhanced Hanging Protocol Matching: Improved hanging protocols for a faster, more user-friendly experience. + +- Customizable Context Menu: Expansion of context menu options allowing for greater customization and addition of sub-menus. + +- UI/UX Improvements: Revamped viewport header design and the addition of double-click functionality to maximize viewport. + +### OHIF Viewer v3.3 - Segmentation Support (November 2022) + +Check out the complete press announcement [here](https://ohif.org/newsletters/2022-11-21-ohif%20viewer:%20dicom%20segmentation%20support). + +- 3D Segmentation: Segmentations are natively loaded and rendered in 3D. The UI includes various interaction tools and rendering display preferences. Segmentation creation and editing tools are in development. + +- Fast and Optimized Multiplanar Reconstruction (MPR): The viewer now supports MPR visualization of volumes and segmentations, leading to significantly reduced memory footprint and improved performance. + +- New Collapsible Side Panels: The OHIF Viewer has redesigned side panels to be more compact and user friendly. + +- Context-aware Drag and Drop via Hanging Protocols: The viewer now allows a more seamless experience when dragging and dropping thumbnails. + +- New Tools: Two new tools have been added: Reference Lines and Stack Synchronizations. + + + +### OHIF Viewer v3.2 - Mode Gallery & TMTV Mode (August 2022) + +Check out the complete press announcement [here](https://ohif.org/newsletters/2022-08-18-mode%20gallery%20and%20tmtv%20mode). + +- New Total Metabolic Tumor Volume (TMTV) Workflow: This new mode includes high-performance rendering of volumetric data in ten distinct viewports, fusion of two series with adjustable colormaps, synchronization of the viewports for both camera and VOI settings, jump-to-click capability to synchronize navigation in all viewports, and support for exporting results. + +- OHIF Workflow Mode Gallery: This new feature is a one-stop shop for users to find, install, and use OHIF modes and share functionality with the community. The gallery is continuously updated with community-submitted modes. + + +### OHIF Viewer v3.1 - Cornerstone3D Integration (July 2022) + +Check out the complete press announcement [here](https://ohif.org/newsletters/2022-07-28-ohif%20&%20cornerstone3d%20integratione). + +- Cornerstone3D Integration: OHIF v3.1 has deprecated the legacy Cornerstone.js extension and replaced all functionality with Cornerstone3D. This includes updating the Measurement Tracking workflow mode. + +- GPU Accelerated 2D Rendering: The Cornerstone3D rendering engine now supports WebGL 2D textures for large images, increasing the interaction speed significantly compared to the legacy Cornerstone.js engine. + +- Upgraded Hanging Protocol Engine: The OHIF Hanging Protocol Engine has been updated for increased flexibility and ease of writing protocols. This includes the ability to position viewports in any non-grid layout and specify viewport configurations such as colormap, initial VOI, initial image to render, orientation, and more. + + +### OHIF Viewer v3.0 Public Beta Launch (September 2021) + +Check out the complete press announcement [here](https://ohif.org/newsletters/2021-09-14-ohif%20update:%20v3%20public%20beta%20launch--release). + +- UI has been completely redesigned with modularity and workflow modes in mind. +- New UI components have been built with Tailwind CSS - Addition of workflow modes - Often, medical imaging use cases involves lots of specific workflows that - re-use functionalities. We have added the capability of workflow modes, that + reuse functionalities. We have added the capability of workflow modes, that enable people to customize user interface and configure application for specific workflow. - - The idea is to re-use the functionalities that extensions provide and create + - The idea is to reuse the functionalities that extensions provide and create a workflow. Brain segmentation workflow is different from prostate segmentation in UI for sure; however, they share the segmentation tools that can be re-used. - Our vision is that technical people focus of developing extensions which provides core functionalities, and experts to build modes by picking the appropriate functionalities from each extension. - - Migrated all the `cornerstone-core` and `cornerstone-tools` usage to the newly released `Cornerstone3D`. -* UI has been completely redesigned with modularity and workflow modes in mind. -* New UI components have been built with Tailwind CSS -* Redux store has been removed from the viewer in favour of services backed by - React's Context **API** -Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`: + + + diff --git a/platform/docs/docs/resources.md b/platform/docs/docs/resources.md index 96d55e343a..b08ffe5b2f 100644 --- a/platform/docs/docs/resources.md +++ b/platform/docs/docs/resources.md @@ -21,7 +21,7 @@ Free, Open Source Tools for Research: MONAI and OHIF Viewer We participated in the 38th Project Week with three projects around OHIF. [[Website](https://projectweek.na-mic.org/PW38_2023_GranCanaria/)] - PolySeg representations for OHIF Viewer ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_PolySeg/)) -- Cross study sychronizer for OHIF Crosshair ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_SyncCrosshair/)) +- Cross study synchronizer for OHIF Crosshair ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_SyncCrosshair/)) - DATSCAN Viewer implementation in OHIF ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_DATSCAN/)) @@ -43,7 +43,7 @@ The Imaging Network Ontario (ImNO) is an annual symposium that brings together medical imaging researchers and scientists from across Canada to share knowledge, ideas, and experiences. [[Slides]](https://docs.google.com/presentation/d/18XZDon4-Sitc2a70V5sFyhyUVZI_mIgfXHGtdxhZMjE/edit?usp=sharing) -[[Video]](https://vimeo.com/691134870/ad7d308a44) +[[Video]](https://vimeo.com/843234581/ad7d308a44) ### [NA-MIC Project Week 36th 2022 - Remote](https://github.com/NA-MIC/ProjectWeek/blob/master/PW36_2022_Virtual/README.md) @@ -81,7 +81,7 @@ Healthcare Imaging with Cloud Healthcare API OHIF pitch for Informatics Technology for Cancer Research (ITCR) [[Slides]](https://docs.google.com/presentation/d/1MZXnZrVAnjmhVIWqC-aRSvJOoMMRLhLddACdCa1TybM/edit?usp=sharing) -[[Video]](https://vimeo.com/678769373/625bdb8793) +[[Video]](https://vimeo.com/843234613/625bdb8793) ## 2019 diff --git a/platform/docs/docs/user-guide/viewer/Language.md b/platform/docs/docs/user-guide/viewer/Language.md index 9032ecc5f6..de064876c1 100644 --- a/platform/docs/docs/user-guide/viewer/Language.md +++ b/platform/docs/docs/user-guide/viewer/Language.md @@ -18,5 +18,5 @@ Summary of language changing usage can be seen below: ## Overview Video
- +
diff --git a/platform/docs/docs/user-guide/viewer/measurement-panel.md b/platform/docs/docs/user-guide/viewer/measurement-panel.md index b760bf37e4..b4a728d1c7 100644 --- a/platform/docs/docs/user-guide/viewer/measurement-panel.md +++ b/platform/docs/docs/user-guide/viewer/measurement-panel.md @@ -62,7 +62,7 @@ For instance, running the Viewer on a local DCM4CHEE:
- +
## Overview Video @@ -70,5 +70,5 @@ An overview of measurement drawing and exporting can be seen below:
- +
diff --git a/platform/docs/docs/user-guide/viewer/measurement-tracking.md b/platform/docs/docs/user-guide/viewer/measurement-tracking.md index a15f7e5dfb..528ec78d6c 100644 --- a/platform/docs/docs/user-guide/viewer/measurement-tracking.md +++ b/platform/docs/docs/user-guide/viewer/measurement-tracking.md @@ -45,7 +45,7 @@ Below, you can see different icons that appear for a tracked vs. untracked serie
- +
@@ -55,7 +55,7 @@ Below, you can see different icons that appear for a tracked vs. untracked serie
- +
@@ -84,7 +84,7 @@ The full workflow for saving measurements to SR and loading SR into the viewer i
- +


@@ -93,13 +93,13 @@ The full workflow for saving measurements to SR and loading SR into the viewer i
- +


- +
### Loading DICOM SR into an Already Tracked Series @@ -120,5 +120,5 @@ you cannot edit the DICOM SR measurement.
- +
diff --git a/platform/docs/docs/user-guide/viewer/toolbar.md b/platform/docs/docs/user-guide/viewer/toolbar.md index cb982ce25b..1fb5527a06 100644 --- a/platform/docs/docs/user-guide/viewer/toolbar.md +++ b/platform/docs/docs/user-guide/viewer/toolbar.md @@ -81,5 +81,5 @@ An overview of tool usage can been seen below:
- +
diff --git a/platform/docs/docs/user-guide/viewer/viewport.md b/platform/docs/docs/user-guide/viewer/viewport.md index 173728ca15..1bfc693cae 100644 --- a/platform/docs/docs/user-guide/viewer/viewport.md +++ b/platform/docs/docs/user-guide/viewer/viewport.md @@ -35,5 +35,5 @@ An overview of viewport layout change, and manipulation can be seen below:
- +
diff --git a/platform/docs/docusaurus.config.js b/platform/docs/docusaurus.config.js index 9e6611c2ce..bdcaa205b1 100644 --- a/platform/docs/docusaurus.config.js +++ b/platform/docs/docusaurus.config.js @@ -6,13 +6,10 @@ */ const path = require('path'); -const versions = require('./versions.json'); -const VersionsArchived = require('./versionsArchived.json'); -const ArchivedVersionsDropdownItems = Object.entries(VersionsArchived).splice( - 0, - 5 -); +// read this text file +const fs = require('fs'); +const versions = fs.readFileSync('../../version.txt', 'utf8').split('\n'); // This probably only makes sense for the beta phase, temporary // function getNextBetaVersionName() { @@ -36,8 +33,7 @@ const ArchivedVersionsDropdownItems = Object.entries(VersionsArchived).splice( const isDev = process.env.NODE_ENV === 'development'; -const isDeployPreview = - process.env.NETLIFY && process.env.CONTEXT === 'deploy-preview'; +const isDeployPreview = process.env.NETLIFY && process.env.CONTEXT === 'deploy-preview'; const baseUrl = process.env.BASE_URL || '/'; const isBootstrapPreset = process.env.DOCUSAURUS_PRESET === 'bootstrap'; @@ -78,14 +74,21 @@ module.exports = { // }, themes: ['@docusaurus/theme-live-codeblock'], plugins: [ + () => ({ + name: 'resolve-react', + configureWebpack() { + return { + resolve: { + alias: { + // assuming root node_modules is up from "./packages/ + react: path.resolve('../../node_modules/react'), + }, + }, + }; + }, + }), path.resolve(__dirname, './pluginOHIFWebpackConfig.js'), 'plugin-image-zoom', // 3rd party plugin for image click to pop - [ - '@docusaurus/plugin-google-gtag', - { - trackingID: 'UA-110573590-2', - }, - ], [ '@docusaurus/plugin-client-redirects', { @@ -93,7 +96,7 @@ module.exports = { redirects: [ { // we need this for https://cloud.google.com/healthcare/docs/how-tos/dicom-viewers - to: '/2.0/deployment/recipes/google-cloud-healthcare', + to: '/2.0-deprecated/deployment/recipes/google-cloud-healthcare', from: [ '/connecting-to-image-archives/google-cloud-healthcare', '/connecting-to-image-archives/google-cloud-healthcare.html', @@ -233,13 +236,17 @@ module.exports = { // : undefined, versions: { current: { - label: 'Latest', + label: `${versions} (Latest)`, }, }, }, theme: { customCss: [require.resolve('./src/css/custom.css')], }, + gtag: { + trackingID: 'G-DDBJFE34EG', + anonymizeIP: true, + }, }), ], ], @@ -260,13 +267,11 @@ module.exports = { disableSwitch: false, // respectPrefersColorScheme: true, }, - /* - announcementBar: { - id: 'supportus', - content: - '⭐️ If you like Docusaurus, give it a star on GitHub! ⭐️', - }, - */ + announcementBar: { + id: 'healthimaging', + content: + '🚀 AWS has announced the general availability of HealthImaging! Easily connect your OHIF to it. Learn more Here!! 🌟', + }, prism: { theme: require('prism-react-renderer/themes/github'), darkTheme: require('prism-react-renderer/themes/dracula'), @@ -285,14 +290,8 @@ module.exports = { }, items: [ { - to: 'https://ohif.org/get-started', - label: 'Get Started', - target: '_blank', - position: 'left', - }, - { - to: 'https://ohif.org/examples', - label: 'Examples', + href: 'https://ohif.org/showcase', + label: 'Showcase', target: '_blank', position: 'left', }, @@ -304,11 +303,17 @@ module.exports = { label: 'Docs', }, { - to: 'https://ohif.org/community', - label: 'Community', + href: 'https://ohif.org/collaborate', + label: 'Collaborate', target: '_blank', position: 'left', }, + { + to: '/migration-guide', + label: 'Migration Guides', + position: 'left', + className: 'new-badge', + }, { to: '/help', //activeBaseRegex: '(^/help$)|(/help)', @@ -320,21 +325,6 @@ module.exports = { position: 'right', dropdownActiveClassDisabled: true, dropdownItemsAfter: [ - { - type: 'html', - value: '', - }, - { - type: 'html', - className: 'dropdown-archived-versions', - value: 'Archived versions', - }, - ...ArchivedVersionsDropdownItems.map( - ([versionName, versionUrl]) => ({ - label: versionName, - href: versionUrl, - }) - ), { type: 'html', value: '', @@ -387,9 +377,17 @@ module.exports = { to: '/', }, { - label: 'Installation', + label: 'Getting Started', to: 'development/getting-started', }, + { + label: 'FAQ', + to: '/faq', + }, + { + label: 'Resources', + to: '/resources', + }, ], }, { @@ -397,7 +395,7 @@ module.exports = { items: [ { label: 'Discussion board', - to: 'https://community.ohif.org/', + href: 'https://community.ohif.org/', }, { label: 'Help', @@ -410,15 +408,15 @@ module.exports = { items: [ { label: 'Donate', - to: 'https://giving.massgeneral.org/ohif', + href: 'https://giving.massgeneral.org/ohif', }, { label: 'GitHub', - to: 'https://github.com/OHIF/Viewers', + href: 'https://github.com/OHIF/Viewers', }, { label: 'Twitter', - to: 'https://twitter.com/OHIFviewer', + href: 'https://twitter.com/OHIFviewer', }, ], }, diff --git a/platform/docs/netlify.toml b/platform/docs/netlify.toml index e83629343b..ff82abc667 100644 --- a/platform/docs/netlify.toml +++ b/platform/docs/netlify.toml @@ -16,7 +16,7 @@ [build.environment] # If 'production', `yarn install` does not install devDependencies NODE_ENV = "development" - NODE_VERSION = "16.14.0" + NODE_VERSION = "18.16.1" YARN_VERSION = "1.22.5" RUBY_VERSION = "2.6.2" YARN_FLAGS = "--no-ignore-optional --pure-lockfile" diff --git a/platform/docs/package.json b/platform/docs/package.json index e513ad61de..b38b3709c7 100644 --- a/platform/docs/package.json +++ b/platform/docs/package.json @@ -1,6 +1,6 @@ { "name": "ohif-docs", - "version": "3.6.0", + "version": "3.7.0-beta.85", "private": true, "workspaces": { "nohoist": [ diff --git a/platform/docs/pluginOHIFWebpackConfig.js b/platform/docs/pluginOHIFWebpackConfig.js index 9362c0163a..484ede7c91 100644 --- a/platform/docs/pluginOHIFWebpackConfig.js +++ b/platform/docs/pluginOHIFWebpackConfig.js @@ -1,4 +1,4 @@ -module.exports = function(context, options) { +module.exports = function (context, options) { return { name: 'plugin-ohif-webpack-config', configureWebpack(config, isServer, utils) { diff --git a/platform/docs/sidebars.js b/platform/docs/sidebars.js index 981a73cd7a..de26dde1e1 100644 --- a/platform/docs/sidebars.js +++ b/platform/docs/sidebars.js @@ -11,7 +11,7 @@ module.exports = { // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], + tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], // But you can create a sidebar manually /* diff --git a/platform/docs/src/css/custom.css b/platform/docs/src/css/custom.css index 2c59a3495b..ee583b616a 100644 --- a/platform/docs/src/css/custom.css +++ b/platform/docs/src/css/custom.css @@ -36,6 +36,8 @@ --ifm-color-warning: #e9e489; --ifm-alert-color: #333333; --ohif-color-border: #7bb2ce; + --site-primary-hue-saturation: 167 68%; + --site-primary-hue-saturation-light: 167 56%; /* do we really need this extra one? */ } html[data-theme='dark'] .header-github-link:before { @@ -273,3 +275,29 @@ html[data-theme='dark'] .docusaurus-highlight-code-line { padding: 0 var(--ifm-pre-padding); border-left: 3px solid #ff000080; } + +.new-badge::after, +.deprecated-badge::after { + font-size: 11px; + @apply inline-flex items-center justify-center rounded-sm; + @apply ml-1.5 px-1 py-0; +} + +.new-badge::after { + content: ' (NEW)'; + @apply bg-red-300 text-red-500; + @apply dark:bg-blue-900 dark:text-blue-100; +} + +div[class^='announcementBar_'] { + --site-announcement-bar-stripe-color1: hsl(var(--site-primary-hue-saturation) 85%); + --site-announcement-bar-stripe-color2: hsl(var(--site-primary-hue-saturation) 95%); + background: repeating-linear-gradient( + 35deg, + var(--site-announcement-bar-stripe-color1), + var(--site-announcement-bar-stripe-color1) 20px, + var(--site-announcement-bar-stripe-color2) 10px, + var(--site-announcement-bar-stripe-color2) 40px + ); + font-weight: 700; +} diff --git a/platform/docs/src/pages/help.md b/platform/docs/src/pages/help.md index 1eccbf4afa..3610b39879 100644 --- a/platform/docs/src/pages/help.md +++ b/platform/docs/src/pages/help.md @@ -3,7 +3,7 @@ We all need a little help sometimes. Don't let a few roadblocks stand in the way of you building something awesome. -## Community Support +## Get Support If you're a developer looking to contribute code, documentation, or discussion; we are more than happy to help provide clarification and answer questions via @@ -19,17 +19,11 @@ Complex issues specific to your organization/situation are still okay to post, but they're less likely to receive a response. Unfortunately, we have limited resources and must be judicious with how we allocate them. If you find yourself in this situation and in need of assistance, it may be in your best interest to -persue paid support. +pursue paid support. -## Commercial Support +If you need additional help, please [reach out to us](https://ohif.org/get-support) to get more information on +how we can help you. -The Open Health Imaging Foundation does not offer commercial support, however, -some community members do offer consulting services. You can search our -[Community Forum](https://community.ohif.org/) for more information. - - [gh-issues]: https://github.com/OHIF/Viewers/issues/ [google-group]: https://groups.google.com/forum/#!forum/cornerstone-platform diff --git a/platform/docs/src/pages/versions.md b/platform/docs/src/pages/versions.md index f94dc5bd65..7f985e56fd 100644 --- a/platform/docs/src/pages/versions.md +++ b/platform/docs/src/pages/versions.md @@ -5,50 +5,16 @@ with the latest software engineering practices, here we are listing the versions the OHIF platform that we are currently supporting, and the versions that have been deprecated. -## Product Version vs Package Version +## Product Version -There are two types of versions that we need to consider here: - -- Product Version: which is the end user visible version of the viewer -- Package Version: which is the underlying libraries/packages of the platform on npm. Currently we have three product versions: - Version 1 (deprecated): Built with Meteor as a full stack application. -- Version 2 (master branch): Front end image viewer built with React -- Version 3 (alpha release): Re-architected Version 2.0 to allow for a more modular and customizable viewer. - -As per package versioning, we follow semantic versioning which looks like *a.b.c* where: - -- *a* is for major breaking changes -- *b* is for new features -- *c* is for patches/bug fixes - -You can read more semantic versioning [here](https://semver.org/). - - -## Maintained Product Versions - -### Version 3.1 Cornerstone3D - -OHIF Version 3.1 is the next major upcoming release of the OHIF platform. It uses -the next generation of the cornerstone library, [Cornerstone 3D](https://github.com/cornerstonejs/cornerstone3D-beta). -We are in the process of performing a parity check between this version and the `master` -branch before we merge it into the master branch. You can read more about the -roadmap timelines [here](https://ohif.org/roadmap/). - -### Version 2.0: Master branch - -Currently, the master branch of OHIF is on Product Version 2.0. - -## Archived Versions - -### Version 3.0 Cornerstone Legacy - -Version 3.0 which uses [cornerstone-core](https://github.com/cornerstonejs/cornerstone) and -[cornerstone-tools](https://github.com/cornerstonejs/cornerstoneTools) for rendering and -manipulation/annotation of images. +- Version 2 (deprecated): Front end image viewer built with React +- Version 3.x-beta (master branch): With latest bug fixes and features but not yet released (released under beta) +- Version 3.x (release branch): Released version of the OHIF platform which is more stable and tested -### Version 1.0 Meteor -Deprecated in favor of Version 2.0. Built using full stack Meteor as a full stack application. +You can read more about the differences between the versions in the [development section](../../docs/development/getting-started#branches) of the documentation +to understand which version is more suitable for your use case. diff --git a/platform/docs/src/utils/getMockedStudies.js b/platform/docs/src/utils/getMockedStudies.js index 04868cf650..e7d1b7dab7 100644 --- a/platform/docs/src/utils/getMockedStudies.js +++ b/platform/docs/src/utils/getMockedStudies.js @@ -9,8 +9,7 @@ const DEFAULT_MOCKED_STUDIES_LIMIT = 1000; * @returns {array} Study list */ const getMockedStudies = (items = 50) => { - const num = - items > DEFAULT_MOCKED_STUDIES_LIMIT ? DEFAULT_MOCKED_STUDIES_LIMIT : items; + const num = items > DEFAULT_MOCKED_STUDIES_LIMIT ? DEFAULT_MOCKED_STUDIES_LIMIT : items; return new Array(num).fill(studyListMock.studies[0]); }; diff --git a/platform/docs/tailwind.config.js b/platform/docs/tailwind.config.js index 3081fe2e22..04f67fcdcb 100644 --- a/platform/docs/tailwind.config.js +++ b/platform/docs/tailwind.config.js @@ -179,32 +179,32 @@ module.exports = { }, spacing: { px: '1px', - '0': '0', - '1': '0.15rem', - '2': '0.5rem', - '3': '0.75rem', - '4': '1rem', - '5': '1.25rem', - '6': '1.5rem', - '8': '2rem', - '10': '2.5rem', - '12': '3rem', - '14': '3.5rem', - '16': '4rem', - '18': '4.5rem', - '20': '5rem', - '24': '6rem', - '32': '8rem', - '40': '10rem', - '48': '12rem', - '56': '14rem', - '64': '16rem', - '72': '18rem', - '80': '20rem', - '88': '22rem', - '96': '24rem', - '104': '26rem', - '112': '28rem', + 0: '0', + 1: '0.15rem', + 2: '0.5rem', + 3: '0.75rem', + 4: '1rem', + 5: '1.25rem', + 6: '1.5rem', + 8: '2rem', + 10: '2.5rem', + 12: '3rem', + 14: '3.5rem', + 16: '4rem', + 18: '4.5rem', + 20: '5rem', + 24: '6rem', + 32: '8rem', + 40: '10rem', + 48: '12rem', + 56: '14rem', + 64: '16rem', + 72: '18rem', + 80: '20rem', + 88: '22rem', + 96: '24rem', + 104: '26rem', + 112: '28rem', '250px': '250px', }, backgroundColor: theme => theme('colors'), @@ -238,22 +238,18 @@ module.exports = { }, borderWidth: { DEFAULT: '1px', - '0': '0', - '2': '2px', - '4': '4px', - '8': '8px', + 0: '0', + 2: '2px', + 4: '4px', + 8: '8px', }, boxShadow: { xs: '0 0 0 1px rgba(0, 0, 0, 0.05)', sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - DEFAULT: - '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', - md: - '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', - lg: - '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', - xl: - '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', + DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', + md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', + lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', + xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', outline: '0 0 0 3px rgba(66, 153, 225, 0.5)', @@ -273,19 +269,19 @@ module.exports = { current: 'currentColor', }, flex: { - '1': '1 1 0%', - '0.3': '0.3 0.3 0%', - '0.5': '0.5 0.5 0%', + 1: '1 1 0%', + 0.3: '0.3 0.3 0%', + 0.5: '0.5 0.5 0%', auto: '1 1 auto', initial: '0 1 auto', none: 'none', }, flexGrow: { - '0': '0', + 0: '0', DEFAULT: '1', }, flexShrink: { - '0': '0', + 0: '0', DEFAULT: '1', }, fontFamily: { @@ -306,14 +302,7 @@ module.exports = { '"Noto Color Emoji"', ], serif: ['Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'], - mono: [ - 'Menlo', - 'Monaco', - 'Consolas', - '"Liberation Mono"', - '"Courier New"', - 'monospace', - ], + mono: ['Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace'], }, fontSize: { xs: '0.65rem', @@ -346,7 +335,7 @@ module.exports = { }), inset: theme => ({ ...theme('spacing'), - '0': '0', + 0: '0', auto: 'auto', full: '100%', viewport: '0.5rem', @@ -368,14 +357,14 @@ module.exports = { normal: '1.5', relaxed: '1.625', loose: '2', - '3': '.75rem', - '4': '1rem', - '5': '1.25rem', - '6': '1.5rem', - '7': '1.75rem', - '8': '2rem', - '9': '2.25rem', - '10': '2.5rem', + 3: '.75rem', + 4: '1rem', + 5: '1.25rem', + 6: '1.5rem', + 7: '1.75rem', + 8: '2rem', + 9: '2.25rem', + 10: '2.5rem', }, listStyleType: { none: 'none', @@ -410,13 +399,13 @@ module.exports = { }), minHeight: theme => ({ ...theme('spacing'), - '0': '0', + 0: '0', full: '100%', screen: '100vh', }), minWidth: theme => ({ ...theme('spacing'), - '0': '0', + 0: '0', xs: '2rem', sm: '4rem', md: '6rem', @@ -436,44 +425,44 @@ module.exports = { top: 'top', }, opacity: { - '0': '0', - '5': '.5', - '10': '.10', - '15': '.15', - '20': '.20', - '25': '.25', - '30': '.30', - '35': '.35', - '40': '.40', - '45': '.45', - '50': '.50', - '55': '.55', - '60': '.60', - '65': '.65', - '70': '.70', - '75': '.75', - '80': '.80', - '85': '.85', - '90': '.90', - '95': '.95', - '100': '1', + 0: '0', + 5: '.5', + 10: '.10', + 15: '.15', + 20: '.20', + 25: '.25', + 30: '.30', + 35: '.35', + 40: '.40', + 45: '.45', + 50: '.50', + 55: '.55', + 60: '.60', + 65: '.65', + 70: '.70', + 75: '.75', + 80: '.80', + 85: '.85', + 90: '.90', + 95: '.95', + 100: '1', }, order: { first: '-9999', last: '9999', none: '0', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', }, padding: theme => theme('spacing'), placeholderColor: theme => theme('colors'), @@ -482,9 +471,9 @@ module.exports = { current: 'currentColor', }), strokeWidth: { - '0': '0', - '1': '1', - '2': '2', + 0: '0', + 1: '1', + 2: '2', }, textColor: theme => theme('colors'), width: theme => ({ @@ -545,28 +534,28 @@ module.exports = { }), zIndex: { auto: 'auto', - '0': '0', - '10': '10', - '20': '20', - '30': '30', - '40': '40', - '50': '50', + 0: '0', + 10: '10', + 20: '20', + 30: '30', + 40: '40', + 50: '50', }, gap: theme => theme('spacing'), gridTemplateColumns: { none: 'none', - '1': 'repeat(1, minmax(0, 1fr))', - '2': 'repeat(2, minmax(0, 1fr))', - '3': 'repeat(3, minmax(0, 1fr))', - '4': 'repeat(4, minmax(0, 1fr))', - '5': 'repeat(5, minmax(0, 1fr))', - '6': 'repeat(6, minmax(0, 1fr))', - '7': 'repeat(7, minmax(0, 1fr))', - '8': 'repeat(8, minmax(0, 1fr))', - '9': 'repeat(9, minmax(0, 1fr))', - '10': 'repeat(10, minmax(0, 1fr))', - '11': 'repeat(11, minmax(0, 1fr))', - '12': 'repeat(12, minmax(0, 1fr))', + 1: 'repeat(1, minmax(0, 1fr))', + 2: 'repeat(2, minmax(0, 1fr))', + 3: 'repeat(3, minmax(0, 1fr))', + 4: 'repeat(4, minmax(0, 1fr))', + 5: 'repeat(5, minmax(0, 1fr))', + 6: 'repeat(6, minmax(0, 1fr))', + 7: 'repeat(7, minmax(0, 1fr))', + 8: 'repeat(8, minmax(0, 1fr))', + 9: 'repeat(9, minmax(0, 1fr))', + 10: 'repeat(10, minmax(0, 1fr))', + 11: 'repeat(11, minmax(0, 1fr))', + 12: 'repeat(12, minmax(0, 1fr))', }, gridColumn: { auto: 'auto', @@ -585,44 +574,44 @@ module.exports = { }, gridColumnStart: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', - '13': '13', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', }, gridColumnEnd: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - '10': '10', - '11': '11', - '12': '12', - '13': '13', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', }, gridTemplateRows: { none: 'none', - '1': 'repeat(1, minmax(0, 1fr))', - '2': 'repeat(2, minmax(0, 1fr))', - '3': 'repeat(3, minmax(0, 1fr))', - '4': 'repeat(4, minmax(0, 1fr))', - '5': 'repeat(5, minmax(0, 1fr))', - '6': 'repeat(6, minmax(0, 1fr))', + 1: 'repeat(1, minmax(0, 1fr))', + 2: 'repeat(2, minmax(0, 1fr))', + 3: 'repeat(3, minmax(0, 1fr))', + 4: 'repeat(4, minmax(0, 1fr))', + 5: 'repeat(5, minmax(0, 1fr))', + 6: 'repeat(6, minmax(0, 1fr))', }, gridRow: { auto: 'auto', @@ -635,23 +624,23 @@ module.exports = { }, gridRowStart: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', }, gridRowEnd: { auto: 'auto', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', }, transformOrigin: { center: 'center', @@ -665,25 +654,25 @@ module.exports = { 'top-left': 'top left', }, scale: { - '0': '0', - '50': '.5', - '75': '.75', - '90': '.9', - '95': '.95', - '100': '1', - '105': '1.05', - '110': '1.1', - '125': '1.25', - '150': '1.5', + 0: '0', + 50: '.5', + 75: '.75', + 90: '.9', + 95: '.95', + 100: '1', + 105: '1.05', + 110: '1.1', + 125: '1.25', + 150: '1.5', }, rotate: { '-180': '-180deg', '-90': '-90deg', '-45': '-45deg', - '0': '0', - '45': '45deg', - '90': '90deg', - '180': '180deg', + 0: '0', + 45: '45deg', + 90: '90deg', + 180: '180deg', }, translate: (theme, { negative }) => ({ ...theme('spacing'), @@ -697,10 +686,10 @@ module.exports = { '-12': '-12deg', '-6': '-6deg', '-3': '-3deg', - '0': '0', - '3': '3deg', - '6': '6deg', - '12': '12deg', + 0: '0', + 3: '3deg', + 6: '6deg', + 12: '12deg', }, transitionProperty: { none: 'none', @@ -720,14 +709,14 @@ module.exports = { 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', }, transitionDuration: { - '75': '75ms', - '100': '100ms', - '150': '150ms', - '200': '200ms', - '300': '300ms', - '500': '500ms', - '700': '700ms', - '1000': '1000ms', + 75: '75ms', + 100: '100ms', + 150: '150ms', + 200: '200ms', + 300: '300ms', + 500: '500ms', + 700: '700ms', + 1000: '1000ms', }, }, variants: { @@ -737,26 +726,12 @@ module.exports = { alignSelf: ['responsive'], appearance: ['responsive'], backgroundAttachment: ['responsive'], - backgroundColor: [ - 'responsive', - 'hover', - 'focus', - 'active', - 'group-focus', - 'group-hover', - ], + backgroundColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'], backgroundPosition: ['responsive'], backgroundRepeat: ['responsive'], backgroundSize: ['responsive'], borderCollapse: ['responsive'], - borderColor: [ - 'responsive', - 'hover', - 'focus', - 'active', - 'group-focus', - 'group-hover', - ], + borderColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'], borderRadius: ['responsive', 'focus', 'first', 'last'], borderStyle: ['responsive', 'focus'], borderWidth: ['responsive', 'focus', 'first', 'last'], diff --git a/platform/docs/versioned_docs/version-1.0/I-want-to/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/I-want-to/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/I-want-to/add-a-logo-to-the-viewer.md b/platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/add-a-logo-to-the-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/I-want-to/add-a-logo-to-the-viewer.md rename to platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/add-a-logo-to-the-viewer.md diff --git a/platform/docs/versioned_docs/version-1.0/I-want-to/add-a-tool-to-the-viewer.md b/platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/add-a-tool-to-the-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/I-want-to/add-a-tool-to-the-viewer.md rename to platform/docs/versioned_docs/version-1.0-deprecated/I-want-to/add-a-tool-to-the-viewer.md diff --git a/platform/docs/versioned_docs/version-1.0/OHIF-Viewer/Standalone-Installation-Instructions.md b/platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/Standalone-Installation-Instructions.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/OHIF-Viewer/Standalone-Installation-Instructions.md rename to platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/Standalone-Installation-Instructions.md diff --git a/platform/docs/versioned_docs/version-1.0/OHIF-Viewer/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/OHIF-Viewer/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/OHIF-Viewer/environment-installations.md b/platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/environment-installations.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/OHIF-Viewer/environment-installations.md rename to platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/environment-installations.md diff --git a/platform/docs/versioned_docs/version-1.0/OHIF-Viewer/usage.md b/platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/usage.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/OHIF-Viewer/usage.md rename to platform/docs/versioned_docs/version-1.0-deprecated/OHIF-Viewer/usage.md diff --git a/platform/docs/versioned_docs/version-1.0/README.md b/platform/docs/versioned_docs/version-1.0-deprecated/README.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/README.md rename to platform/docs/versioned_docs/version-1.0-deprecated/README.md diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Assessment_Progress.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Assessment_Progress.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Assessment_Progress.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Assessment_Progress.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Association_Dialog.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Association_Dialog.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Association_Dialog.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Association_Dialog.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Audit_Trails.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Audit_Trails.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Audit_Trails.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Audit_Trails.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_CINE.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_CINE.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_CINE.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_CINE.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Change_Password.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Change_Password.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Change_Password.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Change_Password.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_CompareMode.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_CompareMode.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_CompareMode.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_CompareMode.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Comparison.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Comparison.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Comparison.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Comparison.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Configuration_Menu.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Configuration_Menu.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Configuration_Menu.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Configuration_Menu.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Conformance_Check.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Conformance_Check.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Conformance_Check.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Conformance_Check.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Download.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Download.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Download.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Download.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Ellipse.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Ellipse.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Ellipse.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Ellipse.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_FlipH.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_FlipH.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_FlipH.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_FlipH.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_FlipV.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_FlipV.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_FlipV.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_FlipV.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Forgot_Password.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Forgot_Password.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Forgot_Password.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Forgot_Password.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Generate_Report.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Generate_Report.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Generate_Report.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Generate_Report.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_HUD_Panel.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_HUD_Panel.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_HUD_Panel.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_HUD_Panel.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Image_Viewer.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Image_Viewer.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Image_Viewer.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Image_Viewer.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_After_Prerequisites.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_After_Prerequisites.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_After_Prerequisites.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_After_Prerequisites.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Desktop_Shortcuts.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Desktop_Shortcuts.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Desktop_Shortcuts.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Desktop_Shortcuts.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Final.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Final.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Final.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Final.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Finish.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Finish.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Finish.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Finish.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Initial.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Initial.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Initial.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Initial.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Launch_Installation.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Launch_Installation.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Launch_Installation.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Launch_Installation.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Launch_Uninstall.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Launch_Uninstall.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Launch_Uninstall.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Launch_Uninstall.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_License_Aggrement.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_License_Aggrement.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_License_Aggrement.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_License_Aggrement.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Prerequisites.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Prerequisites.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Prerequisites.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Prerequisites.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Select_Location.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Select_Location.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Select_Location.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Select_Location.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Services.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Services.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Services.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Services.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Successful.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Successful.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Successful.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Successful.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Uninstall.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Uninstall.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Installer_Uninstall.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Installer_Uninstall.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Invert.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Invert.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Invert.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Invert.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Key_Timepoints.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Key_Timepoints.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Key_Timepoints.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Key_Timepoints.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Keyboard_Shortcuts.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Keyboard_Shortcuts.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Keyboard_Shortcuts.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Keyboard_Shortcuts.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Login.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Login.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Login.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Login.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Logout.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Logout.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Logout.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Logout.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Magnify.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Magnify.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Magnify.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Magnify.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Measurements.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Measurements.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Measurements.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Measurements.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Need_Account.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Need_Account.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Need_Account.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Need_Account.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_NonTarget_Select_Location.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_NonTarget_Select_Location.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_NonTarget_Select_Location.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_NonTarget_Select_Location.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_NonTarget_Tool.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_NonTarget_Tool.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_NonTarget_Tool.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_NonTarget_Tool.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Patient.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Patient.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Patient.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Patient.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Study.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Study.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Study.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Select_Study.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Study.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Study.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Delete_Study.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Delete_Study.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Drag_and_Drop.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Drag_and_Drop.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Drag_and_Drop.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Drag_and_Drop.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Start_Upload.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Start_Upload.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Start_Upload.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Start_Upload.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Upload.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Upload.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Upload.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Upload.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Upload_Result.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Upload_Result.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Orthanc_Upload_Result.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Orthanc_Upload_Result.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Pan.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Pan.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Pan.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Pan.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Quick_Switch_Tool.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Quick_Switch_Tool.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Quick_Switch_Tool.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Quick_Switch_Tool.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Remove_Associate.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Remove_Associate.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Remove_Associate.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Remove_Associate.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Report_PDF.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Report_PDF.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Report_PDF.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Report_PDF.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Reset.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Reset.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Reset.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Reset.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Response_Criteria.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Response_Criteria.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Response_Criteria.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Response_Criteria.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Rotate_Right.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Rotate_Right.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Rotate_Right.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Rotate_Right.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Save1.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Save1.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Save1.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Save1.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Save2.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Save2.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Save2.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Save2.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Scroll.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Scroll.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Scroll.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Scroll.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Select_Associate.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Select_Associate.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Select_Associate.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Select_Associate.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Server_Info.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Server_Info.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Server_Info.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Server_Info.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StackScroll_Multiple.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StackScroll_Multiple.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StackScroll_Multiple.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StackScroll_Multiple.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StackScroll_Single.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StackScroll_Single.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StackScroll_Single.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StackScroll_Single.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Studies_Panel.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Studies_Panel.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Studies_Panel.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Studies_Panel.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StudyList.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StudyList.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_StudyList.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_StudyList.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_CR.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_CR.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_CR.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_CR.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Delete.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Delete.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Delete.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Delete.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Label.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Label.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Label.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Label.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Rename.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Rename.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_Rename.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_Rename.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_UN.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_UN.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Target_UN.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Target_UN.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Temp_Tool.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Temp_Tool.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Temp_Tool.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Temp_Tool.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Themes.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Themes.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Themes.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Themes.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Lesion.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Lesion.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Lesion.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Lesion.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Series_Details.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Series_Details.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Series_Details.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Series_Details.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Study.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Study.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_View_Study.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_View_Study.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Viewer.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Viewer.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Viewer.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Viewer.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_WL.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_WL.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_WL.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_WL.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_WL_Presets.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_WL_Presets.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_WL_Presets.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_WL_Presets.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Zoom.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Zoom.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/LesionTracker/LT_Zoom.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/LesionTracker/LT_Zoom.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/lesionTracker.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/lesionTracker.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/lesionTracker.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/lesionTracker.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/viewer.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/viewer.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/viewer.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/viewer.png diff --git a/platform/docs/versioned_docs/version-1.0/assets/img/worklist.png b/platform/docs/versioned_docs/version-1.0-deprecated/assets/img/worklist.png similarity index 100% rename from platform/docs/versioned_docs/version-1.0/assets/img/worklist.png rename to platform/docs/versioned_docs/version-1.0-deprecated/assets/img/worklist.png diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dcm4chee-with-docker.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dcm4chee-with-docker.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dcm4chee-with-docker.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dcm4chee-with-docker.md diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dicomweb.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dicomweb.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dicomweb.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dicomweb.md diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dimse.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dimse.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/dimse.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/dimse.md diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/google-cloud-healthcare.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/google-cloud-healthcare.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/google-cloud-healthcare.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/google-cloud-healthcare.md diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/options.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/options.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/options.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/options.md diff --git a/platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/orthanc-with-docker.md b/platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/orthanc-with-docker.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/connecting-to-image-archives/orthanc-with-docker.md rename to platform/docs/versioned_docs/version-1.0-deprecated/connecting-to-image-archives/orthanc-with-docker.md diff --git a/platform/docs/versioned_docs/version-1.0/contributing.md b/platform/docs/versioned_docs/version-1.0-deprecated/contributing.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/contributing.md rename to platform/docs/versioned_docs/version-1.0-deprecated/contributing.md diff --git a/platform/docs/versioned_docs/version-1.0/data/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/data/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/data/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/data/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/data/data-hierarchy.md b/platform/docs/versioned_docs/version-1.0-deprecated/data/data-hierarchy.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/data/data-hierarchy.md rename to platform/docs/versioned_docs/version-1.0-deprecated/data/data-hierarchy.md diff --git a/platform/docs/versioned_docs/version-1.0/data/image-viewport.md b/platform/docs/versioned_docs/version-1.0-deprecated/data/image-viewport.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/data/image-viewport.md rename to platform/docs/versioned_docs/version-1.0-deprecated/data/image-viewport.md diff --git a/platform/docs/versioned_docs/version-1.0/data/measurements-and-annotations.md b/platform/docs/versioned_docs/version-1.0-deprecated/data/measurements-and-annotations.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/data/measurements-and-annotations.md rename to platform/docs/versioned_docs/version-1.0-deprecated/data/measurements-and-annotations.md diff --git a/platform/docs/versioned_docs/version-1.0/data/tool-management.md b/platform/docs/versioned_docs/version-1.0-deprecated/data/tool-management.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/data/tool-management.md rename to platform/docs/versioned_docs/version-1.0-deprecated/data/tool-management.md diff --git a/platform/docs/versioned_docs/version-1.0/deployment/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/deployment/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/deployment/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/deployment/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/deployment/building-for-production.md b/platform/docs/versioned_docs/version-1.0-deprecated/deployment/building-for-production.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/deployment/building-for-production.md rename to platform/docs/versioned_docs/version-1.0-deprecated/deployment/building-for-production.md diff --git a/platform/docs/versioned_docs/version-1.0/deployment/security.md b/platform/docs/versioned_docs/version-1.0-deprecated/deployment/security.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/deployment/security.md rename to platform/docs/versioned_docs/version-1.0-deprecated/deployment/security.md diff --git a/platform/docs/versioned_docs/version-1.0/essentials/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/essentials/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/essentials/architecture.md b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/architecture.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/essentials/architecture.md rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/architecture.md diff --git a/platform/docs/versioned_docs/version-1.0/essentials/configuration.md b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/configuration.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/essentials/configuration.md rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/configuration.md diff --git a/platform/docs/versioned_docs/version-1.0/essentials/installation.md b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/installation.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/essentials/installation.md rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/installation.md diff --git a/platform/docs/versioned_docs/version-1.0/essentials/meteor-packages.md b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/meteor-packages.md similarity index 96% rename from platform/docs/versioned_docs/version-1.0/essentials/meteor-packages.md rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/meteor-packages.md index e172aba863..a9fd4d26b7 100644 --- a/platform/docs/versioned_docs/version-1.0/essentials/meteor-packages.md +++ b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/meteor-packages.md @@ -54,7 +54,7 @@ This package also stores Meteor components for the interactive lesion table used ### User (*ohif-user*) ### Basic Viewer Components (*ohif-viewerbase*) -This is the largest package in the repository. It holds a large number of re-usable Meteor components that are used to build both the OHIF Viewer and Lesion Tracker. +This is the largest package in the repository. It holds a large number of reusable Meteor components that are used to build both the OHIF Viewer and Lesion Tracker. ### WADO Proxy (*ohif-wadoproxy*) Proxy for CORS diff --git a/platform/docs/versioned_docs/version-1.0/essentials/troubleshooting.md b/platform/docs/versioned_docs/version-1.0-deprecated/essentials/troubleshooting.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/essentials/troubleshooting.md rename to platform/docs/versioned_docs/version-1.0-deprecated/essentials/troubleshooting.md diff --git a/platform/docs/versioned_docs/version-1.0/example-applications/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/example-applications/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/example-applications/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/example-applications/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/example-applications/lesion-tracker.md b/platform/docs/versioned_docs/version-1.0-deprecated/example-applications/lesion-tracker.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/example-applications/lesion-tracker.md rename to platform/docs/versioned_docs/version-1.0-deprecated/example-applications/lesion-tracker.md diff --git a/platform/docs/versioned_docs/version-1.0/example-applications/ohif-viewer.md b/platform/docs/versioned_docs/version-1.0-deprecated/example-applications/ohif-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/example-applications/ohif-viewer.md rename to platform/docs/versioned_docs/version-1.0-deprecated/example-applications/ohif-viewer.md diff --git a/platform/docs/versioned_docs/version-1.0/example-applications/standalone-viewer.md b/platform/docs/versioned_docs/version-1.0-deprecated/example-applications/standalone-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/example-applications/standalone-viewer.md rename to platform/docs/versioned_docs/version-1.0-deprecated/example-applications/standalone-viewer.md diff --git a/platform/docs/versioned_docs/version-1.0/faq/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/faq/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/faq/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/faq/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/faq/general.md b/platform/docs/versioned_docs/version-1.0-deprecated/faq/general.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/faq/general.md rename to platform/docs/versioned_docs/version-1.0-deprecated/faq/general.md diff --git a/platform/docs/versioned_docs/version-1.0/faq/technical.md b/platform/docs/versioned_docs/version-1.0-deprecated/faq/technical.md similarity index 80% rename from platform/docs/versioned_docs/version-1.0/faq/technical.md rename to platform/docs/versioned_docs/version-1.0-deprecated/faq/technical.md index 2c5f5e8156..f7cc314f2a 100644 --- a/platform/docs/versioned_docs/version-1.0/faq/technical.md +++ b/platform/docs/versioned_docs/version-1.0-deprecated/faq/technical.md @@ -5,4 +5,4 @@ At the time of project conception, Meteor was a simple way to begin using bleedi ## Do you have any plans to stop using Meteor? -We have considered migrating templates from Blaze (http://blazejs.org/) to React (https://reactjs.org/) or Vue (https://vuejs.org/), simply because these decouple the view layer from the remainder of the application. Blaze currently lacks [Stand-alone Support](http://blazejs.org/#Better-Stand-alone-Support) which means that our templates are not re-usable outside of a Meteor application. This is certainly a downside, but the resource cost to migrate every template is significant. +We have considered migrating templates from Blaze (http://blazejs.org/) to React (https://reactjs.org/) or Vue (https://vuejs.org/), simply because these decouple the view layer from the remainder of the application. Blaze currently lacks [Stand-alone Support](http://blazejs.org/#Better-Stand-alone-Support) which means that our templates are not reusable outside of a Meteor application. This is certainly a downside, but the resource cost to migrate every template is significant. diff --git a/platform/docs/versioned_docs/version-1.0/layout/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/layout/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/layout/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/layout/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/layout/hanging-protocols.md b/platform/docs/versioned_docs/version-1.0-deprecated/layout/hanging-protocols.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/layout/hanging-protocols.md rename to platform/docs/versioned_docs/version-1.0-deprecated/layout/hanging-protocols.md diff --git a/platform/docs/versioned_docs/version-1.0/layout/layout-management.md b/platform/docs/versioned_docs/version-1.0-deprecated/layout/layout-management.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/layout/layout-management.md rename to platform/docs/versioned_docs/version-1.0-deprecated/layout/layout-management.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/audit-trail.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/audit-trail.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/audit-trail.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/audit-trail.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/installation-on-windows.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/installation-on-windows.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/installation-on-windows.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/installation-on-windows.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/manage-studies-in-orthanc.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/manage-studies-in-orthanc.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/manage-studies-in-orthanc.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/manage-studies-in-orthanc.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/server-management.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/server-management.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/server-management.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/server-management.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/study-and-timepoint-management.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/study-and-timepoint-management.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/study-and-timepoint-management.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/study-and-timepoint-management.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/user-accounts.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/user-accounts.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/user-accounts.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/user-accounts.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/user-preferences.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/user-preferences.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/user-preferences.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/user-preferences.md diff --git a/platform/docs/versioned_docs/version-1.0/lesion-tracker/using-the-viewer.md b/platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/using-the-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/lesion-tracker/using-the-viewer.md rename to platform/docs/versioned_docs/version-1.0-deprecated/lesion-tracker/using-the-viewer.md diff --git a/platform/docs/versioned_docs/version-1.0/packages/_category_.json b/platform/docs/versioned_docs/version-1.0-deprecated/packages/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-1.0/packages/_category_.json rename to platform/docs/versioned_docs/version-1.0-deprecated/packages/_category_.json diff --git a/platform/docs/versioned_docs/version-1.0/packages/measurements.md b/platform/docs/versioned_docs/version-1.0-deprecated/packages/measurements.md similarity index 100% rename from platform/docs/versioned_docs/version-1.0/packages/measurements.md rename to platform/docs/versioned_docs/version-1.0-deprecated/packages/measurements.md diff --git a/platform/docs/versioned_docs/version-2.0/Architecture.md b/platform/docs/versioned_docs/version-2.0-deprecated/Architecture.md similarity index 98% rename from platform/docs/versioned_docs/version-2.0/Architecture.md rename to platform/docs/versioned_docs/version-2.0-deprecated/Architecture.md index 8b0dcbcb1b..d61e3ffa1e 100644 --- a/platform/docs/versioned_docs/version-2.0/Architecture.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/Architecture.md @@ -59,7 +59,7 @@ This diagram is a conceptual illustration of how the Viewer is architected. 1. (optional) `extensions` can be registered with `@ohif/core`'s `ExtensionManager` -2. `@ohif/core` provides bussiness logic and a way for `@ohif/viewer` to access +2. `@ohif/core` provides business logic and a way for `@ohif/viewer` to access registered extensions 3. The `@ohif/viewer` composes and provides data to components from our component library (`@ohif/ui`) diff --git a/platform/docs/versioned_docs/version-2.0/README.md b/platform/docs/versioned_docs/version-2.0-deprecated/README.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/README.md rename to platform/docs/versioned_docs/version-2.0-deprecated/README.md diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/architecture-diagram b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/architecture-diagram similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/architecture-diagram rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/architecture-diagram diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/canny-full.fig b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/canny-full.fig similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/canny-full.fig rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/canny-full.fig diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/cloud.svg b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/cloud.svg similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/cloud.svg rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/cloud.svg diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/embedded-viewer-diagram b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/embedded-viewer-diagram similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/embedded-viewer-diagram rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/embedded-viewer-diagram diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/nginx-image-archive.fig b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/nginx-image-archive.fig similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/nginx-image-archive.fig rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/nginx-image-archive.fig diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/npm-logo-red.svg b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/npm-logo-red.svg similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/npm-logo-red.svg rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/npm-logo-red.svg diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/scope-of-project.fig b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/scope-of-project.fig similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/scope-of-project.fig rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/scope-of-project.fig diff --git a/platform/docs/versioned_docs/version-2.0/assets/designs/user-access-control-request-flow.fig b/platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/user-access-control-request-flow.fig similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/designs/user-access-control-request-flow.fig rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/designs/user-access-control-request-flow.fig diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_DEPLOY.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_DEPLOY.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_DEPLOY.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_DEPLOY.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_CHECKS.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_PR_CHECKS.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_CHECKS.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_PR_CHECKS.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_RELEASE.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_RELEASE.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/WORKFLOW_RELEASE.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/WORKFLOW_RELEASE.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/architecture-diagram.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/architecture-diagram.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/architecture-diagram.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/architecture-diagram.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/cornerstone-tools-link.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/cornerstone-tools-link.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/cornerstone-tools-link.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/cornerstone-tools-link.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/dialog-example.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/dialog-example.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/dialog-example.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/dialog-example.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/embedded-viewer-diagram.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/embedded-viewer-diagram.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/embedded-viewer-diagram.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/embedded-viewer-diagram.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-diagram.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-diagram.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/extensions-diagram.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-diagram.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-panel.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-panel.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/extensions-panel.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-panel.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar-nested.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-toolbar-nested.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar-nested.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-toolbar-nested.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-toolbar.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/extensions-toolbar.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-toolbar.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/extensions-viewport.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-viewport.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/extensions-viewport.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/extensions-viewport.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/homePage.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/homePage.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/homePage.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/homePage.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/jwt-explained.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/jwt-explained.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/jwt-explained.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/jwt-explained.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/keycloak-default-theme.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/keycloak-default-theme.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/keycloak-default-theme.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/keycloak-default-theme.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/keycloak-ohif-theme.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/keycloak-ohif-theme.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/keycloak-ohif-theme.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/keycloak-ohif-theme.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/lesionTracker.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/lesionTracker.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/lesionTracker.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/lesionTracker.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/locizeSponsor.svg b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/locizeSponsor.svg similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/locizeSponsor.svg rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/locizeSponsor.svg diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/modal-example.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/modal-example.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/modal-example.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/modal-example.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/netlify-drop.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/netlify-drop.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/netlify-drop.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/netlify-drop.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/nginx-image-archive.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/nginx-image-archive.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/nginx-image-archive.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/nginx-image-archive.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/notification-example.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/notification-example.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/notification-example.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/notification-example.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/open-graph.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/open-graph.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/open-graph.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/open-graph.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/scope-of-project.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/scope-of-project.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/scope-of-project.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/scope-of-project.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/services.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/services.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/services.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/services.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/surge-deploy.gif b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/surge-deploy.gif similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/surge-deploy.gif rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/surge-deploy.gif diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/ui-services.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/ui-services.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/ui-services.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/ui-services.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/user-access-control-request-flow.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/user-access-control-request-flow.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/user-access-control-request-flow.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/user-access-control-request-flow.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/viewer.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/viewer.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/viewer.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/viewer.png diff --git a/platform/docs/versioned_docs/version-2.0/assets/img/worklist.png b/platform/docs/versioned_docs/version-2.0-deprecated/assets/img/worklist.png similarity index 100% rename from platform/docs/versioned_docs/version-2.0/assets/img/worklist.png rename to platform/docs/versioned_docs/version-2.0-deprecated/assets/img/worklist.png diff --git a/platform/docs/versioned_docs/version-2.0/configuring/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/configuring/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/configuring/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/configuring/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/configuring/data-source.md b/platform/docs/versioned_docs/version-2.0-deprecated/configuring/data-source.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/configuring/data-source.md rename to platform/docs/versioned_docs/version-2.0-deprecated/configuring/data-source.md diff --git a/platform/docs/versioned_docs/version-2.0/configuring/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/configuring/index.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/configuring/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/configuring/index.md diff --git a/platform/docs/versioned_docs/version-2.0/deployment/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/deployment/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/deployment/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/index.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/deployment/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/index.md index 995a480776..5444026d73 100644 --- a/platform/docs/versioned_docs/version-2.0/deployment/index.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/index.md @@ -99,7 +99,7 @@ support it yet, but it is gaining wider adoption. If you have an existing archive and intend to host the OHIF Viewer at the same domain name as your archive, then connecting the two is as simple as following -the steps layed out in our +the steps laid out in our [Configuration Essentials Guide](./../configuring/index.md). #### What if I don't have an imaging archive? @@ -273,7 +273,7 @@ You can find an example of this setup in our In general, we recommend using the "Authorization Code Flow" ( [see `response_type=code` here][code-flows]); however, the "Implicit Flow" ( [see -`response_type=token` here][code-flows]) can work if additonal precautions are +`response_type=token` here][code-flows]) can work if additional precautions are taken. If the flow you've chosen produces a JWT Token, it's validity can be used to secure access to your Image Archive as well. diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/build-for-production.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/build-for-production.md similarity index 98% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/build-for-production.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/build-for-production.md index 7ba3bb64b8..1c633b79f5 100644 --- a/platform/docs/versioned_docs/version-2.0/deployment/recipes/build-for-production.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/build-for-production.md @@ -75,7 +75,7 @@ directory. Our build process knows which configuration file to use based on the and registered extension's features, are configured using this file. The easiest way to apply your own configuration is to modify the `default.js` -file. For more advanced cofiguration options, check out our +file. For more advanced configuration options, check out our [configuration essentials guide](/configuring/index.md). ## Next Steps diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/embedded-viewer.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/embedded-viewer.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/embedded-viewer.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/embedded-viewer.md diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/google-cloud-healthcare.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/google-cloud-healthcare.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/google-cloud-healthcare.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/google-cloud-healthcare.md diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/nginx--image-archive.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/nginx--image-archive.md similarity index 97% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/nginx--image-archive.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/nginx--image-archive.md index eddb0ddea4..81d15d1b64 100644 --- a/platform/docs/versioned_docs/version-2.0/deployment/recipes/nginx--image-archive.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/nginx--image-archive.md @@ -8,7 +8,7 @@ sidebar_position: 4 At a certain point, you may want others to have access to your instance of the OHIF Viewer and its medical imaging data. This post covers one of many potential -setups that accomplish that. Please note, noticably absent is user account +setups that accomplish that. Please note, noticeably absent is user account control. Do not use this recipe to host sensitive medical data on the open web. Depending @@ -21,12 +21,12 @@ that builds on the lessons learned here. Our two biggest hurdles when hosting our image archive and web client are: -- Risks related to exposing our PACS to the netowrk +- Risks related to exposing our PACS to the network - Cross-Origin Resource Sharing (CORS) requests ### Handling Web Requests -We mittigate our first issue by allowing [Nginx][nginx] to handle incoming web +We mitigate our first issue by allowing [Nginx][nginx] to handle incoming web requests. Nginx is open source software for web serving, reverse proxying, caching, and more. It's designed for maximum performance and stability -- allowing us to more reliably serve content than Orthanc's built-in server can. diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/static-assets.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/static-assets.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/static-assets.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/static-assets.md index de217f4c07..ebacc0ea26 100644 --- a/platform/docs/versioned_docs/version-2.0/deployment/recipes/static-assets.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/static-assets.md @@ -16,7 +16,7 @@ less product offerings. While not required, it can simplify things to host your Web Viewer alongside your image archive. Services with more robust product offerings, like `Google Cloud`, `Microsoft's Azure`, and `Amazon Web Services (AWS)`, are able -to accomodate this setup. +to accommodate this setup. _Drag-n-drop_ diff --git a/platform/docs/versioned_docs/version-2.0/deployment/recipes/user-account-control.md b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/user-account-control.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/deployment/recipes/user-account-control.md rename to platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/user-account-control.md index ea5757df5b..65bab3d9b4 100644 --- a/platform/docs/versioned_docs/version-2.0/deployment/recipes/user-account-control.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/deployment/recipes/user-account-control.md @@ -77,7 +77,7 @@ _Create Your First User_ - Email Verified: `ON` - Click `Save` - Click the `Credentials` Tab - - New Pasword: `test` + - New Password: `test` - Password Confirmation: `test` - Temporary: `OFF` - Click: `Reset Password` diff --git a/platform/docs/versioned_docs/version-2.0/development/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/development/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/development/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/development/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/development/continous-integration.md b/platform/docs/versioned_docs/version-2.0-deprecated/development/continuous-integration.md similarity index 97% rename from platform/docs/versioned_docs/version-2.0/development/continous-integration.md rename to platform/docs/versioned_docs/version-2.0-deprecated/development/continuous-integration.md index fd3bbb45e6..c076186711 100644 --- a/platform/docs/versioned_docs/version-2.0/development/continous-integration.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/development/continuous-integration.md @@ -1,10 +1,10 @@ --- sidebar_position: 3 -title: Continous Integration +title: Continuous Integration --- -# Continous Integration (CI) +# Continuous Integration (CI) -This repository uses `CircleCI` and `Netlify` for continous integration. +This repository uses `CircleCI` and `Netlify` for continuous integration. ## Deploy Previews diff --git a/platform/docs/versioned_docs/version-2.0/development/contributing.md b/platform/docs/versioned_docs/version-2.0-deprecated/development/contributing.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/development/contributing.md rename to platform/docs/versioned_docs/version-2.0-deprecated/development/contributing.md diff --git a/platform/docs/versioned_docs/version-2.0/development/getting-started.md b/platform/docs/versioned_docs/version-2.0-deprecated/development/getting-started.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/development/getting-started.md rename to platform/docs/versioned_docs/version-2.0-deprecated/development/getting-started.md index 7ec30c99d9..219bd1f625 100644 --- a/platform/docs/versioned_docs/version-2.0/development/getting-started.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/development/getting-started.md @@ -14,7 +14,7 @@ we make to the OHIF Viewer, then follow these steps: - [Fork][fork-a-repo] the [OHIF/Viewers][ohif-viewers-repo] repository - [Create a local clone][clone-a-repo] of your fork - `git clone https://github.com/YOUR-USERNAME/Viewers` -- Add OHIF/Viewers as a [remote repository][add-remote-repo] labled `upstream` +- Add OHIF/Viewers as a [remote repository][add-remote-repo] labeled `upstream` - Navigate to the cloned project's directory - `git remote add upstream https://github.com/OHIF/Viewers.git` diff --git a/platform/docs/versioned_docs/version-2.0/development/testing.md b/platform/docs/versioned_docs/version-2.0-deprecated/development/testing.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/development/testing.md rename to platform/docs/versioned_docs/version-2.0-deprecated/development/testing.md index 4ca92dcbd7..c8c66bc4b3 100644 --- a/platform/docs/versioned_docs/version-2.0/development/testing.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/development/testing.md @@ -44,7 +44,7 @@ choice for asserting an element's border color. Modern tooling gives us this "for free". It can catch invalid regular expressions, unused variables, and guarantee we're calling methods/functions -with the expected paramater types. +with the expected parameter types. Example Tooling: diff --git a/platform/docs/versioned_docs/version-2.0/extensions/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/extensions/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/index.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/extensions/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/index.md index 0f4c122218..c739aa8cea 100644 --- a/platform/docs/versioned_docs/version-2.0/extensions/index.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/index.md @@ -56,7 +56,7 @@ export default { */ id: 'example-extension', - // Lifecyle + // Lifecycle preRegistration() { /* */ }, // Modules getCommandsModule() { /* */ }, diff --git a/platform/docs/versioned_docs/version-2.0/extensions/lifecycle/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/lifecycle/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/lifecycle/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/lifecycle/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/extensions/lifecycle/pre-registration.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/lifecycle/pre-registration.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/lifecycle/pre-registration.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/lifecycle/pre-registration.md diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/commands.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/commands.md similarity index 97% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/commands.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/commands.md index 3f9e864a90..dbbbcfdd01 100644 --- a/platform/docs/versioned_docs/version-2.0/extensions/modules/commands.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/commands.md @@ -65,7 +65,7 @@ myCommandName: { | Property | Type | Description | | --------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | `commandFn` | func | The function to call when command is run. Receives `options` and `storeContexts`. | -| `storeContexts` | string[] | (optional) Expected state objects to be passed in as props. Located using `getAppState` fn defined at `CommandsManager`'s instatiation. | +| `storeContexts` | string[] | (optional) Expected state objects to be passed in as props. Located using `getAppState` fn defined at `CommandsManager`'s instantiation. | | `options` | object | (optional) Arguments to pass at the time of calling to the `commandFn` | | `context` | string[] or string | (optional) Overrides the `defaultContext`. Let's us know if command is currently "available" to be run. | @@ -150,7 +150,7 @@ It is up to the consuming application to define what contexts are possible, and which ones are currently active. As extensions depend heavily on these, we will likely publish guidance around creating contexts, and ways to override extension defined contexts in the near future. If you would like to discuss potential -changes to how contexts work, please don't hesistate to createa new GitHub +changes to how contexts work, please don't hesitate to create new GitHub issue. [Some additional information on Contexts can be found here.](./../index.md#contexts) diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/panel.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/panel.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/panel.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/panel.md diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/sop-class-handler.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/sop-class-handler.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/sop-class-handler.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/sop-class-handler.md diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/toolbar.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/toolbar.md similarity index 98% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/toolbar.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/toolbar.md index 87e7050a86..7246713b51 100644 --- a/platform/docs/versioned_docs/version-2.0/extensions/modules/toolbar.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/toolbar.md @@ -64,7 +64,7 @@ The simplest definition has the following properties: | `label` | User/display friendly to show in UI | \* | | `icon` | A string name for an icon supported by the consuming application. | \* | | `type` | Used to determine the button's component and behavior | `"setToolActive"`, `"command"` | -| `commandName` | (optional) The command to run when the button is used. | Any command registed by a `CommandModule` | +| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` | | `commandOptions` | (optional) Options to pass the target `commandName` | \* | | `context` | (optional) Overrides module's `defaultContext` | Array of string context names | diff --git a/platform/docs/versioned_docs/version-2.0/extensions/modules/viewport.md b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/viewport.md similarity index 93% rename from platform/docs/versioned_docs/version-2.0/extensions/modules/viewport.md rename to platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/viewport.md index 4291c99f38..00e244d244 100644 --- a/platform/docs/versioned_docs/version-2.0/extensions/modules/viewport.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/extensions/modules/viewport.md @@ -25,7 +25,7 @@ Each `ViewportComponent` will receive the following props: ```html ``` @@ -34,7 +34,7 @@ Each `ViewportComponent` will receive the following props: | --------------- | --------------- | --------------------------------- | | `children` | React.element[] | | | `viewportData` | object | `viewportSpecificData` (probably) | -| `viewportIndex` | number | | +| `viewportId` | string | | ### `@ohif/viewer` diff --git a/platform/docs/versioned_docs/version-2.0/faq/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/faq/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/faq/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/faq/_category_.json diff --git a/platform/docs/versioned_docs/version-3.0/platform/browser-support.md b/platform/docs/versioned_docs/version-2.0-deprecated/faq/browser-support.md similarity index 98% rename from platform/docs/versioned_docs/version-3.0/platform/browser-support.md rename to platform/docs/versioned_docs/version-2.0-deprecated/faq/browser-support.md index f717432aed..daa8309163 100644 --- a/platform/docs/versioned_docs/version-3.0/platform/browser-support.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/faq/browser-support.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 --- # Browser Support diff --git a/platform/docs/versioned_docs/version-2.0/faq/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/faq/index.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/faq/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/faq/index.md diff --git a/platform/docs/versioned_docs/version-2.0/faq/pwa-vs-packaged.md b/platform/docs/versioned_docs/version-2.0-deprecated/faq/pwa-vs-packaged.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/faq/pwa-vs-packaged.md rename to platform/docs/versioned_docs/version-2.0-deprecated/faq/pwa-vs-packaged.md diff --git a/platform/docs/versioned_docs/version-3.0/platform/scope-of-project.md b/platform/docs/versioned_docs/version-2.0-deprecated/faq/scope-of-project.md similarity index 95% rename from platform/docs/versioned_docs/version-3.0/platform/scope-of-project.md rename to platform/docs/versioned_docs/version-2.0-deprecated/faq/scope-of-project.md index 4ac68daa60..b61f73d804 100644 --- a/platform/docs/versioned_docs/version-3.0/platform/scope-of-project.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/faq/scope-of-project.md @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 --- # Scope of Project @@ -21,7 +21,7 @@ data source. To be more specific, the OHIF Viewer is a collection of HTML, JS, and CSS files. These can be delivered to your end users however you would like: -- From the local network +- From the local networok - From a remote web server - From a CDN (content delivery network) - From a service-worker's cache @@ -38,7 +38,7 @@ data; only the configuration necessary to interface with one or more of these many data sources. The OHIF Viewer's scope **DOES** include configuration and support for services that are protected with OpenID-Connect. -In an effort to aid our users and contributors, we attempt to provide several +In an effort to aide our users and contributors, we attempt to provide several [deployment and hosting recipes](./deployment/index.md) as potential starting points. These are not meant to be rock solid, production ready, solutions; like most recipes, they should be augmented to best fit you and your organization's @@ -55,7 +55,7 @@ the OHIF Viewer as a desktop application. _Does the OHIF Viewer work with the local filesystem?_ -It is possible to accomplish this through extensions; however, for a user +It is possible to accomplish this through extensions; however, for an user experience that accommodates a large number of studies, you would likely need to package the OHIF Viewer as an [Electron app][electron]. diff --git a/platform/docs/versioned_docs/version-2.0/help.md b/platform/docs/versioned_docs/version-2.0-deprecated/help.md similarity index 98% rename from platform/docs/versioned_docs/version-2.0/help.md rename to platform/docs/versioned_docs/version-2.0-deprecated/help.md index 1eccbf4afa..d9acefcbf1 100644 --- a/platform/docs/versioned_docs/version-2.0/help.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/help.md @@ -19,7 +19,7 @@ Complex issues specific to your organization/situation are still okay to post, but they're less likely to receive a response. Unfortunately, we have limited resources and must be judicious with how we allocate them. If you find yourself in this situation and in need of assistance, it may be in your best interest to -persue paid support. +pursue paid support. ## Commercial Support diff --git a/platform/docs/versioned_docs/version-2.0/our-process.md b/platform/docs/versioned_docs/version-2.0-deprecated/our-process.md similarity index 98% rename from platform/docs/versioned_docs/version-2.0/our-process.md rename to platform/docs/versioned_docs/version-2.0-deprecated/our-process.md index 1e1934a4dc..c6d7e51c33 100644 --- a/platform/docs/versioned_docs/version-2.0/our-process.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/our-process.md @@ -100,7 +100,7 @@ quality and test coverage must not changed by a significant margin. For some repositories, visual screenshot-based tests are also included, and video recordings of end-to-end tests are stored for later review. -[You can read more about our continous integration efforts here](/development/continous-integration.md) +[You can read more about our continuous integration efforts here](/development/continuous-integration.md) ## Releases diff --git a/platform/docs/versioned_docs/version-2.0/services/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/services/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/services/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/services/default.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/default.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/default.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/default.md diff --git a/platform/docs/versioned_docs/version-2.0/services/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/index.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/index.md diff --git a/platform/docs/versioned_docs/version-2.0/services/ui/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/services/ui/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/ui/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/services/ui/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/services/ui/index.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/ui/index.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/ui/index.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/ui/index.md diff --git a/platform/docs/versioned_docs/version-2.0/services/ui/ui-dialog-service.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-dialog-service.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/ui/ui-dialog-service.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-dialog-service.md diff --git a/platform/docs/versioned_docs/version-2.0/services/ui/ui-modal-service.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-modal-service.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/ui/ui-modal-service.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-modal-service.md diff --git a/platform/docs/versioned_docs/version-2.0/services/ui/ui-notification-service.md b/platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-notification-service.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/services/ui/ui-notification-service.md rename to platform/docs/versioned_docs/version-2.0-deprecated/services/ui/ui-notification-service.md diff --git a/platform/docs/versioned_docs/version-2.0/viewer/_category_.json b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/_category_.json similarity index 100% rename from platform/docs/versioned_docs/version-2.0/viewer/_category_.json rename to platform/docs/versioned_docs/version-2.0-deprecated/viewer/_category_.json diff --git a/platform/docs/versioned_docs/version-2.0/viewer/configuration.md b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/configuration.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/viewer/configuration.md rename to platform/docs/versioned_docs/version-2.0-deprecated/viewer/configuration.md diff --git a/platform/docs/versioned_docs/version-2.0/viewer/environment-variables.md b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/environment-variables.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/viewer/environment-variables.md rename to platform/docs/versioned_docs/version-2.0-deprecated/viewer/environment-variables.md diff --git a/platform/docs/versioned_docs/version-2.0/viewer/internationalization.md b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/internationalization.md similarity index 99% rename from platform/docs/versioned_docs/version-2.0/viewer/internationalization.md rename to platform/docs/versioned_docs/version-2.0-deprecated/viewer/internationalization.md index 6087b05579..aa1136b345 100644 --- a/platform/docs/versioned_docs/version-2.0/viewer/internationalization.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/internationalization.md @@ -93,7 +93,7 @@ export default withTranslation('MyNameSpace')(MyComponent); > Important: if you are using React outside the OHIF Viewer, check the > [I18nextProvider](#using-outside-of-ohif-viewer) section, `withTranslation` -> HOC doesnt works without a I18nextProvider +> HOC doesn't works without a I18nextProvider #### Using Hooks @@ -187,7 +187,7 @@ the following file tree: index.js | UK |-- Buttons.js - indes.js + index.js | US |-- Buttons.js index.js diff --git a/platform/docs/versioned_docs/version-2.0/viewer/themeing.md b/platform/docs/versioned_docs/version-2.0-deprecated/viewer/themeing.md similarity index 100% rename from platform/docs/versioned_docs/version-2.0/viewer/themeing.md rename to platform/docs/versioned_docs/version-2.0-deprecated/viewer/themeing.md diff --git a/platform/docs/versioned_docs/version-2.0/faq/browser-support.md b/platform/docs/versioned_docs/version-2.0/faq/browser-support.md deleted file mode 100644 index 8d664095a6..0000000000 --- a/platform/docs/versioned_docs/version-2.0/faq/browser-support.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -sidebar_position: 3 ---- -# Browser Support - -The browsers that we support are specified in the `.browserlistrc` file located -in the `platform/viewer` project. While we leverage the latest language features -when writing code, we rely on `babel` to _transpile_ our code so that it can run -in the browsers that we support. - -## In Practice - -The OHIF Viewer is capable of _running_ on: - -- IE 11 -- FireFox -- Chrome -- Safari -- Edge - -However, we do not have the resources to adequately test and maintain bug free -functionality across all of these. In order to push web based medical imaging -forward, we focus our development efforts on recent version of modern evergreen -browsers. - -Our support of older browsers equates to our willingness to review PRs for bug -fixes, and target their minimum JS support whenever possible. - -### Polyfills - -> A polyfill, or polyfiller, is a piece of code (or plugin) that provides the -> technology that you, the developer, expect the browser to provide natively. - -An example of a polyfill is that you expect `Array.prototype.filter` to exist, -but for some reason, the browser that's being used has not implemented that -language feature yet. Our earlier transpilation will rectify _syntax_ -discrepencies, but unimplemented features require a "temporary" implementation. -That's where polyfills step in. - -You can utilize a service like [polyfill.io](https://polyfill.io/v3/) to -auto-detect and apply polyfills as needed, or you can update the PWA build to -include polyfill's in your bundle by incorporating [core-js][core-js] - - - - -[core-js]: https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md - diff --git a/platform/docs/versioned_docs/version-2.0/faq/scope-of-project.md b/platform/docs/versioned_docs/version-2.0/faq/scope-of-project.md deleted file mode 100644 index 05f97c455d..0000000000 --- a/platform/docs/versioned_docs/version-2.0/faq/scope-of-project.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -sidebar_position: 2 ---- -# Scope of Project - -The OHIF Viewer is a web based medical imaging viewer. This allows it to be used -on almost any device, anywhere. The OHIF Viewer is what is commonly reffered to -as a ["Dumb Client"][simplicable] - -> A dumb client is software that fully depends on a connection to a server or -> cloud service for its functionality. Without a network connection, the -> software offers nothing useful. - [simplicable.com][simplicable] - -While the Viewer persists some data, it's scope is limited to caching things -like user preferences and previous query paramaters. Because of this, the Viewer -has been built to be highly configurable to work with almost any web accessible -data source. - -![scope-of-project diagram](./../assets/img/scope-of-project.png) - -To be more specific, the OHIF Viewer is a collection of HTML, JS, and CSS files. -These can be delivered to your end users however you would like: - -- From the local networok -- From a remote web server -- From a CDN (content delivery network) -- From a service-worker's cache -- etc. - -These "static asset" files are referred to collectively as a "Progressive Web -Application" (PWA), and have the same capabilities and limitations that all PWAs -have. - -All studies, series, images, imageframes, metadata, and the images themselves -must come from an external source. There are many, many ways to provide this -information, the OHIF Viewer's scope **DOES NOT** encompass providing _any_ -data; only the configuration necessary to interface with one or more of these -many data sources. The OHIF Viewer's scope **DOES** include configuration and -support for services that are protected with OpenID-Connect. - -In an effort to aide our users and contributors, we attempt to provide several -[deployment and hosting recipes](./deployment/index.md) as potential starting -points. These are not meant to be rock solid, production ready, solutions; like -most recipes, they should be augmented to best fit you and your organization's -taste, preferences, etc. - -## FAQ - -_Am I able to cache studies for offline viewing?_ - -Not currently. A web page's offline cache capabilities are limited and somewhat -volatile (mostly imposed at the browser vendor level). For more robust offline -caching, you may want to consider a server on the local network, or packaging -the OHIF Viewer as a desktop application. - -_Does the OHIF Viewer work with the local filesystem?_ - -It is possible to accomplish this through extensions; however, for an user -experience that accomodates a large number of studies, you would likely need to -package the OHIF Viewer as an [Electron app][electron]. - - - - -[simplicable]: https://simplicable.com/new/dumb-client -[electron]: https://electronjs.org/ - diff --git a/platform/docs/versioned_docs/version-3.0/README.md b/platform/docs/versioned_docs/version-3.0/README.md deleted file mode 100644 index fc840c84fa..0000000000 --- a/platform/docs/versioned_docs/version-3.0/README.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -id: Introduction -slug: / -sidebar_position: 1 ---- - -The [Open Health Imaging Foundation][ohif-org] (OHIF) Viewer is an open source, -web-based, medical imaging platform. It aims to provide a core framework for -building complex imaging applications. - -Key features: - -- Designed to load large radiology studies as quickly as possible. Retrieves - metadata ahead of time and streams in imaging pixel data as needed. -- Leverages [Cornerstone.js](https://cornerstonejs.org/) for decoding, - rendering, and annotating medical images. -- Works out-of-the-box with Image Archives that support [DICOMWeb][dicom-web]. - Offers a Data Source API for communicating with archives over proprietary API - formats. -- Provides a plugin framework for creating task-based workflow modes which can - re-use core functionality. -- Beautiful user interface (UI) designed with extensibility in mind. UI - components available in a reusable component library built with React.js and - Tailwind CSS - -![OHIF Viewer Screenshot](./assets/img/OHIF-Viewer.png) - - - -## Where to next? - -The Open Health Imaging Foundation intends to provide an imaging viewer -framework which can be easily extended for specific uses. If you find yourself -unable to extend the viewer for your purposes, please reach out via our [GitHub -issues][gh-issues]. We are actively seeking feedback on ways to improve our -integration and extension points. - -Check out these helpful links: - -- Ready to dive into some code? Check out our - [Getting Started Guide](./development/getting-started.md). -- We're an active, vibrant community. - [Learn how you can be more involved.](./development/contributing.md) -- Feeling lost? Read our [help page](/help). - -## Citing OHIF - -To cite the OHIF Viewer in an academic publication, please cite - -> _Open Health Imaging Foundation Viewer: An Extensible Open-Source Framework -> for Building Web-Based Imaging Applications to Support Cancer Research_ -> -> Erik Ziegler, Trinity Urban, Danny Brown, James Petts, Steve D. Pieper, Rob -> Lewis, Chris Hafey, and Gordon J. Harris _JCO Clinical Cancer Informatics_, no. 4 (2020), 336-345, DOI: -> [10.1200/CCI.19.00131](https://www.doi.org/10.1200/CCI.19.00131) - -This article is freely available on Pubmed Central: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7259879/ - - -or, for Lesion Tracker of OHIF v1, please cite: - -> _LesionTracker: Extensible Open-Source Zero-Footprint Web Viewer for Cancer -> Imaging Research and Clinical Trials_ -> -> Trinity Urban, Erik Ziegler, Rob Lewis, Chris Hafey, Cheryl Sadow, Annick D. -> Van den Abbeele and Gordon J. Harris _Cancer Research_, November 1 2017 (77) (21) e119-e122 DOI: -> [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334) - -This article is freely available on Pubmed Central. -https://pubmed.ncbi.nlm.nih.gov/29092955/ - - -**Note:** If you use or find this repository helpful, please take the time to -star this repository on Github. This is an easy way for us to assess adoption, -and it can help us obtain future funding for the project. - -## License - -MIT © [OHIF](https://github.com/OHIF) - -  - - - - -[ohif-org]: https://www.ohif.org -[ohif-demo]: http://viewer.ohif.org/ -[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb -[gh-issues]: https://github.com/OHIF/Viewers/issues - diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/architecture-diagram b/platform/docs/versioned_docs/version-3.0/assets/designs/architecture-diagram deleted file mode 100644 index bbf6cf58b7..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/architecture-diagram and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/canny-full.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/canny-full.fig deleted file mode 100644 index 8756e9f795..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/canny-full.fig and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/cloud.svg b/platform/docs/versioned_docs/version-3.0/assets/designs/cloud.svg deleted file mode 100644 index ad04389c61..0000000000 --- a/platform/docs/versioned_docs/version-3.0/assets/designs/cloud.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/embedded-viewer-diagram b/platform/docs/versioned_docs/version-3.0/assets/designs/embedded-viewer-diagram deleted file mode 100644 index 182ad23239..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/embedded-viewer-diagram and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/nginx-image-archive.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/nginx-image-archive.fig deleted file mode 100644 index 460ae95dd3..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/nginx-image-archive.fig and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/npm-logo-red.svg b/platform/docs/versioned_docs/version-3.0/assets/designs/npm-logo-red.svg deleted file mode 100644 index 8e4aac5d23..0000000000 --- a/platform/docs/versioned_docs/version-3.0/assets/designs/npm-logo-red.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/scope-of-project.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/scope-of-project.fig deleted file mode 100644 index 5eb82e561d..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/scope-of-project.fig and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/user-access-control-request-flow.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/user-access-control-request-flow.fig deleted file mode 100644 index 8982a8fedd..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/designs/user-access-control-request-flow.fig and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-Viewer.png b/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-Viewer.png deleted file mode 100644 index 7f28ceb4f3..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-Viewer.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-e2e-test-studies.png b/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-e2e-test-studies.png deleted file mode 100644 index 4a58a1826a..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-e2e-test-studies.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/SR-exported.png b/platform/docs/versioned_docs/version-3.0/assets/img/SR-exported.png deleted file mode 100644 index fc477adf66..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/SR-exported.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_DEPLOY.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_DEPLOY.png deleted file mode 100644 index 3e562a7971..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_DEPLOY.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_CHECKS.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_CHECKS.png deleted file mode 100644 index f9c4a568b6..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_CHECKS.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png deleted file mode 100644 index 54b0aa39fb..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_RELEASE.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_RELEASE.png deleted file mode 100644 index f3c2a80697..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_RELEASE.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/add-extension.png b/platform/docs/versioned_docs/version-3.0/assets/img/add-extension.png deleted file mode 100644 index bb4955e97f..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/add-extension.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/add-mode.png b/platform/docs/versioned_docs/version-3.0/assets/img/add-mode.png deleted file mode 100644 index 6f1a16280d..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/add-mode.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-no-verbose.png b/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-no-verbose.png deleted file mode 100644 index 40b511309b..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-no-verbose.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-with-verbose.png b/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-with-verbose.png deleted file mode 100644 index b15713b398..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-with-verbose.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode.png b/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode.png deleted file mode 100644 index 68ea6dc01a..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode1.png b/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode1.png deleted file mode 100644 index 7b0375dcc3..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode1.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/cornerstone-tools-link.gif b/platform/docs/versioned_docs/version-3.0/assets/img/cornerstone-tools-link.gif deleted file mode 100644 index 22fde7a7f1..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/cornerstone-tools-link.gif and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/create-extension.png b/platform/docs/versioned_docs/version-3.0/assets/img/create-extension.png deleted file mode 100644 index a68264936a..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/create-extension.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/create-mode.png b/platform/docs/versioned_docs/version-3.0/assets/img/create-mode.png deleted file mode 100644 index 3ca4e26b00..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/create-mode.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/custom-logo.png b/platform/docs/versioned_docs/version-3.0/assets/img/custom-logo.png deleted file mode 100644 index ea3c9ac979..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/custom-logo.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json-public.png b/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json-public.png deleted file mode 100644 index 2d77dafe7b..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json-public.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json.png b/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json.png deleted file mode 100644 index 8eed743e2e..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/docker-pacs.png b/platform/docs/versioned_docs/version-3.0/assets/img/docker-pacs.png deleted file mode 100644 index ad33ebe886..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/docker-pacs.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress-final.png b/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress-final.png deleted file mode 100644 index 49b3a4173f..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress-final.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress.png b/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress.png deleted file mode 100644 index 89ccc3e44a..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/embedded-viewer-diagram.png b/platform/docs/versioned_docs/version-3.0/assets/img/embedded-viewer-diagram.png deleted file mode 100644 index 426cb7ab85..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/embedded-viewer-diagram.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/jwt-explained.png b/platform/docs/versioned_docs/version-3.0/assets/img/jwt-explained.png deleted file mode 100644 index f26509a16f..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/jwt-explained.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-default-theme.png b/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-default-theme.png deleted file mode 100644 index 0ea77f9655..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-default-theme.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-ohif-theme.png b/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-ohif-theme.png deleted file mode 100644 index ad060f262a..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-ohif-theme.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/locizeSponsor.svg b/platform/docs/versioned_docs/version-3.0/assets/img/locizeSponsor.svg deleted file mode 100644 index 1139aa2c76..0000000000 --- a/platform/docs/versioned_docs/version-3.0/assets/img/locizeSponsor.svg +++ /dev/null @@ -1,187 +0,0 @@ - - - - Custom Preset 2 Copy - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/locked-sr.png b/platform/docs/versioned_docs/version-3.0/assets/img/locked-sr.png deleted file mode 100644 index 3c6ba7d531..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/locked-sr.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-1.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-1.png deleted file mode 100644 index cafeae72cb..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-1.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-prompt.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-prompt.png deleted file mode 100644 index 12d21a0a7a..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-prompt.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-tracked.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-tracked.png deleted file mode 100644 index 90758691b0..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-tracked.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-temporary.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-temporary.png deleted file mode 100644 index 9d46fd3192..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-temporary.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurements-prevNext.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurements-prevNext.png deleted file mode 100644 index d4bd71bb6c..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/measurements-prevNext.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/mode-archs.png b/platform/docs/versioned_docs/version-3.0/assets/img/mode-archs.png deleted file mode 100644 index f931818c37..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/mode-archs.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/mode-clock.png b/platform/docs/versioned_docs/version-3.0/assets/img/mode-clock.png deleted file mode 100644 index d855edbca7..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/mode-clock.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/mode-template.png b/platform/docs/versioned_docs/version-3.0/assets/img/mode-template.png deleted file mode 100644 index 9442411b5e..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/mode-template.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/nginx-image-archive.png b/platform/docs/versioned_docs/version-3.0/assets/img/nginx-image-archive.png deleted file mode 100644 index bd75479652..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/nginx-image-archive.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/ohif-cli-list.png b/platform/docs/versioned_docs/version-3.0/assets/img/ohif-cli-list.png deleted file mode 100644 index a992f0169b..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/ohif-cli-list.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/open-graph.png b/platform/docs/versioned_docs/version-3.0/assets/img/open-graph.png deleted file mode 100644 index 5b881abdfd..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/open-graph.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/overview.png b/platform/docs/versioned_docs/version-3.0/assets/img/overview.png deleted file mode 100644 index d504f5b141..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/overview.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-left-right.png b/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-left-right.png deleted file mode 100644 index fdbb558848..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-left-right.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-v3.png b/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-v3.png deleted file mode 100644 index 83f9e19198..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-v3.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/panelmodule-icon.png b/platform/docs/versioned_docs/version-3.0/assets/img/panelmodule-icon.png deleted file mode 100644 index b1e4c53513..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/panelmodule-icon.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/restore-exported-sr.png b/platform/docs/versioned_docs/version-3.0/assets/img/restore-exported-sr.png deleted file mode 100644 index 7aeca26925..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/restore-exported-sr.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/scope-of-project.png b/platform/docs/versioned_docs/version-3.0/assets/img/scope-of-project.png deleted file mode 100644 index 6daac8bee3..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/scope-of-project.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services-data.png b/platform/docs/versioned_docs/version-3.0/assets/img/services-data.png deleted file mode 100644 index e5251edd2d..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/services-data.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services-measurements.png b/platform/docs/versioned_docs/version-3.0/assets/img/services-measurements.png deleted file mode 100644 index 900419a8d1..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/services-measurements.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services-ui.png b/platform/docs/versioned_docs/version-3.0/assets/img/services-ui.png deleted file mode 100644 index 34c3bf1ffe..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/services-ui.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services.png b/platform/docs/versioned_docs/version-3.0/assets/img/services.png deleted file mode 100644 index 569c046c0d..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/services.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/surge-deploy.gif b/platform/docs/versioned_docs/version-3.0/assets/img/surge-deploy.gif deleted file mode 100644 index 545f068635..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/surge-deploy.gif and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/template-extension-files.png b/platform/docs/versioned_docs/version-3.0/assets/img/template-extension-files.png deleted file mode 100644 index 465c2a91e3..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/template-extension-files.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-files.png b/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-files.png deleted file mode 100644 index 0c44ca962d..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-files.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-ui.png b/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-ui.png deleted file mode 100644 index c63d1722ba..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-ui.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbar-module.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbar-module.png deleted file mode 100644 index 57536695f6..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/toolbar-module.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-layout.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-layout.png deleted file mode 100644 index 8190b5f96e..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-layout.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-nested-buttons.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-nested-buttons.png deleted file mode 100644 index 1a85837f38..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-nested-buttons.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-zoom.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-zoom.png deleted file mode 100644 index 00acfcaec2..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-zoom.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracked-not-tracked.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracked-not-tracked.png deleted file mode 100644 index d61b36d7e8..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/tracked-not-tracked.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow1.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow1.png deleted file mode 100644 index d5b5959fa2..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow1.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow2.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow2.png deleted file mode 100644 index 988e56d4b0..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow2.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow3.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow3.png deleted file mode 100644 index c62fde75cf..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow3.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/ui-modal.gif b/platform/docs/versioned_docs/version-3.0/assets/img/ui-modal.gif deleted file mode 100644 index 599964e244..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/ui-modal.gif and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/ui-services.png b/platform/docs/versioned_docs/version-3.0/assets/img/ui-services.png deleted file mode 100644 index dd53063777..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/ui-services.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-access-control-request-flow.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-access-control-request-flow.png deleted file mode 100644 index 573c835038..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-access-control-request-flow.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys-default.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys-default.png deleted file mode 100644 index 7621c4fedc..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys-default.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys.png deleted file mode 100644 index 389aa31dd1..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-measurement-export.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-measurement-export.png deleted file mode 100644 index 1ce61742d9..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-measurement-export.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-open-viewer.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-open-viewer.png deleted file mode 100644 index 5e2b29c4ed..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-open-viewer.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-filter.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-filter.png deleted file mode 100644 index 05d0c4ba7d..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-filter.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-list.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-list.png deleted file mode 100644 index 4d589599ef..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-list.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-next.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-next.png deleted file mode 100644 index b082eedea2..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-next.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-panel.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-panel.png deleted file mode 100644 index 42db7c2f78..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-panel.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-summary.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-summary.png deleted file mode 100644 index e5a5aad16a..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-summary.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-studyist-modespecific.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-studyist-modespecific.png deleted file mode 100644 index bf878bc591..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-studyist-modespecific.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-download-icon.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-download-icon.png deleted file mode 100644 index ca7eef55c1..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-download-icon.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-extra.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-extra.png deleted file mode 100644 index 15632fd0a2..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-extra.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-preset.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-preset.png deleted file mode 100644 index 5a24014686..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-preset.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbarDownload.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbarDownload.png deleted file mode 100644 index d39c9cbe07..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbarDownload.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-layout.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-layout.png deleted file mode 100644 index 71110885db..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-layout.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-main.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-main.png deleted file mode 100644 index 1ef3e76da2..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-main.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar-measurements.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar-measurements.png deleted file mode 100644 index 2cb7fd7f0a..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar-measurements.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar.png deleted file mode 100644 index fc36c58c0c..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer.png deleted file mode 100644 index c79dce13dd..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule-layout.png b/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule-layout.png deleted file mode 100644 index 58af0ad27e..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule-layout.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule.png b/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule.png deleted file mode 100644 index 05423370c7..0000000000 Binary files a/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule.png and /dev/null differ diff --git a/platform/docs/versioned_docs/version-3.0/configuration/_category_.json b/platform/docs/versioned_docs/version-3.0/configuration/_category_.json deleted file mode 100644 index 0aea1748dc..0000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Configuration", - "position": 4 -} diff --git a/platform/docs/versioned_docs/version-3.0/configuration/configuration.md b/platform/docs/versioned_docs/version-3.0/configuration/configuration.md deleted file mode 100644 index cea7ce6fab..0000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/configuration.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Configuration ---- - -# Viewer: Configuration - -The OHIF Viewer Platform strives to be highly configurable and extensible. This -makes it easier for our community members to keep their "secret sauce" private, -and incentives contributions back to the platform. The `@ohif/viewer` project of -the platform is the lynchpin that combines everything to create our application. - -There are two configuration mechanisms, one for run-time configuration, allowing -for changes to be decided based on including different configuration files as -specified by the 'theme' URL parameters. This mechanism is intended for -modifications of data exposed as configurable items by the existing code. See -sections below on configuring this type of value. - -The other mechanism is the code-configuration mechanism that specifies load -time configuration. This is intended to load things that require code level -changes to OHIF such as adding a new viewer configuration. This is also used -for base definitions that are shared site-wide such as the data sources. This -was the original configuration mechanism provided, and some of the configurations -specified there are better suited to the run time loading, but are currently -left alone as there hasn't been time to move them. - -We maintain a number of common viewer application configurations at -[`/platform/viewer/public/configs`][config-dir]. - -You can take a look at how to use different configs in the -[Environment Variables](../platform/environment-variables) - -```js title="/platform/viewer/public/configs" -window.config = { - routerBasename: '/', - /** - * "White Labeling" is used to change the branding, look, and feel of the OHIF - * Viewer. These settings, and the color variables that are used by our components, - * are the easiest way to rebrand the application. - * - * More extensive changes are made possible through swapping out the UI library, - * Viewer project, or extensions. - */ - whiteLabeling: { - /** ... **/ - }, - httpErrorHandler: { - /** coming soon **/ - }, - extensions: [], - showStudyList: true, - filterQueryParam: false, - dataSources: [ - { - friendlyName: 'dcmjs DICOMWeb Server', - namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', - sourceName: 'dicomweb', - configuration: { - name: 'DCM4CHEE', - wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - qidoSupportsIncludeField: true, - supportsReject: true, - imageRendering: 'wadors', - thumbnailRendering: 'wadors', - enableStudyLazyLoad: true, - supportsFuzzyMatching: true, - supportsWildcard: true, - }, - }, - ], -}; -``` - - - - - -[config-dir]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config - diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/_category_.json b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/_category_.json deleted file mode 100644 index fe1e3e8595..0000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Data Sources", - "position": 2 -} diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-json.md b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-json.md deleted file mode 100644 index 08cbac7522..0000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-json.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: DICOM JSON ---- - -# DICOM JSON - -You can launch the OHIF Viewer with a JSON file which points to a DICOMWeb -server as well as a list of study and series instance UIDs along with metadata. - -An example would look like - -`https://viewer.ohif.org/viewer/dicomjson?url=https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001.json` - -As you can see the url to the location of the JSON file is passed in the query -after the `dicomjson` string, which is -`https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001.json` (this -json file has been generated by OHIF team and stored in an amazon s3 bucket for -the purpose of the guide). - -## DICOM JSON sample - -Here we are using the LIDC-IDRI-0001 case which is a sample of the LIDC-IDRI -dataset. Let's have a look at the JSON file: - -### Metadata - -JSON file stores the metadata for the study level, series level and instance -level. A JSON launch file should follow the same structure as the one below. - -Note that at the instance level metadata we are storing both the `metadata` and -also the `url` for the dicom file on the dicom server. In this case we are -referring to -`dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm` -which is stored in another directory in our s3. (You can actually try -downloading the dicom file by opening the url in your browser). - -```json -{ - "studies": [ - // first study metadata - { - "StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178", - "StudyDate": "20000101", - "StudyTime": "", - "PatientName": "", - "PatientID": "LIDC-IDRI-0001", - "AccessionNumber": "", - "PatientAge": "", - "PatientSex": "", - "series": [ - // first series metadata - { - "SeriesInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192", - "SeriesNumber": 3000566, - "Modality": "CT", - "SliceThickness": 2.5, - "instances": [ - // first instance metadata - { - "metadata": { - "Columns": 512, - "Rows": 512, - "InstanceNumber": 1, - "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2", - "PhotometricInterpretation": "MONOCHROME2", - "BitsAllocated": 16, - "BitsStored": 16, - "PixelRepresentation": 1, - "SamplesPerPixel": 1, - "PixelSpacing": [0.703125, 0.703125], - "HighBit": 15, - "ImageOrientationPatient": [1, 0, 0, 0, 1, 0], - "ImagePositionPatient": [-166, -171.699997, -10], - "FrameOfReferenceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.229925374658226729607867499499", - "ImageType": ["ORIGINAL", "PRIMARY", "AXIAL"], - "Modality": "CT", - "SOPInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.262721256650280657946440242654", - "SeriesInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192", - "StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178", - "WindowCenter": -600, - "WindowWidth": 1600, - "SeriesDate": "20000101" - }, - "url": "dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm" - }, - // second instance metadata - { - "metadata": { - "Columns": 512, - "Rows": 512, - "InstanceNumber": 2, - "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2", - "PhotometricInterpretation": "MONOCHROME2", - "BitsAllocated": 16, - "BitsStored": 16, - "PixelRepresentation": 1, - "SamplesPerPixel": 1, - "PixelSpacing": [0.703125, 0.703125], - "HighBit": 15, - "ImageOrientationPatient": [1, 0, 0, 0, 1, 0], - "ImagePositionPatient": [-166, -171.699997, -12.5], - "FrameOfReferenceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.229925374658226729607867499499", - "ImageType": ["ORIGINAL", "PRIMARY", "AXIAL"], - "Modality": "CT", - "SOPInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.512235483218154065970649917292", - "SeriesInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192", - "StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178", - "WindowCenter": -600, - "WindowWidth": 1600, - "SeriesDate": "20000101" - }, - "url": "dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-002.dcm" - } - // ..... other instances metadata - ] - } - // ... other series metadata - ], - "NumInstances": 133, - "Modalities": "CT" - } - // second study metadata - ] -} -``` - -![](../../assets/img/dicom-json.png) - -### Local Demo - -You can run OHIF with a JSON data source against you local datasets (given that -their JSON metadata is extracted). - -First you need to put the JSON file and the folder containing the dicom files -inside your `public` folder. Since files are served from your local server the -`url` for the JSON file will be `http://localhost:3000/LIDC-IDRI-0001.json` and -the dicom files will be -`dicomweb:http://localhost:3000/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm`. - -After `yarn install` and running `yarn dev` and opening the browser at -`http://localhost:3000/viewer/dicomjson?url=http://localhost:3000/LIDC-IDRI-0001.json` -will display the viewer. - -Download JSON file from -[here](https://www.dropbox.com/sh/zvkv6mrhpdze67x/AADLGK46WuforD2LopP99gFXa?dl=0) - -Sample DICOM files can be downloaded from -[TCIA](https://wiki.cancerimagingarchive.net/display/Public/LIDC-IDRI) or -directly from -[here](https://www.dropbox.com/sh/zvkv6mrhpdze67x/AADLGK46WuforD2LopP99gFXa?dl=0) - -Your public folder should look like this: - -![](../../assets/img/dicom-json-public.png) diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-web.md b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-web.md deleted file mode 100644 index 83fbc1750b..0000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-web.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: DICOMweb ---- - -# DICOMweb - -## Set up a local DICOM server - -ATTENTION! Already have a remote or local server? Skip to the -[configuration section](#configuration-learn-more) below. - -While the OHIF Viewer can work with any data source, the easiest to configure -are the ones that follow the [DICOMWeb][dicom-web] spec. - -1. Choose and install an Image Archive -2. Upload data to your archive (e.g. with DCMTK's [storescu][storescu] or your - archive's web interface) -3. Keep the server running - -For our purposes, we will be using `Orthanc`, but you can see a list of -[other Open Source options](#open-source-dicom-image-archives) below. - -### Requirements - -- Docker - - [Docker for Mac](https://docs.docker.com/docker-for-mac/) - - [Docker for Windows (recommended)](https://docs.docker.com/docker-for-windows/) - - [Docker Toolbox for Windows](https://docs.docker.com/toolbox/toolbox_install_windows/) - -_Not sure if you have `docker` installed already? Try running `docker --version` -in command prompt or terminal_ - -> If you are using `Docker Toolbox` you need to change the _PROXY_DOMAIN_ -> parameter in _platform/viewer/package.json_ to http://192.168.99.100:8042 or -> the ip docker-machine ip throws. This is the value [`WebPack`][webpack-proxy] -> uses to proxy requests - -## Open Source DICOM Image Archives - -There are a lot of options available to you to use as a local DICOM server. Here -are some of the more popular ones: - -| Archive | Installation | -| --------------------------------------------- | ---------------------------------- | -| [DCM4CHEE Archive 5.x][dcm4chee] | [W/ Docker][dcm4chee-docker] | -| [Orthanc][orthanc] | [W/ Docker][orthanc-docker] | -| [DICOMcloud][dicomcloud] (**DICOM Web only**) | [Installation][dicomcloud-install] | -| [OsiriX][osirix] (**Mac OSX only**) | Desktop Client | -| [Horos][horos] (**Mac OSX only**) | Desktop Client | - -_Feel free to make a Pull Request if you want to add to this list._ - -Below, we will focus on `DCM4CHEE` and `Orthanc` usage: - -### Running Orthanc - -_Start Orthanc:_ - -```bash -# Runs orthanc so long as window remains open -yarn run orthanc:up -``` - -_Upload your first Study:_ - -1. Navigate to - [Orthanc's web interface](http://localhost:8042/app/explorer.html) at - `http://localhost:8042/app/explorer.html` in a web browser. -2. In the top right corner, click "Upload" -3. Click "Select files to upload..." and select one or more DICOM files -4. Click "Start the upload" - -#### Orthanc: Learn More - -You can see the `docker-compose.yml` file this command runs at -[`/.docker/Nginx-Orthanc/`][orthanc-docker-compose], and more on -Orthanc for Docker in [Orthanc's documentation][orthanc-docker]. - -#### Connecting to Orthanc - -Now that we have a local Orthanc instance up and running, we need to configure -our web application to connect to it. Open a new terminal window, navigate to -this repository's root directory, and run: - -```bash -# If you haven't already, enable yarn workspaces -yarn config set workspaces-experimental true - -# Restore dependencies -yarn install - -# Run our dev command, but with the local orthanc config -yarn run dev:orthanc -``` - -#### Configuration: Learn More - -> For more configuration fun, check out the -> [Essentials Configuration](../index.md) guide. - -Let's take a look at what's going on under the hood here. `yarn run dev:orthanc` -is running the `dev:orthanc` script in our project's `package.json` (inside -`platform/viewer`). That script is: - -```js -cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js -w -``` - -- `cross-env` sets three environment variables - - PROXY_TARGET: `/dicom-web` - - PROXY_DOMAIN: `http://localhost:8042` - - APP_CONFIG: `config/docker_nginx-orthanc.js` -- `webpack-dev-server` runs using the `.webpack/webpack.pwa.js` configuration - file. It will watch for changes and update as we develop. - -`PROXY_TARGET` and `PROXY_DOMAIN` tell our development server to proxy requests -to `Orthanc`. This allows us to bypass CORS issues that normally occur when -requesting resources that live at a different domain. - -The `APP_CONFIG` value tells our app which file to load on to `window.config`. -By default, our app uses the file at -`/platform/viewer/public/config/default.js`. Here is what that -configuration looks like: - -```js -window.config = { - routerBasename: '/', - extensions: [], - modes: [], - showStudyList: true, - dataSources: [ - { - friendlyName: 'dcmjs DICOMWeb Server', - namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', - sourceName: 'dicomweb', - configuration: { - name: 'DCM4CHEE', - wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - qidoSupportsIncludeField: true, - supportsReject: true, - imageRendering: 'wadors', - thumbnailRendering: 'wadors', - enableStudyLazyLoad: true, - supportsFuzzyMatching: true, - supportsWildcard: true, - }, - }, - ], - defaultDataSourceName: 'dicomweb', -}; -``` - -To learn more about how you can configure the OHIF Viewer, check out our -[Configuration Guide](../index.md). - -### Running DCM4CHEE - -dcm4che is a collection of open source applications for healthcare enterprise -written in Java programming language which implements DICOM standard. dcm4chee -(extra 'e' at the end) is dcm4che project for an Image Manager/Image Archive -which provides storage, retrieval and other functionalities. You can read more -about dcm4chee in their website [here](https://www.dcm4che.org/) - -DCM4chee installation is out of scope for these tutorials and can be found -[here](https://github.com/dcm4che/dcm4chee-arc-light/wiki/Run-minimum-set-of-archive-services-on-a-single-host) - -An overview of steps for running OHIF Viewer using a local DCM4CHEE is shown -below: - -
- -
- -[dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light -[dcm4chee-docker]: - https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker -[orthanc]: https://www.orthanc-server.com/ -[orthanc-docker]: http://book.orthanc-server.com/users/docker.html -[dicomcloud]: https://github.com/DICOMcloud/DICOMcloud -[dicomcloud-install]: https://github.com/DICOMcloud/DICOMcloud#running-the-code -[osirix]: http://www.osirix-viewer.com/ -[horos]: https://www.horosproject.org/ -[default-config]: - https://github.com/OHIF/Viewers/blob/master/platform/viewer/public/config/default.js -[html-templates]: - https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/html-templates -[config-files]: - https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/static-files.md b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/static-files.md deleted file mode 100644 index 62c53d69b9..0000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/static-files.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Static Files ---- - -# Static Files - -There is a binary DICOM to static file generator, which provides easily served -binary files. The files are all compressed in order to reduce space -significantly, and are pre-computed for the files required for OHIF, so that the -performance of serving the files is just the read from disk/write to http stream -time, without any extra processing time. - -The project for the static wado files is located here: [static-wado]: -https://github.com/wayfarer3130/static-wado - -It can be compiled with Java and Gradle, and then run against a set of dicom, in -the example located in /dicom/study1 outputting to /dicomweb, and then a server -run against that data, like this: - -``` -git clone https://github.com/wayfarer3130/static-wado.git -cd static-wado -./gradlew installDist -StaticWado/build/install/StaticWado/bin/StaticWado -d /dicomweb /dicom/study1 -cd /dicomweb -npx http-server -p 5000 --cors -g -``` - -There is then a dev environment in the platform/viewer directory which can be -run against those files, like this: - -``` -cd platform/viewer -yarn dev:static -``` - -Additional studies can be added to the dicomweb by re-running the StaticWado -command. It will create a single studies.gz index file (JSON DICOM file, -compressed) containing an index of all studies created. There is then a small -extension to OHIF which performs client side indexing. - -The StaticWado command also knows how to deploy a client and dicomweb directory -to Amazon s3, which can then server files up directly. There is another build -setup build:aws in the viewer package.json to create such a deployment. diff --git a/platform/docs/versioned_docs/version-3.0/configuration/index.md b/platform/docs/versioned_docs/version-3.0/configuration/index.md deleted file mode 100644 index f51abd3d3a..0000000000 --- a/platform/docs/versioned_docs/version-3.0/configuration/index.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Overview ---- - -# Overview - -After following the steps outlined in -[Getting Started](./../development/getting-started.md), you'll notice that the -OHIF Viewer has data for several studies and their images. You didn't add this -data, so where is it coming from? - -By default, the viewer is configured to connect to a remote server hosted by the -nice folks over at [dcmjs.org][dcmjs-org]. While convenient for getting started, -the time may come when you want to develop using your own data either locally or -remotely. - -## Configuration Files - -The configuration for our viewer is in the `platform/viewer/public/config` -directory. Our build process knows which configuration file to use based on the -`APP_CONFIG` environment variable. By default, its value is -[`config/default.js`][default-config]. The majority of the viewer's features, -and registered extension's features, are configured using this file. - -The simplest way is to update the existing default config: - -```js title="platform/viewer/public/config/default.js" -window.config = { - routerBasename: '/', - extensions: [], - modes: [], - showStudyList: true, - dataSources: [ - { - friendlyName: 'dcmjs DICOMWeb Server', - namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', - sourceName: 'dicomweb', - configuration: { - name: 'DCM4CHEE', - wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - qidoSupportsIncludeField: true, - supportsReject: true, - imageRendering: 'wadors', - thumbnailRendering: 'wadors', - enableStudyLazyLoad: true, - supportsFuzzyMatching: true, - supportsWildcard: true, - }, - }, - ], - defaultDataSourceName: 'dicomweb', -}; -``` - -> As you can see a new change in `OHIF-v3` is the addition of `dataSources`. You -> can build your own datasource and map it to the internal data structure of -> OHIF’s > metadata and enjoy using other peoples developed mode on your own -> data! -> -> You can read more about data sources at -> [Data Source section in Modes](../platform/modes/index.md) - -The configuration can also be written as a JS Function in case you need to -inject dependencies like external services: - -```js -window.config = ({ servicesManager } = {}) => { - const { UIDialogService } = servicesManager.services; - return { - cornerstoneExtensionConfig: { - tools: { - ArrowAnnotate: { - configuration: { - getTextCallback: (callback, eventDetails) => UIDialogService.create({... - } - } - }, - }, - routerBasename: '/', - dataSources: [ - { - friendlyName: 'dcmjs DICOMWeb Server', - namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', - sourceName: 'dicomweb', - configuration: { - name: 'DCM4CHEE', - wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', - qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', - qidoSupportsIncludeField: true, - supportsReject: true, - imageRendering: 'wadors', - thumbnailRendering: 'wadors', - enableStudyLazyLoad: true, - supportsFuzzyMatching: true, - supportsWildcard: true, - }, - }, - ], - defaultDataSourceName: 'dicomweb', - }; -}; -``` - - - -## Environment Variables - -We use environment variables at build and dev time to change the Viewer's -behavior. We can update the `HTML_TEMPLATE` to easily change which extensions -are registered, and specify a different `APP_CONFIG` to connect to an -alternative data source (or even specify different default hotkeys). - -| Environment Variable | Description | Default | -| -------------------- | -------------------------------------------------------------------------------------------------- | ------------------- | -| `HTML_TEMPLATE` | Which [HTML template][html-templates] to use as our web app's entry point. Specific to PWA builds. | `index.html` | -| `PUBLIC_URL` | The route relative to the host that the app will be served from. Specific to PWA builds. | `/` | -| `APP_CONFIG` | Which [configuration file][config-file] to copy to output as `app-config.js` | `config/default.js` | -| `PROXY_TARGET` | When developing, proxy requests that match this pattern to `PROXY_DOMAIN` | `undefined` | -| `PROXY_DOMAIN` | When developing, proxy requests from `PROXY_TARGET` to `PROXY_DOMAIN` | `undefined` | - -You can also create a new config file and specify its path relative to the build -output's root by setting the `APP_CONFIG` environment variable. You can set the -value of this environment variable a few different ways: - -- ~[Add a temporary environment variable in your shell](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-temporary-environment-variables-in-your-shell)~ - - Previous `react-scripts` functionality that we need to duplicate with - `dotenv-webpack` -- ~[Add environment specific variables in `.env` file(s)](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-development-environment-variables-in-env)~ - - Previous `react-scripts` functionality that we need to duplicate with - `dotenv-webpack` -- Using the `cross-env` package in a npm script: - - `"build": "cross-env APP_CONFIG=config/my-config.js react-scripts build"` - -After updating the configuration, `yarn run build` to generate updated build -output. - - - - -[dcmjs-org]: https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado -[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb -[storescu]: https://support.dcmtk.org/docs/storescu.html -[webpack-proxy]: https://webpack.js.org/configuration/dev-server/#devserverproxy -[orthanc-docker-compose]: https://github.com/OHIF/Viewers/tree/master/.docker/Nginx-Orthanc - -[dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light -[dcm4chee-docker]: https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker -[orthanc]: https://www.orthanc-server.com/ -[orthanc-docker]: https://book.orthanc-server.com/users/docker.html -[dicomcloud]: https://github.com/DICOMcloud/DICOMcloud -[dicomcloud-install]: https://github.com/DICOMcloud/DICOMcloud#running-the-code -[osirix]: https://www.osirix-viewer.com/ -[horos]: https://www.horosproject.org/ -[default-config]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/public/config/default.js -[html-templates]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/html-templates -[config-files]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config - diff --git a/platform/docs/versioned_docs/version-3.0/deployment/_category_.json b/platform/docs/versioned_docs/version-3.0/deployment/_category_.json deleted file mode 100644 index 534be1dfb6..0000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Deployment", - "position": 3 -} diff --git a/platform/docs/versioned_docs/version-3.0/deployment/build-for-production.md b/platform/docs/versioned_docs/version-3.0/deployment/build-for-production.md deleted file mode 100644 index b23463237a..0000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/build-for-production.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Build for Production - -### Build Machine Requirements - -- [Node.js & NPM](https://nodejs.org/en/download/) -- [Yarn](https://yarnpkg.com/lang/en/docs/install/) -- [Git](https://www.atlassian.com/git/tutorials/install-git) - -### Getting the Code - -_With Git:_ - -```bash -# Clone the remote repository to your local machine -git clone https://github.com/OHIF/Viewers.git -``` - -More on: _[`git clone`](https://git-scm.com/docs/git-clone), -[`git checkout`](https://git-scm.com/docs/git-checkout)_ - -_From .zip:_ - -[OHIF/Viewers: master.zip](https://github.com/OHIF/Viewers/archive/master.zip) - -### Restore Dependencies & Build - -Open your terminal, and navigate to the directory containing the source files. -Next run these commands: - -```bash -# If you haven't already, enable yarn workspaces -yarn config set workspaces-experimental true - -# Restore dependencies -yarn install - -# Build source code for production -yarn run build -``` - -If everything worked as expected, you should have a new `dist/` directory in the -`platform/viewer/dist` folder. It should roughly resemble the following: - -```bash title="platform/viewer/dist/" -├── app-config.js -├── app.bundle.js -├── app.css -├── index.html -├── manifest.json -├── service-worker.js -└── ... -``` - -By default, the build output will connect to OHIF's publicly accessible PACS. If -this is your first time setting up the OHIF Viewer, it is recommended that you -test with these default settings. After testing, you can find instructions on -how to configure the project for your own imaging archive below. - -### Configuration - -The configuration for our viewer is in the `platform/viewer/public/config` -directory. Our build process knows which configuration file to use based on the -`APP_CONFIG` environment variable. By default, its value is -[`config/default.js`][default-config]. The majority of the viewer's features, -and registered extension's features, are configured using this file. - -The easiest way to apply your own configuration is to modify the `default.js` -file. For more advanced configuration options, check out our -[configuration essentials guide](../configuration/index.md). - -## Next Steps - -### Deploying Build Output - -_Drag-n-drop_ - -- [Netlify: Drop](./static-assets#netlify-drop) - -_Easy_ - -- [Surge.sh](./static-assets#surgesh) -- [GitHub Pages](./static-assets#github-pages) - -_Advanced_ - -- [AWS S3 + Cloudfront](./static-assets#aws-s3--cloudfront) -- [GCP + Cloudflare](./static-assets#gcp--cloudflare) -- [Azure](./static-assets#azure) - -### Testing Build Output Locally - -A quick way to test your build output locally is to spin up a small webserver. -You can do this by running the following commands in the `dist/` output -directory: - -```bash -# Install http-server as a globally available package -yarn global add http-server - -# Change the directory to the platform/viewer - -# Serve the files in our current directory -# Accessible at: `http://localhost:8080` -npx http-server ./dist -``` - -
- -
- -### Automating Builds and Deployments - -If you found setting up your environment and running all of these steps to be a -bit tedious, then you are in good company. Thankfully, there are a large number -of tools available to assist with automating tasks like building and deploying -web application. For a starting point, check out this repository's own use of: - -- [CircleCI][circleci]: [config.yaml][circleci-config] -- [Netlify][netlify]: [netlify.toml][netlify.toml] | - [build-deploy-preview.sh][build-deploy-preview.sh] - - -[circleci]: https://circleci.com/gh/OHIF/Viewers -[circleci-config]: https://github.com/OHIF/Viewers/blob/master/.circleci/config.yml -[netlify]: https://app.netlify.com/sites/ohif/deploys -[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/netlify.toml -[build-deploy-preview.sh]: https://github.com/OHIF/Viewers/blob/master/.netlify/build-deploy-preview.sh - diff --git a/platform/docs/versioned_docs/version-3.0/deployment/google-cloud-healthcare.md b/platform/docs/versioned_docs/version-3.0/deployment/google-cloud-healthcare.md deleted file mode 100644 index 532bf4cc63..0000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/google-cloud-healthcare.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Google Cloud Healthcare - -> Coming soon - We are working on bringing Google Cloud Healthcare to OHIF-v3 diff --git a/platform/docs/versioned_docs/version-3.0/deployment/index.md b/platform/docs/versioned_docs/version-3.0/deployment/index.md deleted file mode 100644 index 384aefc777..0000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/index.md +++ /dev/null @@ -1,335 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Overview ---- - -# Deployment - -The OHIF Viewer can be embedded in other web applications via it's [packaged -script source][viewer-npm], or served up as a stand-alone PWA ([progressive web -application][pwa-url]) by building and hosting a collection of static assets. In -either case, you will need to configure your instance of the Viewer so that it -can connect to your data source (the database or PACS that provides the data -your Viewer will display). - -## Overview - -Our goal is to make deployment as simple and painless as possible; however, -there is an inherent amount of complexity in configuring and deploying web -applications. If you find yourself a little lost, please don't hesitate to -[reach out for help](/help) - -## Deployment Scenarios - -### Embedded Viewer (deprecated) - -`OHIF-v3` has deprecated deploying the viewer as an embedded viewer the number -of underlying libraries that run web workers are increasing for OHIF. An example -of these libraries is OHIF's 3D rendering functionality that is provided by -`vtk-js`. - -### Stand-alone Viewer - -Deploying the OHIF Viewer as a stand-alone web application provides many -benefits, but comes at the cost of time and complexity. Some benefits include: - -_Today:_ - -- Leverage [extensions](../platform/extensions/index.md) and - [modes](../platform/modes/index.md) to drop-in powerful new features -- Add routes and customize the viewer's workflow -- Finer control over styling and whitelabeling - -_In the future:_ - -- The ability to package the viewer for [App Store distribution][app-store] -- Leverage `service-workers` for offline support and speed benefits from caching - -#### Hosted Static Assets - -At the end of the day, a production OHIF Viewer instance is a collection of -HTML, CSS, JS, Font Files, and Images. We "build" those files from our -`source code` with configuration specific to our project. We then make those -files publicly accessible by hosting them on a Web Server. - -If you have not deployed a web application before, this may be a good time to -[reach out for help](/help), as these steps assume prior web development and -deployment experience. - -##### Part 1 - Build Production Assets - -"Building", or creating, the files you will need is the same regardless of the -web host you choose. You can find detailed instructions on how to configure and -build the OHIF Viewer in our -["Build for Production" guide](./build-for-production.md). - -##### Part 2 - Host Your App - -There are a lot of [benefits to hosting static assets][host-static-assets] over -dynamic content. You can find instructions on how to host your build's output -via one of these guides: - -_Drag-n-drop_ - -- [Netlify: Drop](./static-assets.md#netlify-drop) - -_Easy_ - -- [Surge.sh](./static-assets.md#surgesh) -- [GitHub Pages](./static-assets.md#github-pages) - -_Advanced_ - -- [AWS S3 + Cloudfront](./static-assets.md#aws-s3--cloudfront) -- [GCP + Cloudflare](./static-assets.md#gcp--cloudflare) -- [Azure](./static-assets.md#azure) - -## Data - -The OHIF Viewer is able to connect to any data source that implements the [DICOM -Web Standard][dicom-web-standard]. [DICOM Web][dicom-web] refers to RESTful -DICOM Services -- a recently standardized set of guidelines for exchanging -medical images and imaging metadata over the internet. Not all archives fully -support it yet, but it is gaining wider adoption. - -### Configure Connection - -If you have an existing archive and intend to host the OHIF Viewer at the same -domain name as your archive, then connecting the two is as simple as following -the steps layed out in our -[Configuration Essentials Guide](./../configuration/index.md). - -#### What if I don't have an imaging archive? - -We provide some guidance on configuring a local image archive in our -[Data Source Essentials](./../configuration/index.md#set-up-a-local-DICOM-server) -guide. Hosting an archive remotely is a little trickier. You can check out some -of our [advanced recipes](#recipes) for modeled setups that may work for you. - -#### What if I intend to host the OHIF Viewer at a different domain? - -There are two important steps to making sure this setup works: - -1. Your Image Archive needs to be exposed, in some way, to the open web. This - can be directly, or through a `reverse proxy`, but the Viewer needs _some - way_ to request its data. -2. \* Your Image Archive needs to have appropriate CORS (Cross-Origin Resource - Sharing) Headers - -> \* Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional -> HTTP headers to tell a browser to let a web application running at one origin -> (domain) have permission to access selected resources from a server at a -> different origin. - [MDN Web Docs: Web - Http - CORS][cors] - -Most image archives do not provide either of these features "out of the box". -It's common to use IIS, Nginx, or Apache to route incoming requests and append -appropriate headers. You can find an example of this setup in our -[Nginx + Image Archive Deployment Recipe](./nginx--image-archive.md). - -#### What if my archive doesn't support DicomWeb? - -It's possible to supply all Study data via JSON format, in the event you do not -have a DicomWeb endpoint. You can host all of the relevant files on any web -accessible server (Amazon S3, Azure Blob Storage, Local file server etc.) - -This JSON is supplied via the '?url=' query parameter. It should reference an -endpoint that returns **application/json** formatted text. - -If you do not have an API, you can simply return a text file containing the JSON -from any web server. - -You tell the OHIF viewer to use JSON by using the `dicomjson` datasource and -appending `'?url='` query to your mode's route: - -e.g. -`https://my-test-ohif-server/myMode/dicomjson?url=https://my-json-server/study-uid.json` - -The returned JSON object must contain a single root object with a 'studies' -array. - -You can read more about using different data sources for mode's routes -[here](../platform/modes/routes.md#route-path) - -_Sample JSON format:_ - -```json -{ - "studies": [ - { - "StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78", - "StudyDescription": "BRAIN SELLA", - "StudyDate": "20010108", - "StudyTime": "120022", - "PatientName": "MISTER^MR", - "PatientId": "832040", - "series": [ - { - "SeriesDescription": "SAG T-1", - "SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121", - "SeriesNumber": 2, - "SeriesDate": "20010108", - "SeriesTime": "120318", - "Modality": "MR", - "instances": [ - { - "metadata": { - "Columns": 512, - "Rows": 512, - "InstanceNumber": 3, - "AcquisitionNumber": 0, - "PhotometricInterpretation": "MONOCHROME2", - "BitsAllocated": 16, - "BitsStored": 16, - "PixelRepresentation": 1, - "SamplesPerPixel": 1, - "PixelSpacing": [0.390625, 0.390625], - "HighBit": 15, - "ImageOrientationPatient": [0, 1, 0, 0, 0, -1], - "ImagePositionPatient": [11.6, -92.5, 98.099998], - "FrameOfReferenceUID": "1.2.840.113619.2.5.1762583153.223134.978956938.470", - "ImageType": ["ORIGINAL", "PRIMARY", "OTHER"], - "Modality": "MR", - "SOPInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.124", - "SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121", - "StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78" - }, - "url": "dicomweb://s3.amazonaws.com/lury/MRStudy/1.2.840.113619.2.5.1762583153.215519.978957063.124.dcm" - } - ] - } - ] - } - ] -} -``` - -More info on this JSON format can be found here -[Issue #1500](https://github.com/OHIF/Viewers/issues/1500) - -**Implementation Notes:** - - - -1. For each instance url (dicom object) in the returned JSON, you must prefix - the `url` with `dicomjson:` in order for the cornerstone image loader to - retrieve it correctly. eg. `https://image-server/my-image.dcm` ---> - `dicomjson:https://image-server/my-image.dcm` -2. The JSON format above is compatible with >= v3.7.8 of the application in `V2` - version. Older versions of the viewer used a different JSON format. As of - 20/04/20 the public [https://viewer.ohif.org/] is a pre 3.0 version that does - not support this format yet. -3. The JSON format is case-sensitive. Please ensure you have matched casing with - the naturalised Dicom format referenced in - [Issue #1500](https://github.com/OHIF/Viewers/issues/1500). - -_CORS Issues (Cross-Origin Resource Sharing)_ - -If you host a JSON API or Images on a different domain from the app itself, -you will likely have CORS issues. This will also happen when testing from -Localhost and reaching out to remote servers. Even if the domain is the same, -different ports, subdomains or protocols (https vs http) will also cause CORS -errors. You will to need add a configuration on each server hosting these assets -to allow your App server origin. - -For example: - -Let's assume your application is hosted on `https://my-ohif-server.com`. - -Your JSON API is hosted on `https://my-json-api.aws.com` - -And your images are stored on Amazon S3 at `https://my-s3-bucket.aws.com` - -When you first start your application, browsing to -`https://my-ohif-server.com/myMode/dicomjson?url=https://my-json-api.aws.com/api/my-json-study-info.json`, -you will likely get a CORS error in the browser console as it tries to connect -to `https://my-json-api.aws.com`. - -Adding a setting on the JSON server to allow the CORS origin = -`https://my-ohif-server.com` should solve this. - -Next, you will likely get a similar CORS error, as the browser tries to go to -`https://my-s3-bucket.aws.com`. You will need to go to the S3 bucket -configuration, and add a CORS setting to allow origin = -`https://my-ohif-server.com`. - -Essentially, whenever the application connects to a remote resource, you will -need to add the applications url to the allowed CORS Origins on that resource. -Adding an origin similar to https://localhost:3000 will also allow for local -testing. - -### Securing Your Data - -Coming soon - - - -### Recipes - -We've included a few recipes for common deployment scenarios. There are many, -many possible configurations, so please don't feel limited to these setups. -Please feel free to suggest or contribute your own recipes. - -- [Build for Production](./build-for-production.md) -- [Static](./static-assets.md) -- [Nginx + Image Archive](./nginx--image-archive.md) -- [User Account Control](./user-account-control.md) - - - - -[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer -[pwa-url]: https://developers.google.com/web/progressive-web-apps/ -[static-assets-url]: https://www.maxcdn.com/one/visual-glossary/static-content/ -[app-store]: https://medium.freecodecamp.org/i-built-a-pwa-and-published-it-in-3-app-stores-heres-what-i-learned-7cb3f56daf9b -[dicom-web-standard]: https://www.dicomstandard.org/dicomweb/ -[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb -[host-static-assets]: https://www.netlify.com/blog/2016/05/18/9-reasons-your-site-should-be-static/ -[cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS -[code-flows]: https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660 -[code-sandbox]: https://codesandbox.io/s/viewer-script-tag-tprch - diff --git a/platform/docs/versioned_docs/version-3.0/deployment/nginx--image-archive.md b/platform/docs/versioned_docs/version-3.0/deployment/nginx--image-archive.md deleted file mode 100644 index 79f729d2fb..0000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/nginx--image-archive.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Nginx + Image Archive - -> DISCLAIMER! We make no claims or guarantees of this approach's security. If in -> doubt, enlist the help of an expert and conduct proper audits. - -At a certain point, you may want others to have access to your instance of the -OHIF Viewer and its medical imaging data. This post covers one of many potential -setups that accomplish that. Please note, noticeably absent is user account -control. - -Do not use this recipe to host sensitive medical data on the open web. Depending -on your company's policies, this may be an appropriate setup on an internal -network when protected with a server's basic authentication. For a more robust -setup, check out our [user account control recipe](./user-account-control) -that builds on the lessons learned here. - -## Overview - -Our two biggest hurdles when hosting our image archive and web client are: - -- Risks related to exposing our PACS to the network -- Cross-Origin Resource Sharing (CORS) requests - -### Handling Web Requests - -We mitigate our first issue by allowing [Nginx][nginx] to handle incoming web -requests. Nginx is open source software for web serving, reverse proxying, -caching, and more. It's designed for maximum performance and stability -- -allowing us to more reliably serve content than Orthanc's built-in server can. - -More specifically, we accomplish this by using a -[`reverse proxy`](https://en.wikipedia.org/wiki/Reverse_proxy) to retrieve -resources from our image archive (Orthanc), and when accessing its web admin. - -> A reverse proxy is a type of proxy server that retrieves resources on behalf -> of a client from one or more servers. These resources are then returned to the -> client, appearing as if they originated from the proxy server itself. - -### CORS Issues - -Cross-Origin Resource Sharing (CORS) is a mechanism that uses HTTP headers to -tell a browser which web applications have permission to access selected -resources from a server at a different origin (domain, protocol, port). IE. By -default, a Web App located at `http://my-website.com` can't access resources -hosted at `http://not-my-website.com` - -We can solve this one of two ways: - -1. Have our Image Archive located at the same domain as our Web App -2. Add appropriate `Access-Control-Allow-*` HTTP headers - -**This solution uses the first approach.** - -You can read more about CORS in this Medium article: [Understanding -CORS][understanding-cors] - -### Diagram - -This setup allows us to create a setup similar to the one pictured below: - - -![nginX](../assets/img/nginx-image-archive.png) - - -- All web requests are routed through `nginx` on our `OpenResty` image -- `/pacs` is a reverse proxy for `orthanc`'s `DICOM Web` endpoints -- `/pacs-admin` is a reverse proxy for `orthanc`'s Web Admin -- All static resources for OHIF Viewer are served up by `nginx` when a matching - route for that resource is requested - -## Getting Started - -### Requirements - -- Docker - - [Docker for Mac](https://docs.docker.com/docker-for-mac/) - - [Docker for Windows](https://docs.docker.com/docker-for-windows/) - -_Not sure if you have `docker` installed already? Try running `docker --version` -in command prompt or terminal_ - -### Setup - -- Navigate to `viewer` folder inside `platform` -- then: `cd .recipes/OpenResty-Orthanc` -- run: `docker-compose up --build` -- Navigate to `127.0.0.1` for the viewer -- Navigate to `127.0.0.1/pacs-admin` for uploading studies - - -You can see the overview of the mentioned steps: - - - -
- -
- - - -### Troubleshooting - -_Exit code 137_ - -This means Docker ran out of memory. Open Docker Desktop, go to the `advanced` -tab, and increase the amount of Memory available. - -_Cannot create container for service X_ - -Use this one with caution: `docker system prune` - -_X is already running_ - -Stop running all containers: - -- Win: `docker ps -a -q | ForEach { docker stop $_ }` -- Linux: `docker stop $(docker ps -a -q)` - - -_Traceback (most recent call last):_ - _File "urllib3/connectionpool.py", line 670, in urlopen_ - _...._ - -Are you sure your docker is running? see explanation [here](https://github.com/docker/compose/issues/7896) - - -### Configuration - -After verifying that everything runs with default configuration values, you will -likely want to update: - -- The domain: `http://127.0.0.1` - -#### OHIF Viewer - -The OHIF Viewer's configuration is imported from a static `.js` file. The -configuration we use is set to a specific file when we build the viewer, and -determined by the env variable: `APP_CONFIG`. You can see where we set its value -in the `dockerfile` for this solution: - -`ENV APP_CONFIG=config/docker_openresty-orthanc.js` - -You can find the configuration we're using here: -`/public/config/docker_openresty-orthanc.js` - -To rebuild the `webapp` image created by our `dockerfile` after updating the -Viewer's configuration, you can run: - -- `docker-compose build` OR -- `docker-compose up --build` - -#### Other - -All other files are found in: `/docker/OpenResty-Orthanc/` - -| Service | Configuration | Docs | -| ----------------- | --------------------------------- | ------------------------------------------- | -| OHIF Viewer | [dockerfile][dockerfile] | You're reading them now! | -| OpenResty (Nginx) | [`/nginx.conf`][config-nginx] | [lua-resty-openidc][lua-resty-openidc-docs] | -| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] | - -## Next Steps - -### Deploying to Production - -While these configuration and docker-compose files model an environment suitable -for production, they are not easy to deploy "as is". You can either: - -- Manually recreate this environment and deploy built application files **OR** -- Deploy to a cloud kubernetes provider like - [Digital Ocean](https://www.digitalocean.com/products/kubernetes/) **OR** - - [See a full list of cloud providers here](https://landscape.cncf.io/category=cloud&format=card-mode&grouping=category) -- Find and follow your preferred provider's guide on setting up - [swarms and stacks](https://docs.docker.com/get-started/) - -### Adding SSL - -Adding SSL registration and renewal for your domain with Let's Encrypt that -terminates at Nginx is an incredibly important step toward securing your data. -Here are some resources, specific to this setup, that may be helpful: - -- [lua-resty-auto-ssl](https://github.com/GUI/lua-resty-auto-ssl) -- [Let's Encrypt + Nginx](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) - -While we terminate SSL at Nginx, it may be worth using self-signed certificates -for communication between services. - -- [SSL Termination for TCP Upstream Servers](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-tcp/) - -### Use PostgresSQL w/ Orthanc - -Orthanc can handle a large amount of data and requests, but if you find that -requests start to slow as you add more and more studies, you may want to -configure your Orthanc instance to use PostgresSQL. Instructions on how to do -that can be found in the -[`Orthanc Server Book`](http://book.orthanc-server.com/users/docker.html), under -"PostgreSQL and Orthanc inside Docker" - -### Improving This Guide - -Here are some improvements this guide would benefit from, and that we would be -more than happy to accept Pull Requests for: - -- SSL Support -- Complete configuration with `.env` file (or something similar) -- Any security issues -- One-click deploy to a cloud provider - -## Resources - -### Misc. Helpful Commands - -_Check if `nginx.conf` is valid:_ - -```bash -docker run --rm -t -a stdout --name my-openresty -v $PWD/config/:/usr/local/openresty/nginx/conf/:ro openresty/openresty:alpine-fat openresty -c /usr/local/openresty/nginx/conf/nginx.conf -t -``` - -_Interact w/ running container:_ - -`docker exec -it CONTAINER_NAME bash` - -_List running containers:_ - -`docker ps` - -### Referenced Articles - -For more documentation on the software we've chosen to use, you may find the -following resources helpful: - -- [Orthanc for Docker](http://book.orthanc-server.com/users/docker.html) -- [OpenResty Guide](http://www.staticshin.com/programming/definitely-an-open-resty-guide/) -- [Lua Ngx API](https://openresty-reference.readthedocs.io/en/latest/Lua_Nginx_API/) - -For a different take on this setup, check out the repositories our community -members put together: - -- [mjstealey/ohif-orthanc-dimse-docker](https://github.com/mjstealey/ohif-orthanc-dimse-docker) -- [trypag/ohif-orthanc-postgres-docker](https://github.com/trypag/ohif-orthanc-postgres-docker) - - - - - -[nginx]: https://www.nginx.com/resources/glossary/nginx/ -[understanding-cors]: https://medium.com/@baphemot/understanding-cors-18ad6b478e2b -[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration -[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc - -[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/dockerfile -[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/nginx.conf -[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/orthanc.json - diff --git a/platform/docs/versioned_docs/version-3.0/deployment/static-assets.md b/platform/docs/versioned_docs/version-3.0/deployment/static-assets.md deleted file mode 100644 index 767ad69280..0000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/static-assets.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Deploy Static Assets - -> WARNING! All of these solutions stand-up a publicly accessible web viewer. Do -> not hook your hosted viewer up to a sensitive source of data without -> implementing authentication. - -There are a lot of options for deploying static assets. Some services, like -`netlify` and `surge.sh`, specialize in static websites. You'll notice that -deploying with them requires much less time and effort, but comes at the cost of -less product offerings. - -While not required, it can simplify things to host your Web Viewer alongside -your image archive. Services with more robust product offerings, like -`Google Cloud`, `Microsoft's Azure`, and `Amazon Web Services (AWS)`, are able -to accommodate this setup. - -_Drag-n-drop_ - -- [Netlify: Drop](#netlify-drop) - -_Easy_ - -- [Surge.sh](#surgesh) -- [GitHub Pages](#github-pages) - -_Advanced_ - -- [Deploy Static Assets](#deploy-static-assets) - - [Drag-n-drop](#drag-n-drop) - - [Netlify Drop](#netlify-drop) - - [Easy](#easy) - - [Surge.sh](#surgesh) - - [GitHub Pages](#github-pages) - - [Advanced](#advanced) - - [AWS S3 + Cloudfront](#aws-s3--cloudfront) - - [GCP + Cloudflare](#gcp--cloudflare) - - [Azure](#azure) - -## Drag-n-drop - -### Netlify Drop - - -
- -
- - -_GIF demonstrating deployment with Netlify Drop_ - -1. https://app.netlify.com/drop -2. Drag your `build/` folder on to the drop target -3. ... -4. _annnd you're done_ - -**Features:** - -- Custom domains & HTTPS -- Instant Git integration -- Continuous deployment -- Deploy previews -- Access to add-ons - -(Non-free tiers include identity, FaaS, Forms, etc.) - -Learn more about [Netlify on their website](https://www.netlify.com/) - -## Easy - -### Surge.sh - -> Static web publishing for Front-End Developers. Simple, single-command web -> publishing. Publish HTML, CSS, and JS for free, without leaving the command -> line. - -![surge.sh deploy example](../assets/img/surge-deploy.gif) - -_GIF demonstrating deployment with surge_ - -```shell -# Add surge command -yarn global add surge - -# In the build directory -surge -``` - -**Features:** - -- Free custom domain support -- Free SSL for surge.sh subdomains -- pushState support for single page apps -- Custom 404.html pages -- Barrier-free deployment through the CLI -- Easy integration into your Grunt toolchain -- Cross-origin resource support -- And more… - -Learn more about [surge.sh on their website](https://surge.sh/) - -### GitHub Pages - -> WARNING! While great for project sites and light use, it is not advised to use -> GitHub Pages for production workloads. Please consider using a different -> service for mission critical applications. - -> Websites for you and your projects. Hosted directly from your GitHub -> repository. Just edit, push, and your changes are live. - -This deployment strategy makes more sense if you intend to maintain your project in -a GitHub repository. It allows you to specify a `branch` or `folder` as the -target for a GitHub Page's website. As you push code changes, the hosted content -updates to reflect those changes. - -1. Head over to GitHub.com and create a new repository, or go to an existing - one. Click on the Settings tab. -2. Scroll down to the GitHub Pages section. Choose the `branch` or `folder` you - would like as the "root" of your website. -3. Fire up a browser and go to `http://username.github.io/repository` - -Configuring Your Site: - -- [Setting up a custom domain](https://help.github.com/en/articles/using-a-custom-domain-with-github-pages) -- [Setting up SSL](https://help.github.com/en/articles/securing-your-github-pages-site-with-https) - -Learn more about [GitHub Pages on its website](https://pages.github.com/) - -## Advanced - -All of these options, while using providers with more service offerings, -demonstrate how to host the viewer with their respective file storage and CDN -offerings. While you can serve your static assets this way, if you're going -through the trouble of using AWS/GCP/Azure, it's more likely you're doing so to -avoid using a proxy or to simplify authentication. - -If that is the case, check out some of our more advanced `docker` deployments -that target these providers from the left-hand sidepanel. - -These guides can be a bit longer and an update more frequently. To provide -accurate documentation, we will link to each provider's own recommended steps: - -### AWS S3 + Cloudfront - -- [Host a Static Website](https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) -- [Speed Up Your Website with Cloudfront](https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-cloudfront-walkthrough.html) - -### GCP + Cloudflare - -- [Things to Know Before Getting Started](https://code.luasoftware.com/tutorials/google-cloud-storage/things-to-know-before-hosting-static-website-on-google-cloud-storage/) -- [Hosting a Static Website on GCP](https://cloud.google.com/storage/docs/hosting-static-website) - -### Azure - -- [Host a Static Website](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website) -- [Add SSL Support](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-https-custom-domain-cdn) -- [Configure a Custom Domain](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-custom-domain-name) diff --git a/platform/docs/versioned_docs/version-3.0/deployment/user-account-control.md b/platform/docs/versioned_docs/version-3.0/deployment/user-account-control.md deleted file mode 100644 index e411cc7d53..0000000000 --- a/platform/docs/versioned_docs/version-3.0/deployment/user-account-control.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_position: 5 ---- - -# User Account Control - -> Coming soon - We are working on bringing the User Account Control to OHIF-v3 diff --git a/platform/docs/versioned_docs/version-3.0/development/_category_.json b/platform/docs/versioned_docs/version-3.0/development/_category_.json deleted file mode 100644 index 8627cac492..0000000000 --- a/platform/docs/versioned_docs/version-3.0/development/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Development", - "position": 5 -} diff --git a/platform/docs/versioned_docs/version-3.0/development/architecture.md b/platform/docs/versioned_docs/version-3.0/development/architecture.md deleted file mode 100644 index 4bcebb0175..0000000000 --- a/platform/docs/versioned_docs/version-3.0/development/architecture.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Architecture ---- - -# Architecture - -In order to achieve a platform that can support various workflows and be -extensible for the foreseeable future we went through extensive planning of -possible use cases and decided to significantly change and improve the -architecture. - -Below, we aim to demystify that complexity by providing insight into how -`OHIF Platform` is architected, and the role each of its dependent libraries -plays. - -## Overview - -The [OHIF Medical Image Viewing Platform][viewers-project] is maintained as a -[`monorepo`][monorepo]. This means that this repository, instead of containing a -single project, contains many projects. If you explore our project structure, -you'll see the following: - -```bash -│ -├── extensions -│ ├── _example # Skeleton of example extension -│ ├── default # default functionalities -│ ├── cornerstone # 2D images w/ Cornerstone.js -│ ├── measurement-tracking # measurement tracking -│ ├── dicom-sr # Structured reports -│ └── dicom-pdf # View DICOM wrapped PDFs in viewport -│ -├── modes -│ └── longitudinal # longitudinal measurement tracking mode -│ -├── platform -│ ├── core # Business Logic -│ ├── i18n # Internationalization Support -│ ├── ui # React component library -│ └── viewer # Connects platform and extension projects -│ -├── ... # misc. shared configuration -├── lerna.json # MonoRepo (Lerna) settings -├── package.json # Shared devDependencies and commands -└── README.md -``` - -OHIF v3 is composed of the following components, described in detail in further -sections: - -- `@ohif/viewer`: The core framework that controls extension registration, mode - composition and routing. -- `@ohif/core`: A library of useful and reusable medical imaging functionality - for the web. -- `@ohif/ui`: A library of reusable components to build OHIF-styled applications - with. -- `Extensions`: A set of building blocks for building applications. The OHIF org - maintains a few core libraries. -- `Modes`: Configuration objects that tell @ohif/viewer how to compose - extensions to build applications on different routes of the platform. - -## Extensions - -The `extensions` directory contains many packages that provide essential -functionalities such as rendering, study/series browsers, measurement tracking -that modes can consume to enable a certain workflow. Extensions have had their -behavior changed in `OHIF-v3` and their api is expanded. In summary: - -> In `OHIF-v3`, extensions no longer automatically hook themselves to the app. -> Now, registering an extension makes its component available to `modes` that -> wish to use them. Basically, extensions in `OHIF-v3` are **building blocks** -> for building applications. - -OHIF team maintains several high value and commonly used functionalities in its -own extensions. For a list of extensions maintained by OHIF, -[check out this helpful table](../platform/extensions/index.md#maintained-extensions). -As an example `default` extension provides a default viewer layout, a -study/series browser and a datasource that maps to a DICOMWeb compliant backend. - -[Click here to read more about extensions!](../platform/extensions/index.md) - -## Modes - -The `modes` directory contains workflows that can be registered with OHIF within -certain `routes`. The mode will get used once the user opens the viewer on the -registered route. - -OHIF extensions were designed to provide certain core functionalities for -building your viewer. However, often in medical imaging we face a specific use -case in which we are using some core functionalities, adding our specific UI, -and use it in our workflows. Previously, to achieve this you had to create an -extension to add have such feature. `OHIF-v3` introduces `Modes` to enable -building such workflows by re-using the core functionalities from the -extensions. - -Some common workflows may include: - -- Measurement tracking for lesions -- Segmentation of brain abnormalities -- AI probe mode for detecting prostate cancer - -In the mentioned modes above, they will share the same core rendering module -that the `default` extension provides. However, segmentation mode will require -segmentation tools which is not needed for the other two. As you can see, modes -are a layer on top of extensions, that you can configure in order to achieve -certain workflows. - -To summarize the difference between extensions and modes in `OHIF-v3` and -extensions in `OHIF-v2` - -> - `Modes` are configuration objects that tell _@ohif/viewer_ how to compose -> extensions to build applications on different routes of the platform. -> - In v2 extensions are “plugins” that add functionality to a core viewer. -> - In v3 extensions are building blocks that a mode uses to build an entire -> viewer layout. - -[Click here to read more about modes!](../platform/modes/index.md) - -## Platform - -### `@ohif/viewer` - -This library is the core library which consumes modes and extensions and builds -an application. Extensions can be passed in as app configuration and will be -consumed and initialized at the appropriate time by the application. Upon -initialization the viewer will consume extensions and modes and build up the -route desired, these can then be accessed via the study list, or directly via -url parameters. - -Upon release modes will also be plugged into the app via configuration, but this -is still an area which is under development/discussion, and they are currently -pulled from the window in beta. - -Future ideas for this framework involve only adding modes and fetching the -required extension versions at either runtime or build time, but this decision -is still up for discussion. - -### `@ohif/core` - -OHIF core is a carefully maintained and tested set of web-based medical imaging -functions and classes. This library includes managers and services used from -within the viewer app. - -OHIF core is largely similar to the @ohif/core library in v2, however a lot of -logic has been moved to extensions: however all logic about DICOMWeb and other -data fetching mechanisms have been pulled out, as these now live in extensions, -described later. - -### `@ohif/ui` - -Firstly, a large time-consumer/barrier for entry we discovered was building new -UI in a timely manner that fit OHIF’s theme. For this reason we have built a new -UI component library which contains all the components one needs to build their -own viewer. - -These components are presentational only, so you can reuse them with whatever -logic you desire. As the components are presentational, you may swap out -@ohif/ui for a custom UI library with conforming API if you wish to white label -the viewer. The UI library is here to make development easier and quicker, but -it is not mandatory for extension components to use. - -[Check out our component library!](https://react.ohif.org/) - -## Overview of the architecture - -OHIF-v3 architecture can be seen in the following figure. We will explore each -piece in more detail. - -![mode-archs](../assets/img/mode-archs.png) - -## Common Questions - -> Can I create my own Viewer using Vue.js or Angular.js? - -You can, but you will not be able to leverage as much of the existing code and -components. `@ohif/core` could still be used for business logic, and to provide -a model for extensions. `@ohif/ui` would then become a guide for the components -you would need to recreate. - -> When I want to implement a functionality, should it be in the mode or in an -> extension? - -This is a great question. Modes are designed to consume extensions, so you -should implement your functionality in one of the modules of your new extension, -and let the mode consume it. This way, in the future, if you needed another mode -that utilizes the same functionality, you can easily hook the extension to the -new mode as well. - - - - -[monorepo]: https://github.com/OHIF/Viewers/issues/768 -[viewers-project]: https://github.com/OHIF/Viewers -[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer -[pwa]: https://developers.google.com/web/progressive-web-apps/ -[configuration]: ../configuration/index.md -[extensions]: ../platform/extensions/index.md -[core-github]: https://github.com/OHIF/viewers/platform/core -[ui-github]: https://github.com/OHIF/Viewers/tree/master/platform/ui - diff --git a/platform/docs/versioned_docs/version-3.0/development/continous-integration.md b/platform/docs/versioned_docs/version-3.0/development/continous-integration.md deleted file mode 100644 index 3c3124af83..0000000000 --- a/platform/docs/versioned_docs/version-3.0/development/continous-integration.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -sidebar_position: 7 -sidebar_label: Continous Integration ---- - -# Continous Integration (CI) - -This repository uses `CircleCI` and `Netlify` for continous integration. - -## Deploy Previews - -[Netlify Deploy previews][deploy-previews] are generated for every pull request. -They allow pull request authors and reviewers to "Preview" the OHIF Viewer as if -the changes had been merged. - -Deploy previews can be configured by modifying the `netlify.toml` file in the -root of the repository. Some additional scripts/assets for netlify are included -in the root `.netlify` directory. - -## Workflows - -[CircleCI Workflows][circleci-workflows] are a set of rules for defining a -collection of jobs and their run order. They are self-documenting and their -configuration can be found in our CircleCI configuration file: -`.circleci/config.yml`. - -### Workflow: PR_CHECKS - -The PR_CHECKS workflow (Pull Request Checks) runs our automated unit and -end-to-end tests for every code check-in. These tests must all pass before code -can be merged to our `master` branch. - -![PR_CHECKS](../assets/img/WORKFLOW_PR_CHECKS.png) - -### Workflow: PR_OPTIONAL_DOCKER_PUBLISH - -The PR_OPTIONAL_DOCKER_PUBLISH workflow allows for "manual approval" to publish -the pull request as a tagged docker image. This is helpful when changes need to -be tested with the Google Adapter before merging to `master`. - -![PR_Workflow](../assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png) - -> NOTE: This workflow will fail unless it's for a branch on our `upstream` -> repository. If you need this functionality, but the branch is from a fork, -> merge the changes to a short-lived `feature/` branch on `upstream` - -### Workflow: DEPLOY - -The DEPLOY workflow deploys the OHIF Viewer when changes are merged to master. -It uses the Netlify CLI to deploy assets created as part of the repository's PWA -Build process (`yarn run build`). The workflow allows for "Manual Approval" to -promote the build to `STAGING` and `PRODUCTION` environments. - -![WORKFLOW_DEPLOY](../assets/img/WORKFLOW_DEPLOY.png) - -| Environment | Description | URL | -| ----------- | ---------------------------------------------------------------------------------- | --------------------------------------------- | -| Development | Always reflects latest changes on `master` branch. | [Netlify][netlify-dev] / [OHIF][ohif-dev] | -| Staging | For manual testing before promotion to prod. Keeps development workflow unblocked. | [Netlify][netlify-stage] / [OHIF][ohif-stage] | -| Production | Stable, tested, updated less frequently. | [Netlify][netlify-prod] / [OHIF][ohif-prod] | - -### Workflow: RELEASE - -The RELEASE workflow publishes our `npm` packages, updated documentation, and -`docker` image when changes are merged to master. `Lerna` and "Semantic Commit -Syntax" are used to independently version and publish the many packages in our -monorepository. If a new version is cut/released, a Docker image is created. -Documentation is generated with `gitbook` and pushed to our `gh-pages` branch. -GitHub hosts the `gh-pages` branch with GitHub Pages. - -- Platform Packages: https://github.com/ohif/viewers/#platform -- Extension Packages: https://github.com/ohif/viewers/#extensions -- Documentation: https://docs.ohif.org/ - -![WORKFLOW_RELEASE](../assets/img/WORKFLOW_RELEASE.png) - - - - -[deploy-previews]: https://www.netlify.com/blog/2016/07/20/introducing-deploy-previews-in-netlify/ -[circleci-workflows]: https://circleci.com/docs/2.0/workflows/ -[netlify-dev]: https://ohif-dev.netlify.com -[netlify-stage]: https://ohif-stage.netlify.com -[netlify-prod]: https://ohif-prod.netlify.com -[ohif-dev]: https://viewer-dev.ohif.org -[ohif-stage]: https://viewer-stage.ohif.org -[ohif-prod]: https://viewer-prod.ohif.org - diff --git a/platform/docs/versioned_docs/version-3.0/development/contributing.md b/platform/docs/versioned_docs/version-3.0/development/contributing.md deleted file mode 100644 index 763be20dd0..0000000000 --- a/platform/docs/versioned_docs/version-3.0/development/contributing.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Contributing ---- - -# Contributing - -## How can I help? - -Fork the repository, make your change and submit a pull request. If you would -like to discuss the changes you intend to make to clarify where or how they -should be implemented, please don't hesitate to create a new issue. At a -minimum, you may want to read the following documentation: - -- [Getting Started](/development/getting-started.md) -- [Architecture](./architecture.md) - -Pull requests that are: - -- Small -- [Well tested](./testing.md) -- Decoupled - -Are much more likely to get reviewed and merged in a timely manner. - -## When changes impact multiple repositories - -While this can be tricky, we've tried to reduce how often this situation crops -up this with our [recent switch to a monorepo][monorepo]. Our maintained -extensions, ui components, internationalization library, and business logic can -all be developed by simply running `yarn run dev` from the repository root. - -Testing the viewer with locally developed, unpublished package changes from a -package outside of the monorepo is most common with extension development. Let's -demonstrate how to accomplish this with two commonly forked extension -dependencies: - -### `cornerstone-tools` - -On your local file system: - -```bash title="/my-projects/" -├── cornerstonejs/cornerstone-tools -└── ohif/viewers -``` - -- Open a terminal/shell -- Navigate to `cornerstonejs/cornerstone-tools` - - `yarn install` - - [`yarn link`](https://yarnpkg.com/en/docs/cli/link) - - `yarn run dev` - -* Open a new terminal/shell -* Navigate to `ohif/viewers` (the root of ohif project) - - `yarn install` - - [`yarn link cornerstone-tools`](https://yarnpkg.com/en/docs/cli/link) - - `yarn run dev` - -As you make changed to `cornerstone-tools`, and it's output is rebuilt, you -should see the following behavior: - -![tools](..//assets/img/cornerstone-tools-link.gif) - -If you wish to stop using your local package, run the following commands in the -`ohif/viewers` repository root: - -- `yarn unlink cornerstone-tools` -- `yarn install --force` - - - -#### Other linkage notes - -We're still working out some of the kinks with local package development as -there are a lot of factors that can influence the behavior of our development -server and bundler. If you encounter issues not addressed here, please don't -hesitate to reach out on GitHub. - -Sometimes you might encounter a situation where the linking doesn't work as -expected. This might happen when there are multiple linked packages with the -same name. You can [remove][unlink] the linked packages inside yarn and try -again. - -## Any guidance on submitting changes? - -While we do appreciate code contributions, triaging and integrating contributed -code changes can be very time consuming. Please consider the following tips when -working on your pull requests: - -- Functionality is appropriate for the repository. Consider creating a GitHub - issue to discuss your suggested changes. -- The scope of the pull request is not too large. Please consider separate pull - requests for each feature as big pull requests are very time consuming to - understand. - -We will provide feedback on your pull requests as soon as possible. Following -the tips above will help ensure your changes are reviewed. - - - - - - -[example-url]: https://deploy-preview-237--ohif.netlify.com/viewer/?url=https://s3.eu-central-1.amazonaws.com/ohif-viewer/sampleDICOM.json -[pr-237]: https://github.com/OHIF/Viewers/pull/237 -[monorepo]: https://github.com/OHIF/Viewers/issues/768 -[unlink]: https://stackoverflow.com/questions/58459698/is-there-a-command-to-unlink-all-yarn-packages-yarn-unlink-all - diff --git a/platform/docs/versioned_docs/version-3.0/development/getting-started.md b/platform/docs/versioned_docs/version-3.0/development/getting-started.md deleted file mode 100644 index b9b3bc5d51..0000000000 --- a/platform/docs/versioned_docs/version-3.0/development/getting-started.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Getting Started ---- - -# Getting Started - -## Setup - -### Fork & Clone - -If you intend to contribute back changes, or if you would like to pull updates -we make to the OHIF Viewer, then follow these steps: - -- [Fork][fork-a-repo] the [OHIF/Viewers][ohif-viewers-repo] repository -- [Create a local clone][clone-a-repo] of your fork - - `git clone https://github.com/YOUR-USERNAME/Viewers` -- Add OHIF/Viewers as a [remote repository][add-remote-repo] labeled `upstream` - - Navigate to the cloned project's directory - - `git remote add upstream https://github.com/OHIF/Viewers.git` - -With this setup, you can now [sync your fork][sync-changes] to keep it -up-to-date with the upstream (original) repository. This is called a "Triangular -Workflow" and is common for Open Source projects. The GitHub blog has a [good -graphic that illustrates this setup][triangular-workflow]. - -### Private - -Alternatively, if you intend to use the OHIF Viewer as a starting point, and you -aren't as concerned with syncing updates, then follow these steps: - -1. Navigate to the [OHIF/Viewers][ohif-viewers] repository -2. Click `Clone or download`, and then `Download ZIP` -3. Use the contents of the `.zip` file as a starting point for your viewer - -> NOTE: It is still possible to sync changes using this approach. However, -> submitting pull requests for fixes and features are best done with the -> separate, forked repository setup described in "Fork & Clone" - -## Developing - -### Requirements - -- [Node.js & NPM](https://nodejs.org/en/) -- [Yarn](https://yarnpkg.com/en/) -- Yarn workspaces should be enabled: - - `yarn config set workspaces-experimental true` - -### Kick the tires - -Navigate to the root of the project's directory in your terminal and run the -following commands: - -```bash -# Switch to the v3 branch -git switch v3-stable - -# Restore dependencies -yarn install - -# Start local development server -yarn run dev -``` - -You should see the following output: - -```bash -@ohif/viewer: i 「wds」: Project is running at http://localhost:3000/ -@ohif/viewer: i 「wds」: webpack output is served from / -@ohif/viewer: i 「wds」: Content not from webpack is served from D:\code\ohif\Viewers\platform\viewer -@ohif/viewer: i 「wds」: 404s will fallback to /index.html - -# And a list of all generated files -``` - -### 🎉 Celebrate 🎉 - -
- -
- -### Building for Production - -> More comprehensive guides for building and publishing can be found in our -> [deployment docs](./../deployment/index.md) - -```bash -# Build static assets to host a PWA -yarn run build -``` - -## Troubleshooting - -- If you receive a _"No Studies Found"_ message and do not see your studies, try - changing the Study Date filters to a wider range. -- If you see a 'Loading' message which never resolves, check your browser's - JavaScript console inside the Developer Tools to identify any errors. - - - - -[fork-a-repo]: https://help.github.com/en/articles/fork-a-repo -[clone-a-repo]: https://help.github.com/en/articles/fork-a-repo#step-2-create-a-local-clone-of-your-fork -[add-remote-repo]: https://help.github.com/en/articles/fork-a-repo#step-3-configure-git-to-sync-your-fork-with-the-original-spoon-knife-repository -[sync-changes]: https://help.github.com/en/articles/syncing-a-fork -[triangular-workflow]: https://github.blog/2015-07-29-git-2-5-including-multiple-worktrees-and-triangular-workflows/#improved-support-for-triangular-workflows -[ohif-viewers-repo]: https://github.com/OHIF/Viewers -[ohif-viewers]: https://github.com/OHIF/Viewers - diff --git a/platform/docs/versioned_docs/version-3.0/development/ohif-cli.md b/platform/docs/versioned_docs/version-3.0/development/ohif-cli.md deleted file mode 100644 index cf8af80f58..0000000000 --- a/platform/docs/versioned_docs/version-3.0/development/ohif-cli.md +++ /dev/null @@ -1,291 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: OHIF CLI ---- - -# OHIF Command Line Interface - -OHIF-v3 architecture has been re-designed to enable building applications that -are easily extensible to various use cases (Modes) that behind the scene would -utilize desired functionalities (Extensions) to reach the goal of the use case. -Now, the question is _how to create/remove/install/uninstall an extension and/or -mode?_ - -You can use the `cli` script that comes with the OHIF monorepo to achieve these -goals. - -:::note Info -In the long-term, we envision our `cli` tool to be a separate installable -package that you can invoke anywhere on your local system to achieve the same -goals. In the meantime, `cli` will remain as part of the OHIF monorepo and needs -to be invoked using the `yarn` command. -::: - - -## CLI Installation - -You don't need to install the `cli` currently. You can use `yarn` to invoke its -commands. - -## Commands - -:::note Important -All commands should run from the root of the monorepo. -::: - - -There are various commands that can be used to interact with the OHIF-v3 CLI. If -you run the following command, you will see a list of available commands. - -``` -yarn run cli --help -``` - -which will output - -``` -OHIF CLI - -Options: - -V, --version output the version number - -h, --help display help for command - -Commands: - create-extension Create a new template extension - create-mode Create a new template Mode - add-extension [version] Adds an ohif extension - remove-extension removes an ohif extension - add-mode [version] Removes an ohif mode - remove-mode Removes an ohif mode - link-extension Links a local OHIF extension to the Viewer to be used for development - unlink-extension Unlinks a local OHIF extension from the Viewer - link-mode Links a local OHIF mode to the Viewer to be used for development - unlink-mode Unlinks a local OHIF mode from the Viewer - list List Added Extensions and Modes - search [options] Search NPM for the list of Modes and Extensions - help [command] display help for command -``` - -As seen there are commands for you such as: `create-extension`, `create-mode`, -`add-extension`, `remove-extension`, `add-mode`, `remove-mode`, -`link-extension`, `unlink-extension`, `link-mode`, `unlink-mode`, `list`, -`search`, and `help`. Here we will go through each of the commands and describe -them. - -### create-mode - -If you need to create a new mode, you can use the `create-mode` command. This -command will create a new mode template in the directory that you specify. -The command will ask you couple of information/questions in order -to properly create the mode metadata in the `package.json` file. - -```bash -yarn run cli create-mode -``` - -
- -![image](../assets/img/create-mode.png) - - -
- -Note 1: Some questions have a default answer, which is indicated inside the -parenthesis. If you don't want to answer the question, just hit enter. It will -use the default answer. - -Note 2: As you see in the questions, you can initiate a git repository for the -new mode right away by answering `Y` (default) to the question. - -Note 3: Finally, as indicated by the green lines at the end, `create-mode` command only -create the mode template. You will need to link the mode to the Viewer in order -to use it. See the [`link-mode`](#link-mode) command. - -If we take a look at the directory that we created, we will see the following -files: - -
- -![image](../assets/img/mode-template.png) - -
- - -### create-extension - -Similar to the `create-extension` command, you can use the `create-extension` -command to create a new extension template. This command will create a new -extension template in the directory that you specify the path. - -```bash -yarn run cli create-extension -``` - - -Note: again similar to the `create-extension` command, you need to manually link -the extension to the Viewer in order to use it. See the -[`link-mode`](#link-mode) command. - - -### link-extension - -`link-extension` command will link a local OHIF extension to the Viewer. This -command will utilize `yarn link` to achieve so. - -```bash -yarn run cli link-extension -``` - -### unlink-extension - -There might be situations where you want to unlink an extension from the Viewer -after some developments. `unlink-extension` command will do so. - -```bash -ohif-cli unlink-extension -``` - - - -### link-mode - -Similar to the `link-extension` command, `link-mode` command will link a local -OHIF mode to the Viewer. - -```bash -yarn run cli link-mode -``` - -### unlink-mode - -Similar to the `unlink-extension` command, `unlink-mode` command will unlink a -local OHIF mode from the Viewer. - -```bash -ohif-cli unlink-mode -``` - -### add-mode - -OHIF is a modular viewer. This means that you can install (add) different modes -to the viewer if they are published online . `add-mode` command will add a new mode to -the viewer. It will look for the mode in the NPM registry and installs it. This -command will also add the extension dependencies that the mode relies on to the -Viewer (if specified in the peerDependencies section of the package.json). - -:::note Important -`cli` will validate the npm package before adding it to the Viewer. An OHIF mode -should have `ohif-mode` as one of its keywords. -::: - -Note: If you don't specify the version, the latest version will be used. - -```bash -yarn run cli add-mode [version] -``` - -For instance `@ohif-test/mode-clock` is an example OHIF mode that we have -published to NPM. This mode basically has a panel that shows the clock :) - -We can add this mode to the Viewer by running the following command: - -```bash -yarn run cli add-mode @ohif-test/mode-clock -``` - -After installation, the Viewer has a new mode! - - -![image](../assets/img/add-mode.png) - - -Note: If the mode has an extension peerDependency (in this case @ohif-test/extension-clock), -`cli` will automatically add the extension to the Viewer too. - -The result - -![image](../assets/img/clock-mode.png) -![image](../assets/img/clock-mode1.png) - -### add-extension - -This command will add an OHIF extension to the Viewer. It will look for the -extension in the NPM registry and install it. - -```bash -yarn run cli add-extension [version] -``` - - -### remove-mode - -This command will remove the mode from the Viewer and also remove the extension -dependencies that the mode relies on from the Viewer. - -```bash -yarn run cli remove-mode -``` - - -### remove-extension - -Similar to the `remove-mode` command, this command will remove the extension -from the Viewer. - -```bash -yarn run cli remove-extension -``` - -### list - -`list` command will list all the installed extensions and modes in -the Viewer. It uses the `PluginConfig.json` file to list the installed -extensions and modes. - -```bash -yarn run cli list -``` - -an output would look like this: - -
- -![image](../assets/img/ohif-cli-list.png) - -
- -### search - -Using `search` command, you can search for OHIF extensions and modes -in the NPM registry. This tool can accept a `--verbose` flag to show more -information about the results. - -```bash -yarn run cli search [--verbose] -``` - -
- -![image](../assets/img/cli-search-no-verbose.png) - -
- -with the verbose flag `ohif-cli search --verbose` you will achieve the following -output: - -
- -![image](../assets/img/cli-search-with-verbose.png) - -
- - -## PluginConfig.json - -To make all the above commands work, we have created a new file called `PluginConfig.json` which contains the -information needed to run the commands. You **don't need to (and should not)** -edit/update/modify this file as it is automatically generated by the CLI. You -can take a look at what this file contains by going to -`platform/viewer/PluginConfig.json` in your project's root directory. In short, -this file tracks and stores all the extensions/modes and the their version that -are currently being used by the viewer. diff --git a/platform/docs/versioned_docs/version-3.0/development/our-process.md b/platform/docs/versioned_docs/version-3.0/development/our-process.md deleted file mode 100644 index d8b8ed8621..0000000000 --- a/platform/docs/versioned_docs/version-3.0/development/our-process.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Issue & PR Triage Process ---- - -# Our Process - -Our process is a living, breathing thing. We strive to have regular -[retrospectives][retrospective] that help us shape and adapt our process to our -team's current needs. This document attempts to capture the broad strokes of -that process in an effort to: - -- Strengthen community member involvement and understanding -- Welcome feedback and helpful suggestions - -## Issue Triage - -[GitHub issues][gh-issues] are the best way to provide feedback, ask questions, -and suggest changes to the OHIF Viewer's core team. Community issues generally -fall into one of three categories, and are marked with a `triage` label when -created. - -| Issue Template Name | Description | -| ---------------------- | ---------------------------------------------------------------------------------------- | -| Community: Report 🐛 | Describe a new issue; Provide steps to reproduce; Expected versus actual result? | -| Community: Request ✋ | Describe a proposed new feature. Why should it be implemented? What is the impact/value? | -| Community: Question ❓ | Seek clarification or assistance relevant to the repository. | - -_table 1. issue template names and descriptions_ - -Issues that require `triage` are akin to support tickets. As this is often our -first contact with would-be adopters and contributors, it's important that we -strive for timely responses and satisfactory resolutions. We attempt to -accomplish this by: - -1. Responding to issue requiring `triage` at least once a week -2. Create new "official issues" from "community issues" -3. Provide clear guidance and next steps (when applicable) -4. Regularly clean up old (stale) issues - -> 🖋 Less obviously, patterns in the issues being reported can highlight areas -> that need improvement. For example, users often have difficulty navigating -> CORS issues when deploying the OHIF Viewer -- how do we best reduce our ticket -> volume for this issue? - -### Backlogged Issues - -Community issues serve as vehicles of discussion that lead us to "backlogged -issues". Backlogged issues are the distilled and actionable information -extracted from community issues. They contain the scope and requirements -necessary for hand-off to a core-team (or community) contributor ^\_^ - -| Category | Description | Labels | -| -------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| Bugs | An issue with steps that produce a bug (an unexpected result). | [Bug: Verified 🐛][label-bug] | -| Stories | A feature/enhancement with a clear benefit, boundaries, and requirements. | [Story 🙌][label-story] | -| Tasks | Changes that improve [UX], [DX], or test coverage; but don't impact application behavior | [Task: CI/Tooling 🤖][label-tooling], [Task: Docs 📖][label-docs], [Task: Refactor 🛠][label-refactor], [Task: Tests 🔬][label-tests] | - -_table 2. backlogged issue types ([full list of labels][gh-labels])_ - -## Issue Curation (["backlog grooming"][groom-backlog]) - -If a [GitHub issue][gh-issues] has a `bug`, `story`, or `task` label; it's on -our backlog. If an issue is on our backlog, it means we are, at the very least, -committed to reviewing any community drafted Pull Requests to complete the -issue. If you're interested in seeing an issue completed but don't know where to -start, please don't hesitate to leave a comment! - -While we don't yet have a long-term or quarterly road map, we do regularly add -items to our ["Active Development" GitHub Project Board][gh-board]. Items on -this project board are either in active development by Core Team members, or -queued up for development as in-progress items are completed. - -> 🖋 Want to contribute but not sure where to start? Check out [Up for -> grabs][label-grabs] issues and our [Contributing -> documentation][contributing-docs] - -## Contributions (Pull Requests) - -Incoming Pull Requests (PRs) are triaged using the following labels. Code review -is performed on all PRs where the bug fix or added functionality is deemed -appropriate: - -| Labels | Description | -| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| **Classification** | | -| [PR: Bug Fix][label-bug] | Filed to address a Bug. | -| [PR: Draft][draft] | Filed to gather early feedback from the core team, but which is not intended for merging in the short term. | -| **Review Workflow** | | -| [PR: Awaiting Response 💬][awaiting-response] | The core team is waiting for additional information from the author. | -| [PR: Awaiting Review 👀][awaiting-review] | The core team has not yet performed a code review. | -| [PR: Awaiting Revisions 🖊][awaiting-revisions] | Following code review, this label is applied until the author has made sufficient changes. | -| **QA** | | -| [PR: Awaiting User Cases 💃][awaiting-stories] | The PR code changes need common language descriptions of impact to end users before the review can start | -| [PR: No UX Impact 🙃][no-ux-impact] | The PR code changes do not impact the user's experience | - -We rely on GitHub Checks and integrations with third party services to evaluate -changes in code quality and test coverage. Tests must pass and User cases must -be present (when applicable) before a PR can be merged to master, and code -quality and test coverage must not be changed by a significant margin. For some -repositories, visual screenshot-based tests are also included, and video -recordings of end-to-end tests are stored for later review. - -[You can read more about our continous integration efforts here](/development/continous-integration.md) - -## Releases - -Releases are made automatically based on the type of commits which have been -merged (major.minor.patch). Releases are automatically pushed to NPM. Release -notes are automatically generated. Users can subscribe to GitHub and NPM -releases. - -We host development, staging, and production environments for the Progressive -Web Application version of the OHIF Viewer. [Development][ohif-dev] always -reflects the latest changes on our master branch. [Staging][ohif-stage] is used -to regression test a release before a bi-weekly deploy to our [Production -environment][ohif-prod]. - -Important announcements are made on GitHub, tagged as Announcement, and pinned -so that they remain at the top of the Issue page. - -The Core team occasionally performs full manual testing to begin the process of -releasing a Stable version. Once testing is complete, the known issues are -addressed and a Stable version is released. - - - - -[groom-backlog]: https://www.agilealliance.org/glossary/backlog-grooming -[retrospective]: https://www.atlassian.com/team-playbook/plays/retrospective -[gh-issues]: https://github.com/OHIF/Viewers/issues/new/choose -[gh-labels]: https://github.com/OHIF/Viewers/labels - -[label-story]: https://github.com/OHIF/Viewers/labels/Story%20%3Araised_hands%3A -[label-tooling]: https://github.com/OHIF/Viewers/labels/Task%3A%20CI%2FTooling%20%3Arobot%3A -[label-docs]: https://github.com/OHIF/Viewers/labels/Task%3A%20Docs%20%3Abook%3A -[label-refactor]: https://github.com/OHIF/Viewers/labels/Task%3A%20Refactor%20%3Ahammer_and_wrench%3A -[label-tests]: https://github.com/OHIF/Viewers/labels/Task%3A%20Tests%20%3Amicroscope%3A -[label-bug]: https://github.com/OHIF/Viewers/labels/Bug%3A%20Verified%20%3Abug%3A - -[draft]: https://github.com/OHIF/Viewers/labels/PR%3A%20Draft -[awaiting-response]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20Response%20%3Aspeech_balloon%3A -[awaiting-review]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20Review%20%3Aeyes%3A -[awaiting-stories]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20UX%20Stories%20%3Adancer%3A -[awaiting-revisions]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20Revisions%20%3Apen%3A -[no-ux-impact]: https://github.com/OHIF/Viewers/labels/PR%3A%20No%20UX%20Impact%20%3Aupside_down_face%3A - -[ohif-dev]: https://viewer-dev.ohif.org -[ohif-stage]: https://viewer-stage.ohif.org -[ohif-prod]: https://viewer.ohif.org -[gh-board]: https://github.com/OHIF/Viewers/projects/4 -[label-grabs]: https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs+%3Araising_hand_woman%3A%22 -[contributing-docs]: ./development/contributing.md - diff --git a/platform/docs/versioned_docs/version-3.0/development/testing.md b/platform/docs/versioned_docs/version-3.0/development/testing.md deleted file mode 100644 index c7c846aaa3..0000000000 --- a/platform/docs/versioned_docs/version-3.0/development/testing.md +++ /dev/null @@ -1,217 +0,0 @@ ---- -sidebar_position: 6 -sidebar_label: Testing ---- - -# Running Tests for OHIF - -We introduce here various test types that is available for OHIF, and how to run -each test in order to make sure your contribution hasn't broken any existing -functionalities. Idea and philosophy of each testing category is discussed in -the second part of this page. - -## Unit test - -To run the unit test: - -```bash -yarn run test:unit:ci -``` - -Note: You should have already installed all the packages with `yarn install`. - -Running unit test will generate a report at the end showing the successful and -unsuccessful tests with detailed explanations. - -## End-to-end test -For running the OHIF e2e test you need to run the following steps: - -- Open a new terminal, and from the root of the OHIF mono repo, run the following command: - - ```bash - yarn test:data - ``` - - This will download the required data to run the e2e tests (it might take a while). - The `test:data` only needs to be run once and checks the data out. Read more about - test data [below](#test-data). - -- Run the viewer with e2e config - - ```bash - APP_CONFIG=config/e2e.js yarn start - ``` - - You should be able to see test studies in the study list - - ![OHIF-e2e-test-studies](../assets/img/OHIF-e2e-test-studies.png) - -- Open a new terminal inside the OHIF project, and run the e2e cypress test - - ```bash - yarn test:e2e - ``` - - You should be able to see the cypress window open - - ![e2e-cypress](../assets/img/e2e-cypress.png) - - Run the tests by clicking on the `Run #number integration tests` . - - A new window will open, and you will see e2e tests being executed one after - each other. - - ![e2e-cypress-final](../assets/img/e2e-cypress-final.png) - - ## Test Data - The testing data is stored in two OHIF repositories. The first contains the - binary DICOM data, at [viewer-testdata](https://github.com/OHIF/viewer-testdata.git) - while the second module contains data in the DICOMweb format, installed as a submodule - into OHIF in the `testdata` directory. This is retrieved via the command - ```bash - yarn test:data - ``` - or the equivalent command `git submodule update --init` - When adding new data, run: - ``` - npm install -g dicomp10-to-dicomweb - mkdicomweb -d dicomweb dcm - ``` - to update the local dicomweb submodule in viewer-testdata. Then, commit - that data and update the submodules used in OHIF and in the viewer-testdata - parent modules. - - All data MUST be fully anonymized and allowed to be used for open access. - Any attributions should be included in the DCM directory. - -## Testing Philosophy - -> Testing is an opinionated topic. Here is a rough overview of our testing -> philosophy. See something you want to discuss or think should be changed? Open -> a PR and let's discuss. - -You're an engineer. You know how to write code, and writing tests isn't all that -different. But do you know why we write tests? Do you know when to write one, or -what kind of test to write? How do you know if a test is a _"good"_ test? This -document's goal is to give you the tools you need to make those determinations. - -Okay. So why do we write tests? To increase our... **CONFIDENCE** - -- If I do a large refactor, does everything still work? -- If I changed some critical piece of code, is it safe to push to production? - -Gaining the confidence we need to answer these questions after every change is -costly. Good tests allow us to answer them without manual regression testing. -What and how we choose to test to increase that confidence is nuanced. - -## Further Reading: Kinds of Tests - -Test's buy us confidence, but not all tests are created equal. Each kind of test -has a different cost to write and maintain. An expensive test is worth it if it -gives us confidence that a payment is processed, but it may not be the best -choice for asserting an element's border color. - -| Test Type | Example | Speed | Cost | -| ----------- | ------------------------------------------------------------------------ | ---------------- | ------------------------------------------------------------------------ | -| Static | `addNums(1, '2')` called with `string`, expected `int`. | :rocket: Instant | :money_with_wings: | -| Unit | `addNums(1, 2)` returns expected result `3` | :airplane: Fast | :money_with_wings::money_with_wings: | -| Integration | Clicking "Sign In", navigates to the dashboard (mocked network requests) | :running: Okay | :money_with_wings::money_with_wings::money_with_wings: | -| End-to-end | Clicking "Sign In", navigates to the dashboard (no mocks) | :turtle: Slow | :money_with_wings::money_with_wings::money_with_wings::money_with_wings: | - -- :rocket: Speed: How quickly tests run -- :money_with_wings: Cost: Time to write, and to debug when broken (more points - of failure) - -### Static Code Analysis - -Modern tooling gives us this "for free". It can catch invalid regular -expressions, unused variables, and guarantee we're calling methods/functions -with the expected parameter types. - -Example Tooling: - -- [ESLint][eslint-rules] -- [TypeScript][typescript-docs] or [Flow][flow-org] - -### Unit Tests - -The building blocks of our libraries and applications. For these, you'll often -be testing a single function or method. Conceptually, this equates to: - -_Pure Function Test:_ - -- If I call `sum(2, 2)`, I expect the output to be `4` - -_Side Effect Test:_ - -- If I call `resetViewport(viewport)`, I expect `cornerstone.reset` to be called - with `viewport` - -#### When to use - -Anything that is exposed as public API should have unit tests. - -#### When to avoid - -You're actually testing implementation details. You're testing implementation -details if: - -- Your test does something that the consumer of your code would never do. - - IE. Using a private function -- A refactor can break your tests - -### Integration Tests - -We write integration tests to gain confidence that several units work together. -Generally, we want to mock as little as possible for these tests. In practice, -this means only mocking network requests. - -### End-to-End Tests - -These are the most expensive tests to write and maintain. Largely because, when -they fail, they have the largest number of potential points of failure. So why -do we write them? Because they also buy us the most confidence. - -#### When to use - -Mission critical features and functionality, or to cover a large breadth of -functionality until unit tests catch up. Unsure if we should have a test for -feature `X` or scenario `Y`? Open an issue and let's discuss. - -### General - -- [Assert(js) Conf 2018 Talks][assert-js-talks] - - [Write tests. Not too many. Mostly integration.][kent-talk] - Kent C. Dodds - - [I see your point, but…][gleb-talk] - Gleb Bahmutov -- [Static vs Unit vs Integration vs E2E Testing][kent-blog] - Kent C. Dodds - (Blog) - -### End-to-end Testing w/ Cypress - -- [Getting Started](https://docs.cypress.io/guides/overview/why-cypress.html) - - Be sure to check out `Getting Started` and `Core Concepts` -- [Best Practices](https://docs.cypress.io/guides/references/best-practices.html) -- [Example Recipes](https://docs.cypress.io/examples/examples/recipes.html) - - - - -[eslint-rules]: https://eslint.org/docs/rules/ -[mini-pacs]: https://github.com/OHIF/viewer-testdata -[typescript-docs]: https://www.typescriptlang.org/docs/home.html -[flow-org]: https://flow.org/ - -[assert-js-talks]: https://www.youtube.com/playlist?list=PLZ66c9_z3umNSrKSb5cmpxdXZcIPNvKGw -[kent-talk]: https://www.youtube.com/watch?v=Fha2bVoC8SE -[gleb-talk]: https://www.youtube.com/watch?v=5FnalKRjpZk -[kent-blog]: https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests - -[testing-trophy]: https://twitter.com/kentcdodds/status/960723172591992832?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E960723172591992832&ref_url=https%3A%2F%2Fkentcdodds.com%2Fblog%2Fwrite-tests -[aaron-square]: https://twitter.com/Carofine247/status/966727489274961920 -[gleb-pyramid]: https://twitter.com/Carofine247/status/966764532046684160/photo/3 -[testing-pyramid]: https://dojo.ministryoftesting.com/dojo/lessons/the-mobile-test-pyramid -[testing-dorito]: https://twitter.com/denvercoder/status/960752578198843392 -[testing-dorito-img]: https://pbs.twimg.com/media/DVVHXycUMAAcN-F?format=jpg&name=4096x4096 - diff --git a/platform/docs/versioned_docs/version-3.0/faq.md b/platform/docs/versioned_docs/version-3.0/faq.md deleted file mode 100644 index 67b7d30b2e..0000000000 --- a/platform/docs/versioned_docs/version-3.0/faq.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -sidebar_position: 8 -sidebar_label: FAQ ---- - -# Frequently Asked Questions - -## Index - -- [Report a bug][report-bug] -- [Request a feature][new-feature] -- [Commercial Support & Consulting][commercial-support] -- [Academic collaborations][academic] -- [FDA Clearance or CE Marking][fda-clearance] -- [HIPAA Compliance][hipaa] - -### How do I report a bug? - -Navigate to our [GitHub Repository][new-issue], and submit a new bug report. -Follow the steps outlined in the [Bug Report Template][bug-report-template]. - -### How can I request a new feature? - -At the moment we are in the process of defining our roadmap and will do our best -to communicate this to the community. If your requested feature is on the -roadmap, then it will most likely be built at some point. If it is not, you are -welcome to build it yourself and [contribute it](development/contributing.md). -If you have resources and would like to fund the development of a feature, -please [contact us](https://www.ohif.org) or work with community members that -offer [consulting services][commercial-support]. - -### Who should I contact about Academic Collaborations? - -[Gordon J. Harris](https://www.dfhcc.harvard.edu/insider/member-detail/member/gordon-j-harris-phd/) -at Massachusetts General Hospital is the primary contact for any academic -collaborators. We are always happy to hear about new groups interested in using -the OHIF framework, and may be able to provide development support if the -proposed collaboration has an impact on cancer research. - -### Does OHIF offer commercial support? - -The Open Health Imaging Foundation does not offer commercial support, however, -some community members do offer consulting services. You can search our -[Community Forum](https://community.ohif.org/) for more information. - -### Does The OHIF Viewer have [510(k) Clearance][501k-clearance] from the U.S. F.D.A or [CE Marking][ce-marking] from the European Commission? - -**NO.** The OHIF Viewer is **NOT** F.D.A. cleared or CE Marked. It is the users' -responsibility to ensure compliance with applicable rules and regulations. The -[License](https://github.com/OHIF/Viewers/blob/master/LICENSE) for the OHIF -Platform does not prevent your company or group from seeking F.D.A. clearance -for a product built using the platform. - -If you have gone this route (or are going there), please let us know because we -would be interested to hear about your experience. - -### Is The OHIF Viewer [HIPAA][hipaa-def] Compliant? - -**NO.** The OHIF Viewer **DOES NOT** fulfill all of the criteria to become HIPAA -Compliant. It is the users' responsibility to ensure compliance with applicable -rules and regulations. - - - - - -[report-bug]: #how-do-i-report-a-bug -[new-feature]: #how-can-i-request-a-new-feature -[commercial-support]: #does-ohif-offer-commercial-support -[academic]: #who-should-i-contact-about-academic-collaborations -[fda-clearance]: #does-the-ohif-viewer-have-510k-clearance-from-the-us-fda-or-ce-marking-from-the-european-commission -[hipaa]: #is-the-ohif-viewer-hipaa-compliant - -[501k-clearance]: https://www.fda.gov/MedicalDevices/DeviceRegulationandGuidance/HowtoMarketYourDevice/PremarketSubmissions/PremarketNotification510k/ -[ce-marking]: https://ec.europa.eu/growth/single-market/ce-marking_en -[hipaa-def]: https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act -[new-issue]: https://github.com/OHIF/Viewers/issues/new/choose -[bug-report-template]: https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Bug+Report+%3Abug%3A&template=---bug-report.md&title= - diff --git a/platform/docs/versioned_docs/version-3.0/platform/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/_category_.json deleted file mode 100644 index 842e4abf4a..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Platform", - "position": 6 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/environment-variables.md b/platform/docs/versioned_docs/version-3.0/platform/environment-variables.md deleted file mode 100644 index 4fd2691a4a..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/environment-variables.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Environment Variables ---- -# Environment Variables - -There are a number of environment variables we use at build time to influence the output application's behavior. - -```bash -# Application -NODE_ENV=< production | development > -DEBUG=< true | false > -APP_CONFIG=< relative path to application configuration file > -PUBLIC_URL=<> -VERSION_NUMBER= -BUILD_NUM= -# i18n -USE_LOCIZE= -LOCIZE_PROJECTID= -LOCIZE_API_KEY= -``` - -## Setting Environment Variables - -- `npx cross-env` -- `.env` files -- env variables on build machine, or for terminal session diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/extensions/_category_.json deleted file mode 100644 index b7a30d960f..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Extensions", - "position": 9 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/extension.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/extension.md deleted file mode 100644 index 148b82a9fa..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/extension.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Extension Manager ---- - -# Extension Manager - -## Overview - -The `ExtensionManager` is a class made available to us via the `@ohif/core` -project (platform/core). Our application instantiates a single instance of it, -and provides a `ServicesManager` and `CommandsManager` along with the -application's configuration through the appConfig key (optional). - -```js -const commandsManager = new CommandsManager(); -const servicesManager = new ServicesManager(); -const extensionManager = new ExtensionManager({ - commandsManager, - servicesManager, - appConfig, -}); -``` - -The `ExtensionManager` only has a few public members: - -- `setActiveDataSource` - Sets the active data source for the application -- `getDataSources` - Returns the registered data sources -- `getActiveDataSource` - Returns the currently active data source -- `getModuleEntry` - Returns the module entry by the give id. - -## Accessing Modules - -We use `getModuleEntry` in our `ViewerLayout` logic to find the panels based on -the provided IDs in the mode's configuration. - -For instance: -`extensionManager.getModuleEntry("@ohif/extension-measurement-tracking.panelModule.seriesList")` -accesses the `seriesList` panel from `panelModule` of the -`@ohif/extension-measurement-tracking` extension. - -```js -const getPanelData = id => { - const entry = extensionManager.getModuleEntry(id); - const content = entry.component; - - return { - iconName: entry.iconName, - iconLabel: entry.iconLabel, - label: entry.label, - name: entry.name, - content, - }; -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/index.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/index.md deleted file mode 100644 index b1920b5210..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/index.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Introduction ---- - -# Introduction - -We have re-designed the architecture of the `OHIF-v3` to enable building -applications that are easily extensible to various use cases (modes) that behind -the scene would utilize desired functionalities (extensions) to reach the goal -of the use case. - -Previously, extensions were “additive” and could not easily be mixed and matched -within the same viewer for different use cases. Previous `OHIF-v2` architecture -meant that any minor extension alteration usually would require the user to hard -fork. E.g. removing some tools from the toolbar of the cornerstone -extension meant you had to hard fork it, which was frustrating if the -implementation was otherwise the same as master. - -> - Developers should make packages of _reusable_ functionality as extensions, -> and can consume publicly available extensions. -> - Any conceivable radiological workflow or viewer setup will be able to be -> built with the platform through _modes_. - -Practical examples of extensions include: - -- A set of segmentation tools that build on top of the `cornerstone` viewport -- A set of rendering functionalities to volume render the data -- [See our maintained extensions for more examples of what's possible](#maintained-extensions) - -**Diagram showing how extensions are configured and accessed.** - - - -## Extension Skeleton - -An extension is a plain JavaScript object that has `id` and `version` properties, and one or -more [modules](#modules) and/or [lifecycle hooks](#lifecycle-hooks). - -```js -// prettier-ignore -export default { - /** - * Required properties. Should be a unique value across all extensions. - */ - id, - - // Lifecyle - preRegistration() { /* */ }, - onModeEnter() { /* */ }, - onModeExit() { /* */ }, - // Modules - getLayoutTemplateModule() { /* */ }, - getDataSourcesModule() { /* */ }, - getSopClassHandlerModule() { /* */ }, - getPanelModule() { /* */ }, - getViewportModule() { /* */ }, - getCommandsModule() { /* */ }, - getContextModule() { /* */ }, - getToolbarModule() { /* */ }, - getHangingProtocolModule() { /* */ }, -} -``` - -## OHIF-Maintained Extensions - -A small number of powerful extensions for popular use cases are maintained by -OHIF. They're co-located in the [`OHIF/Viewers`][viewers-repo] repository, in -the top level [`extensions/`][ext-source] directory. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ExtensionDescriptionModules
- - Default - - - Default extension provides default viewer layout, a study/series - browser, and a datasource that maps to a DICOMWeb compliant backend - commandsModule, ContextModule, DataSourceModule, HangingProtocolModule, LayoutTemplateModule, PanelModule, SOPClassHandlerModule, ToolbarModule
- - Cornerstone - - - Provides rendering functionalities for 2D images. - ViewportModule, CommandsModule
- DICOM PDF - - Renders PDFs for a specific SopClassUID. - Viewport, SopClassHandler
- DICOM SR - - Maintained extensions for cornerstone and visualization of DICOM Structured Reports - ViewportModule, CommandsModule, SOPClassHandlerModule
- Measurement tracking - - Tracking measurements in the measurement panel - ContextModule,PanelModule,ViewportModule,CommandsModule
- -## Registering of Extensions - -`viewer` starts by registering all the extensions specified inside the -`pluginConfig.json`, by default we register all extensions in the repo. - - -```js title=platform/viewer/pluginConfig.json -// Simplified version of the `pluginConfig.json` file -{ - "extensions": [ - { - "packageName": "@ohif/extension-cornerstone", - "version": "3.0.0" - }, - { - "packageName": "@ohif/extension-measurement-tracking", - "version": "3.0.0" - }, - // ... - ], - "modes": [ - { - "packageName": "@ohif/mode-longitudinal", - "version": "0.0.1" - } - ] -} -``` - -:::note Important -You SHOULD NOT directly register extensions in the `pluginConfig.json` file. -Use the provided `cli` to add/remove/install/uninstall extensions. Read more [here](../../development/ohif-cli.md) -::: - -The final registration and import of the extensions happen inside a non-tracked file `pluginImport.js` (this file is also for internal use only). - -After an extension gets registered withing the `viewer`, -each [module](#modules) defined by the extension becomes available to the modes -via the `ExtensionManager` by requesting it via its id. -[Read more about Extension Manager](#extension-manager) - -## Lifecycle Hooks - -Currently, there are three lifecycle hook for extensions: - -[`preRegistration`](./lifecycle/#preRegistration) This hook is called once on -initialization of the entire viewer application, used to initialize the -extensions state, and consume user defined extension configuration. If an -extension defines the [`preRegistration`](./lifecycle/#preRegistration) -lifecycle hook, it is called before any modules are registered in the -`ExtensionManager`. It's most commonly used to wire up extensions to -[services](./../services/index.md) and [commands](./modules/commands.md), and to -bootstrap 3rd party libraries. - -[`onModeEnter`](./lifecycle#onModeEnter): This hook is called whenever a new -mode is entered, or a mode’s data or datasource is switched. This hook can be -used to initialize data. - -[`onModeExit`](./lifecycle#onModeExit): Similarly to onModeEnter, this hook is -called when navigating away from a mode, or before a mode’s data or datasource -is changed. This can be used to clean up data (e.g. remove annotations that do -not need to be persisted) - -## Modules - -Modules are the meat of extensions, the `blocks` that we have been talking about -a lot. They provide "definitions", components, and filtering/mapping logic that -are then made available to modes and services. - -Each module type has a special purpose, and is consumed by our viewer -differently. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Types - Description
- - LayoutTemplate (NEW) - - Control Layout of a route
- - DataSource (NEW) - - Control the mapping from DICOM metadata to OHIF-metadata
- - SOPClassHandler - - Determines how retrieved study data is split into "DisplaySets"
- - Panel - - Adds left or right hand side panels
- - Viewport - - Adds a component responsible for rendering a "DisplaySet"
- - Commands - - Adds named commands, scoped to a context, to the CommandsManager
- - Toolbar - - Adds buttons or custom components to the toolbar
- - Context - - Shared state for a workflow or set of extension module definitions
- - HangingProtocol - - Adds hanging protocol rules
- -Tbl. Module types -with abridged descriptions and examples. Each module links to a dedicated -documentation page. - -### Contexts - -The `@ohif/viewer` tracks "active contexts" that extensions can use to scope -their functionality. Some example contexts being: - -- Route: `ROUTE:VIEWER`, `ROUTE:STUDY_LIST` -- Active Viewport: `ACTIVE_VIEWPORT:CORNERSTONE`, `ACTIVE_VIEWPORT:VTK` - -An extension module can use these to say "Only show this Toolbar Button if the -active viewport is a Cornerstone viewport." This helps us use the appropriate UI -and behaviors depending on the current contexts. - -For example, if we have hotkey that "rotates the active viewport", each Viewport -module that supports this behavior can add a command with the same name, scoped -to the appropriate context. When the `command` is fired, the "active contexts" -are used to determine the appropriate implementation of the rotation behavior. - - - - -[viewers-repo]: https://github.com/OHIF/Viewers -[ext-source]: https://github.com/OHIF/Viewers/tree/master/extensions -[module-types]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/extensions/MODULE_TYPES.js - diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/installation.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/installation.md deleted file mode 100644 index 2e5fb81c2a..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/installation.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Installation ---- - -# Extension: Installation - -OHIF-v3 provides the ability to utilize external extensions. - - -You can use ohif `cli` tool to install both local and publicly published -extensions on NPM. You can read more [here](../../development/ohif-cli.md) diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/lifecycle.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/lifecycle.md deleted file mode 100644 index 422bce794c..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/lifecycle.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Lifecycle Hooks ---- - -# Extensions: Lifecycle Hooks - -## Overview - -Extensions can implement specific lifecycle methods. - -- preRegistration -- onModeEnter -- onModeExit - -## preRegistration - -If an extension defines the `preRegistration` lifecycle hook, it is called -before any modules are registered in the `ExtensionManager`. This hook can be -used to: - -- initialize 3rd party libraries -- register event listeners -- add or call services -- add or call commands - -The `preRegistration` hook receives an object containing the -`ExtensionManager`'s associated `ServicesManager`, `CommandsManager`, and any -`configuration` that was provided with the extension at time of registration. - -Example `preRegistration` implementation that register a new service and make it -available in the app. We will talk more in details for creating a new service -for `OHIF-v3`. - -```js -// new service inside new extension -import MyNewService from './MyNewService'; - -export default function MyNewServiceWithServices(serviceManager) { - return { - name: 'MyNewService', - create: ({ configuration = {} }) => { - return new MyNewService(serviceManager); - }, - }; -} -``` - -and - -```js -import MyNewService from './MyNewService' - -export default { - id, - - /** - * @param {object} params - * @param {object} params.configuration - * @param {ServicesManager} params.servicesManager - * @param {CommandsManager} params.commandsManager - * @returns void - */ - preRegistration({ servicesManager, commandsManager, configuration }) { - console.log('Wiring up important stuff.'); - - window.importantStuff = () => { - console.log(configuration); - }; - - console.log('Important stuff has been wired.'); - window.importantStuff(); - - // Registering new services - servicesManager.registerService(MyNewService(servicesManager)); - }, - }, -}; -``` - -## onModeEnter - -If an extension defines the `onModeEnter` lifecycle hook, it is called when a -new mode is enters, or a mode's data or datasource is switched. - -For instance, in DICOM structured report extension (`dicom-sr`), we are using -`onModeEnter` to re-create the displaySets after a new mode is entered. - -_Example `onModeEnter` hook implementation_ - -```js -export default { - id: '@ohif/extension-cornerstone-dicom-sr', - - onModeEnter({ servicesManager }) { - const { DisplaySetService } = servicesManager.services; - const displaySetCache = DisplaySetService.getDisplaySetCache(); - - const srDisplaySets = displaySetCache.filter( - ds => ds.SOPClassHandlerId === SOPClassHandlerId - ); - - srDisplaySets.forEach(ds => { - // New mode route, allow SRs to be hydrated again - ds.isHydrated = false; - }); - }, -}; -``` - -## onModeExit - -If an extension defines the `onModeExit` lifecycle hook, it is called when -navigating away from a mode. This hook can be used to clean up data tasks such -as unregistering services, removing annotations that do not need to be -persisted. - -_Example `onModeExit` hook implementation_ - -```js -export default { - id: 'myExampleExtension', - - onModeExit({ servicesManager, commandsManager }) { - myCacheService.purge(); - }, -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/_category_.json deleted file mode 100644 index c131ccdd7e..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Modules", - "position": 3 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/commands.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/commands.md deleted file mode 100644 index b37202bf61..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/commands.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Commands ---- -# Module: Commands - - -## Overview -`CommandsModule` includes list of arbitrary functions. These may activate tools, communicate with a server, open a modal, etc. -The significant difference between `OHIF-v3` and `OHIF-v2` is that in `v3` a `mode` defines -its toolbar, and which commands each tool call is inside in its toolDefinition - -An extension can register a Commands Module by defining a `getCommandsModule` -method. The Commands Module allows us to register one or more commands scoped to -specific [contexts](./../index.md#contexts). Commands have several unique -characteristics that make them tremendously powerful: - -- Multiple implementations for the same command can be defined -- Only the correct command's implementation will be run, dependent on the - application's "context" -- Commands are used by hotkeys, toolbar buttons and render settings - -Here is a simple example commands module: - -```js -const getCommandsModule = () => ({ - definitions: { - exampleActionDef: { - commandFn: ({ param1 }) => { - console.log(`param1's value is: ${param1}`); - }, - // storeContexts: ['viewports'], - options: { param1: 'param1' }, - context: 'VIEWER', // optional - }, - }, - defaultContext: 'ACTIVE_VIEWPORT::DICOMSR', -}); -``` - - -Each definition returned by the Commands Module is registered to the -`ExtensionManager`'s `CommandsManager`. - -> `storeContexts` has been removed in `OHIF-v3` and now modules have access to all commands and services. This change enables support for user-registered services. - -## Command Definitions - -The command definition consists of a named command (`exampleActionDef` below) and a -`commandFn`. The command name is used to call the command, and the `commandFn` -is the "command" that is actioned. - -```js -exampleActionDef: { - commandFn: ({ param1, options }) => { }, - options: { param1: 'measurement' }, - context: 'DEFAULT', -} -``` - -| Property | Type | Description | -| --------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | -| `commandFn` | func | The function to call when command is run. Receives `options` and `storeContexts`. | -| `options` | object | (optional) Arguments to pass at the time of calling to the `commandFn` | -| `context` | string[] or string | (optional) Overrides the `defaultContext`. Let's us know if command is currently "available" to be run. | - -## Command Behavior - - - -**If there are multiple valid commands for the application's active contexts** - -- What happens: all commands are run -- When to use: A `clearData` command that cleans up state for multiple - extensions - -**If no commands are valid for the application's active contexts** - -- What happens: a warning is printed to the console -- When to use: a `hotkey` (like "invert") that doesn't make sense for the - current viewport (PDF or HTML) - -## `CommandsManager` Public API - -If you would like to run a command in the consuming app or an extension, you can -use `CommandsManager.runCommand(commandName, options = {}, contextName)` - - -```js -// Returns all commands for a given context -commandsManager.getContext('string'); - -// Run a command, it will run all the `speak` commands in all contexts -commandsManager.runCommand('speak', { command: 'hello' }); - -// Run command, from Default context -commandsManager.runCommand('speak', { command: 'hello' }, ['DEFAULT']); -``` - -The `ExtensionManager` handles registering commands and creating contexts, so -most consumer's won't need these methods. If you find yourself using these, ask -yourself "why can't I register these commands via an extension?" - -```js -// Used by the `ExtensionManager` to register new commands -commandsManager.registerCommand('context', 'name', commandDefinition); - -// Creates a new context; clears the context if it already exists -commandsManager.createContext('string'); -``` - -### Contexts - -It is up to the consuming application to define what contexts are possible, and -which ones are currently active. As extensions depend heavily on these, we will -likely publish guidance around creating contexts, and ways to override extension -defined contexts in the near future. If you would like to discuss potential -changes to how contexts work, please don't hesitate to create a new GitHub -issue. - -[Some additional information on Contexts can be found here.](./../index.md#contexts) diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/contextModule.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/contextModule.md deleted file mode 100644 index 35de381a3d..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/contextModule.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -sidebar_position: 9 -sidebar_label: Context ---- -# Module: Context - -## Overview -This new module type allows you to connect components via a shared context. You can create a context that two components, e.g. a viewport and a panel can use to synchronize and communicate. An extensive example of this can be seen in the longitudinal mode’s custom extensions. - - - -```jsx -const ExampleContext = React.createContext(); - -function ExampleContextProvider({ children }) { - return ( - - {children} - - ); -} - -const getContextModule = () => [ - { - name: 'ExampleContext', - context: ExampleContext, - provider: ExampleContextProvider, - }, -]; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/data-source.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/data-source.md deleted file mode 100644 index 381d7ea7e1..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/data-source.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Data Source ---- - -# Module: Data Source - -## Overview - -The internal data structure of OHIF’s metadata follows naturalized DICOM JSON, A -format pioneered by `dcmjs`. In short DICOM metadata headers with DICOM Keywords -instead of tags and sequences as arrays, for easy development and clear code. - -We have built a standard for fetching and mapping data into OHIF’s native -format, which we call DataSources, and have provided one implementation of this -standard. - -You can make another datasource implementation which communicates to your -backend and maps to OHIF’s native format, then use any existing mode on your -platform. Your data doesn’t even need to be DICOM if you can map some -proprietary data to the correct format. - -The DataSource is also a place to add easy helper methods that platform-specific -extensions can call in order to interact with the backend, meaning proprietary -data interactions can be wrapped in extensions. - -```js -const getDataSourcesModule = () => [ - { - name: 'exampleDataSource', - type: 'webApi', // 'webApi' | 'local' | 'other' - createDataSource: dataSourceConfig => { - return IWebApiDataSource.create(/* */); - }, - }, -]; -``` - -Default extension provides two main data sources that are commonly used: -`dicomweb` and `dicomjson` - -```js -import { createDicomWebApi } from './DicomWebDataSource/index.js'; -import { createDicomJSONApi } from './DicomJSONDataSource/index.js'; - -function getDataSourcesModule() { - return [ - { - name: 'dicomweb', - type: 'webApi', - createDataSource: createDicomWebApi, - }, - { - name: 'dicomjson', - type: 'jsonApi', - createDataSource: createDicomJSONApi, - }, - ]; -} -``` - -## Custom DataSource - -You can add your custom datasource by creating the implementation using -`IWebApiDataSource.create` from `@ohif/core`. This factory function creates a -new "Web API" data source that fetches data over HTTP. - -You need to make sure, you implement the following functions for the data -source. - -```js title="platform/core/src/DataSources/IWebApiDataSource.js" -function create({ - query, - retrieve, - store, - reject, - parseRouteParams, - deleteStudyMetadataPromise, - getImageIdsForDisplaySet, - getImageIdsForInstance, -}) { - /* */ -} -``` - -You can take a look at `dicomweb` data source implementation to get an idea -`extensions/default/src/DicomWebDataSource/index.js` - -## Static WADO Client - -If the configuration for the data source has the value staticWado set, then it -is assumed that queries for the studies return a super-set of the studies, as it -is assumed to be returning a static list. The StaticWadoClient performs the -search functionality manually, by interpreting the query parameters and then -applying them to the returned response. This functionality may be useful for -other types of DICOMweb back ends, where they are capable of performing queries, -but don't allow for querying certain types of fields. However, that only works -as long as the size of the studies list isn't too large that client side -selection isn't too expensive. - -## DicomMetadataStore - -In `OHIF-v3` we have a central location for the metadata of studies, and they are -located in `DicomMetadataStore`. Your custom datasource can communicate with -`DicomMetadataStore` to store, and fetch Study/Series/Instance metadata. We will -learn more about `DicomMetadataStore` in services. diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/hpModule.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/hpModule.md deleted file mode 100644 index 649d6644ee..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/hpModule.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -sidebar_position: 8 -sidebar_label: Hanging Protocol ---- -# Module: Hanging Protocol - -## Overview -`hangingProtocolModule` provides the protocols for hanging the displaySets in the viewer. -This module can be as simple as loading a list of pre-defined protocols, or it can be more complex -and `fetch` the protocols from a server. - -You can read more about hanging protocols in HangingProtocolService. - -```js -const deafultProtocol = { - id: 'defaultProtocol', - locked: true, - hasUpdatedPriorsInformation: false, - name: 'Default', - createdDate: '2021-02-23T19:22:08.894Z', - modifiedDate: '2021-02-23T19:22:08.894Z', - availableTo: {}, - editableBy: {}, - protocolMatchingRules: [], - stages: [ - { - id: 'nwzau7jDkEkL8djfr', - name: 'oneByOne', - viewportStructure: { - type: 'grid', - properties: { - rows: 1, - columns: 1, - }, - }, - viewports: [ - { - viewportSettings: [], - imageMatchingRules: [], - seriesMatchingRules: [], - studyMatchingRules: [], - }, - ], - createdDate: '2021-02-23T19:22:08.894Z', - }, - ], - numberOfPriorsReferenced: -1, -}; - -function getHangingProtocolModule() { - return [ - { - name: hangingProtocolName, - protocols: [deafultProtocol], - }, - ]; -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/layout-template.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/layout-template.md deleted file mode 100644 index 66c839dae1..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/layout-template.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -sidebar_position: 7 -sidebar_label: Layout Template ---- - -# Module: Layout Template - -## Overview - -`LayoutTemplates` are a new concept in v3 that modes use to control the layout -of a route. A layout template is a React component that is given a set of -managers that define apis to access toolbar state, commands, and hotkeys, as -well as props defined by the layout template. - -For instance the default LayoutTemplate takes in leftPanels, rightPanels and -viewports as props, which it uses to build its view. - -In addition, `layout template` has complete control over the structure of the -application. You could have tools down the left side, or a strict guided -workflow with tools set programmatically, the choice is yours for your use case. - -```jsx -const getLayoutTemplateModule = (/* ... */) => [ - { - id: 'exampleLayout', - name: 'exampleLayout', - component: ExampleLayoutComponent, - }, -]; -``` - -The `props` that are passed to `layoutTemplate` are managers and service, along -with the defined mode left/right panels, mode's defined viewports and OHIF -`ViewportGridComp`. LayoutTemplate leverages extensionManager to grab typed -extension module entries: `*.getModuleEntry(id)` - -A simplified code for `Default extension`'s layout template is: - -```jsx title="extensions/default/src/ViewerLayout/index.jsx" -import React from 'react'; -import { SidePanel } from '@ohif/ui'; - -function Toolbar({ servicesManager }) { - const { ToolBarService } = servicesManager.services; - - return ( - <> - // ToolBarService.getButtonSection('primary') to get toolbarButtons - {toolbarButtons.map((toolDef, index) => { - const { id, Component, componentProps } = toolDef; - return ( - ToolBarService.recordInteraction(args)} - /> - ); - })} - - ); -} - -function ViewerLayout({ - // From Extension Module Params - extensionManager, - servicesManager, - hotkeysManager, - commandsManager, - // From Modes - leftPanels, - rightPanels, - viewports, - ViewportGridComp, -}) { - const getPanelData = id => { - const entry = extensionManager.getModuleEntry(id); - const content = entry.component; - - return { - iconName: entry.iconName, - iconLabel: entry.iconLabel, - label: entry.label, - name: entry.name, - content, - }; - }; - - const getViewportComponentData = viewportComponent => { - const entry = extensionManager.getModuleEntry(viewportComponent.namespace); - - return { - component: entry.component, - displaySetsToDisplay: viewportComponent.displaySetsToDisplay, - }; - }; - - const leftPanelComponents = leftPanels.map(getPanelData); - const rightPanelComponents = rightPanels.map(getPanelData); - const viewportComponents = viewports.map(getViewportComponentData); - - return ( -
- - -
- {/* LEFT SIDEPANELS */} - - - {/* TOOLBAR + GRID */} - - - {/* Rigth SIDEPANELS */} - -
-
- ); -} -``` - -## Overview Video - -
- -
diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/panel.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/panel.md deleted file mode 100644 index 1ad0d8c081..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/panel.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -sidebar_position: 6 -sidebar_label: Panel ---- - -# Module: Panel - -## Overview - -The default LayoutTemplate has panels on the left and right sides, however one -could make a template with panels at the top or bottom and make extensions with -panels intended for such slots. - -An extension can register a Panel Module by defining a `getPanelModule` method. -The panel module provides the ability to define `menuOptions` and `components` -that can be used by the consuming application. `components` are React Components -that can be displayed in the consuming application's "Panel" Component. - -![panel-module-v3](../../../assets/img/panel-module-v3.png) - -The `menuOptions`'s `target` key, points to a registered `components`'s `id`. A -`defaultContext` is applied to all `menuOption`s; however, each `menuOption` can -optionally provide its own `context` value. - -The `getPanelModule` receives an object containing the `ExtensionManager`'s -associated `ServicesManager` and `CommandsManager`. - -```jsx -import PanelMeasurementTable from './PanelMeasurementTable.js'; - -function getPanelModule({ - commandsManager, - extensionManager, - servicesManager, -}) { - const wrappedMeasurementPanel = () => { - return ( - - ); - }; - - return [ - { - name: 'measure', - iconName: 'list-bullets', - iconLabel: 'Measure', - label: 'Measurements', - isDisabled: studies => {}, // optional - component: wrappedMeasurementPanel, - }, - ]; -} -``` - -## Consuming Panels Inside Modes - -As explained earlier, extensions make the functionalities and components -available and `modes` utilize them to build an app. So, as seen above, we are -not actually defining which side the panel should be opened. Our extension is -providing the component with its. - -New: You can easily add multiple panels to the left/right side of the viewer -using the mode configuration. As seen below, the `leftPanels` and `rightPanels` -accept an `Array` of the `IDs`. - -```js - -const extensionDependencies = { - '@ohif/extension-default': '^3.0.0', - '@ohif/extension-cornerstone': '^3.0.0', - '@ohif/extension-measurement-tracking': '^3.0.0', - '@ohif/extension-cornerstone-dicom-sr': '^3.0.0', -}; - -const id = 'viewer' -const version = '3.0.0 - -function modeFactory({ modeConfiguration }) { - return { - id, - routes: [ - { - path: 'longitudinal', - layoutTemplate: ({ location, servicesManager }) => { - return { - id, - props: { - leftPanels: [ - '@ohif/extension-measurement-tracking.panelModule.seriesList', - ], - rightPanels: [ - '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', - ], - viewports, - }, - }; - }, - }, - ], - extensions: extensionDependencies - }; -} - -const mode = { - id, - modeFactory, - extensionDependencies, -}; - -export default mode; - -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/sop-class-handler.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/sop-class-handler.md deleted file mode 100644 index eb1b7a4641..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/sop-class-handler.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: SOP Class Handler ---- -# Module: SOP Class Handler - -## Overview -This module defines how a specific DICOM SOP class should be processed to make a displaySet, something that can be hung in a viewport. An extension can register a [SOP Class][sop-class-link] Handler Module by defining a `getSopClassHandlerModule` method. The [SOP Class][sop-class-link]. - -The mode chooses what SOPClassHandlers to use, so you could process a series in a different way depending on mode within the same application. - - -SOPClassHandler is a bit different from the other modules, as it doesn't provide a `1:1` -schema for UI or provide its own components. It instead defines: - -- `sopClassUIDs`: an array of string SOP Class UIDs that the - `getDisplaySetFromSeries` method should be applied to. -- `getDisplaySetFromSeries`: a method that maps series and study metadata to a - display set - -A `displaySet` has the following shape: - -```js -return { - Modality: 'MR', - displaySetInstanceUIDD - SeriesDate, - SeriesTime, - SeriesInstanceUID, - StudyInstanceUID, - SeriesNumber, - FrameRate, - SeriesDescription, - isMultiFrame, - numImageFrames, - SOPClassHandlerId, -} -``` - -## Example SOP Class Handler Module - -```js -import ImageSet from '@ohif/core/src/classes/ImageSet'; - - -const sopClassDictionary = { - CTImageStorage: "1.2.840.10008.5.1.4.1.1.2", - MRImageStorage: "1.2.840.10008.5.1.4.1.1.4", -}; - - -// It is important to note that the used SOPClassUIDs in the modes are in the order that is specified in the array. -const sopClassUids = [ - sopClassDictionary.CTImageStorage, - sopClassDictionary.MRImageStorage, -; - -const makeDisplaySet = (instances) => { - const instance = instances[0]; - const imageSet = new ImageSet(instances); - - imageSet.setAttributes({ - displaySetInstanceUID: imageSet.uid, - SeriesDate: instance.SeriesDate, - SeriesTime: instance.SeriesTime, - SeriesInstanceUID: instance.SeriesInstanceUID, - StudyInstanceUID: instance.StudyInstanceUID, - SeriesNumber: instance.SeriesNumber, - FrameRate: instance.FrameTime, - SeriesDescription: instance.SeriesDescription, - Modality: instance.Modality, - isMultiFrame: isMultiFrame(instance), - numImageFrames: instances.length, - SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`, - }); - - return imageSet; -}; - -getSopClassHandlerModule = () => { - return [ - { - name: 'stack, - sopClassUids, - getDisplaySetsFromSeries: makeDisplaySet, - }, - ]; -}; - -``` - -### More examples : -You can find another example for this mapping between raw metadata and displaySet for -`DICOM-SR` extension. - -## `@ohif/viewer` usage - -We use the `sopClassHandlerModule`s in `DisplaySetService` where we -transform instances from the raw metadata format to a OHIF displaySet format. -You can read more about DisplaySetService here. - - -[sop-class-link]: http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_B.5.html -[dicom-html-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js#L4-L12 -[dicom-pdf-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js#L4-L6 -[dicom-micro-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js#L5-L7 -[dicom-seg-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js#L5-L7 - diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md deleted file mode 100644 index 98e0d91a7a..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Toolbar ---- - -# Module: Toolbar - -An extension can register a Toolbar Module by defining a `getToolbarModule` -method. `OHIF-v3`'s `default` extension (`"@ohif/extension-default"`) provides 5 main -toolbar button types: - -![toolbarModule](../../../assets/img/toolbar-module.png) - -## Example Toolbar Module - -The Toolbar Module should return an array of `objects`. There are currently a -few different variations of definitions, each one is detailed further down. - -```js -export default function getToolbarModule({ commandsManager, servicesManager }) { - return [ - { - name: 'ohif.divider', - defaultComponent: ToolbarDivider, - clickHandler: () => {}, - }, - { - name: 'ohif.action', - defaultComponent: ToolbarButton, - clickHandler: () => {}, - }, - { - name: 'ohif.radioGroup', - defaultComponent: ToolbarButton, - clickHandler: () => {}, - }, - { - name: 'ohif.splitButton', - defaultComponent: ToolbarSplitButton, - clickHandler: () => {}, - }, - { - name: 'ohif.layoutSelector', - defaultComponent: ToolbarLayoutSelector, - clickHandler: (evt, clickedBtn, btnSectionName) => {}, - }, - ]; -} -``` - -## Toolbar buttons consumed in modes - -Below we can see a simplified version of the `longitudinal` mode that shows how -a mode can add buttons to the toolbar by calling -`ToolBarService.addButtons(toolbarButtons)`. `toolbarButtons` is an array of -`toolDefinitions` which we will learn next. - -```js -function modeFactory({ modeConfiguration }) { - return { - id: 'viewer', - displayName: 'Basic Viewer', - - onModeEnter: ({ servicesManager, extensionManager }) => { - const { ToolBarService } = servicesManager.services; - - ToolBarService.init(extensionManager); - ToolBarService.addButtons(toolbarButtons); - }, - routes: [ - { - path: 'longitudinal', - layoutTemplate: ({ location, servicesManager }) => { - return { - /* */ - }; - }, - }, - ], - }; -} -``` - -## Button Definitions - -The simplest toolbarButtons definition has the following properties: - -![toolbarModule-zoom](../../../assets/img/toolbarModule-zoom.png) - -```js -{ - id: 'Zoom', - type: 'ohif.radioGroup', - props: { - type: 'tool', - icon: 'tool-zoom', - label: 'Zoom', - commandOptions: { toolName: 'Zoom' }, - }, -}, -``` - -| property | description | values | -| ---------------- | ----------------------------------------------------------------- | ------------------------------------------- | -| `id` | Unique string identifier for the definition | \* | -| `label` | User/display friendly to show in UI | \* | -| `icon` | A string name for an icon supported by the consuming application. | \* | -| `type` | Used to determine the button's behaviour | "tool", "toggle", "action" | -| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` | -| `commandOptions` | (optional) Options to pass the target `commandName` | \* | - -There are three main types of toolbar buttons: - -- `tool`: buttons that enable a tool by running the `setToolActive` command with - the `commandOptions` -- `toggle`: buttons that acts as a toggle: e.g., linking viewports -- `action`: buttons that executes an action: e.g., capture button to save - screenshot - -## Nested Buttons - -You can use the `ohif.splitButton` type to build a button with extra tools in -the dropdown. - -- First you need to give your `primary` tool definition to the split button -- the `secondary` properties can be a simple arrow down (`chevron-down` icon) -- For adding the extra tools add them to the `items` list. - -You can see below how `longitudinal` mode is using the available toolbarModule -to create `MeasurementTools` nested button - -![toolbarModule-nested-buttons](../../../assets/img/toolbarModule-nested-buttons.png) - -```js title="modes/longitudinal/src/toolbarButtons.js" -{ - id: 'MeasurementTools', - type: 'ohif.splitButton', - props: { - groupId: 'MeasurementTools', - isRadio: true, - primary: { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } - }, - secondary: { - icon: 'chevron-down', - label: '', - isActive: true, - tooltip: 'More Measure Tools', - }, - items: [ - // Length tool - { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } - }, - // Bidirectional tool - { - id: 'Bidirectional', - icon: 'tool-bidirectional', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Bidirectional', - } - }, - // Ellipse tool - { - id: 'EllipticalRoi', - icon: 'tool-elipse', - label: 'Ellipse', - type: 'tool', - commandOptions: { - toolName: 'EllipticalRoi', - } - }, - // Circle tool - { - id: 'CircleROI', - icon: 'tool-circle', - label: 'Circle', - type: 'tool', - commandOptions: { - toolName: 'CircleROI', - } - }, - ], - }, -} -``` - -
- -
- -## Layout Template - -Layout selector button and logic is also provided by the OHIF-v3 `default` -extension. To use it, you can just add the following definition to the list of -`toolDefinitions` - -![toolbarModule-layout](../../../assets/img/toolbarModule-layout.png) - -```js -{ - id: 'Layout', - type: 'ohif.layoutSelector', -} -``` - -
- -
- -## Custom Button - -You can also create your own extension, and add your new custom tool appearance -(e.g., split horizontally instead of vertically for split tool). Simply add -`getToolbarModule` to your extension, and pass your tool react component to its -`defaultComponent` property in the returned object. You can use `@ohif/ui` -components such as `IconButton, Icon, Tooltip, ToolbarButton` to build your own -component. - -```js -import myToolComponent from './myToolComponent'; - -export default function getToolbarModule({ commandsManager, servicesManager }) { - return [ - { - name: 'new-tool-type', - defaultComponent: myToolComponent, - clickHandler: () => {}, - }, - ]; -} -``` - -## Custom tool - -**I want to create a new tool** diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/viewport.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/viewport.md deleted file mode 100644 index c3e07aa346..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/viewport.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Viewport ---- - -# Module: Viewport - -## Overview - -Viewports consume a displaySet and display/allow the user to interact with data. -An extension can register a Viewport Module by defining a `getViewportModule` -method that returns a React component. Currently, we use viewport components to -add support for: - -- 2D Medical Image Viewing (cornerstone ext.) -- Structured Reports as SR (DICOM SR ext.) -- Structured Reports as HTML (DICOM html ext.) -- Encapsulated PDFs as PDFs (DICOM pdf ext.) -- Whole Slide Microscopy Viewing (whole slide ext.) -- etc. - -The general pattern is that a mode can define which `Viewport` to use for which -specific `SOPClassHandlerUID`, so if you want to fork just a single Viewport -component for a specialized mode, this is possible. - -```jsx -// displaySet, viewportIndex, dataSource -const getViewportModule = () => { - const wrappedViewport = props => { - return ( - { - commandsManager.runCommand('commandName', data); - }} - /> - ); - }; - - return [{ name: 'example', component: wrappedViewport }]; -}; -``` - -## Example Viewport Component - -A simplified version of the tracked CornerstoneViewport is shown below, which -creates a cornerstone viewport and action bar on top of it. - -```jsx -function TrackedCornerstoneViewport({ - children, - dataSource, - displaySet, - viewportIndex, - servicesManager, - extensionManager, - commandsManager, -}) { - const renderViewport = () => { - const { component: Component } = extensionManager.getModuleEntry( - '@ohif/extension-cornerstone.viewportModule.cornerstone' - ); - return ( - - ); - }; - - return ( - <> - -
- {renderViewport()} -
- - ); -} -``` - -![viewportModule](../../../assets/img/viewportModule.png) - -### `@ohif/viewer` - -Viewport components are managed by the `ViewportGrid` Component. Which Viewport -component is used depends on: - -- Hanging Protocols -- The Layout Configuration -- Registered SopClassHandlers - -![viewportModule-layout](../../../assets/img/viewportModule-layout.png) - -
An example of three cornerstone Viewports
diff --git a/platform/docs/versioned_docs/version-3.0/platform/internationalization.md b/platform/docs/versioned_docs/version-3.0/platform/internationalization.md deleted file mode 100644 index 1bb32de5ba..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/internationalization.md +++ /dev/null @@ -1,396 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Internationalization ---- - -# Viewer: Internationalization - -OHIF supports internationalization using [i18next](https://www.i18next.com/) -through the npm package [@ohif/i18n](https://www.npmjs.com/package/@ohif/i18n), -where is the main instance of i18n containing several languages and tools. - -
-

Our translation management is powered by - Locize - through their generous support of open source.

- - Locize Translation Management Logo - -
- -## How to change language for the viewer? - -You can take a look into user manuals to see how to change the viewer's -language. In summary, you can change the language: - -- In the preference modals -- Using the language query in the URL: `lng=Test-LNG` - -## Installing - -```bash -yarn add @ohif/i18n - -# OR - -npm install --save @ohif/i18n -``` - -## How it works - -After installing `@ohif/i18n` npm package, the translation function -[t](https://www.i18next.com/overview/api#t) can be used [with](#with-react) or -[without](#without-react) React. - -A translation will occur every time a text match happens in a -[t](https://www.i18next.com/overview/api#t) function. - -The [t](https://www.i18next.com/overview/api#t) function is responsible for -getting translations using all the power of i18next. - -E.g. - -Before: - -```html -
my translated text
-``` - -After: - -```html -
{t('my translated text')}
-``` - -If the translation.json file contains a key that matches the HTML content e.g. -`my translated text`, it will be replaced automatically by the -[t](https://www.i18next.com/overview/api#t) function. - ---- - -### With React - -This section will introduce you to [react-i18next](https://react.i18next.com/) -basics and show how to implement the [t](https://www.i18next.com/overview/api#t) -function easily. - -#### Using Hooks - -You can use `useTranslation` hooks that is provided by `react-i18next` - -You can read more about this -[here](https://react.i18next.com/latest/usetranslation-hook). - -```js -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -function MyComponent() { - const { t } = useTranslation(); - - return

{t('my translated text')}

; -} -``` - -### Using outside of OHIF viewer - -OHIF Viewer already sets a main -[I18nextProvider](https://react.i18next.com/latest/i18nextprovider) connected to -the shared i18n instance from `@ohif/i18n`, all extensions inside OHIF Viewer -will share this same provider at the end, you don't need to set new providers at -all. - -But, if you need to use it completely outside of OHIF viewer, you can set the -I18nextProvider this way: - -```jsx -import i18n from '@ohif/i18n'; -import { I18nextProvider } from 'react-i18next'; -import App from './App'; - - - -; -``` - -After setting `I18nextProvider` in your React App, all translations from -`@ohif/i18n` should be available following the basic [With React](#with-react) -usage. - ---- - -### Without React - -When needed, you can also use available translations _without React_. - -E.g. - -```js -import { T } from '@ohif/i18n'; -console.log(T('my translated text')); -console.log(T('$t(Common:Play) my translated text')); -``` - ---- - -## Main Concepts While Translating - -## Namespaces - -Namespaces are being used to organize translations in smaller portions, combined -semantically or by use. Each `.json` file inside `@ohif/i18n` npm package -becomes a new namespace automatically. - -- Buttons: All buttons translations -- CineDialog: Translations for the tool tips inside the Cine Player Dialog -- Common: all common jargons that can be reused like `t('$t(common:image)')` -- Header: translations related to OHIF's Header Top Bar -- MeasurementTable - Translations for the `@ohif/ui` Measurement Table -- UserPreferencesModal - Translations for the `@ohif/ui` Preferences Modal -- Modals - Translations available for other modals -- PatientInfo - Translations for patients info hover -- SidePanel - Translations for side panels -- ToolTip - Translations for tool tips - -### How to use another NameSpace inside the current NameSpace? - -i18next provides a parsing feature able to get translations strings from any -NameSpace, like this following example getting data from `Common` NameSpace: - -``` -$t('Common:Reset') -``` - -## Extending Languages in @ohif/i18n - -Sometimes, even using the same language, some nouns or jargons can change -according to the country, states or even from Hospital to Hospital. - -In this cases, you don't need to set an entire language again, you can extend -languages creating a new folder inside a pre existent language folder and -@ohif/i18n will do the hard work. - -This new folder must to be called with a double character name, like the `UK` in -the following file tree: - -```bash - |-- src - |-- locales - index.js - |-- en - |-- Buttons.json - index.js - | UK - |-- Buttons.js - indes.js - | US - |-- Buttons.js - index.js - ... -``` - -All properties inside a Namespace will be merged in the new sub language, e.g -`en-US` and `en-UK` will merge the props with `en`, using i18next's fallback -languages tool. - -You will need to export all Json files in your `index.js` file, mounting an -object like this: - -```js - { - en: { - NameSpace: { - keyWord1: 'keyWord1Translation', - keyWord2: 'keyWord2Translation', - keyWord3: 'keyWord3Translation', - } - }, - 'en-UK': { - NameSpace: { - keyWord1: 'keyWord1DifferentTranslation', - } - } - } -``` - -Please check the `index.js` files inside locales folder for an example of this -exporting structure. - -### Extending languages dynamically - -You have access to the i18next instance, so you can use the -[addResourceBundle](https://www.i18next.com/how-to/add-or-load-translations#add-after-init) -method to add and change language resources as needed. - -E.g. - -```js -import { i18n } from '@ohif/i18n'; -i18next.addResourceBundle('pt-BR', 'Buttons', { - Angle: 'Ângulo', -}); -``` - ---- - -### How to set a whole new language - -To set a brand new language you can do it in two different ways: - -- Opening a pull request for `@ohif/i18n` and sharing the translation with the - community. 😍 Please see [Contributing](#contributing-with-new-languages) - section for further information. - -- Setting it only in your project or extension: - -You'll need a final object like the following, what is setting French as -language, and send it to `addLocales` method. - -```js -const newLanguage = - { - fr: { - Commons: { - "Reset": "Réinitialiser", - "Previous": "Précédent", - }, - Buttons: { - "Rectangle": "Rectangle", - "Circle": "Cercle", - } - } -``` - -To make it easier to translate, you can copy the .json files in the /locales -folder and theirs index.js exporters, keeping same keys and NameSpaces. -Importing the main index.js file, will provide you an Object as expected by the -method `addlocales`; - -E.g. of `addLocales` usage - -```js -import { addLocales } from '@ohif/i18n'; -import locales from './locales/index.js'; -addLocales(locales); -``` - -You can also set them manually, one by one, using this -[method](#extending-languages-dynamically). - ---- - -## Test Language - -We have created a test language that its translations can be seen in the locales -folder. You can copy paste the folder and its `.json` namespaces and add your -custom language translations. - -> If you apply the test-LNG you can see all the elements get appended with 'Test -> {}'. For instance `Study list` becomes `Test Study list`. - -## Language Detections - -@ohif/i18n uses -[i18next-browser-languageDetector](https://github.com/i18next/i18next-browser-languageDetector) -to manage detections, also exports a method called initI18n that accepts a new -detector config as parameter. - -### Changing the language - -OHIF Viewer accepts a query param called `lng` in the url to change the -language. - -E.g. - -``` -https://docs.ohif.org/demo/?lng=es-MX -``` - -### Language Persistence - -The user's language preference is kept automatically by the detector and stored -at a cookie called 'i18next', and in a localstorage key called 'i18nextLng'. -These names can be changed with a new -[Detector Config](https://github.com/i18next/i18next-browser-languageDetector). - -## Debugging translations - -There is an environment variable responsible for debugging the translations, -called `REACT_APP_I18N_DEBUG`. - -Run the project as following to get full debug information: - -```bash -REACT_APP_I18N_DEBUG=true yarn run dev -``` - -## Contributing with new languages - -We have integrated `i18next` into the OHIF Viewer and hooked it up with Locize -for translation management. Now we need your help to get the app translated into -as many languages as possible, and ensure that we haven't missed pieces of the -app that need translation. Locize has graciously offered to provide us with free -usage of their product. - -Once each crowd-sourcing project is completed, we can approve it and merge the -changes into the main project. At that point, the language will be immediately -available on https://viewer.ohif.org/ for testing, and can be used in any OHIF -project. We will support usage through both the Locize CDN and by copying the -language directly into the `@ohif/i18n` package, so that end users can serve the -content from their own domains. - -Here are a couple examples: - -Spanish: -https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=es - -Chinese: -https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=zh - -Portugese: -https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=pt-BR - -Here are some links you can use to sign up to help translate. All you have to do -is sign up, translate the strings, and click Save. On our side, we have a -dashboard to see how many strings are translated and by whom. - -This is a pretty random set of languages, so please post below if you'd like a -new language link to be added: - -Languages: - -[French](https://www.locize.io/register?invitation=Nj8jRPaFKYwtIfNZ6Y5GVOJOpeiXNAdVuSiOg9ceaiveP6uF6y1wVXM9lgfKoYZX) - -[German](https://www.locize.io/register?invitation=gChNiVi66YINTPpbKESVAVYPapwg3DkpvMSSomLTvVqBJTXrdmPvxi0WZYHER11q) - -[Dutch](https://www.locize.io/register?invitation=2PGe7I184aN0cazM4GXMhzeLtGTf9Zen5uyOEFhHQ8vYkfKHkgR0mJ8dwbNlIeCG) - -[Turkish](https://www.locize.io/register?invitation=NOMIXsfneqPbFDqjce5wI7Z6p2swXSjc0rHOH4KLcM6qXSNA4LGyJaLxS7nqWAe3) - -[Chinese](https://www.locize.io/register?invitation=lrcUbt7DvV4aJmQeEA4SMAj5xNWr3rltOcaZW1cFc6eod0nvzSPFU4V383tDHGGn) - -[Japanese](https://www.locize.io/register?invitation=AaRq2S22o5FsxArwgVuw1gZcQjoe2ffyxarqlAXOpN7JnR2sf2mfamc5qV6LG1Mn) - -[Arabic](https://www.locize.io/register?invitation=BiqI6fOm1sC84N3YJLbImXmaOCk8Hc3TMGpXg7NH2R0b0OKuPCp9wlCHLoqMRpfQ) - -[Hindi](https://www.locize.io/register?invitation=ph7JmOGTV95DF3EFaI1kvK5Hx98dV9w2wj9h9UhUCWnkBNAwWEdWMcyjnF94zkWb) - -[Malay](https://www.locize.io/register?invitation=HsV9F5mKZyeUZYrC3XFRzNI2l0EsIh6hK0MUIKP8IYZA3GxuzfgkvWBLCFwCpDik) - -[Russian](https://www.locize.io/register?invitation=da4V9Q8DVO3M1FIlvfT50ZiS8NDNgvC0dE5hHUEAp47FXy6pLXmf1cp2lgLBfLmb) - -[Swedish](https://www.locize.io/register?invitation=uR4kzBZC1vhJe6jyMwYXgGPj84QDMulQRlt2s6rONU6ljUh5dgwuUyhJEtZ4REA3) - -[Italian](https://www.locize.io/register?invitation=viAS1NC5q342OxtuIv3JFX9DJ3KoR4SmGoElkBlRMphsDKt4hy9bW8JfBjHlfnd7) - -[Spanish](https://www.locize.io/register?invitation=ZikXW3KI4w4eo5Cf6L1aQMWaR69XAQ0a9Va3NGorH7mAPvEPXp8w8NLkPNLs5nG8) - -[Ukrainian](https://www.locize.io/register?invitation=TY0s6onqH3Asl05Bh1qB44SNSABL2pTYoturwxAmcNKRnzBZFK7bGfn7kVi23Vpg) - -[Vietnamese](https://www.locize.io/register?invitation=eqfHDm0vaqxGfQ5TGt6SeV0dx9b2dCp1RrMRdIRavqzOCOAfD3IElzUsyIT689cK) - -[Portugese-Brazil](https://www.locize.io/register?invitation=Qc5Dq449xbblQqLTpWeMfsyFiu3gACcgpj0EIucQjjs9Ph9pzPLpq3MnZupF9t6N) - -Don't see your language in the above list? Add a request -[here](https://github.com/OHIF/Viewers/issues/618) so that we can create the -language for your translation contribution. diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/managers/_category_.json deleted file mode 100644 index 3094077927..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Managers", - "position": 11 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/commands.md b/platform/docs/versioned_docs/version-3.0/platform/managers/commands.md deleted file mode 100644 index fab666daca..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/commands.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Commands Manager ---- -# Commands Manager - -## Overview - - -The `CommandsManager` is a class defined in the `@ohif/core` project. The Commands Manager tracks named commands (or functions) that are scoped to -a context. When we attempt to run a command with a given name, we look for it -in our active contexts. If found, we run the command, passing in any application -or call specific data specified in the command's definition. - -> Note: A single instance of `CommandsManager` should be defined in the consuming application, and it is used when constructing the `ExtensionManager`. - -A `simplified skeleton` of the `CommandsManager` is shown below: - -```js -export class CommandsManager { - constructor({ getActiveContexts } = {}) { - this.contexts = {}; - this._getActiveContexts = getActiveContexts; - } - - getContext(contextName) { - const context = this.contexts[contextName]; - return context; - } - - /**...**/ - - createContext(contextName) { - /** ... **/ - this.contexts[contextName] = {}; - } - - - registerCommand(contextName, commandName, definition) { - /**...**/ - const context = this.getContext(contextName); - /**...**/ - context[commandName] = definition; - } - - runCommand(commandName, options = {}, contextName) { - const definition = this.getCommand(commandName, contextName); - /**...**/ - const { commandFn } = definition; - const commandParams = Object.assign( - {}, - definition.options, // "Command configuration" - options // "Time of call" info - ); - /**...**/ - return commandFn(commandParams); - } - /**...**/ -} -``` - - - - -### Instantiating - -When we instantiate the `CommandsManager`, we are passing two methods: - -- `getAppState` - Should return the application's state when called (Not implemented in `v3`) -- `getActiveContexts` - Should return the application's active contexts when - called - -These methods are used internally to help determine which commands are currently -valid, and how to provide them with any state they may need at the time they are -called. - -```js title="platform/viewer/src/appInit.js" -const commandsManagerConfig = { - getAppState: () => {}, - /** Used by commands to determine active context */ - getActiveContexts: () => [ - 'VIEWER', - 'DEFAULT', - 'ACTIVE_VIEWPORT::CORNERSTONE', - ], -}; - -const commandsManager = new CommandsManager(commandsManagerConfig); -``` - - -## Commands/Context Registration -The `ExtensionManager` handles registering commands and creating contexts, so you don't need to register all your commands manually. Simply, create a `commandsModule` in your extension, and it will get automatically registered in the `context` provided. - -A *simplified version* of this registration is shown below to give an idea about the process. - - -```js -export default class ExtensionManager { - constructor({ commandsManager }) { - this._commandsManager = commandsManager - } - /** ... **/ - registerExtension = (extension, configuration = {}, dataSources = []) => { - let extensionId = extension.id - /** ... **/ - - // Register Modules provided by the extension - moduleTypeNames.forEach((moduleType) => { - const extensionModule = this._getExtensionModule( - moduleType, - extension, - extensionId, - configuration - ) - - if (moduleType === 'commandsModule') { - this._initCommandsModule(extensionModule) - } - /** registering other modules **/ - }) - } - - _initCommandsModule = (extensionModule) => { - let { definitions, defaultContext } = extensionModule - defaultContext = defaultContext || 'VIEWER' - - if (!this._commandsManager.getContext(defaultContext)) { - this._commandsManager.createContext(defaultContext) - } - - Object.keys(definitions).forEach((commandName) => { - const commandDefinition = definitions[commandName] - const commandHasContextThatDoesNotExist = - commandDefinition.context && - !this._commandsManager.getContext(commandDefinition.context) - - if (commandHasContextThatDoesNotExist) { - this._commandsManager.createContext(commandDefinition.context) - } - - this._commandsManager.registerCommand( - commandDefinition.context || defaultContext, - commandName, - commandDefinition - ) - }) - } -} - -``` - - -If you find yourself in a situation where you want to register a command/context manually, ask -yourself "why can't I register these commands via an extension?", but if you insist, you can use the `CommandsManager` API to do so: - -```js -// Command Registration -commandsManager.registerCommand('context', 'name', commandDefinition); - -// Context Creation -commandsManager.createContext('string'); -``` - -## `CommandsManager` Public API - -If you would like to run a command in the consuming app or an extension, you can -use `runCommand(commandName, options = {}, contextName)`. - - -```js -// Run a command, it will run all the `speak` commands in all contexts -commandsManager.runCommand('speak', { command: 'hello' }); - -// Run command, from Default context -commandsManager.runCommand('speak', { command: 'hello' }, ['DEFAULT']); - -// Returns all commands for a given context -commandsManager.getContext('string'); -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/extension.md b/platform/docs/versioned_docs/version-3.0/platform/managers/extension.md deleted file mode 100644 index 9fb5196f84..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/extension.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Extension Manager ---- - -# Extension Manager - -## Overview - -The `ExtensionManager` is a class made available to us via the `@ohif/core` -project (platform/core). Our application instantiates a single instance of it, -and provides a `ServicesManager` and `CommandsManager` along with the -application's configuration through the appConfig key (optional). - -```js -const commandsManager = new CommandsManager(); -const servicesManager = new ServicesManager(); -const extensionManager = new ExtensionManager({ - commandsManager, - servicesManager, - appConfig, -}); -``` - -The `ExtensionManager` only has a few public members: - -- `setActiveDataSource` - Sets the active data source for the application -- `getDataSources` - Returns the registered data sources -- `getActiveDataSource` - Returns the currently active data source -- `getModuleEntry` - Returns the module entry by the give id. - -## Accessing Modules - -We use `getModuleEntry` in our `ViewerLayout` logic to find the panels based on -the provided IDs in the mode's configuration. - -For instance: -`extensionManager.getModuleEntry("@ohif/extension-measurement-tracking.panelModule.seriesList")` -accesses the `seriesList` panel from `panelModule` of the -`@ohif/extension-measurement-tracking` extension. - -```js -const getPanelData = id => { - const entry = extensionManager.getModuleEntry(id); - const content = entry.component; - - return { - iconName: entry.iconName, - iconLabel: entry.iconLabel, - label: entry.label, - name: entry.name, - content, - }; -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/hotkeys.md b/platform/docs/versioned_docs/version-3.0/platform/managers/hotkeys.md deleted file mode 100644 index ebdd2bd133..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/hotkeys.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Hotkeys Manager ---- -# Hotkeys Managers - -## Overview -`HotkeysManager` handles all the logics for adding, setting and enabling/disabling -the hotkeys. - - - -## Instantiation -`HotkeysManager` is instantiated in the `appInit` similar to the other managers. - -```js -const commandsManager = new CommandsManager(commandsManagerConfig); -const servicesManager = new ServicesManager(commandsManager); -const hotkeysManager = new HotkeysManager(commandsManager, servicesManager); -const extensionManager = new ExtensionManager({ - commandsManager, - servicesManager, - hotkeysManager, - appConfig, -}); -``` - - - - -## Hotkeys Manager API - -- `setHotkeys`: The most important method in the `HotkeysManager` which binds the keys with commands. -- `setDefaultHotKeys`: set the defaultHotkeys **property**. Note that, this method **does not** bind the provided hotkeys; however, when `restoreDefaultBindings` -is called, the provided defaultHotkeys will get bound. -- `destroy`: reset the HotkeysManager, and remove the set hotkeys and empty out the `defaultHotkeys` - - - -## Structure of a Hotkey Definition -A hotkey definition should have the following properties: - -- `commandName`: name of the registered command -- `commandOptions`: extra arguments to the commands -- `keys`: an array defining the key to get bound to the command -- `label`: label to be shown in the hotkeys preference panel -- `isEditable`: whether the key can be edited by the user in the hotkey panel - - -### Default hotkeysBindings -The default key bindings can be find in `hotkeyBindings.js` - -```js -// platform/core/src/defaults/hotkeyBindings.js - -export default [ - /**..**/ - { - commandName: 'setToolActive', - commandOptions: { toolName: 'Zoom' }, - label: 'Zoom', - keys: ['z'], - isEditable: true, - }, - - { - commandName: 'flipViewportHorizontal', - label: 'Flip Vertically', - keys: ['v'], - isEditable: true, - }, - /**..**/ -] -``` - - -## Behind the Scene -When you `setHotkeys`, the `commandName` gets registered with the `commandsManager` and -get run after the key is pressed. diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/index.md b/platform/docs/versioned_docs/version-3.0/platform/managers/index.md deleted file mode 100644 index ee6d6b2fc8..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/index.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Introduction ---- - -# Managers - -## Overview - -`OHIF` uses `Managers` to accomplish various purposes such as registering new -services, dependency injection, and aggregating and exposing `extension` -features. - -`OHIF-v3` provides the following managers which we will discuss in depth. - - - - - - - - - - - - - - - - - - - - - - - - - - -
ManagerDescription
- - Extension Manager - - - Aggregating and exposing modules and features through out the app -
- - Services Manager - - - Single point of registration for all internal and external services -
- - Commands Manager - - - Register commands with specific context and run commands in the app -
- - Hotkeys Manager - - - For keyboard keys assignment to commands -
- - - - - -[core-services]: https://github.com/OHIF/Viewers/tree/master/platform/core/src/services -[services-manager]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/ServicesManager.js -[cross-cutting-concerns]: https://en.wikipedia.org/wiki/Cross-cutting_concern - diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/service.md b/platform/docs/versioned_docs/version-3.0/platform/managers/service.md deleted file mode 100644 index 2cacda412e..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/managers/service.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Service Manager ---- - -# Services Manager - -## Overview - -Services manager is the single point of service registration. Each service needs -to implement a `create` method which gets called inside `ServicesManager` to -instantiate the service. In the app, you can get access to a registered service -via the `services` property of the `ServicesManager`. - -## Skeleton - -_Simplified_ skeleton of `ServicesManager` is shown below. There are two public -methods: - -- `registerService`: registering a new service with/without a configuration -- `registerServices`: registering batch of services - -```js -export default class ServicesManager { - constructor(commandsManager) { - this._commandsManager = commandsManager; - this.services = {}; - this.registeredServiceNames = []; - } - - registerService(service, configuration = {}) { - /** validation checks **/ - this.services[service.name] = service.create({ - configuration, - commandsManager: this._commandsManager, - }); - - /* Track service registration */ - this.registeredServiceNames.push(service.name); - } - - registerServices(services) { - /** ... **/ - } -} -``` - -## Default Registered Services - -By default, `OHIF-v3` registers the following services in the `appInit`. - -```js title="platform/viewer/src/appInit.js" -servicesManager.registerServices([ - UINotificationService, - UIModalService, - UIDialogService, - UIViewportDialogService, - MeasurementService, - DisplaySetService, - ToolBarService, - ViewportGridService, - HangingProtocolService, - CineService, -]); -``` - -## Service Architecture - -If you take a look at the folder of each service implementation above, you will -find out that services need to be exported as an object with `name` and `create` -method. - -For instance, `ToolBarService` is exported as: - -```js title="platform/core/src/services/ToolBarService/index.js" -import ToolBarService from './ToolBarService'; - -export default { - name: 'ToolBarService', - create: ({ configuration = {}, commandsManager }) => { - return new ToolBarService(commandsManager); - }, -}; -``` - -and the implementation of `ToolBarService` lies in the same folder at -`./ToolbarSerivce.js`. - -> Note, the create method is critical for any custom service that you write and -> want to add to the list of services - -## Accessing Services - -Throughout the app you can use `services` property of the service manager to -access the desired service. - -For instance in the `PanelMeasurementTableTracking` which is the right panel in -the `longitudinal` mode, we have the _simplified code below_ for downloading the -drawn measurements. - -```js -function PanelMeasurementTableTracking({ servicesManager }) { - const { MeasurementService } = servicesManager.services; - /** ... **/ - - async function exportReport() { - const measurements = MeasurementService.getMeasurements(); - /** ... **/ - downloadCSVReport(measurements, MeasurementService); - } - - /** ... **/ - return <> /** ... **/ ; -} -``` - -## Registering Custom Services - -You might need to write you own custom service in an extension. -`preRegistration` hook inside your extension is the place for registering your -custom service. - -```js title="extensions/customExtension/src/index.js" -import WrappedBackEndService from './services/backEndService'; - -export default { - // ID of the extension - id: 'myExtension', - preRegistration({ servicesManager }) { - servicesManager.registerService(WrappedBackEndService(servicesManager)); - }, -}; -``` - -and the logic for your service shall be - -```js title="extensions/customExtension/src/services/backEndService/index.js" -import backEndService from './backEndService'; - -export default function WrappedBackEndService(serviceManager) { - return { - name: 'myService', - create: ({ configuration = {} }) => { - return new backEndService(serviceManager); - }, - }; -} -``` - -with implementation of - -```js -export default class backEndService { - constructor(serviceManager) { - this.serviceManager = serviceManager; - } - - putAnnotations() { - return post(/*...*/); - } -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/modes/_category_.json deleted file mode 100644 index 80702b9e54..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Modes", - "position": 10 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/index.md b/platform/docs/versioned_docs/version-3.0/platform/modes/index.md deleted file mode 100644 index b01049b0f1..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/index.md +++ /dev/null @@ -1,383 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Introduction ---- - -# Modes - -## Overview - -A mode can be thought of as a viewer app configured to perform a specific task, -such as tracking measurements over time, 3D segmentation, a guided radiological -workflow, etc. Addition of modes enables _application_ with many _applications_ -as each mode become a mini _app configuration_ behind the scene. - -Upon initialization the viewer will consume extensions and modes and build up -the route desired, these can then be accessed via the study list, or directly -via url parameters. - - - -OHIF-v3 architecture can be seen in the following: - -![mode-archs](../../assets/img/mode-archs.png) - -> Note: Templates are now a part of “extensions” Routes are configured by modes -> and/or app - -As mentioned, modes are tied to a specific route in the viewer, and multiple -modes/routes can be present within a single application. This allows for -tremendously more flexibility than before you can now: - -- Simultaneously host multiple viewers with for different use cases from within - the same app deploy. -- Make radiological viewers for specific purposes/workflows, e.g.: - - Tracking the size of lesions over time. - - PET/CT fusion workflows. - - Guided review workflows optimized for a specific clinical trial. -- Still host one single feature-rich viewer if you desire. - -## Anatomy - -A mode configuration has a `route` name which is dynamically transformed into a -viewer route on initialization of the application. Modes that are available to a -study will appear in the study list. - -![user-study-summary](../../assets/img/user-study-summary.png) - -The mode configuration specifies which `extensions` the mode requires, which -`LayoutTemplate` to use, and what props to pass to the template. For the default -template this defines which `side panels` will be available, as well as what -`viewports` and which `displaySets` they may hang. - -Mode's config is composed of three elements: -- `id`: the mode `id` -- `modeFactory`: the function that returns the mode specific configuration -- `extensionDependencies`: the list of extensions that the mode requires - - -that return a config object with certain -properties, the high-level view of this config object is: - -```js title="modes/example/src/index.js" -function modeFactory() { - return { - id: '', - version: '', - displayName: '', - onModeEnter: () => {}, - onModeExit: () => {}, - validationTags: {}, - isValidMode: () => {}, - routes: [ - { - path: '', - init: () => {}, - layoutTemplate: () => {}, - }, - ], - extensions: extensionDependencies, - hangingProtocol: [], - sopClassHandlers: [], - hotkeys: [], - }; -} - -const mode = { - id, - modeFactory, - extensionDependencies, -}; - -export default mode; -``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PropertyDescription
- id - unique mode id used to refer to the mode
- displayName - actual name of the mode being displayed for each study in the study summary panel
- - onModeEnter - - hook is called when the mode is entered by the specified route
- - onModeExit - - hook is called when the mode exited
- - validationTags - - validationTags
- - isValidMode - - Checks if the mode is valid for a study
- - routes - - route config which defines the route address, and the layout for it
- - extensionDependencies - - extensions needed by the mode
- - hanging protocol - - list of hanging protocols that the mode should have access to
- - sopClassHandlers - - list of SOPClass modules needed by the mode
- - hotkeys - - hotkeys
- -### Consuming Extensions - -As mentioned in the [Extensions](../extensions/index.md) section, in `OHIF-v3` -developers write their extensions to create re-usable functionalities that later -can be used by `modes`. Now, it is time to describe how the registered -extensions will get utilized for a workflow mode via its `id`. - -Each `mode` has a list of its `extensions dependencies` which are the -the `extension` name and version number. In addition, to use a module element you can use the -`${extensionId}.${moduleType}.${element.name}` schema. For instance, if a mode -requires the left panel with name of `AIPanel` that is added by the -`myAIExtension` via the following `getPanelModule` code, it should address it as -`myAIExtension.panelModule.AIPanel` inside the mode configuration file. In the -background `OHIF` will handle grabbing the correct panel via `ExtensionManager`. - -```js title="extensions/myAIExtension/getPanelModule.js" -import PanelAI from './PanelAI.js'; - -function getPanelModule({ - commandsManager, - extensionManager, - servicesManager, -}) { - const wrappedAIPanel = () => { - return ( - - ); - }; - - return [ - { - name: 'AIPanel', - iconName: 'list-bullets', - iconLabel: '', - label: 'AI Panel', - isDisabled: studies => {}, // optional - component: wrappedAIPanel, - }, - ]; -} -``` - -Now, let's look at a simplified code of the `basic viewer` mode which consumes various functionalities -from different extensions. - -```js - -const extensionDependencies = { - '@ohif/extension-default': '^3.0.0', - '@ohif/extension-cornerstone': '^3.0.0', - '@ohif/extension-measurement-tracking': '^3.0.0', -}; - -const id = 'viewer'; -const version = '3.0.0'; - -function modeFactory({ modeConfiguration }) { - return { - id, - // ... - routes: [ - { - // ... - layoutTemplate: ({ location, servicesManager }) => { - return { - id: ohif.layout, - props: { - leftPanels: ['@ohif/extension-measurement-tracking.panelModule.seriesList'], - rightPanels: ['@ohif/extension-measurement-tracking.panelModule.trackedMeasurements'], - viewports: [ - { - namespace: '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', - displaySetsToDisplay: ['@ohif/extension-default.sopClassHandlerModule.stack'], - }, - ], - }, - }; - }, - }, - ], - extensions: extensionDependencies, - hangingProtocol: ['@ohif/extension-default.hangingProtocolModule.petCT'], - sopClassHandlers: ['@ohif/extension-default.sopClassHandlerModule.stack'], - // ... - }; -} - -const mode = { - id, - modeFactory, - extensionDependencies, -} - -export default mode -``` - -### Routes - -routes config is an array of route settings, and the overall look and behavior -of the viewer at the designated route is defined by the `layoutTemplate` and -`init` functions for the route. We will learn more about each of the above -properties inside the [route documentation](./routes.md) - - -### HangingProtocols - -Currently, you can pass your defined hanging protocols inside the -`hangingProtocols` property of the mode's config. This will get registered -inside `HangingProtocolService`. - -### SopClassHandlers - -Mode's configuration also accepts the `sopClassHandler` modules that have been -added by the extensions. This information will get used to initialize `DisplaySetService` with the provided SOPClass modules which -handles creation of the displaySets. - - -### Hotkeys - -`hotkeys` is another property in the configuration of a mode that can be defined -to add the specific hotkeys to the viewer at all routes. - -```js -// default hotkeys -import { utils } from '@ohif/ui'; - -const { hotkeys } = utils; - -const myHotkeys = [ - { - commandName: 'setToolActive', - commandOptions: { toolName: 'Zoom' }, - label: 'Zoom', - keys: ['z'], - isEditable: true, - }, - { - commandName: 'scaleUpViewport', - label: 'Zoom In', - keys: ['+'], - isEditable: true, - }, -] - -function modeFactory() { - return { - id: '', - id: '', - displayName: '', - /* - ... - */ - hotkeys: [..hotkeys.defaults.hotkeyBindings, ...myHotkeys], - } -} - -// exports -``` - -## Registration - -Similar to extension registration, `viewer` will look inside the `pluginConfig.json` to -find the `modes` to register. - - -```js title=platform/viewer/pluginConfig.json -// Simplified version of the `pluginConfig.json` file -{ - "extensions": [ - { - "packageName": "@ohif/extension-cornerstone", - "version": "3.0.0" - }, - // ... - ], - "modes": [ - { - "packageName": "@ohif/mode-longitudinal", - "version": "0.0.1" - } - ] -} -``` - -:::note Important -You SHOULD NOT directly register modes in the `pluginConfig.json` file. -Use the provided `cli` to add/remove/install/uninstall modes. Read more [here](../../development/ohif-cli.md) -::: - -The final registration and import of the modes happen inside a non-tracked file `pluginImport.js` (this file is also for internal use only). diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/installation.md b/platform/docs/versioned_docs/version-3.0/platform/modes/installation.md deleted file mode 100644 index 08c456d29e..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/installation.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Installation ---- - -# Modes: Installation - -OHIF-v3 provides the ability to utilize external modes. - - -You can use ohif `cli` tool to install both local and publicly published -modes on NPM. You can read more [here](../../development/ohif-cli.md) diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/lifecycle.md b/platform/docs/versioned_docs/version-3.0/platform/modes/lifecycle.md deleted file mode 100644 index 32ea77cb35..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/lifecycle.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Lifecycle Hooks ---- - -# Modes: Lifecycle Hooks - -## Overview - -Currently, there are two hooks that are called for modes: - -- onModeEnter -- onModeExit - -## onModeEnter - -This hook gets run after the defined route has been entered by the mode. This -hook can be used to initialize the data, services and appearance of the viewer -upon the first render. - -For instance, in `longitudinal` mode we are using this hook to initialize the -`ToolBarService` and set the window level/width tool to be active and add -buttons to the toolbar. - -```js -function modeFactory() { - return { - id: '', - version: '', - displayName: '', - onModeEnter: ({ servicesManager, extensionManager }) => { - const { ToolBarService } = servicesManager.services; - - const interaction = { - groupId: 'primary', - itemId: 'WindowLevel', - interactionType: 'tool', - commandOptions: undefined, - }; - - ToolBarService.recordInteraction(interaction); - - ToolBarService.init(extensionManager); - ToolBarService.addButtons(toolbarButtons); - ToolBarService.createButtonSection('primary', [ - 'MeasurementTools', - 'Zoom', - 'WindowLevel', - 'Pan', - 'Capture', - 'Layout', - 'MoreTools', - ]); - }, - /* - ... - */ - }; -} -``` - -## onModeExit - -This hook is called when the viewer navigate away from the route in the url. -This is the place for cleaning up data, and services by unsubscribing to the -events. - -For instance, it can be used to reset the `ToolBarService` which reset the -toggled buttons. - -```js -function modeFactory() { - return { - id: '', - displayName: '', - onModeExit: ({ servicesManager, extensionManager }) => { - // Turn of the toggled states on exit - const { ToolBarService } = servicesManager.services; - ToolBarService.reset(); - }, - /* - ... - */ - }; -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/routes.md b/platform/docs/versioned_docs/version-3.0/platform/modes/routes.md deleted file mode 100644 index 2204bd77ae..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/routes.md +++ /dev/null @@ -1,315 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: Routes ---- - -# Mode: Routes - -## Overview - -Modes are tied to a specific route in the viewer, and multiple modes/routes can -be present within a single application. This makes `routes` config, THE most -important part of the mode configuration. - -## Route - -`@ohif/viewer` **compose** extensions to build applications on different routes -for the platform. - -Below, you can see a simplified version of the `longitudinal` mode and the -`routes` section which has defined one `route`. Each route has three different -configuration: - -- **route path**: defines the route path to access the built application for - that route -- **route init**: hook that runs when application enters the defined route path, - if not defined the default init function will run for the mode. -- **route layout**: defines the layout of the application for the specified - route (panels, viewports) - -```js -function modeFactory() { - return { - id: 'viewer', - version: '3.0.0', - displayName: '', - routes: [ - { - path: 'longitudinal', - /*init: ({ servicesManager, extensionManager }) => { - //defaultViewerRouteInit - },*/ - layoutTemplate: ({ location, servicesManager }) => { - return { - id: ohif.layout, - props: { - leftPanels: [ - '@ohif/extension-measurement-tracking.panelModule.seriesList', - ], - rightPanels: [ - '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', - ], - viewports: [ - { - namespace: - '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', - displaySetsToDisplay: [ - '@ohif/extension-default.sopClassHandlerModule.stack', - ], - }, - { - namespace: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', - displaySetsToDisplay: [ - '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', - ], - }, - ], - }, - }; - }, - }, - ], - /* - ... - */ - }; -} -``` - -### Route: path - -Upon initialization the viewer will consume extensions and modes and build up -the route desired, these can then be accessed via the study list, or directly -via url parameters. - -> Note: Currently, only one route is built for each mode, but we will enhance -> route creation to create separate routes based on the `path` config for each -> `route` object. - -There are two types of `routes` that are created by the mode. - -- Routes with dataSourceName `/${mode.id}/${dataSourceName}` -- Routes without dataSourceName `/${mode.id}` - -Therefore, navigating to -`http://localhost:3000/viewer/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` -will run the app with the layout and functionalities of the `viewer` mode using -the `defaultDataSourceName` which is defined in the -[App Config](../../configuration/index.md) - -You can use the same exact mode using a different registered data source (e.g., -`dicomjson`) by navigating to -`http://localhost:3000/viewer/dicomjson/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` - -### Route: init - -The mode also has an init hook, which initializes the mode. If you don't define -an `init` function the `default init` function will get run (logic is located -inside `Mode.jsx`). However, you can define you own init function following -certain steps which we will discuss next. - -#### Default init - -Default init function will: - -- `retriveSeriesMetaData` for the `studyInstanceUIDs` that are defined in the - URL. -- Subscribe to `instanceAdded` event, to make display sets after a series have - finished retrieving its instances' metadata. -- Subscribe to `seriesAdded` event, to run the `HangingProtocolService` on the - retrieves series from the study. - -A _simplified_ "pseudocode" for the `defaultRouteInit` is: - -```jsx -async function defaultRouteInit({ - servicesManager, - studyInstanceUIDs, - dataSource, -}) { - const { - DisplaySetService, - HangingProtocolService, - } = servicesManager.services; - - // subscribe to run the function after the event happens - DicomMetadataStore.subscribe( - 'instancesAdded', - ({ StudyInstanceUID, SeriesInstanceUID }) => { - const seriesMetadata = DicomMetadataStore.getSeries( - StudyInstanceUID, - SeriesInstanceUID - ); - DisplaySetService.makeDisplaySets(seriesMetadata.instances); - } - ); - - studyInstanceUIDs.forEach(StudyInstanceUID => { - dataSource.retrieve.series.metadata({ StudyInstanceUID }); - }); - - DicomMetadataStore.subscribe('seriesAdded', ({ StudyInstanceUID }) => { - const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); - HangingProtocolService.run(studyMetadata); - }); - - return unsubscriptions; -} -``` - -#### Writing a custom init - -You can add your custom init function to enhance the default initialization for: - -- Fetching annotations from a server for the current study -- Changing the initial image index of the series to be displayed at first -- Caching the next study in the work list -- Adding a custom sort for the series to be displayed on the study browser panel - -and lots of other modifications. - -You just need to make sure, the mode `dataSource.retrieve.series.metadata`, -`makeDisplaySets` and `run` the HangingProtocols at some point. There are -various `events` that you can subscribe to and add your custom logic. **point to -events** - -For instance for jumping to the slice where a measurement is located at the -initial render, you need to follow a pattern similar to the following: - -```jsx -init: async ({ - servicesManager, - extensionManager, - hotkeysManager, - dataSource, - studyInstanceUIDs, -}) => { - const { DisplaySetService } = servicesManager.services; - - /** - ... - **/ - - const onDisplaySetsAdded = ({ displaySetsAdded, options }) => { - const displaySet = displaySetsAdded[0]; - const { SeriesInstanceUID } = displaySet; - - const toolData = myServer.fetchMeasurements(SeriesInstanceUID); - - if (!toolData.length) { - return; - } - - toolData.forEach(tool => { - const instance = displaySet.images.find( - image => image.SOPInstanceUID === tool.SOPInstanceUID - ); - }); - - MeasurementService.addMeasurement(/**...**/); - }; - - // subscription to the DISPLAY_SETS_ADDED - const { unsubscribe } = DisplaySetService.subscribe( - DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, - onDisplaySetsAdded - ); - - /** - ... - **/ - - return unsubscriptions; -}; -``` - -### Route: layoutTemplate - -`layoutTemplate` is the last configuration for a certain route in a `mode`. -`layoutTemplate` is a function that returns an object that configures the -overall layout of the application. The returned object has two properties: - -- `id`: the id of the `layoutTemplate` being used (it should have been - registered via an extension) -- `props`: the required properties to be passed to the `layoutTemplate`. - -For instance `default extension` provides a layoutTemplate that builds the app -using left/right panels and viewports. Therefore, the `props` include -`leftPanels`, `rightPanels` and `viewports` sections. Note that the -`layoutTemplate` defines the properties it is expecting. So, if you write a -`layoutTemplate-2` that accepts a footer section, its logic should be written in -the extension, and any mode that is interested in using `layoutTemplate-2` -**should** provide the `id` for the footer component. - -**What module should the footer be registered?** - -```js -/* -... -*/ -layoutTemplate: ({ location, servicesManager }) => { - return { - id: '@ohif/extension-default.layoutTemplateModule.viewerLayout', - props: { - leftPanels: [ - 'myExtension.panelModule.leftPanel1', - 'myExtension.panelModule.leftPanel2', - ], - rightPanels: ['myExtension.panelModule.rightPanel'], - viewports: [ - { - namespace: 'myExtension.viewportModule.viewport1', - displaySetsToDisplay: ['myExtension.sopClassHandlerModule.sop1'], - }, - { - namespace: 'myExtension.viewportModule.viewport2', - displaySetsToDisplay: ['myExtension.sopClassHandlerModule.sop2'], - }, - ], - }, - }; -}; -/* -... -*/ -``` - -## FAQ - -> What is the difference between `onModeEnter` and `route.init` - -`onModeEnter` gets run first than `route.init`; however, each route can have -their own `init`, but they share the `onModeEnter`. - -> How can I change the `workList` appearance or add a new login page? - -This is where `OHIF-v3` shines! Since the default `layoutTemplate` is written -for the viewer part, you can simply add a new `layoutTemplate` and use the -component you have written for that route. `Mode` handle showing the correct -component for the specified route. - -```js -function modeFactory() { - return { - id: 'viewer', - displayName: '', - routes: [ - { - path: 'worklist', - init, - layoutTemplate: ({ location, servicesManager }) => { - return { - id: 'worklistLayout', - props: { - component: 'myNewWorkList', - }, - }; - }, - }, - ], - /* - ... - */ - }; -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/validity.md b/platform/docs/versioned_docs/version-3.0/platform/modes/validity.md deleted file mode 100644 index e3f70af766..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/modes/validity.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Validity ---- -# Mode: Validity - - -## Overview -There are two mechanism for checking the validity of a mode for a study. - -- `isValidMode`: which is called on a selected study in the workList. -- `validTags` - - - -## isValidMode -This hook can be used to define a function that return a `boolean` which decided the -validity of the mode based on `StudyInstanceUID` and `modalities` that are in the study. - -For instance, for pet-ct mode, both `PT` and 'CT' modalities should be available inside the study. - -```js -function modeFactory() { - return { - id: '', - displayName: '', - isValidMode: ({ modalities, StudyInstanceUID }) => { - const modalities_list = modalities.split('\\'); - const validMode = ['CT', 'PT'].every(modality => modalities_list.includes(modality)); - return validMode; - }, - /* - ... - */ - } -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/pwa-vs-packaged.md b/platform/docs/versioned_docs/version-3.0/platform/pwa-vs-packaged.md deleted file mode 100644 index bdc411e6cb..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/pwa-vs-packaged.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -sidebar_position: 3 ---- - -# PWA vs Packaged - -It's important to know that the OHIF Viewer project provides two different build -processes: - -```bash -# Static Asset output: For deploying PWAs -yarn run build -``` - -## Progressive Web Application (PWA) - -> [Progressive Web Apps][pwa] are a new breed of web applications that meet the -> [following requirements][pwa-checklist]. Notably, targeting a PWA allows us -> provide a reliable, fast, and engaging experience across different devices and -> network conditions. - -The OHIF Viewer is maintained as a [monorepo][monorepo]. We use WebPack to build -the many small static assets that comprise our application. Also generated is an -`index.html` that will serve as an entry point for loading configuration and the -application, as well as a `service-worker` that can intelligently cache files so -that subsequent requests are from the local file system instead of over the -network. - -You can read more about this particular strategy in our -[Build for Production Deployment Guide](./../deployment/build-for-production.md) - -## Commonjs Bundle (Packaged Script) - -We are not supporting `Commonjs` bundling inside `OHIF-v3`. diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/services/_category_.json deleted file mode 100644 index 8b57449b24..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Services", - "position": 12 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/DicomMetadataStore.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/DicomMetadataStore.md deleted file mode 100644 index 927b8b8e54..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/DicomMetadataStore.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: DICOM Metadata Store ---- -# DICOM Metadata Store - - -## Overview -`DicomMetaDataStore` is the central location that stores the metadata in `OHIF-v3`. There -are several APIs to add study/series/instance metadata and also for getting from the store. -DataSource utilize the `DicomMetaDataStore` to add the retrieved metadata to `OHIF Viewer`. - -> In `OHIF-v3` we have significantly changed the architecture of the metadata storage to -> provide a much cleaner way of handling metadata-related tasks and services. Classes such as -> `OHIFInstanceMetadata`, `OHIFSeriesMetadata` and `OHIFStudyMetadata` has been removed and -> replaced with `DicomMetaDataStore`. -> - - -## Events -There are two events that get publish in `DicomMetaDataStore`: - - -| Event | Description | -|-----------------|------------------------------------------------------------------------------------------------| -| SERIES_ADDED | Fires when all series of one study have added their summary metadata to the `DicomMetaDataStore` | -| INSTANCES_ADDED | Fires when all instances of one series have added their metadata to the `DicomMetaDataStore` | - - -## API -Let's find out about the public API for `DicomMetaDataStore` service. - -- `EVENTS`: Object including the events mentioned above. You can subscribe to these events - by calling DicomMetaDataStore.subscribe(EVENTS.SERIES_ADDED, myFunction). [Read more about pub/sub pattern here](../pubsub.md) - -- `addInstances(instances, madeInClient = false)`: adds the instances' metadata to the store. madeInClient is explained in detail below. After - adding to the store it fires the EVENTS.INSTANCES_ADDED. - -- `addSeriesMetadata(seriesSummaryMetadata, madeInClient = false)`: adds series summary metadata. After adding it fires EVENTS.SERIES_ADDED - -- `addStudy(study)`: creates and add study-level metadata to the store. - -- `getStudy(StudyInstanceUID)`: returns the study metadata from the store. It includes all the series and instance metadata in the requested study - -- `getSeries(StudyInstanceUID, SeriesInstanceUID`: returns the series-level metadata for the requested study and series UIDs. - -- `getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID)`: returns the instance metadata based on the study, series and sop instanceUIDs. - -- `geteInstanceFromImageId`: returns the instance metadata based on the requested imageId. It searches the store for the instance that has the same imageId. - - - -### madeInClient - -Since upon adding the metadata to the store, the relevant events are fired, and there are -other services that are subscribed to these events (`HangingProtocolService` or `DisplaySetService`), sometimes -we want to add instance metadata but don't want the events to get fired. For instance, when -you are caching the data for the next study in advance, you probably don't want to trigger hanging protocol -logic, so you set `madeInClient=true` to not fire events. - - -## Storage -As discussed before, there are several API that enables getting metadata from the store and adding to the store. -However, it is good to have an understanding of where they get -stored and in what format and hierarchy. `_model` is a private variable in the store -which holds all the metadata for all studies, series, and instances, and it looks like: - - -```js title="platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js" -const _model = { - studies: [ - { - /** Study Metadata **/ - seriesLists: [ - { - // Series in study from dicom web server 1 (or different backend 1) - series: [ - { - // Series 1 Metadata - instances: [ - { - // Instance 1 metadata of Series 1 - }, - { - // Instance 2 metadata of Series 1 - }, - /** Other instances metadata **/ - ], - }, - { - // Series 2 Metadata - instances: [ - { - // Instance 1 metadata of Series 2 - }, - { - // Instance 2 metadata of Series 1 - }, - /** Other instances metadata **/ - ], - }, - ], - }, - { - // Series in study from dicom web server 2 (or different backend 2) - /** ... **/ - }, - ], - }, - ], -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/DisplaySetService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/DisplaySetService.md deleted file mode 100644 index defc022c61..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/DisplaySetService.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: DisplaySet Service ---- -# DisplaySet Service - - -## Overview -`DisplaySetService` handles converting the `instanceMetadata` into `DisplaySet` that `OHIF` uses for the visualization. `DisplaySetService` gets initialized in the `Mode.jsx`. During the initialization `SOPClassHandlerIds` of the `modes` gets registered with the `DisplaySetService`. - -> Based on the instanceMetadata's `SOPClassHandlerId`, the correct module from the registered extensions is found by `OHIF` and its `getDisplaySetsFromSeries` runs to create a DisplaySet for the Series. - - -```js title="platform/core/src/services/DisplaySetService/DisplaySetService.js" -init(extensionManager, SOPClassHandlerIds) { - this.extensionManager = extensionManager; - this.SOPClassHandlerIds = SOPClassHandlerIds; - this.activeDisplaySets = []; -} -``` - -in `Mode.jsx` - -```js title="platform/viewer/src/routes/Mode/Mode.jsx" -export default function ModeRoute(/** ... **/) { - /** ... **/ - const { DisplaySetService } = servicesManager.services - const { sopClassHandlers } = mode - /** ... **/ - useEffect( - () => { - /** ... **/ - - // Add SOPClassHandlers to a new SOPClassManager. - DisplaySetService.init(extensionManager, sopClassHandlers) - - /** ... **/ - } - /** ... **/ - ) - /** ... **/ - return <> /** ... **/ -} -``` - - - - -## Events -There are three events that get broadcasted in `DisplaySetService`: - - - -| Event | Description | -| -------------------- | ---------------------------------------------------- | -| DISPLAY_SETS_ADDED | Fires a displayset is added to the displaysets cache | -| DISPLAY_SETS_CHANGED | Fires when a displayset is changed | -| DISPLAY_SETS_REMOVED | Fires when a displayset is removed | - - - -## API -Let's find out about the public API for `DisplaySetService`. - -- `EVENTS`: Object including the events mentioned above. You can subscribe to these events - by calling DisplaySetService.subscribe(EVENTS.DISPLAY_SETS_CHANGED, myFunction). [Read more about pub/sub pattern here](../pubsub.md) - -- `makeDisplaySets(input, { batch, madeInClient, settings } = {}`): Creates displaySet for the provided - array of instances metadata. Each display set gets a random UID assigned. - - - `input`: Array of instances Metadata - - `batch = false`: If you need to pass array of arrays of instances metadata to have a batch creation - - `madeInClient = false`: Disables the events firing - - `settings = {}`: Hanging protocol viewport or rendering settings. For instance, setting the initial `voi`, or activating a tool upon - displaySet rendering. [Read more about hanging protocols settings here](./HangingProtocolService.md#Settings) - - -- `getDisplaySetByUID`: Returns the displaySet based on the DisplaySetUID. - -- `getDisplaySetForSOPInstanceUID`: Returns the displaySet that includes an image with the provided SOPInstanceUID - -- `getActiveDisplaySets`: Returns the active displaySets - -- `deleteDisplaySet`: Deletes the displaySets from the displaySets cache - -- `holdChangeEvents`: Prevents firing change events (currently only works on add event). - -- `fireHoldChangeEvents`: Causes the change event to be fired IF there were any changes. No longer holds events. diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/HangingProtocolService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/HangingProtocolService.md deleted file mode 100644 index 006a557121..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/HangingProtocolService.md +++ /dev/null @@ -1,348 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Hanging Protocol Service ---- - -# Hanging Protocol Service - -## Overview - -`HangingProtocolService` is a migration of the `OHIF-v1` hanging protocol -engine. This service handles the arrangement of the images in the viewport. In -short, the registered protocols will get matched with the DisplaySets that are -available for the study. Each protocol gets a score, and they are ranked. The -winning protocol gets applied and its settings run for the viewports. - -You can read more about hanging protocols -[here](http://dicom.nema.org/dicom/Conf-2005/Day-2_Selected_Papers/B305_Morgan_HangProto_v1.pdf). -In short with `OHIF-v3` hanging protocols you can: - -- Define what layout of the viewport should the viewer starts with (eg 2x2 layout) -- Define which series gets displayed in which position of the layout -- Apply certain initial viewport settings; e.g., inverting the contrast -- Enable certain tools based on what series are displayed: link prostate T2 and - ADC MRI. -- Apply synchronization settings between different viewports or between setting and viewports -- Register custom synchronization settings for viewports -- Register custom attribute extractors -- Select "next display set" from the matching display sets, both on navigation and initial view - -## Skeleton of A Hanging Protocol - -You can find the skeleton of the hanging protocols here: - -```js -const defaultProtocol = { - id: 'test', - locked: true, - hasUpdatedPriorsInformation: false, - name: 'Default', - createdDate: '2021-02-23T19:22:08.894Z', - modifiedDate: '2021-02-23T19:22:08.894Z', - availableTo: {}, - editableBy: {}, - toolGroupIds: [ - 'ctToolGroup', - 'ptToolGroup', - ], - imageLoadStrategy: 'interleaveTopToBottom', // "default" , "interleaveTopToBottom", "interleaveCenter" - protocolMatchingRules: [ - { - id: 'wauZK2QNEfDPwcAQo', - weight: 1, - attribute: 'StudyDescription', - constraint: { - contains: { - value: 'PETCT', - }, - }, - required: false, - }, - ], - stages: [ - { - id: 'hYbmMy3b7pz7GLiaT', - name: 'default', - viewportStructure: { - layoutType: 'grid', - properties: { - rows: 1, - columns: 1, - }, - }, - displaySets: [ - { - id: 'displaySet', - seriesMatchingRules: [ - { - id: 'GPEYqFLv2dwzCM322', - weight: 1, - attribute: 'Modality', - constraint: { - equals: 'CT', - }, - required: true, - }, - { - id: 'vSjk7NCYjtdS3XZAw', - weight: 1, - attribute: 'numImageFrames', - constraint: { - greaterThan: 10, - }, - }, - ], - studyMatchingRules: [], - }, - ], - viewports: [ - { - viewportOptions: { - viewportId: 'ctAXIAL', - viewportType: 'volume', - orientation: 'axial', - toolGroupId: 'ctToolGroup', - initialImageOptions: { - // index: 5, - preset: 'first', // 'first', 'last', 'middle' - }, - syncGroups: [ - { - type: 'cameraPosition', - id: 'axialSync', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'displaySet', - }, - ], - }, - ], - }, - ], - numberOfPriorsReferenced: -1, -} -``` - -Let's discuss each property in depth. - -- `id`: unique identifier for the protocol -- `name`: Name displayed to the user to select this protocol - -- `protocolMatchingRules`: A list of criteria for the protocol along with the - provided points for ranking. - - - `weight`: weight for the matching rule. Eventually, all the registered - protocols get sorted based on the weights, and the winning protocol gets - applied to the viewer. - - `attribute`: tag that needs to be matched against. This can be either - Study-level metadata or a custom attribute. - [Learn more about custom attribute matching](#custom-attribute) - - - `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be - [`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`] - - A sample of the matching rule is: - - ```js - { - weight: 1, - attribute: 'StudyInstanceUID', - constraint: { - equals: '1.3.6.1.4.1.25403.345050719074.3824.20170125112931.11', - }, - required: true, - } - ``` - - -- `stages`: Each protocol can define one or more stages. Each stage defines a certain layout and viewport rules. - Therefore, the `stages` property is array of objects, each object being one stage. - - - `displaySets`: Defines the matching rules for which display sets to use. - - `viewportStructure`: Defines the layout of the viewer. You can define the - number of `rows` and `columns`. - - `viewports` defines the actual viewports to display. There should be `rows * columns` number of - these `viewports` property, ordered rows first, then columns. - - - ```js - stages: [ - { - id: 'hYbmMy3b7pz7GLiaT', - name: 'oneByTwo', - viewportStructure: { - type: 'grid', - properties: { - rows: 1, - columns: 3, - }, - }, - viewports: [ - // viewport 1 - { - viewportOptions: { - viewportId: 'ctAXIAL', - viewportType: 'volume', - orientation: 'axial', - toolGroupId: 'ctToolGroup', - initialImageOptions: { - // index: 5, - preset: 'first', // 'first', 'last', 'middle' - }, - syncGroups: [ - { - type: 'cameraPosition', - id: 'axialSync', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'displaySet', - }, - ], - }, - ]; - ``` - -## Events - -There are two events that get publish in `HangingProtocolService`: - -| Event | Description | -| ------------ | -------------------------------------------------------------------- | -| NEW_LAYOUT | Fires when a new layout is requested by the `HangingProtocolService` | -| STAGE_CHANGE | Fires when the the stage is changed in the hanging protocols | - -## API - -- `getState`: returns an array: `[matchDetails, hpAlreadyApplied]`: - - - `matchDetails`: matching details for the series - - `hpAlreadyApplied`: An array which tracks whether HPServices has been - applied on each viewport. - -- `addProtocols`: adds provided protocols to the list of registered protocols - for matching - -- `run({ studies, displaySets }, protocol)`: runs the HPService with the provided - list of studies, display sets and optional protocol. - If protocol is not given, HP Matching - engine will search all the registered protocols for the best matching one - based on the constraints. - -- `addCustomAttribute`: adding a custom attribute for matching. (see below) - -Default initialization of the modes handles running the `HangingProtocolService` - -## Custom Attribute -In some situations, you might want to match based on a custom attribute and not the DICOM tags. For instance, -if you have assigned a `timepointId` to each study, and you want to match based on it. -Good news is that, in `OHIF-v3` you can define you custom attribute and use it for matching. - -In some situations, you might want to match based on a custom attribute and not -the DICOM tags. For instance, if you have assigned a `timepointId` to each study -and you want to match based on it. Good news is that, in `OHIF-v3` you can -define you custom attribute and use it for matching. - -There are various ways that you can let `HangingProtocolService` know of you -custom attribute. We will show how to add it inside the an extension. This extension -also shows how to register a sync group service which can be referenced -in the sync group settings. - -```js -const myCustomProtocol = { - id: 'myCustomProtocol', - /** ... **/ - protocolMatchingRules: [ - { - id: 'vSjk7NCYjtdS3XZAw', - attribute: 'timepointId', - constraint: { - equals: 'first', - }, - required: false, - }, - ], -... - -// Custom function for custom attribute -const getTimePointUID = metaData => { - // requesting the timePoint Id - return myBackEndAPI(metaData); -}; - - preRegistration: ({ - servicesManager, - }) => { - const { HangingProtocolService, SyncGroupService } = servicesManager.services; - HangingProtocolService.addCustomAttribute('timepointId', 'TimePoint ID', getTimePointUID); - SyncGroupService.setSynchronizer('initialzoompan', initialZoomPan); - } -``` - -## Viewport Settings - -You can define custom settings to be applied to each viewport. There are two -types of settings: - -- `viewport settings`: Currently we support two viewport settings - - - `voi`: applying an initial `voi` by setting the windowWidth and windowCenter - - `inverted`: inverting the viewport color (e.g., for PET images) - -- `props settings`: Running commands after the first render; e.g., enabling the - link tool - -Examples of each settings are : - -```js -viewportSettings: [ - { - options: { - itemId: 'SyncScroll', - interactionType: 'toggle', - commandName: 'toggleSynchronizer', - commandOptions: { toggledState: true }, - }, - commandName: 'setToolActive', - type: 'props', - }, -]; -``` - -and - -```js -viewportSettings: [ - { - options: { - voi: { - windowWidth: 400, - windowCenter: 40, - }, - }, - commandName: '', - type: 'viewport', - }, -]; -``` - -## Sync Groups -The sync groups are listeners to events that synchronize viewport settings to -some other settings. There are three default/provided sync groups: `zoomPan`, -`cameraPosition` and `voi`. These are defined in the `syncGroups` array. -Additionally, other synchronization types can be created and registered on the -`SyncGroupService.setSynchronizer`, by registering a new id, and a creator method. - -The sync group service is specific to the `cornerstone-extension` because the -actual behaviour of the synchronizers is dependent on the specific viewport. -Different viewport types could redifine the same synchronizer names in -different ways appropriate to that viewport. diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/MeasurementService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/MeasurementService.md deleted file mode 100644 index 74605bce1c..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/MeasurementService.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -sidebar_position: 6 -sidebar_label: Measurement Service ---- - -# Measurement Service - -## Overview - -`MeasurementService` handles the internal measurement representation inside -`OHIF` platform. Developers can add their custom `sources` with `mappers` to -enable adding measurements inside OHIF. Currently, we are maintaining -`CornerstoneTools` annotations and corresponding mappers can be found inside the -`cornerstone` extension. However, `MeasurementService` can be configured to work -with any custom tools given that its `mappers` is added to the -`MeasurementService`. We can see the overall architecture of the -`MeasurementService` below: - -![services-measurements](../../../assets/img/services-measurements.png) - -## Events - -There are seven events that get publish in `MeasurementService`: - -| Event | Description | -| --------------------- | ------------------------------------------------------ | -| MEASUREMENT_UPDATED | Fires when a measurement is updated | -| MEASUREMENT_ADDED | Fires when a new measurement is added | -| RAW_MEASUREMENT_ADDED | Fires when a raw measurement is added (e.g., dicom-sr) | -| MEASUREMENT_REMOVED | Fires when a measurement is removed | -| MEASUREMENTS_CLEARED | Fires when all measurements are deleted | -| JUMP_TO_MEASUREMENT | Fires when a measurement is requested to be jump to | - -## API - -- `getMeasurements`: returns array of measurements - -- `getMeasurement(id)`: returns the corresponding measurement based on the - provided Id. - -- `remove(id, source)`: removes a measurement and broadcasts the - `MEASUREMENT_REMOVED` event. - -- `clearMeasurements`: removes all measurements and broadcasts - `MEASUREMENTS_CLEARED` event. - -- `createSource(name, version)`: creates a new measurement source, generates a - uid and adds it to the `sources` property of the service. - -- `addMapping(source, definition, matchingCriteria, toSourceSchema, toMeasurementSchema)`: - adds a new measurement matching criteria along with mapping functions. We will - learn more about [source/mappers below](#source--mappers) - -- `update`: updates the measurement details and fires `MEASUREMENT_UPDATED` - -- `addRawMeasurement(source,definition,data,toMeasurementSchema,dataSource = {}` - : adds a raw measurement into a source so that it may be converted to/from - annotation in the same way. E.g. import serialized data of the same form as - the measurement source. Fires `MEASUREMENT_UPDATED` or `MEASUREMENT_ADDED`. - Note that, `MeasurementService` handles finding the correct mapper upon new - measurements; however, `addRawMeasurement` provides more flexibility. You can - take a look into its usage in `dicom-sr` extension. - - - `source`: The measurement source instance. - - `definition`: The source definition you want to add the measurement to. - - `data`: The data you wish to add to the source. - - `toMeasurementSchema`: A function to get the `data` into the same shape as - the source definition. - -- `jumpToMeasurement(viewportIndex, id)`: calls the listeners who have - subscribed to `JUMP_TO_MEASUREMENT`. - -## Source / Mappers - -To create a custom measurement source and relevant mappers for each tool, you -can take a look at the `init.js` inside the `cornerstone` extension. In which we -are registering our `CornerstoneTools-v4` measurement source to -MeasurementService. Let's take a peek at the _simplified_ implementation -together. To achieve this, for each tool, we need to provide three mappers: - -- `matchingCriteria`: criteria used for finding the correct mapper for the drawn - tool. -- `toAnnotation`: tbd -- `toMeasurement`: a function that converts the tool data to OHIF internal - representation of measurement data. - -```js title="extensions/cornerstone/src/utils/measurementServiceMappings/Length.js" -function toMeasurement( - csToolsAnnotation, - DisplaySetService, - getValueTypeFromToolType -) { - const { element, measurementData } = csToolsAnnotation; - - /** ... **/ - - const { - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes(element); - - const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - SOPInstanceUID, - SeriesInstanceUID - ); - - /** ... **/ - return { - id: measurementData.id, - SOPInstanceUID, - FrameOfReferenceUID, - referenceSeriesUID: SeriesInstanceUID, - referenceStudyUID: StudyInstanceUID, - displaySetInstanceUID: displaySet.displaySetInstanceUID, - label: measurementData.label, - description: measurementData.description, - unit: measurementData.unit, - length: measurementData.length, - type: getValueTypeFromToolType(tool), - points: getPointsFromHandles(measurementData.handles), - }; -} - -////////////////////////////////////////// - -// extensions/cornerstone/src/init.js - -const Length = { - toAnnotation, - toMeasurement, - matchingCriteria: [ - { - valueType: MeasurementService.VALUE_TYPES.POLYLINE, - points: 2, - }, - ], -}; - -const _initMeasurementService = (MeasurementService, DisplaySetService) => { - /** ... **/ - - const csToolsVer4MeasurementSource = MeasurementService.createSource( - 'CornerstoneTools', - '4' - ); - - /* Mappings */ - MeasurementService.addMapping( - csToolsVer4MeasurementSource, - 'Length', - Length.matchingCriteria, - toAnnotation, - toMeasurement - ); - - /** Other tools **/ - return csToolsVer4MeasurementSource; -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md deleted file mode 100644 index 92ab50048b..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md +++ /dev/null @@ -1,226 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: Toolbar Service ---- - -# Toolbar Service - -## Overview - -`ToolBarService` handles the toolbar section buttons, and what happens when a -button is clicked by the user. - -
- -
- -## Events - -| Event | Description | -| ----------------------- | ---------------------------------------------------------------------- | -| TOOL_BAR_MODIFIED | Fires when a button is added/removed to the toolbar | -| TOOL_BAR_STATE_MODIFIED | Fires when an interaction happens and ToolBarService state is modified | - -## API - -- `recordInteraction(interaction)`: executes the provided interaction which is - an object providing the following properties to the ToolBarService: - - - `interactionType`: can be `tool`, `toggle` and `action`. We will discuss - more each type below. - - `itemId`: tool name - - `groupId`: the Id for the tool button group; e.g., `Wwwc` which holds - presets. - - `commandName`: if tool has a command attached to run - - `commandOptions`: arguments for the command. - -- `reset`: reset the state of the toolbarService, set the primary tool to be - `Wwwc` and unsubscribe tools that have registered their functions. - -- `addButtons`: add the button definition to the service. - [See below for button definition](#button-definitions). - -- `setButtons`: sets the buttons defined in the service. It overrides all the - previous buttons - -- `getActiveTools`: returns the active tool + all the toggled-on tools - -## State - -ToolBarService has an internal state that gets updated per tool interaction and -tracks the active toolId, state of the buttons that have toggled state, and the -group buttons and which tool in each group is active. - -```js -state = { - primaryToolId: 'Wwwc', - toggles: { - /* id: true/false */ - }, - groups: { - /* track most recent click per group...*/ - }, -}; -``` - -## Interaction type - -There are three main types that a tool can have which is defined in the -interaction object. - -- `tool`: setting a tool to be active; e.g., measurement tools -- `toggle`: toggling state of a tool; e.g., viewport link (sync) -- `action`: performs a registered action outside of the ToolBarService; e.g., - capture - -A _simplified_ implementation of the ToolBarService is: - -```js -export default class ToolBarService { - /** ... **/ - recordInteraction(interaction) { - /** ... **/ - switch (interactionType) { - case 'action': { - break; - } - case 'tool': { - this.state.primaryToolId = itemId; - - commandsManager.runCommand('setToolActive', interaction.commandOptions); - break; - } - case 'toggle': { - this.state.toggles[itemId] = - this.state.toggles[itemId] === undefined - ? true - : !this.state.toggles[itemId]; - interaction.commandOptions.toggledState = this.state.toggles[itemId]; - break; - } - default: - throw new Error(`Invalid interaction type: ${interactionType}`); - } - /** ... **/ - } - /** ... **/ -} -``` - -## Button Definitions - -The simplest toolbarButtons definition has the following properties: - -![toolbarModule-zoom](../../../assets/img/toolbarModule-zoom.png) - -```js -{ - id: 'Zoom', - type: 'ohif.radioGroup', - props: { - type: 'tool', - icon: 'tool-zoom', - label: 'Zoom', - commandOptions: { toolName: 'Zoom' }, - }, -}, -``` - -| property | description | values | -| ---------------- | ----------------------------------------------------------------- | ------------------------------------------- | -| `id` | Unique string identifier for the definition | \* | -| `label` | User/display friendly to show in UI | \* | -| `icon` | A string name for an icon supported by the consuming application. | \* | -| `type` | Used to determine the button's behaviour | "tool", "toggle", "action" | -| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` | -| `commandOptions` | (optional) Options to pass the target `commandName` | \* | - -There are three main types of toolbar buttons: - -- `tool`: buttons that enable a tool by running the `setToolActive` command with - the `commandOptions` -- `toggle`: buttons that acts as a toggle: e.g., linking viewports -- `action`: buttons that executes an action: e.g., capture button to save - screenshot - -## Nested Buttons - -You can use the `ohif.splitButton` type to build a button with extra tools in -the dropdown. - -- First you need to give your `primary` tool definition to the split button -- the `secondary` properties can be a simple arrow down (`chevron-down` icon) -- For adding the extra tools add them to the `items` list. - -You can see below how `longitudinal` mode is using the available toolbarModule -to create `MeasurementTools` nested button - -![toolbarModule-nested-buttons](../../../assets/img/toolbarModule-nested-buttons.png) - -```js title="modes/longitudinal/src/toolbarButtons.js" -{ - id: 'MeasurementTools', - type: 'ohif.splitButton', - props: { - groupId: 'MeasurementTools', - isRadio: true, - primary: { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } - }, - secondary: { - icon: 'chevron-down', - label: '', - isActive: true, - tooltip: 'More Measure Tools', - }, - items: [ - // Length tool - { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } - }, - // Bidirectional tool - { - id: 'Bidirectional', - icon: 'tool-bidirectional', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Bidirectional', - } - }, - // Ellipse tool - { - id: 'EllipticalRoi', - icon: 'tool-elipse', - label: 'Ellipse', - type: 'tool', - commandOptions: { - toolName: 'EllipticalRoi', - } - }, - // Circle tool - { - id: 'CircleROI', - icon: 'tool-circle', - label: 'Circle', - type: 'tool', - commandOptions: { - toolName: 'CircleROI', - } - }, - ], - }, -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/services/data/_category_.json deleted file mode 100644 index 984ac9a420..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Data Services", - "position": 2 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/index.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/index.md deleted file mode 100644 index fa9128ae4d..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/data/index.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Overview ---- - -# Overview - -Data services are the first category of services which deal with handling non-ui -related state Each service have their own internal state which they handle. - -> We have replaced the _redux_ store. Instead, we have introduced various -> services and a pub/sub pattern to subscribe and run, which makes the `OHIF-v3` -> architecture nice and clean. - -We maintain the following non-ui Services: - -- [DicomMetadata Store](./../data/DicomMetadataStore.md) -- [DisplaySet Service](./../data/DisplaySetService.md) -- [Hanging Protocol Service](../data/HangingProtocolService.md) -- [Toolbar Service](../data/ToolBarService.md) -- [Measurement Service](../data/MeasurementService.md) - -## Service Architecture - -![services-data](../../../assets/img/services-data.png) - -> We have explained services and how to create a custom service in the -> [`ServiceManager`](../../managers/service.md) section of the docs - -To recap: The simplest service return a new object that has a `name` property, -and `Create` method which instantiate the service class. The "Factory Function" -that creates the service is provided with the implementation (this is slightly -different for UI Services). - -```js -// extensions/customExtension/src/services/backEndService/index.js -import backEndService from './backEndService'; - -export default function WrappedBackEndService(serviceManager) { - return { - name: 'myService', - create: ({ configuration = {} }) => { - return new backEndService(serviceManager); - }, - }; -} -``` - -A service, once created, can be registered with the `ServicesManager` to make it -accessible to extensions. Similarly, the application code can access named -services from the `ServicesManager`. - -[Read more of how to design a new custom service and register it](../../managers/service.md) diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/index.md b/platform/docs/versioned_docs/version-3.0/platform/services/index.md deleted file mode 100644 index 87d88e7ad2..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/index.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Introduction ---- - -# Services - -## Overview - -Services are "concern-specific" code modules that can be consumed across layers. -Services provide a set of operations, often tied to some shared state, and are -made available to through out the app via the `ServicesManager`. Services are -particularly well suited to address [cross-cutting -concerns][cross-cutting-concerns]. - -Each service should be: - -- self-contained -- able to fail and/or be removed without breaking the application -- completely interchangeable with another module implementing the same interface - -> In `OHIF-v3` we have added multiple non-UI services and have introduced -> **pub/sub** pattern to reduce coupling between layers. -> -> [Read more about Pub/Sub](./pubsub.md) - -## Services - -The following services is available in the `OHIF-v3`. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ServiceTypeDescription
- - DicomMetadataStore (NEW) - - Data Service - DicomMetadataStore -
- - DisplaySetService (NEW) - - Data Service - DisplaySetService -
- - HangingProtocolService (NEW) - - Data Service - HangingProtocolService -
- - MeasurementService (MODIFIED) - - Data Service - MeasurementService -
- - ToolBarService (NEW) - - Data Service - ToolBarService -
- - ViewportGridService (NEW) - - UI Service - ViewportGridService -
- - Cine Service (NEW) - - UI Service - cine -
- - UIDialogService - - UI Service - UIDialogService -
- - UIModalService - - UI Service - UIModalService -
- - UINotificationService - - UI Service - UINotificationService -
- - UIViewportDialogService (NEW) - - UI Service - UIViewportDialogService -
- - - - - -[core-services]: https://github.com/OHIF/Viewers/tree/master/platform/core/src/services -[services-manager]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/ServicesManager.js -[cross-cutting-concerns]: https://en.wikipedia.org/wiki/Cross-cutting_concern - diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/pubsub.md b/platform/docs/versioned_docs/version-3.0/platform/services/pubsub.md deleted file mode 100644 index fae6978e6a..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/pubsub.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: Pub Sub ---- - -# Pub sub - -## Overview - -Publish–subscribe pattern is a messaging pattern that is one of the fundamentals -patterns used in reusable software components. - -In short, services that implement this pattern, can have listeners subscribed -to their broadcasted events. After the event is fired, the corresponding -listener will execute the function that is registered. - -You can read more about this design pattern -[here](https://cloud.google.com/pubsub/docs/overview). - -## Example: Default Initialization - -In `Mode.jsx` we have a default initialization that demonstrates a series of -subscriptions to various events. - -```js -async function defaultRouteInit({ - servicesManager, - studyInstanceUIDs, - dataSource, -}) { - const { - DisplaySetService, - HangingProtocolService, - } = servicesManager.services; - - const unsubscriptions = []; - - const { - unsubscribe: instanceAddedUnsubscribe, - } = DicomMetadataStore.subscribe( - DicomMetadataStore.EVENTS.INSTANCES_ADDED, - ({ StudyInstanceUID, SeriesInstanceUID, madeInClient = false }) => { - const seriesMetadata = DicomMetadataStore.getSeries( - StudyInstanceUID, - SeriesInstanceUID - ); - - DisplaySetService.makeDisplaySets(seriesMetadata.instances, madeInClient); - } - ); - - unsubscriptions.push(instanceAddedUnsubscribe); - - studyInstanceUIDs.forEach(StudyInstanceUID => { - dataSource.retrieve.series.metadata({ StudyInstanceUID }); - }); - - const { unsubscribe: seriesAddedUnsubscribe } = DicomMetadataStore.subscribe( - DicomMetadataStore.EVENTS.SERIES_ADDED, - ({ StudyInstanceUID }) => { - const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); - HangingProtocolService.run(studyMetadata, DisplaySetService.getActiveDisplaySets()); - } - ); - unsubscriptions.push(seriesAddedUnsubscribe); - - return unsubscriptions; -} -``` - -## Unsubscription - -You need to be careful if you are adding custom subscriptions to the app. Each -subscription will return an unsubscription function that needs to be executed on -component destruction to avoid adding multiple subscriptions to the same -observer. - -Below, we can see `simplified` `Mode.jsx` and the corresponding `useEffect` -where the unsubscription functions are executed upon destruction. - -```js title="platform/viewer/src/routes/Mode/Mode.jsx" -export default function ModeRoute(/**..**/) { - /**...**/ - useEffect(() => { - /**...**/ - - DisplaySetService.init(extensionManager, sopClassHandlers); - - extensionManager.onModeEnter(); - mode?.onModeEnter({ servicesManager, extensionManager }); - - hangingProtocols.forEach(extentionProtocols => { - const { protocols } = extensionManager.getModuleEntry(extentionProtocols); - HangingProtocolService.addProtocols(protocols); - }); - - const setupRouteInit = async () => { - if (route.init) { - return await route.init(/**...**/); - } - - return await defaultRouteInit(/**...**/); - }; - - let unsubscriptions; - setupRouteInit().then(unsubs => { - unsubscriptions = unsubs; - }); - - return () => { - extensionManager.onModeExit(); - mode?.onModeExit({ servicesManager, extensionManager }); - unsubscriptions.forEach(unsub => { - unsub(); - }); - }; - }); - return <> /**...**/ ; -} -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/services/ui/_category_.json deleted file mode 100644 index 9c012134eb..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "UI Services", - "position": 3 -} diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/cine-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/cine-service.md deleted file mode 100644 index 707b5d97f7..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/cine-service.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -sidebar_position: 7 -sidebar_label: CINE Service ---- - -# CINE Service - -TODO... diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/index.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/index.md deleted file mode 100644 index 5ba4a53905..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/index.md +++ /dev/null @@ -1,304 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Overview ---- - -# Overview - - - -A typical web application will have components and state for common UI like -modals, notifications, dialogs, etc. A UI service makes it possible to leverage -these components from an extension. - -We maintain the following UI Services: - -- [UI Notification Service](ui-notification-service.md) -- [UI Modal Service](ui-modal-service.md) -- [UI Dialog Service](ui-dialog-service.md) -- [UI Viewport Dialog Service](ui-viewport-dialog-service.md) -- [CINE Service](cine-service.md) -- [Viewport Grid Service](viewport-grid-service.md) - - - -![UIService](../../../assets/img/ui-services.png) - - - -## Providers for UI services - -**There are several context providers that wraps the application routes. This -makes the context values exposed in the app, and service's `setImplementation` -can get run to override the implementation of the service.** - -```js title="platform/viewer/src/App.jsx" -function App({ config, defaultExtensions }) { - /**...**/ - /**...**/ - return ( - /**...**/ - - - - - - - {appRoutes} - - - - - - - /**...**/ - ); -} -``` - -## Example - -For instance `UIModalService` has the following Public API: - -```js title="platform/core/src/services/UIModalService/index.js" -const publicAPI = { - name, - hide: _hide, - show: _show, - setServiceImplementation, -}; - -function setServiceImplementation({ - hide: hideImplementation, - show: showImplementation, -}) { - /** ... **/ - serviceImplementation._hide = hideImplementation; - serviceImplementation._show = showImplementation; - /** ... **/ -} - -export default { - name: 'UIModalService', - create: ({ configuration = {} }) => { - return publicAPI; - }, -}; -``` - -`UIModalService` implementation can be set (override) in its context provider. -For instance in `ModalProvider` we have: - -```js title="platform/ui/src/contextProviders/ModalProvider.jsx" -import { Modal } from '@ohif/ui'; - -const ModalContext = createContext(null); -const { Provider } = ModalContext; - -export const useModal = () => useContext(ModalContext); - -const ModalProvider = ({ children, modal: Modal, service }) => { - const DEFAULT_OPTIONS = { - content: null, - contentProps: null, - shouldCloseOnEsc: true, - isOpen: true, - closeButton: true, - title: null, - customClassName: '', - }; - - const show = useCallback(props => setOptions({ ...options, ...props }), [ - options, - ]); - - const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), [ - DEFAULT_OPTIONS, - ]); - - useEffect(() => { - if (service) { - service.setServiceImplementation({ hide, show }); - } - }, [hide, service, show]); - - const { - content: ModalContent, - contentProps, - isOpen, - title, - customClassName, - shouldCloseOnEsc, - closeButton, - } = options; - - return ( - - {ModalContent && ( - - - - )} - {children} - - ); -}; - -export default ModalProvider; - -export const ModalConsumer = ModalContext.Consumer; -``` - -Therefore, anywhere in the app that we have access to react context we can use -it by calling the `useModal` from `@ohif/ui`. As a matter of fact, we are -utilizing the modal for the preference window which shows the hotkeys after -clicking on the gear button on the right side of the header. - -A `simplified` code for our worklist is: - -```js title="platform/viewer/src/routes/WorkList/WorkList.jsx" -import { useModal, Header } from '@ohif/ui'; - -function WorkList({ - history, - data: studies, - dataTotal: studiesTotal, - isLoadingData, - dataSource, - hotkeysManager, -}) { - const { show, hide } = useModal(); - - /** ... **/ - - const menuOptions = [ - { - title: t('Header:About'), - icon: 'info', - onClick: () => show({ content: AboutModal, title: 'About OHIF Viewer' }), - }, - { - title: t('Header:Preferences'), - icon: 'settings', - onClick: () => - show({ - title: t('UserPreferencesModal:User Preferences'), - content: UserPreferences, - contentProps: { - hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions( - hotkeyDefaults - ), - hotkeyDefinitions, - onCancel: hide, - currentLanguage: currentLanguage(), - availableLanguages, - defaultLanguage, - onSubmit: state => { - i18n.changeLanguage(state.language.value); - hotkeysManager.setHotkeys(state.hotkeyDefinitions); - hide(); - }, - onReset: () => hotkeysManager.restoreDefaultBindings(), - }, - }), - }, - ]; - /** ... **/ - return ( -
- /** ... **/ -
- /** ... **/ -
- ); -} -``` - - - - - - - -## Tips & Tricks - -It's important to remember that all we're doing is making it possible to control -bits of the application's UI from an extension. Here are a few non-obvious -takeaways worth mentioning: - -- Your application code should continue to use React context - (consumers/providers) as it normally would -- You can substitute our "out of the box" UI implementations with your own -- You can create and register your own UI services -- You can choose not to register a service or provide a service implementation -- In extensions, you can provide fallback/alternative behavior if an expected - service is not registered - - No `UIModalService`? Use the `UINotificationService` to notify users. -- You can technically register a service in an extension and expose it to the - core application - -> Note: These are recommended patterns, not hard and fast rules. Following them -> will help reduce confusion and interoperability with the larger OHIF -> community, but they're not silver bullets. Please speak up, create an issue, -> if you would like to discuss new services or improvements to this pattern. diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-dialog-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-dialog-service.md deleted file mode 100644 index 152841ae62..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-dialog-service.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -sidebar_position: 4 -sidebar_label: UI Dialog Service ---- -# UI Dialog Service - -Dialogs have similar characteristics to that of Modals, but often with a -streamlined focus. They can be helpful when: - -- We need to grab the user's attention -- We need user input -- We need to show additional information - -If you're curious about the DOs and DON'Ts of dialogs and modals, check out this -article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article] - - - -## Interface - -For a more detailed look on the options and return values each of these methods -is expected to support, [check out it's interface in `@ohif/core`][interface] - -| API Member | Description | -| -------------- | ------------------------------------------------------ | -| `create()` | Creates a new Dialog that is displayed until dismissed | -| `dismiss()` | Dismisses the specified dialog | -| `dismissAll()` | Dismisses all dialogs | - -## Implementations - -| Implementation | Consumer | -| ------------------------------------ | -------------------------- | -| [Dialog Provider][dialog-provider]\* | Baked into Dialog Provider | - -`*` - Denotes maintained by OHIF - -> 3rd Party implementers may be added to this table via pull requests. - - - - -[interface]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/UIDialogService/index.js -[dialog-provider]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/contextProviders/DialogProvider.js -[ux-article]: https://uxplanet.org/best-practices-for-modals-overlays-dialog-windows-c00c66cddd8c - diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-modal-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-modal-service.md deleted file mode 100644 index cb36d7e6cf..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-modal-service.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -sidebar_position: 3 -sidebar_label: UI Modal Service ---- -# UI Modal Service - -Modals have similar characteristics to that of Dialogs, but are often larger, -and only allow for a single instance to be viewable at once. They also tend to -be centered, and not draggable. They're commonly used when: - -- We need to grab the user's attention -- We need user input -- We need to show additional information - -If you're curious about the DOs and DON'Ts of dialogs and modals, check out this -article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article] - - -
- -
- -## Interface - -For a more detailed look on the options and return values each of these methods -is expected to support, [check out it's interface in `@ohif/core`][interface] - -| API Member | Description | -| ---------- | ------------------------------------- | -| `hide()` | Hides the open modal | -| `show()` | Shows the provided content in a modal | - -## Implementations - -| Implementation | Consumer | -| ---------------------------------- | --------- | -| [Modal Provider][modal-provider]\* | Modal.jsx | - -`*` - Denotes maintained by OHIF - - - - - -> 3rd Party implementers may be added to this table via pull requests. - - - - -[interface]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/UIModalService/index.js -[modal-provider]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/contextProviders/ModalProvider.js -[modal-consumer]: https://github.com/OHIF/Viewers/tree/master/platform/ui/src/components/ohifModal -[ux-article]: https://uxplanet.org/best-practices-for-modals-overlays-dialog-windows-c00c66cddd8c - diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-notification-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-notification-service.md deleted file mode 100644 index 9618594348..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-notification-service.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: UI Notification Service ---- -# UI Notification Service - -Notifications can be annoying and disruptive. They can also deliver timely -helpful information, or expedite the user's workflow. Here is some high level -guidance on when and how to use them: - -- Notifications should be non-interfering (timely, relevant, important) -- We should only show small/brief notifications -- Notifications should be contextual to current behavior/actions -- Notifications can serve warnings (acting as a confirmation) - -If you're curious about the DOs and DON'Ts of notifications, check out this -article: ["How To Design Notifications For Better UX"][ux-article] - - - -
- -
- - -## Interface - -For a more detailed look on the options and return values each of these methods -is expected to support, [check out it's interface in `@ohif/core`][interface] - -| API Member | Description | -| ---------- | --------------------------------------- | -| `hide()` | Hides the specified notification | -| `show()` | Creates and displays a new notification | - -## Implementations - -| Implementation | Consumer | -| ---------------------------------------- | ----------------------------------------- | -| [Snackbar Provider][snackbar-provider]\* | [SnackbarContainer][snackbar-container]\* | - -`*` - Denotes maintained by OHIF - -> 3rd Party implementers may be added to this table via pull requests. - - - - -[interface]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/UINotificationService/index.js -[snackbar-provider]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/contextProviders/SnackbarProvider.js -[snackbar-container]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/components/snackbar/SnackbarContainer.js -[ux-article]: https://uxplanet.org/how-to-design-notifications-for-better-ux-6fb0711be54d - diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-viewport-dialog-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-viewport-dialog-service.md deleted file mode 100644 index 7370428459..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-viewport-dialog-service.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -sidebar_position: 5 -sidebar_label: UI Viewport Dialog Service ---- - -# UI Viewport Dialog Service - -## Overview -This is a new UI service, that creates a modal inside the viewport. - -Dialogs have similar characteristics to that of Modals, but often with a -streamlined focus. They can be helpful when: - -- We need to grab the user's attention -- We need user input -- We need to show additional information - -If you're curious about the DOs and DON'Ts of dialogs and modals, check out this -article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article] - - - -
- -
- -## Interface - -For a more detailed look on the options and return values each of these methods -is expected to support, [check out it's interface in `@ohif/core`][interface] - -| API Member | Description | -| -------------- | ------------------------------------------------------ | -| `create()` | Creates a new Dialog that is displayed until dismissed | -| `dismiss()` | Dismisses the specified dialog | -| `dismissAll()` | Dismisses all dialogs | - -## Implementations - -| Implementation | Consumer | -| ------------------------ | -------------------------- | -| [ViewportDialogProvider] | Baked into Dialog Provider | - -`*` - Denotes maintained by OHIF - - -## State - -```js -const DEFAULT_STATE = { - viewportIndex: null, - message: undefined, - type: 'info', // "error" | "warning" | "info" | "success" - actions: undefined, // array of { type, text, value } - onSubmit: () => { - console.log('btn value?'); - }, - onOutsideClick: () => { - console.warn('default: onOutsideClick') - }, - onDismiss: () => { - console.log('dismiss? -1'); - }, -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/viewport-grid-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/viewport-grid-service.md deleted file mode 100644 index 39e2378a32..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/services/ui/viewport-grid-service.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -sidebar_position: 6 -sidebar_label: Viewport Grid Service ---- - -# Viewport Grid Service - -## Overview - -This is a new UI service, that handles the grid layout of the viewer. - -## Interface - -For a more detailed look on the options and return values each of these methods -is expected to support, [check out it's interface in `@ohif/core`][interface] - -| API Member | Description | -| --------------------------------------------------------------------- | --------------------------------------------------- | -| `setActiveViewportIndex(index)` | Sets the active viewport index in the app | -| `getState()` | Gets the states of the viewport (see below) | -| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | -| `setLayout({numCols, numRows, keepExtraViewports})` | Sets rows and columns. When the total number of viewports decreases, optionally keep the extra/offscreen viewports. | -| `reset()` | Resets the default states | -| `getNumViewportPanes()` | Gets the number of visible viewport panes | - -## Implementations - -| Implementation | Consumer | -| ---------------------- | -------------------------- | -| [ViewportGridProvider] | Baked into Dialog Provider | - -`*` - Denotes maintained by OHIF - -## State - -```js -const DEFAULT_STATE = { - // starting from null, hanging - // protocol will defined number of rows and cols - numRows: null, - numCols: null, - viewports: [ - /* - * { - * displaySetInstanceUID: string, - * } - */ - ], - activeViewportIndex: 0, -}; -``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/themeing.md b/platform/docs/versioned_docs/version-3.0/platform/themeing.md deleted file mode 100644 index 852c5d3982..0000000000 --- a/platform/docs/versioned_docs/version-3.0/platform/themeing.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Theming ---- - -# Viewer: Theming - -`OHIF-v3` has introduced the -[`LayoutTemplateModule`](./extensions/modules/layout-template.md) which enables -addition of custom layouts. You can easily design your custom components inside -an extension and consume it via the layoutTemplate module you write. - -## Tailwind CSS - -[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework for -creating custom user interfaces. - -Below you can see a compiled version of the tailwind configs. Each section can -be edited accordingly. For instance screen size break points, primary and -secondary colors, etc. - -```js -module.exports = { - prefix: '', - important: false, - separator: ':', - theme: { - screens: { - sm: '640px', - md: '768px', - lg: '1024px', - xl: '1280px', - }, - colors: { - overlay: 'rgba(0, 0, 0, 0.8)', - transparent: 'transparent', - black: '#000', - white: '#fff', - initial: 'initial', - inherit: 'inherit', - - indigo: { - dark: '#0b1a42', - }, - aqua: { - pale: '#7bb2ce', - }, - - primary: { - light: '#5acce6', - main: '#0944b3', - dark: '#090c29', - active: '#348cfd', - }, - - secondary: { - light: '#3a3f99', - main: '#2b166b', - dark: '#041c4a', - active: '#1f1f27', - }, - - common: { - bright: '#e1e1e1', - light: '#a19fad', - main: '#fff', - dark: '#726f7e', - active: '#2c3074', - }, - - customgreen: { - 100: '#05D97C', - }, - - customblue: { - 100: '#c4fdff', - 200: '#38daff', - }, - }, - }, -}; -``` - -You can also use the color variable like before. For instance: - -```js -primary: { - default: ‘var(--default-color)‘, - light: ‘#5ACCE6’, - main: ‘#0944B3’, - dark: ‘#090C29’, - active: ‘#348CFD’, -} -``` - -## White Labeling - -A white-label product is a product or service produced by one company (the -producer) that other companies (the marketers) rebrand to make it appear as if -they had made it - -[Wikipedia: White-Label Product](https://en.wikipedia.org/wiki/White-label_product) - -Current white-labeling options are limited. We expose the ability to replace the -"Logo" section of the application with a custom "Logo" component. You can do -this by adding a whiteLabeling key to your configuration file. - -```js -window.config = { - /** .. **/ - whiteLabeling: { - createLogoComponentFn: function(React) { - return React.createElement( - 'a', - { - target: '_blank', - rel: 'noopener noreferrer', - className: 'text-white underline', - href: 'http://radicalimaging.com', - }, - React.createElement('h5', {}, 'RADICAL IMAGING') - ); - }, - }, - /** .. **/ -}; -``` - -> You can simply use the stylings from tailwind CSS in the whiteLabeling - -In addition to text, you can also add your custom logo. You can put them -inside the platform/viewer/public/assets folder and use them in the -whiteLabeling section. - -```js -window.config = { - /** .. **/ - whiteLabeling: { - createLogoComponentFn: function(React) { - return React.createElement( - 'a', - { - target: '_self', - rel: 'noopener noreferrer', - className: 'text-purple-600 line-through', - href: '/', - }, - React.createElement('img', { - src: './assets/customLogo.svg', - // className: 'w-8 h-8', - }) - ); - }, - }, - /** .. **/ -}; -``` - -The output will look like - -![custom-logo](../assets/img/custom-logo.png) - - - - -[wikipedia]: https://en.wikipedia.org/wiki/White-label_product - diff --git a/platform/docs/versioned_docs/version-3.0/release-notes.md b/platform/docs/versioned_docs/version-3.0/release-notes.md deleted file mode 100644 index 242221bd7d..0000000000 --- a/platform/docs/versioned_docs/version-3.0/release-notes.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Release Notes ---- - -# Release Notes - -> New `OHIF-v3` architecture has made OHIF a general purpose extensible medical -> imaging **platform**, as opposed to a configurable viewer. - -## What's new in `OHIF-v3` - -`OHIF-v3` is our second try for a React-based viewer, and is the third version -of our medical image web viewers from the start. The summary of changes includes: - -- Addition of workflow modes - - Often, medical imaging use cases involves lots of specific workflows that - re-use functionalities. We have added the capability of workflow modes, that - enable people to customize user interface and configure application for - specific workflow. - - The idea is to re-use the functionalities that extensions provide and create - a workflow. Brain segmentation workflow is different from prostate - segmentation in UI for sure; however, they share the segmentation tools that - can be re-used. - - Our vision is that technical people focus of developing extensions which - provides core functionalities, and experts to build modes by picking the - appropriate functionalities from each extension. - -* UI has been completely redesigned with modularity and workflow modes in mind. -* New UI components have been built with Tailwind CSS -* Redux store has been removed from the viewer in favour of services backed by - React's Context API - -Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OHIF-v2 functionalitiesOHIF-v3Comment
Rendering of 2D images via Cornerstone
Study List
Series Browser
DICOM JSON
2D Tools via CornerstoneTools
OpenID Connect standard authentication flow for connecting to identity providers
Internationalization
Drag/drop DICOM data into the viewer (see https://viewer.ohif.org/local)
White-labelling: Easily replace the OHIF Logo with your logo
DICOM Whole-slide imaging viewport🔜In Progress
IHE Invoke Image Display - Standard-compliant launching of the viewer (e.g. from PACS or RIS)🔜Not Started
DICOM PDF support🔜Not Started
Displaying non-renderable DICOM as HTML🔜Not Started
Segmentation support🔜Not Started
RT STRUCT support🔜Not Started
DICOM upload to PACS🔜Not Started
Google Cloud adapter🔜Not Started
VTK Extension + MIP / MPR layoutOther plans that involves amazing news soon!
UMD Build (Embedded Viewer). The problem is that this breaks a bunch of extensions that rely on third party scripts (e.g. VTK) which have their own web worker loaders.
diff --git a/platform/docs/versioned_docs/version-3.0/resources.md b/platform/docs/versioned_docs/version-3.0/resources.md deleted file mode 100644 index be7b10ed75..0000000000 --- a/platform/docs/versioned_docs/version-3.0/resources.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -sidebar_position: 9 -sidebar_label: Resources ---- - -# Resources - -Throughout the development of the OHIF Viewer, we have participated in various -conferences and "hackathons". In this page, we will provide the presentations -and other resources that we have provided to the community in the past: - -## 2022 - -### [NA-MIC Project Week 36th 2022 - Remote](https://github.com/NA-MIC/ProjectWeek/blob/master/PW36_2022_Virtual/README.md) - -The Project Week is a week-long hackathon of hands-on activity in which medical -image computing researchers. OHIF team participated and gave a talk on OHIF and -Cornerstone in the 36th Project Week: -[[Slides]](https://docs.google.com/presentation/d/1-GtOKmr2cQi-r3OFyseSmgLeurtB3KXUkGMx2pVLh1I/edit?usp=sharing) -[[Video]](https://vimeo.com/668339696/63a2c48de8) - -## 2021 - -### [NA-MIC Project Week 35th 2021 - Remote](https://github.com/NA-MIC/ProjectWeek/tree/master/PW35_2021_Virtual) - -The Project Week is a week-long hackathon of hands-on activity in which medical -image computing researchers. OHIF team participated in the 35th Project Week -in 2021. -[[Slides]](https://docs.google.com/presentation/d/1KYNjuiI8lT1foQ4P9TGNV0lBhM6H-5KBs0wkYj4JJbk/edit?usp=sharing) - -### Chan Zuckerberg Initiative (CZI) - -Project presentations and demonstrations of Essential Open Source Software for -Science (EOSS) grantees -[[Slides]](https://docs.google.com/presentation/d/1_CLtG2hsL3ZxOtV2olVnzBOzq-TMLrHLomOy3FiU4NE/edit?usp=sharing) -[[Video]](https://youtu.be/0FjKkTJO0Rc?t=3737) - -### Google Cloud Tech - -Healthcare Imaging with Cloud Healthcare API -[[Video]](https://www.youtube.com/watch?v=2MiX9ScHFhY) - -## 2020 - -### OHIF ITCR Pitch - -OHIF pitch for Informatics Technology for Cancer Research (ITCR) -[[Slides]](https://docs.google.com/presentation/d/1MZXnZrVAnjmhVIWqC-aRSvJOoMMRLhLddACdCa1TybM/edit?usp=sharing) -[[Video]](https://vimeo.com/678769373/625bdb8793) - -## 2019 - -### OHIF and VTK.js Training Course - -OHIF and Kitware collaboration to create a training course for OHIF and VTK.js -developers. Funding for this work was provided by Kitware (NIH NINDS -R44NS081792, NIH NINDS R42NS086295, NIH NIBIB and NIGMS R01EB021396, NIH NIBIB -R01EB014955), Isomics (NIH P41 EB015902), and Massachusetts General Hospital -(NIH U24 CA199460). - -1. Introduction to VTK.js and OHIF - [[Slides]](https://docs.google.com/presentation/d/1NCJxpfx_qUGJI_2DhbECzaOg0k-Z6b65QlUptCofN-A/edit#slide=id.p) - [[Video]](https://vimeo.com/375520781) -2. Developing with VTK.js - [[Slides]](https://docs.google.com/presentation/d/17TCS6EhFi6SWFIrcAJ-DFdFzFFL-WD9BBTv-owmMdDU/edit#slide=id.p) - [[Video]](https://vimeo.com/375521036) -3. VTK.js Architecture and Tooling - [[Slides]](https://docs.google.com/presentation/d/1Sr1OGxMSw0oCt46koKQbmwSIE11Kqq8MGtyW3W0ASpk/edit?usp=gmail_thread) - [[Video]](https://vimeo.com/375521810) -4. OHIF + VTK.js Integration - [[Slides]](https://docs.google.com/presentation/d/1Iwg-u01HGVf1CgC6NbcBD3gm3uHN9WhjU59FSz55TN8/edit?ts=5d9c9ce4#slide=id.g59aa99cda4_0_131) - [[Video]](https://vimeo.com/375521206) - -## 2017 - -### Lesion Tracker - -LesionTracker: Extensible Open-Source Zero-Footprint Web Viewer for Cancer -Imaging Research and Clinical Trials. This project was supported in part by -grant U24 CA199460 from the National Cancer Institute (NCI) Informatics -Technology for Cancer Research (ITCR) Program. -[[Video]](https://www.youtube.com/watch?v=gUIPtoSBL-Q) - -### OHIF Community Meeting - June - -[[Slides]](https://docs.google.com/presentation/d/1K9Y6eP5DYTXoDlfwCZE6GkCUp83AK4_40YQS0dlzVBo/edit?usp=sharing) - -## 2016 - -### Imaging Community Call - -Open Source Oncology Web Viewer; Presentation by Gordon J. Harris -[[Slides]](https://www.slideshare.net/imgcommcall/lesiontracker) - -### OHIF Community Meeting - June - -[[Slides]](https://docs.google.com/presentation/d/1Ai25mBG0ZWUPhaadp3VnbCVmkYs9K51sQ8osMixrvJ0/edit?usp=sharing) - -### OHIF Community Meeting - September - -[[Slides]](https://docs.google.com/presentation/d/1iYZoU7v7KHSLHiKwH1_9_wweAkG7RGnyxrWeeHva4zQ/edit?usp=sharing) diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/_category_.json b/platform/docs/versioned_docs/version-3.0/user-guide/_category_.json deleted file mode 100644 index 68ea78e784..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "User Guide", - "position": 2 -} diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/index.md b/platform/docs/versioned_docs/version-3.0/user-guide/index.md deleted file mode 100644 index 52f44bad8c..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/index.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -sidebar_position: 2 -sidebar_label: Study List ---- - -# Study List - -## Overview - -The first page you will see when the viewer is loaded is the `Study List`. In -this page you can explore all the studies that are stored on the configured -server for the `OHIF Viewer`. - -![user-study-list](../assets/img/user-study-list.png) - -## Sorting - -When the Study List is opened, the application queries the PACS for 101 studies -by default. If there are greater than 100 studies returned, the default sort for -the study list is dictated by the image archive that hosts these studies for the -viewer and study list sorting will be disabled. If there are less than or equal -to 100 studies returned, they will be sorted by study date (most recent to -oldest) and study list sorting will be enabled. Whenever a query returns greater -than 100 studies, use filters to narrow results below 100 studies to enable -Study List sorting. - -## Filters - -There are certain filters that can be used to limit the study list to the -desired criteria. - -- Patient Name: Searches between patients names -- MRN: Searches between patients Medical Record Number -- Study Date: Filters the date of the acquisition -- Description: Searches between study descriptions -- Modality: Filters the modalities -- Accession: Searches between patients accession number - -An example of using study list filter is shown below: - -![user-study-filter](../assets/img/user-study-filter.png) - -Below the study list are pagination options for 25, 50, or 100 studies per page. - -![user-study-next](../assets/img/user-study-next.png) - -## Study Summary - -Click on a study to expand the study summary panel. - -![user-study-summary](../assets/img/user-study-summary.png) - -A summary of series available in the study is shown, which contains the series -description, series number, modality of the series, instances in the series, and -buttons to launch viewer modes to display the study. - -## Study Specific Modes - -All available modes are seen in the study expanded view. Modes can be enabled or -disabled for a study based on the modalities contained within the study. - -In the screenshot below, there are two modes shown for the selected study - -- Basic Viewer: Default mode that enables rendering and measurement tracking - -- PET/CT Fusion: Mode for visualizing the PET CT study in a 3x3 format. - -Based on the mode configurations (e.g., available modalities), PET/CT mode is -disabled for studies that do not contain PET AND CT images. - - - -![user-studyist-modespecific](../assets/img/user-studyist-modespecific.png) - -The previous screenshot shows a study containing PET and CT images and both -Basic Viewer and PET/CT Mode are available. - -## View Study - -The `Basic Viewer` mode is available for all studies by default. Click on the -mode button to launch the viewer. - -![user-open-viewer](../assets/img/user-open-viewer.png) diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/Language.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/Language.md deleted file mode 100644 index 9032ecc5f6..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/Language.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -sidebar_position: 8 ---- - -# Language - -OHIF supports internationalization capabilities and setting the general language -of the Viewer. - -It should be noted that we don't have complete translations for all the components -and all the languages; however, you can easily add the key value translation pairs -following developer guides. - -Summary of language changing usage can be seen below: - - - -## Overview Video - -
- -
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/_category_.json b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/_category_.json deleted file mode 100644 index 417861dba3..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Basic Viewer", - "position": 2 -} diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/hotkeys.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/hotkeys.md deleted file mode 100644 index 5d8aac1fb4..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/hotkeys.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -sidebar_position: 7 ---- - -# Hotkeys - -To open the hotkey assignment panel, you can click on the Preferences gear on the -top right side of the viewer. - - -Below, you can see the default hotkeys key bindings: - -![user-hotkeys-default](../../assets/img/user-hotkeys-default.png) - -Hotkeys can be assigned to custom bindings that persist for the duration of the browser session. diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/index.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/index.md deleted file mode 100644 index 6918ce3b1b..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/index.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Overview ---- - - -# Overview -When you open a mode, viewport, toolbar and panels of the mode get shown. -It is important to note that each mode has a different UI, which serves its purpose. -Here we explain various components of `Basic Viewer` mode which includes measurement -tracking functionalities. - -Basic viewer mode (longitudinal): - -![user-viewer](../../assets/img/user-viewer.png) - -Let's break different aspects of the viewer to the main components: - -- Left Panel (study panel): displays series thumbnails with series details -- Viewport: renders the image and displays annotations -- Right Panel (measurements): displays annotations details -- Toolbar: displays tools and logo - -![user-viewer-components](../../assets/img/overview.png) - - - - -Now, we explain each component and its sub-elements in detail. diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-panel.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-panel.md deleted file mode 100644 index b760bf37e4..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-panel.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Measurement Panel - -## Introduction -In `Basic Viewer` mode, the right panel is the `Measurement Panel`. The Measurement Panel can be expanded or hidden by clicking on the arrow to the left of `Measurements`. - -Select a measurement tool and mark an image to initiate measurement tracking. A pop-up will ask if you want to track measurements for the series on which the annotation was drawn. - -![user-measurement-panel-modal](../../assets/img/measurement-panel-prompt.png) - - - - - -If you select `Yes`, the series becomes a `tracked series`, and the current drawn measurement and next measurements are shown on the measurement panel on the right. - -![user-measurement-panel-tracked](../../assets/img/measurement-panel-tracked.png) - -If you select `No`, the measurement becomes temporary. The next annotation made will repeat the measurement tracking prompt. - -If you select `No, do not ask again`, all annotations made on the study will be temporary. - -![measurement-temporary](../../assets/img/measurement-temporary.png) - - -## Labeling Measurements -You can edit the measurement name by hovering over the measurement and selecting the edit icon. You can also label or relabel a measurement by right-clicking on it in the viewport. - -![user-measurement-edit](../../assets/img/measurement-panel-1.png) - - - -## Deleting a Measurement -A measurement can be deleting by dragging it outside the image in the viewport or by right-clicking on the measurement in the viewport and selecting 'Delete'. - - -## Jumping to a Measurement -Measurement navigation inside the top viewport can be used to move to previous and next measurement. - - -![measurements-prevNext](../../assets/img/measurements-prevNext.png) - -If a series containing a measurement is currently being displayed in a viewport, you can jump to display the measurement in the viewport by clicking on it in the Measurement Panel. - -## Export Measurements - -You can export the measurements by clicking on the `Export`. A CSV file will get downloaded to your local computer containing the drawn measurements. - - -![user-measurement-export](../../assets/img/user-measurement-export.png) - - -If you have set up your DICOM server to be able to store instances from the viewer, then you are able to create a report by clicking on the `Create Report`. -This will create a DICOM Structured Report (SR) from the measurements and push it -to the server. - -For instance, running the Viewer on a local DCM4CHEE: - - - -
- -
- -## Overview Video -An overview of measurement drawing and exporting can be seen below: - - -
- -
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-tracking.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-tracking.md deleted file mode 100644 index a15f7e5dfb..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-tracking.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Measurement Tracking - -## Introduction -OHIF-V3's `Basic Viewer` implements a `Measurement Tracking` workflow. Measurement -tracking allows you to: - -- Draw annotations and have them shown in the measurement panel -- Create a report from the tracked measurement and export them as DICOM SR -- Use already exported DICOM SR to re-hydrate the measurements in the viewer - - -## Status Icon -Each viewport has a left icon indicating whether the series within the viewport -contains: - -- tracked measurement OR -- untracked measurement OR -- Structured Report OR -- Locked (uneditable) Structured Report - -In the following, we will discuss each category. - -### Tracked vs Untracked Measurements - -`OHIF-v3` implements a workflow for measurement tracking that can be seen below. - -![user-measurement-panel-modal](../../assets/img/tracking-workflow1.png) - -In summary, when you create an annotation, a prompt will be shown whether to start tracking or not. If you start the tracking, the annotation style will change to a solid line, and annotation details get displayed on the measurement panel. -On the other hand, if you decline the tracking prompt, the measurement will be considered "temporary," and annotation style remains as a dashed line and not shown on the right panel, and cannot be exported. - - -Below, you can see different icons that appear for a tracked vs. untracked series in -`OHIF-v3`. - -![tracked-not-tracked](../../assets/img/tracked-not-tracked.png) - - - -#### Overview video for starting the tracking for measurements: - - -
- -
- - -

- -#### Overview video for not starting tracking for measurements: - - -
- -
- - -### Reading and Writing DICOM SR - -`OHIF-v3` provides full support for reading, writing and mapping the DICOM Structured -Report (SR) to interactable `Cornerstone Tools`. When you load an already exported -DICOM SR into the viewer, you will be prompted whether to track the measurements -for the series or not. - -![SR-exported](../../assets/img/SR-exported.png) - -If you click Yes, DICOM SR measurements gets re-hydrated into the viewer and -the series become a tracked series. However, If you say no and later decide to say track the measurements, you can always click on the SR button that will prompt you -with the same message again. - -![restore-exported-sr](../../assets/img/restore-exported-sr.png) - -The full workflow for saving measurements to SR and loading SR into the viewer is shown below. - -![user-measurement-panel-modal](../../assets/img/tracking-workflow2.png) -![user-measurement-panel-modal](../../assets/img/tracking-workflow3.png) - - -#### Overview video for loading DICOM SR and making a tracked series: - - -
- -
- -

- -#### Overview video for loading DICOM SR and not making a tracked series: - - -
- -
- -

- -
- -
- -### Loading DICOM SR into an Already Tracked Series - -If you have an already tracked series and try to load a DICOM SR measurements, -you will be shown the following lock icon. This means that, you can review the -DICOM SR measurement, manipulate image and draw "temporary" measurements; however, -you cannot edit the DICOM SR measurement. - - -![locked-sr](../../assets/img/locked-sr.png) - -

- - -#### Overview video for loading DICOM SR inside an already tracked series: - - - -
- -
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/study-panel.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/study-panel.md deleted file mode 100644 index 1b5190ffae..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/study-panel.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Study Panel - -In `Basic Viewer` mode, the left panel includes Studies related to the current -patient. You can see three main type of studies below - -- Primary: The opened study from the study list. This study is always expanded - by default. -- Recent: All studies for the patient that contain study dates within 1 year of - the primary study -- All: All studies available for the patient contained within the source - repository - -The `Study Panel` displays the measurement tracking status of each series within -a study. As you can see in the first picture, the dashed circle on the left side -of each series demonstrates whether the series is being tracked for measurement -or not. - - - -![user-study-panel](../../assets/img/user-study-panel.png) - -Studies can be expanded or collapsed by clicking on the study information in the -Study Panel. If a series is being tracking within a study, the Measurement Panel -will display this information while the study is collapsed. - - - - diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/toolbar.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/toolbar.md deleted file mode 100644 index cb982ce25b..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/toolbar.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -sidebar_position: 6 ---- - - -# Toolbar - -The four main components of the toolbar are: - -- Navigation back to the [Study List](../index.md) -- Logo and white labelling -- [Tools](#tools) -- [Preferences](#preferences) - -![user-viewer-toolbar](../../assets/img/user-viewer-toolbar.png) - - -## Tools -This section displays all the available tools inside the mode. -## Measurement tools -The basic viewer comes with the following default measurement tools: - -- Length Tool: Calculates the linear distance between two points in *mm* -- Bidirectional Tool: Creates a measurement of the longest diameter (LD) and longest perpendicular diameter (LPD) in *mm* -- Annotation: Used to create a qualitative marker with a freetext label -- Ellipse: Measures an elliptical area in *mm2* and Hounsfield Units (HU) -- Calibration Tool: Calibrate (or override) the Pixel Spacing Attribute (Physical distance in the patient between the center of each pixel, specified by a numeric pair - adjacent row spacing (delimiter) adjacent column spacing in mm) - -When a measurement tool is selected from the toolbar, it becomes the `active` tool. Use the caret to expand the measurement tools and select another tool. - - -![user-viewer-toolbar-measurements](../../assets/img/user-viewer-toolbar-measurements.png) - - -## Window/Level -The `Window/Level` tool enables manipulating the window level and window width of the rendered image. Click on the tool to enable freeform adjustment, then click and drag on the viewport to freely adjust the window/level. - -Click on the caret to expand the tool and choose from predefined W/L settings for common imaging scenarios. - - -![user-toolbar-preset](../../assets/img/user-toolbar-preset.png) - - -## Pan and Zoom -With the Zoom tool selected, click and drag the cursor on an image to adjust the zoom. The magnification level is displayed in the viewport. - -With the Pan tool selected, click and drag the cursor on an image to adjust the image position. - -## Image Capture -Click on the Camera icon to download a high quality image capture using common image formats (png, jpg) - -![user-toolbar-download-icon](../../assets/img/user-toolbar-download-icon.png) - -In the opened modal, the filename, image's width and height, and filetype and can be configured before downloading the image to your local computer. - -![user-toolbarDownload](../../assets/img/user-toolbarDownload.png) - - - -## Layout Selector -Please see the `Viewport` section for details. - - -## More Tools Menu -- Reset View: Resets all image manipulation such as position, zoom, and W/L -- Rotate Right: Flips the image 90 degrees clockwise -- Flip Horizontally: Flips the image 180 degrees horizontally -- Stack Scroll: Links all viewports containing images to scroll together -- Magnify: Click on an image to magnify a particular area of interest -- Invert: Inverts the color scale -- Cine: Toggles the Cine player control in the currently selected viewport. Click the `x` on the Cine player or click the tool again to toggle off. -- Angle: Measures an adjustable angle on an image -- Probe: Drag the probe to see pixel values -- Rectangle: Measures a rectangular area in mm^2 and HU - -When a tool is selected from the `More Tools` menu, it becomes the active tool until it is replaced by clicking on a different tool in the More Tools menu or main toolbar. - - -## Overview Video -An overview of tool usage can been seen below: - - -
- -
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/viewport.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/viewport.md deleted file mode 100644 index 173728ca15..0000000000 --- a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/viewport.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Viewport - -Image visualization happens at the viewport which contains canvas or canvases that -renders series. - -![user-viewer-main](../../assets/img/user-viewer-main.png) - - -By default, you can modify: - -- Zoom: right click dragging up or down -- Contrast/brightness: left click dragging up/down to change contrast, and left/right for changing brightness -- Pan: middle click dragging - - -## Changing Series for display -To change the displayed series, you can drag and drop the desired series from the left panel. Start, by dragging the thumbnail of the series, and drop it on the viewport. - -## Changing Layout -If you click on the layout icon on the toolbar, you can use the layout selector UI. After changing the layout, you can select studies for each new viewport by dragging and dropping in to the viewport. - -After changing the layout from 1x1, you will see each viewport gets tagged by a letter, -which matches its series section in the study list. - - -![user-viewer-layout](../../assets/img/user-viewer-layout.png) - - -## Overview Video -An overview of viewport layout change, and manipulation can be seen below: - - -
- -
diff --git a/platform/docs/versioned_sidebars/version-1.0-sidebars.json b/platform/docs/versioned_sidebars/version-1.0-deprecated-sidebars.json similarity index 100% rename from platform/docs/versioned_sidebars/version-1.0-sidebars.json rename to platform/docs/versioned_sidebars/version-1.0-deprecated-sidebars.json diff --git a/platform/docs/versioned_sidebars/version-2.0-sidebars.json b/platform/docs/versioned_sidebars/version-2.0-deprecated-sidebars.json similarity index 100% rename from platform/docs/versioned_sidebars/version-2.0-sidebars.json rename to platform/docs/versioned_sidebars/version-2.0-deprecated-sidebars.json diff --git a/platform/docs/versioned_sidebars/version-3.0-sidebars.json b/platform/docs/versioned_sidebars/version-3.0-sidebars.json deleted file mode 100644 index caea0c03ba..0000000000 --- a/platform/docs/versioned_sidebars/version-3.0-sidebars.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tutorialSidebar": [ - { - "type": "autogenerated", - "dirName": "." - } - ] -} diff --git a/platform/docs/versions.json b/platform/docs/versions.json index 8858c6ab30..fe701e3847 100644 --- a/platform/docs/versions.json +++ b/platform/docs/versions.json @@ -1 +1 @@ -["2.0"] +["2.0-deprecated", "1.0-deprecated"] diff --git a/platform/i18n/.webpack/webpack.dev.js b/platform/i18n/.webpack/webpack.dev.js index 2bc3ced0b9..4bf848b6c5 100644 --- a/platform/i18n/.webpack/webpack.dev.js +++ b/platform/i18n/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/platform/i18n/.webpack/webpack.prod.js b/platform/i18n/.webpack/webpack.prod.js index fe0e4e3592..330256356e 100644 --- a/platform/i18n/.webpack/webpack.prod.js +++ b/platform/i18n/.webpack/webpack.prod.js @@ -12,8 +12,6 @@ const ENTRY = { app: `${SRC_DIR}/index.js`, }; - - module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); diff --git a/platform/i18n/CHANGELOG.md b/platform/i18n/CHANGELOG.md index 9097173143..895ab87ee2 100644 --- a/platform/i18n/CHANGELOG.md +++ b/platform/i18n/CHANGELOG.md @@ -3,6 +3,241 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/i18n + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/i18n + + + + + ## [0.52.8](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.7...@ohif/i18n@0.52.8) (2020-04-07) **Note:** Version bump only for package @ohif/i18n diff --git a/platform/i18n/babel.config.js b/platform/i18n/babel.config.js index fed6f05fec..325ca2a8ee 100644 --- a/platform/i18n/babel.config.js +++ b/platform/i18n/babel.config.js @@ -1 +1 @@ -module.exports = require("../../babel.config.js"); +module.exports = require('../../babel.config.js'); diff --git a/platform/i18n/package.json b/platform/i18n/package.json index d7dc506fc9..a15a220aea 100644 --- a/platform/i18n/package.json +++ b/platform/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/i18n", - "version": "3.6.0", + "version": "3.7.0-beta.85", "description": "Internationalization library for The OHIF Viewer", "author": "OHIF", "license": "MIT", diff --git a/platform/i18n/src/config.js b/platform/i18n/src/config.js index e9e1c16853..e5e4842d97 100644 --- a/platform/i18n/src/config.js +++ b/platform/i18n/src/config.js @@ -1,6 +1,4 @@ -const debugMode = !!( - process.env.NODE_ENV !== 'production' && process.env.REACT_APP_I18N_DEBUG -); +const debugMode = !!(process.env.NODE_ENV !== 'production' && process.env.REACT_APP_I18N_DEBUG); const detectionOptions = { // order and from where user language should be detected @@ -18,7 +16,7 @@ const detectionOptions = { excludeCacheFor: ['cimode'], // languages to not persist (cookie, localStorage) // optional htmlTag with lang attribute, the default is: - htmlTag: document.documentElement + htmlTag: document.documentElement, }; export { debugMode, detectionOptions }; diff --git a/platform/i18n/src/index.js b/platform/i18n/src/index.js index 482b53b7e9..b2ccc6fc3e 100644 --- a/platform/i18n/src/index.js +++ b/platform/i18n/src/index.js @@ -140,7 +140,13 @@ i18n.initializing = initI18n(); i18n.initI18n = initI18n; i18n.addLocales = addLocales; i18n.availableLanguages = getAvailableLanguagesInfo(locales); -i18n.defaultLanguage = { label: getLanguageLabel(DEFAULT_LANGUAGE), value: DEFAULT_LANGUAGE }; -i18n.currentLanguage = () => ({ label: getLanguageLabel(i18n.language), value: i18n.language }); +i18n.defaultLanguage = { + label: getLanguageLabel(DEFAULT_LANGUAGE), + value: DEFAULT_LANGUAGE, +}; +i18n.currentLanguage = () => ({ + label: getLanguageLabel(i18n.language), + value: i18n.language, +}); export default i18n; diff --git a/platform/i18n/src/locales/ar/UserPreferencesModal.json b/platform/i18n/src/locales/ar/UserPreferencesModal.json index 06992c5089..86db2742ab 100644 --- a/platform/i18n/src/locales/ar/UserPreferencesModal.json +++ b/platform/i18n/src/locales/ar/UserPreferencesModal.json @@ -1,3 +1,3 @@ { "No hotkeys found": "Nenhuma tecla de atalho está configurada para este aplicativo. As teclas de atalho podem ser configuradas no arquivo app-config.js do aplicativo." -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/AboutModal.json b/platform/i18n/src/locales/en-US/AboutModal.json index 5ee8b5e242..55e0144c90 100644 --- a/platform/i18n/src/locales/en-US/AboutModal.json +++ b/platform/i18n/src/locales/en-US/AboutModal.json @@ -11,4 +11,4 @@ "Value": "Value", "Version Information": "Version Information", "Visit the forum": "Visit the forum" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/Buttons.json b/platform/i18n/src/locales/en-US/Buttons.json index 7a0b657831..7e5f23b36a 100644 --- a/platform/i18n/src/locales/en-US/Buttons.json +++ b/platform/i18n/src/locales/en-US/Buttons.json @@ -1,11 +1,14 @@ { "Acquired": "Acquired", "Angle": "Angle", + "Annotation": "Annotation", "Axial": "Axial", "Bidirectional": "Bidirectional", "Brush": "Brush", + "Cine": "Cine", "CINE": "CINE", "Cancel": "Cancel", + "Capture": "Capture", "Circle": "Circle", "Clear": "Clear", "Coronal": "Coronal", @@ -14,8 +17,10 @@ "Ellipse": "Ellipse", "Elliptical": "Elliptical", "Flip H": "Flip H", + "Flip Horizontally": "Flip Horizontally", "Flip V": "Flip V", "Freehand": "Freehand", + "Grid Layout": "Grid Layout", "Invert": "Invert", "Layout": "$t(Common:Layout)", "Length": "Length", @@ -24,6 +29,7 @@ "Manual": "Manual", "Measurements": "Measurements", "More": "$t(Common:More)", + "More Tools": "More Tools", "Next": "$t(Common:Next)", "Pan": "Pan", "Play": "$t(Common:Play)", @@ -31,13 +37,15 @@ "Probe": "Probe", "ROI Window": "ROI Window", "Rectangle": "Rectangle", + "Reference Lines": "Reference Lines", "Reset": "$t(Common:Reset)", "Reset to Defaults": "$t(Common:Reset) to Defaults", "Rotate Right": "Rotate Right", "Sagittal": "Sagittal", "Save": "Save", "Stack Scroll": "Stack Scroll", + "Stack Image Sync": "Stack Image Sync", "Stop": "$t(Common:Stop)", "Themes": "Themes", "Zoom": "Zoom" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/CineDialog.json b/platform/i18n/src/locales/en-US/CineDialog.json index b9f6667a75..48398269ce 100644 --- a/platform/i18n/src/locales/en-US/CineDialog.json +++ b/platform/i18n/src/locales/en-US/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Skip to first $t(Common:Image)", "Skip to last image": "Skip to last $t(Common:Image)", "fps": "fps" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/Common.json b/platform/i18n/src/locales/en-US/Common.json index ff2e5d3b17..2a9b748c32 100644 --- a/platform/i18n/src/locales/en-US/Common.json +++ b/platform/i18n/src/locales/en-US/Common.json @@ -6,6 +6,7 @@ "Measurements": "Measurements", "More": "More", "Next": "Next", + "NoStudyDate": "No Study Date", "Play": "Play", "Previous": "Previous", "Reset": "Reset", diff --git a/platform/i18n/src/locales/en-US/DataSourceConfiguration.json b/platform/i18n/src/locales/en-US/DataSourceConfiguration.json new file mode 100644 index 0000000000..092ea55147 --- /dev/null +++ b/platform/i18n/src/locales/en-US/DataSourceConfiguration.json @@ -0,0 +1,24 @@ +{ + "Configure Data Source": "Configure Data Source", + "Data set": "Data set", + "DICOM store": "DICOM store", + "Location": "Location", + "Project": "Project", + "Error fetching Data set list": "Error fetching data sets", + "Error fetching DICOM store list": "Error fetching DICOM stores", + "Error fetching Location list": "Error fetching locations", + "Error fetching Project list": "Error fetching projects", + "No Project available": "No projects available", + "No Location available": "No locations available", + "No Data set available": "No data sets available", + "No DICOM store available": "No DICOM stores available", + "Select": "Select", + "Search Data set list": "Search data sets", + "Search DICOM store list": "Search DICOM stores", + "Search Location list": "Search locations", + "Search Project list": "Search projects", + "Select Data set": "Select a data Set", + "Select DICOM store": "Select a DICOM store", + "Select Location": "Select a location", + "Select Project": "Select a project" +} diff --git a/platform/i18n/src/locales/en-US/DatePicker.json b/platform/i18n/src/locales/en-US/DatePicker.json index e2eb258dec..24187a1cf4 100644 --- a/platform/i18n/src/locales/en-US/DatePicker.json +++ b/platform/i18n/src/locales/en-US/DatePicker.json @@ -2,4 +2,4 @@ "Clear dates": "Clear dates", "End Date": "End Date", "Start Date": "Start Date" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/MeasurementTable.json b/platform/i18n/src/locales/en-US/MeasurementTable.json index ab89cebcf8..9eb9b8d609 100644 --- a/platform/i18n/src/locales/en-US/MeasurementTable.json +++ b/platform/i18n/src/locales/en-US/MeasurementTable.json @@ -6,4 +6,4 @@ "NonTargets": "NonTargets", "Relabel": "Relabel", "Targets": "Targets" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/Messages.json b/platform/i18n/src/locales/en-US/Messages.json new file mode 100644 index 0000000000..13c4f7a7bd --- /dev/null +++ b/platform/i18n/src/locales/en-US/Messages.json @@ -0,0 +1,15 @@ +{ + "1": "No valid instances found in series.", + "2": "DisplaySet has missing position information.", + "3": "DisplaySet is not a reconstructable 3D volume.", + "4": "Multi frame displaySet don't have pixel measurement information.", + "5": "Multi frame displaySet don't have orientation information.", + "6": "Multi frame displaySet don't have position information.", + "7": "DisplaySet has missing frames.", + "8": "DisplaySet has irregular spacing.", + "9": "DisplaySet has inconsistent dimensions between frames.", + "10": "DisplaySet has frames with inconsistent number of components.", + "11": "DisplaySet has frames with inconsistent orientations.", + "12": "DisplaySet has inconsistent position information.", + "13": "Unsupported displaySet." +} diff --git a/platform/i18n/src/locales/en-US/UserPreferencesModal.json b/platform/i18n/src/locales/en-US/UserPreferencesModal.json index 52b0dc1673..33b331f598 100644 --- a/platform/i18n/src/locales/en-US/UserPreferencesModal.json +++ b/platform/i18n/src/locales/en-US/UserPreferencesModal.json @@ -6,4 +6,4 @@ "Save": "$t(Buttons:Save)", "SaveMessage": "Preferences saved", "User Preferences": "User Preferences" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/ViewportDownloadForm.json b/platform/i18n/src/locales/en-US/ViewportDownloadForm.json index cd1624b4b1..85001a4219 100644 --- a/platform/i18n/src/locales/en-US/ViewportDownloadForm.json +++ b/platform/i18n/src/locales/en-US/ViewportDownloadForm.json @@ -11,4 +11,4 @@ "minHeightError": "The minimum valid height is 100px.", "minWidthError": "The minimum valid width is 100px.", "showAnnotations": "Show Annotations" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/en-US/index.js b/platform/i18n/src/locales/en-US/index.js index 279856d0ad..d8b19e6f84 100644 --- a/platform/i18n/src/locales/en-US/index.js +++ b/platform/i18n/src/locales/en-US/index.js @@ -2,6 +2,7 @@ import AboutModal from './AboutModal.json'; import Buttons from './Buttons.json'; import CineDialog from './CineDialog.json'; import Common from './Common.json'; +import DataSourceConfiguration from './DataSourceConfiguration.json'; import DatePicker from './DatePicker.json'; import Header from './Header.json'; import MeasurementTable from './MeasurementTable.json'; @@ -10,6 +11,7 @@ import StudyBrowser from './StudyBrowser.json'; import StudyList from './StudyList.json'; import UserPreferencesModal from './UserPreferencesModal.json'; import ViewportDownloadForm from './ViewportDownloadForm.json'; +import Messages from './Messages.json'; export default { 'en-US': { @@ -17,6 +19,7 @@ export default { Buttons, CineDialog, Common, + DataSourceConfiguration, DatePicker, Header, MeasurementTable, @@ -25,5 +28,6 @@ export default { StudyList, UserPreferencesModal, ViewportDownloadForm, + Messages, }, }; diff --git a/platform/i18n/src/locales/es/Buttons.json b/platform/i18n/src/locales/es/Buttons.json index b8841fa186..d7e0acfbfb 100644 --- a/platform/i18n/src/locales/es/Buttons.json +++ b/platform/i18n/src/locales/es/Buttons.json @@ -40,4 +40,4 @@ "Stop": "$t(Common:Stop)", "Themes": "Temas", "Zoom": "Ampliar" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/es/CineDialog.json b/platform/i18n/src/locales/es/CineDialog.json index fff13fd043..a9cbf4f8fe 100644 --- a/platform/i18n/src/locales/es/CineDialog.json +++ b/platform/i18n/src/locales/es/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Ir a la primera $t(Common:Image)", "Skip to last image": "Ir a la última $t(Common:Image)", "fps": "imágenes/seg." -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/es/Common.json b/platform/i18n/src/locales/es/Common.json index bed045de3a..65481c8b23 100644 --- a/platform/i18n/src/locales/es/Common.json +++ b/platform/i18n/src/locales/es/Common.json @@ -12,4 +12,4 @@ "Show": "Mostrar", "Stop": "Detener", "StudyDate": "Fecha de estudo" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/es/Header.json b/platform/i18n/src/locales/es/Header.json index d2c4bb5966..8a4308c5b3 100644 --- a/platform/i18n/src/locales/es/Header.json +++ b/platform/i18n/src/locales/es/Header.json @@ -5,4 +5,4 @@ "Options": "Opciones", "Preferences": "Preferencias", "Study list": "Lista de estudios" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/es/UserPreferencesModal.json b/platform/i18n/src/locales/es/UserPreferencesModal.json index ca4c770964..4534903afc 100644 --- a/platform/i18n/src/locales/es/UserPreferencesModal.json +++ b/platform/i18n/src/locales/es/UserPreferencesModal.json @@ -3,4 +3,4 @@ "Reset to Defaults": "$t(Buttons:Reset to Defaults)", "Save": "$t(Buttons:Save)", "User Preferences": "Preferencias de Usuario" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/fr/Buttons.json b/platform/i18n/src/locales/fr/Buttons.json index 31c8d301aa..50e7dfadc4 100644 --- a/platform/i18n/src/locales/fr/Buttons.json +++ b/platform/i18n/src/locales/fr/Buttons.json @@ -39,4 +39,4 @@ "Stop": "$t(Common:Stop)", "Themes": "Themes", "Zoom": "Zoom" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/fr/CineDialog.json b/platform/i18n/src/locales/fr/CineDialog.json index 6135824a36..257e8a257b 100644 --- a/platform/i18n/src/locales/fr/CineDialog.json +++ b/platform/i18n/src/locales/fr/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Retour à la première $t(Common:Image)", "Skip to last image": "Aller à la dernière $t(Common:Image)", "fps": "ips" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/fr/Common.json b/platform/i18n/src/locales/fr/Common.json index 5d19769193..e1220f5198 100644 --- a/platform/i18n/src/locales/fr/Common.json +++ b/platform/i18n/src/locales/fr/Common.json @@ -7,4 +7,4 @@ "Previous": "Précédent", "Reset": "Reset", "Stop": "Stop" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/fr/Header.json b/platform/i18n/src/locales/fr/Header.json index 88db056b8a..e9b894874c 100644 --- a/platform/i18n/src/locales/fr/Header.json +++ b/platform/i18n/src/locales/fr/Header.json @@ -5,4 +5,4 @@ "Options": "Options", "Preferences": "Préférences", "Study list": "Liste d'études" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/fr/UserPreferencesModal.json b/platform/i18n/src/locales/fr/UserPreferencesModal.json index a4fc5810e1..506547fd98 100644 --- a/platform/i18n/src/locales/fr/UserPreferencesModal.json +++ b/platform/i18n/src/locales/fr/UserPreferencesModal.json @@ -3,4 +3,4 @@ "Reset to Defaults": "$t(Buttons:Reset to Defaults)", "Save": "$t(Buttons:Save)", "User Preferences": "Préférences utilisateur" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/index.js b/platform/i18n/src/locales/index.js index b950f9ab88..dcfc9ba85e 100644 --- a/platform/i18n/src/locales/index.js +++ b/platform/i18n/src/locales/index.js @@ -19,5 +19,5 @@ export default { ...pt_BR, ...vi, ...zh, - ...test_lng + ...test_lng, }; diff --git a/platform/i18n/src/locales/ja-JP/Buttons.json b/platform/i18n/src/locales/ja-JP/Buttons.json index 5b35d263f5..aed8434026 100644 --- a/platform/i18n/src/locales/ja-JP/Buttons.json +++ b/platform/i18n/src/locales/ja-JP/Buttons.json @@ -39,4 +39,4 @@ "Stop": "$t(Common:Stop)", "Themes": "テーマ", "Zoom": "ズーム" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/ja-JP/CineDialog.json b/platform/i18n/src/locales/ja-JP/CineDialog.json index 5dc36b5315..be38a7bae7 100644 --- a/platform/i18n/src/locales/ja-JP/CineDialog.json +++ b/platform/i18n/src/locales/ja-JP/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "$t(Common:Image)最初にスキップ", "Skip to last image": "$t(Common:Image)最後にスキップ", "fps": "fps" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/ja-JP/Common.json b/platform/i18n/src/locales/ja-JP/Common.json index cec88db893..fa56db25b2 100644 --- a/platform/i18n/src/locales/ja-JP/Common.json +++ b/platform/i18n/src/locales/ja-JP/Common.json @@ -7,4 +7,4 @@ "Previous": "前へ", "Reset": "リセット", "Stop": "ストップ" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/ja-JP/Header.json b/platform/i18n/src/locales/ja-JP/Header.json index 55f344e221..1562950ad7 100644 --- a/platform/i18n/src/locales/ja-JP/Header.json +++ b/platform/i18n/src/locales/ja-JP/Header.json @@ -5,4 +5,4 @@ "Options": "オプション", "Preferences": "プレファレンス", "Study list": "スタディリスト" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/ja-JP/UserPreferencesModal.json b/platform/i18n/src/locales/ja-JP/UserPreferencesModal.json index df544cfa03..b7215966cb 100644 --- a/platform/i18n/src/locales/ja-JP/UserPreferencesModal.json +++ b/platform/i18n/src/locales/ja-JP/UserPreferencesModal.json @@ -3,4 +3,4 @@ "Reset to Defaults": "$t(Buttons:Reset to Defaults)", "Save": "$t(Buttons:Save)", "User Preferences": "ユーザプレファレンス" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/nl/Buttons.json b/platform/i18n/src/locales/nl/Buttons.json index 6748f63f0f..0443cffd18 100644 --- a/platform/i18n/src/locales/nl/Buttons.json +++ b/platform/i18n/src/locales/nl/Buttons.json @@ -3,4 +3,4 @@ "More": "Meer", "Pan": "Pan", "Zoom": "Inzoomen" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/nl/Common.json b/platform/i18n/src/locales/nl/Common.json index 5e64c60395..d35764a7f4 100644 --- a/platform/i18n/src/locales/nl/Common.json +++ b/platform/i18n/src/locales/nl/Common.json @@ -1,3 +1,3 @@ { "More": "Meer" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/nl/Header.json b/platform/i18n/src/locales/nl/Header.json index 482d3c72e7..38995e7b62 100644 --- a/platform/i18n/src/locales/nl/Header.json +++ b/platform/i18n/src/locales/nl/Header.json @@ -4,4 +4,4 @@ "Options": "Opties", "Preferences": "Voorkeuren", "Study list": "Studie Overzicht" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/AboutModal.json b/platform/i18n/src/locales/pt-BR/AboutModal.json index e563303e2e..23ccac43ab 100644 --- a/platform/i18n/src/locales/pt-BR/AboutModal.json +++ b/platform/i18n/src/locales/pt-BR/AboutModal.json @@ -11,4 +11,4 @@ "Value": "Valor", "Version Information": "Informação da Versão", "Visit the forum": "Visite o fórum" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/Buttons.json b/platform/i18n/src/locales/pt-BR/Buttons.json index 6fe4b71a5c..0e910aed10 100644 --- a/platform/i18n/src/locales/pt-BR/Buttons.json +++ b/platform/i18n/src/locales/pt-BR/Buttons.json @@ -40,4 +40,4 @@ "Stop": "Parar", "Themes": "Temas", "Zoom": "Zoom" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/CineDialog.json b/platform/i18n/src/locales/pt-BR/CineDialog.json index edbf25be66..d4d653efa7 100644 --- a/platform/i18n/src/locales/pt-BR/CineDialog.json +++ b/platform/i18n/src/locales/pt-BR/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Pular para a primeira imagem", "Skip to last image": "Pular para a última imagem", "fps": "fps" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/Common.json b/platform/i18n/src/locales/pt-BR/Common.json index bf6631668d..e50b07e3d2 100644 --- a/platform/i18n/src/locales/pt-BR/Common.json +++ b/platform/i18n/src/locales/pt-BR/Common.json @@ -8,4 +8,4 @@ "Previous": "Anterior", "Reset": "Restaurar", "Stop": "Stop" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/DatePicker.json b/platform/i18n/src/locales/pt-BR/DatePicker.json index a8dbd03e53..dbd86e2fc3 100644 --- a/platform/i18n/src/locales/pt-BR/DatePicker.json +++ b/platform/i18n/src/locales/pt-BR/DatePicker.json @@ -2,4 +2,4 @@ "Clear dates": "Limpar datas", "End Date": "Data Final", "Start Date": "Data Inicial" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/Header.json b/platform/i18n/src/locales/pt-BR/Header.json index fdd6f0d8f9..8f292e02aa 100644 --- a/platform/i18n/src/locales/pt-BR/Header.json +++ b/platform/i18n/src/locales/pt-BR/Header.json @@ -5,4 +5,4 @@ "Options": "Opções", "Preferences": "Preferências", "Study list": "Lista de estudos" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/Messages.json b/platform/i18n/src/locales/pt-BR/Messages.json new file mode 100644 index 0000000000..649b86350b --- /dev/null +++ b/platform/i18n/src/locales/pt-BR/Messages.json @@ -0,0 +1,15 @@ +{ + "1": "Série sem imagens.", + "2": "Série nao possui informação de posição.", + "3": "Serie não é reconstruível.", + "4": "Série nulti frame não possui informação de medidas.", + "5": "Série multi frame não possui informação de orientação.", + "6": "Série multi frame não possui informação de posição.", + "7": "Série não possui algumas imagens.", + "8": "Série possui espaçamento irregular.", + "9": "Série possui dimensões inconsistentes entre frames.", + "10": "Série possui frames com componentes inconsistentes.", + "11": "Série possui frames com orientações inconsistentes.", + "12": "Série possui informação de posição inconsistentes.", + "13": "Série não suportada." +} diff --git a/platform/i18n/src/locales/pt-BR/UserPreferencesModal.json b/platform/i18n/src/locales/pt-BR/UserPreferencesModal.json index f87b3ec41a..046be3c074 100644 --- a/platform/i18n/src/locales/pt-BR/UserPreferencesModal.json +++ b/platform/i18n/src/locales/pt-BR/UserPreferencesModal.json @@ -5,4 +5,4 @@ "Save": "Salvar", "SaveMessage": "Preferências salvas", "User Preferences": "Preferências do Usuário" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/pt-BR/index.js b/platform/i18n/src/locales/pt-BR/index.js index 0695b6869f..2dcc04c2b1 100644 --- a/platform/i18n/src/locales/pt-BR/index.js +++ b/platform/i18n/src/locales/pt-BR/index.js @@ -6,6 +6,7 @@ import DatePicker from './DatePicker.json'; import Header from './Header.json'; import UserPreferencesModal from './UserPreferencesModal.json'; import MeasurementTable from './MeasurementTable.json'; +import Messages from './Messages.json'; export default { 'pt-BR': { @@ -17,5 +18,6 @@ export default { Header, UserPreferencesModal, MeasurementTable, + Messages, }, }; diff --git a/platform/i18n/src/locales/test-LNG/AboutModal.json b/platform/i18n/src/locales/test-LNG/AboutModal.json index 5ee8b5e242..55e0144c90 100644 --- a/platform/i18n/src/locales/test-LNG/AboutModal.json +++ b/platform/i18n/src/locales/test-LNG/AboutModal.json @@ -11,4 +11,4 @@ "Value": "Value", "Version Information": "Version Information", "Visit the forum": "Visit the forum" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/test-LNG/CineDialog.json b/platform/i18n/src/locales/test-LNG/CineDialog.json index b9f6667a75..48398269ce 100644 --- a/platform/i18n/src/locales/test-LNG/CineDialog.json +++ b/platform/i18n/src/locales/test-LNG/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Skip to first $t(Common:Image)", "Skip to last image": "Skip to last $t(Common:Image)", "fps": "fps" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/test-LNG/DatePicker.json b/platform/i18n/src/locales/test-LNG/DatePicker.json index e2eb258dec..24187a1cf4 100644 --- a/platform/i18n/src/locales/test-LNG/DatePicker.json +++ b/platform/i18n/src/locales/test-LNG/DatePicker.json @@ -2,4 +2,4 @@ "Clear dates": "Clear dates", "End Date": "End Date", "Start Date": "Start Date" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/test-LNG/ViewportDownloadForm.json b/platform/i18n/src/locales/test-LNG/ViewportDownloadForm.json index cd1624b4b1..85001a4219 100644 --- a/platform/i18n/src/locales/test-LNG/ViewportDownloadForm.json +++ b/platform/i18n/src/locales/test-LNG/ViewportDownloadForm.json @@ -11,4 +11,4 @@ "minHeightError": "The minimum valid height is 100px.", "minWidthError": "The minimum valid width is 100px.", "showAnnotations": "Show Annotations" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/test-LNG/index.js b/platform/i18n/src/locales/test-LNG/index.js index 315deb5d36..f39132793b 100644 --- a/platform/i18n/src/locales/test-LNG/index.js +++ b/platform/i18n/src/locales/test-LNG/index.js @@ -32,6 +32,6 @@ export default { PatientInfo, Modes, SidePanel, - Modals + Modals, }, }; diff --git a/platform/i18n/src/locales/vi/Buttons.json b/platform/i18n/src/locales/vi/Buttons.json index 1d3f15b8c6..ea378b41a8 100644 --- a/platform/i18n/src/locales/vi/Buttons.json +++ b/platform/i18n/src/locales/vi/Buttons.json @@ -39,4 +39,4 @@ "Stop": "$t(Common:Stop)", "Themes": "Giao diện", "Zoom": "Thu phóng" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/vi/CineDialog.json b/platform/i18n/src/locales/vi/CineDialog.json index 6222d6406f..d2e7366be5 100644 --- a/platform/i18n/src/locales/vi/CineDialog.json +++ b/platform/i18n/src/locales/vi/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "Bỏ qua đến đầu $t(Common:Image)", "Skip to last image": "Bỏ qua đến cuối $t(Common:Image)", "fps": "fps" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/vi/Common.json b/platform/i18n/src/locales/vi/Common.json index 15f6d3dfeb..5ae2c4c025 100644 --- a/platform/i18n/src/locales/vi/Common.json +++ b/platform/i18n/src/locales/vi/Common.json @@ -12,4 +12,4 @@ "Show": "Hiển thị", "Stop": "Dừng", "StudyDate": "Ngày chụp" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/vi/Header.json b/platform/i18n/src/locales/vi/Header.json index 6d28ae1c93..07ffb42b91 100644 --- a/platform/i18n/src/locales/vi/Header.json +++ b/platform/i18n/src/locales/vi/Header.json @@ -5,4 +5,4 @@ "Options": "Lựa chọn", "Preferences": "Thiết lập", "Study list": "Danh sách" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/vi/StudyList.json b/platform/i18n/src/locales/vi/StudyList.json index a818f95980..50acf54b68 100644 --- a/platform/i18n/src/locales/vi/StudyList.json +++ b/platform/i18n/src/locales/vi/StudyList.json @@ -7,4 +7,4 @@ "StudyDate": "Ngày chụp", "StudyDescription": "Diễn giải", "StudyList": "Danh sách" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/vi/UserPreferencesModal.json b/platform/i18n/src/locales/vi/UserPreferencesModal.json index 087801611c..f5474c9e5e 100644 --- a/platform/i18n/src/locales/vi/UserPreferencesModal.json +++ b/platform/i18n/src/locales/vi/UserPreferencesModal.json @@ -3,4 +3,4 @@ "Reset to Defaults": "$t(Buttons:Reset to Defaults)", "Save": "$t(Buttons:Save)", "User Preferences": "Thiết lập theo người dùng" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/Buttons.json b/platform/i18n/src/locales/zh/Buttons.json index 38efe7d087..d41174eb57 100644 --- a/platform/i18n/src/locales/zh/Buttons.json +++ b/platform/i18n/src/locales/zh/Buttons.json @@ -1,11 +1,14 @@ { "Acquired": "已获取", "Angle": "角度", + "Annotation": "注释", "Axial": "轴状面", "Bidirectional": "双向", "Brush": "橡皮擦", + "Cine": "播放动画", "CINE": "播放动画", "Cancel": "取消", + "Capture": "下载", "Circle": "圆", "Clear": "清除", "Coronal": "冠状面", @@ -13,8 +16,10 @@ "Ellipse": "椭圆", "Elliptical": "椭圆的", "Flip H": "左右翻转", + "Flip Horizontally": "左右翻转", "Flip V": "上下翻转", "Freehand": "自由画线", + "Grid Layout": "窗口布局", "Invert": "灰度反转", "Layout": "显示窗口", "Length": "长度", @@ -23,6 +28,8 @@ "Manual": "手动", "Measurements": "测量", "More": "更多", + "More Tools": "更多工具", + "More Measure Tools": "更多测量工具", "Next": "下一个", "Pan": "移动", "Play": "播放", @@ -30,13 +37,15 @@ "Probe": "探针", "ROI Window": "选择对比度", "Rectangle": "矩形", + "Reference Lines": "参考线", "Reset": "复原", "Reset to Defaults": "返回默认", "Rotate Right": "顺时针旋转", "Sagittal": "矢状面", "Save": "保存", "Stack Scroll": "滑动切换图层", + "Stack Image Sync": "影像联动", "Stop": "停止", "Themes": "主题", "Zoom": "放大" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/CineDialog.json b/platform/i18n/src/locales/zh/CineDialog.json index e40ae5b594..6bdce24cd1 100644 --- a/platform/i18n/src/locales/zh/CineDialog.json +++ b/platform/i18n/src/locales/zh/CineDialog.json @@ -5,4 +5,4 @@ "Skip to first image": "跳转到第一个图像", "Skip to last image": "跳转到最后一个图像", "fps": "帧率" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/Common.json b/platform/i18n/src/locales/zh/Common.json index 896cf558e3..10ce931e9d 100644 --- a/platform/i18n/src/locales/zh/Common.json +++ b/platform/i18n/src/locales/zh/Common.json @@ -12,4 +12,4 @@ "Show": "显示", "Stop": "停止", "StudyDate": "时间" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/Header.json b/platform/i18n/src/locales/zh/Header.json index 5f64c3a18a..1ecb90e90e 100644 --- a/platform/i18n/src/locales/zh/Header.json +++ b/platform/i18n/src/locales/zh/Header.json @@ -5,4 +5,4 @@ "Options": "选项", "Preferences": "偏好", "Study list": "研究列表" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/MeasurementTable.json b/platform/i18n/src/locales/zh/MeasurementTable.json index 3ff705195b..d9a11da2dc 100644 --- a/platform/i18n/src/locales/zh/MeasurementTable.json +++ b/platform/i18n/src/locales/zh/MeasurementTable.json @@ -6,4 +6,4 @@ "NonTargets": "非靶向", "Relabel": "重新标记", "Targets": "靶向" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/StudyList.json b/platform/i18n/src/locales/zh/StudyList.json index 1c5888c7fc..156b103e3b 100644 --- a/platform/i18n/src/locales/zh/StudyList.json +++ b/platform/i18n/src/locales/zh/StudyList.json @@ -5,4 +5,4 @@ "StudyDate": "检查日期", "StudyDescription": "描述", "StudyList": "检查列表" -} \ No newline at end of file +} diff --git a/platform/i18n/src/locales/zh/UserPreferencesModal.json b/platform/i18n/src/locales/zh/UserPreferencesModal.json index 865b7e3335..e71386abc6 100644 --- a/platform/i18n/src/locales/zh/UserPreferencesModal.json +++ b/platform/i18n/src/locales/zh/UserPreferencesModal.json @@ -3,4 +3,4 @@ "Reset to Defaults": "返回默认", "Save": "保存", "User Preferences": "用户偏好" -} \ No newline at end of file +} diff --git a/platform/i18n/src/utils.js b/platform/i18n/src/utils.js index 245013dac0..93e368cd11 100644 --- a/platform/i18n/src/utils.js +++ b/platform/i18n/src/utils.js @@ -57,7 +57,7 @@ const languagesMap = { 'test-LNG': 'Test Language', }; -const getLanguageLabel = (language) => { +const getLanguageLabel = language => { return languagesMap[language]; }; diff --git a/platform/i18n/writeLocaleIndexFiles.js b/platform/i18n/writeLocaleIndexFiles.js index 9996c0efbd..12e7c3ad62 100644 --- a/platform/i18n/writeLocaleIndexFiles.js +++ b/platform/i18n/writeLocaleIndexFiles.js @@ -21,7 +21,9 @@ const directories = getDirectories(directoryPath); function writeFile(filepath, name, content) { fs.writeFile(path.join(filepath, name), content, err => { - if (err) throw err; + if (err) { + throw err; + } }); } diff --git a/platform/ui/.storybook/main.ts b/platform/ui/.storybook/main.ts index 5bed58d84b..54b817b1ae 100644 --- a/platform/ui/.storybook/main.ts +++ b/platform/ui/.storybook/main.ts @@ -1,12 +1,12 @@ -import path from 'path'; +import path, { dirname, join } from 'path'; import remarkGfm from 'remark-gfm'; import type { StorybookConfig } from '@storybook/react-webpack5'; const config: StorybookConfig = { stories: ['../src/**/*.stories.@(mdx)'], addons: [ - '@storybook/addon-links', - '@storybook/addon-essentials', + getAbsolutePath('@storybook/addon-links'), + getAbsolutePath('@storybook/addon-essentials'), // Other addons go here { name: '@storybook/addon-docs', @@ -19,9 +19,9 @@ const config: StorybookConfig = { }, }, ], - core: { builder: '@storybook/builder-webpack5' }, + core: {}, framework: { - name: '@storybook/react-webpack5', + name: getAbsolutePath('@storybook/react-webpack5'), options: {}, }, docs: { @@ -58,9 +58,7 @@ const config: StorybookConfig = { }); // Default rule for images /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/ - const fileLoaderRule = config.module.rules.find( - rule => rule.test && rule.test.test('.svg') - ); + const fileLoaderRule = config.module.rules.find(rule => rule.test && rule.test.test('.svg')); fileLoaderRule.exclude = /\.svg$/; config.module.rules.push({ @@ -93,3 +91,7 @@ const config: StorybookConfig = { }; export default config; + +function getAbsolutePath(value: string): any { + return dirname(require.resolve(join(value, 'package.json'))); +} diff --git a/platform/ui/.storybook/manager-head.html b/platform/ui/.storybook/manager-head.html index fa916fcbe8..9bca7f170a 100644 --- a/platform/ui/.storybook/manager-head.html +++ b/platform/ui/.storybook/manager-head.html @@ -21,7 +21,9 @@ >