Refactor: Drop isActionName
and convert getCustomActions
to
`filterActions`.
This commit is contained in:
parent
87aba3f619
commit
14c6ea938a
@ -6,7 +6,6 @@ import {
|
|||||||
PanelComponentProps,
|
PanelComponentProps,
|
||||||
ActionSource,
|
ActionSource,
|
||||||
ActionPredicateFn,
|
ActionPredicateFn,
|
||||||
isActionName,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppClassProperties, AppState } from "../types";
|
import { AppClassProperties, AppState } from "../types";
|
||||||
@ -76,29 +75,29 @@ export class ActionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getCustomActions(opts?: {
|
filterActions(
|
||||||
elements?: readonly ExcalidrawElement[];
|
filter: ActionPredicateFn,
|
||||||
data?: Record<string, any>;
|
opts?: {
|
||||||
guardsOnly?: boolean;
|
elements?: readonly ExcalidrawElement[];
|
||||||
}): Action[] {
|
data?: Record<string, any>;
|
||||||
|
},
|
||||||
|
): Action[] {
|
||||||
// For testing
|
// For testing
|
||||||
if (this === undefined) {
|
if (this === undefined) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const filter =
|
const elements = opts?.elements ?? this.getElementsIncludingDeleted();
|
||||||
opts !== undefined &&
|
const appState = this.getAppState();
|
||||||
("elements" in opts || "data" in opts || "guardsOnly" in opts);
|
const data = opts?.data;
|
||||||
const customActions: Action[] = [];
|
|
||||||
|
const actions: Action[] = [];
|
||||||
for (const key in this.actions) {
|
for (const key in this.actions) {
|
||||||
const action = this.actions[key];
|
const action = this.actions[key];
|
||||||
if (
|
if (filter(action, elements, appState, data)) {
|
||||||
!isActionName(action.name) &&
|
actions.push(action);
|
||||||
(!filter || this.isActionEnabled(action, opts))
|
|
||||||
) {
|
|
||||||
customActions.push(action);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return customActions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerAction(action: Action) {
|
registerAction(action: Action) {
|
||||||
@ -117,7 +116,7 @@ export class ActionManager {
|
|||||||
(action) =>
|
(action) =>
|
||||||
(action.name in canvasActions
|
(action.name in canvasActions
|
||||||
? canvasActions[action.name as keyof typeof canvasActions]
|
? canvasActions[action.name as keyof typeof canvasActions]
|
||||||
: this.isActionEnabled(action, { guardsOnly: true })) &&
|
: this.isActionEnabled(action, { noPredicates: true })) &&
|
||||||
action.keyTest &&
|
action.keyTest &&
|
||||||
action.keyTest(
|
action.keyTest(
|
||||||
event,
|
event,
|
||||||
@ -172,7 +171,7 @@ export class ActionManager {
|
|||||||
"PanelComponent" in this.actions[name] &&
|
"PanelComponent" in this.actions[name] &&
|
||||||
(name in canvasActions
|
(name in canvasActions
|
||||||
? canvasActions[name as keyof typeof canvasActions]
|
? canvasActions[name as keyof typeof canvasActions]
|
||||||
: this.isActionEnabled(this.actions[name], { guardsOnly: true }))
|
: this.isActionEnabled(this.actions[name], { noPredicates: true }))
|
||||||
) {
|
) {
|
||||||
const action = this.actions[name];
|
const action = this.actions[name];
|
||||||
const PanelComponent = action.PanelComponent!;
|
const PanelComponent = action.PanelComponent!;
|
||||||
@ -212,7 +211,7 @@ export class ActionManager {
|
|||||||
opts?: {
|
opts?: {
|
||||||
elements?: readonly ExcalidrawElement[];
|
elements?: readonly ExcalidrawElement[];
|
||||||
data?: Record<string, any>;
|
data?: Record<string, any>;
|
||||||
guardsOnly?: boolean;
|
noPredicates?: boolean;
|
||||||
},
|
},
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const elements = opts?.elements ?? this.getElementsIncludingDeleted();
|
const elements = opts?.elements ?? this.getElementsIncludingDeleted();
|
||||||
@ -220,7 +219,7 @@ export class ActionManager {
|
|||||||
const data = opts?.data;
|
const data = opts?.data;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!opts?.guardsOnly &&
|
!opts?.noPredicates &&
|
||||||
action.predicate &&
|
action.predicate &&
|
||||||
!action.predicate(elements, appState, this.app.props, this.app, data)
|
!action.predicate(elements, appState, this.app.props, this.app, data)
|
||||||
) {
|
) {
|
||||||
|
@ -43,92 +43,86 @@ export type ActionPredicateFn = (
|
|||||||
export type UpdaterFn = (res: ActionResult) => void;
|
export type UpdaterFn = (res: ActionResult) => void;
|
||||||
export type ActionFilterFn = (action: Action) => void;
|
export type ActionFilterFn = (action: Action) => void;
|
||||||
|
|
||||||
const actionNames = [
|
export type ActionName =
|
||||||
"copy",
|
| "copy"
|
||||||
"cut",
|
| "cut"
|
||||||
"paste",
|
| "paste"
|
||||||
"copyAsPng",
|
| "copyAsPng"
|
||||||
"copyAsSvg",
|
| "copyAsSvg"
|
||||||
"copyText",
|
| "copyText"
|
||||||
"sendBackward",
|
| "sendBackward"
|
||||||
"bringForward",
|
| "bringForward"
|
||||||
"sendToBack",
|
| "sendToBack"
|
||||||
"bringToFront",
|
| "bringToFront"
|
||||||
"copyStyles",
|
| "copyStyles"
|
||||||
"selectAll",
|
| "selectAll"
|
||||||
"pasteStyles",
|
| "pasteStyles"
|
||||||
"gridMode",
|
| "gridMode"
|
||||||
"zenMode",
|
| "zenMode"
|
||||||
"stats",
|
| "stats"
|
||||||
"changeStrokeColor",
|
| "changeStrokeColor"
|
||||||
"changeBackgroundColor",
|
| "changeBackgroundColor"
|
||||||
"changeFillStyle",
|
| "changeFillStyle"
|
||||||
"changeStrokeWidth",
|
| "changeStrokeWidth"
|
||||||
"changeStrokeShape",
|
| "changeStrokeShape"
|
||||||
"changeSloppiness",
|
| "changeSloppiness"
|
||||||
"changeStrokeStyle",
|
| "changeStrokeStyle"
|
||||||
"changeArrowhead",
|
| "changeArrowhead"
|
||||||
"changeOpacity",
|
| "changeOpacity"
|
||||||
"changeFontSize",
|
| "changeFontSize"
|
||||||
"toggleCanvasMenu",
|
| "toggleCanvasMenu"
|
||||||
"toggleEditMenu",
|
| "toggleEditMenu"
|
||||||
"undo",
|
| "undo"
|
||||||
"redo",
|
| "redo"
|
||||||
"finalize",
|
| "finalize"
|
||||||
"changeProjectName",
|
| "changeProjectName"
|
||||||
"changeExportBackground",
|
| "changeExportBackground"
|
||||||
"changeExportEmbedScene",
|
| "changeExportEmbedScene"
|
||||||
"changeExportScale",
|
| "changeExportScale"
|
||||||
"saveToActiveFile",
|
| "saveToActiveFile"
|
||||||
"saveFileToDisk",
|
| "saveFileToDisk"
|
||||||
"loadScene",
|
| "loadScene"
|
||||||
"duplicateSelection",
|
| "duplicateSelection"
|
||||||
"deleteSelectedElements",
|
| "deleteSelectedElements"
|
||||||
"changeViewBackgroundColor",
|
| "changeViewBackgroundColor"
|
||||||
"clearCanvas",
|
| "clearCanvas"
|
||||||
"zoomIn",
|
| "zoomIn"
|
||||||
"zoomOut",
|
| "zoomOut"
|
||||||
"resetZoom",
|
| "resetZoom"
|
||||||
"zoomToFit",
|
| "zoomToFit"
|
||||||
"zoomToSelection",
|
| "zoomToSelection"
|
||||||
"changeFontFamily",
|
| "changeFontFamily"
|
||||||
"changeTextAlign",
|
| "changeTextAlign"
|
||||||
"changeVerticalAlign",
|
| "changeVerticalAlign"
|
||||||
"toggleFullScreen",
|
| "toggleFullScreen"
|
||||||
"toggleShortcuts",
|
| "toggleShortcuts"
|
||||||
"group",
|
| "group"
|
||||||
"ungroup",
|
| "ungroup"
|
||||||
"goToCollaborator",
|
| "goToCollaborator"
|
||||||
"addToLibrary",
|
| "addToLibrary"
|
||||||
"changeRoundness",
|
| "changeRoundness"
|
||||||
"alignTop",
|
| "alignTop"
|
||||||
"alignBottom",
|
| "alignBottom"
|
||||||
"alignLeft",
|
| "alignLeft"
|
||||||
"alignRight",
|
| "alignRight"
|
||||||
"alignVerticallyCentered",
|
| "alignVerticallyCentered"
|
||||||
"alignHorizontallyCentered",
|
| "alignHorizontallyCentered"
|
||||||
"distributeHorizontally",
|
| "distributeHorizontally"
|
||||||
"distributeVertically",
|
| "distributeVertically"
|
||||||
"flipHorizontal",
|
| "flipHorizontal"
|
||||||
"flipVertical",
|
| "flipVertical"
|
||||||
"viewMode",
|
| "viewMode"
|
||||||
"exportWithDarkMode",
|
| "exportWithDarkMode"
|
||||||
"toggleTheme",
|
| "toggleTheme"
|
||||||
"increaseFontSize",
|
| "increaseFontSize"
|
||||||
"decreaseFontSize",
|
| "decreaseFontSize"
|
||||||
"unbindText",
|
| "unbindText"
|
||||||
"hyperlink",
|
| "hyperlink"
|
||||||
"bindText",
|
| "bindText"
|
||||||
"toggleLock",
|
| "toggleLock"
|
||||||
"toggleLinearEditor",
|
| "toggleLinearEditor"
|
||||||
"toggleEraserTool",
|
| "toggleEraserTool"
|
||||||
"toggleHandTool",
|
| "toggleHandTool";
|
||||||
] as const;
|
|
||||||
|
|
||||||
// So we can have the `isActionName` type guard
|
|
||||||
export type ActionName = typeof actionNames[number];
|
|
||||||
export const isActionName = (n: any): n is ActionName =>
|
|
||||||
actionNames.includes(n);
|
|
||||||
|
|
||||||
export type PanelComponentProps = {
|
export type PanelComponentProps = {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
@ -3,7 +3,7 @@ import { ActionManager } from "../actions/manager";
|
|||||||
import { getNonDeletedElements } from "../element";
|
import { getNonDeletedElements } from "../element";
|
||||||
import { ExcalidrawElement, PointerType } from "../element/types";
|
import { ExcalidrawElement, PointerType } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { useDevice, useExcalidrawActionManager } from "../components/App";
|
import { useDevice } from "../components/App";
|
||||||
import {
|
import {
|
||||||
canChangeRoundness,
|
canChangeRoundness,
|
||||||
canHaveArrowheads,
|
canHaveArrowheads,
|
||||||
@ -23,7 +23,7 @@ import {
|
|||||||
} from "../utils";
|
} from "../utils";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
import { SubtypeToggles } from "./SubtypeButton";
|
import { SubtypeShapeActions, SubtypeToggles } from "./Subtypes";
|
||||||
import { hasStrokeColor } from "../scene/comparisons";
|
import { hasStrokeColor } from "../scene/comparisons";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { hasBoundTextElement } from "../element/typeChecks";
|
import { hasBoundTextElement } from "../element/typeChecks";
|
||||||
@ -93,9 +93,7 @@ export const SelectedShapeActions = ({
|
|||||||
{showChangeBackgroundIcons && (
|
{showChangeBackgroundIcons && (
|
||||||
<div>{renderAction("changeBackgroundColor")}</div>
|
<div>{renderAction("changeBackgroundColor")}</div>
|
||||||
)}
|
)}
|
||||||
{useExcalidrawActionManager()
|
<SubtypeShapeActions elements={targetElements} />
|
||||||
.getCustomActions({ elements: targetElements })
|
|
||||||
.map((action) => renderAction(action.name))}
|
|
||||||
{showFillIcons && renderAction("changeFillStyle")}
|
{showFillIcons && renderAction("changeFillStyle")}
|
||||||
|
|
||||||
{(hasStrokeWidth(appState.activeTool.type) ||
|
{(hasStrokeWidth(appState.activeTool.type) ||
|
||||||
|
@ -242,6 +242,7 @@ import {
|
|||||||
prepareSubtype,
|
prepareSubtype,
|
||||||
selectSubtype,
|
selectSubtype,
|
||||||
subtypeActionPredicate,
|
subtypeActionPredicate,
|
||||||
|
isSubtypeAction,
|
||||||
} from "../subtypes";
|
} from "../subtypes";
|
||||||
import {
|
import {
|
||||||
dataURLToFile,
|
dataURLToFile,
|
||||||
@ -6229,26 +6230,26 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private getContextMenuItems = (
|
private getContextMenuItems = (
|
||||||
type: "canvas" | "element" | "custom",
|
type: "canvas" | "element",
|
||||||
source?: string,
|
|
||||||
): ContextMenuItems => {
|
): ContextMenuItems => {
|
||||||
const custom: ContextMenuItems = [];
|
const subtype: ContextMenuItems = [];
|
||||||
this.actionManager
|
this.actionManager
|
||||||
.getCustomActions({ data: { source: source ?? "" } })
|
.filterActions(isSubtypeAction)
|
||||||
.forEach((action) => custom.push(action));
|
.forEach(
|
||||||
if (type === "custom") {
|
(action) =>
|
||||||
return custom;
|
this.actionManager.isActionEnabled(action, { data: {} }) &&
|
||||||
}
|
subtype.push(action),
|
||||||
if (custom.length > 0) {
|
);
|
||||||
custom.push(CONTEXT_MENU_SEPARATOR);
|
if (subtype.length > 0) {
|
||||||
|
subtype.push(CONTEXT_MENU_SEPARATOR);
|
||||||
}
|
}
|
||||||
const standard: ContextMenuItems = this._getContextMenuItems(type).filter(
|
const standard: ContextMenuItems = this._getContextMenuItems(type).filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
!item ||
|
!item ||
|
||||||
item === CONTEXT_MENU_SEPARATOR ||
|
item === CONTEXT_MENU_SEPARATOR ||
|
||||||
this.actionManager.isActionEnabled(item, { guardsOnly: true }),
|
this.actionManager.isActionEnabled(item, { noPredicates: true }),
|
||||||
);
|
);
|
||||||
return [...custom, ...standard];
|
return [...subtype, ...standard];
|
||||||
};
|
};
|
||||||
|
|
||||||
private _getContextMenuItems = (
|
private _getContextMenuItems = (
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
Subtype,
|
Subtype,
|
||||||
getSubtypeNames,
|
getSubtypeNames,
|
||||||
hasAlwaysEnabledActions,
|
hasAlwaysEnabledActions,
|
||||||
|
isSubtypeAction,
|
||||||
isValidSubtype,
|
isValidSubtype,
|
||||||
subtypeCollides,
|
subtypeCollides,
|
||||||
} from "../subtypes";
|
} from "../subtypes";
|
||||||
@ -30,7 +31,7 @@ export const SubtypeButton = (
|
|||||||
const subtypeAction: Action = {
|
const subtypeAction: Action = {
|
||||||
name: subtype,
|
name: subtype,
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
predicate: (...rest) => rest[4]?.source === subtype,
|
predicate: (...rest) => rest[4]?.subtype === subtype,
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
const inactive = !appState.activeSubtypes?.includes(subtype) ?? true;
|
const inactive = !appState.activeSubtypes?.includes(subtype) ?? true;
|
||||||
const activeSubtypes: Subtype[] = [];
|
const activeSubtypes: Subtype[] = [];
|
||||||
@ -121,7 +122,7 @@ export const SubtypeToggles = () => {
|
|||||||
|
|
||||||
const onContextMenu = (
|
const onContextMenu = (
|
||||||
event: React.MouseEvent<HTMLButtonElement>,
|
event: React.MouseEvent<HTMLButtonElement>,
|
||||||
source: string,
|
subtype: string,
|
||||||
) => {
|
) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@ -131,8 +132,9 @@ export const SubtypeToggles = () => {
|
|||||||
const top = event.clientY - offsetTop;
|
const top = event.clientY - offsetTop;
|
||||||
|
|
||||||
const items: ContextMenuItems = [];
|
const items: ContextMenuItems = [];
|
||||||
am.getCustomActions({ data: { source } }).forEach((action) =>
|
am.filterActions(isSubtypeAction).forEach(
|
||||||
items.push(action),
|
(action) =>
|
||||||
|
am.isActionEnabled(action, { data: { subtype } }) && items.push(action),
|
||||||
);
|
);
|
||||||
setAppState({}, () => {
|
setAppState({}, () => {
|
||||||
setAppState({
|
setAppState({
|
||||||
@ -154,3 +156,18 @@ export const SubtypeToggles = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SubtypeToggles.displayName = "SubtypeToggles";
|
SubtypeToggles.displayName = "SubtypeToggles";
|
||||||
|
|
||||||
|
export const SubtypeShapeActions = (props: {
|
||||||
|
elements: readonly ExcalidrawElement[];
|
||||||
|
}) => {
|
||||||
|
const am = useExcalidrawActionManager();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{am
|
||||||
|
.filterActions(isSubtypeAction, { elements: props.elements })
|
||||||
|
.map((action) => am.renderAction(action.name))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SubtypeShapeActions.displayName = "SubtypeShapeActions";
|
@ -45,7 +45,7 @@ import {
|
|||||||
} from "../../../../subtypes";
|
} from "../../../../subtypes";
|
||||||
import { mathSubtypeIcon } from "./icon";
|
import { mathSubtypeIcon } from "./icon";
|
||||||
import { getMathSubtypeRecord } from "./types";
|
import { getMathSubtypeRecord } from "./types";
|
||||||
import { SubtypeButton } from "../../../../components/SubtypeButton";
|
import { SubtypeButton } from "../../../../components/Subtypes";
|
||||||
import { getMaxContainerWidth } from "../../../../element/newElement";
|
import { getMaxContainerWidth } from "../../../../element/newElement";
|
||||||
|
|
||||||
const mathSubtype = getMathSubtypeRecord().subtype;
|
const mathSubtype = getMathSubtypeRecord().subtype;
|
||||||
@ -1318,7 +1318,7 @@ const createMathActions = () => {
|
|||||||
getMathProps.getUseTex(appState)
|
getMathProps.getUseTex(appState)
|
||||||
? "labels.useTexTrueActive"
|
? "labels.useTexTrueActive"
|
||||||
: "labels.useTexTrueInactive",
|
: "labels.useTexTrueInactive",
|
||||||
predicate: (...rest) => rest.length < 5 || rest[4]?.source === mathSubtype,
|
predicate: (...rest) => rest.length < 5 || rest[4]?.subtype === mathSubtype,
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
};
|
};
|
||||||
const actionUseTexFalse: Action = {
|
const actionUseTexFalse: Action = {
|
||||||
@ -1337,7 +1337,7 @@ const createMathActions = () => {
|
|||||||
!getMathProps.getUseTex(appState)
|
!getMathProps.getUseTex(appState)
|
||||||
? "labels.useTexFalseActive"
|
? "labels.useTexFalseActive"
|
||||||
: "labels.useTexFalseInactive",
|
: "labels.useTexFalseInactive",
|
||||||
predicate: (...rest) => rest.length < 5 || rest[4]?.source === mathSubtype,
|
predicate: (...rest) => rest.length < 5 || rest[4]?.subtype === mathSubtype,
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
};
|
};
|
||||||
const actionResetUseTex: Action = {
|
const actionResetUseTex: Action = {
|
||||||
@ -1466,8 +1466,7 @@ const createMathActions = () => {
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
),
|
),
|
||||||
predicate: (...rest) =>
|
predicate: (...rest) =>
|
||||||
rest[4]?.source === undefined &&
|
rest[4] === undefined && enableActionChangeMathProps(rest[0], rest[1]),
|
||||||
enableActionChangeMathProps(rest[0], rest[1]),
|
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
};
|
};
|
||||||
const actionMath = SubtypeButton(mathSubtype, "text", mathSubtypeIcon, "M");
|
const actionMath = SubtypeButton(mathSubtype, "text", mathSubtypeIcon, "M");
|
||||||
|
@ -99,6 +99,10 @@ const isForSubtype = (
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isSubtypeAction: ActionPredicateFn = function (action) {
|
||||||
|
return isSubtypeActionName(action.name) && !isSubtypeName(action.name);
|
||||||
|
};
|
||||||
|
|
||||||
export const subtypeActionPredicate: ActionPredicateFn = function (
|
export const subtypeActionPredicate: ActionPredicateFn = function (
|
||||||
action,
|
action,
|
||||||
elements,
|
elements,
|
||||||
|
@ -18,7 +18,7 @@ import ExcalidrawApp from "../excalidraw-app";
|
|||||||
|
|
||||||
import { ExcalidrawElement, FontString, Theme } from "../element/types";
|
import { ExcalidrawElement, FontString, Theme } from "../element/types";
|
||||||
import { createIcon, iconFillColor } from "../components/icons";
|
import { createIcon, iconFillColor } from "../components/icons";
|
||||||
import { SubtypeButton } from "../components/SubtypeButton";
|
import { SubtypeButton } from "../components/Subtypes";
|
||||||
import { registerAuxLangData } from "../i18n";
|
import { registerAuxLangData } from "../i18n";
|
||||||
import { getFontString, getShortcutKey } from "../utils";
|
import { getFontString, getShortcutKey } from "../utils";
|
||||||
import * as textElementUtils from "../element/textElement";
|
import * as textElementUtils from "../element/textElement";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user