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,
|
colorPickerDialog,
|
||||||
} from '@ohif/extension-default';
|
} from '@ohif/extension-default';
|
||||||
import { vec3, mat4 } from 'gl-matrix';
|
import { vec3, mat4 } from 'gl-matrix';
|
||||||
|
|
||||||
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
||||||
import toggleImageSliceSync from './utils/imageSliceSync/toggleImageSliceSync';
|
import toggleImageSliceSync from './utils/imageSliceSync/toggleImageSliceSync';
|
||||||
import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection';
|
import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection';
|
||||||
@ -34,6 +33,7 @@ import toggleVOISliceSync from './utils/toggleVOISliceSync';
|
|||||||
import { usePositionPresentationStore, useSegmentationPresentationStore } from './stores';
|
import { usePositionPresentationStore, useSegmentationPresentationStore } from './stores';
|
||||||
import { toolNames } from './initCornerstoneTools';
|
import { toolNames } from './initCornerstoneTools';
|
||||||
|
|
||||||
|
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
||||||
const toggleSyncFunctions = {
|
const toggleSyncFunctions = {
|
||||||
imageSlice: toggleImageSliceSync,
|
imageSlice: toggleImageSliceSync,
|
||||||
voi: toggleVOISliceSync,
|
voi: toggleVOISliceSync,
|
||||||
@ -430,6 +430,37 @@ function commandsModule({
|
|||||||
|
|
||||||
actions.setViewportWindowLevel({ ...props, viewportId });
|
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 }) => {
|
setToolEnabled: ({ toolName, toggle, toolGroupId }) => {
|
||||||
const { viewports } = viewportGridService.getState();
|
const { viewports } = viewportGridService.getState();
|
||||||
|
|
||||||
@ -1327,6 +1358,12 @@ function commandsModule({
|
|||||||
measurementService.remove(activeAnnotationUID);
|
measurementService.remove(activeAnnotationUID);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
undo: () => {
|
||||||
|
DefaultHistoryMemo.undo();
|
||||||
|
},
|
||||||
|
redo: () => {
|
||||||
|
DefaultHistoryMemo.redo();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const definitions = {
|
const definitions = {
|
||||||
@ -1391,6 +1428,9 @@ function commandsModule({
|
|||||||
setWindowLevel: {
|
setWindowLevel: {
|
||||||
commandFn: actions.setWindowLevel,
|
commandFn: actions.setWindowLevel,
|
||||||
},
|
},
|
||||||
|
setWindowLevelPreset: {
|
||||||
|
commandFn: actions.setWindowLevelPreset,
|
||||||
|
},
|
||||||
setToolActive: {
|
setToolActive: {
|
||||||
commandFn: actions.setToolActive,
|
commandFn: actions.setToolActive,
|
||||||
},
|
},
|
||||||
@ -1590,6 +1630,8 @@ function commandsModule({
|
|||||||
deleteActiveAnnotation: {
|
deleteActiveAnnotation: {
|
||||||
commandFn: actions.deleteActiveAnnotation,
|
commandFn: actions.deleteActiveAnnotation,
|
||||||
},
|
},
|
||||||
|
undo: actions.undo,
|
||||||
|
redo: actions.redo,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -199,7 +199,7 @@ function ViewerLayout({
|
|||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
</div>
|
</div>
|
||||||
<Onboarding />
|
<Onboarding tours={customizationService.getCustomization('ohif.tours')} />
|
||||||
<InvestigationalUseDialog dialogConfiguration={appConfig?.investigationalUseDialog} />
|
<InvestigationalUseDialog dialogConfiguration={appConfig?.investigationalUseDialog} />
|
||||||
</div>
|
</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 viewportActionCornersCustomization from './customizations/viewportActionCornersCustomization';
|
||||||
import labellingFlowCustomization from './customizations/labellingFlowCustomization';
|
import labellingFlowCustomization from './customizations/labellingFlowCustomization';
|
||||||
import viewportNotificationCustomization from './customizations/notificationCustomization';
|
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
|
* Note: this is an example of how the customization module can be used
|
||||||
@ -62,6 +63,8 @@ export default function getCustomizationModule({ servicesManager, extensionManag
|
|||||||
...labellingFlowCustomization,
|
...labellingFlowCustomization,
|
||||||
...contextMenuUICustomization,
|
...contextMenuUICustomization,
|
||||||
...viewportNotificationCustomization,
|
...viewportNotificationCustomization,
|
||||||
|
...hotkeyBindingsCustomization,
|
||||||
|
...onboardingCustomization,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -171,7 +171,6 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
dicompdf.sopClassHandler,
|
dicompdf.sopClassHandler,
|
||||||
dicomsr.sopClassHandler,
|
dicomsr.sopClassHandler,
|
||||||
],
|
],
|
||||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -102,6 +102,22 @@ function modeFactory() {
|
|||||||
'Crosshairs',
|
'Crosshairs',
|
||||||
'MoreTools',
|
'MoreTools',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
customizationService.setCustomizations(
|
||||||
|
{
|
||||||
|
'ohif.hotkeyBindings': {
|
||||||
|
$push: [
|
||||||
|
{
|
||||||
|
commandName: 'undo',
|
||||||
|
label: 'Undo',
|
||||||
|
keys: ['ctrl+z'],
|
||||||
|
isEditable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'mode'
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onModeExit: ({ servicesManager }: withAppTypes) => {
|
onModeExit: ({ servicesManager }: withAppTypes) => {
|
||||||
const {
|
const {
|
||||||
@ -209,7 +225,6 @@ function modeFactory() {
|
|||||||
// Don't store the hotkeys for basic-test-mode under the same key
|
// Don't store the hotkeys for basic-test-mode under the same key
|
||||||
// because they get customized by tests
|
// because they get customized by tests
|
||||||
name: 'basic-test-hotkeys',
|
name: 'basic-test-hotkeys',
|
||||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,7 +84,8 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
* Lifecycle hooks
|
* Lifecycle hooks
|
||||||
*/
|
*/
|
||||||
onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) {
|
onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) {
|
||||||
const { measurementService, toolbarService, toolGroupService } = servicesManager.services;
|
const { measurementService, toolbarService, toolGroupService, customizationService } =
|
||||||
|
servicesManager.services;
|
||||||
|
|
||||||
measurementService.clearMeasurements();
|
measurementService.clearMeasurements();
|
||||||
|
|
||||||
@ -238,7 +239,6 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
dicomsr.sopClassHandler,
|
dicomsr.sopClassHandler,
|
||||||
dicomRT.sopClassHandler,
|
dicomRT.sopClassHandler,
|
||||||
],
|
],
|
||||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
|
||||||
...modeConfiguration,
|
...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;
|
export default toolbarButtons;
|
||||||
|
|||||||
@ -123,7 +123,6 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
dicomvideo.sopClassHandler,
|
dicomvideo.sopClassHandler,
|
||||||
dicompdf.sopClassHandler,
|
dicompdf.sopClassHandler,
|
||||||
],
|
],
|
||||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
|
||||||
...modeConfiguration,
|
...modeConfiguration,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -167,7 +167,6 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
// general handler needs to come last. For this case, the dicomvideo must
|
// general handler needs to come last. For this case, the dicomvideo must
|
||||||
// come first to remove video transfer syntax before ohif uses images
|
// come first to remove video transfer syntax before ohif uses images
|
||||||
sopClassHandlers: [ohif.chartSopClassHandler, ohif.defaultSopClassHandler],
|
sopClassHandlers: [ohif.chartSopClassHandler, ohif.defaultSopClassHandler],
|
||||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { hotkeys } from '@ohif/core';
|
|
||||||
import { id } from './id';
|
import { id } from './id';
|
||||||
import toolbarButtons from './toolbarButtons';
|
import toolbarButtons from './toolbarButtons';
|
||||||
import segmentationButtons from './segmentationButtons';
|
import segmentationButtons from './segmentationButtons';
|
||||||
@ -49,7 +48,8 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
* Services and other resources.
|
* Services and other resources.
|
||||||
*/
|
*/
|
||||||
onModeEnter: ({ servicesManager, extensionManager, commandsManager }: withAppTypes) => {
|
onModeEnter: ({ servicesManager, extensionManager, commandsManager }: withAppTypes) => {
|
||||||
const { measurementService, toolbarService, toolGroupService } = servicesManager.services;
|
const { measurementService, toolbarService, toolGroupService, customizationService } =
|
||||||
|
servicesManager.services;
|
||||||
|
|
||||||
measurementService.clearMeasurements();
|
measurementService.clearMeasurements();
|
||||||
|
|
||||||
@ -158,8 +158,6 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
// hangingProtocol: ['@ohif/mnGrid'],
|
// hangingProtocol: ['@ohif/mnGrid'],
|
||||||
/** SopClassHandlers used by the mode */
|
/** SopClassHandlers used by the mode */
|
||||||
sopClassHandlers: [ohif.sopClassHandler, segmentation.sopClassHandler],
|
sopClassHandlers: [ohif.sopClassHandler, segmentation.sopClassHandler],
|
||||||
/** hotkeys for mode */
|
|
||||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -216,7 +216,6 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
extensions: extensionDependencies,
|
extensions: extensionDependencies,
|
||||||
hangingProtocol: tmtv.hangingProtocol,
|
hangingProtocol: tmtv.hangingProtocol,
|
||||||
sopClassHandlers: [ohif.sopClassHandler],
|
sopClassHandlers: [ohif.sopClassHandler],
|
||||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
|
||||||
...modeConfiguration,
|
...modeConfiguration,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,92 +57,4 @@ window.config = {
|
|||||||
// Could use services manager here to bring up a dialog/modal if needed.
|
// Could use services manager here to bring up a dialog/modal if needed.
|
||||||
console.warn('test, navigate to https://ohif.org/');
|
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',
|
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: {
|
i18n: {
|
||||||
LOCIZE_PROJECTID: 'a8da3f9a-e467-4dd6-af33-474d582a0294',
|
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!
|
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.
|
// Could use services manager here to bring up a dialog/modal if needed.
|
||||||
console.warn('test, navigate to https://ohif.org/');
|
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,
|
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.
|
// Could use services manager here to bring up a dialog/modal if needed.
|
||||||
console.warn('test, navigate to https://ohif.org/');
|
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.
|
// Could use services manager here to bring up a dialog/modal if needed.
|
||||||
console.warn('test, navigate to https://ohif.org/');
|
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.
|
// Could use services manager here to bring up a dialog/modal if needed.
|
||||||
console.warn('test, navigate to https://ohif.org/');
|
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) {
|
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!
|
LOCIZE_API_KEY: null, // Developers can use this to do in-context editing. DO NOT COMMIT THIS KEY!
|
||||||
USE_LOCIZE: false,
|
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;
|
locationRef.current = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { displaySetService, panelService, hangingProtocolService, userAuthenticationService } =
|
const {
|
||||||
servicesManager.services;
|
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 runTimeHangingProtocolId = lowerCaseSearchParams.get('hangingprotocolid');
|
||||||
const runTimeStageId = lowerCaseSearchParams.get('stageid');
|
const runTimeStageId = lowerCaseSearchParams.get('stageid');
|
||||||
@ -73,10 +78,6 @@ export default function ModeRoute({
|
|||||||
updateAuthServiceAndCleanUrl(token, location, userAuthenticationService);
|
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.
|
// An undefined dataSourceName implies that the active data source that is already set in the ExtensionManager should be used.
|
||||||
if (dataSourceName !== undefined) {
|
if (dataSourceName !== undefined) {
|
||||||
extensionManager.setActiveDataSource(dataSourceName);
|
extensionManager.setActiveDataSource(dataSourceName);
|
||||||
@ -167,26 +168,6 @@ export default function ModeRoute({
|
|||||||
};
|
};
|
||||||
}, [studyInstanceUIDs, ExtensionDependenciesLoaded]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (!layoutTemplateData.current || !ExtensionDependenciesLoaded || !studyInstanceUIDs?.length) {
|
if (!layoutTemplateData.current || !ExtensionDependenciesLoaded || !studyInstanceUIDs?.length) {
|
||||||
return;
|
return;
|
||||||
@ -239,6 +220,10 @@ export default function ModeRoute({
|
|||||||
appConfig,
|
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
|
* The next line should get all the query parameters provided by the URL
|
||||||
* - except the StudyInstanceUIDs - and create an object called filters
|
* - except the StudyInstanceUIDs - and create an object called filters
|
||||||
@ -298,8 +283,6 @@ export default function ModeRoute({
|
|||||||
setupRouteInit().then(unsubs => {
|
setupRouteInit().then(unsubs => {
|
||||||
unsubscriptions = unsubs;
|
unsubscriptions = unsubs;
|
||||||
|
|
||||||
// Some code may need to run after hanging protocol initialization
|
|
||||||
// (eg: workflowStepsService initialization on 4D mode)
|
|
||||||
mode?.onSetupRouteComplete?.({
|
mode?.onSetupRouteComplete?.({
|
||||||
servicesManager,
|
servicesManager,
|
||||||
extensionManager,
|
extensionManager,
|
||||||
@ -320,6 +303,9 @@ export default function ModeRoute({
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('mode exit failure', e);
|
console.warn('mode exit failure', e);
|
||||||
}
|
}
|
||||||
|
// Clean up hotkeys
|
||||||
|
hotkeysManager.destroy();
|
||||||
|
|
||||||
// The unsubscriptions must occur before the extension onModeExit
|
// The unsubscriptions must occur before the extension onModeExit
|
||||||
// in order to prevent exceptions during cleanup caused by spurious events
|
// in order to prevent exceptions during cleanup caused by spurious events
|
||||||
if (unsubscriptions) {
|
if (unsubscriptions) {
|
||||||
|
|||||||
@ -128,7 +128,6 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
/** SopClassHandlers used by the mode */
|
/** SopClassHandlers used by the mode */
|
||||||
sopClassHandlers: [ohif.sopClassHandler],
|
sopClassHandlers: [ohif.sopClassHandler],
|
||||||
/** hotkeys for mode */
|
/** hotkeys for mode */
|
||||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,14 +30,6 @@ describe('HotkeysManager', () => {
|
|||||||
expect(containsAllExpectedProperties).toBe(true);
|
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()', () => {
|
describe('disable()', () => {
|
||||||
beforeEach(() => hotkeys.pause.mockClear());
|
beforeEach(() => hotkeys.pause.mockClear());
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import objectHash from 'object-hash';
|
import objectHash from 'object-hash';
|
||||||
import isEqual from 'lodash.isequal';
|
import { hotkeys as mouseTrapAPI } from '../utils';
|
||||||
import { hotkeys } from '../utils';
|
|
||||||
import Hotkey from './Hotkey';
|
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} label - Display name for hotkey
|
||||||
* @property {String[]} keys - Keys to bind; Follows Mousetrap.js binding syntax
|
* @property {String[]} keys - Keys to bind; Follows Mousetrap.js binding syntax
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class HotkeysManager {
|
export class HotkeysManager {
|
||||||
private _servicesManager: AppTypes.ServicesManager;
|
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) {
|
constructor(
|
||||||
this.hotkeyDefinitions = {};
|
commandsManager: AppTypes.CommandsManager,
|
||||||
this.hotkeyDefaults = [];
|
servicesManager: AppTypes.ServicesManager
|
||||||
this.isEnabled = true;
|
) {
|
||||||
|
|
||||||
if (!commandsManager) {
|
|
||||||
throw new Error(
|
|
||||||
'HotkeysManager instantiated without a commandsManager. Hotkeys will be unable to find and run commands.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._servicesManager = servicesManager;
|
this._servicesManager = servicesManager;
|
||||||
this._commandsManager = commandsManager;
|
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
|
* @param {*} event
|
||||||
*/
|
*/
|
||||||
record(event) {
|
record(event) {
|
||||||
return hotkeys.record(event);
|
return mouseTrapAPI.record(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
mouseTrapAPI.stopRecord();
|
||||||
|
mouseTrapAPI.unpause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,7 +52,7 @@ export class HotkeysManager {
|
|||||||
*/
|
*/
|
||||||
disable() {
|
disable() {
|
||||||
this.isEnabled = false;
|
this.isEnabled = false;
|
||||||
hotkeys.pause();
|
mouseTrapAPI.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,22 +60,35 @@ export class HotkeysManager {
|
|||||||
*/
|
*/
|
||||||
enable() {
|
enable() {
|
||||||
this.isEnabled = true;
|
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
|
* @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions
|
||||||
*/
|
*/
|
||||||
setHotkeys(hotkeyDefinitions = [], name = 'hotkey-definitions') {
|
setHotkeys(hotkeyDefinitions = []) {
|
||||||
try {
|
try {
|
||||||
const definitions = this.getValidDefinitions(hotkeyDefinitions);
|
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));
|
definitions.forEach(definition => this.registerHotkeys(definition));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { uiNotificationService } = this._servicesManager.services;
|
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
|
* Set default hotkey bindings. These
|
||||||
* values are used in `this.restoreDefaultBindings`.
|
* values are used in `this.restoreDefaultBindings`.
|
||||||
@ -90,6 +116,25 @@ export class HotkeysManager {
|
|||||||
setDefaultHotKeys(hotkeyDefinitions = []) {
|
setDefaultHotKeys(hotkeyDefinitions = []) {
|
||||||
const definitions = this.getValidDefinitions(hotkeyDefinitions);
|
const definitions = this.getValidDefinitions(hotkeyDefinitions);
|
||||||
this.hotkeyDefaults = definitions;
|
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]
|
? [...hotkeyDefinitions]
|
||||||
: this._parseToArrayLike(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;
|
return definitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,18 +194,6 @@ export class HotkeysManager {
|
|||||||
* Return HotkeyDefinition object like based on given property name and property value
|
* Return HotkeyDefinition object like based on given property name and property value
|
||||||
* @param {string} propertyName property name of hotkey definition object
|
* @param {string} propertyName property name of hotkey definition object
|
||||||
* @param {object} propertyValue property value 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) {
|
_parseToHotKeyObj(propertyName, propertyValue) {
|
||||||
return {
|
return {
|
||||||
@ -163,65 +203,44 @@ export class HotkeysManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (unbinds and) binds the specified command to one or more key combinations.
|
* (Unbinds and) binds the specified command to one or more key combinations.
|
||||||
* When a hotkey combination is triggered, the command name and active contexts
|
* When the hotkey combination is triggered, the command name and active contexts
|
||||||
* are used to locate the correct command to call.
|
* are used to locate and execute the appropriate command.
|
||||||
*
|
*
|
||||||
* @param {HotkeyDefinition} command
|
* @param hotkey - The hotkey definition object.
|
||||||
* @param {String} extension
|
* @throws {Error} Throws an error if no commandName is provided.
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
*/
|
||||||
registerHotkeys(
|
registerHotkeys({
|
||||||
{ commandName, commandOptions = {}, context, keys, label, isEditable }: Hotkey = {},
|
commandName,
|
||||||
extension
|
commandOptions = {},
|
||||||
) {
|
context,
|
||||||
|
keys,
|
||||||
|
label,
|
||||||
|
isEditable,
|
||||||
|
}: Hotkey): void {
|
||||||
if (!commandName) {
|
if (!commandName) {
|
||||||
throw new Error(`No command was defined for hotkey "${keys}"`);
|
throw new Error(`No command was defined for hotkey "${keys}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandHash = objectHash({ commandName, commandOptions });
|
const commandHash = this.generateHash({ commandName, commandOptions });
|
||||||
const options = Object.keys(commandOptions).length ? JSON.stringify(commandOptions) : 'no';
|
const existingHotkey = this.hotkeyDefinitions[commandHash];
|
||||||
const previouslyRegisteredDefinition = this.hotkeyDefinitions[commandHash];
|
|
||||||
|
|
||||||
if (previouslyRegisteredDefinition) {
|
// If the hotkey has already been registered with the same keys, skip re-registration.
|
||||||
const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys;
|
if (existingHotkey && existingHotkey.keys === keys) {
|
||||||
this._unbindHotkeys(commandName, previouslyRegisteredKeys);
|
console.debug('HotkeysManager: Identical hotkey registration skipped.');
|
||||||
// log.info(
|
return;
|
||||||
// `[hotkeys] Unbinding ${commandName} with ${options} options from ${previouslyRegisteredKeys}`
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set definition & bind
|
const userPreferredKeys = JSON.parse(localStorage.getItem('user-preferred-keys') || '{}');
|
||||||
this.hotkeyDefinitions[commandHash] = {
|
|
||||||
commandName,
|
if (existingHotkey) {
|
||||||
commandOptions,
|
userPreferredKeys[commandHash] = keys;
|
||||||
keys,
|
localStorage.setItem('user-preferred-keys', JSON.stringify(userPreferredKeys));
|
||||||
label,
|
this._unbindHotkeys(commandName, existingHotkey.keys);
|
||||||
isEditable,
|
}
|
||||||
};
|
|
||||||
|
this.hotkeyDefinitions[commandHash] = { commandName, commandOptions, keys, label, isEditable };
|
||||||
this._bindHotkeys(commandName, commandOptions, context, keys);
|
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 isKeyArray = keys instanceof Array;
|
||||||
const combinedKeys = isKeyArray ? keys.join('+') : keys;
|
const combinedKeys = isKeyArray ? keys.join('+') : keys;
|
||||||
|
|
||||||
hotkeys.bind(combinedKeys, evt => {
|
mouseTrapAPI.bind(combinedKeys, evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
this._commandsManager.runCommand(commandName, { evt, ...commandOptions }, context);
|
this._commandsManager.runCommand(commandName, { evt, ...commandOptions }, context);
|
||||||
@ -269,7 +288,7 @@ export class HotkeysManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hotkeys.unbind(keys);
|
mouseTrapAPI.unbind(keys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,3 @@
|
|||||||
import windowLevelPresets from './windowLevelPresets';
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Supported Keys: https://craig.is/killing/mice
|
|
||||||
*/
|
|
||||||
const bindings = [
|
const bindings = [
|
||||||
{
|
{
|
||||||
commandName: 'setToolActive',
|
commandName: 'setToolActive',
|
||||||
@ -144,63 +139,46 @@ const bindings = [
|
|||||||
keys: ['esc'],
|
keys: ['esc'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
commandName: 'setWindowLevel',
|
commandName: 'setWindowLevelPreset',
|
||||||
commandOptions: windowLevelPresets[1],
|
commandOptions: { presetName: 'ct-soft-tissue', presetIndex: 0 },
|
||||||
label: 'W/L Preset 1',
|
label: 'W/L Preset 1',
|
||||||
keys: ['1'],
|
keys: ['1'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
commandName: 'setWindowLevel',
|
commandName: 'setWindowLevelPreset',
|
||||||
commandOptions: windowLevelPresets[2],
|
commandOptions: { presetName: 'ct-lung', presetIndex: 1 },
|
||||||
label: 'W/L Preset 2',
|
label: 'W/L Preset 2',
|
||||||
keys: ['2'],
|
keys: ['2'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
commandName: 'setWindowLevel',
|
commandName: 'setWindowLevelPreset',
|
||||||
commandOptions: windowLevelPresets[3],
|
commandOptions: { presetName: 'ct-bone', presetIndex: 2 },
|
||||||
label: 'W/L Preset 3',
|
label: 'W/L Preset 3',
|
||||||
keys: ['3'],
|
keys: ['3'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
commandName: 'setWindowLevel',
|
commandName: 'setWindowLevelPreset',
|
||||||
commandOptions: windowLevelPresets[4],
|
commandOptions: { presetName: 'ct-brain', presetIndex: 3 },
|
||||||
label: 'W/L Preset 4',
|
label: 'W/L Preset 4',
|
||||||
keys: ['4'],
|
keys: ['4'],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
commandName: 'setWindowLevel',
|
|
||||||
commandOptions: windowLevelPresets[5],
|
|
||||||
label: 'W/L Preset 5',
|
|
||||||
keys: ['5'],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
commandName: 'deleteActiveAnnotation',
|
commandName: 'deleteActiveAnnotation',
|
||||||
|
label: 'Delete Annotation',
|
||||||
keys: ['backspace'],
|
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',
|
// commandName: 'undo',
|
||||||
// commandOptions: windowLevelPresets[6],
|
// label: 'Undo',
|
||||||
// label: 'W/L Preset 6',
|
// keys: ['ctrl+z'],
|
||||||
// keys: ['6'],
|
// isEditable: true,
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// commandName: 'setWindowLevel',
|
// commandName: 'redo',
|
||||||
// commandOptions: windowLevelPresets[7],
|
// label: 'Redo',
|
||||||
// label: 'W/L Preset 7',
|
// keys: ['ctrl+y'],
|
||||||
// keys: ['7'],
|
// isEditable: true,
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// commandName: 'setWindowLevel',
|
|
||||||
// commandOptions: windowLevelPresets[8],
|
|
||||||
// label: 'W/L Preset 8',
|
|
||||||
// keys: ['8'],
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// commandName: 'setWindowLevel',
|
|
||||||
// commandOptions: windowLevelPresets[9],
|
|
||||||
// label: 'W/L Preset 9',
|
|
||||||
// keys: ['9'],
|
|
||||||
// },
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -1,4 +1,7 @@
|
|||||||
import hotkeyBindings from './hotkeyBindings';
|
import hotkeyBindings from './hotkeyBindings';
|
||||||
import windowLevelPresets from './windowLevelPresets';
|
import windowLevelPresets from './windowLevelPresets';
|
||||||
export { hotkeyBindings, windowLevelPresets };
|
|
||||||
export default { hotkeyBindings, windowLevelPresets };
|
export default {
|
||||||
|
hotkeyBindings,
|
||||||
|
windowLevelPresets,
|
||||||
|
};
|
||||||
|
|||||||
@ -147,12 +147,6 @@ declare global {
|
|||||||
maxNumPrefetchRequests: number;
|
maxNumPrefetchRequests: number;
|
||||||
order: 'closest' | 'downward' | 'upward';
|
order: 'closest' | 'downward' | 'upward';
|
||||||
};
|
};
|
||||||
tours?: Array<{
|
|
||||||
id: string;
|
|
||||||
steps: StepOptions[];
|
|
||||||
tourOptions: TourOptions;
|
|
||||||
route: string;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Test {
|
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
|
### 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
|
```javascript
|
||||||
window.config = {
|
window.config = {
|
||||||
tours: [
|
customizationService: {
|
||||||
{
|
'ohif.tours': {
|
||||||
id: 'basicViewerTour',
|
$set: [
|
||||||
route: '/viewer',
|
|
||||||
steps: [
|
|
||||||
{
|
{
|
||||||
id: 'scroll',
|
id: 'basicViewerTour',
|
||||||
title: 'Scrolling Through Images',
|
route: '/viewer',
|
||||||
text: 'You can scroll through the images using the mouse wheel or scrollbar.',
|
steps: [
|
||||||
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: [
|
|
||||||
{
|
{
|
||||||
text: 'Skip all',
|
id: 'zoom',
|
||||||
action() {
|
title: 'Zooming In and Out',
|
||||||
this.complete();
|
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
|
## Explanation of Parameters
|
||||||
|
|
||||||
### `tours` Array
|
### `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
|
### Global vs Mode specific hotkeys
|
||||||
When you `setHotkeys`, the `commandName` gets registered with the `commandsManager` and
|
|
||||||
get run after the key is pressed.
|
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 = [
|
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',
|
id: 'measurementLabels',
|
||||||
description: 'Labels for measurement tools in the viewer that are automatically asked for.',
|
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
|
conferences and "hackathons". In this page, we will provide the presentations
|
||||||
and other resources that we have provided to the community in the past:
|
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
|
## 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
|
### IMNO 2024 - March 19-20, 2024
|
||||||
|
|||||||
@ -7,22 +7,25 @@ import './Onboarding.css';
|
|||||||
|
|
||||||
import { hasTourBeenShown, markTourAsShown, defaultShowHandler, middleware } from './utilities';
|
import { hasTourBeenShown, markTourAsShown, defaultShowHandler, middleware } from './utilities';
|
||||||
|
|
||||||
const Onboarding = () => {
|
const Onboarding = ({
|
||||||
const Shepherd = useShepherd();
|
tours = [],
|
||||||
const location = useLocation();
|
}: {
|
||||||
const tours = window.config.tours as Array<{
|
tours?: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
route: string;
|
route: string;
|
||||||
tourOptions: TourOptions;
|
tourOptions: TourOptions;
|
||||||
steps: StepOptions[];
|
steps: StepOptions[];
|
||||||
}>;
|
}>;
|
||||||
|
}) => {
|
||||||
|
const Shepherd = useShepherd();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the tour if it hasn't been shown yet based on the current route.
|
* 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.
|
* Constructs a tour instance and adds steps to it based on the matching tour.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!tours) {
|
if (!tours.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user