From 2c11609c556bb7b5363eb2cd701a2b74cac38f2f Mon Sep 17 00:00:00 2001 From: James Petts Date: Tue, 30 Jun 2020 16:27:35 +0100 Subject: [PATCH] OHIF-198 - Jump to thumbnail when a new displaySet is added (#1834) * Viewport letter only in multi-viewport layout. * If SliceThickness not available print N/A * Create displaySet in local session on SR generation. * Jump to thumbnail when a new displaySet is added with the madeInClient property. * Check if scrollIntoView exists. --- .../default/src/Panels/PanelStudyBrowser.jsx | 7 +- .../dicom-sr/src/getSopClassHandlerModule.js | 5 +- .../PanelMeasurementTableTracking/index.js | 6 +- .../PanelStudyBrowserTracking.jsx | 83 ++++++++++++++++++- .../DisplaySetService/DisplaySetService.js | 13 ++- .../ui/src/components/Thumbnail/Thumbnail.jsx | 2 + .../ThumbnailList/ThumbnailList.jsx | 3 + .../ThumbnailNoImage/ThumbnailNoImage.jsx | 5 +- .../ThumbnailTracked/ThumbnailTracked.jsx | 2 + 9 files changed, 113 insertions(+), 13 deletions(-) diff --git a/extensions/default/src/Panels/PanelStudyBrowser.jsx b/extensions/default/src/Panels/PanelStudyBrowser.jsx index 8e813217f..2d66fc634 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.jsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.jsx @@ -28,6 +28,8 @@ function PanelStudyBrowser({ const [displaySets, setDisplaySets] = useState([]); const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState({}); + console.log(DisplaySetService); + // ~~ studyDisplayList useEffect(() => { // Fetch all studies for the patient in each primary study @@ -98,8 +100,9 @@ function PanelStudyBrowser({ // DISPLAY_SETS_ADDED returns an array of DisplaySets that were added const SubscriptionDisplaySetsAdded = DisplaySetService.subscribe( DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, - newDisplaySets => { - newDisplaySets.forEach(async dSet => { + data => { + const { displaySetsAdded } = data; + displaySetsAdded.forEach(async dSet => { const newImageSrcEntry = {}; const displaySet = DisplaySetService.getDisplaySetByUID( dSet.displaySetInstanceUID diff --git a/extensions/dicom-sr/src/getSopClassHandlerModule.js b/extensions/dicom-sr/src/getSopClassHandlerModule.js index 9e57e3dcf..80a597a08 100644 --- a/extensions/dicom-sr/src/getSopClassHandlerModule.js +++ b/extensions/dicom-sr/src/getSopClassHandlerModule.js @@ -103,10 +103,11 @@ function _getDisplaySetsFromSeries( // Subscribe to new displaySets as the source may come in after. DisplaySetService.subscribe( DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, - newDisplaySets => { + data => { + const { displaySetsAdded } = data; // If there are still some measurements that have not yet been loaded into cornerstone, // See if we can load them onto any of the new displaySets. - newDisplaySets.forEach(newDisplaySet => { + displaySetsAdded.forEach(newDisplaySet => { _checkIfCanAddMeasurementsToDisplaySet( displaySet, newDisplaySet, diff --git a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js index b7cf5ae8f..968a51c1f 100644 --- a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js +++ b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js @@ -117,7 +117,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { DICOMSR.downloadReport(trackedMeasurements, dataSource); }; - const createReport = () => { + const createReport = async () => { const measurements = MeasurementService.getMeasurements(); const trackedMeasurements = measurements.filter( m => @@ -134,7 +134,9 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { trackedMeasurements, dataSource, naturalizedReport => { - DisplaySetService.makeDisplaySets([naturalizedReport]); + DisplaySetService.makeDisplaySets([naturalizedReport], { + madeInClient: true, + }); } ); }; diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx index a3ef5f777..b45cd3691 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx @@ -34,6 +34,7 @@ function PanelStudyBrowserTracking({ const [studyDisplayList, setStudyDisplayList] = useState([]); const [displaySets, setDisplaySets] = useState([]); const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState({}); + const [jumpToDisplaySet, setJumpToDisplaySet] = useState(null); // TODO: Should this be somewhere else? Feels more like a mode "lifecycle" setup/destroy? useEffect(() => { @@ -136,18 +137,26 @@ function PanelStudyBrowserTracking({ // DISPLAY_SETS_ADDED returns an array of DisplaySets that were added const SubscriptionDisplaySetsAdded = DisplaySetService.subscribe( DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, - newDisplaySets => { - newDisplaySets.forEach(async dSet => { + data => { + const { displaySetsAdded, options } = data; + displaySetsAdded.forEach(async dSet => { + const displaySetInstanceUID = dSet.displaySetInstanceUID; + const newImageSrcEntry = {}; const displaySet = DisplaySetService.getDisplaySetByUID( - dSet.displaySetInstanceUID + displaySetInstanceUID ); + + if (options.madeInClient) { + setJumpToDisplaySet(displaySetInstanceUID); + } + const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); const imageId = imageIds[Math.floor(imageIds.length / 2)]; // TODO: Is it okay that imageIds are not returned here for SR displaysets? if (imageId) { // When the image arrives, render it and store the result in the thumbnailImgSrcMap - newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc( + newImageSrcEntry[displaySetInstanceUID] = await getImageSrc( imageId ); setThumbnailImageSrcMap(prevState => { @@ -214,6 +223,51 @@ function PanelStudyBrowserTracking({ } } + useEffect(() => { + if (jumpToDisplaySet) { + // Get element by displaySetInstanceUID + const displaySetInstanceUID = jumpToDisplaySet; + const element = document.getElementById( + `thumbnail-${displaySetInstanceUID}` + ); + + if (element && typeof element.scrollIntoView === 'function') { + // TODO: Any way to support IE here? + element.scrollIntoView({ behavior: 'smooth' }); + + setJumpToDisplaySet(null); + } + } + }, [jumpToDisplaySet, expandedStudyInstanceUIDs, activeTabName]); + + useEffect(() => { + if (!jumpToDisplaySet) { + return; + } + + const displaySetInstanceUID = jumpToDisplaySet; + // Set the activeTabName and expand the study + const thumbnailLocation = _findTabAndStudyOfDisplaySet( + displaySetInstanceUID, + tabs + ); + if (!thumbnailLocation) { + console.warn('jumpToThumbnail: displaySet thumbnail not found.'); + + return; + } + const { tabName, StudyInstanceUID } = thumbnailLocation; + setActiveTabName(tabName); + const studyExpanded = expandedStudyInstanceUIDs.includes(StudyInstanceUID); + if (!studyExpanded) { + const updatedExpandedStudyInstanceUIDs = [ + ...expandedStudyInstanceUIDs, + StudyInstanceUID, + ]; + setExpandedStudyInstanceUIDs(updatedExpandedStudyInstanceUIDs); + } + }, [jumpToDisplaySet]); + return ( displaySet.displaySetInstanceUID === displaySetInstanceUid ); - makeDisplaySets = (input, batch = false) => { + makeDisplaySets = (input, { batch = false, madeInClient = false } = {}) => { if (!input || !input.length) { throw new Error('No instances were provided.'); } @@ -78,11 +78,20 @@ export default class DisplaySetService { displaySetsAdded = displaySets; } + const options = {}; + + if (madeInClient) { + options.madeInClient = true; + } + // 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); + this._broadcastEvent(EVENTS.DISPLAY_SETS_ADDED, { + displaySetsAdded, + options, + }); } }; diff --git a/platform/ui/src/components/Thumbnail/Thumbnail.jsx b/platform/ui/src/components/Thumbnail/Thumbnail.jsx index 44460fa8a..7813928a0 100644 --- a/platform/ui/src/components/Thumbnail/Thumbnail.jsx +++ b/platform/ui/src/components/Thumbnail/Thumbnail.jsx @@ -9,6 +9,7 @@ import { Icon } from '@ohif/ui'; * */ const Thumbnail = ({ + displaySetInstanceUID, className, imageSrc, imageAltText, @@ -36,6 +37,7 @@ const Thumbnail = ({ className, 'flex flex-col flex-1 px-3 mb-8 cursor-pointer outline-none' )} + id={`thumbnail-${displaySetInstanceUID}`} onClick={onClick} onKeyDown={onClick} role="button" diff --git a/platform/ui/src/components/ThumbnailList/ThumbnailList.jsx b/platform/ui/src/components/ThumbnailList/ThumbnailList.jsx index 303159df8..0b1febd82 100644 --- a/platform/ui/src/components/ThumbnailList/ThumbnailList.jsx +++ b/platform/ui/src/components/ThumbnailList/ThumbnailList.jsx @@ -33,6 +33,7 @@ const ThumbnailList = ({ return (