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:
1 parent
4734b3bac6
commit
d5ff590dfc
14 files changed
+402
-40
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user