Filter context menu items through isActionEnabled.

This commit is contained in:
Daniel J. Geiger 2023-01-08 20:00:46 -06:00
parent 01432813a6
commit a5bd54b86d
5 changed files with 51 additions and 35 deletions

View File

@ -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<string, any>;
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<string, any>;
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),
)
);
};

View File

@ -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 && (
<div>{renderAction("changeBackgroundColor")}</div>
)}
{getCustomActions({ elements: targetElements }).map((action) =>
renderAction(action.name),
)}
{useExcalidrawActionManager()
.getCustomActions({ elements: targetElements })
.map((action) => renderAction(action.name))}
{showFillIcons && renderAction("changeFillStyle")}
{(hasStrokeWidth(appState.activeTool.type) ||

View File

@ -6167,20 +6167,29 @@ class App extends React.Component<AppProps, AppState> {
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);

View File

@ -244,7 +244,6 @@ const LayerUI = ({
appState={appState}
elements={elements}
renderAction={actionManager.renderAction}
getCustomActions={actionManager.getCustomActions}
/>
</Island>
</Section>

View File

@ -193,7 +193,6 @@ export const MobileMenu = ({
appState={appState}
elements={elements}
renderAction={actionManager.renderAction}
getCustomActions={actionManager.getCustomActions}
/>
</Section>
) : null}