UNPKG

@difizen/mana-core

Version:

211 lines (175 loc) 5.41 kB
/* 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. */ @transient() export class DefaultActionMenuItem implements MenuNode, Disposable { readonly id: string; readonly key: string; disposed?: boolean | undefined; @prop() 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( @inject(CommandRegistry) commands: CommandRegistry, @inject(MenuSymbol.ActionMenuNodeSymbol) node: ActionMenuNode, @inject(MenuSymbol.ParentPathSymbol) 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. */ @transient() export class DefaultGeneralMenuItem implements MenuNode, Disposable { readonly id: string; readonly key: string; readonly path: MenuPath; get isSubmenu(): boolean { return !!this.label; } @prop() readonly children: (MenuItem | DefaultActionMenuItem)[] = []; disposed?: boolean | undefined; @prop() order?: string | undefined; /** * Optional label for the item. */ @prop() label?: React.ReactNode | React.FC; /** * Optional icon for the item. */ @prop() 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( @inject(CommandRegistry) commands: CommandRegistry, @inject(MenuSymbol.MenuNodeSymbol) node: MenuNode, @inject(MenuSymbol.ParentPathSymbol) 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); }; }