diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index d96c38de7..b01d07320 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -50,6 +50,6 @@ "classnames": "^2.2.6", "lodash.merge": "^4.6.2", "lodash.throttle": "^4.1.1", - "react-cornerstone-viewport": "2.3.8" + "react-cornerstone-viewport": "3.0.0" } } diff --git a/extensions/cornerstone/src/OHIFCornerstoneViewport.js b/extensions/cornerstone/src/OHIFCornerstoneViewport.js index e4e1a9485..56f6a10c1 100644 --- a/extensions/cornerstone/src/OHIFCornerstoneViewport.js +++ b/extensions/cornerstone/src/OHIFCornerstoneViewport.js @@ -3,7 +3,6 @@ import CornerstoneViewport from 'react-cornerstone-viewport'; //import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport'; import OHIF from '@ohif/core'; import PropTypes from 'prop-types'; -import cornerstone from 'cornerstone-core'; import debounce from 'lodash.debounce'; import throttle from 'lodash.throttle'; @@ -68,7 +67,7 @@ class OHIFCornerstoneViewport extends Component { * @return {Object} CornerstoneTools Stack */ static getCornerstoneStack(displaySet, dataSource) { - const { frameIndex } = displaySet; + const { imageIndex } = displaySet; // Get stack from Stack Manager const storedStack = StackManager.findOrCreateStack(displaySet, dataSource); @@ -76,7 +75,7 @@ class OHIFCornerstoneViewport extends Component { // Clone the stack here so we don't mutate it const stack = Object.assign({}, storedStack); - stack.currentImageIdIndex = frameIndex; + stack.currentImageIdIndex = imageIndex; // TODO -> Do we ever use this like this? // if (SOPInstanceUID) { @@ -155,9 +154,9 @@ class OHIFCornerstoneViewport extends Component { if ( displaySet.displaySetInstanceUID !== - prevDisplaySet.displaySetInstanceUID || + prevDisplaySet.displaySetInstanceUID || displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID || - displaySet.frameIndex !== prevDisplaySet.frameIndex + displaySet.imageIndex !== prevDisplaySet.imageIndex ) { this.setStateFromProps(); } @@ -199,7 +198,7 @@ class OHIFCornerstoneViewport extends Component { this.props.onNewImage({ StudyInstanceUID, SOPInstanceUID: sopInstanceUid, - frameIndex: currentImageIdIndex, + imageIndex: currentImageIdIndex, activeViewportIndex: viewportIndex, }); } diff --git a/extensions/cornerstone/src/commandsModule.js b/extensions/cornerstone/src/commandsModule.js index 89ffa1bc4..c7cc9b817 100644 --- a/extensions/cornerstone/src/commandsModule.js +++ b/extensions/cornerstone/src/commandsModule.js @@ -271,7 +271,7 @@ const commandsModule = ({ servicesManager }) => { jumpToImage: ({ StudyInstanceUID, SOPInstanceUID, - frameIndex, + imageIndex, activeViewportIndex, }) => { const study = studyMetadataManager.get(StudyInstanceUID); @@ -284,7 +284,7 @@ const commandsModule = ({ servicesManager }) => { }); displaySet.SOPInstanceUID = SOPInstanceUID; - displaySet.frameIndex = frameIndex; + displaySet.imageIndex = imageIndex; window.store.dispatch( setViewportSpecificData(activeViewportIndex, displaySet) diff --git a/extensions/cornerstone/src/init.js b/extensions/cornerstone/src/init.js index 319a4c484..c8f30ee60 100644 --- a/extensions/cornerstone/src/init.js +++ b/extensions/cornerstone/src/init.js @@ -1,5 +1,6 @@ +import React from 'react'; import OHIF from '@ohif/core'; -import { SimpleDialog } from '@ohif/ui'; +import { Dialog, Input } from '@ohif/ui'; import cornerstone from 'cornerstone-core'; import csTools from 'cornerstone-tools'; import merge from 'lodash.merge'; @@ -23,18 +24,48 @@ export default function init({ servicesManager, configuration }) { let dialogId = UIDialogService.create({ centralize: true, isDraggable: false, - content: SimpleDialog.InputDialog, + content: Dialog, useLastPosition: false, showOverlay: true, contentProps: { title: 'Enter your annotation', - label: 'New label', - measurementData: data ? { description: data.text } : {}, + value: { label: data ? data.text : '' }, + noCloseButton: true, onClose: () => UIDialogService.dismiss({ id: dialogId }), - onSubmit: value => { - callback(value); + actions: [ + { id: 'cancel', text: 'Cancel', type: 'secondary' }, + { id: 'save', text: 'Save', type: 'primary' }, + ], + onSubmit: ({ action, value }) => { + switch (action.id) { + case 'save': callback(value.label); + } UIDialogService.dismiss({ id: dialogId }); }, + body: ({ value, setValue }) => { + const onChangeHandler = (event) => { + event.persist(); + setValue(value => ({ ...value, label: event.target.value })); + }; + const onKeyPressHandler = event => { + if (event.key === 'Enter') { + onSubmitHandler({ value, action: { id: 'save' } }); + } + }; + return ( +
+ +
+ ); + }, }, }); } @@ -62,7 +93,7 @@ export default function init({ servicesManager, configuration }) { // THIS // is a way for extensions that "depend" on this extension to notify it of // new cornerstone enabled elements so it's commands continue to work. - const handleOhifCornerstoneEnabledElementEvent = function(evt) { + const handleOhifCornerstoneEnabledElementEvent = function (evt) { const { viewportIndex, enabledElement } = evt.detail; setEnabledElement(viewportIndex, enabledElement); @@ -108,7 +139,6 @@ export default function init({ servicesManager, configuration }) { /* Add extension tools configuration here. */ const internalToolsConfig = { - /* TODO ArrowAnnotate input ArrowAnnotate: { configuration: { getTextCallback: (callback, eventDetails) => @@ -117,7 +147,6 @@ export default function init({ servicesManager, configuration }) { callInputDialog(data, eventDetails, callback), }, }, - */ }; /* Abstract tools configuration using extension configuration. */ diff --git a/extensions/default/src/PanelMeasurementTable.js b/extensions/default/src/PanelMeasurementTable.js index 0967ac378..1cf8cd183 100644 --- a/extensions/default/src/PanelMeasurementTable.js +++ b/extensions/default/src/PanelMeasurementTable.js @@ -51,8 +51,8 @@ export default function PanelMeasurementTable({ title="Measurements" amount={displayMeasurements.length} data={displayMeasurements} - onClick={() => {}} - onEdit={id => alert(`Edit: ${id}`)} + onClick={({ id }) => alert(`Click: ${id}`)} + onEdit={({ id }) => alert(`Edit: ${id}`)} /> diff --git a/extensions/dicom-rt/src/components/RTPanel/RTPanel.js b/extensions/dicom-rt/src/components/RTPanel/RTPanel.js index 384794315..ca100e57f 100644 --- a/extensions/dicom-rt/src/components/RTPanel/RTPanel.js +++ b/extensions/dicom-rt/src/components/RTPanel/RTPanel.js @@ -150,7 +150,7 @@ const RTPanel = ({ imageIds ); - const frameIndex = imageIds.indexOf(imageId); + const imageIndex = imageIds.indexOf(imageId); const SOPInstanceUID = cornerstone.metaData.get( 'SOPInstanceUID', imageId @@ -163,7 +163,7 @@ const RTPanel = ({ onContourItemClick({ StudyInstanceUID, SOPInstanceUID, - frameIndex, + imageIndex, activeViewportIndex: activeIndex, }); }} @@ -237,7 +237,7 @@ const RTPanel = ({ expanded={ isLoaded && loadedSet.SeriesInstanceUID === - state.selectedSet.SeriesInstanceUID + state.selectedSet.SeriesInstanceUID } onVisibilityChange={newVisibility => { const module = cornerstoneTools.getModule('rtstruct'); diff --git a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js index ae751022a..ba3df2d76 100644 --- a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js +++ b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js @@ -320,7 +320,7 @@ const SegmentationPanel = ({ const imageIds = toolState.data[0].imageIds; const imageId = imageIds[closest]; - const frameIndex = imageIds.indexOf(imageId); + const imageIndex = imageIds.indexOf(imageId); const SOPInstanceUID = cornerstone.metaData.get( 'SOPInstanceUID', @@ -334,7 +334,7 @@ const SegmentationPanel = ({ onSegmentItemClick({ StudyInstanceUID, SOPInstanceUID, - frameIndex, + imageIndex, activeViewportIndex: activeIndex, }); } @@ -360,7 +360,7 @@ const SegmentationPanel = ({ SOPClassUID: viewports[activeIndex].sopClassUIDs[0], SOPInstanceUID: currentDisplaySet.SOPInstanceUID, segmentNumber, - frameIndex: closest, + imageIndex: closest, frame, }); } @@ -637,7 +637,7 @@ const _setActiveLabelmap = async ( displaySet, firstImageId, activeLabelmapIndex, - callback = () => {}, + callback = () => { }, onDisplaySetLoadFailure ) => { if (displaySet.labelmapIndex === activeLabelmapIndex) { diff --git a/extensions/dicom-sr/package.json b/extensions/dicom-sr/package.json index 8e5065291..e45746e97 100644 --- a/extensions/dicom-sr/package.json +++ b/extensions/dicom-sr/package.json @@ -50,6 +50,6 @@ "classnames": "^2.2.6", "lodash.merge": "^4.6.2", "lodash.throttle": "^4.1.1", - "react-cornerstone-viewport": "2.3.8" + "react-cornerstone-viewport": "3.0.0" } } diff --git a/extensions/dicom-sr/src/getSopClassHandlerModule.js b/extensions/dicom-sr/src/getSopClassHandlerModule.js index a2c8748d6..66d35127e 100644 --- a/extensions/dicom-sr/src/getSopClassHandlerModule.js +++ b/extensions/dicom-sr/src/getSopClassHandlerModule.js @@ -70,7 +70,7 @@ function _getDisplaySetsFromSeries( if ( !ConceptNameCodeSequence || ConceptNameCodeSequence.CodeValue !== - CodeNameCodeSequenceValues.ImagingMeasurementReport + CodeNameCodeSequenceValues.ImagingMeasurementReport ) { console.warn( 'Only support Imaging Measurement Report SRs (TID1500) for now' @@ -282,7 +282,7 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) { trackingUniqueIdentifier => { const mergedContentSequence = mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier + trackingUniqueIdentifier ]; const measurement = _processMeasurement(mergedContentSequence); @@ -322,7 +322,7 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers( if ( mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier + trackingUniqueIdentifier ] === undefined ) { // Add the full ContentSequence @@ -448,9 +448,9 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { const cornerstoneFreeTextFinding = Findings.find( Finding => Finding.ConceptCodeSequence.CodingSchemeDesignator === - CORNERSTONE_CODING_SCHEME_DESIGNATOR && + CORNERSTONE_CODING_SCHEME_DESIGNATOR && Finding.ConceptCodeSequence.CodeValue === - CORNERSTONE_FREETEXT_CODE_VALUE + CORNERSTONE_FREETEXT_CODE_VALUE ); if (cornerstoneFreeTextFinding) { measurement.labels.push({ diff --git a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js index 24ae3deca..263eca1f3 100644 --- a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js +++ b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js @@ -1,12 +1,20 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { StudySummary, MeasurementTable } from '@ohif/ui'; -import { DicomMetadataStore, DICOMSR } from '@ohif/core'; +import { + StudySummary, + MeasurementTable, + Dialog, + Input, + useViewportGrid, +} from '@ohif/ui'; +import { DicomMetadataStore, DICOMSR, utils } from '@ohif/core'; import { useDebounce } from '@hooks'; import ActionButtons from './ActionButtons'; import { useTrackedMeasurements } from '../../getContextModule'; import createReportAsync from './../../_shared/createReportAsync.js'; +const { formatDate } = utils; + const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = { key: undefined, // date: undefined, // '07-Sep-2010', @@ -15,6 +23,7 @@ const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = { }; function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { + const [viewportGrid, viewportGridService] = useViewportGrid(); const [measurementChangeTimestamp, setMeasurementsUpdated] = useState( Date.now().toString() ); @@ -22,11 +31,8 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { measurementChangeTimestamp, 200 ); - const { MeasurementService } = servicesManager.services; - const [ - trackedMeasurements, - sendTrackedMeasurementsEvent, - ] = useTrackedMeasurements(); + const { MeasurementService, UINotificationService, UIDialogService, DisplaySetService } = servicesManager.services; + const [trackedMeasurements, sendTrackedMeasurementsEvent] = useTrackedMeasurements(); const { trackedStudy, trackedSeries } = trackedMeasurements.context; const [displayStudySummary, setDisplayStudySummary] = useState( DISPLAY_STUDY_SUMMARY_INITIAL_VALUE @@ -53,25 +59,37 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { debouncedMeasurementChangeTimestamp, ]); - // ~~ DisplayStudySummary - useEffect(() => { + const updateDisplayStudySummary = async () => { if (trackedMeasurements.matches('tracking')) { const StudyInstanceUID = trackedStudy; const studyMeta = DicomMetadataStore.getStudy(StudyInstanceUID); const instanceMeta = studyMeta.series[0].instances[0]; - const { Modality, StudyDate, StudyDescription } = instanceMeta; + const { StudyDate, StudyDescription } = instanceMeta; + + const modalities = new Set(); + studyMeta.series.forEach(series => { + if (trackedSeries.includes(series.SeriesInstanceUID)) { + modalities.add(series.instances[0].Modality); + } + }); + const modality = Array.from(modalities).join('/'); if (displayStudySummary.key !== StudyInstanceUID) { setDisplayStudySummary({ key: StudyInstanceUID, date: StudyDate, // TODO: Format: '07-Sep-2010' - modality: Modality, + modality, description: StudyDescription, }); } } else if (trackedStudy === '' || trackedStudy === undefined) { setDisplayStudySummary(DISPLAY_STUDY_SUMMARY_INITIAL_VALUE); } + }; + + // ~~ DisplayStudySummary + useEffect(() => { + updateDisplayStudySummary(); }, [displayStudySummary.key, trackedMeasurements, trackedStudy]); // TODO: Better way to consolidated, debounce, check on change? @@ -129,12 +147,106 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { DICOMSR.downloadReport(trackedMeasurements, dataSource); } + const jumpToImage = ({ id, isActive }) => { + const measurement = MeasurementService.getMeasurement(id); + const { referenceSeriesUID, SOPInstanceUID } = measurement; + + const displaySets = DisplaySetService.getDisplaySetsForSeries(referenceSeriesUID); + const displaySet = displaySets.find(ds => { + return ds.images && ds.images.some(i => i.SOPInstanceUID === SOPInstanceUID) + }); + + const imageIndex = displaySet.images.map(i => i.SOPInstanceUID).indexOf(SOPInstanceUID); + + viewportGridService.setDisplaysetForViewport({ + viewportIndex: viewportGrid.activeViewportIndex, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + imageIndex + }); + + onMeasurementItemClickHandler({ id, isActive }); + }; + + const onMeasurementItemEditHandler = ({ id }) => { + const measurement = MeasurementService.getMeasurement(id); + + let dialogId; + const onSubmitHandler = ({ action, value }) => { + switch (action.id) { + case 'save': { + MeasurementService.update(id, { + ...measurement, + ...value + }); + UINotificationService.show({ + title: 'Measurements', + message: 'Label updated successfully', + type: 'success' + }); + } + } + UIDialogService.dismiss({ id: dialogId }); + }; + dialogId = UIDialogService.create({ + centralize: true, + isDraggable: false, + useLastPosition: false, + showOverlay: true, + content: Dialog, + contentProps: { + title: 'Enter your annotation', + noCloseButton: true, + value: { label: measurement.label || '' }, + body: ({ value, setValue }) => { + const onChangeHandler = (event) => { + event.persist(); + setValue(value => ({ ...value, label: event.target.value })); + }; + + const onKeyPressHandler = event => { + if (event.key === 'Enter') { + onSubmitHandler({ value, action: { id: 'save' } }); + } + }; + return ( +
+ +
+ ); + }, + actions: [ + { id: 'cancel', text: 'Cancel', type: 'secondary' }, + { id: 'save', text: 'Save', type: 'primary' }, + ], + onSubmit: onSubmitHandler + } + }); + }; + + const onMeasurementItemClickHandler = ({ id, isActive }) => { + if (!isActive) { + const measurements = [...displayMeasurements]; + const measurement = measurements.find(m => m.id === id); + measurements.forEach(m => m.isActive = m.id !== id ? false : true); + measurement.isActive = true; + setDisplayMeasurements(measurements); + } + }; + return ( <>
{displayStudySummary.key && ( @@ -143,8 +255,8 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { title="Measurements" amount={displayMeasurements.length} data={displayMeasurements} - onClick={() => { }} - onEdit={id => alert(`Edit: ${id}`)} + onClick={jumpToImage} + onEdit={onMeasurementItemEditHandler} />
@@ -187,8 +299,8 @@ function _mapMeasurementToDisplay(measurement, index, types) { const { PixelSpacing, SeriesNumber, InstanceNumber } = instance; return { - id: index + 1, - label: '(empty)', // 'Label short description', + id: measurement.id, + label: measurement.label || '(empty)', displayText: _getDisplayText( measurement, diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js index 1b639282f..1b5b017f0 100644 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js @@ -33,7 +33,6 @@ const BaseAnnotationTool = cornerstoneTools.importInternal( ); // const cine = viewportSpecificData.cine; - // isPlaying = cine.isPlaying === true; // frameRate = cine.cineFrameRate || frameRate; @@ -47,7 +46,10 @@ function TrackedCornerstoneViewport({ ToolBarService, }) { const [trackedMeasurements] = useTrackedMeasurements(); - const [{ activeViewportIndex, viewports }] = useViewportGrid(); + const [ + { activeViewportIndex, viewports }, + viewportGridService, + ] = useViewportGrid(); // viewportIndex, onSubmit const [viewportDialogState, viewportDialogApi] = useViewportDialog(); const [viewportData, setViewportData] = useState(null); @@ -159,16 +161,15 @@ function TrackedCornerstoneViewport({ ); } - _getViewportData(dataSource, displaySet).then(viewportData => { - setViewportData({ ...viewportData }); - }); - }, [ - dataSource, - displaySet, - displaySet.StudyInstanceUID, - displaySet.displaySetInstanceUID, - displaySet.frameIndex, - ]); + /* + * This grabs `imageIndex from first matching + * We actually want whichever is at our `viewportIndex` + */ + const { imageIndex } = viewports[viewportIndex]; + displaySet.imageIndex = imageIndex; + + _getViewportData(dataSource, displaySet).then(setViewportData); + }, [dataSource, displaySet, viewports, viewportIndex]); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ let childrenWithProps = null; @@ -268,8 +269,8 @@ function TrackedCornerstoneViewport({ spacing: PixelSpacing && PixelSpacing.length ? `${PixelSpacing[0].toFixed(2)}mm x ${PixelSpacing[1].toFixed( - 2 - )}mm` + 2 + )}mm` : '', scanner: ManufacturerModelName || '', }, @@ -282,6 +283,14 @@ function TrackedCornerstoneViewport({ viewportIndex={viewportIndex} imageIds={imageIds} imageIdIndex={currentImageIdIndex} + onNewImageDebounceTime={700} + onNewImageDebounced={({ currentImageIdIndex }) => { + viewportGridService.setDisplaysetForViewport({ + viewportIndex: activeViewportIndex, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + imageIndex: currentImageIdIndex, + }); + }} // TODO: ViewportGrid Context? isActive={true} // todo isStackPrefetchEnabled={true} // todo @@ -336,7 +345,7 @@ const _viewportLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']; * @return {Object} CornerstoneTools Stack */ function _getCornerstoneStack(displaySet, dataSource) { - const { frameIndex } = displaySet; + const { imageIndex } = displaySet; // Get stack from Stack Manager const storedStack = StackManager.findOrCreateStack(displaySet, dataSource); @@ -344,17 +353,22 @@ function _getCornerstoneStack(displaySet, dataSource) { // Clone the stack here so we don't mutate it const stack = Object.assign({}, storedStack); - stack.currentImageIdIndex = frameIndex; + stack.currentImageIdIndex = imageIndex; return stack; } +/** + * Builds the viewport data from a datasource and a displayset. + * + * @param {Object} dataSource + * @param {Object} displaySet + * @return {Object} viewport data + */ async function _getViewportData(dataSource, displaySet) { - let viewportData; - const stack = _getCornerstoneStack(displaySet, dataSource); - viewportData = { + const viewportData = { StudyInstanceUID: displaySet.StudyInstanceUID, displaySetInstanceUID: displaySet.displaySetInstanceUID, stack, diff --git a/extensions/measurement-tracking/src/viewports/ViewportOverlay.js b/extensions/measurement-tracking/src/viewports/ViewportOverlay.js index b042743cf..f4ac84fb1 100644 --- a/extensions/measurement-tracking/src/viewports/ViewportOverlay.js +++ b/extensions/measurement-tracking/src/viewports/ViewportOverlay.js @@ -40,9 +40,9 @@ const ViewportOverlay = ({ {isWwwcActive && (
W: - {windowWidth.toFixed(0)} + {windowWidth.toFixed(0)} L: - {windowCenter.toFixed(0)} + {windowCenter.toFixed(0)}
)}
@@ -51,15 +51,13 @@ const ViewportOverlay = ({
I: - {`${instanceNumber}/${stackSize}`} + {`${instanceNumber} ${imageIndex}/${stackSize}`}
)} -
-
-
-
+
+
); }; diff --git a/extensions/vtk/src/OHIFVTKViewport.js b/extensions/vtk/src/OHIFVTKViewport.js index 6288cb9a0..f2647e0c5 100644 --- a/extensions/vtk/src/OHIFVTKViewport.js +++ b/extensions/vtk/src/OHIFVTKViewport.js @@ -60,7 +60,7 @@ class OHIFVTKViewport extends Component { displaySetInstanceUID: PropTypes.string, sopClassUIDs: PropTypes.arrayOf(PropTypes.string), SOPInstanceUID: PropTypes.string, - frameIndex: PropTypes.number, + imageIndex: PropTypes.number, }), }), viewportIndex: PropTypes.number, @@ -69,7 +69,7 @@ class OHIFVTKViewport extends Component { }; static defaultProps = { - onScroll: () => {}, + onScroll: () => { }, }; static id = 'OHIFVTKViewport'; @@ -88,7 +88,7 @@ class OHIFVTKViewport extends Component { StudyInstanceUID, displaySetInstanceUID, SOPInstanceUID, - frameIndex + imageIndex ) { // Create shortcut to displaySet const study = studies.find( @@ -105,8 +105,8 @@ class OHIFVTKViewport extends Component { // Clone the stack here so we don't mutate it const stack = Object.assign({}, storedStack); - if (frameIndex !== undefined) { - stack.currentImageIdIndex = frameIndex; + if (imageIndex !== undefined) { + stack.currentImageIdIndex = imageIndex; } else if (SOPInstanceUID) { const index = stack.imageIds.findIndex(imageId => { const imageIdSOPInstanceUID = cornerstone.metaData.get( @@ -133,7 +133,7 @@ class OHIFVTKViewport extends Component { displaySetInstanceUID, SOPClassUID, SOPInstanceUID, - frameIndex + imageIndex ) => { const stack = OHIFVTKViewport.getCornerstoneStack( studies, @@ -141,7 +141,7 @@ class OHIFVTKViewport extends Component { displaySetInstanceUID, SOPClassUID, SOPInstanceUID, - frameIndex + imageIndex ); const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID); @@ -270,7 +270,7 @@ class OHIFVTKViewport extends Component { displaySetInstanceUID, sopClassUIDs, SOPInstanceUID, - frameIndex, + imageIndex, } = displaySet; if (sopClassUIDs.length > 1) { @@ -302,7 +302,7 @@ class OHIFVTKViewport extends Component { StudyInstanceUID, displaySetInstanceUID, SOPInstanceUID, - frameIndex + imageIndex ); this.imageDataObject = imageDataObject; @@ -351,9 +351,9 @@ class OHIFVTKViewport extends Component { if ( displaySet.displaySetInstanceUID !== - prevDisplaySet.displaySetInstanceUID || + prevDisplaySet.displaySetInstanceUID || displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID || - displaySet.frameIndex !== prevDisplaySet.frameIndex + displaySet.imageIndex !== prevDisplaySet.imageIndex ) { this.setStateFromProps(); } diff --git a/extensions/vtk/src/commandsModule.js b/extensions/vtk/src/commandsModule.js index c43ba3c99..fe755921b 100644 --- a/extensions/vtk/src/commandsModule.js +++ b/extensions/vtk/src/commandsModule.js @@ -165,7 +165,7 @@ const commandsModule = ({ commandsManager }) => { segmentNumber, frameIndex, frame, - done = () => {}, + done = () => { }, }) => { let api = apis[viewports.activeViewportIndex]; diff --git a/platform/core/src/DICOMSR/parseDicomStructuredReport.js b/platform/core/src/DICOMSR/parseDicomStructuredReport.js index df5770364..84ab2d57b 100644 --- a/platform/core/src/DICOMSR/parseDicomStructuredReport.js +++ b/platform/core/src/DICOMSR/parseDicomStructuredReport.js @@ -39,7 +39,9 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => { const { _study: study, _series: series } = instanceMetadata; const { StudyInstanceUID, PatientID } = study; const { SeriesInstanceUID } = series; + /* TODO: Update frameIndex to imageIndex for measurements */ const { sopInstanceUid, frameIndex } = measurement; + const imagePath = getImagePath( StudyInstanceUID, SeriesInstanceUID, diff --git a/platform/core/src/services/MeasurementService/MeasurementService.js b/platform/core/src/services/MeasurementService/MeasurementService.js index b97c09c55..5972a9877 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.js @@ -282,6 +282,27 @@ class MeasurementService { } } + update(id, measurement) { + if (this.measurements[id]) { + const updatedMeasurement = { + ...measurement, + modifiedTimestamp: Math.floor(Date.now() / 1000) + }; + + log.info(`Updating measurement...`, updatedMeasurement); + + this.measurements[id] = updatedMeasurement; + + this._broadcastChange( + this.EVENTS.MEASUREMENT_UPDATED, + measurement.source, + updatedMeasurement + ); + + return updatedMeasurement.id; + } + } + /** * Add a raw measurement into a source so that it may be * Converted to/from annotation in the same way. E.g. import serialized data diff --git a/platform/ui/src/components/Dialog/Body.js b/platform/ui/src/components/Dialog/Body.js new file mode 100644 index 000000000..31fe0d36a --- /dev/null +++ b/platform/ui/src/components/Dialog/Body.js @@ -0,0 +1,25 @@ +import React from 'react'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; + +import { Typography } from '..'; + +const Body = ({ text, className }) => { + const theme = 'bg-primary-dark'; + return ( +
+
+ + {text} + +
+
+ ); +}; + +Body.propTypes = { + text: PropTypes.string, + className: PropTypes.string, +}; + +export default Body; diff --git a/platform/ui/src/components/Dialog/Dialog.jsx b/platform/ui/src/components/Dialog/Dialog.jsx index aa0295709..868b1b5c3 100644 --- a/platform/ui/src/components/Dialog/Dialog.jsx +++ b/platform/ui/src/components/Dialog/Dialog.jsx @@ -1,12 +1,83 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; -const Dialog = ({ children }) => { - return
{children}
; +import Footer from './Footer'; +import Body from './Body'; +import Header from './Header'; + +const Dialog = ({ + title, + text, + onClose, + noCloseButton, + actions, + onSubmit, + header: HeaderComponent, + body: BodyComponent, + footer: FooterComponent, + value: defaultValue +}) => { + const [value, setValue] = useState(defaultValue); + + const theme = 'bg-secondary-light'; + const flex = 'flex flex-col'; + const border = 'border-0 rounded-lg shadow-lg'; + const outline = 'outline-none focus:outline-none'; + const position = 'relative'; + const width = 'w-full'; + + return ( +
+ + + +
+ ); }; Dialog.propTypes = { - children: PropTypes.node, + title: PropTypes.string, + text: PropTypes.string, + onClose: PropTypes.func, + noCloseButton: PropTypes.bool, + header: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + body: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + footer: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + onSubmit: PropTypes.func.isRequired, + value: PropTypes.object, + actions: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + value: PropTypes.any, + type: PropTypes.oneOf(['primary', 'secondary', 'cancel']).isRequired, + }) + ).isRequired, +}; + +Dialog.defaultProps = { + header: Header, + footer: Footer, + body: Body, + value: {} }; export default Dialog; diff --git a/platform/ui/src/components/Dialog/Footer.js b/platform/ui/src/components/Dialog/Footer.js new file mode 100644 index 000000000..257c6d1c7 --- /dev/null +++ b/platform/ui/src/components/Dialog/Footer.js @@ -0,0 +1,57 @@ +import React, { useEffect } from 'react'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; + +import { Button } from '..'; + +const Footer = ({ actions, className, onSubmit, value }) => { + const flex = 'flex items-center justify-end'; + const border = 'border-t-2 border-solid border-black rounded-b'; + const spacing = 'p-6'; + const theme = 'bg-primary-dark'; + + return ( +
+ {actions.map((action, index) => { + const isFirst = index === 0; + const isPrimary = action.type === 'primary'; + + const onClickHandler = event => onSubmit({ action, value, event }); + + return ( + + ); + })} +
+ ); +}; + +const noop = () => { }; + +Footer.propTypes = { + className: PropTypes.string, + onSubmit: PropTypes.func.isRequired, + actions: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + value: PropTypes.any, + type: PropTypes.oneOf(['primary', 'secondary', 'cancel']).isRequired, + }) + ).isRequired, +}; + +Footer.defaultProps = { + onSubmit: noop, + actions: [] +}; + +export default Footer; diff --git a/platform/ui/src/components/Dialog/Header.js b/platform/ui/src/components/Dialog/Header.js new file mode 100644 index 000000000..72d292b9b --- /dev/null +++ b/platform/ui/src/components/Dialog/Header.js @@ -0,0 +1,50 @@ +import React from 'react'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; + +import { Typography, Icon } from '..'; + +const CloseButton = ({ onClick }) => { + const theme = 'bg-transparent fill-primary-active'; + const outline = 'outline-none focus:outline-none'; + const flex = 'flex h-full'; + + return ( + + ); +}; + +CloseButton.propTypes = { + onClick: PropTypes.func, +}; + +const Header = ({ title, noCloseButton, onClose }) => { + const theme = 'bg-secondary-main'; + const flex = 'flex items-center justify-between'; + const border = 'border-b-2 border-solid border-black rounded-t'; + const spacing = 'p-4'; + + return ( +
+ + {title} + + {!noCloseButton && } +
+ ); +}; + +Header.propTypes = { + className: PropTypes.string, + title: PropTypes.string, + noCloseButton: PropTypes.bool, + onClose: PropTypes.func, +}; + +Header.defaultProps = { + noCloseButton: false +}; + +export default Header; diff --git a/platform/ui/src/components/Input/Input.jsx b/platform/ui/src/components/Input/Input.jsx index 8f26ad4e7..96f17001c 100644 --- a/platform/ui/src/components/Input/Input.jsx +++ b/platform/ui/src/components/Input/Input.jsx @@ -21,6 +21,8 @@ const Input = ({ value, onChange, onFocus, + autoFocus, + onKeyPress, ...otherProps }) => { return ( @@ -32,10 +34,12 @@ const Input = ({ baseInputClasses, transparentClasses[transparent] )} + autoFocus type={type} value={value} onChange={onChange} onFocus={onFocus} + onKeyPress={onKeyPress} {...otherProps} /> @@ -52,6 +56,8 @@ Input.propTypes = { value: PropTypes.any, onChange: PropTypes.func, onFocus: PropTypes.func, + autoFocus: PropTypes.bool, + onKeyPress: PropTypes.func, }; export default Input; diff --git a/platform/ui/src/components/MeasurementTable/MeasurementItem.js b/platform/ui/src/components/MeasurementTable/MeasurementItem.js new file mode 100644 index 000000000..37da6340a --- /dev/null +++ b/platform/ui/src/components/MeasurementTable/MeasurementItem.js @@ -0,0 +1,87 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { Icon } from '@ohif/ui'; + +const MeasurementItem = ({ id, index, label, displayText, isActive, onClick, onEdit }) => { + const [isHovering, setIsHovering] = useState(false); + + const onEditHandler = event => { + event.stopPropagation(); + onEdit({ id, isActive, event }); + }; + + const onClickHandler = event => onClick({ id, isActive, event }); + + const onMouseEnter = () => setIsHovering(true); + const onMouseLeave = () => setIsHovering(false); + + return ( +
+
+ {index} +
+
+ + {label} + + {displayText.map(line => ( + + {line} + + ))} + +
+
+ ); +}; + +MeasurementItem.propTypes = { + id: PropTypes.oneOfType([PropTypes.number.isRequired, PropTypes.string.isRequired]), + index: PropTypes.number.isRequired, + label: PropTypes.string, + displayText: PropTypes.array.isRequired, + isActive: PropTypes.bool, + onClick: PropTypes.func, + onEdit: PropTypes.func +}; + +MeasurementItem.defaultProps = { + isActive: false, +}; + +export default MeasurementItem; diff --git a/platform/ui/src/components/MeasurementTable/MeasurementTable.jsx b/platform/ui/src/components/MeasurementTable/MeasurementTable.jsx index 9898c504d..70efc44c6 100644 --- a/platform/ui/src/components/MeasurementTable/MeasurementTable.jsx +++ b/platform/ui/src/components/MeasurementTable/MeasurementTable.jsx @@ -1,7 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { Icon } from '@ohif/ui'; + +import MeasurementItem from './MeasurementItem'; const MeasurementTable = ({ data, title, amount, onClick, onEdit }) => { return ( @@ -13,66 +14,18 @@ const MeasurementTable = ({ data, title, amount, onClick, onEdit }) => { {amount}
- {!!data.length && - data.map(measurementItem => { - const { id, label, displayText, isActive } = measurementItem; - return ( -
onClick(measurementItem.id)} - onKeyDown={() => onClick(measurementItem.id)} - role="button" - tabIndex="0" - > -
- {id} -
-
- - {label} - - {displayText.map(line => ( - - {line} - - ))} - { - // stopPropagation needed to avoid disable the current active item - e.stopPropagation(); - onEdit(id); - }} - /> -
-
- ); - })} + {data.length && data.map((measurementItem, index) => ( + + ))} {!data.length && (
{ MeasurementTable.defaultProps = { amount: null, data: [], - onClick: () => {}, - onEdit: () => {}, + onClick: () => { }, + onEdit: () => { }, }; MeasurementTable.propTypes = { diff --git a/platform/ui/src/components/StudySummary/StudySummary.mdx b/platform/ui/src/components/StudySummary/StudySummary.mdx deleted file mode 100644 index e69de29bb..000000000 diff --git a/platform/ui/src/contextProviders/DialogProvider.jsx b/platform/ui/src/contextProviders/DialogProvider.jsx index 58ed46432..ec57f0232 100644 --- a/platform/ui/src/contextProviders/DialogProvider.jsx +++ b/platform/ui/src/contextProviders/DialogProvider.jsx @@ -4,6 +4,7 @@ import React, { useContext, useCallback, useEffect, + useRef } from 'react'; import PropTypes from 'prop-types'; @@ -251,12 +252,18 @@ const DialogProvider = ({ children, service }) => { }); }; + const onKeyDownHandler = event => { + if (event.key === "Escape") { + dismissAll(); + } + }; + const validCallback = callback => callback && typeof callback === 'function'; return ( {!isEmpty() && -
+
{renderDialogs()}
} diff --git a/platform/ui/src/contextProviders/ViewportGridProvider.jsx b/platform/ui/src/contextProviders/ViewportGridProvider.jsx index a522ea165..6c5c1120e 100644 --- a/platform/ui/src/contextProviders/ViewportGridProvider.jsx +++ b/platform/ui/src/contextProviders/ViewportGridProvider.jsx @@ -27,10 +27,10 @@ export function ViewportGridProvider({ children, service }) { return { ...state, ...{ activeViewportIndex: action.payload } }; } case 'SET_DISPLAYSET_FOR_VIEWPORT': { - const { viewportIndex, displaySetInstanceUID } = action.payload; + const { viewportIndex, displaySetInstanceUID, imageIndex } = action.payload; const viewports = state.viewports.slice(); - viewports[viewportIndex] = { displaySetInstanceUID }; + viewports[viewportIndex] = { displaySetInstanceUID, imageIndex }; return { ...state, ...{ viewports } }; } @@ -69,12 +69,13 @@ export function ViewportGridProvider({ children, service }) { [dispatch] ); const setDisplaysetForViewport = useCallback( - ({ viewportIndex, displaySetInstanceUID }) => + ({ viewportIndex, displaySetInstanceUID, imageIndex }) => dispatch({ type: 'SET_DISPLAYSET_FOR_VIEWPORT', payload: { viewportIndex, displaySetInstanceUID, + imageIndex }, }), [dispatch] diff --git a/platform/viewer/src/appExtensions/GenericViewerCommands/commandsModule.js b/platform/viewer/src/appExtensions/GenericViewerCommands/commandsModule.js deleted file mode 100644 index e7e122cda..000000000 --- a/platform/viewer/src/appExtensions/GenericViewerCommands/commandsModule.js +++ /dev/null @@ -1,140 +0,0 @@ -import { redux } from '@ohif/core'; -import store from './../../store'; - -const commandsModule = ({ commandsManager }) => { - const { setViewportActive, setActiveViewportSpecificData } = redux.actions; - - const actions = { - updateActiveViewport: ({ viewports, direction }) => { - const { viewportSpecificData, activeViewportIndex } = viewports; - const maxIndex = Object.keys(viewportSpecificData).length - 1; - - let newIndex = activeViewportIndex + direction; - newIndex = newIndex > maxIndex ? 0 : newIndex; - newIndex = newIndex < 0 ? maxIndex : newIndex; - - store.dispatch(setViewportActive(newIndex)); - }, - setWindowLevelPreset: ({ viewports, preset }) => { - const state = store.getState(); - const { preferences = {} } = state; - const { window, level } = - preferences.windowLevelData && preferences.windowLevelData[preset]; - - if (window && level) { - commandsManager.runCommand('setWindowLevel', { - viewports, - window, - level, - }); - } - }, - updateViewportDisplaySet: ({ viewports, direction }) => { - const viewportSpecificData = { ...viewports.viewportSpecificData }; - const activeViewport = - viewportSpecificData[viewports.activeViewportIndex]; - const studyMetadata = utils.studyMetadataManager.get( - activeViewport.StudyInstanceUID - ); - - if (!studyMetadata) { - return; - } - - const allDisplaySets = studyMetadata.getDisplaySets(); - const currentDisplaySetIndex = allDisplaySets.findIndex( - displaySet => - displaySet.displaySetInstanceUID === - activeViewport.displaySetInstanceUID - ); - if (currentDisplaySetIndex < 0) { - return; - } - - const newDisplaySetIndex = currentDisplaySetIndex + direction; - const newDisplaySetData = allDisplaySets[newDisplaySetIndex]; - if (!newDisplaySetData) { - return; - } - - store.dispatch(setActiveViewportSpecificData(newDisplaySetData)); - }, - }; - - const definitions = { - // Next/Previous active viewport - incrementActiveViewport: { - commandFn: actions.updateActiveViewport, - storeContexts: ['viewports'], - options: { direction: 1 }, - }, - decrementActiveViewport: { - commandFn: actions.updateActiveViewport, - storeContexts: ['viewports'], - options: { direction: -1 }, - }, - // Window level Presets - windowLevelPreset1: { - commandFn: actions.setWindowLevelPreset, - storeContexts: ['viewports'], - options: { preset: 1 }, - }, - windowLevelPreset2: { - commandFn: actions.setWindowLevelPreset, - storeContexts: ['viewports'], - options: { preset: 2 }, - }, - windowLevelPreset3: { - commandFn: actions.setWindowLevelPreset, - storeContexts: ['viewports'], - options: { preset: 3 }, - }, - windowLevelPreset4: { - commandFn: actions.setWindowLevelPreset, - storeContexts: ['viewports'], - options: { preset: 4 }, - }, - windowLevelPreset5: { - commandFn: actions.setWindowLevelPreset, - storeContexts: ['viewports'], - options: { preset: 5 }, - }, - windowLevelPreset6: { - commandFn: actions.setWindowLevelPreset, - storeContexts: ['viewports'], - options: { preset: 6 }, - }, - windowLevelPreset7: { - commandFn: actions.setWindowLevelPreset, - storeContexts: ['viewports'], - options: { preset: 7 }, - }, - windowLevelPreset8: { - commandFn: actions.setWindowLevelPreset, - storeContexts: ['viewports'], - options: { preset: 8 }, - }, - windowLevelPreset9: { - commandFn: actions.setWindowLevelPreset, - storeContexts: ['viewports'], - options: { preset: 9 }, - }, - nextViewportDisplaySet: { - commandFn: actions.updateViewportDisplaySet, - storeContexts: ['viewports'], - options: { direction: 1 }, - }, - previousViewportDisplaySet: { - commandFn: actions.updateViewportDisplaySet, - storeContexts: ['viewports'], - options: { direction: -1 }, - }, - }; - - return { - definitions, - defaultContext: 'VIEWER', - }; -}; - -export default commandsModule; diff --git a/platform/viewer/src/appExtensions/MeasurementsPanel/jumpToRowItem.js b/platform/viewer/src/appExtensions/MeasurementsPanel/jumpToRowItem.js deleted file mode 100644 index 4a95119cd..000000000 --- a/platform/viewer/src/appExtensions/MeasurementsPanel/jumpToRowItem.js +++ /dev/null @@ -1,118 +0,0 @@ -import { measurements, utils } from '@ohif/core'; - -const { MeasurementApi } = measurements; -const { studyMetadataManager } = utils; - -// TODO: Move this function to OHIF itself so we can use it on the OHIF measurment table (when it is finished) - -/** - * Activates a set of measurements - * - * @param measurementData - * @param viewportsState - * @param timepointManagerState - * @param options - */ -export default function jumpToRowItem( - measurementData, - viewportsState, - timepointManagerState, - options = { invertViewportTimepointsOrder: false, childToolKey: null } -) { - const numViewports = viewportsState.layout.viewports.length; - const numTimepoints = timepointManagerState.timepoints.length; - const { measurements, timepoints } = timepointManagerState; - const numViewportsToUpdate = Math.min(numTimepoints, numViewports); - const { toolType, measurementNumber } = measurementData; - - if (options.invertViewportTimepointsOrder) { - timepoints.reverse(); - } - - const measurementsForToolGroup = measurements[toolType]; - - // Retrieve the measurements data - const measurementsToJumpTo = []; - for (let i = 0; i < numViewportsToUpdate; i++) { - const { timepointId } = timepoints[i]; - - const dataAtThisTimepoint = measurementsForToolGroup.find(entry => { - return ( - entry.timepointId === timepointId && - entry.measurementNumber === measurementNumber - ); - }); - - if (!dataAtThisTimepoint) { - measurementsToJumpTo.push(null); - continue; - } - - let measurement = dataAtThisTimepoint; - - const { tool } = MeasurementApi.getToolConfiguration(toolType); - if (options.childToolKey) { - measurement = dataAtThisTimepoint[options.childToolKey]; - } else if (Array.isArray(tool.childTools)) { - const key = tool.childTools.find(key => !!dataAtThisTimepoint[key]); - measurement = dataAtThisTimepoint[key]; - } - - measurementsToJumpTo.push(measurement); - } - - // TODO: Add a single viewports state action which allows - // - viewportData to be set - // - layout to be set - // - activeViewport to be set - - // Needs to update viewports.viewportData state to set image set data - - const displaySetContainsSopInstance = (displaySet, SOPInstanceUID) => { - if (!displaySet.images || !displaySet.images.length) { - return; - } - - return displaySet.images.find( - image => image.getSOPInstanceUID() === SOPInstanceUID - ); - }; - - const viewportSpecificData = []; - measurementsToJumpTo.forEach((data, viewportIndex) => { - // Skip if there is no measurement to jump - if (!data) { - return; - } - - const study = studyMetadataManager.get(data.StudyInstanceUID); - if (!study) { - throw new Error('Study not found.'); - } - - const displaySet = study.findDisplaySet(displaySet => { - return displaySetContainsSopInstance(displaySet, data.SOPInstanceUID); - }); - - if (!displaySet) { - throw new Error('Display set not found.'); - } - - displaySet.SOPInstanceUID = data.SOPInstanceUID; - if (data.frameIndex) { - displaySet.frameIndex = data.frameIndex; - } - - viewportIndex = - (viewportIndex + viewportsState.activeViewportIndex) % numViewports; - viewportSpecificData.push({ - viewportIndex, - displaySet, - }); - }); - - return { - viewportSpecificData, - layout: [], // TODO: if we need to change layout, we should return this here - }; -} diff --git a/platform/viewer/src/components/EmptyViewport.jsx b/platform/viewer/src/components/EmptyViewport.jsx index 70c684cfc..a6fa6f3ac 100644 --- a/platform/viewer/src/components/EmptyViewport.jsx +++ b/platform/viewer/src/components/EmptyViewport.jsx @@ -1,11 +1,7 @@ import React from 'react'; function EmptyViewport() { - return ( -
-

Please drag a stack here to view images.

-
- ); + return
; } export default EmptyViewport; diff --git a/platform/viewer/src/components/Header/Header.css b/platform/viewer/src/components/Header/Header.css deleted file mode 100644 index 7d8d1624d..000000000 --- a/platform/viewer/src/components/Header/Header.css +++ /dev/null @@ -1,142 +0,0 @@ -/* Viewer Route */ -.entry-header { - padding: 10px 15px; - height: var(--top-bar-height); -} - -.header-left-box { - display: inline-block; - position: relative; -} - -.header-brand { - text-decoration: none; - height: 30px; - float: left; - clear: both; -} - -/* Home Page */ -.entry-header.header-big { - background: rgba(21, 25, 30, 0.7); - padding: 35px var(--study-list-padding); - height: auto; - display: inline-block; - width: 100%; -} - -.entry-header.header-big .header-brand { - height: 50px; - padding: 0; -} - -.entry-header.header-big .header-logo-image { - margin-right: 20px; - width: 50px; - height: 50px; -} - -.entry-header.header-big .header-logo-text { - width: 474px; - height: auto; -} - -/** OTHER STYLES **/ - -.dd-item { - width: 100%; -} - -.header-viewerLink { - float: left; -} - -.header-logo-image { - fill: transparent; - height: 100%; - margin: 0 8px 0 0; - width: 30px; - float: left; -} - -.header-btn { - color: var(--text-secondary-color); - cursor: pointer; - font-size: 13px; - font-weight: 500; - line-height: 26px; -} - -.header-btn:hover { - color: var(--hover-color); -} - -.header-btn:active { - color: var(--active-color); -} - -.header-studyListLinkSection { - border-left: var(--ui-border-thickness) solid var(--ui-border-color); - margin: 3px 0 0 10px; - padding: 0 0 0 10px; - display: inline-block; -} - -.header-menu { - color: var(--text-primary-color); - float: right; - padding: 4px 0; - font-size: 13px; - font-weight: 400; - line-height: 18px; - text-decoration: none; - margin-right: 10px; -} - -.entry-header.header-big .header-menu { - margin-right: 0; -} - -.header-menu .research-use { - float: left; - font-size: 13px; - font-weight: 700; - color: #91b9cd; - margin-right: 1rem; -} - -.notification-bar { - display: none; - position: absolute; - height: 20px; - line-height: 20px; - width: 100%; - background-color: #91b9cd; - color: #ffffff; - font-weight: bold; - text-align: center; -} - -@media only screen and (max-width: 768px) { - .entry-header, - .entry-header.header-big { - padding: 30px 15px 10px 15px; - } - .entry-header.header-big .header-logo-image { - margin: 0 10px 0 0; - width: 25px; - height: 25px; - } - - .entry-header.header-big .header-logo-text { - width: 40%; - } - - /* Toggle Notification Bar */ - .notification-bar { - display: block; - } - .header-menu .research-use { - display: none; - } -} diff --git a/platform/viewer/src/components/Header/Header.js b/platform/viewer/src/components/Header/Header.js deleted file mode 100644 index 19b7c76c0..000000000 --- a/platform/viewer/src/components/Header/Header.js +++ /dev/null @@ -1,124 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Link, withRouter } from 'react-router-dom'; -import { withTranslation } from 'react-i18next'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { Dropdown, AboutContent, withModal } from '@ohif/ui'; -// -import { UserPreferences } from './../UserPreferences'; -import OHIFLogo from '../OHIFLogo/OHIFLogo.js'; -import './Header.css'; - -function Header(props) { - const { - t, - user, - userManager, - modal: { show }, - useLargeLogo, - linkPath, - linkText, - location, - children, - } = props; - - const [options, setOptions] = useState([]); - const hasLink = linkText && linkPath; - - useEffect(() => { - const optionsValue = [ - { - title: t('About'), - icon: { name: 'info' }, - onClick: () => - show({ - content: AboutContent, - title: t('OHIF Viewer - About'), - }), - }, - { - title: t('Preferences'), - icon: { - name: 'user', - }, - onClick: () => - show({ - content: UserPreferences, - title: t('User Preferences'), - }), - }, - ]; - - if (user && userManager) { - optionsValue.push({ - title: t('Logout'), - icon: { name: 'power-off' }, - onClick: () => userManager.signoutRedirect(), - }); - } - - setOptions(optionsValue); - }, [setOptions, show, t, user, userManager]); - - return ( - <> -
{t('INVESTIGATIONAL USE ONLY')}
-
-
- {location && location.studyLink && ( - - {t('Back to Viewer')} - - )} - - {children} - - {hasLink && ( - - {t(linkText)} - - )} -
- -
- {t('INVESTIGATIONAL USE ONLY')} - -
-
- - ); -} - -Header.propTypes = { - // Study list, / - linkText: PropTypes.string, - linkPath: PropTypes.string, - useLargeLogo: PropTypes.bool, - // - location: PropTypes.object.isRequired, - children: PropTypes.node, - t: PropTypes.func.isRequired, - userManager: PropTypes.object, - user: PropTypes.object, - modal: PropTypes.object, -}; - -Header.defaultProps = { - useLargeLogo: false, - children: OHIFLogo(), -}; - -export default withTranslation(['Header', 'AboutModal'])( - withRouter(withModal(Header)) -); diff --git a/platform/viewer/src/components/SidePanel.css b/platform/viewer/src/components/SidePanel.css deleted file mode 100644 index 891ffefca..000000000 --- a/platform/viewer/src/components/SidePanel.css +++ /dev/null @@ -1,48 +0,0 @@ -.FlexboxLayout { - display: flex; - flex: 1; - flex-flow: row nowrap; - align-items: stretch; - height: calc(100% - var(--toolbar-height) - var(--top-bar-height)); - width: 100%; - overflow: hidden; -} - -.sidepanel { - flex: 1; - height: 100%; - transition: var(--sidepanel-transition); - overflow-y: auto; -} - -.from-left { - border-right: var(--ui-border-thickness) solid var(--ui-border-color); - margin-left: calc(var(--left-sidepanel-menu-width) * -1); - max-width: var(--left-sidepanel-menu-width); -} - -.from-right { - border-left: var(--ui-border-thickness) solid var(--ui-border-color); - margin-right: calc(var(--right-sidepanel-menu-width) * -1); - max-width: var(--right-sidepanel-menu-width); -} - -.sidepanel.is-open { - margin-right: 0; - margin-left: 0; -} - -.main-content { - flex: 1; - height: 100%; - overflow: hidden; - transition: var(--sidepanel-transition); - width: 100%; -} - -@media only screen and (max-width: 768px) { - /* Account for "Investigational Use" banner height */ - .FlexboxLayout { - height: calc(100% - var(--toolbar-height) - var(--top-bar-height) - 16px); - } -} diff --git a/platform/viewer/src/customHooks/useQuery.js b/platform/viewer/src/customHooks/useQuery.js deleted file mode 100644 index 6a2ea87a9..000000000 --- a/platform/viewer/src/customHooks/useQuery.js +++ /dev/null @@ -1,11 +0,0 @@ -import { useLocation } from 'react-router-dom'; - -/** - * hook that builds on useLocation to parse - * the query string for you. - * - * @name useQuery - */ -export default function () { - return new URLSearchParams(useLocation().search); -} diff --git a/yarn.lock b/yarn.lock index 19aeaf498..080570257 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7364,10 +7364,10 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dcmjs@0.14.1: - version "0.14.1" - resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.14.1.tgz#4a4f18d1e5332d38429e2d77340a6ad0052ec71e" - integrity sha512-GGGQt4zjX7Fp0rLO5rcIQgmxZdLsEwBSZ1T74iXeJG5W32P1yiodJfBW900tNZCKj3zWamO3f5jDC1Xkv81fdg== +dcmjs@0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.14.2.tgz#bc4926c3312632ec61a5d68753c0857aebb65793" + integrity sha512-W4/f34Ph6Nrc95QhL/7f1kPlfR2YJMbyEsbPELt3xml17SFnkEgXAeO41qHZ7KywozO/lBW5sXGOpMenuYVV2g== dependencies: "@babel/polyfill" "^7.8.3" "@babel/runtime" "^7.8.4" @@ -17890,20 +17890,20 @@ react-clientside-effect@^1.2.2: dependencies: "@babel/runtime" "^7.0.0" -react-cornerstone-viewport@2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-2.3.8.tgz#7af8360f29bca986ae4e36b4e503269b88ddc52f" - integrity sha512-aiG2uVNrDY6SQx4t/HBxIA3zsMsCwT+6TpcXK9qSSoXhs+X6OTmYEKncWUqL0jtxU1yfh6JTUz8ARTg03gtF+A== +react-cornerstone-viewport@2.3.9: + version "2.3.9" + resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-2.3.9.tgz#f9761da8e536f0a217137c6ca1a983f5882249f9" + integrity sha512-qrhq8CbX/jq6b93cQjV2qC/mhHOvFBpxzxcHlvHzEQZt/rmRMcaYxCqjpNaNbUmmBu61wNkxesUVsggkPTTcqg== dependencies: classnames "^2.2.6" date-fns "^2.2.1" prop-types "^15.7.2" react-resize-detector "^4.2.1" -react-cornerstone-viewport@2.3.9: - version "2.3.9" - resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-2.3.9.tgz#f9761da8e536f0a217137c6ca1a983f5882249f9" - integrity sha512-qrhq8CbX/jq6b93cQjV2qC/mhHOvFBpxzxcHlvHzEQZt/rmRMcaYxCqjpNaNbUmmBu61wNkxesUVsggkPTTcqg== +react-cornerstone-viewport@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-3.0.0.tgz#c7be6dc29762fc844c639a73c3292feb69e31ba5" + integrity sha512-hLneVVOrZZlCkIhsKVdIHBGVPbOmeu5tsrPXaeCfsOlA5y7JhtacjuKHL4xzGmlXiCipftgiZ9+4gC2gcJSlcw== dependencies: classnames "^2.2.6" date-fns "^2.2.1"