From 6ec259a0e71174651bae95d4628138bf6fd68742 Mon Sep 17 00:00:00 2001 From: kj_sh604 Date: Sun, 15 Mar 2026 16:19:35 -0400 Subject: refactor: packages/ --- .../components/main-menu/DefaultItems.tsx | 391 +++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 packages/excalidraw/components/main-menu/DefaultItems.tsx (limited to 'packages/excalidraw/components/main-menu/DefaultItems.tsx') 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: ( + {text}} + br={() =>
} + /> + ), + })) + ) { + actionManager.executeAction(actionLoadScene); + } + }; + + return ( + + {t("buttons.load")} + + ); +}; +LoadScene.displayName = "LoadScene"; + +export const SaveToActiveFile = () => { + const { t } = useI18n(); + const actionManager = useExcalidrawActionManager(); + + if (!actionManager.isActionEnabled(actionSaveToActiveFile)) { + return null; + } + + return ( + actionManager.executeAction(actionSaveToActiveFile)} + icon={save} + aria-label={`${t("buttons.save")}`} + >{`${t("buttons.save")}`} + ); +}; +SaveToActiveFile.displayName = "SaveToActiveFile"; + +export const SaveAsImage = () => { + const setAppState = useExcalidrawSetAppState(); + const { t } = useI18n(); + return ( + setAppState({ openDialog: { name: "imageExport" } })} + shortcut={getShortcutFromShortcutName("imageExport")} + aria-label={t("buttons.exportImage")} + > + {t("buttons.exportImage")} + + ); +}; +SaveAsImage.displayName = "SaveAsImage"; + +export const CommandPalette = (opts?: { className?: string }) => { + const setAppState = useExcalidrawSetAppState(); + const { t } = useI18n(); + + return ( + { + trackEvent("command_palette", "open", "menu"); + setAppState({ openDialog: { name: "commandPalette" } }); + }} + shortcut={getShortcutFromShortcutName("commandPalette")} + aria-label={t("commandPalette.title")} + className={opts?.className} + > + {t("commandPalette.title")} + + ); +}; +CommandPalette.displayName = "CommandPalette"; + +export const SearchMenu = (opts?: { className?: string }) => { + const { t } = useI18n(); + const actionManager = useExcalidrawActionManager(); + + return ( + { + actionManager.executeAction(actionToggleSearchMenu); + }} + shortcut={getShortcutFromShortcutName("searchMenu")} + aria-label={t("search.title")} + className={opts?.className} + > + {t("search.title")} + + ); +}; +SearchMenu.displayName = "SearchMenu"; + +export const Help = () => { + const { t } = useI18n(); + + const actionManager = useExcalidrawActionManager(); + + return ( + actionManager.executeAction(actionShortcuts)} + shortcut="?" + aria-label={t("helpDialog.title")} + > + {t("helpDialog.title")} + + ); +}; +Help.displayName = "Help"; + +export const ClearCanvas = () => { + const { t } = useI18n(); + + const setActiveConfirmDialog = useSetAtom(activeConfirmDialogAtom); + const actionManager = useExcalidrawActionManager(); + + if (!actionManager.isActionEnabled(actionClearCanvas)) { + return null; + } + + return ( + setActiveConfirmDialog("clearCanvas")} + data-testid="clear-canvas-button" + aria-label={t("buttons.clearReset")} + > + {t("buttons.clearReset")} + + ); +}; +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 ( + 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")} + + ); + } + + return ( + { + // 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")} + + ); +}; +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 ( +
+
+ {t("labels.canvasBackground")} +
+
+ {actionManager.renderAction("changeViewBackgroundColor")} +
+
+ ); +}; +ChangeCanvasBackground.displayName = "ChangeCanvasBackground"; + +export const Export = () => { + const { t } = useI18n(); + const setAppState = useExcalidrawSetAppState(); + return ( + { + setAppState({ openDialog: { name: "jsonExport" } }); + }} + data-testid="json-export-button" + aria-label={t("buttons.export")} + > + {t("buttons.export")} + + ); +}; +Export.displayName = "Export"; + +export const Socials = () => { + const { t } = useI18n(); + + return ( + <> + + GitHub + + + {t("labels.followUs")} + + + {t("labels.discordChat")} + + + ); +}; +Socials.displayName = "Socials"; + +export const LiveCollaborationTrigger = ({ + onSelect, + isCollaborating, +}: { + onSelect: () => void; + isCollaborating: boolean; +}) => { + const { t } = useI18n(); + return ( + + {t("labels.liveCollaboration")} + + ); +}; + +LiveCollaborationTrigger.displayName = "LiveCollaborationTrigger"; -- cgit v1.2.3