ohif-viewer/platform/core/src/classes/CommandsManager.ts

227 lines
6.3 KiB
TypeScript

import log from '../log.js';
import { Command, Commands, ComplexCommand } from '../types/Command';
/**
* The definition of a command
*
* @typedef {Object} CommandDefinition
* @property {Function} commandFn - Command to call
* @property {Object} options - Object of params to pass action
*/
/**
* The Commands Manager tracks named commands (or functions) that are scoped to
* a context. When we attempt to run a command with a given name, we look for it
* in our active contexts. If found, we run the command, passing in any application
* or call specific data specified in the command's definition.
*
* NOTE: A more robust version of the CommandsManager lives in v1. If you're looking
* to extend this class, please check it's source before adding new methods.
*/
export class CommandsManager {
constructor({} = {}) {
this.contexts = {};
}
/**
* Allows us to create commands "per context". An example would be the "Cornerstone"
* context having a `SaveImage` command, and the "VTK" context having a `SaveImage`
* command. The distinction of a context allows us to call the command in either
* context, and have faith that the correct command will be run.
*
* @method
* @param {string} contextName - Namespace for commands
* @returns {undefined}
*/
createContext(contextName) {
if (!contextName) {
return;
}
if (this.contexts[contextName]) {
return this.clearContext(contextName);
}
this.contexts[contextName] = {};
}
/**
* Returns all command definitions for a given context
*
* @method
* @param {string} contextName - Namespace for commands
* @returns {Object} - the matched context
*/
getContext(contextName) {
const context = this.contexts[contextName];
if (!context) {
return;
}
return context;
}
/**
* Clears all registered commands for a given context.
*
* @param {string} contextName - Namespace for commands
* @returns {undefined}
*/
clearContext(contextName) {
if (!contextName) {
return;
}
this.contexts[contextName] = {};
}
/**
* Register a new command with the command manager. Scoped to a context, and
* with a definition to assist command callers w/ providing the necessary params
*
* @method
* @param {string} contextName - Namespace for command; often scoped to the extension that added it
* @param {string} commandName - Unique name identifying the command
* @param {CommandDefinition} definition - {@link CommandDefinition}
*/
registerCommand(contextName, commandName, definition) {
if (typeof definition !== 'object') {
return;
}
const context = this.getContext(contextName);
if (!context) {
return;
}
context[commandName] = definition;
}
/**
* Finds a command with the provided name if it exists in the specified context,
* or a currently active context.
*
* @method
* @param {String} commandName - Command to find
* @param {String} [contextName] - Specific command to look in. Defaults to current activeContexts
*/
getCommand = (commandName: string, contextName?: string) => {
const contexts = [];
if (contextName) {
const context = this.getContext(contextName);
if (context) {
contexts.push(context);
}
} else {
Object.keys(this.contexts).forEach(contextName => {
contexts.push(this.getContext(contextName));
});
}
if (contexts.length === 0) {
return;
}
let foundCommand;
contexts.forEach(context => {
if (context[commandName]) {
foundCommand = context[commandName];
}
});
return foundCommand;
};
/**
*
* @method
* @param {String} commandName
* @param {Object} [options={}] - Extra options to pass the command. Like a mousedown event
* @param {String} [contextName]
*/
public runCommand(commandName: string, options = {}, contextName?: string) {
const definition = this.getCommand(commandName, contextName);
if (!definition) {
log.warn(`Command "${commandName}" not found in current context`);
return;
}
const { commandFn } = definition;
const commandParams = Object.assign(
{},
definition.options, // "Command configuration"
options // "Time of call" info
);
if (typeof commandFn !== 'function') {
log.warn(`No commandFn was defined for command "${commandName}"`);
return;
} else {
return commandFn(commandParams);
}
}
/**
* Run one or more commands with specified extra options.
* Returns the result of the last command run.
*
* @param toRun - A specification of one or more commands,
* typically an object of { commandName, commandOptions, context }
* or an array of such objects. It can also be a single commandName as string
* if no options are needed.
* @param options - to include in the commands run beyond
* the commandOptions specified in the base.
*/
public run(
toRun: Command | Commands | Command[] | string | undefined,
options?: Record<string, unknown>
): unknown {
if (!toRun) {
return;
}
// Normalize `toRun` to an array of `ComplexCommand`
let commands: ComplexCommand[] = [];
if (typeof toRun === 'string') {
commands = [{ commandName: toRun }];
} else if ('commandName' in toRun) {
commands = [toRun as ComplexCommand];
} else if ('commands' in toRun) {
const commandsInput = (toRun as Commands).commands;
commands = Array.isArray(commandsInput)
? commandsInput.map(cmd => (typeof cmd === 'string' ? { commandName: cmd } : cmd))
: [{ commandName: commandsInput }];
} else if (Array.isArray(toRun)) {
commands = toRun.map(cmd => (typeof cmd === 'string' ? { commandName: cmd } : cmd));
}
if (commands.length === 0) {
console.log("Command isn't runnable", toRun);
return;
}
// Execute each command in the array
let result: unknown;
commands.forEach(({ commandName, commandOptions, context }) => {
if (commandName) {
result = this.runCommand(
commandName,
{
...commandOptions,
...options,
},
context
);
} else {
console.warn('No command name supplied in', toRun);
}
});
return result;
}
}
export default CommandsManager;