fix(hotkeys): fix the hotkey manager to not save the defaults (#3162)

This commit is contained in:
Bill Wallace 2023-02-10 14:30:56 -05:00 committed by GitHub
parent 4558540357
commit a97fb2fc9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 149 additions and 60 deletions

View File

@ -0,0 +1,8 @@
export default interface Hotkey {
commandName: string;
commandOptions?: Record<string, unknown>;
context?: string;
keys: string[];
label: string;
isEditable?: boolean;
}

View File

@ -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
);
});
}

View File

@ -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);
});
});

View File

@ -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<string, unknown>;
/**
* 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<void> => {
// 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<void> => {
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.

View File

@ -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 };

View File

@ -0,0 +1,7 @@
import Hotkey from '../classes/Hotkey';
export interface AppConfig {
extensions?: string[];
defaultDataSourceName?: string;
hotkeys?: Record<string, Hotkey> | Hotkey[];
}

View File

@ -3,5 +3,3 @@ export interface Command {
commandOptions?: Record<string, unknown>;
context?: string;
}
export default Command;

View File

@ -1,5 +1,4 @@
/**
* Just a function that consumes a single argument, with no return.
*/
type Consumer = (props: Record<string, unknown>) => void;
export default Consumer;
export type Consumer = (props: Record<string, unknown>) => void;

View File

@ -24,4 +24,5 @@ export default interface Services {
viewportGridService?: ViewportGridService;
syncGroupService?: Record<string, unknown>;
cornerstoneCacheService?: Record<string, unknown>;
segmentationService?: Record<string, unknown>;
}

View File

@ -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<string, unknown> {
PatientName?: string;
@ -20,4 +22,3 @@ export interface InstanceMetadata extends SeriesMetadata {
readonly SOPInstanceUID: string;
InstanceNumber?: string | number;
}
export default StudyMetadata;

View File

@ -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 };