diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js new file mode 100644 index 000000000..84aafd9bd --- /dev/null +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js @@ -0,0 +1,70 @@ +import React, { useContext, useReducer } from 'react'; + +const TRACKED_MEASUREMENTS_INITIAL_STATE = { + /** StudyInstanceUID */ + trackedStudy: '', + /** string[] of SeriesInstanceUID */ + trackedSeries: [], + /** key, value => displaySetInstanceUID, string label */ + viewportLabels: new Map(), +}; + +const TrackedMeasurementsContext = React.createContext(); + +TrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext'; + +const useTrackedMeasurements = () => useContext(TrackedMeasurementsContext); + +/** + * + * @param {*} param0 + */ +function TrackedMeasurementsContextProvider({ children }) { + /** + * + * @param {object} state - previous state + * @param {object} action + * @param {string} action.type + * @param {object} action.payload + */ + const reducer = (state, action) => { + switch (action.type) { + case 'TRACK_SERIES': + const { StudyInstanceUID, SeriesInstanceUID } = action.payload; + return state; + case 'UNTRACK_SERIES': + return state; + default: + return state; + } + }; + + // Computed: + // numTrackedSeries + + // API + // - + // - TrackSeries(studyUid, seriesUid) (or... TrackViewport?) + // - Checks against tracked study + series + // - Initiates prompt if required + // - does nothing for SR modality? (how do we know SR viewport?????????) + // - UntrackSeries(seriesUid) + const [trackedMeasurements, dispatchTrackedMeasurements] = useReducer( + reducer, + TRACKED_MEASUREMENTS_INITIAL_STATE + ); + + return ( + + {children} + + ); +} + +export { + TrackedMeasurementsContext, + TrackedMeasurementsContextProvider, + useTrackedMeasurements, +}; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/trackedMeasurementsStateMachine.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/trackedMeasurementsStateMachine.js new file mode 100644 index 000000000..e19044b41 --- /dev/null +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/trackedMeasurementsStateMachine.js @@ -0,0 +1,116 @@ +import { Machine, interpret } from 'xstate'; + +// { +// "type": "TRACK_SERIES", +// "StudyInstanceUID": "study1", +// "SeriesInstanceUID": "series1" +// } + +// TODO: +// userPrompts should invoke promises: +// https://egghead.io/lessons/xstate-invoking-a-promise-for-asynchronous-state-transitions-in-xstate + +const config = { + id: 'measurementTracking', + initial: 'notTracking', + context: { + prevTrackedStudy: '', + prevTrackedSeries: [], + trackedStudy: '', + trackedSeries: [], + }, + states: { + notTracking: { + on: { + TRACK_SERIES: { + target: 'userPromptShouldTrack', + actions: 'trackSeries', + }, + } + }, + userPromptShouldTrack: { + on: { + YES: 'tracking', + CANCEL: 'notTracking', + NEVER: 'neverTracking', + }, + }, + neverTracking: { + type: 'final', + }, + tracking: { + on: { + TRACK_SERIES: [ + { target: 'tracking', cond: 'hasNewSeriesForTrackedStudy', actions: 'trackSeries' }, + { target: 'userPromptTrackingConflict', cond: 'hasConflictingStudyInstanceUID', actions: ['setPrevTracked', 'clearTrackedSeries', 'trackSeries'] }, + ], + UNTRACK_SERIES: [ + { target: 'tracking', cond: '', actions: ''}, + { target: 'notTracking', cond: '', actions: '' }, + ], + }, + }, + userPromptShouldTrackNewStudy: { + on: { + CANCEL: { + target: 'tracking', + actions: 'restorePrevTracked', + }, + YES: 'tracking', + }, + }, + }, + strict: true, +}; + +// Actions: +// - entry +// - exit +// - transition (on) + +// Activities: +// - can return a "clean-up" function to call on exit + +// actions, guards, services, activities, delays +const options = { + actions: { + logStuff: (context, event) => { console.log(context, event ) }, + setPrevTracked: assign(context => ({ + prevTrackedStudy: context.trackedStudy, + prevTrackedSeries: context.trackedSeries.slice(), + })), + restorePrevTracked: assign(context => ({ + trackedStudy: context.prevTrackedStudy, + trackedSeries: context.prevTrackedSeries.slice(), + })), + clearTrackedSeries: assign(() => ({ + trackedStudy: '', + trackedSeries: [], + })), + trackSeries: assign((context, event) => + { + console.log(context, event); + const prevTrackedSeries = context.trackedSeries.slice(); + return { + trackedStudy: event.StudyInstanceUID || '', + trackedSeries: [...prevTrackedSeries, event.SeriesInstanceUID], + } + }), + }, + guards: { + hasConflictingStudyInstanceUID: (context, event) => + context.StudyInstanceUID !== context.trackedStudy, + hasNewSeriesForTrackedStudy: (context, event) => + event.StudyInstanceUID === context.trackedStudy && + !context.trackedSeries.includes(event.SeriesInstanceUID), + }, +} + +const measurementTrackingMachine = Machine(config, options); +// .transition(state, eventArgument).value +const service = interpret(measurementTrackingMachine).start(); +// .send(event): nextState +// .state (getter) +// .onTransition(state => { state.vale }) + +export default measurementTrackingMachine; diff --git a/extensions/measurement-tracking/src/contexts/index.js b/extensions/measurement-tracking/src/contexts/index.js new file mode 100644 index 000000000..8b6723a0e --- /dev/null +++ b/extensions/measurement-tracking/src/contexts/index.js @@ -0,0 +1,5 @@ +export { + TrackedMeasurementsContext, + TrackedMeasurementsContextProvider, + useTrackedMeasurements, +} from './TrackedMeasurementsContext';