Compare commits
80
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25599a2e2a | ||
|
|
1cf7015511 | ||
|
|
756f70cc54 | ||
|
|
ded2e61913 | ||
|
|
b58aa4575a | ||
|
|
f78abe2afa | ||
|
|
f48157117f | ||
|
|
6f8bd0dff8 | ||
|
|
943af89df2 | ||
|
|
cd2da349e5 | ||
|
|
14e86dcddd | ||
|
|
2c8b93656d | ||
|
|
6932468206 | ||
|
|
7bc9efb0f5 | ||
|
|
5d47cc43c1 | ||
|
|
e46a11a01e | ||
|
|
27a950d956 | ||
|
|
03d4c10adc | ||
|
|
7a0c369006 | ||
|
|
8c946a4742 | ||
|
|
30b9e44220 | ||
|
|
380abe2705 | ||
|
|
f4570c96c2 | ||
|
|
1fe951a71e | ||
|
|
ebe1af8d4f | ||
|
|
5cda5159da | ||
|
|
115a3ec0b0 | ||
|
|
ac82a111e7 | ||
|
|
a22fd67e13 | ||
|
|
91f0af058f | ||
|
|
5a1186388d | ||
|
|
2f0f92c095 | ||
|
|
5c686268ab | ||
|
|
974aabc9b2 | ||
|
|
17e3b49b06 | ||
|
|
e3c119489a | ||
|
|
17447baf44 | ||
|
|
daa3ddccdb | ||
|
|
c91b6dddd8 | ||
|
|
92548191e4 | ||
|
|
68fcaef19a | ||
|
|
ef611e4849 | ||
|
|
d128fd6bf0 | ||
|
|
3bdd434488 | ||
|
|
29bac8df7a | ||
|
|
319c67c389 | ||
|
|
b9836f6c71 | ||
|
|
6a6e2d7df3 | ||
|
|
2f8c408ed2 | ||
|
|
27910239e0 | ||
|
|
719bfd02eb | ||
|
|
994818faeb | ||
|
|
f06ef53e83 | ||
|
|
c7008621e4 | ||
|
|
55a580659e | ||
|
|
fd7620ced9 | ||
|
|
cd1cbf0f85 | ||
|
|
08055398de | ||
|
|
f6db7fade6 | ||
|
|
58d2bf1d12 | ||
|
|
87f5eca659 | ||
|
|
922ba8cc42 | ||
|
|
777ff88f3d | ||
|
|
68858db4b6 | ||
|
|
ab106afae3 | ||
|
|
ac69b0a656 | ||
|
|
8edba24eec | ||
|
|
aae232fc3e | ||
|
|
8fad8ceb80 | ||
|
|
f00f48da2b | ||
|
|
076b89ef83 | ||
|
|
4920b527d1 | ||
|
|
482041f053 | ||
|
|
674edf85f0 | ||
|
|
33a1a465ea | ||
|
|
6e04de30b6 | ||
|
|
19117167ac | ||
|
|
22e3c5d953 | ||
|
|
9a34877be4 | ||
|
|
b1b66429b6 |
No files matched your search
+60
-27
@@ -13,14 +13,7 @@ version: 2.1
|
||||
##
|
||||
orbs:
|
||||
codecov: codecov/codecov@1.0.5
|
||||
cypress: cypress-io/cypress@1.11.0
|
||||
executors:
|
||||
# Custom executor to override Cypress config
|
||||
deploy-to-prod-executor:
|
||||
docker:
|
||||
- image: 'cypress/browsers:node10.16.0-chrome77'
|
||||
environment:
|
||||
CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
|
||||
cypress: cypress-io/cypress@1.13.0
|
||||
|
||||
defaults: &defaults
|
||||
docker:
|
||||
@@ -341,6 +334,8 @@ workflows:
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/screenshots
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/videos
|
||||
requires:
|
||||
- UNIT_TESTS
|
||||
# E2E: script-tag
|
||||
@@ -364,9 +359,39 @@ workflows:
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/screenshots
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/videos
|
||||
requires:
|
||||
- UNIT_TESTS
|
||||
|
||||
PR_OPTIONAL_VISUAL_TESTS:
|
||||
jobs:
|
||||
- AWAIT_APPROVAL:
|
||||
type: approval
|
||||
# Update hub.docker.org
|
||||
- cypress/run:
|
||||
name: 'Generate Percy Snapshots'
|
||||
executor: cypress/browsers-chrome76
|
||||
browser: chrome
|
||||
pre-steps:
|
||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||
add wait-on' # Use yarn latest
|
||||
yarn: true
|
||||
store_artifacts: false
|
||||
working_directory: platform/viewer
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||
# start server --> verify running --> percy + chrome + cypress
|
||||
command: yarn run test:e2e:dist
|
||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
no-workspace: true # Don't persist workspace
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/screenshots
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/videos
|
||||
requires:
|
||||
- AWAIT_APPROVAL
|
||||
|
||||
PR_OPTIONAL_DOCKER_PUBLISH:
|
||||
jobs:
|
||||
# https://circleci.com/docs/2.0/workflows/#holding-a-workflow-for-a-manual-approval
|
||||
@@ -419,25 +444,33 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
# Update base branch snapshots
|
||||
# and record a Cypress dashboard test run
|
||||
- cypress/run:
|
||||
name: 'Generate Percy Snapshots'
|
||||
executor: cypress/browsers-chrome76
|
||||
browser: chrome
|
||||
pre-steps:
|
||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||
add wait-on' # Use yarn latest
|
||||
yarn: true
|
||||
store_artifacts: false
|
||||
working_directory: platform/viewer
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||
# start server --> verify running --> percy + chrome + cypress
|
||||
command: yarn run test:e2e:dist
|
||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
no-workspace: true # Don't persist workspace
|
||||
post-steps:
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/screenshots
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/videos
|
||||
- store_test_results:
|
||||
path: platform/viewer/cypress/results
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- DOCKER_MASTER_PUBLISH:
|
||||
requires:
|
||||
- NPM_PUBLISH
|
||||
#
|
||||
#
|
||||
# Potentially use to E2E test STAGING environment
|
||||
# before publishing to production
|
||||
# - cypress/run:
|
||||
# name: 'E2E: PWA'
|
||||
# executor: deploy-to-prod-executor
|
||||
# browser: chrome
|
||||
# working_directory: platform/viewer
|
||||
# cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
# yarn: true
|
||||
# record: false
|
||||
# no-workspace: true
|
||||
# store_artifacts: true
|
||||
# post-steps:
|
||||
# - store_artifacts:
|
||||
# path: platform/viewer/cypress/screenshots
|
||||
# - store_test_results:
|
||||
# path: cypress/results
|
||||
File renamed without changes.
+1
-1
@@ -9,7 +9,7 @@ daysUntilClose: 7
|
||||
exemptLabels:
|
||||
- 'Story :raised_hands:'
|
||||
- 'Bug: Verified :bug:'
|
||||
- 'Task: CI/Tooling 🤖'
|
||||
- 'Task: CI/Tooling :robot:'
|
||||
- 'Task: Docs 📖'
|
||||
- 'Task: Docs :book:'
|
||||
- 'Task: Refactor 🛠'
|
||||
|
||||
@@ -72,12 +72,17 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
/* Application */
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
|
||||
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
|
||||
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
|
||||
'process.env.VERSION_NUMBER': JSON.stringify(PACKAGE.version || ''),
|
||||
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
|
||||
/* i18n */
|
||||
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
|
||||
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
|
||||
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
|
||||
}),
|
||||
],
|
||||
// Fix: https://github.com/webpack-contrib/css-loader/issues/447#issuecomment-285598881
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at danny.ri.brown+OHIFcoc@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -0,0 +1 @@
|
||||
See our contributing guidelines at [`https://docs.ohif.org`](https://docs.ohif.org/development/contributing.html)
|
||||
@@ -271,8 +271,8 @@ MIT © [OHIF](https://github.com/OHIF)
|
||||
<!-- Badges -->
|
||||
[lerna-image]: https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg
|
||||
[lerna-url]: https://lerna.js.org/
|
||||
[netlify-image]: https://api.netlify.com/api/v1/badges/a5d369ab-18a6-41c3-bcde-83805205ac7f/deploy-status
|
||||
[netlify-url]: https://app.netlify.com/sites/ohif/deploys
|
||||
[netlify-image]: https://api.netlify.com/api/v1/badges/32708787-c9b0-4634-b50f-7ca41952da77/deploy-status
|
||||
[netlify-url]: https://app.netlify.com/sites/ohif-dev/deploys
|
||||
[all-contributors-image]: https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square
|
||||
[circleci-image]: https://circleci.com/gh/OHIF/Viewers.svg?style=svg
|
||||
[circleci-url]: https://circleci.com/gh/OHIF/Viewers
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# OHIF Viewers
|
||||
|
||||
- [Our Process](our-process.md)
|
||||
- [Quick Start](quick-start.md)
|
||||
- Development
|
||||
- [Getting Started](development/getting-started.md)
|
||||
- [Contributing](development/contributing.md)
|
||||
- [Continuous Integration](development/continous-integration.md)
|
||||
- [Configuring](configuring/index.md)
|
||||
- [Data Source](configuring/data-source.md)
|
||||
- [Configuration](essentials/configuration.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -27,11 +25,11 @@
|
||||
- [SOP Class Handler](extensions/modules/sop-class-handler.md)
|
||||
- [Toolbar](extensions/modules/toolbar.md)
|
||||
- [Viewport](extensions/modules/viewport.md)
|
||||
- [Contexts](extension/index.md#contexts)
|
||||
- [OHIF Maintained](extension/index.md#maintained-extensions)
|
||||
- [Contexts](extensions/index.md#contexts)
|
||||
- [ExtensionManager](extensions/index.md#extensionmanager)
|
||||
- [OHIF Maintained](extensions/index.md#maintained-extensions)
|
||||
- [Services](services/index.md)
|
||||
- [Default](services/default/index.md)
|
||||
- [Measurements](services/default/measurements.md)
|
||||
- [UI](services/ui/index.md)
|
||||
- [Dialog Service](services/ui/ui-dialog-service.md)
|
||||
- [Modal Service](services/ui/ui-modal-service.md)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 178 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 137 KiB |
@@ -66,6 +66,52 @@ window.config = {
|
||||
};
|
||||
```
|
||||
|
||||
The configuration can also be written as a JS Function in case you need to inject dependencies like external services:
|
||||
|
||||
```js
|
||||
window.config = ({ servicesManager } = {}) => {
|
||||
const { UIDialogService } = servicesManager.services;
|
||||
return {
|
||||
cornerstoneExtensionConfig: {
|
||||
tools: {
|
||||
ArrowAnnotate: {
|
||||
configuration: {
|
||||
getTextCallback: (callback, eventDetails) => UIDialogService.create({...
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
routerBasename: '/',
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
You can also create a new config file and specify its path relative to the build
|
||||
output's root by setting the `APP_CONFIG` environment variable. You can set the
|
||||
value of this environment variable a few different ways:
|
||||
|
||||
- ~[Add a temporary environment variable in your shell](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-temporary-environment-variables-in-your-shell)~
|
||||
- Previous `react-scripts` functionality that we need to duplicate with
|
||||
`dotenv-webpack`
|
||||
- ~[Add environment specific variables in `.env` file(s)](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-development-environment-variables-in-env)~
|
||||
- Previous `react-scripts` functionality that we need to duplicate with
|
||||
`dotenv-webpack`
|
||||
- Using the `cross-env` package in an npm script:
|
||||
- `"build": "cross-env APP_CONFIG=config/my-config.js react-scripts build"`
|
||||
|
||||
After updating the configuration, `yarn run build` to generate updated build
|
||||
output.
|
||||
|
||||
@@ -24,12 +24,12 @@ include tags. Here's how it works:
|
||||
</ul>
|
||||
|
||||
<ol start="2">
|
||||
<li>Create a JS Object to hold the OHIF Viewer's configuration. Here are some
|
||||
<li>Create a JS Object or Function to hold the OHIF Viewer's configuration. Here are some
|
||||
example values that would allow the viewer to hit our public PACS:</li>
|
||||
</ol>
|
||||
|
||||
```js
|
||||
// Set before importing `ohif-viewer`
|
||||
// Set before importing `ohif-viewer` (JS Object)
|
||||
window.config = {
|
||||
// default: '/'
|
||||
routerBasename: '/',
|
||||
@@ -49,6 +49,9 @@ window.config = {
|
||||
};
|
||||
```
|
||||
|
||||
To learn more about how you can configure the OHIF Viewer, check out our
|
||||
[Configuration Guide](./index.md).
|
||||
|
||||
<ol start="3"><li>
|
||||
Render the viewer in the web page's target <code>div</code>
|
||||
</li></ol>
|
||||
|
||||
@@ -75,7 +75,7 @@ You should see the following output:
|
||||
### Building for Production
|
||||
|
||||
> More comprehensive guides for building and publishing can be found in our
|
||||
> [deployment docs](./deployment/index.md)
|
||||
> [deployment docs](./../deployment/index.md)
|
||||
|
||||
```bash
|
||||
# Build static assets to host a PWA
|
||||
|
||||
+104
-22
@@ -8,12 +8,15 @@
|
||||
- [Modules](#modules)
|
||||
- [Contexts](#contexts)
|
||||
- [Consuming Extensions](#consuming-extensions)
|
||||
- [Extension Manager](#extensionmanager)
|
||||
- [Maintained Extensions](#maintained-extensions)
|
||||
|
||||
## Overview
|
||||
|
||||
We use extensions to help us isolate and package groups of related features.
|
||||
Extensions provide functionality, ui components, and new behaviors.
|
||||
Extensions provide functionality, ui components, and new behaviors. Ideally,
|
||||
they're built in a way that allows them to extend entirely different
|
||||
implementations of the `@ohif/viewer` project.
|
||||
|
||||
<div style="text-align: center;">
|
||||
<a href="/assets/img/extensions-diagram.png">
|
||||
@@ -46,9 +49,8 @@ Practical examples of extensions include:
|
||||
|
||||
### Extension Skeleton
|
||||
|
||||
An extension is a plain JavaScript object has an `id` property, and one or more
|
||||
"getModuleFunctions" and/or lifecycle hooks. You can read more about
|
||||
[lifecycle hooks](#lifecycle-hooks) and [modules](#modules) further down.
|
||||
An extension is a plain JavaScript object that has an `id` property, and one or
|
||||
more [modules](#modules) and/or [lifecycle hooks](#lifecycle-hooks).
|
||||
|
||||
```js
|
||||
// prettier-ignore
|
||||
@@ -78,6 +80,12 @@ You can leverage one or both strategies. Which one(s) you choose depend on your
|
||||
application's requirements. Each [module](#modules) defined by the extension
|
||||
becomes available to the core application via the `ExtensionManager`.
|
||||
|
||||
#### Registering at Runtime
|
||||
|
||||
The `@ohif/viewer` uses a [configuration file](#) at startup. The schema for
|
||||
that file includes an `Extensions` key that supports an array of extensions to
|
||||
register.
|
||||
|
||||
```js
|
||||
// prettier-ignore
|
||||
const config = {
|
||||
@@ -91,20 +99,13 @@ const config = {
|
||||
}
|
||||
```
|
||||
|
||||
#### Runtime Extensions
|
||||
|
||||
The `@ohif/viewer` uses a [configuration file](#) at startup. The schema for
|
||||
that file includes an `Extensions` key that supports an array of extensions to
|
||||
register.
|
||||
|
||||
#### Bundled Extensions
|
||||
#### Registering at Build Time
|
||||
|
||||
The `@ohif/viewer` works best when built as a "Progressive Web Application"
|
||||
(PWA). If you know the extensions your application will need, you can specify
|
||||
them at "build time" to leverage some advantaged afforded to us by modern
|
||||
tooling:
|
||||
them at "build time" to leverage advantages afforded to us by modern tooling:
|
||||
|
||||
- Code Splitting
|
||||
- Code Splitting (dynamic imports)
|
||||
- Tree Shaking
|
||||
- Dependency deduplication
|
||||
|
||||
@@ -116,23 +117,104 @@ You can update the list of bundled extensions by:
|
||||
|
||||
### Lifecycle Hooks
|
||||
|
||||
...
|
||||
Currently, there is only a single lifecycle hook for extensions:
|
||||
[`preRegistration`](./lifecycle/pre-registration.md)
|
||||
|
||||
If an extension defines the [`preRegistration`](./lifecycle/pre-registration.md)
|
||||
lifecycle hook, it is called before any modules are registered in the
|
||||
`ExtensionManager`. It's most commonly used to wire up extensions to
|
||||
[services](./../services/index.md) and [commands](./modules/commands.md), and to
|
||||
bootstrap 3rd party libraries.
|
||||
|
||||
### Modules
|
||||
|
||||
There are a few different module types. Each module type allows us to extend the
|
||||
viewer in a different way, and provides a consistent API for us to do so. You
|
||||
can find a full list of the different types of modules
|
||||
[`in ohif-core`][module-types]. Information on each type of module, it's API,
|
||||
and how we determine when/where it should be used is included below.
|
||||
Modules are the meat of extensions. They provide "definitions", components, and
|
||||
filtering/mapping logic that are then made available by various managers and
|
||||
services.
|
||||
|
||||
Each module type has a special purpose, and is consumed by our viewer
|
||||
differently.
|
||||
|
||||
| Type | Description | Examples |
|
||||
| ------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------- |
|
||||
| [Commands](./modules/commands.md) | Adds named commands, scoped to a context, to the CommandsManager | `setToolActive()`, `nextSeries()` |
|
||||
| [Panel](./modules/panel.md) | Adds left or right hand side panels | `<ThumbnailList />`, `<MeasurementsTable />` |
|
||||
| [SOPClassHandler](./modules/sop-class-handler.md) | Determines how retrieved study data is split into "DisplaySets" | `getDisplaySetFromSeries()` |
|
||||
| [Toolbar](./modules/toolbar.md) | Adds buttons or custom components to the toolbar | Toolbar button, nested buttons, custom |
|
||||
| [Viewport](./modules/viewport.md) | Adds a component responsible for rendering a "DisplaySet" | `<CornerstoneViewport />`, `<DicomPdfViewport />` |
|
||||
|
||||
<figure style="text-align: center; font-style: italic;">Tbl. Module types with abridged descriptions and examples. Each module links to a dedicated documentation page.</figure>
|
||||
|
||||
### Contexts
|
||||
|
||||
...
|
||||
The `@ohif/viewer` tracks "active contexts" that extensions can use to scope
|
||||
their functionality. Some example contexts being:
|
||||
|
||||
- Route: `ROUTE:VIEWER`, `ROUTE:STUDY_LIST`
|
||||
- Active Viewport: `ACTIVE_VIEWPORT:CORNERSTONE`, `ACTIVE_VIEWPORT:VTK`
|
||||
|
||||
An extension module can use these to say "Only show this Toolbar Button if the
|
||||
active viewport is a Cornerstone viewport." This helps us use the appropriate UI
|
||||
and behaviors depending on the current contexts.
|
||||
|
||||
For example, if we have hotkey that "rotates the active viewport", each Viewport
|
||||
module that supports this behavior can add a command with the same name, scoped
|
||||
to the appropriate context. When the `command` is fired, the "active contexts"
|
||||
are used to determine the appropriate implementation of the rotate behavior.
|
||||
|
||||
## Consuming Extensions
|
||||
|
||||
...
|
||||
We consume extensions, via the `ExtensionManager`, in our `@ohif/viewer`
|
||||
project.
|
||||
|
||||
```js
|
||||
const extensionManager = new ExtensionManager({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
});
|
||||
|
||||
// prettier-ignore
|
||||
extensionManager.registerExtensions([ /** **/ ]);
|
||||
```
|
||||
|
||||
The `@ohif/viewer` project handles data fetching, basic routing, wires up UI
|
||||
services, and is the home to the more bespoke application logic that doesn't
|
||||
make as much sense to make reusable.
|
||||
|
||||
Long-term, replacing the `@ohif/viewer` application and consuming extensions
|
||||
(and the `ExtensionManager`) in your own project is the ideal path for
|
||||
applications requiring a high degree of customization that can't be achieved
|
||||
with current theming, configuration, extension, and services support.
|
||||
|
||||
If you're not sure how to achieve your goals with the extensibility available
|
||||
today, create a GitHub issue!
|
||||
|
||||
### `ExtensionManager`
|
||||
|
||||
The `ExtensionManager` is a class made available to us via the `@ohif/core`
|
||||
project (platform/core). Our application instantiates a single instance of it,
|
||||
and provides a `ServicesManager` and `CommandsManager`.
|
||||
|
||||
```js
|
||||
const commandsManager = new CommandsManager();
|
||||
const servicesManager = new ServicesManager();
|
||||
const extensionManager = new ExtensionManager({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
});
|
||||
```
|
||||
|
||||
The `ExtensionManager` only has a few public members:
|
||||
|
||||
- `registerExtension` - Registers a single extension
|
||||
- `registerExtensions` - Registers an array of extensions
|
||||
- `modules` - An object containing registered extensions by `MODULE_TYPE`
|
||||
|
||||
During registration, lifecycle hooks and modules have access to
|
||||
`ExtensionManager`'s `ServicesManager` and `CommandsManager`.
|
||||
|
||||
Our `@ohif/viewer` uses the `modules` member to access registered extensions at
|
||||
appropriate places in our application.
|
||||
|
||||
## Maintained Extensions
|
||||
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
# Lifecylce Hook: preRegistration
|
||||
|
||||
If an extension defines the `preRegistration` lifecycle hook, it is called
|
||||
before any modules are registered to the `ExtensionManager`.
|
||||
before any modules are registered in the `ExtensionManager`. This hook can be
|
||||
used to:
|
||||
|
||||
- initialize 3rd party libraries
|
||||
- register event listeners
|
||||
- add or call services
|
||||
- add or call commands
|
||||
|
||||
The `preRegistration` hook receives an object containing the
|
||||
`ExtensionManager`'s associated `ServicesManager`, `CommandsManager`, and any
|
||||
`configuration` that was provided with the extension at time of registration.
|
||||
|
||||
_Example `preRegistration` hook implementation_
|
||||
|
||||
```js
|
||||
export default {
|
||||
id: 'MyExampleExtension',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {object} params.configuration
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
* @returns void
|
||||
*/
|
||||
preRegistration({ servicesManager, commandsManager, configuration }) {
|
||||
console.log('Wiring up important stuff.');
|
||||
|
||||
|
||||
@@ -1,34 +1,158 @@
|
||||
# Module: Commands
|
||||
|
||||
The Commands Module allows us to register one or more commands scoped to
|
||||
specific contexts. Commands can be run by [hotkeys][#], [toolbar buttons][#],
|
||||
and any registered custom react component (like a [viewport][#] or [panel][#]).
|
||||
- [Overview](#overview)
|
||||
- [Command Definitions](#command-definitions)
|
||||
- [Commands Manager](#commands-manager)
|
||||
- [Instantiating](#instatiating)
|
||||
- [Public API](#public-api)
|
||||
- [Contexts](#contexts)
|
||||
|
||||
## Overview
|
||||
|
||||
An extension can register a Commands Module by defining a `getCommandsModule`
|
||||
method. The Commands Module allows us to register one or more commands scoped to
|
||||
specific [contexts](./../index.md#contexts). Commands have several unique
|
||||
characteristics that make them tremendously powerful:
|
||||
|
||||
- Multiple implementations for the same command can be defined
|
||||
- Only the correct command's implementation will be run, dependent on the
|
||||
application's "context"
|
||||
- Commands can be called from extensions, modules, and the consuming application
|
||||
|
||||
Here is a simple example commands module:
|
||||
|
||||
```js
|
||||
{
|
||||
getCommandsModule() {
|
||||
return {
|
||||
actions: {
|
||||
speak: ({ viewports, words }) => {
|
||||
console.log(viewports, words);
|
||||
},
|
||||
},
|
||||
definitions: {
|
||||
rotateViewportCW: {
|
||||
commandFn: actions.rotateViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { rotation: 90 }
|
||||
},
|
||||
rotateViewportCCW: {
|
||||
commandFn: actions.rotateViewport,
|
||||
storeContexts: ['viewports'],
|
||||
options: { rotation: -90 },
|
||||
context: 'ACTIVE_VIEWER::CORNERSTONE'
|
||||
},
|
||||
},
|
||||
defaultContext: 'VIEWER'
|
||||
}
|
||||
}
|
||||
export default {
|
||||
id: 'example-commands-module',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
*/
|
||||
getCommandsModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
definitions: {
|
||||
sayHello: {
|
||||
commandFn: ({ words }) => {
|
||||
console.log(words);
|
||||
},
|
||||
options: { words: 'Hello!' },
|
||||
},
|
||||
},
|
||||
defaultContext: 'VIEWER',
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Each definition returned by the Commands Module is registered to the
|
||||
`ExtensionManager`'s `CommandsManager`.
|
||||
|
||||
## Command Definitions
|
||||
|
||||
The command definition consists of a named command (`myCommandName` below) and a
|
||||
`commandFn`. The command name is used to call the command, and the `commandFn`
|
||||
is the "command" that is actioned.
|
||||
|
||||
```js
|
||||
myCommandName: {
|
||||
commandFn: ({ viewports, other, options }) => { },
|
||||
storeContexts: ['viewports'],
|
||||
options: { words: 'Just kidding! Goodbye!' },
|
||||
context: 'ACTIVE_VIEWPORT::CORNERSTONE',
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| --------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `commandFn` | func | The function to call when command is run. Receives `options` and `storeContexts`. |
|
||||
| `storeContexts` | string[] | (optional) Expected state objects to be passed in as props. Located using `getAppState` fn defined at `CommandsManager`'s instatiation. |
|
||||
| `options` | object | (optional) Arguments to pass at the time of calling to the `commandFn` |
|
||||
| `context` | string[] or string | (optional) Overrides the `defaultContext`. Let's us know if command is currently "available" to be run. |
|
||||
|
||||
## Command Behavior
|
||||
|
||||
**I have many similar commands. How can I share their `commandFn` and make it
|
||||
reusable?**
|
||||
|
||||
This is where `storeContexts` and `options` come in. We use these in our
|
||||
`setToolActive` command. `storeContexts` helps us identify our `activeViewport`,
|
||||
and `options` allow us to pass in the name of a tool we would like to set as
|
||||
active.
|
||||
|
||||
**If there are multiple valid commands for the application's active contexts**
|
||||
|
||||
- What happens: all commands are run
|
||||
- When to use: A `clearData` command that cleans up state for multiple
|
||||
extensions
|
||||
|
||||
**If no commands are valid for the application's active contexts**
|
||||
|
||||
- What happens: a warning is printed to the console
|
||||
- When to use: a `hotkey` (like "invert") that doesn't make sense for the
|
||||
current viewport (PDF or HTML)
|
||||
|
||||
## `CommandsManager`
|
||||
|
||||
The `CommandsManager` is a class defined in the `@ohif/core` project. A single
|
||||
instance of it should be defined in the consuming application, and it should be
|
||||
used when constructing the `ExtensionManager`.
|
||||
|
||||
### Instantiating
|
||||
|
||||
When we instantiate the `CommandsManager`, we need to pass it two methods:
|
||||
|
||||
- `getAppState` - Should return the application's state when called
|
||||
- `getActiveContexts` - Should return the application's active contexts when
|
||||
called
|
||||
|
||||
These methods are used internally to help determine which commands are currently
|
||||
valid, and how to provide them with any state they may need at the time they are
|
||||
called.
|
||||
|
||||
```js
|
||||
const commandsManager = new CommandsManager({
|
||||
getAppState,
|
||||
getActiveContexts,
|
||||
});
|
||||
```
|
||||
|
||||
### Public API
|
||||
|
||||
If you would like to run a command in the consuming app or an extension, you can
|
||||
use one of the following methods:
|
||||
|
||||
```js
|
||||
// Returns all commands for a given context
|
||||
commandsManager.getContext('string');
|
||||
|
||||
// Attempts to run a command
|
||||
commandsManager.runCommand('speak', { command: 'hello' });
|
||||
|
||||
// Run command, but override the active contexts
|
||||
commandsManager.runCommand('speak', { command: 'hello' }, ['VIEWER']);
|
||||
```
|
||||
|
||||
The `ExtensionManager` handles registering commands and creating contexts, so
|
||||
most consumer's won't need these methods. If you find yourself using these, ask
|
||||
yourself "why can't I register these commands via an extension?"
|
||||
|
||||
```js
|
||||
// Used by the `ExtensionManager` to register new commands
|
||||
commandsManager.registerCommand('context', 'name', commandDefinition);
|
||||
|
||||
// Creates a new context; clears the context if it already exists
|
||||
commandsManager.createContext('string');
|
||||
```
|
||||
|
||||
### Contexts
|
||||
|
||||
It is up to the consuming application to define what contexts are possible, and
|
||||
which ones are currently active. As extensions depend heavily on these, we will
|
||||
likely publish guidance around creating contexts, and ways to override extension
|
||||
defined contexts in the near future. If you would like to discuss potential
|
||||
changes to how contexts work, please don't hesistate to createa new GitHub
|
||||
issue.
|
||||
|
||||
[Some additional information on Contexts can be found here.](./../index.md#contexts)
|
||||
@@ -1,3 +1,57 @@
|
||||
# Module: Panel
|
||||
|
||||
...
|
||||
An extension can register a Panel Module by defining a `getPanelModule` method.
|
||||
The panel module provides the ability to define `menuOptions` and `components`
|
||||
that can be used by the consuming application. `components` are React Components
|
||||
that can be displayed in the consuming application's "Panel" Component.
|
||||
|
||||

|
||||
|
||||
<center><i>A panel extension example</i></center>
|
||||
|
||||
The `menuOptions`'s `target` key points to a registered `components`'s `id`. A
|
||||
`defaultContext` is applied to all `menuOption`s; however, each `menuOption` can
|
||||
optional provide it's own `context` value.
|
||||
|
||||
The `getPanelModule` receives an object containing the `ExtensionManager`'s
|
||||
associated `ServicesManager` and `CommandsManager`.
|
||||
|
||||
```js
|
||||
import MyComponent from './MyComponent.js';
|
||||
|
||||
export default {
|
||||
id: 'example-panel-module',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
*/
|
||||
getPanelModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
menuOptions: [
|
||||
{
|
||||
// A suggested icon
|
||||
// Available icons determined by consuming app
|
||||
icon: 'list',
|
||||
// A suggested label
|
||||
label: 'Magic',
|
||||
// 'right' or 'left'
|
||||
from: 'right',
|
||||
// The target component to toggle open/close
|
||||
target: 'target-component-id',
|
||||
// Overrides `defaultContext`, if specified
|
||||
context: ['ACTIVE_VIEWPORT:MAGIC'],
|
||||
},
|
||||
],
|
||||
components: [
|
||||
{
|
||||
id: 'target-component-id',
|
||||
component: MyComponent,
|
||||
},
|
||||
],
|
||||
defaultContext: ['ROUTE:VIEWER'],
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -1,3 +1,90 @@
|
||||
# Module: SOP Class Handler
|
||||
|
||||
...
|
||||
An extension can register a SOP Class Handler Module by defining a
|
||||
`getSopClassHandlerModule` method. The SOP Class Handler is a bit different from
|
||||
the other modules, as it doesn't provide a `1:1` schema for UI or provide it's
|
||||
own components. It instead defines:
|
||||
|
||||
- `sopClassUids`: an array of string SOP Class UIDs that the
|
||||
`getDisplaySetFromSeries` method should be applied to.
|
||||
- `getDisplaySetFromSeries`: a method that maps series and study metadata to a
|
||||
display set
|
||||
|
||||
A `displaySet` has the following shape:
|
||||
|
||||
```js
|
||||
return {
|
||||
plugin: 'html',
|
||||
modality: 'SR',
|
||||
displaySetInstanceUid: 0,
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
```
|
||||
|
||||
Where the `plugin` key is used to influence the default `ViewportComponent` for
|
||||
rendering the `displaySet`. Additional properties are passed to the
|
||||
`ViewportComponent` and used by the default `StudyBrowser` to render
|
||||
"thumbnails" for each `displaySet`
|
||||
|
||||
## Example SOP Class Handler Module
|
||||
|
||||
```js
|
||||
const SOP_CLASS_UIDS = {
|
||||
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
|
||||
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
|
||||
};
|
||||
|
||||
export default {
|
||||
id: 'example-sop-class-handler-module',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
*/
|
||||
getSopClassHandlerModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
id: 'OHIFDicomHtmlSopClassHandler',
|
||||
sopClassUids: Object.values(SOP_CLASS_UIDS),
|
||||
|
||||
/**
|
||||
* @param {object} series -
|
||||
* @param {object} study -
|
||||
* @param {object} dicomWebClient -
|
||||
* @param {object} authorizationHeaders -
|
||||
*/
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
return {
|
||||
plugin: 'html',
|
||||
displaySetInstanceUid: 0,
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## `@ohif/viewer` usage
|
||||
|
||||
We use the `sopClassHandlerModule`s in three different places:
|
||||
|
||||
- `ViewerLocalFileData.js`
|
||||
- `ViewerRetrieveStudyData.js`
|
||||
- `StandaloneRouting.js`
|
||||
|
||||
Each time, it is used to map study and series data to `displaySets`. It does
|
||||
this by working alongside the `StudyMetadataManager` in `@ohif/core`. That
|
||||
manager has the method `createDisplaySets` that takes an array of
|
||||
`sopClassHandlerModules`.
|
||||
@@ -1,48 +1,130 @@
|
||||
# Module: Toolbar
|
||||
|
||||
An extension can register a Toolbar Module by providing a `getToolbarModule()`
|
||||
method that returns a React Component. The component does not receive any props.
|
||||
If you want to modify or react to state, you will need to connect to the redux
|
||||
store. The given toolbar must determine its set of elements and the context of
|
||||
them. The set of elements will be listed on toolbar `definitions`.
|
||||
An extension can register a Toolbar Module by defining a `getToolbarModule`
|
||||
method. This module is commonly used to define:
|
||||
|
||||

|
||||
- [Toolbar buttons](#button-definitions)
|
||||
- [Nested toolbar menus](#nested-toolbar-menus)
|
||||
- [Custom components](#custom-components)
|
||||
|
||||
<center><i>A toolbar extension example</i></center>
|
||||

|
||||
|
||||
Toolbar components are rendered in the `ToolbarRow` component.
|
||||
<center><i>Example toolbar button using the Dialog Service to show CINE controls.</i></center>
|
||||
|
||||
For a complete example implementation,
|
||||
[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/toolbarModule.js).
|
||||
## Example Toolbar Module
|
||||
|
||||
## Toolbar Custom Component
|
||||
|
||||
Toolbar elements can define its own custom react component to be consumed when
|
||||
rendering it. So far, it accepts `Functional` and `Class` Components. For that,
|
||||
you just need to expose your `CustomToolbarComponent` as the value of key
|
||||
`CustomComponent`. In case the property `CustomComponent` is not present, a
|
||||
default toolbar component will be used to render it. See bellow
|
||||
The Toolbar Module should return an array of `definitions` and a
|
||||
`defaultContext`. There are currently a few different variations of definitions,
|
||||
each one is detailed further down.
|
||||
|
||||
```js
|
||||
definitions: [
|
||||
...
|
||||
export default {
|
||||
id: 'example-toolbar-module',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
*/
|
||||
getToolbarModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
definitions: [
|
||||
/* Array of definitions */
|
||||
],
|
||||
defaultContext: ['ROUTE:VIEWER'],
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Button Definitions
|
||||
|
||||
The simplest definition has the following properties:
|
||||
|
||||
```js
|
||||
{
|
||||
id: 'StackScroll',
|
||||
label: 'Stack Scroll',
|
||||
icon: 'bars',
|
||||
type: 'setToolActive',
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'StackScroll' },
|
||||
},
|
||||
```
|
||||
|
||||
| property | description | values |
|
||||
| ---------------- | ----------------------------------------------------------------- | ----------------------------------------- |
|
||||
| `id` | Unique string identifier for the definition | \* |
|
||||
| `label` | User/display friendly to show in UI | \* |
|
||||
| `icon` | A string name for an icon supported by the consuming application. | \* |
|
||||
| `type` | Used to determine the button's component and behavior | `"setToolActive"`, `"command"` |
|
||||
| `commandName` | (optional) The command to run when the button is used. | Any command registed by a `CommandModule` |
|
||||
| `commandOptions` | (optional) Options to pass the target `commandName` | \* |
|
||||
| `context` | (optional) Overrides module's `defaultContext` | Array of string context names |
|
||||
|
||||
Where a button with a `type` of `setToolActive` has an "active" styling applied
|
||||
when clicked; removing the active styling from all other buttons.
|
||||
|
||||
## Nested Toolbar Menus
|
||||
|
||||
You can indicate that buttons should be grouped and nested in a submenu by
|
||||
including `buttons` property in a definition:
|
||||
|
||||
```js
|
||||
{
|
||||
id: 'More',
|
||||
label: 'More',
|
||||
icon: 'ellipse-circle',
|
||||
buttons: [
|
||||
{
|
||||
id: 'Custom',
|
||||
label: 'Custom',
|
||||
icon: 'custom-icon',
|
||||
CustomComponent: CustomToolbarComponent,
|
||||
}
|
||||
...
|
||||
]
|
||||
id: 'cstInvert',
|
||||
label: 'Invert',
|
||||
icon: 'circle',
|
||||
type: 'command',
|
||||
commandName: 'invertViewport',
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||

|
||||
|
||||
<center><i>Example toolbar button demonstrating nested buttons.</i></center>
|
||||
|
||||
## Custom Components
|
||||
|
||||
The Toolbar Modules supports rendering custom components in place of the
|
||||
application's default. In place of the `type`, `commandName`, and
|
||||
`commandOptions` properties, we instead specify a `CustomComponent`.
|
||||
|
||||
```js
|
||||
{
|
||||
id: 'Custom',
|
||||
label: 'Custom',
|
||||
icon: 'custom-icon',
|
||||
CustomComponent: CustomToolbarComponent,
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
`CustomComponent` components will receive the following props:
|
||||
The `CustomComponent` components will receive the following props:
|
||||
|
||||
- parentContext: parent context. (In most of the cases it will be a ToolbarRow
|
||||
instance)
|
||||
- toolbarClickCallback: callback method when clicking on toolbar
|
||||
- button: its own definition object
|
||||
- key: react key prop
|
||||
- activeButtons: list of active elements
|
||||
- isActive: if current
|
||||
```html
|
||||
<CustomComponent
|
||||
parentContext="{parentContext}"
|
||||
toolbarClickCallback="{_handleToolbarButtonClick.bind(this)}"
|
||||
button="{button}"
|
||||
key="{button.id}"
|
||||
activeButtons="{activeButtonsIds}"
|
||||
isActive="{isActive}"
|
||||
/>
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------------- | -------- | ------------------------------- |
|
||||
| `activeButtons` | string[] | list of active buttons |
|
||||
| `button` | object | its own definition object |
|
||||
| `key` | string | React key prop |
|
||||
| `isActive` | boolean | If current button is active |
|
||||
| `parentContext` | ? | The parent component's context? |
|
||||
| `toolbarClickCallback` | func | Callback method for clicks |
|
||||
@@ -1,19 +1,39 @@
|
||||
# Module: Viewport
|
||||
|
||||
An extension can register a Viewport Module by providing a `getViewportModule()`
|
||||
method that returns a React Component. The React component will receive the
|
||||
following props:
|
||||
An extension can register a Viewport Module by defining a `getViewportModule`
|
||||
method that returns a React component. Currently, we use viewport components to
|
||||
add support for:
|
||||
|
||||
```js
|
||||
children: PropTypes.arrayOf(PropTypes.element)
|
||||
studies: PropTypes.object,
|
||||
displaySet: PropTypes.object,
|
||||
viewportData: PropTypes.object, // { studies, displaySet }
|
||||
viewportIndex: PropTypes.number,
|
||||
children: PropTypes.node,
|
||||
customProps: PropTypes.object
|
||||
- 2D Medical Image Viewing (cornerstone ext.)
|
||||
- Structured Reports as HTML (dicom html ext.)
|
||||
- Encapsulated PDFs as PDFs (dicom pdf ext.)
|
||||
- Whole Slide Microscopy Viewing (whole slide ext.)
|
||||
- etc.
|
||||
|
||||
The general pattern is, the [`sopClassHandlerModule`](#) helps us determine
|
||||
which Viewport Component a set of `sopClassUIDs` should default to. The Viewport
|
||||
Component receives props containing a display set it should know how to render.
|
||||
|
||||
## Viewport Component Props
|
||||
|
||||
Each `ViewportComponent` will receive the following props:
|
||||
|
||||
```html
|
||||
<viewportComponent
|
||||
viewportData="{viewportData}"
|
||||
viewportIndex="{viewportIndex}"
|
||||
children="{[children]}"
|
||||
/>
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| --------------- | --------------- | --------------------------------- |
|
||||
| `children` | React.element[] | |
|
||||
| `viewportData` | object | `viewportSpecificData` (probably) |
|
||||
| `viewportIndex` | number | |
|
||||
|
||||
### `@ohif/viewer`
|
||||
|
||||
Viewport components are managed by the `ViewportGrid` Component. Which Viewport
|
||||
component is used depends on:
|
||||
|
||||
@@ -23,7 +43,4 @@ component is used depends on:
|
||||
|
||||

|
||||
|
||||
<center><i>An example of three Viewports</i></center>
|
||||
|
||||
For a complete example implementation,
|
||||
[check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/OHIFCornerstoneViewport.js).
|
||||
<center><i>An example of three cornerstone Viewports</i></center>
|
||||
@@ -13,7 +13,7 @@ like user preferences and previous query paramaters. Because of this, the Viewer
|
||||
has been built to be highly configurable to work with almost any web accessible
|
||||
data source.
|
||||
|
||||

|
||||

|
||||
|
||||
To be more specific, the OHIF Viewer is a collection of HTML, JS, and CSS files.
|
||||
These can be delivered to your end users however you would like:
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Quick Start
|
||||
|
||||
This page details how to get an instance of the OHIF Viewer up and running as
|
||||
fast as possible. It shows how to grab a pre-built version of the application,
|
||||
point it at your data source (PACS), and plop it on a web server.
|
||||
|
||||
## Options
|
||||
|
||||
### 1. Pre-built PWA
|
||||
|
||||
...
|
||||
|
||||
### 2. Script-Tag
|
||||
|
||||
...
|
||||
|
||||
### 3. Docker
|
||||
|
||||
...
|
||||
|
||||
## Security Concerns
|
||||
|
||||
- Secure your data
|
||||
|
||||
## Common Issues
|
||||
|
||||
- Missing server rewrite rules
|
||||
- CORS issues when requesting data from PACS
|
||||
@@ -43,7 +43,7 @@ translate to in practice?
|
||||
|
||||
```js
|
||||
// In the application
|
||||
const UINotificationService = createUINotificationService();
|
||||
import UINotificationService from '@ohif/core';
|
||||
const servicesManager = new ServicesManager();
|
||||
|
||||
servicesManager.registerService(UINotificationService);
|
||||
|
||||
@@ -1 +1,56 @@
|
||||
# Configuration
|
||||
# Viewer: Configuration
|
||||
|
||||
We maintain a number of common viewer application configurations at [`<root>/platform/viewer/public/configs`][config-dir]. How these values are passed to the viewer depend on how it's deployed, but the two most common paths are:
|
||||
|
||||
- `index.html` looks for `https://your-website.com/app-config.js` OR
|
||||
- `index.html` passes the values to `OHIF.installViewer()`
|
||||
|
||||
```js
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
extensions: [],
|
||||
showStudyList: true,
|
||||
filterQueryParam: false,
|
||||
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',
|
||||
enableStudyLazyLoad: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
hotkeys: [
|
||||
{ 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'],
|
||||
},
|
||||
],
|
||||
// Config to pass to the bundled cornerstone extension
|
||||
cornerstoneExtensionConfig: {},
|
||||
};
|
||||
```
|
||||
|
||||
<!--
|
||||
LINKS
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[config-dir]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config
|
||||
<!-- prettier-ignore-end -->
|
||||
@@ -0,0 +1,23 @@
|
||||
# Environment Variables
|
||||
|
||||
There are a number of environment variables we use at build time to influence the output application's behavior.
|
||||
|
||||
```bash
|
||||
# Application
|
||||
NODE_ENV=< production | development >
|
||||
DEBUG=< true | false >
|
||||
APP_CONFIG=< relative path to application configuration file >
|
||||
PUBLIC_URL=<>
|
||||
VERSION_NUMBER=<Set by CircleCI>
|
||||
BUILD_NUM=<Set by CircleCI>
|
||||
# i18n
|
||||
USE_LOCIZE=<false>
|
||||
LOCIZE_PROJECTID=<ProjectID to pull translations for>
|
||||
LOCIZE_API_KEY=<To enable Locize live editing of translations>
|
||||
```
|
||||
|
||||
## Setting Environment Variables
|
||||
|
||||
- `npx cross-env`
|
||||
- `.env` files
|
||||
- env variables on build machine, or for terminal session
|
||||
@@ -1 +1,9 @@
|
||||
# Viewer
|
||||
|
||||
The OHIF Viewing Platform strives to be highly configurable and extensible. This
|
||||
makes it easier for our community members to keep their "secret sauce" private,
|
||||
and incentivises contributions back to the platform. The `@ohif/viewer` project
|
||||
of the platform is the lynchpin that combines everything to create our
|
||||
application.
|
||||
|
||||
- When configuration and themeing aren't enough
|
||||
@@ -1,4 +1,4 @@
|
||||
# Internationalization
|
||||
# Viewer: Internationalization
|
||||
|
||||
OHIF supports internationalization using [i18next](https://www.i18next.com/)
|
||||
through the npm package [@ohif/i18n](https://www.npmjs.com/package/@ohif/i18n),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Themeing
|
||||
# Viewer: Themeing
|
||||
|
||||
Themeing is currently accomplished with color variables that are defined within
|
||||
the [`:root`](https://css-tricks.com/almanac/selectors/r/root/) selector
|
||||
|
||||
@@ -12,8 +12,9 @@ export default {
|
||||
*/
|
||||
|
||||
preRegistration({
|
||||
servicesManager,
|
||||
configuration: extensionConfiguration,
|
||||
servicesManager = {},
|
||||
commandsManager = {},
|
||||
configuration = {},
|
||||
}) {},
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,47 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.2...@ohif/extension-cornerstone@2.1.0) (2019-12-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.1...@ohif/extension-cornerstone@2.0.2) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.0...@ohif/extension-cornerstone@2.0.1) (2019-12-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.7.2...@ohif/extension-cornerstone@2.0.0) (2019-12-09)
|
||||
|
||||
|
||||
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.7.1...@ohif/extension-cornerstone@1.7.2) (2019-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
@@ -67,6 +67,24 @@ Our Viewport wraps [cornerstonejs/react-cornerstone-viewport][react-viewport]
|
||||
and is connected the redux store. This module is the most prone to change as we
|
||||
hammer out our Viewport interface.
|
||||
|
||||
## Tool Configuration
|
||||
|
||||
Tools can be configured through extension configuration using the tools key:
|
||||
|
||||
```js
|
||||
...
|
||||
cornerstoneExtensionConfig: {
|
||||
tools: {
|
||||
ArrowAnnotate: {
|
||||
configuration: {
|
||||
getTextCallback: (callback, eventDetails) => callback(prompt('Enter your custom annotation')),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### Repositories
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "1.7.2",
|
||||
"version": "2.1.0",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -34,7 +34,7 @@
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^4.8.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.6.1",
|
||||
"dcmjs": "^0.8.2",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
@@ -47,6 +47,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"classnames": "^2.2.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"query-string": "^6.8.3",
|
||||
"react-cornerstone-viewport": "2.x.x"
|
||||
|
||||
@@ -248,10 +248,13 @@ class OHIFCornerstoneViewport extends Component {
|
||||
// TODO: Does it make more sense to use Context?
|
||||
if (this.props.children && this.props.children.length) {
|
||||
childrenWithProps = this.props.children.map((child, index) => {
|
||||
return React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index,
|
||||
});
|
||||
return (
|
||||
child &&
|
||||
React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -148,18 +148,118 @@ const commandsModule = ({ servicesManager }) => {
|
||||
showDownloadViewportModal: ({ title, viewports }) => {
|
||||
const activeViewportIndex = viewports.activeViewportIndex;
|
||||
const { UIModalService } = servicesManager.services;
|
||||
UIModalService.show({
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
title,
|
||||
contentProps: {
|
||||
activeViewportIndex,
|
||||
onClose: UIModalService.hide,
|
||||
},
|
||||
if (UIModalService) {
|
||||
UIModalService.show({
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
title,
|
||||
contentProps: {
|
||||
activeViewportIndex,
|
||||
onClose: UIModalService.hide,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
updateTableWithNewMeasurementData({
|
||||
toolType,
|
||||
measurementNumber,
|
||||
location,
|
||||
description,
|
||||
}) {
|
||||
// Update all measurements by measurement number
|
||||
const measurementApi = OHIF.measurements.MeasurementApi.Instance;
|
||||
const measurements = measurementApi.tools[toolType].filter(
|
||||
m => m.measurementNumber === measurementNumber
|
||||
);
|
||||
|
||||
measurements.forEach(measurement => {
|
||||
measurement.location = location;
|
||||
measurement.description = description;
|
||||
|
||||
measurementApi.updateMeasurement(measurement.toolType, measurement);
|
||||
});
|
||||
|
||||
measurementApi.syncMeasurementsAndToolData();
|
||||
|
||||
// Update images in all active viewports
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
},
|
||||
getNearbyToolData({ element, canvasCoordinates, availableToolTypes }) {
|
||||
const nearbyTool = {};
|
||||
let pointNearTool = false;
|
||||
|
||||
availableToolTypes.forEach(toolType => {
|
||||
const elementToolData = cornerstoneTools.getToolState(
|
||||
element,
|
||||
toolType
|
||||
);
|
||||
|
||||
if (!elementToolData) {
|
||||
return;
|
||||
}
|
||||
|
||||
elementToolData.data.forEach((toolData, index) => {
|
||||
let elementToolInstance = cornerstoneTools.getToolForElement(
|
||||
element,
|
||||
toolType
|
||||
);
|
||||
|
||||
if (!elementToolInstance) {
|
||||
elementToolInstance = cornerstoneTools.getToolForElement(
|
||||
element,
|
||||
`${toolType}Tool`
|
||||
);
|
||||
}
|
||||
|
||||
if (!elementToolInstance) {
|
||||
console.warn('Tool not found.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
elementToolInstance.pointNearTool(
|
||||
element,
|
||||
toolData,
|
||||
canvasCoordinates
|
||||
)
|
||||
) {
|
||||
pointNearTool = true;
|
||||
nearbyTool.tool = toolData;
|
||||
nearbyTool.index = index;
|
||||
nearbyTool.toolType = toolType;
|
||||
}
|
||||
});
|
||||
|
||||
if (pointNearTool) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return pointNearTool ? nearbyTool : undefined;
|
||||
},
|
||||
removeToolState: ({ element, toolType, tool }) => {
|
||||
cornerstoneTools.removeToolState(element, toolType, tool);
|
||||
cornerstone.updateImage(element);
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
getNearbyToolData: {
|
||||
commandFn: actions.getNearbyToolData,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
removeToolState: {
|
||||
commandFn: actions.removeToolState,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
updateTableWithNewMeasurementData: {
|
||||
commandFn: actions.updateTableWithNewMeasurementData,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
showDownloadViewportModal: {
|
||||
commandFn: actions.showDownloadViewportModal,
|
||||
storeContexts: ['viewports'],
|
||||
|
||||
@@ -4,6 +4,7 @@ import csTools from 'cornerstone-tools';
|
||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||
import queryString from 'query-string';
|
||||
import { SimpleDialog } from '@ohif/ui';
|
||||
import merge from 'lodash.merge';
|
||||
|
||||
function fallbackMetaDataProvider(type, imageId) {
|
||||
if (!imageId.includes('wado?requestType=WADO')) {
|
||||
@@ -26,30 +27,33 @@ cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} configuration
|
||||
* @param {Object} servicesManager
|
||||
* @param {Object} configuration
|
||||
* @param {Object|Array} configuration.csToolsConfig
|
||||
*/
|
||||
export default function init({ servicesManager, configuration = {} }) {
|
||||
const { UIDialogService } = servicesManager.services;
|
||||
export default function init({ servicesManager, configuration }) {
|
||||
const callInputDialog = (data, event, callback) => {
|
||||
let dialogId = UIDialogService.create({
|
||||
content: SimpleDialog.InputDialog,
|
||||
defaultPosition: {
|
||||
x: (event && event.currentPoints.canvas.x) || 0,
|
||||
y: (event && event.currentPoints.canvas.y) || 0,
|
||||
},
|
||||
showOverlay: true,
|
||||
contentProps: {
|
||||
title: 'Enter your annotation',
|
||||
label: 'New label',
|
||||
measurementData: data ? { description: data.text } : {},
|
||||
onClose: () => UIDialogService.dismiss({ id: dialogId }),
|
||||
onSubmit: value => {
|
||||
callback(value);
|
||||
UIDialogService.dismiss({ id: dialogId });
|
||||
const { UIDialogService } = servicesManager.services;
|
||||
|
||||
if (UIDialogService) {
|
||||
let dialogId = UIDialogService.create({
|
||||
centralize: true,
|
||||
isDraggable: false,
|
||||
content: SimpleDialog.InputDialog,
|
||||
useLastPosition: false,
|
||||
showOverlay: true,
|
||||
contentProps: {
|
||||
title: 'Enter your annotation',
|
||||
label: 'New label',
|
||||
measurementData: data ? { description: data.text } : {},
|
||||
onClose: () => UIDialogService.dismiss({ id: dialogId }),
|
||||
onSubmit: value => {
|
||||
callback(value);
|
||||
UIDialogService.dismiss({ id: dialogId });
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const { csToolsConfig } = configuration;
|
||||
@@ -73,62 +77,62 @@ export default function init({ servicesManager, configuration = {} }) {
|
||||
initCornerstoneTools(defaultCsToolsConfig);
|
||||
|
||||
// ~~ Toooools 🙌
|
||||
const {
|
||||
PanTool,
|
||||
ZoomTool,
|
||||
WwwcTool,
|
||||
MagnifyTool,
|
||||
StackScrollTool,
|
||||
StackScrollMouseWheelTool,
|
||||
// Touch
|
||||
PanMultiTouchTool,
|
||||
ZoomTouchPinchTool,
|
||||
// Annotations
|
||||
EraserTool,
|
||||
BidirectionalTool,
|
||||
LengthTool,
|
||||
AngleTool,
|
||||
FreehandRoiTool,
|
||||
EllipticalRoiTool,
|
||||
DragProbeTool,
|
||||
RectangleRoiTool,
|
||||
// Segmentation
|
||||
BrushTool,
|
||||
} = csTools;
|
||||
const tools = [
|
||||
PanTool,
|
||||
ZoomTool,
|
||||
WwwcTool,
|
||||
MagnifyTool,
|
||||
StackScrollTool,
|
||||
StackScrollMouseWheelTool,
|
||||
csTools.PanTool,
|
||||
csTools.ZoomTool,
|
||||
csTools.WwwcTool,
|
||||
csTools.MagnifyTool,
|
||||
csTools.StackScrollTool,
|
||||
csTools.StackScrollMouseWheelTool,
|
||||
// Touch
|
||||
PanMultiTouchTool,
|
||||
ZoomTouchPinchTool,
|
||||
csTools.PanMultiTouchTool,
|
||||
csTools.ZoomTouchPinchTool,
|
||||
// Annotations
|
||||
EraserTool,
|
||||
BidirectionalTool,
|
||||
LengthTool,
|
||||
AngleTool,
|
||||
FreehandRoiTool,
|
||||
EllipticalRoiTool,
|
||||
DragProbeTool,
|
||||
RectangleRoiTool,
|
||||
csTools.ArrowAnnotateTool,
|
||||
csTools.EraserTool,
|
||||
csTools.BidirectionalTool,
|
||||
csTools.LengthTool,
|
||||
csTools.AngleTool,
|
||||
csTools.FreehandRoiTool,
|
||||
csTools.EllipticalRoiTool,
|
||||
csTools.DragProbeTool,
|
||||
csTools.RectangleRoiTool,
|
||||
// Segmentation
|
||||
BrushTool,
|
||||
csTools.BrushTool,
|
||||
];
|
||||
|
||||
tools.forEach(tool => csTools.addTool(tool));
|
||||
|
||||
csTools.addTool(csTools.ArrowAnnotateTool, {
|
||||
configuration: {
|
||||
getTextCallback: (callback, eventDetails) =>
|
||||
callInputDialog(null, eventDetails, callback),
|
||||
changeTextCallback: (data, eventDetails, callback) =>
|
||||
callInputDialog(data, eventDetails, callback),
|
||||
/* Add extension tools configuration here. */
|
||||
const internalToolsConfig = {
|
||||
ArrowAnnotate: {
|
||||
configuration: {
|
||||
getTextCallback: (callback, eventDetails) =>
|
||||
callInputDialog(null, eventDetails, callback),
|
||||
changeTextCallback: (data, eventDetails, callback) =>
|
||||
callInputDialog(data, eventDetails, callback),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/* Add tools with its custom props through extension configuration. */
|
||||
tools.forEach(tool => {
|
||||
const toolName = tool.name.replace('Tool', '');
|
||||
const externalToolsConfig = configuration.tools || {};
|
||||
const externalToolProps = externalToolsConfig[toolName] || {};
|
||||
const internalToolProps = internalToolsConfig[toolName] || {};
|
||||
const props = merge(internalToolProps, externalToolProps);
|
||||
csTools.addTool(tool, props);
|
||||
});
|
||||
|
||||
// TODO -> We need a better way to do this with maybe global tool state setting all tools passive.
|
||||
const BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool');
|
||||
tools.forEach(tool => {
|
||||
if (tool.prototype instanceof BaseAnnotationTool) {
|
||||
// BaseAnnotationTool would likely come from csTools lib exports
|
||||
const toolName = new tool().name;
|
||||
csTools.setToolPassive(toolName); // there may be a better place to determine name; may not be on uninstantiated class
|
||||
}
|
||||
});
|
||||
|
||||
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
|
||||
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
|
||||
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.0.2...@ohif/extension-dicom-html@1.1.0) (2019-12-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.0.1...@ohif/extension-dicom-html@1.0.2) (2019-12-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-html",
|
||||
"version": "1.0.2",
|
||||
"version": "1.1.0",
|
||||
"description": "OHIF extension for rendering structured reports to HTML",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"dcmjs": "^0.6.1",
|
||||
"dcmjs": "^0.8.2",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -3,6 +3,95 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.3...@ohif/extension-vtk@1.1.4) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.2...@ohif/extension-vtk@1.1.3) (2019-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.1...@ohif/extension-vtk@1.1.2) (2019-12-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.0...@ohif/extension-vtk@1.1.1) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.0.2...@ohif/extension-vtk@1.1.0) (2019-12-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.0.1...@ohif/extension-vtk@1.0.2) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.0.0...@ohif/extension-vtk@1.0.1) (2019-12-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.6...@ohif/extension-vtk@1.0.0) (2019-12-09)
|
||||
|
||||
|
||||
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.54.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.5...@ohif/extension-vtk@0.54.6) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.54.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.4...@ohif/extension-vtk@0.54.5) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.54.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.54.3...@ohif/extension-vtk@0.54.4) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "0.54.4",
|
||||
"version": "1.1.4",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -34,7 +34,7 @@
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.6.1",
|
||||
"dcmjs": "^0.8.2",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
@@ -52,11 +52,10 @@
|
||||
"react-vtkjs-viewport": "^0.3.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^1.13.3",
|
||||
"@ohif/ui": "^0.65.2",
|
||||
"@ohif/core": "^2.1.1",
|
||||
"@ohif/ui": "^1.1.4",
|
||||
"cornerstone-tools": "^4.8.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.6.1",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"gh-pages": "^2.0.1",
|
||||
"i18next": "^17.0.3",
|
||||
|
||||
@@ -358,10 +358,13 @@ class OHIFVTKViewport extends Component {
|
||||
// TODO: Does it make more sense to use Context?
|
||||
if (this.props.children && this.props.children.length) {
|
||||
childrenWithProps = this.props.children.map((child, index) => {
|
||||
return React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index,
|
||||
});
|
||||
return (
|
||||
child &&
|
||||
React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,55 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.1.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.1.0...@ohif/core@2.1.1) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.0.2...@ohif/core@2.1.0) (2019-12-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.0.1...@ohif/core@2.0.2) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.0.0...@ohif/core@2.0.1) (2019-12-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.13.3...@ohif/core@2.0.0) (2019-12-09)
|
||||
|
||||
|
||||
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.13.3](https://github.com/OHIF/Viewers/compare/@ohif/core@1.13.2...@ohif/core@1.13.3) (2019-12-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "1.13.3",
|
||||
"version": "2.1.1",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -59,7 +59,7 @@ export class CommandsManager {
|
||||
*
|
||||
* @method
|
||||
* @param {string} contextName - Namespace for commands
|
||||
* @returs {Object} - the matched context
|
||||
* @returns {Object} - the matched context
|
||||
*/
|
||||
getContext(contextName) {
|
||||
const context = this.contexts[contextName];
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class ExtensionManager {
|
||||
const hasConfiguration = Array.isArray(extension);
|
||||
|
||||
if (hasConfiguration) {
|
||||
const [ohifExtension, configuration] = extensions;
|
||||
const [ohifExtension, configuration] = extension;
|
||||
this.registerExtension(ohifExtension, configuration);
|
||||
} else {
|
||||
this.registerExtension(extension);
|
||||
|
||||
@@ -37,6 +37,24 @@ describe('ExtensionManager.js', () => {
|
||||
// Assert
|
||||
expect(extensionManager.registerExtension.mock.calls.length).toBe(3);
|
||||
});
|
||||
|
||||
it('calls registerExtension() for each extension passing its configuration if tuple', () => {
|
||||
const fakeConfiguration = { testing: true };
|
||||
extensionManager.registerExtension = jest.fn();
|
||||
|
||||
// SUT
|
||||
const fakeExtensions = [
|
||||
{ one: '1' },
|
||||
[{ two: '2' }, fakeConfiguration],
|
||||
{ three: '3 ' },
|
||||
];
|
||||
extensionManager.registerExtensions(fakeExtensions);
|
||||
|
||||
// Assert
|
||||
expect(extensionManager.registerExtension.mock.calls[1]).toContain(
|
||||
fakeConfiguration
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerExtension()', () => {
|
||||
|
||||
@@ -20,9 +20,9 @@ import user from './user.js';
|
||||
import utils from './utils/';
|
||||
|
||||
import {
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
createUIDialogService,
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
} from './services';
|
||||
|
||||
const OHIF = {
|
||||
@@ -50,9 +50,9 @@ const OHIF = {
|
||||
measurements,
|
||||
hangingProtocols,
|
||||
//
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
createUIDialogService,
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -79,9 +79,9 @@ export {
|
||||
measurements,
|
||||
hangingProtocols,
|
||||
//
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
createUIDialogService,
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
};
|
||||
|
||||
export { OHIF };
|
||||
|
||||
@@ -10,9 +10,9 @@ describe('Top level exports', () => {
|
||||
'HotkeysManager',
|
||||
'ServicesManager',
|
||||
//
|
||||
'createUINotificationService',
|
||||
'createUIModalService',
|
||||
'createUIDialogService',
|
||||
'UINotificationService',
|
||||
'UIModalService',
|
||||
'UIDialogService',
|
||||
//
|
||||
'utils',
|
||||
'studies',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import log from '../../log';
|
||||
import getLabel from '../lib/getLabel';
|
||||
import getDescription from '../lib/getDescription';
|
||||
@@ -7,13 +8,32 @@ import guid from '../../utils/guid';
|
||||
import studyMetadataManager from '../../utils/studyMetadataManager';
|
||||
import { measurementApiDefaultConfig } from './../configuration.js';
|
||||
|
||||
|
||||
const configuration = {
|
||||
...measurementApiDefaultConfig,
|
||||
...measurementApiDefaultConfig
|
||||
};
|
||||
|
||||
export default class MeasurementApi {
|
||||
static Instance;
|
||||
|
||||
/**
|
||||
* Set configuration: It should merge default configuration with any new one
|
||||
*
|
||||
* @static
|
||||
* @param {Object} config
|
||||
* @param {Object} config.server
|
||||
* @param {string} config.server.type - The server type
|
||||
* @param {string} config.server.wadoRoot - The server wado URL root
|
||||
* @param {Array} config.measurementTools
|
||||
* @param {string} config.measurementTools[].id - The tool group id
|
||||
* @param {string} config.measurementTools[].name - The tool group name
|
||||
* @param {Array} config.measurementTools[].childTools - The child tool's configuration
|
||||
* @param {Object} config.dataExchange
|
||||
* @param {Function} config.dataExchange.store - Function that store measurement data
|
||||
* @param {Function} config.dataExchange.retrieve - Function that retrieves measurement data
|
||||
*
|
||||
* @memberof MeasurementApi
|
||||
*/
|
||||
static setConfiguration(config) {
|
||||
Object.assign(configuration, config);
|
||||
}
|
||||
@@ -203,13 +223,14 @@ export default class MeasurementApi {
|
||||
|
||||
retrieveMeasurements(patientId, timepointIds) {
|
||||
const retrievalFn = configuration.dataExchange.retrieve;
|
||||
const { server } = configuration;
|
||||
if (typeof retrievalFn !== 'function') {
|
||||
log.error('Measurement retrieval function has not been configured.');
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
retrievalFn(patientId, timepointIds).then(measurementData => {
|
||||
retrievalFn(server).then(measurementData => {
|
||||
if (measurementData) {
|
||||
log.info('Measurement data retrieval');
|
||||
log.info(measurementData);
|
||||
@@ -230,6 +251,10 @@ export default class MeasurementApi {
|
||||
// Synchronize the new tool data
|
||||
this.syncMeasurementsAndToolData();
|
||||
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
|
||||
// Let others know that the measurements are updated
|
||||
this.onMeasurementsUpdated();
|
||||
}, reject);
|
||||
@@ -237,6 +262,7 @@ export default class MeasurementApi {
|
||||
}
|
||||
|
||||
storeMeasurements(timepointId) {
|
||||
const { server } = configuration;
|
||||
const storeFn = configuration.dataExchange.store;
|
||||
if (typeof storeFn !== 'function') {
|
||||
log.error('Measurement store function has not been configured.');
|
||||
@@ -278,8 +304,9 @@ export default class MeasurementApi {
|
||||
};
|
||||
|
||||
log.info('Saving Measurements for timepoints:', timepoints);
|
||||
return storeFn(measurementData, filter).then(() => {
|
||||
return storeFn(measurementData, filter, server).then(result => {
|
||||
log.info('Measurement storage completed');
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ export default class ServicesManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new service.
|
||||
*
|
||||
* @param {Object} service
|
||||
* @param {Object} configuration
|
||||
*/
|
||||
registerService(service) {
|
||||
registerService(service, configuration = {}) {
|
||||
if (!service) {
|
||||
log.warn(
|
||||
'Attempting to register a null/undefined service. Exiting early.'
|
||||
@@ -18,32 +20,45 @@ export default class ServicesManager {
|
||||
return;
|
||||
}
|
||||
|
||||
let serviceName = service.name;
|
||||
|
||||
if (!serviceName) {
|
||||
if (!service.name) {
|
||||
log.warn(`Service name not set. Exiting early.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.registeredServiceNames.includes(serviceName)) {
|
||||
if (this.registeredServiceNames.includes(service.name)) {
|
||||
log.warn(
|
||||
`Extension name ${serviceName} has already been registered. Exiting before duplicating services.`
|
||||
`Service name ${service.name} has already been registered. Exiting before duplicating services.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.services[service.name] = service;
|
||||
if (service.create) {
|
||||
this.services[service.name] = service.create({ configuration });
|
||||
} else {
|
||||
log.warn(`Service create factory function not defined. Exiting early.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Track service registration
|
||||
this.registeredServiceNames.push(serviceName);
|
||||
/* Track service registration */
|
||||
this.registeredServiceNames.push(service.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of services.
|
||||
* An array of services, or an array of arrays that contains service
|
||||
* configuration pairs.
|
||||
*
|
||||
* @param {Object[]} services - Array of services
|
||||
*/
|
||||
registerServices(services) {
|
||||
services.forEach(service => this.registerService(service));
|
||||
services.forEach(service => {
|
||||
const hasConfiguration = Array.isArray(service);
|
||||
|
||||
if (hasConfiguration) {
|
||||
const [ohifService, configuration] = service;
|
||||
this.registerService(ohifService, configuration);
|
||||
} else {
|
||||
this.registerService(service);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,18 +16,32 @@ describe('ServicesManager.js', () => {
|
||||
it('calls registerService() for each service', () => {
|
||||
servicesManager.registerService = jest.fn();
|
||||
|
||||
const fakeServices = [
|
||||
{ name: 'UINotificationTestService', hide: jest.fn() },
|
||||
{ name: 'UIModalTestService', hide: jest.fn() },
|
||||
];
|
||||
|
||||
servicesManager.registerServices(fakeServices);
|
||||
servicesManager.registerServices([
|
||||
{ name: 'UINotificationTestService', create: jest.fn() },
|
||||
{ name: 'UIModalTestService', create: jest.fn() },
|
||||
]);
|
||||
|
||||
expect(servicesManager.registerService.mock.calls.length).toBe(2);
|
||||
});
|
||||
|
||||
it('calls registerService() for each service passing its configuration if tuple', () => {
|
||||
servicesManager.registerService = jest.fn();
|
||||
const fakeConfiguration = { testing: true };
|
||||
|
||||
servicesManager.registerServices([
|
||||
{ name: 'UINotificationTestService', create: jest.fn() },
|
||||
[{ name: 'UIModalTestService', create: jest.fn() }, fakeConfiguration],
|
||||
]);
|
||||
|
||||
expect(servicesManager.registerService.mock.calls[1]).toContain(
|
||||
fakeConfiguration
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerService()', () => {
|
||||
const fakeService = { name: 'UINotificationService', create: jest.fn() };
|
||||
|
||||
it('logs a warning if the service is null or undefined', () => {
|
||||
const undefinedService = undefined;
|
||||
const nullService = null;
|
||||
@@ -39,8 +53,8 @@ describe('ServicesManager.js', () => {
|
||||
});
|
||||
|
||||
it('logs a warning if the service does not have a name', () => {
|
||||
const serviceWithEmptyName = { name: '', hide: jest.fn() };
|
||||
const serviceWithoutName = { hide: jest.fn() };
|
||||
const serviceWithEmptyName = { name: '', create: jest.fn() };
|
||||
const serviceWithoutName = { create: jest.fn() };
|
||||
|
||||
servicesManager.registerService(serviceWithEmptyName);
|
||||
servicesManager.registerService(serviceWithoutName);
|
||||
@@ -48,23 +62,37 @@ describe('ServicesManager.js', () => {
|
||||
expect(log.warn.mock.calls.length).toBe(2);
|
||||
});
|
||||
|
||||
it('tracks which services have been registered', () => {
|
||||
const service = {
|
||||
name: 'UINotificationService',
|
||||
};
|
||||
it('logs a warning if the service does not have a create factory function', () => {
|
||||
const serviceWithoutCreate = { name: 'UINotificationService' };
|
||||
|
||||
servicesManager.registerService(service);
|
||||
|
||||
expect(servicesManager.registeredServiceNames).toContain(service.name);
|
||||
});
|
||||
|
||||
it('logs a warning if the service has an name that has already been registered', () => {
|
||||
const service = { name: 'UINotificationService' };
|
||||
|
||||
servicesManager.registerService(service);
|
||||
servicesManager.registerService(service);
|
||||
servicesManager.registerService(serviceWithoutCreate);
|
||||
|
||||
expect(log.warn.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('tracks which services have been registered', () => {
|
||||
servicesManager.registerService(fakeService);
|
||||
|
||||
expect(servicesManager.registeredServiceNames).toContain(
|
||||
fakeService.name
|
||||
);
|
||||
});
|
||||
|
||||
it('logs a warning if the service has an name that has already been registered', () => {
|
||||
servicesManager.registerService(fakeService);
|
||||
servicesManager.registerService(fakeService);
|
||||
|
||||
expect(log.warn.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('pass configuration to service create factory function', () => {
|
||||
const configuration = { config: 'Some configuration' };
|
||||
|
||||
servicesManager.registerService(fakeService, configuration);
|
||||
|
||||
expect(fakeService.create.mock.calls[0][0]).toEqual({
|
||||
configuration,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,59 +17,60 @@
|
||||
* @property {Object} contentProps The dialog content props.
|
||||
* @property {boolean} [isDraggable=true] Controls if dialog content is draggable or not.
|
||||
* @property {boolean} [showOverlay=false] Controls dialog overlay.
|
||||
* @property {boolean} [centralize=false] Center the dialog on the screen.
|
||||
* @property {boolean} [preservePosition=true] Use last position instead of default.
|
||||
* @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at.
|
||||
* @property {ElementPosition} position If this property is present, the item becomes 'controlled' and is not responsive to user input.
|
||||
* @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.
|
||||
* @property {Function} onStop Called when dragging stops.
|
||||
* @property {Function} onDrag Called while dragging.
|
||||
*/
|
||||
|
||||
const uiDialogServicePublicAPI = {
|
||||
name: 'UIDialogService',
|
||||
dismiss,
|
||||
dismissAll,
|
||||
create,
|
||||
const name = 'UIDialogService';
|
||||
|
||||
const publicAPI = {
|
||||
name,
|
||||
dismiss: _dismiss,
|
||||
dismissAll: _dismissAll,
|
||||
create: _create,
|
||||
setServiceImplementation,
|
||||
};
|
||||
|
||||
const uiDialogServiceImplementation = {
|
||||
const serviceImplementation = {
|
||||
_dismiss: () => console.warn('dismiss() NOT IMPLEMENTED'),
|
||||
_dismissAll: () => console.warn('dismissAll() NOT IMPLEMENTED'),
|
||||
_create: () => console.warn('create() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
function createUIDialogService() {
|
||||
return uiDialogServicePublicAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a new UI dialog;
|
||||
*
|
||||
* @param {DialogProps} props { id, content, contentProps, onStart, onDrag, onStop, isDraggable, showOverlay, defaultPosition, position }
|
||||
* @param {DialogProps} props { id, content, contentProps, onStart, onDrag, onStop, centralize, isDraggable, showOverlay, preservePosition, defaultPosition }
|
||||
*/
|
||||
function create({
|
||||
function _create({
|
||||
id,
|
||||
content,
|
||||
contentProps,
|
||||
onStart,
|
||||
onDrag,
|
||||
onStop,
|
||||
centralize = false,
|
||||
preservePosition = true,
|
||||
isDraggable = true,
|
||||
showOverlay = false,
|
||||
defaultPosition,
|
||||
position,
|
||||
}) {
|
||||
return uiDialogServiceImplementation._create({
|
||||
return serviceImplementation._create({
|
||||
id,
|
||||
content,
|
||||
contentProps,
|
||||
onStart,
|
||||
onDrag,
|
||||
onStop,
|
||||
centralize,
|
||||
preservePosition,
|
||||
isDraggable,
|
||||
showOverlay,
|
||||
defaultPosition,
|
||||
position,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,8 +79,8 @@ function create({
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
function dismissAll() {
|
||||
return uiDialogServiceImplementation._dismissAll();
|
||||
function _dismissAll() {
|
||||
return serviceImplementation._dismissAll();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,8 +88,8 @@ function dismissAll() {
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
function dismiss({ id }) {
|
||||
return uiDialogServiceImplementation._dismiss({ id });
|
||||
function _dismiss({ id }) {
|
||||
return serviceImplementation._dismiss({ id });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,14 +107,19 @@ function setServiceImplementation({
|
||||
create: createImplementation,
|
||||
}) {
|
||||
if (dismissImplementation) {
|
||||
uiDialogServiceImplementation._dismiss = dismissImplementation;
|
||||
serviceImplementation._dismiss = dismissImplementation;
|
||||
}
|
||||
if (dismissAllImplementation) {
|
||||
uiDialogServiceImplementation._dismissAll = dismissAllImplementation;
|
||||
serviceImplementation._dismissAll = dismissAllImplementation;
|
||||
}
|
||||
if (createImplementation) {
|
||||
uiDialogServiceImplementation._create = createImplementation;
|
||||
serviceImplementation._create = createImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
export default createUIDialogService;
|
||||
export default {
|
||||
name,
|
||||
create: ({ configuration = {} }) => {
|
||||
return publicAPI;
|
||||
},
|
||||
};
|
||||
@@ -11,28 +11,26 @@
|
||||
* @property {string} [customClassName=null] The custom class to style the modal.
|
||||
*/
|
||||
|
||||
const uiModalServicePublicAPI = {
|
||||
name: 'UIModalService',
|
||||
hide,
|
||||
show,
|
||||
const name = 'UIModalService';
|
||||
|
||||
const publicAPI = {
|
||||
name,
|
||||
hide: _hide,
|
||||
show: _show,
|
||||
setServiceImplementation,
|
||||
};
|
||||
|
||||
const uiModalServiceImplementation = {
|
||||
const serviceImplementation = {
|
||||
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
||||
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
function createUIModalService() {
|
||||
return uiModalServicePublicAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a new UI modal;
|
||||
*
|
||||
* @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, closeButton, title, customClassName }
|
||||
*/
|
||||
function show({
|
||||
function _show({
|
||||
content = null,
|
||||
contentProps = null,
|
||||
shouldCloseOnEsc = false,
|
||||
@@ -41,7 +39,7 @@ function show({
|
||||
title = null,
|
||||
customClassName = null,
|
||||
}) {
|
||||
return uiModalServiceImplementation._show({
|
||||
return serviceImplementation._show({
|
||||
content,
|
||||
contentProps,
|
||||
shouldCloseOnEsc,
|
||||
@@ -57,8 +55,8 @@ function show({
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
function hide() {
|
||||
return uiModalServiceImplementation._hide();
|
||||
function _hide() {
|
||||
return serviceImplementation._hide();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,11 +72,16 @@ function setServiceImplementation({
|
||||
show: showImplementation,
|
||||
}) {
|
||||
if (hideImplementation) {
|
||||
uiModalServiceImplementation._hide = hideImplementation;
|
||||
serviceImplementation._hide = hideImplementation;
|
||||
}
|
||||
if (showImplementation) {
|
||||
uiModalServiceImplementation._show = showImplementation;
|
||||
serviceImplementation._show = showImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
export default createUIModalService;
|
||||
export default {
|
||||
name,
|
||||
create: ({ configuration = {} }) => {
|
||||
return publicAPI;
|
||||
},
|
||||
};
|
||||
@@ -10,22 +10,20 @@
|
||||
* @property {boolean} [autoClose=true]
|
||||
*/
|
||||
|
||||
const uiNotificationServicePublicAPI = {
|
||||
name: 'UINotificationService',
|
||||
hide,
|
||||
show,
|
||||
const name = 'UINotificationService';
|
||||
|
||||
const publicAPI = {
|
||||
name,
|
||||
hide: _hide,
|
||||
show: _show,
|
||||
setServiceImplementation,
|
||||
};
|
||||
|
||||
const uiNotificationServiceImplementation = {
|
||||
const serviceImplementation = {
|
||||
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
||||
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
function createUINotificationService() {
|
||||
return uiNotificationServicePublicAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and show a new UI notification; returns the
|
||||
* ID of the created notification.
|
||||
@@ -33,7 +31,7 @@ function createUINotificationService() {
|
||||
* @param {Notification} notification { title, message, duration, position, type, autoClose}
|
||||
* @returns {number} id
|
||||
*/
|
||||
function show({
|
||||
function _show({
|
||||
title,
|
||||
message,
|
||||
duration = 5000,
|
||||
@@ -41,7 +39,7 @@ function show({
|
||||
type = 'info',
|
||||
autoClose = true,
|
||||
}) {
|
||||
return uiNotificationServiceImplementation._show({
|
||||
return serviceImplementation._show({
|
||||
title,
|
||||
message,
|
||||
duration,
|
||||
@@ -57,8 +55,8 @@ function show({
|
||||
* @param {number} id - id of the notification to hide/dismiss
|
||||
* @returns undefined
|
||||
*/
|
||||
function hide(id) {
|
||||
return uiNotificationServiceImplementation._hide({ id });
|
||||
function _hide(id) {
|
||||
return serviceImplementation._hide({ id });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,11 +72,16 @@ function setServiceImplementation({
|
||||
show: showImplementation,
|
||||
}) {
|
||||
if (hideImplementation) {
|
||||
uiNotificationServiceImplementation._hide = hideImplementation;
|
||||
serviceImplementation._hide = hideImplementation;
|
||||
}
|
||||
if (showImplementation) {
|
||||
uiNotificationServiceImplementation._show = showImplementation;
|
||||
serviceImplementation._show = showImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
export default createUINotificationService;
|
||||
export default {
|
||||
name,
|
||||
create: ({ configuration = {} }) => {
|
||||
return publicAPI;
|
||||
},
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
import ServicesManager from './ServicesManager.js';
|
||||
import createUINotificationService from './UINotificationService';
|
||||
import createUIModalService from './UIModalService';
|
||||
import createUIDialogService from './UIDialogService';
|
||||
import UINotificationService from './UINotificationService';
|
||||
import UIModalService from './UIModalService';
|
||||
import UIDialogService from './UIDialogService';
|
||||
|
||||
export {
|
||||
createUINotificationService,
|
||||
createUIModalService,
|
||||
createUIDialogService,
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
ServicesManager,
|
||||
};
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.3...@ohif/i18n@0.52.4) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/i18n
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.3](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.2...@ohif/i18n@0.52.3) (2019-12-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* translations ([#1234](https://github.com/OHIF/Viewers/issues/1234)) ([30b9e44](https://github.com/OHIF/Viewers/commit/30b9e4422073557287ef26a80b38eeb3f3fcff4c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.2](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.1...@ohif/i18n@0.52.2) (2019-11-28)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/i18n",
|
||||
"version": "0.52.2",
|
||||
"version": "0.52.4",
|
||||
"description": "Internationalization library for The OHIF Viewer",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -29,19 +29,20 @@ function addLocales(newLocales) {
|
||||
customDebug(resourceBundle, 'info');
|
||||
}
|
||||
|
||||
// Note: Developers can add the API key to use the in-context editor.
|
||||
// DO NOT commit the API key
|
||||
const config = (window.config && window.config.i18n) || {};
|
||||
|
||||
/*
|
||||
* Note: Developers can add the API key to use the
|
||||
* in-context editor using environment variables.
|
||||
* (DO NOT commit the API key)
|
||||
*/
|
||||
const locizeOptions = {
|
||||
projectId: config.LOCIZE_PROJECTID,
|
||||
apiKey: config.LOCIZE_API_KEY,
|
||||
projectId: process.env.LOCIZE_PROJECTID,
|
||||
apiKey: process.env.LOCIZE_API_KEY,
|
||||
referenceLng: 'en-US',
|
||||
fallbacklng: 'en-US',
|
||||
};
|
||||
|
||||
const envUseLocize = !!config.USE_LOCIZE;
|
||||
const envApiKeyAvailable = !!config.LOCIZE_API_KEY;
|
||||
const envUseLocize = !!process.env.USE_LOCIZE;
|
||||
const envApiKeyAvailable = !!process.env.LOCIZE_API_KEY;
|
||||
const DEFAULT_LANGUAGE = 'en-US';
|
||||
|
||||
function initI18n(
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"Clear": "Clear",
|
||||
"Coronal": "Coronal",
|
||||
"Crosshairs": "Crosshairs",
|
||||
"Download": "Download",
|
||||
"Ellipse": "Ellipse",
|
||||
"Elliptical": "Elliptical",
|
||||
"Flip H": "Flip H",
|
||||
|
||||
@@ -10,18 +10,19 @@
|
||||
"Clear": "Limpiar",
|
||||
"Coronal": "Coronal",
|
||||
"Crosshairs": "Punto de mira",
|
||||
"Download": "Descargar",
|
||||
"Ellipse": "Elipse",
|
||||
"Elliptical": "Elíptico",
|
||||
"Flip H": "Espejo H",
|
||||
"Flip V": "Espejo V",
|
||||
"Flip H": "Voltear H",
|
||||
"Flip V": "Voltear V",
|
||||
"Freehand": "Mano alzada",
|
||||
"Invert": "Invertir",
|
||||
"Invert": "Negativo",
|
||||
"Layout": "$t(Common:Layout)",
|
||||
"Length": "Longitud",
|
||||
"Levels": "Niveles",
|
||||
"Magnify": "Aumentar",
|
||||
"Levels": "W/L",
|
||||
"Magnify": "Lupa",
|
||||
"Manual": "Manual",
|
||||
"Measurements": "Mediciones",
|
||||
"Measurements": "Medidas",
|
||||
"More": "$t(Common:More)",
|
||||
"Next": "$t(Common:Next)",
|
||||
"Pan": "Mover",
|
||||
@@ -32,11 +33,11 @@
|
||||
"Rectangle": "Rectángulo",
|
||||
"Reset": "$t(Common:Reset)",
|
||||
"Reset to Defaults": "$t(Common:Reset) por defecto",
|
||||
"Rotate Right": "Rotar ->",
|
||||
"Rotate Right": "Girar ->",
|
||||
"Sagittal": "Sagital",
|
||||
"Save": "Guardar",
|
||||
"Stack Scroll": "Scroll en pila",
|
||||
"Stack Scroll": "Scroll",
|
||||
"Stop": "$t(Common:Stop)",
|
||||
"Themes": "Temas",
|
||||
"Zoom": "Zoom"
|
||||
"Zoom": "Ampliar"
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"Image": "Imagen",
|
||||
"Layout": "Disposición",
|
||||
"Layout": "Formato",
|
||||
"Measurements": "Medidas",
|
||||
"More": "Más",
|
||||
"Next": "Siguiente",
|
||||
"Play": "Play",
|
||||
"Previous": "Anterior",
|
||||
"Reset": "Resetear",
|
||||
"Reset": "Restaurar",
|
||||
"RowsPerPage": "filas por página",
|
||||
"Series": "Secuencia",
|
||||
"Show": "Mostrar",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"Clear": "Limpar",
|
||||
"Coronal": "Coronal",
|
||||
"Crosshairs": "Localizador",
|
||||
"Download": "Baixar",
|
||||
"Ellipse": "Elipse",
|
||||
"Elliptical": "Elíptico",
|
||||
"Flip H": "Inverter H",
|
||||
@@ -30,8 +31,8 @@
|
||||
"Probe": "Prova",
|
||||
"ROI Window": "Janela ROI",
|
||||
"Rectangle": "Retângulo",
|
||||
"Reset": "Resetar",
|
||||
"Reset to Defaults": "Restaurar para o Padrão",
|
||||
"Reset": "$t(Common:Reset)",
|
||||
"Reset to Defaults": "$t(Common:Reset) para o Padrão",
|
||||
"Rotate Right": "Girar à direita",
|
||||
"Sagittal": "Sagital",
|
||||
"Save": "Salvar",
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"Next": "Próximo",
|
||||
"Play": "Play",
|
||||
"Previous": "Anterior",
|
||||
"Reset": "Reset",
|
||||
"Reset": "Restaurar",
|
||||
"Stop": "Stop"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Cancel": "Cancelar",
|
||||
"Reset to Defaults": "Restaurar Default",
|
||||
"Reset to Defaults": "$t(Common:Reset) para Padrão",
|
||||
"ResetDefaultMessage": "Preferências resetadas com sucesso. <br /> Você deve <strong>Salvar</strong> para que essa ação seja realizada.",
|
||||
"Save": "Salvar",
|
||||
"SaveMessage": "Preferências salvas",
|
||||
|
||||
@@ -3,6 +3,93 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.3...@ohif/ui@1.1.4) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.2...@ohif/ui@1.1.3) (2019-12-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow empty values for dimensions ([#1295](https://github.com/OHIF/Viewers/issues/1295)) ([cd2da34](https://github.com/OHIF/Viewers/commit/cd2da349e5212cccdd8e65ffa3f7fdc6bad1057c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.1...@ohif/ui@1.1.2) (2019-12-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* translations ([#1234](https://github.com/OHIF/Viewers/issues/1234)) ([30b9e44](https://github.com/OHIF/Viewers/commit/30b9e4422073557287ef26a80b38eeb3f3fcff4c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.0...@ohif/ui@1.1.1) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.0.1...@ohif/ui@1.1.0) (2019-12-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.0.0...@ohif/ui@1.0.1) (2019-12-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.65.4...@ohif/ui@1.0.0) (2019-12-09)
|
||||
|
||||
|
||||
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.65.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.65.3...@ohif/ui@0.65.4) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.65.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.65.2...@ohif/ui@0.65.3) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.65.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.65.1...@ohif/ui@0.65.2) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/ui",
|
||||
"version": "0.65.2",
|
||||
"version": "1.1.4",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -32,69 +32,52 @@ const ViewportDownloadForm = ({
|
||||
maximumSize,
|
||||
canvasClass,
|
||||
}) => {
|
||||
const [t] = useTranslation('ViewportDownloadForm');
|
||||
const [t] = useTranslation('Buttons');
|
||||
|
||||
const [filename, setFilename] = useState(DEFAULT_FILENAME);
|
||||
const [fileType, setFileType] = useState('jpg');
|
||||
|
||||
const [height, setHeight] = useState(defaultSize);
|
||||
const [width, setWidth] = useState(defaultSize);
|
||||
const [dimensions, setDimensions] = useState({
|
||||
width: defaultSize,
|
||||
height: defaultSize,
|
||||
});
|
||||
|
||||
const [showAnnotations, setShowAnnotations] = useState(true);
|
||||
|
||||
const [keepAspect, setKeepAspect] = useState(true);
|
||||
const [lastImage, setLastImage] = useState();
|
||||
|
||||
const [viewportElement, setViewportElement] = useState();
|
||||
const [viewportElementHeight, setViewportElementHeight] = useState(
|
||||
minimumSize
|
||||
);
|
||||
const [viewportElementWidth, setViewportElementWidth] = useState(minimumSize);
|
||||
const [viewportElementDimensions, setViewportElementDimensions] = useState({
|
||||
width: defaultSize,
|
||||
height: defaultSize,
|
||||
});
|
||||
|
||||
const [downloadCanvas, setDownloadCanvas] = useState({
|
||||
ref: createRef(),
|
||||
width: minimumSize,
|
||||
height: minimumSize,
|
||||
width: defaultSize,
|
||||
height: defaultSize,
|
||||
});
|
||||
|
||||
const [viewportPreview, setViewportPreview] = useState({
|
||||
src: null,
|
||||
width: minimumSize,
|
||||
height: minimumSize,
|
||||
width: defaultSize,
|
||||
height: defaultSize,
|
||||
});
|
||||
|
||||
// Cornerstone's `enable/disable`
|
||||
useEffect(() => {
|
||||
enableViewport(viewportElement);
|
||||
|
||||
return () => {
|
||||
disableViewport(viewportElement);
|
||||
|
||||
setHeight(defaultSize);
|
||||
setWidth(defaultSize);
|
||||
};
|
||||
}, [defaultSize, disableViewport, enableViewport, viewportElement]);
|
||||
}, [disableViewport, enableViewport, viewportElement]);
|
||||
|
||||
useEffect(() => {
|
||||
const { width, height } = viewportElementDimensions;
|
||||
const validSize = value => (value >= minimumSize ? value : minimumSize);
|
||||
const loadAndUpdateViewports = async () => {
|
||||
const {
|
||||
image,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
} = await loadImage(activeViewport, viewportElement, width, height);
|
||||
setLastImage(image);
|
||||
|
||||
await loadImage(activeViewport, viewportElement, width, height);
|
||||
toggleAnnotations(showAnnotations, viewportElement);
|
||||
|
||||
setViewportElementHeight(validSize(scaledHeight));
|
||||
setViewportElementWidth(validSize(scaledWidth));
|
||||
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
height: validSize(scaledHeight),
|
||||
width: validSize(scaledWidth),
|
||||
}));
|
||||
|
||||
const {
|
||||
dataUrl,
|
||||
width: viewportElementWidth,
|
||||
@@ -118,8 +101,6 @@ const ViewportDownloadForm = ({
|
||||
activeViewport,
|
||||
viewportElement,
|
||||
showAnnotations,
|
||||
height,
|
||||
width,
|
||||
loadImage,
|
||||
toggleAnnotations,
|
||||
updateViewportPreview,
|
||||
@@ -127,54 +108,41 @@ const ViewportDownloadForm = ({
|
||||
downloadCanvas.ref,
|
||||
minimumSize,
|
||||
maximumSize,
|
||||
viewportElementDimensions,
|
||||
]);
|
||||
|
||||
const onHeightChange = event => {
|
||||
const newHeight = Math.min(event.target.value, maximumSize);
|
||||
setHeight(newHeight);
|
||||
/**
|
||||
* @param {object} event - Input change event
|
||||
* @param {string} dimension - "height" | "width"
|
||||
*/
|
||||
const onDimensionsChange = (event, dimension) => {
|
||||
const sanitizedTargetValue = event.target.value.replace(/\D/, '');
|
||||
const isEmpty = sanitizedTargetValue === '';
|
||||
const updatedDimension = isEmpty
|
||||
? ''
|
||||
: Math.min(sanitizedTargetValue, maximumSize);
|
||||
|
||||
setViewportElementHeight(newHeight);
|
||||
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
height: newHeight,
|
||||
}));
|
||||
|
||||
if (keepAspect) {
|
||||
const multiplier = newHeight / lastImage.height;
|
||||
const newWidth = Math.round(lastImage.width * multiplier);
|
||||
|
||||
setWidth(newWidth);
|
||||
setViewportElementWidth(newWidth);
|
||||
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
width: newWidth,
|
||||
}));
|
||||
if (updatedDimension === dimensions.width) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const onWidthChange = event => {
|
||||
const newWidth = Math.min(event.target.value, maximumSize);
|
||||
setWidth(newWidth);
|
||||
|
||||
setViewportElementWidth(newWidth);
|
||||
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
width: newWidth,
|
||||
}));
|
||||
|
||||
if (keepAspect) {
|
||||
const multiplier = newWidth / lastImage.width;
|
||||
const newHeight = Math.round(lastImage.height * multiplier);
|
||||
|
||||
setHeight(newHeight);
|
||||
setViewportElementHeight(newHeight);
|
||||
// In current code, keepAspect is always `true`
|
||||
// And we always start w/ a square width/height
|
||||
setDimensions({
|
||||
width: updatedDimension,
|
||||
height: updatedDimension,
|
||||
});
|
||||
|
||||
// Only update if value is non-empty
|
||||
if (!isEmpty) {
|
||||
setViewportElementDimensions({
|
||||
height: updatedDimension,
|
||||
width: updatedDimension,
|
||||
});
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
height: newHeight,
|
||||
height: updatedDimension,
|
||||
width: updatedDimension,
|
||||
}));
|
||||
}
|
||||
};
|
||||
@@ -196,26 +164,22 @@ const ViewportDownloadForm = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="file-info-container">
|
||||
<div className="file-info-container" data-cy="file-info-container">
|
||||
<div className="col">
|
||||
<div className="width">
|
||||
<TextInput
|
||||
type="number"
|
||||
min={minimumSize}
|
||||
max={maximumSize}
|
||||
value={width}
|
||||
data-cy="image-width"
|
||||
value={dimensions.width}
|
||||
label={t('Image width (px)')}
|
||||
onChange={onWidthChange}
|
||||
onChange={evt => onDimensionsChange(evt, 'height')}
|
||||
/>
|
||||
</div>
|
||||
<div className="height">
|
||||
<TextInput
|
||||
type="number"
|
||||
min={minimumSize}
|
||||
max={maximumSize}
|
||||
value={height}
|
||||
data-cy="image-height"
|
||||
value={dimensions.height}
|
||||
label={t('Image height (px)')}
|
||||
onChange={onHeightChange}
|
||||
onChange={evt => onDimensionsChange(evt, 'width')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -224,6 +188,7 @@ const ViewportDownloadForm = ({
|
||||
<div className="file-name">
|
||||
<TextInput
|
||||
type="text"
|
||||
data-cy="file-name"
|
||||
value={filename}
|
||||
onChange={event => setFilename(event.target.value)}
|
||||
label={t('File name')}
|
||||
@@ -233,6 +198,7 @@ const ViewportDownloadForm = ({
|
||||
<div className="file-type">
|
||||
<Select
|
||||
value={fileType}
|
||||
data-cy="file-type"
|
||||
onChange={event => setFileType(event.target.value)}
|
||||
options={FILE_TYPE_OPTIONS}
|
||||
label={t('File type')}
|
||||
@@ -245,6 +211,7 @@ const ViewportDownloadForm = ({
|
||||
<label htmlFor="show-annotations" className="form-check-label">
|
||||
<input
|
||||
id="show-annotations"
|
||||
data-cy="show-annotations"
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={showAnnotations}
|
||||
@@ -258,8 +225,8 @@ const ViewportDownloadForm = ({
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: viewportElementHeight,
|
||||
width: viewportElementWidth,
|
||||
height: viewportElementDimensions.height,
|
||||
width: viewportElementDimensions.width,
|
||||
position: 'absolute',
|
||||
left: '9999px',
|
||||
}}
|
||||
@@ -278,23 +245,33 @@ const ViewportDownloadForm = ({
|
||||
></canvas>
|
||||
</div>
|
||||
|
||||
<div className="preview">
|
||||
<div className="preview" data-cy="image-preview">
|
||||
<h4> {t('Image Preview')}</h4>
|
||||
<img
|
||||
className="viewport-preview"
|
||||
src={viewportPreview.src}
|
||||
alt="Viewport Preview"
|
||||
data-cy="viewport-preview-img"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
<div className="action-cancel">
|
||||
<button type="button" className="btn btn-danger" onClick={onClose}>
|
||||
<button
|
||||
type="button"
|
||||
data-cy="cancel-btn"
|
||||
className="btn btn-danger"
|
||||
onClick={onClose}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="action-save">
|
||||
<button onClick={downloadImage} className="btn btn-primary">
|
||||
<button
|
||||
onClick={downloadImage}
|
||||
className="btn btn-primary"
|
||||
data-cy="download-btn"
|
||||
>
|
||||
{t('Download')}
|
||||
</button>
|
||||
</div>
|
||||
@@ -312,6 +289,7 @@ ViewportDownloadForm.propTypes = {
|
||||
toggleAnnotations: PropTypes.func.isRequired,
|
||||
loadImage: PropTypes.func.isRequired,
|
||||
downloadBlob: PropTypes.func.isRequired,
|
||||
/** A default width & height, between the minimum and maximum size */
|
||||
defaultSize: PropTypes.number.isRequired,
|
||||
minimumSize: PropTypes.number.isRequired,
|
||||
maximumSize: PropTypes.number.isRequired,
|
||||
|
||||
+6
-7
@@ -1,20 +1,19 @@
|
||||
.ToolContextMenu {
|
||||
position: absolute;
|
||||
.ContextMenu {
|
||||
position: relative;
|
||||
background-color: white;
|
||||
border: 1px solid white;
|
||||
border-radius: 5px;
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.ToolContextMenu > ul {
|
||||
.ContextMenu>ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ToolContextMenu > ul > li > button {
|
||||
.ContextMenu>ul>li>button {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
@@ -25,10 +24,10 @@
|
||||
background: none;
|
||||
}
|
||||
|
||||
.ToolContextMenu > ul > li > button:hover {
|
||||
.ContextMenu>ul>li>button:hover {
|
||||
color: #16202b;
|
||||
}
|
||||
|
||||
.ToolContextMenu > ul > li > button:active {
|
||||
.ContextMenu>ul>li>button:active {
|
||||
color: #79f9fe;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './ContextMenu.css';
|
||||
|
||||
const ContextMenu = ({ items, onClick }) => {
|
||||
return (
|
||||
<div className="ContextMenu">
|
||||
<ul>
|
||||
{items.map((item, index) => (
|
||||
<li key={index}>
|
||||
<button className="form-action" onClick={() => onClick(item)}>
|
||||
<span key={index}>{item.label}</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ContextMenu.propTypes = {
|
||||
items: PropTypes.array.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ContextMenu;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ContextMenu } from './ContextMenu.js';
|
||||
@@ -17,6 +17,7 @@ import { RoundedButtonGroup } from './roundedButtonGroup';
|
||||
import { SelectTree } from './selectTree';
|
||||
import { SimpleDialog } from './simpleDialog';
|
||||
import { OHIFModal } from './ohifModal';
|
||||
import { ContextMenu } from './contextMenu';
|
||||
import {
|
||||
PageToolbar,
|
||||
StudyList,
|
||||
@@ -27,6 +28,7 @@ import { ToolbarSection } from './toolbarSection';
|
||||
import { Tooltip } from './tooltip';
|
||||
|
||||
export {
|
||||
ContextMenu,
|
||||
Checkbox,
|
||||
CineDialog,
|
||||
ViewportDownloadForm,
|
||||
|
||||
@@ -22,8 +22,9 @@ class MeasurementTable extends Component {
|
||||
onDeleteClick: PropTypes.func,
|
||||
onEditDescriptionClick: PropTypes.func,
|
||||
selectedMeasurementNumber: PropTypes.number,
|
||||
overwallWarnings: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
saveFunction: PropTypes.func,
|
||||
onSaveComplete: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -38,8 +39,9 @@ class MeasurementTable extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const hasOverallWarnings =
|
||||
this.props.overallWarnings.warningList.length > 0;
|
||||
const { overallWarnings, saveFunction, t } = this.props;
|
||||
const hasOverallWarnings = overallWarnings.warningList.length > 0;
|
||||
|
||||
return (
|
||||
<div className="measurementTable">
|
||||
<div className="measurementTableHeader">
|
||||
@@ -55,7 +57,7 @@ class MeasurementTable extends Component {
|
||||
style={{}}
|
||||
>
|
||||
<div className="warningTitle">
|
||||
{this.props.t('Criteria nonconformities')}
|
||||
{t('Criteria nonconformities')}
|
||||
</div>
|
||||
<div className="warningContent">
|
||||
{this.getWarningContent()}
|
||||
@@ -75,10 +77,42 @@ class MeasurementTable extends Component {
|
||||
<ScrollableArea>
|
||||
<div>{this.getMeasurementsGroups()}</div>
|
||||
</ScrollableArea>
|
||||
<div className="measurementTableFooter">
|
||||
{saveFunction && (
|
||||
<button onClick={this.saveFunction} className="saveBtn">
|
||||
<Icon name="save" width="14px" height="14px" />
|
||||
Save measurements
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
saveFunction = async event => {
|
||||
const { saveFunction, onSaveComplete } = this.props;
|
||||
if (saveFunction) {
|
||||
try {
|
||||
const result = await saveFunction();
|
||||
if (onSaveComplete) {
|
||||
onSaveComplete({
|
||||
title: 'STOW SR',
|
||||
message: result.message,
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (onSaveComplete) {
|
||||
onSaveComplete({
|
||||
title: 'STOW SR',
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getMeasurementsGroups = () => {
|
||||
return this.props.measurementCollection.map((measureGroup, index) => {
|
||||
return (
|
||||
@@ -157,7 +191,7 @@ class MeasurementTable extends Component {
|
||||
};
|
||||
|
||||
getWarningContent = () => {
|
||||
const { warningList = '' } = this.props.overwallWarnings;
|
||||
const { warningList = '' } = this.props.overallWarnings;
|
||||
|
||||
if (Array.isArray(warningList)) {
|
||||
const listedWarnings = warningList.map((warn, index) => {
|
||||
|
||||
@@ -1,92 +1,117 @@
|
||||
.measurementTable
|
||||
background-color: var(--primary-background-color)
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%
|
||||
width: 100%
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.maxMeasurements
|
||||
border-radius: 3px;
|
||||
color: var(--primary-background-color);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
height: 19px;
|
||||
line-height: 17px;
|
||||
margin-top: 22px;
|
||||
margin-right: 15px;
|
||||
padding: 2px 6px 0;
|
||||
text-transform: uppercase;
|
||||
transition: all 0.3s ease;
|
||||
background-color: var(--default-color);
|
||||
border-radius: 3px
|
||||
color: var(--primary-background-color)
|
||||
font-size: 12px
|
||||
font-weight: 500
|
||||
height: 19px
|
||||
line-height: 17px
|
||||
margin-top: 22px
|
||||
margin-right: 15px
|
||||
padding: 2px 6px 0
|
||||
text-transform: uppercase
|
||||
transition: all 0.3s ease
|
||||
background-color: var(--default-color)
|
||||
|
||||
.measurementTableHeader
|
||||
padding-left: 45px;
|
||||
padding-left: 45px
|
||||
|
||||
.measurementTableHeaderItem
|
||||
display: inline-block;
|
||||
margin-top: 9px;
|
||||
margin-left: 9px;
|
||||
padding-left: 9px;
|
||||
width: 90px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
border-left: 1px solid var(--text-secondary-color);
|
||||
color: var(--text-primary-color);
|
||||
display: inline-block
|
||||
margin-top: 9px
|
||||
margin-left: 9px
|
||||
padding-left: 9px
|
||||
width: 90px
|
||||
font-size: 14px
|
||||
line-height: 20px
|
||||
border-left: 1px solid var(--text-secondary-color)
|
||||
color: var(--text-primary-color)
|
||||
|
||||
.timepointLabel
|
||||
color: var(--text-secondary-color);
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary-color)
|
||||
font-size: 12px
|
||||
|
||||
.warning-status
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
height: 40px;
|
||||
padding-left: 7px;
|
||||
left: 15px;
|
||||
top: 7px;
|
||||
display: block
|
||||
position: absolute
|
||||
width: 50px
|
||||
height: 40px
|
||||
padding-left: 7px
|
||||
left: 15px
|
||||
top: 7px
|
||||
|
||||
.warning-border
|
||||
display: block;
|
||||
border: 2px solid #e29e4a;
|
||||
border-radius: 36px;
|
||||
padding: 6px 7px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: block
|
||||
border: 2px solid #e29e4a
|
||||
border-radius: 36px
|
||||
padding: 6px 7px
|
||||
width: 40px
|
||||
height: 40px
|
||||
|
||||
svg
|
||||
color: #e29e4a;
|
||||
width: 22px;
|
||||
height: 20px;
|
||||
pointer-events: inherit;
|
||||
color: #e29e4a
|
||||
width: 22px
|
||||
height: 20px
|
||||
pointer-events: inherit
|
||||
|
||||
.measurementTableFooter
|
||||
padding: 10px
|
||||
color: #fff
|
||||
text-align: center
|
||||
z-index: 1
|
||||
|
||||
.saveBtn
|
||||
border: 1px solid var(--ui-border-color-active)
|
||||
border-radius: 25px
|
||||
background-color: var(--active-color)
|
||||
color: var(--ui-gray-dark)
|
||||
line-height: 25px
|
||||
padding: 0 20px
|
||||
outline: none
|
||||
cursor: pointer
|
||||
|
||||
&:hover, &:active
|
||||
background-color: var(--ui-sky-blue)
|
||||
|
||||
svg
|
||||
margin-right: 4px
|
||||
position: relative
|
||||
top: 2px
|
||||
|
||||
|
||||
.tooltip-warning
|
||||
&.in
|
||||
opacity 1;
|
||||
opacity 1
|
||||
|
||||
.tooltip-inner
|
||||
padding: 0
|
||||
|
||||
.warningTitle
|
||||
text-align: left
|
||||
background-color: var(--ui-gray-darkest);
|
||||
padding: 7px 15px;
|
||||
border-top-left-radius: 7px;
|
||||
border-top-right-radius: 7px;
|
||||
background-color: var(--ui-gray-darkest)
|
||||
padding: 7px 15px
|
||||
border-top-left-radius: 7px
|
||||
border-top-right-radius: 7px
|
||||
|
||||
.warningContent
|
||||
text-align center
|
||||
background-color: var(--ui-gray);
|
||||
border-bottom-left-radius: 7px;
|
||||
border-bottom-right-radius: 7px;
|
||||
background-color: var(--ui-gray)
|
||||
border-bottom-left-radius: 7px
|
||||
border-bottom-right-radius: 7px
|
||||
|
||||
ol
|
||||
margin-top: auto;
|
||||
padding-inline-start: 20px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 25px;
|
||||
text-align: left;
|
||||
margin-top: auto
|
||||
padding-inline-start: 20px
|
||||
padding-top: 10px
|
||||
padding-bottom: 10px
|
||||
padding-left: 25px
|
||||
text-align: left
|
||||
|
||||
li
|
||||
padding-right: 10px;
|
||||
padding-right: 10px
|
||||
@@ -29,7 +29,7 @@ const OHIFModal = ({
|
||||
const renderHeader = () => {
|
||||
return (
|
||||
title && (
|
||||
<div className="OHIFModal__header">
|
||||
<div className="OHIFModal__header" data-cy="modal-header">
|
||||
<h4>{title}</h4>
|
||||
{closeButton && (
|
||||
<button data-cy="close-button" onClick={onClose}>
|
||||
@@ -44,6 +44,7 @@ const OHIFModal = ({
|
||||
return (
|
||||
<Modal
|
||||
className={classNames('OHIFModal', className)}
|
||||
data-cy="modal"
|
||||
shouldCloseOnEsc={shouldCloseOnEsc}
|
||||
isOpen={isOpen}
|
||||
title={title}
|
||||
@@ -51,7 +52,9 @@ const OHIFModal = ({
|
||||
>
|
||||
<>
|
||||
{renderHeader()}
|
||||
<div className="OHIFModal__content">{children}</div>
|
||||
<div className="OHIFModal__content" data-cy="modal-content">
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
position: relative
|
||||
|
||||
.simpleDialog
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
|
||||
@@ -20,7 +20,30 @@ export const useDialog = () => useContext(DialogContext);
|
||||
const DialogProvider = ({ children, service }) => {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [dialogs, setDialogs] = useState([]);
|
||||
const [lastDialogId, setLastDialogId] = useState(null);
|
||||
const [lastDialogPosition, setLastDialogPosition] = useState(null);
|
||||
const [centerPositions, setCenterPositions] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setCenterPositions(
|
||||
dialogs.map(dialog => ({
|
||||
id: dialog.id,
|
||||
...getCenterPosition(dialog.id),
|
||||
}))
|
||||
);
|
||||
}, [dialogs]);
|
||||
|
||||
const getCenterPosition = id => {
|
||||
const root = document.querySelector('#root');
|
||||
const centerX = root.offsetLeft + root.offsetWidth / 2;
|
||||
const centerY = root.offsetTop + root.offsetHeight / 2;
|
||||
const item = document.querySelector(`#draggableItem-${id}`);
|
||||
const itemBounds = item.getBoundingClientRect();
|
||||
return {
|
||||
x: centerX - itemBounds.width / 2,
|
||||
y: centerY - itemBounds.height / 2,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the implementation of a dialog service that can be used by extensions.
|
||||
@@ -42,13 +65,16 @@ const DialogProvider = ({ children, service }) => {
|
||||
* @property {Object} contentProps The dialog content props.
|
||||
* @property {boolean} isDraggable Controls if dialog content is draggable or not.
|
||||
* @property {boolean} showOverlay Controls dialog overlay.
|
||||
* @property {boolean} centralize Center the dialog on the screen.
|
||||
* @property {boolean} preservePosition Use last position instead of default.
|
||||
* @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at.
|
||||
* @property {ElementPosition} position If this property is present, the item becomes 'controlled' and is not responsive to user input.
|
||||
* @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.
|
||||
* @property {Function} onStop Called when dragging stops.
|
||||
* @property {Function} onDrag Called while dragging.
|
||||
*/
|
||||
|
||||
useEffect(() => _bringToFront(lastDialogId), [_bringToFront, lastDialogId]);
|
||||
|
||||
/**
|
||||
* Creates a new dialog and return its id.
|
||||
*
|
||||
@@ -64,6 +90,7 @@ const DialogProvider = ({ children, service }) => {
|
||||
}
|
||||
|
||||
setDialogs(dialogs => [...dialogs, { ...props, id: dialogId }]);
|
||||
setLastDialogId(dialogId);
|
||||
|
||||
return dialogId;
|
||||
}, []);
|
||||
@@ -75,9 +102,11 @@ const DialogProvider = ({ children, service }) => {
|
||||
* @property {string} props.id The dialog id.
|
||||
* @returns void
|
||||
*/
|
||||
const dismiss = useCallback(({ id }) => {
|
||||
setDialogs(dialogs => dialogs.filter(dialog => dialog.id !== id));
|
||||
}, []);
|
||||
const dismiss = useCallback(
|
||||
({ id }) =>
|
||||
setDialogs(dialogs => dialogs.filter(dialog => dialog.id !== id)),
|
||||
[]
|
||||
);
|
||||
|
||||
/**
|
||||
* Dismisses all dialogs.
|
||||
@@ -101,14 +130,14 @@ const DialogProvider = ({ children, service }) => {
|
||||
* @param {string} id The dialog id.
|
||||
* @returns void
|
||||
*/
|
||||
const _bringToFront = id => {
|
||||
const _bringToFront = useCallback(id => {
|
||||
setDialogs(dialogs => {
|
||||
const topDialog = dialogs.find(dialog => dialog.id === id);
|
||||
return topDialog
|
||||
? [...dialogs.filter(dialog => dialog.id !== id), topDialog]
|
||||
: [];
|
||||
: dialogs;
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderDialogs = () =>
|
||||
dialogs.map(dialog => {
|
||||
@@ -116,20 +145,27 @@ const DialogProvider = ({ children, service }) => {
|
||||
id,
|
||||
content: DialogContent,
|
||||
contentProps,
|
||||
position,
|
||||
defaultPosition,
|
||||
centralize = false,
|
||||
preservePosition = true,
|
||||
isDraggable = true,
|
||||
onStart,
|
||||
onStop,
|
||||
onDrag,
|
||||
} = dialog;
|
||||
|
||||
let position =
|
||||
(preservePosition && lastDialogPosition) || defaultPosition;
|
||||
if (centralize) {
|
||||
position = centerPositions.find(position => position.id === id);
|
||||
}
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
key={id}
|
||||
disabled={!isDraggable}
|
||||
position={position}
|
||||
defaultPosition={lastDialogPosition || defaultPosition}
|
||||
defaultPosition={position}
|
||||
bounds="parent"
|
||||
onStart={event => {
|
||||
const e = event || window.event;
|
||||
@@ -169,7 +205,11 @@ const DialogProvider = ({ children, service }) => {
|
||||
>
|
||||
<div
|
||||
id={`draggableItem-${id}`}
|
||||
className={classNames('DraggableItem', isDragging && 'dragging')}
|
||||
className={classNames(
|
||||
'DraggableItem',
|
||||
isDragging && 'dragging',
|
||||
isDraggable && 'draggable'
|
||||
)}
|
||||
style={{ zIndex: '999', position: 'absolute' }}
|
||||
onClick={() => _bringToFront(id)}
|
||||
>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
.DraggableItem
|
||||
.DraggableItem.draggable
|
||||
div
|
||||
cursor: grab !important
|
||||
|
||||
.DraggableItem.dragging
|
||||
.DraggableItem.draggable.dragging
|
||||
div
|
||||
cursor: grabbing !important
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ import powerOff from './icons/power-off.svg';
|
||||
import reset from './icons/reset.svg';
|
||||
import rotate from './icons/rotate.svg';
|
||||
import rotateRight from './icons/rotate-right.svg';
|
||||
import saveRegular from './icons/save-regular.svg';
|
||||
import search from './icons/search.svg';
|
||||
import searchPlus from './icons/search-plus.svg';
|
||||
import softTissue from './icons/soft-tissue.svg';
|
||||
@@ -164,6 +165,7 @@ const ICONS = {
|
||||
'soft-tissue': softTissue,
|
||||
lung,
|
||||
liver,
|
||||
save: saveRegular,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-icon="save"
|
||||
role="img" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM272 80v80H144V80h128zm122 352H54a6 6 0 0 1-6-6V86a6 6 0 0 1 6-6h42v104c0 13.255 10.745 24 24 24h176c13.255 0 24-10.745 24-24V83.882l78.243 78.243a6 6 0 0 1 1.757 4.243V426a6 6 0 0 1-6 6zM224 232c-48.523 0-88 39.477-88 88s39.477 88 88 88 88-39.477 88-88-39.477-88-88-88zm0 128c-22.056 0-40-17.944-40-40s17.944-40 40-40 40 17.944 40 40-17.944 40-40 40z">
|
||||
</path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 738 B |
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ContextMenu,
|
||||
Checkbox,
|
||||
CineDialog,
|
||||
ViewportDownloadForm,
|
||||
@@ -72,6 +73,7 @@ export {
|
||||
TextArea,
|
||||
TextInput,
|
||||
CineDialog,
|
||||
ContextMenu,
|
||||
ViewportDownloadForm,
|
||||
ExpandableToolMenu,
|
||||
Icon,
|
||||
|
||||
@@ -3,6 +3,143 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.1.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.8...@ohif/viewer@3.1.9) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.7...@ohif/viewer@3.1.8) (2019-12-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.6...@ohif/viewer@3.1.7) (2019-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.5...@ohif/viewer@3.1.6) (2019-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.4...@ohif/viewer@3.1.5) (2019-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.3...@ohif/viewer@3.1.4) (2019-12-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.2...@ohif/viewer@3.1.3) (2019-12-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.1...@ohif/viewer@3.1.2) (2019-12-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.1.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.0...@ohif/viewer@3.1.1) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.1.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.0.3...@ohif/viewer@3.1.0) (2019-12-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.0.2...@ohif/viewer@3.0.3) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.0.1...@ohif/viewer@3.0.2) (2019-12-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.0.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.0.0...@ohif/viewer@3.0.1) (2019-12-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.0.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.11.8...@ohif/viewer@3.0.0) (2019-12-09)
|
||||
|
||||
|
||||
* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.11.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.11.7...@ohif/viewer@2.11.8) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.11.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.11.6...@ohif/viewer@2.11.7) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.11.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.11.5...@ohif/viewer@2.11.6) (2019-12-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"video": false,
|
||||
"chromeWebSecurity": false,
|
||||
"waitForAnimations": true,
|
||||
"defaultCommandTimeout": 10000,
|
||||
|
||||
@@ -81,10 +81,8 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
||||
});
|
||||
|
||||
it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => {
|
||||
//Click on Layout button
|
||||
cy.get('@layoutBtn').click();
|
||||
//Select 3 viewports
|
||||
cy.get('tbody > :nth-child(1) > :nth-child(3)').click();
|
||||
//Select viewport layout (3,1)
|
||||
cy.setLayout(3, 1);
|
||||
cy.waitViewportImageLoading();
|
||||
|
||||
// Press multiples hotkeys on viewport #1
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
describe('OHIF Download Snapshot File', () => {
|
||||
before(() => {
|
||||
cy.openStudy('MISTER^MR');
|
||||
cy.waitDicomImage();
|
||||
cy.expectMinimumThumbnails(5);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.openDownloadImageModal();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Close modal
|
||||
cy.get('[data-cy="close-button"]')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
});
|
||||
|
||||
it('checks displayed information for Tablet experience', function() {
|
||||
// Set Tablet resolution
|
||||
cy.viewport(1000, 660);
|
||||
// Visual comparison
|
||||
cy.screenshot('Download Image Modal - Tablet experience');
|
||||
cy.percyCanvasSnapshot('Download Image Modal - Tablet experience');
|
||||
});
|
||||
|
||||
it('checks displayed information for Desktop experience', function() {
|
||||
// Set Desktop resolution
|
||||
cy.viewport(1750, 720);
|
||||
// Visual comparison
|
||||
cy.screenshot('Download Image Modal - Desktop experience');
|
||||
cy.percyCanvasSnapshot('Download Image Modal - Desktop experience');
|
||||
//Check if all elements are displayed
|
||||
cy.get('[data-cy=modal-header]')
|
||||
.as('downloadImageModal')
|
||||
.should('contain.text', 'Download High Quality Image');
|
||||
// Check input fields
|
||||
cy.get('[data-cy=file-type]')
|
||||
.select('png')
|
||||
.should('have.value', 'png')
|
||||
.select('jpg')
|
||||
.should('have.value', 'jpg');
|
||||
// Check image preview
|
||||
cy.get('[data-cy="image-preview"]').should('contain.text', 'Image Preview');
|
||||
cy.get('[data-cy="viewport-preview-img"]')
|
||||
.should('have.attr', 'src')
|
||||
.and('include', 'data:image');
|
||||
// Check buttons
|
||||
cy.get('[data-cy="cancel-btn"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible');
|
||||
cy.get('[data-cy="download-btn"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
it('cancel changes on download modal', function() {
|
||||
//Change Image Width, Filename and File Type
|
||||
cy.get('[data-cy="image-width"]')
|
||||
.clear()
|
||||
.type('300');
|
||||
cy.get('[data-cy="image-height"]') //Image Height should be the same as width
|
||||
.should('have.value', '300');
|
||||
cy.get('[data-cy="file-name"]')
|
||||
.clear()
|
||||
.type('new-filename');
|
||||
cy.get('[data-cy=file-type]').select('png');
|
||||
//Click on Cancel button
|
||||
cy.get('[data-cy="cancel-btn"]')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
//Check modal is closed
|
||||
cy.get('[data-cy="modal"]').should('not.exist');
|
||||
//Open Modal
|
||||
cy.openDownloadImageModal();
|
||||
//Verify default values was restored
|
||||
cy.get('[data-cy="image-width"]').should('have.value', '512');
|
||||
cy.get('[data-cy="file-name"]').should('have.value', 'image');
|
||||
cy.get('[data-cy=file-type]').should('have.value', 'jpg');
|
||||
});
|
||||
|
||||
// TO-DO once issue is fixed: https://github.com/OHIF/Viewers/issues/1217
|
||||
// it('checks error messages for empty fields', function() {
|
||||
// //Clear fields Image Width and Filename
|
||||
// cy.get('[data-cy="image-width"]')
|
||||
// .clear();
|
||||
// cy.get('[data-cy="file-name"]').clear();
|
||||
|
||||
// //Click on Download button
|
||||
// cy.get('[data-cy="download-btn"]')
|
||||
// .scrollIntoView()
|
||||
// .click();
|
||||
// //Check error message
|
||||
// });
|
||||
|
||||
it('checks if "Show Annotations" checkbox will display annotations', function() {
|
||||
// Close modal that is initially opened
|
||||
cy.get('[data-cy="close-button"]').click();
|
||||
|
||||
// Add measurements in the viewport
|
||||
cy.addLengthMeasurement();
|
||||
cy.addAngleMeasurement();
|
||||
|
||||
// Open Modal
|
||||
cy.openDownloadImageModal();
|
||||
// Select "Show Annotations" option
|
||||
cy.get('[data-cy="show-annotations"]').check();
|
||||
// Check image preview
|
||||
cy.get('[data-cy="image-preview"]').scrollIntoView();
|
||||
// Visual comparison
|
||||
cy.screenshot('Download Image Modal - Show Annotations checked');
|
||||
cy.percyCanvasSnapshot('Download Image Modal - Show Annotations checked');
|
||||
//Compare classes that exists on Image Preview with Annotations and Without Annotation
|
||||
cy.get('[data-cy="modal-content"]')
|
||||
.find('canvas')
|
||||
.should('have.class', 'magnifyTool'); //Class "MagnifyTool" exists with annotations displayed on Image preview
|
||||
// Uncheck "Show Annotations" option
|
||||
cy.get('[data-cy="show-annotations"]')
|
||||
.uncheck()
|
||||
.wait(300);
|
||||
// Check that class "MagnifyTool" should not exist
|
||||
cy.get('[data-cy="modal-content"]')
|
||||
.find('canvas')
|
||||
.should('not.have.class', 'magnifyTool');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
//We excluded the tests for '**/studies/**' because the bulk/all of our other study/viewer tests use this route
|
||||
|
||||
describe('OHIF Routes', function() {
|
||||
beforeEach(function() {
|
||||
cy.openStudyList();
|
||||
});
|
||||
|
||||
it('checks PT/CT json url study route', function() {
|
||||
cy.visit('/viewer?url=https://s3.eu-central-1.amazonaws.com/ohif-viewer/JSON/PTCTStudy.json');
|
||||
|
||||
cy.server();
|
||||
cy.route('GET', '**/PTCTStudy/**').as('getPTCTStudy');
|
||||
|
||||
cy.wait('@getPTCTStudy.all');
|
||||
cy.get('@getPTCTStudy').should($route => {
|
||||
expect($route.status).to.be.eq(200);
|
||||
});
|
||||
|
||||
cy.screenshot();
|
||||
cy.percyCanvasSnapshot('PT/CT json study route');
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
describe('ViewerRouting', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
cy.contains('Study List');
|
||||
cy.get(
|
||||
'[data-cy="study-list-results"]> :nth-child(1) > .patientId'
|
||||
).click();
|
||||
});
|
||||
|
||||
// it('thumbnails list has more than 2 items', () => {
|
||||
// cy.get('.scrollable-study-thumbnails div.thumbnail-container')
|
||||
// .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');
|
||||
// });
|
||||
});
|
||||
@@ -11,6 +11,7 @@ export function initCornerstoneToolsAliases() {
|
||||
cy.get('.ToolbarRow > :nth-child(10)').as('cineBtn');
|
||||
cy.get('.expandableToolMenu').as('moreBtn');
|
||||
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
||||
cy.get('.viewport-element').as('viewport');
|
||||
}
|
||||
|
||||
//Creating aliases for Common page elements
|
||||
|
||||
@@ -469,6 +469,18 @@ Cypress.Commands.add(
|
||||
}
|
||||
);
|
||||
|
||||
Cypress.Commands.add('openDownloadImageModal', () => {
|
||||
// Click on More button
|
||||
cy.get('.expandableToolMenu')
|
||||
.as('moreBtn')
|
||||
.click();
|
||||
|
||||
// Click on Download button
|
||||
cy.get('.tooltip-inner > :nth-child(13)')
|
||||
.as('downloadBtn')
|
||||
.click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('setLanguage', (language, save = true) => {
|
||||
cy.openPreferences();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "2.11.6",
|
||||
"version": "3.1.9",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -27,7 +27,7 @@
|
||||
"dev:viewer": "yarn run dev",
|
||||
"start": "yarn run dev",
|
||||
"test:e2e": "cypress open",
|
||||
"test:e2e:ci": "percy exec -- cypress run --browser chrome",
|
||||
"test:e2e:ci": "percy exec -- cypress run --record --browser chrome",
|
||||
"test:e2e:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
|
||||
"test:e2e:serve": "serve -n -l 3000 -s dist",
|
||||
"test:unit": "jest --watchAll",
|
||||
@@ -45,14 +45,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^1.13.3",
|
||||
"@ohif/extension-cornerstone": "^2.0.0",
|
||||
"@ohif/extension-dicom-html": "^1.0.2",
|
||||
"@ohif/core": "^2.1.1",
|
||||
"@ohif/extension-cornerstone": "^2.1.0",
|
||||
"@ohif/extension-dicom-html": "^1.1.0",
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.0",
|
||||
"@ohif/extension-vtk": "^0.54.4",
|
||||
"@ohif/i18n": "^0.52.2",
|
||||
"@ohif/ui": "^0.65.2",
|
||||
"@ohif/extension-vtk": "^1.1.4",
|
||||
"@ohif/i18n": "^0.52.4",
|
||||
"@ohif/ui": "^1.1.4",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.2.1",
|
||||
@@ -60,7 +60,7 @@
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^4.8.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.6.1",
|
||||
"dcmjs": "^0.8.2",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"dicomweb-client": "^0.4.4",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -85,7 +85,7 @@
|
||||
"vtk.js": "^11.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "^3.3.1",
|
||||
"cypress": "^3.8.0",
|
||||
"gh-pages": "2.0.1",
|
||||
"identity-obj-proxy": "3.0.x",
|
||||
"lodash": "4.17.11",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
window.config = {
|
||||
// default: '/'
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
extensions: [],
|
||||
showStudyList: true,
|
||||
filterQueryParam: false,
|
||||
@@ -69,4 +70,5 @@ window.config = {
|
||||
// ~ Cornerstone Tools
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
],
|
||||
cornerstoneExtensionConfig: {},
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
extensions: [],
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
enableGoogleCloudAdapter: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
@@ -14,7 +15,8 @@ window.config = {
|
||||
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
|
||||
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=',
|
||||
@@ -23,4 +25,4 @@ window.config = {
|
||||
},
|
||||
],
|
||||
studyListFunctionsEnabled: true,
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
window.config = {
|
||||
// default: '/'
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
// default: ''
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/pwa',
|
||||
whiteLabelling: {},
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
whiteLabelling: {},
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
|
||||
@@ -1,74 +1,102 @@
|
||||
<!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" />
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="OHIF Viewer">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="@ohif/viewer">
|
||||
<meta name="msapplication-TileColor" content="#fff">
|
||||
<meta name="msapplication-TileImage" content="<%= PUBLIC_URL %>assets/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="<%= PUBLIC_URL %>assets/browserconfig.xml">
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json">
|
||||
|
||||
<link rel="shortcut icon" href="<%= PUBLIC_URL %>assets/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="<%= PUBLIC_URL %>assets/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="<%= PUBLIC_URL %>assets/favicon-32x32.png">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="<%= PUBLIC_URL %>assets/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="<%= PUBLIC_URL %>assets/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="<%= PUBLIC_URL %>assets/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="<%= PUBLIC_URL %>assets/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="<%= PUBLIC_URL %>assets/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="<%= PUBLIC_URL %>assets/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="<%= PUBLIC_URL %>assets/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= PUBLIC_URL %>assets/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="<%= PUBLIC_URL %>assets/apple-touch-icon-167x167.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= PUBLIC_URL %>assets/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="1024x1024" href="<%= PUBLIC_URL %>assets/apple-touch-icon-1024x1024.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 1)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-320x460.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x920.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x1096.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-750x1294.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 3)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1182x2208.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 3)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1242x2148.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 1)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-748x1024.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 1)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-768x1004.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1496x2048.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1536x2008.png">
|
||||
<link rel="icon" type="image/png" sizes="228x228" href="<%= PUBLIC_URL %>assets/coast-228x228.png">
|
||||
<link rel="yandex-tableau-widget" href="<%= PUBLIC_URL %>assets/yandex-browser-manifest.json">
|
||||
<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" />
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="OHIF Viewer">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="@ohif/viewer">
|
||||
<meta name="msapplication-TileColor" content="#fff">
|
||||
<meta name="msapplication-TileImage" content="<%= PUBLIC_URL %>assets/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="<%= PUBLIC_URL %>assets/browserconfig.xml">
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json">
|
||||
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script type="text/javascript">
|
||||
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
||||
</script>
|
||||
<script type="text/javascript" src="<%= PUBLIC_URL %>app-config.js"></script>
|
||||
<script type="module" src="<%= PUBLIC_URL %>init-service-worker.js"></script>
|
||||
<link rel="shortcut icon" href="<%= PUBLIC_URL %>assets/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="<%= PUBLIC_URL %>assets/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="<%= PUBLIC_URL %>assets/favicon-32x32.png">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="<%= PUBLIC_URL %>assets/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="<%= PUBLIC_URL %>assets/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="<%= PUBLIC_URL %>assets/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="<%= PUBLIC_URL %>assets/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="<%= PUBLIC_URL %>assets/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="<%= PUBLIC_URL %>assets/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="<%= PUBLIC_URL %>assets/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= PUBLIC_URL %>assets/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="<%= PUBLIC_URL %>assets/apple-touch-icon-167x167.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= PUBLIC_URL %>assets/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="1024x1024" href="<%= PUBLIC_URL %>assets/apple-touch-icon-1024x1024.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 1)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-320x460.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x920.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x1096.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-750x1294.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 414px) and (device-height: 736px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 3)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1182x2208.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 414px) and (device-height: 736px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 3)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1242x2148.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 1)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-748x1024.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 1)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-768x1004.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1496x2048.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1536x2008.png">
|
||||
<link rel="icon" type="image/png" sizes="228x228" href="<%= PUBLIC_URL %>assets/coast-228x228.png">
|
||||
<link rel="yandex-tableau-widget" href="<%= PUBLIC_URL %>assets/yandex-browser-manifest.json">
|
||||
|
||||
<title>OHIF Viewer</title>
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script
|
||||
src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script type="text/javascript">
|
||||
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
||||
</script>
|
||||
<script type="text/javascript" src="<%= PUBLIC_URL %>app-config.js"></script>
|
||||
<script type="module" src="<%= PUBLIC_URL %>init-service-worker.js"></script>
|
||||
|
||||
<!-- WEB FONTS -->
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>OHIF Viewer</title>
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
<!-- WEB FONTS -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
|
||||
<script>
|
||||
// If configuration file is a JS Object:
|
||||
window.config.extensions = [SomeExtension];
|
||||
|
||||
// If configuration file is a JS Function:
|
||||
window.config = () => {
|
||||
return {
|
||||
extensions: [SomeExtension]
|
||||
};
|
||||
};
|
||||
</script> -->
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,86 +1,117 @@
|
||||
<!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" />
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="OHIF Viewer">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="@ohif/viewer">
|
||||
<meta name="msapplication-TileColor" content="#fff">
|
||||
<meta name="msapplication-TileImage" content="<%= PUBLIC_URL %>assets/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="<%= PUBLIC_URL %>assets/browserconfig.xml">
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json">
|
||||
|
||||
<link rel="shortcut icon" href="<%= PUBLIC_URL %>assets/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="<%= PUBLIC_URL %>assets/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="<%= PUBLIC_URL %>assets/favicon-32x32.png">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="<%= PUBLIC_URL %>assets/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="<%= PUBLIC_URL %>assets/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="<%= PUBLIC_URL %>assets/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="<%= PUBLIC_URL %>assets/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="<%= PUBLIC_URL %>assets/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="<%= PUBLIC_URL %>assets/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="<%= PUBLIC_URL %>assets/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= PUBLIC_URL %>assets/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="<%= PUBLIC_URL %>assets/apple-touch-icon-167x167.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= PUBLIC_URL %>assets/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="1024x1024" href="<%= PUBLIC_URL %>assets/apple-touch-icon-1024x1024.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 1)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-320x460.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x920.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x1096.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-750x1294.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 3)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1182x2208.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 3)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1242x2148.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 1)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-748x1024.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 1)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-768x1004.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1496x2048.png">
|
||||
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 2)" href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1536x2008.png">
|
||||
<link rel="icon" type="image/png" sizes="228x228" href="<%= PUBLIC_URL %>assets/coast-228x228.png">
|
||||
<link rel="yandex-tableau-widget" href="<%= PUBLIC_URL %>assets/yandex-browser-manifest.json">
|
||||
<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" />
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="OHIF Viewer">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="@ohif/viewer">
|
||||
<meta name="msapplication-TileColor" content="#fff">
|
||||
<meta name="msapplication-TileImage" content="<%= PUBLIC_URL %>assets/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="<%= PUBLIC_URL %>assets/browserconfig.xml">
|
||||
<link rel="manifest" href="<%= PUBLIC_URL %>manifest.json">
|
||||
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script type="text/javascript">
|
||||
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
||||
</script>
|
||||
<script type="text/javascript" src="<%= PUBLIC_URL %>app-config.js"></script>
|
||||
<script type="module" src="<%= PUBLIC_URL %>init-service-worker.js"></script>
|
||||
<link rel="shortcut icon" href="<%= PUBLIC_URL %>assets/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="<%= PUBLIC_URL %>assets/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="<%= PUBLIC_URL %>assets/favicon-32x32.png">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="<%= PUBLIC_URL %>assets/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="<%= PUBLIC_URL %>assets/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="<%= PUBLIC_URL %>assets/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="<%= PUBLIC_URL %>assets/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="<%= PUBLIC_URL %>assets/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="<%= PUBLIC_URL %>assets/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="<%= PUBLIC_URL %>assets/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= PUBLIC_URL %>assets/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="<%= PUBLIC_URL %>assets/apple-touch-icon-167x167.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= PUBLIC_URL %>assets/apple-touch-icon-180x180.png">
|
||||
<link rel="apple-touch-icon" sizes="1024x1024" href="<%= PUBLIC_URL %>assets/apple-touch-icon-1024x1024.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 1)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-320x460.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x920.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-640x1096.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-750x1294.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 414px) and (device-height: 736px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 3)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1182x2208.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 414px) and (device-height: 736px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 3)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1242x2148.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 1)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-748x1024.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 1)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-768x1004.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: landscape) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1496x2048.png">
|
||||
<link rel="apple-touch-startup-image"
|
||||
media="(device-width: 768px) and (device-height: 1024px) and (orientation: portrait) and (-webkit-device-pixel-ratio: 2)"
|
||||
href="<%= PUBLIC_URL %>assets/apple-touch-startup-image-1536x2008.png">
|
||||
<link rel="icon" type="image/png" sizes="228x228" href="<%= PUBLIC_URL %>assets/coast-228x228.png">
|
||||
<link rel="yandex-tableau-widget" href="<%= PUBLIC_URL %>assets/yandex-browser-manifest.json">
|
||||
|
||||
<title>OHIF Viewer</title>
|
||||
<!-- Built with: https://polyfill.io/v3/url-builder/ -->
|
||||
<!-- Targets IE11 -->
|
||||
<script
|
||||
src="https://polyfill.io/v3/polyfill.min.js?flags=gated&features=default%2CObject.values%2CArray.prototype.flat%2CObject.entries%2CSymbol%2CArray.prototype.includes%2CString.prototype.repeat%2CArray.prototype.find"></script>
|
||||
<script type="text/javascript">
|
||||
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
||||
</script>
|
||||
<script type="text/javascript" src="<%= PUBLIC_URL %>app-config.js"></script>
|
||||
<script type="module" src="<%= PUBLIC_URL %>init-service-worker.js"></script>
|
||||
|
||||
<!-- WEB FONTS -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<title>OHIF Viewer</title>
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
<!-- WEB FONTS -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- EXTENSIONS -->
|
||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||
|
||||
<script>
|
||||
// If configuration file is a JS Object:
|
||||
window.config.extensions = [SomeExtension];
|
||||
|
||||
// If configuration file is a JS Function:
|
||||
window.config = () => {
|
||||
return {
|
||||
extensions: [SomeExtension]
|
||||
};
|
||||
};
|
||||
</script> -->
|
||||
</head>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
</head>
|
||||
|
||||
<div id="root"></div>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
|
||||
<script>
|
||||
// Note: This is a client access token so there is no issue with having it publicly available
|
||||
var _rollbarConfig = {
|
||||
accessToken: "739c64cd39e84592ba062bc29aac9769",
|
||||
captureUncaught: true,
|
||||
captureUnhandledRejections: true,
|
||||
payload: {
|
||||
environment: "viewer.ohif.org"
|
||||
}
|
||||
};
|
||||
// Rollbar Snippet
|
||||
!function (r) { var e = {}; function o(n) { if (e[n]) return e[n].exports; var t = e[n] = { i: n, l: !1, exports: {} }; return r[n].call(t.exports, t, t.exports, o), t.l = !0, t.exports } o.m = r, o.c = e, o.d = function (r, e, n) { o.o(r, e) || Object.defineProperty(r, e, { enumerable: !0, get: n }) }, o.r = function (r) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(r, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(r, "__esModule", { value: !0 }) }, o.t = function (r, e) { if (1 & e && (r = o(r)), 8 & e) return r; if (4 & e && "object" == typeof r && r && r.__esModule) return r; var n = Object.create(null); if (o.r(n), Object.defineProperty(n, "default", { enumerable: !0, value: r }), 2 & e && "string" != typeof r) for (var t in r) o.d(n, t, function (e) { return r[e] }.bind(null, t)); return n }, o.n = function (r) { var e = r && r.__esModule ? function () { return r.default } : function () { return r }; return o.d(e, "a", e), e }, o.o = function (r, e) { return Object.prototype.hasOwnProperty.call(r, e) }, o.p = "", o(o.s = 0) }([function (r, e, o) { var n = o(1), t = o(4); _rollbarConfig = _rollbarConfig || {}, _rollbarConfig.rollbarJsUrl = _rollbarConfig.rollbarJsUrl || "https://cdnjs.cloudflare.com/ajax/libs/rollbar.js/2.7.1/rollbar.min.js", _rollbarConfig.async = void 0 === _rollbarConfig.async || _rollbarConfig.async; var a = n.setupShim(window, _rollbarConfig), l = t(_rollbarConfig); window.rollbar = n.Rollbar, a.loadFull(window, document, !_rollbarConfig.async, _rollbarConfig, l) }, function (r, e, o) { var n = o(2); function t(r) { return function () { try { return r.apply(this, arguments) } catch (r) { try { console.error("[Rollbar]: Internal error", r) } catch (r) { } } } } var a = 0; function l(r, e) { this.options = r, this._rollbarOldOnError = null; var o = a++; this.shimId = function () { return o }, "undefined" != typeof window && window._rollbarShims && (window._rollbarShims[o] = { handler: e, messages: [] }) } var i = o(3), d = function (r, e) { return new l(r, e) }, c = function (r) { return new i(d, r) }; function s(r) { return t(function () { var e = Array.prototype.slice.call(arguments, 0), o = { shim: this, method: r, args: e, ts: new Date }; window._rollbarShims[this.shimId()].messages.push(o) }) } l.prototype.loadFull = function (r, e, o, n, a) { var l = !1, i = e.createElement("script"), d = e.getElementsByTagName("script")[0], c = d.parentNode; i.crossOrigin = "", i.src = n.rollbarJsUrl, o || (i.async = !0), i.onload = i.onreadystatechange = t(function () { if (!(l || this.readyState && "loaded" !== this.readyState && "complete" !== this.readyState)) { i.onload = i.onreadystatechange = null; try { c.removeChild(i) } catch (r) { } l = !0, function () { var e; if (void 0 === r._rollbarDidLoad) { e = new Error("rollbar.js did not load"); for (var o, n, t, l, i = 0; o = r._rollbarShims[i++];)for (o = o.messages || []; n = o.shift();)for (t = n.args || [], i = 0; i < t.length; ++i)if ("function" == typeof (l = t[i])) { l(e); break } } "function" == typeof a && a(e) }() } }), c.insertBefore(i, d) }, l.prototype.wrap = function (r, e, o) { try { var n; if (n = "function" == typeof e ? e : function () { return e || {} }, "function" != typeof r) return r; if (r._isWrap) return r; if (!r._rollbar_wrapped && (r._rollbar_wrapped = function () { o && "function" == typeof o && o.apply(this, arguments); try { return r.apply(this, arguments) } catch (o) { var e = o; throw e && ("string" == typeof e && (e = new String(e)), e._rollbarContext = n() || {}, e._rollbarContext._wrappedSource = r.toString(), window._rollbarWrappedError = e), e } }, r._rollbar_wrapped._isWrap = !0, r.hasOwnProperty)) for (var t in r) r.hasOwnProperty(t) && (r._rollbar_wrapped[t] = r[t]); return r._rollbar_wrapped } catch (e) { return r } }; for (var p = "log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad".split(","), u = 0; u < p.length; ++u)l.prototype[p[u]] = s(p[u]); r.exports = { setupShim: function (r, e) { if (r) { var o = e.globalAlias || "Rollbar"; if ("object" == typeof r[o]) return r[o]; r._rollbarShims = {}, r._rollbarWrappedError = null; var a = new c(e); return t(function () { e.captureUncaught && (a._rollbarOldOnError = r.onerror, n.captureUncaughtExceptions(r, a, !0), n.wrapGlobals(r, a, !0)), e.captureUnhandledRejections && n.captureUnhandledRejections(r, a, !0); var t = e.autoInstrument; return !1 !== e.enabled && (void 0 === t || !0 === t || "object" == typeof t && t.network) && r.addEventListener && (r.addEventListener("load", a.captureLoad.bind(a)), r.addEventListener("DOMContentLoaded", a.captureDomContentLoaded.bind(a))), r[o] = a, a })() } }, Rollbar: c } }, function (r, e) { function o(r, e, o) { if (e.hasOwnProperty && e.hasOwnProperty("addEventListener")) { for (var n = e.addEventListener; n._rollbarOldAdd && n.belongsToShim;)n = n._rLine truncated
|
||||
<div id="root"></div>
|
||||
|
||||
<script>
|
||||
// Note: This is a client access token so there is no issue with having it publicly available
|
||||
var _rollbarConfig = {
|
||||
accessToken: "739c64cd39e84592ba062bc29aac9769",
|
||||
captureUncaught: true,
|
||||
captureUnhandledRejections: true,
|
||||
payload: {
|
||||
environment: "viewer.ohif.org"
|
||||
}
|
||||
};
|
||||
// Rollbar Snippet
|
||||
!function (r) { var e = {}; function o(n) { if (e[n]) return e[n].exports; var t = e[n] = { i: n, l: !1, exports: {} }; return r[n].call(t.exports, t, t.exports, o), t.l = !0, t.exports } o.m = r, o.c = e, o.d = function (r, e, n) { o.o(r, e) || Object.defineProperty(r, e, { enumerable: !0, get: n }) }, o.r = function (r) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(r, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(r, "__esModule", { value: !0 }) }, o.t = function (r, e) { if (1 & e && (r = o(r)), 8 & e) return r; if (4 & e && "object" == typeof r && r && r.__esModule) return r; var n = Object.create(null); if (o.r(n), Object.defineProperty(n, "default", { enumerable: !0, value: r }), 2 & e && "string" != typeof r) for (var t in r) o.d(n, t, function (e) { return r[e] }.bind(null, t)); return n }, o.n = function (r) { var e = r && r.__esModule ? function () { return r.default } : function () { return r }; return o.d(e, "a", e), e }, o.o = function (r, e) { return Object.prototype.hasOwnProperty.call(r, e) }, o.p = "", o(o.s = 0) }([function (r, e, o) { var n = o(1), t = o(4); _rollbarConfig = _rollbarConfig || {}, _rollbarConfig.rollbarJsUrl = _rollbarConfig.rollbarJsUrl || "https://cdnjs.cloudflare.com/ajax/libs/rollbar.js/2.7.1/rollbar.min.js", _rollbarConfig.async = void 0 === _rollbarConfig.async || _rollbarConfig.async; var a = n.setupShim(window, _rollbarConfig), l = t(_rollbarConfig); window.rollbar = n.Rollbar, a.loadFull(window, document, !_rollbarConfig.async, _rollbarConfig, l) }, function (r, e, o) { var n = o(2); function t(r) { return function () { try { return r.apply(this, arguments) } catch (r) { try { console.error("[Rollbar]: Internal error", r) } catch (r) { } } } } var a = 0; function l(r, e) { this.options = r, this._rollbarOldOnError = null; var o = a++; this.shimId = function () { return o }, "undefined" != typeof window && window._rollbarShims && (window._rollbarShims[o] = { handler: e, messages: [] }) } var i = o(3), d = function (r, e) { return new l(r, e) }, c = function (r) { return new i(d, r) }; function s(r) { return t(function () { var e = Array.prototype.slice.call(arguments, 0), o = { shim: this, method: r, args: e, ts: new Date }; window._rollbarShims[this.shimId()].messages.push(o) }) } l.prototype.loadFull = function (r, e, o, n, a) { var l = !1, i = e.createElement("script"), d = e.getElementsByTagName("script")[0], c = d.parentNode; i.crossOrigin = "", i.src = n.rollbarJsUrl, o || (i.async = !0), i.onload = i.onreadystatechange = t(function () { if (!(l || this.readyState && "loaded" !== this.readyState && "complete" !== this.readyState)) { i.onload = i.onreadystatechange = null; try { c.removeChild(i) } catch (r) { } l = !0, function () { var e; if (void 0 === r._rollbarDidLoad) { e = new Error("rollbar.js did not load"); for (var o, n, t, l, i = 0; o = r._rollbarShims[i++];)for (o = o.messages || []; n = o.shift();)for (t = n.args || [], i = 0; i < t.length; ++i)if ("function" == typeof (l = t[i])) { l(e); break } } "function" == typeof a && a(e) }() } }), c.insertBefore(i, d) }, l.prototype.wrap = function (r, e, o) { try { var n; if (n = "function" == typeof e ? e : function () { return e || {} }, "function" != typeof r) return r; if (r._isWrap) return r; if (!r._rollbar_wrapped && (r._rollbar_wrapped = function () { o && "function" == typeof o && o.apply(this, arguments); try { return r.apply(this, arguments) } catch (o) { var e = o; throw e && ("string" == typeof e && (e = new String(e)), e._rollbarContext = n() || {}, e._rollbarContext._wrappedSource = r.toString(), window._rollbarWrappedError = e), e } }, r._rollbar_wrapped._isWrap = !0, r.hasOwnProperty)) for (var t in r) r.hasOwnProperty(t) && (r._rollbar_wrapped[t] = r[t]); return r._rollbar_wrapped } catch (e) { return r } }; for (var p = "log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad".split(","), u = 0; u < p.length; ++u)l.prototype[p[u]] = s(p[u]); r.exports = { setupShim: function (r, e) { if (r) { var o = e.globalAlias || "Rollbar"; if ("object" == typeof r[o]) return r[o]; r._rollbarShims = {}, r._rollbarWrappedError = null; var a = new c(e); return t(function () { e.captureUncaught && (a._rollbarOldOnError = r.onerror, n.captureUncaughtExceptions(r, a, !0), n.wrapGlobals(r, a, !0)), e.captureUnhandledRejections && n.captureUnhandledRejections(r, a, !0); var t = e.autoInstrument; return !1 !== e.enabled && (void 0 === t || !0 === t || "object" == typeof t && t.network) && r.addEventListener && (r.addEventListener("load", a.captureLoad.bind(a)), r.addEventListener("DOMContentLoaded", a.captureDomContentLoaded.bind(a))), r[o] = a, a })() } }, Rollbar: c } }, function (r, e) { function o(r, e, o) { if (e.hasOwnProperty && e.hasOwnProperty("addEventListener")) { for (var n = e.addEventListener; n._rollbarOldAdd && n.belongsToShim;)n = n._rolLine truncated
|
||||
// End Rollbar Snippet
|
||||
</script>
|
||||
</body>
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user