You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
3.9 KiB
TypeScript
129 lines
3.9 KiB
TypeScript
4 years ago
|
import clsx from "clsx";
|
||
|
import { Popover } from "./Popover";
|
||
2 years ago
|
import { t, TranslationKeys } from "../i18n";
|
||
5 years ago
|
|
||
5 years ago
|
import "./ContextMenu.scss";
|
||
4 years ago
|
import {
|
||
|
getShortcutFromShortcutName,
|
||
|
ShortcutName,
|
||
|
} from "../actions/shortcuts";
|
||
4 years ago
|
import { Action } from "../actions/types";
|
||
|
import { ActionManager } from "../actions/manager";
|
||
1 year ago
|
import { useExcalidrawAppState, useExcalidrawElements } from "./App";
|
||
2 years ago
|
import React from "react";
|
||
|
|
||
|
export type ContextMenuItem = typeof CONTEXT_MENU_SEPARATOR | Action;
|
||
5 years ago
|
|
||
2 years ago
|
export type ContextMenuItems = (ContextMenuItem | false | null | undefined)[];
|
||
5 years ago
|
|
||
4 years ago
|
type ContextMenuProps = {
|
||
2 years ago
|
actionManager: ActionManager;
|
||
|
items: ContextMenuItems;
|
||
5 years ago
|
top: number;
|
||
|
left: number;
|
||
1 year ago
|
onClose: (callback?: () => void) => void;
|
||
5 years ago
|
};
|
||
|
|
||
2 years ago
|
export const CONTEXT_MENU_SEPARATOR = "separator";
|
||
4 years ago
|
|
||
2 years ago
|
export const ContextMenu = React.memo(
|
||
1 year ago
|
({ actionManager, items, top, left, onClose }: ContextMenuProps) => {
|
||
2 years ago
|
const appState = useExcalidrawAppState();
|
||
|
const elements = useExcalidrawElements();
|
||
5 years ago
|
|
||
2 years ago
|
const filteredItems = items.reduce((acc: ContextMenuItem[], item) => {
|
||
|
if (
|
||
|
item &&
|
||
|
(item === CONTEXT_MENU_SEPARATOR ||
|
||
2 years ago
|
!item.predicate ||
|
||
|
item.predicate(
|
||
2 years ago
|
elements,
|
||
|
appState,
|
||
|
actionManager.app.props,
|
||
|
actionManager.app,
|
||
|
))
|
||
|
) {
|
||
|
acc.push(item);
|
||
|
}
|
||
|
return acc;
|
||
|
}, []);
|
||
4 years ago
|
|
||
2 years ago
|
return (
|
||
|
<Popover
|
||
1 year ago
|
onCloseRequest={() => {
|
||
|
onClose();
|
||
|
}}
|
||
2 years ago
|
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" />;
|
||
|
}
|
||
5 years ago
|
|
||
2 years ago
|
const actionName = item.name;
|
||
|
let label = "";
|
||
|
if (item.contextItemLabel) {
|
||
|
if (typeof item.contextItemLabel === "function") {
|
||
2 years ago
|
label = t(
|
||
2 years ago
|
item.contextItemLabel(
|
||
|
elements,
|
||
|
appState,
|
||
|
actionManager.app,
|
||
|
) as unknown as TranslationKeys,
|
||
2 years ago
|
);
|
||
2 years ago
|
} else {
|
||
2 years ago
|
label = t(item.contextItemLabel as unknown as TranslationKeys);
|
||
2 years ago
|
}
|
||
|
}
|
||
5 years ago
|
|
||
2 years ago
|
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.
|
||
1 year ago
|
onClose(() => {
|
||
2 years ago
|
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>
|
||
|
);
|
||
5 years ago
|
},
|
||
2 years ago
|
);
|