mirror of
https://github.com/excalidraw/excalidraw.git
synced 2024-11-10 11:35:52 +01:00
feat: move contextMenu into the component tree and control via appState (#6021)
This commit is contained in:
parent
b704705ed8
commit
7e135c4e22
@ -3,6 +3,7 @@ import { register } from "./register";
|
|||||||
import {
|
import {
|
||||||
copyTextToSystemClipboard,
|
copyTextToSystemClipboard,
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
|
probablySupportsClipboardBlob,
|
||||||
probablySupportsClipboardWriteText,
|
probablySupportsClipboardWriteText,
|
||||||
} from "../clipboard";
|
} from "../clipboard";
|
||||||
import { actionDeleteSelected } from "./actionDeleteSelected";
|
import { actionDeleteSelected } from "./actionDeleteSelected";
|
||||||
@ -23,11 +24,31 @@ export const actionCopy = register({
|
|||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
contextItemPredicate: (elements, appState, appProps, app) => {
|
||||||
|
return app.device.isMobile && !!navigator.clipboard;
|
||||||
|
},
|
||||||
contextItemLabel: "labels.copy",
|
contextItemLabel: "labels.copy",
|
||||||
// don't supply a shortcut since we handle this conditionally via onCopy event
|
// don't supply a shortcut since we handle this conditionally via onCopy event
|
||||||
keyTest: undefined,
|
keyTest: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const actionPaste = register({
|
||||||
|
name: "paste",
|
||||||
|
trackEvent: { category: "element" },
|
||||||
|
perform: (elements: any, appStates: any, data, app) => {
|
||||||
|
app.pasteFromClipboard(null);
|
||||||
|
return {
|
||||||
|
commitToHistory: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
contextItemPredicate: (elements, appState, appProps, app) => {
|
||||||
|
return app.device.isMobile && !!navigator.clipboard;
|
||||||
|
},
|
||||||
|
contextItemLabel: "labels.paste",
|
||||||
|
// don't supply a shortcut since we handle this conditionally via onCopy event
|
||||||
|
keyTest: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
export const actionCut = register({
|
export const actionCut = register({
|
||||||
name: "cut",
|
name: "cut",
|
||||||
trackEvent: { category: "element" },
|
trackEvent: { category: "element" },
|
||||||
@ -35,6 +56,9 @@ export const actionCut = register({
|
|||||||
actionCopy.perform(elements, appState, data, app);
|
actionCopy.perform(elements, appState, data, app);
|
||||||
return actionDeleteSelected.perform(elements, appState);
|
return actionDeleteSelected.perform(elements, appState);
|
||||||
},
|
},
|
||||||
|
contextItemPredicate: (elements, appState, appProps, app) => {
|
||||||
|
return app.device.isMobile && !!navigator.clipboard;
|
||||||
|
},
|
||||||
contextItemLabel: "labels.cut",
|
contextItemLabel: "labels.cut",
|
||||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X,
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X,
|
||||||
});
|
});
|
||||||
@ -77,6 +101,9 @@ export const actionCopyAsSvg = register({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
contextItemPredicate: (elements) => {
|
||||||
|
return probablySupportsClipboardWriteText && elements.length > 0;
|
||||||
|
},
|
||||||
contextItemLabel: "labels.copyAsSvg",
|
contextItemLabel: "labels.copyAsSvg",
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -131,6 +158,9 @@ export const actionCopyAsPng = register({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
contextItemPredicate: (elements) => {
|
||||||
|
return probablySupportsClipboardBlob && elements.length > 0;
|
||||||
|
},
|
||||||
contextItemLabel: "labels.copyAsPng",
|
contextItemLabel: "labels.copyAsPng",
|
||||||
keyTest: (event) => event.code === CODES.C && event.altKey && event.shiftKey,
|
keyTest: (event) => event.code === CODES.C && event.altKey && event.shiftKey,
|
||||||
});
|
});
|
||||||
|
@ -20,6 +20,9 @@ export const actionToggleGridMode = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
checked: (appState: AppState) => appState.gridSize !== null,
|
checked: (appState: AppState) => appState.gridSize !== null,
|
||||||
|
contextItemPredicate: (element, appState, props) => {
|
||||||
|
return typeof props.gridModeEnabled === "undefined";
|
||||||
|
},
|
||||||
contextItemLabel: "labels.showGrid",
|
contextItemLabel: "labels.showGrid",
|
||||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE,
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE,
|
||||||
});
|
});
|
||||||
|
@ -41,15 +41,9 @@ export const actionToggleLock = register({
|
|||||||
: "labels.elementLock.lock";
|
: "labels.elementLock.lock";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selected.length > 1) {
|
return getOperation(selected) === "lock"
|
||||||
return getOperation(selected) === "lock"
|
? "labels.elementLock.lockAll"
|
||||||
? "labels.elementLock.lockAll"
|
: "labels.elementLock.unlockAll";
|
||||||
: "labels.elementLock.unlockAll";
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
"Unexpected zero elements to lock/unlock. This should never happen.",
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
keyTest: (event, appState, elements) => {
|
keyTest: (event, appState, elements) => {
|
||||||
return (
|
return (
|
||||||
|
@ -18,6 +18,9 @@ export const actionToggleViewMode = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
checked: (appState) => appState.viewModeEnabled,
|
checked: (appState) => appState.viewModeEnabled,
|
||||||
|
contextItemPredicate: (elements, appState, appProps) => {
|
||||||
|
return typeof appProps.viewModeEnabled === "undefined";
|
||||||
|
},
|
||||||
contextItemLabel: "labels.viewMode",
|
contextItemLabel: "labels.viewMode",
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.R,
|
!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.R,
|
||||||
|
@ -18,6 +18,9 @@ export const actionToggleZenMode = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
checked: (appState) => appState.zenModeEnabled,
|
checked: (appState) => appState.zenModeEnabled,
|
||||||
|
contextItemPredicate: (elements, appState, appProps) => {
|
||||||
|
return typeof appProps.zenModeEnabled === "undefined";
|
||||||
|
},
|
||||||
contextItemLabel: "buttons.zenMode",
|
contextItemLabel: "buttons.zenMode",
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.Z,
|
!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.Z,
|
||||||
|
@ -143,6 +143,8 @@ export interface Action {
|
|||||||
contextItemPredicate?: (
|
contextItemPredicate?: (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
appProps: ExcalidrawProps,
|
||||||
|
app: AppClassProperties,
|
||||||
) => boolean;
|
) => boolean;
|
||||||
checked?: (appState: Readonly<AppState>) => boolean;
|
checked?: (appState: Readonly<AppState>) => boolean;
|
||||||
trackEvent:
|
trackEvent:
|
||||||
|
@ -64,6 +64,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
lastPointerDownWith: "mouse",
|
lastPointerDownWith: "mouse",
|
||||||
multiElement: null,
|
multiElement: null,
|
||||||
name: `${t("labels.untitled")}-${getDateTime()}`,
|
name: `${t("labels.untitled")}-${getDateTime()}`,
|
||||||
|
contextMenu: null,
|
||||||
openMenu: null,
|
openMenu: null,
|
||||||
openPopup: null,
|
openPopup: null,
|
||||||
openSidebar: null,
|
openSidebar: null,
|
||||||
@ -157,6 +158,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
name: { browser: true, export: false, server: false },
|
name: { browser: true, export: false, server: false },
|
||||||
offsetLeft: { browser: false, export: false, server: false },
|
offsetLeft: { browser: false, export: false, server: false },
|
||||||
offsetTop: { browser: false, export: false, server: false },
|
offsetTop: { browser: false, export: false, server: false },
|
||||||
|
contextMenu: { browser: false, export: false, server: false },
|
||||||
openMenu: { browser: true, export: false, server: false },
|
openMenu: { browser: true, export: false, server: false },
|
||||||
openPopup: { browser: false, export: false, server: false },
|
openPopup: { browser: false, export: false, server: false },
|
||||||
openSidebar: { browser: true, export: false, server: false },
|
openSidebar: { browser: true, export: false, server: false },
|
||||||
|
@ -42,11 +42,7 @@ import { actions } from "../actions/register";
|
|||||||
import { ActionResult } from "../actions/types";
|
import { ActionResult } from "../actions/types";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { getDefaultAppState, isEraserActive } from "../appState";
|
import { getDefaultAppState, isEraserActive } from "../appState";
|
||||||
import {
|
import { parseClipboard } from "../clipboard";
|
||||||
parseClipboard,
|
|
||||||
probablySupportsClipboardBlob,
|
|
||||||
probablySupportsClipboardWriteText,
|
|
||||||
} from "../clipboard";
|
|
||||||
import {
|
import {
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
CURSOR_TYPE,
|
CURSOR_TYPE,
|
||||||
@ -227,7 +223,11 @@ import {
|
|||||||
updateActiveTool,
|
updateActiveTool,
|
||||||
getShortcutKey,
|
getShortcutKey,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import ContextMenu, { ContextMenuOption } from "./ContextMenu";
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuItems,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
} from "./ContextMenu";
|
||||||
import LayerUI from "./LayerUI";
|
import LayerUI from "./LayerUI";
|
||||||
import { Toast } from "./Toast";
|
import { Toast } from "./Toast";
|
||||||
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
|
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
|
||||||
@ -274,6 +274,7 @@ import {
|
|||||||
import { shouldShowBoundingBox } from "../element/transformHandles";
|
import { shouldShowBoundingBox } from "../element/transformHandles";
|
||||||
import { atom } from "jotai";
|
import { atom } from "jotai";
|
||||||
import { Fonts } from "../scene/Fonts";
|
import { Fonts } from "../scene/Fonts";
|
||||||
|
import { actionPaste } from "../actions/actionClipboard";
|
||||||
|
|
||||||
export const isMenuOpenAtom = atom(false);
|
export const isMenuOpenAtom = atom(false);
|
||||||
export const isDropdownOpenAtom = atom(false);
|
export const isDropdownOpenAtom = atom(false);
|
||||||
@ -383,7 +384,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
hitLinkElement?: NonDeletedExcalidrawElement;
|
hitLinkElement?: NonDeletedExcalidrawElement;
|
||||||
lastPointerDown: React.PointerEvent<HTMLCanvasElement> | null = null;
|
lastPointerDown: React.PointerEvent<HTMLCanvasElement> | null = null;
|
||||||
lastPointerUp: React.PointerEvent<HTMLElement> | PointerEvent | null = null;
|
lastPointerUp: React.PointerEvent<HTMLElement> | PointerEvent | null = null;
|
||||||
contextMenuOpen: boolean = false;
|
|
||||||
lastScenePointer: { x: number; y: number } | null = null;
|
lastScenePointer: { x: number; y: number } | null = null;
|
||||||
|
|
||||||
constructor(props: AppProps) {
|
constructor(props: AppProps) {
|
||||||
@ -602,6 +602,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
<div className="excalidraw-textEditorContainer" />
|
<div className="excalidraw-textEditorContainer" />
|
||||||
<div className="excalidraw-contextMenuContainer" />
|
<div className="excalidraw-contextMenuContainer" />
|
||||||
{selectedElement.length === 1 &&
|
{selectedElement.length === 1 &&
|
||||||
|
!this.state.contextMenu &&
|
||||||
this.state.showHyperlinkPopup && (
|
this.state.showHyperlinkPopup && (
|
||||||
<Hyperlink
|
<Hyperlink
|
||||||
key={selectedElement[0].id}
|
key={selectedElement[0].id}
|
||||||
@ -618,6 +619,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
closable={this.state.toast.closable}
|
closable={this.state.toast.closable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{this.state.contextMenu && (
|
||||||
|
<ContextMenu
|
||||||
|
items={this.state.contextMenu.items}
|
||||||
|
top={this.state.contextMenu.top}
|
||||||
|
left={this.state.contextMenu.left}
|
||||||
|
actionManager={this.actionManager}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<main>{this.renderCanvas()}</main>
|
<main>{this.renderCanvas()}</main>
|
||||||
</ExcalidrawElementsContext.Provider>{" "}
|
</ExcalidrawElementsContext.Provider>{" "}
|
||||||
</ExcalidrawAppStateContext.Provider>
|
</ExcalidrawAppStateContext.Provider>
|
||||||
@ -644,8 +653,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
private syncActionResult = withBatchedUpdates(
|
private syncActionResult = withBatchedUpdates(
|
||||||
(actionResult: ActionResult) => {
|
(actionResult: ActionResult) => {
|
||||||
// Since context menu closes when action triggered so setting to false
|
|
||||||
this.contextMenuOpen = false;
|
|
||||||
if (this.unmounted || actionResult === false) {
|
if (this.unmounted || actionResult === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -674,7 +681,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.addNewImagesToImageCache();
|
this.addNewImagesToImageCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actionResult.appState || editingElement) {
|
if (actionResult.appState || editingElement || this.state.contextMenu) {
|
||||||
if (actionResult.commitToHistory) {
|
if (actionResult.commitToHistory) {
|
||||||
this.history.resumeRecording();
|
this.history.resumeRecording();
|
||||||
}
|
}
|
||||||
@ -700,12 +707,17 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (typeof this.props.name !== "undefined") {
|
if (typeof this.props.name !== "undefined") {
|
||||||
name = this.props.name;
|
name = this.props.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
(state) => {
|
(state) => {
|
||||||
// using Object.assign instead of spread to fool TS 4.2.2+ into
|
// using Object.assign instead of spread to fool TS 4.2.2+ into
|
||||||
// regarding the resulting type as not containing undefined
|
// regarding the resulting type as not containing undefined
|
||||||
// (which the following expression will never contain)
|
// (which the following expression will never contain)
|
||||||
return Object.assign(actionResult.appState || {}, {
|
return Object.assign(actionResult.appState || {}, {
|
||||||
|
// NOTE this will prevent opening context menu using an action
|
||||||
|
// or programmatically from the host, so it will need to be
|
||||||
|
// rewritten later
|
||||||
|
contextMenu: null,
|
||||||
editingElement:
|
editingElement:
|
||||||
editingElement || actionResult.appState?.editingElement || null,
|
editingElement || actionResult.appState?.editingElement || null,
|
||||||
viewModeEnabled,
|
viewModeEnabled,
|
||||||
@ -1462,7 +1474,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private pasteFromClipboard = withBatchedUpdates(
|
public pasteFromClipboard = withBatchedUpdates(
|
||||||
async (event: ClipboardEvent | null) => {
|
async (event: ClipboardEvent | null) => {
|
||||||
const isPlainPaste = !!(IS_PLAIN_PASTE && event);
|
const isPlainPaste = !!(IS_PLAIN_PASTE && event);
|
||||||
|
|
||||||
@ -1470,7 +1482,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const target = document.activeElement;
|
const target = document.activeElement;
|
||||||
const isExcalidrawActive =
|
const isExcalidrawActive =
|
||||||
this.excalidrawContainerRef.current?.contains(target);
|
this.excalidrawContainerRef.current?.contains(target);
|
||||||
if (!isExcalidrawActive) {
|
if (event && !isExcalidrawActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1744,10 +1756,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.history.resumeRecording();
|
this.history.resumeRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collaboration
|
setAppState: React.Component<any, AppState>["setState"] = (
|
||||||
|
state,
|
||||||
setAppState: React.Component<any, AppState>["setState"] = (state) => {
|
callback,
|
||||||
this.setState(state);
|
) => {
|
||||||
|
this.setState(state, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
removePointer = (event: React.PointerEvent<HTMLElement> | PointerEvent) => {
|
removePointer = (event: React.PointerEvent<HTMLElement> | PointerEvent) => {
|
||||||
@ -3101,7 +3114,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
hitElement &&
|
hitElement &&
|
||||||
hitElement.link &&
|
hitElement.link &&
|
||||||
this.state.selectedElementIds[hitElement.id] &&
|
this.state.selectedElementIds[hitElement.id] &&
|
||||||
!this.contextMenuOpen &&
|
!this.state.contextMenu &&
|
||||||
!this.state.showHyperlinkPopup
|
!this.state.showHyperlinkPopup
|
||||||
) {
|
) {
|
||||||
this.setState({ showHyperlinkPopup: "info" });
|
this.setState({ showHyperlinkPopup: "info" });
|
||||||
@ -3323,6 +3336,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
private handleCanvasPointerDown = (
|
private handleCanvasPointerDown = (
|
||||||
event: React.PointerEvent<HTMLCanvasElement>,
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
) => {
|
) => {
|
||||||
|
// since contextMenu options are potentially evaluated on each render,
|
||||||
|
// and an contextMenu action may depend on selection state, we must
|
||||||
|
// close the contextMenu before we update the selection on pointerDown
|
||||||
|
// (e.g. resetting selection)
|
||||||
|
if (this.state.contextMenu) {
|
||||||
|
this.setState({ contextMenu: null });
|
||||||
|
}
|
||||||
|
|
||||||
// remove any active selection when we start to interact with canvas
|
// remove any active selection when we start to interact with canvas
|
||||||
// (mainly, we care about removing selection outside the component which
|
// (mainly, we care about removing selection outside the component which
|
||||||
// would prevent our copy handling otherwise)
|
// would prevent our copy handling otherwise)
|
||||||
@ -3389,8 +3410,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since context menu closes on pointer down so setting to false
|
|
||||||
this.contextMenuOpen = false;
|
|
||||||
this.clearSelectionIfNotUsingSelection();
|
this.clearSelectionIfNotUsingSelection();
|
||||||
this.updateBindingEnabledOnPointerMove(event);
|
this.updateBindingEnabledOnPointerMove(event);
|
||||||
|
|
||||||
@ -5949,7 +5968,17 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
includeLockedElements: true,
|
includeLockedElements: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const type = element ? "element" : "canvas";
|
const selectedElements = getSelectedElements(
|
||||||
|
this.scene.getNonDeletedElements(),
|
||||||
|
this.state,
|
||||||
|
);
|
||||||
|
const isHittignCommonBoundBox =
|
||||||
|
this.isHittingCommonBoundingBoxOfSelectedElements(
|
||||||
|
{ x, y },
|
||||||
|
selectedElements,
|
||||||
|
);
|
||||||
|
|
||||||
|
const type = element || isHittignCommonBoundBox ? "element" : "canvas";
|
||||||
|
|
||||||
const container = this.excalidrawContainerRef.current!;
|
const container = this.excalidrawContainerRef.current!;
|
||||||
const { top: offsetTop, left: offsetLeft } =
|
const { top: offsetTop, left: offsetLeft } =
|
||||||
@ -5957,25 +5986,30 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const left = event.clientX - offsetLeft;
|
const left = event.clientX - offsetLeft;
|
||||||
const top = event.clientY - offsetTop;
|
const top = event.clientY - offsetTop;
|
||||||
|
|
||||||
if (element && !this.state.selectedElementIds[element.id]) {
|
trackEvent("contextMenu", "openContextMenu", type);
|
||||||
this.setState(
|
|
||||||
selectGroupsForSelectedElements(
|
this.setState(
|
||||||
{
|
{
|
||||||
...this.state,
|
...(element && !this.state.selectedElementIds[element.id]
|
||||||
selectedElementIds: { [element.id]: true },
|
? selectGroupsForSelectedElements(
|
||||||
selectedLinearElement: isLinearElement(element)
|
{
|
||||||
? new LinearElementEditor(element, this.scene)
|
...this.state,
|
||||||
: null,
|
selectedElementIds: { [element.id]: true },
|
||||||
},
|
selectedLinearElement: isLinearElement(element)
|
||||||
this.scene.getNonDeletedElements(),
|
? new LinearElementEditor(element, this.scene)
|
||||||
),
|
: null,
|
||||||
() => {
|
},
|
||||||
this._openContextMenu({ top, left }, type);
|
this.scene.getNonDeletedElements(),
|
||||||
},
|
)
|
||||||
);
|
: this.state),
|
||||||
} else {
|
showHyperlinkPopup: false,
|
||||||
this._openContextMenu({ top, left }, type);
|
},
|
||||||
}
|
() => {
|
||||||
|
this.setState({
|
||||||
|
contextMenu: { top, left, items: this.getContextMenuItems(type) },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private maybeDragNewGenericElement = (
|
private maybeDragNewGenericElement = (
|
||||||
@ -6083,215 +6117,84 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @private use this.handleCanvasContextMenu */
|
private getContextMenuItems = (
|
||||||
private _openContextMenu = (
|
|
||||||
{
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
}: {
|
|
||||||
left: number;
|
|
||||||
top: number;
|
|
||||||
},
|
|
||||||
type: "canvas" | "element",
|
type: "canvas" | "element",
|
||||||
) => {
|
): ContextMenuItems => {
|
||||||
trackEvent("contextMenu", "openContextMenu", type);
|
const options: ContextMenuItems = [];
|
||||||
if (this.state.showHyperlinkPopup) {
|
|
||||||
this.setState({ showHyperlinkPopup: false });
|
|
||||||
}
|
|
||||||
this.contextMenuOpen = true;
|
|
||||||
const maybeGroupAction = actionGroup.contextItemPredicate!(
|
|
||||||
this.actionManager.getElementsIncludingDeleted(),
|
|
||||||
this.actionManager.getAppState(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const maybeUngroupAction = actionUngroup.contextItemPredicate!(
|
options.push(actionCopyAsPng, actionCopyAsSvg);
|
||||||
this.actionManager.getElementsIncludingDeleted(),
|
|
||||||
this.actionManager.getAppState(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const maybeFlipHorizontal = actionFlipHorizontal.contextItemPredicate!(
|
// canvas contextMenu
|
||||||
this.actionManager.getElementsIncludingDeleted(),
|
// -------------------------------------------------------------------------
|
||||||
this.actionManager.getAppState(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const maybeFlipVertical = actionFlipVertical.contextItemPredicate!(
|
|
||||||
this.actionManager.getElementsIncludingDeleted(),
|
|
||||||
this.actionManager.getAppState(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const mayBeAllowUnbinding = actionUnbindText.contextItemPredicate(
|
|
||||||
this.actionManager.getElementsIncludingDeleted(),
|
|
||||||
this.actionManager.getAppState(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const mayBeAllowBinding = actionBindText.contextItemPredicate(
|
|
||||||
this.actionManager.getElementsIncludingDeleted(),
|
|
||||||
this.actionManager.getAppState(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const mayBeAllowToggleLineEditing =
|
|
||||||
actionToggleLinearEditor.contextItemPredicate(
|
|
||||||
this.actionManager.getElementsIncludingDeleted(),
|
|
||||||
this.actionManager.getAppState(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const separator = "separator";
|
|
||||||
|
|
||||||
const elements = this.scene.getNonDeletedElements();
|
|
||||||
|
|
||||||
const selectedElements = getSelectedElements(
|
|
||||||
this.scene.getNonDeletedElements(),
|
|
||||||
this.state,
|
|
||||||
);
|
|
||||||
|
|
||||||
const options: ContextMenuOption[] = [];
|
|
||||||
if (probablySupportsClipboardBlob && elements.length > 0) {
|
|
||||||
options.push(actionCopyAsPng);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (probablySupportsClipboardWriteText && elements.length > 0) {
|
|
||||||
options.push(actionCopyAsSvg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
type === "element" &&
|
|
||||||
copyText.contextItemPredicate(elements, this.state) &&
|
|
||||||
probablySupportsClipboardWriteText
|
|
||||||
) {
|
|
||||||
options.push(copyText);
|
|
||||||
}
|
|
||||||
if (type === "canvas") {
|
if (type === "canvas") {
|
||||||
const viewModeOptions = [
|
if (this.state.viewModeEnabled) {
|
||||||
...options,
|
return [
|
||||||
typeof this.props.gridModeEnabled === "undefined" &&
|
...options,
|
||||||
actionToggleGridMode,
|
actionToggleGridMode,
|
||||||
typeof this.props.zenModeEnabled === "undefined" && actionToggleZenMode,
|
actionToggleZenMode,
|
||||||
typeof this.props.viewModeEnabled === "undefined" &&
|
|
||||||
actionToggleViewMode,
|
actionToggleViewMode,
|
||||||
|
actionToggleStats,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
actionPaste,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionCopyAsPng,
|
||||||
|
actionCopyAsSvg,
|
||||||
|
copyText,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionSelectAll,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionToggleGridMode,
|
||||||
|
actionToggleZenMode,
|
||||||
|
actionToggleViewMode,
|
||||||
actionToggleStats,
|
actionToggleStats,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (this.state.viewModeEnabled) {
|
|
||||||
ContextMenu.push({
|
|
||||||
options: viewModeOptions,
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
actionManager: this.actionManager,
|
|
||||||
appState: this.state,
|
|
||||||
container: this.excalidrawContainerRef.current!,
|
|
||||||
elements,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ContextMenu.push({
|
|
||||||
options: [
|
|
||||||
this.device.isMobile &&
|
|
||||||
navigator.clipboard && {
|
|
||||||
trackEvent: false,
|
|
||||||
name: "paste",
|
|
||||||
perform: (elements, appStates) => {
|
|
||||||
this.pasteFromClipboard(null);
|
|
||||||
return {
|
|
||||||
commitToHistory: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
contextItemLabel: "labels.paste",
|
|
||||||
},
|
|
||||||
this.device.isMobile && navigator.clipboard && separator,
|
|
||||||
probablySupportsClipboardBlob &&
|
|
||||||
elements.length > 0 &&
|
|
||||||
actionCopyAsPng,
|
|
||||||
probablySupportsClipboardWriteText &&
|
|
||||||
elements.length > 0 &&
|
|
||||||
actionCopyAsSvg,
|
|
||||||
probablySupportsClipboardWriteText &&
|
|
||||||
selectedElements.length > 0 &&
|
|
||||||
copyText,
|
|
||||||
((probablySupportsClipboardBlob && elements.length > 0) ||
|
|
||||||
(probablySupportsClipboardWriteText && elements.length > 0)) &&
|
|
||||||
separator,
|
|
||||||
actionSelectAll,
|
|
||||||
separator,
|
|
||||||
typeof this.props.gridModeEnabled === "undefined" &&
|
|
||||||
actionToggleGridMode,
|
|
||||||
typeof this.props.zenModeEnabled === "undefined" &&
|
|
||||||
actionToggleZenMode,
|
|
||||||
typeof this.props.viewModeEnabled === "undefined" &&
|
|
||||||
actionToggleViewMode,
|
|
||||||
actionToggleStats,
|
|
||||||
],
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
actionManager: this.actionManager,
|
|
||||||
appState: this.state,
|
|
||||||
container: this.excalidrawContainerRef.current!,
|
|
||||||
elements,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (type === "element") {
|
|
||||||
if (this.state.viewModeEnabled) {
|
|
||||||
ContextMenu.push({
|
|
||||||
options: [navigator.clipboard && actionCopy, ...options],
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
actionManager: this.actionManager,
|
|
||||||
appState: this.state,
|
|
||||||
container: this.excalidrawContainerRef.current!,
|
|
||||||
elements,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ContextMenu.push({
|
|
||||||
options: [
|
|
||||||
this.device.isMobile && actionCut,
|
|
||||||
this.device.isMobile && navigator.clipboard && actionCopy,
|
|
||||||
this.device.isMobile &&
|
|
||||||
navigator.clipboard && {
|
|
||||||
name: "paste",
|
|
||||||
trackEvent: false,
|
|
||||||
perform: (elements, appStates) => {
|
|
||||||
this.pasteFromClipboard(null);
|
|
||||||
return {
|
|
||||||
commitToHistory: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
contextItemLabel: "labels.paste",
|
|
||||||
},
|
|
||||||
this.device.isMobile && separator,
|
|
||||||
...options,
|
|
||||||
separator,
|
|
||||||
actionCopyStyles,
|
|
||||||
actionPasteStyles,
|
|
||||||
separator,
|
|
||||||
maybeGroupAction && actionGroup,
|
|
||||||
mayBeAllowUnbinding && actionUnbindText,
|
|
||||||
mayBeAllowBinding && actionBindText,
|
|
||||||
maybeUngroupAction && actionUngroup,
|
|
||||||
(maybeGroupAction || maybeUngroupAction) && separator,
|
|
||||||
actionAddToLibrary,
|
|
||||||
separator,
|
|
||||||
actionSendBackward,
|
|
||||||
actionBringForward,
|
|
||||||
actionSendToBack,
|
|
||||||
actionBringToFront,
|
|
||||||
separator,
|
|
||||||
maybeFlipHorizontal && actionFlipHorizontal,
|
|
||||||
maybeFlipVertical && actionFlipVertical,
|
|
||||||
(maybeFlipHorizontal || maybeFlipVertical) && separator,
|
|
||||||
mayBeAllowToggleLineEditing && actionToggleLinearEditor,
|
|
||||||
actionLink.contextItemPredicate(elements, this.state) && actionLink,
|
|
||||||
actionDuplicateSelection,
|
|
||||||
actionToggleLock,
|
|
||||||
separator,
|
|
||||||
actionDeleteSelected,
|
|
||||||
],
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
actionManager: this.actionManager,
|
|
||||||
appState: this.state,
|
|
||||||
container: this.excalidrawContainerRef.current!,
|
|
||||||
elements,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// element contextMenu
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
options.push(copyText);
|
||||||
|
|
||||||
|
if (this.state.viewModeEnabled) {
|
||||||
|
return [actionCopy, ...options];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
actionCut,
|
||||||
|
actionCopy,
|
||||||
|
actionPaste,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
...options,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionCopyStyles,
|
||||||
|
actionPasteStyles,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionGroup,
|
||||||
|
actionUnbindText,
|
||||||
|
actionBindText,
|
||||||
|
actionUngroup,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionAddToLibrary,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionSendBackward,
|
||||||
|
actionBringForward,
|
||||||
|
actionSendToBack,
|
||||||
|
actionBringToFront,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionFlipHorizontal,
|
||||||
|
actionFlipVertical,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionToggleLinearEditor,
|
||||||
|
actionLink,
|
||||||
|
actionDuplicateSelection,
|
||||||
|
actionToggleLock,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionDeleteSelected,
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleWheel = withBatchedUpdates((event: WheelEvent) => {
|
private handleWheel = withBatchedUpdates((event: WheelEvent) => {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
color: var(--popup-text-color);
|
color: var(--popup-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-option {
|
.context-menu-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 9.5rem;
|
min-width: 9.5rem;
|
||||||
@ -43,16 +43,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.dangerous {
|
&.dangerous {
|
||||||
.context-menu-option__label {
|
.context-menu-item__label {
|
||||||
color: $oc-red-7;
|
color: $oc-red-7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-option__label {
|
.context-menu-item__label {
|
||||||
justify-self: start;
|
justify-self: start;
|
||||||
margin-inline-end: 20px;
|
margin-inline-end: 20px;
|
||||||
}
|
}
|
||||||
.context-menu-option__shortcut {
|
.context-menu-item__shortcut {
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
@ -60,37 +60,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-option:hover {
|
.context-menu-item:hover {
|
||||||
color: var(--popup-bg-color);
|
color: var(--popup-bg-color);
|
||||||
background-color: var(--select-highlight-color);
|
background-color: var(--select-highlight-color);
|
||||||
|
|
||||||
&.dangerous {
|
&.dangerous {
|
||||||
.context-menu-option__label {
|
.context-menu-item__label {
|
||||||
color: var(--popup-bg-color);
|
color: var(--popup-bg-color);
|
||||||
}
|
}
|
||||||
background-color: $oc-red-6;
|
background-color: $oc-red-6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-option:focus {
|
.context-menu-item:focus {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include isMobile {
|
@include isMobile {
|
||||||
.context-menu-option {
|
.context-menu-item {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
.context-menu-option__label {
|
.context-menu-item__label {
|
||||||
margin-inline-end: 0;
|
margin-inline-end: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-option__shortcut {
|
.context-menu-item__shortcut {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-option-separator {
|
.context-menu-item-separator {
|
||||||
border: none;
|
border: none;
|
||||||
border-top: 1px solid $oc-gray-5;
|
border-top: 1px solid $oc-gray-5;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { createRoot, Root } from "react-dom/client";
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Popover } from "./Popover";
|
import { Popover } from "./Popover";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
@ -10,135 +9,116 @@ import {
|
|||||||
} from "../actions/shortcuts";
|
} from "../actions/shortcuts";
|
||||||
import { Action } from "../actions/types";
|
import { Action } from "../actions/types";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import { AppState } from "../types";
|
import {
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
useExcalidrawAppState,
|
||||||
|
useExcalidrawElements,
|
||||||
|
useExcalidrawSetAppState,
|
||||||
|
} from "./App";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export type ContextMenuOption = "separator" | Action;
|
export type ContextMenuItem = typeof CONTEXT_MENU_SEPARATOR | Action;
|
||||||
|
|
||||||
|
export type ContextMenuItems = (ContextMenuItem | false | null | undefined)[];
|
||||||
|
|
||||||
type ContextMenuProps = {
|
type ContextMenuProps = {
|
||||||
options: ContextMenuOption[];
|
actionManager: ActionManager;
|
||||||
onCloseRequest?(): void;
|
items: ContextMenuItems;
|
||||||
top: number;
|
top: number;
|
||||||
left: number;
|
left: number;
|
||||||
actionManager: ActionManager;
|
|
||||||
appState: Readonly<AppState>;
|
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ContextMenu = ({
|
export const CONTEXT_MENU_SEPARATOR = "separator";
|
||||||
options,
|
|
||||||
onCloseRequest,
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
actionManager,
|
|
||||||
appState,
|
|
||||||
elements,
|
|
||||||
}: ContextMenuProps) => {
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
onCloseRequest={onCloseRequest}
|
|
||||||
top={top}
|
|
||||||
left={left}
|
|
||||||
fitInViewport={true}
|
|
||||||
offsetLeft={appState.offsetLeft}
|
|
||||||
offsetTop={appState.offsetTop}
|
|
||||||
viewportWidth={appState.width}
|
|
||||||
viewportHeight={appState.height}
|
|
||||||
>
|
|
||||||
<ul
|
|
||||||
className="context-menu"
|
|
||||||
onContextMenu={(event) => event.preventDefault()}
|
|
||||||
>
|
|
||||||
{options.map((option, idx) => {
|
|
||||||
if (option === "separator") {
|
|
||||||
return <hr key={idx} className="context-menu-option-separator" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionName = option.name;
|
export const ContextMenu = React.memo(
|
||||||
let label = "";
|
({ actionManager, items, top, left }: ContextMenuProps) => {
|
||||||
if (option.contextItemLabel) {
|
const appState = useExcalidrawAppState();
|
||||||
if (typeof option.contextItemLabel === "function") {
|
const setAppState = useExcalidrawSetAppState();
|
||||||
label = t(option.contextItemLabel(elements, appState));
|
const elements = useExcalidrawElements();
|
||||||
} else {
|
|
||||||
label = t(option.contextItemLabel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<li key={idx} data-testid={actionName} onClick={onCloseRequest}>
|
|
||||||
<button
|
|
||||||
className={clsx("context-menu-option", {
|
|
||||||
dangerous: actionName === "deleteSelectedElements",
|
|
||||||
checkmark: option.checked?.(appState),
|
|
||||||
})}
|
|
||||||
onClick={() =>
|
|
||||||
actionManager.executeAction(option, "contextMenu")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="context-menu-option__label">{label}</div>
|
|
||||||
<kbd className="context-menu-option__shortcut">
|
|
||||||
{actionName
|
|
||||||
? getShortcutFromShortcutName(actionName as ShortcutName)
|
|
||||||
: ""}
|
|
||||||
</kbd>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const contextMenuRoots = new WeakMap<HTMLElement, Root>();
|
const filteredItems = items.reduce((acc: ContextMenuItem[], item) => {
|
||||||
|
if (
|
||||||
const getContextMenuRoot = (container: HTMLElement): Root => {
|
item &&
|
||||||
let contextMenuRoot = contextMenuRoots.get(container);
|
(item === CONTEXT_MENU_SEPARATOR ||
|
||||||
if (contextMenuRoot) {
|
!item.contextItemPredicate ||
|
||||||
return contextMenuRoot;
|
item.contextItemPredicate(
|
||||||
}
|
elements,
|
||||||
contextMenuRoot = createRoot(
|
appState,
|
||||||
container.querySelector(".excalidraw-contextMenuContainer")!,
|
actionManager.app.props,
|
||||||
);
|
actionManager.app,
|
||||||
contextMenuRoots.set(container, contextMenuRoot);
|
))
|
||||||
return contextMenuRoot;
|
) {
|
||||||
};
|
acc.push(item);
|
||||||
|
|
||||||
const handleClose = (container: HTMLElement) => {
|
|
||||||
const contextMenuRoot = contextMenuRoots.get(container);
|
|
||||||
if (contextMenuRoot) {
|
|
||||||
contextMenuRoot.unmount();
|
|
||||||
contextMenuRoots.delete(container);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
push(params: {
|
|
||||||
options: (ContextMenuOption | false | null | undefined)[];
|
|
||||||
top: ContextMenuProps["top"];
|
|
||||||
left: ContextMenuProps["left"];
|
|
||||||
actionManager: ContextMenuProps["actionManager"];
|
|
||||||
appState: Readonly<AppState>;
|
|
||||||
container: HTMLElement;
|
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
|
||||||
}) {
|
|
||||||
const options = Array.of<ContextMenuOption>();
|
|
||||||
params.options.forEach((option) => {
|
|
||||||
if (option) {
|
|
||||||
options.push(option);
|
|
||||||
}
|
}
|
||||||
});
|
return acc;
|
||||||
if (options.length) {
|
}, []);
|
||||||
getContextMenuRoot(params.container).render(
|
|
||||||
<ContextMenu
|
return (
|
||||||
top={params.top}
|
<Popover
|
||||||
left={params.left}
|
onCloseRequest={() => setAppState({ contextMenu: null })}
|
||||||
options={options}
|
top={top}
|
||||||
onCloseRequest={() => handleClose(params.container)}
|
left={left}
|
||||||
actionManager={params.actionManager}
|
fitInViewport={true}
|
||||||
appState={params.appState}
|
offsetLeft={appState.offsetLeft}
|
||||||
elements={params.elements}
|
offsetTop={appState.offsetTop}
|
||||||
/>,
|
viewportWidth={appState.width}
|
||||||
);
|
viewportHeight={appState.height}
|
||||||
}
|
>
|
||||||
|
<ul
|
||||||
|
className="context-menu"
|
||||||
|
onContextMenu={(event) => event.preventDefault()}
|
||||||
|
>
|
||||||
|
{filteredItems.map((item, idx) => {
|
||||||
|
if (item === CONTEXT_MENU_SEPARATOR) {
|
||||||
|
if (
|
||||||
|
!filteredItems[idx - 1] ||
|
||||||
|
filteredItems[idx - 1] === CONTEXT_MENU_SEPARATOR
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <hr key={idx} className="context-menu-item-separator" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionName = item.name;
|
||||||
|
let label = "";
|
||||||
|
if (item.contextItemLabel) {
|
||||||
|
if (typeof item.contextItemLabel === "function") {
|
||||||
|
label = t(item.contextItemLabel(elements, appState));
|
||||||
|
} else {
|
||||||
|
label = t(item.contextItemLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={idx}
|
||||||
|
data-testid={actionName}
|
||||||
|
onClick={() => {
|
||||||
|
// we need update state before executing the action in case
|
||||||
|
// the action uses the appState it's being passed (that still
|
||||||
|
// contains a defined contextMenu) to return the next state.
|
||||||
|
setAppState({ contextMenu: null }, () => {
|
||||||
|
actionManager.executeAction(item, "contextMenu");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className={clsx("context-menu-item", {
|
||||||
|
dangerous: actionName === "deleteSelectedElements",
|
||||||
|
checkmark: item.checked?.(appState),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className="context-menu-item__label">{label}</div>
|
||||||
|
<kbd className="context-menu-item__shortcut">
|
||||||
|
{actionName
|
||||||
|
? getShortcutFromShortcutName(actionName as ShortcutName)
|
||||||
|
: ""}
|
||||||
|
</kbd>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -544,6 +545,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -1085,6 +1087,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -1991,6 +1994,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -2220,6 +2224,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -2752,6 +2757,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -3040,6 +3046,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -3223,6 +3230,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -3738,6 +3746,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "#fa5252",
|
"currentItemBackgroundColor": "#fa5252",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -4005,6 +4014,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -4234,6 +4244,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -4509,6 +4520,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -4796,6 +4808,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -5213,6 +5226,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -5553,6 +5567,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -5866,6 +5881,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -6103,6 +6119,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -6288,6 +6305,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -6815,6 +6833,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -7179,6 +7198,7 @@ Object {
|
|||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -9530,6 +9550,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -9948,6 +9969,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "#fa5252",
|
"currentItemBackgroundColor": "#fa5252",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -10236,6 +10258,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -10483,6 +10506,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -10803,6 +10827,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -10986,6 +11011,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -11169,6 +11195,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -11352,6 +11379,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -11588,6 +11616,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -11824,6 +11853,7 @@ Object {
|
|||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -12051,6 +12081,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -12287,6 +12318,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -12470,6 +12502,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -12706,6 +12739,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -12889,6 +12923,7 @@ Object {
|
|||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -13116,6 +13151,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -13299,6 +13335,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -14137,6 +14174,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -14425,6 +14463,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -14535,6 +14574,7 @@ Object {
|
|||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -14643,6 +14683,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -14829,6 +14870,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -15196,6 +15238,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -15826,6 +15869,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "#fa5252",
|
"currentItemBackgroundColor": "#fa5252",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -16051,6 +16095,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -17013,6 +17058,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -17121,6 +17167,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -17979,6 +18026,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -18450,6 +18498,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -18790,6 +18839,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -18900,6 +18950,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -19470,6 +19521,7 @@ Object {
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
@ -19578,6 +19630,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
|
@ -152,7 +152,7 @@ describe("element locking", () => {
|
|||||||
expect(contextMenu).not.toBeNull();
|
expect(contextMenu).not.toBeNull();
|
||||||
expect(
|
expect(
|
||||||
contextMenu?.querySelector(
|
contextMenu?.querySelector(
|
||||||
`li[data-testid="toggleLock"] .context-menu-option__label`,
|
`li[data-testid="toggleLock"] .context-menu-item__label`,
|
||||||
),
|
),
|
||||||
).toHaveTextContent(t("labels.elementLock.unlock"));
|
).toHaveTextContent(t("labels.elementLock.unlock"));
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ Object {
|
|||||||
"type": "selection",
|
"type": "selection",
|
||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
|
"contextMenu": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "transparent",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
|
@ -30,6 +30,7 @@ import { MaybeTransformHandleType } from "./element/transformHandles";
|
|||||||
import Library from "./data/library";
|
import Library from "./data/library";
|
||||||
import type { FileSystemHandle } from "./data/filesystem";
|
import type { FileSystemHandle } from "./data/filesystem";
|
||||||
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||||
|
import { ContextMenuItems } from "./components/ContextMenu";
|
||||||
|
|
||||||
export type Point = Readonly<RoughPoint>;
|
export type Point = Readonly<RoughPoint>;
|
||||||
|
|
||||||
@ -92,6 +93,11 @@ export type LastActiveToolBeforeEraser =
|
|||||||
| null;
|
| null;
|
||||||
|
|
||||||
export type AppState = {
|
export type AppState = {
|
||||||
|
contextMenu: {
|
||||||
|
items: ContextMenuItems;
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
} | null;
|
||||||
showWelcomeScreen: boolean;
|
showWelcomeScreen: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
errorMessage: string | null;
|
errorMessage: string | null;
|
||||||
@ -147,6 +153,7 @@ export type AppState = {
|
|||||||
isResizing: boolean;
|
isResizing: boolean;
|
||||||
isRotating: boolean;
|
isRotating: boolean;
|
||||||
zoom: Zoom;
|
zoom: Zoom;
|
||||||
|
// mobile-only
|
||||||
openMenu: "canvas" | "shape" | null;
|
openMenu: "canvas" | "shape" | null;
|
||||||
openPopup:
|
openPopup:
|
||||||
| "canvasColorPicker"
|
| "canvasColorPicker"
|
||||||
@ -407,6 +414,7 @@ export type AppClassProperties = {
|
|||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
device: App["device"];
|
device: App["device"];
|
||||||
scene: App["scene"];
|
scene: App["scene"];
|
||||||
|
pasteFromClipboard: App["pasteFromClipboard"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PointerDownState = Readonly<{
|
export type PointerDownState = Readonly<{
|
||||||
|
Loading…
Reference in New Issue
Block a user