diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.jsx b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.jsx new file mode 100644 index 000000000..b8d2c72e1 --- /dev/null +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.jsx @@ -0,0 +1,81 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import { Machine } from 'xstate'; +import { useMachine } from '@xstate/react'; +import { + machineConfiguration, + defaultOptions, +} from './measurementTrackingMachine'; + +const TrackedMeasurementsContext = React.createContext(); +TrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext'; +const useTrackedMeasurements = () => useContext(TrackedMeasurementsContext); + +/** + * + * @param {*} param0 + */ +function TrackedMeasurementsContextProvider( + UIViewportDialogService, + { children } +) { + function promptUser() { + // TODO: ... ActiveViewport? Or Study + Series --> Viewport? + // Let's just use zero for meow? + return new Promise(function(resolve, reject) { + const handleSubmit = result => { + UIViewportDialogService.hide(); + resolve(result); + }; + + UIViewportDialogService.show({ + viewportIndex: 0, + type: 'info', + message: 'Would you like to track?', + actions: [ + { type: 'cancel', text: 'No', value: 0 }, + { type: 'secondary', text: 'No, do not ask again', value: -1 }, + { type: 'primary', text: 'Yes', value: 1 }, + ], + onSubmit: handleSubmit, + }); + }); + } + + const machOptions = Object.assign({}, defaultOptions); + // Merge services + machOptions.services = Object.assign({}, machOptions.services, { + shouldTrackPrompt: promptUser, + }); + + console.log( + '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~', + machOptions + ); + + const measurementTrackingMachine = Machine(machineConfiguration, machOptions); + + const [ + trackedMeasurements, + sendTrackedMeasurementsEvent, + trackedMeasurementsService, + ] = useMachine(measurementTrackingMachine); + + return ( + + {children} + + ); +} + +TrackedMeasurementsContextProvider.propTypes = { + children: PropTypes.oneOf([PropTypes.func, PropTypes.node]), +}; + +export { + TrackedMeasurementsContext, + TrackedMeasurementsContextProvider, + useTrackedMeasurements, +}; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js index ca158c1de..1385a7f84 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js @@ -1,45 +1,5 @@ -import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; -import { useMachine } from '@xstate/react'; -import { - measurementTrackingMachine, - defaultOptions, -} from './measurementTrackingMachine'; - -const TrackedMeasurementsContext = React.createContext(); - -TrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext'; - -const useTrackedMeasurements = () => useContext(TrackedMeasurementsContext); - -/** - * - * @param {*} param0 - */ -function TrackedMeasurementsContextProvider({ children }) { - // TODO: Configure `UIViewportNotificationService` for appropriate actions - const measurementTrackingMachineOptions = Object.assign({}, defaultOptions); - const [ - trackedMeasurements, - sendTrackedMeasurementsEvent, - trackedMeasurementsService, - ] = useMachine(measurementTrackingMachine, measurementTrackingMachineOptions); - - return ( - - {children} - - ); -} - -TrackedMeasurementsContextProvider.propTypes = { - children: PropTypes.oneOf([PropTypes.func, PropTypes.node]), -}; - export { TrackedMeasurementsContext, TrackedMeasurementsContextProvider, useTrackedMeasurements, -}; +} from './TrackedMeasurementsContext.jsx'; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js index 21e9f2b35..1ba59ca65 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js @@ -27,7 +27,7 @@ const machineConfiguration = { prompt: { invoke: { id: 'shouldTrackPrompt', - src: () => confirmDialog('Should we start tracking?'), + src: 'shouldTrackPrompt', onDone: { target: 'validateResponse', actions: assign({ promptResponse: (ctx, evt) => evt.data }), @@ -135,6 +135,11 @@ function confirmDialog(msg) { } const defaultOptions = { + services: { + shouldTrackPrompt: () => { + return confirmDialog('Should we start tracking?'); + }, + }, actions: { clearContext: assign({ prevTrackedStudy: '', @@ -186,11 +191,13 @@ const defaultOptions = { }, }; -const measurementTrackingMachine = Machine(machineConfiguration, defaultOptions); +// const measurementTrackingMachine = Machine( +// machineConfiguration, +// defaultOptions +// ); // .transition(state, eventArgument).value // const service = interpret(measurementTrackingMachine).start(); // .send(event): nextState // .state (getter) // .onTransition(state => { state.vale }) -export { defaultOptions, machineConfiguration, measurementTrackingMachine }; -export default measurementTrackingMachine; +export { defaultOptions, machineConfiguration }; diff --git a/extensions/measurement-tracking/src/getContextModule.js b/extensions/measurement-tracking/src/getContextModule.js index bb0fd4043..573a00fa2 100644 --- a/extensions/measurement-tracking/src/getContextModule.js +++ b/extensions/measurement-tracking/src/getContextModule.js @@ -4,12 +4,18 @@ import { useTrackedMeasurements, } from './contexts'; -function getContextModule() { +function getContextModule({ servicesManager }) { + const { UIViewportDialogService } = servicesManager.services; + const BoundTrackedMeasurementsContextProvider = TrackedMeasurementsContextProvider.bind( + null, + UIViewportDialogService + ); + return [ { name: 'TrackedMeasurementsContext', context: TrackedMeasurementsContext, - provider: TrackedMeasurementsContextProvider, + provider: BoundTrackedMeasurementsContextProvider, }, ]; } diff --git a/extensions/measurement-tracking/src/getViewportModule.js b/extensions/measurement-tracking/src/getViewportModule.js index 1f657ec37..b494f418b 100644 --- a/extensions/measurement-tracking/src/getViewportModule.js +++ b/extensions/measurement-tracking/src/getViewportModule.js @@ -1,7 +1,7 @@ import React from 'react'; const Component = React.lazy(() => { - return import('./viewports/OHIFCornerstoneViewport'); + return import('./viewports/TrackedCornerstoneViewport'); }); const OHIFCornerstoneViewport = props => { diff --git a/extensions/measurement-tracking/src/viewports/OHIFCornerstoneViewport.js b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js similarity index 79% rename from extensions/measurement-tracking/src/viewports/OHIFCornerstoneViewport.js rename to extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js index 8bcda29c8..4b725622a 100644 --- a/extensions/measurement-tracking/src/viewports/OHIFCornerstoneViewport.js +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js @@ -3,12 +3,16 @@ import PropTypes from 'prop-types'; import cornerstone from 'cornerstone-core'; import CornerstoneViewport from 'react-cornerstone-viewport'; import OHIF, { DicomMetadataStore } from '@ohif/core'; -import { ViewportActionBar, useViewportGrid } from '@ohif/ui'; +import { + Notification, + ViewportActionBar, + useViewportGrid, + useViewportDialog, +} from '@ohif/ui'; import debounce from 'lodash.debounce'; import throttle from 'lodash.throttle'; import { useTrackedMeasurements } from './../getContextModule'; - // const cine = viewportSpecificData.cine; // isPlaying = cine.isPlaying === true; @@ -16,7 +20,7 @@ import { useTrackedMeasurements } from './../getContextModule'; const { StackManager } = OHIF.utils; -function OHIFCornerstoneViewport({ +function TrackedCornerstoneViewport({ children, dataSource, displaySet, @@ -24,6 +28,10 @@ function OHIFCornerstoneViewport({ }) { const [trackedMeasurements] = useTrackedMeasurements(); const [{ viewports }, dispatchViewportGrid] = useViewportGrid(); + // viewportIndex, onSubmit + const [viewportDialogState, viewportDialogApi] = useViewportDialog(); + console.warn('VIEWPORT DIALOG', viewportDialogState, viewportDialogApi); + const [viewportData, setViewportData] = useState(null); // TODO: Still needed? Better way than import `OHIF` and destructure? // Why is this managed by `core`? @@ -98,7 +106,7 @@ function OHIFCornerstoneViewport({ // ); // TODO: This display contains the meta for all instances. // That can't be right... - console.log('DISPLAYSET', displaySet) + console.log('DISPLAYSET', displaySet); // const seriesMeta = DicomMetadataStore.getSeries(this.props.displaySet.StudyInstanceUID, ''); // console.log(seriesMeta); @@ -145,22 +153,35 @@ function OHIFCornerstoneViewport({ }, }} /> - - {childrenWithProps} + {/* TODO: Viewport interface to accept stack or layers of content like this? */} +
+ +
+ {viewportDialogState.viewportIndex === viewportIndex && ( + + )} +
+ {childrenWithProps} +
); } -OHIFCornerstoneViewport.propTypes = { +TrackedCornerstoneViewport.propTypes = { displaySet: PropTypes.object.isRequired, viewportIndex: PropTypes.number.isRequired, dataSource: PropTypes.object, @@ -168,7 +189,7 @@ OHIFCornerstoneViewport.propTypes = { customProps: PropTypes.object, }; -OHIFCornerstoneViewport.defaultProps = { +TrackedCornerstoneViewport.defaultProps = { customProps: {}, }; @@ -207,6 +228,6 @@ async function _getViewportData(dataSource, displaySet) { }; return viewportData; -}; +} -export default OHIFCornerstoneViewport; +export default TrackedCornerstoneViewport; diff --git a/platform/core/src/services/UIViewportDialogService/index.js b/platform/core/src/services/UIViewportDialogService/index.js index 8b0aeddeb..8fdb91437 100644 --- a/platform/core/src/services/UIViewportDialogService/index.js +++ b/platform/core/src/services/UIViewportDialogService/index.js @@ -17,7 +17,8 @@ const publicAPI = { }; const serviceImplementation = { - _viewports: [], + _hide: () => console.warn('hide() NOT IMPLEMENTED'), + _show: () => console.warn('show() NOT IMPLEMENTED'), }; /** @@ -25,39 +26,21 @@ const serviceImplementation = { * * @param {ViewportDialogProps} props { content, contentProps, viewportIndex } */ -function _show({ content = null, contentProps = null, viewportIndex }) { - const viewportIndexImplementation = - (viewportIndex !== undefined && - serviceImplementation._viewports[viewportIndex]) || - {}; - - if (!viewportIndexImplementation._show) { - console.warn('show() NOT IMPLEMENTED'); - return; - } - - return viewportIndexImplementation._show({ - content, - contentProps, +function _show({ viewportIndex, type, message, actions, onSubmit }) { + return serviceImplementation._show({ viewportIndex, + type, + message, + actions, + onSubmit, }); } /** * Hides/dismisses the viewport dialog, if currently shown - * - * @param {*} { viewportIndex } */ -function _hide({ viewportIndex }) { - const viewportIndexImplementation = - (viewportIndex && serviceImplementation._viewports[viewportIndex]) || {}; - - if (!viewportIndexImplementation._hide) { - console.warn('hide() NOT IMPLEMENTED'); - return; - } - - return viewportIndexImplementation._hide(); +function _hide() { + return serviceImplementation._hide(); } /** @@ -72,22 +55,12 @@ function _hide({ viewportIndex }) { function setServiceImplementation({ hide: hideImplementation, show: showImplementation, - viewportIndex, }) { - if (viewportIndex !== undefined) { - const newImplementations = {}; - if (hideImplementation) { - newImplementations._hide = hideImplementation; - } - if (showImplementation) { - newImplementations._show = showImplementation; - } - - serviceImplementation._viewports[viewportIndex] = Object.assign( - {}, - serviceImplementation._viewports[viewportIndex], - newImplementations - ); + if (hideImplementation) { + serviceImplementation._hide = hideImplementation; + } + if (showImplementation) { + serviceImplementation._show = showImplementation; } } diff --git a/platform/ui/src/components/Notification/Notification.jsx b/platform/ui/src/components/Notification/Notification.jsx index 951c9c9ed..f980f2f07 100644 --- a/platform/ui/src/components/Notification/Notification.jsx +++ b/platform/ui/src/components/Notification/Notification.jsx @@ -1,9 +1,9 @@ import React from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; -import { Icon } from '@ohif/ui'; +import { Button, Icon } from '@ohif/ui'; -const Notification = ({ type, text, actionButtons }) => { +const Notification = ({ type, message, actions, onSubmit }) => { const iconsByType = { error: { icon: 'info', @@ -35,12 +35,30 @@ const Notification = ({ type, text, actionButtons }) => { const { icon, color } = getIconData(); return ( -
+
- {text} + {message} +
+
+ {actions.map((action, index) => { + const isFirst = index === 0; + const isPrimary = action.type === 'primary'; + + return ( + + ); + })}
-
{actionButtons}
); }; @@ -51,8 +69,15 @@ Notification.defaultProps = { Notification.propTypes = { type: PropTypes.string, - text: PropTypes.string.isRequired, - actionButtons: PropTypes.node.isRequired, + message: PropTypes.string.isRequired, + actions: PropTypes.arrayOf( + PropTypes.shape({ + text: PropTypes.string.isRequired, + value: PropTypes.any.isRequired, + type: PropTypes.oneOf(['primary', 'secondary', 'cancel']).isRequired, + }) + ).isRequired, + onSubmit: PropTypes.func.isRequired, }; export default Notification; diff --git a/platform/ui/src/components/Notification/Notification.mdx b/platform/ui/src/components/Notification/Notification.mdx index 4b379d03e..926eb66e0 100644 --- a/platform/ui/src/components/Notification/Notification.mdx +++ b/platform/ui/src/components/Notification/Notification.mdx @@ -5,7 +5,7 @@ route: components/notification --- import { Playground, Props } from 'docz'; -import { Notification, Button } from '@ohif/ui'; +import { Notification } from '@ohif/ui'; # Notification @@ -22,23 +22,31 @@ import { Notification } from '@ohif/ui'; {() => { - const actionButtons = () => { - return ( -
- - - -
- ); - }; return (
{ + window.alert(value); + }} />
); diff --git a/platform/ui/src/components/Viewport/Viewport.jsx b/platform/ui/src/components/Viewport/Viewport.jsx index ccf8a067d..404e85790 100644 --- a/platform/ui/src/components/Viewport/Viewport.jsx +++ b/platform/ui/src/components/Viewport/Viewport.jsx @@ -4,7 +4,7 @@ import { ViewportActionBar, Notification, Button } from '@ohif/ui'; const Viewport = ({ viewportIndex, onSeriesChange, studyData, children }) => { return ( -
+
{ {/* TODO: NOTIFICATION API DEFINITION - OHIF-112 */} - - - -
- } + actions={[ + { + type: 'cancel', + text: 'No', + value: 0, + }, + { + type: 'secondary', + text: 'No, do not ask again', + value: -1, + }, + { + type: 'primary', + text: 'Yes', + value: 1, + }, + ]} + onSubmit={value => { + window.alert(value); + }} />
diff --git a/platform/ui/src/contextProviders/ViewportDialogProvider.jsx b/platform/ui/src/contextProviders/ViewportDialogProvider.jsx index e412188ee..325ed3ea3 100644 --- a/platform/ui/src/contextProviders/ViewportDialogProvider.jsx +++ b/platform/ui/src/contextProviders/ViewportDialogProvider.jsx @@ -7,10 +7,20 @@ import React, { } from 'react'; import PropTypes from 'prop-types'; -const DEFAULT_OPTIONS = { - content: null, - contentProps: null, - customClassName: null, +const DEFAULT_STATE = { + viewportIndex: null, + message: undefined, + type: 'info', // "error" | "warning" | "info" | "success" + actions: undefined, // array of { type, text, value } + // dismissable? + // blockInteraction (single viewport? allViewports? everything?) + // TODO: Buttons --> type/color? text? value? + onSubmit: () => { + console.log('btn value?'); + }, + onDismiss: () => { + console.log('dismiss? -1'); + }, }; const ViewportDialogContext = createContext(null); @@ -18,42 +28,26 @@ const { Provider } = ViewportDialogContext; export const useViewportDialog = () => useContext(ViewportDialogContext); -const ViewportDialogProvider = ({ - children, - dialog: Dialog, - service, - viewportIndex, -}) => { - const [options, setOptions] = useState(DEFAULT_OPTIONS); - - const show = useCallback((props) => setOptions({ ...options, ...props }), [ - options, - ]); - - const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), []); +const ViewportDialogProvider = ({ children, service }) => { + const [viewportDialogState, setViewportDialogState] = useState(DEFAULT_STATE); + const show = useCallback( + params => setViewportDialogState({ ...viewportDialogState, ...params }), + [viewportDialogState] + ); + const hide = useCallback(() => setViewportDialogState(DEFAULT_STATE), []); useEffect(() => { if (service) { - service.setServiceImplementation({ hide, show, viewportIndex }); + service.setServiceImplementation({ + hide, + show, + }); } - }, [hide, service, show, viewportIndex]); - - const { - content: ViewportDialogContent, - contentProps, - customClassName, - } = options; + }, [hide, service, show]); return ( - -
- {ViewportDialogContent && ( - - - - )} - {children} -
+ + {children} ); }; @@ -64,16 +58,9 @@ ViewportDialogProvider.propTypes = { PropTypes.arrayOf(PropTypes.node), PropTypes.node, ]).isRequired, - /** dialog component */ - dialog: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.node), - PropTypes.node, - PropTypes.func, - ]).isRequired, service: PropTypes.shape({ setServiceImplementation: PropTypes.func, }), - viewportIndex: PropTypes.number, }; export default ViewportDialogProvider; diff --git a/platform/ui/src/contextProviders/ViewportDialogProvider.mdx b/platform/ui/src/contextProviders/ViewportDialogProvider.mdx index ffc0ee55e..34e66c1b8 100644 --- a/platform/ui/src/contextProviders/ViewportDialogProvider.mdx +++ b/platform/ui/src/contextProviders/ViewportDialogProvider.mdx @@ -28,19 +28,26 @@ component across all application. const ViewportNotification = ({ hide }) => { return ( - - - -
- } + actions={[ + { + type: 'cancel', + text: 'No', + value: 0, + }, + { + type: 'secondary', + text: 'No, do not ask again', + value: -1, + }, + { + type: 'primary', + text: 'Yes', + value: 1, + }, + ]} + onSubmit={value => { window.alert(value); }} /> ); }; @@ -88,17 +95,26 @@ component across all application. const ViewportNotification = ({ hide }) => { return ( - - - -
- } + actions={[ + { + type: 'cancel', + text: 'No', + value: 0, + }, + { + type: 'secondary', + text: 'No, do not ask again', + value: -1, + }, + { + type: 'primary', + text: 'Yes', + value: 1, + }, + ]} + onSubmit={value => { window.alert(value); }} /> ); }; diff --git a/platform/viewer/src/App.jsx b/platform/viewer/src/App.jsx index 22e3c23c0..4b7d4bd60 100644 --- a/platform/viewer/src/App.jsx +++ b/platform/viewer/src/App.jsx @@ -6,8 +6,10 @@ import { DialogProvider, Modal, ModalProvider, + Notification, SnackbarProvider, ThemeWrapper, + ViewportDialogProvider, ViewportGridProvider, } from '@ohif/ui'; // Viewer Project @@ -48,7 +50,12 @@ function App({ config, defaultExtensions }) { extensionManager, servicesManager ); - const { UIDialogService, UIModalService, UINotificationService } = servicesManager.services; + const { + UIDialogService, + UIModalService, + UINotificationService, + UIViewportDialogService, + } = servicesManager.services; // A UI Service may need to use the ViewportGrid context const viewportGridReducer = (state, action) => { @@ -82,13 +89,15 @@ function App({ config, defaultExtensions }) { }} reducer={viewportGridReducer} > - - - - {appRoutes} - - - + + + + + {appRoutes} + + + + diff --git a/platform/viewer/src/appInit.js b/platform/viewer/src/appInit.js index 4d236ee73..8ddb6b02e 100644 --- a/platform/viewer/src/appInit.js +++ b/platform/viewer/src/appInit.js @@ -6,6 +6,7 @@ import { UINotificationService, UIModalService, UIDialogService, + UIViewportDialogService, MeasurementService, DisplaySetService, ToolBarSerivce, @@ -44,6 +45,7 @@ function appInit(appConfigOrFunc, defaultExtensions) { UINotificationService, UIModalService, UIDialogService, + UIViewportDialogService, MeasurementService, DisplaySetService, ToolBarSerivce,