@difizen/mana-core
Version:
211 lines (175 loc) • 5.41 kB
text/typescript
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { Disposable, Event } from '@difizen/mana-common';
import { Emitter } from '@difizen/mana-common';
import { getOrigin, prop } from '@difizen/mana-observable';
import { inject, transient } from '@difizen/mana-syringe';
import { CommandRegistry } from '../command/command-registry';
import { renderNode } from '../view/utils';
import { CommandMenuNode, ExecutableMenuNode, MenuSymbol } from './menu-protocol';
import type { MenuItem, MenuPath, ActionMenuNode, MenuNode } from './menu-protocol';
/**
* Node representing a (sub)menu in the menu tree structure.
*/
export class DefaultActionMenuItem implements MenuNode, Disposable {
readonly id: string;
readonly key: string;
disposed?: boolean | undefined;
readonly order?: string | undefined;
/**
* Optional label for the item.
*/
label?: React.ReactNode | React.FC;
/**
* Optional icon for the item.
*/
readonly icon?: React.ReactNode | React.FC;
readonly command?: string;
execute?: (...args: any[]) => any;
isEnabled?: ((...args: any[]) => boolean) | undefined;
isVisible?: ((...args: any[]) => boolean) | undefined;
isActive?: ((...args: any[]) => boolean) | undefined;
protected disposedEventEmitter: Emitter<void> = new Emitter();
protected readonly commands: CommandRegistry;
protected readonly node: ActionMenuNode;
protected readonly parentPath: MenuPath;
onDisposed: Event<void> = this.disposedEventEmitter.event;
constructor(
commands: CommandRegistry,
node: ActionMenuNode,
parentPath: MenuPath,
) {
this.commands = commands;
this.node = node;
this.parentPath = parentPath;
this.order = node.order;
this.label = node.label;
this.icon = node.icon;
this.id = node.id;
this.key = parentPath.join('/') + '/' + this.id;
if (CommandMenuNode.is(node)) {
this.command = node.command;
this.execute = this.doCommandExecute;
}
if (ExecutableMenuNode.is(node)) {
this.execute = node.execute;
this.isEnabled = node.isEnabled;
this.isVisible = node.isVisible;
this.isActive = node.isActive;
}
}
protected doCommandExecute = (...args: any[]): any => {
if (this.command) {
return this.commands.executeCommand(this.command, ...args);
}
};
dispose() {
this.disposedEventEmitter.fire();
this.disposed = true;
}
renderTitle = () => {
let label = this.label;
if (!label && this.command) {
label = this.commands.getCommand(this.command)?.label;
}
return renderNode(label);
};
renderIcon = () => {
let icon = this.icon;
if (!icon && this.command) {
icon = this.commands.getCommand(this.command)?.icon;
}
return renderNode(icon);
};
}
/**
* Node representing a (sub)menu in the menu tree structure.
*/
export class DefaultGeneralMenuItem implements MenuNode, Disposable {
readonly id: string;
readonly key: string;
readonly path: MenuPath;
get isSubmenu(): boolean {
return !!this.label;
}
readonly children: (MenuItem | DefaultActionMenuItem)[] = [];
disposed?: boolean | undefined;
order?: string | undefined;
/**
* Optional label for the item.
*/
label?: React.ReactNode | React.FC;
/**
* Optional icon for the item.
*/
icon?: React.ReactNode | React.FC;
execute?: (...args: any[]) => any;
isEnabled?: (...args: any[]) => boolean;
isVisible?: (...args: any[]) => boolean;
protected disposedEventEmitter: Emitter<void> = new Emitter();
protected readonly commands: CommandRegistry;
protected readonly node: MenuNode;
protected readonly parentPath: MenuPath;
onDisposed: Event<void> = this.disposedEventEmitter.event;
constructor(
commands: CommandRegistry,
node: MenuNode,
parentPath: MenuPath,
) {
this.commands = commands;
this.node = node;
this.parentPath = parentPath;
this.order = node.order;
this.label = node.label;
this.icon = node.icon;
this.id = node.id;
this.key = parentPath.join('/') + '/' + this.id;
this.path = [...parentPath, this.id];
}
/**
* Inserts the given node at the position indicated by `sortString`.
*
* @returns a disposable which, when called, will remove the given node again.
*/
public addNode = (item: MenuItem): MenuItem => {
this.children.push(item);
const remove = () => {
const idx = this.children.indexOf(getOrigin(item));
if (idx >= 0) {
this.children.splice(idx, 1);
}
};
item.onDisposed(remove);
return item;
};
/**
* Removes the first node with the given id.
*
* @param id node id.
*/
public removeNode(id: string): void {
const node = this.children.find((n) => n.id === id);
if (node) {
const idx = this.children.indexOf(node);
if (idx >= 0) {
this.children.splice(idx, 1);
}
}
}
dispose() {
this.disposedEventEmitter.fire();
this.disposed = true;
}
renderTitle = () => {
return renderNode(this.label);
};
renderIcon = () => {
return renderNode(this.icon);
};
}