Chore/kill yalc with fire (#629)
* init * more shifting * Move @babel dependencies up to workspace root * Begin updating dependent libraries to use sync'd webpack builds w/ watches * Commit changes before a long weekend * Split packages into platform and extensions * Consolidate how/where we specify file/module type entrypoints * Make it possible to pass in extensions as App props * Resolve viewer's modules * Target for UMD bundle * Add Webpack Stylus loader * Provide WebPack build options for microscopy, vtk, and ui * Better entrypoint for extensions * Push changes up to switch PCs * Fix ESM symlink build for Viewers * Shift docs to root * Specify additional lerna config props * Begin providing basic readme info * Rename example extension folder * Shift links to bottom of doc * First attempt at a netlify deploy preview * Attempt to fix deploy preview build * push workspace enabled to initial command; remove second yarn install; use npx to call gitbook cli commands * Try to use global gitbook-cli * Export node_modules path * More agressive with modifying PATH * Follow deploy log output advice * try again to set bin path * Try swapping bash with a new shell * Try bash -l instead of exec bash * Annd let's try again * and again * We may have figured it out johnny, boy * We need to pull cornerstone-wado-image-loader files from hoisted node_modules * Attempt to fix ticks/escapes * Change copy syntax; try running tests on viewers from root for circleci * Lower version to match circleci image * Try alternative jest-canvas-mock location and version file syntax * Run version command instead of calling node directly * Try alternative jest transform * Set path and enable workspaces * Add codecov flags * Use cpx so our copying finishes? * Get all platform unit tests to run * Run and report individually and in parallel * Try once relying on codecov yaml to split w/ flags * Add projects to split by flags * Generate separate reports * Trigger codecov after everything has finished running; these may not support flags * Use recommended flags from issue comments for codecov * Fix typo * Remove clear flag * Shift back to calling codecov from root * Generate example for codecov issue * Try fixing paths * Simplify * Try using codecov orb * Bump circleci version * Upload core and viewer * Use full string paths * Remove individual codecov calls * Fix dir * Combine files before upload * Escape anchors * tryfix syntax * Escape string literal * Combine lines to reduce path * long paths * Use home alias * Fix filename * Try to see the contents of our cat'd file * Also upload `core` * Try running with aliased folder * Fix path; split PR and Merge unit tests into separate jobs * Set default threshold * Fix typo * Changing to scoped package names * Adding note * Catch more updates * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.0 - @ohif/extension-dicom-html@0.0.4-alpha.0 - @ohif/extension-dicom-microscopy@0.0.9-alpha.0 - @ohif/extension-dicom-pdf@0.0.8-alpha.0 - @ohif/extension-vtk@0.1.4-alpha.0 - @ohif/core@0.11.1-alpha.0 - @ohif/i18n@0.2.3-alpha.0 - @ohif/ui@0.2.18-alpha.0 - @ohif/viewer@0.0.22-alpha.0 * Clean up primary readme * Support for dev and dev:* commands * Shift build command; satisfy default PWA build * Remove old scripts * Clean up UI to set webpack scripts * Tidy up project links * Clean duplicate code in UI project * Update UI project's docs * Clean up ui and i18n config * Reduce duplicate code in `@ohif/core` * Fix @ohif/ui versions * bump @ohif/core versions * Redux testkit dep * Remove unused/duplicate config files for projects/packages * ohif-core --> @ohif/core * Safer publish command * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.1 - @ohif/extension-dicom-html@0.0.4-alpha.1 - @ohif/extension-dicom-microscopy@0.0.9-alpha.1 - @ohif/extension-dicom-pdf@0.0.8-alpha.1 - @ohif/extension-vtk@0.1.4-alpha.1 - @ohif/core@0.11.1-alpha.1 - @ohif/i18n@0.2.3-alpha.1 - @ohif/ui@0.2.18-alpha.1 - @ohif/viewer@0.0.22-alpha.1 * test version bump * Add global prettier config * chore(release): publish %s [skip ci] - @ohif/viewer@0.0.22-alpha.2 * test lerna publish * Update package.jsons * Fix build scripts * catch changes * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.2 - @ohif/extension-dicom-html@0.0.4-alpha.2 - @ohif/extension-dicom-microscopy@0.0.9-alpha.2 - @ohif/extension-dicom-pdf@0.0.8-alpha.2 - @ohif/extension-vtk@0.1.4-alpha.2 - @ohif/core@0.11.1-alpha.2 - @ohif/i18n@0.2.3-alpha.2 - @ohif/ui@0.2.18-alpha.2 - @ohif/viewer@0.0.22-alpha.3 * fix eslint issues caused by eslint 6 and our react-app config * Publish with canary tag * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.3 - @ohif/extension-dicom-html@0.0.4-alpha.3 - @ohif/extension-dicom-microscopy@0.0.9-alpha.3 - @ohif/extension-dicom-pdf@0.0.8-alpha.3 - @ohif/extension-vtk@0.1.4-alpha.3 - @ohif/core@0.11.1-alpha.3 - @ohif/i18n@0.2.3-alpha.3 - @ohif/ui@0.2.18-alpha.3 - @ohif/viewer@0.0.22-alpha.4 * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.4 - @ohif/extension-dicom-html@0.0.4-alpha.4 - @ohif/extension-dicom-microscopy@0.0.9-alpha.4 - @ohif/extension-dicom-pdf@0.0.8-alpha.4 - @ohif/extension-vtk@0.1.4-alpha.4 - @ohif/core@0.11.1-alpha.4 - @ohif/i18n@0.2.3-alpha.4 - @ohif/ui@0.2.18-alpha.4 - @ohif/viewer@0.0.22-alpha.5 * Simplify build * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.5 - @ohif/extension-dicom-html@0.0.4-alpha.5 - @ohif/extension-dicom-microscopy@0.0.9-alpha.5 - @ohif/extension-dicom-pdf@0.0.8-alpha.5 - @ohif/extension-vtk@0.1.4-alpha.5 - @ohif/core@0.11.1-alpha.5 - @ohif/i18n@0.2.3-alpha.5 - @ohif/ui@0.2.18-alpha.5 - @ohif/viewer@0.0.22-alpha.6 * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.6 - @ohif/extension-dicom-html@0.0.4-alpha.6 - @ohif/extension-dicom-microscopy@0.0.9-alpha.6 - @ohif/extension-dicom-pdf@0.0.8-alpha.6 - @ohif/extension-vtk@0.1.4-alpha.6 - @ohif/core@0.11.1-alpha.6 - @ohif/i18n@0.2.3-alpha.6 - @ohif/ui@0.2.18-alpha.6 - @ohif/viewer@0.0.22-alpha.7 * Split version and publish * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.7 - @ohif/extension-dicom-html@0.0.4-alpha.7 - @ohif/extension-dicom-microscopy@0.0.9-alpha.7 - @ohif/extension-dicom-pdf@0.0.8-alpha.7 - @ohif/extension-vtk@0.1.4-alpha.7 - @ohif/core@0.11.1-alpha.7 - @ohif/i18n@0.2.3-alpha.7 - @ohif/ui@0.2.18-alpha.7 - @ohif/viewer@0.0.22-alpha.8 * Try simplify netlify build * react-viewerbase --> @ohif/ui * add test for stylus loader * Try new way to specify publish path * Use an output folder that won't be ignored * Try move command * fiddle with paths * Dig for some information * Make sure we're in root for our commands * Remove discovery * Setup script-tag example * Fix typo * Move config folder * Update cornerstone extension to use shared/root webpack configs * capture changes * Update extension webpack configs * Don't check in docz output * Fix babel runtime resolution issue * Simplify ui build steps * Push in progress * Shift directories * Get viewer dev path to work * Fix viewer commonjs build * Fix build pwa config for viewers * Make sure our package specific command can be called from root * Update notes * Fix imports and misc. merge notes * Bump wado-image-loader version * Switch up commands to remove webworkers copy * Fix CI config * Fix more broken package references * Add react-dropzone * Persist so e2e test can use * Fix lockfile * Use cypress orb to simplify CI * Fix config * Try again * Install yarn latest for orb * Install yarn a different way * Wait on correct port * Run tests in viewer project * Fix cypress config * Make Cypress happier - use default location * Simplify e2e testing * Capture UI changes since fork * Catch core up * Grab latest deps after merge * Fix broken import * Make sure cornerstone-wado-image-loader resolves to 3+ * Note regarding orbs * Fix refresh on 404 routes * Fix odd error for cornerstone-wado-image-loader * Different target for UMD * Simplify dev script * Try to get fancy with netlify output * Let's do it again * Try to fix measurementTools issue * See if we can get our lerna magic to work on merge to monorepo branch
This commit is contained in:
1 parent
cac911f08e
commit
08ff214ea6
919 files changed
+45652
-52097
No files matched your search
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"imageSize": 100,
|
||||
"commit": false,
|
||||
"contributors": [
|
||||
{
|
||||
"login": "swederik",
|
||||
"name": "Erik Ziegler",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/607793?v=4",
|
||||
"profile": "https://github.com/swederik",
|
||||
"contributions": [
|
||||
"code",
|
||||
"infra"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "evren217",
|
||||
"name": "Evren Ozkan",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/4920551?v=4",
|
||||
"profile": "https://github.com/evren217",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "galelis",
|
||||
"name": "Gustavo André Lelis",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/2378326?v=4",
|
||||
"profile": "https://github.com/galelis",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dannyrb",
|
||||
"name": "Danny Brown",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/5797588?v=4",
|
||||
"profile": "http://dannyrb.com/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"infra"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "allcontributors",
|
||||
"name": "allcontributors[bot]",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/46843839?v=4",
|
||||
"profile": "https://github.com/all-contributors/all-contributors-bot",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "EsrefDurna",
|
||||
"name": "Esref Durna",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/1230575?v=4",
|
||||
"profile": "https://www.linkedin.com/in/siliconvalleynextgeneration/",
|
||||
"contributions": [
|
||||
"question"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "diego0020",
|
||||
"name": "diego0020",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/7297450?v=4",
|
||||
"profile": "https://github.com/diego0020",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dlwire",
|
||||
"name": "David Wire",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/1167291?v=4",
|
||||
"profile": "https://github.com/dlwire",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jfmedeiros1820",
|
||||
"name": "João Felipe de Medeiros Moreira",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/2211708?v=4",
|
||||
"profile": "https://github.com/jfmedeiros1820",
|
||||
"contributions": [
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "pavertomato",
|
||||
"name": "Egor Lezhnin",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/878990?v=4",
|
||||
"profile": "http://egor.lezhn.in",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
"projectName": "Viewers",
|
||||
"projectOwner": "OHIF",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Output
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Root
|
||||
README.md
|
||||
Dockerfile
|
||||
dockerfile
|
||||
|
||||
# Misc. Config
|
||||
.git
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.vscode
|
||||
.circleci
|
||||
|
||||
# Unnecessary things to pull into container
|
||||
extensions
|
||||
docs
|
||||
cypress
|
||||
@@ -0,0 +1,10 @@
|
||||
##
|
||||
# Environment: Default
|
||||
#
|
||||
# We're using this to set variables for development.
|
||||
# Please feel free to delete or modify this file for your own setup.
|
||||
# Be careful not to commit anything sensitive to source control.
|
||||
#
|
||||
|
||||
PUBLIC_URL=/
|
||||
APP_CONFIG=config/default.js
|
||||
@@ -0,0 +1,9 @@
|
||||
##
|
||||
# EXAMPLE
|
||||
#
|
||||
# Read more about .env files when using create-react-app here:
|
||||
# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-development-environment-variables-in-env
|
||||
#
|
||||
|
||||
PUBLIC_URL=/demo
|
||||
APP_CONFIG=config/netlify.js
|
||||
@@ -0,0 +1,3 @@
|
||||
config/**
|
||||
docs/**
|
||||
img/**
|
||||
@@ -0,0 +1,4 @@
|
||||
STORAGE_DIR=/storage/fs1
|
||||
POSTGRES_DB=pacsdb
|
||||
POSTGRES_USER=pacs
|
||||
POSTGRES_PASSWORD=pacs
|
||||
@@ -0,0 +1,77 @@
|
||||
version: '3.5'
|
||||
|
||||
services:
|
||||
ldap:
|
||||
image: dcm4che/slapd-dcm4chee:2.4.44-15.0
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: '10m'
|
||||
ports:
|
||||
- '389:389'
|
||||
env_file: ./dcm4che/docker-compose-dcm4che.env
|
||||
volumes:
|
||||
- ./dcm4che/etc/localtime:/etc/localtime:ro
|
||||
- ./dcm4che/etc/timezone:/etc/timezone:ro
|
||||
- ./dcm4che/dcm4che-arc/ldap:/var/lib/ldap
|
||||
- ./dcm4che/dcm4che-arc/slapd.d:/etc/ldap/slapd.d
|
||||
networks:
|
||||
- dcm4che_default
|
||||
db:
|
||||
image: dcm4che/postgres-dcm4chee:11.1-15
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: '10m'
|
||||
ports:
|
||||
- '5432:5432'
|
||||
env_file: ./dcm4che/docker-compose-dcm4che.env
|
||||
volumes:
|
||||
- ./dcm4che/etc/localtime:/etc/localtime:ro
|
||||
- ./dcm4che/etc/timezone:/etc/timezone:ro
|
||||
- ./dcm4che/dcm4che-arc/db:/var/lib/postgresql/data
|
||||
networks:
|
||||
- dcm4che_default
|
||||
arc:
|
||||
image: dcm4che/dcm4chee-arc-psql:5.15.0
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: '10m'
|
||||
ports:
|
||||
- '8080:8080'
|
||||
- '8443:8443'
|
||||
- '9990:9990'
|
||||
- '11112:11112'
|
||||
- '2575:2575'
|
||||
env_file: ./dcm4che/docker-compose-dcm4che.env
|
||||
environment:
|
||||
WILDFLY_CHOWN: /opt/wildfly/standalone /storage
|
||||
WILDFLY_WAIT_FOR: ldap:389 db:5432
|
||||
depends_on:
|
||||
- ldap
|
||||
- db
|
||||
volumes:
|
||||
- ./dcm4che/etc/localtime:/etc/localtime:ro
|
||||
- ./dcm4che/etc/timezone:/etc/timezone:ro
|
||||
- ./dcm4che/dcm4che-arc/wildfly:/opt/wildfly/standalone
|
||||
- ./dcm4che/dcm4che-arc/storage:/storage
|
||||
networks:
|
||||
- dcm4che_default
|
||||
viewer:
|
||||
container_name: ohif-viewer
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- '80:80'
|
||||
# depends_on:
|
||||
# - orthanc
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- REACT_APP_CONFIG=config/local_dcm4chee
|
||||
restart: always
|
||||
networks:
|
||||
- dcm4che_default
|
||||
|
||||
networks: dcm4che_default:
|
||||
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
America/New_York
|
||||
@@ -0,0 +1,49 @@
|
||||
events {
|
||||
worker_connections 4096; ## Default: 1024
|
||||
}
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name localhost;
|
||||
|
||||
#
|
||||
# Wide-open CORS config for nginx
|
||||
#
|
||||
location / {
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
#
|
||||
# Custom headers and headers various browsers *should* be OK with but aren't
|
||||
#
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
#
|
||||
# Tell client that this pre-flight info is valid for 20 days
|
||||
#
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization';
|
||||
add_header 'Access-Control-Allow-Credentials' true;
|
||||
add_header 'Access-Control-Max-Age' 1728000;
|
||||
add_header 'Content-Length' 0;
|
||||
return 204;
|
||||
}
|
||||
if ($request_method = 'POST') {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
||||
}
|
||||
if ($request_method = 'GET') {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization';
|
||||
add_header 'Access-Control-Allow-Credentials' true;
|
||||
}
|
||||
|
||||
proxy_pass http://orthanc:8042;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
worker_processes 1;
|
||||
|
||||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
|
||||
upstream orthanc-server {
|
||||
server orthanc:8042;
|
||||
}
|
||||
|
||||
server {
|
||||
listen [::]:80 default_server;
|
||||
listen 80;
|
||||
|
||||
# CORS Magic
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow_Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
|
||||
|
||||
location / {
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow_Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
|
||||
add_header 'Access-Control-Max-Age' 1728000;
|
||||
add_header 'Content-Type' 'text/plain charset=UTF-8';
|
||||
add_header 'Content-Length' 0;
|
||||
return 204;
|
||||
}
|
||||
|
||||
proxy_pass http://orthanc:8042;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Host $server_name;
|
||||
|
||||
# CORS Magic
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow_Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"Name": "Orthanc inside Docker",
|
||||
"StorageDirectory": "/var/lib/orthanc/db",
|
||||
"IndexDirectory": "/var/lib/orthanc/db",
|
||||
"StorageCompression": false,
|
||||
"MaximumStorageSize": 0,
|
||||
"MaximumPatientCount": 0,
|
||||
"LuaScripts": [],
|
||||
"Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"],
|
||||
"ConcurrentJobs": 2,
|
||||
"HttpServerEnabled": true,
|
||||
"HttpPort": 8042,
|
||||
"HttpDescribeErrors": true,
|
||||
"HttpCompressionEnabled": true,
|
||||
"DicomServerEnabled": true,
|
||||
"DicomAet": "ORTHANC",
|
||||
"DicomCheckCalledAet": false,
|
||||
"DicomPort": 4242,
|
||||
"DefaultEncoding": "Latin1",
|
||||
"DeflatedTransferSyntaxAccepted": true,
|
||||
"JpegTransferSyntaxAccepted": true,
|
||||
"Jpeg2000TransferSyntaxAccepted": true,
|
||||
"JpegLosslessTransferSyntaxAccepted": true,
|
||||
"JpipTransferSyntaxAccepted": true,
|
||||
"Mpeg2TransferSyntaxAccepted": true,
|
||||
"RleTransferSyntaxAccepted": true,
|
||||
"UnknownSopClassAccepted": false,
|
||||
"DicomScpTimeout": 30,
|
||||
|
||||
"RemoteAccessAllowed": true,
|
||||
"SslEnabled": false,
|
||||
"SslCertificate": "certificate.pem",
|
||||
"AuthenticationEnabled": false,
|
||||
"RegisteredUsers": {
|
||||
"test": "test"
|
||||
},
|
||||
"DicomModalities": {},
|
||||
"DicomModalitiesInDatabase": false,
|
||||
"DicomAlwaysAllowEcho": true,
|
||||
"DicomAlwaysAllowStore": true,
|
||||
"DicomCheckModalityHost": false,
|
||||
"DicomScuTimeout": 10,
|
||||
"OrthancPeers": {},
|
||||
"OrthancPeersInDatabase": false,
|
||||
"HttpProxy": "",
|
||||
|
||||
"HttpVerbose": true,
|
||||
|
||||
"HttpTimeout": 10,
|
||||
"HttpsVerifyPeers": true,
|
||||
"HttpsCACertificates": "",
|
||||
"UserMetadata": {},
|
||||
"UserContentType": {},
|
||||
"StableAge": 60,
|
||||
"StrictAetComparison": false,
|
||||
"StoreMD5ForAttachments": true,
|
||||
"LimitFindResults": 0,
|
||||
"LimitFindInstances": 0,
|
||||
"LimitJobs": 10,
|
||||
"LogExportedResources": false,
|
||||
"KeepAlive": true,
|
||||
"TcpNoDelay": true,
|
||||
"HttpThreadsCount": 50,
|
||||
"StoreDicom": true,
|
||||
"DicomAssociationCloseDelay": 5,
|
||||
"QueryRetrieveSize": 10,
|
||||
"CaseSensitivePN": false,
|
||||
"LoadPrivateDictionary": true,
|
||||
"Dictionary": {},
|
||||
"SynchronousCMove": true,
|
||||
"JobsHistorySize": 10,
|
||||
"SaveJobs": true,
|
||||
"OverwriteInstances": false,
|
||||
"MediaArchiveSize": 1,
|
||||
"StorageAccessOnFind": "Always",
|
||||
"MetricsEnabled": true,
|
||||
|
||||
"DicomWeb": {
|
||||
"Enable": true,
|
||||
"Root": "/dicom-web/",
|
||||
"EnableWado": true,
|
||||
"WadoRoot": "/wado",
|
||||
"Host": "127.0.0.1",
|
||||
"Ssl": false,
|
||||
"StowMaxInstances": 10,
|
||||
"StowMaxSize": 10,
|
||||
"QidoCaseSensitive": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
version: '3.5'
|
||||
|
||||
services:
|
||||
orthanc:
|
||||
image: jodogne/orthanc-plugins:1.5.6
|
||||
hostname: orthanc
|
||||
volumes:
|
||||
# Config
|
||||
- ./config/orthanc.json:/etc/orthanc/orthanc.json:ro
|
||||
# Persist data
|
||||
- ./volumes/orthanc-db/:/var/lib/orthanc/db/
|
||||
ports:
|
||||
- '4242:4242' # DICOM
|
||||
- '8042:8042' # Web
|
||||
restart: unless-stopped
|
||||
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -0,0 +1,16 @@
|
||||
# Output
|
||||
dist/
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Root
|
||||
README.md
|
||||
Dockerfile
|
||||
|
||||
# Misc. Config
|
||||
.git
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.vscode
|
||||
.circleci
|
||||
@@ -0,0 +1,5 @@
|
||||
# Docker ENV and ARG Variables
|
||||
# ----------------------------
|
||||
# https://vsupalov.com/docker-arg-env-variable-guide/
|
||||
#
|
||||
#
|
||||
@@ -0,0 +1,259 @@
|
||||
worker_processes 2;
|
||||
error_log /var/logs/nginx/mydomain.error.log;
|
||||
pid /var/run/nginx.pid;
|
||||
include /usr/share/nginx/modules/*.conf; # See /usr/share/doc/nginx/README.dynamic.
|
||||
|
||||
events {
|
||||
worker_connections 1024; ## Default: 1024
|
||||
use epoll; # http://nginx.org/en/docs/events.html
|
||||
multi_accept on; # http://nginx.org/en/docs/ngx_core_module.html#multi_accept
|
||||
}
|
||||
|
||||
# Core Modules Docs:
|
||||
# http://nginx.org/en/docs/http/ngx_http_core_module.html
|
||||
http {
|
||||
include '/usr/local/openresty/nginx/conf/mime.types';
|
||||
default_type application/octet-stream;
|
||||
|
||||
keepalive_timeout 65;
|
||||
keepalive_requests 100000;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
|
||||
# lua_ settings
|
||||
#
|
||||
lua_package_path '/usr/local/openresty/lualib/?.lua;;';
|
||||
lua_shared_dict discovery 1m; # cache for discovery metadata documents
|
||||
lua_shared_dict jwks 1m; # cache for JWKs
|
||||
# lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
|
||||
|
||||
variables_hash_max_size 2048;
|
||||
server_names_hash_bucket_size 128;
|
||||
server_tokens off;
|
||||
|
||||
resolver 8.8.8.8 valid=30s ipv6=off;
|
||||
resolver_timeout 11s;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
# No idea what this is doing
|
||||
# https://stackoverflow.com/a/5877989/1867984
|
||||
# upstream upstream_server {
|
||||
# # server 10.100.4.200:1010 max_fails=3 fail_timeout=30s;
|
||||
# server 127.0.0.1:
|
||||
# }
|
||||
|
||||
# Nginx `listener` block
|
||||
server {
|
||||
listen [::]:80 default_server;
|
||||
listen 80;
|
||||
# listen 443 ssl;
|
||||
access_log /var/logs/nginx/mydomain.access.log;
|
||||
|
||||
# Domain to protect
|
||||
server_name 127.0.0.1 localhost; # mydomain.com;
|
||||
proxy_intercept_errors off;
|
||||
# ssl_certificate /etc/letsencrypt/live/mydomain.co.uk/fullchain.pem;
|
||||
# ssl_certificate_key /etc/letsencrypt/live/mydomain.co.uk/privkey.pem;
|
||||
gzip on;
|
||||
gzip_types text/css application/javascript application/json image/svg+xml;
|
||||
gzip_comp_level 9;
|
||||
etag on;
|
||||
|
||||
# https://github.com/bungle/lua-resty-session/issues/15
|
||||
set $session_check_ssi off;
|
||||
lua_code_cache off;
|
||||
set $session_secret Eeko7aeb6iu5Wohch9Loo1aitha0ahd1;
|
||||
set $session_storage cookie;
|
||||
|
||||
server_tokens off; # Hides server version num
|
||||
|
||||
# [PROTECTED] Reverse Proxy for `orthanc` admin
|
||||
#
|
||||
location /pacs-admin/ {
|
||||
access_by_lua_block {
|
||||
local opts = {
|
||||
redirect_uri = "http://127.0.0.1/pacs-admin/admin",
|
||||
discovery = "http://127.0.0.1/auth/realms/ohif/.well-known/openid-configuration",
|
||||
token_endpoint_auth_method = "client_secret_basic",
|
||||
client_id = "pacs",
|
||||
client_secret = "66279641-eba6-47f5-9fdb-70c4ac74d548",
|
||||
client_jwt_assertion_expires_in = 60 * 60,
|
||||
ssl_verify = "no",
|
||||
scope = "openid email profile",
|
||||
refresh_session_interval = 900,
|
||||
redirect_uri_scheme = "http",
|
||||
redirect_after_logout_uri = "/",
|
||||
session_contents = {id_token=true}
|
||||
}
|
||||
|
||||
-- call authenticate for OpenID Connect user authentication
|
||||
local res, err = require("resty.openidc").authenticate(opts)
|
||||
|
||||
if err or not res then
|
||||
ngx.print(err)
|
||||
ngx.status = 200
|
||||
ngx.say(err and err or "no access_token provided")
|
||||
ngx.exit(ngx.HTTP_FORBIDDEN)
|
||||
end
|
||||
|
||||
-- Or set cookie?
|
||||
-- ngx.req.set_header("Authorization", "Bearer " .. res.access_token)
|
||||
ngx.req.set_header("X-USER", res.id_token.sub)
|
||||
}
|
||||
|
||||
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
expires 0;
|
||||
add_header Cache-Control private;
|
||||
|
||||
proxy_pass http://orthanc:8042/;
|
||||
}
|
||||
|
||||
# [PROTECTED] Reverse Proxy for `orthanc` APIs (including DICOMWeb)
|
||||
#
|
||||
location /pacs/ {
|
||||
access_by_lua_block {
|
||||
local opts = {
|
||||
discovery = "http://127.0.0.1/auth/realms/ohif/.well-known/openid-configuration",
|
||||
}
|
||||
|
||||
-- call bearer_jwt_verify for OAuth 2.0 JWT validation
|
||||
local res, err = require("resty.openidc").bearer_jwt_verify(opts)
|
||||
|
||||
if err or not res then
|
||||
ngx.status = 403
|
||||
ngx.say(err and err or "no access_token provided")
|
||||
ngx.exit(ngx.HTTP_FORBIDDEN)
|
||||
end
|
||||
}
|
||||
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# proxy_set_header Upgrade $http_upgrade;
|
||||
# proxy_set_header Connection "upgrade";
|
||||
|
||||
expires 0;
|
||||
add_header Cache-Control private;
|
||||
|
||||
proxy_pass http://orthanc:8042/;
|
||||
|
||||
# By default, this endpoint is protected by CORS (cross-origin-resource-sharing)
|
||||
# You can add headers to allow other domains to request this resource.
|
||||
# See the "Updating CORS Settings" example below
|
||||
}
|
||||
|
||||
# Keycloak
|
||||
#
|
||||
location /auth/ {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_pass http://keycloak:8080/auth/;
|
||||
}
|
||||
|
||||
# Do not cache sw.js, required for offline-first updates.
|
||||
location /sw.js {
|
||||
add_header Cache-Control "no-cache";
|
||||
proxy_cache_bypass $http_pragma;
|
||||
proxy_cache_revalidate on;
|
||||
expires off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Single Page App
|
||||
# Try files, fallback to index.html
|
||||
#
|
||||
location / {
|
||||
alias /var/www/html/;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
}
|
||||
|
||||
# EXAMPLE: Reverse Proxy, no auth
|
||||
# [UNPROTECTED] reverse proxy for `orthanc`
|
||||
#
|
||||
# location /pacs/ {
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $remote_addr;
|
||||
# proxy_set_header Host $host;
|
||||
#
|
||||
# proxy_pass http://orthanc:8042/;
|
||||
#
|
||||
# # OR
|
||||
# # rewrite ^/pacs(.*) /$1 break;
|
||||
# # proxy_pass http://orthanc:8042;
|
||||
# }
|
||||
|
||||
# EXAMPLE: Modifying headers to allow requests from other domains
|
||||
# IE. Updating CORS settings
|
||||
#
|
||||
# location / {
|
||||
# if ($request_method = 'OPTIONS') {
|
||||
# add_header 'Access-Control-Allow-Origin' '*';
|
||||
# add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
# #
|
||||
# # Custom headers and headers various browsers *should* be OK with but aren't
|
||||
# #
|
||||
# add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
# #
|
||||
# # Tell client that this pre-flight info is valid for 20 days
|
||||
# #
|
||||
# add_header 'Access-Control-Allow-Headers' 'Authorization';
|
||||
# add_header 'Access-Control-Allow-Credentials' true;
|
||||
# add_header 'Access-Control-Max-Age' 1728000;
|
||||
# add_header 'Content-Length' 0;
|
||||
# return 204;
|
||||
# }
|
||||
# if ($request_method = 'POST') {
|
||||
# add_header 'Access-Control-Allow-Origin' '*';
|
||||
# add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
# add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
# add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
||||
# }
|
||||
# if ($request_method = 'GET') {
|
||||
# add_header 'Access-Control-Allow-Origin' '*';
|
||||
# add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
# add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
# add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
||||
# add_header 'Access-Control-Allow-Headers' 'Authorization';
|
||||
# add_header 'Access-Control-Allow-Credentials' true;
|
||||
# }
|
||||
#
|
||||
# # proxy_http_version 1.1;
|
||||
#
|
||||
# # proxy_set_header Host $host;
|
||||
# # proxy_set_header X-Real-IP $remote_addr;
|
||||
# # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# # proxy_set_header X-Forwarded-Proto $scheme;
|
||||
#
|
||||
# proxy_pass http://orthanc:8042;
|
||||
# }
|
||||
|
||||
# EXAMPLE: Redirect server error pages to the static page /40x.html
|
||||
#
|
||||
# error_page 404 /404.html;
|
||||
# location = /40x.html {
|
||||
# }
|
||||
|
||||
# EXAMPLE: Redirect server error pages to the static page /50x.html
|
||||
#
|
||||
# error_page 500 502 503 504 /50x.html;
|
||||
# location = /50x.html {
|
||||
# }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"Name": "Orthanc inside Docker",
|
||||
"StorageDirectory": "/var/lib/orthanc/db",
|
||||
"IndexDirectory": "/var/lib/orthanc/db",
|
||||
"StorageCompression": false,
|
||||
"MaximumStorageSize": 0,
|
||||
"MaximumPatientCount": 0,
|
||||
"LuaScripts": [],
|
||||
"Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"],
|
||||
"ConcurrentJobs": 2,
|
||||
"HttpServerEnabled": true,
|
||||
"HttpPort": 8042,
|
||||
"HttpDescribeErrors": true,
|
||||
"HttpCompressionEnabled": true,
|
||||
"DicomServerEnabled": true,
|
||||
"DicomAet": "ORTHANC",
|
||||
"DicomCheckCalledAet": false,
|
||||
"DicomPort": 4242,
|
||||
"DefaultEncoding": "Latin1",
|
||||
"DeflatedTransferSyntaxAccepted": true,
|
||||
"JpegTransferSyntaxAccepted": true,
|
||||
"Jpeg2000TransferSyntaxAccepted": true,
|
||||
"JpegLosslessTransferSyntaxAccepted": true,
|
||||
"JpipTransferSyntaxAccepted": true,
|
||||
"Mpeg2TransferSyntaxAccepted": true,
|
||||
"RleTransferSyntaxAccepted": true,
|
||||
"UnknownSopClassAccepted": false,
|
||||
"DicomScpTimeout": 30,
|
||||
|
||||
"RemoteAccessAllowed": true,
|
||||
"SslEnabled": false,
|
||||
"SslCertificate": "certificate.pem",
|
||||
"AuthenticationEnabled": false,
|
||||
"RegisteredUsers": {
|
||||
"test": "test"
|
||||
},
|
||||
"DicomModalities": {},
|
||||
"DicomModalitiesInDatabase": false,
|
||||
"DicomAlwaysAllowEcho": true,
|
||||
"DicomAlwaysAllowStore": true,
|
||||
"DicomCheckModalityHost": false,
|
||||
"DicomScuTimeout": 10,
|
||||
"OrthancPeers": {},
|
||||
"OrthancPeersInDatabase": false,
|
||||
"HttpProxy": "",
|
||||
|
||||
"HttpVerbose": true,
|
||||
|
||||
"HttpTimeout": 10,
|
||||
"HttpsVerifyPeers": true,
|
||||
"HttpsCACertificates": "",
|
||||
"UserMetadata": {},
|
||||
"UserContentType": {},
|
||||
"StableAge": 60,
|
||||
"StrictAetComparison": false,
|
||||
"StoreMD5ForAttachments": true,
|
||||
"LimitFindResults": 0,
|
||||
"LimitFindInstances": 0,
|
||||
"LimitJobs": 10,
|
||||
"LogExportedResources": false,
|
||||
"KeepAlive": true,
|
||||
"TcpNoDelay": true,
|
||||
"HttpThreadsCount": 50,
|
||||
"StoreDicom": true,
|
||||
"DicomAssociationCloseDelay": 5,
|
||||
"QueryRetrieveSize": 10,
|
||||
"CaseSensitivePN": false,
|
||||
"LoadPrivateDictionary": true,
|
||||
"Dictionary": {},
|
||||
"SynchronousCMove": true,
|
||||
"JobsHistorySize": 10,
|
||||
"SaveJobs": true,
|
||||
"OverwriteInstances": false,
|
||||
"MediaArchiveSize": 1,
|
||||
"StorageAccessOnFind": "Always",
|
||||
"MetricsEnabled": true,
|
||||
|
||||
"DicomWeb": {
|
||||
"Enable": true,
|
||||
"Root": "/dicom-web/",
|
||||
"EnableWado": true,
|
||||
"WadoRoot": "/wado",
|
||||
"Host": "127.0.0.1",
|
||||
"Ssl": false,
|
||||
"StowMaxInstances": 10,
|
||||
"StowMaxSize": 10,
|
||||
"QidoCaseSensitive": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
# Reference:
|
||||
# - https://docs.docker.com/compose/compose-file
|
||||
# - https://eclipsesource.com/blogs/2018/01/11/authenticating-reverse-proxy-with-keycloak/
|
||||
|
||||
version: '3.5'
|
||||
|
||||
services:
|
||||
# Exposed server that's handling incoming web requests
|
||||
# Underlying image: openresty/openresty:alpine-fat
|
||||
ohif_viewer:
|
||||
build:
|
||||
# Project root
|
||||
context: ./../../
|
||||
# Relative to context
|
||||
dockerfile: ./docker/OpenResty-Orthanc-Keycloak/dockerfile
|
||||
image: webapp:latest
|
||||
container_name: webapp
|
||||
volumes:
|
||||
# Nginx config
|
||||
- ./config/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
|
||||
# Logs
|
||||
- ./logs/nginx:/var/logs/nginx
|
||||
# Let's Encrypt
|
||||
# - letsencrypt_certificates:/etc/letsencrypt
|
||||
# - letsencrypt_challenges:/var/www/letsencrypt
|
||||
ports:
|
||||
- '443:443' # SSL
|
||||
- '80:80' # Web
|
||||
depends_on:
|
||||
- keycloak
|
||||
- orthanc
|
||||
restart: on-failure
|
||||
|
||||
# LINK: https://hub.docker.com/r/jodogne/orthanc-plugins/
|
||||
# TODO: Update to use Postgres
|
||||
# https://github.com/mrts/docker-postgresql-multiple-databases
|
||||
orthanc:
|
||||
image: jodogne/orthanc-plugins:1.5.6
|
||||
hostname: orthanc
|
||||
container_name: orthanc
|
||||
volumes:
|
||||
# Config
|
||||
- ./config/orthanc.json:/etc/orthanc/orthanc.json:ro
|
||||
# Persist data
|
||||
- ./volumes/orthanc-db/:/var/lib/orthanc/db/
|
||||
restart: unless-stopped
|
||||
|
||||
# LINK: https://hub.docker.com/r/jboss/keycloak
|
||||
keycloak:
|
||||
image: jboss/keycloak:6.0.1
|
||||
hostname: keycloak
|
||||
container_name: keycloak
|
||||
volumes:
|
||||
# Theme: https://www.keycloak.org/docs/latest/server_development/index.html#_themes
|
||||
- ./volumes/keycloak-themes/ohif:/opt/jboss/keycloak/themes/ohif
|
||||
# Previous Realm Config
|
||||
- ./config/ohif-keycloak-realm.json:/tmp/ohif-keycloak-realm.json
|
||||
environment:
|
||||
# Database
|
||||
DB_VENDOR: postgres
|
||||
DB_ADDR: postgres
|
||||
DB_PORT: 5432
|
||||
DB_DATABASE: keycloak
|
||||
DB_SCHEMA: public
|
||||
DB_USER: keycloak
|
||||
DB_PASSWORD: password
|
||||
# Keycloak
|
||||
KEYCLOAK_USER: admin
|
||||
KEYCLOAK_PASSWORD: password
|
||||
KEYCLOAK_IMPORT: /tmp/ohif-keycloak-realm.json
|
||||
# KEYCLOAK_WELCOME_THEME: <theme-name>
|
||||
# KEYCLOAK_DEFAULT_THEME: <theme-name>
|
||||
# KEYCLOAK_HOSTNAME: (recommended in prod)
|
||||
# KEYCLOAK_LOGLEVEL: DEBUG
|
||||
PROXY_ADDRESS_FORWARDING: 'true'
|
||||
depends_on:
|
||||
- postgres
|
||||
restart: unless-stopped
|
||||
|
||||
# LINK: https://hub.docker.com/_/postgres/
|
||||
postgres:
|
||||
image: postgres:11.2
|
||||
hostname: postgres
|
||||
container_name: postgres
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_DB: keycloak
|
||||
POSTGRES_USER: keycloak
|
||||
POSTGRES_PASSWORD: password
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
@@ -0,0 +1,65 @@
|
||||
# docker-compose
|
||||
# --------------
|
||||
# This dockerfile is used by the `docker-compose.yml` adjacent file. When
|
||||
# running `docker-compose build`, this dockerfile helps build the "webapp" image.
|
||||
# All paths are relative to the `context`, which is the project root directory.
|
||||
#
|
||||
# docker build
|
||||
# --------------
|
||||
# If you would like to use this dockerfile to build and tag an image, make sure
|
||||
# you set the context to the project's root directory:
|
||||
# https://docs.docker.com/engine/reference/commandline/build/
|
||||
#
|
||||
#
|
||||
# SUMMARY
|
||||
# --------------
|
||||
# This dockerfile has two stages:
|
||||
#
|
||||
# 1. Building the React application for production
|
||||
# 2. Setting up our Nginx (OpenResty*) image w/ step one's output
|
||||
#
|
||||
# * OpenResty is functionally identical to Nginx with the addition of Lua out of
|
||||
# the box.
|
||||
|
||||
|
||||
# Stage 1: Build the application
|
||||
FROM node:11.2.0-slim as builder
|
||||
|
||||
RUN mkdir /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
ENV REACT_APP_CONFIG=config/docker_openresty-orthanc-keycloak.js
|
||||
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
||||
|
||||
COPY package.json /usr/src/app/package.json
|
||||
COPY yarn.lock /usr/src/app/yarn.lock
|
||||
|
||||
ADD . /usr/src/app/
|
||||
RUN yarn install
|
||||
RUN yarn run build:web
|
||||
|
||||
# Stage 2: Bundle the built application into a Docker container
|
||||
# which runs openresty (nginx) using Alpine Linux
|
||||
# LINK: https://hub.docker.com/r/openresty/openresty
|
||||
FROM openresty/openresty:1.15.8.1rc1-0-alpine-fat
|
||||
|
||||
RUN mkdir /var/log/nginx
|
||||
RUN apk add --no-cache openssl
|
||||
RUN apk add --no-cache openssl-dev
|
||||
RUN apk add --no-cache git
|
||||
RUN apk add --no-cache gcc
|
||||
# !!!
|
||||
RUN luarocks install lua-resty-openidc
|
||||
|
||||
#
|
||||
RUN luarocks install lua-resty-jwt
|
||||
RUN luarocks install lua-resty-session
|
||||
RUN luarocks install lua-resty-http
|
||||
# !!!
|
||||
RUN luarocks install lua-resty-openidc
|
||||
RUN luarocks install luacrypto
|
||||
|
||||
# Copy build output to image
|
||||
COPY --from=builder /usr/src/app/build /var/www/html
|
||||
|
||||
ENTRYPOINT ["/usr/local/openresty/nginx/sbin/nginx", "-g", "daemon off;"]
|
||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
+212
@@ -0,0 +1,212 @@
|
||||
body {
|
||||
background-color: #040507;
|
||||
background-image: url('../img/background.jpg');
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
color: #fff;
|
||||
font-family: sans-serif;
|
||||
text-shadow: 0px 0px 10px #000;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
div#kc-content {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
left: 50%;
|
||||
width: 550px;
|
||||
margin-left: -180px;
|
||||
}
|
||||
|
||||
div#kc-form {
|
||||
float: left;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
div#kc-form label {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
div#info-area {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin-top: 40px;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div#info-area p {
|
||||
margin-right: 30px;
|
||||
display: inline;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
input[type='password'] {
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
background-color: rgba(256, 256, 256, 0.7);
|
||||
border: 0px solid rgba(0, 0, 0, 0.2);
|
||||
box-shadow: inset 0 0 2px 2px rgba(0, 0, 0, 0.15);
|
||||
padding: 10px;
|
||||
width: 296px;
|
||||
}
|
||||
|
||||
input[type='text']:hover,
|
||||
input[type='password']:hover {
|
||||
background-color: rgba(256, 256, 256, 0.9);
|
||||
}
|
||||
|
||||
input[type='submit'] {
|
||||
border: none;
|
||||
|
||||
background: -webkit-linear-gradient(
|
||||
top,
|
||||
rgba(255, 255, 255, 0.8),
|
||||
rgba(255, 255, 255, 0.1)
|
||||
);
|
||||
background: -moz-linear-gradient(
|
||||
top,
|
||||
rgba(255, 255, 255, 0.8),
|
||||
rgba(255, 255, 255, 0.1)
|
||||
);
|
||||
background: -ms-linear-gradient(
|
||||
top,
|
||||
rgba(255, 255, 255, 0.8),
|
||||
rgba(255, 255, 255, 0.1)
|
||||
);
|
||||
background: -o-linear-gradient(
|
||||
top,
|
||||
rgba(255, 255, 255, 0.8),
|
||||
rgba(255, 255, 255, 0.1)
|
||||
);
|
||||
|
||||
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.5);
|
||||
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
margin-right: 10px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
input[type='submit']:hover {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
div#kc-form-options div {
|
||||
display: inline-block;
|
||||
margin-right: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
div#kc-form-options div label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
div#kc-feedback {
|
||||
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div#kc-feedback-wrapper {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
div.feedback-success {
|
||||
background-color: rgba(155, 155, 255, 0.1);
|
||||
}
|
||||
|
||||
div.feedback-warning {
|
||||
background-color: rgba(255, 175, 0, 0.1);
|
||||
}
|
||||
|
||||
div.feedback-error {
|
||||
background-color: rgba(255, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
div#kc-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div#kc-registration {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
div#social-login {
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
||||
float: right;
|
||||
width: 150px;
|
||||
padding: 20px 0 200px 40px;
|
||||
}
|
||||
|
||||
div.social-login span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div#kc-social-providers ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div#kc-social-providers ul li {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
div#kc-social-providers ul li span {
|
||||
display: inline;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
a.zocial {
|
||||
border: none;
|
||||
background: -webkit-linear-gradient(
|
||||
top,
|
||||
rgba(255, 255, 255, 0.8),
|
||||
rgba(255, 255, 255, 0.1)
|
||||
) !important;
|
||||
background: -moz-linear-gradient(
|
||||
top,
|
||||
rgba(255, 255, 255, 0.8),
|
||||
rgba(255, 255, 255, 0.1)
|
||||
) !important;
|
||||
background: -ms-linear-gradient(
|
||||
top,
|
||||
rgba(255, 255, 255, 0.8),
|
||||
rgba(255, 255, 255, 0.1)
|
||||
) !important;
|
||||
background: -o-linear-gradient(
|
||||
top,
|
||||
rgba(255, 255, 255, 0.8),
|
||||
rgba(255, 255, 255, 0.1)
|
||||
) !important;
|
||||
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.5);
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
width: 130px;
|
||||
text-shadow: none;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
+3
@@ -0,0 +1,3 @@
|
||||
parent=base
|
||||
import=common/keycloak
|
||||
styles=lib/zocial/zocial.css css/styles.css
|
||||
Whitespace-only changes.
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -0,0 +1,16 @@
|
||||
# Output
|
||||
dist/
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Root
|
||||
README.md
|
||||
Dockerfile
|
||||
|
||||
# Misc. Config
|
||||
.git
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.vscode
|
||||
.circleci
|
||||
@@ -0,0 +1,5 @@
|
||||
# Docker ENV and ARG Variables
|
||||
# ----------------------------
|
||||
# https://vsupalov.com/docker-arg-env-variable-guide/
|
||||
#
|
||||
#
|
||||
@@ -0,0 +1,135 @@
|
||||
worker_processes 2;
|
||||
error_log /var/logs/nginx/mydomain.error.log;
|
||||
pid /var/run/nginx.pid;
|
||||
include /usr/share/nginx/modules/*.conf; # See /usr/share/doc/nginx/README.dynamic.
|
||||
|
||||
events {
|
||||
worker_connections 1024; ## Default: 1024
|
||||
use epoll; # http://nginx.org/en/docs/events.html
|
||||
multi_accept on; # http://nginx.org/en/docs/ngx_core_module.html#multi_accept
|
||||
}
|
||||
|
||||
# Core Modules Docs:
|
||||
# http://nginx.org/en/docs/http/ngx_http_core_module.html
|
||||
http {
|
||||
include '/usr/local/openresty/nginx/conf/mime.types';
|
||||
default_type application/octet-stream;
|
||||
|
||||
keepalive_timeout 65;
|
||||
keepalive_requests 100000;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
|
||||
# lua_ settings
|
||||
#
|
||||
lua_package_path '/usr/local/openresty/lualib/?.lua;;';
|
||||
lua_shared_dict discovery 1m; # cache for discovery metadata documents
|
||||
lua_shared_dict jwks 1m; # cache for JWKs
|
||||
# lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
|
||||
|
||||
variables_hash_max_size 2048;
|
||||
server_names_hash_bucket_size 128;
|
||||
server_tokens off;
|
||||
|
||||
resolver 8.8.8.8 valid=30s ipv6=off;
|
||||
resolver_timeout 11s;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
# Nginx `listener` block
|
||||
server {
|
||||
listen [::]:80 default_server;
|
||||
listen 80;
|
||||
# listen 443 ssl;
|
||||
access_log /var/logs/nginx/mydomain.access.log;
|
||||
|
||||
# Domain to protect
|
||||
server_name 127.0.0.1 localhost; # mydomain.com;
|
||||
proxy_intercept_errors off;
|
||||
# ssl_certificate /etc/letsencrypt/live/mydomain.co.uk/fullchain.pem;
|
||||
# ssl_certificate_key /etc/letsencrypt/live/mydomain.co.uk/privkey.pem;
|
||||
gzip on;
|
||||
gzip_types text/css application/javascript application/json image/svg+xml;
|
||||
gzip_comp_level 9;
|
||||
etag on;
|
||||
|
||||
# https://github.com/bungle/lua-resty-session/issues/15
|
||||
set $session_check_ssi off;
|
||||
lua_code_cache off;
|
||||
set $session_secret Eeko7aeb6iu5Wohch9Loo1aitha0ahd1;
|
||||
set $session_storage cookie;
|
||||
|
||||
server_tokens off; # Hides server version num
|
||||
|
||||
# Reverse Proxy for `orthanc` admin
|
||||
#
|
||||
location /pacs-admin/ {
|
||||
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
expires 0;
|
||||
add_header Cache-Control private;
|
||||
|
||||
proxy_pass http://orthanc:8042/;
|
||||
}
|
||||
|
||||
# Reverse Proxy for `orthanc` APIs (including DICOMWeb)
|
||||
#
|
||||
location /pacs/ {
|
||||
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
expires 0;
|
||||
add_header Cache-Control private;
|
||||
|
||||
proxy_pass http://orthanc:8042/;
|
||||
|
||||
# By default, this endpoint is protected by CORS (cross-origin-resource-sharing)
|
||||
# You can add headers to allow other domains to request this resource.
|
||||
# See the "Updating CORS Settings" example below
|
||||
}
|
||||
|
||||
# Do not cache sw.js, required for offline-first updates.
|
||||
location /sw.js {
|
||||
add_header Cache-Control "no-cache";
|
||||
proxy_cache_bypass $http_pragma;
|
||||
proxy_cache_revalidate on;
|
||||
expires off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Single Page App
|
||||
# Try files, fallback to index.html
|
||||
#
|
||||
location / {
|
||||
alias /var/www/html/;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
}
|
||||
|
||||
# EXAMPLE: Redirect server error pages to the static page /40x.html
|
||||
#
|
||||
# error_page 404 /404.html;
|
||||
# location = /40x.html {
|
||||
# }
|
||||
|
||||
# EXAMPLE: Redirect server error pages to the static page /50x.html
|
||||
#
|
||||
# error_page 500 502 503 504 /50x.html;
|
||||
# location = /50x.html {
|
||||
# }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"Name": "Orthanc inside Docker",
|
||||
"StorageDirectory": "/var/lib/orthanc/db",
|
||||
"IndexDirectory": "/var/lib/orthanc/db",
|
||||
"StorageCompression": false,
|
||||
"MaximumStorageSize": 0,
|
||||
"MaximumPatientCount": 0,
|
||||
"LuaScripts": [],
|
||||
"Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"],
|
||||
"ConcurrentJobs": 2,
|
||||
"HttpServerEnabled": true,
|
||||
"HttpPort": 8042,
|
||||
"HttpDescribeErrors": true,
|
||||
"HttpCompressionEnabled": true,
|
||||
"DicomServerEnabled": true,
|
||||
"DicomAet": "ORTHANC",
|
||||
"DicomCheckCalledAet": false,
|
||||
"DicomPort": 4242,
|
||||
"DefaultEncoding": "Latin1",
|
||||
"DeflatedTransferSyntaxAccepted": true,
|
||||
"JpegTransferSyntaxAccepted": true,
|
||||
"Jpeg2000TransferSyntaxAccepted": true,
|
||||
"JpegLosslessTransferSyntaxAccepted": true,
|
||||
"JpipTransferSyntaxAccepted": true,
|
||||
"Mpeg2TransferSyntaxAccepted": true,
|
||||
"RleTransferSyntaxAccepted": true,
|
||||
"UnknownSopClassAccepted": false,
|
||||
"DicomScpTimeout": 30,
|
||||
|
||||
"RemoteAccessAllowed": true,
|
||||
"SslEnabled": false,
|
||||
"SslCertificate": "certificate.pem",
|
||||
"AuthenticationEnabled": false,
|
||||
"RegisteredUsers": {
|
||||
"test": "test"
|
||||
},
|
||||
"DicomModalities": {},
|
||||
"DicomModalitiesInDatabase": false,
|
||||
"DicomAlwaysAllowEcho": true,
|
||||
"DicomAlwaysAllowStore": true,
|
||||
"DicomCheckModalityHost": false,
|
||||
"DicomScuTimeout": 10,
|
||||
"OrthancPeers": {},
|
||||
"OrthancPeersInDatabase": false,
|
||||
"HttpProxy": "",
|
||||
|
||||
"HttpVerbose": true,
|
||||
|
||||
"HttpTimeout": 10,
|
||||
"HttpsVerifyPeers": true,
|
||||
"HttpsCACertificates": "",
|
||||
"UserMetadata": {},
|
||||
"UserContentType": {},
|
||||
"StableAge": 60,
|
||||
"StrictAetComparison": false,
|
||||
"StoreMD5ForAttachments": true,
|
||||
"LimitFindResults": 0,
|
||||
"LimitFindInstances": 0,
|
||||
"LimitJobs": 10,
|
||||
"LogExportedResources": false,
|
||||
"KeepAlive": true,
|
||||
"TcpNoDelay": true,
|
||||
"HttpThreadsCount": 50,
|
||||
"StoreDicom": true,
|
||||
"DicomAssociationCloseDelay": 5,
|
||||
"QueryRetrieveSize": 10,
|
||||
"CaseSensitivePN": false,
|
||||
"LoadPrivateDictionary": true,
|
||||
"Dictionary": {},
|
||||
"SynchronousCMove": true,
|
||||
"JobsHistorySize": 10,
|
||||
"SaveJobs": true,
|
||||
"OverwriteInstances": false,
|
||||
"MediaArchiveSize": 1,
|
||||
"StorageAccessOnFind": "Always",
|
||||
"MetricsEnabled": true,
|
||||
|
||||
"DicomWeb": {
|
||||
"Enable": true,
|
||||
"Root": "/dicom-web/",
|
||||
"EnableWado": true,
|
||||
"WadoRoot": "/wado",
|
||||
"Host": "127.0.0.1",
|
||||
"Ssl": false,
|
||||
"StowMaxInstances": 10,
|
||||
"StowMaxSize": 10,
|
||||
"QidoCaseSensitive": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# Reference:
|
||||
# - https://docs.docker.com/compose/compose-file
|
||||
# - https://eclipsesource.com/blogs/2018/01/11/authenticating-reverse-proxy-with-keycloak/
|
||||
|
||||
version: '3.5'
|
||||
|
||||
services:
|
||||
# Exposed server that's handling incoming web requests
|
||||
# Underlying image: openresty/openresty:alpine-fat
|
||||
ohif_viewer:
|
||||
build:
|
||||
# Project root
|
||||
context: ./../../
|
||||
# Relative to context
|
||||
dockerfile: ./docker/OpenResty-Orthanc/dockerfile
|
||||
image: webapp:latest
|
||||
container_name: webapp
|
||||
volumes:
|
||||
# Nginx config
|
||||
- ./config/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
|
||||
# Logs
|
||||
- ./logs/nginx:/var/logs/nginx
|
||||
# Let's Encrypt
|
||||
# - letsencrypt_certificates:/etc/letsencrypt
|
||||
# - letsencrypt_challenges:/var/www/letsencrypt
|
||||
ports:
|
||||
- '443:443' # SSL
|
||||
- '80:80' # Web
|
||||
depends_on:
|
||||
- orthanc
|
||||
restart: on-failure
|
||||
|
||||
# LINK: https://hub.docker.com/r/jodogne/orthanc-plugins/
|
||||
# TODO: Update to use Postgres
|
||||
# https://github.com/mrts/docker-postgresql-multiple-databases
|
||||
orthanc:
|
||||
image: jodogne/orthanc-plugins:1.5.6
|
||||
hostname: orthanc
|
||||
container_name: orthanc
|
||||
volumes:
|
||||
# Config
|
||||
- ./config/orthanc.json:/etc/orthanc/orthanc.json:ro
|
||||
# Persist data
|
||||
- ./volumes/orthanc-db/:/var/lib/orthanc/db/
|
||||
restart: unless-stopped
|
||||
@@ -0,0 +1,65 @@
|
||||
# docker-compose
|
||||
# --------------
|
||||
# This dockerfile is used by the `docker-compose.yml` adjacent file. When
|
||||
# running `docker-compose build`, this dockerfile helps build the "webapp" image.
|
||||
# All paths are relative to the `context`, which is the project root directory.
|
||||
#
|
||||
# docker build
|
||||
# --------------
|
||||
# If you would like to use this dockerfile to build and tag an image, make sure
|
||||
# you set the context to the project's root directory:
|
||||
# https://docs.docker.com/engine/reference/commandline/build/
|
||||
#
|
||||
#
|
||||
# SUMMARY
|
||||
# --------------
|
||||
# This dockerfile has two stages:
|
||||
#
|
||||
# 1. Building the React application for production
|
||||
# 2. Setting up our Nginx (OpenResty*) image w/ step one's output
|
||||
#
|
||||
# * OpenResty is functionally identical to Nginx with the addition of Lua out of
|
||||
# the box.
|
||||
|
||||
|
||||
# Stage 1: Build the application
|
||||
FROM node:11.2.0-slim as builder
|
||||
|
||||
RUN mkdir /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
ENV REACT_APP_CONFIG=config/docker_openresty-orthanc.js
|
||||
ENV PATH /usr/src/app/node_modules/.bin:$PATH
|
||||
|
||||
COPY package.json /usr/src/app/package.json
|
||||
COPY yarn.lock /usr/src/app/yarn.lock
|
||||
|
||||
ADD . /usr/src/app/
|
||||
RUN yarn install
|
||||
RUN yarn run build:web
|
||||
|
||||
# Stage 2: Bundle the built application into a Docker container
|
||||
# which runs openresty (nginx) using Alpine Linux
|
||||
# LINK: https://hub.docker.com/r/openresty/openresty
|
||||
FROM openresty/openresty:1.15.8.1rc1-0-alpine-fat
|
||||
|
||||
RUN mkdir /var/log/nginx
|
||||
RUN apk add --no-cache openssl
|
||||
RUN apk add --no-cache openssl-dev
|
||||
RUN apk add --no-cache git
|
||||
RUN apk add --no-cache gcc
|
||||
# !!!
|
||||
RUN luarocks install lua-resty-openidc
|
||||
|
||||
#
|
||||
RUN luarocks install lua-resty-jwt
|
||||
RUN luarocks install lua-resty-session
|
||||
RUN luarocks install lua-resty-http
|
||||
# !!!
|
||||
RUN luarocks install lua-resty-openidc
|
||||
RUN luarocks install luacrypto
|
||||
|
||||
# Copy build output to image
|
||||
COPY --from=builder /usr/src/app/build /var/www/html
|
||||
|
||||
ENTRYPOINT ["/usr/local/openresty/nginx/sbin/nginx", "-g", "daemon off;"]
|
||||
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -0,0 +1,53 @@
|
||||
# Docker compose files
|
||||
|
||||
This folder contains docker-compose files used to spin up OHIF-Viewer with
|
||||
differnt options such as locally or with any PAS you desire to
|
||||
|
||||
## Public Server
|
||||
|
||||
#### build
|
||||
|
||||
`$ docker-compose -f docker-compose-publicserver.yml build`
|
||||
|
||||
#### run
|
||||
|
||||
`$ docker-compose -f docker-compose-publicserver.yml up -d`
|
||||
|
||||
then, access the application at [http://localhost](http://localhost)
|
||||
|
||||
## Local Orthanc
|
||||
|
||||
### Build
|
||||
|
||||
`$ docker-compose -f docker-compose-orthanc.yml build`
|
||||
|
||||
### Run
|
||||
|
||||
Starts containers and leaves them running in the background.
|
||||
|
||||
`$ docker-compose -f docker-compose-orthanc.yml up -d`
|
||||
|
||||
then, access the application at [http://localhost](http://localhost)
|
||||
|
||||
**remember that you have to access orthanc application and include your studies
|
||||
there**
|
||||
|
||||
## Local Dcm4chee
|
||||
|
||||
#### build
|
||||
|
||||
`$ docker-compose -f docker-compose-dcm4chee.yml build`
|
||||
|
||||
#### run
|
||||
|
||||
`$ docker-compose -f docker-compose-dcm4chee.yml up -d`
|
||||
|
||||
then, access the application at [http://localhost](http://localhost)
|
||||
|
||||
**remember that you have to access dcm4chee application and include your studies
|
||||
there** You can use the following command to import your studies into dcm4che
|
||||
|
||||
`$ docker run -v {YOUR_STUDY_FOLDER}:/tmp --rm --network=docker_dcm4che_default dcm4che/dcm4che-tools:5.14.0 storescu -cDCM4CHEE@arc:11112 /tmp`
|
||||
|
||||
**make sure that your Docker network name is docker_dcm4chee_default or change
|
||||
it to the right one**
|
||||
@@ -0,0 +1,12 @@
|
||||
server {
|
||||
listen 80;
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
# If CLIENT_ID is specified, use the google.js configuration with the modified ID
|
||||
if [ ! -z "$CLIENT_ID" ]
|
||||
then
|
||||
echo "Google Cloud Healthcare $CLIENT_ID has been provided: "
|
||||
echo $CLIENT_ID
|
||||
echo "Updating config..."
|
||||
|
||||
# - Use SED to replace the CLIENT_ID that is currently in public/config/google.js
|
||||
sed -i -e "s/YOURCLIENTID.apps.googleusercontent.com/$CLIENT_ID/g" /usr/share/nginx/html/config/google.js
|
||||
|
||||
# - Copy public/config/google.js to overwrite public/config/default.js
|
||||
cp /usr/share/nginx/html/config/google.js /usr/share/nginx/html/config/default.js
|
||||
fi
|
||||
|
||||
echo "Starting Nginx to serve the OHIF Viewer..."
|
||||
|
||||
exec "$@"
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Notice that we're not extracting CSS or generating a service-worker. This
|
||||
* may create a slightly inconsistent experience, but should allow for faster/
|
||||
* easier development.
|
||||
*
|
||||
* We do still need to generate the "index.html" and copy over files from
|
||||
* "public/" so our `webpack-dev-server` can _serve_ them for us :+1:
|
||||
*/
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const webpack = require('webpack');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.common.js');
|
||||
// Plugins
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
// Const
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
const PUBLIC_DIR = path.join(__dirname, '../public');
|
||||
const ASSET_PATH = process.env.ASSET_PATH || '/';
|
||||
// Env Vars
|
||||
const PUBLIC_URL = process.env.PUBLIC_URL || '';
|
||||
const APP_CONFIG = process.env.APP_CONFIG || 'config/default.js';
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
|
||||
return merge(commonConfig, {
|
||||
// https://webpack.js.org/configuration/mode/#mode-development
|
||||
mode: 'development',
|
||||
output: {
|
||||
path: DIST_DIR, // push to common?
|
||||
publicPath: ASSET_PATH,
|
||||
// filename: '[name].bundle.js',
|
||||
},
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
// Copy "Public" Folder to Dist
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: PUBLIC_DIR,
|
||||
to: DIST_DIR,
|
||||
toType: 'dir',
|
||||
// Ignore our HtmlWebpackPlugin template file
|
||||
ignore: ['index.html', '.DS_Store'],
|
||||
},
|
||||
]),
|
||||
// Generate "index.html" w/ correct includes/imports
|
||||
new HtmlWebpackPlugin({
|
||||
template: `${PUBLIC_DIR}/index.html`,
|
||||
filename: 'index.html',
|
||||
templateParameters: {
|
||||
PUBLIC_URL: PUBLIC_URL,
|
||||
APP_CONFIG: APP_CONFIG,
|
||||
},
|
||||
}),
|
||||
],
|
||||
// https://webpack.js.org/configuration/dev-server/
|
||||
devServer: {
|
||||
open: true,
|
||||
port: 3000,
|
||||
historyApiFallback: {
|
||||
disableDotRule: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const webpack = require('webpack');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.common.js');
|
||||
//
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
// const
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
|
||||
return merge(commonConfig, {
|
||||
// https://webpack.js.org/configuration/mode/#mode-production
|
||||
mode: 'production',
|
||||
entry: {
|
||||
bundle: `${SRC_DIR}/index-umd.js`,
|
||||
},
|
||||
devtool: 'source-map',
|
||||
stats: {
|
||||
colors: true,
|
||||
hash: true,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
warnings: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
output: {
|
||||
path: DIST_DIR,
|
||||
library: 'ohifViewer',
|
||||
libraryTarget: 'umd',
|
||||
filename: 'index.umd.js',
|
||||
},
|
||||
/**
|
||||
* For CommonJS, we want to bundle whatever font we've landed on. This allows
|
||||
* us to reduce the number of script-tags we need to specify for simple use.
|
||||
*
|
||||
* PWA will grab these externally to reduce bundle size (think code split),
|
||||
* and cache the grab using service-worker.
|
||||
*/
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ttf|eot|woff|woff2)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// Longer build. Let's report progress
|
||||
new webpack.ProgressPlugin(),
|
||||
// Clean output.path
|
||||
new CleanWebpackPlugin(),
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const webpack = require('webpack');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.common.js');
|
||||
// Plugins
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const ExtractCssChunksPlugin = require('extract-css-chunks-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const workboxPlugin = require('workbox-webpack-plugin');
|
||||
//
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
const PUBLIC_DIR = path.join(__dirname, '../public');
|
||||
// Env Vars
|
||||
const PUBLIC_URL = process.env.PUBLIC_URL || '';
|
||||
const APP_CONFIG = process.env.APP_CONFIG || 'config/default.js';
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
|
||||
return merge(commonConfig, {
|
||||
// https://webpack.js.org/configuration/mode/#mode-production
|
||||
mode: 'production',
|
||||
stats: {
|
||||
colors: true,
|
||||
hash: true,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
warnings: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
output: {
|
||||
path: DIST_DIR,
|
||||
filename: '[name].bundle.[chunkhash].js',
|
||||
},
|
||||
// TODO:
|
||||
// Do we need to rip anything out of the more generic common.js we're
|
||||
// merging with this?
|
||||
plugins: [
|
||||
// Longer build. Let's report progress
|
||||
new webpack.ProgressPlugin(),
|
||||
// Clean output.path
|
||||
new CleanWebpackPlugin(),
|
||||
// "Public" Folder
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: PUBLIC_DIR,
|
||||
to: DIST_DIR,
|
||||
toType: 'dir',
|
||||
// Ignore our HtmlWebpackPlugin template file
|
||||
ignore: ['index.html', '.DS_Store'],
|
||||
},
|
||||
]),
|
||||
new ExtractCssChunksPlugin({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css',
|
||||
// hot: true /* only necessary if hot reloading not function*/
|
||||
}),
|
||||
/**
|
||||
* This generates our index.html file from the specified template.
|
||||
* This is the easiest way to inject custom configuration and extensions.
|
||||
*/
|
||||
new HtmlWebpackPlugin({
|
||||
template: `${PUBLIC_DIR}/index.html`,
|
||||
filename: 'index.html',
|
||||
templateParameters: {
|
||||
PUBLIC_URL: PUBLIC_URL,
|
||||
APP_CONFIG: APP_CONFIG,
|
||||
},
|
||||
// favicon: `${PUBLIC_DIR}/favicon.ico`,
|
||||
}),
|
||||
new workboxPlugin.GenerateSW({
|
||||
swDest: 'sw.js',
|
||||
clientsClaim: true,
|
||||
skipWaiting: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See
|
||||
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.0.22-alpha.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@0.0.22-alpha.7...@ohif/viewer@0.0.22-alpha.8) (2019-08-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See
|
||||
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.0.22-alpha.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@0.0.22-alpha.6...@ohif/viewer@0.0.22-alpha.7) (2019-08-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See
|
||||
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.0.22-alpha.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@0.0.22-alpha.5...@ohif/viewer@0.0.22-alpha.6) (2019-08-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See
|
||||
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.0.22-alpha.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@0.0.22-alpha.4...@ohif/viewer@0.0.22-alpha.5) (2019-08-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See
|
||||
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.0.22-alpha.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@0.0.22-alpha.3...@ohif/viewer@0.0.22-alpha.4) (2019-08-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See
|
||||
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.0.22-alpha.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@0.0.22-alpha.2...@ohif/viewer@0.0.22-alpha.3) (2019-08-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See
|
||||
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.0.22-alpha.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@0.0.22-alpha.1...@ohif/viewer@0.0.22-alpha.2) (2019-08-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
## [0.0.22-alpha.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@0.0.22-alpha.0...@ohif/viewer@0.0.22-alpha.1) (2019-08-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
## 0.0.22-alpha.0 (2019-08-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Open Health Imaging Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,233 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<div align="center">
|
||||
<h1>@ohif/viewer</h1>
|
||||
<p><strong>@ohif/viewer</strong> is a zero-footprint medical image viewer provided by the <a href="http://ohif.org/">Open Health Imaging Foundation (OHIF)</a>. It is a configurable and extensible progressive web application with out-of-the-box support for image archives which support <a href="https://www.dicomstandard.org/dicomweb/">DICOMweb</a>.</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div align="center">
|
||||
<a href="https://docs.ohif.org/"><strong>Read The Docs</strong></a> |
|
||||
<a href="https://github.com/OHIF/Viewers/tree/master/docs/latest">Edit the docs</a>
|
||||
</div>
|
||||
<div align="center">
|
||||
<a href="https://docs.ohif.org/demo">Demo</a> |
|
||||
<a href="https://ohif.canny.io/">Roadmap</a> |
|
||||
<a href="https://react.ohif.org/">Component Library</a>
|
||||
</div>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
[![NPM version][npm-version-image]][npm-url]
|
||||
[![NPM downloads][npm-downloads-image]][npm-url]
|
||||
[![Pulls][docker-pulls-img]][docker-image-url]
|
||||
[](#contributors)
|
||||
[![MIT License][license-image]][license-url]
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
> ATTENTION: If you are looking for Version 1 (the Meteor Version) of this
|
||||
> repository, it lives on
|
||||
> [the `v1.x` branch](https://github.com/OHIF/Viewers/tree/v1.x)
|
||||
|
||||
## Why?
|
||||
|
||||
Building a web based medical imaging viewer from scratch is time intensive, hard
|
||||
to get right, and expensive. Instead of re-inventing the wheel, you can use the
|
||||
OHIF Viewer as a rock solid platform to build on top of. The Viewer is a
|
||||
[React][react-url] [Progressive Web Application][pwa-url] that can be embedded
|
||||
in existing applications via it's [packaged source
|
||||
(ohif-viewer)][ohif-viewer-url] or hosted stand-alone. The Viewer exposes
|
||||
[configuration][configuration-url] and [extensions][extensions-url] to support
|
||||
workflow customization and advanced functionality at common integration points.
|
||||
|
||||
If you're interested in using the OHIF Viewer, but you're not sure it supports
|
||||
your use case [check out our docs](https://docs.ohif.org/). Still not sure, or
|
||||
you would like to propose new features? Don't hesitate to
|
||||
[create an issue](https://github.com/OHIF/Viewers/issues) or open a pull
|
||||
request.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This readme is specific to testing and developing locally. If you're more
|
||||
interested in production deployment strategies,
|
||||
[you can check out our documentation on publishing](https://docs.ohif.org/).
|
||||
|
||||
Want to play around before you dig in?
|
||||
[Check out our LIVE Demo](https://viewer.ohif.org/)
|
||||
|
||||
### Setup
|
||||
|
||||
_Requirements:_
|
||||
|
||||
- [NodeJS & NPM](https://nodejs.org/en/download/)
|
||||
- [Yarn](https://yarnpkg.com/lang/en/docs/install/)
|
||||
|
||||
_Steps:_
|
||||
|
||||
1. Fork this repository
|
||||
2. Clone your forked repository (your `origin`)
|
||||
|
||||
- `git clone git@github.com:YOUR_GITHUB_USERNAME/Viewers.git`
|
||||
|
||||
3. Add `OHIF/Viewers` as a `remote` repository (the `upstream`)
|
||||
|
||||
- `git remote add upstream git@github.com:OHIF/Viewers.git`
|
||||
|
||||
### Developing Locally
|
||||
|
||||
In your cloned repository's root folder, run:
|
||||
|
||||
```js
|
||||
// Restore dependencies
|
||||
yarn install
|
||||
|
||||
// Stands up local server to host Viewer.
|
||||
// Viewer connects to our public cloud PACS by default
|
||||
yarn start
|
||||
```
|
||||
|
||||
For more advanced local development scenarios, like using your own locally
|
||||
hosted PACS and test data,
|
||||
[check out our Essential: Getting Started](https://docs.ohif.org/essentials/getting-started.html)
|
||||
guide.
|
||||
|
||||
### E2E Tests
|
||||
|
||||
Using [Cypress](https://www.cypress.io/) to create End-to-End tests and check whether the application flow is performing correctly, ensuring that the integrated components are working as expected.
|
||||
|
||||
#### Why Cypress?
|
||||
|
||||
Cypress is a next generation front end testing tool built for the modern web.
|
||||
With Cypress is easy to set up, write, run and debug tests
|
||||
|
||||
It allow us to write different types of tests:
|
||||
|
||||
- End-to-End tests
|
||||
- Integration tests
|
||||
- Unit tets
|
||||
|
||||
All tests must be in `./cypress/integration` folder.
|
||||
|
||||
Commands to run the tests:
|
||||
|
||||
```js
|
||||
// Open Cypress Dashboard that provides insight into what happened when your tests ran
|
||||
yarn run cy
|
||||
|
||||
// Run all tests using Electron browser headless
|
||||
yarn run cy:run
|
||||
|
||||
// Run all tests in CI mode
|
||||
yarn run cy:run:ci
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
> Large portions of the Viewer's functionality are maintained in other
|
||||
> repositories. To get a better understanding of the Viewer's architecture and
|
||||
> "where things live", read
|
||||
> [our docs on the Viewer's architecture](https://docs.ohif.org/advanced/architecture.html#diagram)
|
||||
|
||||
It is notoriously difficult to setup multiple dependent repositories for
|
||||
end-to-end testing and development. That's why we recommend writing and running
|
||||
unit tests when adding and modifying features. This allows us to program in
|
||||
isolation without a complex setup, and has the added benefit of producing
|
||||
well-tested business logic.
|
||||
|
||||
1. Clone this repository
|
||||
2. Navigate to the project directory, and `yarn install`
|
||||
3. To begin making changes, `yarn run dev`
|
||||
4. To commit changes, run `yarn run cm`
|
||||
|
||||
When creating tests, place the test file "next to" the file you're testing.
|
||||
[For example](https://github.com/OHIF/Viewers/blob/master/src/utils/index.test.js):
|
||||
|
||||
```js
|
||||
// File
|
||||
index.js
|
||||
|
||||
// Test for file
|
||||
index.test.js
|
||||
```
|
||||
|
||||
As you add and modify code, `jest` will watch for uncommitted changes and run
|
||||
your tests, reporting the results to your terminal. Make a pull request with
|
||||
your changes to `master`, and a core team member will review your work. If you
|
||||
have any questions, please don't hesitate to reach out via a GitHub issue.
|
||||
|
||||
## Roadmap
|
||||
|
||||
If you want to know what's planned for the very near future,
|
||||
[check out our roadmap](https://ohif.canny.io/). The best way to influence when
|
||||
and what is worked on is to contribute to the conversation by creating GitHub
|
||||
issues, and contributing code through pull requests. OHIF's high level
|
||||
priorities for the near future are:
|
||||
|
||||
- Feature parity with version 1
|
||||
- Extension and configuration improvements with key integration partners
|
||||
- Continued Developer Experience Improvements
|
||||
- Segmentation Tools, and improved VTK.js support
|
||||
|
||||
More granular information will make it's way to the backlog as these items
|
||||
become scoped for development by core maintainers.
|
||||
|
||||
> Don't hesitate to ask questions, propose features, or create pull requests.
|
||||
> We're here, we're listening, and we're ready to build the best open source
|
||||
> medical imaging viewer on the web.
|
||||
|
||||
#### Roadmap Generously Powered by Canny.io
|
||||
|
||||
<a href="https://ohif.canny.io/">
|
||||
<img height="30" src="docs/latest/assets/img/canny-full.png" />
|
||||
</a>
|
||||
|
||||
## Contributors
|
||||
|
||||
Thanks goes to these wonderful people
|
||||
([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore -->
|
||||
<table><tr><td align="center"><a href="https://github.com/swederik"><img src="https://avatars3.githubusercontent.com/u/607793?v=4" width="100px;" alt="Erik Ziegler"/><br /><sub><b>Erik Ziegler</b></sub></a><br /><a href="https://github.com/OHIF/Viewers/commits?author=swederik" title="Code">💻</a> <a href="#infra-swederik" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td><td align="center"><a href="https://github.com/evren217"><img src="https://avatars1.githubusercontent.com/u/4920551?v=4" width="100px;" alt="Evren Ozkan"/><br /><sub><b>Evren Ozkan</b></sub></a><br /><a href="https://github.com/OHIF/Viewers/commits?author=evren217" title="Code">💻</a></td><td align="center"><a href="https://github.com/galelis"><img src="https://avatars3.githubusercontent.com/u/2378326?v=4" width="100px;" alt="Gustavo André Lelis"/><br /><sub><b>Gustavo André Lelis</b></sub></a><br /><a href="https://github.com/OHIF/Viewers/commits?author=galelis" title="Code">💻</a></td><td align="center"><a href="http://dannyrb.com/"><img src="https://avatars1.githubusercontent.com/u/5797588?v=4" width="100px;" alt="Danny Brown"/><br /><sub><b>Danny Brown</b></sub></a><br /><a href="https://github.com/OHIF/Viewers/commits?author=dannyrb" title="Code">💻</a> <a href="#infra-dannyrb" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td><td align="center"><a href="https://github.com/all-contributors/all-contributors-bot"><img src="https://avatars3.githubusercontent.com/u/46843839?v=4" width="100px;" alt="allcontributors[bot]"/><br /><sub><b>allcontributors[bot]</b></sub></a><br /><a href="https://github.com/OHIF/Viewers/commits?author=allcontributors" title="Documentation">📖</a></td><td align="center"><a href="https://www.linkedin.com/in/siliconvalleynextgeneration/"><img src="https://avatars0.githubusercontent.com/u/1230575?v=4" width="100px;" alt="Esref Durna"/><br /><sub><b>Esref Durna</b></sub></a><br /><a href="#question-EsrefDurna" title="Answering Questions">💬</a></td><td align="center"><a href="https://github.com/diego0020"><img src="https://avatars3.githubusercontent.com/u/7297450?v=4" width="100px;" alt="diego0020"/><br /><sub><b>diego0020</b></sub></a><br /><a href="https://github.com/OHIF/Viewers/commits?author=diego0020" title="Code">💻</a></td></tr><tr><td align="center"><a href="https://github.com/dlwire"><img src="https://avatars3.githubusercontent.com/u/1167291?v=4" width="100px;" alt="David Wire"/><br /><sub><b>David Wire</b></sub></a><br /><a href="https://github.com/OHIF/Viewers/commits?author=dlwire" title="Code">💻</a></td><td align="center"><a href="https://github.com/jfmedeiros1820"><img src="https://avatars1.githubusercontent.com/u/2211708?v=4" width="100px;" alt="João Felipe de Medeiros Moreira"/><br /><sub><b>João Felipe de Medeiros Moreira</b></sub></a><br /><a href="https://github.com/OHIF/Viewers/commits?author=jfmedeiros1820" title="Tests">⚠️</a></td></tr></table>
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the
|
||||
[all-contributors](https://github.com/all-contributors/all-contributors)
|
||||
specification. Contributions of any kind welcome!
|
||||
|
||||
## License
|
||||
|
||||
MIT © [OHIF](https://github.com/OHIF)
|
||||
|
||||
<!--
|
||||
Links:
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[npm-url]: https://npmjs.org/package/ohif-viewer
|
||||
[npm-downloads-image]: https://img.shields.io/npm/dm/ohif-viewer.svg?style=flat-square
|
||||
[npm-version-image]: https://img.shields.io/npm/v/ohif-viewer.svg?style=flat-square
|
||||
[docker-pulls-img]: https://img.shields.io/docker/pulls/ohif/viewer.svg?style=flat-square
|
||||
[docker-image-url]: https://hub.docker.com/r/ohif/viewer
|
||||
[all-contributors-image]: https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square
|
||||
[contributing-url]: https://github.com/OHIF/Viewers/blob/react/CONTRIBUTING.md
|
||||
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
|
||||
[license-url]: LICENSE
|
||||
<!-- DOCS -->
|
||||
[react-url]: https://reactjs.org/
|
||||
[pwa-url]: https://developers.google.com/web/progressive-web-apps/
|
||||
[ohif-viewer-url]: https://www.npmjs.com/package/ohif-viewer
|
||||
[configuration-url]: https://docs.ohif.org/essentials/configuration.html
|
||||
[extensions-url]: https://docs.ohif.org/advanced/extensions.html
|
||||
<!-- Misc. -->
|
||||
[react-viewer]: https://github.com/OHIF/Viewers/tree/react
|
||||
<!-- Issue Boilerplate -->
|
||||
[bugs]: https://github.com/OHIF/Viewers/labels/bug
|
||||
[requests-feature]: https://github.com/OHIF/Viewers/labels/enhancement
|
||||
[good-first-issue]: https://github.com/OHIF/Viewers/labels/good%20first%20issue
|
||||
[google-group]: https://groups.google.com/forum/#!forum/cornerstone-platform
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
module.exports = require("../../babel.config.js");
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"video": true,
|
||||
"projectId": "4oe38f"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
describe('OHIFStandaloneViewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('loads route with at least 2 rows', () => {
|
||||
cy.get('#studyListData tr')
|
||||
.its('length')
|
||||
.should('be.gt', 2);
|
||||
});
|
||||
|
||||
it('first 2 rows has values', () => {
|
||||
cy.get('#studyListData > :nth-child(1) > .patientId').should('be.visible');
|
||||
cy.get('#studyListData > :nth-child(2) > .patientId').should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
describe('ViewerRouting', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
cy.contains('Study List');
|
||||
cy.get('#studyListData > :nth-child(1) > .patientId').click();
|
||||
});
|
||||
|
||||
it('thumbnails list has more than 2 items', () => {
|
||||
cy.get('.scrollable-study-thumbnails div.ThumbnailEntryContainer')
|
||||
.its('length')
|
||||
.should('be.gte', 2);
|
||||
});
|
||||
|
||||
it('loads route with at least 2 thumbnails', () => {
|
||||
cy.get(
|
||||
':nth-child(1) > .ThumbnailEntry > .p-x-1 > .ImageThumbnail > .image-thumbnail-canvas > canvas'
|
||||
).should('be.visible');
|
||||
cy.get(
|
||||
':nth-child(2) > .ThumbnailEntry > .p-x-1 > .ImageThumbnail > .image-thumbnail-canvas > canvas'
|
||||
).should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
on('before:browser:launch', (browser = {}, args) => {
|
||||
if (browser.name === 'chrome') {
|
||||
// `args` is an araay of all the arguments
|
||||
// that will be passed to Chrome when it launchers
|
||||
args.push('--start-fullscreen');
|
||||
|
||||
// whatever you return here becomes the new args
|
||||
return args;
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<meta name="description" content="Open Health Imaging Foundation DICOM Viewer" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1,maximum-scale=1,user-scalable=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta http-equiv="cleartype" content="on" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
|
||||
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous" />
|
||||
|
||||
<title>OHIF Standalone Viewer</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
|
||||
<!-- Load React. -->
|
||||
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
|
||||
|
||||
<script src="/index.umd.js" crossorigin></script>
|
||||
<script>
|
||||
window.config = {
|
||||
// default: '/'
|
||||
routerBasename: '/',
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
window.OHIFStandaloneViewer.installViewer(window.config);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,14 @@
|
||||
const base = require("../../jest.config.base.js");
|
||||
const pkg = require("./package");
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
name: pkg.name,
|
||||
displayName: pkg.name,
|
||||
setupFilesAfterEnv: ["<rootDir>/src/__tests__/globalSetup.js"]
|
||||
// rootDir: "../.."
|
||||
// testMatch: [
|
||||
// //`<rootDir>/platform/${pack.name}/**/*.spec.js`
|
||||
// "<rootDir>/platform/viewer/**/*.test.js"
|
||||
// ]
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
const babelJest = require("babel-jest");
|
||||
|
||||
module.exports = babelJest.createTransformer({
|
||||
rootMode: "upward"
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "0.0.22-alpha.8",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
"repository": "OHIF/Viewers",
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "src/index.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"proxy": "http://localhost:8042",
|
||||
"scripts": {
|
||||
"build:package": "webpack --config .webpack/commonjs.prod.js",
|
||||
"build:viewer": "webpack --config .webpack/pwa.prod.js",
|
||||
"build:viewer:ci": "cross-env PUBLIC_URL=/pwa APP_CONFIG=config/netlify.js webpack --config .webpack/pwa.prod.js",
|
||||
"build:viewer:package": "yarn run build:package",
|
||||
"dev": "webpack-dev-server --config .webpack/all.dev.js -w",
|
||||
"dev:orthanc": "cross-env APP_CONFIG=config/docker_nginx-orthanc.js react-scripts start",
|
||||
"dev:viewer": "yarn run dev",
|
||||
"start": "yarn run dev",
|
||||
"orthanc:up": "docker-compose -f docker/Nginx-Orthanc/docker-compose.yml up",
|
||||
"test:e2e": "cypress open",
|
||||
"test:unit": "jest --watchAll",
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage"
|
||||
},
|
||||
"resolutions": {
|
||||
"browserslist": "4.6.2",
|
||||
"caniuse-lite": "1.0.30000974"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^0.11.1-alpha.7",
|
||||
"@ohif/extension-cornerstone": "^0.0.39-alpha.7",
|
||||
"@ohif/extension-dicom-html": "^0.0.4-alpha.7",
|
||||
"@ohif/extension-dicom-microscopy": "^0.0.9-alpha.7",
|
||||
"@ohif/extension-dicom-pdf": "^0.0.8-alpha.7",
|
||||
"@ohif/extension-vtk": "^0.1.4-alpha.7",
|
||||
"@ohif/i18n": "^0.2.3-alpha.7",
|
||||
"@ohif/ui": "^0.2.18-alpha.7",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.1.4",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^3.15.1",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.4.7",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"dicomweb-client": "^0.4.4",
|
||||
"hammerjs": "^2.0.8",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"moment": "^2.24.0",
|
||||
"oidc-client": "1.7.x",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-dropzone": "^10.1.7",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-redux": "^7.1.0",
|
||||
"react-resize-detector": "^4.2.0",
|
||||
"react-router": "^5.0.1",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-transition-group": "^4.1.1",
|
||||
"redux": "^4.0.1",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-oidc": "3.1.x",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "^3.3.1",
|
||||
"gh-pages": "2.0.1",
|
||||
"identity-obj-proxy": "3.0.x",
|
||||
"lodash": "4.17.11",
|
||||
"lodash.clonedeep": "4.5.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require("../../postcss.config.js");
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="228" height="80"><defs><style>@font-face{font-family:"Roboto-Bold";src:url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAmMAA4AAAAADfAAAiMSAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAE0AAABgdXsSBmNtYXAAAAGUAAAAcAAAAYoC6ByTY3Z0IAAAAgQAAABIAAAASCt+BLVmcGdtAAACTAAAAToAAAG8X/Iaq2dhc3AAAAOIAAAADAAAAAwACAATZ2x5ZgAAA5QAAALaAAAEcPpD8NdoZWFkAAAGcAAAADYAAAA2/oPRxGhoZWEAAAaoAAAAHwAAACQM0QOkaG10eAAABsgAAAAsAAAALC+eBFRsb2NhAAAG9AAAABgAAAAYBNIGFG1heHAAAAcMAAAAHgAAACACHAHcbmFtZQAABywAAAF0AAACi5COTQJwb3N0AAAIoAAAABQAAAAg/20Ab3ByZXAAAAi0AAAA2AAAASkqdjYweJxjYGFZw7SHgZWBgXUWqzEDA6M8hGa+yJDGxMDAAMIQ0MDAoC7AgADu/v7uDApAGMrG8I+BIY19FhOQwzgfJMdixboBLMcMABbLCv8AAAB4nGNgYGBmgGAZBkYGEGgB8hjBfBaGDCAtxiAAFGFjUGBwZHBhcGVwY/Bk8GHwZwhgCP3/HyiPVfz/0/8n/h/7v///7v87/28H441Q87EARjZcMgjASkgBJwMDFwvQCiawdzjYQWJEmDtYAABl0hyEACoA6QCkAP4ATgBgATEArADFANQAfAAtAAAAFP5gABQCmwAgAyEACwQ6ABQEjQAQBbAAFAYYABUBpgARBsAADgbfAAIAAAAAeJxdkMtKAzEYhRNnrK0XcCkEIWmoQklw72oWmYJ0MzpdJC68YAu2K19AmI0wZOGznNnN7Po4fQnRdKpF3eTknJ98nD8gaoxeZitK311NP99gTqtedH+nQRXn6dyAPmjsKNCh0IgUHyEajG6sdNxzfzX1fMSfH6eIB62Gwcy7Cw6S23k4J1YgcWx7nTl3qRGvMXGL8S4AFt+ARQsI7z80dtWYIzrL7LVFYRgS45gQPMUys1gaJpzT6Gw7Bn2dn2za7il0hhrdDSG3SBiI837jpEDhPfNhgx+//OtrSv4Hye8g/EBa0yJrJ4UUbB1IIUVo6IxGT41zm4aKIlTcV+inGgcKMsihqs5pyX1um4TE5KnuknJiG9KPVi+OQQY4L+tjss3WWx4pJGXNya2tJDGsITJaGae/AFMsiLUAAAABAAIACAAC//8AD3icfZNdSBRRFMfPuXNn1/0wZtddCRY/1k9CcdVW7YOskNCCKBJKIXvqIUioh0CL7MEi6iGih1AyCGIjCNSZxdCiyAVfSoglNg1EezB7KDXMKIyda2dmXXbXwLkM3DPMPf///Z1zAKEXgNfLKtigFVQ41KbygE/j2xvbzYBRwJyN7WEb4y73btUWCD+yYcdIp+2qjXWED9NeA1TtFW3+T75f7SpXNEsqrK5Bv4uWl15pWR8XMWlE72YHWKs+KKur4hYg3AfACOlLkAcpyaQ+UACkX11TSjkwImJYCfR3wncF7WTYsYXvdHNGDsMJDrGe+CmsXFmh4/Qkcp2gwAFtiVwWOm5J5jITGwwc3GIwcARG9jiOONh/N2dpoUXRsjaD8KahYKF4tfReP0MwusSVFYPGE9PLpfVFHiUvCuwLE3IShBSJpKUsCrI2+9OyMwRdHvAXQTnx9+TurM1x8aj4KPR1qgFW3r6sc1mNH/whvuG2ReldfP/d62iX3gIzWcyaLDxwLOzw5ppXVtTsaMKHRGpSUhopQAONU0LjP2cgg4mkaDmZnuj2tfno9XB/qb+2vi5YVk7lEDG2K7R2U8wIXayhs6vn2gWxxFBfl9Xu2TtTf1Yvnjt/g5G3k+uLcpU8QWzyoSXMCwoNTa6ong1vTrLj3IxFdSqaJ7NQvgxTHsaLi0pYeV3QXVLI3d7SYFlxkcXqNbDVN8hV/WJh9Ln48hAH0Ied6BsQ0VB/XwhDD/oeIxudFq/UIWz60DKNzcPD4sUULix/n2/+/HtpHsizFUA+TTytYIfmLTo11XfmuHGWGLd0nmG7+dVoJsmP5pKkMf3nWRFh+ZjL/CKiz2HnKFaKmKz+Pc4KmB9go6YzZk29sBdSlds8ZSoqmjMNFSiZPdVA1ePuPIOYVXIZdKiCxb04Po6BZ09F9E3fPfUlG9NoQgdFZLJpUkwMIX8dD36dOjq3QFb+AVIoYAwAAAABAAAAAiMSDvY9Ol8PPPUAGQgAAAAAAMTwES4AAAAA1QFS1gAH/+wFXAXEAAEACQACAAAAAAAAeJxjYGRgYM/5x8PAwNrGwM7AxhrDABRBAdwAQ2YChAADjABkBGIAggJVAJUEVQCCBIAAggH+AAAFRAB0BSkAggWGAFYFYgAHBTMAggAAAAAAQABcAIIAyADIAQgBVgGwAfICOHicY2BkYGDgZpBnYGIAAUYwyccA5DLuATEBCQoA/AAAeJyFkstKw0AUhv+xFyxKEQS3zsqFkEkvlGK7amsphVIhlW4EoZehCcROTUJLn8Q38o3c+ycZiroxw8x85z9zbhAAV/iEQP653DkLlGnlfIYiWpYLuMCD5SL50XIJ13iyXKa+tFzFPd4YJYoVWq/4sCxwiS/LZzgXwnIBN6JiuUi+tVzCnWhZLlN/sVzFs3gfmN0xCjZ+Ihu1el2OjNmEWo63KyV7YSi91BVLT8c62uu18szSJKZvwnVO8g/OdRQHZisbqt5sd9Oc7dzrpF4/SXYd1z0cDmqxW6x8rUy0ccNgpbexjt3JeDCczoZOQ9UwgMEOR0QIsIGPBBIN1FDnkhjRa6iH0LTG2GIFRepRCXl7p6g4szRvzVx7nmu+9Bi95E64+9wh1Z+a/EedZ9liVjGsnXam2FcTbXRPfbZ/xTqn2LSrhLN1+JO4OGRLYUFlwSl8ZlZ8GbF/l+8Dapo14mwGFxNOO8AQU8x4Olnl2jdOS28leJxjYGYAg/9ZDCkMmIAbACoqAdx4nD3NTw7BQByG4Rn/qVDSyJDQhkhEm5RNF900EVYW7EnYWhGUlXADcQM30MzGKcRRHMCC34fYPHk7nS8TpM2AmUxyJWSyK2839EKGJcxrqOtaRh4V/p9FTtuxW0NFN9O+U0bF6rrIKah4x2lWPouEa1dFFpV0rF+lRn23WUQpz9t52aNiMq3E6XWjt5uJrmxl+EoGKjFU+VoewELlvvQ0OpuAPbA1+jEAc3AEF/ACuvZd+Fj4WPgyK2g2F/hEPYAu6PIEHMEdvICHe3YJC2AbhEe8AeAyUcg=) format("woff"); font-weight:bold;font-style:normal;}</style></defs><path d="M6 0h216a6 6 0 0 1 6 6v68a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6a6 6 0 0 1 6-6z" fill="#52ABD3"/><text font-family="Roboto-Bold, Roboto" font-size="14" font-weight="bold" fill="#fff"><tspan x="72.148" y="45">FILE UPLOAD</tspan></text><path d="M58.285 79.648H40.642L36.616 58.63l-24.92 10.66 11.258 5.986-2.325 4.373H6a6 6 0 0 1-6-6V55.073L22.088 13.53a1 1 0 0 1 1.352-.413l36.2 19.248L68.3 60.813 58.285 79.648zm-27.108 0h-.923l.203-.382.72.382zM54.232 34.02l-9.86 18.542 18.542 9.86-8.683-28.4z" fill="#4591B3"/></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="228" height="80"><defs><style>@font-face{font-family:"Roboto-Bold";src:url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAngAA4AAAAADmgAAiMSAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAE0AAABgdXsSVWNtYXAAAAGUAAAAdAAAAYIIaRq4Y3Z0IAAAAggAAABIAAAASCt+BLVmcGdtAAACUAAAAToAAAG8X/Iaq2dhc3AAAAOMAAAADAAAAAwACAATZ2x5ZgAAA5gAAAMqAAAE8AOZz5doZWFkAAAGxAAAADYAAAA2/oPRxGhoZWEAAAb8AAAAHwAAACQM0QOkaG10eAAABxwAAAAsAAAALDJkBEFsb2NhAAAHSAAAABgAAAAYBioHqG1heHAAAAdgAAAAHgAAACACHAHcbmFtZQAAB4AAAAF0AAACi5COTQJwb3N0AAAI9AAAABQAAAAg/20Ab3ByZXAAAAkIAAAA2AAAASkqdjYweJxjYGH5zbSHgZWBgXUWqzEDA6M8hGa+yJDGxMDAAMIQ0MDAoC7AgADu/v7uDApAGMrG8I+BIY19FhOQwzgfJMdixboBLMcMADN+C04AAAB4nGNgYGBmgGAZBkYGEKgB8hjBfBaGBCAtwiAAFGFhUGBwZHBlcGPwYfBnCGAIYgj9/x8oCxJ1QRX9//z/yf8H/u/+v/3/5v87/2/5vxlqMgZgZMMujgzYCSngYmBgYYVYAPQGEycD0FAOwuYOFgAANFkaMQAqAOkApAD+AE4AYAExAKwAxQDUAHwALQAAABT+YAAUApsAIAMhAAsEOgAUBI0AEAWwABQGGAAVAaYAEQbAAA4G3wACAAAAAHicXZDLSgMxGIUTZ6ytF3ApBCFpqEJJcO9qFpmCdDM6XSQuvGALtitfQJiNMGThs5zZzez6OH0J0XSqRd3k5JyffJw/IGqMXmYrSt9dTT/fYE6rXnR/p0EV5+ncgD5o7CjQodCIFB8hGoxurHTcc3819XzEnx+niAethsHMuwsOktt5OCdWIHFse505d6kRrzFxi/EuABbfgEULCO8/NHbVmCM6y+y1RWEYEuOYEDzFMrNYGiac0+hsOwZ9nZ9s2u4pdIYa3Q0ht0gYiPN+46RA4T3zYYMfv/zra0r+B8nvIPxAWtMiayeFFGwdSCFFaOiMRk+Nc5uGiiJU3FfopxoHCjLIoarOacl9bpuExOSp7pJyYhvSj1YvjkEGOC/rY7LN1lseKSRlzcmtrSQxrCEyWhmnvwBTLIi1AAAAAQACAAgAAv//AA94nIWUa0gUURTHz7lzZx/uKrO2sxlL5vhIQnHV7Cm9iNCCKApKIftSUZBQ0UONCtKI+hDRhzB6EMRGEKh3BkOLHgpBlBCLmAaRfTD7kK+sKIyda3dmM3cNbIaBPcve8/+f3/nPAkI9AF0qM3DDdmCwoYLRUFCnaasr7YKIgnhXVxpuQn2pK5g7ZNx2Y1VrtfuUm1QZG8VnHZAl5VVob4PfKxlVdMd0WViEmk/cqnikUbOD90itZi1ZS7abTTL7xi8AgR2Tw3KB/BwUSIdygy7IsGSowvyRmB2vcOCdsmN7E3aYV9H9ibLBBFk/oVmZ2SR3SUlqdgZNVXNKFmZlOpyqP7C4eOkyueAaH2x7wD/exBsYxGoM3uCR8LXGMIavN95B0tbHH7NmXN9d3odlLS38YS8Ojg4NlH34MTIAEGOWJ5jJsGgWZvFgCotybArYTE5Hd2L++Lg4Li5i93onCg+oUBrrhqIBTnUDUYA1Miq6N25kUPTk+P7LfFoxTZ1vTe6UfNaUS8TM9djRgaH793jkWeMV9oi06yCzJt7Ztb6LP29G+iRa8ql3c/+g5SU21zbbS0XMiUOIO2bCNzzUYS3JE2pd6dnkIf8kgMSVDkV3zQyEGhcJEo4WSq/N3SIUNfzkuJWKu3+5fBFeUmAu7DHktHmWpKywQGQW5HaRLIpky6dCiXVICcUbNDxAYxHTAwnGsqykZlDV73CiqqEgKADmIbk1xnvIfCSYdrYBS/lTDl+PnDpeN05Uc0hmn7qvvig1m1Ik19F9+08Ihscmh2lE+FZglSGEhBRM73HKr0sUrplcE/eJPj9omZArXFmhneOjEf6Gm5PiHcL8i3UmlVl03Rj/jCnD0qvomssNmCS9/MPtvb1DP2wxPGrAXpXCkv9wk4SaNCVtJ81C5ZXQ+p03ARWTFH1OoieBqDgdVT/VcjQbUK6ItOCzPDxxnr/jJp9Ab83pM4f4CEFzUma17y/1/vx2+MDBc0R4cwLIu4Q3JyRB2f/WaGfP/uuJrdGduMYk+1srUJKG9i1J7ebXvbyTpGOAaLzT7MfqNsznPTL7tZUsIBrAbz0Oh+oAAAABAAAAAiMS6Qx7uF8PPPUAGQgAAAAAAMTwES4AAAAA1QFS1gAH/+wFXAXEAAEACQACAAAAAAAAeJxjYGRgYM/5x8PAwNrGwM7AxhrDABRBAdwAQ2YChAADjABkBGIAggWGAFYEVQCCBTMAggSAAIIFGwCCAf4AAAVEAHQFKQCCBWIABwAAAAAAQACaAMABBgFMAagBqAHoAjYCeHicY2BkYGDgZpBnYGIAAUYwyccA5DLuATEBCQoA/AAAeJyFkstKw0AUhv+xFyxKEQS3zsqFkEkvlGK7amsphVIhlW4EoZehCcROTUJLn8Q38o3c+ycZiroxw8x85z9zbhAAV/iEQP653DkLlGnlfIYiWpYLuMCD5SL50XIJ13iyXKa+tFzFPd4YJYoVWq/4sCxwiS/LZzgXwnIBN6JiuUi+tVzCnWhZLlN/sVzFs3gfmN0xCjZ+Ihu1el2OjNmEWo63KyV7YSi91BVLT8c62uu18szSJKZvwnVO8g/OdRQHZisbqt5sd9Oc7dzrpF4/SXYd1z0cDmqxW6x8rUy0ccNgpbexjt3JeDCczoZOQ9UwgMEOR0QIsIGPBBIN1FDnkhjRa6iH0LTG2GIFRepRCXl7p6g4szRvzVx7nmu+9Bi95E64+9wh1Z+a/EedZ9liVjGsnXam2FcTbXRPfbZ/xTqn2LSrhLN1+JO4OGRLYUFlwSl8ZlZ8GbF/l+8Dapo14mwGFxNOO8AQU8x4Olnl2jdOS28leJxjYGYAg/9ZDCkMmIAbACoqAdx4nD3NTw7BQByG4Rn/qVDSyJDQhkhEm5RNF900EVYW7EnYWhGUlXADcQM30MzGKcRRHMCC34fYPHk7nS8TpM2AmUxyJWSyK2839EKGJcxrqOtaRh4V/p9FTtuxW0NFN9O+U0bF6rrIKah4x2lWPouEa1dFFpV0rF+lRn23WUQpz9t52aNiMq3E6XWjt5uJrmxl+EoGKjFU+VoewELlvvQ0OpuAPbA1+jEAc3AEF/ACuvZd+Fj4WPgyK2g2F/hEPYAu6PIEHMEdvICHe3YJC2AbhEe8AeAyUcg=) format("woff"); font-weight:bold;font-style:normal;}</style></defs><path d="M6 0h216a6 6 0 0 1 6 6v68a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6a6 6 0 0 1 6-6z" fill="#52ABD3"/><text font-family="Roboto-Bold, Roboto" font-size="14" font-weight="bold" fill="#fff"><tspan x="60" y="45">FOLDER UPLOAD</tspan></text><path d="M51.706 79.74H39.752L38.32 56.483l-25.104 6.482 10.012 7.274-6.904 9.502H6a6 6 0 0 1-6-6V13.585c2.513-1.005 5.48-.74 7.84.975l19.902 14.46L29.6 40.413l26.536 19.28c3.65 2.65 4.485 7.778 1.858 11.393l-6.288 8.655zm-15.4 0H26.52l3.38-4.653 6.404 4.653z" fill="#4591B3"/></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,73 @@
|
||||
window.config = {
|
||||
// default: '/'
|
||||
routerBasename: '/',
|
||||
extensions: [],
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
requestOptions: {
|
||||
requestFromBrowser: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Extensions should be able to suggest default values for these?
|
||||
// Or we can require that these be explicitly set
|
||||
hotkeys: [
|
||||
// ~ Global
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Image Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Image Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
// ~ Cornerstone Extension
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
// clearAnnotations
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// firstImage
|
||||
// lastImage
|
||||
{
|
||||
commandName: 'nextViewportDisplaySet',
|
||||
label: 'Previous Series',
|
||||
keys: ['pagedown'],
|
||||
},
|
||||
{
|
||||
commandName: 'previousViewportDisplaySet',
|
||||
label: 'Next Series',
|
||||
keys: ['pageup'],
|
||||
},
|
||||
// ~ Cornerstone Tools
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
requestOptions: {
|
||||
requestFromBrowser: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
i18n: {
|
||||
LOCIZE_PROJECTID: 'a8da3f9a-e467-4dd6-af33-474d582a0294',
|
||||
LOCIZE_API_KEY: null, // Developers can use this to do in-context editing. DO NOT COMMIT THIS KEY!
|
||||
USE_LOCIZE: true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'Orthanc',
|
||||
wadoUriRoot: '/wado',
|
||||
qidoRoot: '/dicom-web',
|
||||
wadoRoot: '/dicom-web',
|
||||
qidoSupportsIncludeField: false,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
requestOptions: {
|
||||
requestFromBrowser: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'Orthanc',
|
||||
wadoUriRoot: 'http://127.0.0.1/pacs/wado',
|
||||
qidoRoot: 'http://127.0.0.1/pacs/dicom-web',
|
||||
wadoRoot: 'http://127.0.0.1/pacs/dicom-web',
|
||||
qidoSupportsIncludeField: false,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
// REQUIRED TAG:
|
||||
// https://github.com/OHIF/ohif-core/blob/59e1e04b92be24aee5d4402445cb3dcedb746995/src/studies/retrieveStudyMetadata.js#L54
|
||||
// TODO: Remove tag after https://github.com/OHIF/ohif-core/pull/19 is merged and we bump version
|
||||
requestOptions: {
|
||||
// undefined to use JWT + Bearer auth
|
||||
// auth: 'orthanc:orthanc',
|
||||
requestFromBrowser: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
oidc: [
|
||||
{
|
||||
// ~ REQUIRED
|
||||
// Authorization Server URL
|
||||
authority: 'http://127.0.0.1/auth/realms/ohif',
|
||||
client_id: 'ohif-viewer',
|
||||
redirect_uri: 'http://127.0.0.1/callback', // `OHIFStandaloneViewer.js`
|
||||
// "Authorization Code Flow"
|
||||
// Resource: https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660
|
||||
response_type: 'code',
|
||||
scope: 'openid', // email profile openid
|
||||
// ~ OPTIONAL
|
||||
post_logout_redirect_uri: '/logout-redirect.html',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'Orthanc',
|
||||
wadoUriRoot: 'http://127.0.0.1/pacs/wado',
|
||||
qidoRoot: 'http://127.0.0.1/pacs/dicom-web',
|
||||
wadoRoot: 'http://127.0.0.1/pacs/dicom-web',
|
||||
qidoSupportsIncludeField: false,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
// REQUIRED TAG:
|
||||
// https://github.com/OHIF/ohif-core/blob/59e1e04b92be24aee5d4402445cb3dcedb746995/src/studies/retrieveStudyMetadata.js#L54
|
||||
// TODO: Remove tag after https://github.com/OHIF/ohif-core/pull/19 is merged and we bump version
|
||||
requestOptions: {
|
||||
// undefined to use JWT + Bearer auth
|
||||
// auth: 'orthanc:orthanc',
|
||||
requestFromBrowser: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
enableGoogleCloudAdapter: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
dicomWeb: [],
|
||||
},
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
oidc: [
|
||||
{
|
||||
// ~ REQUIRED
|
||||
// Authorization Server URL
|
||||
authority: 'https://accounts.google.com',
|
||||
client_id: 'YOURCLIENTID.apps.googleusercontent.com',
|
||||
redirect_uri: '/callback', // `OHIFStandaloneViewer.js`
|
||||
response_type: 'id_token token',
|
||||
scope: 'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
|
||||
// ~ OPTIONAL
|
||||
post_logout_redirect_uri: '/logout-redirect.html',
|
||||
revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
|
||||
automaticSilentRenew: true,
|
||||
revokeAccessTokenOnSignout: true,
|
||||
},
|
||||
],
|
||||
studyListFunctionsEnabled: true,
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
window.config = {
|
||||
// default: '/'
|
||||
routerBasename: '/',
|
||||
// default: ''
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot: 'http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot: 'http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot: 'http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
requestOptions: {
|
||||
requestFromBrowser: true,
|
||||
auth: 'admin:admin',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
studyListFunctionsEnabled: true,
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
window.config = {
|
||||
routerBasename: '/pwa',
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
requestOptions: {
|
||||
requestFromBrowser: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
hotkeys: [
|
||||
// ~ Global
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Image Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Image Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
// ~ Cornerstone Extension
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
// clearAnnotations
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// firstImage
|
||||
// lastImage
|
||||
{
|
||||
commandName: 'nextViewportDisplaySet',
|
||||
label: 'Previous Series',
|
||||
keys: ['pagedown'],
|
||||
},
|
||||
{
|
||||
commandName: 'previousViewportDisplaySet',
|
||||
label: 'Next Series',
|
||||
keys: ['pageup'],
|
||||
},
|
||||
// ~ Cornerstone Tools
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
],
|
||||
i18n: {
|
||||
LOCIZE_PROJECTID: 'a8da3f9a-e467-4dd6-af33-474d582a0294',
|
||||
LOCIZE_API_KEY: null, // Developers can use this to do in-context editing. DO NOT COMMIT THIS KEY!
|
||||
USE_LOCIZE: true,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
requestOptions: {
|
||||
requestFromBrowser: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
i18n: {
|
||||
LOCIZE_PROJECTID: 'a8da3f9a-e467-4dd6-af33-474d582a0294',
|
||||
LOCIZE_API_KEY: null, // Developers can use this to do in-context editing. DO NOT COMMIT THIS KEY!
|
||||
USE_LOCIZE: false
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* https://github.com/paulmillr/es6-shim
|
||||
* @license es6-shim Copyright 2013-2016 by Paul Miller (http://paulmillr.com)
|
||||
* and contributors, MIT License
|
||||
* es6-shim: v0.35.4
|
||||
* see https://github.com/paulmillr/es6-shim/blob/0.35.3/LICENSE
|
||||
* Details and documentation:
|
||||
* https://github.com/paulmillr/es6-shim/
|
||||
*/
|
||||
(function(e,t){if(typeof define==="function"&&define.amd){define(t)}else if(typeof exports==="object"){module.exports=t()}else{e.returnExports=t()}})(this,function(){"use strict";var e=Function.call.bind(Function.apply);var t=Function.call.bind(Function.call);var r=Array.isArray;var n=Object.keys;var o=function notThunker(t){return function notThunk(){return!e(t,this,arguments)}};var i=function(e){try{e();return false}catch(t){return true}};var a=function valueOrFalseIfThrows(e){try{return e()}catch(t){return false}};var u=o(i);var f=function(){return!i(function(){return Object.defineProperty({},"x",{get:function(){}})})};var s=!!Object.defineProperty&&f();var c=function foo(){}.name==="foo";var l=Function.call.bind(Array.prototype.forEach);var p=Function.call.bind(Array.prototype.reduce);var v=Function.call.bind(Array.prototype.filter);var y=Function.call.bind(Array.prototype.some);var h=function(e,t,r,n){if(!n&&t in e){return}if(s){Object.defineProperty(e,t,{configurable:true,enumerable:false,writable:true,value:r})}else{e[t]=r}};var b=function(e,t,r){l(n(t),function(n){var o=t[n];h(e,n,o,!!r)})};var g=Function.call.bind(Object.prototype.toString);var d=typeof/abc/==="function"?function IsCallableSlow(e){return typeof e==="function"&&g(e)==="[object Function]"}:function IsCallableFast(e){return typeof e==="function"};var m={getter:function(e,t,r){if(!s){throw new TypeError("getters require true ES5 support")}Object.defineProperty(e,t,{configurable:true,enumerable:false,get:r})},proxy:function(e,t,r){if(!s){throw new TypeError("getters require true ES5 support")}var n=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(r,t,{configurable:n.configurable,enumerable:n.enumerable,get:function getKey(){return e[t]},set:function setKey(r){e[t]=r}})},redefine:function(e,t,r){if(s){var n=Object.getOwnPropertyDescriptor(e,t);n.value=r;Object.defineProperty(e,t,n)}else{e[t]=r}},defineByDescriptor:function(e,t,r){if(s){Object.defineProperty(e,t,r)}else if("value"in r){e[t]=r.value}},preserveToString:function(e,t){if(t&&d(t.toString)){h(e,"toString",t.toString.bind(t),true)}}};var O=Object.create||function(e,t){var r=function Prototype(){};r.prototype=e;var o=new r;if(typeof t!=="undefined"){n(t).forEach(function(e){m.defineByDescriptor(o,e,t[e])})}return o};var w=function(e,t){if(!Object.setPrototypeOf){return false}return a(function(){var r=function Subclass(t){var r=new e(t);Object.setPrototypeOf(r,Subclass.prototype);return r};Object.setPrototypeOf(r,e);r.prototype=O(e.prototype,{constructor:{value:r}});return t(r)})};var j=function(){if(typeof self!=="undefined"){return self}if(typeof window!=="undefined"){return window}if(typeof global!=="undefined"){return global}throw new Error("unable to locate global object")};var S=j();var T=S.isFinite;var I=Function.call.bind(String.prototype.indexOf);var E=Function.apply.bind(Array.prototype.indexOf);var P=Function.call.bind(Array.prototype.concat);var C=Function.call.bind(String.prototype.slice);var M=Function.call.bind(Array.prototype.push);var x=Function.apply.bind(Array.prototype.push);var N=Function.call.bind(Array.prototype.shift);var A=Math.max;var R=Math.min;var _=Math.floor;var k=Math.abs;var L=Math.exp;var F=Math.log;var D=Math.sqrt;var z=Function.call.bind(Object.prototype.hasOwnProperty);var q;var W=function(){};var G=S.Map;var H=G&&G.prototype["delete"];var V=G&&G.prototype.get;var B=G&&G.prototype.has;var U=G&&G.prototype.set;var $=S.Symbol||{};var J=$.species||"@@species";var X=Number.isNaN||function isNaN(e){return e!==e};var K=Number.isFinite||function isFinite(e){return typeof e==="number"&&T(e)};var Z=d(Math.sign)?Math.sign:function sign(e){var t=Number(e);if(t===0){return t}if(X(t)){return t}return t<0?-1:1};var Y=function log1p(e){var t=Number(e);if(t<-1||X(t)){return NaN}if(t===0||t===Infinity){return t}if(t===-1){return-Infinity}return 1+t-1===0?t:t*(F(1+t)/(1+t-1))};var Q=function isArguments(e){return g(e)==="[object Arguments]"};var ee=function isArguments(e){return e!==null&&typeof e==="object"&&typeof e.length==="number"&&e.length>=0&&g(e)!=="[object Array]"&&g(e.callee)==="[object Function]"};var te=Q(arguments)?Q:ee;var re={primitive:function(e){return e===null||typeof e!=="function"&&typeof e!=="object"},string:function(e){return g(e)==="[object String]"},regex:function(e){return g(e)==="[object RegExp]"},symbol:function(e){return typeof S.Symbol==="function"&&typeof e==="symbol"}};var ne=function overrideNative(e,t,r){var n=e[t];h(e,t,r,true);m.preserveToString(e[t],n)};var oe=typeof $==="function"&&typeof $["for"]==="function"&&re.symbol($());var ie=re.symbol($.iterator)?$.iterator:"_es6-shim iterator_";if(S.Set&&typeof(new S.Set)["@@iterator"]==="function"){ie="@@iterator"}if(!S.Reflect){h(S,"Reflect",{},true)}var ae=S.Reflect;var ue=String;var fe=typeof document==="undefined"||!document?null:document.all;var se=fe==null?function isNullOrUndefined(e){return e==null}:function isNullOrUndefinedAndNotDocumentAll(e){return e==null&&e!=Line truncated
|
||||
h(Math,"acosh",Cr.acosh,Math.acosh(Number.MAX_VALUE)===Infinity);h(Math,"acosh",Cr.acosh,!Mr(Math.acosh(1+Number.EPSILON),Math.sqrt(2*Number.EPSILON)));h(Math,"cbrt",Cr.cbrt,!Mr(Math.cbrt(1e-300),1e-100));h(Math,"sinh",Cr.sinh,Math.sinh(-2e-17)!==-2e-17);var xr=Math.expm1(10);h(Math,"expm1",Cr.expm1,xr>22025.465794806718||xr<22025.465794806718);var Nr=Math.round;var Ar=Math.round(.5-Number.EPSILON/4)===0&&Math.round(-.5+Number.EPSILON/3.99)===1;var Rr=mr+1;var _r=2*mr-1;var kr=[Rr,_r].every(function(e){return Math.round(e)===e});h(Math,"round",function round(e){var t=_(e);var r=t===-1?-0:t+1;return e-t<.5?t:r},!Ar||!kr);m.preserveToString(Math.round,Nr);var Lr=Math.imul;if(Math.imul(4294967295,5)!==-5){Math.imul=Cr.imul;m.preserveToString(Math.imul,Lr)}if(Math.imul.length!==2){ne(Math,"imul",function imul(e,t){return ce.Call(Lr,Math,arguments)})}var Fr=function(){var e=S.setTimeout;if(typeof e!=="function"&&typeof e!=="object"){return}ce.IsPromise=function(e){if(!ce.TypeIsObject(e)){return false}if(typeof e._promise==="undefined"){return false}return true};var r=function(e){if(!ce.IsConstructor(e)){throw new TypeError("Bad promise constructor")}var t=this;var r=function(e,r){if(t.resolve!==void 0||t.reject!==void 0){throw new TypeError("Bad Promise implementation!")}t.resolve=e;t.reject=r};t.resolve=void 0;t.reject=void 0;t.promise=new e(r);if(!(ce.IsCallable(t.resolve)&&ce.IsCallable(t.reject))){throw new TypeError("Bad promise constructor")}};var n;if(typeof window!=="undefined"&&ce.IsCallable(window.postMessage)){n=function(){var e=[];var t="zero-timeout-message";var r=function(r){M(e,r);window.postMessage(t,"*")};var n=function(r){if(r.source===window&&r.data===t){r.stopPropagation();if(e.length===0){return}var n=N(e);n()}};window.addEventListener("message",n,true);return r}}var o=function(){var e=S.Promise;var t=e&&e.resolve&&e.resolve();return t&&function(e){return t.then(e)}};var i=ce.IsCallable(S.setImmediate)?S.setImmediate:typeof process==="object"&&process.nextTick?process.nextTick:o()||(ce.IsCallable(n)?n():function(t){e(t,0)});var a=function(e){return e};var u=function(e){throw e};var f=0;var s=1;var c=2;var l=0;var p=1;var v=2;var y={};var h=function(e,t,r){i(function(){g(e,t,r)})};var g=function(e,t,r){var n,o;if(t===y){return e(r)}try{n=e(r);o=t.resolve}catch(i){n=i;o=t.reject}o(n)};var d=function(e,t){var r=e._promise;var n=r.reactionLength;if(n>0){h(r.fulfillReactionHandler0,r.reactionCapability0,t);r.fulfillReactionHandler0=void 0;r.rejectReactions0=void 0;r.reactionCapability0=void 0;if(n>1){for(var o=1,i=0;o<n;o++,i+=3){h(r[i+l],r[i+v],t);e[i+l]=void 0;e[i+p]=void 0;e[i+v]=void 0}}}r.result=t;r.state=s;r.reactionLength=0};var m=function(e,t){var r=e._promise;var n=r.reactionLength;if(n>0){h(r.rejectReactionHandler0,r.reactionCapability0,t);r.fulfillReactionHandler0=void 0;r.rejectReactions0=void 0;r.reactionCapability0=void 0;if(n>1){for(var o=1,i=0;o<n;o++,i+=3){h(r[i+p],r[i+v],t);e[i+l]=void 0;e[i+p]=void 0;e[i+v]=void 0}}}r.result=t;r.state=c;r.reactionLength=0};var O=function(e){var t=false;var r=function(r){var n;if(t){return}t=true;if(r===e){return m(e,new TypeError("Self resolution"))}if(!ce.TypeIsObject(r)){return d(e,r)}try{n=r.then}catch(o){return m(e,o)}if(!ce.IsCallable(n)){return d(e,r)}i(function(){j(e,r,n)})};var n=function(r){if(t){return}t=true;return m(e,r)};return{resolve:r,reject:n}};var w=function(e,r,n,o){if(e===I){t(e,r,n,o,y)}else{t(e,r,n,o)}};var j=function(e,t,r){var n=O(e);var o=n.resolve;var i=n.reject;try{w(r,t,o,i)}catch(a){i(a)}};var T,I;var E=function(){var e=function Promise(t){if(!(this instanceof e)){throw new TypeError('Constructor Promise requires "new"')}if(this&&this._promise){throw new TypeError("Bad construction")}if(!ce.IsCallable(t)){throw new TypeError("not a valid resolver")}var r=Ae(this,e,T,{_promise:{result:void 0,state:f,reactionLength:0,fulfillReactionHandler0:void 0,rejectReactionHandler0:void 0,reactionCapability0:void 0}});var n=O(r);var o=n.reject;try{t(n.resolve,o)}catch(i){o(i)}return r};return e}();T=E.prototype;var P=function(e,t,r,n){var o=false;return function(i){if(o){return}o=true;t[e]=i;if(--n.count===0){var a=r.resolve;a(t)}}};var C=function(e,t,r){var n=e.iterator;var o=[];var i={count:1};var a,u;var f=0;while(true){try{a=ce.IteratorStep(n);if(a===false){e.done=true;break}u=a.value}catch(s){e.done=true;throw s}o[f]=void 0;var c=t.resolve(u);var l=P(f,o,r,i);i.count+=1;w(c.then,c,l,r.reject);f+=1}if(--i.count===0){var p=r.resolve;p(o)}return r.promise};var x=function(e,t,r){var n=e.iterator;var o,i,a;while(true){try{o=ce.IteratorStep(n);if(o===false){e.done=true;break}i=o.value}catch(u){e.done=true;throw u}a=t.resolve(i);w(a.then,a,r.resolve,r.reject)}return r.promise};b(E,{all:function all(e){var t=this;if(!ce.TypeIsObject(t)){throw new TypeError("Promise is not object")}var n=new r(t);var o,i;try{o=ce.GetIterator(e);i={iterator:o,done:false};return C(i,t,n)}catch(a){var u=a;if(i&&!i.done){try{ce.IteratorClose(o,true)}cLine truncated
|
||||
//# sourceMappingURL=es6-shim.map
|
||||
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>/manifest.json" />
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="<%= PUBLIC_URL %>/<%= APP_CONFIG %>"
|
||||
></script>
|
||||
|
||||
<title>OHIF Viewer</title>
|
||||
|
||||
<!-- WEB FONTS -->
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Sanchez&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
|
||||
<script>
|
||||
window.config.extensions = [SomeExtension];
|
||||
</script> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"short_name": "react-cornerstone-viewport",
|
||||
"name": "react-cornerstone-viewport",
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
!function webpackUniversalModuleDefinition(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var r=t();for(var n in r)("object"==typeof exports?exports:e)[n]=r[n]}}(window,function(){return function(e){var t={};function __webpack_require__(r){if(t[r])return t[r].exports;var n=t[r]={i:r,l:!1,exports:{}};return e[r].call(n.exports,n,n.exports,__webpack_require__),n.l=!0,n.exports}return __webpack_require__.m=e,__webpack_require__.c=t,__webpack_require__.d=function(e,t,r){__webpack_require__.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},__webpack_require__.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.t=function(e,t){if(1&t&&(e=__webpack_require__(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(__webpack_require__.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)__webpack_require__.d(r,n,function(t){return e[t]}.bind(null,n));return r},__webpack_require__.n=function(e){var t=e&&e.__esModule?function getDefault(){return e.default}:function getModuleExports(){return e};return __webpack_require__.d(t,"a",t),t},__webpack_require__.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},__webpack_require__.p="",__webpack_require__(__webpack_require__.s=45)}([function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function defineProperties(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(e,t,r){return t&&defineProperties(e.prototype,t),r&&defineProperties(e,r),e}}();var i={debug:function debug(){},info:function info(){},warn:function warn(){},error:function error(){}},o=void 0,s=void 0;(t.Log=function(){function Log(){!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,Log)}return Log.reset=function reset(){s=3,o=i},Log.debug=function debug(){if(s>=4){for(var e=arguments.length,t=Array(e),r=0;r<e;r++)t[r]=arguments[r];o.debug.apply(o,Array.from(t))}},Log.info=function info(){if(s>=3){for(var e=arguments.length,t=Array(e),r=0;r<e;r++)t[r]=arguments[r];o.info.apply(o,Array.from(t))}},Log.warn=function warn(){if(s>=2){for(var e=arguments.length,t=Array(e),r=0;r<e;r++)t[r]=arguments[r];o.warn.apply(o,Array.from(t))}},Log.error=function error(){if(s>=1){for(var e=arguments.length,t=Array(e),r=0;r<e;r++)t[r]=arguments[r];o.error.apply(o,Array.from(t))}},n(Log,null,[{key:"NONE",get:function get(){return 0}},{key:"ERROR",get:function get(){return 1}},{key:"WARN",get:function get(){return 2}},{key:"INFO",get:function get(){return 3}},{key:"DEBUG",get:function get(){return 4}},{key:"level",get:function get(){return s},set:function set(e){if(!(0<=e&&e<=4))throw new Error("Invalid log level");s=e}},{key:"logger",get:function get(){return o},set:function set(e){if(!e.debug&&e.info&&(e.debug=e.info),!(e.debug&&e.info&&e.warn&&e.error))throw new Error("Invalid logger");o=e}}]),Log}()).reset()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function defineProperties(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(e,t,r){return t&&defineProperties(e.prototype,t),r&&defineProperties(e,r),e}}();var i={setInterval:function(e){function setInterval(t,r){return e.apply(this,arguments)}return setInterval.toString=function(){return e.toString()},setInterval}(function(e,t){return setInterval(e,t)}),clearInterval:function(e){function clearInterval(t){return e.apply(this,arguments)}return clearInterval.toString=function(){return e.toString()},clearInterval}(function(e){return clearInterval(e)})},o=!1,s=null;t.Global=function(){function Global(){!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,Global)}return Global._testing=function _testing(){o=!0},Global.setXMLHttpRequest=function setXMLHttpRequest(e){s=e},n(Global,null,[{key:"location",get:function get(){if(!o)return location}},{key:"localStorage",get:function get(){if(!o&&"undefined"!=typeof window)return localStorage}},{key:"sessionStorage",get:function get(){if(!o&&"undefined"!=typeof window)return sessionStorage}},{key:"XMLHttpRequest",get:function get(){if(!o&&"undefined"!=typeof window)return s||XMLHttpRequest}},{key:"timer",get:function get(){if(!o)return i}}]),Global}()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.UrlUtility=void 0;var n=r(0),i=r(1);t.UrlUtility=function(){function UrlUtility(){!function _classCallCheck(e,t){if(!(e instaLine truncated
|
||||
// @preserve Copyright (c) Microsoft Open Technologies, Inc.
|
||||
function random(){for(var e="xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx",t="0123456789abcdef",r=0,n="",i=0;i<e.length;i++)"-"!==e[i]&&"4"!==e[i]&&(r=16*Math.random()|0),"x"===e[i]?n+=t[r]:"y"===e[i]?(r&=3,n+=t[r|=8]):n+=e[i];return n},e.exports=t.default},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SigninState=void 0;var n=function(){function defineProperties(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(e,t,r){return t&&defineProperties(e.prototype,t),r&&defineProperties(e,r),e}}(),i=r(0),o=r(4),s=function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}(r(14));t.SigninState=function(e){function SigninState(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=t.nonce,n=t.authority,i=t.client_id;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,SigninState);var o=function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this,arguments[0]));return!0===r?o._nonce=(0,s.default)():r&&(o._nonce=r),o._authority=n,o._client_id=i,o}return function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(SigninState,e),SigninState.prototype.toStorageString=function toStorageString(){return i.Log.debug("SigninState.toStorageString"),JSON.stringify({id:this.id,data:this.data,created:this.created,nonce:this.nonce,authority:this.authority,client_id:this.client_id})},SigninState.fromStorageString=function fromStorageString(e){return i.Log.debug("SigninState.fromStorageString"),new SigninState(JSON.parse(e))},n(SigninState,[{key:"nonce",get:function get(){return this._nonce}},{key:"authority",get:function get(){return this._authority}},{key:"client_id",get:function get(){return this._client_id}}]),SigninState}(o.State)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ErrorResponse=void 0;var n=r(0);t.ErrorResponse=function(e){function ErrorResponse(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=t.error,i=t.error_description,o=t.error_uri,s=t.state;if(function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,ErrorResponse),!r)throw n.Log.error("No error passed to ErrorResponse"),new Error("error");var a=function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,e.call(this,i||r));return a.name="ErrorResponse",a.error=r,a.error_description=i,a.error_uri=o,a.state=s,a}return function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(ErrorResponse,e),ErrorResponse}(Error)},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.JsonService=void 0;var n=r(0),i=r(1);t.JsonService=function(){function JsonService(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:i.Global.XMLHttpRequest;!function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,JsonService),e&&Array.isArray(e)?this._contentTypes=e.slice():this._contentTypes=[],this._contentTypes.push("application/json"),this._XMLHttpRequest=t}return JsonService.prototype.getJson=function getJson(e,t){var r=this;if(!e)throw n.Log.error("JsonService.getJson: No url passed"),new Error("url");return n.Log.debug("JsonService.getJson, url: ",e),new Promise(function(i,o){var s=new r._XMLHttpRequest;s.open("GET",e);var a=r._contentTypes;s.onload=function(){if(n.Log.debug("JsonService.getJson: HTTP response received, status",s.status),200===s.status){var t=s.getResponseHeader("Content-Type");if(t)if(a.find(function(e){if(t.startsWith(e))return!0}))try{return void i(JSON.parse(s.responseText))}catch(e){return n.Log.error("JsonService.getJson: Error parsing JSON response",e.message),void o(e)}o(Error("Invalid response Content-Type: "+t+", from URL: "+e))}else o(Error(s.statusText+" ("+s.status+")"))},s.onerror=function(){n.Log.error("JsonService.getJson: network error"),o(Error("Network Error"))},t&&(n.Log.debug("JsonService.getJson: token passed, setting Authorization header"),s.setRequestHeader("AutLine truncated
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
var n=r(38),i=r(37),o=r(36);function kMaxLength(){return Buffer.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function createBuffer(e,t){if(kMaxLength()<t)throw new RangeError("Invalid typed array length");return Buffer.TYPED_ARRAY_SUPPORT?(e=new Uint8Array(t)).__proto__=Buffer.prototype:(null===e&&(e=new Buffer(t)),e.length=t),e}function Buffer(e,t,r){if(!(Buffer.TYPED_ARRAY_SUPPORT||this instanceof Buffer))return new Buffer(e,t,r);if("number"==typeof e){if("string"==typeof t)throw new Error("If encoding is specified then the first argument must be a string");return allocUnsafe(this,e)}return from(this,e,t,r)}function from(e,t,r,n){if("number"==typeof t)throw new TypeError('"value" argument must not be a number');return"undefined"!=typeof ArrayBuffer&&t instanceof ArrayBuffer?function fromArrayBuffer(e,t,r,n){if(t.byteLength,r<0||t.byteLength<r)throw new RangeError("'offset' is out of bounds");if(t.byteLength<r+(n||0))throw new RangeError("'length' is out of bounds");t=void 0===r&&void 0===n?new Uint8Array(t):void 0===n?new Uint8Array(t,r):new Uint8Array(t,r,n);Buffer.TYPED_ARRAY_SUPPORT?(e=t).__proto__=Buffer.prototype:e=fromArrayLike(e,t);return e}(e,t,r,n):"string"==typeof t?function fromString(e,t,r){"string"==typeof r&&""!==r||(r="utf8");if(!Buffer.isEncoding(r))throw new TypeError('"encoding" must be a valid string encoding');var n=0|byteLength(t,r),i=(e=createBuffer(e,n)).write(t,r);i!==n&&(e=e.slice(0,i));return e}(e,t,r):function fromObject(e,t){if(Buffer.isBuffer(t)){var r=0|checked(t.length);return 0===(e=createBuffer(e,r)).length?e:(t.copy(e,0,0,r),e)}if(t){if("undefined"!=typeof ArrayBuffer&&t.buffer instanceof ArrayBuffer||"length"in t)return"number"!=typeof t.length||function isnan(e){return e!=e}(t.length)?createBuffer(e,0):fromArrayLike(e,t);if("Buffer"===t.type&&o(t.data))return fromArrayLike(e,t.data)}throw new TypeError("First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.")}(e,t)}function assertSize(e){if("number"!=typeof e)throw new TypeError('"size" argument must be a number');if(e<0)throw new RangeError('"size" argument must not be negative')}function allocUnsafe(e,t){if(assertSize(t),e=createBuffer(e,t<0?0:0|checked(t)),!Buffer.TYPED_ARRAY_SUPPORT)for(var r=0;r<t;++r)e[r]=0;return e}function fromArrayLike(e,t){var r=t.length<0?0:0|checked(t.length);e=createBuffer(e,r);for(var n=0;n<r;n+=1)e[n]=255&t[n];return e}function checked(e){if(e>=kMaxLength())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+kMaxLength().toString(16)+" bytes");return 0|e}function byteLength(e,t){if(Buffer.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var r=e.length;if(0===r)return 0;for(var n=!1;;)switch(t){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":case void 0:return utf8ToBytes(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return base64ToBytes(e).length;default:if(n)return utf8ToBytes(e).length;t=(""+t).toLowerCase(),n=!0}}function swap(e,t,r){var n=e[t];e[t]=e[r],e[r]=n}function bidirectionalIndexOf(e,t,r,n,i){if(0===e.length)return-1;if("string"==typeof r?(n=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),r=+r,isNaN(r)&&(r=i?0:e.length-1),r<0&&(r=e.length+r),r>=e.length){if(i)return-1;r=e.length-1}else if(r<0){if(!i)return-1;r=0}if("string"==typeof t&&(t=Buffer.from(t,n)),Buffer.isBuffer(t))return 0===t.length?-1:arrayIndexOf(e,t,r,n,i);if("number"==typeof t)return t&=255,Buffer.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,r):Uint8Array.prototype.lastIndexOf.call(e,t,r):arrayIndexOf(e,[t],r,n,i);throw new TypeError("val must be string, number or Buffer")}function arrayIndexOf(e,t,r,n,i){var o,s=1,a=e.length,u=t.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(e.length<2||t.length<2)return-1;s=2,a/=2,u/=2,r/=2}function read(e,t){return 1===s?e[t]:e.readUInt16BE(t*s)}if(i){var c=-1;for(o=r;o<a;o++)if(read(e,o)===read(t,-1===c?0:o-c)){if(-1===c&&(c=o),o-c+1===u)return c*s}else-1!==c&&(o-=o-c),c=-1}else for(r+u>a&&(r=a-u),o=r;o>=0;o--){for(var h=!0,f=0;f<u;f++)if(read(e,o+f)!==read(t,f)){h=!1;break}if(h)return o}return-1}function hexWrite(e,t,r,n){r=Number(r)||0;var i=e.length-r;n?(n=Number(n))>i&&(n=i):n=i;var o=t.length;if(o%2!=0)throw new TypeError("Invalid hex string");n>o/2&&(n=o/2);for(var s=0;s<n;++s){var a=parseInt(t.substr(2*s,2),16);if(isNaN(a))return s;e[r+s]=a}return s}function utf8Write(e,t,r,n){return blitBuffer(utf8ToBytes(t,e.length-r),e,r,n)}function asciiWrite(e,t,r,n){return blitBuffer(function asciiToBytes(e){for(var t=[],r=0;r<e.length;++r)t.push(255&e.charCodeAt(r));return t}(t),e,r,n)}function latin1Write(e,t,r,n){return asciiWrite(e,t,r,n)}functioLine truncated
|
||||
/*!
|
||||
Copyright (c) 2011, Yahoo! Inc. All rights reserved.
|
||||
Code licensed under the BSD License:
|
||||
http://developer.yahoo.com/yui/license.html
|
||||
version: 2.9.0
|
||||
*/
|
||||
if(void 0===v)var v={};v.lang={extend:function extend(t,r,n){if(!r||!t)throw new Error("YAHOO.lang.extend failed, please check that all dependencies are included.");var i=function d(){};if(i.prototype=r.prototype,t.prototype=new i,t.prototype.constructor=t,t.superclass=r.prototype,r.prototype.constructor==Object.prototype.constructor&&(r.prototype.constructor=r),n){var o;for(o in n)t.prototype[o]=n[o];var s=function e(){},a=["toString","valueOf"];try{/MSIE/.test(u.userAgent)&&(s=function e(t,r){for(o=0;o<a.length;o+=1){var n=a[o],i=r[n];"function"==typeof i&&i!=Object.prototype[n]&&(t[n]=i)}})}catch(e){}s(t.prototype,n)}}};
|
||||
/*! CryptoJS v3.1.2 core-fix.js
|
||||
* code.google.com/p/crypto-js
|
||||
* (c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
* code.google.com/p/crypto-js/wiki/License
|
||||
* THIS IS FIX of 'core.js' to fix Hmac issue.
|
||||
* https://code.google.com/p/crypto-js/issues/detail?id=84
|
||||
* https://crypto-js.googlecode.com/svn-history/r667/branches/3.x/src/core.js
|
||||
*/
|
||||
var y=y||function(e,t){var r={},n=r.lib={},i=n.Base=function(){function n(){}return{extend:function extend(e){n.prototype=this;var t=new n;return e&&t.mixIn(e),t.hasOwnProperty("init")||(t.init=function(){t.$super.init.apply(this,arguments)}),t.init.prototype=t,t.$super=this,t},create:function create(){var e=this.extend();return e.init.apply(e,arguments),e},init:function init(){},mixIn:function mixIn(e){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);e.hasOwnProperty("toString")&&(this.toString=e.toString)},clone:function clone(){return this.init.prototype.extend(this)}}}(),o=n.WordArray=i.extend({init:function init(e,t){e=this.words=e||[],this.sigBytes=void 0!=t?t:4*e.length},toString:function toString(e){return(e||a).stringify(this)},concat:function concat(e){var t=this.words,r=e.words,n=this.sigBytes,i=e.sigBytes;if(this.clamp(),n%4)for(var o=0;o<i;o++){var s=r[o>>>2]>>>24-o%4*8&255;t[n+o>>>2]|=s<<24-(n+o)%4*8}else for(o=0;o<i;o+=4)t[n+o>>>2]=r[o>>>2];return this.sigBytes+=i,this},clamp:function clamp(){var t=this.words,r=this.sigBytes;t[r>>>2]&=4294967295<<32-r%4*8,t.length=e.ceil(r/4)},clone:function clone(){var e=i.clone.call(this);return e.words=this.words.slice(0),e},random:function random(t){for(var r=[],n=0;n<t;n+=4)r.push(4294967296*e.random()|0);return new o.init(r,t)}}),s=r.enc={},a=s.Hex={stringify:function stringify(e){for(var t=e.words,r=e.sigBytes,n=[],i=0;i<r;i++){var o=t[i>>>2]>>>24-i%4*8&255;n.push((o>>>4).toString(16)),n.push((15&o).toString(16))}return n.join("")},parse:function parse(e){for(var t=e.length,r=[],n=0;n<t;n+=2)r[n>>>3]|=parseInt(e.substr(n,2),16)<<24-n%8*4;return new o.init(r,t/2)}},u=s.Latin1={stringify:function stringify(e){for(var t=e.words,r=e.sigBytes,n=[],i=0;i<r;i++){var o=t[i>>>2]>>>24-i%4*8&255;n.push(String.fromCharCode(o))}return n.join("")},parse:function parse(e){for(var t=e.length,r=[],n=0;n<t;n++)r[n>>>2]|=(255&e.charCodeAt(n))<<24-n%4*8;return new o.init(r,t)}},c=s.Utf8={stringify:function stringify(e){try{return decodeURIComponent(escape(u.stringify(e)))}catch(e){throw new Error("Malformed UTF-8 data")}},parse:function parse(e){return u.parse(unescape(encodeURIComponent(e)))}},h=n.BufferedBlockAlgorithm=i.extend({reset:function reset(){this._data=new o.init,this._nDataBytes=0},_append:function _append(e){"string"==typeof e&&(e=c.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function _process(t){var r=this._data,n=r.words,i=r.sigBytes,s=this.blockSize,a=i/(4*s),u=(a=t?e.ceil(a):e.max((0|a)-this._minBufferSize,0))*s,c=e.min(4*u,i);if(u){for(var h=0;h<u;h+=s)this._doProcessBlock(n,h);var f=n.splice(0,u);r.sigBytes-=c}return new o.init(f,c)},clone:function clone(){var e=i.clone.call(this);return e._data=this._data.clone(),e},_minBufferSize:0}),f=(n.Hasher=h.extend({cfg:i.extend(),init:function init(e){this.cfg=this.cfg.extend(e),this.reset()},reset:function reset(){h.reset.call(this),this._doReset()},update:function update(e){return this._append(e),this._process(),this},finalize:function finalize(e){return e&&this._append(e),this._doFinalize()},blockSize:16,_createHelper:function _createHelper(e){return function(t,r){return new e.init(r).finalize(t)}},_createHmacHelper:function _createHmacHelper(e){return function(t,r){return new f.HMAC.init(e,r).finalize(t)}}}),r.algo={});return r}(Math);!function(e){var t,r=(t=y).lib,n=r.Base,i=r.WordArray;(t=t.x64={}).Word=n.extend({init:function init(e,t){this.high=e,this.low=t}}),t.WordArray=n.extend({init:function init(e,t){e=this.words=e||[],this.sigBytes=void 0!=t?t:8*e.length},toX32:function toX32(){for(var e=this.words,t=e.length,r=[],n=0;n<t;n++){var o=e[n];r.push(o.high),r.push(o.low)}return i.create(r,this.sigBytes)},clone:function clone(){for(var e=n.clone.call(this),t=e.words=this.words.slice(0),r=t.length,i=0;i<r;i++)t[i]=t[i].clone();return e}})}(),function(){var e=y,t=e.lib.WordArray;e.enc.Base64={stringify:function stringify(e){var t=e.words,r=e.sigBytes,n=this._map;e.clamp(),e=[];for(var i=0;i<r;i+=3)for(var o=(t[i>>>2]>>>24-i%4*8&255)<<16|(t[i+1>>>2]>>>24-(i+1)%4*8&255)<<8|t[i+2>>>2]>>>24-(i+2)%4*8&255,s=0;4>s&&i+.75*s<r;s++)e.push(n.charAt(o>>>6*(3-s)&63));if(t=n.charAt(64))for(;e.length%4;)e.push(t);return e.join("")},parse:function parse(e){var r=e.length,n=this._map;(i=n.charAt(64))&&(-1!=(i=e.indexOf(i))&&(r=i));for(var i=[],o=0,s=0;s<r;s++)if(s%4){var a=n.indexOf(e.charAt(s-1))<<s%4*2,u=n.indexOf(e.charAt(s))>>>6-s%4*2;i[o>>>2]|=(a|u)<<24-o%4*8,o++}return t.create(i,o)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}}(),function(e){for(var t=y,r=(i=t.lib).WordArray,n=i.Hasher,i=t.algo,o=[],s=[],a=function u(e){return 4294967296*(e-(0|e))|0},u=2,c=0;64>c;){var h;e:{h=u;for(var f=e.sqrt(h),l=2;l<=f;l++)if(!(h%l)){h=!1;break e}h=!0}h&&(8>c&&(o[c]=a(e.pow(u,.5))),s[c]=a(e.pow(u,1/3)),c++),u++}var g=[];i=i.SHA256=n.extend({_doReset:function _doReset(){this._hash=new r.init(o.slice(0))},_doProcessBlock:function _doProcessBlock(e,t){for(var r=this._Line truncated
|
||||
/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/
|
||||
*/
|
||||
var S,F="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",_="=";function hex2b64(e){var t,r,n="";for(t=0;t+3<=e.length;t+=3)r=parseInt(e.substring(t,t+3),16),n+=F.charAt(r>>6)+F.charAt(63&r);if(t+1==e.length?(r=parseInt(e.substring(t,t+1),16),n+=F.charAt(r<<2)):t+2==e.length&&(r=parseInt(e.substring(t,t+2),16),n+=F.charAt(r>>2)+F.charAt((3&r)<<4)),_)for(;(3&n.length)>0;)n+=_;return n}function b64tohex(e){var t,r,n,i="",o=0;for(t=0;t<e.length&&e.charAt(t)!=_;++t)(n=F.indexOf(e.charAt(t)))<0||(0==o?(i+=int2char(n>>2),r=3&n,o=1):1==o?(i+=int2char(r<<2|n>>4),r=15&n,o=2):2==o?(i+=int2char(r),i+=int2char(n>>2),r=3&n,o=3):(i+=int2char(r<<2|n>>4),i+=int2char(15&n),o=0));return 1==o&&(i+=int2char(r<<2)),i}function b64toBA(e){var t,r=b64tohex(e),n=new Array;for(t=0;2*t<r.length;++t)n[t]=parseInt(r.substring(2*t,2*t+2),16);return n}function BigInteger(e,t,r){null!=e&&("number"==typeof e?this.fromNumber(e,t,r):null==t&&"string"!=typeof e?this.fromString(e,256):this.fromString(e,t))}function nbi(){return new BigInteger(null)}"Microsoft Internet Explorer"==u.appName?(BigInteger.prototype.am=function am2(e,t,r,n,i,o){for(var s=32767&t,a=t>>15;--o>=0;){var u=32767&this[e],c=this[e++]>>15,h=a*u+c*s;i=((u=s*u+((32767&h)<<15)+r[n]+(1073741823&i))>>>30)+(h>>>15)+a*c+(i>>>30),r[n++]=1073741823&u}return i},S=30):"Netscape"!=u.appName?(BigInteger.prototype.am=function am1(e,t,r,n,i,o){for(;--o>=0;){var s=t*this[e++]+r[n]+i;i=Math.floor(s/67108864),r[n++]=67108863&s}return i},S=26):(BigInteger.prototype.am=function am3(e,t,r,n,i,o){for(var s=16383&t,a=t>>14;--o>=0;){var u=16383&this[e],c=this[e++]>>14,h=a*u+c*s;i=((u=s*u+((16383&h)<<14)+r[n]+i)>>28)+(h>>14)+a*c,r[n++]=268435455&u}return i},S=28),BigInteger.prototype.DB=S,BigInteger.prototype.DM=(1<<S)-1,BigInteger.prototype.DV=1<<S;BigInteger.prototype.FV=Math.pow(2,52),BigInteger.prototype.F1=52-S,BigInteger.prototype.F2=2*S-52;var w,E,C="0123456789abcdefghijklmnopqrstuvwxyz",P=new Array;for(w="0".charCodeAt(0),E=0;E<=9;++E)P[w++]=E;for(w="a".charCodeAt(0),E=10;E<36;++E)P[w++]=E;for(w="A".charCodeAt(0),E=10;E<36;++E)P[w++]=E;function int2char(e){return C.charAt(e)}function intAt(e,t){var r=P[e.charCodeAt(t)];return null==r?-1:r}function nbv(e){var t=nbi();return t.fromInt(e),t}function nbits(e){var t,r=1;return 0!=(t=e>>>16)&&(e=t,r+=16),0!=(t=e>>8)&&(e=t,r+=8),0!=(t=e>>4)&&(e=t,r+=4),0!=(t=e>>2)&&(e=t,r+=2),0!=(t=e>>1)&&(e=t,r+=1),r}function Classic(e){this.m=e}function Montgomery(e){this.m=e,this.mp=e.invDigit(),this.mpl=32767&this.mp,this.mph=this.mp>>15,this.um=(1<<e.DB-15)-1,this.mt2=2*e.t}function op_and(e,t){return e&t}function op_or(e,t){return e|t}function op_xor(e,t){return e^t}function op_andnot(e,t){return e&~t}function lbit(e){if(0==e)return-1;var t=0;return 0==(65535&e)&&(e>>=16,t+=16),0==(255&e)&&(e>>=8,t+=8),0==(15&e)&&(e>>=4,t+=4),0==(3&e)&&(e>>=2,t+=2),0==(1&e)&&++t,t}function cbit(e){for(var t=0;0!=e;)e&=e-1,++t;return t}function NullExp(){}function nNop(e){return e}function Barrett(e){this.r2=nbi(),this.q3=nbi(),BigInteger.ONE.dlShiftTo(2*e.t,this.r2),this.mu=this.r2.divide(e),this.m=e}Classic.prototype.convert=function cConvert(e){return e.s<0||e.compareTo(this.m)>=0?e.mod(this.m):e},Classic.prototype.revert=function cRevert(e){return e},Classic.prototype.reduce=function cReduce(e){e.divRemTo(this.m,null,e)},Classic.prototype.mulTo=function cMulTo(e,t,r){e.multiplyTo(t,r),this.reduce(r)},Classic.prototype.sqrTo=function cSqrTo(e,t){e.squareTo(t),this.reduce(t)},Montgomery.prototype.convert=function montConvert(e){var t=nbi();return e.abs().dlShiftTo(this.m.t,t),t.divRemTo(this.m,null,t),e.s<0&&t.compareTo(BigInteger.ZERO)>0&&this.m.subTo(t,t),t},Montgomery.prototype.revert=function montRevert(e){var t=nbi();return e.copyTo(t),this.reduce(t),t},Montgomery.prototype.reduce=function montReduce(e){for(;e.t<=this.mt2;)e[e.t++]=0;for(var t=0;t<this.m.t;++t){var r=32767&e[t],n=r*this.mpl+((r*this.mph+(e[t]>>15)*this.mpl&this.um)<<15)&e.DM;for(e[r=t+this.m.t]+=this.m.am(0,n,e,t,0,this.m.t);e[r]>=e.DV;)e[r]-=e.DV,e[++r]++}e.clamp(),e.drShiftTo(this.m.t,e),e.compareTo(this.m)>=0&&e.subTo(this.m,e)},Montgomery.prototype.mulTo=function montMulTo(e,t,r){e.multiplyTo(t,r),this.reduce(r)},Montgomery.prototype.sqrTo=function montSqrTo(e,t){e.squareTo(t),this.reduce(t)},BigInteger.prototype.copyTo=function bnpCopyTo(e){for(var t=this.t-1;t>=0;--t)e[t]=this[t];e.t=this.t,e.s=this.s},BigInteger.prototype.fromInt=function bnpFromInt(e){this.t=1,this.s=e<0?-1:0,e>0?this[0]=e:e<-1?this[0]=e+this.DV:this.t=0},BigInteger.prototype.fromString=function bnpFromString(e,t){var r;if(16==t)r=4;else if(8==t)r=3;else if(256==t)r=8;else if(2==t)r=1;else if(32==t)r=5;else{if(4!=t)return void this.fromRadix(e,t);r=2}this.t=0,this.s=0;for(var n=e.length,i=!1,o=0;--n>=0;){var s=8==r?255&e[n]:intAt(e,n);s<0?"-"==e.charAt(n)&&(i=!0):(i=!1,0==o?this[this.t++]=s:o+r>this.DB?(this[this.t-1]|=(s&(1<<this.DB-o)-1)<<o,this[this.t++]=s>>this.DB-o):this[this.t-1]|=s<<o,(o+=r)>=this.DLine truncated
|
||||
/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/
|
||||
*/
|
||||
function Arcfour(){this.i=0,this.j=0,this.S=new Array}BigInteger.prototype.chunkSize=function bnpChunkSize(e){return Math.floor(Math.LN2*this.DB/Math.log(e))},BigInteger.prototype.toRadix=function bnpToRadix(e){if(null==e&&(e=10),0==this.signum()||e<2||e>36)return"0";var t=this.chunkSize(e),r=Math.pow(e,t),n=nbv(r),i=nbi(),o=nbi(),s="";for(this.divRemTo(n,i,o);i.signum()>0;)s=(r+o.intValue()).toString(e).substr(1)+s,i.divRemTo(n,i,o);return o.intValue().toString(e)+s},BigInteger.prototype.fromRadix=function bnpFromRadix(e,t){this.fromInt(0),null==t&&(t=10);for(var r=this.chunkSize(t),n=Math.pow(t,r),i=!1,o=0,s=0,a=0;a<e.length;++a){var u=intAt(e,a);u<0?"-"==e.charAt(a)&&0==this.signum()&&(i=!0):(s=t*s+u,++o>=r&&(this.dMultiply(n),this.dAddOffset(s,0),o=0,s=0))}o>0&&(this.dMultiply(Math.pow(t,o)),this.dAddOffset(s,0)),i&&BigInteger.ZERO.subTo(this,this)},BigInteger.prototype.fromNumber=function bnpFromNumber(e,t,r){if("number"==typeof t)if(e<2)this.fromInt(1);else for(this.fromNumber(e,r),this.testBit(e-1)||this.bitwiseTo(BigInteger.ONE.shiftLeft(e-1),op_or,this),this.isEven()&&this.dAddOffset(1,0);!this.isProbablePrime(t);)this.dAddOffset(2,0),this.bitLength()>e&&this.subTo(BigInteger.ONE.shiftLeft(e-1),this);else{var n=new Array,i=7&e;n.length=1+(e>>3),t.nextBytes(n),i>0?n[0]&=(1<<i)-1:n[0]=0,this.fromString(n,256)}},BigInteger.prototype.bitwiseTo=function bnpBitwiseTo(e,t,r){var n,i,o=Math.min(e.t,this.t);for(n=0;n<o;++n)r[n]=t(this[n],e[n]);if(e.t<this.t){for(i=e.s&this.DM,n=o;n<this.t;++n)r[n]=t(this[n],i);r.t=this.t}else{for(i=this.s&this.DM,n=o;n<e.t;++n)r[n]=t(i,e[n]);r.t=e.t}r.s=t(this.s,e.s),r.clamp()},BigInteger.prototype.changeBit=function bnpChangeBit(e,t){var r=BigInteger.ONE.shiftLeft(e);return this.bitwiseTo(r,t,r),r},BigInteger.prototype.addTo=function bnpAddTo(e,t){for(var r=0,n=0,i=Math.min(e.t,this.t);r<i;)n+=this[r]+e[r],t[r++]=n&this.DM,n>>=this.DB;if(e.t<this.t){for(n+=e.s;r<this.t;)n+=this[r],t[r++]=n&this.DM,n>>=this.DB;n+=this.s}else{for(n+=this.s;r<e.t;)n+=e[r],t[r++]=n&this.DM,n>>=this.DB;n+=e.s}t.s=n<0?-1:0,n>0?t[r++]=n:n<-1&&(t[r++]=this.DV+n),t.t=r,t.clamp()},BigInteger.prototype.dMultiply=function bnpDMultiply(e){this[this.t]=this.am(0,e-1,this,0,0,this.t),++this.t,this.clamp()},BigInteger.prototype.dAddOffset=function bnpDAddOffset(e,t){if(0!=e){for(;this.t<=t;)this[this.t++]=0;for(this[t]+=e;this[t]>=this.DV;)this[t]-=this.DV,++t>=this.t&&(this[this.t++]=0),++this[t]}},BigInteger.prototype.multiplyLowerTo=function bnpMultiplyLowerTo(e,t,r){var n,i=Math.min(this.t+e.t,t);for(r.s=0,r.t=i;i>0;)r[--i]=0;for(n=r.t-this.t;i<n;++i)r[i+this.t]=this.am(0,e[i],r,i,0,this.t);for(n=Math.min(e.t,t);i<n;++i)this.am(0,e[i],r,i,0,t-i);r.clamp()},BigInteger.prototype.multiplyUpperTo=function bnpMultiplyUpperTo(e,t,r){--t;var n=r.t=this.t+e.t-t;for(r.s=0;--n>=0;)r[n]=0;for(n=Math.max(t-this.t,0);n<e.t;++n)r[this.t+n-t]=this.am(t-n,e[n],r,0,0,this.t+n-t);r.clamp(),r.drShiftTo(1,r)},BigInteger.prototype.modInt=function bnpModInt(e){if(e<=0)return 0;var t=this.DV%e,r=this.s<0?e-1:0;if(this.t>0)if(0==t)r=this[0]%e;else for(var n=this.t-1;n>=0;--n)r=(t*r+this[n])%e;return r},BigInteger.prototype.millerRabin=function bnpMillerRabin(e){var t=this.subtract(BigInteger.ONE),r=t.getLowestSetBit();if(r<=0)return!1;var n=t.shiftRight(r);(e=e+1>>1)>I.length&&(e=I.length);for(var i=nbi(),o=0;o<e;++o){i.fromInt(I[Math.floor(Math.random()*I.length)]);var s=i.modPow(n,this);if(0!=s.compareTo(BigInteger.ONE)&&0!=s.compareTo(t)){for(var a=1;a++<r&&0!=s.compareTo(t);)if(0==(s=s.modPowInt(2,this)).compareTo(BigInteger.ONE))return!1;if(0!=s.compareTo(t))return!1}}return!0},BigInteger.prototype.clone=
|
||||
/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/
|
||||
*/
|
||||
function bnClone(){var e=nbi();return this.copyTo(e),e},BigInteger.prototype.intValue=function bnIntValue(){if(this.s<0){if(1==this.t)return this[0]-this.DV;if(0==this.t)return-1}else{if(1==this.t)return this[0];if(0==this.t)return 0}return(this[1]&(1<<32-this.DB)-1)<<this.DB|this[0]},BigInteger.prototype.byteValue=function bnByteValue(){return 0==this.t?this.s:this[0]<<24>>24},BigInteger.prototype.shortValue=function bnShortValue(){return 0==this.t?this.s:this[0]<<16>>16},BigInteger.prototype.signum=function bnSigNum(){return this.s<0?-1:this.t<=0||1==this.t&&this[0]<=0?0:1},BigInteger.prototype.toByteArray=function bnToByteArray(){var e=this.t,t=new Array;t[0]=this.s;var r,n=this.DB-e*this.DB%8,i=0;if(e-- >0)for(n<this.DB&&(r=this[e]>>n)!=(this.s&this.DM)>>n&&(t[i++]=r|this.s<<this.DB-n);e>=0;)n<8?(r=(this[e]&(1<<n)-1)<<8-n,r|=this[--e]>>(n+=this.DB-8)):(r=this[e]>>(n-=8)&255,n<=0&&(n+=this.DB,--e)),0!=(128&r)&&(r|=-256),0==i&&(128&this.s)!=(128&r)&&++i,(i>0||r!=this.s)&&(t[i++]=r);return t},BigInteger.prototype.equals=function bnEquals(e){return 0==this.compareTo(e)},BigInteger.prototype.min=function bnMin(e){return this.compareTo(e)<0?this:e},BigInteger.prototype.max=function bnMax(e){return this.compareTo(e)>0?this:e},BigInteger.prototype.and=function bnAnd(e){var t=nbi();return this.bitwiseTo(e,op_and,t),t},BigInteger.prototype.or=function bnOr(e){var t=nbi();return this.bitwiseTo(e,op_or,t),t},BigInteger.prototype.xor=function bnXor(e){var t=nbi();return this.bitwiseTo(e,op_xor,t),t},BigInteger.prototype.andNot=function bnAndNot(e){var t=nbi();return this.bitwiseTo(e,op_andnot,t),t},BigInteger.prototype.not=function bnNot(){for(var e=nbi(),t=0;t<this.t;++t)e[t]=this.DM&~this[t];return e.t=this.t,e.s=~this.s,e},BigInteger.prototype.shiftLeft=function bnShiftLeft(e){var t=nbi();return e<0?this.rShiftTo(-e,t):this.lShiftTo(e,t),t},BigInteger.prototype.shiftRight=function bnShiftRight(e){var t=nbi();return e<0?this.lShiftTo(-e,t):this.rShiftTo(e,t),t},BigInteger.prototype.getLowestSetBit=function bnGetLowestSetBit(){for(var e=0;e<this.t;++e)if(0!=this[e])return e*this.DB+lbit(this[e]);return this.s<0?this.t*this.DB:-1},BigInteger.prototype.bitCount=function bnBitCount(){for(var e=0,t=this.s&this.DM,r=0;r<this.t;++r)e+=cbit(this[r]^t);return e},BigInteger.prototype.testBit=function bnTestBit(e){var t=Math.floor(e/this.DB);return t>=this.t?0!=this.s:0!=(this[t]&1<<e%this.DB)},BigInteger.prototype.setBit=function bnSetBit(e){return this.changeBit(e,op_or)},BigInteger.prototype.clearBit=function bnClearBit(e){return this.changeBit(e,op_andnot)},BigInteger.prototype.flipBit=function bnFlipBit(e){return this.changeBit(e,op_xor)},BigInteger.prototype.add=function bnAdd(e){var t=nbi();return this.addTo(e,t),t},BigInteger.prototype.subtract=function bnSubtract(e){var t=nbi();return this.subTo(e,t),t},BigInteger.prototype.multiply=function bnMultiply(e){var t=nbi();return this.multiplyTo(e,t),t},BigInteger.prototype.divide=function bnDivide(e){var t=nbi();return this.divRemTo(e,t,null),t},BigInteger.prototype.remainder=function bnRemainder(e){var t=nbi();return this.divRemTo(e,null,t),t},BigInteger.prototype.divideAndRemainder=function bnDivideAndRemainder(e){var t=nbi(),r=nbi();return this.divRemTo(e,t,r),new Array(t,r)},BigInteger.prototype.modPow=function bnModPow(e,t){var r,n,i=e.bitLength(),o=nbv(1);if(i<=0)return o;r=i<18?1:i<48?3:i<144?4:i<768?5:6,n=i<8?new Classic(t):t.isEven()?new Barrett(t):new Montgomery(t);var s=new Array,a=3,u=r-1,c=(1<<r)-1;if(s[1]=n.convert(this),r>1){var h=nbi();for(n.sqrTo(s[1],h);a<=c;)s[a]=nbi(),n.mulTo(h,s[a-2],s[a]),a+=2}var f,l,g=e.t-1,p=!0,d=nbi();for(i=nbits(e[g])-1;g>=0;){for(i>=u?f=e[g]>>i-u&c:(f=(e[g]&(1<<i+1)-1)<<u-i,g>0&&(f|=e[g-1]>>this.DB+i-u)),a=r;0==(1&f);)f>>=1,--a;if((i-=a)<0&&(i+=this.DB,--g),p)s[f].copyTo(o),p=!1;else{for(;a>1;)n.sqrTo(o,d),n.sqrTo(d,o),a-=2;a>0?n.sqrTo(o,d):(l=o,o=d,d=l),n.mulTo(d,s[f],o)}for(;g>=0&&0==(e[g]&1<<i);)n.sqrTo(o,d),l=o,o=d,d=l,--i<0&&(i=this.DB-1,--g)}return n.revert(o)},BigInteger.prototype.modInverse=function bnModInverse(e){var t=e.isEven();if(this.isEven()&&t||0==e.signum())return BigInteger.ZERO;for(var r=e.clone(),n=this.clone(),i=nbv(1),o=nbv(0),s=nbv(0),a=nbv(1);0!=r.signum();){for(;r.isEven();)r.rShiftTo(1,r),t?(i.isEven()&&o.isEven()||(i.addTo(this,i),o.subTo(e,o)),i.rShiftTo(1,i)):o.isEven()||o.subTo(e,o),o.rShiftTo(1,o);for(;n.isEven();)n.rShiftTo(1,n),t?(s.isEven()&&a.isEven()||(s.addTo(this,s),a.subTo(e,a)),s.rShiftTo(1,s)):a.isEven()||a.subTo(e,a),a.rShiftTo(1,a);r.compareTo(n)>=0?(r.subTo(n,r),t&&i.subTo(s,i),o.subTo(a,o)):(n.subTo(r,n),t&&s.subTo(i,s),a.subTo(o,a))}return 0!=n.compareTo(BigInteger.ONE)?BigInteger.ZERO:a.compareTo(e)>=0?a.subtract(e):a.signum()<0?(a.addTo(e,a),a.signum()<0?a.add(e):a):a},BigInteger.prototype.pow=function bnPow(e){return this.exp(e,new NullExp)},BigInteger.prototype.gcd=function bnGCD(e){var t=this.s<0?this.negate():this.clone(),r=e.s<0?e.negate():e.clone();if(t.compareTo(rLine truncated
|
||||
/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/
|
||||
*/function rng_seed_time(){!function rng_seed_int(e){U[M++]^=255&e,U[M++]^=e>>8&255,U[M++]^=e>>16&255,U[M++]^=e>>24&255,M>=L&&(M-=L)}((new Date).getTime())}if(null==U){var D;if(U=new Array,M=0,void 0!==p&&(void 0!==p.crypto||void 0!==p.msCrypto)){var N=p.crypto||p.msCrypto;if(N.getRandomValues){var O=new Uint8Array(32);for(N.getRandomValues(O),D=0;D<32;++D)U[M++]=O[D]}else if("Netscape"==u.appName&&u.appVersion<"5"){var H=p.crypto.random(32);for(D=0;D<H.length;++D)U[M++]=255&H.charCodeAt(D)}}for(;M<L;)D=Math.floor(65536*Math.random()),U[M++]=D>>>8,U[M++]=255&D;M=0,rng_seed_time()}function rng_get_byte(){if(null==T){for(rng_seed_time(),(T=function prng_newstate(){return new Arcfour}()).init(U),M=0;M<U.length;++M)U[M]=0;M=0}return T.next()}function SecureRandom(){}
|
||||
/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/
|
||||
*/
|
||||
function parseBigInt(e,t){return new BigInteger(e,t)}function oaep_mgf1_arr(e,t,r){for(var n="",i=0;n.length<t;)n+=r(String.fromCharCode.apply(String,e.concat([(4278190080&i)>>24,(16711680&i)>>16,(65280&i)>>8,255&i]))),i+=1;return n}function RSAKey(){this.n=null,this.e=0,this.d=null,this.p=null,this.q=null,this.dmp1=null,this.dmq1=null,this.coeff=null}
|
||||
/*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/
|
||||
*/
|
||||
function ECFieldElementFp(e,t){this.x=t,this.q=e}function ECPointFp(e,t,r,n){this.curve=e,this.x=t,this.y=r,this.z=null==n?BigInteger.ONE:n,this.zinv=null}function ECCurveFp(e,t,r){this.q=e,this.a=this.fromBigInteger(t),this.b=this.fromBigInteger(r),this.infinity=new ECPointFp(this,null,null)}SecureRandom.prototype.nextBytes=function rng_get_bytes(e){var t;for(t=0;t<e.length;++t)e[t]=rng_get_byte()},RSAKey.prototype.doPublic=function RSADoPublic(e){return e.modPowInt(this.e,this.n)},RSAKey.prototype.setPublic=function RSASetPublic(e,t){if(this.isPublic=!0,this.isPrivate=!1,"string"!=typeof e)this.n=e,this.e=t;else{if(!(null!=e&&null!=t&&e.length>0&&t.length>0))throw"Invalid RSA public key";this.n=parseBigInt(e,16),this.e=parseInt(t,16)}},RSAKey.prototype.encrypt=function RSAEncrypt(e){var t=function pkcs1pad2(e,t){if(t<e.length+11)throw"Message too long for RSA";for(var r=new Array,n=e.length-1;n>=0&&t>0;){var i=e.charCodeAt(n--);i<128?r[--t]=i:i>127&&i<2048?(r[--t]=63&i|128,r[--t]=i>>6|192):(r[--t]=63&i|128,r[--t]=i>>6&63|128,r[--t]=i>>12|224)}r[--t]=0;for(var o=new SecureRandom,s=new Array;t>2;){for(s[0]=0;0==s[0];)o.nextBytes(s);r[--t]=s[0]}return r[--t]=2,r[--t]=0,new BigInteger(r)}(e,this.n.bitLength()+7>>3);if(null==t)return null;var r=this.doPublic(t);if(null==r)return null;var n=r.toString(16);return 0==(1&n.length)?n:"0"+n},RSAKey.prototype.encryptOAEP=function RSAEncryptOAEP(e,t,r){var n=function oaep_pad(e,t,r,n){var i=K.crypto.MessageDigest,o=K.crypto.Util,s=null;if(r||(r="sha1"),"string"==typeof r&&(s=i.getCanonicalAlgName(r),n=i.getHashLength(s),r=function f(e){return hextorstr(o.hashHex(rstrtohex(e),s))}),e.length+2*n+2>t)throw"Message too long for RSA";var a,u="";for(a=0;a<t-e.length-2*n-2;a+=1)u+="\0";var c=r("")+u+""+e,h=new Array(n);(new SecureRandom).nextBytes(h);var l=oaep_mgf1_arr(h,c.length,r),g=[];for(a=0;a<c.length;a+=1)g[a]=c.charCodeAt(a)^l.charCodeAt(a);var p=oaep_mgf1_arr(g,h.length,r),d=[0];for(a=0;a<h.length;a+=1)d[a+1]=h[a]^p.charCodeAt(a);return new BigInteger(d.concat(g))}(e,this.n.bitLength()+7>>3,t,r);if(null==n)return null;var i=this.doPublic(n);if(null==i)return null;var o=i.toString(16);return 0==(1&o.length)?o:"0"+o},RSAKey.prototype.type="RSA",ECFieldElementFp.prototype.equals=function feFpEquals(e){return e==this||this.q.equals(e.q)&&this.x.equals(e.x)},ECFieldElementFp.prototype.toBigInteger=function feFpToBigInteger(){return this.x},ECFieldElementFp.prototype.negate=function feFpNegate(){return new ECFieldElementFp(this.q,this.x.negate().mod(this.q))},ECFieldElementFp.prototype.add=function feFpAdd(e){return new ECFieldElementFp(this.q,this.x.add(e.toBigInteger()).mod(this.q))},ECFieldElementFp.prototype.subtract=function feFpSubtract(e){return new ECFieldElementFp(this.q,this.x.subtract(e.toBigInteger()).mod(this.q))},ECFieldElementFp.prototype.multiply=function feFpMultiply(e){return new ECFieldElementFp(this.q,this.x.multiply(e.toBigInteger()).mod(this.q))},ECFieldElementFp.prototype.square=function feFpSquare(){return new ECFieldElementFp(this.q,this.x.square().mod(this.q))},ECFieldElementFp.prototype.divide=function feFpDivide(e){return new ECFieldElementFp(this.q,this.x.multiply(e.toBigInteger().modInverse(this.q)).mod(this.q))},ECPointFp.prototype.getX=function pointFpGetX(){return null==this.zinv&&(this.zinv=this.z.modInverse(this.curve.q)),this.curve.fromBigInteger(this.x.toBigInteger().multiply(this.zinv).mod(this.curve.q))},ECPointFp.prototype.getY=function pointFpGetY(){return null==this.zinv&&(this.zinv=this.z.modInverse(this.curve.q)),this.curve.fromBigInteger(this.y.toBigInteger().multiply(this.zinv).mod(this.curve.q))},ECPointFp.prototype.equals=function pointFpEquals(e){return e==this||(this.isInfinity()?e.isInfinity():e.isInfinity()?this.isInfinity():!!e.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(e.z)).mod(this.curve.q).equals(BigInteger.ZERO)&&e.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(e.z)).mod(this.curve.q).equals(BigInteger.ZERO))},ECPointFp.prototype.isInfinity=function pointFpIsInfinity(){return null==this.x&&null==this.y||this.z.equals(BigInteger.ZERO)&&!this.y.toBigInteger().equals(BigInteger.ZERO)},ECPointFp.prototype.negate=function pointFpNegate(){return new ECPointFp(this.curve,this.x,this.y.negate(),this.z)},ECPointFp.prototype.add=function pointFpAdd(e){if(this.isInfinity())return e;if(e.isInfinity())return this;var t=e.y.toBigInteger().multiply(this.z).subtract(this.y.toBigInteger().multiply(e.z)).mod(this.curve.q),r=e.x.toBigInteger().multiply(this.z).subtract(this.x.toBigInteger().multiply(e.z)).mod(this.curve.q);if(BigInteger.ZERO.equals(r))return BigInteger.ZERO.equals(t)?this.twice():this.curve.getInfinity();var n=new BigInteger("3"),i=this.x.toBigInteger(),o=this.y.toBigInteger(),s=(e.x.toBigInteger(),e.y.toBigInteger(),r.square()),a=s.multiply(r),u=i.multiply(s),c=t.square().multiply(this.z),h=c.subtract(u.shiftLeft(1)).multiply(e.z).subtract(a).multiply(rLine truncated
|
||||
/*! Mike Samuel (c) 2009 | code.google.com/p/json-sans-eval
|
||||
*/
|
||||
var K,q,W,V=function(){var e=new RegExp('(?:false|true|null|[\\{\\}\\[\\]]|(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)|(?:"(?:[^\\0-\\x08\\x0a-\\x1f"\\\\]|\\\\(?:["/\\\\bfnrt]|u[0-9A-Fa-f]{4}))*"))',"g"),t=new RegExp("\\\\(?:([^u])|u(.{4}))","g"),r={'"':'"',"/":"/","\\":"\\",b:"\b",f:"\f",n:"\n",r:"\r",t:"\t"};function h(e,t,n){return t?r[t]:String.fromCharCode(parseInt(n,16))}var n=new String(""),o=(Object,Array,Object.hasOwnProperty);return function(r,a){var u,c,f=r.match(e),l=f[0],g=!1;"{"===l?u={}:"["===l?u=[]:(u=[],g=!0);for(var p=[u],d=1-g,v=f.length;d<v;++d){var y;switch((l=f[d]).charCodeAt(0)){default:(y=p[0])[c||y.length]=+l,c=void 0;break;case 34:if(-1!==(l=l.substring(1,l.length-1)).indexOf("\\")&&(l=l.replace(t,h)),y=p[0],!c){if(!(y instanceof Array)){c=l||n;break}c=y.length}y[c]=l,c=void 0;break;case 91:y=p[0],p.unshift(y[c||y.length]=[]),c=void 0;break;case 93:p.shift();break;case 102:(y=p[0])[c||y.length]=!1,c=void 0;break;case 110:(y=p[0])[c||y.length]=null,c=void 0;break;case 116:(y=p[0])[c||y.length]=!0,c=void 0;break;case 123:y=p[0],p.unshift(y[c||y.length]={}),c=void 0;break;case 125:p.shift()}}if(g){if(1!==p.length)throw new Error;u=u[0]}else if(p.length)throw new Error;if(a){u=function s(e,t){var r=e[t];if(r&&"object"===(void 0===r?"undefined":i(r))){var n=null;for(var u in r)if(o.call(r,u)&&r!==e){var c=s(r,u);void 0!==c?r[u]=c:(n||(n=[]),n.push(u))}if(n)for(var h=n.length;--h>=0;)delete r[n[h]]}return a.call(e,t,r)}({"":u},"")}return u}}(),J=new function(){};function stoBA(e){for(var t=new Array,r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}function BAtos(e){for(var t="",r=0;r<e.length;r++)t+=String.fromCharCode(e[r]);return t}function BAtohex(e){for(var t="",r=0;r<e.length;r++){var n=e[r].toString(16);1==n.length&&(n="0"+n),t+=n}return t}function stohex(e){return BAtohex(stoBA(e))}function b64tob64u(e){return e=(e=(e=e.replace(/\=/g,"")).replace(/\+/g,"-")).replace(/\//g,"_")}function b64utob64(e){return e.length%4==2?e+="==":e.length%4==3&&(e+="="),e=(e=e.replace(/-/g,"+")).replace(/_/g,"/")}function hextob64u(e){return e.length%2==1&&(e="0"+e),b64tob64u(hex2b64(e))}function b64utohex(e){return b64tohex(b64utob64(e))}function utf8tohex(e){return uricmptohex(encodeURIComponentAll(e))}function hextoutf8(e){return decodeURIComponent(hextouricmp(e))}function hextorstr(e){for(var t="",r=0;r<e.length-1;r+=2)t+=String.fromCharCode(parseInt(e.substr(r,2),16));return t}function rstrtohex(e){for(var t="",r=0;r<e.length;r++)t+=("0"+e.charCodeAt(r).toString(16)).slice(-2);return t}function hextob64(e){return hex2b64(e)}function hextob64nl(e){var t=hextob64(e).replace(/(.{64})/g,"$1\r\n");return t=t.replace(/\r\n$/,"")}function b64nltohex(e){return b64tohex(e.replace(/[^0-9A-Za-z\/+=]*/g,""))}function hextopem(e,t){return"-----BEGIN "+t+"-----\r\n"+hextob64nl(e)+"\r\n-----END "+t+"-----\r\n"}function pemtohex(e,t){if(-1==e.indexOf("-----BEGIN "))throw"can't find PEM header: "+t;return b64nltohex(e=void 0!==t?(e=e.replace("-----BEGIN "+t+"-----","")).replace("-----END "+t+"-----",""):(e=e.replace(/-----BEGIN [^-]+-----/,"")).replace(/-----END [^-]+-----/,""))}function zulutomsec(e){var t,r,n,i,o,s,a,u,c,h,f;if(f=e.match(/^(\d{2}|\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(|\.\d+)Z$/))return u=f[1],t=parseInt(u),2===u.length&&(50<=t&&t<100?t=1900+t:0<=t&&t<50&&(t=2e3+t)),r=parseInt(f[2])-1,n=parseInt(f[3]),i=parseInt(f[4]),o=parseInt(f[5]),s=parseInt(f[6]),a=0,""!==(c=f[7])&&(h=(c.substr(1)+"00").substr(0,3),a=parseInt(h)),Date.UTC(t,r,n,i,o,s,a);throw"unsupported zulu format: "+e}function zulutosec(e){return~~(zulutomsec(e)/1e3)}function uricmptohex(e){return e.replace(/%/g,"")}function hextouricmp(e){return e.replace(/(..)/g,"%$1")}function ipv6tohex(e){var t="malformed IPv6 address";if(!e.match(/^[0-9A-Fa-f:]+$/))throw t;var r=(e=e.toLowerCase()).split(":").length-1;if(r<2)throw t;var n=":".repeat(7-r+2),i=(e=e.replace("::",n)).split(":");if(8!=i.length)throw t;for(var o=0;o<8;o++)i[o]=("0000"+i[o]).slice(-4);return i.join("")}function hextoipv6(e){if(!e.match(/^[0-9A-Fa-f]{32}$/))throw"malformed IPv6 address octet";for(var t=(e=e.toLowerCase()).match(/.{1,4}/g),r=0;r<8;r++)t[r]=t[r].replace(/^0+/,""),""==t[r]&&(t[r]="0");var n=(e=":"+t.join(":")+":").match(/:(0:){2,}/g);if(null===n)return e.slice(1,-1);var i="";for(r=0;r<n.length;r++)n[r].length>i.length&&(i=n[r]);return(e=e.replace(i,"::")).slice(1,-1)}function hextoip(e){var t="malformed hex value";if(!e.match(/^([0-9A-Fa-f][0-9A-Fa-f]){1,}$/))throw t;if(8!=e.length)return 32==e.length?hextoipv6(e):e;try{return parseInt(e.substr(0,2),16)+"."+parseInt(e.substr(2,2),16)+"."+parseInt(e.substr(4,2),16)+"."+parseInt(e.substr(6,2),16)}catch(e){throw t}}function encodeURIComponentAll(e){for(var t=encodeURIComponent(e),r="",n=0;n<t.length;n++)"%"==t[n]?(r+=t.substr(n,3),n+=2):r=r+"%"+stohex(t[n]);return r}function hextoposhex(e){return e.length%2==1?"0"+e:e.substr(0,1)>"7"?"00"+e:e}J.getLblen=function(e,t){if("8"!=e.substr(t+2,Line truncated
|
||||
+1
@@ -0,0 +1 @@
|
||||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n()}(0,function(){"use strict";function e(e){var n=this.constructor;return this.then(function(t){return n.resolve(e()).then(function(){return t})},function(t){return n.resolve(e()).then(function(){return n.reject(t)})})}function n(){}function t(e){if(!(this instanceof t))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=undefined,this._deferreds=[],u(e,this)}function o(e,n){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,t._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null!==t){var o;try{o=t(e._value)}catch(f){return void i(n.promise,f)}r(n.promise,o)}else(1===e._state?r:i)(n.promise,e._value)})):e._deferreds.push(n)}function r(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var o=n.then;if(n instanceof t)return e._state=3,e._value=n,void f(e);if("function"==typeof o)return void u(function(e,n){return function(){e.apply(n,arguments)}}(o,n),e)}e._state=1,e._value=n,f(e)}catch(r){i(e,r)}}function i(e,n){e._state=2,e._value=n,f(e)}function f(e){2===e._state&&0===e._deferreds.length&&t._immediateFn(function(){e._handled||t._unhandledRejectionFn(e._value)});for(var n=0,r=e._deferreds.length;r>n;n++)o(e,e._deferreds[n]);e._deferreds=null}function u(e,n){var t=!1;try{e(function(e){t||(t=!0,r(n,e))},function(e){t||(t=!0,i(n,e))})}catch(o){if(t)return;t=!0,i(n,o)}}var c=setTimeout;t.prototype["catch"]=function(e){return this.then(null,e)},t.prototype.then=function(e,t){var r=new this.constructor(n);return o(this,new function(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}(e,t,r)),r},t.prototype["finally"]=e,t.all=function(e){return new t(function(n,t){function o(e,f){try{if(f&&("object"==typeof f||"function"==typeof f)){var u=f.then;if("function"==typeof u)return void u.call(f,function(n){o(e,n)},t)}r[e]=f,0==--i&&n(r)}catch(c){t(c)}}if(!e||"undefined"==typeof e.length)throw new TypeError("Promise.all accepts an array");var r=Array.prototype.slice.call(e);if(0===r.length)return n([]);for(var i=r.length,f=0;r.length>f;f++)o(f,r[f])})},t.resolve=function(e){return e&&"object"==typeof e&&e.constructor===t?e:new t(function(n){n(e)})},t.reject=function(e){return new t(function(n,t){t(e)})},t.race=function(e){return new t(function(n,t){for(var o=0,r=e.length;r>o;o++)e[o].then(n,t)})},t._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){c(e,0)},t._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var l=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"Promise"in l?l.Promise.prototype["finally"]||(l.Promise.prototype["finally"]=e):l.Promise=t});
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Silent OpenID Connect Token Refresh Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.7/es5-shim.min.js"></script>
|
||||
<script type="text/javascript" src='es6-shim.min.js'></script>
|
||||
<script type="text/javascript" src="oidc-client.min.js"></script>
|
||||
<script type="text/javascript" src='polyfill.min.js'></script>
|
||||
<script type="text/javascript">
|
||||
new UserManager().signinSilentCallback().catch(console.error);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,198 @@
|
||||
import './config';
|
||||
// Polyfills
|
||||
// PWA Only?
|
||||
import 'core-js/features/array/flat';
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
|
||||
import {
|
||||
CommandsManager,
|
||||
ExtensionManager,
|
||||
HotkeysManager,
|
||||
utils,
|
||||
} from '@ohif/core';
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
getUserManagerForOpenIdConnectClient,
|
||||
initWebWorkers,
|
||||
} from './utils/index.js';
|
||||
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||
|
||||
// ~~ EXTENSIONS
|
||||
import { GenericViewerCommands, MeasurementsPanel } from './appExtensions';
|
||||
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
|
||||
import OHIFStandaloneViewer from './OHIFStandaloneViewer';
|
||||
import { OidcProvider } from 'redux-oidc';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import WhiteLabellingContext from './WhiteLabellingContext';
|
||||
import { getActiveContexts } from './store/layout/selectors.js';
|
||||
import i18n from '@ohif/i18n';
|
||||
import setupTools from './setupTools.js';
|
||||
import store from './store';
|
||||
import UserManagerContext from './UserManagerContext';
|
||||
|
||||
// ~~~~ APP SETUP
|
||||
initCornerstoneTools({
|
||||
globalToolSyncEnabled: true,
|
||||
showSVGCursors: true,
|
||||
});
|
||||
|
||||
const commandsManagerConfig = {
|
||||
getAppState: () => store.getState(),
|
||||
getActiveContexts: () => getActiveContexts(store.getState()),
|
||||
};
|
||||
|
||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||
const hotkeysManager = new HotkeysManager(commandsManager);
|
||||
const extensionManager = new ExtensionManager({ commandsManager });
|
||||
|
||||
// CornerstoneTools and labeling/measurements?
|
||||
setupTools(store);
|
||||
// ~~~~ END APP SETUP
|
||||
|
||||
// TODO[react] Use a provider when the whole tree is React
|
||||
window.store = store;
|
||||
|
||||
class App extends Component {
|
||||
static propTypes = {
|
||||
routerBasename: PropTypes.string.isRequired,
|
||||
servers: PropTypes.object.isRequired,
|
||||
//
|
||||
oidc: PropTypes.array,
|
||||
whiteLabelling: PropTypes.object,
|
||||
extensions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
whiteLabelling: {},
|
||||
oidc: [],
|
||||
extensions: [],
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (this.props.oidc.length) {
|
||||
const firstOpenIdClient = this.props.oidc[0];
|
||||
|
||||
const { protocol, host } = window.location;
|
||||
const { routerBasename } = this.props;
|
||||
const baseUri = `${protocol}//${host}${routerBasename}`;
|
||||
|
||||
const redirect_uri = firstOpenIdClient.redirect_uri || '/callback';
|
||||
const silent_redirect_uri =
|
||||
firstOpenIdClient.silent_redirect_uri || '/silent-refresh.html';
|
||||
const post_logout_redirect_uri =
|
||||
firstOpenIdClient.post_logout_redirect_uri || '/';
|
||||
|
||||
const openIdConnectConfiguration = Object.assign({}, firstOpenIdClient, {
|
||||
redirect_uri: _makeAbsoluteIfNecessary(redirect_uri, baseUri),
|
||||
silent_redirect_uri: _makeAbsoluteIfNecessary(
|
||||
silent_redirect_uri,
|
||||
baseUri
|
||||
),
|
||||
post_logout_redirect_uri: _makeAbsoluteIfNecessary(
|
||||
post_logout_redirect_uri,
|
||||
baseUri
|
||||
),
|
||||
});
|
||||
|
||||
this.userManager = getUserManagerForOpenIdConnectClient(
|
||||
store,
|
||||
openIdConnectConfiguration
|
||||
);
|
||||
}
|
||||
|
||||
_initExtensions(this.props.extensions);
|
||||
_initServers(this.props.servers);
|
||||
initWebWorkers();
|
||||
}
|
||||
|
||||
render() {
|
||||
const userManager = this.userManager;
|
||||
|
||||
if (userManager) {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<OidcProvider store={store} userManager={userManager}>
|
||||
<UserManagerContext.Provider value={userManager}>
|
||||
<Router basename={this.props.routerBasename}>
|
||||
<WhiteLabellingContext.Provider
|
||||
value={this.props.whiteLabelling}
|
||||
>
|
||||
<OHIFStandaloneViewer userManager={userManager} />
|
||||
</WhiteLabellingContext.Provider>
|
||||
</Router>
|
||||
</UserManagerContext.Provider>
|
||||
</OidcProvider>
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Router basename={this.props.routerBasename}>
|
||||
<WhiteLabellingContext.Provider value={this.props.whiteLabelling}>
|
||||
<OHIFStandaloneViewer />
|
||||
</WhiteLabellingContext.Provider>
|
||||
</Router>
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param
|
||||
*/
|
||||
function _initExtensions(extensions) {
|
||||
const defaultExtensions = [
|
||||
GenericViewerCommands,
|
||||
MeasurementsPanel,
|
||||
OHIFCornerstoneExtension,
|
||||
];
|
||||
const mergedExtensions = defaultExtensions.concat(extensions);
|
||||
extensionManager.registerExtensions(mergedExtensions);
|
||||
|
||||
// Must run after extension commands are registered
|
||||
if (window.config.hotkeys) {
|
||||
hotkeysManager.setHotkeys(window.config.hotkeys, true);
|
||||
}
|
||||
}
|
||||
|
||||
function _initServers(servers) {
|
||||
if (servers) {
|
||||
utils.addServers(servers, store);
|
||||
}
|
||||
}
|
||||
|
||||
function _isAbsoluteUrl(url) {
|
||||
return url.includes('http://') || url.includes('https://');
|
||||
}
|
||||
|
||||
function _makeAbsoluteIfNecessary(url, base_url) {
|
||||
if (_isAbsoluteUrl(url)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// Make sure base_url and url are not duplicating slashes
|
||||
if (base_url[base_url.length - 1] === '/') {
|
||||
base_url = base_url.slice(0, base_url.length - 1);
|
||||
}
|
||||
|
||||
return base_url + url;
|
||||
}
|
||||
|
||||
export default App;
|
||||
export { commandsManager, extensionManager, hotkeysManager };
|
||||
@@ -0,0 +1,37 @@
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
background-color: black;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: Roboto, OpenSans, HelveticaNeue-Light, Helvetica Neue Light,
|
||||
Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 14px;
|
||||
line-height: 1.42857143;
|
||||
}
|
||||
|
||||
#root .fade-enter {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#root .fade-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
|
||||
#root .fade-exit {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#root .fade-exit-active {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter, matchPath } from 'react-router';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { NProgress } from '@tanem/react-nprogress';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import { connect } from 'react-redux';
|
||||
import { ViewerbaseDragDropContext } from '@ohif/ui';
|
||||
import { SignoutCallbackComponent } from 'redux-oidc';
|
||||
// import asyncComponent from './components/AsyncComponent.js'
|
||||
import IHEInvokeImageDisplay from './routes/IHEInvokeImageDisplay.js';
|
||||
import ViewerRouting from './routes/ViewerRouting.js';
|
||||
import ViewerLocalFileData from './connectedComponents/ViewerLocalFileData.js';
|
||||
import StudyListRouting from './studylist/StudyListRouting.js';
|
||||
import StandaloneRouting from './routes/StandaloneRouting.js';
|
||||
import CallbackPage from './routes/CallbackPage.js';
|
||||
import NotFound from './routes/NotFound.js';
|
||||
import { Bar, Container } from './components/LoadingBar/';
|
||||
import './OHIFStandaloneViewer.css';
|
||||
import './variables.css';
|
||||
import './theme-tide.css';
|
||||
|
||||
// Dynamic Import Routes (CodeSplitting)
|
||||
// const IHEInvokeImageDisplay = asyncComponent(() =>
|
||||
// import('./routes/IHEInvokeImageDisplay.js')
|
||||
// )
|
||||
// const ViewerRouting = asyncComponent(() => import('./routes/ViewerRouting.js'))
|
||||
// const StudyListRouting = asyncComponent(() =>
|
||||
// import('./studylist/StudyListRouting.js')
|
||||
// )
|
||||
// const StandaloneRouting = asyncComponent(() =>
|
||||
// import('./routes/StandaloneRouting.js')
|
||||
// )
|
||||
// const CallbackPage = asyncComponent(() => import('./CallbackPage.js'))
|
||||
//
|
||||
|
||||
const reload = () => window.location.reload();
|
||||
|
||||
class OHIFStandaloneViewer extends Component {
|
||||
state = {
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
history: PropTypes.object.isRequired,
|
||||
user: PropTypes.object,
|
||||
setContext: PropTypes.func,
|
||||
userManager: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.unlisten = this.props.history.listen((location, action) => {
|
||||
if (this.props.setContext) {
|
||||
this.props.setContext(window.location.pathname);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unlisten();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user, userManager } = this.props;
|
||||
|
||||
const userNotLoggedIn = userManager && (!user || user.expired);
|
||||
if (userNotLoggedIn) {
|
||||
const pathname = this.props.location.pathname;
|
||||
|
||||
if (pathname !== '/callback') {
|
||||
sessionStorage.setItem('ohif-redirect-to', pathname);
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/silent-refresh.html" onEnter={reload} />
|
||||
<Route
|
||||
exact
|
||||
path="/logout-redirect"
|
||||
render={() => (
|
||||
<SignoutCallbackComponent
|
||||
userManager={userManager}
|
||||
successCallback={() => console.log('Signout successful')}
|
||||
errorCallback={error => {
|
||||
console.warn(error);
|
||||
console.warn('Signout failed');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/callback"
|
||||
render={() => <CallbackPage userManager={userManager} />}
|
||||
/>
|
||||
<Route
|
||||
component={() => {
|
||||
userManager.getUser().then(user => {
|
||||
if (user) {
|
||||
userManager.signinSilent();
|
||||
} else {
|
||||
userManager.signinRedirect();
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this approach for routing is caused by the conflict between
|
||||
* react-transition-group and react-router's <Switch> component.
|
||||
*
|
||||
* See http://reactcommunity.org/react-transition-group/with-react-router/
|
||||
*/
|
||||
const routes = [
|
||||
{
|
||||
path: '/local',
|
||||
Component: ViewerLocalFileData,
|
||||
},
|
||||
{
|
||||
path: '/viewer',
|
||||
Component: StandaloneRouting,
|
||||
},
|
||||
{
|
||||
path: '/viewer/:studyInstanceUids',
|
||||
Component: ViewerRouting,
|
||||
},
|
||||
{
|
||||
path: '/study/:studyInstanceUids/series/:seriesInstanceUids',
|
||||
Component: ViewerRouting,
|
||||
},
|
||||
{
|
||||
path: '/IHEInvokeImageDisplay',
|
||||
Component: IHEInvokeImageDisplay,
|
||||
},
|
||||
];
|
||||
|
||||
const showStudyList =
|
||||
window.config && window.config.showStudyList !== undefined
|
||||
? window.config.showStudyList
|
||||
: true;
|
||||
if (showStudyList) {
|
||||
routes.push({
|
||||
path: '/studylist',
|
||||
Component: StudyListRouting,
|
||||
});
|
||||
routes.push({
|
||||
path: '/',
|
||||
Component: StudyListRouting,
|
||||
});
|
||||
}
|
||||
|
||||
const currentPath = this.props.location.pathname;
|
||||
const noMatchingRoutes = !routes.find(r =>
|
||||
matchPath(currentPath, {
|
||||
path: r.path,
|
||||
exact: true,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NProgress isAnimating={this.state.isLoading}>
|
||||
{({ isFinished, progress, animationDuration }) => (
|
||||
<Container
|
||||
isFinished={isFinished}
|
||||
animationDuration={animationDuration}
|
||||
>
|
||||
<Bar progress={progress} animationDuration={animationDuration} />
|
||||
</Container>
|
||||
)}
|
||||
</NProgress>
|
||||
<Route exact path="/silent-refresh.html" onEnter={reload} />
|
||||
<Route exact path="/logout-redirect.html" onEnter={reload} />
|
||||
{!noMatchingRoutes &&
|
||||
routes.map(({ path, Component }) => (
|
||||
<Route key={path} exact path={path}>
|
||||
{({ match }) => (
|
||||
<CSSTransition
|
||||
in={match !== null}
|
||||
timeout={300}
|
||||
classNames="fade"
|
||||
unmountOnExit
|
||||
onEnter={() => {
|
||||
this.setState({ isLoading: true });
|
||||
}}
|
||||
onEntered={() => {
|
||||
this.setState({ isLoading: false });
|
||||
}}
|
||||
>
|
||||
{match === null ? (
|
||||
<></>
|
||||
) : (
|
||||
<Component match={match} location={this.props.location} />
|
||||
)}
|
||||
</CSSTransition>
|
||||
)}
|
||||
</Route>
|
||||
))}
|
||||
{noMatchingRoutes && <NotFound />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
user: state.oidc.user,
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedOHIFStandaloneViewer = connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(OHIFStandaloneViewer);
|
||||
|
||||
export default ViewerbaseDragDropContext(
|
||||
withRouter(ConnectedOHIFStandaloneViewer)
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
const UserManagerContext = React.createContext();
|
||||
|
||||
export default UserManagerContext;
|
||||
@@ -0,0 +1,10 @@
|
||||
import OHIFLogo from './components/OHIFLogo/OHIFLogo.js';
|
||||
import React from 'react';
|
||||
|
||||
const defaultContextValues = {
|
||||
logoComponent: OHIFLogo(),
|
||||
};
|
||||
|
||||
const WhiteLabellingContext = React.createContext(defaultContextValues);
|
||||
|
||||
export default WhiteLabellingContext;
|
||||
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
webWorkerManager: {
|
||||
initialize: jest.fn(),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
// https://jestjs.io/docs/en/webpack#handling-static-assets
|
||||
|
||||
module.exports = 'test-file-stub';
|
||||
@@ -0,0 +1,4 @@
|
||||
const createUserManager = () => jest.fn().mockReturnValue({});
|
||||
const loadUser = () => jest.fn();
|
||||
|
||||
export { createUserManager, loadUser };
|
||||
@@ -0,0 +1,9 @@
|
||||
const _ = require('lodash');
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
// JSDom's CSS Parser has limited support for certain features
|
||||
// This supresses error warnings caused by it
|
||||
console.error = function(msg) {
|
||||
if (_.startsWith(msg, 'Error: Could not parse CSS stylesheet')) return;
|
||||
originalConsoleError(msg);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { redux } from "@ohif/core";
|
||||
import store from "./../../store";
|
||||
const { setViewportActive } = redux.actions;
|
||||
|
||||
const actions = {
|
||||
updateViewportDisplaySet: ({ direction }) => {
|
||||
// TODO
|
||||
// console.warn('updateDisplaySet: ', direction);
|
||||
},
|
||||
updateActiveViewport: ({ viewports, direction }) => {
|
||||
const { viewportSpecificData, activeViewportIndex } = viewports;
|
||||
const maxIndex = Object.keys(viewportSpecificData).length - 1;
|
||||
|
||||
let newIndex = activeViewportIndex + direction;
|
||||
newIndex = newIndex > maxIndex ? 0 : newIndex;
|
||||
newIndex = newIndex < 0 ? maxIndex : newIndex;
|
||||
|
||||
store.dispatch(setViewportActive(newIndex));
|
||||
}
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
// Next/Previous active viewport
|
||||
incrementActiveViewport: {
|
||||
commandFn: actions.updateActiveViewport,
|
||||
storeContexts: ["viewports"],
|
||||
options: { direction: 1 }
|
||||
},
|
||||
decrementActiveViewport: {
|
||||
commandFn: actions.updateActiveViewport,
|
||||
storeContexts: ["viewports"],
|
||||
options: { direction: -1 }
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
definitions,
|
||||
defaultContext: "VIEWER"
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import commandsModule from './commandsModule.js';
|
||||
|
||||
export default {
|
||||
id: 'generic-viewer-commands',
|
||||
getCommandsModule() {
|
||||
return commandsModule;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,356 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { MeasurementTable } from '@ohif/ui';
|
||||
import OHIF from '@ohif/core';
|
||||
import moment from 'moment';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
|
||||
//
|
||||
import jumpToRowItem from './jumpToRowItem.js';
|
||||
import getMeasurementLocationCallback from './../../lib/getMeasurementLocationCallback';
|
||||
|
||||
const { setViewportSpecificData } = OHIF.redux.actions;
|
||||
const { MeasurementApi } = OHIF.measurements;
|
||||
|
||||
function groupBy(list, props) {
|
||||
return list.reduce((a, b) => {
|
||||
(a[b[props]] = a[b[props]] || []).push(b);
|
||||
return a;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function getAllTools() {
|
||||
const config = OHIF.measurements.MeasurementApi.getConfiguration();
|
||||
let tools = [];
|
||||
config.measurementTools.forEach(
|
||||
toolGroup => (tools = tools.concat(toolGroup.childTools))
|
||||
);
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
function getMeasurementText(measurementData) {
|
||||
const { location, description } = measurementData;
|
||||
let text = '...';
|
||||
if (location) {
|
||||
text = location;
|
||||
if (description) {
|
||||
text += `(${description})`;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function getDataForEachMeasurementNumber(
|
||||
measurementNumberList,
|
||||
timepoints,
|
||||
displayFunction
|
||||
) {
|
||||
const data = [];
|
||||
// on each measurement number we should get each measurement data by available timepoint
|
||||
measurementNumberList.forEach(measurement => {
|
||||
timepoints.forEach(timepoint => {
|
||||
const eachData = {
|
||||
displayText: '...',
|
||||
};
|
||||
if (measurement.timepointId === timepoint.timepointId) {
|
||||
eachData.displayText = displayFunction(measurement);
|
||||
}
|
||||
data.push(eachData);
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function convertMeasurementsToTableData(toolCollections, timepoints) {
|
||||
const config = OHIF.measurements.MeasurementApi.getConfiguration();
|
||||
const toolGroups = config.measurementTools;
|
||||
const tools = getAllTools();
|
||||
|
||||
const tableMeasurements = toolGroups.map(toolGroup => {
|
||||
return {
|
||||
groupName: toolGroup.name,
|
||||
groupId: toolGroup.id,
|
||||
measurements: [],
|
||||
};
|
||||
});
|
||||
|
||||
Object.keys(toolCollections).forEach(toolId => {
|
||||
const toolMeasurements = toolCollections[toolId];
|
||||
const tool = tools.find(tool => tool.id === toolId);
|
||||
const { displayFunction } = tool.options.measurementTable;
|
||||
|
||||
// Group by measurementNumber so we can display then all in the same line
|
||||
const groupedMeasurements = groupBy(toolMeasurements, 'measurementNumber');
|
||||
|
||||
Object.keys(groupedMeasurements).forEach(groupedMeasurementsIndex => {
|
||||
const measurementNumberList =
|
||||
groupedMeasurements[groupedMeasurementsIndex];
|
||||
const measurementData = measurementNumberList[0];
|
||||
const {
|
||||
measurementNumber,
|
||||
lesionNamingNumber,
|
||||
toolType,
|
||||
} = measurementData;
|
||||
const measurementId = measurementData._id;
|
||||
|
||||
//check if all measurements with same measurementNumber will have same LABEL
|
||||
const tableMeasurement = {
|
||||
itemNumber: lesionNamingNumber,
|
||||
label: getMeasurementText(measurementData),
|
||||
measurementId,
|
||||
measurementNumber,
|
||||
lesionNamingNumber,
|
||||
toolType,
|
||||
hasWarnings: false, //TODO
|
||||
warningTitle: '', //TODO
|
||||
isSplitLesion: false, //TODO
|
||||
warningList: [], //TODO
|
||||
data: getDataForEachMeasurementNumber(
|
||||
measurementNumberList,
|
||||
timepoints,
|
||||
displayFunction
|
||||
),
|
||||
};
|
||||
|
||||
// find the group object for the tool
|
||||
const toolGroupMeasurements = tableMeasurements.find(group => {
|
||||
return group.groupId === tool.toolGroup;
|
||||
});
|
||||
// inject the new measurement for this measurementNumer
|
||||
toolGroupMeasurements.measurements.push(tableMeasurement);
|
||||
});
|
||||
});
|
||||
|
||||
// Sort measurements by lesion naming number
|
||||
tableMeasurements.forEach(tm => {
|
||||
tm.measurements.sort((m1, m2) =>
|
||||
m1.lesionNamingNumber > m2.lesionNamingNumber ? 1 : -1
|
||||
);
|
||||
});
|
||||
|
||||
return tableMeasurements;
|
||||
}
|
||||
|
||||
function convertTimepointsToTableData(timepoints) {
|
||||
if (!timepoints || !timepoints.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'Study date:',
|
||||
key: 'StudyDate',
|
||||
date: moment(timepoints[0].latestDate).format('DD-MMM-YY'),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { timepoints, measurements } = state.timepointManager;
|
||||
return {
|
||||
timepoints: convertTimepointsToTableData(timepoints),
|
||||
measurementCollection: convertMeasurementsToTableData(
|
||||
measurements,
|
||||
timepoints
|
||||
),
|
||||
timepointManager: state.timepointManager,
|
||||
viewports: state.viewports,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
dispatchRelabel: (event, measurementData, viewportsState) => {
|
||||
const activeViewportIndex =
|
||||
(viewportsState && viewportsState.activeViewportIndex) || 0;
|
||||
|
||||
const enabledElements = cornerstone.getEnabledElements();
|
||||
if (!enabledElements || enabledElements.length <= activeViewportIndex) {
|
||||
OHIF.log.error('Failed to find the enabled element');
|
||||
return;
|
||||
}
|
||||
|
||||
const { element } = enabledElements[activeViewportIndex];
|
||||
|
||||
const eventData = {
|
||||
event: {
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
},
|
||||
element,
|
||||
};
|
||||
|
||||
const { toolType, measurementId } = measurementData;
|
||||
const tool = MeasurementApi.Instance.tools[toolType].find(measurement => {
|
||||
return measurement._id === measurementId;
|
||||
});
|
||||
|
||||
const options = {
|
||||
skipAddLabelButton: true,
|
||||
editLocation: true,
|
||||
};
|
||||
|
||||
// Clone the tool not to set empty location initially
|
||||
const toolForLocation = Object.assign({}, tool, { location: null });
|
||||
getMeasurementLocationCallback(eventData, toolForLocation, options);
|
||||
},
|
||||
dispatchEditDescription: (event, measurementData, viewportsState) => {
|
||||
const activeViewportIndex =
|
||||
(viewportsState && viewportsState.activeViewportIndex) || 0;
|
||||
|
||||
const enabledElements = cornerstone.getEnabledElements();
|
||||
if (!enabledElements || enabledElements.length <= activeViewportIndex) {
|
||||
OHIF.log.error('Failed to find the enabled element');
|
||||
return;
|
||||
}
|
||||
|
||||
const { element } = enabledElements[activeViewportIndex];
|
||||
|
||||
const eventData = {
|
||||
event: {
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
},
|
||||
element,
|
||||
};
|
||||
|
||||
const { toolType, measurementId } = measurementData;
|
||||
const tool = MeasurementApi.Instance.tools[toolType].find(measurement => {
|
||||
return measurement._id === measurementId;
|
||||
});
|
||||
|
||||
const options = {
|
||||
editDescriptionOnDialog: true,
|
||||
};
|
||||
|
||||
getMeasurementLocationCallback(eventData, tool, options);
|
||||
},
|
||||
dispatchJumpToRowItem: (
|
||||
measurementData,
|
||||
viewportsState,
|
||||
timepointManagerState,
|
||||
options
|
||||
) => {
|
||||
const actionData = jumpToRowItem(
|
||||
measurementData,
|
||||
viewportsState,
|
||||
timepointManagerState,
|
||||
dispatch,
|
||||
options
|
||||
);
|
||||
|
||||
actionData.viewportSpecificData.forEach(viewportSpecificData => {
|
||||
const { viewportIndex, displaySet } = viewportSpecificData;
|
||||
|
||||
dispatch(setViewportSpecificData(viewportIndex, displaySet));
|
||||
});
|
||||
|
||||
const { toolType, measurementNumber } = measurementData;
|
||||
const measurementApi = MeasurementApi.Instance;
|
||||
|
||||
Object.keys(measurementApi.tools).forEach(toolType => {
|
||||
const measurements = measurementApi.tools[toolType];
|
||||
|
||||
measurements.forEach(measurement => {
|
||||
measurement.active = false;
|
||||
});
|
||||
});
|
||||
|
||||
const measurementsToActive = measurementApi.tools[toolType].filter(
|
||||
measurement => {
|
||||
return measurement.measurementNumber === measurementNumber;
|
||||
}
|
||||
);
|
||||
|
||||
measurementsToActive.forEach(measurementToActive => {
|
||||
measurementToActive.active = true;
|
||||
});
|
||||
|
||||
measurementApi.syncMeasurementsAndToolData();
|
||||
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
|
||||
// Needs to update viewports.layout state to set layout
|
||||
//const layout = actionData.layout;
|
||||
//dispatch(setLayout(layout))
|
||||
|
||||
// Needs to update viewports.activeViewportIndex to the first updated viewport
|
||||
//const viewportIndex = actionData.viewportIndex;
|
||||
//dispatch(setViewportActive(viewportIndex));
|
||||
|
||||
// Needs to update timepointsManager.measurements state to set active measurementId
|
||||
// TODO: Not yet implemented
|
||||
//dispatch(setActiveMeasurement(measurementData.measurementId))
|
||||
|
||||
// (later): Needs to set some property on state.extensions.cornerstone to synchronize viewport scrolling
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
|
||||
return {
|
||||
timepoints: propsFromState.timepoints,
|
||||
measurementCollection: propsFromState.measurementCollection,
|
||||
selectedMeasurementNumber: ownProps.selectedMeasurementNumber,
|
||||
...propsFromDispatch,
|
||||
onItemClick: (event, measurementData) => {
|
||||
// TODO: Add timepointId to .data for measurementData?
|
||||
// TODO: Tooltype should be on the level below? This should
|
||||
// provide the entire row item?
|
||||
|
||||
const viewportsState = propsFromState.viewports;
|
||||
const timepointManagerState = propsFromState.timepointManager;
|
||||
|
||||
// TODO: invertViewportTimepointsOrder should be stored in / read from user preferences
|
||||
// TODO: childToolKey should come from the measurement table when it supports child tools
|
||||
const options = {
|
||||
invertViewportTimepointsOrder: false,
|
||||
childToolKey: null,
|
||||
};
|
||||
|
||||
propsFromDispatch.dispatchJumpToRowItem(
|
||||
measurementData,
|
||||
viewportsState,
|
||||
timepointManagerState,
|
||||
options
|
||||
);
|
||||
},
|
||||
onRelabelClick: (event, measurementData) => {
|
||||
const viewportsState = propsFromState.viewports;
|
||||
propsFromDispatch.dispatchRelabel(event, measurementData, viewportsState);
|
||||
},
|
||||
onEditDescriptionClick: (event, measurementData) => {
|
||||
const viewportsState = propsFromState.viewports;
|
||||
propsFromDispatch.dispatchEditDescription(
|
||||
event,
|
||||
measurementData,
|
||||
viewportsState
|
||||
);
|
||||
},
|
||||
onDeleteClick: (event, measurementData) => {
|
||||
const { MeasurementHandlers } = OHIF.measurements;
|
||||
|
||||
MeasurementHandlers.onRemoved({
|
||||
detail: {
|
||||
toolType: measurementData.toolType,
|
||||
measurementData: {
|
||||
_id: measurementData.measurementId,
|
||||
lesionNamingNumber: measurementData.lesionNamingNumber,
|
||||
measurementNumber: measurementData.measurementNumber,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedMeasurementTable = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mergeProps
|
||||
)(MeasurementTable);
|
||||
|
||||
export default ConnectedMeasurementTable;
|
||||
@@ -0,0 +1,23 @@
|
||||
import ConnectedMeasurementTable from './ConnectedMeasurementTable.js';
|
||||
|
||||
export default {
|
||||
id: 'measurements-table',
|
||||
getPanelModule() {
|
||||
return {
|
||||
menuOptions: [
|
||||
{
|
||||
icon: 'list',
|
||||
label: 'Measurements',
|
||||
target: 'measurement-panel',
|
||||
},
|
||||
],
|
||||
components: [
|
||||
{
|
||||
id: 'measurement-panel',
|
||||
component: ConnectedMeasurementTable,
|
||||
},
|
||||
],
|
||||
defaultContext: ['VIEWER'],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
import { measurements, utils } from "@ohif/core";
|
||||
|
||||
const { MeasurementApi } = measurements;
|
||||
const { studyMetadataManager } = utils;
|
||||
|
||||
// TODO: Move this function to OHIF itself so we can use it on the OHIF measurment table (when it is finished)
|
||||
|
||||
/**
|
||||
* Activates a set of measurements
|
||||
*
|
||||
* @param measurementData
|
||||
* @param viewportsState
|
||||
* @param timepointManagerState
|
||||
* @param options
|
||||
*/
|
||||
export default function jumpToRowItem(
|
||||
measurementData,
|
||||
viewportsState,
|
||||
timepointManagerState,
|
||||
options = { invertViewportTimepointsOrder: false, childToolKey: null }
|
||||
) {
|
||||
const numViewports = viewportsState.layout.viewports.length;
|
||||
const numTimepoints = timepointManagerState.timepoints.length;
|
||||
const { measurements, timepoints } = timepointManagerState;
|
||||
const numViewportsToUpdate = Math.min(numTimepoints, numViewports);
|
||||
const { toolType, measurementNumber } = measurementData;
|
||||
|
||||
if (options.invertViewportTimepointsOrder) {
|
||||
timepoints.reverse();
|
||||
}
|
||||
|
||||
const measurementsForToolGroup = measurements[toolType];
|
||||
|
||||
// Retrieve the measurements data
|
||||
const measurementsToJumpTo = [];
|
||||
for (let i = 0; i < numViewportsToUpdate; i++) {
|
||||
const { timepointId } = timepoints[i];
|
||||
|
||||
const dataAtThisTimepoint = measurementsForToolGroup.find(entry => {
|
||||
return (
|
||||
entry.timepointId === timepointId &&
|
||||
entry.measurementNumber === measurementNumber
|
||||
);
|
||||
});
|
||||
|
||||
if (!dataAtThisTimepoint) {
|
||||
measurementsToJumpTo.push(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
let measurement = dataAtThisTimepoint;
|
||||
|
||||
const { tool } = MeasurementApi.getToolConfiguration(toolType);
|
||||
if (options.childToolKey) {
|
||||
measurement = dataAtThisTimepoint[options.childToolKey];
|
||||
} else if (Array.isArray(tool.childTools)) {
|
||||
const key = tool.childTools.find(key => !!dataAtThisTimepoint[key]);
|
||||
measurement = dataAtThisTimepoint[key];
|
||||
}
|
||||
|
||||
measurementsToJumpTo.push(measurement);
|
||||
}
|
||||
|
||||
// TODO: Add a single viewports state action which allows
|
||||
// - viewportData to be set
|
||||
// - layout to be set
|
||||
// - activeViewport to be set
|
||||
|
||||
// Needs to update viewports.viewportData state to set image set data
|
||||
|
||||
const displaySetContainsSopInstance = (displaySet, sopInstanceUid) =>
|
||||
displaySet.images.find(
|
||||
image => image.getSOPInstanceUID() === sopInstanceUid
|
||||
);
|
||||
|
||||
const viewportSpecificData = [];
|
||||
measurementsToJumpTo.forEach((data, viewportIndex) => {
|
||||
// Skip if there is no measurement to jump
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const study = studyMetadataManager.get(data.studyInstanceUid);
|
||||
if (!study) {
|
||||
throw new Error("Study not found.");
|
||||
}
|
||||
|
||||
const displaySet = study.findDisplaySet(displaySet => {
|
||||
return displaySetContainsSopInstance(displaySet, data.sopInstanceUid);
|
||||
});
|
||||
|
||||
if (!displaySet) {
|
||||
throw new Error("Display set not found.");
|
||||
}
|
||||
|
||||
displaySet.sopInstanceUid = data.sopInstanceUid;
|
||||
if (data.frameIndex) {
|
||||
displaySet.frameIndex = data.frameIndex;
|
||||
}
|
||||
|
||||
viewportSpecificData.push({
|
||||
viewportIndex,
|
||||
displaySet
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
viewportSpecificData,
|
||||
layout: [] // TODO: if we need to change layout, we should return this here
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import GenericViewerCommands from './GenericViewerCommands/index.js';
|
||||
import MeasurementsPanel from './MeasurementsPanel/index.js';
|
||||
|
||||
export { GenericViewerCommands, MeasurementsPanel };
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* We use this component to leverage "Code Splitting"
|
||||
*
|
||||
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export default function asyncComponent(importComponent) {
|
||||
class AsyncComponent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
component: null,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
// Add dynamically loaded component to state
|
||||
const { default: component } = await importComponent();
|
||||
|
||||
this.setState({
|
||||
component: component,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const C = this.state.component;
|
||||
|
||||
// Render the loaded component, or null
|
||||
return C ? <C {...this.props} /> : null;
|
||||
}
|
||||
}
|
||||
|
||||
return AsyncComponent;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
.editDescriptionDialog {
|
||||
position: absolute;
|
||||
z-index: 300;
|
||||
width: 320px;
|
||||
transition: all 300ms linear;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Component } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import SimpleDialog from '../SimpleDialog/SimpleDialog.js';
|
||||
|
||||
import bounding from '../../lib/utils/bounding.js';
|
||||
import { getDialogStyle } from './../Labelling/labellingPositionUtils.js';
|
||||
|
||||
import './EditDescriptionDialog.css';
|
||||
|
||||
export default class EditDescriptionDialog extends Component {
|
||||
static defaultProps = {
|
||||
componentRef: React.createRef(),
|
||||
componentStyle: {},
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
measurementData: PropTypes.object.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
componentRef: PropTypes.object,
|
||||
componentStyle: PropTypes.object,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
description: props.measurementData.description || '',
|
||||
};
|
||||
|
||||
this.mainElement = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
bounding(this.mainElement);
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.description !== prevProps.description) {
|
||||
this.setState({
|
||||
description: this.props.description,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = getDialogStyle(this.props.componentStyle);
|
||||
|
||||
return (
|
||||
<SimpleDialog
|
||||
headerTitle="Edit Description"
|
||||
onClose={this.onClose}
|
||||
onConfirm={this.onConfirm}
|
||||
rootClass="editDescriptionDialog"
|
||||
componentRef={this.mainElement}
|
||||
componentStyle={style}
|
||||
>
|
||||
<input
|
||||
value={this.state.description}
|
||||
className="simpleDialogInput"
|
||||
id="description"
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</SimpleDialog>
|
||||
);
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.props.onCancel();
|
||||
};
|
||||
|
||||
onConfirm = e => {
|
||||
e.preventDefault();
|
||||
this.props.onUpdate(this.state.description);
|
||||
};
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ description: event.target.value });
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
.entry-header {
|
||||
padding: 10px 10px 0;
|
||||
height: var(--top-bar-height);
|
||||
}
|
||||
|
||||
.entry-header.header-big {
|
||||
background: rgba(21,25,30,0.7);
|
||||
padding: 10px var(--study-list-padding);
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-left-box {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-viewerLink {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.header-brand {
|
||||
height: 30px;
|
||||
text-decoration: none;
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.entry-header.header-big .header-brand {
|
||||
height: auto;
|
||||
padding: 25px 0;
|
||||
}
|
||||
|
||||
|
||||
.header-logo-image {
|
||||
fill: transparent;
|
||||
height: 100%;
|
||||
margin: 0 8px 0 0;
|
||||
width: 30px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.entry-header.header-big .header-logo-image {
|
||||
margin: 0 20px 0 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.header-logo-text {
|
||||
display: inline-block;
|
||||
font-family: var(--logo-font-family);
|
||||
font-size: 14px;
|
||||
font-weight: var(--logo-font-weight);
|
||||
color: var(--text-primary-color);
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.entry-header.header-big .header-logo-text {
|
||||
font-size: 30px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
color: var(--text-secondary-color);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.header-btn:hover {
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
.header-btn:active {
|
||||
color: var(--active-color);
|
||||
}
|
||||
|
||||
.header-studyListLinkSection {
|
||||
border-left: var(--ui-border-thickness) solid var(--ui-border-color);
|
||||
margin: 3px 0 0 10px;
|
||||
padding: 0 0 0 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header-menu {
|
||||
color: var(--text-primary-color);
|
||||
float: right;
|
||||
padding: 4px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
text-decoration: none;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.entry-header.header-big .header-menu {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.header-menu .research-use {
|
||||
float: left;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #91b9cd;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import './Header.css';
|
||||
import './Header.css';
|
||||
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Dropdown } from '@ohif/ui';
|
||||
import OHIFLogo from '../OHIFLogo/OHIFLogo.js';
|
||||
import PropTypes from 'prop-types';
|
||||
import { AboutModal } from '@ohif/ui';
|
||||
import { hotkeysManager } from './../../App.js';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
|
||||
class Header extends Component {
|
||||
static propTypes = {
|
||||
home: PropTypes.bool.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
children: PropTypes.node,
|
||||
t: PropTypes.func.isRequired,
|
||||
userManager: PropTypes.object
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
home: true,
|
||||
children: OHIFLogo(),
|
||||
};
|
||||
|
||||
// onSave: data => {
|
||||
// const contextName = store.getState().commandContext.context;
|
||||
// const preferences = cloneDeep(store.getState().preferences);
|
||||
// preferences[contextName] = data;
|
||||
// dispatch(setUserPreferences(preferences));
|
||||
// dispatch(setUserPreferencesModalOpen(false));
|
||||
// OHIF.hotkeysUtil.setHotkeys(data.hotKeysData);
|
||||
// },
|
||||
// onResetToDefaults: () => {
|
||||
// dispatch(setUserPreferences());
|
||||
// dispatch(setUserPreferencesModalOpen(false));
|
||||
// OHIF.hotkeysUtil.setHotkeys();
|
||||
// },
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { isUserPreferencesOpen: false, isOpen: false };
|
||||
|
||||
this.loadOptions();
|
||||
}
|
||||
|
||||
loadOptions() {
|
||||
const { t } = this.props;
|
||||
this.options = [
|
||||
{
|
||||
title: t('About'),
|
||||
icon: { name: 'info' },
|
||||
onClick: () => {
|
||||
this.setState({
|
||||
isOpen: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (this.props.user && this.props.userManager) {
|
||||
this.options.push({
|
||||
title: t('Logout'),
|
||||
icon: { name: 'power-off' },
|
||||
onClick: () => {
|
||||
this.props.userManager.signoutRedirect();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.hotKeysData = hotkeysManager.hotkeyDefinitions;
|
||||
}
|
||||
|
||||
onUserPreferencesSave({ windowLevelData, hotKeysData }) {
|
||||
// console.log(windowLevelData);
|
||||
// console.log(hotKeysData);
|
||||
// TODO: Update hotkeysManager
|
||||
// TODO: reset `this.hotKeysData`
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const showStudyList =
|
||||
window.config.showStudyList !== undefined
|
||||
? window.config.showStudyList
|
||||
: true;
|
||||
return (
|
||||
<div className={`entry-header ${this.props.home ? 'header-big' : ''}`}>
|
||||
<div className="header-left-box">
|
||||
{this.props.location && this.props.location.studyLink && (
|
||||
<Link
|
||||
to={this.props.location.studyLink}
|
||||
className="header-btn header-viewerLink"
|
||||
>
|
||||
{t('Back to Viewer')}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{this.props.children}
|
||||
|
||||
{showStudyList && !this.props.home && (
|
||||
<Link
|
||||
className="header-btn header-studyListLinkSection"
|
||||
to={{
|
||||
pathname: '/',
|
||||
state: { studyLink: this.props.location.pathname },
|
||||
}}
|
||||
>
|
||||
{t('Study list')}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="header-menu">
|
||||
<span className="research-use">{t('INVESTIGATIONAL USE ONLY')}</span>
|
||||
<Dropdown title={t('Options')} list={this.options} align="right" />
|
||||
<AboutModal
|
||||
{...this.state}
|
||||
onCancel={() =>
|
||||
this.setState({
|
||||
isOpen: false,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation('Header')(withRouter(Header));
|
||||
@@ -0,0 +1,3 @@
|
||||
import Header from './Header';
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,317 @@
|
||||
import { Icon, SelectTree } from '@ohif/ui';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import LabellingTransition from './LabellingTransition.js';
|
||||
import OHIFLabellingData from './OHIFLabellingData.js';
|
||||
import PropTypes from 'prop-types';
|
||||
import bounding from '../../lib/utils/bounding.js';
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
import { getAddLabelButtonStyle } from './labellingPositionUtils.js';
|
||||
|
||||
export default class LabellingFlow extends Component {
|
||||
static propTypes = {
|
||||
eventData: PropTypes.object.isRequired,
|
||||
measurementData: PropTypes.object.isRequired,
|
||||
labellingDoneCallback: PropTypes.func.isRequired,
|
||||
updateLabelling: PropTypes.func.isRequired,
|
||||
|
||||
initialTopDistance: PropTypes.number,
|
||||
skipAddLabelButton: PropTypes.bool,
|
||||
editLocation: PropTypes.bool,
|
||||
editDescription: PropTypes.bool,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { location, locationLabel, description } = props.measurementData;
|
||||
|
||||
let style = props.componentStyle;
|
||||
if (!props.skipAddLabelButton) {
|
||||
style = getAddLabelButtonStyle(props.measurementData, props.eventData);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
location,
|
||||
locationLabel,
|
||||
description,
|
||||
skipAddLabelButton: props.skipAddLabelButton,
|
||||
editDescription: props.editDescription,
|
||||
editLocation: props.editLocation,
|
||||
componentStyle: style,
|
||||
confirmationState: false,
|
||||
displayComponent: true,
|
||||
};
|
||||
|
||||
this.mainElement = React.createRef();
|
||||
this.descriptionInput = React.createRef();
|
||||
|
||||
this.initialItems = OHIFLabellingData;
|
||||
this.currentItems = cloneDeep(this.initialItems);
|
||||
}
|
||||
|
||||
componentDidUpdate = () => {
|
||||
this.repositionComponent();
|
||||
if (this.state.editDescription) {
|
||||
this.descriptionInput.current.focus();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let mainElementClassName = 'labellingComponent';
|
||||
if (this.state.editDescription) {
|
||||
mainElementClassName += ' editDescription';
|
||||
}
|
||||
|
||||
const style = Object.assign({}, this.state.componentStyle);
|
||||
if (this.state.skipAddLabelButton) {
|
||||
if (style.left - 160 < 0) {
|
||||
style.left = 0;
|
||||
} else {
|
||||
style.left -= 160;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.editLocation) {
|
||||
style.maxHeight = '70vh';
|
||||
if (!this.initialTopDistance) {
|
||||
this.initialTopDistance = window.innerHeight - window.innerHeight * 0.3;
|
||||
style.top = `${this.state.componentStyle.top -
|
||||
this.initialTopDistance / 2}px`;
|
||||
} else {
|
||||
style.top = `${this.state.componentStyle.top}px`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<LabellingTransition
|
||||
displayComponent={this.state.displayComponent}
|
||||
onTransitionExit={this.props.labellingDoneCallback}
|
||||
>
|
||||
<>
|
||||
<div className="labellingComponent-overlay"></div>
|
||||
<div
|
||||
className={mainElementClassName}
|
||||
style={style}
|
||||
ref={this.mainElement}
|
||||
onMouseLeave={this.fadeOutAndLeave}
|
||||
onMouseEnter={this.clearFadeOutTimer}
|
||||
>
|
||||
{this.labellingStateFragment()}
|
||||
</div>
|
||||
</>
|
||||
</LabellingTransition>
|
||||
);
|
||||
}
|
||||
|
||||
labellingStateFragment = () => {
|
||||
const {
|
||||
skipAddLabelButton,
|
||||
editLocation,
|
||||
description,
|
||||
locationLabel,
|
||||
} = this.state;
|
||||
|
||||
if (!skipAddLabelButton) {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="addLabelButton"
|
||||
onClick={this.showLabelling}
|
||||
>
|
||||
{this.state.location ? 'Edit' : 'Add'} Label
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
if (editLocation) {
|
||||
return (
|
||||
<SelectTree
|
||||
items={this.currentItems}
|
||||
columns={1}
|
||||
onSelected={this.selectTreeSelectCalback}
|
||||
selectTreeFirstTitle="Assign Label"
|
||||
onComponentChange={this.repositionComponent}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="checkIconWrapper"
|
||||
onClick={this.fadeOutAndLeaveFast}
|
||||
>
|
||||
<Icon name="check" className="checkIcon" />
|
||||
</div>
|
||||
<div className="locationDescriptionWrapper">
|
||||
<div className="location">{locationLabel}</div>
|
||||
<div className="description">
|
||||
<input
|
||||
id="descriptionInput"
|
||||
ref={this.descriptionInput}
|
||||
defaultValue={description || ''}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="commonButtons">
|
||||
<button
|
||||
type="button"
|
||||
className="commonButton left"
|
||||
onClick={this.relabel}
|
||||
>
|
||||
Relabel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="commonButton right"
|
||||
onClick={this.setDescriptionUpdateMode}
|
||||
>
|
||||
{description ? 'Edit ' : 'Add '}
|
||||
Description
|
||||
</button>
|
||||
</div>
|
||||
<div className="editDescriptionButtons">
|
||||
<button
|
||||
type="button"
|
||||
className="commonButton left"
|
||||
onClick={this.descriptionCancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="commonButton right"
|
||||
onClick={this.descriptionSave}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
relabel = event => {
|
||||
const viewportTopPosition = this.mainElement.current.offsetParent.offsetTop;
|
||||
const componentStyle = {
|
||||
top: event.nativeEvent.y - viewportTopPosition - 55,
|
||||
left: event.nativeEvent.x,
|
||||
};
|
||||
this.setState({
|
||||
editLocation: true,
|
||||
componentStyle,
|
||||
});
|
||||
};
|
||||
|
||||
setDescriptionUpdateMode = () => {
|
||||
this.descriptionInput.current.focus();
|
||||
|
||||
this.setState({
|
||||
editDescription: true,
|
||||
});
|
||||
};
|
||||
|
||||
descriptionCancel = () => {
|
||||
const { description = '' } = cloneDeep(this.state);
|
||||
this.descriptionInput.current.value = description;
|
||||
|
||||
this.setState({
|
||||
editDescription: false,
|
||||
});
|
||||
};
|
||||
|
||||
descriptionSave = () => {
|
||||
const description = this.descriptionInput.current.value;
|
||||
this.props.updateLabelling({ description });
|
||||
|
||||
this.setState({
|
||||
description,
|
||||
editDescription: false,
|
||||
});
|
||||
};
|
||||
|
||||
selectTreeSelectCalback = (event, itemSelected) => {
|
||||
const location = itemSelected.value;
|
||||
this.props.updateLabelling({ location });
|
||||
|
||||
const viewportTopPosition = this.mainElement.current.offsetParent.offsetTop;
|
||||
const componentStyle = {
|
||||
top: event.nativeEvent.y - viewportTopPosition - 25,
|
||||
left: event.nativeEvent.x,
|
||||
};
|
||||
|
||||
this.setState({
|
||||
editLocation: false,
|
||||
confirmationState: true,
|
||||
location: itemSelected.value,
|
||||
locationLabel: itemSelected.label,
|
||||
componentStyle,
|
||||
});
|
||||
|
||||
if (this.isTouchScreen) {
|
||||
this.setTimeout = setTimeout(() => {
|
||||
this.setState({
|
||||
displayComponent: false,
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
showLabelling = () => {
|
||||
this.setState({
|
||||
skipAddLabelButton: true,
|
||||
editLocation: false,
|
||||
});
|
||||
};
|
||||
|
||||
fadeOutAndLeave = () => {
|
||||
// Wait for 1 sec to dismiss the labelling component
|
||||
this.fadeOutTimer = setTimeout(() => {
|
||||
this.setState({
|
||||
displayComponent: false,
|
||||
});
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
fadeOutAndLeaveFast = () => {
|
||||
this.setState({
|
||||
displayComponent: false,
|
||||
});
|
||||
};
|
||||
|
||||
clearFadeOutTimer = () => {
|
||||
if (!this.fadeOutTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.fadeOutTimer);
|
||||
};
|
||||
|
||||
calculateTopDistance = () => {
|
||||
const height = window.innerHeight - window.innerHeight * 0.3;
|
||||
let top = this.state.componentStyle.top - height / 2 + 55;
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
} else {
|
||||
if (top + height > window.innerHeight) {
|
||||
top -= top + height - window.innerHeight;
|
||||
}
|
||||
}
|
||||
return top;
|
||||
};
|
||||
|
||||
repositionComponent = () => {
|
||||
// SetTimeout for the css animation to end.
|
||||
setTimeout(() => {
|
||||
bounding(this.mainElement);
|
||||
if (this.state.editLocation) {
|
||||
this.mainElement.current.style.maxHeight = '70vh';
|
||||
const top = this.calculateTopDistance();
|
||||
this.mainElement.current.style.top = `${top}px`;
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
.labellingComponent-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.labellingComponent {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
z-index: 999;
|
||||
transition: all 200ms linear;
|
||||
}
|
||||
|
||||
.labellingComponent .selectedLabel,
|
||||
.labellingComponent .selectedDescription {
|
||||
padding: 5px;
|
||||
background-color: white;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.labellingComponent .addLabelButton {
|
||||
color: #000000;
|
||||
background-color: #20a5d6;
|
||||
border: 2px solid #44626f;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
opacity: 1;
|
||||
padding: 0 14px;
|
||||
transition: opacity 0.3s ease;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.labellingComponent .commonButton {
|
||||
border: 1px solid #44626f;
|
||||
color: #ffffff;
|
||||
background-color: #000000;
|
||||
border-radius: 16px;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
line-height: 26px;
|
||||
padding: 0 12px;
|
||||
margin: 10px 5px 0 0;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.labellingComponent .locationDescriptionWrapper {
|
||||
background-color: #ffffff;
|
||||
line-height: 46px;
|
||||
height: 46px;
|
||||
font-size: 13px;
|
||||
position: relative;
|
||||
width: 290px;
|
||||
min-width: 260px;
|
||||
padding: 0 12px;
|
||||
margin: 0 auto;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.labellingComponent .locationDescriptionWrapper .location {
|
||||
transition: all 300ms linear;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.labellingComponent.editDescription .locationDescriptionWrapper .location {
|
||||
bottom: 38px;
|
||||
}
|
||||
|
||||
.labellingComponent .locationDescriptionWrapper #descriptionInput {
|
||||
transition-delay: all 300ms linear;
|
||||
visibility: hidden;
|
||||
outline: none;
|
||||
height: 46px;
|
||||
width: 100%;
|
||||
line-height: 20px;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.labellingComponent.editDescription
|
||||
.locationDescriptionWrapper
|
||||
#descriptionInput {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.labellingComponent.editDescription .location {
|
||||
color: #337ab7;
|
||||
}
|
||||
|
||||
.labellingComponent .commonButtons,
|
||||
.labellingComponent.editDescription .editDescriptionButtons {
|
||||
display: block;
|
||||
margin-left: 55px;
|
||||
}
|
||||
|
||||
.labellingComponent.editDescription .commonButtons,
|
||||
.labellingComponent .editDescriptionButtons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.labellingComponent .commonButtons {
|
||||
text-align: center;
|
||||
margin-left: 55px;
|
||||
}
|
||||
|
||||
.labellingComponent .commonButton.left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.labellingComponent .commonButton.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.labellingComponent .checkIconWrapper {
|
||||
display: inline-block;
|
||||
background-color: #337ab7;
|
||||
border-radius: 46px;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
margin-right: 10px;
|
||||
vertical-align: bottom;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.labellingComponent .checkIcon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 13px;
|
||||
fill: black;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
|
||||
import EditDescriptionDialog from './../EditDescriptionDialog/EditDescriptionDialog.js';
|
||||
import LabellingFlow from './LabellingFlow.js';
|
||||
|
||||
import './LabellingManager.css';
|
||||
|
||||
export default class LabellingManager extends Component {
|
||||
static propTypes = {
|
||||
eventData: PropTypes.object.isRequired,
|
||||
measurementData: PropTypes.object.isRequired,
|
||||
|
||||
labellingDoneCallback: PropTypes.func.isRequired,
|
||||
updateLabelling: PropTypes.func.isRequired,
|
||||
|
||||
skipAddLabelButton: PropTypes.bool,
|
||||
editLocation: PropTypes.bool,
|
||||
editDescription: PropTypes.bool,
|
||||
editDescriptionOnDialog: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
skipAddLabelButton: false,
|
||||
editLocation: false,
|
||||
editDescription: false,
|
||||
editDescriptionOnDialog: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const measurementData = cloneDeep(props.measurementData);
|
||||
this.treatMeasurementData(measurementData);
|
||||
|
||||
let editLocation = props.editLocation;
|
||||
if (!props.editDescription && !props.editLocation) {
|
||||
editLocation = true;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
componentStyle: getComponentPosition(props.eventData),
|
||||
skipAddLabelButton: props.skipAddLabelButton,
|
||||
editLocation: editLocation,
|
||||
editDescription: props.editDescription,
|
||||
editDescriptionOnDialog: props.editDescriptionOnDialog,
|
||||
measurementData: measurementData,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
document.addEventListener('touchstart', this.onTouchStart);
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
document.removeEventListener('touchstart', this.onTouchStart);
|
||||
};
|
||||
|
||||
render() {
|
||||
return this.getRenderComponent();
|
||||
}
|
||||
|
||||
getRenderComponent = () => {
|
||||
const {
|
||||
editLocation,
|
||||
editDescription,
|
||||
editDescriptionOnDialog,
|
||||
measurementData,
|
||||
} = this.state;
|
||||
|
||||
if (editDescriptionOnDialog) {
|
||||
return (
|
||||
<EditDescriptionDialog
|
||||
onCancel={this.props.labellingDoneCallback}
|
||||
onUpdate={this.descriptionDialogUpdate}
|
||||
componentRef={this.editDescriptionDialog}
|
||||
componentStyle={this.state.componentStyle}
|
||||
measurementData={measurementData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (editLocation || editDescription) {
|
||||
return (
|
||||
<LabellingFlow
|
||||
{...this.props}
|
||||
componentStyle={this.state.componentStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
treatMeasurementData = measurementData => {
|
||||
const { editDescription, editLocation } = this.props;
|
||||
|
||||
if (editDescription) {
|
||||
measurementData.description = undefined;
|
||||
}
|
||||
if (editLocation) {
|
||||
measurementData.location = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
responseDialogUpdate = response => {
|
||||
this.props.updateLabelling({
|
||||
response,
|
||||
});
|
||||
this.props.labellingDoneCallback();
|
||||
};
|
||||
|
||||
descriptionDialogUpdate = description => {
|
||||
this.props.updateLabelling({
|
||||
description,
|
||||
});
|
||||
this.props.labellingDoneCallback();
|
||||
};
|
||||
}
|
||||
|
||||
function getComponentPosition(eventData) {
|
||||
const {
|
||||
event: { clientX: left, clientY: top },
|
||||
} = eventData;
|
||||
|
||||
return {
|
||||
left,
|
||||
top,
|
||||
};
|
||||
}
|
||||
Loaded 100 of 919 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user