From b01b0108b0375a2a582e019746ee37b98d14c3bb Mon Sep 17 00:00:00 2001 From: Danny Brown Date: Fri, 11 Oct 2019 12:03:59 -0400 Subject: [PATCH] refactor: layout manager to viewer (#1026) * docs: formatting * Prefer numRows and numColumns to percentage width/height for layout * fix: LayoutManager --> ViewportGrid (get out of UI component library) * docs: remove outdated docs * Don't expose ExampleDropTarget * Revert thumnail entry drag source removal * Update screaming tests * fix drag-n-drop * fix vtk mpr2d CC: @jamesapetts * remove setSingleLayoutData * remove vtk qualifier --- docs/latest/advanced/extensions.md | 19 +- extensions/vtk/src/commandsModule.js | 10 +- extensions/vtk/src/utils/setMPRLayout.js | 49 ++--- .../vtk/src/utils/setSingleLayoutData.js | 12 -- extensions/vtk/src/utils/setViewportToVTK.js | 21 +-- platform/core/src/classes/README.md | 167 ----------------- .../src/hanging-protocols/ProtocolEngine.js | 27 ++- platform/core/src/redux/actions.js | 35 +++- platform/core/src/redux/actions.test.js | 21 +-- platform/core/src/redux/reducers/viewports.js | 32 +++- .../core/src/redux/reducers/viewports.test.js | 37 ++-- .../ui/src/LayoutChooser/LayoutManager.js | 162 ----------------- .../LayoutChooser/LayoutPanelDropTarget.css | 12 -- .../LayoutChooser/LayoutPanelDropTarget.js | 74 -------- platform/ui/src/components/index.js | 7 +- .../{ => __docs__}/ExampleDropTarget.css | 0 .../{ => __docs__}/ExampleDropTarget.js | 0 .../__docs__/wrappedStudyBrowser.js | 3 +- .../ui/src/components/studyBrowser/index.js | 1 - platform/ui/src/index.js | 6 - .../ViewportGrid/ConnectedViewportGrid.js} | 18 +- .../ViewportGrid/DefaultViewport.js | 10 ++ .../components/ViewportGrid/EmptyViewport.css | 7 + .../components/ViewportGrid/EmptyViewport.js | 12 ++ .../components/ViewportGrid/ViewportGrid.css} | 13 -- .../components/ViewportGrid/ViewportGrid.js | 168 ++++++++++++++++++ .../components/ViewportGrid/ViewportPane.css | 10 ++ .../components/ViewportGrid/ViewportPane.js | 51 ++++++ .../src/components/ViewportGrid/index.js | 5 + .../ConnectedLayoutButton.js | 22 +-- .../ConnectedPluginSwitch.js | 38 ++-- .../src/connectedComponents/PluginSwitch.js | 4 +- .../src/connectedComponents/ViewerMain.js | 16 +- 33 files changed, 456 insertions(+), 613 deletions(-) delete mode 100644 extensions/vtk/src/utils/setSingleLayoutData.js delete mode 100644 platform/core/src/classes/README.md delete mode 100644 platform/ui/src/LayoutChooser/LayoutManager.js delete mode 100644 platform/ui/src/LayoutChooser/LayoutPanelDropTarget.css delete mode 100644 platform/ui/src/LayoutChooser/LayoutPanelDropTarget.js rename platform/ui/src/components/studyBrowser/{ => __docs__}/ExampleDropTarget.css (100%) rename platform/ui/src/components/studyBrowser/{ => __docs__}/ExampleDropTarget.js (100%) rename platform/viewer/src/{connectedComponents/ConnectedLayoutManager.js => components/ViewportGrid/ConnectedViewportGrid.js} (69%) create mode 100644 platform/viewer/src/components/ViewportGrid/DefaultViewport.js create mode 100644 platform/viewer/src/components/ViewportGrid/EmptyViewport.css create mode 100644 platform/viewer/src/components/ViewportGrid/EmptyViewport.js rename platform/{ui/src/LayoutChooser/LayoutManager.css => viewer/src/components/ViewportGrid/ViewportGrid.css} (55%) create mode 100644 platform/viewer/src/components/ViewportGrid/ViewportGrid.js create mode 100644 platform/viewer/src/components/ViewportGrid/ViewportPane.css create mode 100644 platform/viewer/src/components/ViewportGrid/ViewportPane.js create mode 100644 platform/viewer/src/components/ViewportGrid/index.js diff --git a/docs/latest/advanced/extensions.md b/docs/latest/advanced/extensions.md index d9e1718e4..e83ffad76 100644 --- a/docs/latest/advanced/extensions.md +++ b/docs/latest/advanced/extensions.md @@ -140,8 +140,8 @@ children: PropTypes.node, customProps: PropTypes.object ``` -Viewport components are managed by the `LayoutManager`. Which Viewport component -is used depends on: +Viewport components are managed by the `ViewportGrid` Component. Which Viewport +component is used depends on: - The Layout Configuration - Registered SopClassHandlers @@ -159,8 +159,8 @@ For a complete example implementation, An extension can register a Toolbar Module by providing a `getToolbarModule()` method that returns a React Component. The component does not receive any props. If you want to modify or react to state, you will need to connect to the redux -store. -The given toolbar must determine its set of elements and the context of them. The set of elements will be listed on toolbar `definitions`. +store. The given toolbar must determine its set of elements and the context of +them. The set of elements will be listed on toolbar `definitions`. ![Toolbar Extension](../assets/img/extensions-toolbar.gif) @@ -173,7 +173,11 @@ For a complete example implementation, ##### Toolbar Custom Component -Toolbar elements can define its own custom react component to be consumed when rendering it. So far, it accepts `Functional` and `Class` Components. For that, you just need to expose your `CustomToolbarComponent` as the value of key `CustomComponent`. In case the property `CustomComponent` is not present, a default toolbar component will be used to render it. See bellow +Toolbar elements can define its own custom react component to be consumed when +rendering it. So far, it accepts `Functional` and `Class` Components. For that, +you just need to expose your `CustomToolbarComponent` as the value of key +`CustomComponent`. In case the property `CustomComponent` is not present, a +default toolbar component will be used to render it. See bellow ```js definitions: [ @@ -188,8 +192,11 @@ definitions: [ ] ``` + `CustomComponent` components will receive the following props: -- parentContext: parent context. (In most of the cases it will be a ToolbarRow instance) + +- parentContext: parent context. (In most of the cases it will be a ToolbarRow + instance) - toolbarClickCallback: callback method when clicking on toolbar - button: its own definition object - key: react key prop diff --git a/extensions/vtk/src/commandsModule.js b/extensions/vtk/src/commandsModule.js index d0c18f3d4..a44e33b8e 100644 --- a/extensions/vtk/src/commandsModule.js +++ b/extensions/vtk/src/commandsModule.js @@ -66,7 +66,13 @@ function getCrosshairCallbackForIndex(index) { } async function _getActiveViewportVTKApi(viewports) { - const { layout, viewportSpecificData, activeViewportIndex } = viewports; + const { + numRows, + numColumns, + layout, + viewportSpecificData, + activeViewportIndex, + } = viewports; const currentData = layout.viewports[activeViewportIndex]; if (currentData && currentData.plugin === 'vtk') { @@ -84,6 +90,8 @@ async function _getActiveViewportVTKApi(viewports) { api = await setViewportToVTK( displaySet, activeViewportIndex, + numRows, + numColumns, layout, viewportSpecificData ); diff --git a/extensions/vtk/src/utils/setMPRLayout.js b/extensions/vtk/src/utils/setMPRLayout.js index 6de1023f4..4e14ba799 100644 --- a/extensions/vtk/src/utils/setMPRLayout.js +++ b/extensions/vtk/src/utils/setMPRLayout.js @@ -1,60 +1,43 @@ import setLayoutAndViewportData from './setLayoutAndViewportData.js'; -import setSingleLayoutData from './setSingleLayoutData.js'; export default function setMPRLayout(displaySet) { return new Promise((resolve, reject) => { - let viewports = []; - const rows = 1; - const columns = 3; - const numViewports = rows * columns; + const viewports = []; + const numRows = 1; + const numColumns = 3; + const numViewports = numRows * numColumns; const viewportSpecificData = {}; - for (let i = 0; i < numViewports; i++) { - viewports.push({ - height: `${100 / rows}%`, - width: `${100 / columns}%`, - }); + for (let i = 0; i < numViewports; i++) { + viewports.push({}); viewportSpecificData[i] = displaySet; viewportSpecificData[i].plugin = 'vtk'; } - const layout = { - viewports, - }; - - const viewportIndices = [0, 1, 2]; - let updatedViewports = layout.viewports; const apis = []; - viewportIndices.forEach(viewportIndex => { - apis[viewportIndex] = null; - /*const currentData = layout.viewports[viewportIndex]; - if (currentData && currentData.plugin === 'vtk') { - reject(new Error('Should not have reached this point??')); - }*/ - - const data = { + viewports.forEach((viewport, index) => { + apis[index] = null; + viewports[index] = Object.assign({}, viewports[index], { // plugin: 'vtk', vtk: { mode: 'mpr', // TODO: not used afterCreation: api => { - apis[viewportIndex] = api; + apis[index] = api; if (apis.every(a => !!a)) { resolve(apis); } }, }, - }; - - updatedViewports = setSingleLayoutData( - updatedViewports, - viewportIndex, - data - ); + }); }); setLayoutAndViewportData( - { viewports: updatedViewports }, + { + numRows, + numColumns, + viewports, + }, viewportSpecificData ); }); diff --git a/extensions/vtk/src/utils/setSingleLayoutData.js b/extensions/vtk/src/utils/setSingleLayoutData.js deleted file mode 100644 index 22ea7e9ef..000000000 --- a/extensions/vtk/src/utils/setSingleLayoutData.js +++ /dev/null @@ -1,12 +0,0 @@ -export default function setSingleLayoutData( - originalArray, - viewportIndex, - data -) { - const viewports = originalArray.slice(); - const layoutData = Object.assign({}, viewports[viewportIndex], data); - - viewports[viewportIndex] = layoutData; - - return viewports; -} diff --git a/extensions/vtk/src/utils/setViewportToVTK.js b/extensions/vtk/src/utils/setViewportToVTK.js index aa3dfb679..babcc8f1a 100644 --- a/extensions/vtk/src/utils/setViewportToVTK.js +++ b/extensions/vtk/src/utils/setViewportToVTK.js @@ -1,9 +1,10 @@ import setLayoutAndViewportData from './setLayoutAndViewportData.js'; -import setSingleLayoutData from './setSingleLayoutData.js'; export default function setViewportToVTK( displaySet, viewportIndex, + numRows, + numColumns, layout, viewportSpecificData ) { @@ -13,7 +14,9 @@ export default function setViewportToVTK( reject(new Error('Should not have reached this point??')); }*/ - const data = { + const viewports = layout.viewports.slice(); + + viewports[viewportIndex] = Object.assign({}, viewports[viewportIndex], { // plugin: 'vtk', vtk: { mode: 'mpr', // TODO: not used @@ -21,18 +24,16 @@ export default function setViewportToVTK( resolve(api); }, }, - }; - - const updatedViewports = setSingleLayoutData( - layout.viewports, - viewportIndex, - data - ); + }); const updatedViewportData = viewportSpecificData; setLayoutAndViewportData( - { viewports: updatedViewports }, + { + numRows, + numColumns, + viewports, + }, updatedViewportData ); }); diff --git a/platform/core/src/classes/README.md b/platform/core/src/classes/README.md deleted file mode 100644 index a93a1ea60..000000000 --- a/platform/core/src/classes/README.md +++ /dev/null @@ -1,167 +0,0 @@ -# Table of contents -In this document, some important objects are described. In the files there are comments that can help better undestand their methods and properties. - - [ResizeViewportManager object](#the-resize-viewport-manager-object) - - [ImageSet object](#the-image-set-object) - - [Layout Manager](#the-layout-manager-object) - - [Type Safe Collections](#the-type-safe-collections) - -# The Resize Viewport Manager object -This object has multiple functions to manage window resize event. It relocates Dialogs, resizes viewport elements and scrollbars and some other UI components such as Study and Series Quick Switch, when available. - -## Usage -It's only necessary to bind **handleResize** function to the window resize event as follows. The **ohif:viewerbase** package needs to be imported by the referring code as well. -```javascript -import { Viewerbase } from 'meteor/ohif:viewerbase'; - -const ResizeViewportManager = new Viewerbase.ResizeViewportManager(); -window.addEventListener('resize', ResizeViewportManager.getResizeHandler()); -``` -An example os its usage can be found in **ohif-viewerbase/client/components/viewer/viewerMain/viewerMain.js**. - -# The Image Set object -An object that represents a list of images that are associated by any arbitrary criteria being thus content agnostic. Besides the main attributes (**images** and **uid**) it allows additional attributes to be appended to it (currently indiscriminately, but this should be changed). - -## Usage -ImageSet constructor requires an array of SOP instances like in the example below. It's necessary to import **ohif:viewerbase**. - -```javascript -import { Viewerbase } from 'meteor/ohif:viewerbase'; - -const imageSet = new Viewerbase.ImageSet(sopInstances); - -imageSet.setAttributes({ - displaySetInstanceUid: imageSet.uid, - seriesInstanceUid: seriesData.seriesInstanceUid, - seriesNumber: seriesData.seriesNumber, - seriesDescription: seriesData.seriesDescription, - numImageFrames: instances.length, - frameRate: instance.getRawValue('x00181063'), - modality: seriesData.modality, - isMultiFrame: isMultiFrame(instance) -}); - -// Sort instances by InstanceNumber (0020,0013) -imageSet.sortBy((a, b) => { - return (parseInt(a.getRawValue('x00200013', 0)) || 0) - (parseInt(b.getRawValue('x00200013', 0)) || 0); -}); -``` -Each SOP instance in this example is an instance of **OHIFInstanceMetadata** object, which is a specialization of **InstanceMetadata**. To read more about the **Metadata API** click [here](metadata/). - -# The Layout Manager object -Objects of this class are responsible for creating, organizing and maintaining (manage) viewport rendering. It creates a grid, positioning viewports accordingly to it's configuration keeping all viewports data (in **viewportData** property) for easy access from other components. It support many layout configurations and some of them were fully tested: 1x1, 1x2, 1x3, 2x1, 2x2, 2x3, 3x1, 3x2, 3x3. Other configurations may work as well. -Finally it provides some useful functions to move through viewports and zoom it. - -## Usage -In order to use _LayoutManager_ the **ohif:viewerbase** package needs to be imported by the referring code and instantiated as follows. An example os its usage is in **ohif-viewerbase/client/components/viewer/viewerMain/viewerMain.js**. - -```javascript -import { Viewerbase } from 'meteor/ohif:viewerbase'; - -// Get an array of studies object. This function needs to be implemented, it does not exist. -const studies = getArrayOfStudiesObjects(); -const parentElement = document.getElementById('layoutManagerTarget'); -const LayoutManager = new Viewerbase.LayoutManager(parentElement, studies); -``` - -The default configuration is 1x1, and to change it just set **layoutProps** and call **updateViewports** to update the layout as follows. - -```javascript -import { Viewerbase } from 'meteor/ohif:viewerbase'; - -// Get an array of studies object. This function needs to be implemented, it does not exist. -const studies = getArrayOfStudiesObjects(); -const parentElement = document.getElementById('layoutManagerTarget'); -const LayoutManager = new LayoutManager(parentElement, studies); - -// Set the layout proprerties to 2x2 layout -LayoutManager.layoutProps = { - rows: 2, - columns: 2 -}; - -// It will render four viewports: two in each row. -LayoutManager.updateViewports(); -``` - -The layoutManagerTarget element will have a new class **layout-2-2** (to allow further styling) and it's inner content will a new div#imageViewerViewports that has four inner elements like the following (some elements and attributes were removed for example purpose): -```html -
-
-
- -
-
-
-
-
-
-
-``` - -Each of this _div.viewportContainer_ will have some classes to help CSS specific styling accordingly to the element's position in the grid: **top**, **middle** and **bottom**. This classes are added by **viewer/components/gridLayout/** component in ohif-viewerbase package. - -# The Type Safe Collections - -With the introduction of the new _Study Metadata API_ in which study metadata is represented by class hierarchies (using prototype-based inheritance), the usage of standard _Minimongo_ collections as a central client-side storage for this data became no longer an option. Standard _Mongo_ and _Minimongo_ collections internally _flatten_ data (in other words, data gets serialized) before storage hence no functions or prototype chains are preserved. In that scenario, when an object is restored (fetched), what is returned is actually a flattened copy of the original object with no functions or prototype (it's no longer an instance of it's original class). As an attempt to overcome this limitation a new type of collection was intruduced: the *TypeSafeCollection*. - -The `TypeSafeCollection` is a simple list-like collection which tries to implement an API _similar_ but not compatible with _Mongo_'s API. It supports basic features like search by attribute map and ID, retrieval by index, sorting of result sets, insertion, removal and reactive operations but, unlike _Mongo_'s API, it (still) lacks support to advanced functionality like complex search criterea or flexible sorting options. - -## Implementation - -The `TypeSafeCollection` is implemented on top of the _JavaScript_ `Array` object. Each element inserted in the collection is appended to the end of its internal array as a _key-value pair (KVP)_ object where the _key_ is a unique randomly generated ID string and the _value_ is the element itself. Once the object has been successfully stored, the generated ID (its ID) is returned to the client code and can later be used to access that specific element. At this point, an important difference to the _Minimongo_ API can be highlighted: a _TypeSafeCollection_ instance will never make any changes to the stored element (e.g., no "\_id" property will ever be assigned to the original object). Another relevant feature that is supported by this design decision is that _not only objects_ can be stored in this collections, but literally _anything_. - -Inside the codebase, the _value_ attribute of each _KVP_ entry in the collection is refered to as _the **payload** of the entry_ since it's what really matters to the user. Hence, this term will also be used here to refer to the _value that has been stored in the collection_. That being said, we can approach another important feature of these collections: A single _payload_ cannot be stored more than once in a given collection. When an attempt of inserting a _payload_ which is already present in the collection is detected, the insert operation will fail and `null` will be returned. In that regard, the collection behaves like `Set` object not permitting a payload to be stored more than once. Strict equality is used when comparing payloads, thus cloned objects are not considered the same. This feature adds an additional garantee that a given study/series/instance will not be listed more than once (it was designed as a replacement for central study collections which were always checked for duplicates). - -Please refer to the codebase for the full `TypeSafeCollection` API. - -## Usage - -In order to use the `TypeSafeCollection` class, the **ohif:viewerbase** package needs to be imported by the referring code and instantiated as follows: - -```javascript -import { Viewerbase } from 'meteor/ohif:viewerbase'; // i.e., Viewerbase.TypeSafeCollection -OR - -import 'meteor/ohif:viewerbase'; // i.e., OHIF.viewerbase.TypeSafeCollection -// The later is preferred when the client code already makes use of the "OHIF" namespace making the second -// "import" a garantee that the ".viewerbase" namespace has been properly loaded. -``` - -A few usage examples: - -```javascript - -const Users = new OHIF.viewerbase.TypeSafeCollection(); - -[[ ... ]] - -// Insert a User object... -let userId = Users.insert({ - data: { - firstName: 'John', - lastName: 'Doe', - age: 45 - }, - getFullName() { - return `${this.data.firstName} ${this.data.lastName}`; - }, - getAge() { - return this.data.age; - } -}); - -[[ ... ]] - -let theUserWeJustStored = Users.findById(userId); // ;-) - -[[ ... ]] - -// Retrieve a single user with "Doe" as `lastName`... -let myUser = Users.findBy({ 'data.lastName': 'Doe' }); -// Or all users with "Doe" as `lastName`, sorted by `firstName` in ascending -// order and using the `age` attribute to break ties in descending order... -let myUsers = Users.findAllBy({ 'data.lastName': 'Doe' }, { - sort: [ [ 'data.firstName', 'asc' ], [ 'data.age', 'desc' ] ] -}); - -``` diff --git a/platform/core/src/hanging-protocols/ProtocolEngine.js b/platform/core/src/hanging-protocols/ProtocolEngine.js index 3172678dc..ea32b2f1e 100644 --- a/platform/core/src/hanging-protocols/ProtocolEngine.js +++ b/platform/core/src/hanging-protocols/ProtocolEngine.js @@ -504,12 +504,12 @@ export default class ProtocolEngine { /** * Sets the current layout * - * @param rows - * @param columns + * @param {number} numRows + * @param {number} numColumns */ - setLayout(rows, columns) { - if (rows < 1 && columns < 1) { - log.error(`Invalid layout ${rows} x ${columns}`); + setLayout(numRows, numColumns) { + if (numRows < 1 && numColumns < 1) { + log.error(`Invalid layout ${numRows} x ${numColumns}`); return; } @@ -519,16 +519,13 @@ export default class ProtocolEngine { } let viewports = []; - const numViewports = rows * columns; + const numViewports = numRows * numColumns; for (let i = 0; i < numViewports; i++) { - viewports.push({ - height: `${100 / rows}%`, - width: `${100 / columns}%`, - }); + viewports.push({}); } - this.options.setLayout({ viewports }); + this.options.setLayout({ numRows, numColumns, viewports }); } /** @@ -634,14 +631,10 @@ export default class ProtocolEngine { //console.log('renderedCallback for ' + element.id); customSettings.forEach(customSetting => { log.trace( - `ProtocolEngine::currentViewportData.renderedCallback Applying custom setting: ${ - customSetting.id - }` + `ProtocolEngine::currentViewportData.renderedCallback Applying custom setting: ${customSetting.id}` ); log.trace( - `ProtocolEngine::currentViewportData.renderedCallback with value: ${ - customSetting.value - }` + `ProtocolEngine::currentViewportData.renderedCallback with value: ${customSetting.value}` ); const setting = CustomViewportSettings[customSetting.id]; diff --git a/platform/core/src/redux/actions.js b/platform/core/src/redux/actions.js index 323fefb8e..5b4b008d3 100644 --- a/platform/core/src/redux/actions.js +++ b/platform/core/src/redux/actions.js @@ -26,9 +26,34 @@ export const setViewportActive = viewportIndex => ({ viewportIndex, }); -export const setLayout = layout => ({ +/** + * @param {object} layout + * @param {number} layout.numRows + * @param {number} layout.numColumns + * @param {array} layout.viewports + */ +export const setLayout = ({ numRows, numColumns, viewports }) => ({ type: SET_VIEWPORT_LAYOUT, - layout, + numRows, + numColumns, + viewports, +}); + +/** + * @param {object} layout + * @param {number} layout.numRows + * @param {number} layout.numColumns + * @param {array} layout.viewports + */ +export const setViewportLayoutAndData = ( + { numRows, numColumns, viewports }, + viewportSpecificData +) => ({ + type: SET_VIEWPORT_LAYOUT_AND_DATA, + numRows, + numColumns, + viewports, + viewportSpecificData, }); export const clearViewportSpecificData = viewportIndex => ({ @@ -87,12 +112,6 @@ export const setServers = servers => ({ servers, }); -export const setViewportLayoutAndData = (layout, viewportSpecificData) => ({ - type: SET_VIEWPORT_LAYOUT_AND_DATA, - layout, - viewportSpecificData, -}); - const actions = { // VIEWPORT setViewportActive, diff --git a/platform/core/src/redux/actions.test.js b/platform/core/src/redux/actions.test.js index 2613586bd..581d5cdc8 100644 --- a/platform/core/src/redux/actions.test.js +++ b/platform/core/src/redux/actions.test.js @@ -94,19 +94,20 @@ describe('actions', () => { }); it('should create an action to set the viewport layout', () => { - const layout = { - viewports: [ - { - height: '100%', - width: '100%', - }, - ], - }; + const numRows = 1; + const numColumns = 2; + const viewports = [{ plugin: 'vtk' }, { plugin: 'vtk' }]; + const expectedAction = { type: types.SET_VIEWPORT_LAYOUT, - layout, + numRows, + numColumns, + viewports, }; - expect(actions.setLayout(layout)).toEqual(expectedAction); + + expect(actions.setLayout({ numRows, numColumns, viewports })).toEqual( + expectedAction + ); }); }); }); diff --git a/platform/core/src/redux/reducers/viewports.js b/platform/core/src/redux/reducers/viewports.js index be0cc2313..e08166bf6 100644 --- a/platform/core/src/redux/reducers/viewports.js +++ b/platform/core/src/redux/reducers/viewports.js @@ -12,13 +12,13 @@ import cloneDeep from 'lodash.clonedeep'; import merge from 'lodash.merge'; const defaultState = { + numRows: 1, + numColumns: 1, activeViewportIndex: 0, layout: { viewports: [ { // plugin: 'cornerstone', - height: '100%', - width: '100%', }, ], }, @@ -38,17 +38,31 @@ const viewports = (state = defaultState, action) => { let viewportSpecificData; let useActiveViewport = false; switch (action.type) { - case SET_VIEWPORT_LAYOUT_AND_DATA: - return Object.assign({}, state, { - viewportSpecificData: action.viewportSpecificData, - layout: action.layout, - }); case SET_VIEWPORT_ACTIVE: return Object.assign({}, state, { activeViewportIndex: action.viewportIndex, }); - case SET_VIEWPORT_LAYOUT: - return Object.assign({}, state, { layout: action.layout }); + case SET_VIEWPORT_LAYOUT: { + const { numRows, numColumns, viewports } = action; + const layout = { + viewports: [...viewports], + }; + + return Object.assign({}, state, { numRows, numColumns, layout }); + } + case SET_VIEWPORT_LAYOUT_AND_DATA: { + const { numRows, numColumns, viewports, viewportSpecificData } = action; + const layout = { + viewports: [...viewports], + }; + + return Object.assign({}, state, { + numRows, + numColumns, + layout, + viewportSpecificData: cloneDeep(viewportSpecificData), + }); + } case SET_VIEWPORT: { const layout = cloneDeep(state.layout); const hasPlugin = action.data && action.data.plugin; diff --git a/platform/core/src/redux/reducers/viewports.test.js b/platform/core/src/redux/reducers/viewports.test.js index fd949e20e..57ad1687c 100644 --- a/platform/core/src/redux/reducers/viewports.test.js +++ b/platform/core/src/redux/reducers/viewports.test.js @@ -6,13 +6,10 @@ describe('viewports reducer', () => { it('should return the initial state', () => { expect(reducer(undefined, {})).toEqual({ activeViewportIndex: 0, + numRows: 1, + numColumns: 1, layout: { - viewports: [ - { - height: '100%', - width: '100%', - }, - ], + viewports: [{}], }, viewportSpecificData: {}, }); @@ -34,23 +31,25 @@ describe('viewports reducer', () => { it('should handle SET_VIEWPORT_LAYOUT', () => { const setViewportLayoutAction = { type: types.SET_VIEWPORT_LAYOUT, - layout: { - viewports: [ - { - height: '100%', - width: '50%', - }, - { - height: '100%', - width: '50%', - }, - ], - }, + numRows: 1, + numColumns: 2, + viewports: [ + { + plugin: 'cornerstone', + }, + { + plugin: 'vtk', + }, + ], }; const updatedState = reducer({}, setViewportLayoutAction); - expect(updatedState.layout).toEqual(setViewportLayoutAction.layout); + expect(updatedState.numRows).toEqual(setViewportLayoutAction.numRows); + expect(updatedState.numColumns).toEqual(setViewportLayoutAction.numColumns); + expect(updatedState.layout.viewports).toEqual( + setViewportLayoutAction.viewports + ); }); // If there were previous keys, this would have diff --git a/platform/ui/src/LayoutChooser/LayoutManager.js b/platform/ui/src/LayoutChooser/LayoutManager.js deleted file mode 100644 index 6ec9f65f0..000000000 --- a/platform/ui/src/LayoutChooser/LayoutManager.js +++ /dev/null @@ -1,162 +0,0 @@ -import './LayoutManager.css'; - -import React, { Component } from 'react'; - -import LayoutPanelDropTarget from './LayoutPanelDropTarget.js'; -import PropTypes from 'prop-types'; - -function defaultViewportPlugin(props) { - return
{JSON.stringify(props)}
; -} - -function EmptyViewport() { - return ( -
-

Please drag a stack here to view images.

-
- ); -} - -export class LayoutManager extends Component { - static className = 'LayoutManager'; - static defaultProps = { - viewportData: [], - layout: { - viewports: [ - { - top: 0, - left: 0, - height: '100%', - width: '100%', - }, - ], - }, - activeViewportIndex: 0, - supportsDragAndDrop: true, - availablePlugins: { - defaultViewportPlugin, - }, - defaultPlugin: 'defaultViewportPlugin', - }; - - static propTypes = { - viewportData: PropTypes.array.isRequired, - supportsDragAndDrop: PropTypes.bool.isRequired, - activeViewportIndex: PropTypes.number.isRequired, - layout: PropTypes.object.isRequired, - availablePlugins: PropTypes.object.isRequired, - setViewportData: PropTypes.func, - studies: PropTypes.array, - children: PropTypes.node, - }; - - onDrop = ({ viewportIndex, item }) => { - if (this.props.setViewportData) { - this.props.setViewportData({ viewportIndex, item }); - } - }; - - getPluginComponent = plugin => { - const pluginComponent = this.props.availablePlugins[ - plugin || this.props.defaultPlugin - ]; - - if (!pluginComponent) { - throw new Error( - `No Viewport Plugin available for plugin ${plugin}. Available plugins: ${JSON.stringify( - this.props.availablePlugins - )}` - ); - } - - return pluginComponent; - }; - - getChildComponent(plugin, data, viewportIndex, children) { - if (data.displaySet) { - const PluginComponent = this.getPluginComponent(plugin); - - return ( - - ); - } - - return ; - } - - getContent(childComponent, supportsDragAndDrop, viewportIndex) { - if (supportsDragAndDrop) { - return ( - - {childComponent} - - ); - } - - return
{childComponent}
; - } - - render() { - if (!this.props.viewportData.length) { - return ''; - } - - const { supportsDragAndDrop, studies, viewportData } = this.props; - const viewports = this.props.layout.viewports; - const viewportElements = viewports.map((layout, viewportIndex) => { - const displaySet = viewportData[viewportIndex]; - const data = { - displaySet, - studies, - }; - - // Use whichever plugin is currently in use in the panel - // unless nothing is specified. If nothing is specified - // and the display set has a plugin specified, use that. - // - // TODO: Change this logic to: - // - Plugins define how capable they are of displaying a SopClass - // - When updating a panel, ensure that the currently enabled plugin - // in the viewport is capable of rendering this display set. If not - // then use the most capable available plugin - let plugin = layout.plugin; - if (!layout.plugin && displaySet && displaySet.plugin) { - plugin = displaySet.plugin; - } - - const childComponent = this.getChildComponent( - plugin, - data, - viewportIndex, - this.props.children - ); - const content = this.getContent( - childComponent, - supportsDragAndDrop, - viewportIndex - ); - - let className = 'viewport-container'; - if (this.props.activeViewportIndex === viewportIndex) { - className += ' active'; - } - - return ( -
- {content} -
- ); - }); - - return
{viewportElements}
; - } -} - -export default LayoutManager; diff --git a/platform/ui/src/LayoutChooser/LayoutPanelDropTarget.css b/platform/ui/src/LayoutChooser/LayoutPanelDropTarget.css deleted file mode 100644 index 7c629feaf..000000000 --- a/platform/ui/src/LayoutChooser/LayoutPanelDropTarget.css +++ /dev/null @@ -1,12 +0,0 @@ -.LayoutPanelDropTarget { - width: 100%; - height: 100%; - - opacity: 1; - transition: 0.3s all ease; -} - -.LayoutPanelDropTarget.hovered { - opacity: 0.5; - cursor: copy; -} diff --git a/platform/ui/src/LayoutChooser/LayoutPanelDropTarget.js b/platform/ui/src/LayoutChooser/LayoutPanelDropTarget.js deleted file mode 100644 index 96355b519..000000000 --- a/platform/ui/src/LayoutChooser/LayoutPanelDropTarget.js +++ /dev/null @@ -1,74 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { DropTarget } from 'react-dnd'; -import './LayoutPanelDropTarget.css'; - -// Drag sources and drop targets only interact -// if they have the same string type. -const Types = { - THUMBNAIL: 'thumbnail', -}; - -const divTarget = { - drop(props, monitor, component) { - const item = monitor.getItem(); - - if (props.onDrop) { - props.onDrop({ - viewportIndex: props.viewportIndex, - item, - }); - } - - return { - id: `LayoutPanelDropTarget-${props.viewportIndex}`, - viewportIndex: props.viewportIndex, - item, - }; - }, -}; - -// TODO: Find out why we can't move this into the Example app instead. -// It looks like the context isn't properly shared. -class LayoutPanelDropTarget extends Component { - static className = 'LayoutPanelDropTarget'; - - static defaultProps = { - isOver: false, - canDrop: false, - }; - - static propTypes = { - connectDropTarget: PropTypes.func.isRequired, - canDrop: PropTypes.bool.isRequired, - isOver: PropTypes.bool.isRequired, - viewportComponent: PropTypes.object, - }; - - render() { - const { canDrop, isOver, connectDropTarget } = this.props; - const isActive = canDrop && isOver; - - let className = LayoutPanelDropTarget.className; - - if (isActive) { - className += ' hovered'; - } else if (canDrop) { - className += ' can-drop'; - } - - return connectDropTarget( -
{this.props.children}
- ); - } -} - -const collect = (connect, monitor) => ({ - connectDropTarget: connect.dropTarget(), - canDrop: monitor.canDrop(), - isOver: monitor.isOver(), -}); - -export default DropTarget(Types.THUMBNAIL, divTarget, collect)( - LayoutPanelDropTarget -); diff --git a/platform/ui/src/components/index.js b/platform/ui/src/components/index.js index 34b3d421e..609026ba5 100644 --- a/platform/ui/src/components/index.js +++ b/platform/ui/src/components/index.js @@ -1,8 +1,4 @@ -import { - ExampleDropTarget, - StudyBrowser, - ThumbnailEntry, -} from './studyBrowser'; +import { StudyBrowser, ThumbnailEntry } from './studyBrowser'; import { LayoutButton, LayoutChooser } from './layoutButton'; import { MeasurementTable, MeasurementTableItem } from './measurementTable'; import { Overlay, OverlayTrigger } from './overlayTrigger'; @@ -26,7 +22,6 @@ import { Tooltip } from './tooltip'; export { Checkbox, CineDialog, - ExampleDropTarget, LayoutButton, LayoutChooser, MeasurementTable, diff --git a/platform/ui/src/components/studyBrowser/ExampleDropTarget.css b/platform/ui/src/components/studyBrowser/__docs__/ExampleDropTarget.css similarity index 100% rename from platform/ui/src/components/studyBrowser/ExampleDropTarget.css rename to platform/ui/src/components/studyBrowser/__docs__/ExampleDropTarget.css diff --git a/platform/ui/src/components/studyBrowser/ExampleDropTarget.js b/platform/ui/src/components/studyBrowser/__docs__/ExampleDropTarget.js similarity index 100% rename from platform/ui/src/components/studyBrowser/ExampleDropTarget.js rename to platform/ui/src/components/studyBrowser/__docs__/ExampleDropTarget.js diff --git a/platform/ui/src/components/studyBrowser/__docs__/wrappedStudyBrowser.js b/platform/ui/src/components/studyBrowser/__docs__/wrappedStudyBrowser.js index 9d2c29037..d7fa76f54 100644 --- a/platform/ui/src/components/studyBrowser/__docs__/wrappedStudyBrowser.js +++ b/platform/ui/src/components/studyBrowser/__docs__/wrappedStudyBrowser.js @@ -8,7 +8,8 @@ import { onThumbnailClick, onThumbnailDoubleClick, } from './exampleStudies.js'; -import { ExampleDropTarget, StudyBrowser } from './../index.js'; +import ExampleDropTarget from './ExampleDropTarget.js'; +import { StudyBrowser } from './../index.js'; class StudyBrowserContainer extends Component { render() { diff --git a/platform/ui/src/components/studyBrowser/index.js b/platform/ui/src/components/studyBrowser/index.js index 9ed1c3194..cf0d9d68a 100644 --- a/platform/ui/src/components/studyBrowser/index.js +++ b/platform/ui/src/components/studyBrowser/index.js @@ -1,3 +1,2 @@ -export { ExampleDropTarget } from './ExampleDropTarget.js'; export { StudyBrowser } from './StudyBrowser.js'; export { ThumbnailEntry } from './ThumbnailEntry.js'; diff --git a/platform/ui/src/index.js b/platform/ui/src/index.js index ba4d32550..357d18c61 100644 --- a/platform/ui/src/index.js +++ b/platform/ui/src/index.js @@ -1,7 +1,6 @@ import { Checkbox, CineDialog, - ExampleDropTarget, LayoutButton, LayoutChooser, MeasurementTable, @@ -28,8 +27,6 @@ import { ICONS, Icon } from './elements'; // Alias this for now as not all dependents are using strict versioning import { DropdownMenu as Dropdown, Range, Select } from './elements/form'; import ExpandableToolMenu from './viewer/ExpandableToolMenu.js'; -import LayoutManager from './LayoutChooser/LayoutManager.js'; -import LayoutPanelDropTarget from './LayoutChooser/LayoutPanelDropTarget.js'; import PlayClipButton from './viewer/PlayClipButton.js'; import { ScrollableArea } from './ScrollableArea/ScrollableArea.js'; import Toolbar from './viewer/Toolbar.js'; @@ -43,12 +40,9 @@ export { CineDialog, Dropdown, ExpandableToolMenu, - ExampleDropTarget, Icon, LayoutButton, LayoutChooser, - LayoutManager, - LayoutPanelDropTarget, MeasurementTable, MeasurementTableItem, Overlay, diff --git a/platform/viewer/src/connectedComponents/ConnectedLayoutManager.js b/platform/viewer/src/components/ViewportGrid/ConnectedViewportGrid.js similarity index 69% rename from platform/viewer/src/connectedComponents/ConnectedLayoutManager.js rename to platform/viewer/src/components/ViewportGrid/ConnectedViewportGrid.js index 611b0f703..774b10401 100644 --- a/platform/viewer/src/connectedComponents/ConnectedLayoutManager.js +++ b/platform/viewer/src/components/ViewportGrid/ConnectedViewportGrid.js @@ -1,7 +1,7 @@ -import { LayoutManager } from '@ohif/ui'; +import ViewportGrid from './ViewportGrid.js'; import { MODULE_TYPES } from '@ohif/core'; import { connect } from 'react-redux'; -import { extensionManager } from './../App.js'; +import { extensionManager } from './../../App.js'; const mapStateToProps = state => { const availableViewportModules = {}; @@ -18,9 +18,13 @@ const mapStateToProps = state => { defaultPlugin = viewportModules[0].extensionId; } + const { numRows, numColumns, layout, activeViewportIndex } = state.viewports; + return { - layout: state.viewports.layout, - activeViewportIndex: state.viewports.activeViewportIndex, + numRows, + numColumns, + layout, + activeViewportIndex, // TODO: rename `availableViewportModules` availablePlugins: availableViewportModules, // TODO: rename `defaultViewportModule` @@ -28,9 +32,9 @@ const mapStateToProps = state => { }; }; -const ConnectedLayoutManager = connect( +const ConnectedViewportGrid = connect( mapStateToProps, null -)(LayoutManager); +)(ViewportGrid); -export default ConnectedLayoutManager; +export default ConnectedViewportGrid; diff --git a/platform/viewer/src/components/ViewportGrid/DefaultViewport.js b/platform/viewer/src/components/ViewportGrid/DefaultViewport.js new file mode 100644 index 000000000..3b9470c20 --- /dev/null +++ b/platform/viewer/src/components/ViewportGrid/DefaultViewport.js @@ -0,0 +1,10 @@ +/** + * + * + * @export + * @param {*} props + * @returns + */ +export default function DefaultViewport(props) { + return
{JSON.stringify(props)}
; +} diff --git a/platform/viewer/src/components/ViewportGrid/EmptyViewport.css b/platform/viewer/src/components/ViewportGrid/EmptyViewport.css new file mode 100644 index 000000000..cf3be0940 --- /dev/null +++ b/platform/viewer/src/components/ViewportGrid/EmptyViewport.css @@ -0,0 +1,7 @@ +.empty-viewport { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + color: var(--text-secondary-color); +} diff --git a/platform/viewer/src/components/ViewportGrid/EmptyViewport.js b/platform/viewer/src/components/ViewportGrid/EmptyViewport.js new file mode 100644 index 000000000..e4b9c1258 --- /dev/null +++ b/platform/viewer/src/components/ViewportGrid/EmptyViewport.js @@ -0,0 +1,12 @@ +/** + * + * + * @returns + */ +function EmptyViewport() { + return ( +
+

Please drag a stack here to view images.

+
+ ); +} diff --git a/platform/ui/src/LayoutChooser/LayoutManager.css b/platform/viewer/src/components/ViewportGrid/ViewportGrid.css similarity index 55% rename from platform/ui/src/LayoutChooser/LayoutManager.css rename to platform/viewer/src/components/ViewportGrid/ViewportGrid.css index 0b568e259..9fdf80029 100644 --- a/platform/ui/src/LayoutChooser/LayoutManager.css +++ b/platform/viewer/src/components/ViewportGrid/ViewportGrid.css @@ -7,16 +7,3 @@ .viewport-container.active { border: var(--viewport-border-thickness) solid var(--active-color); } - -.EmptyViewport { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - color: var(--text-secondary-color); -} - -.LayoutPanel { - width: 100%; - height: 100%; -} diff --git a/platform/viewer/src/components/ViewportGrid/ViewportGrid.js b/platform/viewer/src/components/ViewportGrid/ViewportGrid.js new file mode 100644 index 000000000..66f874eca --- /dev/null +++ b/platform/viewer/src/components/ViewportGrid/ViewportGrid.js @@ -0,0 +1,168 @@ +import './ViewportGrid.css'; + +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +// +import ViewportPane from './ViewportPane.js'; +import DefaultViewport from './DefaultViewport.js'; +import EmptyViewport from './EmptyViewport.js'; + +const ViewportGrid = function(props) { + const { + activeViewportIndex, + availablePlugins, + defaultPlugin: defaultPluginName, + layout, + numRows, + numColumns, + setViewportData, + studies, + viewportData, + children, + } = props; + + const rowSize = 100 / numRows; + const colSize = 100 / numColumns; + + // http://grid.malven.co/ + if (!viewportData || !viewportData.length) { + return null; + } + + const ViewportPanes = layout.viewports.map((layout, viewportIndex) => { + const displaySet = viewportData[viewportIndex]; + const data = { + displaySet, + studies, + }; + + // Use whichever plugin is currently in use in the panel + // unless nothing is specified. If nothing is specified + // and the display set has a plugin specified, use that. + // + // TODO: Change this logic to: + // - Plugins define how capable they are of displaying a SopClass + // - When updating a panel, ensure that the currently enabled plugin + // in the viewport is capable of rendering this display set. If not + // then use the most capable available plugin + const pluginName = + !layout.plugin && displaySet && displaySet.plugin + ? displaySet.plugin + : layout.plugin; + + const ViewportComponent = _getViewportComponent( + data, // Why do we pass this as `ViewportData`, when that's not really what it is? + viewportIndex, + children, + availablePlugins, + pluginName, + defaultPluginName + ); + + return ( + { + setViewportData({ + viewportIndex, + studyInstanceUid, + displaySetInstanceUid, + }); + }} + viewportIndex={viewportIndex} // Needed by `setViewportData` + className={classNames('viewport-container', { + active: activeViewportIndex === viewportIndex, + })} + key={viewportIndex} + > + {ViewportComponent} + + ); + }); + + return ( +
+ {ViewportPanes} +
+ ); +}; + +ViewportGrid.propTypes = { + viewportData: PropTypes.array.isRequired, + supportsDragAndDrop: PropTypes.bool.isRequired, + activeViewportIndex: PropTypes.number.isRequired, + layout: PropTypes.object.isRequired, + availablePlugins: PropTypes.object.isRequired, + setViewportData: PropTypes.func.isRequired, + studies: PropTypes.array, + children: PropTypes.node, +}; + +ViewportGrid.defaultProps = { + viewportData: [], + numRows: 1, + numColumns: 1, + layout: { + viewports: [{}], + }, + activeViewportIndex: 0, + supportsDragAndDrop: true, + availablePlugins: { + DefaultViewport, + }, + defaultPlugin: 'defaultViewportPlugin', +}; + +/** + * + * + * @param {*} plugin + * @param {*} viewportData + * @param {*} viewportIndex + * @param {*} children + * @returns + */ +function _getViewportComponent( + viewportData, + viewportIndex, + children, + availablePlugins, + pluginName, + defaultPluginName +) { + if (viewportData.displaySet) { + pluginName = pluginName || defaultPluginName; + const ViewportComponent = availablePlugins[pluginName]; + + if (!ViewportComponent) { + throw new Error( + `No Viewport Component available for name ${pluginName}. + Available plugins: ${JSON.stringify(availablePlugins)}` + ); + } + + return ( + + ); + } + + return ; +} + +export default ViewportGrid; diff --git a/platform/viewer/src/components/ViewportGrid/ViewportPane.css b/platform/viewer/src/components/ViewportGrid/ViewportPane.css new file mode 100644 index 000000000..7fafa2903 --- /dev/null +++ b/platform/viewer/src/components/ViewportGrid/ViewportPane.css @@ -0,0 +1,10 @@ +.viewport-drop-target { + opacity: 1; + position: relative; /* Locks in Scrollbar */ + transition: 0.3s all ease; +} + +.viewport-drop-target.hovered { + opacity: 0.5; + cursor: copy; +} diff --git a/platform/viewer/src/components/ViewportGrid/ViewportPane.js b/platform/viewer/src/components/ViewportGrid/ViewportPane.js new file mode 100644 index 000000000..0878003eb --- /dev/null +++ b/platform/viewer/src/components/ViewportGrid/ViewportPane.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { useDrop } from 'react-dnd'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import './ViewportPane.css'; + +const ViewportPane = function(props) { + const { children, onDrop, viewportIndex, className: propClassName } = props; + const [{ hovered, highlighted }, drop] = useDrop({ + accept: 'thumbnail', + drop: (droppedItem, monitor) => { + const canDrop = monitor.canDrop(); + const isOver = monitor.isOver(); + + if (canDrop && isOver && onDrop) { + const { studyInstanceUid, displaySetInstanceUid } = droppedItem; + + onDrop({ viewportIndex, studyInstanceUid, displaySetInstanceUid }); + } + }, + // Monitor, and collect props. + // Returned as values by `useDrop` + collect: monitor => ({ + highlighted: monitor.canDrop(), + hovered: monitor.isOver(), + }), + }); + + return ( +
+ {children} +
+ ); +}; + +ViewportPane.propTypes = { + children: PropTypes.node.isRequired, + viewportIndex: PropTypes.number.isRequired, + onDrop: PropTypes.func.isRequired, + className: PropTypes.string, +}; + +export default ViewportPane; diff --git a/platform/viewer/src/components/ViewportGrid/index.js b/platform/viewer/src/components/ViewportGrid/index.js new file mode 100644 index 000000000..d580c005a --- /dev/null +++ b/platform/viewer/src/components/ViewportGrid/index.js @@ -0,0 +1,5 @@ +import ConnectedViewportGrid from './ConnectedViewportGrid.js'; +import ViewportGrid from './ViewportGrid.js'; + +export default ViewportGrid; +export { ConnectedViewportGrid, ViewportGrid }; diff --git a/platform/viewer/src/connectedComponents/ConnectedLayoutButton.js b/platform/viewer/src/connectedComponents/ConnectedLayoutButton.js index 2f2588e4f..8be957827 100644 --- a/platform/viewer/src/connectedComponents/ConnectedLayoutButton.js +++ b/platform/viewer/src/connectedComponents/ConnectedLayoutButton.js @@ -7,7 +7,7 @@ const { setLayout, setViewportActive } = OHIF.redux.actions; const mapStateToProps = state => { return { currentLayout: state.viewports.layout, - activeViewportIndex: state.viewports.activeViewportIndex + activeViewportIndex: state.viewports.activeViewportIndex, }; }; @@ -15,10 +15,11 @@ const mapDispatchToProps = dispatch => { return { // TODO: Change if layout switched becomes more complex onChange: (selectedCell, currentLayout, activeViewportIndex) => { - let viewports = []; - const rows = selectedCell.row + 1; - const columns = selectedCell.col + 1; - const numViewports = rows * columns; + const viewports = []; + const numRows = selectedCell.row + 1; + const numColumns = selectedCell.col + 1; + const numViewports = numRows * numColumns; + for (let i = 0; i < numViewports; i++) { // Hacky way to allow users to exit MPR "mode" const viewport = currentLayout.viewports[i]; @@ -28,16 +29,16 @@ const mapDispatchToProps = dispatch => { } viewports.push({ - height: `${100 / rows}%`, - width: `${100 / columns}%`, plugin, }); } const layout = { + numRows, + numColumns, viewports, }; - const maxActiveIndex = rows * columns - 1; + const maxActiveIndex = numViewports - 1; if (activeViewportIndex > maxActiveIndex) { dispatch(setViewportActive(0)); } @@ -52,9 +53,10 @@ const mergeProps = (propsFromState, propsFromDispatch) => { const { currentLayout, activeViewportIndex } = propsFromState; return { - onChange: selectedCell => onChangeFromDispatch(selectedCell, currentLayout, activeViewportIndex) + onChange: selectedCell => + onChangeFromDispatch(selectedCell, currentLayout, activeViewportIndex), }; -} +}; const ConnectedLayoutButton = connect( mapStateToProps, diff --git a/platform/viewer/src/connectedComponents/ConnectedPluginSwitch.js b/platform/viewer/src/connectedComponents/ConnectedPluginSwitch.js index 24221887b..c54e52eff 100644 --- a/platform/viewer/src/connectedComponents/ConnectedPluginSwitch.js +++ b/platform/viewer/src/connectedComponents/ConnectedPluginSwitch.js @@ -1,9 +1,9 @@ -import OHIF from "@ohif/core"; -import PluginSwitch from "./PluginSwitch.js"; -import { commandsManager } from "./../App.js"; -import { connect } from "react-redux"; +// import OHIF from '@ohif/core'; +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 mapStateToProps = state => { const { activeViewportIndex, layout, viewportSpecificData } = state.viewports; @@ -11,17 +11,17 @@ const mapStateToProps = state => { return { activeViewportIndex, viewportSpecificData, - layout + layout, }; }; -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(); @@ -39,19 +39,17 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => { // TODO: Do not display certain options if the current display set // cannot be displayed using these view types const mpr = () => { - commandsManager.runCommand("mpr2d"); - } - ; - + commandsManager.runCommand('mpr2d'); + }; return { - mpr + mpr, }; }; const ConnectedPluginSwitch = connect( mapStateToProps, - mapDispatchToProps, + null, // mapDispatchToProps mergeProps )(PluginSwitch); -export default ConnectedPluginSwitch; \ No newline at end of file +export default ConnectedPluginSwitch; diff --git a/platform/viewer/src/connectedComponents/PluginSwitch.js b/platform/viewer/src/connectedComponents/PluginSwitch.js index f9437cef6..c3718a163 100644 --- a/platform/viewer/src/connectedComponents/PluginSwitch.js +++ b/platform/viewer/src/connectedComponents/PluginSwitch.js @@ -13,9 +13,7 @@ class PluginSwitch extends Component { render() { return (
- +
); } diff --git a/platform/viewer/src/connectedComponents/ViewerMain.js b/platform/viewer/src/connectedComponents/ViewerMain.js index 64ae2549a..073bc6f31 100644 --- a/platform/viewer/src/connectedComponents/ViewerMain.js +++ b/platform/viewer/src/connectedComponents/ViewerMain.js @@ -1,7 +1,7 @@ import './ViewerMain.css'; import { Component } from 'react'; -import ConnectedLayoutManager from './ConnectedLayoutManager.js'; +import { ConnectedViewportGrid } from './../components/ViewportGrid/index.js'; import ConnectedToolContextMenu from './ConnectedToolContextMenu.js'; import PropTypes from 'prop-types'; import React from 'react'; @@ -124,11 +124,15 @@ class ViewerMain extends Component { return viewportData; }; - setViewportData = ({ viewportIndex, item }) => { + setViewportData = ({ + viewportIndex, + studyInstanceUid, + displaySetInstanceUid, + }) => { const displaySet = this.findDisplaySet( this.props.studies, - item.studyInstanceUid, - item.displaySetInstanceUid + studyInstanceUid, + displaySetInstanceUid ); this.props.setViewportSpecificData(viewportIndex, displaySet); @@ -138,14 +142,14 @@ class ViewerMain extends Component { return (
{this.state.displaySets.length && ( - {/* Children to add to each viewport that support children */} - + )}
);