OHIF-323: Cine Dialog (#1961)

* ohif-323: add cineplayer flow

* ohif-323: improve toolbar flow of cine

* ohif-323: improve cine styles

* ohif-323: use toggle instead of set

* ohif-323: add compatibility comment

* ohif-323: move cine logic to command

* ohif-323: cr update

* ohif-323: remove typo and update svg title

* ohif-323: remove log

* ohif-323: update props and remove dead code

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
This commit is contained in:
Igor Octaviano 2020-08-19 16:24:07 -03:00 committed by GitHub
parent 3e944780dc
commit 760e2a62ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 575 additions and 70 deletions

View File

@ -6,12 +6,6 @@ import debounce from 'lodash.debounce';
import { setEnabledElement } from './state';
// const cine = viewportSpecificData.cine;
// isPlaying = cine.isPlaying === true;
// frameRate = cine.cineFrameRate || frameRate;
const { StackManager } = OHIF.utils;
class OHIFCornerstoneViewport extends Component {

View File

@ -91,10 +91,10 @@ function ToolbarSecondary({ servicesManager }) {
isActive: false,
};
const [toolbars, setToolbars] = useState({ primary: [], secondary: [] });
const [activeTool, setActiveTool] = useState(defaultTool);
const [nestedActiveTool, setNestedActiveTool] = useState(defaultTool);
const setActiveToolHandler = (tool, isNested) => {
setActiveTool(isNested ? tool : defaultTool);
setNestedActiveTool(isNested ? tool : defaultTool);
};
const onPrimaryClickHandler = (evt, btn) => {
@ -257,6 +257,7 @@ function ViewerLayout({
<ViewportGridComp
servicesManager={servicesManager}
viewportComponents={viewportComponents}
commandsManager={commandsManager}
/>
</ErrorBoundary>
</div>

View File

@ -1,10 +1,17 @@
const commandsModule = ({ servicesManager }) => {
const { MeasurementService } = servicesManager.services;
const commandsModule = ({ servicesManager, commandsManager }) => {
const { MeasurementService, ViewportGridService, ToolBarService, CineService } = servicesManager.services;
const actions = {
clearMeasurements: () => {
MeasurementService.clear();
},
toggleCine: () => {
const { viewports } = ViewportGridService.getState();
const { isCineEnabled } = CineService.getState();
CineService.setIsCineEnabled(!isCineEnabled);
ToolBarService.setButton('Cine', { props: { isActive: !isCineEnabled } });
viewports.forEach((_, index) => CineService.setCine({ id: index, isPlaying: false }));
},
};
const definitions = {
@ -13,6 +20,11 @@ const commandsModule = ({ servicesManager }) => {
storeContexts: [],
options: {},
},
toggleCine: {
commandFn: actions.toggleCine,
storeContexts: [],
options: {},
},
};
return {

View File

@ -85,8 +85,8 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
thisButton.props.isActive = !thisButton.props.isActive;
// Run button logic/command
// MAKE SURE THIS SUPPORTS TOGGLE!
// commandsManager.runCommand(props.commandName, props.commandOptions);
commandsManager.runCommand(props.commandName, props.commandOptions);
// What if just toggled "content"?
// commandName OR content?

View File

@ -18,7 +18,7 @@ export default {
getPanelModule,
getSopClassHandlerModule,
getToolbarModule,
getCommandsModule({ servicesManager }) {
return commandsModule({ servicesManager });
getCommandsModule({ servicesManager, commandsManager }) {
return commandsModule({ servicesManager, commandsManager });
},
};

View File

@ -12,10 +12,10 @@ const OHIFCornerstoneViewport = props => {
);
};
function getViewportModule({ servicesManager }) {
function getViewportModule({ servicesManager, commandsManager }) {
const ExtendedOHIFCornerstoneSRViewport = props => {
return (
<OHIFCornerstoneViewport servicesManager={servicesManager} {...props} />
<OHIFCornerstoneViewport servicesManager={servicesManager} commandsManager={commandsManager} {...props} />
);
};

View File

@ -7,6 +7,7 @@ import OHIF, { utils } from '@ohif/core';
import {
Notification,
ViewportActionBar,
useCine,
useViewportGrid,
useViewportDialog,
} from '@ohif/ui';
@ -35,10 +36,6 @@ const BaseAnnotationTool = cornerstoneTools.importInternal(
'base/BaseAnnotationTool'
);
// const cine = viewportSpecificData.cine;
// isPlaying = cine.isPlaying === true;
// frameRate = cine.cineFrameRate || frameRate;
const { StackManager } = OHIF.utils;
function TrackedCornerstoneViewport({
@ -47,6 +44,7 @@ function TrackedCornerstoneViewport({
displaySet,
viewportIndex,
servicesManager,
commandsManager
}) {
const {
ToolBarService,
@ -54,12 +52,11 @@ function TrackedCornerstoneViewport({
MeasurementService,
} = servicesManager.services;
const [trackedMeasurements] = useTrackedMeasurements();
const [{ activeViewportIndex, viewports }] = useViewportGrid();
// viewportIndex, onSubmit
const [{ activeViewportIndex, viewports }, viewportGridService] = useViewportGrid();
const [{ isCineEnabled, cines }, cineService] = useCine();
const [viewportDialogState, viewportDialogApi] = useViewportDialog();
const [viewportData, setViewportData] = useState(null);
const [element, setElement] = useState(null);
const [isTracked, setIsTracked] = useState(false);
const [trackedMeasurementId, setTrackedMeasurementId] = useState(null);
@ -71,6 +68,10 @@ function TrackedCornerstoneViewport({
};
}, []);
useEffect(() => {
cineService.setCine({ id: viewportIndex });
}, [viewportIndex]);
useEffect(() => {
const unsubcribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents(
MeasurementService,
@ -298,7 +299,9 @@ function TrackedCornerstoneViewport({
);
}
// TODO -> disabled double click for now: onDoubleClick={_onDoubleClick}
const cine = cines[viewportIndex];
const isPlaying = cine && cine.isPlaying || false;
const frameRate = cine && cine.frameRate || 24;
return (
<>
@ -333,6 +336,14 @@ function TrackedCornerstoneViewport({
scanner: ManufacturerModelName || '',
},
}}
showNavArrows={!isCineEnabled}
showCine={isCineEnabled}
cineProps={{
isPlaying,
onClose: () => commandsManager.runCommand('toggleCine'),
onPlayPauseChange: isPlaying => cineService.setCine({ id: activeViewportIndex, isPlaying }),
onFrameRateChange: frameRate => cineService.setCine({ id: activeViewportIndex, frameRate }),
}}
/>
{/* TODO: Viewport interface to accept stack or layers of content like this? */}
<div className="relative flex flex-row w-full h-full overflow-hidden">
@ -347,8 +358,8 @@ function TrackedCornerstoneViewport({
// TODO: ViewportGrid Context?
isActive={true} // todo
isStackPrefetchEnabled={true} // todo
isPlaying={false}
frameRate={24}
isPlaying={isPlaying}
frameRate={frameRate}
isOverlayVisible={true}
loadingIndicatorComponent={ViewportLoadingIndicator}
viewportOverlayComponent={props => {
@ -408,6 +419,7 @@ function _getCornerstoneStack(displaySet, dataSource) {
return stack;
}
// TODO -> disabled double click for now: onDoubleClick={_onDoubleClick}
function _onDoubleClick() {
const cancelActiveManipulatorsForElement = cornerstoneTools.getModule(
'manipulatorState'

View File

@ -189,14 +189,17 @@ export default [
type: 'primary',
},
},
// TODO: Toggle
{
id: 'Cine',
type: 'ohif.action',
type: 'ohif.toggle',
config: {
groupName: 'primaryTool',
},
props: {
isActive: false,
icon: 'old-youtube',
label: 'Cine',
commandName: '',
commandName: 'toggleCine',
type: 'primary',
},
},

View File

@ -22,6 +22,11 @@ export default [
keys: ['v'],
isEditable: true,
},
{
commandName: 'toggleCine',
label: 'Cine',
keys: ['c'],
},
{
commandName: 'invertViewport',
label: 'Invert',

View File

@ -24,6 +24,7 @@ import utils from './utils/';
import defaults from './defaults';
import {
CineService,
UIDialogService,
UIModalService,
UINotificationService,
@ -31,7 +32,7 @@ import {
//
DicomMetadataStore,
DisplaySetService,
ToolBarSerivce, // TODO: Typo
ToolBarService,
MeasurementService,
ViewportGridService,
HangingProtocolService,
@ -73,13 +74,14 @@ const OHIF = {
measurements,
hangingProtocols,
//
CineService,
UIDialogService,
UIModalService,
UINotificationService,
UIViewportDialogService,
DisplaySetService,
MeasurementService,
ToolBarSerivce, // TODO: TYPO
ToolBarService, // TODO: TYPO
ViewportGridService,
HangingProtocolService,
IWebApiDataSource,
@ -117,13 +119,14 @@ export {
measurements,
hangingProtocols,
//
CineService,
UIDialogService,
UIModalService,
UINotificationService,
UIViewportDialogService,
DisplaySetService,
MeasurementService,
ToolBarSerivce,
ToolBarService,
ViewportGridService,
HangingProtocolService,
IWebApiDataSource,

View File

@ -0,0 +1,52 @@
const name = 'CineService';
const publicAPI = {
name,
getState: _getState,
setCine: _setCine,
setIsCineEnabled: _setIsCineEnabled,
setServiceImplementation,
};
const serviceImplementation = {
_getState: () => console.warn('getState() NOT IMPLEMENTED'),
_setCine: () =>
console.warn('setCine() NOT IMPLEMENTED'),
_setIsCineEnabled: () =>
console.warn('setIsCineEnabled() NOT IMPLEMENTED'),
};
function _getState() {
return serviceImplementation._getState();
}
function _setCine({ id, frameRate, isPlaying }) {
return serviceImplementation._setCine({ id, frameRate, isPlaying });
}
function _setIsCineEnabled(isCineEnabled) {
return serviceImplementation._setIsCineEnabled(isCineEnabled);
}
function setServiceImplementation({
getState: getStateImplementation,
setCine: setCineImplementation,
setIsCineEnabled: setIsCineEnabledImplementation,
}) {
if (getStateImplementation) {
serviceImplementation._getState = getStateImplementation;
}
if (setCineImplementation) {
serviceImplementation._setCine = setCineImplementation;
}
if (setIsCineEnabledImplementation) {
serviceImplementation._setIsCineEnabled = setIsCineEnabledImplementation;
}
}
export default {
name,
create: ({ configuration = {} }) => {
return publicAPI;
},
};

View File

@ -0,0 +1,2 @@
import CineService from './CineService';
export default CineService;

View File

@ -1,3 +1,4 @@
import merge from 'lodash.merge';
import pubSubServiceInterface from '../_shared/pubSubServiceInterface';
const EVENTS = {
@ -36,9 +37,23 @@ export default class ToolBarService {
});
}
setButton(id, button) {
if (this.buttons[id]) {
this.buttons[id] = merge(this.buttons[id], button);
this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, {
buttons: this.buttons,
button: this.buttons[id],
buttonSections: this.buttonSections
});
}
}
setButtons(buttons) {
this.buttons = buttons;
this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, {});
this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, {
buttons: this.buttons,
buttonSections: this.buttonSections
});
}
_buttonTypes() {
@ -61,7 +76,6 @@ export default class ToolBarService {
return buttonTypes;
}
createButtonSection(key, buttons) {
// Maybe do this mapping at time of return, instead of time of create
// Props check important for validation here...

View File

@ -50,6 +50,7 @@ function _set(state) {
function _reset() {
return serviceImplementation._reset({});
}
function _setCachedLayout({ numCols, numRows, viewports }) {
return serviceImplementation._setLayout({ numCols, numRows, viewports });
}

View File

@ -6,8 +6,9 @@ import UINotificationService from './UINotificationService';
import UIViewportDialogService from './UIViewportDialogService';
import DicomMetadataStore from './DicomMetadataStore';
import DisplaySetService from './DisplaySetService';
import ToolBarSerivce from './ToolBarService';
import ToolBarService from './ToolBarService';
import ViewportGridService from './ViewportGridService';
import CineService from './CineService';
import HangingProtocolService from './HangingProtocolService';
export {
@ -19,7 +20,8 @@ export {
UIViewportDialogService,
DicomMetadataStore,
DisplaySetService,
ToolBarSerivce,
ToolBarService,
ViewportGridService,
HangingProtocolService,
CineService
};

View File

@ -4,6 +4,8 @@ export { utils };
/** CONTEXT/HOOKS */
export {
useCine,
CineProvider,
DialogProvider,
useDialog,
withDialog,
@ -38,6 +40,7 @@ export {
Button,
ButtonGroup,
ContextMenu,
CinePlayer,
DateRange,
Dialog,
Dropdown,

View File

@ -0,0 +1,9 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
aria-labelledby="title"
fill="currentColor"
>
<title id="title">Play</title>
<path d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z" />
</svg>

After

Width:  |  Height:  |  Size: 283 B

View File

@ -0,0 +1,9 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
aria-labelledby="title"
fill="currentColor"
>
<title id="title">Stop</title>
<path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z" />
</svg>

After

Width:  |  Height:  |  Size: 277 B

View File

@ -0,0 +1,97 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
import { IconButton, Icon } from '@ohif/ui';
import './CinePlayerCustomInputRange.css';
const CinePlayer = ({
isPlaying,
minFrameRate,
maxFrameRate,
stepFrameRate,
frameRate: defaultFrameRate,
onFrameRateChange,
onPlayPauseChange,
onClose
}) => {
const [frameRate, setFrameRate] = useState(defaultFrameRate);
const debouncedSetFrameRate = debounce(onFrameRateChange, 300);
const onFrameRateChangeHandler = ({ target }) => {
const frameRate = parseFloat(target.value);
debouncedSetFrameRate(frameRate);
setFrameRate(frameRate);
};
const onPlayPauseChangeHandler = () => onPlayPauseChange(!isPlaying);
const action = {
false: { icon: 'old-play' },
true: { icon: 'old-stop' }
};
return (
<div className="CinePlayer flex flex-row h-10 items-center justify-center border border-primary-light rounded-full">
<IconButton
variant="text"
color="inherit"
size="initial"
className="mr-3 ml-4 text-primary-active"
onClick={onPlayPauseChangeHandler}
>
<Icon width="15px" height="15px" name={action[isPlaying].icon} />
</IconButton>
<div className="flex flex-col h-full justify-center pt-2 mr-3 pl-1 pr-1">
<input
type="range"
name="frameRate"
min={minFrameRate}
max={maxFrameRate}
step={stepFrameRate}
value={frameRate}
onChange={onFrameRateChangeHandler}
/>
<p className="mt-1 text-sm text-primary-light">{`${frameRate.toFixed(1)} fps`}</p>
</div>
<IconButton
color="inherit"
size="initial"
className="mr-3 text-primary-active border border-primary-active rounded-full"
onClick={onClose}
>
<Icon name="close" width="15px" height="15px" />
</IconButton>
</div>
);
};
const noop = () => { };
CinePlayer.defaultProps = {
isPlaying: false,
minFrameRate: 1,
maxFrameRate: 90,
stepFrameRate: 1,
frameRate: 24,
onPlayPauseChange: noop,
onFrameRateChange: noop,
onClose: noop
};
CinePlayer.propTypes = {
/** Minimum value for range slider */
minFrameRate: PropTypes.number.isRequired,
/** Maximum value for range slider */
maxFrameRate: PropTypes.number.isRequired,
/** Increment range slider can "step" in either direction */
stepFrameRate: PropTypes.number.isRequired,
frameRate: PropTypes.number.isRequired,
/** 'true' if playing, 'false' if paused */
isPlaying: PropTypes.bool.isRequired,
onPlayPauseChange: PropTypes.func,
onFrameRateChange: PropTypes.func,
onClose: PropTypes.func,
};
export default CinePlayer;

View File

@ -0,0 +1,42 @@
---
name: CinePlayer
menu: General
route: components/CinePlayer
---
import { useState } from 'react';
import { Playground, Props } from 'docz';
import { CinePlayer } from '@ohif/ui';
# Input Text
## Import
```javascript
import { CinePlayer } from '@ohif/ui';
```
## Basic usage
<Playground>
{() => {
return (
<div className="flex flex-col flex-1 p-4 items-center">
<div className="w-56">
<CinePlayer
cineMinFrameRate={1}
cineMaxFrameRate={90}
cineStepFrameRate={1}
cineFrameRate={24}
isPlaying={false}
/>
</div>
</div>
)
}}
</Playground>
## Properties
<Props of={CinePlayer} />

View File

@ -0,0 +1,105 @@
/*
* This is a custom input style scoped specifically to CinePlayer
* written in plain CSS with color variables from tailwind
* to avoid complex compatibility configuration.
*/
.CinePlayer input[type=range] {
-webkit-appearance: none;
background: transparent;
width: 100%;
}
.CinePlayer input[type=range]:focus {
outline: none;
}
.CinePlayer input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 2px;
cursor: pointer;
animate: 0.2s;
box-shadow: 0px 0px 0px #000000;
@apply bg-primary-light;
border-radius: 5px;
border: 0px solid #000000;
}
.CinePlayer input[type=range]::-webkit-slider-thumb {
box-shadow: 0px 0px 0px #000000;
border: 4px solid #000000;
height: 18px;
width: 17px;
border-radius: 50px;
@apply bg-primary-light;
cursor: pointer;
-webkit-appearance: none;
margin-top: -9px;
}
.CinePlayer input[type=range]:focus::-webkit-slider-runnable-track {
@apply bg-primary-light;
}
.CinePlayer input[type=range]::-moz-range-track {
width: 100%;
height: 2px;
cursor: pointer;
animate: 0.2s;
box-shadow: 0px 0px 0px #000000;
@apply bg-primary-light;
border-radius: 5px;
border: 0px solid #000000;
}
.CinePlayer input[type=range]::-moz-range-thumb {
box-shadow: 0px 0px 0px #000000;
border: 4px solid #000000;
height: 18px;
width: 17px;
border-radius: 50px;
@apply bg-primary-light;
cursor: pointer;
}
.CinePlayer input[type=range]::-ms-track {
width: 100%;
height: 2px;
cursor: pointer;
animate: 0.2s;
background: transparent;
border-color: transparent;
color: transparent;
}
.CinePlayer input[type=range]::-ms-fill-lower {
@apply bg-primary-light;
border: 0px solid #000000;
border-radius: 10px;
box-shadow: 0px 0px 0px #000000;
}
.CinePlayer input[type=range]::-ms-fill-upper {
@apply bg-primary-light;
border: 0px solid #000000;
border-radius: 10px;
box-shadow: 0px 0px 0px #000000;
}
.CinePlayer input[type=range]::-ms-thumb {
margin-top: 1px;
box-shadow: 0px 0px 0px #000000;
border: 4px solid #000000;
height: 18px;
width: 17px;
border-radius: 50px;
@apply bg-primary-light;
cursor: pointer;
}
.CinePlayer input[type=range]:focus::-ms-fill-lower {
@apply bg-primary-light;
}
.CinePlayer input[type=range]:focus::-ms-fill-upper {
@apply bg-primary-light;
}

View File

@ -0,0 +1,2 @@
import CinePlayer from './CinePlayer';
export default CinePlayer;

View File

@ -60,6 +60,8 @@ import oldAngleLeft from './../../assets/icons/old-angle-left.svg';
import oldReset from './../../assets/icons/old-reset.svg';
import oldCircleO from './../../assets/icons/old-circle-o.svg';
import oldTrash from './../../assets/icons/old-trash.svg';
import oldPlay from './../../assets/icons/old-play.svg';
import oldStop from './../../assets/icons/old-stop.svg';
const ICONS = {
'arrow-down': arrowDown,
@ -121,6 +123,8 @@ const ICONS = {
'old-reset': oldReset,
'old-circle-o': oldCircleO,
'old-trash': oldTrash,
'old-play': oldPlay,
'old-stop': oldStop,
};
/**

View File

@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Icon, ButtonGroup, Button, Tooltip } from '../';
import { Icon, ButtonGroup, Button, Tooltip, CinePlayer } from '../';
import useOnClickOutside from '../../utils/useOnClickOutside';
const classes = {
@ -14,6 +14,8 @@ const classes = {
const ViewportActionBar = ({
studyData,
showNavArrows,
showCine,
cineProps,
showPatientInfo: patientInfoVisibility,
onSeriesChange,
onDoubleClick,
@ -71,11 +73,11 @@ const ViewportActionBar = ({
if (modality === 'SR') {
const TooltipMessage = isLocked
? () => (
<div>
This SR is locked. <br />
<div>
This SR is locked. <br />
Measurements cannot be duplicated.
</div>
)
</div>
)
: () => <div>This SR is unlocked.</div>;
return (
<>
@ -117,13 +119,13 @@ const ViewportActionBar = ({
can be viewed <br /> in the measurement panel
</>
) : (
<>
Measurements for
<>
Measurements for
<span className="font-bold text-white"> untracked </span>
series <br /> will not be shown in the <br /> measurements
panel
</>
)}
)}
</span>
</div>
</div>
@ -137,10 +139,10 @@ const ViewportActionBar = ({
return (
<div
className="flex items-center p-2 border-b select-none border-primary-light min-h-12"
className="flex flex-wrap items-center p-2 border-b select-none border-primary-light -mt-2"
onDoubleClick={onDoubleClick}
>
<div className="flex flex-grow">
<div className="flex flex-grow min-w-48 flex-1 mt-2">
<div className="flex items-center">
{renderIconStatus()}
<span className="ml-2 text-white text-large">{label}</span>
@ -166,8 +168,8 @@ const ViewportActionBar = ({
</div>
</div>
</div>
{showNavArrows && (
<div className="ml-2">
{showNavArrows && !showCine && (
<div className="mt-2">
<ButtonGroup>
<Button
size="initial"
@ -186,7 +188,12 @@ const ViewportActionBar = ({
</ButtonGroup>
</div>
)}
<div className="flex ml-4 mr-2" onClick={onPatientInfoClick}>
{showCine && !showNavArrows && (
<div className="mt-2 min-w-48 max-w-48 mr-auto">
<CinePlayer {...cineProps} />
</div>
)}
<div className="flex h-8 ml-4 mr-2 mt-2" onClick={onPatientInfoClick}>
<PatientInfo
showPatientInfoRef={showPatientInfoRef}
isOpen={showPatientInfo}
@ -206,6 +213,8 @@ const ViewportActionBar = ({
ViewportActionBar.propTypes = {
onSeriesChange: PropTypes.func.isRequired,
showNavArrows: PropTypes.bool,
showCine: PropTypes.bool,
cineProps: PropTypes.object,
showPatientInfo: PropTypes.bool,
studyData: PropTypes.shape({
label: PropTypes.string.isRequired,
@ -228,6 +237,8 @@ ViewportActionBar.propTypes = {
};
ViewportActionBar.defaultProps = {
cineProps: {},
showCine: false,
showNavArrows: true,
showPatientInfo: false,
};

View File

@ -2,6 +2,7 @@ import AboutModal from './AboutModal';
import Button from './Button';
import ButtonGroup from './ButtonGroup';
import ContextMenu from './ContextMenu';
import CinePlayer from './CinePlayer';
import DateRange from './DateRange';
import Dialog from './Dialog';
import Dropdown from './Dropdown';
@ -68,6 +69,7 @@ export {
Button,
ButtonGroup,
ContextMenu,
CinePlayer,
DateRange,
Dialog,
Dropdown,

View File

@ -0,0 +1,105 @@
import React, {
createContext,
useCallback,
useContext,
useEffect,
useReducer,
} from 'react';
import PropTypes from 'prop-types';
const DEFAULT_STATE = {
isCineEnabled: false,
cines: {
/*
* 1: { isPlaying: false, frameRate: 24 };
*/
}
};
const DEFAULT_CINE = { isPlaying: false, frameRate: 24 };
export const CineContext = createContext(DEFAULT_STATE);
export default function CineProvider({ children, service }) {
const reducer = (state, action) => {
switch (action.type) {
case 'SET_CINE': {
const { id, frameRate, isPlaying = undefined } = action.payload;
const cines = state.cines;
if (!cines[id]) cines[id] = { id, ...DEFAULT_CINE };
cines[id].frameRate = frameRate || cines[id].frameRate;
cines[id].isPlaying = isPlaying !== undefined ? isPlaying : cines[id].isPlaying;
return { ...state, ...{ cines } };
}
case 'SET_IS_CINE_ENABLED': {
return { ...state, ...{ isCineEnabled: action.payload } };
}
default:
return action.payload;
}
};
const [state, dispatch] = useReducer(
reducer,
DEFAULT_STATE
);
const getState = useCallback(() => state, [state]);
const setIsCineEnabled = useCallback(
isCineEnabled => dispatch({ type: 'SET_IS_CINE_ENABLED', payload: isCineEnabled }),
[dispatch]
);
const setCine = useCallback(
({ id, frameRate, isPlaying }) =>
dispatch({
type: 'SET_CINE',
payload: {
id,
frameRate,
isPlaying,
},
}),
[dispatch]
);
/**
* Sets the implementation of a modal service that can be used by extensions.
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({ getState, setIsCineEnabled, setCine });
}
}, [
getState,
service,
setCine,
setIsCineEnabled,
]);
const api = {
getState,
setCine,
setIsCineEnabled,
};
return (
<CineContext.Provider value={[state, api]}>
{children}
</CineContext.Provider>
);
}
CineProvider.propTypes = {
children: PropTypes.any,
service: PropTypes.shape({
setServiceImplementation: PropTypes.func,
}).isRequired,
};
export const useCine = () => useContext(CineContext);

View File

@ -11,9 +11,11 @@ const DEFAULT_STATE = {
numRows: 1,
numCols: 1,
viewports: [
// {
// displaySetInstanceUID: string,
// }
/*
* {
* displaySetInstanceUID: string,
* }
*/
],
activeViewportIndex: 0,
};
@ -59,7 +61,9 @@ export function ViewportGridProvider({ children, service }) {
numCols: 1,
numRows: 1,
activeViewportIndex: 0,
viewports: [{ displaySetInstanceUID: null }],
viewports: [{
displaySetInstanceUID: null,
}],
cachedLayout: null,
};
}
@ -84,13 +88,13 @@ export function ViewportGridProvider({ children, service }) {
DEFAULT_STATE
);
console.log('viewportGridState',viewportGridState)
const getState = useCallback(() => viewportGridState, [viewportGridState]);
const setActiveViewportIndex = useCallback(
index => dispatch({ type: 'SET_ACTIVE_VIEWPORT_INDEX', payload: index }),
[dispatch]
);
const setDisplaysetForViewport = useCallback(
({ viewportIndex, displaySetInstanceUID }) =>
dispatch({
@ -123,6 +127,7 @@ export function ViewportGridProvider({ children, service }) {
}),
[dispatch]
);
const setCachedLayout = useCallback(
payload =>
dispatch({

View File

@ -19,6 +19,12 @@ export {
useImageViewer,
} from './ImageViewerProvider';
export {
CineContext,
default as CineProvider,
useCine,
} from './CineProvider';
export {
default as SnackbarProvider,
useSnackbar,

View File

@ -13,6 +13,7 @@ import {
ViewportDialogProvider,
ViewportGridProvider,
HangingProtocolProvider,
CineProvider,
} from '@ohif/ui';
// Viewer Project
// TODO: Should this influence study list?
@ -60,6 +61,7 @@ function App({ config, defaultExtensions }) {
UIViewportDialogService,
ViewportGridService, // TODO: Should this be a "UI" Service?
HangingProtocolService,
CineService
} = servicesManager.services;
return (
@ -70,13 +72,15 @@ function App({ config, defaultExtensions }) {
<ViewportGridProvider service={ViewportGridService}>
<HangingProtocolProvider service={HangingProtocolService}>
<ViewportDialogProvider service={UIViewportDialogService}>
<SnackbarProvider service={UINotificationService}>
<DialogProvider service={UIDialogService}>
<ModalProvider modal={Modal} service={UIModalService}>
{appRoutes}
</ModalProvider>
</DialogProvider>
</SnackbarProvider>
<CineProvider service={CineService}>
<SnackbarProvider service={UINotificationService}>
<DialogProvider service={UIDialogService}>
<ModalProvider modal={Modal} service={UIModalService}>
{appRoutes}
</ModalProvider>
</DialogProvider>
</SnackbarProvider>
</CineProvider>
</ViewportDialogProvider>
</HangingProtocolProvider>
</ViewportGridProvider>

View File

@ -9,9 +9,10 @@ import {
UIViewportDialogService,
MeasurementService,
DisplaySetService,
ToolBarSerivce,
ToolBarService,
ViewportGridService,
HangingProtocolService
HangingProtocolService,
CineService
// utils,
// redux as reduxOHIF,
} from '@ohif/core';
@ -54,9 +55,10 @@ function appInit(appConfigOrFunc, defaultExtensions) {
UIViewportDialogService,
MeasurementService,
DisplaySetService,
ToolBarSerivce,
ToolBarService,
ViewportGridService,
HangingProtocolService
HangingProtocolService,
CineService
]);
/**

View File

@ -1,12 +1,10 @@
/**
* CSS Grid Reference: http://grid.malven.co/
*/
import React, { useEffect, useCallback, useState } from 'react';
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
import EmptyViewport from './EmptyViewport';
import { classes } from '@ohif/core';
const { ImageSet } = classes;
import classNames from 'classnames';
function ViewerViewportGrid(props) {
@ -194,7 +192,7 @@ function ViewerViewportGrid(props) {
isActive={isActive}
>
<div
className={classNames('h-full w-full flex flex-col align-center', {
className={classNames('h-full w-full flex flex-col', {
'pointer-events-none': !isActive,
})}
>