From a97fb2fc9aec10ca3e213fdbd9022289ac14e2d1 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Fri, 10 Feb 2023 14:30:56 -0500 Subject: [PATCH] fix(hotkeys): fix the hotkey manager to not save the defaults (#3162) --- platform/core/src/classes/Hotkey.ts | 8 ++ .../{HotkeysManager.js => HotkeysManager.ts} | 49 ++++++++--- .../src/extensions/ExtensionManager.test.js | 11 ++- ...xtensionManager.js => ExtensionManager.ts} | 81 ++++++++++++++++--- platform/core/src/extensions/index.js | 6 +- platform/core/src/types/AppConfig.ts | 7 ++ platform/core/src/types/Command.ts | 2 - platform/core/src/types/Consumer.ts | 3 +- platform/core/src/types/Services.ts | 1 + platform/core/src/types/StudyMetadata.ts | 5 +- platform/core/src/types/index.ts | 36 +++------ 11 files changed, 149 insertions(+), 60 deletions(-) create mode 100644 platform/core/src/classes/Hotkey.ts rename platform/core/src/classes/{HotkeysManager.js => HotkeysManager.ts} (85%) rename platform/core/src/extensions/{ExtensionManager.js => ExtensionManager.ts} (83%) create mode 100644 platform/core/src/types/AppConfig.ts diff --git a/platform/core/src/classes/Hotkey.ts b/platform/core/src/classes/Hotkey.ts new file mode 100644 index 000000000..73ca2d35f --- /dev/null +++ b/platform/core/src/classes/Hotkey.ts @@ -0,0 +1,8 @@ +export default interface Hotkey { + commandName: string; + commandOptions?: Record; + context?: string; + keys: string[]; + label: string; + isEditable?: boolean; +} diff --git a/platform/core/src/classes/HotkeysManager.js b/platform/core/src/classes/HotkeysManager.ts similarity index 85% rename from platform/core/src/classes/HotkeysManager.js rename to platform/core/src/classes/HotkeysManager.ts index 50a6dcdbc..03cafd55e 100644 --- a/platform/core/src/classes/HotkeysManager.js +++ b/platform/core/src/classes/HotkeysManager.ts @@ -1,6 +1,9 @@ import objectHash from 'object-hash'; -import log from './../log.js'; +import log from '../log.js'; import { hotkeys } from '../utils'; +import isequal from 'lodash.isequal'; +import Hotkey from './Hotkey'; +import ServicesManager from '../services/ServicesManager'; /** * @@ -13,6 +16,8 @@ import { hotkeys } from '../utils'; */ export class HotkeysManager { + private _servicesManager: ServicesManager; + constructor(commandsManager, servicesManager) { this.hotkeyDefinitions = {}; this.hotkeyDefaults = []; @@ -59,11 +64,17 @@ export class HotkeysManager { * * @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions */ - setHotkeys(hotkeyDefinitions = []) { + setHotkeys(hotkeyDefinitions = [], key = 'hotkey-definitions') { try { const definitions = this.getValidDefinitions(hotkeyDefinitions); + if (isequal(definitions, this.hotkeyDefaults)) { + console.log('hotkeys REMOVING unused definition', key); + localStorage.removeItem(key); + } else { + console.log('hotkeys setting local storage', key); + localStorage.setItem(key, JSON.stringify(definitions)); + } definitions.forEach(definition => this.registerHotkeys(definition)); - localStorage.setItem('hotkey-definitions', JSON.stringify(definitions)); } catch (error) { const { UINotificationService } = this._servicesManager.services; UINotificationService.show({ @@ -165,7 +176,14 @@ export class HotkeysManager { * @returns {undefined} */ registerHotkeys( - { commandName, commandOptions = {}, keys, label, isEditable } = {}, + { + commandName, + commandOptions = {}, + context, + keys, + label, + isEditable, + }: Hotkey = {}, extension ) { if (!commandName) { @@ -181,9 +199,9 @@ export class HotkeysManager { if (previouslyRegisteredDefinition) { const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys; this._unbindHotkeys(commandName, previouslyRegisteredKeys); - log.info( - `[hotkeys] Unbinding ${commandName} with ${options} options from ${previouslyRegisteredKeys}` - ); + // log.info( + // `[hotkeys] Unbinding ${commandName} with ${options} options from ${previouslyRegisteredKeys}` + // ); } // Set definition & bind @@ -194,10 +212,11 @@ export class HotkeysManager { label, isEditable, }; - this._bindHotkeys(commandName, commandOptions, keys); - log.info( - `[hotkeys] Binding ${commandName} with ${options} options to ${keys}` - ); + this._bindHotkeys(commandName, commandOptions, context, keys); + // log.info( + // `[hotkeys] Binding ${commandName} with ${options} from ${context || + // 'default'} options to ${keys}` + // ); } /** @@ -226,7 +245,7 @@ export class HotkeysManager { * @param {string[]} keys - One or more key combinations that should trigger command * @returns {undefined} */ - _bindHotkeys(commandName, commandOptions = {}, keys) { + _bindHotkeys(commandName, commandOptions = {}, context, keys) { const isKeyDefined = keys === '' || keys === undefined; if (isKeyDefined) { return; @@ -238,7 +257,11 @@ export class HotkeysManager { hotkeys.bind(combinedKeys, evt => { evt.preventDefault(); evt.stopPropagation(); - this._commandsManager.runCommand(commandName, { evt, ...commandOptions }); + this._commandsManager.runCommand( + commandName, + { evt, ...commandOptions }, + context + ); }); } diff --git a/platform/core/src/extensions/ExtensionManager.test.js b/platform/core/src/extensions/ExtensionManager.test.js index 47c737518..4177deab7 100644 --- a/platform/core/src/extensions/ExtensionManager.test.js +++ b/platform/core/src/extensions/ExtensionManager.test.js @@ -1,10 +1,10 @@ -import ExtensionManager from './ExtensionManager.js'; -import MODULE_TYPES from './MODULE_TYPES.js'; +import ExtensionManager from './ExtensionManager'; +import MODULE_TYPES from './MODULE_TYPES'; import log from './../log.js'; jest.mock('./../log.js'); -describe('ExtensionManager.js', () => { +describe('ExtensionManager.ts', () => { let extensionManager, commandsManager, servicesManager, appConfig; beforeEach(() => { @@ -238,6 +238,9 @@ describe('ExtensionManager.js', () => { getCustomizationModule: () => { return [{}]; }, + getStateSyncModule: () => { + return [{}]; + }, }; await extensionManager.registerExtension(extension); @@ -245,7 +248,7 @@ describe('ExtensionManager.js', () => { // Registers 1 module per module type Object.keys(extensionManager.modules).forEach(moduleType => { const modulesForType = extensionManager.modules[moduleType]; - + console.log('moduleType', moduleType); expect(modulesForType.length).toBe(1); }); }); diff --git a/platform/core/src/extensions/ExtensionManager.js b/platform/core/src/extensions/ExtensionManager.ts similarity index 83% rename from platform/core/src/extensions/ExtensionManager.js rename to platform/core/src/extensions/ExtensionManager.ts index b8ea618e6..81a4a54cd 100644 --- a/platform/core/src/extensions/ExtensionManager.js +++ b/platform/core/src/extensions/ExtensionManager.ts @@ -1,13 +1,59 @@ -import MODULE_TYPES from './MODULE_TYPES.js'; -import log from './../log.js'; +import MODULE_TYPES from './MODULE_TYPES'; +import log from '../log'; +import { AppConfig } from '../types/AppConfig'; +import { ServicesManager } from '../services'; +import { HotkeysManager, CommandsManager } from '../classes'; + +/** + * This is the arguments given to create the extension. + */ +export interface ExtensionConstructor { + servicesManager: ServicesManager; + commandsManager: CommandsManager; + hotkeysManager: HotkeysManager; + appConfig: AppConfig; +} + +/** + * The configuration of an extension. + * This uses type as the extension manager only knows that the configuration + * is an object of some sort, and doesn't know anything else about it. + */ +export type ExtensionConfiguration = Record; + +/** + * The parameters passed to the extension. + */ +export interface ExtensionParams extends ExtensionConstructor { + extensionManager: ExtensionManager; + configuration?: ExtensionConfiguration; +} + +/** + * The type of an actual extension instance. + * This is an interface as it declares possible calls, but extensions can + * have more values than this. + */ +export interface Extension { + preRegistration?: (p: ExtensionParams) => void; +} + +export type ExtensionRegister = { + id: string; + create: (p: ExtensionParams) => Extension; +}; export default class ExtensionManager { + private _commandsManager: CommandsManager; + private _servicesManager: ServicesManager; + private _hotkeysManager: HotkeysManager; + constructor({ commandsManager, servicesManager, hotkeysManager, appConfig = {}, - }) { + }: ExtensionConstructor) { this.modules = {}; this.registeredExtensionIds = []; this.moduleTypeNames = Object.values(MODULE_TYPES); @@ -28,11 +74,17 @@ export default class ExtensionManager { this.activeDataSource = undefined; } - setActiveDataSource(dataSourceName) { + public setActiveDataSource(dataSourceName: string): void { this.activeDataSource = dataSourceName; } - onModeEnter() { + /** + * Calls all the services and extension on mode enters. + * The service onModeEnter is called first + * Then registered extensions onModeEnter is called + * This is supposed to setup the extension for a standard entry. + */ + public onModeEnter(): void { const { registeredExtensionIds, _servicesManager, @@ -61,7 +113,7 @@ export default class ExtensionManager { }); } - onModeExit() { + public onModeExit(): void { const { registeredExtensionIds, _servicesManager, @@ -97,7 +149,13 @@ export default class ExtensionManager { * * @param {Object[]} extensions - Array of extensions */ - registerExtensions = async (extensions, dataSources = []) => { + public registerExtensions = async ( + extensions: ( + | ExtensionRegister + | [ExtensionRegister, ExtensionConfiguration] + )[], + dataSources: unknown[] = [] + ): Promise => { // Todo: we ideally should be able to run registrations in parallel // but currently since some extensions need to be registered before // others, we need to run them sequentially. We need a postInit hook @@ -127,16 +185,16 @@ export default class ExtensionManager { * @param {Object} extension * @param {Object} configuration */ - registerExtension = async ( - extension, + public registerExtension = async ( + extension: ExtensionRegister, configuration = {}, dataSources = [] - ) => { + ): Promise => { if (!extension) { throw new Error('Attempting to register a null/undefined extension.'); } - let extensionId = extension.id; + const extensionId = extension.id; if (!extensionId) { // Note: Mode framework cannot function without IDs. @@ -203,6 +261,7 @@ export default class ExtensionManager { case MODULE_TYPES.CONTEXT: case MODULE_TYPES.LAYOUT_TEMPLATE: case MODULE_TYPES.CUSTOMIZATION: + case MODULE_TYPES.STATE_SYNC: case MODULE_TYPES.UTILITY: // Default for most extension points, // Just adds each entry ready for consumption by mode. diff --git a/platform/core/src/extensions/index.js b/platform/core/src/extensions/index.js index 50a27db11..e5919875e 100644 --- a/platform/core/src/extensions/index.js +++ b/platform/core/src/extensions/index.js @@ -1,9 +1,11 @@ import ExtensionManager from './ExtensionManager'; -import MODULE_TYPES from './MODULE_TYPES.js'; +import MODULE_TYPES from './MODULE_TYPES'; -export default { +const DEFAULT_EXPORTS = { ExtensionManager, MODULE_TYPES, }; +export default DEFAULT_EXPORTS; + export { ExtensionManager, MODULE_TYPES }; diff --git a/platform/core/src/types/AppConfig.ts b/platform/core/src/types/AppConfig.ts new file mode 100644 index 000000000..3c75694cb --- /dev/null +++ b/platform/core/src/types/AppConfig.ts @@ -0,0 +1,7 @@ +import Hotkey from '../classes/Hotkey'; + +export interface AppConfig { + extensions?: string[]; + defaultDataSourceName?: string; + hotkeys?: Record | Hotkey[]; +} diff --git a/platform/core/src/types/Command.ts b/platform/core/src/types/Command.ts index 42d75f63b..83c774379 100644 --- a/platform/core/src/types/Command.ts +++ b/platform/core/src/types/Command.ts @@ -3,5 +3,3 @@ export interface Command { commandOptions?: Record; context?: string; } - -export default Command; diff --git a/platform/core/src/types/Consumer.ts b/platform/core/src/types/Consumer.ts index a761630d4..8c59ebc34 100644 --- a/platform/core/src/types/Consumer.ts +++ b/platform/core/src/types/Consumer.ts @@ -1,5 +1,4 @@ /** * Just a function that consumes a single argument, with no return. */ -type Consumer = (props: Record) => void; -export default Consumer; +export type Consumer = (props: Record) => void; diff --git a/platform/core/src/types/Services.ts b/platform/core/src/types/Services.ts index e710bfe9f..d906d17b5 100644 --- a/platform/core/src/types/Services.ts +++ b/platform/core/src/types/Services.ts @@ -24,4 +24,5 @@ export default interface Services { viewportGridService?: ViewportGridService; syncGroupService?: Record; cornerstoneCacheService?: Record; + segmentationService?: Record; } diff --git a/platform/core/src/types/StudyMetadata.ts b/platform/core/src/types/StudyMetadata.ts index 5a0cf8155..e06299e3c 100644 --- a/platform/core/src/types/StudyMetadata.ts +++ b/platform/core/src/types/StudyMetadata.ts @@ -1,4 +1,6 @@ -/** Defines a typescript type for study metadata */ +/** Defines a typescript interface for study metadata. + * This defines the types for when using study metadata as interfaces. + */ export interface PatientMetadata extends Record { PatientName?: string; @@ -20,4 +22,3 @@ export interface InstanceMetadata extends SeriesMetadata { readonly SOPInstanceUID: string; InstanceNumber?: string | number; } -export default StudyMetadata; diff --git a/platform/core/src/types/index.ts b/platform/core/src/types/index.ts index 0d1c9a1cb..f3229b98d 100644 --- a/platform/core/src/types/index.ts +++ b/platform/core/src/types/index.ts @@ -1,29 +1,17 @@ -import { - StudyMetadata, - SeriesMetadata, - InstanceMetadata, -} from './StudyMetadata'; - -import Consumer from './Consumer'; -import { ExtensionManager } from '../extensions'; -import { CustomizationService, PubSubService } from '../services'; +import * as Extensions from '../extensions/ExtensionManager'; import * as HangingProtocol from './HangingProtocol'; -import Command from './Command'; import Services from './Services'; -import { CommandsManager } from '../classes'; +import Hotkey from '../classes/Hotkey'; export * from '../services/CustomizationService/types'; +// Separate out some generic types +export * from './AppConfig'; +export * from './Consumer'; +export * from './Command'; +export * from './StudyMetadata'; -export type { - ExtensionManager, - HangingProtocol, - StudyMetadata, - SeriesMetadata, - InstanceMetadata, - Consumer, - PubSubService, - CustomizationService, - Command, - Services, - CommandsManager, -}; +/** + * Export the types used within the various services and managers, but + * not the services/managers themselves, which are exported at the top level. + */ +export { Extensions, HangingProtocol, Services, Hotkey };