diff --git a/extensions/default/src/Panels/PanelStudyBrowser.jsx b/extensions/default/src/Panels/PanelStudyBrowser.jsx
index ea705cabc..97564c115 100644
--- a/extensions/default/src/Panels/PanelStudyBrowser.jsx
+++ b/extensions/default/src/Panels/PanelStudyBrowser.jsx
@@ -51,7 +51,7 @@ function PanelStudyBrowser({
// ~~ Initial Thumbnails
useEffect(() => {
- const currentDisplaySets = DisplaySetService.activeDisplaySets || [];
+ const currentDisplaySets = DisplaySetService.activeDisplaySets;
currentDisplaySets.forEach(async dSet => {
const newImageSrcEntry = {};
const displaySet = DisplaySetService.getDisplaySetByUID(
@@ -71,7 +71,7 @@ function PanelStudyBrowser({
// ~~ displaySets
useEffect(() => {
// TODO: Are we sure `activeDisplaySets` will always be accurate?
- const currentDisplaySets = DisplaySetService.activeDisplaySets || [];
+ const currentDisplaySets = DisplaySetService.activeDisplaySets;
const mappedDisplaySets = _mapDisplaySets(
currentDisplaySets,
thumbnailImageSrcMap
@@ -164,6 +164,7 @@ function PanelStudyBrowser({
PanelStudyBrowser.propTypes = {
DisplaySetService: PropTypes.shape({
EVENTS: PropTypes.object.isRequired,
+ activeDisplaySets: PropTypes.arrayOf(PropTypes.object).isRequired,
getDisplaySetByUID: PropTypes.func.isRequired,
hasDisplaySetsForStudy: PropTypes.func.isRequired,
subscribe: PropTypes.func.isRequired,
diff --git a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js
index 55bcf70f2..8fb176959 100644
--- a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js
+++ b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js
@@ -72,11 +72,16 @@ const BaseImplementation = {
study = _model.studies[_model.studies.length - 1];
}
- study.addSeries(instances);
- this._broadcastEvent(EVENTS.INSTANCES_ADDED, {
- StudyInstanceUID,
- SeriesInstanceUID,
- });
+ // TODO: Worth identifying why this is being called many times with series
+ // that are already "added"?
+ const didAddSeries = study.addSeries(instances);
+
+ if (didAddSeries) {
+ this._broadcastEvent(EVENTS.INSTANCES_ADDED, {
+ StudyInstanceUID,
+ SeriesInstanceUID,
+ });
+ }
},
addStudy(study) {
const { StudyInstanceUID } = study;
diff --git a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js
index d645218b1..3472215b6 100644
--- a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js
+++ b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js
@@ -4,9 +4,24 @@ function createStudyMetadata(StudyInstanceUID) {
return {
StudyInstanceUID,
series: [],
+ /**
+ *
+ * @param {object[]} instances
+ * @param {string} instances[].SeriesInstanceUID
+ * @returns {bool} true if series were added; false if series already exist
+ */
addSeries: function(instances) {
- const series = createSeriesMetadata(instances);
- this.series.push(series);
+ const { SeriesInstanceUID } = instances[0];
+ const seriesExists = this.series.some(
+ s => s.SeriesInstanceUID === SeriesInstanceUID
+ );
+
+ if (!seriesExists) {
+ const series = createSeriesMetadata(instances);
+ this.series.push(series);
+ }
+
+ return !seriesExists;
},
};
}
diff --git a/platform/core/src/services/DisplaySetService/DisplaySetService.js b/platform/core/src/services/DisplaySetService/DisplaySetService.js
index b09aa8c03..d9275c648 100644
--- a/platform/core/src/services/DisplaySetService/DisplaySetService.js
+++ b/platform/core/src/services/DisplaySetService/DisplaySetService.js
@@ -6,6 +6,7 @@ const displaySetCache = [];
export default class DisplaySetService {
constructor() {
this.displaySets = {};
+ this.activeDisplaySets = [];
this.listeners = {};
this.EVENTS = EVENTS;
@@ -77,9 +78,10 @@ export default class DisplaySetService {
displaySetsAdded = displaySets;
}
+ // TODO: This is tricky. How do we know we're not resetting to the same/existing DSs?
+ // TODO: This is likely run anytime we touch DicomMetadataStore. How do we prevent uneccessary broadcasts?
if (displaySetsAdded && displaySetsAdded.length) {
this._broadcastEvent(EVENTS.DISPLAY_SETS_ADDED, displaySetsAdded);
-
this._broadcastEvent(EVENTS.DISPLAY_SETS_CHANGED, this.activeDisplaySets);
}
};
diff --git a/platform/core/src/services/_shared/pubSubServiceInterface.js b/platform/core/src/services/_shared/pubSubServiceInterface.js
index 1719be1ca..9a22a3ccb 100644
--- a/platform/core/src/services/_shared/pubSubServiceInterface.js
+++ b/platform/core/src/services/_shared/pubSubServiceInterface.js
@@ -13,7 +13,7 @@ export default {
};
/**
- * Subscribe to measurement updates.
+ * Subscribe to updates.
*
* @param {string} eventName The name of the event
* @param {Function} callback Events callback
@@ -24,7 +24,7 @@ function subscribe(eventName, callback) {
const listenerId = guid();
const subscription = { id: listenerId, callback };
- console.info(`displaySetService: Subscribing to '${eventName}'.`);
+ console.info(`Subscribing to '${eventName}'.`);
if (Array.isArray(this.listeners[eventName])) {
this.listeners[eventName].push(subscription);
} else {
@@ -60,7 +60,7 @@ function _unsubscribe(eventName, listenerId) {
}
/**
- * Check if a given displaySetService event is valid.
+ * Check if a given event is valid.
*
* @param {string} eventName The name of the event
* @return {boolean} Event name validation
@@ -70,7 +70,7 @@ function _isValidEvent(eventName) {
}
/**
- * Broadcasts displaySetService changes.
+ * Broadcasts changes.
*
* @param {string} eventName - The event name
* @param {func} callbackProps - Properties to pass callback
diff --git a/platform/ui/index.js b/platform/ui/index.js
index 7c33669f1..827147317 100644
--- a/platform/ui/index.js
+++ b/platform/ui/index.js
@@ -14,6 +14,9 @@ export {
ImageViewerContext,
ImageViewerProvider,
useImageViewer,
+ ViewportGridContext,
+ ViewportGridProvider,
+ useViewportGrid,
} from './src/contextProviders';
/** COMPONENTS */
diff --git a/platform/ui/src/contextProviders/ViewportGridProvider.jsx b/platform/ui/src/contextProviders/ViewportGridProvider.jsx
new file mode 100644
index 000000000..c56268cc4
--- /dev/null
+++ b/platform/ui/src/contextProviders/ViewportGridProvider.jsx
@@ -0,0 +1,20 @@
+import React, { createContext, useContext, useReducer } from 'react';
+
+// export const VIEWPORT_GRID_DEFAULT_VALUE = {
+// numCols: 1,
+// numRows: 1,
+// activeViewportIndex: 0,
+// viewports: [],
+// };
+
+export const ViewportGridContext = createContext();
+
+export function ViewportGridProvider({ reducer, initialState, children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export const useViewportGrid = () => useContext(ViewportGridContext);
diff --git a/platform/ui/src/contextProviders/index.js b/platform/ui/src/contextProviders/index.js
index d879ca9ef..571ce4294 100644
--- a/platform/ui/src/contextProviders/index.js
+++ b/platform/ui/src/contextProviders/index.js
@@ -16,4 +16,10 @@ export {
useImageViewer,
} from './ImageViewerProvider';
+export {
+ ViewportGridContext,
+ ViewportGridProvider,
+ useViewportGrid,
+} from './ViewportGridProvider';
+
export { default as DragAndDropProvider } from './DragAndDropProvider';
diff --git a/platform/viewer/src/App.jsx b/platform/viewer/src/App.jsx
index e85fea0cc..28ab3a4e9 100644
--- a/platform/viewer/src/App.jsx
+++ b/platform/viewer/src/App.jsx
@@ -6,7 +6,7 @@ import { ThemeWrapper } from '@ohif/ui';
// Viewer Project
// TODO: Should this influence study list?
import { appConfigContext } from '@state/appConfig.context';
-import { useAppConfig } from './hooks/useAppConfig';
+import { useAppConfig } from '@hooks/useAppConfig';
import createRoutes from './routes';
import appInit from './appInit.js';
diff --git a/platform/viewer/src/components/EmptyViewport.jsx b/platform/viewer/src/components/EmptyViewport.jsx
new file mode 100644
index 000000000..70c684cfc
--- /dev/null
+++ b/platform/viewer/src/components/EmptyViewport.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+function EmptyViewport() {
+ return (
+
+
Please drag a stack here to view images.
+
+ );
+}
+
+export default EmptyViewport;
diff --git a/platform/viewer/src/components/ViewportGrid.jsx b/platform/viewer/src/components/ViewportGrid.jsx
index 20b4b54d7..e58bf8bbd 100644
--- a/platform/viewer/src/components/ViewportGrid.jsx
+++ b/platform/viewer/src/components/ViewportGrid.jsx
@@ -1,19 +1,17 @@
/**
* CSS Grid Reference: http://grid.malven.co/
*/
-import React, { useState, useCallback, useEffect } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
-import { ViewportGrid, ViewportPane } from '@ohif/ui';
-// import DefaultViewport from './DefaultViewport.js';
-// import EmptyViewport from './EmptyViewport.js';
+import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
+import EmptyViewport from './EmptyViewport';
function ViewerViewportGrid(props) {
- const {
- activeViewportIndex,
- servicesManager,
- viewportComponents,
- dataSource,
- } = props;
+ const { servicesManager, viewportComponents, dataSource } = props;
+ const [
+ { numCols, numRows, activeViewportIndex, viewports },
+ dispatch,
+ ] = useViewportGrid();
// TODO -> Need some way of selecting which displaySets hit the viewports.
const { DisplaySetService } = servicesManager.services;
@@ -23,8 +21,8 @@ function ViewerViewportGrid(props) {
let displaySetInstanceUID;
// Fallback
- if(!displaySets || !displaySets.length) {
- const displaySet = DisplaySetService.activeDisplaySets[0]
+ if (!displaySets || !displaySets.length) {
+ const displaySet = DisplaySetService.activeDisplaySets[0];
displaySetInstanceUID = displaySet.displaySetInstanceUID;
} else {
const displaySet = displaySets[0];
@@ -43,86 +41,97 @@ function ViewerViewportGrid(props) {
};
};
- // From ViewportGridService and/or ContextProvider
- const [viewportGrid, setViewportGrid] = useState({
- numCols: 1,
- numRows: 1,
- viewports: []
- });
+ // TODO:
+ // Hmm... Should a "displaySet" being added update the viewport based on HP?
+ // I guess it might.
+ // This is where you would likely "fill" emptyViewports if none had content
+ // Or to recheck best placement/priority based on all activeDisplaySets
+ // useEffect(() => {
+ // const { unsubscribe } = DisplaySetService.subscribe(
+ // DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
+ // displaySets => {
+ // displaySets.forEach(ds => console.log(`VPG:ADD::${ds.StudyInstanceUID}`));
+ // const hp = HangingProtocolService(displaySets);
+ // viewportGrid.setViewportGrid(hp);
+ // },
+ // );
-
- useEffect(() => {
- const { unsubscribe } = DisplaySetService.subscribe(
- DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
- displaySets => {
- setViewportGrid(HangingProtocolService(displaySets));
- },
- );
-
- return unsubscribe;
- }, []);
+ // return unsubscribe;
+ // }, []);
// TODO: either need hover to change "active viewport"
// so we can use it as our target for setting the displaySet,
// or the dropHandler needs to know which viewport was dropped on
// in event data
- const onDropHandler = ({displaySetInstanceUID}) => {
- const droppedDisplaySet = DisplaySetService.getDisplaySetByUID(displaySetInstanceUID);
- const updatedViewportGridState = HangingProtocolService([droppedDisplaySet]);
+ const onDropHandler = ({ displaySetInstanceUID }) => {
+ const droppedDisplaySet = DisplaySetService.getDisplaySetByUID(
+ displaySetInstanceUID
+ );
+ const updatedViewportGridState = HangingProtocolService([
+ droppedDisplaySet,
+ ]);
- console.warn('DROPPED: ', displaySetInstanceUID, droppedDisplaySet, updatedViewportGridState);
+ console.warn(
+ 'DROPPED: ',
+ displaySetInstanceUID,
+ droppedDisplaySet,
+ updatedViewportGridState
+ );
- // This is not updating the displayed DisplaySet
- setViewportGrid(updatedViewportGridState);
- }
+ dispatch({ type: 'action-name', payload: updatedViewportGridState });
+ };
- // viewportData --> displaySets
- const getViewportPanes = () =>
- viewportGrid.viewports.map((viewport, viewportIndex) => {
- const displaySetInstanceUID = viewport.displaySetInstanceUID;
- if (!displaySetInstanceUID) {
- return null;
- }
+ const getViewportPanes = () => {
+ const viewportPanes = [];
+ const numViewportPanes = numCols * numRows;
- const displaySet = DisplaySetService.getDisplaySetByUID(displaySetInstanceUID);
+ for (let i = 0; i < numViewportPanes; i++) {
+ const viewportIndex = i;
+ const paneMeta = viewports[i];
+ const isEmpty = !paneMeta || !paneMeta.displaySetInstanceUID;
- // TODO: Better Empty Viewport
- if (!displaySet) {
- return (
+ if (isEmpty) {
+ viewportPanes[i] = (
+ >
+
+
+ );
+ } else {
+ const displaySet = DisplaySetService.getDisplaySetByUID(
+ paneMeta.displaySetInstanceUID
+ );
+
+ const ViewportComponent = _getViewportComponent(
+ displaySet,
+ viewportComponents
+ );
+
+ viewportPanes[i] = (
+
+
+
);
}
+ }
- // TODO -> Need way for other viewport e.g. vtk to be used.
-
- const ViewportComponent = _getViewportComponent(
- displaySet,
- viewportComponents
- );
-
- return (
-
-
-
- );
- });
+ return viewportPanes;
+ };
// const ViewportPanes = React.useMemo(getViewportPanes, [
// viewportComp'onents,
@@ -131,7 +140,7 @@ function ViewerViewportGrid(props) {
// ]);
return (
-
+
{/* {ViewportPanes} */}
{getViewportPanes()}
@@ -139,19 +148,11 @@ function ViewerViewportGrid(props) {
}
ViewerViewportGrid.propTypes = {
- // viewports: PropTypes.array.isRequired,
- activeViewportIndex: PropTypes.number.isRequired,
viewportComponents: PropTypes.array.isRequired,
- // numRows: PropTypes.number.isRequired,
- // numColumns: PropTypes.number.isRequired,
};
ViewerViewportGrid.defaultProps = {
- // numRows: 1,
- // numColumns: 1,
- viewportData: [],
viewportComponents: [],
- activeViewportIndex: 0,
};
function _getViewportComponent(displaySet, viewportComponents) {
diff --git a/platform/viewer/src/routes/Mode/Mode.jsx b/platform/viewer/src/routes/Mode/Mode.jsx
index 6bcde79d5..615bd5817 100644
--- a/platform/viewer/src/routes/Mode/Mode.jsx
+++ b/platform/viewer/src/routes/Mode/Mode.jsx
@@ -3,7 +3,11 @@ import { useParams } from 'react-router';
import PropTypes from 'prop-types';
// TODO: DicomMetadataStore should be injected?
import { DicomMetadataStore, ToolBarManager } from '@ohif/core';
-import { DragAndDropProvider, ImageViewerProvider } from '@ohif/ui';
+import {
+ DragAndDropProvider,
+ ImageViewerProvider,
+ ViewportGridProvider,
+} from '@ohif/ui';
//
import { useQuery } from '@hooks';
import ViewportGrid from '@components/ViewportGrid';
@@ -119,23 +123,44 @@ export default function ModeRoute({
console.log(state, action);
};
+ const viewportGridReducer = (state, action) => {
+ console.log(state, action);
+
+ switch (action.type) {
+ case 'DO_TODO':
+ return state;
+ default:
+ return action.payload;
+ }
+ };
+
return (
-
- {/* TODO: extensionManager is already provided to the extension module.
- * Use it from there instead of passing as a prop here.
- */}
-
-
-
-
+
+
+ {/* TODO: extensionManager is already provided to the extension module.
+ * Use it from there instead of passing as a prop here.
+ */}
+
+
+
+
+
);
}
diff --git a/platform/viewer/src/state/ViewportGridProvider.js b/platform/viewer/src/state/ViewportGridProvider.js
deleted file mode 100644
index 85fc225cc..000000000
--- a/platform/viewer/src/state/ViewportGridProvider.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React, { useState, createContext, useContext } from 'react';
-import PropTypes from 'prop-types';
-
-const ViewportGridContext = createContext(null);
-
-export const useViewportGrid = () => useContext(ViewportGridContext);
-
-export default function ViewportGridProvider({ children }) {
- const [viewportGrid, setViewportGrid] = useState({
- rows: 1,
- columns: 1,
- viewports: [],
- });
-
- const get = () => {
- return viewportGrid;
- };
-
- const set = ({ rows, columns, viewports }) => {
- setViewportGrid({ rows, columns, viewports });
- };
-
- return (
-
- {/* {children} */}
-
- );
-}
-
-ViewportGridProvider.propTypes = {
- children: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- ]).isRequired,
-};
-
-export default ViewportGridProvider;