Compare commits
61
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 |
No files matched your search
@@ -62,7 +62,7 @@ echo "Done generating documentation output"
|
||||
echo 'STARTING PUBLISH'
|
||||
|
||||
# WILL ALWAYS FAIL IF INITIATED FROM PR BRANCH
|
||||
./node_modules/.bin/gh-pages \
|
||||
npx gh-pages \
|
||||
--silent \
|
||||
--repo https://$GITHUB_TOKEN@github.com/OHIF/Viewers.git \
|
||||
--message 'Autogenerated Message: [ci skip]' \
|
||||
|
||||
+228
-245
@@ -14,52 +14,53 @@ version: 2.1
|
||||
orbs:
|
||||
codecov: codecov/codecov@1.0.5
|
||||
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
|
||||
docker:
|
||||
- image: circleci/node:12.9.1
|
||||
environment:
|
||||
TERM: xterm # Enable colors in term
|
||||
QUICK_BUILD: true
|
||||
working_directory: ~/repo
|
||||
|
||||
jobs:
|
||||
###
|
||||
# Workflow: PR_CHECKS
|
||||
###
|
||||
UNIT_TESTS:
|
||||
<<: *defaults
|
||||
steps:
|
||||
# Update yarn
|
||||
- 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:
|
||||
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'
|
||||
@@ -73,7 +74,6 @@ jobs:
|
||||
- codecov/upload:
|
||||
file: '/home/circleci/repo/platform/viewer/coverage/reports'
|
||||
flags: 'viewer'
|
||||
|
||||
# PLATFORM/CORE
|
||||
- run:
|
||||
name: 'CORE: Combine report output'
|
||||
@@ -88,145 +88,9 @@ jobs:
|
||||
file: '/home/circleci/repo/platform/core/coverage/reports'
|
||||
flags: 'core'
|
||||
|
||||
UNIT_TESTS_PERSIST_WORKSPACE:
|
||||
<<: *defaults
|
||||
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
|
||||
|
||||
###
|
||||
# Workflow: PR_OPTIONAL_DOCKER_PUBLISH
|
||||
###
|
||||
DOCKER_PR_PUBLISH:
|
||||
<<: *defaults
|
||||
steps:
|
||||
@@ -264,55 +128,190 @@ jobs:
|
||||
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
|
||||
docker push ohif/viewer:PR_BUILD-$CIRCLE_BUILD_NUM
|
||||
|
||||
VIEWER_DEMO_BUILD:
|
||||
###
|
||||
# Workflow: DEPLOY
|
||||
###
|
||||
BUILD:
|
||||
<<: *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:
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
# Build & Test
|
||||
- run: cd .netlify && npm install
|
||||
- run:
|
||||
name: 'Build Demo Site'
|
||||
command: yarn run build:demo
|
||||
no_output_timeout: 45m
|
||||
cp .netlify/deploy-workflow/_redirects platform/viewer/dist/_redirects
|
||||
- run: cd .netlify && npm run deploy
|
||||
|
||||
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:
|
||||
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: |
|
||||
# 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:
|
||||
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:
|
||||
name: Authenticate with NPM registry
|
||||
command:
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
||||
- run: npx lerna version
|
||||
- run: npx lerna publish from-package
|
||||
- persist_to_workspace:
|
||||
root: ~/repo
|
||||
paths: .
|
||||
|
||||
VIEWER_DEMO_PUBLISH:
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
- image: google/cloud-sdk
|
||||
DOCS_PUBLISH:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- 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:
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: false
|
||||
- run:
|
||||
name: Deploy latest version to viewer.ohif.org
|
||||
name: Build and push Docker image
|
||||
command: |
|
||||
# https://circleci.com/docs/2.0/google-auth/#authorization
|
||||
echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=-
|
||||
gcloud --quiet config set project ${GOOGLE_PROJECT_ID}
|
||||
gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}
|
||||
# Remove all files in Storage Bucket; Push latest
|
||||
gsutil -m rm gs://$GOOGLE_STORAGE_BUCKET/**
|
||||
gsutil -m rsync -R platform/viewer/dist gs://$GOOGLE_STORAGE_BUCKET
|
||||
# This file will exist if a new version was published by
|
||||
# our command in the previous job. Created in npm postpublish hook
|
||||
# in the `platform/viewer` project.
|
||||
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
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
# PULL REQUESTS
|
||||
PR_CHECKS:
|
||||
jobs:
|
||||
- UNIT_TESTS:
|
||||
@@ -379,82 +378,66 @@ workflows:
|
||||
requires:
|
||||
- AWAIT_APPROVAL
|
||||
|
||||
# MERGE TO MASTER
|
||||
RELEASE:
|
||||
###
|
||||
# Our workflow for building, deploying, and promoting builds across our
|
||||
# development, staging, and production environments.
|
||||
###
|
||||
DEPLOY:
|
||||
jobs:
|
||||
- UNIT_TESTS_PERSIST_WORKSPACE:
|
||||
- BUILD:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
# E2E: PWA + Persist
|
||||
- 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
|
||||
- DEPLOY_TO_DEV:
|
||||
requires:
|
||||
- UNIT_TESTS_PERSIST_WORKSPACE
|
||||
# E2E: script-tag
|
||||
- cypress/run:
|
||||
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
|
||||
- BUILD
|
||||
- PROMOTE_TO_STAGING:
|
||||
type: approval
|
||||
requires:
|
||||
- UNIT_TESTS_PERSIST_WORKSPACE
|
||||
# Update NPM
|
||||
- DEPLOY_TO_DEV
|
||||
- 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:
|
||||
requires:
|
||||
- 'E2E: Script Tag'
|
||||
- 'E2E: PWA'
|
||||
# Update docs.ohif.org
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- DOCS_PUBLISH:
|
||||
requires:
|
||||
- 'E2E: Script Tag'
|
||||
- 'E2E: PWA'
|
||||
- VIEWER_DEMO_BUILD:
|
||||
requires:
|
||||
- 'E2E: Script Tag'
|
||||
- 'E2E: PWA'
|
||||
# Update viewer.ohif.org
|
||||
- VIEWER_DEMO_PUBLISH:
|
||||
requires:
|
||||
- VIEWER_DEMO_BUILD
|
||||
# Update hub.docker.org
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- DOCKER_MASTER_PUBLISH:
|
||||
requires:
|
||||
- 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"
|
||||
}
|
||||
}
|
||||
@@ -47,5 +47,6 @@
|
||||
|
||||
- [FAQ](frequently-asked-questions.md)
|
||||
- [Contributing](contributing/index.md)
|
||||
- [Continuous Integration](continous-integration.md)
|
||||
- [Browser Support](browser-support.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
|
||||
*/
|
||||
|
||||
preRegistration({ serviceManager, configuration: extensionConfiguration }) {},
|
||||
preRegistration({
|
||||
servicesManager,
|
||||
configuration: extensionConfiguration,
|
||||
}) {},
|
||||
|
||||
/**
|
||||
* MODULE GETTERS
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -24,8 +24,8 @@ export default {
|
||||
* @param {object} [configuration={}]
|
||||
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
|
||||
*/
|
||||
preRegistration({ serviceManager, configuration = {} }) {
|
||||
init({ serviceManager, configuration });
|
||||
preRegistration({ servicesManager, configuration = {} }) {
|
||||
init({ servicesManager, configuration });
|
||||
},
|
||||
getViewportModule() {
|
||||
return OHIFCornerstoneViewport;
|
||||
|
||||
@@ -28,7 +28,7 @@ cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
|
||||
* @param {object} configuration
|
||||
* @param {Object|Array} configuration.csToolsConfig
|
||||
*/
|
||||
export default function init({ serviceManager, configuration = {} }) {
|
||||
export default function init({ servicesManager, configuration = {} }) {
|
||||
const { csToolsConfig } = configuration;
|
||||
const { StackManager } = OHIF.utils;
|
||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "0.53.5",
|
||||
"version": "0.53.9",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -52,8 +52,8 @@
|
||||
"react-vtkjs-viewport": "^0.3.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^1.9.0",
|
||||
"@ohif/ui": "^0.62.0",
|
||||
"@ohif/core": "^1.10.0",
|
||||
"@ohif/ui": "^0.62.4",
|
||||
"cornerstone-tools": "^4.0.9",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.6.1",
|
||||
|
||||
+2
-14
@@ -8,15 +8,6 @@
|
||||
# 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
|
||||
# YARN_FLAGS: https://www.netlify.com/docs/build-gotchas/#yarn
|
||||
[build.environment]
|
||||
@@ -28,10 +19,8 @@
|
||||
|
||||
# Production context: all deploys from the Production branch set in your site's
|
||||
# deploy contexts will inherit these settings.
|
||||
# [context.production]
|
||||
# publish = "project/output/"
|
||||
# command = "make publish"
|
||||
# environment = { ACCESS_TOKEN = "super secret", NODE_VERSION = "8.0.1" }
|
||||
[context.production]
|
||||
ignore = "exit 0" # Never build production; We'll let our CI do that
|
||||
|
||||
# Deploy Preview context: all deploys generated from a pull/merge request will
|
||||
# inherit these settings.
|
||||
@@ -39,7 +28,6 @@
|
||||
base = ""
|
||||
publish = ".netlify/www/"
|
||||
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]]
|
||||
# Define which paths this specific [[headers]] block will cover.
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "1.9.0",
|
||||
"version": "1.10.0",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -68,7 +68,7 @@ export default class ExtensionManager {
|
||||
// preRegistrationHook
|
||||
if (extension.preRegistration) {
|
||||
extension.preRegistration({
|
||||
serviceManager: this._servicesManager,
|
||||
servicesManager: this._servicesManager,
|
||||
configuration,
|
||||
});
|
||||
}
|
||||
@@ -110,7 +110,9 @@ export default class ExtensionManager {
|
||||
}
|
||||
|
||||
try {
|
||||
const extensionModule = getModuleFn();
|
||||
const extensionModule = getModuleFn({
|
||||
servicesManager: this._servicesManager,
|
||||
});
|
||||
|
||||
if (!extensionModule) {
|
||||
log.warn(
|
||||
|
||||
@@ -40,6 +40,30 @@ describe('ExtensionManager.js', () => {
|
||||
});
|
||||
|
||||
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', () => {
|
||||
const undefinedExtension = undefined;
|
||||
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', () => {
|
||||
const extension = {
|
||||
id: 'hello-world',
|
||||
|
||||
@@ -19,7 +19,7 @@ import ui from './ui';
|
||||
import user from './user.js';
|
||||
import utils from './utils/';
|
||||
|
||||
import { createUiNotificationService } from './services';
|
||||
import { createUINotificationService, createUIModalService } from './services';
|
||||
|
||||
const OHIF = {
|
||||
MODULE_TYPES,
|
||||
@@ -46,7 +46,8 @@ const OHIF = {
|
||||
measurements,
|
||||
hangingProtocols,
|
||||
//
|
||||
createUiNotificationService,
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -73,7 +74,8 @@ export {
|
||||
measurements,
|
||||
hangingProtocols,
|
||||
//
|
||||
createUiNotificationService,
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
};
|
||||
|
||||
export { OHIF };
|
||||
|
||||
@@ -10,7 +10,8 @@ describe('Top level exports', () => {
|
||||
'HotkeysManager',
|
||||
'ServicesManager',
|
||||
//
|
||||
'createUiNotificationService',
|
||||
'createUINotificationService',
|
||||
'createUIModalService',
|
||||
//
|
||||
'utils',
|
||||
'studies',
|
||||
|
||||
@@ -1,9 +1,49 @@
|
||||
import log from './../log.js';
|
||||
|
||||
export default class ServicesManager {
|
||||
constructor() {
|
||||
this.services = {};
|
||||
this.registeredServiceNames = [];
|
||||
}
|
||||
|
||||
register(service) {
|
||||
/**
|
||||
*
|
||||
* @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;
|
||||
@@ -10,7 +10,7 @@
|
||||
* @property {boolean} [autoClose=true]
|
||||
*/
|
||||
|
||||
const uiNotificationServicePublicApi = {
|
||||
const uiNotificationServicePublicAPI = {
|
||||
name: 'UINotificationService',
|
||||
hide,
|
||||
show,
|
||||
@@ -22,8 +22,8 @@ const uiNotificationServiceImplementation = {
|
||||
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
function createUiNotificationService() {
|
||||
return uiNotificationServicePublicApi;
|
||||
function createUINotificationService() {
|
||||
return uiNotificationServicePublicAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,4 +81,4 @@ function setServiceImplementation({
|
||||
}
|
||||
}
|
||||
|
||||
export default createUiNotificationService;
|
||||
export default createUINotificationService;
|
||||
@@ -1,4 +1,5 @@
|
||||
import ServicesManager from './ServicesManager.js';
|
||||
import createUiNotificationService from './UINotificationService';
|
||||
import createUINotificationService from './UINotificationService';
|
||||
import createUIModalService from './UIModalService';
|
||||
|
||||
export { createUiNotificationService, ServicesManager };
|
||||
export { createUINotificationService, createUIModalService, ServicesManager };
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/i18n",
|
||||
"version": "0.52.0",
|
||||
"version": "0.52.1",
|
||||
"description": "Internationalization library for The OHIF Viewer",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"Close": "Close",
|
||||
"Image": "Image",
|
||||
"Layout": "Layout",
|
||||
"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 CineDialog from './CineDialog.json';
|
||||
import Common from './Common.json';
|
||||
import DatePicker from './DatePicker.json';
|
||||
import Header from './Header.json';
|
||||
import MeasurementTable from './MeasurementTable.json';
|
||||
import StudyList from './StudyList.json';
|
||||
@@ -13,6 +14,7 @@ export default {
|
||||
Buttons,
|
||||
CineDialog,
|
||||
Common,
|
||||
DatePicker,
|
||||
Header,
|
||||
MeasurementTable,
|
||||
StudyList,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"Close": "Fechar",
|
||||
"Image": "Imagem",
|
||||
"Layout": "Layout",
|
||||
"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 CineDialog from './CineDialog.json';
|
||||
import Common from './Common.json';
|
||||
import DatePicker from './DatePicker.json';
|
||||
import Header from './Header.json';
|
||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||
|
||||
@@ -11,6 +12,7 @@ export default {
|
||||
Buttons,
|
||||
CineDialog,
|
||||
Common,
|
||||
DatePicker,
|
||||
Header,
|
||||
UserPreferencesModal,
|
||||
},
|
||||
|
||||
@@ -3,6 +3,41 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/ui",
|
||||
"version": "0.62.0",
|
||||
"version": "0.62.4",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -13,7 +13,7 @@ const OHIFModal = ({
|
||||
onHide,
|
||||
footer: Footer,
|
||||
header: Header,
|
||||
children: Component,
|
||||
children,
|
||||
}) => (
|
||||
<ReactBootstrapModal
|
||||
className={classNames('modal fade themed in', className)}
|
||||
@@ -32,12 +32,9 @@ const OHIFModal = ({
|
||||
{Header && <Header hide={onHide} />}
|
||||
</ReactBootstrapModal.Header>
|
||||
)}
|
||||
<ReactBootstrapModal.Body>
|
||||
{Component && <Component hide={onHide} />}
|
||||
</ReactBootstrapModal.Body>
|
||||
<ReactBootstrapModal.Body>{children}</ReactBootstrapModal.Body>
|
||||
{Footer && (
|
||||
<ReactBootstrapModal.Footer>
|
||||
{' '}
|
||||
<Footer hide={onHide} />
|
||||
</ReactBootstrapModal.Footer>
|
||||
)}
|
||||
@@ -52,9 +49,20 @@ OHIFModal.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
title: PropTypes.string,
|
||||
onHide: PropTypes.func,
|
||||
footer: PropTypes.node,
|
||||
header: PropTypes.node,
|
||||
children: PropTypes.node,
|
||||
footer: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(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;
|
||||
@@ -37,8 +37,8 @@ const SnackbarContainer = () => {
|
||||
|
||||
return (
|
||||
<div key={pos} className={`sb-container sb-${pos}`}>
|
||||
{items[pos].map(item => (
|
||||
<div key={item.id}>{renderItem(item)}</div>
|
||||
{items[pos].map((item, index) => (
|
||||
<div key={item.id + index}>{renderItem(item)}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,22 +2,23 @@
|
||||
|
||||
/* Will edit everything selected including everything between a range of dates */
|
||||
.CalendarDay__selected_span {
|
||||
background: var(--table-text-secondary-color);
|
||||
color: #fff;
|
||||
background: var(--datePicker-picker-background);
|
||||
color: var(--datePicker-picker-text);
|
||||
border-color: #e4e7e7;
|
||||
}
|
||||
|
||||
/* Will edit selected date or the endpoints of a range of dates */
|
||||
.CalendarDay__selected {
|
||||
background: var(--table-text-secondary-color);
|
||||
color: #fff;
|
||||
background: var(--datePicker-picker-background);
|
||||
color: var(--datePicker-picker-text);
|
||||
border-color: #e4e7e7;
|
||||
}
|
||||
|
||||
/* Will edit when hovered over. _span style also has this property */
|
||||
.CalendarDay__selected:hover {
|
||||
background: var(--table-text-secondary-color);
|
||||
color: #fff;
|
||||
.CalendarDay__selected:hover,
|
||||
.CalendarDay__default:hover {
|
||||
background: var(--datePicker-picker-background);
|
||||
color: var(--datePicker-picker-text);
|
||||
border-color: #e4e7e7;
|
||||
}
|
||||
|
||||
@@ -25,8 +26,8 @@
|
||||
is not yet selected. Edits the dates between your mouse and said date */
|
||||
.CalendarDay__hovered_span:hover,
|
||||
.CalendarDay__hovered_span {
|
||||
background: var(--table-text-secondary-color);
|
||||
color: #fff;
|
||||
background: var(--datePicker-picker-background);
|
||||
color: var(--datePicker-picker-text);
|
||||
border-color: #e4e7e7;
|
||||
}
|
||||
|
||||
@@ -42,15 +43,15 @@ is not yet selected. Edits the dates between your mouse and said date */
|
||||
/* Container - visual */
|
||||
.DateRangePickerInput {
|
||||
width: calc(100% - 10px); /* Just use padding? */
|
||||
background-color: var(--input-background-color);
|
||||
border-color: var(--input-background-color);
|
||||
color: var(--input-placeholder-color);
|
||||
background-color: var(--datePicker-input-background);
|
||||
border-color: var(--datePicker-input-background);
|
||||
color: var(--datePicker-picker-text-input);
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.DateRangePickerInput.DateRangePickerInput__withBorder {
|
||||
border-radius: 4px;
|
||||
background-color: var(--input-background-color);
|
||||
background-color: var(--datePicker-input-background);
|
||||
}
|
||||
|
||||
/* Input Container */
|
||||
@@ -58,14 +59,14 @@ is not yet selected. Edits the dates between your mouse and said date */
|
||||
width: 97px;
|
||||
height: 38px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--input-background-color);
|
||||
background-color: var(--datePicker-input-background);
|
||||
}
|
||||
|
||||
/* Actual Input Element */
|
||||
.DateInput > .DateInput_input {
|
||||
border-color: transparent;
|
||||
background-color: transparent;
|
||||
color: var(--input-placeholder-color);
|
||||
color: var(--datePicker-picker-text-input);
|
||||
height: 38px;
|
||||
font-size: 10pt;
|
||||
padding: 0;
|
||||
@@ -80,4 +81,39 @@ is not yet selected. Edits the dates between your mouse and said date */
|
||||
.PresetDateRangePicker_button {
|
||||
margin: 0;
|
||||
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 PropTypes from 'prop-types';
|
||||
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 {
|
||||
static propTypes = {
|
||||
presets: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
text: PropTypes.string,
|
||||
start: PropTypes.required,
|
||||
end: PropTypes.required,
|
||||
})
|
||||
),
|
||||
};
|
||||
function CustomDateRangePicker(props) {
|
||||
moment.locale(i18n.language); // using i18n in the date picker
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
startDate: this.props.startDate,
|
||||
endDate: this.props.endDate,
|
||||
};
|
||||
const { t } = useTranslation('DatePicker');
|
||||
|
||||
this.renderDatePresets = this.renderDatePresets.bind(this);
|
||||
this.onDatesChange = this.onDatesChange.bind(this);
|
||||
}
|
||||
const {
|
||||
onDatesChange,
|
||||
startDate,
|
||||
endDate,
|
||||
presets,
|
||||
...dateRangePickerProps
|
||||
} = props;
|
||||
|
||||
onDatesChange({ startDate, endDate, preset }) {
|
||||
this.setState({ startDate, endDate });
|
||||
if (this.props.onDatesChange) {
|
||||
this.props.onDatesChange({ startDate, endDate, preset });
|
||||
}
|
||||
}
|
||||
|
||||
renderDatePresets() {
|
||||
const { presets } = this.props;
|
||||
const { startDate, endDate } = this.state;
|
||||
const renderDatePresets = () => {
|
||||
const { presets } = props;
|
||||
|
||||
return (
|
||||
<div className="PresetDateRangePicker_panel">
|
||||
@@ -54,7 +40,7 @@ export default class CustomDateRangePicker extends React.Component {
|
||||
isSelected ? 'PresetDateRangePicker_button__selected' : ''
|
||||
}`}
|
||||
onClick={() =>
|
||||
this.onDatesChange({
|
||||
onDatesChange({
|
||||
startDate: start,
|
||||
endDate: end,
|
||||
preset: true,
|
||||
@@ -67,30 +53,89 @@ export default class CustomDateRangePicker extends React.Component {
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
const renderMonthElement = ({ month, onMonthSelect, onYearSelect }) => {
|
||||
const containerStyle = {
|
||||
margin: '0 5px',
|
||||
};
|
||||
|
||||
render() {
|
||||
let {
|
||||
autoFocus,
|
||||
autoFocusEndDate,
|
||||
initialStartDate,
|
||||
initialEndDate,
|
||||
stateDateWrapper,
|
||||
onDatesChange,
|
||||
startDate,
|
||||
endDate,
|
||||
presets,
|
||||
...dateRangePickerProps
|
||||
} = this.props;
|
||||
const renderYearsOptions = () => {
|
||||
const yearsRange = 20;
|
||||
const options = [];
|
||||
|
||||
for (let i = 0; i < yearsRange; i++) {
|
||||
const year = moment().year() - i;
|
||||
options.push(<option value={year}>{year}</option>);
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
renderMonthElement.propTypes = {
|
||||
onMonthSelect: PropTypes.func,
|
||||
onYearSelect: PropTypes.func,
|
||||
};
|
||||
|
||||
return (
|
||||
<DateRangePicker
|
||||
{...dateRangePickerProps}
|
||||
startDate={this.state.startDate}
|
||||
endDate={this.state.endDate}
|
||||
renderCalendarInfo={this.renderDatePresets}
|
||||
onDatesChange={this.onDatesChange}
|
||||
/>
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<div style={containerStyle}>
|
||||
<select
|
||||
className="DateRangePicker_select"
|
||||
value={month.month()}
|
||||
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,
|
||||
onSelectItem: handleSelectItem,
|
||||
t,
|
||||
//
|
||||
studyListDateFilterNumDays,
|
||||
} = props;
|
||||
|
||||
@@ -208,7 +207,6 @@ StudyList.propTypes = {
|
||||
allFields: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
onFilterChange: PropTypes.func.isRequired,
|
||||
//
|
||||
studyListDateFilterNumDays: PropTypes.number,
|
||||
};
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ table.table
|
||||
margin: 0 auto;
|
||||
color: var(--table-text-primary-color);
|
||||
font-weight: 400;
|
||||
padding: 20px;
|
||||
padding: 20px 8px;
|
||||
user-select: none;
|
||||
font-size: 15px;
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ import CustomDateRangePicker from './CustomDateRangePicker.js';
|
||||
import { Icon } from './../../elements/Icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
||||
|
||||
function TableSearchFilter(props) {
|
||||
const {
|
||||
meta,
|
||||
@@ -17,7 +15,7 @@ function TableSearchFilter(props) {
|
||||
sortFieldName,
|
||||
sortDirection,
|
||||
// TODO: Rename
|
||||
studyListDateFilterNumDays
|
||||
studyListDateFilterNumDays,
|
||||
} = props;
|
||||
const [focusedInput, setFocusedInput] = useState(null);
|
||||
const [t] = useTranslation(); // 'Common'?
|
||||
@@ -25,11 +23,13 @@ function TableSearchFilter(props) {
|
||||
const sortIcons = ['sort', 'sort-up', 'sort-down'];
|
||||
const sortIconForSortField =
|
||||
sortDirection === 'asc' ? sortIcons[1] : sortIcons[2];
|
||||
//
|
||||
const today = moment();
|
||||
const lastWeek = moment().subtract(7, 'day');
|
||||
const lastMonth = moment().subtract(1, 'month');
|
||||
const defaultStartDate = moment().subtract(studyListDateFilterNumDays, 'days');
|
||||
const defaultStartDate = moment().subtract(
|
||||
studyListDateFilterNumDays,
|
||||
'days'
|
||||
);
|
||||
const defaultEndDate = today;
|
||||
const studyDatePresets = [
|
||||
{
|
||||
@@ -82,8 +82,8 @@ function TableSearchFilter(props) {
|
||||
endDateId="end-date"
|
||||
// TODO: We need a dynamic way to determine which fields values to update
|
||||
onDatesChange={({ startDate, endDate, preset = false }) => {
|
||||
onValueChange('studyDateTo', startDate);
|
||||
onValueChange('studyDateFrom', endDate);
|
||||
onValueChange('studyDateFrom', startDate);
|
||||
onValueChange('studyDateTo', endDate);
|
||||
}}
|
||||
focusedInput={focusedInput}
|
||||
onFocusChange={updatedVal => setFocusedInput(updatedVal)}
|
||||
|
||||
@@ -10,7 +10,13 @@ import PropTypes from 'prop-types';
|
||||
|
||||
export class HotKeysPreferences extends Component {
|
||||
static propTypes = {
|
||||
hotkeyDefinitions: PropTypes.array.isRequired,
|
||||
hotkeyDefinitions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
commandName: PropTypes.string,
|
||||
keys: PropTypes.arrayOf(PropTypes.string),
|
||||
label: PropTypes.string,
|
||||
})
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -76,7 +82,7 @@ export class HotKeysPreferences extends Component {
|
||||
specialKeyName ||
|
||||
keyDownEvent.key ||
|
||||
String.fromCharCode(keyDownEvent.keyCode);
|
||||
pressedKeys.push(keyName);
|
||||
pressedKeys.push(keyName.toLowerCase());
|
||||
}
|
||||
|
||||
this.updateHotKeysState(commandName, pressedKeys.join('+'));
|
||||
@@ -123,7 +129,7 @@ export class HotKeysPreferences extends Component {
|
||||
const hotKey = this.state.hotKeys[hotKeyIndex];
|
||||
const keys = hotKey.keys[0];
|
||||
const pressedKeys = keys.split('+');
|
||||
const lastPressedKey = pressedKeys[pressedKeys.length - 1];
|
||||
const lastPressedKey = pressedKeys[pressedKeys.length - 1].toLowerCase();
|
||||
|
||||
// clear the prior errors
|
||||
this.setState({ errorMessages: {} }, () => {
|
||||
@@ -156,7 +162,8 @@ export class HotKeysPreferences extends Component {
|
||||
*/
|
||||
const modifierCommand = pressedKeys
|
||||
.slice(0, pressedKeys.length - 1)
|
||||
.join('+');
|
||||
.join('+')
|
||||
.toLowerCase();
|
||||
|
||||
const disallowedCombination = disallowedCombinations[modifierCommand];
|
||||
const hasDisallowedCombinations = disallowedCombination
|
||||
@@ -215,7 +222,7 @@ export class HotKeysPreferences extends Component {
|
||||
this.state.hotKeys.length
|
||||
);
|
||||
|
||||
return (
|
||||
return this.state.hotKeys.length > 0 ? (
|
||||
<div className="HotKeysPreferences">
|
||||
{/* <!-- Column 1 --> */}
|
||||
<div className="column">
|
||||
@@ -250,6 +257,8 @@ export class HotKeysPreferences extends Component {
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p>{`No hotkeys are configured for this application. Hotkeys can be configured in the application's app-config.js file.`}</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,13 @@ export class UserPreferences extends Component {
|
||||
|
||||
// TODO: Make this more generic. Tabs should not be restricted to these entries
|
||||
static propTypes = {
|
||||
hotkeyDefinitions: PropTypes.array.isRequired,
|
||||
hotkeyDefinitions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
commandName: PropTypes.string,
|
||||
keys: PropTypes.arrayOf(PropTypes.string),
|
||||
label: PropTypes.string,
|
||||
})
|
||||
).isRequired,
|
||||
windowLevelData: PropTypes.object.isRequired,
|
||||
generalData: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
@@ -15,7 +15,13 @@ class UserPreferencesForm extends Component {
|
||||
onSave: PropTypes.func,
|
||||
onResetToDefaults: PropTypes.func,
|
||||
windowLevelData: PropTypes.object,
|
||||
hotkeyDefinitions: PropTypes.array,
|
||||
hotkeyDefinitions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
commandName: PropTypes.string,
|
||||
keys: PropTypes.arrayOf(PropTypes.string),
|
||||
label: PropTypes.string,
|
||||
})
|
||||
).isRequired,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -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 classNames from 'classnames';
|
||||
|
||||
const ModalContext = createContext(null);
|
||||
const { Provider, Consumer } = ModalContext;
|
||||
const { Provider } = ModalContext;
|
||||
|
||||
export const useModal = () => useContext(ModalContext);
|
||||
|
||||
const ModalProvider = ({ children, modal: Modal }) => {
|
||||
const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
const DEFAULT_OPTIONS = {
|
||||
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. */,
|
||||
keyboard: false /* Modal is dismissible via the esc key. */,
|
||||
show: true /* Make the Modal visible or hidden. */,
|
||||
@@ -20,51 +28,81 @@ const ModalProvider = ({ children, modal: Modal }) => {
|
||||
|
||||
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.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
const show = (component, props = {}) =>
|
||||
setOptions(Object.assign({}, options, props, { component }));
|
||||
const show = useCallback(
|
||||
(component, props = {}) =>
|
||||
setOptions(Object.assign({}, options, props, { component })),
|
||||
[options]
|
||||
);
|
||||
|
||||
/**
|
||||
* Hide the modal and set its properties to default.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
const hide = () => setOptions(DEFAULT_OPTIONS);
|
||||
const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), [
|
||||
DEFAULT_OPTIONS,
|
||||
]);
|
||||
|
||||
const { component: Component } = options;
|
||||
|
||||
return (
|
||||
<Provider value={{ ...options, show, hide }}>
|
||||
<Consumer>
|
||||
{props => {
|
||||
const { component, footer, header, customClassName } = props;
|
||||
return component ? (
|
||||
<Modal
|
||||
className={classNames(customClassName, component.className)}
|
||||
backdrop={options.backdrop}
|
||||
keyboard={options.keyboard}
|
||||
show={options.show}
|
||||
title={options.title}
|
||||
closeButton={options.closeButton}
|
||||
onHide={hide}
|
||||
footer={footer}
|
||||
header={header}
|
||||
>
|
||||
{component}
|
||||
</Modal>
|
||||
) : null;
|
||||
}}
|
||||
</Consumer>
|
||||
<Provider value={{ show, hide }}>
|
||||
{options.component && (
|
||||
<Modal
|
||||
className={classNames(
|
||||
options.customClassName,
|
||||
options.component.className
|
||||
)}
|
||||
backdrop={options.backdrop}
|
||||
keyboard={options.keyboard}
|
||||
show={options.show}
|
||||
title={options.title}
|
||||
closeButton={options.closeButton}
|
||||
footer={options.footer}
|
||||
header={options.header}
|
||||
onHide={hide}
|
||||
>
|
||||
<Component {...options} show={show} hide={hide} />
|
||||
</Modal>
|
||||
)}
|
||||
{children}
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
ModalProvider.defaultProps = {
|
||||
service: null,
|
||||
};
|
||||
|
||||
ModalProvider.propTypes = {
|
||||
children: PropTypes.node,
|
||||
modal: PropTypes.node,
|
||||
children: PropTypes.oneOfType([
|
||||
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 => {
|
||||
return function WrappedComponent(props) {
|
||||
return <Component {...props} modalContext={{ ...useModal() }} />;
|
||||
const { show, hide } = useModal();
|
||||
return <Component {...props} modal={{ show, hide }} />;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import SnackbarContainer from '../components/snackbar/SnackbarContainer';
|
||||
import SnackbarTypes from '../components/snackbar/SnackbarTypes';
|
||||
|
||||
@@ -25,49 +27,62 @@ const SnackbarProvider = ({ children, service }) => {
|
||||
const [count, setCount] = useState(1);
|
||||
const [snackbarItems, setSnackbarItems] = useState([]);
|
||||
|
||||
/**
|
||||
* Sets the implementation of a notification service that can be used by extensions.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
useEffect(() => {
|
||||
service.setServiceImplementation({ hide, show });
|
||||
if (service) {
|
||||
service.setServiceImplementation({ hide, show });
|
||||
}
|
||||
}, [service, hide, show]);
|
||||
|
||||
const show = useCallback(options => {
|
||||
if (!options || (!options.title && !options.message)) {
|
||||
console.warn(
|
||||
'Snackbar cannot be rendered without required parameters: title | message'
|
||||
);
|
||||
const show = useCallback(
|
||||
options => {
|
||||
if (!options || (!options.title && !options.message)) {
|
||||
console.warn(
|
||||
'Snackbar cannot be rendered without required parameters: title | message'
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const newItem = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options,
|
||||
id: count,
|
||||
visible: true,
|
||||
};
|
||||
const newItem = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options,
|
||||
id: count,
|
||||
visible: true,
|
||||
};
|
||||
|
||||
setSnackbarItems(state => [...state, newItem]);
|
||||
setCount(count + 1);
|
||||
});
|
||||
setSnackbarItems(state => [...state, newItem]);
|
||||
setCount(count + 1);
|
||||
},
|
||||
[count, DEFAULT_OPTIONS]
|
||||
);
|
||||
|
||||
const hide = useCallback(id => {
|
||||
const hideItem = items => {
|
||||
const newItems = items.map(item => {
|
||||
if (item.id === id) {
|
||||
item.visible = false;
|
||||
}
|
||||
const hide = useCallback(
|
||||
id => {
|
||||
const hideItem = items => {
|
||||
const newItems = items.map(item => {
|
||||
if (item.id === id) {
|
||||
item.visible = false;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
return item;
|
||||
});
|
||||
|
||||
return newItems;
|
||||
};
|
||||
return newItems;
|
||||
};
|
||||
|
||||
setSnackbarItems(state => hideItem(state));
|
||||
setSnackbarItems(state => hideItem(state));
|
||||
|
||||
setTimeout(() => {
|
||||
setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
|
||||
}, 1000);
|
||||
});
|
||||
setTimeout(() => {
|
||||
setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
|
||||
}, 1000);
|
||||
},
|
||||
[setSnackbarItems]
|
||||
);
|
||||
|
||||
const hideAll = () => {
|
||||
// reset count
|
||||
@@ -95,6 +110,21 @@ const SnackbarProvider = ({ children, service }) => {
|
||||
);
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
@@ -3,6 +3,49 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -46,8 +46,8 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
||||
// Hotkey I
|
||||
cy.get('body').type('I');
|
||||
// Visual comparison
|
||||
cy.screenshot();
|
||||
cy.percyCanvasSnapshot('Hotkey I - invert image');
|
||||
cy.screenshot('Hotkey I - Should Invert Image');
|
||||
cy.percyCanvasSnapshot('Hotkey I - Should Invert Image');
|
||||
});
|
||||
|
||||
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%');
|
||||
|
||||
// Visual comparison to make sure the 'inverted' image was reset
|
||||
cy.screenshot();
|
||||
cy.percyCanvasSnapshot('Hotkey SPACEBAR - Reset Image');
|
||||
cy.screenshot('Hotkey SPACEBAR - Should Reset Image');
|
||||
cy.percyCanvasSnapshot('Hotkey SPACEBAR - Should Reset Image');
|
||||
});
|
||||
|
||||
it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
before(() => {
|
||||
cy.openStudy('MISTER^MR');
|
||||
cy.waitDicomImage();
|
||||
cy.expectMinimumThumbnails(3);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -112,25 +112,14 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
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')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'bottom', { which: 1 })
|
||||
.trigger('mouseup', 'bottom')
|
||||
.then(() => {
|
||||
expect(currentPan().y > 0).to.eq(true);
|
||||
});
|
||||
.trigger('mouseup', 'bottom');
|
||||
|
||||
// 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', () => {
|
||||
@@ -312,18 +301,18 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
.find('td')
|
||||
.its('length')
|
||||
.should('be.eq', 9);
|
||||
cy.get('@layoutBtn').click();
|
||||
});
|
||||
|
||||
//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.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
});
|
||||
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(1)').click();
|
||||
cy.setLayout(2, 1);
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
@@ -331,8 +320,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
//verify if layout has changed to 3 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(1) > :nth-child(3)').click();
|
||||
cy.setLayout(1, 3);
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wait(1000);
|
||||
cy.wrap($viewport)
|
||||
@@ -340,8 +328,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
.should('be.eq', 3);
|
||||
});
|
||||
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(1)').click();
|
||||
cy.setLayout(3, 1);
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
@@ -349,8 +336,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
//verify if layout has changed to 4 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(2)').click();
|
||||
cy.setLayout(2, 2);
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
@@ -358,16 +344,14 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
//verify if layout has changed to 6 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(3)').click();
|
||||
cy.setLayout(2, 3);
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
.should('be.eq', 6);
|
||||
});
|
||||
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(2)').click();
|
||||
cy.setLayout(3, 2);
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
@@ -375,8 +359,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
//verify if layout has changed to 9 viewports
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(3) > :nth-child(3)').click();
|
||||
cy.setLayout(3, 3);
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
@@ -384,8 +367,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
//verify if layout has changed to 1 viewport
|
||||
cy.get('@layoutBtn').click();
|
||||
cy.get('tbody > :nth-child(1) > :nth-child(1)').click();
|
||||
cy.setLayout(1, 1);
|
||||
cy.get('.viewport-container').then($viewport => {
|
||||
cy.wrap($viewport)
|
||||
.its('length')
|
||||
@@ -425,7 +407,9 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
.as('toolbarOverlay')
|
||||
.should('be.visible');
|
||||
//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
|
||||
cy.get('.measurementItem').should('not.exist');
|
||||
@@ -457,8 +441,15 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
|
||||
//Click More button
|
||||
cy.get('@moreBtn').click();
|
||||
//Click Eraser button
|
||||
cy.get('.tooltip-inner > :nth-child(12)').click();
|
||||
//Verify if overlay is displayed
|
||||
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
|
||||
const [x1, y1] = [150, 100];
|
||||
@@ -472,4 +463,70 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
cy.get('@viewport').click(x2, y2, { force: true });
|
||||
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', () => {
|
||||
cy.screenshot();
|
||||
cy.screenshot('Study List');
|
||||
cy.percyCanvasSnapshot('Study List');
|
||||
|
||||
cy.get('[data-cy="study-list-results"] tr')
|
||||
|
||||
@@ -11,8 +11,8 @@ describe('OHIF Study Viewer Page', function() {
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', function() {
|
||||
cy.screenshot();
|
||||
cy.percyCanvasSnapshot('Series Thumbnails');
|
||||
cy.screenshot('Series Thumbnails - Should Display Thumbnails');
|
||||
cy.percyCanvasSnapshot('Series Thumbnails - Should Display Thumbnails');
|
||||
|
||||
cy.get('[data-cy="thumbnail-list"]')
|
||||
.its('length')
|
||||
@@ -291,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
|
||||
cy.get('.close').click();
|
||||
cy.get('@aboutOverlay').should('not.be.enabled');
|
||||
|
||||
@@ -21,6 +21,10 @@ describe('OHIF Microscopy Extension', () => {
|
||||
.should('be.eq', 1);
|
||||
|
||||
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`
|
||||
// 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(() => {
|
||||
//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
|
||||
// This is a known bug; 2D MPR button does not show until viewport
|
||||
// has data from a drag-n-drop
|
||||
// Drag and drop first thumbnail into first viewport
|
||||
cy.get('[data-cy="thumbnail-list"]:nth-child(3)').drag(
|
||||
'.viewport-drop-target'
|
||||
);
|
||||
// Drag and drop third thumbnail into first viewport
|
||||
cy.get('[data-cy="thumbnail-list"]')
|
||||
.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')
|
||||
.as('twodmprBtn')
|
||||
.should('be.visible')
|
||||
.then(btn => {
|
||||
if (!btn.text().includes('Exit')) {
|
||||
btn.click();
|
||||
}
|
||||
});
|
||||
//Select 2D MPR button
|
||||
cy.get('.PluginSwitch > .toolbar-button').click();
|
||||
|
||||
//Wait Reformatting Images
|
||||
cy.waitVTKReformatting();
|
||||
|
||||
cy.initVTKToolsAliases();
|
||||
});
|
||||
|
||||
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')
|
||||
.should('be.visible')
|
||||
.contains('Crosshairs');
|
||||
@@ -57,7 +56,67 @@ describe('OHIF VTK Extension', () => {
|
||||
.should('be.visible')
|
||||
.contains('Layout');
|
||||
|
||||
cy.wait(3000);
|
||||
cy.percyCanvasSnapshot('VTK Extension');
|
||||
// Visual comparison
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -90,6 +90,18 @@ Cypress.Commands.add('waitStudyList', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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.
|
||||
* Example of usage: cy.get(element-to-be-dragged).drag(dropzone-element)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "2.8.0",
|
||||
"version": "2.8.5",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -45,14 +45,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^1.9.0",
|
||||
"@ohif/core": "^1.10.0",
|
||||
"@ohif/extension-cornerstone": "^2.0.0",
|
||||
"@ohif/extension-dicom-html": "^1.0.1",
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.0",
|
||||
"@ohif/extension-vtk": "^0.53.5",
|
||||
"@ohif/i18n": "^0.52.0",
|
||||
"@ohif/ui": "^0.62.0",
|
||||
"@ohif/extension-vtk": "^0.53.9",
|
||||
"@ohif/i18n": "^0.52.1",
|
||||
"@ohif/ui": "^0.62.4",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.2.1",
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
ExtensionManager,
|
||||
ServicesManager,
|
||||
HotkeysManager,
|
||||
createUiNotificationService,
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
utils,
|
||||
} from '@ohif/core';
|
||||
import React, { Component } from 'react';
|
||||
@@ -44,7 +45,8 @@ const commandsManagerConfig = {
|
||||
};
|
||||
|
||||
// Services
|
||||
const UINotificationService = createUiNotificationService();
|
||||
const UINotificationService = createUINotificationService();
|
||||
const UIModalService = createUIModalService();
|
||||
|
||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||
const hotkeysManager = new HotkeysManager(commandsManager);
|
||||
@@ -89,7 +91,7 @@ class App extends Component {
|
||||
const { servers, extensions, hotkeys, oidc } = props;
|
||||
|
||||
this.initUserManager(oidc);
|
||||
_initServices([UINotificationService]);
|
||||
_initServices([UINotificationService, UIModalService]);
|
||||
_initExtensions(extensions, hotkeys);
|
||||
_initServers(servers);
|
||||
initWebWorkers();
|
||||
@@ -112,7 +114,10 @@ class App extends Component {
|
||||
<Router basename={routerBasename}>
|
||||
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<ModalProvider modal={OHIFModal}>
|
||||
<ModalProvider
|
||||
modal={OHIFModal}
|
||||
service={UIModalService}
|
||||
>
|
||||
<OHIFStandaloneViewer userManager={userManager} />
|
||||
</ModalProvider>
|
||||
</SnackbarProvider>
|
||||
@@ -133,7 +138,7 @@ class App extends Component {
|
||||
<Router basename={routerBasename}>
|
||||
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<ModalProvider modal={OHIFModal}>
|
||||
<ModalProvider modal={OHIFModal} service={UIModalService}>
|
||||
<OHIFStandaloneViewer />
|
||||
</ModalProvider>
|
||||
</SnackbarProvider>
|
||||
@@ -180,7 +185,7 @@ class App extends Component {
|
||||
}
|
||||
|
||||
function _initServices(services) {
|
||||
services.forEach(service => servicesManager.register(service));
|
||||
servicesManager.registerServices(services);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,10 +7,10 @@ export default {
|
||||
*/
|
||||
id: 'measurements-table',
|
||||
|
||||
preRegistration({ serviceManager, configuration = {} }) {
|
||||
init({ serviceManager, configuration });
|
||||
preRegistration({ servicesManager, configuration = {} }) {
|
||||
init({ servicesManager, configuration });
|
||||
},
|
||||
getPanelModule() {
|
||||
getPanelModule({ servicesManager }) {
|
||||
return {
|
||||
menuOptions: [
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ const MEASUREMENT_ACTION_MAP = {
|
||||
* @export
|
||||
* @param {*} configuration
|
||||
*/
|
||||
export default function init({ serviceManager, configuration = {} }) {
|
||||
export default function init({ servicesManager, configuration = {} }) {
|
||||
// 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
|
||||
// our custom `getMeasurementLocationCallback`, we can...
|
||||
|
||||
@@ -20,7 +20,7 @@ class Header extends Component {
|
||||
t: PropTypes.func.isRequired,
|
||||
userManager: PropTypes.object,
|
||||
user: PropTypes.object,
|
||||
modalContext: PropTypes.object,
|
||||
modal: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -40,7 +40,7 @@ class Header extends Component {
|
||||
t,
|
||||
user,
|
||||
userManager,
|
||||
modalContext: { show },
|
||||
modal: { show },
|
||||
} = this.props;
|
||||
this.options = [
|
||||
{
|
||||
|
||||
@@ -287,7 +287,7 @@ function _handleBuiltIn({ behavior } = {}) {
|
||||
}
|
||||
|
||||
if (behavior === 'DOWNLOAD_SCREEN_SHOT') {
|
||||
this.props.modalContext.show(ConnectedViewportDownloadForm, {
|
||||
this.props.modal.show(ConnectedViewportDownloadForm, {
|
||||
title: this.props.t('Download High Quality Image'),
|
||||
customClassName: 'ViewportDownloadForm',
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import OHIF from '@ohif/core';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -113,7 +112,8 @@ function StudyListRoute(props) {
|
||||
pageNumber,
|
||||
displaySize,
|
||||
server,
|
||||
]);
|
||||
]
|
||||
);
|
||||
|
||||
// TODO: Update Server
|
||||
// if (this.props.server !== prevProps.server) {
|
||||
@@ -188,10 +188,12 @@ function StudyListRoute(props) {
|
||||
}
|
||||
|
||||
function handleFilterChange(fieldName, value) {
|
||||
const updatedFilterValues = Object.assign({}, filterValues);
|
||||
|
||||
updatedFilterValues[fieldName] = value;
|
||||
setFilterValues(updatedFilterValues);
|
||||
setFilterValues(state => {
|
||||
return {
|
||||
...state,
|
||||
[fieldName]: value,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -31,4 +31,12 @@
|
||||
|
||||
/** z-Index **/
|
||||
--snackbar-zIndex: 10;
|
||||
|
||||
/** DatePicker Colors **/
|
||||
--datePicker-input-background: #2c363f;
|
||||
--datePicker-picker-background: #9ccef9;
|
||||
--datePicker-picker-disabled: #d3d3d3;
|
||||
--datePicker-picker-text: #484848;
|
||||
--datePicker-picker-text-disabled: #aaa;
|
||||
--datePicker-picker-text-input: #fff;
|
||||
}
|
||||
@@ -1100,34 +1100,13 @@
|
||||
pirates "^4.0.0"
|
||||
source-map-support "^0.5.9"
|
||||
|
||||
"@babel/runtime@7.1.2":
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.2.tgz#81c89935f4647706fc54541145e6b4ecfef4b8e3"
|
||||
integrity sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.0.tgz#4fc1d642a9fd0299754e8b5de62c631cf5568205"
|
||||
integrity sha512-89eSBLJsxNxOERC0Op4vd+0Bqm6wRMqMbFtV3i0/fbaWw/mJ8Q3eBvgX0G4SyrOOLCtbu98HspF8o09MRT+KzQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5":
|
||||
"@babel/runtime@7.1.2", "@babel/runtime@7.5.5", "@babel/runtime@7.6.0", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
|
||||
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3":
|
||||
version "7.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.2.tgz#111a78002a5c25fc8e3361bedc9529c696b85a6a"
|
||||
integrity sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"
|
||||
@@ -16634,11 +16613,6 @@ regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
||||
|
||||
regenerator-runtime@^0.12.0:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
|
||||
|
||||
regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.2:
|
||||
version "0.13.3"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
|
||||
|
||||
Reference in new issue
Block a user