diff --git a/OHIFViewer/client/components/toolbarSection/toolbarSection.js b/OHIFViewer/client/components/toolbarSection/toolbarSection.js index f807f53db..8eb8689d0 100644 --- a/OHIFViewer/client/components/toolbarSection/toolbarSection.js +++ b/OHIFViewer/client/components/toolbarSection/toolbarSection.js @@ -102,7 +102,7 @@ Template.toolbarSection.helpers({ iconClasses: 'fa fa-trash' }); - var buttonData = []; + const buttonData = []; buttonData.push({ id: 'zoom', @@ -175,7 +175,8 @@ Template.toolbarSection.helpers({ id: 'toggleCinePlay', title: 'Toggle CINE Play', classes: 'imageViewerCommand', - buttonTemplateName: 'playClipButton' + buttonTemplateName: 'playClipButton', + active: OHIF.viewerbase.viewportUtils.isPlaying }); buttonData.push({ @@ -183,7 +184,8 @@ Template.toolbarSection.helpers({ title: 'CINE', classes: 'imageViewerCommand', iconClasses: 'fa fa-youtube-play', - disableFunction: OHIF.viewerbase.viewportUtils.hasMultipleFrames + disableFunction: OHIF.viewerbase.viewportUtils.hasMultipleFrames, + active: () => $('#cineDialog').is(':visible') }); } diff --git a/Packages/ohif-commands/client/classes/CommandsManager.js b/Packages/ohif-commands/client/classes/CommandsManager.js index 3b1c86c57..c7c1d0699 100644 --- a/Packages/ohif-commands/client/classes/CommandsManager.js +++ b/Packages/ohif-commands/client/classes/CommandsManager.js @@ -1,5 +1,4 @@ -import { Tracker } from 'meteor/tracker'; -import { Session } from 'meteor/session'; +import { ReactiveVar } from 'meteor/reactive-var'; import { _ } from 'meteor/underscore'; import { OHIF } from 'meteor/ohif:core'; @@ -7,11 +6,8 @@ export class CommandsManager { constructor() { this.contexts = {}; - Tracker.autorun(() => { - const currentContextName = OHIF.context.get(); - OHIF.log.info(currentContextName); - // TODO: initialize hotkeys context - }); + // Enable reactivity by storing the last executed command + this.last = new ReactiveVar(''); } getContext(contextName) { @@ -80,7 +76,12 @@ export class CommandsManager { return OHIF.log.warn(`No action was defined for command "${command}"`); } else { const result = action(params); - Session.set('lastCommand', command); + if (this.last.get() === command) { + this.last.dep.changed(); + } else { + this.last.set(command); + } + return result; } } diff --git a/Packages/ohif-hotkeys/client/classes/HotkeysContext.js b/Packages/ohif-hotkeys/client/classes/HotkeysContext.js index 99cc84a4b..d65b32622 100644 --- a/Packages/ohif-hotkeys/client/classes/HotkeysContext.js +++ b/Packages/ohif-hotkeys/client/classes/HotkeysContext.js @@ -9,39 +9,48 @@ export class HotkeysContext { extend(definitions={}) { if (typeof definitions !== 'object') return; - this.destroy(); - Object.assign(this.definitions, definitions); - this.initialize(); + Object.keys(definitions).forEach(command => { + const hotkey = definitions[command]; + this.unregister(command); + this.register(command, hotkey); + this.definitions[command] = hotkey; + }); + } + + register(command, hotkey) { + if (!hotkey) { + return OHIF.log.warn(`No hotkey was defined for "${command}"`); + } + + if (!command) { + return OHIF.log.warn(`No command was defined for hotkey "${hotkey}"`); + } + + const bindingKey = `keydown.hotkey.${this.name}.${command}`; + const bind = hotkey => $(document).bind(bindingKey, hotkey, event => { + if (!this.enabled.get()) return; + OHIF.commands.run(command); + }); + + if (hotkey instanceof Array) { + hotkey.forEach(hotkey => bind(hotkey)); + } else { + bind(hotkey); + } + } + + unregister(command) { + const bindingKey = `keydown.hotkey.${this.name}.${command}`; + if (this.definitions[command]) { + $(document).unbind(bindingKey); + delete this.definitions[command]; + } } initialize() { - Object.keys(this.definitions).forEach(definitionKey => { - const { hotkey, action, disabled } = this.definitions[definitionKey]; - let message; - if (!hotkey) { - message = `No hotkey was defined for "${definitionKey}"`; - } else if (!action) { - message = `No action was defined for "${definitionKey}" using "${hotkey}" hotkey`; - } - - if (message) { - return OHIF.log.warn(message); - } - - const bindHotkey = hotkey => $(document).bind(`keydown.hotkey.${this.name}`, hotkey, event => { - if (!this.enabled.get()) return; - if (typeof disabled !== undefined) { - if ((typeof disabled === 'function' && disabled()) || disabled) return; - } - - action(event); - }); - - if (hotkey instanceof Array) { - hotkey.forEach(hotkey => bindHotkey(hotkey)); - } else { - bindHotkey(hotkey); - } + Object.keys(this.definitions).forEach(command => { + const hotkey = this.definitions[command]; + this.register(command, hotkey); }); } diff --git a/Packages/ohif-hotkeys/client/classes/HotkeysManager.js b/Packages/ohif-hotkeys/client/classes/HotkeysManager.js index 466e44859..91e54fb89 100644 --- a/Packages/ohif-hotkeys/client/classes/HotkeysManager.js +++ b/Packages/ohif-hotkeys/client/classes/HotkeysManager.js @@ -1,4 +1,6 @@ import { ReactiveVar } from 'meteor/reactive-var'; +import { Tracker } from 'meteor/tracker'; +import { OHIF } from 'meteor/ohif:core'; import { HotkeysContext } from 'meteor/ohif:hotkeys/client/classes/HotkeysContext'; export class HotkeysManager { @@ -6,6 +8,11 @@ export class HotkeysManager { this.contexts = {}; this.currentContextName = null; this.enabled = new ReactiveVar(true); + + Tracker.autorun(() => { + const contextName = OHIF.context.get(); + this.switchToContext(contextName); + }); } disable() { @@ -24,7 +31,7 @@ export class HotkeysManager { return this.getContext(this.currentContextName); } - setContext(contextName, contextDefinitions) { + set(contextName, contextDefinitions, extend=false) { const enabled = this.enabled; const context = new HotkeysContext(contextName, contextDefinitions, enabled); const currentContext = this.getCurrentContext(); @@ -36,6 +43,16 @@ export class HotkeysManager { this.contexts[contextName] = context; } + register(contextName, command, hotkey) { + if (!command || !hotkey) return; + const context = this.getContext(contextName); + if (!context) { + this.set(contextName, {}); + } + + context.register(command, hotkey); + } + unsetContext(contextName) { if (contextName === this.currentContextName) { this.getCurrentContext().destroy(); diff --git a/Packages/ohif-hotkeys/package.js b/Packages/ohif-hotkeys/package.js index 4718480b2..1650e3167 100644 --- a/Packages/ohif-hotkeys/package.js +++ b/Packages/ohif-hotkeys/package.js @@ -16,7 +16,6 @@ Package.onUse(function(api) { ]); // OHIF dependencies - api.use('ohif:core'); api.use('ohif:commands'); // Main module definition diff --git a/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js b/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js index b9665b48b..09467215c 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js +++ b/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js @@ -7,7 +7,7 @@ Template.toolbarSectionButton.onCreated(() => { const instance = Template.instance(); instance.isActive = activeToolId => { - // TODO: Find a way to prevent the 'flash' after a click, but before this helper runs + OHIF.commands.last.dep.depend(); const instance = Template.instance(); const subTools = instance.data.subTools; const currentId = instance.data.id; @@ -15,9 +15,10 @@ Template.toolbarSectionButton.onCreated(() => { const isSubTool = subTools && _.findWhere(subTools, { id: activeToolId }); const activeCommandButtons = Session.get('ToolManagerActiveCommandButtons') || []; const isActiveCommandButton = activeCommandButtons.indexOf(instance.data.id) !== -1; + const isActive = typeof instance.data.active === 'function' && instance.data.active(); // Check if the current tool, a sub tool or a command button is active - return isCurrentTool || isSubTool || isActiveCommandButton; + return isActive || isCurrentTool || isSubTool || isActiveCommandButton; }; instance.getActiveToolSubProperty = (propertyName, activeToolId) => { @@ -36,19 +37,34 @@ Template.toolbarSectionButton.onCreated(() => { instance.autorun(computation => { // Get the last executed command - const lastCommand = Session.get('lastCommand'); + const lastCommand = OHIF.commands.last.get(); // Prevent running this computation on its first run if (computation.firstRun) return; // Stop here if it's not the last command or if it's already an active tool const { id } = instance.data; - if (lastCommand !== id || instance.isActive(id)) return; + const activeToolId = OHIF.viewerbase.toolManager.getActiveTool(); + if (lastCommand !== id || instance.isActive(activeToolId)) return; // Add an active class to a button for 100ms to give the impression the button was pressed - const $button = instance.$('.toolbarSectionButton:first'); - $button.addClass('active'); - setTimeout(() => $button.removeClass('active'), 100); + const flashButton = $element => { + $element.addClass('active'); + setTimeout(() => { + if ($element.hasClass('expandable') && $element.find('.toolbarSectionButton.active').length) return; + $element.removeClass('active'); + }, 100); + }; + + // Flash the active button + const $button = instance.$('.toolbarSectionButton').first(); + flashButton($button); + + // Flash the parent button as well in case of this button is inside a drawer + const $parentButton = $button.closest('.toolbarSectionButton.expandable'); + if ($parentButton.length) { + flashButton($parentButton); + } }); }); diff --git a/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js b/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js index 1326fe542..db6b7f5c3 100644 --- a/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js +++ b/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js @@ -37,7 +37,7 @@ Meteor.startup(function() { flipH: 'H', wwwc: 'W', zoom: 'Z', - cinePlay: 'SPACE', + toggleCinePlay: 'SPACE', rotateR: 'R', rotateL: 'L', toggleOverlayTags: 'SHIFT', @@ -71,6 +71,7 @@ Meteor.startup(function() { angle: 'Angle', dragProbe: 'Probe', ellipticalRoi: 'Ellipse', + rectangleRoi: 'Rectangle', magnify: 'Magnify', annotate: 'Annotate', stackScroll: 'Stack Scroll', @@ -98,7 +99,10 @@ Meteor.startup(function() { flipV: 'Flip vertically', rotateR: 'Rotate right', rotateL: 'Rotate left', - toggleCinePlay: 'Play/Pause' + toggleCinePlay: 'Play/Pause', + toggleCineDialog: 'CINE dialog', + resetViewport: 'Reset', + clearTools: 'Clear' }); // Functions to register the preset switching commands @@ -172,10 +176,6 @@ Meteor.startup(function() { const $dicomTags = $('.imageViewerViewportOverlay .dicomTag'); $dicomTags.toggle($dicomTags.eq(0).css('display') === 'none'); } - }, - cineDialog: { - name: 'Toggle CINE', - action: viewportUtils.toggleCineDialog() } }, true); @@ -203,45 +203,14 @@ function setOHIFHotkeys(hotkeys) { OHIF.viewer.hotkeys = hotkeys; } -/** - * Add an active class to a button for 100ms only - * to give the impressiont the button was pressed. - * This is for tools that don't keep the button "pressed" - * all the time the tool is active. - * - * @param button DOM Element for the button to be "flashed" - */ -function flashButton(button) { - if (!button) { - return; - } - - button.classList.add('active'); - setTimeout(() => { - button.classList.remove('active'); - }, 100); -} - /** * Binds all hotkeys keydown events to the tasks defined in * OHIF.viewer.hotkeys or a given param * @param {Object} hotkeys hotkey and task mapping (not required). If not given, uses OHIF.viewer.hotkeys */ function enableHotkeys(hotkeys) { - const viewerHotkeys = hotkeys || OHIF.viewer.hotkeys; - - const definitions = {}; - Object.keys(viewerHotkeys).forEach(definition => { - const hotkey = viewerHotkeys[definition]; - const action = OHIF.viewer.hotkeyFunctions[definition]; - definitions[definition] = { - hotkey, - action - }; - }); - - OHIF.hotkeys.setContext('viewer', definitions); - OHIF.hotkeys.switchToContext('viewer'); + const definitions = hotkeys || OHIF.viewer.hotkeys; + OHIF.hotkeys.set('viewer', definitions); OHIF.context.set('viewer'); }