feat(hotkeys): Migrate hotkeys to customization service and fix issues with overrides (#4777)
This commit is contained in:
parent
4559d39a91
commit
3e6913b097
@ -25,7 +25,6 @@ import {
|
||||
colorPickerDialog,
|
||||
} from '@ohif/extension-default';
|
||||
import { vec3, mat4 } from 'gl-matrix';
|
||||
|
||||
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
||||
import toggleImageSliceSync from './utils/imageSliceSync/toggleImageSliceSync';
|
||||
import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection';
|
||||
@ -34,6 +33,7 @@ import toggleVOISliceSync from './utils/toggleVOISliceSync';
|
||||
import { usePositionPresentationStore, useSegmentationPresentationStore } from './stores';
|
||||
import { toolNames } from './initCornerstoneTools';
|
||||
|
||||
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
||||
const toggleSyncFunctions = {
|
||||
imageSlice: toggleImageSliceSync,
|
||||
voi: toggleVOISliceSync,
|
||||
@ -430,6 +430,37 @@ function commandsModule({
|
||||
|
||||
actions.setViewportWindowLevel({ ...props, viewportId });
|
||||
},
|
||||
setWindowLevelPreset: ({ presetName, presetIndex }) => {
|
||||
const windowLevelPresets = customizationService.getCustomization(
|
||||
'cornerstone.windowLevelPresets'
|
||||
);
|
||||
|
||||
const activeViewport = viewportGridService.getActiveViewportId();
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(activeViewport);
|
||||
const metadata = viewport.getImageData().metadata;
|
||||
|
||||
const modality = metadata.Modality;
|
||||
|
||||
if (!modality) {
|
||||
return;
|
||||
}
|
||||
|
||||
const windowLevelPresetForModality = windowLevelPresets[modality];
|
||||
|
||||
if (!windowLevelPresetForModality) {
|
||||
return;
|
||||
}
|
||||
|
||||
const windowLevelPreset =
|
||||
windowLevelPresetForModality[presetName] ??
|
||||
Object.values(windowLevelPresetForModality)[presetIndex];
|
||||
|
||||
actions.setViewportWindowLevel({
|
||||
viewportId: activeViewport,
|
||||
window: windowLevelPreset.window,
|
||||
level: windowLevelPreset.level,
|
||||
});
|
||||
},
|
||||
setToolEnabled: ({ toolName, toggle, toolGroupId }) => {
|
||||
const { viewports } = viewportGridService.getState();
|
||||
|
||||
@ -1327,6 +1358,12 @@ function commandsModule({
|
||||
measurementService.remove(activeAnnotationUID);
|
||||
});
|
||||
},
|
||||
undo: () => {
|
||||
DefaultHistoryMemo.undo();
|
||||
},
|
||||
redo: () => {
|
||||
DefaultHistoryMemo.redo();
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@ -1391,6 +1428,9 @@ function commandsModule({
|
||||
setWindowLevel: {
|
||||
commandFn: actions.setWindowLevel,
|
||||
},
|
||||
setWindowLevelPreset: {
|
||||
commandFn: actions.setWindowLevelPreset,
|
||||
},
|
||||
setToolActive: {
|
||||
commandFn: actions.setToolActive,
|
||||
},
|
||||
@ -1590,6 +1630,8 @@ function commandsModule({
|
||||
deleteActiveAnnotation: {
|
||||
commandFn: actions.deleteActiveAnnotation,
|
||||
},
|
||||
undo: actions.undo,
|
||||
redo: actions.redo,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -199,7 +199,7 @@ function ViewerLayout({
|
||||
</ResizablePanelGroup>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
<Onboarding />
|
||||
<Onboarding tours={customizationService.getCustomization('ohif.tours')} />
|
||||
<InvestigationalUseDialog dialogConfiguration={appConfig?.investigationalUseDialog} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import { defaults } from '@ohif/core';
|
||||
|
||||
export default {
|
||||
'ohif.hotkeyBindings': defaults.hotkeyBindings,
|
||||
};
|
||||
210
extensions/default/src/customizations/onboardingCustomization.ts
Normal file
210
extensions/default/src/customizations/onboardingCustomization.ts
Normal file
@ -0,0 +1,210 @@
|
||||
function waitForElement(selector, maxAttempts = 20, interval = 25) {
|
||||
return new Promise(resolve => {
|
||||
let attempts = 0;
|
||||
|
||||
const checkForElement = setInterval(() => {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (element || attempts >= maxAttempts) {
|
||||
clearInterval(checkForElement);
|
||||
resolve();
|
||||
}
|
||||
|
||||
attempts++;
|
||||
}, interval);
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
'ohif.tours': [
|
||||
{
|
||||
id: 'basicViewerTour',
|
||||
route: '/viewer',
|
||||
steps: [
|
||||
{
|
||||
id: 'scroll',
|
||||
title: 'Scrolling Through Images',
|
||||
text: 'You can scroll through the images using the mouse wheel or scrollbar.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'top',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_WHEEL',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'zoom',
|
||||
title: 'Zooming In and Out',
|
||||
text: 'You can zoom the images using the right click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'pan',
|
||||
title: 'Panning the Image',
|
||||
text: 'You can pan the images using the middle click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'top',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'windowing',
|
||||
title: 'Adjusting Window Level',
|
||||
text: 'You can modify the window level using the left click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'length',
|
||||
title: 'Using the Measurement Tools',
|
||||
text: 'You can measure the length of a region using the Length tool.',
|
||||
attachTo: {
|
||||
element: '[data-cy="MeasurementTools-split-button-primary"]',
|
||||
on: 'bottom',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="MeasurementTools-split-button-primary"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () =>
|
||||
waitForElement('[data-cy="MeasurementTools-split-button-primary]'),
|
||||
},
|
||||
{
|
||||
id: 'drawAnnotation',
|
||||
title: 'Drawing Length Annotations',
|
||||
text: 'Use the length tool on the viewport to measure the length of a region.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'right',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: 'body',
|
||||
event: 'event::measurement_added',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'trackMeasurement',
|
||||
title: 'Tracking Measurements in the Panel',
|
||||
text: 'Click yes to track the measurements in the measurement panel.',
|
||||
attachTo: {
|
||||
element: '[data-cy="prompt-begin-tracking-yes-btn"]',
|
||||
on: 'bottom',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="prompt-begin-tracking-yes-btn"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="prompt-begin-tracking-yes-btn"]'),
|
||||
},
|
||||
{
|
||||
id: 'openMeasurementPanel',
|
||||
title: 'Opening the Measurements Panel',
|
||||
text: 'Click the measurements button to open the measurements panel.',
|
||||
attachTo: {
|
||||
element: '#trackedMeasurements-btn',
|
||||
on: 'left-start',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '#trackedMeasurements-btn',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('#trackedMeasurements-btn'),
|
||||
},
|
||||
{
|
||||
id: 'scrollAwayFromMeasurement',
|
||||
title: 'Scrolling Away from a Measurement',
|
||||
text: 'Scroll the images using the mouse wheel away from the measurement.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_WHEEL',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'jumpToMeasurement',
|
||||
title: 'Jumping to Measurements in the Panel',
|
||||
text: 'Click the measurement in the measurement panel to jump to it.',
|
||||
attachTo: {
|
||||
element: '[data-cy="data-row"]',
|
||||
on: 'left-start',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="data-row"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="data-row"]'),
|
||||
},
|
||||
{
|
||||
id: 'changeLayout',
|
||||
title: 'Changing Layout',
|
||||
text: 'You can change the layout of the viewer using the layout button.',
|
||||
attachTo: {
|
||||
element: '[data-cy="Layout"]',
|
||||
on: 'bottom',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="Layout"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="Layout"]'),
|
||||
},
|
||||
{
|
||||
id: 'selectLayout',
|
||||
title: 'Selecting the MPR Layout',
|
||||
text: 'Select the MPR layout to view the images in MPR mode.',
|
||||
attachTo: {
|
||||
element: '[data-cy="MPR"]',
|
||||
on: 'left-start',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="MPR"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="MPR"]'),
|
||||
},
|
||||
],
|
||||
tourOptions: {
|
||||
useModalOverlay: true,
|
||||
defaultStepOptions: {
|
||||
buttons: [
|
||||
{
|
||||
text: 'Skip all',
|
||||
action() {
|
||||
this.complete();
|
||||
},
|
||||
secondary: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -18,7 +18,8 @@ import progressLoadingBarCustomization from './customizations/progressLoadingBar
|
||||
import viewportActionCornersCustomization from './customizations/viewportActionCornersCustomization';
|
||||
import labellingFlowCustomization from './customizations/labellingFlowCustomization';
|
||||
import viewportNotificationCustomization from './customizations/notificationCustomization';
|
||||
|
||||
import hotkeyBindingsCustomization from './customizations/hotkeyBindingsCustomization';
|
||||
import onboardingCustomization from './customizations/onboardingCustomization';
|
||||
/**
|
||||
*
|
||||
* Note: this is an example of how the customization module can be used
|
||||
@ -62,6 +63,8 @@ export default function getCustomizationModule({ servicesManager, extensionManag
|
||||
...labellingFlowCustomization,
|
||||
...contextMenuUICustomization,
|
||||
...viewportNotificationCustomization,
|
||||
...hotkeyBindingsCustomization,
|
||||
...onboardingCustomization,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -171,7 +171,6 @@ function modeFactory({ modeConfiguration }) {
|
||||
dicompdf.sopClassHandler,
|
||||
dicomsr.sopClassHandler,
|
||||
],
|
||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -102,6 +102,22 @@ function modeFactory() {
|
||||
'Crosshairs',
|
||||
'MoreTools',
|
||||
]);
|
||||
|
||||
customizationService.setCustomizations(
|
||||
{
|
||||
'ohif.hotkeyBindings': {
|
||||
$push: [
|
||||
{
|
||||
commandName: 'undo',
|
||||
label: 'Undo',
|
||||
keys: ['ctrl+z'],
|
||||
isEditable: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
'mode'
|
||||
);
|
||||
},
|
||||
onModeExit: ({ servicesManager }: withAppTypes) => {
|
||||
const {
|
||||
@ -209,7 +225,6 @@ function modeFactory() {
|
||||
// Don't store the hotkeys for basic-test-mode under the same key
|
||||
// because they get customized by tests
|
||||
name: 'basic-test-hotkeys',
|
||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -84,7 +84,8 @@ function modeFactory({ modeConfiguration }) {
|
||||
* Lifecycle hooks
|
||||
*/
|
||||
onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) {
|
||||
const { measurementService, toolbarService, toolGroupService } = servicesManager.services;
|
||||
const { measurementService, toolbarService, toolGroupService, customizationService } =
|
||||
servicesManager.services;
|
||||
|
||||
measurementService.clearMeasurements();
|
||||
|
||||
@ -238,7 +239,6 @@ function modeFactory({ modeConfiguration }) {
|
||||
dicomsr.sopClassHandler,
|
||||
dicomRT.sopClassHandler,
|
||||
],
|
||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
...modeConfiguration,
|
||||
};
|
||||
}
|
||||
|
||||
@ -205,6 +205,32 @@ const toolbarButtons: Button[] = [
|
||||
},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// id: 'Undo',
|
||||
// uiType: 'ohif.toolButton',
|
||||
// props: {
|
||||
// type: 'tool',
|
||||
// icon: 'prev-arrow',
|
||||
// label: 'Undo',
|
||||
// commands: {
|
||||
// commandName: 'undo',
|
||||
// },
|
||||
// evaluate: 'evaluate.action',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// id: 'Redo',
|
||||
// uiType: 'ohif.toolButton',
|
||||
// props: {
|
||||
// type: 'tool',
|
||||
// icon: 'next-arrow',
|
||||
// label: 'Redo',
|
||||
// commands: {
|
||||
// commandName: 'redo',
|
||||
// },
|
||||
// evaluate: 'evaluate.action',
|
||||
// },
|
||||
// },
|
||||
];
|
||||
|
||||
export default toolbarButtons;
|
||||
|
||||
@ -123,7 +123,6 @@ function modeFactory({ modeConfiguration }) {
|
||||
dicomvideo.sopClassHandler,
|
||||
dicompdf.sopClassHandler,
|
||||
],
|
||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
...modeConfiguration,
|
||||
};
|
||||
}
|
||||
|
||||
@ -167,7 +167,6 @@ function modeFactory({ modeConfiguration }) {
|
||||
// general handler needs to come last. For this case, the dicomvideo must
|
||||
// come first to remove video transfer syntax before ohif uses images
|
||||
sopClassHandlers: [ohif.chartSopClassHandler, ohif.defaultSopClassHandler],
|
||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { hotkeys } from '@ohif/core';
|
||||
import { id } from './id';
|
||||
import toolbarButtons from './toolbarButtons';
|
||||
import segmentationButtons from './segmentationButtons';
|
||||
@ -49,7 +48,8 @@ function modeFactory({ modeConfiguration }) {
|
||||
* Services and other resources.
|
||||
*/
|
||||
onModeEnter: ({ servicesManager, extensionManager, commandsManager }: withAppTypes) => {
|
||||
const { measurementService, toolbarService, toolGroupService } = servicesManager.services;
|
||||
const { measurementService, toolbarService, toolGroupService, customizationService } =
|
||||
servicesManager.services;
|
||||
|
||||
measurementService.clearMeasurements();
|
||||
|
||||
@ -158,8 +158,6 @@ function modeFactory({ modeConfiguration }) {
|
||||
// hangingProtocol: ['@ohif/mnGrid'],
|
||||
/** SopClassHandlers used by the mode */
|
||||
sopClassHandlers: [ohif.sopClassHandler, segmentation.sopClassHandler],
|
||||
/** hotkeys for mode */
|
||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -216,7 +216,6 @@ function modeFactory({ modeConfiguration }) {
|
||||
extensions: extensionDependencies,
|
||||
hangingProtocol: tmtv.hangingProtocol,
|
||||
sopClassHandlers: [ohif.sopClassHandler],
|
||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
...modeConfiguration,
|
||||
};
|
||||
}
|
||||
|
||||
@ -57,92 +57,4 @@ window.config = {
|
||||
// Could use services manager here to bring up a dialog/modal if needed.
|
||||
console.warn('test, navigate to https://ohif.org/');
|
||||
},
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// {
|
||||
// commandName: 'previousViewportDisplaySet',
|
||||
// label: 'Previous Series',
|
||||
// keys: ['pagedown'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'nextViewportDisplaySet',
|
||||
// label: 'Next Series',
|
||||
// keys: ['pageup'],
|
||||
// },
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -293,305 +293,4 @@ window.config = {
|
||||
// ))
|
||||
// },
|
||||
// },
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// {
|
||||
// commandName: 'previousViewportDisplaySet',
|
||||
// label: 'Previous Series',
|
||||
// keys: ['pagedown'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'nextViewportDisplaySet',
|
||||
// label: 'Next Series',
|
||||
// keys: ['pageup'],
|
||||
// },
|
||||
{
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Zoom' },
|
||||
label: 'Zoom',
|
||||
keys: ['z'],
|
||||
},
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
tours: [
|
||||
{
|
||||
id: 'basicViewerTour',
|
||||
route: '/viewer',
|
||||
steps: [
|
||||
{
|
||||
id: 'scroll',
|
||||
title: 'Scrolling Through Images',
|
||||
text: 'You can scroll through the images using the mouse wheel or scrollbar.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'top',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_WHEEL',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'zoom',
|
||||
title: 'Zooming In and Out',
|
||||
text: 'You can zoom the images using the right click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'pan',
|
||||
title: 'Panning the Image',
|
||||
text: 'You can pan the images using the middle click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'top',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'windowing',
|
||||
title: 'Adjusting Window Level',
|
||||
text: 'You can modify the window level using the left click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'length',
|
||||
title: 'Using the Measurement Tools',
|
||||
text: 'You can measure the length of a region using the Length tool.',
|
||||
attachTo: {
|
||||
element: '[data-cy="MeasurementTools-split-button-primary"]',
|
||||
on: 'bottom',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="MeasurementTools-split-button-primary"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () =>
|
||||
waitForElement('[data-cy="MeasurementTools-split-button-primary]'),
|
||||
},
|
||||
{
|
||||
id: 'drawAnnotation',
|
||||
title: 'Drawing Length Annotations',
|
||||
text: 'Use the length tool on the viewport to measure the length of a region.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'right',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: 'body',
|
||||
event: 'event::measurement_added',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'trackMeasurement',
|
||||
title: 'Tracking Measurements in the Panel',
|
||||
text: 'Click yes to track the measurements in the measurement panel.',
|
||||
attachTo: {
|
||||
element: '[data-cy="prompt-begin-tracking-yes-btn"]',
|
||||
on: 'bottom',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="prompt-begin-tracking-yes-btn"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="prompt-begin-tracking-yes-btn"]'),
|
||||
},
|
||||
{
|
||||
id: 'openMeasurementPanel',
|
||||
title: 'Opening the Measurements Panel',
|
||||
text: 'Click the measurements button to open the measurements panel.',
|
||||
attachTo: {
|
||||
element: '#trackedMeasurements-btn',
|
||||
on: 'left-start',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '#trackedMeasurements-btn',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('#trackedMeasurements-btn'),
|
||||
},
|
||||
{
|
||||
id: 'scrollAwayFromMeasurement',
|
||||
title: 'Scrolling Away from a Measurement',
|
||||
text: 'Scroll the images using the mouse wheel away from the measurement.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_WHEEL',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'jumpToMeasurement',
|
||||
title: 'Jumping to Measurements in the Panel',
|
||||
text: 'Click the measurement in the measurement panel to jump to it.',
|
||||
attachTo: {
|
||||
element: '[data-cy="data-row"]',
|
||||
on: 'left-start',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="data-row"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="data-row"]'),
|
||||
},
|
||||
{
|
||||
id: 'changeLayout',
|
||||
title: 'Changing Layout',
|
||||
text: 'You can change the layout of the viewer using the layout button.',
|
||||
attachTo: {
|
||||
element: '[data-cy="Layout"]',
|
||||
on: 'bottom',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="Layout"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="Layout"]'),
|
||||
},
|
||||
{
|
||||
id: 'selectLayout',
|
||||
title: 'Selecting the MPR Layout',
|
||||
text: 'Select the MPR layout to view the images in MPR mode.',
|
||||
attachTo: {
|
||||
element: '[data-cy="MPR"]',
|
||||
on: 'left-start',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="MPR"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="MPR"]'),
|
||||
},
|
||||
],
|
||||
tourOptions: {
|
||||
useModalOverlay: true,
|
||||
defaultStepOptions: {
|
||||
buttons: [
|
||||
{
|
||||
text: 'Skip all',
|
||||
action() {
|
||||
this.complete();
|
||||
},
|
||||
secondary: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function waitForElement(selector, maxAttempts = 20, interval = 25) {
|
||||
return new Promise(resolve => {
|
||||
let attempts = 0;
|
||||
|
||||
const checkForElement = setInterval(() => {
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (element || attempts >= maxAttempts) {
|
||||
clearInterval(checkForElement);
|
||||
resolve();
|
||||
}
|
||||
|
||||
attempts++;
|
||||
}, interval);
|
||||
});
|
||||
}
|
||||
|
||||
@ -93,97 +93,4 @@ window.config = {
|
||||
// },
|
||||
// },
|
||||
defaultDataSourceName: 'dicomweb',
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// {
|
||||
// commandName: 'previousViewportDisplaySet',
|
||||
// label: 'Previous Series',
|
||||
// keys: ['pagedown'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'nextViewportDisplaySet',
|
||||
// label: 'Next Series',
|
||||
// keys: ['pageup'],
|
||||
// },
|
||||
{
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Zoom' },
|
||||
label: 'Zoom',
|
||||
keys: ['z'],
|
||||
},
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -29,94 +29,6 @@ window.config = {
|
||||
},
|
||||
},
|
||||
],
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// {
|
||||
// commandName: 'previousViewportDisplaySet',
|
||||
// label: 'Previous Series',
|
||||
// keys: ['pagedown'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'nextViewportDisplaySet',
|
||||
// label: 'Next Series',
|
||||
// keys: ['pageup'],
|
||||
// },
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
i18n: {
|
||||
LOCIZE_PROJECTID: 'a8da3f9a-e467-4dd6-af33-474d582a0294',
|
||||
LOCIZE_API_KEY: null, // Developers can use this to do in-context editing. DO NOT COMMIT THIS KEY!
|
||||
|
||||
@ -56,92 +56,4 @@ window.config = {
|
||||
// Could use services manager here to bring up a dialog/modal if needed.
|
||||
console.warn('test, navigate to https://ohif.org/');
|
||||
},
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// {
|
||||
// commandName: 'previousViewportDisplaySet',
|
||||
// label: 'Previous Series',
|
||||
// keys: ['pagedown'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'nextViewportDisplaySet',
|
||||
// label: 'Next Series',
|
||||
// keys: ['pageup'],
|
||||
// },
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -31,23 +31,3 @@ window.config = {
|
||||
],
|
||||
studyListFunctionsEnabled: true,
|
||||
};
|
||||
|
||||
// window.config = function(props) {
|
||||
// var servicesManager = props.servicesManager;
|
||||
|
||||
// return {
|
||||
// routerBasename: '/',
|
||||
// enableGoogleCloudAdapter: true,
|
||||
// enableGoogleCloudAdapterUI: false,
|
||||
// showStudyList: true,
|
||||
// httpErrorHandler: error => {
|
||||
// // This is 429 when rejected from the public idc sandbox too often.
|
||||
// console.warn(error.status);
|
||||
|
||||
// // Could use services manager here to bring up a dialog/modal if needed.
|
||||
// console.warn('test, navigate to https://ohif.org/');
|
||||
// window.location = 'https://ohif.org/';
|
||||
// },
|
||||
// healthcareApiEndpoint: 'https://idc-sandbox-002.appspot.com/v1beta1',
|
||||
// };
|
||||
// };
|
||||
|
||||
@ -199,97 +199,4 @@ window.config = {
|
||||
// ))
|
||||
// },
|
||||
// },
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// {
|
||||
// commandName: 'previousViewportDisplaySet',
|
||||
// label: 'Previous Series',
|
||||
// keys: ['pagedown'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'nextViewportDisplaySet',
|
||||
// label: 'Next Series',
|
||||
// keys: ['pageup'],
|
||||
// },
|
||||
{
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Zoom' },
|
||||
label: 'Zoom',
|
||||
keys: ['z'],
|
||||
},
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -69,92 +69,4 @@ window.config = {
|
||||
// Could use services manager here to bring up a dialog/modal if needed.
|
||||
console.warn('test, navigate to https://ohif.org/');
|
||||
},
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
{
|
||||
commandName: 'previousViewportDisplaySet',
|
||||
label: 'Previous Series',
|
||||
keys: ['pagedown'],
|
||||
},
|
||||
{
|
||||
commandName: 'nextViewportDisplaySet',
|
||||
label: 'Next Series',
|
||||
keys: ['pageup'],
|
||||
},
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -131,92 +131,4 @@ window.config = {
|
||||
// Could use services manager here to bring up a dialog/modal if needed.
|
||||
console.warn('test, navigate to https://ohif.org/');
|
||||
},
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// {
|
||||
// commandName: 'previousViewportDisplaySet',
|
||||
// label: 'Previous Series',
|
||||
// keys: ['pagedown'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'nextViewportDisplaySet',
|
||||
// label: 'Next Series',
|
||||
// keys: ['pageup'],
|
||||
// },
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -158,285 +158,6 @@ window.config = {
|
||||
// Could use services manager here to bring up a dialog/modal if needed.
|
||||
console.warn('test, navigate to https://ohif.org/');
|
||||
},
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// {
|
||||
// commandName: 'previousViewportDisplaySet',
|
||||
// label: 'Previous Series',
|
||||
// keys: ['pagedown'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'nextViewportDisplaySet',
|
||||
// label: 'Next Series',
|
||||
// keys: ['pageup'],
|
||||
// },
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
tours: [
|
||||
{
|
||||
id: 'basicViewerTour',
|
||||
route: '/viewer',
|
||||
steps: [
|
||||
{
|
||||
id: 'scroll',
|
||||
title: 'Scrolling Through Images',
|
||||
text: 'You can scroll through the images using the mouse wheel or scrollbar.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'top',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_WHEEL',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'zoom',
|
||||
title: 'Zooming In and Out',
|
||||
text: 'You can zoom the images using the right click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'pan',
|
||||
title: 'Panning the Image',
|
||||
text: 'You can pan the images using the middle click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'top',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'windowing',
|
||||
title: 'Adjusting Window Level',
|
||||
text: 'You can modify the window level using the left click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'length',
|
||||
title: 'Using the Measurement Tools',
|
||||
text: 'You can measure the length of a region using the Length tool.',
|
||||
attachTo: {
|
||||
element: '[data-cy="MeasurementTools-split-button-primary"]',
|
||||
on: 'bottom',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="MeasurementTools-split-button-primary"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () =>
|
||||
waitForElement('[data-cy="MeasurementTools-split-button-primary]'),
|
||||
},
|
||||
{
|
||||
id: 'drawAnnotation',
|
||||
title: 'Drawing Length Annotations',
|
||||
text: 'Use the length tool on the viewport to measure the length of a region.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'right',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: 'body',
|
||||
event: 'event::measurement_added',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'trackMeasurement',
|
||||
title: 'Tracking Measurements in the Panel',
|
||||
text: 'Click yes to track the measurements in the measurement panel.',
|
||||
attachTo: {
|
||||
element: '[data-cy="prompt-begin-tracking-yes-btn"]',
|
||||
on: 'bottom',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="prompt-begin-tracking-yes-btn"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="prompt-begin-tracking-yes-btn"]'),
|
||||
},
|
||||
{
|
||||
id: 'openMeasurementPanel',
|
||||
title: 'Opening the Measurements Panel',
|
||||
text: 'Click the measurements button to open the measurements panel.',
|
||||
attachTo: {
|
||||
element: '#trackedMeasurements-btn',
|
||||
on: 'left-start',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '#trackedMeasurements-btn',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('#trackedMeasurements-btn'),
|
||||
},
|
||||
{
|
||||
id: 'scrollAwayFromMeasurement',
|
||||
title: 'Scrolling Away from a Measurement',
|
||||
text: 'Scroll the images using the mouse wheel away from the measurement.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_WHEEL',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('.viewport-element'),
|
||||
},
|
||||
{
|
||||
id: 'jumpToMeasurement',
|
||||
title: 'Jumping to Measurements in the Panel',
|
||||
text: 'Click the measurement in the measurement panel to jump to it.',
|
||||
attachTo: {
|
||||
element: '[data-cy="data-row"]',
|
||||
on: 'left-start',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="data-row"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="data-row"]'),
|
||||
},
|
||||
{
|
||||
id: 'changeLayout',
|
||||
title: 'Changing Layout',
|
||||
text: 'You can change the layout of the viewer using the layout button.',
|
||||
attachTo: {
|
||||
element: '[data-cy="Layout"]',
|
||||
on: 'bottom',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="Layout"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="Layout"]'),
|
||||
},
|
||||
{
|
||||
id: 'selectLayout',
|
||||
title: 'Selecting the MPR Layout',
|
||||
text: 'Select the MPR layout to view the images in MPR mode.',
|
||||
attachTo: {
|
||||
element: '[data-cy="MPR"]',
|
||||
on: 'left-start',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '[data-cy="MPR"]',
|
||||
event: 'click',
|
||||
},
|
||||
beforeShowPromise: () => waitForElement('[data-cy="MPR"]'),
|
||||
},
|
||||
],
|
||||
tourOptions: {
|
||||
useModalOverlay: true,
|
||||
defaultStepOptions: {
|
||||
buttons: [
|
||||
{
|
||||
text: 'Skip all',
|
||||
action() {
|
||||
this.complete();
|
||||
},
|
||||
secondary: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function waitForElement(selector, maxAttempts = 20, interval = 25) {
|
||||
|
||||
@ -26,92 +26,4 @@ window.config = {
|
||||
LOCIZE_API_KEY: null, // Developers can use this to do in-context editing. DO NOT COMMIT THIS KEY!
|
||||
USE_LOCIZE: false,
|
||||
},
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// {
|
||||
// commandName: 'previousViewportDisplaySet',
|
||||
// label: 'Previous Series',
|
||||
// keys: ['pagedown'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'nextViewportDisplaySet',
|
||||
// label: 'Next Series',
|
||||
// keys: ['pageup'],
|
||||
// },
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -60,10 +60,15 @@ export default function ModeRoute({
|
||||
locationRef.current = location;
|
||||
}
|
||||
|
||||
const { displaySetService, panelService, hangingProtocolService, userAuthenticationService } =
|
||||
servicesManager.services;
|
||||
const {
|
||||
displaySetService,
|
||||
panelService,
|
||||
hangingProtocolService,
|
||||
userAuthenticationService,
|
||||
customizationService,
|
||||
} = servicesManager.services;
|
||||
|
||||
const { extensions, sopClassHandlers, hotkeys: hotkeyObj, hangingProtocol } = mode;
|
||||
const { extensions, sopClassHandlers, hangingProtocol } = mode;
|
||||
|
||||
const runTimeHangingProtocolId = lowerCaseSearchParams.get('hangingprotocolid');
|
||||
const runTimeStageId = lowerCaseSearchParams.get('stageid');
|
||||
@ -73,10 +78,6 @@ export default function ModeRoute({
|
||||
updateAuthServiceAndCleanUrl(token, location, userAuthenticationService);
|
||||
}
|
||||
|
||||
// Preserve the old array interface for hotkeys
|
||||
const hotkeys = Array.isArray(hotkeyObj) ? hotkeyObj : hotkeyObj?.hotkeys;
|
||||
const hotkeyName = hotkeyObj?.name || 'hotkey-definitions';
|
||||
|
||||
// An undefined dataSourceName implies that the active data source that is already set in the ExtensionManager should be used.
|
||||
if (dataSourceName !== undefined) {
|
||||
extensionManager.setActiveDataSource(dataSourceName);
|
||||
@ -167,26 +168,6 @@ export default function ModeRoute({
|
||||
};
|
||||
}, [studyInstanceUIDs, ExtensionDependenciesLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hotkeys || !ExtensionDependenciesLoaded || !studyInstanceUIDs?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
hotkeysManager.setDefaultHotKeys(hotkeys);
|
||||
|
||||
const userPreferredHotkeys = JSON.parse(localStorage.getItem(hotkeyName));
|
||||
|
||||
if (userPreferredHotkeys?.length) {
|
||||
hotkeysManager.setHotkeys(userPreferredHotkeys, hotkeyName);
|
||||
} else {
|
||||
hotkeysManager.setHotkeys(hotkeys, hotkeyName);
|
||||
}
|
||||
|
||||
return () => {
|
||||
hotkeysManager.destroy();
|
||||
};
|
||||
}, [ExtensionDependenciesLoaded, hotkeys, studyInstanceUIDs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!layoutTemplateData.current || !ExtensionDependenciesLoaded || !studyInstanceUIDs?.length) {
|
||||
return;
|
||||
@ -239,6 +220,10 @@ export default function ModeRoute({
|
||||
appConfig,
|
||||
});
|
||||
|
||||
// Move hotkeys setup here, after onModeEnter
|
||||
const hotkeys = customizationService.getCustomization('ohif.hotkeyBindings');
|
||||
hotkeysManager.setDefaultHotKeys(hotkeys);
|
||||
|
||||
/**
|
||||
* The next line should get all the query parameters provided by the URL
|
||||
* - except the StudyInstanceUIDs - and create an object called filters
|
||||
@ -298,8 +283,6 @@ export default function ModeRoute({
|
||||
setupRouteInit().then(unsubs => {
|
||||
unsubscriptions = unsubs;
|
||||
|
||||
// Some code may need to run after hanging protocol initialization
|
||||
// (eg: workflowStepsService initialization on 4D mode)
|
||||
mode?.onSetupRouteComplete?.({
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
@ -320,6 +303,9 @@ export default function ModeRoute({
|
||||
} catch (e) {
|
||||
console.warn('mode exit failure', e);
|
||||
}
|
||||
// Clean up hotkeys
|
||||
hotkeysManager.destroy();
|
||||
|
||||
// The unsubscriptions must occur before the extension onModeExit
|
||||
// in order to prevent exceptions during cleanup caused by spurious events
|
||||
if (unsubscriptions) {
|
||||
|
||||
@ -128,7 +128,6 @@ function modeFactory({ modeConfiguration }) {
|
||||
/** SopClassHandlers used by the mode */
|
||||
sopClassHandlers: [ohif.sopClassHandler],
|
||||
/** hotkeys for mode */
|
||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -30,14 +30,6 @@ describe('HotkeysManager', () => {
|
||||
expect(containsAllExpectedProperties).toBe(true);
|
||||
});
|
||||
|
||||
it('throws Error if instantiated without a commandsManager', () => {
|
||||
expect(() => {
|
||||
new HotkeysManager();
|
||||
}).toThrow(
|
||||
'HotkeysManager instantiated without a commandsManager. Hotkeys will be unable to find and run commands.'
|
||||
);
|
||||
});
|
||||
|
||||
describe('disable()', () => {
|
||||
beforeEach(() => hotkeys.pause.mockClear());
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import objectHash from 'object-hash';
|
||||
import isEqual from 'lodash.isequal';
|
||||
import { hotkeys } from '../utils';
|
||||
import { hotkeys as mouseTrapAPI } from '../utils';
|
||||
import Hotkey from './Hotkey';
|
||||
import migrateOldHotkeyDefinitions from '../utils/hotkeys/migrateHotkeys';
|
||||
|
||||
/**
|
||||
*
|
||||
@ -12,23 +12,24 @@ import Hotkey from './Hotkey';
|
||||
* @property {String} label - Display name for hotkey
|
||||
* @property {String[]} keys - Keys to bind; Follows Mousetrap.js binding syntax
|
||||
*/
|
||||
|
||||
export class HotkeysManager {
|
||||
private _servicesManager: AppTypes.ServicesManager;
|
||||
private _commandsManager: AppTypes.CommandsManager;
|
||||
private isEnabled: boolean = true;
|
||||
public hotkeyDefinitions: Record<string, any> = {};
|
||||
public hotkeyDefaults: any[] = [];
|
||||
|
||||
constructor(commandsManager, servicesManager: AppTypes.ServicesManager) {
|
||||
this.hotkeyDefinitions = {};
|
||||
this.hotkeyDefaults = [];
|
||||
this.isEnabled = true;
|
||||
|
||||
if (!commandsManager) {
|
||||
throw new Error(
|
||||
'HotkeysManager instantiated without a commandsManager. Hotkeys will be unable to find and run commands.'
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
commandsManager: AppTypes.CommandsManager,
|
||||
servicesManager: AppTypes.ServicesManager
|
||||
) {
|
||||
this._servicesManager = servicesManager;
|
||||
this._commandsManager = commandsManager;
|
||||
|
||||
// Check for old hotkey definitions format and migrate if needed
|
||||
migrateOldHotkeyDefinitions({
|
||||
generateHash: this.generateHash,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,7 +38,12 @@ export class HotkeysManager {
|
||||
* @param {*} event
|
||||
*/
|
||||
record(event) {
|
||||
return hotkeys.record(event);
|
||||
return mouseTrapAPI.record(event);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
mouseTrapAPI.stopRecord();
|
||||
mouseTrapAPI.unpause();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,7 +52,7 @@ export class HotkeysManager {
|
||||
*/
|
||||
disable() {
|
||||
this.isEnabled = false;
|
||||
hotkeys.pause();
|
||||
mouseTrapAPI.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,22 +60,35 @@ export class HotkeysManager {
|
||||
*/
|
||||
enable() {
|
||||
this.isEnabled = true;
|
||||
hotkeys.unpause();
|
||||
mouseTrapAPI.unpause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a list of hotkeydefinitions.
|
||||
* Uses most recent
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
restoreDefaultBindings() {
|
||||
this.setHotkeys(this.hotkeyDefaults);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
destroy() {
|
||||
this.hotkeyDefaults = [];
|
||||
this.hotkeyDefinitions = {};
|
||||
mouseTrapAPI.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a list of hotkey definitions.
|
||||
*
|
||||
* @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions
|
||||
*/
|
||||
setHotkeys(hotkeyDefinitions = [], name = 'hotkey-definitions') {
|
||||
setHotkeys(hotkeyDefinitions = []) {
|
||||
try {
|
||||
const definitions = this.getValidDefinitions(hotkeyDefinitions);
|
||||
if (isEqual(definitions, this.hotkeyDefaults)) {
|
||||
localStorage.removeItem(name);
|
||||
} else {
|
||||
localStorage.setItem(name, JSON.stringify(definitions));
|
||||
}
|
||||
definitions.forEach(definition => this.registerHotkeys(definition));
|
||||
} catch (error) {
|
||||
const { uiNotificationService } = this._servicesManager.services;
|
||||
@ -81,6 +100,13 @@ export class HotkeysManager {
|
||||
}
|
||||
}
|
||||
|
||||
generateHash(definition) {
|
||||
return objectHash({
|
||||
commandName: definition.commandName,
|
||||
commandOptions: definition.commandOptions || {},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default hotkey bindings. These
|
||||
* values are used in `this.restoreDefaultBindings`.
|
||||
@ -90,6 +116,25 @@ export class HotkeysManager {
|
||||
setDefaultHotKeys(hotkeyDefinitions = []) {
|
||||
const definitions = this.getValidDefinitions(hotkeyDefinitions);
|
||||
this.hotkeyDefaults = definitions;
|
||||
|
||||
// Get user preferred keys from localStorage
|
||||
const userPreferredKeys = JSON.parse(localStorage.getItem('user-preferred-keys') || '{}');
|
||||
|
||||
// Update definitions with user preferred keys before setting
|
||||
const updatedDefinitions = definitions.map(definition => {
|
||||
const commandHash = this.generateHash(definition);
|
||||
// If user has a preferred key binding, use it
|
||||
if (userPreferredKeys[commandHash]) {
|
||||
return {
|
||||
...definition,
|
||||
keys: userPreferredKeys[commandHash],
|
||||
};
|
||||
}
|
||||
|
||||
return definition;
|
||||
});
|
||||
|
||||
this.setHotkeys(updatedDefinitions);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,6 +148,13 @@ export class HotkeysManager {
|
||||
? [...hotkeyDefinitions]
|
||||
: this._parseToArrayLike(hotkeyDefinitions);
|
||||
|
||||
// make sure isEditable is true for all definitions if not provided
|
||||
definitions.forEach(definition => {
|
||||
if (definition.isEditable === undefined) {
|
||||
definition.isEditable = true;
|
||||
}
|
||||
});
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
@ -142,18 +194,6 @@ export class HotkeysManager {
|
||||
* Return HotkeyDefinition object like based on given property name and property value
|
||||
* @param {string} propertyName property name of hotkey definition object
|
||||
* @param {object} propertyValue property value of hotkey definition object
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* const hotKeyObj = {hotKeyDefA: {keys:[],....}}
|
||||
*
|
||||
* const parsed = _parseToHotKeyObj(Object.keys(hotKeyDefA)[0], hotKeyObj[hotKeyDefA]);
|
||||
* {
|
||||
* commandName: hotKeyDefA,
|
||||
* keys: [],
|
||||
* ....
|
||||
* }
|
||||
*
|
||||
*/
|
||||
_parseToHotKeyObj(propertyName, propertyValue) {
|
||||
return {
|
||||
@ -163,65 +203,44 @@ export class HotkeysManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* (unbinds and) binds the specified command to one or more key combinations.
|
||||
* When a hotkey combination is triggered, the command name and active contexts
|
||||
* are used to locate the correct command to call.
|
||||
* (Unbinds and) binds the specified command to one or more key combinations.
|
||||
* When the hotkey combination is triggered, the command name and active contexts
|
||||
* are used to locate and execute the appropriate command.
|
||||
*
|
||||
* @param {HotkeyDefinition} command
|
||||
* @param {String} extension
|
||||
* @returns {undefined}
|
||||
* @param hotkey - The hotkey definition object.
|
||||
* @throws {Error} Throws an error if no commandName is provided.
|
||||
*/
|
||||
registerHotkeys(
|
||||
{ commandName, commandOptions = {}, context, keys, label, isEditable }: Hotkey = {},
|
||||
extension
|
||||
) {
|
||||
registerHotkeys({
|
||||
commandName,
|
||||
commandOptions = {},
|
||||
context,
|
||||
keys,
|
||||
label,
|
||||
isEditable,
|
||||
}: Hotkey): void {
|
||||
if (!commandName) {
|
||||
throw new Error(`No command was defined for hotkey "${keys}"`);
|
||||
}
|
||||
|
||||
const commandHash = objectHash({ commandName, commandOptions });
|
||||
const options = Object.keys(commandOptions).length ? JSON.stringify(commandOptions) : 'no';
|
||||
const previouslyRegisteredDefinition = this.hotkeyDefinitions[commandHash];
|
||||
const commandHash = this.generateHash({ commandName, commandOptions });
|
||||
const existingHotkey = this.hotkeyDefinitions[commandHash];
|
||||
|
||||
if (previouslyRegisteredDefinition) {
|
||||
const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys;
|
||||
this._unbindHotkeys(commandName, previouslyRegisteredKeys);
|
||||
// log.info(
|
||||
// `[hotkeys] Unbinding ${commandName} with ${options} options from ${previouslyRegisteredKeys}`
|
||||
// );
|
||||
// If the hotkey has already been registered with the same keys, skip re-registration.
|
||||
if (existingHotkey && existingHotkey.keys === keys) {
|
||||
console.debug('HotkeysManager: Identical hotkey registration skipped.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set definition & bind
|
||||
this.hotkeyDefinitions[commandHash] = {
|
||||
commandName,
|
||||
commandOptions,
|
||||
keys,
|
||||
label,
|
||||
isEditable,
|
||||
};
|
||||
const userPreferredKeys = JSON.parse(localStorage.getItem('user-preferred-keys') || '{}');
|
||||
|
||||
if (existingHotkey) {
|
||||
userPreferredKeys[commandHash] = keys;
|
||||
localStorage.setItem('user-preferred-keys', JSON.stringify(userPreferredKeys));
|
||||
this._unbindHotkeys(commandName, existingHotkey.keys);
|
||||
}
|
||||
|
||||
this.hotkeyDefinitions[commandHash] = { commandName, commandOptions, keys, label, isEditable };
|
||||
this._bindHotkeys(commandName, commandOptions, context, keys);
|
||||
// log.info(
|
||||
// `[hotkeys] Binding ${commandName} with ${options} from ${context ||
|
||||
// 'default'} options to ${keys}`
|
||||
// );
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses most recent
|
||||
*
|
||||
* @returns {undefined}
|
||||
*/
|
||||
restoreDefaultBindings() {
|
||||
this.setHotkeys(this.hotkeyDefaults);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
destroy() {
|
||||
this.hotkeyDefaults = [];
|
||||
this.hotkeyDefinitions = {};
|
||||
hotkeys.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,7 +260,7 @@ export class HotkeysManager {
|
||||
const isKeyArray = keys instanceof Array;
|
||||
const combinedKeys = isKeyArray ? keys.join('+') : keys;
|
||||
|
||||
hotkeys.bind(combinedKeys, evt => {
|
||||
mouseTrapAPI.bind(combinedKeys, evt => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
this._commandsManager.runCommand(commandName, { evt, ...commandOptions }, context);
|
||||
@ -269,7 +288,7 @@ export class HotkeysManager {
|
||||
return;
|
||||
}
|
||||
|
||||
hotkeys.unbind(keys);
|
||||
mouseTrapAPI.unbind(keys);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,3 @@
|
||||
import windowLevelPresets from './windowLevelPresets';
|
||||
|
||||
/*
|
||||
* Supported Keys: https://craig.is/killing/mice
|
||||
*/
|
||||
const bindings = [
|
||||
{
|
||||
commandName: 'setToolActive',
|
||||
@ -144,63 +139,46 @@ const bindings = [
|
||||
keys: ['esc'],
|
||||
},
|
||||
{
|
||||
commandName: 'setWindowLevel',
|
||||
commandOptions: windowLevelPresets[1],
|
||||
commandName: 'setWindowLevelPreset',
|
||||
commandOptions: { presetName: 'ct-soft-tissue', presetIndex: 0 },
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'setWindowLevel',
|
||||
commandOptions: windowLevelPresets[2],
|
||||
commandName: 'setWindowLevelPreset',
|
||||
commandOptions: { presetName: 'ct-lung', presetIndex: 1 },
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'setWindowLevel',
|
||||
commandOptions: windowLevelPresets[3],
|
||||
commandName: 'setWindowLevelPreset',
|
||||
commandOptions: { presetName: 'ct-bone', presetIndex: 2 },
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'setWindowLevel',
|
||||
commandOptions: windowLevelPresets[4],
|
||||
commandName: 'setWindowLevelPreset',
|
||||
commandOptions: { presetName: 'ct-brain', presetIndex: 3 },
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'setWindowLevel',
|
||||
commandOptions: windowLevelPresets[5],
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'deleteActiveAnnotation',
|
||||
label: 'Delete Annotation',
|
||||
keys: ['backspace'],
|
||||
},
|
||||
// These don't exist, so don't try applying them....
|
||||
// after we have the ui for undo/redo, we can add these back in
|
||||
// {
|
||||
// commandName: 'setWindowLevel',
|
||||
// commandOptions: windowLevelPresets[6],
|
||||
// label: 'W/L Preset 6',
|
||||
// keys: ['6'],
|
||||
// commandName: 'undo',
|
||||
// label: 'Undo',
|
||||
// keys: ['ctrl+z'],
|
||||
// isEditable: true,
|
||||
// },
|
||||
// {
|
||||
// commandName: 'setWindowLevel',
|
||||
// commandOptions: windowLevelPresets[7],
|
||||
// label: 'W/L Preset 7',
|
||||
// keys: ['7'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'setWindowLevel',
|
||||
// commandOptions: windowLevelPresets[8],
|
||||
// label: 'W/L Preset 8',
|
||||
// keys: ['8'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'setWindowLevel',
|
||||
// commandOptions: windowLevelPresets[9],
|
||||
// label: 'W/L Preset 9',
|
||||
// keys: ['9'],
|
||||
// commandName: 'redo',
|
||||
// label: 'Redo',
|
||||
// keys: ['ctrl+y'],
|
||||
// isEditable: true,
|
||||
// },
|
||||
];
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import hotkeyBindings from './hotkeyBindings';
|
||||
import windowLevelPresets from './windowLevelPresets';
|
||||
export { hotkeyBindings, windowLevelPresets };
|
||||
export default { hotkeyBindings, windowLevelPresets };
|
||||
|
||||
export default {
|
||||
hotkeyBindings,
|
||||
windowLevelPresets,
|
||||
};
|
||||
|
||||
@ -147,12 +147,6 @@ declare global {
|
||||
maxNumPrefetchRequests: number;
|
||||
order: 'closest' | 'downward' | 'upward';
|
||||
};
|
||||
tours?: Array<{
|
||||
id: string;
|
||||
steps: StepOptions[];
|
||||
tourOptions: TourOptions;
|
||||
route: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface Test {
|
||||
|
||||
76
platform/core/src/utils/hotkeys/migrateHotkeys.ts
Normal file
76
platform/core/src/utils/hotkeys/migrateHotkeys.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import defaults from '../../defaults';
|
||||
const defaultHotkeyBindings = defaults.hotkeyBindings;
|
||||
|
||||
/**
|
||||
* Migrates old hotkey definitions from localStorage to the new format
|
||||
* Old format: 'hotkey-definitions' containing full hotkey definitions array
|
||||
* New format: 'user-preferred-keys' containing hashed command keys with their key bindings
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function migrateOldHotkeyDefinitions({
|
||||
generateHash,
|
||||
}: {
|
||||
generateHash: (definition: Record<string, unknown>) => string;
|
||||
}): void {
|
||||
try {
|
||||
const oldHotkeyDefinitions = localStorage.getItem('hotkey-definitions');
|
||||
const migrated = localStorage.getItem('hotkeys-migrated');
|
||||
|
||||
if (!oldHotkeyDefinitions || migrated === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldDefinitions = JSON.parse(oldHotkeyDefinitions);
|
||||
|
||||
if (!Array.isArray(oldDefinitions) || oldDefinitions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultBindings = defaultHotkeyBindings || [];
|
||||
|
||||
const userPreferredKeys = JSON.parse(localStorage.getItem('user-preferred-keys') || '{}');
|
||||
|
||||
oldDefinitions.forEach(oldDefinition => {
|
||||
if (!oldDefinition.commandName || !oldDefinition.keys) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matchingDefault = defaultBindings.find(defaultBinding => {
|
||||
const sameCommand = defaultBinding.commandName === oldDefinition.commandName;
|
||||
|
||||
const oldOptions = oldDefinition.commandOptions || {};
|
||||
const defaultOptions = defaultBinding.commandOptions || {};
|
||||
|
||||
const sameOptions = JSON.stringify(oldOptions) === JSON.stringify(defaultOptions);
|
||||
|
||||
return sameCommand && sameOptions;
|
||||
});
|
||||
|
||||
if (
|
||||
!matchingDefault ||
|
||||
JSON.stringify(matchingDefault.keys) !== JSON.stringify(oldDefinition.keys)
|
||||
) {
|
||||
const commandHash = generateHash({
|
||||
commandName: oldDefinition.commandName,
|
||||
commandOptions: oldDefinition.commandOptions || {},
|
||||
});
|
||||
|
||||
userPreferredKeys[commandHash] = oldDefinition.keys;
|
||||
console.debug(`HotkeysManager: Migrated custom hotkey for ${oldDefinition.commandName}`);
|
||||
}
|
||||
});
|
||||
|
||||
localStorage.setItem('user-preferred-keys', JSON.stringify(userPreferredKeys));
|
||||
localStorage.setItem('hotkeys-migrated', 'true');
|
||||
localStorage.removeItem('hotkey-definitions');
|
||||
|
||||
console.debug('HotkeysManager: Successfully migrated hotkey definitions to new format');
|
||||
} catch (error) {
|
||||
console.error('HotkeysManager: Error migrating hotkey definitions', error);
|
||||
|
||||
localStorage.setItem('hotkeys-migrated', 'false');
|
||||
}
|
||||
}
|
||||
|
||||
export default migrateOldHotkeyDefinitions;
|
||||
@ -13,62 +13,39 @@ Tours allow you to provide step-by-step guidance to users, explaining different
|
||||
|
||||
### Adding a Tour to your Configuration
|
||||
|
||||
Here’s an example of adding a tour to your configuration file:
|
||||
Here's how you can add a tour to your configuration file:
|
||||
|
||||
```javascript
|
||||
window.config = {
|
||||
tours: [
|
||||
{
|
||||
id: 'basicViewerTour',
|
||||
route: '/viewer',
|
||||
steps: [
|
||||
customizationService: {
|
||||
'ohif.tours': {
|
||||
$set: [
|
||||
{
|
||||
id: 'scroll',
|
||||
title: 'Scrolling Through Images',
|
||||
text: 'You can scroll through the images using the mouse wheel or scrollbar.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'top',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_WHEEL',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'zoom',
|
||||
title: 'Zooming In and Out',
|
||||
text: 'You can zoom the images using the right click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
},
|
||||
// Add more steps as needed
|
||||
],
|
||||
tourOptions: {
|
||||
useModalOverlay: true,
|
||||
defaultStepOptions: {
|
||||
buttons: [
|
||||
id: 'basicViewerTour',
|
||||
route: '/viewer',
|
||||
steps: [
|
||||
{
|
||||
text: 'Skip all',
|
||||
action() {
|
||||
this.complete();
|
||||
id: 'zoom',
|
||||
title: 'Zooming In and Out',
|
||||
text: 'You can zoom the images using the right click.',
|
||||
attachTo: {
|
||||
element: '.viewport-element',
|
||||
on: 'left',
|
||||
},
|
||||
advanceOn: {
|
||||
selector: '.cornerstone-viewport-element',
|
||||
event: 'CORNERSTONE_TOOLS_MOUSE_UP',
|
||||
},
|
||||
secondary: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## Explanation of Parameters
|
||||
|
||||
### `tours` Array
|
||||
|
||||
@ -0,0 +1,129 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: Hotkeys
|
||||
---
|
||||
|
||||
|
||||
## Key Changes:
|
||||
|
||||
* Hotkeys are no longer defined in mode factory via `hotkeys: [...hotkeys.defaults.hotkeyBindings]`
|
||||
* Hotkeys are now managed through the `customizationService` under the key `ohif.hotkeyBindings`
|
||||
* Default hotkeys are set automatically and can be customized using the customization service
|
||||
* User-defined hotkey preferences are now stored in a new format in localStorage
|
||||
* The `HotkeysManager` has undergone significant updates including better handling of defaults, key persistence, and cleanup
|
||||
|
||||
## Migration Steps:
|
||||
|
||||
### 1. Remove hotkeys array from mode factory definition
|
||||
|
||||
**Before:**
|
||||
```diff
|
||||
- function modeFactory({ modeConfiguration }) {
|
||||
- return {
|
||||
- id: 'basic',
|
||||
- // ... other configuration
|
||||
- hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
- };
|
||||
- }
|
||||
```
|
||||
|
||||
**After:**
|
||||
```diff
|
||||
+ function modeFactory({ modeConfiguration }) {
|
||||
+ return {
|
||||
+ id: 'basic',
|
||||
+ // ... other configuration
|
||||
+ // No hotkeys array necessary
|
||||
+ };
|
||||
+ }
|
||||
```
|
||||
|
||||
|
||||
### 2. Set custom hotkeys using the customization service
|
||||
|
||||
There are several methods to modify hotkeys using the customization service:
|
||||
|
||||
#### a. Completely replace all hotkeys using `$set`:
|
||||
|
||||
```diff
|
||||
+ onModeEnter: function ({ servicesManager }) {
|
||||
+ const { customizationService } = servicesManager.services;
|
||||
+ customizationService.setCustomizations({
|
||||
+ 'ohif.hotkeyBindings': {
|
||||
+ $set: [
|
||||
+ {
|
||||
+ commandName: 'setToolActive',
|
||||
+ commandOptions: { toolName: 'Zoom' },
|
||||
+ label: 'Zoom',
|
||||
+ keys: ['z'],
|
||||
+ isEditable: true,
|
||||
+ },
|
||||
+ ],
|
||||
+ },
|
||||
+ });
|
||||
```
|
||||
|
||||
#### b. Add new hotkeys using `$push`:
|
||||
|
||||
```diff
|
||||
+ onModeEnter: function ({ servicesManager }) {
|
||||
+ const { customizationService } = servicesManager.services;
|
||||
+ customizationService.setCustomizations({
|
||||
+ 'ohif.hotkeyBindings': {
|
||||
+ $push: [
|
||||
+ {
|
||||
+ commandName: 'myCustomCommand',
|
||||
+ label: 'My Custom Function',
|
||||
+ keys: ['ctrl+m'],
|
||||
+ isEditable: true,
|
||||
+ },
|
||||
+ ],
|
||||
+ },
|
||||
+ });
|
||||
+}
|
||||
```
|
||||
|
||||
### 4. Update configuration file if you were setting window.config.hotkeys
|
||||
|
||||
If you were previously defining hotkeys in your window.config.js file, it was not really
|
||||
taken into account. So you can safely remove it now.
|
||||
|
||||
**Before:**
|
||||
```diff
|
||||
- window.config = {
|
||||
- // ...other config
|
||||
- hotkeys: [
|
||||
- {
|
||||
- commandName: 'incrementActiveViewport',
|
||||
- label: 'Next Viewport',
|
||||
- keys: ['right'],
|
||||
- },
|
||||
- // ...more hotkeys
|
||||
- ],
|
||||
- };
|
||||
```
|
||||
|
||||
**After:**
|
||||
```diff
|
||||
+ window.config = {
|
||||
+ // ...other config
|
||||
+ };
|
||||
```
|
||||
|
||||
### 5. Be aware that user preferences are now handled differently
|
||||
|
||||
The new system automatically handles user-preferred hotkey mappings:
|
||||
|
||||
- User hotkey preferences are stored in `localStorage` under the key `user-preferred-keys`
|
||||
- The format is a hash-based mapping rather than a full array of definitions
|
||||
- There's a migration utility that converts old preferences to the new format
|
||||
- You don't need to manually handle this, but be aware of it if you're accessing localStorage directly
|
||||
|
||||
|
||||
## Benefits of the Change
|
||||
|
||||
1. **Consistent API**: Hotkeys now follow the same customization pattern as other OHIF features
|
||||
2. **More flexible**: Easier to modify specific hotkeys without replacing the entire set
|
||||
3. **Better user preferences**: User customizations are better preserved and migrated
|
||||
4. **Runtime updates**: Hotkeys can be modified at runtime through the customization service
|
||||
5. **Improved cleanup**: Better lifecycle management of hotkey bindings
|
||||
@ -0,0 +1,66 @@
|
||||
---
|
||||
title: Tours and Onboarding
|
||||
---
|
||||
|
||||
## Migration Guide: Tours
|
||||
|
||||
* Tours are no longer defined directly in `window.config.tours` but through the customization service under the key `ohif.tours`
|
||||
* The `waitForElement` utility function has been moved from the config file to a dedicated customization file
|
||||
* The structure of tour definitions (steps, options, etc.) remains largely the same
|
||||
|
||||
## Migration Steps:
|
||||
|
||||
|
||||
|
||||
### 1. Update any direct references to window.config.tours
|
||||
|
||||
If you have any code that directly references window.config.tours, update it to use the customization service:
|
||||
|
||||
```diff
|
||||
- const tours = window.config.tours;
|
||||
+ const tours = customizationService.getCustomization('ohif.tours');
|
||||
```
|
||||
|
||||
### 2. Use config update patterns for configuring tours
|
||||
|
||||
**Before:**
|
||||
```diff
|
||||
- window.config = {
|
||||
- tours: [
|
||||
- {
|
||||
- id: 'basicViewerTour',
|
||||
- route: '/viewer',
|
||||
- steps: [
|
||||
- // tour steps...
|
||||
- ],
|
||||
- tourOptions: {
|
||||
- // tour options...
|
||||
- },
|
||||
- },
|
||||
- ],
|
||||
- };
|
||||
```
|
||||
|
||||
**After:**
|
||||
```javascript
|
||||
window.config = {
|
||||
customizationService: {
|
||||
'ohif.tours': {
|
||||
$set: [
|
||||
{
|
||||
id: 'basicViewerTour',
|
||||
route: '/viewer',
|
||||
steps: [
|
||||
// Your tour steps
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## Benefits of the Change
|
||||
|
||||
4. **Mode-specific Tours**: now you can have different tours for different modes
|
||||
@ -74,6 +74,7 @@ export default [
|
||||
```
|
||||
|
||||
|
||||
## Behind the Scene
|
||||
When you `setHotkeys`, the `commandName` gets registered with the `commandsManager` and
|
||||
get run after the key is pressed.
|
||||
### Global vs Mode specific hotkeys
|
||||
|
||||
You can can set the global hotkeys and override them using the `$set` method
|
||||
in the customization service.
|
||||
|
||||
@ -148,6 +148,46 @@ window.config = {
|
||||
];
|
||||
|
||||
export const customizations = [
|
||||
{
|
||||
id: 'ohif.hotkeyBindings',
|
||||
description: 'Defines the hotkeys for the application.',
|
||||
default: 'look at hotkeyBindingsCustomization.ts file',
|
||||
configuration: `
|
||||
window.config = {
|
||||
// rest of window config
|
||||
customizationService: [
|
||||
{
|
||||
// this will override the default hotkeys and only have one hotkey
|
||||
'ohif.hotkeyBindings': {
|
||||
$set: [
|
||||
{
|
||||
commandName: 'scaleDownViewport',
|
||||
label: 'Zoom Out',
|
||||
keys: ['-'],
|
||||
isEditable: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// or lets say you want to change one key of the default hotkeys to default
|
||||
// something else
|
||||
customizationService: [
|
||||
{
|
||||
// this will override the default hotkeys and only have one hotkey
|
||||
'ohif.hotkeyBindings': {
|
||||
$filter: {
|
||||
match: { commandName: 'scaleDownViewport' },
|
||||
$set: {
|
||||
keys: ['ctrl+shift+-'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'measurementLabels',
|
||||
description: 'Labels for measurement tools in the viewer that are automatically asked for.',
|
||||
|
||||
@ -9,9 +9,27 @@ Throughout the development of the OHIF Viewer, we have participated in various
|
||||
conferences and "hackathons". In this page, we will provide the presentations
|
||||
and other resources that we have provided to the community in the past:
|
||||
|
||||
## 2025
|
||||
|
||||
### Machine Learning in Medical Imaging Consortium (MaLMIC) | January 2025
|
||||
|
||||
We presented two talks at the Machine Learning in Medical Imaging Consortium (MaLMIC) 2025 conference.
|
||||
|
||||
- Advanced Medical Imaging Visualization [Slides](https://docs.google.com/presentation/d/1HZDL-72nNe4BPawDxR3XnSFLB3oLo72RjExc-KHDZfo/edit?usp=sharing)
|
||||
- Introducing Advanced Segmentation Tools in the OHIF Viewer and Cornerstone3D [Slides](https://docs.google.com/presentation/d/146oJ24PPsFZaDPHeFudRF1dmbL42K9yHzQdXXAXdWxk/edit?usp=sharing)
|
||||
|
||||
|
||||
## 2024
|
||||
|
||||
### ITCR Sustainment Session 2024
|
||||
|
||||
Dr. Gordon Harris presented at ITCR sustainment session about the future of OHIF.
|
||||
|
||||
- OHIF Sustainability [Slides](https://docs.google.com/presentation/d/15380mjCzBKBj9PuysCW1Q9ODnyoypJrCDpj3atTtK6I/edit?usp=sharing)
|
||||
|
||||
### ITCR Sustainment Panel 2024
|
||||
|
||||
- Advanced Medical Imaging Visualization [Slides](https://docs.google.com/presentation/d/1alUp9uJpoJs3aAUE0KqrufGo6e6HHvXYmOAdJp-Rlkc/edit?usp=sharing)
|
||||
|
||||
|
||||
### IMNO 2024 - March 19-20, 2024
|
||||
|
||||
@ -7,22 +7,25 @@ import './Onboarding.css';
|
||||
|
||||
import { hasTourBeenShown, markTourAsShown, defaultShowHandler, middleware } from './utilities';
|
||||
|
||||
const Onboarding = () => {
|
||||
const Shepherd = useShepherd();
|
||||
const location = useLocation();
|
||||
const tours = window.config.tours as Array<{
|
||||
const Onboarding = ({
|
||||
tours = [],
|
||||
}: {
|
||||
tours?: Array<{
|
||||
id: string;
|
||||
route: string;
|
||||
tourOptions: TourOptions;
|
||||
steps: StepOptions[];
|
||||
}>;
|
||||
}) => {
|
||||
const Shepherd = useShepherd();
|
||||
const location = useLocation();
|
||||
|
||||
/**
|
||||
* Show the tour if it hasn't been shown yet based on the current route.
|
||||
* Constructs a tour instance and adds steps to it based on the matching tour.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!tours) {
|
||||
if (!tours.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user