diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.jsx index 06e710dd7..3e1a9d26e 100644 --- a/extensions/default/src/ViewerLayout/index.jsx +++ b/extensions/default/src/ViewerLayout/index.jsx @@ -133,6 +133,7 @@ function ViewerLayout({ hide(); }, onReset: () => hotkeysManager.restoreDefaultBindings(), + hotkeysModule: hotkeys }, }), }, diff --git a/platform/core/src/classes/HotkeysManager.js b/platform/core/src/classes/HotkeysManager.js index afd4ffea2..3439c76ea 100644 --- a/platform/core/src/classes/HotkeysManager.js +++ b/platform/core/src/classes/HotkeysManager.js @@ -26,8 +26,6 @@ export class HotkeysManager { this._servicesManager = servicesManager; this._commandsManager = commandsManager; - - hotkeys.initialize(); } /** diff --git a/platform/core/src/classes/HotkeysManager.test.js b/platform/core/src/classes/HotkeysManager.test.js index 0298128df..159dab8c7 100644 --- a/platform/core/src/classes/HotkeysManager.test.js +++ b/platform/core/src/classes/HotkeysManager.test.js @@ -10,9 +10,6 @@ jest.mock('./../log.js'); describe('HotkeysManager', () => { let hotkeysManager, commandsManager; - beforeAll(() => { - hotkeys.initialize(); - }); beforeEach(() => { commandsManager = new CommandsManager(); diff --git a/platform/core/src/utils/hotkeys/index.js b/platform/core/src/utils/hotkeys/index.js index e12068400..9cd157a1a 100644 --- a/platform/core/src/utils/hotkeys/index.js +++ b/platform/core/src/utils/hotkeys/index.js @@ -2,30 +2,7 @@ import Mousetrap from 'mousetrap'; import pausePlugin from './pausePlugin'; import recordPlugin from './recordPlugin'; -Mousetrap.initialize = () => { - if (!Mousetrap._initialized) { - recordPlugin(Mousetrap); - pausePlugin(Mousetrap); - - Mousetrap._initialized = true; - } -}; - -// These are only here so that Jest mocks them properly before .initialize() is called -Mousetrap.handleKey = () => - console.debug('Mousetrap recordPlugin not yet initialized'); -Mousetrap.startRecording = () => - console.debug('Mousetrap recordPlugin not yet initialized'); -Mousetrap.stopRecording = () => - console.debug('Mousetrap recordPlugin not yet initialized'); -Mousetrap.record = () => - console.debug('Mousetrap recordPlugin not yet initialized'); - -Mousetrap.stopCallback = () => - console.debug('Mousetrap pausePlugin not yet initialized'); -Mousetrap.pause = () => - console.debug('Mousetrap pausePlugin not yet initialized'); -Mousetrap.unpause = () => - console.debug('Mousetrap pausePlugin not yet initialized'); +recordPlugin(Mousetrap); +pausePlugin(Mousetrap); export default Mousetrap; diff --git a/platform/core/src/utils/hotkeys/pausePlugin.js b/platform/core/src/utils/hotkeys/pausePlugin.js index 80c5513f0..6774d760f 100644 --- a/platform/core/src/utils/hotkeys/pausePlugin.js +++ b/platform/core/src/utils/hotkeys/pausePlugin.js @@ -5,7 +5,7 @@ * * https://github.com/ccampbell/mousetrap/blob/master/plugins/pause/mousetrap-pause.js */ -export default function(Mousetrap) { +export default function pausePlugin(Mousetrap) { var _originalStopCallback = Mousetrap.prototype.stopCallback; Mousetrap.prototype.stopCallback = function(e, element, combo) { diff --git a/platform/core/src/utils/hotkeys/recordPlugin.js b/platform/core/src/utils/hotkeys/recordPlugin.js index 186964967..e425ce5bf 100644 --- a/platform/core/src/utils/hotkeys/recordPlugin.js +++ b/platform/core/src/utils/hotkeys/recordPlugin.js @@ -1,10 +1,9 @@ /** * This extension allows you to record a sequence using Mousetrap. - * {@link https://craig.is/killing/mice} * * @author Dan Tao */ -export default function (Mousetrap) { +export default function recordPlugin(Mousetrap, options = { timeout: 100 }) { /** * the sequence currently being recorded * @@ -109,7 +108,7 @@ export default function (Mousetrap) { _recordedSequence.push(_currentRecordedKeys); _currentRecordedKeys = []; _recordedCharacterKey = false; - _finishRecording(); + _restartRecordTimer(); } /** @@ -123,7 +122,7 @@ export default function (Mousetrap) { */ function _normalizeSequence(sequence) { for (let i = 0; i < sequence.length; ++i) { - sequence[i].sort(function (x, y) { + sequence[i].sort(function(x, y) { // modifier keys always come first, in alphabetical order if (x.length > 1 && y.length === 1) { return -1; @@ -168,7 +167,7 @@ export default function (Mousetrap) { */ function _restartRecordTimer() { clearTimeout(_recordTimer); - _recordTimer = setTimeout(_finishRecording, 1000); + _recordTimer = setTimeout(_finishRecording, options.timeout); } /** @@ -178,10 +177,10 @@ export default function (Mousetrap) { * @param {Function} callback * @returns void */ - Mousetrap.prototype.record = function (callback) { + Mousetrap.prototype.record = function(callback) { var self = this; self.recording = true; - _recordedSequenceCallback = function () { + _recordedSequenceCallback = function() { self.recording = false; callback.apply(self, arguments); }; @@ -193,7 +192,7 @@ export default function (Mousetrap) { * @param {Function} callback * @returns void */ - Mousetrap.prototype.stopRecord = function () { + Mousetrap.prototype.stopRecord = function() { var self = this; self.recording = false; }; @@ -204,12 +203,11 @@ export default function (Mousetrap) { * @param {Function} callback * @returns void */ - Mousetrap.prototype.startRecording = function () { + Mousetrap.prototype.startRecording = function() { var self = this; self.recording = true; }; - - Mousetrap.prototype.handleKey = function () { + Mousetrap.prototype.handleKey = function() { var self = this; _handleKey.apply(self, arguments); }; diff --git a/platform/docs/netlify.toml b/platform/docs/netlify.toml index 20dc48f58..b9b50acfb 100644 --- a/platform/docs/netlify.toml +++ b/platform/docs/netlify.toml @@ -9,7 +9,7 @@ # #[build] -# ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF" + ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../ui/ ../core/ ../i18n/" # NODE_VERSION in root `.nvmrc` takes priority # YARN_FLAGS: https://www.netlify.com/docs/build-gotchas/#yarn diff --git a/platform/ui/src/components/HotkeyField/HotkeyField.jsx b/platform/ui/src/components/HotkeyField/HotkeyField.jsx index 23f6fd641..9124573f4 100644 --- a/platform/ui/src/components/HotkeyField/HotkeyField.jsx +++ b/platform/ui/src/components/HotkeyField/HotkeyField.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import Input from '../Input'; @@ -18,14 +18,7 @@ import { getKeys, formatKeysForInput } from './utils'; const HotkeyField = ({ disabled, keys, onChange, className, modifierKeys, hotkeys }) => { const inputValue = formatKeysForInput(keys); - useEffect(() => { - hotkeys.initialize(); - }, []) - const onInputKeyDown = event => { - event.stopPropagation(); - event.preventDefault(); - hotkeys.record(sequence => { const keys = getKeys({ sequence, modifierKeys }); hotkeys.unpause(); diff --git a/platform/viewer/public/config/default.js b/platform/viewer/public/config/default.js index 9afb85fe6..fa6358a54 100644 --- a/platform/viewer/public/config/default.js +++ b/platform/viewer/public/config/default.js @@ -66,4 +66,92 @@ window.config = { // }, // }, defaultDataSourceName: 'dicomweb', + hotkeys: [ + { + commandName: 'incrementActiveViewport', + label: 'Next Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Viewport', + keys: ['left'], + }, + { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, + { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, + { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, + { + commandName: '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'], + }, + ], }; diff --git a/platform/viewer/public/config/netlify.js b/platform/viewer/public/config/netlify.js index bae63f6ef..a20b3ac21 100644 --- a/platform/viewer/public/config/netlify.js +++ b/platform/viewer/public/config/netlify.js @@ -26,4 +26,92 @@ window.config = { }, ], defaultDataSourceName: 'dicomweb', + hotkeys: [ + { + commandName: 'incrementActiveViewport', + label: 'Next Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Viewport', + keys: ['left'], + }, + { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, + { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, + { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, + { + commandName: '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'], + }, + ], }; diff --git a/platform/viewer/public/config/public_dicomweb.js b/platform/viewer/public/config/public_dicomweb.js index 0720249a7..dd2a54b01 100644 --- a/platform/viewer/public/config/public_dicomweb.js +++ b/platform/viewer/public/config/public_dicomweb.js @@ -20,4 +20,92 @@ window.config = { LOCIZE_API_KEY: null, // Developers can use this to do in-context editing. DO NOT COMMIT THIS KEY! USE_LOCIZE: false, }, + hotkeys: [ + { + commandName: 'incrementActiveViewport', + label: 'Next Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Viewport', + keys: ['left'], + }, + { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, + { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, + { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, + { + commandName: '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'], + }, + ], }; diff --git a/platform/viewer/src/routes/WorkList/WorkList.jsx b/platform/viewer/src/routes/WorkList/WorkList.jsx index 731735384..f0c3d65fd 100644 --- a/platform/viewer/src/routes/WorkList/WorkList.jsx +++ b/platform/viewer/src/routes/WorkList/WorkList.jsx @@ -10,9 +10,9 @@ import { useTranslation } from 'react-i18next'; import filtersMeta from './filtersMeta.js'; import { useAppConfig } from '@state'; import { useDebounce, useQuery } from '@hooks'; -import { utils } from '@ohif/core'; +import { utils, hotkeys } from '@ohif/core'; -const { sortBySeriesDate, hotkeys } = utils; +const { sortBySeriesDate } = utils; import { Icon,