fix(hotkeys): fix the hotkey manager to not save the defaults (#3162)
This commit is contained in:
parent
4558540357
commit
a97fb2fc9a
8
platform/core/src/classes/Hotkey.ts
Normal file
8
platform/core/src/classes/Hotkey.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default interface Hotkey {
|
||||||
|
commandName: string;
|
||||||
|
commandOptions?: Record<string, unknown>;
|
||||||
|
context?: string;
|
||||||
|
keys: string[];
|
||||||
|
label: string;
|
||||||
|
isEditable?: boolean;
|
||||||
|
}
|
||||||
@ -1,6 +1,9 @@
|
|||||||
import objectHash from 'object-hash';
|
import objectHash from 'object-hash';
|
||||||
import log from './../log.js';
|
import log from '../log.js';
|
||||||
import { hotkeys } from '../utils';
|
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 {
|
export class HotkeysManager {
|
||||||
|
private _servicesManager: ServicesManager;
|
||||||
|
|
||||||
constructor(commandsManager, servicesManager) {
|
constructor(commandsManager, servicesManager) {
|
||||||
this.hotkeyDefinitions = {};
|
this.hotkeyDefinitions = {};
|
||||||
this.hotkeyDefaults = [];
|
this.hotkeyDefaults = [];
|
||||||
@ -59,11 +64,17 @@ export class HotkeysManager {
|
|||||||
*
|
*
|
||||||
* @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions
|
* @param {HotkeyDefinition[] | Object} [hotkeyDefinitions=[]] Contains hotkeys definitions
|
||||||
*/
|
*/
|
||||||
setHotkeys(hotkeyDefinitions = []) {
|
setHotkeys(hotkeyDefinitions = [], key = 'hotkey-definitions') {
|
||||||
try {
|
try {
|
||||||
const definitions = this.getValidDefinitions(hotkeyDefinitions);
|
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));
|
definitions.forEach(definition => this.registerHotkeys(definition));
|
||||||
localStorage.setItem('hotkey-definitions', JSON.stringify(definitions));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { UINotificationService } = this._servicesManager.services;
|
const { UINotificationService } = this._servicesManager.services;
|
||||||
UINotificationService.show({
|
UINotificationService.show({
|
||||||
@ -165,7 +176,14 @@ export class HotkeysManager {
|
|||||||
* @returns {undefined}
|
* @returns {undefined}
|
||||||
*/
|
*/
|
||||||
registerHotkeys(
|
registerHotkeys(
|
||||||
{ commandName, commandOptions = {}, keys, label, isEditable } = {},
|
{
|
||||||
|
commandName,
|
||||||
|
commandOptions = {},
|
||||||
|
context,
|
||||||
|
keys,
|
||||||
|
label,
|
||||||
|
isEditable,
|
||||||
|
}: Hotkey = {},
|
||||||
extension
|
extension
|
||||||
) {
|
) {
|
||||||
if (!commandName) {
|
if (!commandName) {
|
||||||
@ -181,9 +199,9 @@ export class HotkeysManager {
|
|||||||
if (previouslyRegisteredDefinition) {
|
if (previouslyRegisteredDefinition) {
|
||||||
const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys;
|
const previouslyRegisteredKeys = previouslyRegisteredDefinition.keys;
|
||||||
this._unbindHotkeys(commandName, previouslyRegisteredKeys);
|
this._unbindHotkeys(commandName, previouslyRegisteredKeys);
|
||||||
log.info(
|
// log.info(
|
||||||
`[hotkeys] Unbinding ${commandName} with ${options} options from ${previouslyRegisteredKeys}`
|
// `[hotkeys] Unbinding ${commandName} with ${options} options from ${previouslyRegisteredKeys}`
|
||||||
);
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set definition & bind
|
// Set definition & bind
|
||||||
@ -194,10 +212,11 @@ export class HotkeysManager {
|
|||||||
label,
|
label,
|
||||||
isEditable,
|
isEditable,
|
||||||
};
|
};
|
||||||
this._bindHotkeys(commandName, commandOptions, keys);
|
this._bindHotkeys(commandName, commandOptions, context, keys);
|
||||||
log.info(
|
// log.info(
|
||||||
`[hotkeys] Binding ${commandName} with ${options} options to ${keys}`
|
// `[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
|
* @param {string[]} keys - One or more key combinations that should trigger command
|
||||||
* @returns {undefined}
|
* @returns {undefined}
|
||||||
*/
|
*/
|
||||||
_bindHotkeys(commandName, commandOptions = {}, keys) {
|
_bindHotkeys(commandName, commandOptions = {}, context, keys) {
|
||||||
const isKeyDefined = keys === '' || keys === undefined;
|
const isKeyDefined = keys === '' || keys === undefined;
|
||||||
if (isKeyDefined) {
|
if (isKeyDefined) {
|
||||||
return;
|
return;
|
||||||
@ -238,7 +257,11 @@ export class HotkeysManager {
|
|||||||
hotkeys.bind(combinedKeys, evt => {
|
hotkeys.bind(combinedKeys, evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
this._commandsManager.runCommand(commandName, { evt, ...commandOptions });
|
this._commandsManager.runCommand(
|
||||||
|
commandName,
|
||||||
|
{ evt, ...commandOptions },
|
||||||
|
context
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import ExtensionManager from './ExtensionManager.js';
|
import ExtensionManager from './ExtensionManager';
|
||||||
import MODULE_TYPES from './MODULE_TYPES.js';
|
import MODULE_TYPES from './MODULE_TYPES';
|
||||||
import log from './../log.js';
|
import log from './../log.js';
|
||||||
|
|
||||||
jest.mock('./../log.js');
|
jest.mock('./../log.js');
|
||||||
|
|
||||||
describe('ExtensionManager.js', () => {
|
describe('ExtensionManager.ts', () => {
|
||||||
let extensionManager, commandsManager, servicesManager, appConfig;
|
let extensionManager, commandsManager, servicesManager, appConfig;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -238,6 +238,9 @@ describe('ExtensionManager.js', () => {
|
|||||||
getCustomizationModule: () => {
|
getCustomizationModule: () => {
|
||||||
return [{}];
|
return [{}];
|
||||||
},
|
},
|
||||||
|
getStateSyncModule: () => {
|
||||||
|
return [{}];
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await extensionManager.registerExtension(extension);
|
await extensionManager.registerExtension(extension);
|
||||||
@ -245,7 +248,7 @@ describe('ExtensionManager.js', () => {
|
|||||||
// Registers 1 module per module type
|
// Registers 1 module per module type
|
||||||
Object.keys(extensionManager.modules).forEach(moduleType => {
|
Object.keys(extensionManager.modules).forEach(moduleType => {
|
||||||
const modulesForType = extensionManager.modules[moduleType];
|
const modulesForType = extensionManager.modules[moduleType];
|
||||||
|
console.log('moduleType', moduleType);
|
||||||
expect(modulesForType.length).toBe(1);
|
expect(modulesForType.length).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,13 +1,59 @@
|
|||||||
import MODULE_TYPES from './MODULE_TYPES.js';
|
import MODULE_TYPES from './MODULE_TYPES';
|
||||||
import log from './../log.js';
|
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 {
|
export default class ExtensionManager {
|
||||||
|
private _commandsManager: CommandsManager;
|
||||||
|
private _servicesManager: ServicesManager;
|
||||||
|
private _hotkeysManager: HotkeysManager;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
commandsManager,
|
commandsManager,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
hotkeysManager,
|
hotkeysManager,
|
||||||
appConfig = {},
|
appConfig = {},
|
||||||
}) {
|
}: ExtensionConstructor) {
|
||||||
this.modules = {};
|
this.modules = {};
|
||||||
this.registeredExtensionIds = [];
|
this.registeredExtensionIds = [];
|
||||||
this.moduleTypeNames = Object.values(MODULE_TYPES);
|
this.moduleTypeNames = Object.values(MODULE_TYPES);
|
||||||
@ -28,11 +74,17 @@ export default class ExtensionManager {
|
|||||||
this.activeDataSource = undefined;
|
this.activeDataSource = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveDataSource(dataSourceName) {
|
public setActiveDataSource(dataSourceName: string): void {
|
||||||
this.activeDataSource = dataSourceName;
|
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 {
|
const {
|
||||||
registeredExtensionIds,
|
registeredExtensionIds,
|
||||||
_servicesManager,
|
_servicesManager,
|
||||||
@ -61,7 +113,7 @@ export default class ExtensionManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onModeExit() {
|
public onModeExit(): void {
|
||||||
const {
|
const {
|
||||||
registeredExtensionIds,
|
registeredExtensionIds,
|
||||||
_servicesManager,
|
_servicesManager,
|
||||||
@ -97,7 +149,13 @@ export default class ExtensionManager {
|
|||||||
*
|
*
|
||||||
* @param {Object[]} extensions - Array of extensions
|
* @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
|
// Todo: we ideally should be able to run registrations in parallel
|
||||||
// but currently since some extensions need to be registered before
|
// but currently since some extensions need to be registered before
|
||||||
// others, we need to run them sequentially. We need a postInit hook
|
// 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} extension
|
||||||
* @param {Object} configuration
|
* @param {Object} configuration
|
||||||
*/
|
*/
|
||||||
registerExtension = async (
|
public registerExtension = async (
|
||||||
extension,
|
extension: ExtensionRegister,
|
||||||
configuration = {},
|
configuration = {},
|
||||||
dataSources = []
|
dataSources = []
|
||||||
) => {
|
): Promise<void> => {
|
||||||
if (!extension) {
|
if (!extension) {
|
||||||
throw new Error('Attempting to register a null/undefined extension.');
|
throw new Error('Attempting to register a null/undefined extension.');
|
||||||
}
|
}
|
||||||
|
|
||||||
let extensionId = extension.id;
|
const extensionId = extension.id;
|
||||||
|
|
||||||
if (!extensionId) {
|
if (!extensionId) {
|
||||||
// Note: Mode framework cannot function without IDs.
|
// Note: Mode framework cannot function without IDs.
|
||||||
@ -203,6 +261,7 @@ export default class ExtensionManager {
|
|||||||
case MODULE_TYPES.CONTEXT:
|
case MODULE_TYPES.CONTEXT:
|
||||||
case MODULE_TYPES.LAYOUT_TEMPLATE:
|
case MODULE_TYPES.LAYOUT_TEMPLATE:
|
||||||
case MODULE_TYPES.CUSTOMIZATION:
|
case MODULE_TYPES.CUSTOMIZATION:
|
||||||
|
case MODULE_TYPES.STATE_SYNC:
|
||||||
case MODULE_TYPES.UTILITY:
|
case MODULE_TYPES.UTILITY:
|
||||||
// Default for most extension points,
|
// Default for most extension points,
|
||||||
// Just adds each entry ready for consumption by mode.
|
// Just adds each entry ready for consumption by mode.
|
||||||
@ -1,9 +1,11 @@
|
|||||||
import ExtensionManager from './ExtensionManager';
|
import ExtensionManager from './ExtensionManager';
|
||||||
import MODULE_TYPES from './MODULE_TYPES.js';
|
import MODULE_TYPES from './MODULE_TYPES';
|
||||||
|
|
||||||
export default {
|
const DEFAULT_EXPORTS = {
|
||||||
ExtensionManager,
|
ExtensionManager,
|
||||||
MODULE_TYPES,
|
MODULE_TYPES,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default DEFAULT_EXPORTS;
|
||||||
|
|
||||||
export { ExtensionManager, MODULE_TYPES };
|
export { ExtensionManager, MODULE_TYPES };
|
||||||
|
|||||||
7
platform/core/src/types/AppConfig.ts
Normal file
7
platform/core/src/types/AppConfig.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Hotkey from '../classes/Hotkey';
|
||||||
|
|
||||||
|
export interface AppConfig {
|
||||||
|
extensions?: string[];
|
||||||
|
defaultDataSourceName?: string;
|
||||||
|
hotkeys?: Record<string, Hotkey> | Hotkey[];
|
||||||
|
}
|
||||||
@ -3,5 +3,3 @@ export interface Command {
|
|||||||
commandOptions?: Record<string, unknown>;
|
commandOptions?: Record<string, unknown>;
|
||||||
context?: string;
|
context?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Command;
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Just a function that consumes a single argument, with no return.
|
* Just a function that consumes a single argument, with no return.
|
||||||
*/
|
*/
|
||||||
type Consumer = (props: Record<string, unknown>) => void;
|
export type Consumer = (props: Record<string, unknown>) => void;
|
||||||
export default Consumer;
|
|
||||||
|
|||||||
@ -24,4 +24,5 @@ export default interface Services {
|
|||||||
viewportGridService?: ViewportGridService;
|
viewportGridService?: ViewportGridService;
|
||||||
syncGroupService?: Record<string, unknown>;
|
syncGroupService?: Record<string, unknown>;
|
||||||
cornerstoneCacheService?: Record<string, unknown>;
|
cornerstoneCacheService?: Record<string, unknown>;
|
||||||
|
segmentationService?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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> {
|
export interface PatientMetadata extends Record<string, unknown> {
|
||||||
PatientName?: string;
|
PatientName?: string;
|
||||||
@ -20,4 +22,3 @@ export interface InstanceMetadata extends SeriesMetadata {
|
|||||||
readonly SOPInstanceUID: string;
|
readonly SOPInstanceUID: string;
|
||||||
InstanceNumber?: string | number;
|
InstanceNumber?: string | number;
|
||||||
}
|
}
|
||||||
export default StudyMetadata;
|
|
||||||
|
|||||||
@ -1,29 +1,17 @@
|
|||||||
import {
|
import * as Extensions from '../extensions/ExtensionManager';
|
||||||
StudyMetadata,
|
|
||||||
SeriesMetadata,
|
|
||||||
InstanceMetadata,
|
|
||||||
} from './StudyMetadata';
|
|
||||||
|
|
||||||
import Consumer from './Consumer';
|
|
||||||
import { ExtensionManager } from '../extensions';
|
|
||||||
import { CustomizationService, PubSubService } from '../services';
|
|
||||||
import * as HangingProtocol from './HangingProtocol';
|
import * as HangingProtocol from './HangingProtocol';
|
||||||
import Command from './Command';
|
|
||||||
import Services from './Services';
|
import Services from './Services';
|
||||||
import { CommandsManager } from '../classes';
|
import Hotkey from '../classes/Hotkey';
|
||||||
|
|
||||||
export * from '../services/CustomizationService/types';
|
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,
|
* Export the types used within the various services and managers, but
|
||||||
HangingProtocol,
|
* not the services/managers themselves, which are exported at the top level.
|
||||||
StudyMetadata,
|
*/
|
||||||
SeriesMetadata,
|
export { Extensions, HangingProtocol, Services, Hotkey };
|
||||||
InstanceMetadata,
|
|
||||||
Consumer,
|
|
||||||
PubSubService,
|
|
||||||
CustomizationService,
|
|
||||||
Command,
|
|
||||||
Services,
|
|
||||||
CommandsManager,
|
|
||||||
};
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user