Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8d56c11f0 | ||
|
|
a6b92a4505 | ||
|
|
a4c9a86772 | ||
|
|
73e440968c | ||
|
|
3f7eb8a5d3 | ||
|
|
7b2d44f2c1 | ||
|
|
225da81135 | ||
|
|
686d12da5c | ||
|
|
c22756051b | ||
|
|
8fdfcc0c7d | ||
|
|
44ab65c981 | ||
|
|
b9574b6efc | ||
|
|
96a8e26786 | ||
|
|
80f5b23622 | ||
|
|
2b4950e954 | ||
|
|
e16f14089d | ||
|
|
c3807a89ed | ||
|
|
049ef4a666 | ||
|
|
d5fa880a29 | ||
|
|
1b9a6db73f | ||
|
|
ed2692908e | ||
|
|
0c6c5a5d51 |
No files matched your search
+2
-1
@@ -12,10 +12,11 @@ exemptLabels:
|
||||
- 'Task: CI/Tooling :robot:'
|
||||
- 'Task: Docs 📖'
|
||||
- 'Task: Docs :book:'
|
||||
- 'Task: Refactor 🛠'
|
||||
- 'Task: Refactor :hammer_and_wrench:'
|
||||
- 'Task: Tests :microscope:'
|
||||
- 'PR: Awaiting Review 👀'
|
||||
- 'Triage :white_flag:'
|
||||
- 'Extension: Discussion'
|
||||
- 'Announcement 🎉'
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: 'Stale :baguette_bread:'
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
const merge = require('webpack-merge');
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
const pkg = require('./../package.json');
|
||||
|
||||
const ROOT_DIR = path.join(__dirname, './..');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
|
||||
return merge(commonConfig, {
|
||||
devtool: 'source-map',
|
||||
stats: {
|
||||
colors: true,
|
||||
hash: true,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
warnings: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
output: {
|
||||
path: ROOT_DIR,
|
||||
library: 'OHIFExtLesionTracker',
|
||||
libraryTarget: 'umd',
|
||||
libraryExport: 'default',
|
||||
filename: pkg.main,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# 0.1.0 (2020-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* lesion-tracker extension ([#1420](https://github.com/OHIF/Viewers/issues/1420)) ([73e4409](https://github.com/OHIF/Viewers/commit/73e440968ce4699d081a9c9f2d21dd68095b3056))
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Open Health Imaging Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,38 @@
|
||||
# @ohif/extension-lesion-tracker
|
||||
|
||||
This project is an OHIF extension that can be used with the Core OHIF Platform,
|
||||
either at runtime, or as an ES6 dependency, to create a medical image viewing
|
||||
application similar to that of the [legacy Lesion
|
||||
Tracker][legacy-lesion-tracker] viewer.
|
||||
|
||||
## About
|
||||
|
||||
...
|
||||
|
||||
## Scope
|
||||
|
||||
...
|
||||
|
||||
### Configuration
|
||||
|
||||
...
|
||||
|
||||
### Extensions
|
||||
|
||||
...
|
||||
|
||||
## Build & Deploy
|
||||
|
||||
...
|
||||
|
||||
## Funding/Support
|
||||
|
||||
...
|
||||
|
||||
<!--
|
||||
LINKS
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[legacy-lesion-tracker]: http://lesiontracker.ohif.org/studylist
|
||||
<!-- prettier-ignore-end -->
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@ohif/extension-lesion-tracker",
|
||||
"version": "0.1.0",
|
||||
"description": "OHIF extension for Lesion Tracker",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
"repository": "OHIF/Viewers",
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "src/index.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.16.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
|
||||
"dev:lesion-tracker": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"prepublishOnly": "yarn run build",
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"classnames": "^2.2.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// SEE: Async imports (code-splitting)
|
||||
// import asyncComponent from './asyncComponent.js';
|
||||
// import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
|
||||
|
||||
// const ConnectedOHIFDicomPDFViewer = asyncComponent(() =>
|
||||
// import(
|
||||
// /* webpackChunkName: "ConnectedOHIFDicomPDFViewer" */ './ConnectedOHIFDicomPDFViewer'
|
||||
// )
|
||||
// );
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'lesion-tracker',
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
*/
|
||||
getPanelModule({ servicesManager, commandsManager }) {
|
||||
return undefined;
|
||||
// {
|
||||
// TODO:
|
||||
// - Right, alternative StudyBrowser
|
||||
// - Left, alternative Measurements Table
|
||||
// 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'],
|
||||
// };
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
* @returns Object
|
||||
*/
|
||||
getToolbarModule({ servicesManager, commandsManager }) {
|
||||
// TODO: Zoom, Levels, Pan, More (see lesiontracker.ohif.org)
|
||||
// These may be driven through configuration of `cornerstone` extension instead of defined here
|
||||
return {
|
||||
definitions: [
|
||||
{
|
||||
id: 'say-hell-world',
|
||||
label: '🎉 HELLO WORLD 🎉',
|
||||
icon: 'exclamation-triangle',
|
||||
type: 'command',
|
||||
commandName: 'sayHelloWorld',
|
||||
},
|
||||
],
|
||||
defaultContext: 'VIEWER',
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {ServicesManager} params.servicesManager
|
||||
* @param {CommandsManager} params.commandsManager
|
||||
* @returns Object
|
||||
*/
|
||||
getCommandsModule({ servicesManager }) {
|
||||
const { UINotificationService } = servicesManager.services;
|
||||
return {
|
||||
definitions: {
|
||||
sayHelloWorld: {
|
||||
commandFn: function() {
|
||||
console.log(UINotificationService);
|
||||
UINotificationService.show({
|
||||
title: 'What does a nosey pepper do?',
|
||||
message: 'Gets jalapeno business!',
|
||||
});
|
||||
},
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
defaultContext: ['VIEWER'],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -3,6 +3,49 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.2.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.14...@ohif/extension-vtk@1.2.15) (2020-02-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.13...@ohif/extension-vtk@1.2.14) (2020-02-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.12...@ohif/extension-vtk@1.2.13) (2020-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Set VTK viewport as active by interaction ([#1139](https://github.com/OHIF/Viewers/issues/1139)) ([686d12d](https://github.com/OHIF/Viewers/commit/686d12da5c9d3d435b1e326c2a5caee36e2ed27c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.11...@ohif/extension-vtk@1.2.12) (2020-01-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.10...@ohif/extension-vtk@1.2.11) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.9...@ohif/extension-vtk@1.2.10) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "1.2.10",
|
||||
"version": "1.2.15",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -49,11 +49,11 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react-vtkjs-viewport": "^0.3.9"
|
||||
"react-vtkjs-viewport": "^0.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^2.3.5",
|
||||
"@ohif/ui": "^1.1.8",
|
||||
"@ohif/core": "^2.3.9",
|
||||
"@ohif/ui": "^1.1.9",
|
||||
"cornerstone-tools": "^4.12.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import { View2D } from 'react-vtkjs-viewport';
|
||||
import { connect } from 'react-redux';
|
||||
import VTKViewport from './VTKViewport';
|
||||
|
||||
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
|
||||
|
||||
@@ -18,6 +18,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const pluginDetails = viewportLayout.vtk || {};
|
||||
|
||||
return {
|
||||
activeViewportIndex: state.viewports.activeViewportIndex,
|
||||
layout: state.viewports.layout,
|
||||
isActive,
|
||||
...pluginDetails,
|
||||
@@ -75,6 +76,6 @@ const ConnectedVTKViewport = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mergeProps
|
||||
)(View2D);
|
||||
)(VTKViewport);
|
||||
|
||||
export default ConnectedVTKViewport;
|
||||
@@ -77,6 +77,11 @@ class OHIFVTKViewport extends Component {
|
||||
}),
|
||||
viewportIndex: PropTypes.number,
|
||||
children: PropTypes.node,
|
||||
onScroll: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onScroll: () => {},
|
||||
};
|
||||
|
||||
static id = 'OHIFVTKViewport';
|
||||
@@ -386,6 +391,7 @@ class OHIFVTKViewport extends Component {
|
||||
this.state.paintFilterBackgroundImageData
|
||||
}
|
||||
viewportIndex={this.props.viewportIndex}
|
||||
onScroll={this.props.onScroll}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.vtk-viewport-handler svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { View2D } from 'react-vtkjs-viewport';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './VTKViewport.css';
|
||||
|
||||
const VTKViewport = props => {
|
||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||
|
||||
const setViewportActiveHandler = useCallback(() => {
|
||||
const { setViewportActive, viewportIndex, activeViewportIndex } = props;
|
||||
|
||||
if (viewportIndex !== activeViewportIndex) {
|
||||
// set in Connected
|
||||
setViewportActive();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleScrollEvent = evt => {
|
||||
const vtkViewportApiReference = props.onScroll(props.viewportIndex) || {};
|
||||
const viewportUid = vtkViewportApiReference.uid;
|
||||
const viewportWasScrolled = viewportUid === evt.detail.uid;
|
||||
|
||||
if (viewportWasScrolled) {
|
||||
setViewportActiveHandler();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('vtkscrollevent', handleScrollEvent);
|
||||
return () =>
|
||||
window.removeEventListener('vtkscrollevent', handleScrollEvent);
|
||||
}, [props, props.onScroll, props.viewportIndex, setViewportActiveHandler]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="vtk-viewport-handler"
|
||||
style={style}
|
||||
onClick={setViewportActiveHandler}
|
||||
>
|
||||
<View2D {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
VTKViewport.propTypes = {
|
||||
setViewportActive: PropTypes.func.isRequired,
|
||||
viewportIndex: PropTypes.number.isRequired,
|
||||
activeViewportIndex: PropTypes.number.isRequired,
|
||||
/* Receives viewportIndex */
|
||||
onScroll: PropTypes.func,
|
||||
};
|
||||
|
||||
VTKViewport.defaultProps = {
|
||||
onScroll: () => {},
|
||||
};
|
||||
|
||||
export default VTKViewport;
|
||||
@@ -103,6 +103,9 @@ const commandsModule = ({ commandsManager }) => {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getVtkApis: ({ index }) => {
|
||||
return apis[index];
|
||||
},
|
||||
axial: async ({ viewports }) => {
|
||||
const api = await _getActiveViewportVTKApi(viewports);
|
||||
|
||||
@@ -266,11 +269,12 @@ const commandsModule = ({ commandsManager }) => {
|
||||
'crosshairsWidget'
|
||||
);
|
||||
|
||||
const uid = api.uid;
|
||||
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
||||
|
||||
api.setInteractorStyle({
|
||||
istyle,
|
||||
configuration: { apis, apiIndex },
|
||||
configuration: { apis, apiIndex, uid },
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -296,55 +300,45 @@ const commandsModule = ({ commandsManager }) => {
|
||||
},
|
||||
enableRotateTool: {
|
||||
commandFn: actions.enableRotateTool,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
enableCrosshairsTool: {
|
||||
commandFn: actions.enableCrosshairsTool,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
enableLevelTool: {
|
||||
commandFn: actions.enableLevelTool,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
setBlendModeToComposite: {
|
||||
commandFn: actions.setBlendModeToComposite,
|
||||
storeContexts: [],
|
||||
options: { blendMode: BlendMode.COMPOSITE_BLEND },
|
||||
},
|
||||
setBlendModeToMaximumIntensity: {
|
||||
commandFn: actions.setBlendModeToMaximumIntensity,
|
||||
storeContexts: [],
|
||||
options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND },
|
||||
},
|
||||
setBlendModeToMinimumIntensity: {
|
||||
commandFn: actions.setBlendMode,
|
||||
storeContexts: [],
|
||||
options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND },
|
||||
},
|
||||
setBlendModeToAverageIntensity: {
|
||||
commandFn: actions.setBlendMode,
|
||||
storeContexts: [],
|
||||
options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND },
|
||||
},
|
||||
setSlabThickness: {
|
||||
// TODO: How do we pass in a function argument?
|
||||
commandFn: actions.setSlabThickness,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
increaseSlabThickness: {
|
||||
commandFn: actions.changeSlabThickness,
|
||||
storeContexts: [],
|
||||
options: {
|
||||
change: 3,
|
||||
},
|
||||
},
|
||||
decreaseSlabThickness: {
|
||||
commandFn: actions.changeSlabThickness,
|
||||
storeContexts: [],
|
||||
options: {
|
||||
change: -3,
|
||||
},
|
||||
@@ -355,6 +349,10 @@ const commandsModule = ({ commandsManager }) => {
|
||||
options: {},
|
||||
context: 'VIEWER',
|
||||
},
|
||||
getVtkApiForViewportIndex: {
|
||||
commandFn: actions.getVtkApis,
|
||||
context: 'VIEWER',
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncComponent from './asyncComponent.js';
|
||||
import commandsModule from './commandsModule.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import withCommandsManager from './withCommandsManager.js';
|
||||
// This feels weird
|
||||
// import loadLocales from './loadLocales';
|
||||
|
||||
@@ -14,8 +15,8 @@ const vtkExtension = {
|
||||
*/
|
||||
id: 'vtk',
|
||||
|
||||
getViewportModule() {
|
||||
return OHIFVTKViewport;
|
||||
getViewportModule({ commandsManager }) {
|
||||
return withCommandsManager(OHIFVTKViewport, commandsManager);
|
||||
},
|
||||
getToolbarModule() {
|
||||
return toolbarModule;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function withCommandsManager(Component, commandsManager = {}) {
|
||||
return class WithCommandsManager extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Component
|
||||
{...this.props}
|
||||
onScroll={viewportIndex =>
|
||||
commandsManager.runCommand('getVtkApiForViewportIndex', {
|
||||
index: viewportIndex,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
+3
-1
@@ -13,11 +13,13 @@
|
||||
"scripts": {
|
||||
"cm": "npx git-cz",
|
||||
"build": "lerna run build:viewer --stream",
|
||||
"build:lt": "lerna run build:viewer:lesion-tracker --stream",
|
||||
"build:ci": "lerna run build:viewer:ci --stream",
|
||||
"build:demo": "lerna run build:viewer:demo --stream",
|
||||
"build:package": "lerna run build:viewer:package --stream",
|
||||
"build:package-all": "lerna run build:package --parallel --stream",
|
||||
"dev": "lerna run dev:viewer --stream",
|
||||
"dev:lt": "lerna run dev:viewer:lesion-tracker --stream",
|
||||
"dev:project": ".scripts/dev.sh",
|
||||
"dev:orthanc": "lerna run dev:orthanc --stream",
|
||||
"orthanc:up": "docker-compose -f .docker/Nginx-Orthanc/docker-compose.yml up",
|
||||
@@ -59,6 +61,7 @@
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^3.2.0",
|
||||
"cypress-file-upload": "^3.5.3",
|
||||
"dotenv": "^8.1.0",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-prettier": "^6.4.0",
|
||||
@@ -72,7 +75,6 @@
|
||||
"eslint-plugin-react": "7.x",
|
||||
"eslint-plugin-react-hooks": "1.5.0",
|
||||
"extract-css-chunks-webpack-plugin": "^4.5.4",
|
||||
"favicons-webpack-plugin": "^1.0.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^3.0.0",
|
||||
"jest": "^24.8.0",
|
||||
|
||||
@@ -3,6 +3,44 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.3.9](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.8...@ohif/core@2.3.9) (2020-02-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.7...@ohif/core@2.3.8) (2020-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Remove trash data from redux storage after updates ([#1358](https://github.com/OHIF/Viewers/issues/1358)) ([7b2d44f](https://github.com/OHIF/Viewers/commit/7b2d44f2c18241ea521b8d3652aee32e36eaddb8))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.6...@ohif/core@2.3.7) (2020-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Set VTK viewport as active by interaction ([#1139](https://github.com/OHIF/Viewers/issues/1139)) ([686d12d](https://github.com/OHIF/Viewers/commit/686d12da5c9d3d435b1e326c2a5caee36e2ed27c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.5...@ohif/core@2.3.6) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.4...@ohif/core@2.3.5) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "2.3.5",
|
||||
"version": "2.3.9",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -161,7 +161,7 @@ export class CommandsManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const { commandFn, storeContexts } = definition;
|
||||
const { commandFn, storeContexts = [] } = definition;
|
||||
const definitionOptions = definition.options;
|
||||
|
||||
let commandParams = {};
|
||||
|
||||
@@ -11,20 +11,63 @@ import {
|
||||
SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||
} from './../constants/ActionTypes.js';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
export const DEFAULT_STATE = {
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
activeViewportIndex: 0,
|
||||
layout: {
|
||||
viewports: [
|
||||
{
|
||||
// plugin: 'cornerstone',
|
||||
},
|
||||
],
|
||||
viewports: [{}],
|
||||
},
|
||||
viewportSpecificData: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* Take the new number of rows and columns, delete all not used viewport data and also set
|
||||
* active viewport as default in case current one is not available anymore.
|
||||
*
|
||||
* @param {Number} numRows
|
||||
* @param {Number} numColumns
|
||||
* @param {Object} currentViewportSpecificData
|
||||
* @returns
|
||||
*/
|
||||
const findActiveViewportSpecificData = (
|
||||
numRows,
|
||||
numColumns,
|
||||
currentViewportSpecificData = {}
|
||||
) => {
|
||||
const numberOfViewports = numRows * numColumns;
|
||||
const viewportSpecificData = cloneDeep(currentViewportSpecificData);
|
||||
|
||||
if (numberOfViewports < Object.keys(viewportSpecificData).length) {
|
||||
Object.keys(viewportSpecificData).forEach(key => {
|
||||
if (key > numberOfViewports - 1) {
|
||||
delete viewportSpecificData[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return viewportSpecificData;
|
||||
};
|
||||
/**
|
||||
* Take new number of rows and columns and make sure the current active viewport index is still available, if not, return the default
|
||||
*
|
||||
* @param {Number} numRows
|
||||
* @param {Number} numColumns
|
||||
* @param {Number} currentActiveViewportIndex
|
||||
* @returns
|
||||
*/
|
||||
const getActiveViewportIndex = (
|
||||
numRows,
|
||||
numColumns,
|
||||
currentActiveViewportIndex
|
||||
) => {
|
||||
const numberOfViewports = numRows * numColumns;
|
||||
|
||||
return currentActiveViewportIndex > numberOfViewports - 1
|
||||
? DEFAULT_STATE.activeViewportIndex
|
||||
: currentActiveViewportIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* The definition of a viewport action.
|
||||
*
|
||||
@@ -50,7 +93,12 @@ const viewports = (state = DEFAULT_STATE, action) => {
|
||||
* @return {Object} New state.
|
||||
*/
|
||||
case SET_VIEWPORT_ACTIVE: {
|
||||
return { ...state, activeViewportIndex: action.viewportIndex };
|
||||
const activeViewportIndex = getActiveViewportIndex(
|
||||
state.numRows,
|
||||
state.numColumns,
|
||||
action.viewportIndex
|
||||
);
|
||||
return { ...state, activeViewportIndex };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,11 +107,25 @@ const viewports = (state = DEFAULT_STATE, action) => {
|
||||
* @return {Object} New state.
|
||||
*/
|
||||
case SET_VIEWPORT_LAYOUT: {
|
||||
const { numRows, numColumns } = action;
|
||||
const viewportSpecificData = findActiveViewportSpecificData(
|
||||
numRows,
|
||||
numColumns,
|
||||
state.viewportSpecificData
|
||||
);
|
||||
const activeViewportIndex = getActiveViewportIndex(
|
||||
numRows,
|
||||
numColumns,
|
||||
state.activeViewportIndex
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
numRows: action.numRows,
|
||||
numColumns: action.numColumns,
|
||||
layout: { viewports: [...action.viewports] },
|
||||
viewportSpecificData,
|
||||
activeViewportIndex,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,12 +135,25 @@ const viewports = (state = DEFAULT_STATE, action) => {
|
||||
* @return {Object} New state.
|
||||
*/
|
||||
case SET_VIEWPORT_LAYOUT_AND_DATA: {
|
||||
const { numRows, numColumns } = action;
|
||||
const viewportSpecificData = findActiveViewportSpecificData(
|
||||
numRows,
|
||||
numColumns,
|
||||
action.viewportSpecificData
|
||||
);
|
||||
const activeViewportIndex = getActiveViewportIndex(
|
||||
numRows,
|
||||
numColumns,
|
||||
state.activeViewportIndex
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
numRows: action.numRows,
|
||||
numColumns: action.numColumns,
|
||||
layout: { viewports: [...action.viewports] },
|
||||
viewportSpecificData: cloneDeep(action.viewportSpecificData),
|
||||
viewportSpecificData,
|
||||
activeViewportIndex,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,61 @@
|
||||
// https://medium.com/@netxm/testing-redux-reducers-with-jest-6653abbfe3e1
|
||||
import reducer from './viewports.js';
|
||||
import { Reducer } from 'redux-testkit';
|
||||
|
||||
import reducer, { DEFAULT_STATE } from './viewports.js';
|
||||
import * as types from './../constants/ActionTypes.js';
|
||||
|
||||
describe('viewports reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(undefined, {})).toEqual({
|
||||
activeViewportIndex: 0,
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
layout: {
|
||||
viewports: [{}],
|
||||
},
|
||||
viewportSpecificData: {},
|
||||
});
|
||||
expect(reducer(undefined, {})).toEqual(DEFAULT_STATE);
|
||||
});
|
||||
|
||||
it('should handle SET_VIEWPORT_ACTIVE', () => {
|
||||
const setViewportActiveAction = {
|
||||
it('should handle SET_VIEWPORT_ACTIVE with inexistent viewport index', () => {
|
||||
const initialState = {
|
||||
numRows: 4,
|
||||
numColumns: 4,
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
const action = {
|
||||
type: types.SET_VIEWPORT_ACTIVE,
|
||||
viewportIndex: 100,
|
||||
};
|
||||
|
||||
const updatedState = reducer({}, setViewportActiveAction);
|
||||
const expectedToChangeInState = {
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
expect(updatedState.activeViewportIndex).toEqual(
|
||||
setViewportActiveAction.viewportIndex
|
||||
);
|
||||
Reducer(reducer)
|
||||
.withState(initialState)
|
||||
.expect(action)
|
||||
.toChangeInState(expectedToChangeInState);
|
||||
});
|
||||
|
||||
it('should handle SET_VIEWPORT_ACTIVE with existent viewport index', () => {
|
||||
const initialState = {
|
||||
numRows: 4,
|
||||
numColumns: 4,
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
const action = {
|
||||
type: types.SET_VIEWPORT_ACTIVE,
|
||||
viewportIndex: 5,
|
||||
};
|
||||
|
||||
const expectedToChangeInState = {
|
||||
activeViewportIndex: 5,
|
||||
};
|
||||
|
||||
Reducer(reducer)
|
||||
.withState(initialState)
|
||||
.expect(action)
|
||||
.toChangeInState(expectedToChangeInState);
|
||||
});
|
||||
|
||||
it('should handle SET_VIEWPORT_LAYOUT', () => {
|
||||
const setViewportLayoutAction = {
|
||||
const initialState = DEFAULT_STATE;
|
||||
|
||||
const action = {
|
||||
type: types.SET_VIEWPORT_LAYOUT,
|
||||
numRows: 1,
|
||||
numColumns: 2,
|
||||
@@ -43,18 +69,193 @@ describe('viewports reducer', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const updatedState = reducer({}, setViewportLayoutAction);
|
||||
const expectedToChangeInState = {
|
||||
numRows: 1,
|
||||
numColumns: 2,
|
||||
layout: {
|
||||
viewports: [
|
||||
{
|
||||
plugin: 'cornerstone',
|
||||
},
|
||||
{
|
||||
plugin: 'vtk',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(updatedState.numRows).toEqual(setViewportLayoutAction.numRows);
|
||||
expect(updatedState.numColumns).toEqual(setViewportLayoutAction.numColumns);
|
||||
expect(updatedState.layout.viewports).toEqual(
|
||||
setViewportLayoutAction.viewports
|
||||
);
|
||||
Reducer(reducer)
|
||||
.withState(initialState)
|
||||
.expect(action)
|
||||
.toChangeInState(expectedToChangeInState);
|
||||
});
|
||||
|
||||
it('should handle SET_VIEWPORT_LAYOUT when we reduce the number of viewports', () => {
|
||||
const initialState = {
|
||||
numRows: 1,
|
||||
numColumns: 2,
|
||||
viewportSpecificData: {
|
||||
0: { viewportData0: 'viewportData0' },
|
||||
1: { viewportData1: 'viewportData1' },
|
||||
},
|
||||
layout: {
|
||||
viewports: [],
|
||||
},
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
const action = {
|
||||
type: types.SET_VIEWPORT_LAYOUT,
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
viewports: [],
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
viewportSpecificData: {
|
||||
0: { viewportData0: 'viewportData0' },
|
||||
},
|
||||
layout: {
|
||||
viewports: [],
|
||||
},
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
Reducer(reducer)
|
||||
.withState(initialState)
|
||||
.expect(action)
|
||||
.toReturnState(expectedState);
|
||||
});
|
||||
|
||||
it('should handle SET_VIEWPORT_LAYOUT_AND_DATA', () => {
|
||||
const initialState = {
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
viewportSpecificData: {
|
||||
0: { viewportData0: 'data0' },
|
||||
},
|
||||
layout: {
|
||||
viewports: [{ plugin: 'cornerstone' }],
|
||||
},
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
const action = {
|
||||
type: types.SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||
numRows: 1,
|
||||
numColumns: 2,
|
||||
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
|
||||
viewportSpecificData: {
|
||||
0: { viewportData0: 'NEWdata0' },
|
||||
1: { viewportData1: 'NEWdata1' },
|
||||
},
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
numRows: 1,
|
||||
numColumns: 2,
|
||||
viewportSpecificData: {
|
||||
0: { viewportData0: 'NEWdata0' },
|
||||
1: { viewportData1: 'NEWdata1' },
|
||||
},
|
||||
layout: {
|
||||
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
|
||||
},
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
Reducer(reducer)
|
||||
.withState(initialState)
|
||||
.expect(action)
|
||||
.toReturnState(expectedState);
|
||||
});
|
||||
|
||||
it('should handle SET_VIEWPORT_LAYOUT_AND_DATA when we reduce the number of viewports', () => {
|
||||
const initialState = {
|
||||
numRows: 1,
|
||||
numColumns: 3,
|
||||
viewportSpecificData: {
|
||||
0: { viewportData0: 'vtkData0' },
|
||||
1: { viewportData1: 'vtkData1' },
|
||||
2: { viewportData2: 'vtkData2' },
|
||||
},
|
||||
layout: {
|
||||
viewports: [{ plugin: 'vtk' }, { plugin: 'vtk' }, { plugin: 'vtk' }],
|
||||
},
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
const action = {
|
||||
type: types.SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
viewports: [{ plugin: 'cornerstone' }],
|
||||
viewportSpecificData: {
|
||||
0: { viewportData0: 'cornerstoneData0' },
|
||||
},
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
numRows: 1,
|
||||
numColumns: 1,
|
||||
viewportSpecificData: {
|
||||
0: { viewportData0: 'cornerstoneData0' },
|
||||
},
|
||||
layout: {
|
||||
viewports: [{ plugin: 'cornerstone' }],
|
||||
},
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
Reducer(reducer)
|
||||
.withState(initialState)
|
||||
.expect(action)
|
||||
.toReturnState(expectedState);
|
||||
});
|
||||
|
||||
it('should handle SET_VIEWPORT when we only set one viewport specific data', () => {
|
||||
const initialState = {
|
||||
numRows: 1,
|
||||
numColumns: 2,
|
||||
viewportSpecificData: {
|
||||
0: { viewportData0: 'data0' },
|
||||
1: { viewportData1: 'data1' },
|
||||
},
|
||||
layout: {
|
||||
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
|
||||
},
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
const action = {
|
||||
type: types.SET_VIEWPORT,
|
||||
viewportIndex: 1,
|
||||
viewportSpecificData: {
|
||||
viewportData1: 'NEWdata1',
|
||||
},
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
numRows: 1,
|
||||
numColumns: 2,
|
||||
viewportSpecificData: {
|
||||
0: { viewportData0: 'data0' },
|
||||
1: { viewportData1: 'NEWdata1' },
|
||||
},
|
||||
layout: {
|
||||
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
|
||||
},
|
||||
activeViewportIndex: 0,
|
||||
};
|
||||
|
||||
Reducer(reducer)
|
||||
.withState(initialState)
|
||||
.expect(action)
|
||||
.toReturnState(expectedState);
|
||||
});
|
||||
|
||||
// If there were previous keys, this would have
|
||||
// "merge" behavior, not a clear & set
|
||||
// May be worth another test?
|
||||
it('should handle SET_VIEWPORT', () => {
|
||||
const viewportToSet = 0;
|
||||
const setViewportAction = {
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class RetrieveMetadataLoader {
|
||||
}
|
||||
|
||||
if (loaders.next().done && !result) {
|
||||
throw 'cant find data';
|
||||
throw new Error('RetrieveMetadataLoader failed');
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -337,40 +337,41 @@ async function makeSOPInstance(server, study, instance) {
|
||||
return sopInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert String to ArrayBuffer
|
||||
*
|
||||
* @param {String} str Input String
|
||||
* @return {ArrayBuffer} Output converted ArrayBuffer
|
||||
*/
|
||||
function str2ab(str) {
|
||||
const strLen = str.length;
|
||||
const bytes = new Uint8Array(strLen);
|
||||
|
||||
for (let i = 0; i < strLen; i++) {
|
||||
bytes[i] = str.charCodeAt(i);
|
||||
}
|
||||
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
function getPaletteColor(server, instance, tag, lutDescriptor) {
|
||||
const numLutEntries = lutDescriptor[0];
|
||||
const bits = lutDescriptor[2];
|
||||
|
||||
let uri = WADOProxy.convertURL(instance[tag].BulkDataURI, server);
|
||||
|
||||
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
|
||||
// incorrect bulk data URIs
|
||||
if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) {
|
||||
uri = uri.replace('http', 'https');
|
||||
}
|
||||
|
||||
const config = {
|
||||
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
};
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
BulkDataURI: uri,
|
||||
};
|
||||
|
||||
const readUInt16 = (byteArray, position) => {
|
||||
return byteArray[position] + byteArray[position + 1] * 256;
|
||||
};
|
||||
|
||||
const arrayBufferToPaletteColorLUT = result => {
|
||||
const arraybuffer = result[0];
|
||||
const arrayBufferToPaletteColorLUT = arraybuffer => {
|
||||
const byteArray = new Uint8Array(arraybuffer);
|
||||
const lut = [];
|
||||
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
if (bits === 16) {
|
||||
if (bits === 16) {
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = readUInt16(byteArray, i * 2);
|
||||
} else {
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = byteArray[i];
|
||||
}
|
||||
}
|
||||
@@ -378,7 +379,38 @@ function getPaletteColor(server, instance, tag, lutDescriptor) {
|
||||
return lut;
|
||||
};
|
||||
|
||||
return dicomWeb.retrieveBulkData(options).then(arrayBufferToPaletteColorLUT);
|
||||
if (instance[tag].BulkDataURI) {
|
||||
let uri = WADOProxy.convertURL(instance[tag].BulkDataURI, server);
|
||||
|
||||
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
|
||||
// incorrect bulk data URIs
|
||||
if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) {
|
||||
uri = uri.replace('http', 'https');
|
||||
}
|
||||
|
||||
const config = {
|
||||
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
};
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
BulkDataURI: uri,
|
||||
};
|
||||
|
||||
return dicomWeb
|
||||
.retrieveBulkData(options)
|
||||
.then(result => result[0])
|
||||
.then(arrayBufferToPaletteColorLUT);
|
||||
} else if (instance[tag].InlineBinary) {
|
||||
const inlineBinaryData = atob(instance[tag].InlineBinary);
|
||||
const arraybuf = str2ab(inlineBinaryData);
|
||||
|
||||
return arrayBufferToPaletteColorLUT(arraybuf);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Palette Color LUT was not provided as InlineBinary or BulkDataURI'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,6 +67,12 @@ function processSingleframe(instances) {
|
||||
if (instances.length > 2) {
|
||||
const firstIpp = _getImagePositionPatient(firstImage);
|
||||
const lastIpp = _getImagePositionPatient(instances[instances.length - 1]);
|
||||
|
||||
// We can't reconstruct if we are missing imagePositionPatient values
|
||||
if (!firstIpp || !lastIpp) {
|
||||
return { value: false };
|
||||
}
|
||||
|
||||
const averageSpacingBetweenFrames =
|
||||
_getPerpendicularDistance(firstIpp, lastIpp) / (instances.length - 1);
|
||||
|
||||
@@ -136,8 +142,13 @@ function _getSpacingIssue(spacing, averageSpacing) {
|
||||
}
|
||||
|
||||
function _getImagePositionPatient(instance) {
|
||||
return instance
|
||||
.getTagValue('x00200032')
|
||||
const tagValue = instance
|
||||
.getTagValue('x00200032');
|
||||
if (!tagValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
return tagValue
|
||||
.split('\\')
|
||||
.map(element => Number(element));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.52.5](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.4...@ohif/i18n@0.52.5) (2020-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* download tool fixes & improvements ([#1235](https://github.com/OHIF/Viewers/issues/1235)) ([b9574b6](https://github.com/OHIF/Viewers/commit/b9574b6efcfeb85cde35b5cae63282f8e1b35be6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/i18n",
|
||||
"version": "0.52.4",
|
||||
"version": "0.52.5",
|
||||
"description": "Internationalization library for The OHIF Viewer",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"emptyFilenameError": "The file name cannot be empty.",
|
||||
"fileType": "File Type",
|
||||
"filename": "File Name",
|
||||
"formTitle": "Please specify the dimensions, filename, and desired type for the output image.",
|
||||
"imageHeight": "Image height (px)",
|
||||
"imagePreview": "Image Preview",
|
||||
"imageWidth": "Image width (px)",
|
||||
"keepAspectRatio": "Keep aspect ratio",
|
||||
"loadingPreview": "Loading Image Preview...",
|
||||
"minHeightError": "The minimum valid height is 100px.",
|
||||
"minWidthError": "The minimum valid width is 100px.",
|
||||
"showAnnotations": "Show Annotations"
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import Header from './Header.json';
|
||||
import MeasurementTable from './MeasurementTable.json';
|
||||
import StudyList from './StudyList.json';
|
||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||
import ViewportDownloadForm from './ViewportDownloadForm.json';
|
||||
|
||||
export default {
|
||||
'en-US': {
|
||||
@@ -19,5 +20,6 @@ export default {
|
||||
MeasurementTable,
|
||||
StudyList,
|
||||
UserPreferencesModal,
|
||||
ViewportDownloadForm,
|
||||
},
|
||||
};
|
||||
@@ -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.9](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.8...@ohif/ui@1.1.9) (2020-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* download tool fixes & improvements ([#1235](https://github.com/OHIF/Viewers/issues/1235)) ([b9574b6](https://github.com/OHIF/Viewers/commit/b9574b6efcfeb85cde35b5cae63282f8e1b35be6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.1.8](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.7...@ohif/ui@1.1.8) (2020-01-08)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/ui",
|
||||
"version": "1.1.8",
|
||||
"version": "1.1.9",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import React, { useEffect, useState, createRef } from 'react';
|
||||
import React, {
|
||||
useRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
createRef,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import './ViewportDownloadForm.styl';
|
||||
import { TextInput, Select } from '@ohif/ui';
|
||||
import { TextInput, Select, Icon } from '@ohif/ui';
|
||||
import classnames from 'classnames';
|
||||
|
||||
const FILE_TYPE_OPTIONS = [
|
||||
{
|
||||
@@ -17,6 +24,7 @@ const FILE_TYPE_OPTIONS = [
|
||||
];
|
||||
|
||||
const DEFAULT_FILENAME = 'image';
|
||||
const REFRESH_VIEWPORT_TIMEOUT = 1000;
|
||||
|
||||
const ViewportDownloadForm = ({
|
||||
activeViewport,
|
||||
@@ -32,7 +40,7 @@ const ViewportDownloadForm = ({
|
||||
maximumSize,
|
||||
canvasClass,
|
||||
}) => {
|
||||
const [t] = useTranslation('Buttons');
|
||||
const [t] = useTranslation('ViewportDownloadForm');
|
||||
|
||||
const [filename, setFilename] = useState(DEFAULT_FILENAME);
|
||||
const [fileType, setFileType] = useState('jpg');
|
||||
@@ -44,6 +52,12 @@ const ViewportDownloadForm = ({
|
||||
|
||||
const [showAnnotations, setShowAnnotations] = useState(true);
|
||||
|
||||
const [keepAspect, setKeepAspect] = useState(true);
|
||||
const [aspectMultiplier, setAspectMultiplier] = useState({
|
||||
width: 1,
|
||||
height: 1,
|
||||
});
|
||||
|
||||
const [viewportElement, setViewportElement] = useState();
|
||||
const [viewportElementDimensions, setViewportElementDimensions] = useState({
|
||||
width: defaultSize,
|
||||
@@ -62,41 +76,129 @@ const ViewportDownloadForm = ({
|
||||
height: defaultSize,
|
||||
});
|
||||
|
||||
// Cornerstone's `enable/disable`
|
||||
useEffect(() => {
|
||||
enableViewport(viewportElement);
|
||||
const [error, setError] = useState({
|
||||
width: false,
|
||||
height: false,
|
||||
filename: false,
|
||||
});
|
||||
|
||||
return () => {
|
||||
disableViewport(viewportElement);
|
||||
};
|
||||
}, [disableViewport, enableViewport, viewportElement]);
|
||||
const hasError = Object.values(error).includes(true);
|
||||
|
||||
useEffect(() => {
|
||||
const { width, height } = viewportElementDimensions;
|
||||
const validSize = value => (value >= minimumSize ? value : minimumSize);
|
||||
const loadAndUpdateViewports = async () => {
|
||||
await loadImage(activeViewport, viewportElement, width, height);
|
||||
toggleAnnotations(showAnnotations, viewportElement);
|
||||
const refreshViewport = useRef(null);
|
||||
|
||||
const {
|
||||
dataUrl,
|
||||
width: viewportElementWidth,
|
||||
height: viewportElementHeight,
|
||||
} = await updateViewportPreview(
|
||||
viewportElement,
|
||||
downloadCanvas.ref.current,
|
||||
fileType
|
||||
const downloadImage = () => {
|
||||
downloadBlob(
|
||||
filename || DEFAULT_FILENAME,
|
||||
fileType,
|
||||
viewportElement,
|
||||
downloadCanvas.ref.current
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {object} event - Input change event
|
||||
* @param {string} dimension - "height" | "width"
|
||||
*/
|
||||
const onDimensionsChange = (event, dimension) => {
|
||||
const oppositeDimension = dimension === 'height' ? 'width' : 'height';
|
||||
const sanitizedTargetValue = event.target.value.replace(/\D/, '');
|
||||
const isEmpty = sanitizedTargetValue === '';
|
||||
const newDimensions = { ...dimensions };
|
||||
const updatedDimension = isEmpty
|
||||
? ''
|
||||
: Math.min(sanitizedTargetValue, maximumSize);
|
||||
|
||||
if (updatedDimension === dimensions[dimension]) {
|
||||
return;
|
||||
}
|
||||
|
||||
newDimensions[dimension] = updatedDimension;
|
||||
|
||||
if (keepAspect && newDimensions[oppositeDimension] !== '') {
|
||||
newDimensions[oppositeDimension] = Math.round(
|
||||
newDimensions[dimension] * aspectMultiplier[oppositeDimension]
|
||||
);
|
||||
}
|
||||
|
||||
setViewportPreview(state => ({
|
||||
// In current code, keepAspect is always `true`
|
||||
// And we always start w/ a square width/height
|
||||
setDimensions(newDimensions);
|
||||
|
||||
// Only update if value is non-empty
|
||||
if (!isEmpty) {
|
||||
setViewportElementDimensions(newDimensions);
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
src: dataUrl,
|
||||
width: validSize(viewportElementWidth),
|
||||
height: validSize(viewportElementHeight),
|
||||
...newDimensions,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const error_messages = {
|
||||
width: t('minWidthError'),
|
||||
height: t('minHeightError'),
|
||||
filename: t('emptyFilenameError'),
|
||||
};
|
||||
|
||||
const renderErrorHandler = errorType => {
|
||||
if (!error[errorType]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className="input-error">{error_messages[errorType]}</div>;
|
||||
};
|
||||
|
||||
const onKeepAspectToggle = () => {
|
||||
const { width, height } = dimensions;
|
||||
const aspectMultiplier = { ...aspectMultiplier };
|
||||
if (!keepAspect) {
|
||||
const base = Math.min(width, height);
|
||||
aspectMultiplier.width = width / base;
|
||||
aspectMultiplier.height = height / base;
|
||||
setAspectMultiplier(aspectMultiplier);
|
||||
}
|
||||
|
||||
setKeepAspect(!keepAspect);
|
||||
};
|
||||
|
||||
const validSize = value => (value >= minimumSize ? value : minimumSize);
|
||||
const loadAndUpdateViewports = useCallback(async () => {
|
||||
const { width: scaledWidth, height: scaledHeight } = await loadImage(
|
||||
activeViewport,
|
||||
viewportElement,
|
||||
dimensions.width,
|
||||
dimensions.height
|
||||
);
|
||||
|
||||
toggleAnnotations(showAnnotations, viewportElement);
|
||||
|
||||
const scaledDimensions = {
|
||||
height: validSize(scaledHeight),
|
||||
width: validSize(scaledWidth),
|
||||
};
|
||||
|
||||
loadAndUpdateViewports();
|
||||
setViewportElementDimensions(scaledDimensions);
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
...scaledDimensions,
|
||||
}));
|
||||
|
||||
const {
|
||||
dataUrl,
|
||||
width: viewportElementWidth,
|
||||
height: viewportElementHeight,
|
||||
} = await updateViewportPreview(
|
||||
viewportElement,
|
||||
downloadCanvas.ref.current,
|
||||
fileType
|
||||
);
|
||||
|
||||
setViewportPreview(state => ({
|
||||
...state,
|
||||
src: dataUrl,
|
||||
width: validSize(viewportElementWidth),
|
||||
height: validSize(viewportElementHeight),
|
||||
}));
|
||||
}, [
|
||||
activeViewport,
|
||||
viewportElement,
|
||||
@@ -111,76 +213,96 @@ const ViewportDownloadForm = ({
|
||||
viewportElementDimensions,
|
||||
]);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
useEffect(() => {
|
||||
enableViewport(viewportElement);
|
||||
|
||||
if (updatedDimension === dimensions.width) {
|
||||
return;
|
||||
return () => {
|
||||
disableViewport(viewportElement);
|
||||
};
|
||||
}, [disableViewport, enableViewport, viewportElement]);
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshViewport.current !== null) {
|
||||
clearTimeout(refreshViewport.current);
|
||||
}
|
||||
|
||||
// In current code, keepAspect is always `true`
|
||||
// And we always start w/ a square width/height
|
||||
setDimensions({
|
||||
width: updatedDimension,
|
||||
height: updatedDimension,
|
||||
});
|
||||
refreshViewport.current = setTimeout(() => {
|
||||
refreshViewport.current = null;
|
||||
loadAndUpdateViewports();
|
||||
}, REFRESH_VIEWPORT_TIMEOUT);
|
||||
}, [
|
||||
activeViewport,
|
||||
viewportElement,
|
||||
showAnnotations,
|
||||
dimensions,
|
||||
loadImage,
|
||||
toggleAnnotations,
|
||||
updateViewportPreview,
|
||||
fileType,
|
||||
downloadCanvas.ref,
|
||||
minimumSize,
|
||||
maximumSize,
|
||||
]);
|
||||
|
||||
// Only update if value is non-empty
|
||||
if (!isEmpty) {
|
||||
setViewportElementDimensions({
|
||||
height: updatedDimension,
|
||||
width: updatedDimension,
|
||||
});
|
||||
setDownloadCanvas(state => ({
|
||||
...state,
|
||||
height: updatedDimension,
|
||||
width: updatedDimension,
|
||||
}));
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
const { width, height } = dimensions;
|
||||
const hasError = {
|
||||
width: width < minimumSize,
|
||||
height: height < minimumSize,
|
||||
filename: !filename,
|
||||
};
|
||||
|
||||
const downloadImage = () => {
|
||||
downloadBlob(
|
||||
filename || DEFAULT_FILENAME,
|
||||
fileType,
|
||||
viewportElement,
|
||||
downloadCanvas.ref.current
|
||||
);
|
||||
};
|
||||
setError({ ...hasError });
|
||||
}, [dimensions, filename, minimumSize]);
|
||||
|
||||
return (
|
||||
<div className="ViewportDownloadForm">
|
||||
<div className="title">
|
||||
{t(
|
||||
'Please specify the dimensions, filename, and desired type for the output image.'
|
||||
)}
|
||||
</div>
|
||||
<div className="title">{t('formTitle')}</div>
|
||||
|
||||
<div className="file-info-container" data-cy="file-info-container">
|
||||
<div className="col">
|
||||
<div className="width">
|
||||
<TextInput
|
||||
data-cy="image-width"
|
||||
value={dimensions.width}
|
||||
label={t('Image width (px)')}
|
||||
onChange={evt => onDimensionsChange(evt, 'height')}
|
||||
/>
|
||||
<div className="dimension-wrapper">
|
||||
<div className="dimensions">
|
||||
<div className="width">
|
||||
<TextInput
|
||||
type="number"
|
||||
min={minimumSize}
|
||||
max={maximumSize}
|
||||
value={dimensions.width}
|
||||
label={t('imageWidth')}
|
||||
onChange={evt => onDimensionsChange(evt, 'width')}
|
||||
data-cy="image-width"
|
||||
/>
|
||||
{renderErrorHandler('width')}
|
||||
</div>
|
||||
<div className="height">
|
||||
<TextInput
|
||||
type="number"
|
||||
min={minimumSize}
|
||||
max={maximumSize}
|
||||
value={dimensions.height}
|
||||
label={t('imageHeight')}
|
||||
onChange={evt => onDimensionsChange(evt, 'height')}
|
||||
data-cy="image-height"
|
||||
/>
|
||||
{renderErrorHandler('height')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="height">
|
||||
<TextInput
|
||||
data-cy="image-height"
|
||||
value={dimensions.height}
|
||||
label={t('Image height (px)')}
|
||||
onChange={evt => onDimensionsChange(evt, 'width')}
|
||||
/>
|
||||
<div className="keep-aspect-wrapper">
|
||||
<button
|
||||
id="keep-aspect"
|
||||
className={classnames(
|
||||
'form-button btn',
|
||||
keepAspect ? 'active' : ''
|
||||
)}
|
||||
data-cy="keep-aspect"
|
||||
alt={t('keepAspectRatio')}
|
||||
onClick={onKeepAspectToggle}
|
||||
>
|
||||
<Icon
|
||||
name={keepAspect ? 'link' : 'unlink'}
|
||||
alt={keepAspect ? 'Dismiss Aspect' : 'Keep Aspect'}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -191,9 +313,10 @@ const ViewportDownloadForm = ({
|
||||
data-cy="file-name"
|
||||
value={filename}
|
||||
onChange={event => setFilename(event.target.value)}
|
||||
label={t('File name')}
|
||||
label={t('filename')}
|
||||
id="file-name"
|
||||
/>
|
||||
{renderErrorHandler('filename')}
|
||||
</div>
|
||||
<div className="file-type">
|
||||
<Select
|
||||
@@ -201,7 +324,7 @@ const ViewportDownloadForm = ({
|
||||
data-cy="file-type"
|
||||
onChange={event => setFileType(event.target.value)}
|
||||
options={FILE_TYPE_OPTIONS}
|
||||
label={t('File type')}
|
||||
label={t('fileType')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -217,7 +340,7 @@ const ViewportDownloadForm = ({
|
||||
checked={showAnnotations}
|
||||
onChange={event => setShowAnnotations(event.target.checked)}
|
||||
/>
|
||||
{t('Show Annotations')}
|
||||
{t('showAnnotations')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -245,15 +368,23 @@ const ViewportDownloadForm = ({
|
||||
></canvas>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
{viewportPreview.src ? (
|
||||
<div className="preview" data-cy="image-preview">
|
||||
<div className="preview-header"> {t('imagePreview')}</div>
|
||||
<img
|
||||
className="viewport-preview"
|
||||
src={viewportPreview.src}
|
||||
alt={t('imagePreview')}
|
||||
data-cy="image-preview"
|
||||
data-cy="viewport-preview-img"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="loading-image">
|
||||
<Icon name="circle-notch" className="icon-spin" />
|
||||
{t('loadingPreview')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="actions">
|
||||
<div className="action-cancel">
|
||||
@@ -263,16 +394,17 @@ const ViewportDownloadForm = ({
|
||||
className="btn btn-danger"
|
||||
onClick={onClose}
|
||||
>
|
||||
{t('Cancel')}
|
||||
{t('Buttons:Cancel')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="action-save">
|
||||
<button
|
||||
disabled={hasError}
|
||||
onClick={downloadImage}
|
||||
className="btn btn-primary"
|
||||
data-cy="download-btn"
|
||||
>
|
||||
{t('Download')}
|
||||
{t('Buttons:Download')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
input, select
|
||||
max-height: 30px;
|
||||
|
||||
#keep-aspect svg
|
||||
margin-top: 3px;
|
||||
|
||||
.title
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
@@ -17,85 +20,117 @@
|
||||
.file-info-container
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
padding: 20px 10px 0;
|
||||
background-color: #16202b;
|
||||
|
||||
.form-control.input-ohif
|
||||
padding: 6px 12px;
|
||||
padding: 20px 10px;
|
||||
background-color: var(--ui-gray-dark);
|
||||
|
||||
@media screen and (max-width: 1023px)
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.col
|
||||
flex-grow: 1;
|
||||
.width,
|
||||
.height,
|
||||
.file-name,
|
||||
.file-type
|
||||
height: 56px;
|
||||
|
||||
.input-ohif
|
||||
margin-left: 15px;
|
||||
.input-ohif
|
||||
margin: 0 5px;
|
||||
|
||||
.file-name,
|
||||
.file-type
|
||||
.select-ohif, .input-ohif
|
||||
width: 170px;
|
||||
|
||||
.input-ohif-label, .select-ohif-label
|
||||
width: 90px;
|
||||
display: inline-block;
|
||||
@media screen and (max-width: 1023px)
|
||||
margin-left: 0;
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
width: 120px;
|
||||
|
||||
.file-type
|
||||
.select-ohif
|
||||
margin-left: 17px;
|
||||
.dimension-wrapper
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.dimensions
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.input-ohif-label
|
||||
width: 120px;
|
||||
display: inline-block;
|
||||
|
||||
.input-ohif
|
||||
@media screen and (max-width: 1023px)
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
width: 170px;
|
||||
|
||||
.show-annotations
|
||||
font-weight: bold;
|
||||
line-height: 30px;
|
||||
input
|
||||
margin-right: 7px;
|
||||
vertical-align: middle;
|
||||
label
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.keep-aspect-wrapper
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
height: 86px;
|
||||
|
||||
.show-annotations
|
||||
font-weight: bold;
|
||||
line-height: 30px;
|
||||
input
|
||||
margin-right: 7px;
|
||||
vertical-align: middle;
|
||||
label
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.loading-image
|
||||
height: 580px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--active-color);
|
||||
font-size: 20px;
|
||||
|
||||
.icon-spin
|
||||
margin-right: 15px;
|
||||
|
||||
.preview
|
||||
display: flex;
|
||||
flex-direction column;
|
||||
height: fit-content;
|
||||
background-color: #16202b;
|
||||
width: fit-content;
|
||||
flex-direction: column;
|
||||
background-color: var(--ui-gray-dark);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
align-self: center;
|
||||
margin-bottom: 20px;
|
||||
@media screen and (max-width: 1023px)
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
height: 580px;
|
||||
|
||||
.viewport-preview
|
||||
max-height: 512px;
|
||||
max-width: 512px;
|
||||
|
||||
h4
|
||||
.preview-header
|
||||
width: 100%;
|
||||
text-align center;
|
||||
font-size: 1.3em;
|
||||
margin: 0 0 10px;
|
||||
|
||||
.preview-container
|
||||
width: auto;
|
||||
height: 100%;
|
||||
max-height: 400px;
|
||||
object-fit contain;
|
||||
|
||||
.actions
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
.action-cancel
|
||||
margin: 0 20px;
|
||||
.actions-save
|
||||
margin: 0 0 0 10px;
|
||||
margin-top: 20px;
|
||||
|
||||
.btn
|
||||
margin: 0 10px;
|
||||
|
||||
.input-error
|
||||
font-size: 12px;
|
||||
color: red;
|
||||
text-align: center;
|
||||
margin: 3px 0;
|
||||
|
||||
|
||||
.modal-dialog
|
||||
height: 100%;
|
||||
|
||||
@@ -10,7 +10,10 @@ export class TableListItem extends Component {
|
||||
children: PropTypes.node,
|
||||
itemClass: PropTypes.string,
|
||||
itemIndex: PropTypes.number,
|
||||
itemKey: PropTypes.oneOfType(['number', 'string']),
|
||||
itemKey: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
onItemClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
.icon-pulse
|
||||
fa-spin 1s infinite steps(8)
|
||||
|
||||
.icon-spin {
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes fa-spin{
|
||||
0%{ transform:rotate(0deg) }
|
||||
to{ transform:rotate(1turn) }
|
||||
|
||||
@@ -80,6 +80,7 @@ import thLarge from './icons/th-large.svg';
|
||||
import thList from './icons/th-list.svg';
|
||||
import times from './icons/times.svg';
|
||||
import trash from './icons/trash.svg';
|
||||
import unlink from './icons/unlink.svg';
|
||||
import user from './icons/user.svg';
|
||||
import youtube from './icons/youtube.svg';
|
||||
|
||||
@@ -158,6 +159,7 @@ const ICONS = {
|
||||
rotate,
|
||||
'rotate-right': rotateRight,
|
||||
trash,
|
||||
unlink,
|
||||
'exclamation-circle': exclamationCircle,
|
||||
link,
|
||||
'exclamation-triangle': exclamationTriangle,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby="unlink"
|
||||
viewBox="0 0 512 512"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
>
|
||||
<title id="title">Unlink</title>
|
||||
<path d="M304.083 388.936c4.686 4.686 4.686 12.284 0 16.971l-65.057 65.056c-54.709 54.711-143.27 54.721-197.989 0-54.713-54.713-54.719-143.27 0-197.989l65.056-65.057c4.686-4.686 12.284-4.686 16.971 0l22.627 22.627c4.686 4.686 4.686 12.284 0 16.971L81.386 311.82c-34.341 34.341-33.451 88.269.597 120.866 32.577 31.187 84.788 31.337 117.445-1.32l65.057-65.056c4.686-4.686 12.284-4.686 16.971 0l22.627 22.626zm-56.568-243.245l64.304-64.304c34.346-34.346 88.286-33.453 120.882.612 31.18 32.586 31.309 84.785-1.335 117.43l-65.056 65.057c-4.686 4.686-4.686 12.284 0 16.971l22.627 22.627c4.686 4.686 12.284 4.686 16.971 0l65.056-65.057c54.711-54.709 54.721-143.271 0-197.99-54.71-54.711-143.27-54.72-197.989 0l-65.057 65.057c-4.686 4.686-4.686 12.284 0 16.971l22.627 22.627c4.685 4.685 12.283 4.685 16.97-.001zm238.343 362.794l22.627-22.627c4.686-4.686 4.686-12.284 0-16.971L43.112 3.515c-4.686-4.686-12.284-4.686-16.971 0L3.515 26.142c-4.686 4.686-4.686 12.284 0 16.971l465.373 465.373c4.686 4.686 12.284 4.686 16.97-.001z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -29,18 +29,18 @@ class Select extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="select-ohif-container">
|
||||
<label className="select-ohif-label" htmlFor={this.id}>
|
||||
{this.props.label}
|
||||
<select className="form-control select-ohif" {...this.props}>
|
||||
{this.props.options.map(({ key, value }) => {
|
||||
return (
|
||||
<option key={key} value={value}>
|
||||
{key}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</label>
|
||||
{this.props.label && (
|
||||
<label className="select-ohif-label" htmlFor={this.id}>{this.props.label}</label>
|
||||
)}
|
||||
<select className="form-control select-ohif" {...this.props}>
|
||||
{this.props.options.map(({ key, value }) => {
|
||||
return (
|
||||
<option key={key} value={value}>
|
||||
{key}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class TextInput extends React.Component {
|
||||
PropTypes.number
|
||||
]),
|
||||
id: PropTypes.string,
|
||||
label:PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
@@ -28,15 +28,15 @@ class TextInput extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="input-ohif-container">
|
||||
<label className="input-ohif-label" htmlFor={this.props.id}>
|
||||
{this.props.label}
|
||||
<input
|
||||
type={this.props.type}
|
||||
id={this.props.id}
|
||||
className="form-control input-ohif"
|
||||
{...this.props}
|
||||
/>
|
||||
</label>
|
||||
{this.props.label && (
|
||||
<label className="input-ohif-label" htmlFor={this.props.id}>{this.props.label}</label>
|
||||
)}
|
||||
<input
|
||||
type={this.props.type}
|
||||
id={this.props.id}
|
||||
className="form-control input-ohif"
|
||||
{...this.props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const ExtractCssChunksPlugin = require('extract-css-chunks-webpack-plugin');
|
||||
// const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const { InjectManifest } = require('workbox-webpack-plugin');
|
||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||
@@ -27,6 +26,7 @@ const PUBLIC_URL = process.env.PUBLIC_URL || '/';
|
||||
const APP_CONFIG = process.env.APP_CONFIG || 'config/default.js';
|
||||
const PROXY_TARGET = process.env.PROXY_TARGET;
|
||||
const PROXY_DOMAIN = process.env.PROXY_DOMAIN;
|
||||
const ENTRY_TARGET = process.env.ENTRY_TARGET || `${SRC_DIR}/index.js`;
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const baseConfig = webpackBase(env, argv, { SRC_DIR, DIST_DIR });
|
||||
@@ -34,6 +34,9 @@ module.exports = (env, argv) => {
|
||||
const hasProxy = PROXY_TARGET && PROXY_DOMAIN;
|
||||
|
||||
const mergedConfig = merge(baseConfig, {
|
||||
entry: {
|
||||
app: ENTRY_TARGET,
|
||||
},
|
||||
output: {
|
||||
path: DIST_DIR,
|
||||
filename: isProdBuild ? '[name].bundle.[chunkhash].js' : '[name].js',
|
||||
|
||||
@@ -3,6 +3,92 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.5.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.5.0...@ohif/viewer@3.5.1) (2020-02-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.5.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.14...@ohif/viewer@3.5.0) (2020-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* lesion-tracker extension ([#1420](https://github.com/OHIF/Viewers/issues/1420)) ([73e4409](https://github.com/OHIF/Viewers/commit/73e440968ce4699d081a9c9f2d21dd68095b3056))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.4.14](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.13...@ohif/viewer@3.4.14) (2020-02-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.4.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.12...@ohif/viewer@3.4.13) (2020-01-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.4.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.11...@ohif/viewer@3.4.12) (2020-01-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.4.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.10...@ohif/viewer@3.4.11) (2020-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* download tool fixes & improvements ([#1235](https://github.com/OHIF/Viewers/issues/1235)) ([b9574b6](https://github.com/OHIF/Viewers/commit/b9574b6efcfeb85cde35b5cae63282f8e1b35be6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.4.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.9...@ohif/viewer@3.4.10) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.4.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.8...@ohif/viewer@3.4.9) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.4.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.7...@ohif/viewer@3.4.8) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.4.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.6...@ohif/viewer@3.4.7) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.4.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.5...@ohif/viewer@3.4.6) (2020-01-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
Binary file not shown.
@@ -16,14 +16,14 @@ describe('OHIF Download Snapshot File', () => {
|
||||
.click();
|
||||
});
|
||||
|
||||
it('checks displayed information for Tablet experience', function() {
|
||||
it('checks displayed information for Tablet experience', function () {
|
||||
// Set Tablet resolution
|
||||
cy.viewport(1000, 660);
|
||||
// Visual comparison
|
||||
cy.screenshot('Download Image Modal - Tablet experience');
|
||||
});
|
||||
|
||||
it('checks displayed information for Desktop experience', function() {
|
||||
it('checks displayed information for Desktop experience', function () {
|
||||
// Set Desktop resolution
|
||||
cy.viewport(1750, 720);
|
||||
// Visual comparison
|
||||
@@ -52,7 +52,7 @@ describe('OHIF Download Snapshot File', () => {
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
it('cancel changes on download modal', function() {
|
||||
it('cancel changes on download modal', function () {
|
||||
//Change Image Width, Filename and File Type
|
||||
cy.get('[data-cy="image-width"]')
|
||||
.clear()
|
||||
@@ -91,7 +91,7 @@ describe('OHIF Download Snapshot File', () => {
|
||||
// //Check error message
|
||||
// });
|
||||
|
||||
it('checks if "Show Annotations" checkbox will display annotations', function() {
|
||||
it('checks if "Show Annotations" checkbox will display annotations', function () {
|
||||
// Close modal that is initially opened
|
||||
cy.get('[data-cy="close-button"]').click();
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
describe('OHIF Load Local File', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/local');
|
||||
});
|
||||
|
||||
it('checks if user can navigate to /local', () => {
|
||||
cy.get('.drag-drop-contents').should(
|
||||
'contain',
|
||||
'Drag and Drop DICOM files here to load them in the Viewer'
|
||||
);
|
||||
});
|
||||
|
||||
it('loads an invalid file and verify if viewer is empty', () => {
|
||||
const fileName = 'example.json';
|
||||
|
||||
cy.fixture(fileName).then(fileContent => {
|
||||
cy.get('input[type=file]')
|
||||
.first()
|
||||
.upload({
|
||||
fileContent,
|
||||
fileName,
|
||||
mimeType: 'application/json',
|
||||
});
|
||||
});
|
||||
|
||||
//Verify if there is no thumbnail visible
|
||||
cy.expectMinimumThumbnails(0);
|
||||
//Verify if DOC thumnails is displayed and load it on viewport
|
||||
cy.get('[data-cy="thumbnail-list"]').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
describe('OHIF Load Local PDF File', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/local');
|
||||
});
|
||||
|
||||
it('loads a PDF DICOM file and visualize it', () => {
|
||||
const fileName = 'PDFDICOMfile.pdf';
|
||||
|
||||
cy.fixture(fileName).then(fileContent => {
|
||||
cy.get('input[type=file]')
|
||||
.first()
|
||||
.upload({
|
||||
fileContent,
|
||||
fileName,
|
||||
mimeType: 'application/pdf',
|
||||
});
|
||||
});
|
||||
|
||||
//Verify if 1 thumbnail is visible
|
||||
cy.expectMinimumThumbnails(1);
|
||||
//Verify if DOC thumnails is displayed and load it on viewport
|
||||
cy.get('[data-cy="thumbnail-list"]')
|
||||
.contains('DOC')
|
||||
.click({ force: true });
|
||||
|
||||
//Verify if PDF document is displayed on the viewport
|
||||
cy.get('#pdf-canvas-container').should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import '@percy/cypress';
|
||||
import 'cypress-file-upload';
|
||||
import { DragSimulator } from '../helpers/DragSimulator.js';
|
||||
import {
|
||||
initCornerstoneToolsAliases,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "3.4.6",
|
||||
"version": "3.5.1",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -19,12 +19,14 @@
|
||||
"scripts": {
|
||||
"build:package": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.commonjs.js --progress",
|
||||
"build:viewer": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
|
||||
"build:viewer:lesion-tracker": "cross-env ENTRY_TARGET=index-lesion-tracker.js NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
|
||||
"build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/pwa/ APP_CONFIG=config/netlify.js QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:package": "yarn run build:package",
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"dev:viewer": "yarn run dev",
|
||||
"dev:viewer:lesion-tracker": "cross-env ENTRY_TARGET=index-lesion-tracker.js NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"start": "yarn run dev",
|
||||
"test:e2e": "cypress open",
|
||||
"test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'",
|
||||
@@ -45,14 +47,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^2.3.5",
|
||||
"@ohif/core": "^2.3.9",
|
||||
"@ohif/extension-cornerstone": "^2.2.2",
|
||||
"@ohif/extension-dicom-html": "^1.1.0",
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.1",
|
||||
"@ohif/extension-vtk": "^1.2.10",
|
||||
"@ohif/i18n": "^0.52.4",
|
||||
"@ohif/ui": "^1.1.8",
|
||||
"@ohif/extension-lesion-tracker": "^0.1.0",
|
||||
"@ohif/extension-vtk": "^1.2.15",
|
||||
"@ohif/i18n": "^0.52.5",
|
||||
"@ohif/ui": "^1.1.9",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.2.1",
|
||||
@@ -88,7 +91,7 @@
|
||||
"cypress": "^3.8.0",
|
||||
"gh-pages": "2.0.1",
|
||||
"identity-obj-proxy": "3.0.x",
|
||||
"lodash": "4.17.11",
|
||||
"lodash": "4.17.15",
|
||||
"lodash.clonedeep": "4.5.0"
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,44 @@ class OHIFStandaloneViewer extends Component {
|
||||
path="/callback"
|
||||
render={() => <CallbackPage userManager={userManager} />}
|
||||
/>
|
||||
<Route
|
||||
path="/login"
|
||||
component={() => {
|
||||
const queryParams = new URLSearchParams(
|
||||
this.props.location.search
|
||||
);
|
||||
const iss = queryParams.get('iss');
|
||||
const loginHint = queryParams.get('login_hint');
|
||||
const targetLinkUri = queryParams.get('target_link_uri');
|
||||
const oidcAuthority =
|
||||
appConfig.oidc !== null && appConfig.oidc[0].authority;
|
||||
if (iss !== oidcAuthority) {
|
||||
console.error(
|
||||
'iss of /login does not match the oidc authority'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
userManager.removeUser().then(() => {
|
||||
if (targetLinkUri !== null) {
|
||||
sessionStorage.setItem(
|
||||
'ohif-redirect-to',
|
||||
new URL(targetLinkUri).pathname
|
||||
);
|
||||
} else {
|
||||
sessionStorage.setItem('ohif-redirect-to', '/');
|
||||
}
|
||||
|
||||
if (loginHint !== null) {
|
||||
userManager.signinRedirect({ login_hint: loginHint });
|
||||
} else {
|
||||
userManager.signinRedirect();
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
component={() => {
|
||||
userManager.getUser().then(user => {
|
||||
|
||||
@@ -253,6 +253,7 @@ function ViewerRetrieveStudyData({
|
||||
.catch(error => {
|
||||
if (error && !error.isCanceled) {
|
||||
setError(true);
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -290,11 +291,13 @@ function ViewerRetrieveStudyData({
|
||||
.catch(error => {
|
||||
if (error && !error.isCanceled) {
|
||||
setError(true);
|
||||
log.error(error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error) {
|
||||
setError(true);
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Entry point for development and production PWA builds.
|
||||
* Packaged (NPM) builds go through `index-umd.js`
|
||||
*/
|
||||
|
||||
import 'regenerator-runtime/runtime';
|
||||
|
||||
import App from './App.js';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
/**
|
||||
* EXTENSIONS
|
||||
* =================
|
||||
*
|
||||
* Importing and modifying the extensions our app uses HERE allows us to leverage
|
||||
* tree shaking and a few other niceties. However, by including them here they become
|
||||
* "baked in" to the published application.
|
||||
*
|
||||
* Depending on your use case/needs, you may want to consider not adding any extensions
|
||||
* by default HERE, and instead provide them via the extensions configuration key or
|
||||
* by using the exported `App` component, and passing in your extensions as props using
|
||||
* the defaultExtensions property.
|
||||
*/
|
||||
import OHIFLesionTrackerExtension from '@ohif/extension-lesion-tracker';
|
||||
import OHIFDicomPDFExtension from '@ohif/extension-dicom-pdf';
|
||||
|
||||
/*
|
||||
* Default Settings
|
||||
*/
|
||||
let config = {};
|
||||
|
||||
if (window) {
|
||||
config = window.config || {};
|
||||
}
|
||||
|
||||
const appProps = {
|
||||
config,
|
||||
defaultExtensions: [OHIFLesionTrackerExtension, OHIFDicomPDFExtension],
|
||||
};
|
||||
|
||||
/** Create App */
|
||||
const app = React.createElement(App, appProps, null);
|
||||
|
||||
/** Render */
|
||||
ReactDOM.render(app, document.getElementById('root'));
|
||||
Reference in new issue
Block a user