feat(DoubleClick): double click a viewport to one up and back (#3285)
* feat(DoubleClick): double click a viewport to one up and back Added a toggleOneUp command that puts the active viewport into a 1x1 grid layout and it toggles out of 'one-up' by restoring its saved 'toggleOneUpViewportGridStore' from the StateSyncService. Added double click customization for the Cornerstone extension with the default double click handling being the toggleOneUp command. Added a cypress test for the double click functionality. * PR feedback: - tracked viewport measurements no longer show as dashed when toggling one up - disallowed double clicking near a measurement - updated cornerstone3D dependencies to fix double click of TMTV and volume viewport 3D - created ViewportGridService.getLayoutOptionsFromState * Updated the ViewportGridService docs. * Switched to using 'cornerstoneViewportClickCommands' and consistency with the context menu clicks.
This commit is contained in:
parent
4734b3bac6
commit
d5ff590dfc
@ -25,6 +25,7 @@ import interleaveCenterLoader from './utils/interleaveCenterLoader';
|
||||
import nthLoader from './utils/nthLoader';
|
||||
import interleaveTopToBottom from './utils/interleaveTopToBottom';
|
||||
import initContextMenu from './initContextMenu';
|
||||
import initDoubleClick from './initDoubleClick';
|
||||
|
||||
// TODO: Cypress tests are currently grabbing this from the window?
|
||||
window.cornerstone = cornerstone;
|
||||
@ -104,6 +105,12 @@ export default async function init({
|
||||
clearOnModeExit: true,
|
||||
});
|
||||
|
||||
// Stores the entire ViewportGridService getState when toggling to one up
|
||||
// (e.g. via a double click) so that it can be restored when toggling back.
|
||||
stateSyncService.register('toggleOneUpViewportGridStore', {
|
||||
clearOnModeExit: true,
|
||||
});
|
||||
|
||||
const labelmapRepresentation =
|
||||
cornerstoneTools.Enums.SegmentationRepresentations.Labelmap;
|
||||
|
||||
@ -198,6 +205,11 @@ export default async function init({
|
||||
commandsManager,
|
||||
});
|
||||
|
||||
initDoubleClick({
|
||||
customizationService,
|
||||
commandsManager,
|
||||
});
|
||||
|
||||
const newStackCallback = evt => {
|
||||
const { element } = evt.detail;
|
||||
utilities.stackPrefetch.enable(element);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { eventTarget, EVENTS } from '@cornerstonejs/core';
|
||||
import { Enums } from '@cornerstonejs/tools';
|
||||
import { setEnabledElement } from './state';
|
||||
import { findNearbyToolData } from './utils/findNearbyToolData';
|
||||
|
||||
const cs3DToolsEvents = Enums.Events;
|
||||
|
||||
@ -47,28 +48,6 @@ function initContextMenu({
|
||||
customizationService,
|
||||
commandsManager,
|
||||
}): void {
|
||||
/**
|
||||
* Finds tool nearby event position triggered.
|
||||
*
|
||||
* @param {Object} commandsManager mannager of commands
|
||||
* @param {Object} event that has being triggered
|
||||
* @returns cs toolData or undefined if not found.
|
||||
*/
|
||||
const findNearbyToolData = evt => {
|
||||
if (!evt?.detail) {
|
||||
return;
|
||||
}
|
||||
const { element, currentPoints } = evt.detail;
|
||||
return commandsManager.runCommand(
|
||||
'getNearbyToolData',
|
||||
{
|
||||
element,
|
||||
canvasCoordinates: currentPoints?.canvas,
|
||||
},
|
||||
'CORNERSTONE'
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* Run the commands associated with the given button press,
|
||||
* defaults on button1 and button2
|
||||
@ -80,7 +59,7 @@ function initContextMenu({
|
||||
const toRun = customizations[name];
|
||||
console.log('initContextMenu::cornerstoneViewportHandleEvent', name, toRun);
|
||||
const options = {
|
||||
nearbyToolData: findNearbyToolData(evt),
|
||||
nearbyToolData: findNearbyToolData(commandsManager, evt),
|
||||
event: evt,
|
||||
};
|
||||
commandsManager.run(toRun, options);
|
||||
|
||||
92
extensions/cornerstone/src/initDoubleClick.ts
Normal file
92
extensions/cornerstone/src/initDoubleClick.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { eventTarget, EVENTS } from '@cornerstonejs/core';
|
||||
import { Enums } from '@cornerstonejs/tools';
|
||||
import { CommandsManager, CustomizationService, Types } from '@ohif/core';
|
||||
import { findNearbyToolData } from './utils/findNearbyToolData';
|
||||
|
||||
const cs3DToolsEvents = Enums.Events;
|
||||
|
||||
const DEFAULT_DOUBLE_CLICK = {
|
||||
doubleClick: {
|
||||
commandName: 'toggleOneUp',
|
||||
commandOptions: {},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a double click event name, consisting of:
|
||||
* * alt when the alt key is down
|
||||
* * ctrl when the cctrl key is down
|
||||
* * shift when the shift key is down
|
||||
* * 'doubleClick'
|
||||
*/
|
||||
function getDoubleClickEventName(evt: CustomEvent) {
|
||||
const nameArr = [];
|
||||
if (evt.detail.event.altKey) nameArr.push('alt');
|
||||
if (evt.detail.event.ctrlKey) nameArr.push('ctrl');
|
||||
if (evt.detail.event.shiftKey) nameArr.push('shift');
|
||||
nameArr.push('doubleClick');
|
||||
return nameArr.join('');
|
||||
}
|
||||
|
||||
export type initDoubleClickArgs = {
|
||||
customizationService: CustomizationService;
|
||||
commandsManager: CommandsManager;
|
||||
};
|
||||
|
||||
function initDoubleClick({
|
||||
customizationService,
|
||||
commandsManager,
|
||||
}: initDoubleClickArgs): void {
|
||||
const cornerstoneViewportHandleDoubleClick = (evt: CustomEvent) => {
|
||||
// Do not allow double click on a tool.
|
||||
const nearbyToolData = findNearbyToolData(commandsManager, evt);
|
||||
if (nearbyToolData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventName = getDoubleClickEventName(evt);
|
||||
|
||||
// Allows for the customization of the double click on a viewport.
|
||||
const customizations =
|
||||
customizationService.get('cornerstoneViewportClickCommands') ||
|
||||
DEFAULT_DOUBLE_CLICK;
|
||||
|
||||
const toRun = customizations[eventName];
|
||||
|
||||
if (!toRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
commandsManager.run(toRun);
|
||||
};
|
||||
|
||||
function elementEnabledHandler(evt: CustomEvent) {
|
||||
const { element } = evt.detail;
|
||||
|
||||
element.addEventListener(
|
||||
cs3DToolsEvents.MOUSE_DOUBLE_CLICK,
|
||||
cornerstoneViewportHandleDoubleClick
|
||||
);
|
||||
}
|
||||
|
||||
function elementDisabledHandler(evt: CustomEvent) {
|
||||
const { element } = evt.detail;
|
||||
|
||||
element.removeEventListener(
|
||||
cs3DToolsEvents.MOUSE_DOUBLE_CLICK,
|
||||
cornerstoneViewportHandleDoubleClick
|
||||
);
|
||||
}
|
||||
|
||||
eventTarget.addEventListener(
|
||||
EVENTS.ELEMENT_ENABLED,
|
||||
elementEnabledHandler.bind(null)
|
||||
);
|
||||
|
||||
eventTarget.addEventListener(
|
||||
EVENTS.ELEMENT_DISABLED,
|
||||
elementDisabledHandler.bind(null)
|
||||
);
|
||||
}
|
||||
|
||||
export default initDoubleClick;
|
||||
21
extensions/cornerstone/src/utils/findNearbyToolData.ts
Normal file
21
extensions/cornerstone/src/utils/findNearbyToolData.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Finds tool nearby event position triggered.
|
||||
*
|
||||
* @param {Object} commandsManager mannager of commands
|
||||
* @param {Object} event that has being triggered
|
||||
* @returns cs toolData or undefined if not found.
|
||||
*/
|
||||
export const findNearbyToolData = (commandsManager, evt) => {
|
||||
if (!evt?.detail) {
|
||||
return;
|
||||
}
|
||||
const { element, currentPoints } = evt.detail;
|
||||
return commandsManager.runCommand(
|
||||
'getNearbyToolData',
|
||||
{
|
||||
element,
|
||||
canvasCoordinates: currentPoints?.canvas,
|
||||
},
|
||||
'CORNERSTONE'
|
||||
);
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import { ServicesManager, Types } from '@ohif/core';
|
||||
import { ServicesManager, utils } from '@ohif/core';
|
||||
|
||||
import {
|
||||
ContextMenuController,
|
||||
@ -12,6 +12,8 @@ import findViewportsByPosition, {
|
||||
|
||||
import { ContextMenuProps } from './CustomizeableContextMenu/types';
|
||||
|
||||
const { subscribeToNextViewportGridChange } = utils;
|
||||
|
||||
export type HangingProtocolParams = {
|
||||
protocolId?: string;
|
||||
stageIndex?: number;
|
||||
@ -159,12 +161,14 @@ const commandsModule = ({
|
||||
* @param options.protocolId - the protocol ID to change to
|
||||
* @param options.stageId - the stageId to apply
|
||||
* @param options.stageIndex - the index of the stage to go to.
|
||||
* @param options.reset - flag to indicate if the HP should be reset to its original and not restored to a previous state
|
||||
*/
|
||||
setHangingProtocol: ({
|
||||
activeStudyUID = '',
|
||||
protocolId,
|
||||
stageId,
|
||||
stageIndex,
|
||||
reset = false,
|
||||
}: HangingProtocolParams): boolean => {
|
||||
try {
|
||||
// Stores in the state the reuseID to displaySetUID mapping
|
||||
@ -210,10 +214,11 @@ const commandsModule = ({
|
||||
hangingProtocolService.setActiveStudyUID(activeStudyUID);
|
||||
}
|
||||
|
||||
const storedHanging = `${hangingProtocolService.getState().activeStudyUID
|
||||
const storedHanging = `${
|
||||
hangingProtocolService.getState().activeStudyUID
|
||||
}:${protocolId}:${useStageIdx || 0}`;
|
||||
|
||||
const restoreProtocol = !!viewportGridStore[storedHanging];
|
||||
const restoreProtocol = !reset && viewportGridStore[storedHanging];
|
||||
|
||||
if (
|
||||
protocolId === hpInfo.protocolId &&
|
||||
@ -273,8 +278,9 @@ const commandsModule = ({
|
||||
activeStudy,
|
||||
} = hangingProtocolService.getActiveProtocol();
|
||||
const { toggleHangingProtocol } = stateSyncService.getState();
|
||||
const storedHanging = `${activeStudy.StudyInstanceUID
|
||||
}:${protocolId}:${stageIndex | 0}`;
|
||||
const storedHanging = `${
|
||||
activeStudy.StudyInstanceUID
|
||||
}:${protocolId}:${stageIndex | 0}`;
|
||||
if (
|
||||
protocol.id === protocolId &&
|
||||
(stageIndex === undefined || stageIndex === desiredStageIndex)
|
||||
@ -294,7 +300,11 @@ const commandsModule = ({
|
||||
},
|
||||
},
|
||||
});
|
||||
return actions.setHangingProtocol({ protocolId, stageIndex });
|
||||
return actions.setHangingProtocol({
|
||||
protocolId,
|
||||
stageIndex,
|
||||
reset: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -365,6 +375,111 @@ const commandsModule = ({
|
||||
window.setTimeout(completeLayout, 0);
|
||||
},
|
||||
|
||||
toggleOneUp() {
|
||||
const viewportGridState = viewportGridService.getState();
|
||||
const { activeViewportIndex, viewports, layout } = viewportGridState;
|
||||
const {
|
||||
displaySetInstanceUIDs,
|
||||
displaySetOptions,
|
||||
viewportOptions,
|
||||
} = viewports[activeViewportIndex];
|
||||
|
||||
if (layout.numCols === 1 && layout.numRows === 1) {
|
||||
// The viewer is in one-up. Check if there is a state to restore/toggle back to.
|
||||
const { toggleOneUpViewportGridStore } = stateSyncService.getState();
|
||||
|
||||
if (!toggleOneUpViewportGridStore.layout) {
|
||||
return;
|
||||
}
|
||||
// There is a state to toggle back to. The viewport that was
|
||||
// originally toggled to one up was the former active viewport.
|
||||
const viewportIndexToUpdate =
|
||||
toggleOneUpViewportGridStore.activeViewportIndex;
|
||||
|
||||
// Determine which viewports need to be updated. This is particularly
|
||||
// important when MPR is toggled to one up and a different reconstructable
|
||||
// is swapped in. Note that currently HangingProtocolService.getViewportsRequireUpdate
|
||||
// does not support viewport with multiple display sets.
|
||||
const updatedViewports =
|
||||
displaySetInstanceUIDs.length > 1
|
||||
? []
|
||||
: displaySetInstanceUIDs
|
||||
.map(displaySetInstanceUID =>
|
||||
hangingProtocolService.getViewportsRequireUpdate(
|
||||
viewportIndexToUpdate,
|
||||
displaySetInstanceUID
|
||||
)
|
||||
)
|
||||
.flat();
|
||||
|
||||
// This findOrCreateViewport returns either one of the updatedViewports
|
||||
// returned from the HP service OR if there is not one from the HP service then
|
||||
// simply returns what was in the previous state.
|
||||
const findOrCreateViewport = (viewportIndex: number) => {
|
||||
const viewport = updatedViewports.find(
|
||||
viewport => viewport.viewportIndex === viewportIndex
|
||||
);
|
||||
|
||||
return viewport
|
||||
? { viewportOptions, displaySetOptions, ...viewport }
|
||||
: toggleOneUpViewportGridStore.viewports[viewportIndex];
|
||||
};
|
||||
|
||||
const layoutOptions = viewportGridService.getLayoutOptionsFromState(
|
||||
toggleOneUpViewportGridStore
|
||||
);
|
||||
|
||||
// Restore the previous layout including the active viewport.
|
||||
viewportGridService.setLayout({
|
||||
numRows: toggleOneUpViewportGridStore.layout.numRows,
|
||||
numCols: toggleOneUpViewportGridStore.layout.numCols,
|
||||
activeViewportIndex: viewportIndexToUpdate,
|
||||
layoutOptions,
|
||||
findOrCreateViewport,
|
||||
});
|
||||
} else {
|
||||
// We are not in one-up, so toggle to one up.
|
||||
|
||||
// Store the current viewport grid state so we can toggle it back later.
|
||||
stateSyncService.store({
|
||||
toggleOneUpViewportGridStore: viewportGridState,
|
||||
});
|
||||
|
||||
// This findOrCreateViewport only return one viewport - the active
|
||||
// one being toggled to one up.
|
||||
const findOrCreateViewport = () => {
|
||||
return {
|
||||
displaySetInstanceUIDs,
|
||||
displaySetOptions,
|
||||
viewportOptions,
|
||||
};
|
||||
};
|
||||
|
||||
// Set the layout to be 1x1/one-up.
|
||||
viewportGridService.setLayout({
|
||||
numRows: 1,
|
||||
numCols: 1,
|
||||
findOrCreateViewport,
|
||||
});
|
||||
|
||||
// Subscribe to ANY (i.e. manual and hanging protocol) layout changes so that
|
||||
// any grid layout state to toggle to from one up is cleared. This is performed on
|
||||
// a timeout to avoid clearing the state for the actual to one up change.
|
||||
// Whenever the next layout change event is fired, the subscriptions are unsubscribed.
|
||||
const clearToggleOneUpViewportGridStore = () => {
|
||||
const toggleOneUpViewportGridStore = {};
|
||||
stateSyncService.store({
|
||||
toggleOneUpViewportGridStore,
|
||||
});
|
||||
};
|
||||
|
||||
subscribeToNextViewportGridChange(
|
||||
viewportGridService,
|
||||
clearToggleOneUpViewportGridStore
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
openDICOMTagViewer() {
|
||||
const { activeViewportIndex, viewports } = viewportGridService.getState();
|
||||
const activeViewportSpecificData = viewports[activeViewportIndex];
|
||||
@ -440,6 +555,11 @@ const commandsModule = ({
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
toggleOneUp: {
|
||||
commandFn: actions.toggleOneUp,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
openDICOMTagViewer: {
|
||||
commandFn: actions.openDICOMTagViewer,
|
||||
},
|
||||
|
||||
@ -80,7 +80,7 @@ function TrackedCornerstoneViewport(props) {
|
||||
return;
|
||||
}
|
||||
|
||||
annotation.config.style.setViewportToolStyles(`viewport-${viewportIndex}`, {
|
||||
annotation.config.style.setViewportToolStyles(viewportId, {
|
||||
global: {
|
||||
lineDash: '4,4',
|
||||
},
|
||||
|
||||
@ -2,6 +2,8 @@ import { PubSubService } from '../_shared/pubSubServiceInterface';
|
||||
|
||||
const EVENTS = {
|
||||
ACTIVE_VIEWPORT_INDEX_CHANGED: 'event::activeviewportindexchanged',
|
||||
LAYOUT_CHANGED: 'event::layoutChanged',
|
||||
GRID_STATE_CHANGED: 'event::gridStateChanged',
|
||||
};
|
||||
|
||||
class ViewportGridService extends PubSubService {
|
||||
@ -101,12 +103,26 @@ class ViewportGridService extends PubSubService {
|
||||
* options that is initially provided as {} (eg to store intermediate state)
|
||||
* The function returns a viewport object to use at the given position.
|
||||
*/
|
||||
public setLayout({ numCols, numRows, findOrCreateViewport = undefined }) {
|
||||
public setLayout({
|
||||
numCols,
|
||||
numRows,
|
||||
layoutOptions,
|
||||
layoutType = 'grid',
|
||||
activeViewportIndex = undefined,
|
||||
findOrCreateViewport = undefined,
|
||||
}) {
|
||||
this.serviceImplementation._setLayout({
|
||||
numCols,
|
||||
numRows,
|
||||
layoutOptions,
|
||||
layoutType,
|
||||
activeViewportIndex,
|
||||
findOrCreateViewport,
|
||||
});
|
||||
this._broadcastEvent(this.EVENTS.LAYOUT_CHANGED, {
|
||||
numCols,
|
||||
numRows,
|
||||
});
|
||||
}
|
||||
|
||||
public reset() {
|
||||
@ -125,11 +141,25 @@ class ViewportGridService extends PubSubService {
|
||||
|
||||
public set(state) {
|
||||
this.serviceImplementation._set(state);
|
||||
this._broadcastEvent(this.EVENTS.GRID_STATE_CHANGED, {
|
||||
state,
|
||||
});
|
||||
}
|
||||
|
||||
public getNumViewportPanes() {
|
||||
return this.serviceImplementation._getNumViewportPanes();
|
||||
}
|
||||
|
||||
public getLayoutOptionsFromState(state) {
|
||||
return state.viewports.map(viewport => {
|
||||
return {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
width: viewport.width,
|
||||
height: viewport.height,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ViewportGridService;
|
||||
|
||||
@ -6,5 +6,5 @@ export interface Command {
|
||||
|
||||
/** A set of commands, typically contained in a tool item or other configuration */
|
||||
export interface Commands {
|
||||
commands: Commands[];
|
||||
commands: Command[];
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ import {
|
||||
sortingCriteria,
|
||||
seriesSortCriteria,
|
||||
} from './sortStudy';
|
||||
import { subscribeToNextViewportGridChange } from './subscribeToNextViewportGridChange';
|
||||
|
||||
// Commented out unused functionality.
|
||||
// Need to implement new mechanism for derived displaySets using the displaySetManager.
|
||||
@ -69,6 +70,7 @@ const utils = {
|
||||
debounce,
|
||||
roundNumber,
|
||||
downloadCSVReport,
|
||||
subscribeToNextViewportGridChange,
|
||||
};
|
||||
|
||||
export {
|
||||
|
||||
@ -35,6 +35,7 @@ describe('Top level exports', () => {
|
||||
'resolveObjectPath',
|
||||
'hierarchicalListUtils',
|
||||
'progressTrackingUtils',
|
||||
'subscribeToNextViewportGridChange',
|
||||
].sort();
|
||||
|
||||
const exports = Object.keys(utils.default).sort();
|
||||
|
||||
37
platform/core/src/utils/subscribeToNextViewportGridChange.ts
Normal file
37
platform/core/src/utils/subscribeToNextViewportGridChange.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { ViewportGridService } from '../services';
|
||||
|
||||
/**
|
||||
* Subscribes to the very next LAYOUT_CHANGED or GRID_STATE_CHANGED event that
|
||||
* is not currently on the event queue. The subscriptions are made on a 'zero'
|
||||
* timeout so as to avoid responding to any of those events currently on the event queue.
|
||||
* The subscription persists only for a single invocation of either event.
|
||||
* Once either event is fired, the subscriptions are unsubscribed.
|
||||
* @param viewportGridService the viewport grid service to subscribe to
|
||||
* @param gridChangeCallback the callback
|
||||
*/
|
||||
function subscribeToNextViewportGridChange(
|
||||
viewportGridService: ViewportGridService,
|
||||
gridChangeCallback: (arg: unknown) => void
|
||||
): void {
|
||||
const subscriber = () => {
|
||||
const callback = (callbackProps: unknown) => {
|
||||
subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
gridChangeCallback(callbackProps);
|
||||
};
|
||||
|
||||
const subscriptions = [
|
||||
viewportGridService.subscribe(
|
||||
viewportGridService.EVENTS.LAYOUT_CHANGED,
|
||||
callback
|
||||
),
|
||||
viewportGridService.subscribe(
|
||||
viewportGridService.EVENTS.GRID_STATE_CHANGED,
|
||||
callback
|
||||
),
|
||||
];
|
||||
};
|
||||
|
||||
window.setTimeout(subscriber, 0);
|
||||
}
|
||||
|
||||
export { subscribeToNextViewportGridChange };
|
||||
@ -9,6 +9,15 @@ sidebar_label: Viewport Grid Service
|
||||
|
||||
This is a new UI service, that handles the grid layout of the viewer.
|
||||
|
||||
## Events
|
||||
|
||||
There are seven events that get publish in `ViewportGridService `:
|
||||
|
||||
| Event | Description |
|
||||
| ----------------------------- | --------------------------------------------------|
|
||||
| ACTIVE_VIEWPORT_INDEX_CHANGED | Fires the index of the active viewport is changed |
|
||||
| LAYOUT_CHANGED | Fires the layout is changed |
|
||||
| GRID_STATE_CHANGED | Fires when the entire grid state is changed |
|
||||
## Interface
|
||||
|
||||
For a more detailed look on the options and return values each of these methods
|
||||
@ -19,9 +28,10 @@ is expected to support, [check out it's interface in `@ohif/core`][interface]
|
||||
| `setActiveViewportIndex(index)` | Sets the active viewport index in the app |
|
||||
| `getState()` | Gets the states of the viewport (see below) |
|
||||
| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id |
|
||||
| `setLayout({numCols, numRows, keepExtraViewports})` | Sets rows and columns. When the total number of viewports decreases, optionally keep the extra/offscreen viewports. |
|
||||
| `setLayout({numCols, numRows, keepExtraViewports})` | Sets rows and columns. When the total number of viewports decreases, optionally keep the extra/offscreen viewports. |
|
||||
| `reset()` | Resets the default states |
|
||||
| `getNumViewportPanes()` | Gets the number of visible viewport panes |
|
||||
| `getLayoutOptionsFromState(gridState)` | Utility method that produces a `ViewportLayoutOptions` based on the passed in state|
|
||||
|
||||
## Implementations
|
||||
|
||||
|
||||
@ -146,6 +146,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
numRows,
|
||||
layoutOptions,
|
||||
layoutType = 'grid',
|
||||
activeViewportIndex,
|
||||
findOrCreateViewport,
|
||||
} = action.payload;
|
||||
|
||||
@ -160,7 +161,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
// haven't been viewed yet, and add them in the appropriate order.
|
||||
const options = {};
|
||||
|
||||
let activeViewportIndex;
|
||||
let activeViewportIndexToSet = activeViewportIndex;
|
||||
for (let row = 0; row < numRows; row++) {
|
||||
for (let col = 0; col < numCols; col++) {
|
||||
const pos = col + row * numCols;
|
||||
@ -170,10 +171,11 @@ export function ViewportGridProvider({ children, service }) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
!activeViewportIndex ||
|
||||
state.viewports[pos]?.positionId === positionId
|
||||
activeViewportIndexToSet == null &&
|
||||
state.viewports[state.activeViewportIndex]?.positionId ===
|
||||
positionId
|
||||
) {
|
||||
activeViewportIndex = pos;
|
||||
activeViewportIndexToSet = pos;
|
||||
}
|
||||
const viewport = findOrCreateViewport(pos, positionId, options);
|
||||
if (!viewport) continue;
|
||||
@ -199,6 +201,8 @@ export function ViewportGridProvider({ children, service }) {
|
||||
}
|
||||
}
|
||||
|
||||
activeViewportIndexToSet = activeViewportIndexToSet ?? 0;
|
||||
|
||||
const viewportIdSet = {};
|
||||
for (
|
||||
let viewportIndex = 0;
|
||||
@ -223,7 +227,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
|
||||
const ret = {
|
||||
...state,
|
||||
activeViewportIndex,
|
||||
activeViewportIndex: activeViewportIndexToSet,
|
||||
layout: {
|
||||
...state.layout,
|
||||
numCols,
|
||||
@ -300,6 +304,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
numRows,
|
||||
numCols,
|
||||
layoutOptions = [],
|
||||
activeViewportIndex,
|
||||
findOrCreateViewport,
|
||||
}) =>
|
||||
dispatch({
|
||||
@ -309,6 +314,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
numRows,
|
||||
numCols,
|
||||
layoutOptions,
|
||||
activeViewportIndex,
|
||||
findOrCreateViewport,
|
||||
},
|
||||
}),
|
||||
@ -375,9 +381,9 @@ export function ViewportGridProvider({ children, service }) {
|
||||
setActiveViewportIndex: index => service.setActiveViewportIndex(index), // run it through the service itself since we want to publish events
|
||||
setDisplaySetsForViewport,
|
||||
setDisplaySetsForViewports,
|
||||
setLayout,
|
||||
setLayout: layout => service.setLayout(layout), // run it through the service itself since we want to publish events
|
||||
reset,
|
||||
set,
|
||||
set: gridLayoutState => service.setState(gridLayoutState), // run it through the service itself since we want to publish events
|
||||
getNumViewportPanes,
|
||||
};
|
||||
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
describe('OHIF Double Click', () => {
|
||||
beforeEach(() => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1',
|
||||
'&hangingProtocolId=@ohif/hp-extension.mn'
|
||||
);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
});
|
||||
|
||||
it('Should double click each viewport to one up and back', () => {
|
||||
const numExpectedViewports = 3;
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.its('length')
|
||||
.should('be.eq', numExpectedViewports);
|
||||
|
||||
for (let i = 0; i < numExpectedViewports; i += 1) {
|
||||
// For whatever reason, with Cypress tests, we have to activate the
|
||||
// viewport we are double clicking first.
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.eq(i)
|
||||
.trigger('mousedown', 'center', { force: true })
|
||||
.trigger('mouseup', 'center', { force: true });
|
||||
|
||||
// Wait for the viewport to be 'active'.
|
||||
// TODO Is there a better way to do this?
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.eq(i)
|
||||
.parent()
|
||||
.find('[data-cy="viewport-pane"]')
|
||||
.not('.pointer-events-none');
|
||||
|
||||
// The actual double click.
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.eq(i)
|
||||
.trigger('dblclick', 'center');
|
||||
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.its('length')
|
||||
.should('be.eq', 1);
|
||||
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.eq(0)
|
||||
.trigger('dblclick', 'center');
|
||||
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.its('length')
|
||||
.should('be.eq', numExpectedViewports);
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user