import { getShortcutKey, updateActiveTool } from "../utils"; import { t } from "../i18n"; import { Action } from "../actions/types"; import clsx from "clsx"; import { Subtype, getSubtypeNames, hasAlwaysEnabledActions, isSubtypeAction, isValidSubtype, subtypeCollides, } from "../element/subtypes"; import { ExcalidrawElement, Theme } from "../element/types"; import { useExcalidrawActionManager, useExcalidrawContainer, useExcalidrawSetAppState, } from "./App"; import { ContextMenuItems } from "./ContextMenu"; export const SubtypeButton = ( subtype: Subtype, parentType: ExcalidrawElement["type"], icon: ({ theme }: { theme: Theme }) => JSX.Element, key?: string, ) => { const title = key !== undefined ? ` - ${getShortcutKey(key)}` : ""; const keyTest: Action["keyTest"] = key !== undefined ? (event) => event.code === `Key${key}` : undefined; const subtypeAction: Action = { name: subtype, trackEvent: false, predicate: (...rest) => rest[4]?.subtype === subtype, perform: (elements, appState) => { const inactive = !appState.activeSubtypes?.includes(subtype) ?? true; const activeSubtypes: Subtype[] = []; if (appState.activeSubtypes) { activeSubtypes.push(...appState.activeSubtypes); } let activated = false; if (inactive) { // Ensure `element.subtype` is well-defined if (!subtypeCollides(subtype, activeSubtypes)) { activeSubtypes.push(subtype); activated = true; } } else { // Can only be active if appState.activeSubtypes is defined // and contains subtype. activeSubtypes.splice(activeSubtypes.indexOf(subtype), 1); } const type = appState.activeTool.type !== "custom" && isValidSubtype(subtype, appState.activeTool.type) ? appState.activeTool.type : parentType; const activeTool = !inactive ? appState.activeTool : updateActiveTool(appState, { type }); const selectedElementIds = activated ? {} : appState.selectedElementIds; const selectedGroupIds = activated ? {} : appState.selectedGroupIds; return { appState: { ...appState, activeSubtypes, selectedElementIds, selectedGroupIds, activeTool, }, commitToHistory: true, }; }, keyTest, PanelComponent: ({ elements, appState, updateData, data }) => ( ), }; if (key === "") { delete subtypeAction.keyTest; } return subtypeAction; }; export const SubtypeToggles = () => { const am = useExcalidrawActionManager(); const { container } = useExcalidrawContainer(); const setAppState = useExcalidrawSetAppState(); const onContextMenu = ( event: React.MouseEvent, subtype: string, ) => { event.preventDefault(); const { top: offsetTop, left: offsetLeft } = container!.getBoundingClientRect(); const left = event.clientX - offsetLeft; const top = event.clientY - offsetTop; const items: ContextMenuItems = []; am.filterActions(isSubtypeAction).forEach( (action) => am.isActionEnabled(action, { data: { subtype } }) && items.push(action), ); setAppState({}, () => { setAppState({ contextMenu: { top, left, items }, }); }); }; return ( <> {getSubtypeNames().map((subtype) => am.renderAction( subtype, hasAlwaysEnabledActions(subtype) ? { onContextMenu } : {}, ), )} ); }; 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";