2020-10-19 17:14:28 +03:00
|
|
|
import clsx from "clsx";
|
|
|
|
import { Popover } from "./Popover";
|
2021-01-28 00:41:17 +05:30
|
|
|
import { t } from "../i18n";
|
2020-01-07 07:50:59 +05:00
|
|
|
|
2020-04-10 18:09:29 -04:00
|
|
|
import "./ContextMenu.scss";
|
2020-12-12 23:03:58 +01:00
|
|
|
import {
|
|
|
|
getShortcutFromShortcutName,
|
|
|
|
ShortcutName,
|
|
|
|
} from "../actions/shortcuts";
|
2021-01-28 00:41:17 +05:30
|
|
|
import { Action } from "../actions/types";
|
|
|
|
import { ActionManager } from "../actions/manager";
|
2022-12-21 12:47:09 +01:00
|
|
|
import {
|
|
|
|
useExcalidrawAppState,
|
|
|
|
useExcalidrawElements,
|
|
|
|
useExcalidrawSetAppState,
|
|
|
|
} from "./App";
|
|
|
|
import React from "react";
|
|
|
|
|
|
|
|
export type ContextMenuItem = typeof CONTEXT_MENU_SEPARATOR | Action;
|
2020-01-07 07:50:59 +05:00
|
|
|
|
2022-12-21 12:47:09 +01:00
|
|
|
export type ContextMenuItems = (ContextMenuItem | false | null | undefined)[];
|
2020-01-07 07:50:59 +05:00
|
|
|
|
2021-01-28 00:41:17 +05:30
|
|
|
type ContextMenuProps = {
|
2022-12-21 12:47:09 +01:00
|
|
|
actionManager: ActionManager;
|
|
|
|
items: ContextMenuItems;
|
2020-01-07 07:50:59 +05:00
|
|
|
top: number;
|
|
|
|
left: number;
|
|
|
|
};
|
|
|
|
|
2022-12-21 12:47:09 +01:00
|
|
|
export const CONTEXT_MENU_SEPARATOR = "separator";
|
2021-01-28 00:41:17 +05:30
|
|
|
|
2022-12-21 12:47:09 +01:00
|
|
|
export const ContextMenu = React.memo(
|
|
|
|
({ actionManager, items, top, left }: ContextMenuProps) => {
|
|
|
|
const appState = useExcalidrawAppState();
|
|
|
|
const setAppState = useExcalidrawSetAppState();
|
|
|
|
const elements = useExcalidrawElements();
|
2020-01-07 07:50:59 +05:00
|
|
|
|
2022-12-21 12:47:09 +01:00
|
|
|
const filteredItems = items.reduce((acc: ContextMenuItem[], item) => {
|
|
|
|
if (
|
|
|
|
item &&
|
|
|
|
(item === CONTEXT_MENU_SEPARATOR ||
|
2023-01-06 14:32:55 +01:00
|
|
|
!item.predicate ||
|
|
|
|
item.predicate(
|
2022-12-21 12:47:09 +01:00
|
|
|
elements,
|
|
|
|
appState,
|
|
|
|
actionManager.app.props,
|
|
|
|
actionManager.app,
|
|
|
|
))
|
|
|
|
) {
|
|
|
|
acc.push(item);
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}, []);
|
2021-04-05 17:26:37 +02:00
|
|
|
|
2022-12-21 12:47:09 +01:00
|
|
|
return (
|
|
|
|
<Popover
|
|
|
|
onCloseRequest={() => setAppState({ contextMenu: null })}
|
|
|
|
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()}
|
|
|
|
>
|
|
|
|
{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" />;
|
|
|
|
}
|
2020-01-07 07:50:59 +05:00
|
|
|
|
2022-12-21 12:47:09 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2020-01-07 07:50:59 +05:00
|
|
|
|
2022-12-21 12:47:09 +01:00
|
|
|
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>
|
|
|
|
);
|
2020-01-24 12:04:54 +02:00
|
|
|
},
|
2022-12-21 12:47:09 +01:00
|
|
|
);
|