Compare commits

..
Author SHA1 Message Date
ohif-bot 74f3cbc4b2 chore(release): publish [skip ci]
- @ohif/viewer@1.9.1
2019-10-15 16:10:17 +00:00
James Petts d091cd604c fix: 🐛 Remove debugger statement left in from last PR (#1052) 2019-10-15 18:03:29 +02:00
ohif-bot 3a4280163d chore(release): publish [skip ci]
- @ohif/extension-vtk@0.52.10
 - @ohif/core@1.4.0
 - @ohif/viewer@1.9.0
2019-10-15 15:18:15 +00:00
James Petts 2d75e01ea0 feat: 🎸 Only allow reconstruction of datasets that make sense (#1010)
* feat: 🎸 Only allow reconstruction of datasets that make sense

Only allow reconstruction of datasets which are imaging data, that have
frames in the same orientation, with the same size and make sense to be
reconstructed in 3D.

Closes: #561
2019-10-15 17:08:53 +02:00
Danny Brown 5c5a49486d docs: more specific location for build output 2019-10-15 08:56:54 -04:00
ohif-bot a8a57132a7 chore(release): publish [skip ci]
- @ohif/extension-vtk@0.52.9
 - @ohif/ui@0.54.0
 - @ohif/viewer@1.8.0
2019-10-14 15:46:13 +00:00
Rodrigo Antinarelli 92c8996ed4 feat: Notification Service (#1011)
* WIP - Notification Service

* add snackbar provider into the main App component

* feat: Notification Service

* revert unmodified file

* Update DicomHtmlViewport.js

* update css classnames

* revert toolbarRow

* fix revert toolbarRow

* styles adjustments and hide method

* remove unecessary default options

* minor fixes

* fix long truncated messages

* move SnackbarContext to ui project, rename, export
2019-10-14 11:39:31 -04:00
ohif-bot f07dca5fa4 chore(release): publish [skip ci]
- @ohif/viewer@1.7.0
2019-10-14 12:50:19 +00:00
Karthik Rao c99e0d8a23 feat: Implement a 'Exit 2D MPR' button in the toolbar
* feat: Implement a 'Exit 2D MPR' button in the toolbar to act as a toggle when '2D MPR' button is clicked

* Move logic to exit MPR and toggling into ConnectedPluginSwitch and PluginSwitch. Also, discard the ConnectedExitPluginSwitch and ExitPluginSwitch components.

* Update ToolbarRow.js
2019-10-14 08:41:29 -04:00
25 changed files with 802 additions and 47 deletions

No files matched your search

@@ -48,7 +48,7 @@ If everything worked as expected, you should have a new `dist/` directory in the
project's folder. It should roughly resemble the following:
```bash
dist/
<root>platform/viewer/dist/
├── app-config.js
├── app.bundle.js
├── app.css
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.52.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.9...@ohif/extension-vtk@0.52.10) (2019-10-15)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.8...@ohif/extension-vtk@0.52.9) (2019-10-14)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.7...@ohif/extension-vtk@0.52.8) (2019-10-14)
**Note:** Version bump only for package @ohif/extension-vtk
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "0.52.8",
"version": "0.52.10",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -51,8 +51,8 @@
"react-vtkjs-viewport": "^0.1.6"
},
"devDependencies": {
"@ohif/core": "^1.3.2",
"@ohif/ui": "^0.53.4",
"@ohif/core": "^1.4.0",
"@ohif/ui": "^0.54.0",
"cornerstone-tools": "^4.0.9",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.6.1",
+11
View File
@@ -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.4.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.2...@ohif/core@1.4.0) (2019-10-15)
### Features
* 🎸 Only allow reconstruction of datasets that make sense ([#1010](https://github.com/OHIF/Viewers/issues/1010)) ([2d75e01](https://github.com/OHIF/Viewers/commit/2d75e01)), closes [#561](https://github.com/OHIF/Viewers/issues/561)
## [1.3.2](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.1...@ohif/core@1.3.2) (2019-10-14)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "1.3.2",
"version": "1.4.0",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -9,6 +9,7 @@ import { SeriesMetadata } from './SeriesMetadata';
import { api } from 'dicomweb-client';
// - createStacks
import { isImage } from '../../utils/isImage';
import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable';
import isLowPriorityModality from '../../utils/isLowPriorityModality';
export class StudyMetadata extends Metadata {
@@ -620,6 +621,16 @@ const makeDisplaySet = (series, instances) => {
imageSet.getImage(0).getRawValue('x00200013')
);
const isReconstructable = isDisplaySetReconstructable(series, instances);
imageSet.isReconstructable = isReconstructable.value;
if (isReconstructable.missingFrames) {
// TODO -> This is currently unused, but may be used for reconstructing
// Volumes with gaps later on.
imageSet.missingFrames = isReconstructable.missingFrames;
}
return imageSet;
};
@@ -0,0 +1,157 @@
/**
* Checks if a series is reconstructable to a 3D volume.
*
* @param {Object} series The `OHIFSeriesMetadata` object.
* @param {Object[]} instances The `OHIFInstanceMetadata` object
*/
export default function isDisplaySetReconstructable(series, instances) {
// Can't reconstruct if we only have one image.
const modality = series._data.modality; // TODO -> Is there a better way to get this?
const isMultiframe = instances[0].getRawValue('x00280008') > 1;
if (!constructableModalities.includes(modality)) {
return { value: false };
}
if (!isMultiframe && instances.length === 1) {
return { values: false };
}
if (isMultiframe) {
return processMultiframe(instances[0]);
} else {
return processSingleframe(instances);
}
}
function processMultiframe(instance) {
//TODO: deal with multriframe checks! return true for now.
return { value: true };
}
function processSingleframe(instances) {
const firstImage = instances[0];
const firstImageRows = firstImage.getTagValue('x00280010');
const firstImageColumns = firstImage.getTagValue('x00280011');
const firstImageSamplesPerPixel = firstImage.getTagValue('x00280002');
// Note: No need to unpack iop, can compare string form.
const firstImageOrientationPatient = firstImage.getTagValue('x00200037');
// Can't reconstruct if we:
// -- Have a different dimensions within a displaySet.
// -- Have a different number of components within a displaySet.
// -- Have different orientations within a displaySet.
for (let i = 1; i < instances.length; i++) {
const instance = instances[i];
const rows = instance.getTagValue('x00280010');
const columns = instance.getTagValue('x00280011');
const samplesPerPixel = instance.getTagValue('x00280002');
const imageOrientationPatient = instance.getTagValue('x00200037');
if (
rows !== firstImageRows ||
columns !== firstImageColumns ||
samplesPerPixel !== firstImageSamplesPerPixel ||
imageOrientationPatient !== firstImageOrientationPatient
) {
return { value: false };
}
}
let missingFrames = 0;
// Check if frame spacing is approximately equal within a tolerance.
// If spacing is on a uniform grid but we are missing frames,
// Allow reconstruction, but pass back the number of missing frames.
if (instances.length > 2) {
const firstIpp = _getImagePositionPatient(firstImage);
const lastIpp = _getImagePositionPatient(instances[instances.length - 1]);
const averageSpacingBetweenFrames =
_getPerpendicularDistance(firstIpp, lastIpp) / (instances.length - 1);
let previousIpp = firstIpp;
for (let i = 1; i < instances.length; i++) {
const instance = instances[i];
const ipp = _getImagePositionPatient(instance);
const spacingBetweenFrames = _getPerpendicularDistance(ipp, previousIpp);
const spacingIssue = _getSpacingIssue(
spacingBetweenFrames,
averageSpacingBetweenFrames
);
if (spacingIssue) {
const issue = spacingIssue.issue;
if (issue === reconstructionIssues.MISSING_FRAMES) {
missingFrames += spacingIssue.missingFrames;
} else if (issue === reconstructionIssues.IRREGULAR_SPACING) {
return { value: false };
}
}
previousIpp = ipp;
}
}
return { value: true, missingFrames };
}
// TODO: Is 10% a reasonable tolerance for spacing?
const tolerance = 0.1;
/**
* Checks for spacing issues.
*
* @param {number} spacing The spacing between two frames.
* @param {number} averageSpacing The average spacing between all frames.
*
* @returns {Object} An object containing the issue and extra information if necessary.
*/
function _getSpacingIssue(spacing, averageSpacing) {
const equalWithinTolerance =
Math.abs(spacing - averageSpacing) < averageSpacing * tolerance;
if (equalWithinTolerance) {
return;
}
const multipleOfAverageSpacing = spacing / averageSpacing;
const numberOfSpacings = Math.round(multipleOfAverageSpacing);
const errorForEachSpacing =
Math.abs(spacing - numberOfSpacings * averageSpacing) / numberOfSpacings;
if (errorForEachSpacing < tolerance * averageSpacing) {
return {
issue: reconstructionIssues.MISSING_FRAMES,
missingFrames: numberOfSpacings - 1,
};
}
return { issue: reconstructionIssues.IRREGULAR_SPACING };
}
function _getImagePositionPatient(instance) {
return instance
.getTagValue('x00200032')
.split('\\')
.map(element => Number(element));
}
function _getPerpendicularDistance(a, b) {
return Math.sqrt(
Math.pow(a[0] - b[0], 2) +
Math.pow(a[1] - b[1], 2) +
Math.pow(a[2] - b[2], 2)
);
}
const constructableModalities = ['MR', 'CT', 'PT', 'NM'];
const reconstructionIssues = {
MISSING_FRAMES: 'missingframes',
IRREGULAR_SPACING: 'irregularspacing',
};
+11
View File
@@ -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.54.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.4...@ohif/ui@0.54.0) (2019-10-14)
### Features
* Notification Service ([#1011](https://github.com/OHIF/Viewers/issues/1011)) ([92c8996](https://github.com/OHIF/Viewers/commit/92c8996))
## [0.53.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.3...@ohif/ui@0.53.4) (2019-10-11)
**Note:** Version bump only for package @ohif/ui
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/ui",
"version": "0.53.4",
"version": "0.54.0",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",
@@ -0,0 +1,196 @@
.sb-container {
position: fixed;
width: var(--snackbar-size);
padding: 20px;
z-index: var(--snackbar-zIndex);
box-sizing: border-box;
height: auto;
}
.sb-topLeft {
top: 0;
bottom: auto;
left: 0;
right: auto;
}
.sb-topCenter {
left: 50%;
top: 0;
bottom: auto;
transform: translateX(-50%);
}
.sb-topRight {
top: 0;
bottom: auto;
left: auto;
right: 0;
}
.sb-bottomLeft {
top: auto;
bottom: 0px;
left: 0px;
right: auto;
}
.sb-bottomCenter {
left: 50%;
bottom: 0;
top: auto;
transform: translateX(-50%);
}
.sb-bottomRight {
top: auto;
bottom: 0px;
left: auto;
right: 0px;
margin: 10px 0 0;
}
.sb-topLeft .sb-item,
.sb-topCenter .sb-item,
.sb-topRight .sb-item {
margin: 10px 0 0;
}
.sb-bottomLeft .sb-item,
.sb-bottomCenter .sb-item,
.sb-bottomRight .sb-item {
margin: 0 0 10px;
}
.sb-closeBtn {
height: 20px;
opacity: 1;
overflow: hidden;
padding: 2px;
text-align: center;
text-shadow: none;
width: 20px;
cursor: pointer;
position: absolute;
right: 5px;
top: 5px;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.6);
border-radius: 100%;
}
.sb-closeBtn:hover {
background: #fff;
}
.sb-closeIcon {
display: block;
font-size: 0;
height: 100%;
line-height: 0;
overflow: hidden;
position: relative;
width: 100%;
}
.sb-closeIcon:after,
.sb-closeIcon:before {
content: ' ';
display: block;
height: 2px;
transition: all 0.3s ease;
width: 12px;
background-color: #222;
opacity: 1;
position: absolute;
}
.sb-closeIcon:before {
left: 4px;
top: 3px;
transform: rotate(45deg);
transform-origin: 0px 50%;
}
.sb-closeIcon:after {
right: 3px;
top: 5px;
transform: rotate(-45deg);
transform-origin: calc(100% - 3px) 50%;
}
.sb-title {
font-size: 16px;
font-weight: bold;
}
.sb-message {
font-size: 14px;
word-break: break-all;
}
.sb-item {
position: relative;
transition: height 300ms ease;
animation: fadein 1s;
padding: 20px;
color: white;
overflow: hidden;
border-radius: 4px;
box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.2), 0 1px 18px 0 rgba(0, 0, 0, 0.12),
0 3px 5px -1px rgba(0, 0, 0, 0.14);
}
.sb-item a {
color: white;
text-decoration: underline;
}
.sb-container .sb-hidden {
padding-top: 0;
padding-bottom: 0;
margin-bottom: 0;
height: 0;
opacity: 0;
transition: all 300ms ease;
}
.sb-success {
background-color: var(--snackbar-success);
}
.sb-error {
background-color: var(--snackbar-error);
}
.sb-warning {
background-color: var(--snackbar-warning);
}
.sb-info {
background-color: var(--snackbar-info);
}
@keyframes fadein {
from {
opacity: 0;
top: 30px;
}
to {
opacity: 1;
top: 0;
}
}
/* Internet Explorer */
@-ms-keyframes fadein {
from {
opacity: 0;
top: 30px;
}
to {
opacity: 1;
top: 0;
}
}
@@ -0,0 +1,53 @@
import React from 'react';
import SnackbarItem from './SnackbarItem';
import './Snackbar.css';
import { useSnackbarContext } from '../../utils/SnackbarProvider';
const SnackbarContainer = () => {
const { snackbarItems, hide } = useSnackbarContext();
const renderItem = item => {
return <SnackbarItem key={item.itemId} options={item} onClose={hide} />;
};
if (!snackbarItems) {
return null;
}
const renderItems = () => {
const items = {
topLeft: [],
topCenter: [],
topRight: [],
bottomLeft: [],
bottomCenter: [],
bottomRight: [],
};
snackbarItems.map(item => {
items[item.position].push(item);
});
return (
<div>
{Object.keys(items).map(pos => {
if (!items[pos].length) {
return null;
}
return (
<div key={pos} className={`sb-container sb-${pos}`}>
{items[pos].map(item => (
<div key={item.id}>{renderItem(item)}</div>
))}
</div>
);
})}
</div>
);
};
return <>{renderItems()}</>;
};
export default SnackbarContainer;
@@ -0,0 +1,31 @@
import React, { useState, useEffect } from 'react';
const SnackbarItem = ({ options, onClose }) => {
const handleClose = () => {
onClose(options.id);
};
useEffect(() => {
if (options.autoClose) {
setTimeout(() => {
handleClose();
}, options.duration);
}
}, []);
return (
<div
className={`${options.visible ? '' : 'sb-hidden'} sb-${
options.type
} sb-item`}
>
<span className="sb-closeBtn" onClick={handleClose}>
<span className="sb-closeIcon">x</span>
</span>
{options.title && <div className="sb-title">{options.title}</div>}
{options.message && <div className="sb-message">{options.message}</div>}
</div>
);
};
export default SnackbarItem;
@@ -0,0 +1,6 @@
export default {
INFO: 'info',
WARNING: 'warning',
SUCCESS: 'success',
ERROR: 'error',
};
+7
View File
@@ -32,6 +32,10 @@ import { ScrollableArea } from './ScrollableArea/ScrollableArea.js';
import Toolbar from './viewer/Toolbar.js';
import ToolbarButton from './viewer/ToolbarButton.js';
import ViewerbaseDragDropContext from './utils/viewerbaseDragDropContext.js';
import SnackbarProvider, {
useSnackbarContext,
withSnackbar,
} from './utils/SnackbarProvider';
export {
ICONS,
@@ -68,4 +72,7 @@ export {
UserPreferences,
UserPreferencesModal,
ViewerbaseDragDropContext,
SnackbarProvider,
useSnackbarContext,
withSnackbar,
};
+102
View File
@@ -0,0 +1,102 @@
import React, { useState, createContext, useContext } from 'react';
import SnackbarContainer from '../components/snackbar/SnackbarContainer';
import SnackbarTypes from '../components/snackbar/SnackbarTypes';
const SnackbarContext = createContext(null);
export const useSnackbarContext = () => useContext(SnackbarContext);
const SnackbarProvider = ({ children }) => {
const DEFAULT_OPTIONS = {
title: '',
message: '',
duration: 5000,
autoClose: true,
position: 'bottomRight',
type: SnackbarTypes.INFO,
};
const [count, setCount] = useState(1);
const [snackbarItems, setSnackbarItems] = useState([]);
const show = options => {
if (!options || (!options.title && !options.message)) {
console.warn(
'Snackbar cannot be rendered without required parameters: title | message'
);
return null;
}
const newItem = {
...DEFAULT_OPTIONS,
...options,
id: count,
visible: true,
};
setSnackbarItems(state => [...state, newItem]);
setCount(count + 1);
};
const hide = id => {
const hideItem = items => {
const newItems = items.map(item => {
if (item.id === id) {
item.visible = false;
}
return item;
});
return newItems;
};
setSnackbarItems(state => hideItem(state));
setTimeout(() => {
setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
}, 1000);
};
const hideAll = () => {
// reset count
setCount(1);
// remove all items from array
setSnackbarItems(() => []);
};
/**
* expose snackbar methods to window for debug purposes
* TODO: Check if it's really necessary
*/
window.snackbar = {
show,
hide,
hideAll,
};
return (
<SnackbarContext.Provider value={{ show, hide, hideAll, snackbarItems }}>
{!!snackbarItems && <SnackbarContainer />}
{children}
</SnackbarContext.Provider>
);
};
/**
*
* High Order Component to use the snackbar methods through a Class Component
*
*/
export const withSnackbar = Component => {
return function WrappedComponent(props) {
const snackbarContext = {
...useSnackbarContext(),
};
return <Component {...props} snackbarContext={snackbarContext} />;
};
};
export default SnackbarProvider;
+44
View File
@@ -3,6 +3,50 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.9.0...@ohif/viewer@1.9.1) (2019-10-15)
### Bug Fixes
* 🐛 Remove debugger statement left in from last PR ([#1052](https://github.com/OHIF/Viewers/issues/1052)) ([d091cd6](https://github.com/OHIF/Viewers/commit/d091cd6))
# [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.8.0...@ohif/viewer@1.9.0) (2019-10-15)
### Features
* 🎸 Only allow reconstruction of datasets that make sense ([#1010](https://github.com/OHIF/Viewers/issues/1010)) ([2d75e01](https://github.com/OHIF/Viewers/commit/2d75e01)), closes [#561](https://github.com/OHIF/Viewers/issues/561)
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.7.0...@ohif/viewer@1.8.0) (2019-10-14)
### Features
* Notification Service ([#1011](https://github.com/OHIF/Viewers/issues/1011)) ([92c8996](https://github.com/OHIF/Viewers/commit/92c8996))
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.6.3...@ohif/viewer@1.7.0) (2019-10-14)
### Features
* Implement a 'Exit 2D MPR' button in the toolbar ([c99e0d8](https://github.com/OHIF/Viewers/commit/c99e0d8))
## [1.6.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.6.2...@ohif/viewer@1.6.3) (2019-10-14)
**Note:** Version bump only for package @ohif/viewer
@@ -42,9 +42,6 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.get('@moreBtn')
.should('be.visible')
.contains('More');
cy.get('@twodmprBtn')
.should('be.visible')
.contains('2D MPR');
cy.get('@layoutBtn')
.should('be.visible')
.contains('Layout');
@@ -10,7 +10,6 @@ export function initCornerstoneToolsAliases() {
cy.get('.ToolbarRow > :nth-child(9)').as('resetBtn');
cy.get('.ToolbarRow > :nth-child(10)').as('cineBtn');
cy.get('.expandableToolMenu').as('moreBtn');
cy.get('.PluginSwitch > .toolbar-button').as('twodmprBtn');
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
}
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "1.6.3",
"version": "1.9.1",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@@ -45,14 +45,14 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/core": "^1.3.2",
"@ohif/core": "^1.4.0",
"@ohif/extension-cornerstone": "^2.0.0",
"@ohif/extension-dicom-html": "^1.0.0",
"@ohif/extension-dicom-microscopy": "^0.50.6",
"@ohif/extension-dicom-pdf": "^1.0.0",
"@ohif/extension-vtk": "^0.52.8",
"@ohif/extension-vtk": "^0.52.10",
"@ohif/i18n": "^0.50.5",
"@ohif/ui": "^0.53.4",
"@ohif/ui": "^0.54.0",
"@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6",
"core-js": "^3.2.1",
+7 -2
View File
@@ -27,6 +27,7 @@ import { BrowserRouter as Router } from 'react-router-dom';
import { getActiveContexts } from './store/layout/selectors.js';
import i18n from '@ohif/i18n';
import store from './store';
import { SnackbarProvider } from '@ohif/ui';
// Contexts
import WhiteLabellingContext from './context/WhiteLabellingContext';
@@ -99,7 +100,9 @@ class App extends Component {
<WhiteLabellingContext.Provider
value={this.props.whiteLabelling}
>
<OHIFStandaloneViewer userManager={userManager} />
<SnackbarProvider>
<OHIFStandaloneViewer userManager={userManager} />
</SnackbarProvider>
</WhiteLabellingContext.Provider>
</Router>
</UserManagerContext.Provider>
@@ -116,7 +119,9 @@ class App extends Component {
<I18nextProvider i18n={i18n}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider value={this.props.whiteLabelling}>
<OHIFStandaloneViewer />
<SnackbarProvider>
<OHIFStandaloneViewer />
</SnackbarProvider>
</WhiteLabellingContext.Provider>
</Router>
</I18nextProvider>
@@ -1,9 +1,14 @@
// import OHIF from '@ohif/core';
import OHIF from '@ohif/core';
import React from 'react';
import PluginSwitch from './PluginSwitch.js';
import { commandsManager } from './../App.js';
import { connect } from 'react-redux';
// const { setLayout } = OHIF.redux.actions;
const { setLayout } = OHIF.redux.actions;
const ConnectedPluginSwitch = props => {
return <PluginSwitch {...props} />;
};
const mapStateToProps = state => {
const { activeViewportIndex, layout, viewportSpecificData } = state.viewports;
@@ -15,13 +20,13 @@ const mapStateToProps = state => {
};
};
// const mapDispatchToProps = dispatch => {
// return {
// setLayout: data => {
// dispatch(setLayout(data));
// }
// };
// };
const mapDispatchToProps = dispatch => {
return {
setLayout: data => {
dispatch(setLayout(data));
},
};
};
/*function setSingleLayoutData(originalArray, viewportIndex, data) {
const viewports = originalArray.slice();
@@ -33,23 +38,35 @@ const mapStateToProps = state => {
}*/
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
//const { activeViewportIndex, layout } = propsFromState;
//const { setLayout } = propsFromDispatch;
const { activeViewportIndex, viewportSpecificData } = propsFromState;
const { studies } = ownProps;
const { setLayout } = propsFromDispatch;
// TODO: Do not display certain options if the current display set
// cannot be displayed using these view types
const mpr = () => {
commandsManager.runCommand('mpr2d');
};
const exitMpr = () => {
const layout = {
numRows: 1,
numColumns: 1,
viewports: [{ plugin: 'cornerstone' }],
};
setLayout(layout);
};
return {
mpr,
exitMpr,
activeViewportIndex,
viewportSpecificData,
studies,
};
};
const ConnectedPluginSwitch = connect(
export default connect(
mapStateToProps,
null, // mapDispatchToProps
mapDispatchToProps,
mergeProps
)(PluginSwitch);
export default ConnectedPluginSwitch;
)(ConnectedPluginSwitch);
@@ -6,17 +6,101 @@ import './PluginSwitch.css';
class PluginSwitch extends Component {
static propTypes = {
mpr: PropTypes.func,
activeViewportIndex: PropTypes.number,
viewportSpecificData: PropTypes.object,
studies: PropTypes.array,
exitMpr: PropTypes.func,
};
static defaultProps = {};
constructor(props) {
super(props);
this.state = {
isPlugSwitchOn: false,
label: '2D MPR',
icon: 'cube',
};
}
handleClick = () => {
if (this.state.isPlugSwitchOn) {
this.setState({
isPlugSwitchOn: false,
label: '2D MPR',
icon: 'cube',
});
this.props.exitMpr();
} else {
this.setState({
isPlugSwitchOn: true,
label: 'Exit 2D MPR',
icon: 'times',
});
this.props.mpr();
}
};
render() {
const { label, icon } = this.state;
// Render exit mpr if switched on, otherwise check if mpr button should be displayed.
const shouldRender =
this.state.isPlugSwitchOn || _shouldRenderMpr2DButton.call(this);
return (
<div className="PluginSwitch">
<ToolbarButton label="2D MPR" icon="cube" onClick={this.props.mpr} />
</div>
<>
{shouldRender && (
<div className="PluginSwitch">
<ToolbarButton
label={label}
icon={icon}
onClick={this.handleClick}
/>
</div>
)}
</>
);
}
}
function _shouldRenderMpr2DButton() {
const { viewportSpecificData, studies, activeViewportIndex } = this.props;
if (!viewportSpecificData[activeViewportIndex]) {
return;
}
const { displaySetInstanceUid, studyInstanceUid } = viewportSpecificData[
activeViewportIndex
];
const displaySet = _getDisplaySet(
studies,
studyInstanceUid,
displaySetInstanceUid
);
if (!displaySet) {
return;
}
return displaySet.isReconstructable;
}
function _getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
const study = studies.find(
study => study.studyInstanceUid === studyInstanceUid
);
if (!study) {
return;
}
const displaySet = study.displaySets.find(set => {
return set.displaySetInstanceUid === displaySetInstanceUid;
});
return displaySet;
}
export default PluginSwitch;
@@ -25,6 +25,7 @@ class ToolbarRow extends Component {
selectedRightSidePanel: PropTypes.string.isRequired,
handleSidePanelChange: PropTypes.func,
activeContexts: PropTypes.arrayOf(PropTypes.string).isRequired,
studies: PropTypes.array,
};
constructor(props) {
@@ -128,7 +129,7 @@ class ToolbarRow extends Component {
</div>
{buttonComponents}
<ConnectedLayoutButton />
<ConnectedPluginSwitch />
<ConnectedPluginSwitch studies={this.props.studies} />
<div
className="pull-right m-t-1 rm-x-1"
style={{ marginLeft: 'auto' }}
@@ -229,7 +230,6 @@ function _getButtonComponents(toolbarButtons, activeButtons) {
});
}
/**
* A handy way for us to handle different button types. IE. firing commands for
* buttons, or initiation built in behavior.
@@ -279,6 +279,7 @@ class Viewer extends Component {
this.setState(updatedState);
}}
studies={this.props.studies}
/>
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
+15 -8
View File
@@ -1,6 +1,6 @@
/* Sizes */
:root {
/* Sizes */
--top-bar-height: 40px;
--top-bar-expanded-height: 160px;
--toolbar-height: 78px;
@@ -9,19 +9,26 @@
--right-sidepanel-menu-width: 323px;
--study-list-padding: 8%;
--study-list-padding-medium-screen: 10px;
}
--snackbar-size: 344px;
/* Transitions */
/* Transitions */
:root {
--transition-duration: 0.3s;
--transition-effect: ease;
--sidepanel-transition: all 0.3s ease;
}
/* Thicknesses */
/* Thicknesses */
:root {
--viewport-border-thickness: 1px;
--ui-border-thickness: 1px;
/** Colors **/
--snackbar-success: rgba(94, 164, 0, 0.9);
--snackbar-error: rgba(236, 61, 61, 0.9);
--snackbar-warning: rgba(235, 173, 23, 0.9);
--snackbar-info: rgba(54, 156, 199, 0.9);
/** z-Index **/
--snackbar-zIndex: 10;
}