aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/components/main-menu/DefaultItems.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/excalidraw/components/main-menu/DefaultItems.tsx')
-rw-r--r--packages/excalidraw/components/main-menu/DefaultItems.tsx391
1 files changed, 391 insertions, 0 deletions
diff --git a/packages/excalidraw/components/main-menu/DefaultItems.tsx b/packages/excalidraw/components/main-menu/DefaultItems.tsx
new file mode 100644
index 0000000..632ea4f
--- /dev/null
+++ b/packages/excalidraw/components/main-menu/DefaultItems.tsx
@@ -0,0 +1,391 @@
+import { getShortcutFromShortcutName } from "../../actions/shortcuts";
+import { useI18n } from "../../i18n";
+import {
+ useExcalidrawSetAppState,
+ useExcalidrawActionManager,
+ useExcalidrawElements,
+ useAppProps,
+} from "../App";
+import {
+ boltIcon,
+ DeviceDesktopIcon,
+ ExportIcon,
+ ExportImageIcon,
+ HelpIcon,
+ LoadIcon,
+ MoonIcon,
+ save,
+ searchIcon,
+ SunIcon,
+ TrashIcon,
+ usersIcon,
+} from "../icons";
+import { GithubIcon, DiscordIcon, XBrandIcon } from "../icons";
+import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem";
+import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink";
+import {
+ actionClearCanvas,
+ actionLoadScene,
+ actionSaveToActiveFile,
+ actionShortcuts,
+ actionToggleSearchMenu,
+ actionToggleTheme,
+} from "../../actions";
+import clsx from "clsx";
+import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
+import { useSetAtom } from "../../editor-jotai";
+import { useUIAppState } from "../../context/ui-appState";
+import { openConfirmModal } from "../OverwriteConfirm/OverwriteConfirmState";
+import Trans from "../Trans";
+import DropdownMenuItemContentRadio from "../dropdownMenu/DropdownMenuItemContentRadio";
+import { THEME } from "../../constants";
+import type { Theme } from "../../element/types";
+import { trackEvent } from "../../analytics";
+import "./DefaultItems.scss";
+
+export const LoadScene = () => {
+ const { t } = useI18n();
+ const actionManager = useExcalidrawActionManager();
+ const elements = useExcalidrawElements();
+
+ if (!actionManager.isActionEnabled(actionLoadScene)) {
+ return null;
+ }
+
+ const handleSelect = async () => {
+ if (
+ !elements.length ||
+ (await openConfirmModal({
+ title: t("overwriteConfirm.modal.loadFromFile.title"),
+ actionLabel: t("overwriteConfirm.modal.loadFromFile.button"),
+ color: "warning",
+ description: (
+ <Trans
+ i18nKey="overwriteConfirm.modal.loadFromFile.description"
+ bold={(text) => <strong>{text}</strong>}
+ br={() => <br />}
+ />
+ ),
+ }))
+ ) {
+ actionManager.executeAction(actionLoadScene);
+ }
+ };
+
+ return (
+ <DropdownMenuItem
+ icon={LoadIcon}
+ onSelect={handleSelect}
+ data-testid="load-button"
+ shortcut={getShortcutFromShortcutName("loadScene")}
+ aria-label={t("buttons.load")}
+ >
+ {t("buttons.load")}
+ </DropdownMenuItem>
+ );
+};
+LoadScene.displayName = "LoadScene";
+
+export const SaveToActiveFile = () => {
+ const { t } = useI18n();
+ const actionManager = useExcalidrawActionManager();
+
+ if (!actionManager.isActionEnabled(actionSaveToActiveFile)) {
+ return null;
+ }
+
+ return (
+ <DropdownMenuItem
+ shortcut={getShortcutFromShortcutName("saveScene")}
+ data-testid="save-button"
+ onSelect={() => actionManager.executeAction(actionSaveToActiveFile)}
+ icon={save}
+ aria-label={`${t("buttons.save")}`}
+ >{`${t("buttons.save")}`}</DropdownMenuItem>
+ );
+};
+SaveToActiveFile.displayName = "SaveToActiveFile";
+
+export const SaveAsImage = () => {
+ const setAppState = useExcalidrawSetAppState();
+ const { t } = useI18n();
+ return (
+ <DropdownMenuItem
+ icon={ExportImageIcon}
+ data-testid="image-export-button"
+ onSelect={() => setAppState({ openDialog: { name: "imageExport" } })}
+ shortcut={getShortcutFromShortcutName("imageExport")}
+ aria-label={t("buttons.exportImage")}
+ >
+ {t("buttons.exportImage")}
+ </DropdownMenuItem>
+ );
+};
+SaveAsImage.displayName = "SaveAsImage";
+
+export const CommandPalette = (opts?: { className?: string }) => {
+ const setAppState = useExcalidrawSetAppState();
+ const { t } = useI18n();
+
+ return (
+ <DropdownMenuItem
+ icon={boltIcon}
+ data-testid="command-palette-button"
+ onSelect={() => {
+ trackEvent("command_palette", "open", "menu");
+ setAppState({ openDialog: { name: "commandPalette" } });
+ }}
+ shortcut={getShortcutFromShortcutName("commandPalette")}
+ aria-label={t("commandPalette.title")}
+ className={opts?.className}
+ >
+ {t("commandPalette.title")}
+ </DropdownMenuItem>
+ );
+};
+CommandPalette.displayName = "CommandPalette";
+
+export const SearchMenu = (opts?: { className?: string }) => {
+ const { t } = useI18n();
+ const actionManager = useExcalidrawActionManager();
+
+ return (
+ <DropdownMenuItem
+ icon={searchIcon}
+ data-testid="search-menu-button"
+ onSelect={() => {
+ actionManager.executeAction(actionToggleSearchMenu);
+ }}
+ shortcut={getShortcutFromShortcutName("searchMenu")}
+ aria-label={t("search.title")}
+ className={opts?.className}
+ >
+ {t("search.title")}
+ </DropdownMenuItem>
+ );
+};
+SearchMenu.displayName = "SearchMenu";
+
+export const Help = () => {
+ const { t } = useI18n();
+
+ const actionManager = useExcalidrawActionManager();
+
+ return (
+ <DropdownMenuItem
+ data-testid="help-menu-item"
+ icon={HelpIcon}
+ onSelect={() => actionManager.executeAction(actionShortcuts)}
+ shortcut="?"
+ aria-label={t("helpDialog.title")}
+ >
+ {t("helpDialog.title")}
+ </DropdownMenuItem>
+ );
+};
+Help.displayName = "Help";
+
+export const ClearCanvas = () => {
+ const { t } = useI18n();
+
+ const setActiveConfirmDialog = useSetAtom(activeConfirmDialogAtom);
+ const actionManager = useExcalidrawActionManager();
+
+ if (!actionManager.isActionEnabled(actionClearCanvas)) {
+ return null;
+ }
+
+ return (
+ <DropdownMenuItem
+ icon={TrashIcon}
+ onSelect={() => setActiveConfirmDialog("clearCanvas")}
+ data-testid="clear-canvas-button"
+ aria-label={t("buttons.clearReset")}
+ >
+ {t("buttons.clearReset")}
+ </DropdownMenuItem>
+ );
+};
+ClearCanvas.displayName = "ClearCanvas";
+
+export const ToggleTheme = (
+ props:
+ | {
+ allowSystemTheme: true;
+ theme: Theme | "system";
+ onSelect: (theme: Theme | "system") => void;
+ }
+ | {
+ allowSystemTheme?: false;
+ onSelect?: (theme: Theme) => void;
+ },
+) => {
+ const { t } = useI18n();
+ const appState = useUIAppState();
+ const actionManager = useExcalidrawActionManager();
+ const shortcut = getShortcutFromShortcutName("toggleTheme");
+
+ if (!actionManager.isActionEnabled(actionToggleTheme)) {
+ return null;
+ }
+
+ if (props?.allowSystemTheme) {
+ return (
+ <DropdownMenuItemContentRadio
+ name="theme"
+ value={props.theme}
+ onChange={(value: Theme | "system") => props.onSelect(value)}
+ choices={[
+ {
+ value: THEME.LIGHT,
+ label: SunIcon,
+ ariaLabel: `${t("buttons.lightMode")} - ${shortcut}`,
+ },
+ {
+ value: THEME.DARK,
+ label: MoonIcon,
+ ariaLabel: `${t("buttons.darkMode")} - ${shortcut}`,
+ },
+ {
+ value: "system",
+ label: DeviceDesktopIcon,
+ ariaLabel: t("buttons.systemMode"),
+ },
+ ]}
+ >
+ {t("labels.theme")}
+ </DropdownMenuItemContentRadio>
+ );
+ }
+
+ return (
+ <DropdownMenuItem
+ onSelect={(event) => {
+ // do not close the menu when changing theme
+ event.preventDefault();
+
+ if (props?.onSelect) {
+ props.onSelect(
+ appState.theme === THEME.DARK ? THEME.LIGHT : THEME.DARK,
+ );
+ } else {
+ return actionManager.executeAction(actionToggleTheme);
+ }
+ }}
+ icon={appState.theme === THEME.DARK ? SunIcon : MoonIcon}
+ data-testid="toggle-dark-mode"
+ shortcut={shortcut}
+ aria-label={
+ appState.theme === THEME.DARK
+ ? t("buttons.lightMode")
+ : t("buttons.darkMode")
+ }
+ >
+ {appState.theme === THEME.DARK
+ ? t("buttons.lightMode")
+ : t("buttons.darkMode")}
+ </DropdownMenuItem>
+ );
+};
+ToggleTheme.displayName = "ToggleTheme";
+
+export const ChangeCanvasBackground = () => {
+ const { t } = useI18n();
+ const appState = useUIAppState();
+ const actionManager = useExcalidrawActionManager();
+ const appProps = useAppProps();
+
+ if (
+ appState.viewModeEnabled ||
+ !appProps.UIOptions.canvasActions.changeViewBackgroundColor
+ ) {
+ return null;
+ }
+ return (
+ <div style={{ marginTop: "0.5rem" }}>
+ <div
+ data-testid="canvas-background-label"
+ style={{ fontSize: ".75rem", marginBottom: ".5rem" }}
+ >
+ {t("labels.canvasBackground")}
+ </div>
+ <div style={{ padding: "0 0.625rem" }}>
+ {actionManager.renderAction("changeViewBackgroundColor")}
+ </div>
+ </div>
+ );
+};
+ChangeCanvasBackground.displayName = "ChangeCanvasBackground";
+
+export const Export = () => {
+ const { t } = useI18n();
+ const setAppState = useExcalidrawSetAppState();
+ return (
+ <DropdownMenuItem
+ icon={ExportIcon}
+ onSelect={() => {
+ setAppState({ openDialog: { name: "jsonExport" } });
+ }}
+ data-testid="json-export-button"
+ aria-label={t("buttons.export")}
+ >
+ {t("buttons.export")}
+ </DropdownMenuItem>
+ );
+};
+Export.displayName = "Export";
+
+export const Socials = () => {
+ const { t } = useI18n();
+
+ return (
+ <>
+ <DropdownMenuItemLink
+ icon={GithubIcon}
+ href="https://github.com/excalidraw/excalidraw"
+ aria-label="GitHub"
+ >
+ GitHub
+ </DropdownMenuItemLink>
+ <DropdownMenuItemLink
+ icon={XBrandIcon}
+ href="https://x.com/excalidraw"
+ aria-label="X"
+ >
+ {t("labels.followUs")}
+ </DropdownMenuItemLink>
+ <DropdownMenuItemLink
+ icon={DiscordIcon}
+ href="https://discord.gg/UexuTaE"
+ aria-label="Discord"
+ >
+ {t("labels.discordChat")}
+ </DropdownMenuItemLink>
+ </>
+ );
+};
+Socials.displayName = "Socials";
+
+export const LiveCollaborationTrigger = ({
+ onSelect,
+ isCollaborating,
+}: {
+ onSelect: () => void;
+ isCollaborating: boolean;
+}) => {
+ const { t } = useI18n();
+ return (
+ <DropdownMenuItem
+ data-testid="collab-button"
+ icon={usersIcon}
+ className={clsx({
+ "active-collab": isCollaborating,
+ })}
+ onSelect={onSelect}
+ >
+ {t("labels.liveCollaboration")}
+ </DropdownMenuItem>
+ );
+};
+
+LiveCollaborationTrigger.displayName = "LiveCollaborationTrigger";