From a5bd54b86d6effe690bdc37cb3958c108cf13034 Mon Sep 17 00:00:00 2001 From: "Daniel J. Geiger" <1852529+DanielJGeiger@users.noreply.github.com> Date: Sun, 8 Jan 2023 20:00:46 -0600 Subject: [PATCH] Filter context menu items through `isActionEnabled`. --- src/actions/manager.tsx | 45 ++++++++++++++++++++++------------- src/components/Actions.tsx | 10 ++++---- src/components/App.tsx | 29 ++++++++++++++-------- src/components/LayerUI.tsx | 1 - src/components/MobileMenu.tsx | 1 - 5 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/actions/manager.tsx b/src/actions/manager.tsx index 1f4d3cdaf..662528f8f 100644 --- a/src/actions/manager.tsx +++ b/src/actions/manager.tsx @@ -75,7 +75,7 @@ export class ActionManager { this.app = app; } - public registerActionGuards() { + registerActionGuards() { const disablers = getActionDisablers(); for (const d in disablers) { const dName = d as ActionName; @@ -90,7 +90,7 @@ export class ActionManager { } } - public registerDisableFn(name: ActionName, disabler: DisableFn) { + registerDisableFn(name: ActionName, disabler: DisableFn) { if (!(name in this.disablers)) { this.disablers[name] = [] as DisableFn[]; } @@ -99,7 +99,7 @@ export class ActionManager { } } - public registerEnableFn(name: Action["name"], enabler: EnableFn) { + registerEnableFn(name: Action["name"], enabler: EnableFn) { if (!(name in this.enablers)) { this.enablers[name] = [] as EnableFn[]; } @@ -108,18 +108,25 @@ export class ActionManager { } } - public getCustomActions(opts?: { + getCustomActions(opts?: { elements?: readonly ExcalidrawElement[]; data?: Record; + guardsOnly?: boolean; }): Action[] { // For testing if (this === undefined) { return []; } + const filter = + opts !== undefined && + ("elements" in opts || "data" in opts || "guardsOnly" in opts); const customActions: Action[] = []; for (const key in this.actions) { const action = this.actions[key]; - if (!isActionName(action.name) && this.isActionEnabled(action, opts)) { + if ( + !isActionName(action.name) && + (!filter || this.isActionEnabled(action, opts)) + ) { customActions.push(action); } } @@ -142,7 +149,7 @@ export class ActionManager { (action) => (action.name in canvasActions ? canvasActions[action.name as keyof typeof canvasActions] - : this.isActionEnabled(action)) && + : this.isActionEnabled(action, { guardsOnly: true })) && action.keyTest && action.keyTest( event, @@ -200,7 +207,7 @@ export class ActionManager { "PanelComponent" in this.actions[name] && (name in canvasActions ? canvasActions[name as keyof typeof canvasActions] - : this.isActionEnabled(this.actions[name])) + : this.isActionEnabled(this.actions[name], { guardsOnly: true })) ) { const action = this.actions[name]; const PanelComponent = action.PanelComponent!; @@ -236,35 +243,39 @@ export class ActionManager { }; isActionEnabled = ( - action: Action, + action: Action | ActionName, opts?: { elements?: readonly ExcalidrawElement[]; data?: Record; + guardsOnly?: boolean; }, ): boolean => { const elements = opts?.elements ?? this.getElementsIncludingDeleted(); const appState = this.getAppState(); const data = opts?.data; + const _action = isActionName(action) ? this.actions[action] : action; + if ( - action.predicate && - !action.predicate(elements, appState, this.app.props, this.app, data) + !opts?.guardsOnly && + _action.predicate && + !_action.predicate(elements, appState, this.app.props, this.app, data) ) { return false; } - if (isActionName(action.name)) { + if (isActionName(_action.name)) { return !( - action.name in this.disablers && - this.disablers[action.name].some((fn) => - fn(elements, appState, action.name as ActionName), + _action.name in this.disablers && + this.disablers[_action.name].some((fn) => + fn(elements, appState, _action.name as ActionName), ) ); } return ( - action.name in this.enablers && - this.enablers[action.name].some((fn) => - fn(elements, appState, action.name), + _action.name in this.enablers && + this.enablers[_action.name].some((fn) => + fn(elements, appState, _action.name), ) ); }; diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx index 59eadde65..2f0bd0067 100644 --- a/src/components/Actions.tsx +++ b/src/components/Actions.tsx @@ -3,7 +3,7 @@ import { ActionManager } from "../actions/manager"; import { getNonDeletedElements } from "../element"; import { ExcalidrawElement, PointerType } from "../element/types"; import { t } from "../i18n"; -import { useDevice } from "../components/App"; +import { useDevice, useExcalidrawActionManager } from "../components/App"; import { canChangeRoundness, canHaveArrowheads, @@ -36,12 +36,10 @@ export const SelectedShapeActions = ({ appState, elements, renderAction, - getCustomActions, }: { appState: AppState; elements: readonly ExcalidrawElement[]; renderAction: ActionManager["renderAction"]; - getCustomActions: ActionManager["getCustomActions"]; }) => { const targetElements = getTargetElements( getNonDeletedElements(elements), @@ -94,9 +92,9 @@ export const SelectedShapeActions = ({ {showChangeBackgroundIcons && (
{renderAction("changeBackgroundColor")}
)} - {getCustomActions({ elements: targetElements }).map((action) => - renderAction(action.name), - )} + {useExcalidrawActionManager() + .getCustomActions({ elements: targetElements }) + .map((action) => renderAction(action.name))} {showFillIcons && renderAction("changeFillStyle")} {(hasStrokeWidth(appState.activeTool.type) || diff --git a/src/components/App.tsx b/src/components/App.tsx index 5a58511f9..7454db5b7 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -6167,20 +6167,29 @@ class App extends React.Component { type: "canvas" | "element" | "custom", source?: string, ): ContextMenuItems => { - const options: ContextMenuItems = []; - let addedCustom = false; + const custom: ContextMenuItems = []; this.actionManager - .getCustomActions({ data: { source } }) - .forEach((action) => { - addedCustom = true; - options.push(action); - }); + .getCustomActions({ data: { source: source ?? "" } }) + .forEach((action) => custom.push(action)); if (type === "custom") { - return options; + return custom; } - if (addedCustom) { - options.push(CONTEXT_MENU_SEPARATOR); + if (custom.length > 0) { + custom.push(CONTEXT_MENU_SEPARATOR); } + const standard: ContextMenuItems = this._getContextMenuItems(type).filter( + (item) => + !item || + item === CONTEXT_MENU_SEPARATOR || + this.actionManager.isActionEnabled(item, { guardsOnly: true }), + ); + return [...custom, ...standard]; + }; + + private _getContextMenuItems = ( + type: "canvas" | "element", + ): ContextMenuItems => { + const options: ContextMenuItems = []; options.push(actionCopyAsPng, actionCopyAsSvg); diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index c672a4446..3b8c8f7ec 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -244,7 +244,6 @@ const LayerUI = ({ appState={appState} elements={elements} renderAction={actionManager.renderAction} - getCustomActions={actionManager.getCustomActions} /> diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index a51bf917c..7e592a5b6 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -193,7 +193,6 @@ export const MobileMenu = ({ appState={appState} elements={elements} renderAction={actionManager.renderAction} - getCustomActions={actionManager.getCustomActions} /> ) : null}