Compare commits
79
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a12320083 | ||
|
|
813ee5ed4d | ||
|
|
6085b5f733 | ||
|
|
ffe12789dd | ||
|
|
0b68429dfb | ||
|
|
b747ea5144 | ||
|
|
caadf089df | ||
|
|
e0fc69c181 | ||
|
|
2b9765304f | ||
|
|
57730483e9 | ||
|
|
731b64e321 | ||
|
|
e97a09b0f4 | ||
|
|
f63d8a73d8 | ||
|
|
f10adf82f4 | ||
|
|
6174109d5a | ||
|
|
244e822afc | ||
|
|
cbdeeae597 | ||
|
|
7f6de72e8e | ||
|
|
1f514e5f1b | ||
|
|
f3fed511b9 | ||
|
|
31796d272c | ||
|
|
b41e6f93f3 | ||
|
|
882a45d398 | ||
|
|
fb0cd9355f | ||
|
|
2b12193020 | ||
|
|
9a626a2064 | ||
|
|
903777044a | ||
|
|
5e4725860d | ||
|
|
670470119f | ||
|
|
0d4f90f3cd | ||
|
|
43dd87c2c1 | ||
|
|
98f8583fba | ||
|
|
97a9a505ca | ||
|
|
bb74f3a761 | ||
|
|
aa33397fe6 | ||
|
|
8ae731fe4c | ||
|
|
add3416e11 | ||
|
|
136969bf18 | ||
|
|
e3e039ad47 | ||
|
|
0797eff0b7 | ||
|
|
55a5f04923 | ||
|
|
ed9e4d6751 | ||
|
|
b6119f8e29 | ||
|
|
2e38f4b2e4 | ||
|
|
e03abc45f5 | ||
|
|
f03d64ce39 | ||
|
|
e895bfb1aa | ||
|
|
17358c3f4b | ||
|
|
ec7337e801 | ||
|
|
538ea2516d | ||
|
|
fe687cd2f9 | ||
|
|
bdb9af5c4b | ||
|
|
db0f751a68 | ||
|
|
08fdb10f6d | ||
|
|
95c18b4986 | ||
|
|
e684558783 | ||
|
|
9104e7be50 | ||
|
|
81ad25b39f | ||
|
|
54d2cde6da | ||
|
|
9edc366ff7 | ||
|
|
79c10bb424 | ||
|
|
d8055e458f | ||
|
|
5c04e34c8f | ||
|
|
c59c5b3f14 | ||
|
|
c4adcfd39e | ||
|
|
0fbaf95971 | ||
|
|
a2a7e05e7d | ||
|
|
9da8647c84 | ||
|
|
e62f5f8dd2 | ||
|
|
dc6e9d6bb5 | ||
|
|
84a097212b | ||
|
|
2381810ba9 | ||
|
|
31b1adfa59 | ||
|
|
cd28877ed8 | ||
|
|
e81a39f5be | ||
|
|
9a2cf70504 | ||
|
|
d70eae3eb0 | ||
|
|
8481e94d4d | ||
|
|
19b22c39ae |
No files matched your search
@@ -62,7 +62,7 @@ echo "Done generating documentation output"
|
|||||||
echo 'STARTING PUBLISH'
|
echo 'STARTING PUBLISH'
|
||||||
|
|
||||||
# WILL ALWAYS FAIL IF INITIATED FROM PR BRANCH
|
# WILL ALWAYS FAIL IF INITIATED FROM PR BRANCH
|
||||||
./node_modules/.bin/gh-pages \
|
npx gh-pages \
|
||||||
--silent \
|
--silent \
|
||||||
--repo https://$GITHUB_TOKEN@github.com/OHIF/Viewers.git \
|
--repo https://$GITHUB_TOKEN@github.com/OHIF/Viewers.git \
|
||||||
--message 'Autogenerated Message: [ci skip]' \
|
--message 'Autogenerated Message: [ci skip]' \
|
||||||
|
|||||||
+228
-245
@@ -14,52 +14,53 @@ version: 2.1
|
|||||||
orbs:
|
orbs:
|
||||||
codecov: codecov/codecov@1.0.5
|
codecov: codecov/codecov@1.0.5
|
||||||
cypress: cypress-io/cypress@1.11.0
|
cypress: cypress-io/cypress@1.11.0
|
||||||
|
executors:
|
||||||
|
# Custom executor to override Cypress config
|
||||||
|
deploy-to-prod-executor:
|
||||||
|
docker:
|
||||||
|
- image: 'cypress/browsers:node10.16.0-chrome77'
|
||||||
|
environment:
|
||||||
|
CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
|
||||||
|
|
||||||
defaults: &defaults
|
defaults: &defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:12.9.1
|
- image: circleci/node:12.9.1
|
||||||
environment:
|
environment:
|
||||||
TERM: xterm # Enable colors in term
|
TERM: xterm # Enable colors in term
|
||||||
|
QUICK_BUILD: true
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
###
|
||||||
|
# Workflow: PR_CHECKS
|
||||||
|
###
|
||||||
UNIT_TESTS:
|
UNIT_TESTS:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
steps:
|
steps:
|
||||||
# Update yarn
|
# Update yarn
|
||||||
- run: yarn -v
|
- run: yarn -v
|
||||||
#- run: rm -rf ~/.yarn && npm i -g yarn && yarn -v
|
|
||||||
|
|
||||||
# Enable yarn workspaces
|
|
||||||
- run: yarn config set workspaces-experimental true
|
|
||||||
|
|
||||||
# Checkout code and ALL Git Tags
|
# Checkout code and ALL Git Tags
|
||||||
- checkout:
|
- checkout:
|
||||||
post:
|
post:
|
||||||
- git fetch --all
|
- git fetch --all
|
||||||
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
name: Restore Yarn and Cypress Package Cache
|
name: Restore Yarn and Cypress Package Cache
|
||||||
keys:
|
keys:
|
||||||
# when lock file changes, use increasingly general patterns to restore cache
|
# when lock file changes, use increasingly general patterns to restore cache
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
- yarn-packages-
|
- yarn-packages-
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile
|
command: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
name: Save Yarn Package Cache
|
name: Save Yarn Package Cache
|
||||||
paths:
|
paths:
|
||||||
- ~/.cache ## Cache yarn and Cypress
|
- ~/.cache ## Cache yarn and Cypress
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
|
||||||
# RUN TESTS
|
# RUN TESTS
|
||||||
- run:
|
- run:
|
||||||
name: 'JavaScript Test Suite'
|
name: 'JavaScript Test Suite'
|
||||||
command: yarn run test:unit:ci
|
command: yarn run test:unit:ci
|
||||||
|
|
||||||
# PLATFORM/VIEWER
|
# PLATFORM/VIEWER
|
||||||
- run:
|
- run:
|
||||||
name: 'VIEWER: Combine report output'
|
name: 'VIEWER: Combine report output'
|
||||||
@@ -73,7 +74,6 @@ jobs:
|
|||||||
- codecov/upload:
|
- codecov/upload:
|
||||||
file: '/home/circleci/repo/platform/viewer/coverage/reports'
|
file: '/home/circleci/repo/platform/viewer/coverage/reports'
|
||||||
flags: 'viewer'
|
flags: 'viewer'
|
||||||
|
|
||||||
# PLATFORM/CORE
|
# PLATFORM/CORE
|
||||||
- run:
|
- run:
|
||||||
name: 'CORE: Combine report output'
|
name: 'CORE: Combine report output'
|
||||||
@@ -88,145 +88,9 @@ jobs:
|
|||||||
file: '/home/circleci/repo/platform/core/coverage/reports'
|
file: '/home/circleci/repo/platform/core/coverage/reports'
|
||||||
flags: 'core'
|
flags: 'core'
|
||||||
|
|
||||||
UNIT_TESTS_PERSIST_WORKSPACE:
|
###
|
||||||
<<: *defaults
|
# Workflow: PR_OPTIONAL_DOCKER_PUBLISH
|
||||||
steps:
|
###
|
||||||
# Update yarn
|
|
||||||
#- run: rm -rf ~/.yarn && npm i -g yarn && yarn -v
|
|
||||||
|
|
||||||
# Enable yarn workspaces
|
|
||||||
- run: yarn config set workspaces-experimental true
|
|
||||||
|
|
||||||
# Checkout code and ALL Git Tags
|
|
||||||
- checkout:
|
|
||||||
post:
|
|
||||||
- git fetch --all
|
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
paths:
|
|
||||||
- ~/.cache ## Cache yarn and Cypress
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
|
|
||||||
# RUN TESTS
|
|
||||||
- run:
|
|
||||||
name: 'JavaScript Test Suite'
|
|
||||||
command: yarn run test:unit:ci
|
|
||||||
|
|
||||||
# PLATFORM/VIEWER
|
|
||||||
- run:
|
|
||||||
name: 'VIEWER: Combine report output'
|
|
||||||
command: |
|
|
||||||
viewerCov="/home/circleci/repo/platform/viewer/coverage"
|
|
||||||
touch "${viewerCov}/reports"
|
|
||||||
cat "${viewerCov}/clover.xml" >> "${viewerCov}/reports"
|
|
||||||
echo "\<<\<<\<< EOF" >> "${viewerCov}/reports"
|
|
||||||
cat "${viewerCov}/lcov.info" >>"${viewerCov}/reports"
|
|
||||||
echo "\<<\<<\<< EOF" >> "${viewerCov}/reports"
|
|
||||||
- codecov/upload:
|
|
||||||
file: '/home/circleci/repo/platform/viewer/coverage/reports'
|
|
||||||
flags: 'viewer'
|
|
||||||
|
|
||||||
# PLATFORM/CORE
|
|
||||||
- run:
|
|
||||||
name: 'CORE: Combine report output'
|
|
||||||
command: |
|
|
||||||
coreCov="/home/circleci/repo/platform/core/coverage"
|
|
||||||
touch "${coreCov}/reports"
|
|
||||||
cat "${coreCov}/clover.xml" >> "${coreCov}/reports"
|
|
||||||
echo "\<<\<<\<< EOF" >> "${coreCov}/reports"
|
|
||||||
cat "${coreCov}/lcov.info" >> "${coreCov}/reports"
|
|
||||||
echo "\<<\<<\<< EOF" >> "${coreCov}/reports"
|
|
||||||
- codecov/upload:
|
|
||||||
file: '/home/circleci/repo/platform/core/coverage/reports'
|
|
||||||
flags: 'core'
|
|
||||||
|
|
||||||
# Persist :+1:
|
|
||||||
- persist_to_workspace: &persist_to_workspace
|
|
||||||
root: ~/repo
|
|
||||||
paths: .
|
|
||||||
|
|
||||||
NPM_PUBLISH:
|
|
||||||
<<: *defaults
|
|
||||||
steps:
|
|
||||||
- attach_workspace:
|
|
||||||
at: ~/repo
|
|
||||||
- run:
|
|
||||||
name: Avoid hosts unknown for github
|
|
||||||
command:
|
|
||||||
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking
|
|
||||||
no\n" > ~/.ssh/config
|
|
||||||
- run: git config --global user.email "danny.ri.brown+ohif-bot@gmail.com"
|
|
||||||
- run: git config --global user.name "ohif-bot"
|
|
||||||
- run:
|
|
||||||
name: Authenticate with registry
|
|
||||||
command:
|
|
||||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
|
||||||
- run: npx lerna version
|
|
||||||
- run: npx lerna publish from-package
|
|
||||||
|
|
||||||
# Persist :+1:
|
|
||||||
- persist_to_workspace:
|
|
||||||
root: ~/repo
|
|
||||||
paths: .
|
|
||||||
|
|
||||||
DOCS_PUBLISH:
|
|
||||||
<<: *defaults
|
|
||||||
steps:
|
|
||||||
- attach_workspace:
|
|
||||||
at: ~/repo
|
|
||||||
- run:
|
|
||||||
name: Avoid hosts unknown for github
|
|
||||||
command:
|
|
||||||
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking
|
|
||||||
no\n" > ~/.ssh/config
|
|
||||||
- run: git config --global user.email "danny.ri.brown+ohif-bot@gmail.com"
|
|
||||||
- run: git config --global user.name "ohif-bot"
|
|
||||||
- run: yarn global add gitbook-cli
|
|
||||||
- run: yarn global add gh-pages
|
|
||||||
- run: chmod +x ~/repo/.circleci/build-and-publish-docs.sh
|
|
||||||
- run: ~/repo/.circleci/build-and-publish-docs.sh
|
|
||||||
|
|
||||||
DOCKER_MASTER_PUBLISH:
|
|
||||||
<<: *defaults
|
|
||||||
steps:
|
|
||||||
- attach_workspace:
|
|
||||||
at: ~/repo
|
|
||||||
- setup_remote_docker:
|
|
||||||
docker_layer_caching: false
|
|
||||||
- run:
|
|
||||||
name: Build and push Docker image
|
|
||||||
command: |
|
|
||||||
# This file will exist if a new version was published by
|
|
||||||
# our `semantic-release` command in the previous job
|
|
||||||
if [[ ! -e platform/viewer/success_version.txt ]]; then
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
# Remove npm config
|
|
||||||
rm -f ./.npmrc
|
|
||||||
# Set our version number using vars
|
|
||||||
export IMAGE_VERSION=$(cat platform/viewer/success_version.txt)
|
|
||||||
export IMAGE_VERSION_FULL=v$IMAGE_VERSION.${CIRCLE_BUILD_NUM}
|
|
||||||
echo $IMAGE_VERSION
|
|
||||||
echo $IMAGE_VERSION_FULL
|
|
||||||
# Build our image, auth, and push
|
|
||||||
docker build --tag ohif/$IMAGE_NAME:$IMAGE_VERSION_FULL --tag ohif/$IMAGE_NAME:latest .
|
|
||||||
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
|
|
||||||
docker push ohif/$IMAGE_NAME:$IMAGE_VERSION_FULL
|
|
||||||
docker push ohif/$IMAGE_NAME:latest
|
|
||||||
fi
|
|
||||||
|
|
||||||
DOCKER_PR_PUBLISH:
|
DOCKER_PR_PUBLISH:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
steps:
|
steps:
|
||||||
@@ -264,55 +128,190 @@ jobs:
|
|||||||
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
|
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
|
||||||
docker push ohif/viewer:PR_BUILD-$CIRCLE_BUILD_NUM
|
docker push ohif/viewer:PR_BUILD-$CIRCLE_BUILD_NUM
|
||||||
|
|
||||||
VIEWER_DEMO_BUILD:
|
###
|
||||||
|
# Workflow: DEPLOY
|
||||||
|
###
|
||||||
|
BUILD:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
|
steps:
|
||||||
|
# Checkout code and ALL Git Tags
|
||||||
|
- checkout:
|
||||||
|
post:
|
||||||
|
- git fetch --all
|
||||||
|
- 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
|
||||||
|
- save_cache:
|
||||||
|
name: Save Yarn Package Cache
|
||||||
|
paths:
|
||||||
|
- ~/.cache ## Cache yarn and Cypress
|
||||||
|
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
# Build & Test
|
||||||
|
- run:
|
||||||
|
name: 'Build the OHIF Viewer'
|
||||||
|
command: yarn run build
|
||||||
|
no_output_timeout: 45m
|
||||||
|
# - run:
|
||||||
|
# name: 'Upload SourceMaps, Send Deploy Notification'
|
||||||
|
# command: |
|
||||||
|
# # export FILE_1=$(find ./build/static/js -type f -name "2.*.js" -exec basename {} \;)
|
||||||
|
# # export FILE_MAIN=$(find ./build/static/js -type f -name "main.*.js" -exec basename {} \;)
|
||||||
|
# # export FILE_RUNTIME_MAIN=$(find ./build/static/js -type f -name "runtime~main.*.js" -exec basename {} \;)
|
||||||
|
# # curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_1.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_1
|
||||||
|
# # curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_MAIN.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_MAIN
|
||||||
|
# # curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_RUNTIME_MAIN.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_RUNTIME_MAIN
|
||||||
|
# curl --request POST https://api.rollbar.com/api/1/deploy/ -F access_token=$ROLLBAR_TOKEN -F environment=$GOOGLE_STORAGE_BUCKET -F revision=$CIRCLE_SHA1 -F local_username=CircleCI
|
||||||
|
# Persist :+1:
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: ~/repo
|
||||||
|
paths:
|
||||||
|
- platform/viewer/dist
|
||||||
|
- netlify.toml
|
||||||
|
- .netlify
|
||||||
|
|
||||||
|
DEPLOY_TO_DEV:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:12.9.1
|
||||||
|
environment:
|
||||||
|
TERM: xterm
|
||||||
|
NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77
|
||||||
|
working_directory: ~/repo
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: ~/repo
|
at: ~/repo
|
||||||
# Build & Test
|
- run: cd .netlify && npm install
|
||||||
- run:
|
- run:
|
||||||
name: 'Build Demo Site'
|
cp .netlify/deploy-workflow/_redirects platform/viewer/dist/_redirects
|
||||||
command: yarn run build:demo
|
- run: cd .netlify && npm run deploy
|
||||||
no_output_timeout: 45m
|
|
||||||
|
DEPLOY_TO_STAGING:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:12.9.1
|
||||||
|
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:
|
- run:
|
||||||
name: 'Upload SourceMaps, Send Deploy Notification'
|
cp .netlify/deploy-workflow/_redirects platform/viewer/dist/_redirects
|
||||||
|
- run: cd .netlify && npm run deploy
|
||||||
|
|
||||||
|
DEPLOY_TO_PRODUCTION:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:12.9.1
|
||||||
|
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/viewer/dist/_redirects
|
||||||
|
- run: cd .netlify && npm run deploy
|
||||||
|
|
||||||
|
###
|
||||||
|
# Workflow: RELEASE
|
||||||
|
###
|
||||||
|
NPM_PUBLISH:
|
||||||
|
<<: *defaults
|
||||||
|
steps:
|
||||||
|
- run: yarn -v
|
||||||
|
# Checkout code and ALL Git Tags
|
||||||
|
- checkout:
|
||||||
|
post:
|
||||||
|
- git fetch --all
|
||||||
|
# Use increasingly general patterns to restore cache
|
||||||
|
- restore_cache:
|
||||||
|
name: Restore Yarn and Cypress Package Cache
|
||||||
|
keys:
|
||||||
|
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
- yarn-packages-
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: yarn install --frozen-lockfile
|
||||||
|
- save_cache:
|
||||||
|
name: Save Yarn Package Cache
|
||||||
|
paths:
|
||||||
|
- ~/.cache/yarn
|
||||||
|
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||||
|
- run:
|
||||||
|
name: Avoid hosts unknown for github
|
||||||
command: |
|
command: |
|
||||||
# export FILE_1=$(find ./build/static/js -type f -name "2.*.js" -exec basename {} \;)
|
rm -rf ~/.ssh
|
||||||
# export FILE_MAIN=$(find ./build/static/js -type f -name "main.*.js" -exec basename {} \;)
|
mkdir ~/.ssh/
|
||||||
# export FILE_RUNTIME_MAIN=$(find ./build/static/js -type f -name "runtime~main.*.js" -exec basename {} \;)
|
echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
|
||||||
# curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_1.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_1
|
git config --global user.email "danny.ri.brown+ohif-bot@gmail.com"
|
||||||
# curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_MAIN.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_MAIN
|
git config --global user.name "ohif-bot"
|
||||||
# curl https://api.rollbar.com/api/1/sourcemap -F source_map=@build/static/js/$FILE_RUNTIME_MAIN.map -F access_token=$ROLLBAR_TOKEN -F version=$CIRCLE_SHA1 -F minified_url=https://$GOOGLE_STORAGE_BUCKET/static/js/$FILE_RUNTIME_MAIN
|
- run:
|
||||||
curl --request POST https://api.rollbar.com/api/1/deploy/ -F access_token=$ROLLBAR_TOKEN -F environment=$GOOGLE_STORAGE_BUCKET -F revision=$CIRCLE_SHA1 -F local_username=CircleCI
|
name: Authenticate with NPM registry
|
||||||
# Persist :+1:
|
command:
|
||||||
|
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
||||||
|
- run: npx lerna version
|
||||||
|
- run: npx lerna publish from-package
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: ~/repo
|
root: ~/repo
|
||||||
paths: .
|
paths: .
|
||||||
|
|
||||||
VIEWER_DEMO_PUBLISH:
|
DOCS_PUBLISH:
|
||||||
working_directory: ~/repo
|
<<: *defaults
|
||||||
docker:
|
steps:
|
||||||
- image: google/cloud-sdk
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Avoid hosts unknown for github
|
||||||
|
command: |
|
||||||
|
rm -rf ~/.ssh
|
||||||
|
mkdir ~/.ssh/
|
||||||
|
echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
|
||||||
|
git config --global user.email "danny.ri.brown+ohif-bot@gmail.com"
|
||||||
|
git config --global user.name "ohif-bot"
|
||||||
|
- run: yarn global add gitbook-cli gh-pages
|
||||||
|
- run: chmod +x ~/repo/.circleci/build-and-publish-docs.sh
|
||||||
|
- run: ~/repo/.circleci/build-and-publish-docs.sh
|
||||||
|
|
||||||
|
DOCKER_MASTER_PUBLISH:
|
||||||
|
<<: *defaults
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: ~/repo
|
at: ~/repo
|
||||||
- setup_remote_docker:
|
- setup_remote_docker:
|
||||||
docker_layer_caching: false
|
docker_layer_caching: false
|
||||||
- run:
|
- run:
|
||||||
name: Deploy latest version to viewer.ohif.org
|
name: Build and push Docker image
|
||||||
command: |
|
command: |
|
||||||
# https://circleci.com/docs/2.0/google-auth/#authorization
|
# This file will exist if a new version was published by
|
||||||
echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=-
|
# our command in the previous job. Created in npm postpublish hook
|
||||||
gcloud --quiet config set project ${GOOGLE_PROJECT_ID}
|
# in the `platform/viewer` project.
|
||||||
gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}
|
if [[ ! -e platform/viewer/success_version.txt ]]; then
|
||||||
# Remove all files in Storage Bucket; Push latest
|
exit 0
|
||||||
gsutil -m rm gs://$GOOGLE_STORAGE_BUCKET/**
|
else
|
||||||
gsutil -m rsync -R platform/viewer/dist gs://$GOOGLE_STORAGE_BUCKET
|
# Remove npm config
|
||||||
|
rm -f ./.npmrc
|
||||||
|
# Set our version number using vars
|
||||||
|
export IMAGE_VERSION=$(cat platform/viewer/success_version.txt)
|
||||||
|
export IMAGE_VERSION_FULL=v$IMAGE_VERSION.${CIRCLE_BUILD_NUM}
|
||||||
|
echo $IMAGE_VERSION
|
||||||
|
echo $IMAGE_VERSION_FULL
|
||||||
|
# Build our image, auth, and push
|
||||||
|
docker build --tag ohif/$IMAGE_NAME:$IMAGE_VERSION_FULL --tag ohif/$IMAGE_NAME:latest .
|
||||||
|
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
|
||||||
|
docker push ohif/$IMAGE_NAME:$IMAGE_VERSION_FULL
|
||||||
|
docker push ohif/$IMAGE_NAME:latest
|
||||||
|
fi
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
# PULL REQUESTS
|
|
||||||
PR_CHECKS:
|
PR_CHECKS:
|
||||||
jobs:
|
jobs:
|
||||||
- UNIT_TESTS:
|
- UNIT_TESTS:
|
||||||
@@ -379,82 +378,66 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
- AWAIT_APPROVAL
|
- AWAIT_APPROVAL
|
||||||
|
|
||||||
# MERGE TO MASTER
|
###
|
||||||
RELEASE:
|
# Our workflow for building, deploying, and promoting builds across our
|
||||||
|
# development, staging, and production environments.
|
||||||
|
###
|
||||||
|
DEPLOY:
|
||||||
jobs:
|
jobs:
|
||||||
- UNIT_TESTS_PERSIST_WORKSPACE:
|
- BUILD:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: master
|
only: master
|
||||||
# E2E: PWA + Persist
|
- DEPLOY_TO_DEV:
|
||||||
- cypress/run:
|
|
||||||
name: 'E2E: PWA'
|
|
||||||
executor: cypress/browsers-chrome76
|
|
||||||
browser: chrome
|
|
||||||
pre-steps:
|
|
||||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
|
||||||
add wait-on' # Use yarn latest
|
|
||||||
yarn: true
|
|
||||||
record: false
|
|
||||||
store_artifacts: true
|
|
||||||
working_directory: platform/viewer
|
|
||||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
|
||||||
start: yarn run test:e2e:serve
|
|
||||||
wait-on: 'http://localhost:3000'
|
|
||||||
command: 'yarn run test:e2e:ci'
|
|
||||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
|
||||||
no-workspace: true # Don't persist workspace
|
|
||||||
post-steps:
|
|
||||||
- store_artifacts:
|
|
||||||
path: platform/viewer/cypress/screenshots
|
|
||||||
- store_test_results:
|
|
||||||
path: cypress/results
|
|
||||||
requires:
|
requires:
|
||||||
- UNIT_TESTS_PERSIST_WORKSPACE
|
- BUILD
|
||||||
# E2E: script-tag
|
- PROMOTE_TO_STAGING:
|
||||||
- cypress/run:
|
type: approval
|
||||||
name: 'E2E: Script Tag'
|
|
||||||
executor: cypress/browsers-chrome76
|
|
||||||
browser: chrome
|
|
||||||
pre-steps:
|
|
||||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
|
||||||
add wait-on' # Use yarn latest
|
|
||||||
yarn: true
|
|
||||||
record: false
|
|
||||||
store_artifacts: false
|
|
||||||
working_directory: platform/viewer
|
|
||||||
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
|
||||||
start: yarn run test:e2e:serve
|
|
||||||
spec: 'cypress/integration/common/**/*'
|
|
||||||
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/viewer/cypress/screenshots
|
|
||||||
- store_test_results:
|
|
||||||
path: cypress/results
|
|
||||||
requires:
|
requires:
|
||||||
- UNIT_TESTS_PERSIST_WORKSPACE
|
- DEPLOY_TO_DEV
|
||||||
# Update NPM
|
- DEPLOY_TO_STAGING:
|
||||||
|
requires:
|
||||||
|
- PROMOTE_TO_STAGING
|
||||||
|
- PROMOTE_TO_PRODUCTION:
|
||||||
|
type: approval
|
||||||
|
requires:
|
||||||
|
- DEPLOY_TO_STAGING
|
||||||
|
- DEPLOY_TO_PRODUCTION:
|
||||||
|
requires:
|
||||||
|
- PROMOTE_TO_PRODUCTION
|
||||||
|
###
|
||||||
|
# Unit and E2E tests have already run for PR_CHECKS
|
||||||
|
# Re-running should not gain us any confidence here
|
||||||
|
###
|
||||||
|
RELEASE:
|
||||||
|
jobs:
|
||||||
- NPM_PUBLISH:
|
- NPM_PUBLISH:
|
||||||
requires:
|
filters:
|
||||||
- 'E2E: Script Tag'
|
branches:
|
||||||
- 'E2E: PWA'
|
only: master
|
||||||
# Update docs.ohif.org
|
|
||||||
- DOCS_PUBLISH:
|
- DOCS_PUBLISH:
|
||||||
requires:
|
filters:
|
||||||
- 'E2E: Script Tag'
|
branches:
|
||||||
- 'E2E: PWA'
|
only: master
|
||||||
- VIEWER_DEMO_BUILD:
|
|
||||||
requires:
|
|
||||||
- 'E2E: Script Tag'
|
|
||||||
- 'E2E: PWA'
|
|
||||||
# Update viewer.ohif.org
|
|
||||||
- VIEWER_DEMO_PUBLISH:
|
|
||||||
requires:
|
|
||||||
- VIEWER_DEMO_BUILD
|
|
||||||
# Update hub.docker.org
|
|
||||||
- DOCKER_MASTER_PUBLISH:
|
- DOCKER_MASTER_PUBLISH:
|
||||||
requires:
|
requires:
|
||||||
- NPM_PUBLISH
|
- NPM_PUBLISH
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Potentially use to E2E test STAGING environment
|
||||||
|
# before publishing to production
|
||||||
|
# - cypress/run:
|
||||||
|
# name: 'E2E: PWA'
|
||||||
|
# executor: deploy-to-prod-executor
|
||||||
|
# browser: chrome
|
||||||
|
# working_directory: platform/viewer
|
||||||
|
# cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||||
|
# yarn: true
|
||||||
|
# record: false
|
||||||
|
# no-workspace: true
|
||||||
|
# store_artifacts: true
|
||||||
|
# post-steps:
|
||||||
|
# - store_artifacts:
|
||||||
|
# path: platform/viewer/cypress/screenshots
|
||||||
|
# - store_test_results:
|
||||||
|
# path: cypress/results
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Specific to our non-deploy-preview deploys
|
||||||
|
# Confgure redirects using netlify.toml
|
||||||
|
|
||||||
|
# PWA Redirect
|
||||||
|
/* /index.html 200
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "root",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10",
|
||||||
|
"npm": ">=6",
|
||||||
|
"yarn": ">=1.16.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"deploy": "netlify deploy --prod --dir ./../platform/viewer/dist"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"netlify-cli": "^2.21.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
[![NPM downloads][npm-downloads-image]][npm-url]
|
[![NPM downloads][npm-downloads-image]][npm-url]
|
||||||
[![Pulls][docker-pulls-img]][docker-image-url]
|
[![Pulls][docker-pulls-img]][docker-image-url]
|
||||||
[![MIT License][license-image]][license-url]
|
[![MIT License][license-image]][license-url]
|
||||||
|
[](https://app.fossa.io/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_shield)
|
||||||
|
|
||||||
[![Netlify Status][netlify-image]][netlify-url]
|
[![Netlify Status][netlify-image]][netlify-url]
|
||||||
[![CircleCI][circleci-image]][circleci-url]
|
[![CircleCI][circleci-image]][circleci-url]
|
||||||
@@ -324,3 +325,6 @@ MIT © [OHIF](https://github.com/OHIF)
|
|||||||
[extension-vtk]: extensions/vtk/README.md
|
[extension-vtk]: extensions/vtk/README.md
|
||||||
[vtk-npm]: https://www.npmjs.com/package/@ohif/extension-vtk
|
[vtk-npm]: https://www.npmjs.com/package/@ohif/extension-vtk
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
|
||||||
|
[](https://app.fossa.io/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_large)
|
||||||
@@ -47,5 +47,6 @@
|
|||||||
|
|
||||||
- [FAQ](frequently-asked-questions.md)
|
- [FAQ](frequently-asked-questions.md)
|
||||||
- [Contributing](contributing/index.md)
|
- [Contributing](contributing/index.md)
|
||||||
|
- [Continuous Integration](continous-integration.md)
|
||||||
- [Browser Support](browser-support.md)
|
- [Browser Support](browser-support.md)
|
||||||
- [Help](help.md)
|
- [Help](help.md)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,109 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<a href="/assets/img/WORKFLOW_PR_CHECKS.png">
|
||||||
|
<img src="/assets/img/WORKFLOW_PR_CHECKS.png" alt="workflow diagram" style="margin: 0 auto; max-width: 500px;" />
|
||||||
|
</a>
|
||||||
|
<div><i>Workflow diagram for PR_CHECKS</i></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### 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`.
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<a href="/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png">
|
||||||
|
<img src="/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png" alt="workflow diagram" style="margin: 0 auto; max-width: 500px;" />
|
||||||
|
</a>
|
||||||
|
<div><i>Workflow diagram for PR_WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH</i></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
> 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.
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<a href="/assets/img/WORKFLOW_DEPLOY.png">
|
||||||
|
<img src="/assets/img/WORKFLOW_DEPLOY.png" alt="workflow diagram" style="margin: 0 auto; max-width: 500px;" />
|
||||||
|
</a>
|
||||||
|
<div><i>Workflow diagram for WORKFLOW_DEPLOY</i></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
| 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/
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<a href="/assets/img/WORKFLOW_RELEASE.png">
|
||||||
|
<img src="/assets/img/WORKFLOW_RELEASE.png" alt="workflow diagram" style="margin: 0 auto; max-width: 500px;" />
|
||||||
|
</a>
|
||||||
|
<div><i>Workflow diagram for WORKFLOW_RELEASE</i></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### HOTFIX
|
||||||
|
|
||||||
|
_Not yet implemented_
|
||||||
|
|
||||||
|
<!--
|
||||||
|
LINKS
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
[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
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
@@ -11,7 +11,10 @@ export default {
|
|||||||
* LIFECYCLE HOOKS
|
* LIFECYCLE HOOKS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
preRegistration(extensionConfiguration) {},
|
preRegistration({
|
||||||
|
servicesManager,
|
||||||
|
configuration: extensionConfiguration,
|
||||||
|
}) {},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MODULE GETTERS
|
* MODULE GETTERS
|
||||||
|
|||||||
@@ -3,6 +3,36 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.5.0...@ohif/extension-cornerstone@1.5.1) (2019-11-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.4.1...@ohif/extension-cornerstone@1.5.0) (2019-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.4.0...@ohif/extension-cornerstone@1.4.1) (2019-11-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Add a fallback metadata provider which pulls metadata from WADO-… ([#1158](https://github.com/OHIF/Viewers/issues/1158)) ([31b1adf](https://github.com/OHIF/Viewers/commit/31b1adfa5993d6c8e3e9c8b03fa9856f2621b037))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.3.1...@ohif/extension-cornerstone@1.4.0) (2019-10-26)
|
# [1.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.3.1...@ohif/extension-cornerstone@1.4.0) (2019-10-26)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone",
|
"name": "@ohif/extension-cornerstone",
|
||||||
"version": "1.4.0",
|
"version": "1.5.1",
|
||||||
"description": "OHIF extension for Cornerstone",
|
"description": "OHIF extension for Cornerstone",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
|
"query-string": "^6.8.3",
|
||||||
"react-cornerstone-viewport": "2.x.x"
|
"react-cornerstone-viewport": "2.x.x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,8 +24,8 @@ export default {
|
|||||||
* @param {object} [configuration={}]
|
* @param {object} [configuration={}]
|
||||||
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
|
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
|
||||||
*/
|
*/
|
||||||
preRegistration(configuration = {}) {
|
preRegistration({ servicesManager, configuration = {} }) {
|
||||||
init(configuration);
|
init({ servicesManager, configuration });
|
||||||
},
|
},
|
||||||
getViewportModule() {
|
getViewportModule() {
|
||||||
return OHIFCornerstoneViewport;
|
return OHIFCornerstoneViewport;
|
||||||
|
|||||||
@@ -2,13 +2,33 @@ import OHIF from '@ohif/core';
|
|||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import csTools from 'cornerstone-tools';
|
import csTools from 'cornerstone-tools';
|
||||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||||
|
import queryString from 'query-string';
|
||||||
|
|
||||||
|
function fallbackMetaDataProvider(type, imageId) {
|
||||||
|
if (!imageId.includes('wado?requestType=WADO')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you call for an WADO-URI imageId and get no
|
||||||
|
// metadata, try reformatting to WADO-RS imageId
|
||||||
|
const qs = queryString.parse(imageId);
|
||||||
|
const wadoRoot = window.store.getState().servers.servers[0].wadoRoot;
|
||||||
|
const wadoRsImageId = `wadors:${wadoRoot}/studies/${qs.studyUID}/series/${
|
||||||
|
qs.seriesUID
|
||||||
|
}/instances/${qs.objectUID}/frames/${qs.frame || 1}`;
|
||||||
|
|
||||||
|
return cornerstone.metaData.get(type, wadoRsImageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this fallback provider with a low priority so it is handled last
|
||||||
|
cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {object} configuration
|
* @param {object} configuration
|
||||||
* @param {Object|Array} configuration.csToolsConfig
|
* @param {Object|Array} configuration.csToolsConfig
|
||||||
*/
|
*/
|
||||||
export default function init(configuration = {}) {
|
export default function init({ servicesManager, configuration = {} }) {
|
||||||
const { csToolsConfig } = configuration;
|
const { csToolsConfig } = configuration;
|
||||||
const { StackManager } = OHIF.utils;
|
const { StackManager } = OHIF.utils;
|
||||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||||
@@ -78,6 +98,7 @@ export default function init(configuration = {}) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
tools.forEach(tool => csTools.addTool(tool));
|
tools.forEach(tool => csTools.addTool(tool));
|
||||||
|
|
||||||
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
|
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
|
||||||
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
|
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
|
||||||
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
|
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
|
||||||
|
|||||||
@@ -3,6 +3,81 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.53.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.8...@ohif/extension-vtk@0.53.9) (2019-11-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.53.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.7...@ohif/extension-vtk@0.53.8) (2019-11-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.53.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.6...@ohif/extension-vtk@0.53.7) (2019-11-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.53.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.5...@ohif/extension-vtk@0.53.6) (2019-11-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.53.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.4...@ohif/extension-vtk@0.53.5) (2019-11-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.53.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.3...@ohif/extension-vtk@0.53.4) (2019-11-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.53.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.2...@ohif/extension-vtk@0.53.3) (2019-11-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* mpr2d vtkjs viewport does not render if range is set to `NaN` values ([#1157](https://github.com/OHIF/Viewers/issues/1157)) ([84a0972](https://github.com/OHIF/Viewers/commit/84a097212babc0f98198b9ced1def9743973a5a8))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.53.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.1...@ohif/extension-vtk@0.53.2) (2019-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.53.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.0...@ohif/extension-vtk@0.53.1) (2019-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [0.53.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.32...@ohif/extension-vtk@0.53.0) (2019-11-06)
|
# [0.53.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.32...@ohif/extension-vtk@0.53.0) (2019-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-vtk",
|
"name": "@ohif/extension-vtk",
|
||||||
"version": "0.53.0",
|
"version": "0.53.9",
|
||||||
"description": "OHIF extension for VTK.js",
|
"description": "OHIF extension for VTK.js",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -52,8 +52,8 @@
|
|||||||
"react-vtkjs-viewport": "^0.3.9"
|
"react-vtkjs-viewport": "^0.3.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ohif/core": "^1.7.0",
|
"@ohif/core": "^1.10.0",
|
||||||
"@ohif/ui": "^0.60.0",
|
"@ohif/ui": "^0.62.4",
|
||||||
"cornerstone-tools": "^4.0.9",
|
"cornerstone-tools": "^4.0.9",
|
||||||
"cornerstone-wado-image-loader": "^3.0.0",
|
"cornerstone-wado-image-loader": "^3.0.0",
|
||||||
"dcmjs": "^0.6.1",
|
"dcmjs": "^0.6.1",
|
||||||
|
|||||||
@@ -65,8 +65,16 @@ class OHIFVTKViewport extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
studies: PropTypes.object,
|
viewportData: PropTypes.shape({
|
||||||
displaySet: PropTypes.object,
|
studies: PropTypes.array,
|
||||||
|
displaySet: PropTypes.shape({
|
||||||
|
studyInstanceUid: PropTypes.string,
|
||||||
|
displaySetInstanceUid: PropTypes.string,
|
||||||
|
sopClassUids: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
sopInstanceUid: PropTypes.string,
|
||||||
|
frameIndex: PropTypes.number,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
viewportIndex: PropTypes.number,
|
viewportIndex: PropTypes.number,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
};
|
};
|
||||||
@@ -175,6 +183,19 @@ class OHIFVTKViewport extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {object} imageDataObject
|
||||||
|
* @param {object} imageDataObject.vtkImageData
|
||||||
|
* @param {object} imageDataObject.imageMetaData0
|
||||||
|
* @param {number} [imageDataObject.imageMetaData0.windowWidth] - The volume's initial windowWidth
|
||||||
|
* @param {number} [imageDataObject.imageMetaData0.windowCenter] - The volume's initial windowCenter
|
||||||
|
* @param {string} imageDataObject.imageMetaData0.modality - CT, MR, PT, etc
|
||||||
|
* @param {string} displaySetInstanceUid
|
||||||
|
* @returns vtkVolumeActor
|
||||||
|
* @memberof OHIFVTKViewport
|
||||||
|
*/
|
||||||
getOrCreateVolume(imageDataObject, displaySetInstanceUid) {
|
getOrCreateVolume(imageDataObject, displaySetInstanceUid) {
|
||||||
if (volumeCache[displaySetInstanceUid]) {
|
if (volumeCache[displaySetInstanceUid]) {
|
||||||
return volumeCache[displaySetInstanceUid];
|
return volumeCache[displaySetInstanceUid];
|
||||||
@@ -183,18 +204,11 @@ class OHIFVTKViewport extends Component {
|
|||||||
const { vtkImageData, imageMetaData0 } = imageDataObject;
|
const { vtkImageData, imageMetaData0 } = imageDataObject;
|
||||||
const { windowWidth, windowCenter, modality } = imageMetaData0;
|
const { windowWidth, windowCenter, modality } = imageMetaData0;
|
||||||
|
|
||||||
let lower;
|
const { lower, upper } = _getRangeFromWindowLevels(
|
||||||
let upper;
|
windowWidth,
|
||||||
|
windowCenter,
|
||||||
if (modality === 'PT') {
|
modality
|
||||||
// For PET just set the range to 0-5 SUV
|
);
|
||||||
lower = 0;
|
|
||||||
upper = 5;
|
|
||||||
} else {
|
|
||||||
lower = windowCenter - windowWidth / 2.0;
|
|
||||||
upper = windowCenter + windowWidth / 2.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const volumeActor = vtkVolume.newInstance();
|
const volumeActor = vtkVolume.newInstance();
|
||||||
const volumeMapper = vtkVolumeMapper.newInstance();
|
const volumeMapper = vtkVolumeMapper.newInstance();
|
||||||
|
|
||||||
@@ -379,4 +393,32 @@ class OHIFVTKViewport extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes window levels and converts them to a range (lower/upper)
|
||||||
|
* for use with VTK RGBTransferFunction
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {number} [width] - the width of our window
|
||||||
|
* @param {number} [center] - the center of our window
|
||||||
|
* @param {string} [modality] - 'PT', 'CT', etc.
|
||||||
|
* @returns { lower, upper } - range
|
||||||
|
*/
|
||||||
|
function _getRangeFromWindowLevels(width, center, modality = undefined) {
|
||||||
|
const levelsAreNotNumbers = isNaN(center) || isNaN(width);
|
||||||
|
|
||||||
|
if (levelsAreNotNumbers) {
|
||||||
|
return { lower: 0, upper: 512 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// For PET just set the range to 0-5 SUV
|
||||||
|
if (modality === 'PT') {
|
||||||
|
return { lower: 0, upper: 5 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
lower: center - width / 2.0,
|
||||||
|
upper: center + width / 2.0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default OHIFVTKViewport;
|
export default OHIFVTKViewport;
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
vtkInteractorStyleMPRCrosshairs,
|
vtkInteractorStyleMPRCrosshairs,
|
||||||
vtkInteractorStyleMPRWindowLevel,
|
vtkInteractorStyleMPRWindowLevel,
|
||||||
vtkInteractorStyleMPRSlice,
|
|
||||||
vtkInteractorStyleMPRRotate,
|
vtkInteractorStyleMPRRotate,
|
||||||
vtkSVGCrosshairsWidget,
|
vtkSVGCrosshairsWidget,
|
||||||
} from 'react-vtkjs-viewport';
|
} from 'react-vtkjs-viewport';
|
||||||
|
|||||||
+2
-14
@@ -8,15 +8,6 @@
|
|||||||
# managed by CircleCI and deployed to our Google Hosting
|
# managed by CircleCI and deployed to our Google Hosting
|
||||||
#
|
#
|
||||||
|
|
||||||
# Settings in the [build] context are global and are applied to all contexts
|
|
||||||
# unless otherwise overridden by more specific contexts.
|
|
||||||
[build]
|
|
||||||
# Directory to change to before starting a build.
|
|
||||||
# This is where we will look for package.json/.nvmrc/etc.
|
|
||||||
base = ""
|
|
||||||
publish = "project/build-output/"
|
|
||||||
|
|
||||||
|
|
||||||
# NODE_VERSION in root `.nvmrc` takes priority
|
# NODE_VERSION in root `.nvmrc` takes priority
|
||||||
# YARN_FLAGS: https://www.netlify.com/docs/build-gotchas/#yarn
|
# YARN_FLAGS: https://www.netlify.com/docs/build-gotchas/#yarn
|
||||||
[build.environment]
|
[build.environment]
|
||||||
@@ -28,10 +19,8 @@
|
|||||||
|
|
||||||
# Production context: all deploys from the Production branch set in your site's
|
# Production context: all deploys from the Production branch set in your site's
|
||||||
# deploy contexts will inherit these settings.
|
# deploy contexts will inherit these settings.
|
||||||
# [context.production]
|
[context.production]
|
||||||
# publish = "project/output/"
|
ignore = "exit 0" # Never build production; We'll let our CI do that
|
||||||
# command = "make publish"
|
|
||||||
# environment = { ACCESS_TOKEN = "super secret", NODE_VERSION = "8.0.1" }
|
|
||||||
|
|
||||||
# Deploy Preview context: all deploys generated from a pull/merge request will
|
# Deploy Preview context: all deploys generated from a pull/merge request will
|
||||||
# inherit these settings.
|
# inherit these settings.
|
||||||
@@ -39,7 +28,6 @@
|
|||||||
base = ""
|
base = ""
|
||||||
publish = ".netlify/www/"
|
publish = ".netlify/www/"
|
||||||
command = "chmod +x .netlify/build-deploy-preview.sh && .netlify/build-deploy-preview.sh"
|
command = "chmod +x .netlify/build-deploy-preview.sh && .netlify/build-deploy-preview.sh"
|
||||||
# command = "yarn config set workspaces-experimental true && chmod +x .netlify/build-deploy-preview.sh && .netlify/build-deploy-preview.sh"
|
|
||||||
|
|
||||||
[[headers]]
|
[[headers]]
|
||||||
# Define which paths this specific [[headers]] block will cover.
|
# Define which paths this specific [[headers]] block will cover.
|
||||||
|
|||||||
@@ -3,6 +3,58 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.10.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.9.1...@ohif/core@1.10.0) (2019-11-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Inject into Extension Modules / improve tests ([f63d8a7](https://github.com/OHIF/Viewers/commit/f63d8a73d867ad9dfd8ee0cad74edce180eb34f0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.9.0...@ohif/core@1.9.1) (2019-11-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.8.0...@ohif/core@1.9.0) (2019-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.7.1...@ohif/core@1.8.0) (2019-11-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.7.0...@ohif/core@1.7.1) (2019-11-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Add a fallback metadata provider which pulls metadata from WADO-… ([#1158](https://github.com/OHIF/Viewers/issues/1158)) ([31b1adf](https://github.com/OHIF/Viewers/commit/31b1adfa5993d6c8e3e9c8b03fa9856f2621b037))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.6.2...@ohif/core@1.7.0) (2019-11-05)
|
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.6.2...@ohif/core@1.7.0) (2019-11-05)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/core",
|
"name": "@ohif/core",
|
||||||
"version": "1.7.0",
|
"version": "1.10.0",
|
||||||
"description": "Generic business logic for web-based medical imaging applications",
|
"description": "Generic business logic for web-based medical imaging applications",
|
||||||
"author": "OHIF Core Team",
|
"author": "OHIF Core Team",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
import hotkeys from './hotkeys';
|
import hotkeys from './hotkeys';
|
||||||
import log from './../log.js';
|
import log from './../log.js';
|
||||||
|
|
||||||
@@ -51,10 +52,11 @@ export class HotkeysManager {
|
|||||||
* @param {Boolean} [isDefaultDefinitions]
|
* @param {Boolean} [isDefaultDefinitions]
|
||||||
*/
|
*/
|
||||||
setHotkeys(hotkeyDefinitions, isDefaultDefinitions = false) {
|
setHotkeys(hotkeyDefinitions, isDefaultDefinitions = false) {
|
||||||
hotkeyDefinitions.forEach(definition => this.registerHotkeys(definition));
|
const definitions = cloneDeep(hotkeyDefinitions);
|
||||||
|
definitions.forEach(definition => this.registerHotkeys(definition));
|
||||||
|
|
||||||
if (isDefaultDefinitions) {
|
if (isDefaultDefinitions) {
|
||||||
this.hotkeyDefaults = hotkeyDefinitions;
|
this.hotkeyDefaults = definitions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import MODULE_TYPES from './MODULE_TYPES.js';
|
|||||||
import log from './../log.js';
|
import log from './../log.js';
|
||||||
|
|
||||||
export default class ExtensionManager {
|
export default class ExtensionManager {
|
||||||
constructor({ commandsManager }) {
|
constructor({ commandsManager, servicesManager }) {
|
||||||
this.modules = {};
|
this.modules = {};
|
||||||
this.registeredExtensionIds = [];
|
this.registeredExtensionIds = [];
|
||||||
this.moduleTypeNames = Object.values(MODULE_TYPES);
|
this.moduleTypeNames = Object.values(MODULE_TYPES);
|
||||||
//
|
//
|
||||||
this._commandsManager = commandsManager;
|
this._commandsManager = commandsManager;
|
||||||
|
this._servicesManager = servicesManager;
|
||||||
|
|
||||||
this.moduleTypeNames.forEach(moduleType => {
|
this.moduleTypeNames.forEach(moduleType => {
|
||||||
this.modules[moduleType] = [];
|
this.modules[moduleType] = [];
|
||||||
@@ -66,7 +67,10 @@ export default class ExtensionManager {
|
|||||||
|
|
||||||
// preRegistrationHook
|
// preRegistrationHook
|
||||||
if (extension.preRegistration) {
|
if (extension.preRegistration) {
|
||||||
extension.preRegistration(configuration);
|
extension.preRegistration({
|
||||||
|
servicesManager: this._servicesManager,
|
||||||
|
configuration,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register Modules
|
// Register Modules
|
||||||
@@ -106,7 +110,9 @@ export default class ExtensionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const extensionModule = getModuleFn();
|
const extensionModule = getModuleFn({
|
||||||
|
servicesManager: this._servicesManager,
|
||||||
|
});
|
||||||
|
|
||||||
if (!extensionModule) {
|
if (!extensionModule) {
|
||||||
log.warn(
|
log.warn(
|
||||||
|
|||||||
@@ -40,6 +40,30 @@ describe('ExtensionManager.js', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('registerExtension()', () => {
|
describe('registerExtension()', () => {
|
||||||
|
it('calls preRegistration() for extension', () => {
|
||||||
|
// SUT
|
||||||
|
const fakeExtension = { one: '1', preRegistration: jest.fn() };
|
||||||
|
extensionManager.registerExtension(fakeExtension);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(fakeExtension.preRegistration.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls preRegistration() passing configuration and servicesManager instance for extension', () => {
|
||||||
|
const configuration = { config: 'Some configuration' };
|
||||||
|
extensionManager._servicesManager = { services: { TestService: {} } };
|
||||||
|
|
||||||
|
// SUT
|
||||||
|
const fakeExtension = { one: '1', preRegistration: jest.fn() };
|
||||||
|
extensionManager.registerExtension(fakeExtension, configuration);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(fakeExtension.preRegistration.mock.calls[0][0]).toEqual({
|
||||||
|
servicesManager: extensionManager._servicesManager,
|
||||||
|
configuration,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('logs a warning if the extension is null or undefined', () => {
|
it('logs a warning if the extension is null or undefined', () => {
|
||||||
const undefinedExtension = undefined;
|
const undefinedExtension = undefined;
|
||||||
const nullExtension = null;
|
const nullExtension = null;
|
||||||
@@ -110,6 +134,25 @@ describe('ExtensionManager.js', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('successfully passes a servicesManager instance to each module', () => {
|
||||||
|
extensionManager._servicesManager = { services: { TestService: {} } };
|
||||||
|
|
||||||
|
const extension = {
|
||||||
|
id: 'hello-world',
|
||||||
|
getViewportModule: jest.fn(),
|
||||||
|
getSopClassHandlerModule: jest.fn(),
|
||||||
|
getPanelModule: jest.fn(),
|
||||||
|
getToolbarModule: jest.fn(),
|
||||||
|
getCommandsModule: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
extensionManager.registerExtension(extension);
|
||||||
|
|
||||||
|
expect(extension.getViewportModule.mock.calls[0][0]).toEqual({
|
||||||
|
servicesManager: extensionManager._servicesManager,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('successfully registers a module for each module type', () => {
|
it('successfully registers a module for each module type', () => {
|
||||||
const extension = {
|
const extension = {
|
||||||
id: 'hello-world',
|
id: 'hello-world',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import './lib';
|
import './lib';
|
||||||
|
|
||||||
import { ExtensionManager, MODULE_TYPES } from './extensions';
|
import { ExtensionManager, MODULE_TYPES } from './extensions';
|
||||||
|
import { ServicesManager } from './services';
|
||||||
import classes, { CommandsManager, HotkeysManager } from './classes/';
|
import classes, { CommandsManager, HotkeysManager } from './classes/';
|
||||||
|
|
||||||
import DICOMWeb from './DICOMWeb';
|
import DICOMWeb from './DICOMWeb';
|
||||||
@@ -18,12 +19,15 @@ import ui from './ui';
|
|||||||
import user from './user.js';
|
import user from './user.js';
|
||||||
import utils from './utils/';
|
import utils from './utils/';
|
||||||
|
|
||||||
|
import { createUINotificationService, createUIModalService } from './services';
|
||||||
|
|
||||||
const OHIF = {
|
const OHIF = {
|
||||||
MODULE_TYPES,
|
MODULE_TYPES,
|
||||||
//
|
//
|
||||||
CommandsManager,
|
CommandsManager,
|
||||||
ExtensionManager,
|
ExtensionManager,
|
||||||
HotkeysManager,
|
HotkeysManager,
|
||||||
|
ServicesManager,
|
||||||
//
|
//
|
||||||
utils,
|
utils,
|
||||||
studies,
|
studies,
|
||||||
@@ -41,6 +45,9 @@ const OHIF = {
|
|||||||
viewer: {},
|
viewer: {},
|
||||||
measurements,
|
measurements,
|
||||||
hangingProtocols,
|
hangingProtocols,
|
||||||
|
//
|
||||||
|
createUINotificationService,
|
||||||
|
createUIModalService,
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -49,6 +56,7 @@ export {
|
|||||||
CommandsManager,
|
CommandsManager,
|
||||||
ExtensionManager,
|
ExtensionManager,
|
||||||
HotkeysManager,
|
HotkeysManager,
|
||||||
|
ServicesManager,
|
||||||
//
|
//
|
||||||
utils,
|
utils,
|
||||||
studies,
|
studies,
|
||||||
@@ -65,6 +73,9 @@ export {
|
|||||||
DICOMWeb,
|
DICOMWeb,
|
||||||
measurements,
|
measurements,
|
||||||
hangingProtocols,
|
hangingProtocols,
|
||||||
|
//
|
||||||
|
createUINotificationService,
|
||||||
|
createUIModalService,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { OHIF };
|
export { OHIF };
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ describe('Top level exports', () => {
|
|||||||
'CommandsManager',
|
'CommandsManager',
|
||||||
'ExtensionManager',
|
'ExtensionManager',
|
||||||
'HotkeysManager',
|
'HotkeysManager',
|
||||||
|
'ServicesManager',
|
||||||
|
//
|
||||||
|
'createUINotificationService',
|
||||||
|
'createUIModalService',
|
||||||
//
|
//
|
||||||
'utils',
|
'utils',
|
||||||
'studies',
|
'studies',
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
// Top level key
|
// First tab
|
||||||
viewer: {
|
hotkeyDefinitions: [
|
||||||
// First tab
|
// commandName, label, keys
|
||||||
hotKeysData: {
|
// [{ zoom: { label: 'Zoom', keys: ['z'] }}]
|
||||||
// hotkeyName, label, keys, column
|
],
|
||||||
// zoom: { label: 'Zoom', command: 'Z', column: 0 },
|
// Second tab
|
||||||
},
|
windowLevelData: {
|
||||||
// Second tab
|
// order, description, window (int), level (int)
|
||||||
windowLevelData: {
|
// 0: { description: 'Soft tissue', window: '', level: '' },
|
||||||
// order, description, window (int), level (int)
|
|
||||||
// 0: { description: 'Soft tissue', window: '', level: '' },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import log from './../log.js';
|
||||||
|
|
||||||
|
export default class ServicesManager {
|
||||||
|
constructor() {
|
||||||
|
this.services = {};
|
||||||
|
this.registeredServiceNames = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} service
|
||||||
|
*/
|
||||||
|
registerService(service) {
|
||||||
|
if (!service) {
|
||||||
|
log.warn(
|
||||||
|
'Attempting to register a null/undefined service. Exiting early.'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let serviceName = service.name;
|
||||||
|
|
||||||
|
if (!serviceName) {
|
||||||
|
log.warn(`Service name not set. Exiting early.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.registeredServiceNames.includes(serviceName)) {
|
||||||
|
log.warn(
|
||||||
|
`Extension name ${serviceName} has already been registered. Exiting before duplicating services.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.services[service.name] = service;
|
||||||
|
|
||||||
|
// Track service registration
|
||||||
|
this.registeredServiceNames.push(serviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of services.
|
||||||
|
*
|
||||||
|
* @param {Object[]} services - Array of services
|
||||||
|
*/
|
||||||
|
registerServices(services) {
|
||||||
|
services.forEach(service => this.registerService(service));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import ServicesManager from './ServicesManager.js';
|
||||||
|
import log from '../log.js';
|
||||||
|
|
||||||
|
jest.mock('./../log.js');
|
||||||
|
|
||||||
|
describe('ServicesManager.js', () => {
|
||||||
|
let servicesManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
servicesManager = new ServicesManager();
|
||||||
|
log.warn.mockClear();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('registerServices()', () => {
|
||||||
|
it('calls registerService() for each service', () => {
|
||||||
|
servicesManager.registerService = jest.fn();
|
||||||
|
|
||||||
|
const fakeServices = [
|
||||||
|
{ name: 'UINotificationTestService', hide: jest.fn() },
|
||||||
|
{ name: 'UIModalTestService', hide: jest.fn() },
|
||||||
|
];
|
||||||
|
|
||||||
|
servicesManager.registerServices(fakeServices);
|
||||||
|
|
||||||
|
expect(servicesManager.registerService.mock.calls.length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('registerService()', () => {
|
||||||
|
it('logs a warning if the service is null or undefined', () => {
|
||||||
|
const undefinedService = undefined;
|
||||||
|
const nullService = null;
|
||||||
|
|
||||||
|
servicesManager.registerService(undefinedService);
|
||||||
|
servicesManager.registerService(nullService);
|
||||||
|
|
||||||
|
expect(log.warn.mock.calls.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs a warning if the service does not have a name', () => {
|
||||||
|
const serviceWithEmptyName = { name: '', hide: jest.fn() };
|
||||||
|
const serviceWithoutName = { hide: jest.fn() };
|
||||||
|
|
||||||
|
servicesManager.registerService(serviceWithEmptyName);
|
||||||
|
servicesManager.registerService(serviceWithoutName);
|
||||||
|
|
||||||
|
expect(log.warn.mock.calls.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks which services have been registered', () => {
|
||||||
|
const service = {
|
||||||
|
name: 'UINotificationService',
|
||||||
|
};
|
||||||
|
|
||||||
|
servicesManager.registerService(service);
|
||||||
|
|
||||||
|
expect(servicesManager.registeredServiceNames).toContain(service.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs a warning if the service has an name that has already been registered', () => {
|
||||||
|
const service = { name: 'UINotificationService' };
|
||||||
|
|
||||||
|
servicesManager.registerService(service);
|
||||||
|
servicesManager.registerService(service);
|
||||||
|
|
||||||
|
expect(log.warn.mock.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* A UI Element
|
||||||
|
*
|
||||||
|
* @typedef {ReactElement|HTMLElement} Modal
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI Modal
|
||||||
|
*
|
||||||
|
* @typedef {Object} ModalProps
|
||||||
|
* @property {string} [header=null] -
|
||||||
|
* @property {string} [footer=null] -
|
||||||
|
* @property {string} [backdrop=false] -
|
||||||
|
* @property {string} [keyboard=false] -
|
||||||
|
* @property {number} [show=true] -
|
||||||
|
* @property {string} [closeButton=true] -
|
||||||
|
* @property {string} [title=null] - 'Modal Title'
|
||||||
|
* @property {boolean} [customClassName=null] - '.ModalClass'
|
||||||
|
*/
|
||||||
|
|
||||||
|
const uiModalServicePublicAPI = {
|
||||||
|
name: 'UIModalService',
|
||||||
|
hide,
|
||||||
|
show,
|
||||||
|
setServiceImplementation,
|
||||||
|
};
|
||||||
|
|
||||||
|
const uiModalServiceImplementation = {
|
||||||
|
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
||||||
|
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
||||||
|
};
|
||||||
|
|
||||||
|
function createUIModalService() {
|
||||||
|
return uiModalServicePublicAPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a new UI modal;
|
||||||
|
*
|
||||||
|
* @param {Modal} component React component
|
||||||
|
* @param {ModalProps} props { header, footer, backdrop, keyboard, show, closeButton, title, customClassName }
|
||||||
|
*/
|
||||||
|
function show(
|
||||||
|
component,
|
||||||
|
props = {
|
||||||
|
header: null,
|
||||||
|
footer: null,
|
||||||
|
backdrop: false,
|
||||||
|
keyboard: false,
|
||||||
|
show: true,
|
||||||
|
closeButton: true,
|
||||||
|
title: null,
|
||||||
|
customClassName: null,
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return uiModalServiceImplementation._show(component, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides/dismisses the modal, if currently shown
|
||||||
|
*
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
function hide() {
|
||||||
|
return uiModalServiceImplementation._hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {*} {
|
||||||
|
* hide: hideImplementation,
|
||||||
|
* show: showImplementation,
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function setServiceImplementation({
|
||||||
|
hide: hideImplementation,
|
||||||
|
show: showImplementation,
|
||||||
|
}) {
|
||||||
|
if (hideImplementation) {
|
||||||
|
uiModalServiceImplementation._hide = hideImplementation;
|
||||||
|
}
|
||||||
|
if (showImplementation) {
|
||||||
|
uiModalServiceImplementation._show = showImplementation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createUIModalService;
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* A UI Notification
|
||||||
|
*
|
||||||
|
* @typedef {Object} Notification
|
||||||
|
* @property {string} title -
|
||||||
|
* @property {string} message -
|
||||||
|
* @property {number} [duration=5000] - in ms
|
||||||
|
* @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight"
|
||||||
|
* @property {string} [type="info"] - "info" | "error" | "warning" | "success"
|
||||||
|
* @property {boolean} [autoClose=true]
|
||||||
|
*/
|
||||||
|
|
||||||
|
const uiNotificationServicePublicAPI = {
|
||||||
|
name: 'UINotificationService',
|
||||||
|
hide,
|
||||||
|
show,
|
||||||
|
setServiceImplementation,
|
||||||
|
};
|
||||||
|
|
||||||
|
const uiNotificationServiceImplementation = {
|
||||||
|
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
||||||
|
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
||||||
|
};
|
||||||
|
|
||||||
|
function createUINotificationService() {
|
||||||
|
return uiNotificationServicePublicAPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and show a new UI notification; returns the
|
||||||
|
* ID of the created notification.
|
||||||
|
*
|
||||||
|
* @param {Notification} notification { title, message, duration, position, type, autoClose}
|
||||||
|
* @returns {number} id
|
||||||
|
*/
|
||||||
|
function show({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
duration = 5000,
|
||||||
|
position = 'bottomRight',
|
||||||
|
type = 'info',
|
||||||
|
autoClose = true,
|
||||||
|
}) {
|
||||||
|
return uiNotificationServiceImplementation._show({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
duration,
|
||||||
|
position,
|
||||||
|
type,
|
||||||
|
autoClose,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides/dismisses the notification, if currently shown
|
||||||
|
*
|
||||||
|
* @param {number} id - id of the notification to hide/dismiss
|
||||||
|
* @returns undefined
|
||||||
|
*/
|
||||||
|
function hide(id) {
|
||||||
|
return uiNotificationServiceImplementation._hide({ id });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {*} {
|
||||||
|
* hide: hideImplementation,
|
||||||
|
* show: showImplementation,
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function setServiceImplementation({
|
||||||
|
hide: hideImplementation,
|
||||||
|
show: showImplementation,
|
||||||
|
}) {
|
||||||
|
if (hideImplementation) {
|
||||||
|
uiNotificationServiceImplementation._hide = hideImplementation;
|
||||||
|
}
|
||||||
|
if (showImplementation) {
|
||||||
|
uiNotificationServiceImplementation._show = showImplementation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createUINotificationService;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import ServicesManager from './ServicesManager.js';
|
||||||
|
import createUINotificationService from './UINotificationService';
|
||||||
|
import createUIModalService from './UIModalService';
|
||||||
|
|
||||||
|
export { createUINotificationService, createUIModalService, ServicesManager };
|
||||||
@@ -77,14 +77,6 @@ const getWadoRsInstanceMetaData = (study, series, instance) => {
|
|||||||
*/
|
*/
|
||||||
function updateMetaDataManagerForSeries(study, series) {
|
function updateMetaDataManagerForSeries(study, series) {
|
||||||
series.instances.forEach(instance => {
|
series.instances.forEach(instance => {
|
||||||
// Cache just images that are going to be loaded via WADO-RS
|
|
||||||
if (
|
|
||||||
instance.imageRendering !== 'wadors' &&
|
|
||||||
instance.thumbnailRendering !== 'wadors'
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metaData = getWadoRsInstanceMetaData(study, series, instance);
|
const metaData = getWadoRsInstanceMetaData(study, series, instance);
|
||||||
const numberOfFrames = instance.numberOfFrames || 1;
|
const numberOfFrames = instance.numberOfFrames || 1;
|
||||||
|
|
||||||
@@ -94,7 +86,7 @@ function updateMetaDataManagerForSeries(study, series) {
|
|||||||
for (let frameNumber = 0; frameNumber < numberOfFrames; frameNumber++) {
|
for (let frameNumber = 0; frameNumber < numberOfFrames; frameNumber++) {
|
||||||
const imageId = getWADORSImageId(instance, frameNumber);
|
const imageId = getWADORSImageId(instance, frameNumber);
|
||||||
|
|
||||||
// TODO Drop dependency on this
|
// TODO Make a metadata manager which uses study/series/instance UIDS instead of imageIds
|
||||||
cornerstoneWADOImageLoader.wadors.metaDataManager.add(imageId, metaData);
|
cornerstoneWADOImageLoader.wadors.metaDataManager.add(imageId, metaData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,17 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.52.1](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.0...@ohif/i18n@0.52.1) (2019-11-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* minor date picker UX improvements ([813ee5e](https://github.com/OHIF/Viewers/commit/813ee5ed4d78b7bda234922d5f3389efe346451c))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [0.52.0](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.51.0...@ohif/i18n@0.52.0) (2019-11-06)
|
# [0.52.0](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.51.0...@ohif/i18n@0.52.0) (2019-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/i18n",
|
"name": "@ohif/i18n",
|
||||||
"version": "0.52.0",
|
"version": "0.52.1",
|
||||||
"description": "Internationalization library for The OHIF Viewer",
|
"description": "Internationalization library for The OHIF Viewer",
|
||||||
"author": "OHIF",
|
"author": "OHIF",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"Close": "Close",
|
||||||
"Image": "Image",
|
"Image": "Image",
|
||||||
"Layout": "Layout",
|
"Layout": "Layout",
|
||||||
"Measurements": "Measurements",
|
"Measurements": "Measurements",
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"Clear dates": "Clear dates",
|
||||||
|
"End Date": "End Date",
|
||||||
|
"Start Date": "Start Date"
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import AboutModal from './AboutModal.json';
|
|||||||
import Buttons from './Buttons.json';
|
import Buttons from './Buttons.json';
|
||||||
import CineDialog from './CineDialog.json';
|
import CineDialog from './CineDialog.json';
|
||||||
import Common from './Common.json';
|
import Common from './Common.json';
|
||||||
|
import DatePicker from './DatePicker.json';
|
||||||
import Header from './Header.json';
|
import Header from './Header.json';
|
||||||
import MeasurementTable from './MeasurementTable.json';
|
import MeasurementTable from './MeasurementTable.json';
|
||||||
import StudyList from './StudyList.json';
|
import StudyList from './StudyList.json';
|
||||||
@@ -13,6 +14,7 @@ export default {
|
|||||||
Buttons,
|
Buttons,
|
||||||
CineDialog,
|
CineDialog,
|
||||||
Common,
|
Common,
|
||||||
|
DatePicker,
|
||||||
Header,
|
Header,
|
||||||
MeasurementTable,
|
MeasurementTable,
|
||||||
StudyList,
|
StudyList,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"Close": "Fechar",
|
||||||
"Image": "Imagem",
|
"Image": "Imagem",
|
||||||
"Layout": "Layout",
|
"Layout": "Layout",
|
||||||
"More": "Mais",
|
"More": "Mais",
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"Clear dates": "Limpar datas",
|
||||||
|
"End Date": "Data Final",
|
||||||
|
"Start Date": "Data Inicial"
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import AboutModal from './AboutModal.json';
|
|||||||
import Buttons from './Buttons.json';
|
import Buttons from './Buttons.json';
|
||||||
import CineDialog from './CineDialog.json';
|
import CineDialog from './CineDialog.json';
|
||||||
import Common from './Common.json';
|
import Common from './Common.json';
|
||||||
|
import DatePicker from './DatePicker.json';
|
||||||
import Header from './Header.json';
|
import Header from './Header.json';
|
||||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ export default {
|
|||||||
Buttons,
|
Buttons,
|
||||||
CineDialog,
|
CineDialog,
|
||||||
Common,
|
Common,
|
||||||
|
DatePicker,
|
||||||
Header,
|
Header,
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,74 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [0.62.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.62.3...@ohif/ui@0.62.4) (2019-11-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* minor date picker UX improvements ([813ee5e](https://github.com/OHIF/Viewers/commit/813ee5ed4d78b7bda234922d5f3389efe346451c))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.62.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.62.2...@ohif/ui@0.62.3) (2019-11-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/ui
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.62.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.62.1...@ohif/ui@0.62.2) (2019-11-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/ui
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.62.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.62.0...@ohif/ui@0.62.1) (2019-11-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/ui
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.62.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.61.0...@ohif/ui@0.62.0) (2019-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.61.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.60.1...@ohif/ui@0.61.0) (2019-11-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.60.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.60.0...@ohif/ui@0.60.1) (2019-11-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix display issues with incorrect thumbnails. Change ImageThumb to functional component. ([#1148](https://github.com/OHIF/Viewers/issues/1148)) ([d70eae3](https://github.com/OHIF/Viewers/commit/d70eae3eb04fe854464f3e62316df8869bba6f11))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [0.60.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.59.1...@ohif/ui@0.60.0) (2019-11-06)
|
# [0.60.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.59.1...@ohif/ui@0.60.0) (2019-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/ui",
|
"name": "@ohif/ui",
|
||||||
"version": "0.60.0",
|
"version": "0.62.4",
|
||||||
"description": "A set of React components for Medical Imaging Viewers",
|
"description": "A set of React components for Medical Imaging Viewers",
|
||||||
"author": "OHIF Contributors",
|
"author": "OHIF Contributors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { TableList, TableListItem } from './tableList';
|
|||||||
import {
|
import {
|
||||||
AboutContent,
|
AboutContent,
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
UserPreferencesModal,
|
UserPreferencesForm,
|
||||||
} from './userPreferencesModal';
|
} from './userPreferencesForm';
|
||||||
|
|
||||||
import { Checkbox } from './checkbox';
|
import { Checkbox } from './checkbox';
|
||||||
import { CineDialog } from './cineDialog';
|
import { CineDialog } from './cineDialog';
|
||||||
@@ -52,6 +52,6 @@ export {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
AboutContent,
|
AboutContent,
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
UserPreferencesModal,
|
UserPreferencesForm,
|
||||||
OHIFModal,
|
OHIFModal,
|
||||||
};
|
};
|
||||||
@@ -13,7 +13,7 @@ const OHIFModal = ({
|
|||||||
onHide,
|
onHide,
|
||||||
footer: Footer,
|
footer: Footer,
|
||||||
header: Header,
|
header: Header,
|
||||||
children: Component,
|
children,
|
||||||
}) => (
|
}) => (
|
||||||
<ReactBootstrapModal
|
<ReactBootstrapModal
|
||||||
className={classNames('modal fade themed in', className)}
|
className={classNames('modal fade themed in', className)}
|
||||||
@@ -32,12 +32,9 @@ const OHIFModal = ({
|
|||||||
{Header && <Header hide={onHide} />}
|
{Header && <Header hide={onHide} />}
|
||||||
</ReactBootstrapModal.Header>
|
</ReactBootstrapModal.Header>
|
||||||
)}
|
)}
|
||||||
<ReactBootstrapModal.Body>
|
<ReactBootstrapModal.Body>{children}</ReactBootstrapModal.Body>
|
||||||
{Component && <Component hide={onHide} />}
|
|
||||||
</ReactBootstrapModal.Body>
|
|
||||||
{Footer && (
|
{Footer && (
|
||||||
<ReactBootstrapModal.Footer>
|
<ReactBootstrapModal.Footer>
|
||||||
{' '}
|
|
||||||
<Footer hide={onHide} />
|
<Footer hide={onHide} />
|
||||||
</ReactBootstrapModal.Footer>
|
</ReactBootstrapModal.Footer>
|
||||||
)}
|
)}
|
||||||
@@ -52,9 +49,20 @@ OHIFModal.propTypes = {
|
|||||||
show: PropTypes.bool,
|
show: PropTypes.bool,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
onHide: PropTypes.func,
|
onHide: PropTypes.func,
|
||||||
footer: PropTypes.node,
|
footer: PropTypes.oneOfType([
|
||||||
header: PropTypes.node,
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
children: PropTypes.node,
|
PropTypes.node,
|
||||||
|
PropTypes.func,
|
||||||
|
]),
|
||||||
|
header: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node,
|
||||||
|
PropTypes.func,
|
||||||
|
]),
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node,
|
||||||
|
]).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OHIFModal;
|
export default OHIFModal;
|
||||||
@@ -37,8 +37,8 @@ const SnackbarContainer = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={pos} className={`sb-container sb-${pos}`}>
|
<div key={pos} className={`sb-container sb-${pos}`}>
|
||||||
{items[pos].map(item => (
|
{items[pos].map((item, index) => (
|
||||||
<div key={item.id}>{renderItem(item)}</div>
|
<div key={item.id + index}>{renderItem(item)}</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import './ImageThumbnail.styl';
|
import './ImageThumbnail.styl';
|
||||||
|
|
||||||
import { utils } from '@ohif/core';
|
import { utils } from '@ohif/core';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { useState, useEffect, createRef } from 'react';
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ViewportErrorIndicator from '../../viewer/ViewportErrorIndicator';
|
import ViewportErrorIndicator from '../../viewer/ViewportErrorIndicator';
|
||||||
@@ -13,114 +13,132 @@ import ViewportLoadingIndicator from '../../viewer/ViewportLoadingIndicator';
|
|||||||
// - Set as external dependency?
|
// - Set as external dependency?
|
||||||
// - Pass in the entire load and render function as a prop?
|
// - Pass in the entire load and render function as a prop?
|
||||||
//import cornerstone from 'cornerstone-core';
|
//import cornerstone from 'cornerstone-core';
|
||||||
|
function ImageThumbnail(props) {
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
imageSrc,
|
||||||
|
imageId,
|
||||||
|
stackPercentComplete,
|
||||||
|
error: propsError,
|
||||||
|
} = props;
|
||||||
|
|
||||||
export default class ImageThumbnail extends PureComponent {
|
const [isLoading, setLoading] = useState(false);
|
||||||
static propTypes = {
|
const [error, setError] = useState(false);
|
||||||
imageSrc: PropTypes.string,
|
const [image, setImage] = useState({});
|
||||||
imageId: PropTypes.string,
|
const canvasRef = createRef();
|
||||||
error: PropTypes.bool.isRequired,
|
|
||||||
width: PropTypes.number.isRequired,
|
let loadingOrError;
|
||||||
height: PropTypes.number.isRequired,
|
let cancelablePromise;
|
||||||
stackPercentComplete: PropTypes.number.isRequired,
|
|
||||||
|
if (propsError || error) {
|
||||||
|
loadingOrError = <ViewportErrorIndicator />;
|
||||||
|
} else if (isLoading) {
|
||||||
|
loadingOrError = <ViewportLoadingIndicator />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const showStackLoadingProgressBar = stackPercentComplete !== undefined;
|
||||||
|
|
||||||
|
const shouldRenderToCanvas = () => {
|
||||||
|
return imageId && !imageSrc;
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
const fetchImagePromise = () => {
|
||||||
error: false,
|
if (!cancelablePromise) {
|
||||||
stackPercentComplete: 0,
|
return;
|
||||||
width: 217,
|
}
|
||||||
height: 123,
|
|
||||||
|
setLoading(true);
|
||||||
|
cancelablePromise
|
||||||
|
.then(response => {
|
||||||
|
setImage(response);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error.isCanceled) return;
|
||||||
|
setLoading(false);
|
||||||
|
setError(true);
|
||||||
|
throw new Error(error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
const setImagePromise = () => {
|
||||||
super(props);
|
if (shouldRenderToCanvas()) {
|
||||||
this.cancelablePromises = [];
|
cancelablePromise = utils.makeCancelable(
|
||||||
this.canvas = React.createRef();
|
cornerstone.loadAndCacheImage(imageId)
|
||||||
this.state = {
|
);
|
||||||
loading: this.shouldRenderToCanvas(),
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const purgeCancelablePromise = () => {
|
||||||
|
if (cancelablePromise) {
|
||||||
|
cancelablePromise.cancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
purgeCancelablePromise();
|
||||||
};
|
};
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
shouldRenderToCanvas() {
|
useEffect(() => {
|
||||||
return this.props.imageId && !this.props.imageSrc;
|
if (image.imageId) {
|
||||||
}
|
cornerstone.renderToCanvas(canvasRef.current, image);
|
||||||
|
setLoading(false);
|
||||||
fetchImage() {
|
|
||||||
const cancelablePromise = utils.makeCancelable(
|
|
||||||
cornerstone.loadAndCacheImage(this.props.imageId)
|
|
||||||
);
|
|
||||||
this.cancelablePromises.push(cancelablePromise);
|
|
||||||
return cancelablePromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
if (this.shouldRenderToCanvas()) {
|
|
||||||
this.fetchImage()
|
|
||||||
.then(image => {
|
|
||||||
cornerstone.renderToCanvas(this.canvas.current, image);
|
|
||||||
this.setState({
|
|
||||||
loading: false,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
if (error.isCanceled) return;
|
|
||||||
this.setState({
|
|
||||||
loading: false,
|
|
||||||
error: true,
|
|
||||||
});
|
|
||||||
throw new Error(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}, [image.imageId]);
|
||||||
|
|
||||||
componentWillUnmount() {
|
useEffect(() => {
|
||||||
while (this.cancelablePromises.length > 0) {
|
if (!image.imageId || image.imageId !== imageId) {
|
||||||
this.cancelablePromises.pop().cancel();
|
purgeCancelablePromise();
|
||||||
|
setImagePromise();
|
||||||
|
fetchImagePromise();
|
||||||
}
|
}
|
||||||
}
|
}, [imageId]);
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
let loadingOrError;
|
<div className="ImageThumbnail">
|
||||||
if (this.props.error) {
|
<div className="image-thumbnail-canvas">
|
||||||
loadingOrError = <ViewportErrorIndicator />;
|
{shouldRenderToCanvas() ? (
|
||||||
} else if (this.state.loading) {
|
<canvas ref={canvasRef} width={width} height={height} />
|
||||||
loadingOrError = <ViewportLoadingIndicator />;
|
) : (
|
||||||
}
|
<img
|
||||||
|
className="static-image"
|
||||||
const showStackLoadingProgressBar =
|
src={imageSrc}
|
||||||
this.props.stackPercentComplete !== undefined;
|
//width={this.props.width}
|
||||||
|
height={height}
|
||||||
return (
|
alt={''}
|
||||||
<div className="ImageThumbnail">
|
/>
|
||||||
<div className="image-thumbnail-canvas">
|
|
||||||
{this.shouldRenderToCanvas() ? (
|
|
||||||
<canvas
|
|
||||||
ref={this.canvas}
|
|
||||||
width={this.props.width}
|
|
||||||
height={this.props.height}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
className="static-image"
|
|
||||||
src={this.props.imageSrc}
|
|
||||||
//width={this.props.width}
|
|
||||||
height={this.props.height}
|
|
||||||
alt={''}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{loadingOrError}
|
|
||||||
{showStackLoadingProgressBar && (
|
|
||||||
<div className="image-thumbnail-progress-bar">
|
|
||||||
<div
|
|
||||||
className="image-thumbnail-progress-bar-inner"
|
|
||||||
style={{ width: `${this.props.stackPercentComplete}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{this.state.loading && (
|
|
||||||
<div className="image-thumbnail-loading-indicator"></div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
{loadingOrError}
|
||||||
}
|
{showStackLoadingProgressBar && (
|
||||||
|
<div className="image-thumbnail-progress-bar">
|
||||||
|
<div
|
||||||
|
className="image-thumbnail-progress-bar-inner"
|
||||||
|
style={{ width: `${stackPercentComplete}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isLoading && <div className="image-thumbnail-loading-indicator"></div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageThumbnail.propTypes = {
|
||||||
|
imageSrc: PropTypes.string,
|
||||||
|
imageId: PropTypes.string,
|
||||||
|
error: PropTypes.bool,
|
||||||
|
width: PropTypes.number,
|
||||||
|
height: PropTypes.number,
|
||||||
|
stackPercentComplete: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
ImageThumbnail.defaultProps = {
|
||||||
|
error: false,
|
||||||
|
stackPercentComplete: 0,
|
||||||
|
width: 217,
|
||||||
|
height: 123,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageThumbnail;
|
||||||
@@ -32,7 +32,7 @@ function StudyBrowser(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`container_${studyIndex}_${thumbIndex}`}
|
key={thumb.displaySetInstanceUid}
|
||||||
className="thumbnail-container"
|
className="thumbnail-container"
|
||||||
data-cy="thumbnail-list"
|
data-cy="thumbnail-list"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,22 +2,23 @@
|
|||||||
|
|
||||||
/* Will edit everything selected including everything between a range of dates */
|
/* Will edit everything selected including everything between a range of dates */
|
||||||
.CalendarDay__selected_span {
|
.CalendarDay__selected_span {
|
||||||
background: var(--table-text-secondary-color);
|
background: var(--datePicker-picker-background);
|
||||||
color: #fff;
|
color: var(--datePicker-picker-text);
|
||||||
border-color: #e4e7e7;
|
border-color: #e4e7e7;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Will edit selected date or the endpoints of a range of dates */
|
/* Will edit selected date or the endpoints of a range of dates */
|
||||||
.CalendarDay__selected {
|
.CalendarDay__selected {
|
||||||
background: var(--table-text-secondary-color);
|
background: var(--datePicker-picker-background);
|
||||||
color: #fff;
|
color: var(--datePicker-picker-text);
|
||||||
border-color: #e4e7e7;
|
border-color: #e4e7e7;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Will edit when hovered over. _span style also has this property */
|
/* Will edit when hovered over. _span style also has this property */
|
||||||
.CalendarDay__selected:hover {
|
.CalendarDay__selected:hover,
|
||||||
background: var(--table-text-secondary-color);
|
.CalendarDay__default:hover {
|
||||||
color: #fff;
|
background: var(--datePicker-picker-background);
|
||||||
|
color: var(--datePicker-picker-text);
|
||||||
border-color: #e4e7e7;
|
border-color: #e4e7e7;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,8 +26,8 @@
|
|||||||
is not yet selected. Edits the dates between your mouse and said date */
|
is not yet selected. Edits the dates between your mouse and said date */
|
||||||
.CalendarDay__hovered_span:hover,
|
.CalendarDay__hovered_span:hover,
|
||||||
.CalendarDay__hovered_span {
|
.CalendarDay__hovered_span {
|
||||||
background: var(--table-text-secondary-color);
|
background: var(--datePicker-picker-background);
|
||||||
color: #fff;
|
color: var(--datePicker-picker-text);
|
||||||
border-color: #e4e7e7;
|
border-color: #e4e7e7;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,15 +43,15 @@ is not yet selected. Edits the dates between your mouse and said date */
|
|||||||
/* Container - visual */
|
/* Container - visual */
|
||||||
.DateRangePickerInput {
|
.DateRangePickerInput {
|
||||||
width: calc(100% - 10px); /* Just use padding? */
|
width: calc(100% - 10px); /* Just use padding? */
|
||||||
background-color: var(--input-background-color);
|
background-color: var(--datePicker-input-background);
|
||||||
border-color: var(--input-background-color);
|
border-color: var(--datePicker-input-background);
|
||||||
color: var(--input-placeholder-color);
|
color: var(--datePicker-picker-text-input);
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.DateRangePickerInput.DateRangePickerInput__withBorder {
|
.DateRangePickerInput.DateRangePickerInput__withBorder {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--input-background-color);
|
background-color: var(--datePicker-input-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Input Container */
|
/* Input Container */
|
||||||
@@ -58,14 +59,14 @@ is not yet selected. Edits the dates between your mouse and said date */
|
|||||||
width: 97px;
|
width: 97px;
|
||||||
height: 38px;
|
height: 38px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--input-background-color);
|
background-color: var(--datePicker-input-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actual Input Element */
|
/* Actual Input Element */
|
||||||
.DateInput > .DateInput_input {
|
.DateInput > .DateInput_input {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: var(--input-placeholder-color);
|
color: var(--datePicker-picker-text-input);
|
||||||
height: 38px;
|
height: 38px;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -80,4 +81,39 @@ is not yet selected. Edits the dates between your mouse and said date */
|
|||||||
.PresetDateRangePicker_button {
|
.PresetDateRangePicker_button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
|
color: #00a699;
|
||||||
|
background: var(--datePicker-picker-text-input);
|
||||||
|
}
|
||||||
|
|
||||||
|
.PresetDateRangePicker_button:hover {
|
||||||
|
background: #00a699;
|
||||||
|
color: var(--datePicker-picker-text-input);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CalendarDay__blocked_out_of_range:hover {
|
||||||
|
background: #fff;
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: var(--datePicker-picker-text-disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
.DateRangePicker_select {
|
||||||
|
background-color: white;
|
||||||
|
color: var(--datePicker-picker-text);
|
||||||
|
border: 1px solid #e4e7e7;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 20px 3px 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
|
||||||
|
background-image: linear-gradient(45deg, transparent 50%, gray 50%),
|
||||||
|
linear-gradient(135deg, gray 50%, transparent 50%);
|
||||||
|
background-position: calc(100% - 11px) 11px, calc(100% - 6px) calc(11px);
|
||||||
|
background-size: 5px 5px, 5px 5px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DateRangePicker_select:hover {
|
||||||
|
border-color: #c4c4c4;
|
||||||
}
|
}
|
||||||
@@ -7,39 +7,25 @@ import './CustomDateRangePicker.css';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { DateRangePicker } from 'react-dates';
|
import { DateRangePicker } from 'react-dates';
|
||||||
|
import moment from 'moment';
|
||||||
|
import i18n from '@ohif/i18n';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default class CustomDateRangePicker extends React.Component {
|
function CustomDateRangePicker(props) {
|
||||||
static propTypes = {
|
moment.locale(i18n.language); // using i18n in the date picker
|
||||||
presets: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
text: PropTypes.string,
|
|
||||||
start: PropTypes.required,
|
|
||||||
end: PropTypes.required,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
const { t } = useTranslation('DatePicker');
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
startDate: this.props.startDate,
|
|
||||||
endDate: this.props.endDate,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.renderDatePresets = this.renderDatePresets.bind(this);
|
const {
|
||||||
this.onDatesChange = this.onDatesChange.bind(this);
|
onDatesChange,
|
||||||
}
|
startDate,
|
||||||
|
endDate,
|
||||||
|
presets,
|
||||||
|
...dateRangePickerProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
onDatesChange({ startDate, endDate, preset }) {
|
const renderDatePresets = () => {
|
||||||
this.setState({ startDate, endDate });
|
const { presets } = props;
|
||||||
if (this.props.onDatesChange) {
|
|
||||||
this.props.onDatesChange({ startDate, endDate, preset });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDatePresets() {
|
|
||||||
const { presets } = this.props;
|
|
||||||
const { startDate, endDate } = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="PresetDateRangePicker_panel">
|
<div className="PresetDateRangePicker_panel">
|
||||||
@@ -54,7 +40,7 @@ export default class CustomDateRangePicker extends React.Component {
|
|||||||
isSelected ? 'PresetDateRangePicker_button__selected' : ''
|
isSelected ? 'PresetDateRangePicker_button__selected' : ''
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
this.onDatesChange({
|
onDatesChange({
|
||||||
startDate: start,
|
startDate: start,
|
||||||
endDate: end,
|
endDate: end,
|
||||||
preset: true,
|
preset: true,
|
||||||
@@ -67,30 +53,89 @@ export default class CustomDateRangePicker extends React.Component {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
const renderMonthElement = ({ month, onMonthSelect, onYearSelect }) => {
|
||||||
|
const containerStyle = {
|
||||||
|
margin: '0 5px',
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
const renderYearsOptions = () => {
|
||||||
let {
|
const yearsRange = 20;
|
||||||
autoFocus,
|
const options = [];
|
||||||
autoFocusEndDate,
|
|
||||||
initialStartDate,
|
for (let i = 0; i < yearsRange; i++) {
|
||||||
initialEndDate,
|
const year = moment().year() - i;
|
||||||
stateDateWrapper,
|
options.push(<option value={year}>{year}</option>);
|
||||||
onDatesChange,
|
}
|
||||||
startDate,
|
|
||||||
endDate,
|
return options;
|
||||||
presets,
|
};
|
||||||
...dateRangePickerProps
|
|
||||||
} = this.props;
|
renderMonthElement.propTypes = {
|
||||||
|
onMonthSelect: PropTypes.func,
|
||||||
|
onYearSelect: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DateRangePicker
|
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
{...dateRangePickerProps}
|
<div style={containerStyle}>
|
||||||
startDate={this.state.startDate}
|
<select
|
||||||
endDate={this.state.endDate}
|
className="DateRangePicker_select"
|
||||||
renderCalendarInfo={this.renderDatePresets}
|
value={month.month()}
|
||||||
onDatesChange={this.onDatesChange}
|
onChange={e => onMonthSelect(month, e.target.value)}
|
||||||
/>
|
>
|
||||||
|
{moment.months().map((label, value) => (
|
||||||
|
<option key={value} value={value}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div style={containerStyle}>
|
||||||
|
{}
|
||||||
|
<select
|
||||||
|
className="DateRangePicker_select"
|
||||||
|
value={month.year()}
|
||||||
|
onChange={e => onYearSelect(month, e.target.value)}
|
||||||
|
>
|
||||||
|
{renderYearsOptions()}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DateRangePicker
|
||||||
|
{...dateRangePickerProps}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
renderCalendarInfo={renderDatePresets}
|
||||||
|
onDatesChange={onDatesChange}
|
||||||
|
renderMonthElement={renderMonthElement}
|
||||||
|
startDatePlaceholderText={t('Start Date')}
|
||||||
|
endDatePlaceholderText={t('End Date')}
|
||||||
|
phrases={{
|
||||||
|
closeDatePicker: t('Common:Close'),
|
||||||
|
clearDates: t('Clear dates'),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CustomDateRangePicker.propTypes = {
|
||||||
|
presets: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
text: PropTypes.string,
|
||||||
|
start: PropTypes.required,
|
||||||
|
end: PropTypes.required,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
autoFocus: PropTypes.bool.isRequired,
|
||||||
|
onDatesChange: PropTypes.func.isRequired,
|
||||||
|
startDate: PropTypes.instanceOf(Date),
|
||||||
|
endDate: PropTypes.instanceOf(Date),
|
||||||
|
month: PropTypes.instanceOf(Date),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomDateRangePicker;
|
||||||
@@ -25,7 +25,6 @@ function StudyList(props) {
|
|||||||
onFilterChange: handleFilterChange,
|
onFilterChange: handleFilterChange,
|
||||||
onSelectItem: handleSelectItem,
|
onSelectItem: handleSelectItem,
|
||||||
t,
|
t,
|
||||||
//
|
|
||||||
studyListDateFilterNumDays,
|
studyListDateFilterNumDays,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -208,7 +207,6 @@ StudyList.propTypes = {
|
|||||||
allFields: PropTypes.string.isRequired,
|
allFields: PropTypes.string.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
onFilterChange: PropTypes.func.isRequired,
|
onFilterChange: PropTypes.func.isRequired,
|
||||||
//
|
|
||||||
studyListDateFilterNumDays: PropTypes.number,
|
studyListDateFilterNumDays: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ table.table
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
color: var(--table-text-primary-color);
|
color: var(--table-text-primary-color);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding: 20px;
|
padding: 20px 8px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import CustomDateRangePicker from './CustomDateRangePicker.js';
|
|||||||
import { Icon } from './../../elements/Icon';
|
import { Icon } from './../../elements/Icon';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function TableSearchFilter(props) {
|
function TableSearchFilter(props) {
|
||||||
const {
|
const {
|
||||||
meta,
|
meta,
|
||||||
@@ -17,7 +15,7 @@ function TableSearchFilter(props) {
|
|||||||
sortFieldName,
|
sortFieldName,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
// TODO: Rename
|
// TODO: Rename
|
||||||
studyListDateFilterNumDays
|
studyListDateFilterNumDays,
|
||||||
} = props;
|
} = props;
|
||||||
const [focusedInput, setFocusedInput] = useState(null);
|
const [focusedInput, setFocusedInput] = useState(null);
|
||||||
const [t] = useTranslation(); // 'Common'?
|
const [t] = useTranslation(); // 'Common'?
|
||||||
@@ -25,11 +23,13 @@ function TableSearchFilter(props) {
|
|||||||
const sortIcons = ['sort', 'sort-up', 'sort-down'];
|
const sortIcons = ['sort', 'sort-up', 'sort-down'];
|
||||||
const sortIconForSortField =
|
const sortIconForSortField =
|
||||||
sortDirection === 'asc' ? sortIcons[1] : sortIcons[2];
|
sortDirection === 'asc' ? sortIcons[1] : sortIcons[2];
|
||||||
//
|
|
||||||
const today = moment();
|
const today = moment();
|
||||||
const lastWeek = moment().subtract(7, 'day');
|
const lastWeek = moment().subtract(7, 'day');
|
||||||
const lastMonth = moment().subtract(1, 'month');
|
const lastMonth = moment().subtract(1, 'month');
|
||||||
const defaultStartDate = moment().subtract(studyListDateFilterNumDays, 'days');
|
const defaultStartDate = moment().subtract(
|
||||||
|
studyListDateFilterNumDays,
|
||||||
|
'days'
|
||||||
|
);
|
||||||
const defaultEndDate = today;
|
const defaultEndDate = today;
|
||||||
const studyDatePresets = [
|
const studyDatePresets = [
|
||||||
{
|
{
|
||||||
@@ -82,8 +82,8 @@ function TableSearchFilter(props) {
|
|||||||
endDateId="end-date"
|
endDateId="end-date"
|
||||||
// TODO: We need a dynamic way to determine which fields values to update
|
// TODO: We need a dynamic way to determine which fields values to update
|
||||||
onDatesChange={({ startDate, endDate, preset = false }) => {
|
onDatesChange={({ startDate, endDate, preset = false }) => {
|
||||||
onValueChange('studyDateTo', startDate);
|
onValueChange('studyDateFrom', startDate);
|
||||||
onValueChange('studyDateFrom', endDate);
|
onValueChange('studyDateTo', endDate);
|
||||||
}}
|
}}
|
||||||
focusedInput={focusedInput}
|
focusedInput={focusedInput}
|
||||||
onFocusChange={updatedVal => setFocusedInput(updatedVal)}
|
onFocusChange={updatedVal => setFocusedInput(updatedVal)}
|
||||||
|
|||||||
File renamed without changes.
+29
-42
@@ -10,31 +10,20 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
export class HotKeysPreferences extends Component {
|
export class HotKeysPreferences extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
hotKeysData: PropTypes.objectOf(
|
hotkeyDefinitions: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
keys: PropTypes.arrayOf(PropTypes.string).isRequired,
|
commandName: PropTypes.string,
|
||||||
label: PropTypes.string.isRequired,
|
keys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
label: PropTypes.string,
|
||||||
})
|
})
|
||||||
).isRequired,
|
).isRequired,
|
||||||
onChange: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const hotkeyCommands = Object.keys(this.props.hotKeysData);
|
|
||||||
const localHotKeys = hotkeyCommands.map(commandName => {
|
|
||||||
const definition = this.props.hotKeysData[commandName];
|
|
||||||
|
|
||||||
return {
|
|
||||||
commandName,
|
|
||||||
keys: definition.keys,
|
|
||||||
label: definition.label,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
hotKeys: localHotKeys,
|
hotKeys: this.props.hotkeyDefinitions,
|
||||||
errorMessages: {},
|
errorMessages: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,24 +42,28 @@ export class HotKeysPreferences extends Component {
|
|||||||
const { ctrlKey, altKey, shiftKey } = keyDownEvent;
|
const { ctrlKey, altKey, shiftKey } = keyDownEvent;
|
||||||
|
|
||||||
if (ctrlKey && !altKey) {
|
if (ctrlKey && !altKey) {
|
||||||
keysPressedArray.push('CTRL');
|
keysPressedArray.push('ctrl');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shiftKey && !altKey) {
|
if (shiftKey && !altKey) {
|
||||||
keysPressedArray.push('SHIFT');
|
keysPressedArray.push('shift');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (altKey && !ctrlKey) {
|
if (altKey && !ctrlKey) {
|
||||||
keysPressedArray.push('ALT');
|
keysPressedArray.push('alt');
|
||||||
}
|
}
|
||||||
|
|
||||||
return keysPressedArray;
|
return keysPressedArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
getConflictingCommand(currentToolKey, hotKeyCommand) {
|
getConflictingCommand(currentCommandName, currentHotKeys) {
|
||||||
return Object.keys(this.state.hotKeys).find(tool => {
|
return this.state.hotKeys.find((tool, index) => {
|
||||||
const value = this.state.hotKeys[tool].command;
|
const toolHotKeys = tool.keys[0];
|
||||||
return value && value === hotKeyCommand && tool !== currentToolKey;
|
return (
|
||||||
|
toolHotKeys &&
|
||||||
|
toolHotKeys === currentHotKeys &&
|
||||||
|
tool.commandName !== currentCommandName
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +82,7 @@ export class HotKeysPreferences extends Component {
|
|||||||
specialKeyName ||
|
specialKeyName ||
|
||||||
keyDownEvent.key ||
|
keyDownEvent.key ||
|
||||||
String.fromCharCode(keyDownEvent.keyCode);
|
String.fromCharCode(keyDownEvent.keyCode);
|
||||||
pressedKeys.push(keyName.toUpperCase());
|
pressedKeys.push(keyName.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateHotKeysState(commandName, pressedKeys.join('+'));
|
this.updateHotKeysState(commandName, pressedKeys.join('+'));
|
||||||
@@ -136,17 +129,17 @@ export class HotKeysPreferences extends Component {
|
|||||||
const hotKey = this.state.hotKeys[hotKeyIndex];
|
const hotKey = this.state.hotKeys[hotKeyIndex];
|
||||||
const keys = hotKey.keys[0];
|
const keys = hotKey.keys[0];
|
||||||
const pressedKeys = keys.split('+');
|
const pressedKeys = keys.split('+');
|
||||||
const lastPressedKey = pressedKeys[pressedKeys.length - 1].toUpperCase();
|
const lastPressedKey = pressedKeys[pressedKeys.length - 1].toLowerCase();
|
||||||
|
|
||||||
// clear the prior errors
|
// clear the prior errors
|
||||||
this.setState({ errorMessages: {} }, () => {
|
this.setState({ errorMessages: {} }, () => {
|
||||||
// Check if it has a valid modifier
|
// Check if it has a valid modifier
|
||||||
const isModifier = ['CTRL', 'ALT', 'SHIFT'].includes(lastPressedKey);
|
const isModifier = ['ctrl', 'alt', 'shift'].includes(lastPressedKey);
|
||||||
if (isModifier) {
|
if (isModifier) {
|
||||||
this.updateHotKeysState(commandName, '');
|
this.updateHotKeysState(commandName, '');
|
||||||
this.updateErrorsState(
|
this.updateErrorsState(
|
||||||
commandName,
|
commandName,
|
||||||
"It's not possible to define only modifier keys (CTRL, ALT and SHIFT) as a shortcut"
|
"It's not possible to define only modifier keys (ctrl, alt and shift) as a shortcut"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -154,21 +147,13 @@ export class HotKeysPreferences extends Component {
|
|||||||
/*
|
/*
|
||||||
* Check if it has some conflict
|
* Check if it has some conflict
|
||||||
*/
|
*/
|
||||||
const conflictedCommandKey = this.getConflictingCommand(
|
const conflictedCommand = this.getConflictingCommand(commandName, keys);
|
||||||
commandName,
|
if (conflictedCommand) {
|
||||||
keys
|
this.updateHotKeysState(commandName, '');
|
||||||
);
|
|
||||||
if (conflictedCommandKey) {
|
|
||||||
const conflictedCommand = this.state.hotKeys[conflictedCommandKey];
|
|
||||||
|
|
||||||
this.updateErrorsState(
|
this.updateErrorsState(
|
||||||
commandName,
|
commandName,
|
||||||
`"${conflictedCommand.label}" is already using the "${
|
`"${conflictedCommand.label}" is already using the "${keys}" shortcut.`
|
||||||
conflictedCommand.command
|
|
||||||
}" shortcut.`
|
|
||||||
);
|
);
|
||||||
this.updateErrorsState(conflictedCommandKey, '');
|
|
||||||
this.updateHotKeysState(commandName, '');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +163,7 @@ export class HotKeysPreferences extends Component {
|
|||||||
const modifierCommand = pressedKeys
|
const modifierCommand = pressedKeys
|
||||||
.slice(0, pressedKeys.length - 1)
|
.slice(0, pressedKeys.length - 1)
|
||||||
.join('+')
|
.join('+')
|
||||||
.toUpperCase();
|
.toLowerCase();
|
||||||
|
|
||||||
const disallowedCombination = disallowedCombinations[modifierCommand];
|
const disallowedCombination = disallowedCombinations[modifierCommand];
|
||||||
const hasDisallowedCombinations = disallowedCombination
|
const hasDisallowedCombinations = disallowedCombination
|
||||||
@@ -189,7 +174,7 @@ export class HotKeysPreferences extends Component {
|
|||||||
this.updateHotKeysState(commandName, '');
|
this.updateHotKeysState(commandName, '');
|
||||||
this.updateErrorsState(
|
this.updateErrorsState(
|
||||||
commandName,
|
commandName,
|
||||||
"It's not possible to define only modifier keys (CTRL, ALT and SHIFT) as a shortcut"
|
`"${pressedKeys.join('+')}" shortcut combination is not allowed`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -237,7 +222,7 @@ export class HotKeysPreferences extends Component {
|
|||||||
this.state.hotKeys.length
|
this.state.hotKeys.length
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return this.state.hotKeys.length > 0 ? (
|
||||||
<div className="HotKeysPreferences">
|
<div className="HotKeysPreferences">
|
||||||
{/* <!-- Column 1 --> */}
|
{/* <!-- Column 1 --> */}
|
||||||
<div className="column">
|
<div className="column">
|
||||||
@@ -272,6 +257,8 @@ export class HotKeysPreferences extends Component {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<p>{`No hotkeys are configured for this application. Hotkeys can be configured in the application's app-config.js file.`}</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File renamed without changes.
+23
-13
@@ -8,14 +8,20 @@ import './UserPreferences.styl';
|
|||||||
|
|
||||||
export class UserPreferences extends Component {
|
export class UserPreferences extends Component {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
hotKeysData: {},
|
hotkeyDefinitions: [],
|
||||||
windowLevelData: {},
|
windowLevelData: {},
|
||||||
generalData: {},
|
generalData: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Make this more generic. Tabs should not be restricted to these entries
|
// TODO: Make this more generic. Tabs should not be restricted to these entries
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
hotKeysData: PropTypes.object.isRequired,
|
hotkeyDefinitions: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
commandName: PropTypes.string,
|
||||||
|
keys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
label: PropTypes.string,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
windowLevelData: PropTypes.object.isRequired,
|
windowLevelData: PropTypes.object.isRequired,
|
||||||
generalData: PropTypes.object.isRequired,
|
generalData: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
@@ -32,7 +38,9 @@ export class UserPreferences extends Component {
|
|||||||
return (
|
return (
|
||||||
<form className="form-themed themed">
|
<form className="form-themed themed">
|
||||||
<div className="form-content">
|
<div className="form-content">
|
||||||
<HotKeysPreferences hotKeysData={this.props.hotKeysData} />
|
<HotKeysPreferences
|
||||||
|
hotkeyDefinitions={this.props.hotkeyDefinitions}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
@@ -66,8 +74,8 @@ export class UserPreferences extends Component {
|
|||||||
switch (tabIndex) {
|
switch (tabIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
return this.renderHotkeysTab();
|
return this.renderHotkeysTab();
|
||||||
case 1:
|
/* case 1:
|
||||||
return this.renderWindowLevelTab();
|
return this.renderWindowLevelTab(); */
|
||||||
case 2:
|
case 2:
|
||||||
return this.renderGeneralTab();
|
return this.renderGeneralTab();
|
||||||
|
|
||||||
@@ -93,14 +101,16 @@ export class UserPreferences extends Component {
|
|||||||
>
|
>
|
||||||
<button>Hotkeys</button>
|
<button>Hotkeys</button>
|
||||||
</li>
|
</li>
|
||||||
<li
|
{false && (
|
||||||
onClick={() => {
|
<li
|
||||||
this.tabClick(1);
|
onClick={() => {
|
||||||
}}
|
this.tabClick(1);
|
||||||
className={this.getTabClass(1)}
|
}}
|
||||||
>
|
className={this.getTabClass(1)}
|
||||||
<button>Window Level</button>
|
>
|
||||||
</li>
|
<button>Window Level</button>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
<li
|
<li
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.tabClick(2);
|
this.tabClick(2);
|
||||||
File renamed without changes.
@@ -0,0 +1,92 @@
|
|||||||
|
import './UserPreferencesForm.styl';
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { withTranslation } from '../../utils/LanguageProvider';
|
||||||
|
|
||||||
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
import isEqual from 'lodash.isequal';
|
||||||
|
import { UserPreferences } from './UserPreferences';
|
||||||
|
|
||||||
|
class UserPreferencesForm extends Component {
|
||||||
|
// TODO: Make this component more generic to allow things other than W/L and hotkeys...
|
||||||
|
static propTypes = {
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
onSave: PropTypes.func,
|
||||||
|
onResetToDefaults: PropTypes.func,
|
||||||
|
windowLevelData: PropTypes.object,
|
||||||
|
hotkeyDefinitions: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
commandName: PropTypes.string,
|
||||||
|
keys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
label: PropTypes.string,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
t: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
windowLevelData: cloneDeep(props.windowLevelData),
|
||||||
|
hotkeyDefinitions: cloneDeep(props.hotkeyDefinitions),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
save = () => {
|
||||||
|
this.props.onSave({
|
||||||
|
windowLevelData: this.state.windowLevelData,
|
||||||
|
hotkeyDefinitions: this.state.hotkeyDefinitions,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidUpdate(prev, next) {
|
||||||
|
const newStateData = {};
|
||||||
|
|
||||||
|
if (!isEqual(prev.windowLevelData, next.windowLevelData)) {
|
||||||
|
newStateData.windowLevelData = prev.windowLevelData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEqual(prev.hotkeyDefinitions, next.hotkeyDefinitions)) {
|
||||||
|
newStateData.hotkeyDefinitions = prev.hotkeyDefinitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newStateData.hotkeyDefinitions || newStateData.windowLevelData) {
|
||||||
|
this.setState(newStateData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="UserPreferencesForm">
|
||||||
|
<UserPreferences
|
||||||
|
windowLevelData={this.state.windowLevelData}
|
||||||
|
hotkeyDefinitions={this.state.hotkeyDefinitions}
|
||||||
|
/>
|
||||||
|
<div className="footer">
|
||||||
|
<button
|
||||||
|
className="btn btn-danger pull-left"
|
||||||
|
onClick={this.props.onResetToDefaults}
|
||||||
|
>
|
||||||
|
{this.props.t('Reset to Defaults')}
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<div onClick={this.props.onClose} className="btn btn-default">
|
||||||
|
{this.props.t('Cancel')}
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-primary" onClick={this.save}>
|
||||||
|
{this.props.t('Save')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectedComponent = withTranslation('UserPreferencesForm')(
|
||||||
|
UserPreferencesForm
|
||||||
|
);
|
||||||
|
export { connectedComponent as UserPreferencesForm };
|
||||||
|
export default connectedComponent;
|
||||||
+10
-3
@@ -12,6 +12,13 @@
|
|||||||
text-shadow: 0 1px 0 #fff;
|
text-shadow: 0 1px 0 #fff;
|
||||||
opacity: .2;
|
opacity: .2;
|
||||||
|
|
||||||
.ModalHeader
|
.UserPreferencesForm
|
||||||
ol, ul
|
.footer
|
||||||
margin-top: 0;
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
padding-bottom: 20px
|
||||||
|
justify-content: space-between
|
||||||
|
|
||||||
|
div
|
||||||
|
button:last-child
|
||||||
|
margin-left: 10px
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+8
-8
@@ -1,18 +1,18 @@
|
|||||||
---
|
---
|
||||||
name: User Preferences Modal
|
name: User Preferences Form
|
||||||
menu: Components
|
menu: Components
|
||||||
route: /components/user-preferences-modal
|
route: /components/user-preferences-form
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Playground, Props } from 'docz'
|
import { Playground, Props } from 'docz'
|
||||||
import { State } from 'react-powerplug'
|
import { State } from 'react-powerplug'
|
||||||
import { UserPreferencesModal } from './../index.js'
|
import { UserPreferencesForm } from './../index.js'
|
||||||
import NameSpace from '../../../__docs__/NameSpace'
|
import NameSpace from '../../../__docs__/NameSpace'
|
||||||
//
|
//
|
||||||
import windowLevelDefaults from './windowLevelDefaults.js'
|
import windowLevelDefaults from './windowLevelDefaults.js'
|
||||||
import hotkeyDefaults from './hotkeyDefaults.js'
|
import hotkeyDefaults from './hotkeyDefaults.js'
|
||||||
|
|
||||||
# User Preferences Modal
|
# User Preferences Form
|
||||||
|
|
||||||
## Basic usage
|
## Basic usage
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ import hotkeyDefaults from './hotkeyDefaults.js'
|
|||||||
<State initial={{
|
<State initial={{
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
windowLevelData: windowLevelDefaults,
|
windowLevelData: windowLevelDefaults,
|
||||||
hotKeysData: hotkeyDefaults,
|
hotkeyDefinitions: hotkeyDefaults,
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
{({ state, setState }) => (
|
{({ state, setState }) => (
|
||||||
@@ -32,7 +32,7 @@ import hotkeyDefaults from './hotkeyDefaults.js'
|
|||||||
>
|
>
|
||||||
Open user preferences
|
Open user preferences
|
||||||
</button>
|
</button>
|
||||||
<UserPreferencesModal
|
<UserPreferencesForm
|
||||||
{...state}
|
{...state}
|
||||||
onCancel={() => setState({ isOpen: false })}
|
onCancel={() => setState({ isOpen: false })}
|
||||||
onSave={() => alert('on save')}
|
onSave={() => alert('on save')}
|
||||||
@@ -47,8 +47,8 @@ import hotkeyDefaults from './hotkeyDefaults.js'
|
|||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
<Props of={UserPreferencesModal} />
|
<Props of={UserPreferencesForm} />
|
||||||
|
|
||||||
## Translation Namespace
|
## Translation Namespace
|
||||||
|
|
||||||
<NameSpace name="UserPreferencesModal" />
|
<NameSpace name="UserPreferencesForm" />
|
||||||
File renamed without changes.
+28
-28
@@ -4,38 +4,38 @@ const range = (start, end) => {
|
|||||||
|
|
||||||
export const disallowedCombinations = {
|
export const disallowedCombinations = {
|
||||||
'': [],
|
'': [],
|
||||||
ALT: ['SPACE'],
|
alt: ['space'],
|
||||||
SHIFT: [],
|
shift: [],
|
||||||
CTRL: [
|
ctrl: [
|
||||||
'F4',
|
'f4',
|
||||||
'F5',
|
'f5',
|
||||||
'F11',
|
'f11',
|
||||||
'W',
|
'w',
|
||||||
'R',
|
'r',
|
||||||
'T',
|
't',
|
||||||
'O',
|
'o',
|
||||||
'P',
|
'p',
|
||||||
'A',
|
'a',
|
||||||
'D',
|
'd',
|
||||||
'F',
|
'f',
|
||||||
'G',
|
'g',
|
||||||
'H',
|
'h',
|
||||||
'J',
|
'j',
|
||||||
'L',
|
'l',
|
||||||
'Z',
|
'z',
|
||||||
'X',
|
'x',
|
||||||
'C',
|
'c',
|
||||||
'V',
|
'v',
|
||||||
'B',
|
'b',
|
||||||
'N',
|
'n',
|
||||||
'PAGEDOWN',
|
'pagedown',
|
||||||
'PAGEUP',
|
'pageup',
|
||||||
],
|
],
|
||||||
'CTRL+SHIFT': ['Q', 'W', 'R', 'T', 'P', 'A', 'H', 'V', 'B', 'N'],
|
'ctrl+shift': ['q', 'w', 'r', 't', 'p', 'a', 'h', 'v', 'b', 'n'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const allowedKeys = [
|
export const allowedKeys = [
|
||||||
...[8, 13, 27, 32, 46], // BACKSPACE, ENTER, ESCAPE, SPACE, DELETE
|
...[8, 13, 27, 32, 46], // backspace, enter, escape, space, delete
|
||||||
...[12, 106, 107, 109, 110, 111], // Numpad keys
|
...[12, 106, 107, 109, 110, 111], // Numpad keys
|
||||||
...range(218, 220), // [\]
|
...range(218, 220), // [\]
|
||||||
...range(185, 190), // ;=,-./
|
...range(185, 190), // ;=,-./
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
export { UserPreferences } from './UserPreferences.js';
|
export { UserPreferences } from './UserPreferences.js';
|
||||||
export { AboutContent } from '../content/aboutContent/AboutContent.js';
|
export { AboutContent } from '../content/aboutContent/AboutContent.js';
|
||||||
export { UserPreferencesModal } from './UserPreferencesModal.js';
|
export { UserPreferencesForm } from './UserPreferencesForm.js';
|
||||||
export { GeneralPreferences } from './GeneralPreferences.js';
|
export { GeneralPreferences } from './GeneralPreferences.js';
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import './UserPreferencesModal.styl';
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Modal from 'react-bootstrap-modal';
|
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
|
||||||
|
|
||||||
import 'react-bootstrap-modal/lib/css/rbm-patch.css';
|
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
|
||||||
import isEqual from 'lodash.isequal';
|
|
||||||
import { UserPreferences } from './UserPreferences';
|
|
||||||
|
|
||||||
// TODO: Is this the only component importing these?
|
|
||||||
import './../../design/styles/common/modal.styl';
|
|
||||||
|
|
||||||
class UserPreferencesModal extends Component {
|
|
||||||
// TODO: Make this component more generic to allow things other than W/L and hotkeys...
|
|
||||||
static propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onCancel: PropTypes.func,
|
|
||||||
onSave: PropTypes.func,
|
|
||||||
onResetToDefaults: PropTypes.func,
|
|
||||||
windowLevelData: PropTypes.object,
|
|
||||||
hotKeysData: PropTypes.object,
|
|
||||||
t: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
windowLevelData: cloneDeep(props.windowLevelData),
|
|
||||||
hotKeysData: cloneDeep(props.hotKeysData),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
isOpen: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
save = () => {
|
|
||||||
this.props.onSave({
|
|
||||||
windowLevelData: this.state.windowLevelData,
|
|
||||||
hotKeysData: this.state.hotKeysData,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidUpdate(prev, next) {
|
|
||||||
const newStateData = {};
|
|
||||||
|
|
||||||
if (!isEqual(prev.windowLevelData, next.windowLevelData)) {
|
|
||||||
newStateData.windowLevelData = prev.windowLevelData;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isEqual(prev.hotKeysData, next.hotKeysData)) {
|
|
||||||
newStateData.hotKeysData = prev.hotKeysData;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newStateData.hotKeysData || newStateData.windowLevelData) {
|
|
||||||
this.setState(newStateData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
show={this.props.isOpen}
|
|
||||||
onHide={this.props.onCancel}
|
|
||||||
aria-labelledby="ModalHeader"
|
|
||||||
className="ModalHeader modal fade themed in"
|
|
||||||
backdrop={false}
|
|
||||||
large={true}
|
|
||||||
keyboard={false}
|
|
||||||
>
|
|
||||||
<Modal.Header closeButton>
|
|
||||||
<Modal.Title>{this.props.t('User Preferences')}</Modal.Title>
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Body>
|
|
||||||
<UserPreferences
|
|
||||||
windowLevelData={this.state.windowLevelData}
|
|
||||||
hotKeysData={this.state.hotKeysData}
|
|
||||||
/>
|
|
||||||
</Modal.Body>
|
|
||||||
<Modal.Footer>
|
|
||||||
<button
|
|
||||||
className="btn btn-danger pull-left"
|
|
||||||
onClick={this.props.onResetToDefaults}
|
|
||||||
>
|
|
||||||
{this.props.t('Reset to Defaults')}
|
|
||||||
</button>
|
|
||||||
<Modal.Dismiss className="btn btn-default">
|
|
||||||
{this.props.t('Cancel')}
|
|
||||||
</Modal.Dismiss>
|
|
||||||
<button className="btn btn-primary" onClick={this.save}>
|
|
||||||
{this.props.t('Save')}
|
|
||||||
</button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectedComponent = withTranslation('UserPreferencesModal')(
|
|
||||||
UserPreferencesModal
|
|
||||||
);
|
|
||||||
export { connectedComponent as UserPreferencesModal };
|
|
||||||
export default connectedComponent;
|
|
||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
AboutContent,
|
AboutContent,
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
UserPreferencesModal,
|
UserPreferencesForm,
|
||||||
OHIFModal,
|
OHIFModal,
|
||||||
} from './components';
|
} from './components';
|
||||||
import { useDebounce, useMedia } from './hooks';
|
import { useDebounce, useMedia } from './hooks';
|
||||||
@@ -101,7 +101,7 @@ export {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
AboutContent,
|
AboutContent,
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
UserPreferencesModal,
|
UserPreferencesForm,
|
||||||
ViewerbaseDragDropContext,
|
ViewerbaseDragDropContext,
|
||||||
SnackbarProvider,
|
SnackbarProvider,
|
||||||
useSnackbarContext,
|
useSnackbarContext,
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
import React, { useState, createContext, useContext } from 'react';
|
import React, {
|
||||||
|
useState,
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const ModalContext = createContext(null);
|
const ModalContext = createContext(null);
|
||||||
const { Provider, Consumer } = ModalContext;
|
const { Provider } = ModalContext;
|
||||||
|
|
||||||
export const useModal = () => useContext(ModalContext);
|
export const useModal = () => useContext(ModalContext);
|
||||||
|
|
||||||
const ModalProvider = ({ children, modal: Modal }) => {
|
const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||||
const DEFAULT_OPTIONS = {
|
const DEFAULT_OPTIONS = {
|
||||||
component: null /* The component instance inside the modal. */,
|
component: null /* The component instance inside the modal. */,
|
||||||
|
header: null /* The content inside the modal header. */,
|
||||||
|
footer: null /* The content inside the modal footer. */,
|
||||||
backdrop: false /* Should the modal render a backdrop overlay. */,
|
backdrop: false /* Should the modal render a backdrop overlay. */,
|
||||||
keyboard: false /* Modal is dismissible via the esc key. */,
|
keyboard: false /* Modal is dismissible via the esc key. */,
|
||||||
show: true /* Make the Modal visible or hidden. */,
|
show: true /* Make the Modal visible or hidden. */,
|
||||||
@@ -20,51 +28,81 @@ const ModalProvider = ({ children, modal: Modal }) => {
|
|||||||
|
|
||||||
const [options, setOptions] = useState(DEFAULT_OPTIONS);
|
const [options, setOptions] = useState(DEFAULT_OPTIONS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the implementation of a modal service that can be used by extensions.
|
||||||
|
*
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (service) {
|
||||||
|
service.setServiceImplementation({ hide, show });
|
||||||
|
}
|
||||||
|
}, [hide, service, show]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the modal and override its configuration props.
|
* Show the modal and override its configuration props.
|
||||||
*
|
*
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
const show = (component, props = {}) =>
|
const show = useCallback(
|
||||||
setOptions(Object.assign({}, options, props, { component }));
|
(component, props = {}) =>
|
||||||
|
setOptions(Object.assign({}, options, props, { component })),
|
||||||
|
[options]
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide the modal and set its properties to default.
|
* Hide the modal and set its properties to default.
|
||||||
*
|
*
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
const hide = () => setOptions(DEFAULT_OPTIONS);
|
const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), [
|
||||||
|
DEFAULT_OPTIONS,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { component: Component } = options;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider value={{ ...options, show, hide }}>
|
<Provider value={{ show, hide }}>
|
||||||
<Consumer>
|
{options.component && (
|
||||||
{props => {
|
<Modal
|
||||||
const { component, footer, header, customClassName } = props;
|
className={classNames(
|
||||||
return component ? (
|
options.customClassName,
|
||||||
<Modal
|
options.component.className
|
||||||
className={classNames(customClassName, component.className)}
|
)}
|
||||||
backdrop={options.backdrop}
|
backdrop={options.backdrop}
|
||||||
keyboard={options.keyboard}
|
keyboard={options.keyboard}
|
||||||
show={options.show}
|
show={options.show}
|
||||||
title={options.title}
|
title={options.title}
|
||||||
closeButton={options.closeButton}
|
closeButton={options.closeButton}
|
||||||
onHide={hide}
|
footer={options.footer}
|
||||||
footer={footer}
|
header={options.header}
|
||||||
header={header}
|
onHide={hide}
|
||||||
>
|
>
|
||||||
{component}
|
<Component {...options} show={show} hide={hide} />
|
||||||
</Modal>
|
</Modal>
|
||||||
) : null;
|
)}
|
||||||
}}
|
|
||||||
</Consumer>
|
|
||||||
{children}
|
{children}
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ModalProvider.defaultProps = {
|
||||||
|
service: null,
|
||||||
|
};
|
||||||
|
|
||||||
ModalProvider.propTypes = {
|
ModalProvider.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.oneOfType([
|
||||||
modal: PropTypes.node,
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node,
|
||||||
|
]).isRequired,
|
||||||
|
modal: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node,
|
||||||
|
PropTypes.func,
|
||||||
|
]).isRequired,
|
||||||
|
service: PropTypes.shape({
|
||||||
|
setServiceImplementation: PropTypes.func,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,7 +112,8 @@ ModalProvider.propTypes = {
|
|||||||
*/
|
*/
|
||||||
export const withModal = Component => {
|
export const withModal = Component => {
|
||||||
return function WrappedComponent(props) {
|
return function WrappedComponent(props) {
|
||||||
return <Component {...props} modalContext={{ ...useModal() }} />;
|
const { show, hide } = useModal();
|
||||||
|
return <Component {...props} modal={{ show, hide }} />;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import React, { useState, createContext, useContext } from 'react';
|
import React, {
|
||||||
|
useState,
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
} from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import SnackbarContainer from '../components/snackbar/SnackbarContainer';
|
import SnackbarContainer from '../components/snackbar/SnackbarContainer';
|
||||||
import SnackbarTypes from '../components/snackbar/SnackbarTypes';
|
import SnackbarTypes from '../components/snackbar/SnackbarTypes';
|
||||||
|
|
||||||
@@ -6,7 +14,7 @@ const SnackbarContext = createContext(null);
|
|||||||
|
|
||||||
export const useSnackbarContext = () => useContext(SnackbarContext);
|
export const useSnackbarContext = () => useContext(SnackbarContext);
|
||||||
|
|
||||||
const SnackbarProvider = ({ children }) => {
|
const SnackbarProvider = ({ children, service }) => {
|
||||||
const DEFAULT_OPTIONS = {
|
const DEFAULT_OPTIONS = {
|
||||||
title: '',
|
title: '',
|
||||||
message: '',
|
message: '',
|
||||||
@@ -19,45 +27,62 @@ const SnackbarProvider = ({ children }) => {
|
|||||||
const [count, setCount] = useState(1);
|
const [count, setCount] = useState(1);
|
||||||
const [snackbarItems, setSnackbarItems] = useState([]);
|
const [snackbarItems, setSnackbarItems] = useState([]);
|
||||||
|
|
||||||
const show = options => {
|
/**
|
||||||
if (!options || (!options.title && !options.message)) {
|
* Sets the implementation of a notification service that can be used by extensions.
|
||||||
console.warn(
|
*
|
||||||
'Snackbar cannot be rendered without required parameters: title | message'
|
* @returns void
|
||||||
);
|
*/
|
||||||
|
useEffect(() => {
|
||||||
return null;
|
if (service) {
|
||||||
|
service.setServiceImplementation({ hide, show });
|
||||||
}
|
}
|
||||||
|
}, [service, hide, show]);
|
||||||
|
|
||||||
const newItem = {
|
const show = useCallback(
|
||||||
...DEFAULT_OPTIONS,
|
options => {
|
||||||
...options,
|
if (!options || (!options.title && !options.message)) {
|
||||||
id: count,
|
console.warn(
|
||||||
visible: true,
|
'Snackbar cannot be rendered without required parameters: title | message'
|
||||||
};
|
);
|
||||||
|
|
||||||
setSnackbarItems(state => [...state, newItem]);
|
return null;
|
||||||
setCount(count + 1);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const hide = id => {
|
const newItem = {
|
||||||
const hideItem = items => {
|
...DEFAULT_OPTIONS,
|
||||||
const newItems = items.map(item => {
|
...options,
|
||||||
if (item.id === id) {
|
id: count,
|
||||||
item.visible = false;
|
visible: true,
|
||||||
}
|
};
|
||||||
|
|
||||||
return item;
|
setSnackbarItems(state => [...state, newItem]);
|
||||||
});
|
setCount(count + 1);
|
||||||
|
},
|
||||||
|
[count, DEFAULT_OPTIONS]
|
||||||
|
);
|
||||||
|
|
||||||
return newItems;
|
const hide = useCallback(
|
||||||
};
|
id => {
|
||||||
|
const hideItem = items => {
|
||||||
|
const newItems = items.map(item => {
|
||||||
|
if (item.id === id) {
|
||||||
|
item.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
setSnackbarItems(state => hideItem(state));
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
return newItems;
|
||||||
setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
|
};
|
||||||
}, 1000);
|
|
||||||
};
|
setSnackbarItems(state => hideItem(state));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
[setSnackbarItems]
|
||||||
|
);
|
||||||
|
|
||||||
const hideAll = () => {
|
const hideAll = () => {
|
||||||
// reset count
|
// reset count
|
||||||
@@ -85,6 +110,21 @@ const SnackbarProvider = ({ children }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SnackbarProvider.defaultProps = {
|
||||||
|
service: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
SnackbarProvider.propTypes = {
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node,
|
||||||
|
PropTypes.func,
|
||||||
|
]).isRequired,
|
||||||
|
service: PropTypes.shape({
|
||||||
|
setServiceImplementation: PropTypes.func,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* High Order Component to use the snackbar methods through a Class Component
|
* High Order Component to use the snackbar methods through a Class Component
|
||||||
|
|||||||
@@ -3,6 +3,115 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.8.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.8.4...@ohif/viewer@2.8.5) (2019-11-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* minor date picker UX improvements ([813ee5e](https://github.com/OHIF/Viewers/commit/813ee5ed4d78b7bda234922d5f3389efe346451c))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.8.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.8.3...@ohif/viewer@2.8.4) (2019-11-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.8.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.8.2...@ohif/viewer@2.8.3) (2019-11-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.8.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.8.1...@ohif/viewer@2.8.2) (2019-11-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.8.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.8.0...@ohif/viewer@2.8.1) (2019-11-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.8.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.7.1...@ohif/viewer@2.8.0) (2019-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
|
||||||
|
* filter field for google api windows ([#1170](https://github.com/OHIF/Viewers/issues/1170)) ([c59c5b3](https://github.com/OHIF/Viewers/commit/c59c5b3f14d44f1c06aa396125a1f4caaa431c25))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.7.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.7.0...@ohif/viewer@2.7.1) (2019-11-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix for JS breaking on header ([#1164](https://github.com/OHIF/Viewers/issues/1164)) ([0fbaf95](https://github.com/OHIF/Viewers/commit/0fbaf95971dc0b3a671e1f586a876d9019e860ed))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.7.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.4...@ohif/viewer@2.7.0) (2019-11-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.6.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.3...@ohif/viewer@2.6.4) (2019-11-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.6.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.2...@ohif/viewer@2.6.3) (2019-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.6.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.1...@ohif/viewer@2.6.2) (2019-11-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.6.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.0...@ohif/viewer@2.6.1) (2019-11-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [2.6.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.5.0...@ohif/viewer@2.6.0) (2019-11-06)
|
# [2.6.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.5.0...@ohif/viewer@2.6.0) (2019-11-06)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
|||||||
// Hotkey I
|
// Hotkey I
|
||||||
cy.get('body').type('I');
|
cy.get('body').type('I');
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot();
|
cy.screenshot('Hotkey I - Should Invert Image');
|
||||||
cy.percyCanvasSnapshot('Hotkey I - invert image');
|
cy.percyCanvasSnapshot('Hotkey I - Should Invert Image');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if hotkeys "+", "-" and "=" can zoom in, out and fit to viewport', () => {
|
it('checks if hotkeys "+", "-" and "=" can zoom in, out and fit to viewport', () => {
|
||||||
@@ -76,8 +76,8 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
|||||||
cy.get('@viewportInfoBottomRight').should('contains.text', 'Zoom: 211%');
|
cy.get('@viewportInfoBottomRight').should('contains.text', 'Zoom: 211%');
|
||||||
|
|
||||||
// Visual comparison to make sure the 'inverted' image was reset
|
// Visual comparison to make sure the 'inverted' image was reset
|
||||||
cy.screenshot();
|
cy.screenshot('Hotkey SPACEBAR - Should Reset Image');
|
||||||
cy.percyCanvasSnapshot('Hotkey SPACEBAR - Reset Image');
|
cy.percyCanvasSnapshot('Hotkey SPACEBAR - Should Reset Image');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => {
|
it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.openStudy('MISTER^MR');
|
cy.openStudy('MISTER^MR');
|
||||||
cy.waitDicomImage();
|
cy.waitDicomImage();
|
||||||
cy.expectMinimumThumbnails(3);
|
cy.expectMinimumThumbnails(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -112,25 +112,14 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
cy.wrap($panBtn).should('have.class', 'active');
|
cy.wrap($panBtn).should('have.class', 'active');
|
||||||
});
|
});
|
||||||
|
|
||||||
//Get image position from cornerstone and check if y axis was modified
|
|
||||||
let cornerstone;
|
|
||||||
let currentPan;
|
|
||||||
|
|
||||||
cy.window()
|
|
||||||
.its('cornerstone')
|
|
||||||
.then(c => {
|
|
||||||
cornerstone = c;
|
|
||||||
currentPan = () =>
|
|
||||||
cornerstone.getEnabledElements()[0].viewport.translation;
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get('@viewport')
|
cy.get('@viewport')
|
||||||
.trigger('mousedown', 'center', { which: 1 })
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
.trigger('mousemove', 'bottom', { which: 1 })
|
.trigger('mousemove', 'bottom', { which: 1 })
|
||||||
.trigger('mouseup', 'bottom')
|
.trigger('mouseup', 'bottom');
|
||||||
.then(() => {
|
|
||||||
expect(currentPan().y > 0).to.eq(true);
|
// Visual comparison
|
||||||
});
|
cy.screenshot('Pan tool moved the image inside the viewport');
|
||||||
|
cy.percyCanvasSnapshot('Pan tool moved the image inside the viewport');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
|
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
|
||||||
@@ -312,18 +301,18 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
.find('td')
|
.find('td')
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.eq', 9);
|
.should('be.eq', 9);
|
||||||
|
cy.get('@layoutBtn').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
//verify if layout has changed to 2 viewports
|
//verify if layout has changed to 2 viewports
|
||||||
cy.get('tbody > :nth-child(1) > :nth-child(2)').click();
|
cy.setLayout(1, 2);
|
||||||
cy.get('.viewport-container').then($viewport => {
|
cy.get('.viewport-container').then($viewport => {
|
||||||
cy.wrap($viewport)
|
cy.wrap($viewport)
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.eq', 2);
|
.should('be.eq', 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('@layoutBtn').click();
|
cy.setLayout(2, 1);
|
||||||
cy.get('tbody > :nth-child(2) > :nth-child(1)').click();
|
|
||||||
cy.get('.viewport-container').then($viewport => {
|
cy.get('.viewport-container').then($viewport => {
|
||||||
cy.wrap($viewport)
|
cy.wrap($viewport)
|
||||||
.its('length')
|
.its('length')
|
||||||
@@ -331,8 +320,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//verify if layout has changed to 3 viewports
|
//verify if layout has changed to 3 viewports
|
||||||
cy.get('@layoutBtn').click();
|
cy.setLayout(1, 3);
|
||||||
cy.get('tbody > :nth-child(1) > :nth-child(3)').click();
|
|
||||||
cy.get('.viewport-container').then($viewport => {
|
cy.get('.viewport-container').then($viewport => {
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
cy.wrap($viewport)
|
cy.wrap($viewport)
|
||||||
@@ -340,8 +328,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
.should('be.eq', 3);
|
.should('be.eq', 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('@layoutBtn').click();
|
cy.setLayout(3, 1);
|
||||||
cy.get('tbody > :nth-child(3) > :nth-child(1)').click();
|
|
||||||
cy.get('.viewport-container').then($viewport => {
|
cy.get('.viewport-container').then($viewport => {
|
||||||
cy.wrap($viewport)
|
cy.wrap($viewport)
|
||||||
.its('length')
|
.its('length')
|
||||||
@@ -349,8 +336,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//verify if layout has changed to 4 viewports
|
//verify if layout has changed to 4 viewports
|
||||||
cy.get('@layoutBtn').click();
|
cy.setLayout(2, 2);
|
||||||
cy.get('tbody > :nth-child(2) > :nth-child(2)').click();
|
|
||||||
cy.get('.viewport-container').then($viewport => {
|
cy.get('.viewport-container').then($viewport => {
|
||||||
cy.wrap($viewport)
|
cy.wrap($viewport)
|
||||||
.its('length')
|
.its('length')
|
||||||
@@ -358,16 +344,14 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//verify if layout has changed to 6 viewports
|
//verify if layout has changed to 6 viewports
|
||||||
cy.get('@layoutBtn').click();
|
cy.setLayout(2, 3);
|
||||||
cy.get('tbody > :nth-child(2) > :nth-child(3)').click();
|
|
||||||
cy.get('.viewport-container').then($viewport => {
|
cy.get('.viewport-container').then($viewport => {
|
||||||
cy.wrap($viewport)
|
cy.wrap($viewport)
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.eq', 6);
|
.should('be.eq', 6);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('@layoutBtn').click();
|
cy.setLayout(3, 2);
|
||||||
cy.get('tbody > :nth-child(3) > :nth-child(2)').click();
|
|
||||||
cy.get('.viewport-container').then($viewport => {
|
cy.get('.viewport-container').then($viewport => {
|
||||||
cy.wrap($viewport)
|
cy.wrap($viewport)
|
||||||
.its('length')
|
.its('length')
|
||||||
@@ -375,8 +359,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//verify if layout has changed to 9 viewports
|
//verify if layout has changed to 9 viewports
|
||||||
cy.get('@layoutBtn').click();
|
cy.setLayout(3, 3);
|
||||||
cy.get('tbody > :nth-child(3) > :nth-child(3)').click();
|
|
||||||
cy.get('.viewport-container').then($viewport => {
|
cy.get('.viewport-container').then($viewport => {
|
||||||
cy.wrap($viewport)
|
cy.wrap($viewport)
|
||||||
.its('length')
|
.its('length')
|
||||||
@@ -384,8 +367,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//verify if layout has changed to 1 viewport
|
//verify if layout has changed to 1 viewport
|
||||||
cy.get('@layoutBtn').click();
|
cy.setLayout(1, 1);
|
||||||
cy.get('tbody > :nth-child(1) > :nth-child(1)').click();
|
|
||||||
cy.get('.viewport-container').then($viewport => {
|
cy.get('.viewport-container').then($viewport => {
|
||||||
cy.wrap($viewport)
|
cy.wrap($viewport)
|
||||||
.its('length')
|
.its('length')
|
||||||
@@ -425,7 +407,9 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
.as('toolbarOverlay')
|
.as('toolbarOverlay')
|
||||||
.should('be.visible');
|
.should('be.visible');
|
||||||
//Click on Clear button
|
//Click on Clear button
|
||||||
cy.get('.tooltip-inner > :nth-child(10)').click();
|
cy.get('.tooltip-inner')
|
||||||
|
.contains('Clear')
|
||||||
|
.click();
|
||||||
|
|
||||||
//Verify if measurements were removed from the measurements panel
|
//Verify if measurements were removed from the measurements panel
|
||||||
cy.get('.measurementItem').should('not.exist');
|
cy.get('.measurementItem').should('not.exist');
|
||||||
@@ -457,8 +441,15 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
|
|
||||||
//Click More button
|
//Click More button
|
||||||
cy.get('@moreBtn').click();
|
cy.get('@moreBtn').click();
|
||||||
//Click Eraser button
|
//Verify if overlay is displayed
|
||||||
cy.get('.tooltip-inner > :nth-child(12)').click();
|
cy.get('.tooltip-toolbar-overlay')
|
||||||
|
.should('be.visible')
|
||||||
|
.then(() => {
|
||||||
|
//Click Eraser button
|
||||||
|
cy.get('.tooltip-inner')
|
||||||
|
.contains('Eraser')
|
||||||
|
.click({ force: true });
|
||||||
|
});
|
||||||
|
|
||||||
//Erase measurement #1 and Verify if it was removed from the measurements panel
|
//Erase measurement #1 and Verify if it was removed from the measurements panel
|
||||||
const [x1, y1] = [150, 100];
|
const [x1, y1] = [150, 100];
|
||||||
@@ -472,4 +463,70 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
cy.get('@viewport').click(x2, y2, { force: true });
|
cy.get('@viewport').click(x2, y2, { force: true });
|
||||||
cy.get('.measurementItem').should('not.exist');
|
cy.get('.measurementItem').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('check if Invert tool will change the colors of the image in the viewport', () => {
|
||||||
|
// Click on More button
|
||||||
|
cy.get('@moreBtn').click();
|
||||||
|
// Verify if overlay is displayed
|
||||||
|
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
||||||
|
|
||||||
|
// Click on Invert button
|
||||||
|
cy.get('.tooltip-inner')
|
||||||
|
.contains('Invert')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.screenshot('Invert tool - Should Invert Canvas');
|
||||||
|
cy.percyCanvasSnapshot('Invert tool - Should Invert Canvas');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('check if Rotate tool will change the image orientation in the viewport', () => {
|
||||||
|
//Click on More button
|
||||||
|
cy.get('@moreBtn').click();
|
||||||
|
//Verify if overlay is displayed
|
||||||
|
cy.get('.tooltip-toolbar-overlay')
|
||||||
|
.should('be.visible')
|
||||||
|
.then(() => {
|
||||||
|
//Click on Rotate button
|
||||||
|
cy.get('.tooltip-inner')
|
||||||
|
.contains('Rotate')
|
||||||
|
.click({ force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.screenshot('Rotate tool - Should Rotate Image to Right');
|
||||||
|
cy.percyCanvasSnapshot('Rotate tool - Should Rotate Image to Right');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('check if Flip H tool will flip the image horizontally in the viewport', () => {
|
||||||
|
//Click on More button
|
||||||
|
cy.get('@moreBtn').click();
|
||||||
|
//Verify if overlay is displayed
|
||||||
|
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
||||||
|
|
||||||
|
//Click on Flip H button
|
||||||
|
cy.get('.tooltip-inner')
|
||||||
|
.contains('Flip H')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.screenshot('Flip H tool - Should Flip Image on Y axis');
|
||||||
|
cy.percyCanvasSnapshot('Flip H tool - Should Flip Image on Y axis');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('check if Flip V tool will flip the image vertically in the viewport', () => {
|
||||||
|
//Click on More button
|
||||||
|
cy.get('@moreBtn').click();
|
||||||
|
//Verify if overlay is displayed
|
||||||
|
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
||||||
|
|
||||||
|
//Click on Flip V button
|
||||||
|
cy.get('.tooltip-inner')
|
||||||
|
.contains('Flip V')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.screenshot('Flip V tool - Should Flip Image on X axis');
|
||||||
|
cy.percyCanvasSnapshot('Flip V tool - Should Flip Image on X axis');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -4,7 +4,7 @@ describe('OHIFStandaloneViewer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('loads route with at least 2 rows', () => {
|
it('loads route with at least 2 rows', () => {
|
||||||
cy.screenshot();
|
cy.screenshot('Study List');
|
||||||
cy.percyCanvasSnapshot('Study List');
|
cy.percyCanvasSnapshot('Study List');
|
||||||
|
|
||||||
cy.get('[data-cy="study-list-results"] tr')
|
cy.get('[data-cy="study-list-results"] tr')
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
//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() {
|
||||||
|
cy.viewport(1750, 720);
|
||||||
|
cy.openStudyList();
|
||||||
|
cy.initStudyListAliasesOnDesktop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches Patient Name with exact string', function() {
|
||||||
|
cy.get('@patientName').type('Juno');
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList();
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list.length).to.be.eq(2);
|
||||||
|
expect($list).to.contain('Juno');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches MRN with exact string', function() {
|
||||||
|
cy.get('@MRN').type('ProstateX-0000');
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList();
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list.length).to.be.eq(2);
|
||||||
|
expect($list).to.contain('ProstateX-0000');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches Accession with exact string', function() {
|
||||||
|
cy.get('@accessionNumber').type('fpcben98890');
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList();
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list.length).to.be.eq(1);
|
||||||
|
expect($list).to.contain('fpcben98890');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches Modality with camel case', function() {
|
||||||
|
cy.get('@modalities').type('Mr');
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList();
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list.length).to.be.eq(18);
|
||||||
|
expect($list).to.contain('MR');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches Description with exact string', function() {
|
||||||
|
cy.get('@studyDescription').type('CHEST');
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList();
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list.length).to.be.eq(2);
|
||||||
|
expect($list).to.contain('CHEST');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes rows per page and checks the study count', function() {
|
||||||
|
//Show rows per page options
|
||||||
|
const pageRows = [25, 50, 100];
|
||||||
|
|
||||||
|
//Check all options of rows
|
||||||
|
pageRows.forEach(numRows => {
|
||||||
|
cy.get('select').select(numRows.toString()); //Select rows per page option
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList().then(() => {
|
||||||
|
//Compare the search result with the Study Count on the table header
|
||||||
|
cy.get('@studyCount')
|
||||||
|
.should($studyCount => {
|
||||||
|
expect(parseInt($studyCount.text())).to.be.at.most(numRows); //less than or equals to
|
||||||
|
})
|
||||||
|
.then($studyCount => {
|
||||||
|
//Compare to the number of rows in the search result
|
||||||
|
cy.get('@searchResult').then($searchResult => {
|
||||||
|
let countResults = $searchResult.length;
|
||||||
|
expect($studyCount.text()).to.be.eq(countResults.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//TO-TO: This test should be uncommented once issue #1120 is fixed:
|
||||||
|
//https://github.com/OHIF/Viewers/issues/1120
|
||||||
|
// it('filter study list by Study Date', function() {
|
||||||
|
// //Type Start and End dates
|
||||||
|
// cy.get('@studyListStartDate').type('01/01/2000');
|
||||||
|
// cy.get('@studyListEndDate').type('01/01/2019');
|
||||||
|
// //Display all results into one page
|
||||||
|
// cy.get('select').select('100');
|
||||||
|
// //Checks if all expected results are displayed
|
||||||
|
// //Wait result list to be displayed
|
||||||
|
// cy.waitStudyList();
|
||||||
|
// cy.get('@searchResult').should($list => {
|
||||||
|
// expect($list.length).to.be.eq(42);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
|
context('Tablet resolution', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
cy.viewport(1000, 660);
|
||||||
|
cy.openStudyList();
|
||||||
|
cy.initStudyListAliasesOnTablet();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches Patient Name with exact string', function() {
|
||||||
|
cy.get('@patientNameOrMRN').type('Juno');
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList();
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list.length).to.be.eq(2);
|
||||||
|
expect($list).to.contain('Juno');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches MRN with with exact string', function() {
|
||||||
|
cy.get('@patientNameOrMRN').type('ProstateX-0000');
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList();
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list.length).to.be.eq(2);
|
||||||
|
expect($list).to.contain('ProstateX-0000');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches Modality with exact string', function() {
|
||||||
|
cy.get('@accessionModalityDescription').type('MR');
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList();
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list.length).to.be.eq(18);
|
||||||
|
expect($list).to.contain('MR');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches Accession with exact string', function() {
|
||||||
|
cy.get('@accessionModalityDescription').type('fpcben98890');
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList();
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list.length).to.be.eq(1);
|
||||||
|
expect($list).to.contain('fpcben98890');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('searches Description with exact string', function() {
|
||||||
|
cy.get('@accessionModalityDescription').type('CHEST');
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList();
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list.length).to.be.eq(2);
|
||||||
|
expect($list).to.contain('CHEST');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes rows per page and checks the study count', function() {
|
||||||
|
//Show rows per page options
|
||||||
|
const pageRows = [25, 50, 100];
|
||||||
|
|
||||||
|
//Check all options of rows
|
||||||
|
pageRows.forEach(numRows => {
|
||||||
|
cy.get('select').select(numRows.toString()); //Select rows per page option
|
||||||
|
//Wait result list to be displayed
|
||||||
|
cy.waitStudyList().then(() => {
|
||||||
|
//Compare the search result with the Study Count on the table header
|
||||||
|
cy.get('@studyCount')
|
||||||
|
.should($studyCount => {
|
||||||
|
expect(parseInt($studyCount.text())).to.be.at.most(numRows); //less than or equals to
|
||||||
|
})
|
||||||
|
.then($studyCount => {
|
||||||
|
//Compare to the number of rows in the search result
|
||||||
|
cy.get('@searchResult').then($searchResult => {
|
||||||
|
let countResults = $searchResult.length;
|
||||||
|
expect($studyCount.text()).to.be.eq(countResults.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//TO-TO: This test should be uncommented once issue #1120 is fixed:
|
||||||
|
//https://github.com/OHIF/Viewers/issues/1120
|
||||||
|
// it('filter study list by Study Date', function() {
|
||||||
|
// //Type Start and End dates
|
||||||
|
// cy.get('@studyListStartDate').type('01/01/2000');
|
||||||
|
// cy.get('@studyListEndDate').type('01/01/2019');
|
||||||
|
// //Display all results into one page
|
||||||
|
// cy.get('select').select('100');
|
||||||
|
// //Checks if all expected results are displayed
|
||||||
|
// //Wait result list to be displayed
|
||||||
|
// cy.waitStudyList();
|
||||||
|
// cy.get('@searchResult').should($list => {
|
||||||
|
// expect($list.length).to.be.eq(42);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -11,8 +11,8 @@ describe('OHIF Study Viewer Page', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('checks if series thumbnails are being displayed', function() {
|
it('checks if series thumbnails are being displayed', function() {
|
||||||
cy.screenshot();
|
cy.screenshot('Series Thumbnails - Should Display Thumbnails');
|
||||||
cy.percyCanvasSnapshot('Series Thumbnails');
|
cy.percyCanvasSnapshot('Series Thumbnails - Should Display Thumbnails');
|
||||||
|
|
||||||
cy.get('[data-cy="thumbnail-list"]')
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
.its('length')
|
.its('length')
|
||||||
@@ -72,30 +72,26 @@ describe('OHIF Study Viewer Page', function() {
|
|||||||
cy.get('@measurementsPanel').should('not.be.enabled');
|
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||||
});
|
});
|
||||||
|
|
||||||
//TO-DO: Test case will fail due to issue #1013: https://github.com/OHIF/Viewers/issues/1013
|
it('checks if Description can be added to measurement item under Measurements panel', () => {
|
||||||
|
cy.addLengthMeasurement(); //Adding measurement in the viewport
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
cy.get('.measurementItem').click();
|
||||||
|
|
||||||
// it('checks if Description can be added to measurement item under Measurements panel', () => {
|
// Click "Description"
|
||||||
// cy.addLengthMeasurement(); //Adding measurement in the viewport
|
cy.get('.btnAction')
|
||||||
// cy.get('@measurementsBtn').click();
|
.contains('Description')
|
||||||
// cy.get('.measurementItem').click();
|
.click();
|
||||||
//
|
|
||||||
// // Click "Description"
|
// Enter description text
|
||||||
// cy.get('.btnAction')
|
const descriptionText = 'Adding text for description test';
|
||||||
// .contains('Description')
|
cy.get('#description').type(descriptionText);
|
||||||
// .click();
|
|
||||||
//
|
// Confirm
|
||||||
// // Enter description text
|
cy.get('.btn-confirm').click();
|
||||||
// const descriptionText = 'Adding text for description test';
|
|
||||||
// cy.get('#description')
|
//Verify if descriptionText was added
|
||||||
// .type(descriptionText);
|
cy.get('.measurementLocation').should('contain.text', descriptionText);
|
||||||
//
|
});
|
||||||
// // Confirm
|
|
||||||
// cy.get('.btn-confirm').click();
|
|
||||||
//
|
|
||||||
// //Verify if descriptionText was added
|
|
||||||
// cy.get('.measurementLocation')
|
|
||||||
// .should('contain.text', descriptionText);
|
|
||||||
// });
|
|
||||||
|
|
||||||
it('checks if measurement item can be deleted through the context menu on the viewport', function() {
|
it('checks if measurement item can be deleted through the context menu on the viewport', function() {
|
||||||
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
|
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
|
||||||
@@ -228,14 +224,12 @@ describe('OHIF Study Viewer Page', function() {
|
|||||||
cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
|
cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
|
||||||
});
|
});
|
||||||
|
|
||||||
//TO-DO: this test is blocked due to issue #1072: https://github.com/OHIF/Viewers/issues/1072
|
it('performs single-click to load thumbnail in active viewport', () => {
|
||||||
// Uncomment this once #1072 is fixed.
|
cy.get('[data-cy="thumbnail-list"]:nth-child(3)').click();
|
||||||
// it('performs single-click to load thumbnail in active viewport', () => {
|
|
||||||
// cy.get('[data-cy="thumbnail-list"]:nth-child(3)').click();
|
|
||||||
|
|
||||||
// const expectedText = 'Ser 3';
|
const expectedText = 'Ser: 3';
|
||||||
// cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
|
cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
|
||||||
// });
|
});
|
||||||
|
|
||||||
it('performs right click to zoom', function() {
|
it('performs right click to zoom', function() {
|
||||||
//Right click on viewport
|
//Right click on viewport
|
||||||
@@ -244,7 +238,7 @@ describe('OHIF Study Viewer Page', function() {
|
|||||||
.trigger('mousemove', 'center', { which: 3 })
|
.trigger('mousemove', 'center', { which: 3 })
|
||||||
.trigger('mouseup');
|
.trigger('mouseup');
|
||||||
|
|
||||||
const expectedText = 'Zoom: 442%';
|
const expectedText = 'Zoom: 301%';
|
||||||
cy.get('@viewportInfoBottomRight').should('contains.text', expectedText);
|
cy.get('@viewportInfoBottomRight').should('contains.text', expectedText);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -273,8 +267,12 @@ describe('OHIF Study Viewer Page', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('opens About modal and verify the displayed information', function() {
|
it('opens About modal and verify the displayed information', function() {
|
||||||
cy.get('[data-cy="options-menu"]').click();
|
cy.get('[data-cy="options-menu"]')
|
||||||
cy.get('[data-cy="about-item-menu"]').click();
|
.first()
|
||||||
|
.click();
|
||||||
|
cy.get('[data-cy="about-item-menu"]')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
cy.get('.modal-content')
|
cy.get('.modal-content')
|
||||||
.as('aboutOverlay')
|
.as('aboutOverlay')
|
||||||
.should('be.visible');
|
.should('be.visible');
|
||||||
@@ -293,6 +291,10 @@ describe('OHIF Study Viewer Page', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.screenshot('About modal - Should display modal');
|
||||||
|
cy.percyCanvasSnapshot('About modal - Should display modal');
|
||||||
|
|
||||||
//close modal
|
//close modal
|
||||||
cy.get('.close').click();
|
cy.get('.close').click();
|
||||||
cy.get('@aboutOverlay').should('not.be.enabled');
|
cy.get('@aboutOverlay').should('not.be.enabled');
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ describe('OHIF Microscopy Extension', () => {
|
|||||||
.should('be.eq', 1);
|
.should('be.eq', 1);
|
||||||
|
|
||||||
cy.wait(3000); //Waiting for image to render before taking the snapshot
|
cy.wait(3000); //Waiting for image to render before taking the snapshot
|
||||||
cy.percyCanvasSnapshot('Microscopy Extension');
|
// Visual comparison
|
||||||
|
cy.screenshot('Microscopy Extension - Should display loaded canvas');
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'Microscopy Extension - Should display loaded canvas'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -22,6 +22,7 @@ describe('OHIF PDF Extension', () => {
|
|||||||
|
|
||||||
// This won't work unless we switch to an extension that renders using `canvas`
|
// This won't work unless we switch to an extension that renders using `canvas`
|
||||||
// Currently, we rely on the browser's built-in implementation
|
// Currently, we rely on the browser's built-in implementation
|
||||||
cy.percyCanvasSnapshot('PDF Extension');
|
cy.screenshot('PDF Extension - Should load PDF file');
|
||||||
|
cy.percyCanvasSnapshot('PDF Extension - Should load PDF file');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -6,37 +6,36 @@ describe('OHIF VTK Extension', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
//TODO: Due to percy snapshot, the images inside the viewports are being resized
|
||||||
|
//and once the browser continues the test execution, the images are not centered in the viewport.
|
||||||
|
//To fix that, we need to reload the page before each test.
|
||||||
|
//https://github.com/OHIF/Viewers/issues/1168
|
||||||
|
cy.reload();
|
||||||
|
|
||||||
|
//Waiting for the desired thumbnail content to be displayed
|
||||||
|
cy.get('[data-cy="thumbnail-list"]').should($list => {
|
||||||
|
expect($list).to.contain('CT WB 5.0 B35f');
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: We shouldn't have to drag the thumbnail
|
// TODO: We shouldn't have to drag the thumbnail
|
||||||
// This is a known bug; 2D MPR button does not show until viewport
|
// This is a known bug; 2D MPR button does not show until viewport
|
||||||
// has data from a drag-n-drop
|
// has data from a drag-n-drop
|
||||||
// Drag and drop first thumbnail into first viewport
|
// Drag and drop third thumbnail into first viewport
|
||||||
cy.get('[data-cy="thumbnail-list"]:nth-child(3)').drag(
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
'.viewport-drop-target'
|
.contains('CT WB 5.0 B35f')
|
||||||
);
|
.drag('.viewport-drop-target')
|
||||||
|
.wait(1000); // TODO: Once issue #1167 is fixed, the wait command should be removed. (https://github.com/OHIF/Viewers/issues/1167)
|
||||||
|
|
||||||
cy.get('.PluginSwitch > .toolbar-button')
|
//Select 2D MPR button
|
||||||
.as('twodmprBtn')
|
cy.get('.PluginSwitch > .toolbar-button').click();
|
||||||
.should('be.visible')
|
|
||||||
.then(btn => {
|
//Wait Reformatting Images
|
||||||
if (!btn.text().includes('Exit')) {
|
cy.waitVTKReformatting();
|
||||||
btn.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.initVTKToolsAliases();
|
cy.initVTKToolsAliases();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if VTK buttons are displayed on the toolbar', () => {
|
it('checks if VTK buttons are displayed on the toolbar', () => {
|
||||||
// Wait for start reformatting
|
|
||||||
cy.get('[data-cy="viewprt-grid"]', { timeout: 10000 }).should($grid => {
|
|
||||||
expect($grid).to.contain.text('Reform');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for finish reformatting
|
|
||||||
cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => {
|
|
||||||
expect($grid).not.to.contain.text('Reform');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get('@crosshairsBtn')
|
cy.get('@crosshairsBtn')
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.contains('Crosshairs');
|
.contains('Crosshairs');
|
||||||
@@ -57,7 +56,67 @@ describe('OHIF VTK Extension', () => {
|
|||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.contains('Layout');
|
.contains('Layout');
|
||||||
|
|
||||||
cy.wait(3000);
|
// Visual comparison
|
||||||
cy.percyCanvasSnapshot('VTK Extension');
|
cy.screenshot('VTK initial state - Should display toolbar and 3 viewports');
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'VTK initial state - Should display toolbar and 3 viewports'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks Crosshairs tool', () => {
|
||||||
|
cy.get('@crosshairsBtn').click();
|
||||||
|
|
||||||
|
// Click and Move the mouse inside the viewport
|
||||||
|
cy.get('[data-cy="viewport-container-0"]')
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
|
.trigger('mouseup');
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.screenshot(
|
||||||
|
"VTK Crosshairs tool - Should display crosshairs' green lines"
|
||||||
|
);
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
"VTK Crosshairs tool - Should display crosshairs' green lines"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks WWWC tool', () => {
|
||||||
|
cy.get('@wwwcBtn').click();
|
||||||
|
|
||||||
|
// Click and Move the mouse inside the viewport
|
||||||
|
cy.get('[data-cy="viewport-container-0"]')
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
|
.trigger('mouseup', { which: 1 })
|
||||||
|
.then(() => {
|
||||||
|
const expectedText = 'W: 350 L: -1044';
|
||||||
|
cy.get('.ViewportOverlay > div.bottom-right.overlay-element').should(
|
||||||
|
'contains.text',
|
||||||
|
expectedText
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.screenshot('VTK WWWC tool - Canvas should be bright');
|
||||||
|
cy.percyCanvasSnapshot('VTK WWWC tool - Canvas should be bright');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks Rotate tool', () => {
|
||||||
|
cy.get('@rotateBtn').click();
|
||||||
|
|
||||||
|
// Click and Move the mouse inside the viewport
|
||||||
|
cy.get('[data-cy="viewport-container-0"]')
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
|
.trigger('mouseup', { which: 1 });
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.screenshot('VTK Rotate tool - Should rotate image');
|
||||||
|
cy.percyCanvasSnapshot('VTK Rotate tool - Should rotate image');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -52,3 +52,28 @@ export function initVTKToolsAliases() {
|
|||||||
cy.get('.ohif-check-label').as('modeCheckbox');
|
cy.get('.ohif-check-label').as('modeCheckbox');
|
||||||
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Creating aliases for Study List page elements on Desktop experience
|
||||||
|
export function initStudyListAliasesOnDesktop() {
|
||||||
|
cy.get('.study-count').as('studyCount');
|
||||||
|
cy.get('#filter-patientName').as('patientName');
|
||||||
|
cy.get('#filter-patientId').as('MRN');
|
||||||
|
cy.get('#filter-accessionNumber').as('accessionNumber');
|
||||||
|
cy.get('#start-date').as('studyListStartDate');
|
||||||
|
cy.get('#end-date').as('studyListEndDate');
|
||||||
|
cy.get('#filter-modalities').as('modalities');
|
||||||
|
cy.get('#filter-studyDescription').as('studyDescription');
|
||||||
|
cy.get('[data-cy="study-list-results"] > tr').as('searchResult');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Creating aliases for Study List page elements on Tablet experience
|
||||||
|
export function initStudyListAliasesOnTablet() {
|
||||||
|
cy.get('.study-count').as('studyCount');
|
||||||
|
cy.get('#filter-patientNameOrId').as('patientNameOrMRN');
|
||||||
|
cy.get('#filter-accessionOrModalityOrDescription').as(
|
||||||
|
'accessionModalityDescription'
|
||||||
|
);
|
||||||
|
cy.get('#start-date').as('studyListStartDate');
|
||||||
|
cy.get('#end-date').as('studyListEndDate');
|
||||||
|
cy.get('[data-cy="study-list-results"] > tr').as('searchResult');
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
initCommonElementsAliases,
|
initCommonElementsAliases,
|
||||||
initRouteAliases,
|
initRouteAliases,
|
||||||
initVTKToolsAliases,
|
initVTKToolsAliases,
|
||||||
|
initStudyListAliasesOnDesktop,
|
||||||
|
initStudyListAliasesOnTablet,
|
||||||
} from './aliases.js';
|
} from './aliases.js';
|
||||||
|
|
||||||
// ***********************************************
|
// ***********************************************
|
||||||
@@ -76,12 +78,30 @@ Cypress.Commands.add('isPageLoaded', (url = '/viewer/') => {
|
|||||||
return cy.location('pathname', { timeout: 60000 }).should('include', url);
|
return cy.location('pathname', { timeout: 60000 }).should('include', url);
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('openStudyList', patientName => {
|
Cypress.Commands.add('openStudyList', () => {
|
||||||
cy.initRouteAliases();
|
cy.initRouteAliases();
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.wait('@getStudies');
|
cy.wait('@getStudies');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('waitStudyList', () => {
|
||||||
|
cy.get('@searchResult').should($list => {
|
||||||
|
expect($list).to.not.have.class('no-hover');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('waitVTKReformatting', () => {
|
||||||
|
// Wait for start reformatting
|
||||||
|
cy.get('[data-cy="viewprt-grid"]', { timeout: 10000 }).should($grid => {
|
||||||
|
expect($grid).to.contain.text('Reform');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for finish reformatting
|
||||||
|
cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => {
|
||||||
|
expect($grid).not.to.contain.text('Reform');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command to perform a drag and drop action. Before using this command, we must get the element that should be dragged first.
|
* Command to perform a drag and drop action. Before using this command, we must get the element that should be dragged first.
|
||||||
* Example of usage: cy.get(element-to-be-dragged).drag(dropzone-element)
|
* Example of usage: cy.get(element-to-be-dragged).drag(dropzone-element)
|
||||||
@@ -240,6 +260,16 @@ Cypress.Commands.add('initVTKToolsAliases', () => {
|
|||||||
initVTKToolsAliases();
|
initVTKToolsAliases();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Initialize aliases for Study List page elements
|
||||||
|
Cypress.Commands.add('initStudyListAliasesOnDesktop', () => {
|
||||||
|
initStudyListAliasesOnDesktop();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Initialize aliases for Study List page elements
|
||||||
|
Cypress.Commands.add('initStudyListAliasesOnTablet', () => {
|
||||||
|
initStudyListAliasesOnTablet();
|
||||||
|
});
|
||||||
|
|
||||||
//Add measurements in the viewport
|
//Add measurements in the viewport
|
||||||
Cypress.Commands.add(
|
Cypress.Commands.add(
|
||||||
'addLengthMeasurement',
|
'addLengthMeasurement',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/viewer",
|
"name": "@ohif/viewer",
|
||||||
"version": "2.6.0",
|
"version": "2.8.5",
|
||||||
"description": "OHIF Viewer",
|
"description": "OHIF Viewer",
|
||||||
"author": "OHIF Contributors",
|
"author": "OHIF Contributors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -45,14 +45,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"@ohif/core": "^1.7.0",
|
"@ohif/core": "^1.10.0",
|
||||||
"@ohif/extension-cornerstone": "^2.0.0",
|
"@ohif/extension-cornerstone": "^2.0.0",
|
||||||
"@ohif/extension-dicom-html": "^1.0.1",
|
"@ohif/extension-dicom-html": "^1.0.1",
|
||||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||||
"@ohif/extension-dicom-pdf": "^1.0.0",
|
"@ohif/extension-dicom-pdf": "^1.0.0",
|
||||||
"@ohif/extension-vtk": "^0.53.0",
|
"@ohif/extension-vtk": "^0.53.9",
|
||||||
"@ohif/i18n": "^0.52.0",
|
"@ohif/i18n": "^0.52.1",
|
||||||
"@ohif/ui": "^0.60.0",
|
"@ohif/ui": "^0.62.4",
|
||||||
"@tanem/react-nprogress": "^1.1.25",
|
"@tanem/react-nprogress": "^1.1.25",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"core-js": "^3.2.1",
|
"core-js": "^3.2.1",
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import './config';
|
|||||||
import {
|
import {
|
||||||
CommandsManager,
|
CommandsManager,
|
||||||
ExtensionManager,
|
ExtensionManager,
|
||||||
|
ServicesManager,
|
||||||
HotkeysManager,
|
HotkeysManager,
|
||||||
|
createUINotificationService,
|
||||||
|
createUIModalService,
|
||||||
utils,
|
utils,
|
||||||
} from '@ohif/core';
|
} from '@ohif/core';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
@@ -41,9 +44,17 @@ const commandsManagerConfig = {
|
|||||||
getActiveContexts: () => getActiveContexts(store.getState()),
|
getActiveContexts: () => getActiveContexts(store.getState()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Services
|
||||||
|
const UINotificationService = createUINotificationService();
|
||||||
|
const UIModalService = createUIModalService();
|
||||||
|
|
||||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||||
const hotkeysManager = new HotkeysManager(commandsManager);
|
const hotkeysManager = new HotkeysManager(commandsManager);
|
||||||
const extensionManager = new ExtensionManager({ commandsManager });
|
const servicesManager = new ServicesManager();
|
||||||
|
const extensionManager = new ExtensionManager({
|
||||||
|
commandsManager,
|
||||||
|
servicesManager,
|
||||||
|
});
|
||||||
// ~~~~ END APP SETUP
|
// ~~~~ END APP SETUP
|
||||||
|
|
||||||
// TODO[react] Use a provider when the whole tree is React
|
// TODO[react] Use a provider when the whole tree is React
|
||||||
@@ -76,9 +87,11 @@ class App extends Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._appConfig = props;
|
this._appConfig = props;
|
||||||
|
|
||||||
const { servers, extensions, hotkeys, oidc } = props;
|
const { servers, extensions, hotkeys, oidc } = props;
|
||||||
|
|
||||||
this.initUserManager(oidc);
|
this.initUserManager(oidc);
|
||||||
|
_initServices([UINotificationService, UIModalService]);
|
||||||
_initExtensions(extensions, hotkeys);
|
_initExtensions(extensions, hotkeys);
|
||||||
_initServers(servers);
|
_initServers(servers);
|
||||||
initWebWorkers();
|
initWebWorkers();
|
||||||
@@ -100,8 +113,11 @@ class App extends Component {
|
|||||||
<UserManagerContext.Provider value={userManager}>
|
<UserManagerContext.Provider value={userManager}>
|
||||||
<Router basename={routerBasename}>
|
<Router basename={routerBasename}>
|
||||||
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
||||||
<SnackbarProvider>
|
<SnackbarProvider service={UINotificationService}>
|
||||||
<ModalProvider modal={OHIFModal}>
|
<ModalProvider
|
||||||
|
modal={OHIFModal}
|
||||||
|
service={UIModalService}
|
||||||
|
>
|
||||||
<OHIFStandaloneViewer userManager={userManager} />
|
<OHIFStandaloneViewer userManager={userManager} />
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
@@ -121,8 +137,8 @@ class App extends Component {
|
|||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<Router basename={routerBasename}>
|
<Router basename={routerBasename}>
|
||||||
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
||||||
<SnackbarProvider>
|
<SnackbarProvider service={UINotificationService}>
|
||||||
<ModalProvider modal={OHIFModal}>
|
<ModalProvider modal={OHIFModal} service={UIModalService}>
|
||||||
<OHIFStandaloneViewer />
|
<OHIFStandaloneViewer />
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
@@ -168,6 +184,10 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _initServices(services) {
|
||||||
|
servicesManager.registerServices(services);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param
|
* @param
|
||||||
*/
|
*/
|
||||||
@@ -214,4 +234,4 @@ function _makeAbsoluteIfNecessary(url, base_url) {
|
|||||||
const ExportedApp = process.env.NODE_ENV === 'development' ? hot(App) : App;
|
const ExportedApp = process.env.NODE_ENV === 'development' ? hot(App) : App;
|
||||||
|
|
||||||
export default ExportedApp;
|
export default ExportedApp;
|
||||||
export { commandsManager, extensionManager, hotkeysManager };
|
export { commandsManager, extensionManager, hotkeysManager, servicesManager };
|
||||||
@@ -7,10 +7,10 @@ export default {
|
|||||||
*/
|
*/
|
||||||
id: 'measurements-table',
|
id: 'measurements-table',
|
||||||
|
|
||||||
preRegistration(configuration = {}) {
|
preRegistration({ servicesManager, configuration = {} }) {
|
||||||
init(configuration);
|
init({ servicesManager, configuration });
|
||||||
},
|
},
|
||||||
getPanelModule() {
|
getPanelModule({ servicesManager }) {
|
||||||
return {
|
return {
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const MEASUREMENT_ACTION_MAP = {
|
|||||||
* @export
|
* @export
|
||||||
* @param {*} configuration
|
* @param {*} configuration
|
||||||
*/
|
*/
|
||||||
export default function init(configuration) {
|
export default function init({ servicesManager, configuration = {} }) {
|
||||||
// If these tools were already added by a different extension, we want to replace
|
// If these tools were already added by a different extension, we want to replace
|
||||||
// them with the same tools that have an alternative configuration. By passing in
|
// them with the same tools that have an alternative configuration. By passing in
|
||||||
// our custom `getMeasurementLocationCallback`, we can...
|
// our custom `getMeasurementLocationCallback`, we can...
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
height: var(--top-bar-height);
|
height: var(--top-bar-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dd-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* Home Page */
|
/* Home Page */
|
||||||
.entry-header.header-big {
|
.entry-header.header-big {
|
||||||
background: rgba(21, 25, 30, 0.7);
|
background: rgba(21, 25, 30, 0.7);
|
||||||
|
|||||||
@@ -3,13 +3,11 @@ import { Link, withRouter } from 'react-router-dom';
|
|||||||
import { withTranslation } from 'react-i18next';
|
import { withTranslation } from 'react-i18next';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Dropdown } from '@ohif/ui';
|
import ConnectedUserPreferencesForm from '../../connectedComponents/ConnectedUserPreferencesForm';
|
||||||
import { AboutContent } from '@ohif/ui';
|
import { Dropdown, AboutContent, withModal } from '@ohif/ui';
|
||||||
import { withModal } from '@ohif/ui';
|
|
||||||
|
|
||||||
import OHIFLogo from '../OHIFLogo/OHIFLogo.js';
|
import OHIFLogo from '../OHIFLogo/OHIFLogo.js';
|
||||||
import { hotkeysManager } from './../../App.js';
|
|
||||||
import './Header.css';
|
import './Header.css';
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
import AppContext from './../../context/AppContext';
|
import AppContext from './../../context/AppContext';
|
||||||
|
|
||||||
@@ -22,7 +20,7 @@ class Header extends Component {
|
|||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
userManager: PropTypes.object,
|
userManager: PropTypes.object,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
modalContext: PropTypes.object,
|
modal: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -30,23 +28,9 @@ class Header extends Component {
|
|||||||
children: OHIFLogo(),
|
children: OHIFLogo(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// onSave: data => {
|
|
||||||
// const contextName = store.getState().commandContext.context;
|
|
||||||
// const preferences = cloneDeep(store.getState().preferences);
|
|
||||||
// preferences[contextName] = data;
|
|
||||||
// dispatch(setUserPreferences(preferences));
|
|
||||||
// dispatch(setUserPreferencesModalOpen(false));
|
|
||||||
// OHIF.hotkeysUtil.setHotkeys(data.hotKeysData);
|
|
||||||
// },
|
|
||||||
// onResetToDefaults: () => {
|
|
||||||
// dispatch(setUserPreferences());
|
|
||||||
// dispatch(setUserPreferencesModalOpen(false));
|
|
||||||
// OHIF.hotkeysUtil.setHotkeys();
|
|
||||||
// },
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { isUserPreferencesOpen: false, isOpen: false };
|
this.state = { isOpen: false };
|
||||||
|
|
||||||
this.loadOptions();
|
this.loadOptions();
|
||||||
}
|
}
|
||||||
@@ -56,7 +40,7 @@ class Header extends Component {
|
|||||||
t,
|
t,
|
||||||
user,
|
user,
|
||||||
userManager,
|
userManager,
|
||||||
modalContext: { show },
|
modal: { show },
|
||||||
} = this.props;
|
} = this.props;
|
||||||
this.options = [
|
this.options = [
|
||||||
{
|
{
|
||||||
@@ -68,6 +52,16 @@ class Header extends Component {
|
|||||||
customClassName: 'AboutContent',
|
customClassName: 'AboutContent',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Preferences ',
|
||||||
|
icon: {
|
||||||
|
name: 'user',
|
||||||
|
},
|
||||||
|
onClick: () =>
|
||||||
|
show(ConnectedUserPreferencesForm, {
|
||||||
|
title: t('User Preferences'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (user && userManager) {
|
if (user && userManager) {
|
||||||
@@ -77,15 +71,6 @@ class Header extends Component {
|
|||||||
onClick: () => userManager.signoutRedirect(),
|
onClick: () => userManager.signoutRedirect(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hotKeysData = hotkeysManager.hotkeyDefinitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
onUserPreferencesSave({ windowLevelData, hotKeysData }) {
|
|
||||||
// console.log(windowLevelData);
|
|
||||||
// console.log(hotKeysData);
|
|
||||||
// TODO: Update hotkeysManager
|
|
||||||
// TODO: reset `this.hotKeysData`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANTD -- Hamburger, Drawer, Menu
|
// ANTD -- Hamburger, Drawer, Menu
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
import Header from '../components/Header/Header.js';
|
import Header from '../components/Header/Header.js';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { hotkeysManager } from '../App.js';
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
|
const { hotkeyDefinitions = [] } = state.preferences || {};
|
||||||
|
|
||||||
|
const _hotkeyDefinitions =
|
||||||
|
hotkeyDefinitions.length > 0
|
||||||
|
? hotkeyDefinitions
|
||||||
|
: hotkeysManager.hotkeyDefaults;
|
||||||
|
|
||||||
|
hotkeysManager.setHotkeys(_hotkeyDefinitions);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: state.oidc && state.oidc.user,
|
user: state.oidc && state.oidc.user,
|
||||||
isOpen: state.ui.userPreferencesModalOpen,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { UserPreferencesForm } from '@ohif/ui';
|
||||||
|
import OHIF from '@ohif/core';
|
||||||
|
import { hotkeysManager } from '../App.js';
|
||||||
|
|
||||||
|
const { setUserPreferences } = OHIF.redux.actions;
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
const hotkeyDefinitions =
|
||||||
|
state.preferences.hotkeyDefinitions.length > 0
|
||||||
|
? state.preferences.hotkeyDefinitions
|
||||||
|
: hotkeysManager.hotkeyDefaults;
|
||||||
|
hotkeysManager.setHotkeys(hotkeyDefinitions);
|
||||||
|
return {
|
||||||
|
onClose: ownProps.hide,
|
||||||
|
windowLevelData: state.preferences ? state.preferences.windowLevelData : {},
|
||||||
|
hotkeyDefinitions,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||||
|
return {
|
||||||
|
onSave: ({ windowLevelData, hotkeyDefinitions }) => {
|
||||||
|
hotkeysManager.setHotkeys(hotkeyDefinitions);
|
||||||
|
ownProps.hide();
|
||||||
|
dispatch(setUserPreferences({ windowLevelData, hotkeyDefinitions }));
|
||||||
|
},
|
||||||
|
onResetToDefaults: () => {
|
||||||
|
hotkeysManager.restoreDefaultBindings();
|
||||||
|
ownProps.hide();
|
||||||
|
dispatch(setUserPreferences());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConnectedUserPreferencesForm = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(UserPreferencesForm);
|
||||||
|
|
||||||
|
export default ConnectedUserPreferencesForm;
|
||||||
@@ -287,7 +287,7 @@ function _handleBuiltIn({ behavior } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (behavior === 'DOWNLOAD_SCREEN_SHOT') {
|
if (behavior === 'DOWNLOAD_SCREEN_SHOT') {
|
||||||
this.props.modalContext.show(ConnectedViewportDownloadForm, {
|
this.props.modal.show(ConnectedViewportDownloadForm, {
|
||||||
title: this.props.t('Download High Quality Image'),
|
title: this.props.t('Download High Quality Image'),
|
||||||
customClassName: 'ViewportDownloadForm',
|
customClassName: 'ViewportDownloadForm',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export default class DatasetPicker extends Component {
|
|||||||
error: null,
|
error: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
datasets: [],
|
datasets: [],
|
||||||
|
filterStr: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -41,15 +42,23 @@ export default class DatasetPicker extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { datasets, loading, error } = this.state;
|
const { datasets, loading, error, filterStr } = this.state;
|
||||||
const { onSelect } = this.props;
|
const { onSelect } = this.props;
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
|
<input class="form-control gcp-input"
|
||||||
|
type="text"
|
||||||
|
value={ filterStr }
|
||||||
|
onChange={ e => this.setState({ filterStr: e.target.value }) }
|
||||||
|
/>
|
||||||
<DatasetsList
|
<DatasetsList
|
||||||
datasets={datasets}
|
datasets={datasets}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
|
filter={filterStr}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,21 +46,23 @@ class DatasetsList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.error) {
|
const { loading, datasets, filter, error } = this.props;
|
||||||
return <p>{this.props.error}</p>;
|
|
||||||
|
if (error) {
|
||||||
|
return <p>{error}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadingIcon = (
|
const loadingIcon = (
|
||||||
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
|
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.props.loading) {
|
if (loading) {
|
||||||
return loadingIcon;
|
return loadingIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = (
|
const body = (
|
||||||
<tbody id="DatasetList">
|
<tbody id="DatasetList">
|
||||||
{this.props.datasets.map(this.renderTableRow)}
|
{datasets.filter(dataset => (dataset.name.split('/')[5].toLowerCase().includes(filter.toLowerCase()) || filter=="")).map(this.renderTableRow)}
|
||||||
</tbody>
|
</tbody>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ class DatasetsList extends Component {
|
|||||||
<th>{this.props.t('Dataset')}</th>
|
<th>{this.props.t('Dataset')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{this.props.datasets && body}
|
{datasets && body}
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,20 +46,24 @@ class DicomStoreList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.error) {
|
const { loading, stores, filter, error } = this.props;
|
||||||
return <p>{this.props.error}</p>;
|
|
||||||
|
if (error) {
|
||||||
|
return <p>{error}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadingIcon = (
|
const loadingIcon = (
|
||||||
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
|
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.props.loading) {
|
if (loading) {
|
||||||
return loadingIcon;
|
return loadingIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = (
|
const body = (
|
||||||
<tbody id="StoreList">{this.props.stores.map(this.renderTableRow)}</tbody>
|
<tbody id="StoreList">{
|
||||||
|
stores.filter(store => (store.name.split('/')[7].toLowerCase().includes(filter.toLowerCase()) || filter=="")).map(this.renderTableRow)}
|
||||||
|
</tbody>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -69,7 +73,7 @@ class DicomStoreList extends Component {
|
|||||||
<th>{this.props.t('DICOM Store')}</th>
|
<th>{this.props.t('DICOM Store')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{this.props.stores && body}
|
{stores && body}
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default class DicomStorePicker extends Component {
|
|||||||
loading: true,
|
loading: true,
|
||||||
stores: [],
|
stores: [],
|
||||||
locations: [],
|
locations: [],
|
||||||
|
filterStr: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -38,16 +39,24 @@ export default class DicomStorePicker extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { stores, loading, error } = this.state;
|
const { stores, loading, error, filterStr } = this.state;
|
||||||
const { onSelect } = this.props;
|
const { onSelect } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DicomStoreList
|
<div>
|
||||||
stores={stores}
|
<input class="form-control gcp-input"
|
||||||
loading={loading}
|
type="text"
|
||||||
error={error}
|
value={ filterStr }
|
||||||
onSelect={onSelect}
|
onChange={ e => this.setState({ filterStr: e.target.value }) }
|
||||||
/>
|
/>
|
||||||
|
<DicomStoreList
|
||||||
|
stores={stores}
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
filter={filterStr}
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ export default class LocationPicker extends Component {
|
|||||||
error: null,
|
error: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
locations: [],
|
locations: [],
|
||||||
|
filterStr: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -37,15 +38,23 @@ export default class LocationPicker extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { locations, loading, error } = this.state;
|
const { locations, loading, error, filterStr } = this.state;
|
||||||
const { onSelect } = this.props;
|
const { onSelect } = this.props;
|
||||||
return (
|
return (
|
||||||
<LocationsList
|
<div>
|
||||||
locations={locations}
|
<input class="form-control gcp-input"
|
||||||
loading={loading}
|
type="text"
|
||||||
error={error}
|
value={ filterStr }
|
||||||
onSelect={onSelect}
|
onChange={ e => this.setState({ filterStr: e.target.value }) }
|
||||||
/>
|
/>
|
||||||
|
<LocationsList
|
||||||
|
locations={locations}
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
filter={filterStr}
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loaded 100 of 107 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user