fix: stale appState of MainMenu defaultItems rendered from Actions (#6074)

This commit is contained in:
David Luzar 2023-01-06 14:32:55 +01:00 committed by GitHub
parent 9803a85381
commit 40d53d9231
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 463 additions and 431 deletions

View File

@ -26,7 +26,7 @@ export const actionUnbindText = register({
name: "unbindText",
contextItemLabel: "labels.unbindText",
trackEvent: { category: "element" },
contextItemPredicate: (elements, appState) => {
predicate: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState);
return selectedElements.some((element) => hasBoundTextElement(element));
},
@ -76,7 +76,7 @@ export const actionBindText = register({
name: "bindText",
contextItemLabel: "labels.bindText",
trackEvent: { category: "element" },
contextItemPredicate: (elements, appState) => {
predicate: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState);
if (selectedElements.length === 2) {

View File

@ -1,11 +1,5 @@
import { ColorPicker } from "../components/ColorPicker";
import {
eraser,
MoonIcon,
SunIcon,
ZoomInIcon,
ZoomOutIcon,
} from "../components/icons";
import { eraser, ZoomInIcon, ZoomOutIcon } from "../components/icons";
import { ToolButton } from "../components/ToolButton";
import { MIN_ZOOM, THEME, ZOOM_STEP } from "../constants";
import { getCommonBounds, getNonDeletedElements } from "../element";
@ -21,14 +15,17 @@ import { register } from "./register";
import { Tooltip } from "../components/Tooltip";
import { newElementWith } from "../element/mutateElement";
import { getDefaultAppState, isEraserActive } from "../appState";
import ClearCanvas from "../components/ClearCanvas";
import clsx from "clsx";
import DropdownMenuItem from "../components/dropdownMenu/DropdownMenuItem";
import { getShortcutFromShortcutName } from "./shortcuts";
export const actionChangeViewBackgroundColor = register({
name: "changeViewBackgroundColor",
trackEvent: false,
predicate: (elements, appState, props, app) => {
return (
!!app.props.UIOptions.canvasActions.changeViewBackgroundColor &&
!appState.viewModeEnabled
);
},
perform: (_, appState, value) => {
return {
appState: { ...appState, ...value },
@ -36,6 +33,7 @@ export const actionChangeViewBackgroundColor = register({
};
},
PanelComponent: ({ elements, appState, updateData }) => {
// FIXME move me to src/components/mainMenu/DefaultItems.tsx
return (
<div style={{ position: "relative" }}>
<ColorPicker
@ -59,6 +57,12 @@ export const actionChangeViewBackgroundColor = register({
export const actionClearCanvas = register({
name: "clearCanvas",
trackEvent: { category: "canvas" },
predicate: (elements, appState, props, app) => {
return (
!!app.props.UIOptions.canvasActions.clearCanvas &&
!appState.viewModeEnabled
);
},
perform: (elements, appState, _, app) => {
app.imageCache.clear();
return {
@ -84,8 +88,6 @@ export const actionClearCanvas = register({
commitToHistory: true,
};
},
PanelComponent: ({ updateData }) => <ClearCanvas onConfirm={updateData} />,
});
export const actionZoomIn = register({
@ -298,26 +300,10 @@ export const actionToggleTheme = register({
commitToHistory: false,
};
},
PanelComponent: ({ appState, updateData }) => (
<DropdownMenuItem
onSelect={() => {
updateData(appState.theme === THEME.LIGHT ? THEME.DARK : THEME.LIGHT);
}}
icon={appState.theme === "dark" ? SunIcon : MoonIcon}
dataTestId="toggle-dark-mode"
shortcut={getShortcutFromShortcutName("toggleTheme")}
ariaLabel={
appState.theme === "dark"
? t("buttons.lightMode")
: t("buttons.darkMode")
}
>
{appState.theme === "dark"
? t("buttons.lightMode")
: t("buttons.darkMode")}
</DropdownMenuItem>
),
keyTest: (event) => event.altKey && event.shiftKey && event.code === CODES.D,
predicate: (elements, appState, props, app) => {
return !!app.props.UIOptions.canvasActions.toggleTheme;
},
});
export const actionErase = register({

View File

@ -24,7 +24,7 @@ export const actionCopy = register({
commitToHistory: false,
};
},
contextItemPredicate: (elements, appState, appProps, app) => {
predicate: (elements, appState, appProps, app) => {
return app.device.isMobile && !!navigator.clipboard;
},
contextItemLabel: "labels.copy",
@ -41,7 +41,7 @@ export const actionPaste = register({
commitToHistory: false,
};
},
contextItemPredicate: (elements, appState, appProps, app) => {
predicate: (elements, appState, appProps, app) => {
return app.device.isMobile && !!navigator.clipboard;
},
contextItemLabel: "labels.paste",
@ -56,7 +56,7 @@ export const actionCut = register({
actionCopy.perform(elements, appState, data, app);
return actionDeleteSelected.perform(elements, appState);
},
contextItemPredicate: (elements, appState, appProps, app) => {
predicate: (elements, appState, appProps, app) => {
return app.device.isMobile && !!navigator.clipboard;
},
contextItemLabel: "labels.cut",
@ -101,7 +101,7 @@ export const actionCopyAsSvg = register({
};
}
},
contextItemPredicate: (elements) => {
predicate: (elements) => {
return probablySupportsClipboardWriteText && elements.length > 0;
},
contextItemLabel: "labels.copyAsSvg",
@ -158,7 +158,7 @@ export const actionCopyAsPng = register({
};
}
},
contextItemPredicate: (elements) => {
predicate: (elements) => {
return probablySupportsClipboardBlob && elements.length > 0;
},
contextItemLabel: "labels.copyAsPng",
@ -188,7 +188,7 @@ export const copyText = register({
commitToHistory: false,
};
},
contextItemPredicate: (elements, appState) => {
predicate: (elements, appState) => {
return (
probablySupportsClipboardWriteText &&
getSelectedElements(elements, appState, true).some(isTextElement)

View File

@ -1,7 +1,6 @@
import { LoadIcon, questionCircle, saveAs } from "../components/icons";
import { questionCircle, saveAs } from "../components/icons";
import { ProjectName } from "../components/ProjectName";
import { ToolButton } from "../components/ToolButton";
import "../components/ToolIcon.scss";
import { Tooltip } from "../components/Tooltip";
import { DarkModeToggle } from "../components/DarkModeToggle";
import { loadFromJSON, saveAsJSON } from "../data";
@ -15,12 +14,11 @@ import { getExportSize } from "../scene/export";
import { DEFAULT_EXPORT_PADDING, EXPORT_SCALES, THEME } from "../constants";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import { getNonDeletedElements } from "../element";
import { ActiveFile } from "../components/ActiveFile";
import { isImageFileHandle } from "../data/blob";
import { nativeFileSystemSupported } from "../data/filesystem";
import { Theme } from "../element/types";
import DropdownMenuItem from "../components/dropdownMenu/DropdownMenuItem";
import { getShortcutFromShortcutName } from "./shortcuts";
import "../components/ToolIcon.scss";
export const actionChangeProjectName = register({
name: "changeProjectName",
@ -133,6 +131,13 @@ export const actionChangeExportEmbedScene = register({
export const actionSaveToActiveFile = register({
name: "saveToActiveFile",
trackEvent: { category: "export" },
predicate: (elements, appState, props, app) => {
return (
!!app.props.UIOptions.canvasActions.saveToActiveFile &&
!!appState.fileHandle &&
!appState.viewModeEnabled
);
},
perform: async (elements, appState, value, app) => {
const fileHandleExists = !!appState.fileHandle;
@ -169,12 +174,6 @@ export const actionSaveToActiveFile = register({
},
keyTest: (event) =>
event.key === KEYS.S && event[KEYS.CTRL_OR_CMD] && !event.shiftKey,
PanelComponent: ({ updateData, appState }) => (
<ActiveFile
onSave={() => updateData(null)}
fileName={appState.fileHandle?.name}
/>
),
});
export const actionSaveFileToDisk = register({
@ -220,6 +219,11 @@ export const actionSaveFileToDisk = register({
export const actionLoadScene = register({
name: "loadScene",
trackEvent: { category: "export" },
predicate: (elements, appState, props, app) => {
return (
!!app.props.UIOptions.canvasActions.loadScene && !appState.viewModeEnabled
);
},
perform: async (elements, appState, _, app) => {
try {
const {
@ -247,19 +251,6 @@ export const actionLoadScene = register({
}
},
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.O,
PanelComponent: ({ updateData }) => {
return (
<DropdownMenuItem
icon={LoadIcon}
onSelect={updateData}
dataTestId="load-button"
shortcut={getShortcutFromShortcutName("loadScene")}
ariaLabel={t("buttons.load")}
>
{t("buttons.load")}
</DropdownMenuItem>
);
},
});
export const actionExportWithDarkMode = register({

View File

@ -50,7 +50,7 @@ export const actionFlipHorizontal = register({
},
keyTest: (event) => event.shiftKey && event.code === "KeyH",
contextItemLabel: "labels.flipHorizontal",
contextItemPredicate: (elements, appState) =>
predicate: (elements, appState) =>
enableActionFlipHorizontal(elements, appState),
});
@ -67,7 +67,7 @@ export const actionFlipVertical = register({
keyTest: (event) =>
event.shiftKey && event.code === "KeyV" && !event[KEYS.CTRL_OR_CMD],
contextItemLabel: "labels.flipVertical",
contextItemPredicate: (elements, appState) =>
predicate: (elements, appState) =>
enableActionFlipVertical(elements, appState),
});

View File

@ -129,8 +129,7 @@ export const actionGroup = register({
};
},
contextItemLabel: "labels.group",
contextItemPredicate: (elements, appState) =>
enableActionGroup(elements, appState),
predicate: (elements, appState) => enableActionGroup(elements, appState),
keyTest: (event) =>
!event.shiftKey && event[KEYS.CTRL_OR_CMD] && event.key === KEYS.G,
PanelComponent: ({ elements, appState, updateData }) => (
@ -193,8 +192,7 @@ export const actionUngroup = register({
event[KEYS.CTRL_OR_CMD] &&
event.key === KEYS.G.toUpperCase(),
contextItemLabel: "labels.ungroup",
contextItemPredicate: (elements, appState) =>
getSelectedGroupIds(appState).length > 0,
predicate: (elements, appState) => getSelectedGroupIds(appState).length > 0,
PanelComponent: ({ elements, appState, updateData }) => (
<ToolButton

View File

@ -10,7 +10,7 @@ export const actionToggleLinearEditor = register({
trackEvent: {
category: "element",
},
contextItemPredicate: (elements, appState) => {
predicate: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState);
if (selectedElements.length === 1 && isLinearElement(selectedElements[0])) {
return true;

View File

@ -1,12 +1,10 @@
import { HamburgerMenuIcon, HelpIcon, palette } from "../components/icons";
import { HamburgerMenuIcon, palette } from "../components/icons";
import { ToolButton } from "../components/ToolButton";
import { t } from "../i18n";
import { showSelectedShapeActions, getNonDeletedElements } from "../element";
import { register } from "./register";
import { allowFullScreen, exitFullScreen, isFullScreen } from "../utils";
import { KEYS } from "../keys";
import { HelpButton } from "../components/HelpButton";
import DropdownMenuItem from "../components/dropdownMenu/DropdownMenuItem";
export const actionToggleCanvasMenu = register({
name: "toggleCanvasMenu",
@ -88,19 +86,5 @@ export const actionShortcuts = register({
commitToHistory: false,
};
},
PanelComponent: ({ updateData, isInHamburgerMenu }) =>
isInHamburgerMenu ? (
<DropdownMenuItem
dataTestId="help-menu-item"
icon={HelpIcon}
onSelect={updateData}
shortcut="?"
ariaLabel={t("helpDialog.title")}
>
{t("helpDialog.title")}
</DropdownMenuItem>
) : (
<HelpButton title={t("helpDialog.title")} onClick={updateData} />
),
keyTest: (event) => event.key === KEYS.QUESTION_MARK,
});

View File

@ -20,7 +20,7 @@ export const actionToggleGridMode = register({
};
},
checked: (appState: AppState) => appState.gridSize !== null,
contextItemPredicate: (element, appState, props) => {
predicate: (element, appState, props) => {
return typeof props.gridModeEnabled === "undefined";
},
contextItemLabel: "labels.showGrid",

View File

@ -18,7 +18,7 @@ export const actionToggleViewMode = register({
};
},
checked: (appState) => appState.viewModeEnabled,
contextItemPredicate: (elements, appState, appProps) => {
predicate: (elements, appState, appProps) => {
return typeof appProps.viewModeEnabled === "undefined";
},
contextItemLabel: "labels.viewMode",

View File

@ -18,7 +18,7 @@ export const actionToggleZenMode = register({
};
},
checked: (appState) => appState.zenModeEnabled,
contextItemPredicate: (elements, appState, appProps) => {
predicate: (elements, appState, appProps) => {
return typeof appProps.zenModeEnabled === "undefined";
},
contextItemLabel: "buttons.zenMode",

View File

@ -131,11 +131,7 @@ export class ActionManager {
/**
* @param data additional data sent to the PanelComponent
*/
renderAction = (
name: ActionName,
data?: PanelComponentProps["data"],
isInHamburgerMenu = false,
) => {
renderAction = (name: ActionName, data?: PanelComponentProps["data"]) => {
const canvasActions = this.app.props.UIOptions.canvasActions;
if (
@ -170,11 +166,20 @@ export class ActionManager {
updateData={updateData}
appProps={this.app.props}
data={data}
isInHamburgerMenu={isInHamburgerMenu}
/>
);
}
return null;
};
isActionEnabled = (action: Action) => {
const elements = this.getElementsIncludingDeleted();
const appState = this.getAppState();
return (
!action.predicate ||
action.predicate(elements, appState, this.app.props, this.app)
);
};
}

View File

@ -124,9 +124,7 @@ export type PanelComponentProps = {
export interface Action {
name: ActionName;
PanelComponent?: React.FC<
PanelComponentProps & { isInHamburgerMenu: boolean }
>;
PanelComponent?: React.FC<PanelComponentProps>;
perform: ActionFn;
keyPriority?: number;
keyTest?: (
@ -140,7 +138,7 @@ export interface Action {
elements: readonly ExcalidrawElement[],
appState: Readonly<AppState>,
) => string);
contextItemPredicate?: (
predicate?: (
elements: readonly ExcalidrawElement[],
appState: AppState,
appProps: ExcalidrawProps,

View File

@ -1,23 +0,0 @@
// TODO barnabasmolnar/editor-redesign
// this icon is not great
import { getShortcutFromShortcutName } from "../actions/shortcuts";
import { save } from "../components/icons";
import { t } from "../i18n";
import "./ActiveFile.scss";
import DropdownMenuItem from "./dropdownMenu/DropdownMenuItem";
type ActiveFileProps = {
fileName?: string;
onSave: () => void;
};
export const ActiveFile = ({ fileName, onSave }: ActiveFileProps) => (
<DropdownMenuItem
shortcut={getShortcutFromShortcutName("saveScene")}
dataTestId="save-button"
onSelect={onSave}
icon={save}
ariaLabel={`${t("buttons.save")}`}
>{`${t("buttons.save")}`}</DropdownMenuItem>
);

View File

@ -312,9 +312,9 @@ const ExcalidrawSetAppStateContext = React.createContext<
>(() => {});
ExcalidrawSetAppStateContext.displayName = "ExcalidrawSetAppStateContext";
const ExcalidrawActionManagerContext = React.createContext<
ActionManager | { renderAction: ActionManager["renderAction"] }
>({ renderAction: () => null });
const ExcalidrawActionManagerContext = React.createContext<ActionManager>(
null!,
);
ExcalidrawActionManagerContext.displayName = "ExcalidrawActionManagerContext";
export const useExcalidrawElements = () =>

View File

@ -1,41 +0,0 @@
import { useState } from "react";
import { t } from "../i18n";
import { TrashIcon } from "./icons";
import ConfirmDialog from "./ConfirmDialog";
import DropdownMenuItem from "./dropdownMenu/DropdownMenuItem";
const ClearCanvas = ({ onConfirm }: { onConfirm: () => void }) => {
const [showDialog, setShowDialog] = useState(false);
const toggleDialog = () => {
setShowDialog(!showDialog);
};
return (
<>
<DropdownMenuItem
icon={TrashIcon}
onSelect={toggleDialog}
dataTestId="clear-canvas-button"
ariaLabel={t("buttons.clearReset")}
>
{t("buttons.clearReset")}
</DropdownMenuItem>
{showDialog && (
<ConfirmDialog
onConfirm={() => {
onConfirm();
toggleDialog();
}}
onCancel={toggleDialog}
title={t("clearCanvasDialog.title")}
>
<p className="clear-canvas__content"> {t("alerts.clearReset")}</p>
</ConfirmDialog>
)}
</>
);
};
export default ClearCanvas;

View File

@ -2,48 +2,30 @@ import { t } from "../i18n";
import { UsersIcon } from "./icons";
import "./CollabButton.scss";
import DropdownMenuItem from "./dropdownMenu/DropdownMenuItem";
import clsx from "clsx";
const CollabButton = ({
isCollaborating,
collaboratorCount,
onClick,
isInHamburgerMenu = true,
}: {
isCollaborating: boolean;
collaboratorCount: number;
onClick: () => void;
isInHamburgerMenu?: boolean;
}) => {
return (
<>
{isInHamburgerMenu ? (
<DropdownMenuItem
dataTestId="collab-button"
icon={UsersIcon}
onSelect={onClick}
ariaLabel={t("labels.liveCollaboration")}
>
{t("labels.liveCollaboration")}
</DropdownMenuItem>
) : (
<button
className={clsx("collab-button", { active: isCollaborating })}
type="button"
onClick={onClick}
style={{ position: "relative" }}
title={t("labels.liveCollaboration")}
>
{UsersIcon}
{collaboratorCount > 0 && (
<div className="CollabButton-collaborators">
{collaboratorCount}
</div>
)}
</button>
<button
className={clsx("collab-button", { active: isCollaborating })}
type="button"
onClick={onClick}
style={{ position: "relative" }}
title={t("labels.liveCollaboration")}
>
{UsersIcon}
{collaboratorCount > 0 && (
<div className="CollabButton-collaborators">{collaboratorCount}</div>
)}
</>
</button>
);
};

View File

@ -39,8 +39,8 @@ export const ContextMenu = React.memo(
if (
item &&
(item === CONTEXT_MENU_SEPARATOR ||
!item.contextItemPredicate ||
item.contextItemPredicate(
!item.predicate ||
item.predicate(
elements,
appState,
actionManager.app.props,

View File

@ -183,7 +183,9 @@ const LayerUI = ({
<MainMenu>
<MainMenu.DefaultItems.LoadScene />
<MainMenu.DefaultItems.SaveToActiveFile />
{/* FIXME we should to test for this inside the item itself */}
{UIOptions.canvasActions.export && <MainMenu.DefaultItems.Export />}
{/* FIXME we should to test for this inside the item itself */}
{UIOptions.canvasActions.saveAsImage && (
<MainMenu.DefaultItems.SaveAsImage />
)}
@ -350,7 +352,6 @@ const LayerUI = ({
<UserList collaborators={appState.collaborators} />
{onCollabButtonClick && (
<CollabButton
isInHamburgerMenu={false}
isCollaborating={isCollaborating}
collaboratorCount={appState.collaborators.size}
onClick={onCollabButtonClick}

View File

@ -1,4 +1,5 @@
import clsx from "clsx";
import { actionShortcuts } from "../../actions";
import { ActionManager } from "../../actions/manager";
import { t } from "../../i18n";
import { AppState, UIChildrenComponents } from "../../types";
@ -9,6 +10,7 @@ import {
ZoomActions,
} from "../Actions";
import { useDevice } from "../App";
import { HelpButton } from "../HelpButton";
import { WelcomeScreenHelpArrow } from "../icons";
import { Section } from "../Section";
import Stack from "../Stack";
@ -86,7 +88,10 @@ const Footer = ({
</div>
</WelcomeScreenDecor>
{actionManager.renderAction("toggleShortcuts")}
<HelpButton
title={t("helpDialog.title")}
onClick={() => actionManager.executeAction(actionShortcuts)}
/>
</div>
</div>
<ExitZenModeAction

View File

@ -6,34 +6,81 @@ import {
useExcalidrawSetAppState,
useExcalidrawActionManager,
} from "../App";
import { ExportIcon, ExportImageIcon, UsersIcon } from "../icons";
import {
ExportIcon,
ExportImageIcon,
HelpIcon,
LoadIcon,
MoonIcon,
save,
SunIcon,
TrashIcon,
UsersIcon,
} from "../icons";
import { GithubIcon, DiscordIcon, TwitterIcon } from "../icons";
import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem";
import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink";
import {
actionClearCanvas,
actionLoadScene,
actionSaveToActiveFile,
actionShortcuts,
actionToggleTheme,
} from "../../actions";
import "./DefaultItems.scss";
import { useState } from "react";
import ConfirmDialog from "../ConfirmDialog";
export const LoadScene = () => {
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
if (appState.viewModeEnabled) {
if (!actionManager.isActionEnabled(actionLoadScene)) {
return null;
}
return actionManager.renderAction("loadScene");
return (
<DropdownMenuItem
icon={LoadIcon}
onSelect={() => actionManager.executeAction(actionLoadScene)}
dataTestId="load-button"
shortcut={getShortcutFromShortcutName("loadScene")}
ariaLabel={t("buttons.load")}
>
{t("buttons.load")}
</DropdownMenuItem>
);
};
LoadScene.displayName = "LoadScene";
export const SaveToActiveFile = () => {
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
if (!appState.fileHandle) {
if (!actionManager.isActionEnabled(actionSaveToActiveFile)) {
return null;
}
return actionManager.renderAction("saveToActiveFile");
return (
<DropdownMenuItem
shortcut={getShortcutFromShortcutName("saveScene")}
dataTestId="save-button"
onSelect={() => actionManager.executeAction(actionSaveToActiveFile)}
icon={save}
ariaLabel={`${t("buttons.save")}`}
>{`${t("buttons.save")}`}</DropdownMenuItem>
);
};
SaveToActiveFile.displayName = "SaveToActiveFile";
export const SaveAsImage = () => {
const setAppState = useExcalidrawSetAppState();
// Hack until we tie "t" to lang state
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
return (
@ -51,32 +98,96 @@ export const SaveAsImage = () => {
SaveAsImage.displayName = "SaveAsImage";
export const Help = () => {
// Hack until we tie "t" to lang state
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
return actionManager.renderAction("toggleShortcuts", undefined, true);
return (
<DropdownMenuItem
dataTestId="help-menu-item"
icon={HelpIcon}
onSelect={() => actionManager.executeAction(actionShortcuts)}
shortcut="?"
ariaLabel={t("helpDialog.title")}
>
{t("helpDialog.title")}
</DropdownMenuItem>
);
};
Help.displayName = "Help";
export const ClearCanvas = () => {
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
if (appState.viewModeEnabled) {
const [showDialog, setShowDialog] = useState(false);
const toggleDialog = () => setShowDialog(!showDialog);
if (!actionManager.isActionEnabled(actionClearCanvas)) {
return null;
}
return actionManager.renderAction("clearCanvas");
return (
<>
<DropdownMenuItem
icon={TrashIcon}
onSelect={toggleDialog}
dataTestId="clear-canvas-button"
ariaLabel={t("buttons.clearReset")}
>
{t("buttons.clearReset")}
</DropdownMenuItem>
{/* FIXME this should live outside MainMenu so it stays open
if menu is closed */}
{showDialog && (
<ConfirmDialog
onConfirm={() => {
actionManager.executeAction(actionClearCanvas);
toggleDialog();
}}
onCancel={toggleDialog}
title={t("clearCanvasDialog.title")}
>
<p className="clear-canvas__content"> {t("alerts.clearReset")}</p>
</ConfirmDialog>
)}
</>
);
};
ClearCanvas.displayName = "ClearCanvas";
export const ToggleTheme = () => {
// Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
return actionManager.renderAction("toggleTheme");
if (!actionManager.isActionEnabled(actionToggleTheme)) {
return null;
}
return (
<DropdownMenuItem
onSelect={() => {
return actionManager.executeAction(actionToggleTheme);
}}
icon={appState.theme === "dark" ? SunIcon : MoonIcon}
dataTestId="toggle-dark-mode"
shortcut={getShortcutFromShortcutName("toggleTheme")}
ariaLabel={
appState.theme === "dark"
? t("buttons.lightMode")
: t("buttons.darkMode")
}
>
{appState.theme === "dark"
? t("buttons.lightMode")
: t("buttons.darkMode")}
</DropdownMenuItem>
);
};
ToggleTheme.displayName = "ToggleTheme";
@ -101,7 +212,7 @@ export const ChangeCanvasBackground = () => {
ChangeCanvasBackground.displayName = "ChangeCanvasBackground";
export const Export = () => {
// Hack until we tie "t" to lang state
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
const setAppState = useExcalidrawSetAppState();
@ -154,7 +265,7 @@ export const LiveCollaboration = ({
onSelect: () => void;
isCollaborating: boolean;
}) => {
// Hack until we tie "t" to lang state
// FIXME Hack until we tie "t" to lang state
// eslint-disable-next-line
const appState = useExcalidrawAppState();
return (

View File

@ -267,7 +267,7 @@ export const actionLink = register({
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.K,
contextItemLabel: (elements, appState) =>
getContextMenuLabel(elements, appState),
contextItemPredicate: (elements, appState) => {
predicate: (elements, appState) => {
const selectedElements = getSelectedElements(elements, appState);
return selectedElements.length === 1;
},

View File

@ -13,30 +13,30 @@ Object {
"items": Array [
Object {
"contextItemLabel": "labels.cut",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "cut",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copy",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "copy",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.paste",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "paste",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -44,28 +44,28 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.copyAsPng",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "copyAsPng",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyAsSvg",
"contextItemPredicate": [Function],
"name": "copyAsSvg",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyText",
"contextItemPredicate": [Function],
"name": "copyText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -93,28 +93,28 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.group",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "group",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.unbindText",
"contextItemPredicate": [Function],
"name": "unbindText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.bindText",
"contextItemPredicate": [Function],
"name": "bindText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -122,10 +122,10 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.ungroup",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "ungroup",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -185,20 +185,20 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.flipHorizontal",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "flipHorizontal",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.flipVertical",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "flipVertical",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -206,9 +206,9 @@ Object {
"separator",
Object {
"contextItemLabel": [Function],
"contextItemPredicate": [Function],
"name": "toggleLinearEditor",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -216,10 +216,10 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": [Function],
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "hyperlink",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"action": "click",
"category": "hyperlink",
@ -4401,30 +4401,30 @@ Object {
"items": Array [
Object {
"contextItemLabel": "labels.cut",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "cut",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copy",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "copy",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.paste",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "paste",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -4432,28 +4432,28 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.copyAsPng",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "copyAsPng",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyAsSvg",
"contextItemPredicate": [Function],
"name": "copyAsSvg",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyText",
"contextItemPredicate": [Function],
"name": "copyText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -4481,28 +4481,28 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.group",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "group",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.unbindText",
"contextItemPredicate": [Function],
"name": "unbindText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.bindText",
"contextItemPredicate": [Function],
"name": "bindText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -4510,10 +4510,10 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.ungroup",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "ungroup",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -4573,20 +4573,20 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.flipHorizontal",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "flipHorizontal",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.flipVertical",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "flipVertical",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -4594,9 +4594,9 @@ Object {
"separator",
Object {
"contextItemLabel": [Function],
"contextItemPredicate": [Function],
"name": "toggleLinearEditor",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -4604,10 +4604,10 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": [Function],
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "hyperlink",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"action": "click",
"category": "hyperlink",
@ -4942,30 +4942,30 @@ Object {
"items": Array [
Object {
"contextItemLabel": "labels.cut",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "cut",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copy",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "copy",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.paste",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "paste",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -4973,28 +4973,28 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.copyAsPng",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "copyAsPng",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyAsSvg",
"contextItemPredicate": [Function],
"name": "copyAsSvg",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyText",
"contextItemPredicate": [Function],
"name": "copyText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5022,28 +5022,28 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.group",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "group",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.unbindText",
"contextItemPredicate": [Function],
"name": "unbindText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.bindText",
"contextItemPredicate": [Function],
"name": "bindText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5051,10 +5051,10 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.ungroup",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "ungroup",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5114,20 +5114,20 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.flipHorizontal",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "flipHorizontal",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.flipVertical",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "flipVertical",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5135,9 +5135,9 @@ Object {
"separator",
Object {
"contextItemLabel": [Function],
"contextItemPredicate": [Function],
"name": "toggleLinearEditor",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5145,10 +5145,10 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": [Function],
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "hyperlink",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"action": "click",
"category": "hyperlink",
@ -5568,10 +5568,10 @@ Object {
"items": Array [
Object {
"contextItemLabel": "labels.paste",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "paste",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5579,28 +5579,28 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.copyAsPng",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "copyAsPng",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyAsSvg",
"contextItemPredicate": [Function],
"name": "copyAsSvg",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyText",
"contextItemPredicate": [Function],
"name": "copyText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5619,10 +5619,10 @@ Object {
Object {
"checked": [Function],
"contextItemLabel": "labels.showGrid",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "gridMode",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "canvas",
"predicate": [Function],
@ -5632,10 +5632,10 @@ Object {
Object {
"checked": [Function],
"contextItemLabel": "buttons.zenMode",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "zenMode",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "canvas",
"predicate": [Function],
@ -5645,10 +5645,10 @@ Object {
Object {
"checked": [Function],
"contextItemLabel": "labels.viewMode",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "viewMode",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "canvas",
"predicate": [Function],
@ -5782,30 +5782,30 @@ Object {
"items": Array [
Object {
"contextItemLabel": "labels.cut",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "cut",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copy",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "copy",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.paste",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "paste",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5813,28 +5813,28 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.copyAsPng",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "copyAsPng",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyAsSvg",
"contextItemPredicate": [Function],
"name": "copyAsSvg",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyText",
"contextItemPredicate": [Function],
"name": "copyText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5862,28 +5862,28 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.group",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "group",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.unbindText",
"contextItemPredicate": [Function],
"name": "unbindText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.bindText",
"contextItemPredicate": [Function],
"name": "bindText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5891,10 +5891,10 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.ungroup",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "ungroup",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5954,20 +5954,20 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.flipHorizontal",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "flipHorizontal",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.flipVertical",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "flipVertical",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5975,9 +5975,9 @@ Object {
"separator",
Object {
"contextItemLabel": [Function],
"contextItemPredicate": [Function],
"name": "toggleLinearEditor",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -5985,10 +5985,10 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": [Function],
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "hyperlink",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"action": "click",
"category": "hyperlink",
@ -6119,30 +6119,30 @@ Object {
"items": Array [
Object {
"contextItemLabel": "labels.cut",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "cut",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copy",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "copy",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.paste",
"contextItemPredicate": [Function],
"keyTest": undefined,
"name": "paste",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -6150,28 +6150,28 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.copyAsPng",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "copyAsPng",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyAsSvg",
"contextItemPredicate": [Function],
"name": "copyAsSvg",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.copyText",
"contextItemPredicate": [Function],
"name": "copyText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -6199,28 +6199,28 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.group",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "group",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.unbindText",
"contextItemPredicate": [Function],
"name": "unbindText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.bindText",
"contextItemPredicate": [Function],
"name": "bindText",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -6228,10 +6228,10 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": "labels.ungroup",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "ungroup",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -6291,20 +6291,20 @@ Object {
"separator",
Object {
"contextItemLabel": "labels.flipHorizontal",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "flipHorizontal",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
},
Object {
"contextItemLabel": "labels.flipVertical",
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "flipVertical",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -6312,9 +6312,9 @@ Object {
"separator",
Object {
"contextItemLabel": [Function],
"contextItemPredicate": [Function],
"name": "toggleLinearEditor",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"category": "element",
},
@ -6322,10 +6322,10 @@ Object {
Object {
"PanelComponent": [Function],
"contextItemLabel": [Function],
"contextItemPredicate": [Function],
"keyTest": [Function],
"name": "hyperlink",
"perform": [Function],
"predicate": [Function],
"trackEvent": Object {
"action": "click",
"category": "hyperlink",

View File

@ -1,5 +1,114 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Excalidraw/> <MainMenu/> should render main menu with host menu items if passed from host 1`] = `
<div
class="dropdown-menu"
data-testid="dropdown-menu"
>
<div
class="Island dropdown-menu-container"
style="--padding: 2; z-index: 1;"
>
<button
class="dropdown-menu-item dropdown-menu-item-base"
type="button"
>
<div
class="dropdown-menu-item__icon"
/>
<div
class="dropdown-menu-item__text"
>
Click me
</div>
</button>
<a
class="dropdown-menu-item dropdown-menu-item-base"
href="blog.excalidaw.com"
rel="noreferrer"
target="_blank"
>
<div
class="dropdown-menu-item__icon"
/>
<div
class="dropdown-menu-item__text"
>
Excalidraw blog
</div>
</a>
<div
class="dropdown-menu-item-base dropdown-menu-item-custom"
>
<button
style="height: 2rem;"
>
custom menu item
</button>
</div>
<button
aria-label="Help"
class="dropdown-menu-item dropdown-menu-item-base"
data-testid="help-menu-item"
title="Help"
type="button"
>
<div
class="dropdown-menu-item__icon"
>
<svg
aria-hidden="true"
class=""
fill="none"
focusable="false"
role="img"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
>
<g
stroke-width="1.5"
>
<path
d="M0 0h24v24H0z"
fill="none"
stroke="none"
/>
<circle
cx="12"
cy="12"
r="9"
/>
<line
x1="12"
x2="12"
y1="17"
y2="17.01"
/>
<path
d="M12 13.5a1.5 1.5 0 0 1 1 -1.5a2.6 2.6 0 1 0 -3 -4"
/>
</g>
</svg>
</div>
<div
class="dropdown-menu-item__text"
>
Help
</div>
<div
class="dropdown-menu-item__shortcut"
>
?
</div>
</button>
</div>
</div>
`;
exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should render menu with default items when "UIOPtions" is "undefined" 1`] = `
<div
class="dropdown-menu"
@ -454,112 +563,3 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should render menu
</div>
</div>
`;
exports[`<Excalidraw/> should render main menu with host menu items if passed from host 1`] = `
<div
class="dropdown-menu"
data-testid="dropdown-menu"
>
<div
class="Island dropdown-menu-container"
style="--padding: 2; z-index: 1;"
>
<button
class="dropdown-menu-item dropdown-menu-item-base"
type="button"
>
<div
class="dropdown-menu-item__icon"
/>
<div
class="dropdown-menu-item__text"
>
Click me
</div>
</button>
<a
class="dropdown-menu-item dropdown-menu-item-base"
href="blog.excalidaw.com"
rel="noreferrer"
target="_blank"
>
<div
class="dropdown-menu-item__icon"
/>
<div
class="dropdown-menu-item__text"
>
Excalidraw blog
</div>
</a>
<div
class="dropdown-menu-item-base dropdown-menu-item-custom"
>
<button
style="height: 2rem;"
>
custom menu item
</button>
</div>
<button
aria-label="Help"
class="dropdown-menu-item dropdown-menu-item-base"
data-testid="help-menu-item"
title="Help"
type="button"
>
<div
class="dropdown-menu-item__icon"
>
<svg
aria-hidden="true"
class=""
fill="none"
focusable="false"
role="img"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
>
<g
stroke-width="1.5"
>
<path
d="M0 0h24v24H0z"
fill="none"
stroke="none"
/>
<circle
cx="12"
cy="12"
r="9"
/>
<line
x1="12"
x2="12"
y1="17"
y2="17.01"
/>
<path
d="M12 13.5a1.5 1.5 0 0 1 1 -1.5a2.6 2.6 0 1 0 -3 -4"
/>
</g>
</svg>
</div>
<div
class="dropdown-menu-item__text"
>
Help
</div>
<div
class="dropdown-menu-item__shortcut"
>
?
</div>
</button>
</div>
</div>
`;

View File

@ -3,6 +3,7 @@ import { Excalidraw, Footer, MainMenu } from "../../packages/excalidraw/index";
import { queryByText, queryByTestId } from "@testing-library/react";
import { GRID_SIZE, THEME } from "../../constants";
import { t } from "../../i18n";
import { useMemo } from "react";
const { h } = window;
@ -13,6 +14,7 @@ describe("<Excalidraw/>", () => {
toggleMenu(document.querySelector(".excalidraw")!);
}
});
describe("Test zenModeEnabled prop", () => {
it('should show exit zen mode button when zen mode is set and zen mode option in context menu when zenModeEnabled is "undefined"', async () => {
const { container } = await render(<Excalidraw />);
@ -121,34 +123,6 @@ describe("<Excalidraw/>", () => {
});
});
it("should render main menu with host menu items if passed from host", async () => {
const { container } = await render(
<Excalidraw UIOptions={undefined}>
<MainMenu>
<MainMenu.Item onSelect={() => window.alert("Clicked")}>
Click me
</MainMenu.Item>
<MainMenu.ItemLink href="blog.excalidaw.com">
Excalidraw blog
</MainMenu.ItemLink>
<MainMenu.ItemCustom>
<button
style={{ height: "2rem" }}
onClick={() => window.alert("custom menu item")}
>
{" "}
custom menu item
</button>
</MainMenu.ItemCustom>
<MainMenu.DefaultItems.Help />
</MainMenu>
</Excalidraw>,
);
//open menu
toggleMenu(container);
expect(queryByTestId(container, "dropdown-menu")).toMatchSnapshot();
});
describe("Test UIOptions prop", () => {
describe("Test canvasActions", () => {
it('should render menu with default items when "UIOPtions" is "undefined"', async () => {
@ -306,7 +280,7 @@ describe("<Excalidraw/>", () => {
//open menu
toggleMenu(container);
const darkModeToggle = queryByTestId(container, "toggle-dark-mode");
expect(darkModeToggle).toBeFalsy();
expect(darkModeToggle).toBe(null);
});
});
@ -336,6 +310,7 @@ describe("<Excalidraw/>", () => {
expect(textInput?.nodeName).toBe("SPAN");
});
});
describe("Test autoFocus prop", () => {
it("should not focus when autoFocus is false", async () => {
const { container } = await render(<Excalidraw />);
@ -353,4 +328,64 @@ describe("<Excalidraw/>", () => {
).toBe(true);
});
});
describe("<MainMenu/>", () => {
it("should render main menu with host menu items if passed from host", async () => {
const { container } = await render(
<Excalidraw>
<MainMenu>
<MainMenu.Item onSelect={() => window.alert("Clicked")}>
Click me
</MainMenu.Item>
<MainMenu.ItemLink href="blog.excalidaw.com">
Excalidraw blog
</MainMenu.ItemLink>
<MainMenu.ItemCustom>
<button
style={{ height: "2rem" }}
onClick={() => window.alert("custom menu item")}
>
{" "}
custom menu item
</button>
</MainMenu.ItemCustom>
<MainMenu.DefaultItems.Help />
</MainMenu>
</Excalidraw>,
);
//open menu
toggleMenu(container);
expect(queryByTestId(container, "dropdown-menu")).toMatchSnapshot();
});
it("should update themeToggle text even if MainMenu memoized", async () => {
const CustomExcalidraw = () => {
const customMenu = useMemo(() => {
return (
<MainMenu>
<MainMenu.DefaultItems.ToggleTheme />
</MainMenu>
);
}, []);
return <Excalidraw>{customMenu}</Excalidraw>;
};
const { container } = await render(<CustomExcalidraw />);
//open menu
toggleMenu(container);
expect(h.state.theme).toBe(THEME.LIGHT);
expect(
queryByTestId(container, "toggle-dark-mode")?.textContent,
).toContain(t("buttons.darkMode"));
fireEvent.click(queryByTestId(container, "toggle-dark-mode")!);
expect(
queryByTestId(container, "toggle-dark-mode")?.textContent,
).toContain(t("buttons.lightMode"));
});
});
});