From db795f3cb03dd0a1bd2f295842f0aef394af7820 Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Mon, 29 Jun 2020 11:53:46 -0300 Subject: [PATCH 1/5] Add hotkeys config for modes and defaults to core --- modes/example/src/index.js | 4 + modes/longitudinal/src/index.js | 4 + platform/core/src/defaults/hotkeyBindings.js | 95 +++++++++++++++++++ platform/core/src/defaults/index.js | 2 + platform/core/src/index.js | 8 +- platform/viewer/src/App.jsx | 10 +- platform/viewer/src/appInit.js | 11 ++- platform/viewer/src/customHooks/useQuery.js | 2 +- platform/viewer/src/hooks/index.js | 3 +- platform/viewer/src/hooks/useHotkeys.js | 31 ++++++ platform/viewer/src/routes/Mode/Mode.jsx | 6 +- platform/viewer/src/routes/buildModeRoutes.js | 9 +- platform/viewer/src/routes/index.js | 20 ++-- 13 files changed, 182 insertions(+), 23 deletions(-) create mode 100644 platform/core/src/defaults/hotkeyBindings.js create mode 100644 platform/core/src/defaults/index.js create mode 100644 platform/viewer/src/hooks/useHotkeys.js diff --git a/modes/example/src/index.js b/modes/example/src/index.js index 33aae7ad1..597f179ca 100644 --- a/modes/example/src/index.js +++ b/modes/example/src/index.js @@ -1,4 +1,5 @@ import toolbarButtons from './toolbarButtons.js'; +import { hotkeys } from '@ohif/core'; export default function mode({ modeConfiguration }) { return { @@ -68,6 +69,9 @@ export default function mode({ modeConfiguration }) { ], extensions: ['org.ohif.default', 'org.ohif.cornerstone'], sopClassHandlers: ['org.ohif.default.sopClassHandlerModule.stack'], + hotkeys: [ + ...hotkeys.defaults.hotkeyBindings + ] }; } diff --git a/modes/longitudinal/src/index.js b/modes/longitudinal/src/index.js index 71c249341..2bf100c09 100644 --- a/modes/longitudinal/src/index.js +++ b/modes/longitudinal/src/index.js @@ -1,4 +1,5 @@ import toolbarButtons from './toolbarButtons.js'; +import { hotkeys } from '@ohif/core'; const ohif = { layout: 'org.ohif.default.layoutTemplateModule.viewerLayout', @@ -94,6 +95,9 @@ export default function mode({ modeConfiguration }) { 'org.ohif.dicom-sr', ], sopClassHandlers: [ohif.sopClassHandler, dicomsr.sopClassHandler], + hotkeys: [ + ...hotkeys.defaults.hotkeyBindings + ] }; } diff --git a/platform/core/src/defaults/hotkeyBindings.js b/platform/core/src/defaults/hotkeyBindings.js new file mode 100644 index 000000000..960ed605d --- /dev/null +++ b/platform/core/src/defaults/hotkeyBindings.js @@ -0,0 +1,95 @@ +export default [ + // ~ Global + { + commandName: 'incrementActiveViewport', + label: 'Next Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Viewport', + keys: ['left'], + }, + // Supported Keys: https://craig.is/killing/mice + // ~ Cornerstone Extension + { 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'] }, + // clearAnnotations + { commandName: 'nextImage', label: 'Next Image', keys: ['down'] }, + { commandName: 'previousImage', label: 'Previous Image', keys: ['up'] }, + // firstImage + // lastImage + { + commandName: 'previousViewportDisplaySet', + label: 'Previous Series', + keys: ['pagedown'], + }, + { + commandName: 'nextViewportDisplaySet', + label: 'Next Series', + keys: ['pageup'], + }, + // ~ Cornerstone Tools + { 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/core/src/defaults/index.js b/platform/core/src/defaults/index.js new file mode 100644 index 000000000..7c87bc150 --- /dev/null +++ b/platform/core/src/defaults/index.js @@ -0,0 +1,2 @@ +import hotkeyBindings from './hotkeyBindings'; +export { hotkeyBindings }; diff --git a/platform/core/src/index.js b/platform/core/src/index.js index acfdd3fbf..d355da5cc 100644 --- a/platform/core/src/index.js +++ b/platform/core/src/index.js @@ -20,7 +20,8 @@ import studies from './studies/'; import ui from './ui'; import user from './user.js'; import { ViewModelProvider, useViewModel } from './ViewModelContext'; -import utils, { hotkeys } from './utils/'; +import utils from './utils/'; +import { hotkeyBindings } from './defaults'; import { UIDialogService, @@ -37,6 +38,11 @@ import { import IWebApiDataSource from './DataSources/IWebApiDataSource'; +const hotkeys = { + ...utils.hotkeys, + defaults: { hotkeyBindings } +}; + const OHIF = { MODULE_TYPES, // diff --git a/platform/viewer/src/App.jsx b/platform/viewer/src/App.jsx index 5ab382d4f..1cb815d60 100644 --- a/platform/viewer/src/App.jsx +++ b/platform/viewer/src/App.jsx @@ -30,7 +30,7 @@ const Router = JSON.parse(process.env.USE_HASH_ROUTER) ? HashRouter : BrowserRouter; -let commandsManager, extensionManager, servicesManager; +let commandsManager, extensionManager, servicesManager, hotkeysManager; function App({ config, defaultExtensions }) { const init = appInit(config, defaultExtensions); @@ -39,17 +39,19 @@ function App({ config, defaultExtensions }) { commandsManager = init.commandsManager; extensionManager = init.extensionManager; servicesManager = init.servicesManager; + hotkeysManager = init.hotkeysManager; // Set appConfig const appConfigState = init.appConfig; const { routerBasename, modes, dataSources } = appConfigState; // Use config to create routes - const appRoutes = createRoutes( + const appRoutes = createRoutes({ modes, dataSources, extensionManager, - servicesManager - ); + servicesManager, + hotkeysManager + }); const { UIDialogService, UIModalService, diff --git a/platform/viewer/src/appInit.js b/platform/viewer/src/appInit.js index 2f5433f54..eb14613ce 100644 --- a/platform/viewer/src/appInit.js +++ b/platform/viewer/src/appInit.js @@ -2,7 +2,7 @@ import { CommandsManager, ExtensionManager, ServicesManager, - // HotkeysManager, + HotkeysManager, UINotificationService, UIModalService, UIDialogService, @@ -31,13 +31,14 @@ function appInit(appConfigOrFunc, defaultExtensions) { // TODO: Wire this up to Rodrigo's basic Context "ContextService" const commandsManagerConfig = { /** Used by commands to inject `viewports` from "redux" */ - getAppState: () => {}, + getAppState: () => { }, /** Used by commands to determine active context */ getActiveContexts: () => ['VIEWER', 'ACTIVE_VIEWPORT::CORNERSTONE'], }; - const commandsManager = new CommandsManager(commandsManagerConfig); + const servicesManager = new ServicesManager(); - // const hotkeysManager = new HotkeysManager(commandsManager, servicesManager); + const commandsManager = new CommandsManager(commandsManagerConfig); + const hotkeysManager = new HotkeysManager(commandsManager, servicesManager); const extensionManager = new ExtensionManager({ commandsManager, servicesManager, @@ -64,7 +65,6 @@ function appInit(appConfigOrFunc, defaultExtensions) { appConfig.dataSources ); - // TODO: Init global hotkeys, or the hotkeys manager? // TODO: We no longer use `utils.addServer` // TODO: We no longer init webWorkers at app level // TODO: We no longer init the user Manager @@ -80,6 +80,7 @@ function appInit(appConfigOrFunc, defaultExtensions) { commandsManager, extensionManager, servicesManager, + hotkeysManager }; } diff --git a/platform/viewer/src/customHooks/useQuery.js b/platform/viewer/src/customHooks/useQuery.js index d9ac2f95a..6a2ea87a9 100644 --- a/platform/viewer/src/customHooks/useQuery.js +++ b/platform/viewer/src/customHooks/useQuery.js @@ -6,6 +6,6 @@ import { useLocation } from 'react-router-dom'; * * @name useQuery */ -export default function() { +export default function () { return new URLSearchParams(useLocation().search); } diff --git a/platform/viewer/src/hooks/index.js b/platform/viewer/src/hooks/index.js index 8b80a355b..54693b08e 100644 --- a/platform/viewer/src/hooks/index.js +++ b/platform/viewer/src/hooks/index.js @@ -1,4 +1,5 @@ import useDebounce from './useDebounce.js'; import useQuery from './useQuery.js'; +import useHotkeys from './useHotkeys.js'; -export { useDebounce, useQuery }; +export { useDebounce, useQuery, useHotkeys }; diff --git a/platform/viewer/src/hooks/useHotkeys.js b/platform/viewer/src/hooks/useHotkeys.js new file mode 100644 index 000000000..2ee986e73 --- /dev/null +++ b/platform/viewer/src/hooks/useHotkeys.js @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react'; + +/** + * @param {Object} manager HotkeysManager instance + * @param {Object} hotkeys hotkey bindings + * @param {Object} defaultHotkeys default hotkey bindings + */ +const useHotkeys = (manager, hotkeys, defaultHotkeys) => { + const [isLoaded, setIsLoaded] = useState(false); + + useEffect(() => { + if (!hotkeys) { + console.warn('[hotkeys] No bindings defined for hotkeys hook!'); + return; + } + + console.debug('[hotkeys] Setting up hotkeys...'); + manager.setDefaultHotKeys(defaultHotkeys || hotkeys); + manager.setHotkeys(hotkeys); + setIsLoaded(true); + + return () => { + console.debug('[hotkeys] Removing hotkeys...'); + manager.destroy(); + }; + }, [manager, hotkeys, defaultHotkeys]); + + return isLoaded; +} + +export default useHotkeys; diff --git a/platform/viewer/src/routes/Mode/Mode.jsx b/platform/viewer/src/routes/Mode/Mode.jsx index 371aa4c0d..583f9350c 100644 --- a/platform/viewer/src/routes/Mode/Mode.jsx +++ b/platform/viewer/src/routes/Mode/Mode.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { DicomMetadataStore } from '@ohif/core'; import { DragAndDropProvider, ImageViewerProvider } from '@ohif/ui'; // -import { useQuery } from '@hooks'; +import { useQuery, useHotkeys } from '@hooks'; import ViewportGrid from '@components/ViewportGrid'; import Compose from './Compose'; @@ -15,7 +15,9 @@ export default function ModeRoute({ dataSourceName, extensionManager, servicesManager, + hotkeysManager }) { + const isHotkeysLoaded = useHotkeys(hotkeysManager, mode.hotkeys); // Parse route params/querystring const query = useQuery(); const queryStudyInstanceUIDs = query.get('StudyInstanceUIDs'); @@ -83,6 +85,8 @@ export default function ModeRoute({ route, servicesManager, extensionManager, + hotkeysManager, + isHotkeysLoaded ]); // This queries for series, but... What does it do with them? diff --git a/platform/viewer/src/routes/buildModeRoutes.js b/platform/viewer/src/routes/buildModeRoutes.js index bd63e3076..8087b6703 100644 --- a/platform/viewer/src/routes/buildModeRoutes.js +++ b/platform/viewer/src/routes/buildModeRoutes.js @@ -22,12 +22,13 @@ import { ViewModelProvider } from '@ohif/core'; /:modeId/:modeRoute/?queryParameters=example */ -export default function buildModeRoutes( +export default function buildModeRoutes({ modes, dataSources, extensionManager, - servicesManager -) { + servicesManager, + hotkeysManager +}) { const routes = []; // const dataSources = Object.keys(extensionManager.dataSourceMap).map(a => @@ -58,6 +59,7 @@ export default function buildModeRoutes( dataSourceName={dataSourceName} extensionManager={extensionManager} servicesManager={servicesManager} + hotkeysManager={hotkeysManager} /> ); @@ -83,6 +85,7 @@ export default function buildModeRoutes( dataSourceName={defaultDataSourceName} extensionManager={extensionManager} servicesManager={servicesManager} + hotkeysManager={hotkeysManager} /> ); diff --git a/platform/viewer/src/routes/index.js b/platform/viewer/src/routes/index.js index fe66c51fd..2a1eb8087 100644 --- a/platform/viewer/src/routes/index.js +++ b/platform/viewer/src/routes/index.js @@ -21,19 +21,25 @@ const bakedInRoutes = [ { component: NotFound }, ]; -const createRoutes = ( +const createRoutes = ({ modes, dataSources, extensionManager, - servicesManager -) => { - const routes = - buildModeRoutes(modes, dataSources, extensionManager, servicesManager) || - []; + servicesManager, + hotkeysManager +}) => { + const routes = buildModeRoutes({ + modes, + dataSources, + extensionManager, + servicesManager, + hotkeysManager + }) || []; + const allRoutes = [...routes, ...bakedInRoutes]; console.log( - 'Creating Routes: ', + 'Creating Routes:', modes, dataSources, routes, From c8052e0e0a33e714d8fdc73a533855aa746e29a4 Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Mon, 29 Jun 2020 11:57:16 -0300 Subject: [PATCH 2/5] Add context to hotkeys manager --- platform/core/src/classes/HotkeysManager.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platform/core/src/classes/HotkeysManager.js b/platform/core/src/classes/HotkeysManager.js index ee65f08a3..eb4c3ea2a 100644 --- a/platform/core/src/classes/HotkeysManager.js +++ b/platform/core/src/classes/HotkeysManager.js @@ -18,7 +18,7 @@ export class HotkeysManager { if (!commandsManager) { log.warn( - 'HotkeysManager instantiated without a commandsManager. Hotkeys will be unable to find and run commands.' + '[hotkeys] HotkeysManager instantiated without a commandsManager. Hotkeys will be unable to find and run commands.' ); } @@ -147,7 +147,7 @@ export class HotkeysManager { */ registerHotkeys({ commandName, keys, label } = {}, extension) { if (!commandName) { - log.warn(`No command was defined for hotkey "${keys}"`); + log.warn(`[hotkeys] No command was defined for hotkey "${keys}"`); return; } @@ -156,13 +156,13 @@ export class HotkeysManager { if (previouslyRegisteredDefinition) { const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys; this._unbindHotkeys(commandName, previouslyRegisteredKeys); - log.info(`Unbinding ${commandName} from ${previouslyRegisteredKeys}`); + log.info(`[hotkeys] Unbinding ${commandName} from ${previouslyRegisteredKeys}`); } // Set definition & bind this.hotkeyDefinitions[commandName] = { keys, label }; this._bindHotkeys(commandName, keys); - log.info(`Binding ${commandName} to ${keys}`); + log.info(`[hotkeys] Binding ${commandName} to ${keys}`); } /** From f96c1f6648fcde53b99e337b251e39b35888472d Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Mon, 29 Jun 2020 13:58:36 -0300 Subject: [PATCH 3/5] CR Updates --- platform/core/src/defaults/hotkeyBindings.js | 42 +++++++++++--------- platform/viewer/src/hooks/index.js | 3 +- platform/viewer/src/hooks/useHotkeys.js | 31 --------------- platform/viewer/src/routes/Mode/Mode.jsx | 24 ++++++++--- 4 files changed, 44 insertions(+), 56 deletions(-) delete mode 100644 platform/viewer/src/hooks/useHotkeys.js diff --git a/platform/core/src/defaults/hotkeyBindings.js b/platform/core/src/defaults/hotkeyBindings.js index 960ed605d..817f28717 100644 --- a/platform/core/src/defaults/hotkeyBindings.js +++ b/platform/core/src/defaults/hotkeyBindings.js @@ -1,5 +1,8 @@ +/* + * Supported Keys: https://craig.is/killing/mice + */ export default [ - // ~ Global + /** Global */ { commandName: 'incrementActiveViewport', label: 'Next Viewport', @@ -10,8 +13,7 @@ export default [ label: 'Previous Viewport', keys: ['left'], }, - // Supported Keys: https://craig.is/killing/mice - // ~ Cornerstone Extension + /** Viewport */ { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, @@ -29,11 +31,8 @@ export default [ { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] }, { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] }, { commandName: 'resetViewport', label: 'Reset', keys: ['space'] }, - // clearAnnotations { commandName: 'nextImage', label: 'Next Image', keys: ['down'] }, { commandName: 'previousImage', label: 'Previous Image', keys: ['up'] }, - // firstImage - // lastImage { commandName: 'previousViewportDisplaySet', label: 'Previous Series', @@ -44,51 +43,58 @@ export default [ label: 'Next Series', keys: ['pageup'], }, - // ~ Cornerstone Tools - { commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] }, - // ~ Window level presets + /** Window level presets */ { - commandName: 'windowLevelPreset1', + commandName: 'setWindowLevelPreset', + commandOptions: { preset: 1 }, label: 'W/L Preset 1', keys: ['1'], }, { - commandName: 'windowLevelPreset2', + commandName: 'setWindowLevelPreset', + commandOptions: { preset: 2 }, label: 'W/L Preset 2', keys: ['2'], }, { - commandName: 'windowLevelPreset3', + commandName: 'setWindowLevelPreset', + commandOptions: { preset: 3 }, label: 'W/L Preset 3', keys: ['3'], }, { - commandName: 'windowLevelPreset4', + commandName: 'setWindowLevelPreset', + commandOptions: { preset: 4 }, label: 'W/L Preset 4', keys: ['4'], }, { - commandName: 'windowLevelPreset5', + commandName: 'setWindowLevelPreset', + commandOptions: { preset: 5 }, label: 'W/L Preset 5', keys: ['5'], }, { - commandName: 'windowLevelPreset6', + commandName: 'setWindowLevelPreset', + commandOptions: { preset: 6 }, label: 'W/L Preset 6', keys: ['6'], }, { - commandName: 'windowLevelPreset7', + commandName: 'setWindowLevelPreset', + commandOptions: { preset: 7 }, label: 'W/L Preset 7', keys: ['7'], }, { - commandName: 'windowLevelPreset8', + commandName: 'setWindowLevelPreset', + commandOptions: { preset: 8 }, label: 'W/L Preset 8', keys: ['8'], }, { - commandName: 'windowLevelPreset9', + commandName: 'setWindowLevelPreset', + commandOptions: { preset: 9 }, label: 'W/L Preset 9', keys: ['9'], }, diff --git a/platform/viewer/src/hooks/index.js b/platform/viewer/src/hooks/index.js index 54693b08e..8b80a355b 100644 --- a/platform/viewer/src/hooks/index.js +++ b/platform/viewer/src/hooks/index.js @@ -1,5 +1,4 @@ import useDebounce from './useDebounce.js'; import useQuery from './useQuery.js'; -import useHotkeys from './useHotkeys.js'; -export { useDebounce, useQuery, useHotkeys }; +export { useDebounce, useQuery }; diff --git a/platform/viewer/src/hooks/useHotkeys.js b/platform/viewer/src/hooks/useHotkeys.js deleted file mode 100644 index 2ee986e73..000000000 --- a/platform/viewer/src/hooks/useHotkeys.js +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect, useState } from 'react'; - -/** - * @param {Object} manager HotkeysManager instance - * @param {Object} hotkeys hotkey bindings - * @param {Object} defaultHotkeys default hotkey bindings - */ -const useHotkeys = (manager, hotkeys, defaultHotkeys) => { - const [isLoaded, setIsLoaded] = useState(false); - - useEffect(() => { - if (!hotkeys) { - console.warn('[hotkeys] No bindings defined for hotkeys hook!'); - return; - } - - console.debug('[hotkeys] Setting up hotkeys...'); - manager.setDefaultHotKeys(defaultHotkeys || hotkeys); - manager.setHotkeys(hotkeys); - setIsLoaded(true); - - return () => { - console.debug('[hotkeys] Removing hotkeys...'); - manager.destroy(); - }; - }, [manager, hotkeys, defaultHotkeys]); - - return isLoaded; -} - -export default useHotkeys; diff --git a/platform/viewer/src/routes/Mode/Mode.jsx b/platform/viewer/src/routes/Mode/Mode.jsx index 583f9350c..5bf8e37f2 100644 --- a/platform/viewer/src/routes/Mode/Mode.jsx +++ b/platform/viewer/src/routes/Mode/Mode.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { DicomMetadataStore } from '@ohif/core'; import { DragAndDropProvider, ImageViewerProvider } from '@ohif/ui'; // -import { useQuery, useHotkeys } from '@hooks'; +import { useQuery } from '@hooks'; import ViewportGrid from '@components/ViewportGrid'; import Compose from './Compose'; @@ -17,7 +17,6 @@ export default function ModeRoute({ servicesManager, hotkeysManager }) { - const isHotkeysLoaded = useHotkeys(hotkeysManager, mode.hotkeys); // Parse route params/querystring const query = useQuery(); const queryStudyInstanceUIDs = query.get('StudyInstanceUIDs'); @@ -28,7 +27,7 @@ export default function ModeRoute({ ? StudyInstanceUIDs : [StudyInstanceUIDs]; - const { extensions, sopClassHandlers } = mode; + const { extensions, sopClassHandlers, hotkeys } = mode; if (dataSourceName === undefined) { dataSourceName = extensionManager.defaultDataSourceName; @@ -76,6 +75,22 @@ export default function ModeRoute({ return ViewportGrid({ ...props, dataSource }); } + useEffect(() => { + if (!hotkeys) { + console.warn('[hotkeys] No bindings defined for hotkeys hook!'); + return; + } + + console.debug('[hotkeys] Setting up hotkeys...'); + hotkeysManager.setDefaultHotKeys(hotkeys); + hotkeysManager.setHotkeys(hotkeys); + + return () => { + console.debug('[hotkeys] Removing hotkeys...'); + hotkeysManager.destroy(); + }; + }, []); + useEffect(() => { route.init({ servicesManager, extensionManager }); }, [ @@ -85,8 +100,7 @@ export default function ModeRoute({ route, servicesManager, extensionManager, - hotkeysManager, - isHotkeysLoaded + hotkeysManager ]); // This queries for series, but... What does it do with them? From 686acabd9f6732d6eec436c7a7cc8d12fa4d44b4 Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Mon, 29 Jun 2020 14:53:49 -0300 Subject: [PATCH 4/5] Wire presets --- modes/longitudinal/src/toolbarButtons.js | 45 ++++++++++--------- platform/core/src/classes/HotkeysManager.js | 22 +++++---- platform/core/src/defaults/hotkeyBindings.js | 38 ++++++++-------- platform/core/src/defaults/index.js | 4 +- .../core/src/defaults/windowLevelPresets.js | 12 +++++ platform/core/src/index.js | 6 ++- platform/core/src/index.test.js | 1 + .../core/src/redux/reducers/preferences.js | 15 ++----- 8 files changed, 80 insertions(+), 63 deletions(-) create mode 100644 platform/core/src/defaults/windowLevelPresets.js diff --git a/modes/longitudinal/src/toolbarButtons.js b/modes/longitudinal/src/toolbarButtons.js index c2e5cd5e4..b0a271638 100644 --- a/modes/longitudinal/src/toolbarButtons.js +++ b/modes/longitudinal/src/toolbarButtons.js @@ -1,8 +1,11 @@ // TODO: torn, can either bake this here; or have to create a whole new button type // Only ways that you can pass in a custom React component for render :l -import { ExpandableToolbarButton, ListMenu } from '@ohif/ui'; import React from 'react'; import classnames from 'classnames'; +import { ExpandableToolbarButton, ListMenu } from '@ohif/ui'; +import { defaults } from '@ohif/core'; + +const { windowLevelPresets } = defaults; export default [ // Divider @@ -39,36 +42,36 @@ export default [ commandName: 'setToolActive', commandOptions: { toolName: 'Wwwc' }, commands: { - 'windowLevelPreset1': { - commandName: 'windowLevelPreset1', - commandOptions: {}, + 1: { + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[1], }, - 'windowLevelPreset2': { - commandName: 'windowLevelPreset2', - commandOptions: {}, + 2: { + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[2], }, - 'windowLevelPreset3': { - commandName: 'windowLevelPreset3', - commandOptions: {}, + 3: { + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[3], }, - 'windowLevelPreset4': { - commandName: 'windowLevelPreset4', - commandOptions: {}, + 4: { + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[4], }, - 'windowLevelPreset5': { - commandName: 'windowLevelPreset5', - commandOptions: {}, + 5: { + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[5], } }, type: 'primary', content: ListMenu, contentProps: { options: [ - { value: 'windowLevelPreset1', title: 'Soft tissue', subtitle: '400 / 40' }, - { value: 'windowLevelPreset2', title: 'Lung', subtitle: '1500 / -600' }, - { value: 'windowLevelPreset3', title: 'Liver', subtitle: '150 / 90' }, - { value: 'windowLevelPreset4', title: 'Bone', subtitle: '80 / 40' }, - { value: 'windowLevelPreset5', title: 'Brain', subtitle: '2500 / 480' }, + { value: 1, title: 'Soft tissue', subtitle: '400 / 40' }, + { value: 2, title: 'Lung', subtitle: '1500 / -600' }, + { value: 3, title: 'Liver', subtitle: '150 / 90' }, + { value: 4, title: 'Bone', subtitle: '80 / 40' }, + { value: 5, title: 'Brain', subtitle: '2500 / 480' }, ], renderer: ({ title, subtitle, isActive, index }) => ( <> diff --git a/platform/core/src/classes/HotkeysManager.js b/platform/core/src/classes/HotkeysManager.js index eb4c3ea2a..0848e0437 100644 --- a/platform/core/src/classes/HotkeysManager.js +++ b/platform/core/src/classes/HotkeysManager.js @@ -1,3 +1,4 @@ +import objectHash from 'object-hash'; import hotkeys from './../utils/hotkeys'; import log from './../log.js'; @@ -6,6 +7,7 @@ import log from './../log.js'; * * @typedef {Object} HotkeyDefinition * @property {String} commandName - Command to call + * @property {Object} commandOptions - Command options * @property {String} label - Display name for hotkey * @property {String[]} keys - Keys to bind; Follows Mousetrap.js binding syntax */ @@ -141,28 +143,30 @@ export class HotkeysManager { * When a hotkey combination is triggered, the command name and active contexts * are used to locate the correct command to call. * - * @param {HotkeyDefinition} commandName + * @param {HotkeyDefinition} command * @param {String} extension * @returns {undefined} */ - registerHotkeys({ commandName, keys, label } = {}, extension) { + registerHotkeys({ commandName, commandOptions = {}, keys, label } = {}, extension) { if (!commandName) { log.warn(`[hotkeys] No command was defined for hotkey "${keys}"`); return; } - const previouslyRegisteredDefinition = this.hotkeyDefinitions[commandName]; + const commandHash = objectHash({ commandName, commandOptions }); + const options = Object.keys(commandOptions).length ? JSON.stringify(commandOptions) : 'no'; + const previouslyRegisteredDefinition = this.hotkeyDefinitions[commandHash]; if (previouslyRegisteredDefinition) { const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys; this._unbindHotkeys(commandName, previouslyRegisteredKeys); - log.info(`[hotkeys] Unbinding ${commandName} from ${previouslyRegisteredKeys}`); + log.info(`[hotkeys] Unbinding ${commandName} with ${options} options from ${previouslyRegisteredKeys}`); } // Set definition & bind - this.hotkeyDefinitions[commandName] = { keys, label }; - this._bindHotkeys(commandName, keys); - log.info(`[hotkeys] Binding ${commandName} to ${keys}`); + this.hotkeyDefinitions[commandHash] = { keys, label }; + this._bindHotkeys(commandName, commandOptions, keys); + log.info(`[hotkeys] Binding ${commandName} with ${options} options to ${keys}`); } /** @@ -191,7 +195,7 @@ export class HotkeysManager { * @param {string[]} keys - One or more key combinations that should trigger command * @returns {undefined} */ - _bindHotkeys(commandName, keys) { + _bindHotkeys(commandName, commandOptions = {}, keys) { const isKeyDefined = keys === '' || keys === undefined; if (isKeyDefined) { return; @@ -203,7 +207,7 @@ export class HotkeysManager { hotkeys.bind(combinedKeys, evt => { evt.preventDefault(); evt.stopPropagation(); - this._commandsManager.runCommand(commandName, { evt }); + this._commandsManager.runCommand(commandName, { evt, ...commandOptions }); }); } diff --git a/platform/core/src/defaults/hotkeyBindings.js b/platform/core/src/defaults/hotkeyBindings.js index 817f28717..cbce9728b 100644 --- a/platform/core/src/defaults/hotkeyBindings.js +++ b/platform/core/src/defaults/hotkeyBindings.js @@ -1,3 +1,5 @@ +import windowLevelPresets from "./windowLevelPresets"; + /* * Supported Keys: https://craig.is/killing/mice */ @@ -45,56 +47,56 @@ export default [ }, /** Window level presets */ { - commandName: 'setWindowLevelPreset', - commandOptions: { preset: 1 }, + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[1], label: 'W/L Preset 1', keys: ['1'], }, { - commandName: 'setWindowLevelPreset', - commandOptions: { preset: 2 }, + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[2], label: 'W/L Preset 2', keys: ['2'], }, { - commandName: 'setWindowLevelPreset', - commandOptions: { preset: 3 }, + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[3], label: 'W/L Preset 3', keys: ['3'], }, { - commandName: 'setWindowLevelPreset', - commandOptions: { preset: 4 }, + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[4], label: 'W/L Preset 4', keys: ['4'], }, { - commandName: 'setWindowLevelPreset', - commandOptions: { preset: 5 }, + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[5], label: 'W/L Preset 5', keys: ['5'], }, { - commandName: 'setWindowLevelPreset', - commandOptions: { preset: 6 }, + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[6], label: 'W/L Preset 6', keys: ['6'], }, { - commandName: 'setWindowLevelPreset', - commandOptions: { preset: 7 }, + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[7], label: 'W/L Preset 7', keys: ['7'], }, { - commandName: 'setWindowLevelPreset', - commandOptions: { preset: 8 }, + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[8], label: 'W/L Preset 8', keys: ['8'], }, { - commandName: 'setWindowLevelPreset', - commandOptions: { preset: 9 }, + commandName: 'setWindowLevel', + commandOptions: windowLevelPresets[9], label: 'W/L Preset 9', keys: ['9'], }, diff --git a/platform/core/src/defaults/index.js b/platform/core/src/defaults/index.js index 7c87bc150..f8c065d7b 100644 --- a/platform/core/src/defaults/index.js +++ b/platform/core/src/defaults/index.js @@ -1,2 +1,4 @@ import hotkeyBindings from './hotkeyBindings'; -export { hotkeyBindings }; +import windowLevelPresets from './windowLevelPresets'; +export { hotkeyBindings, windowLevelPresets }; +export default { hotkeyBindings, windowLevelPresets }; diff --git a/platform/core/src/defaults/windowLevelPresets.js b/platform/core/src/defaults/windowLevelPresets.js new file mode 100644 index 000000000..83a7321ba --- /dev/null +++ b/platform/core/src/defaults/windowLevelPresets.js @@ -0,0 +1,12 @@ +export default { + 1: { description: 'Soft tissue', window: '400', level: '40' }, + 2: { description: 'Lung', window: '1500', level: '-600' }, + 3: { description: 'Liver', window: '150', level: '90' }, + 4: { description: 'Bone', window: '2500', level: '480' }, + 5: { description: 'Brain', window: '80', level: '40' }, + 6: { description: 'Trest', window: '1', level: '1' }, + 7: { description: '', window: '', level: '' }, + 8: { description: '', window: '', level: '' }, + 9: { description: '', window: '', level: '' }, + 10: { description: '', window: '', level: '' }, +}; diff --git a/platform/core/src/index.js b/platform/core/src/index.js index d355da5cc..a16604cc0 100644 --- a/platform/core/src/index.js +++ b/platform/core/src/index.js @@ -21,7 +21,7 @@ import ui from './ui'; import user from './user.js'; import { ViewModelProvider, useViewModel } from './ViewModelContext'; import utils from './utils/'; -import { hotkeyBindings } from './defaults'; +import defaults from './defaults'; import { UIDialogService, @@ -40,7 +40,7 @@ import IWebApiDataSource from './DataSources/IWebApiDataSource'; const hotkeys = { ...utils.hotkeys, - defaults: { hotkeyBindings } + defaults: { hotkeyBindings: defaults.hotkeyBindings } }; const OHIF = { @@ -51,6 +51,7 @@ const OHIF = { HotkeysManager, ServicesManager, // + defaults, utils, hotkeys, studies, @@ -94,6 +95,7 @@ export { HotkeysManager, ServicesManager, // + defaults, utils, hotkeys, studies, diff --git a/platform/core/src/index.test.js b/platform/core/src/index.test.js index 47ee09488..d7aef6f69 100644 --- a/platform/core/src/index.test.js +++ b/platform/core/src/index.test.js @@ -15,6 +15,7 @@ describe('Top level exports', () => { 'UIDialogService', 'MeasurementService', // + 'defaults', 'utils', 'hotkeys', 'studies', diff --git a/platform/core/src/redux/reducers/preferences.js b/platform/core/src/redux/reducers/preferences.js index 2ec01c917..a9eca66e3 100644 --- a/platform/core/src/redux/reducers/preferences.js +++ b/platform/core/src/redux/reducers/preferences.js @@ -1,16 +1,7 @@ +import { windowLevelPresets } from '../../defaults'; + const defaultState = { - windowLevelData: { - 1: { description: 'Soft tissue', window: '550', level: '40' }, - 2: { description: 'Lung', window: '150', level: '-600' }, - 3: { description: 'Liver', window: '150', level: '90' }, - 4: { description: 'Bone', window: '2500', level: '480' }, - 5: { description: 'Brain', window: '80', level: '40' }, - 6: { description: 'Trest', window: '1', level: '1' }, - 7: { description: '', window: '', level: '' }, - 8: { description: '', window: '', level: '' }, - 9: { description: '', window: '', level: '' }, - 10: { description: '', window: '', level: '' }, - }, + windowLevelData: windowLevelPresets, generalPreferences: { // language: 'en-US' }, From fc9a0941dbe5293ebe340e65ffcc400a4c32adcf Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Mon, 29 Jun 2020 15:30:22 -0300 Subject: [PATCH 5/5] Fix problem with focus --- .../components/ViewportPane/ViewportPane.jsx | 33 ++++++++++++++----- .../viewer/src/components/ViewportGrid.jsx | 8 +++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/platform/ui/src/components/ViewportPane/ViewportPane.jsx b/platform/ui/src/components/ViewportPane/ViewportPane.jsx index b1374ca14..7c9bea5bf 100644 --- a/platform/ui/src/components/ViewportPane/ViewportPane.jsx +++ b/platform/ui/src/components/ViewportPane/ViewportPane.jsx @@ -14,6 +14,7 @@ function ViewportPane({ onInteraction, acceptDropsFor, }) { + let dropElement = null; const [{ isHovered, isHighlighted }, drop] = useDrop({ accept: acceptDropsFor, // TODO: pass in as prop? @@ -22,7 +23,7 @@ function ViewportPane({ const isOver = monitor.isOver(); if (canDrop && isOver && onDrop) { - onInteraction(); + onInteractionHandler(); onDrop(droppedItem); } }, @@ -33,16 +34,32 @@ function ViewportPane({ }), }); + const focus = () => { + if (dropElement) { + dropElement.focus(); + } + }; + + const onInteractionHandler = () => { + onInteraction(); + focus(); + }; + + const refHandler = element => { + drop(element); + dropElement = element; + }; + return (
{}; +const noop = () => { }; ViewportPane.defaultProps = { onInteraction: noop, diff --git a/platform/viewer/src/components/ViewportGrid.jsx b/platform/viewer/src/components/ViewportGrid.jsx index e6384b82e..4052cbd0a 100644 --- a/platform/viewer/src/components/ViewportGrid.jsx +++ b/platform/viewer/src/components/ViewportGrid.jsx @@ -115,15 +115,17 @@ function ViewerViewportGrid(props) { viewportComponents ); + const onInterationHandler = () => { + setActiveViewportIndex(viewportIndex); + }; + viewportPanes[i] = ( { - setActiveViewportIndex(viewportIndex); - }} + onInteraction={onInterationHandler} isActive={activeViewportIndex === viewportIndex} >