snapshot of https://github.com/excalidraw/excalidraw.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Tests / test (push) Failing after 15s
Some checks failed
Tests / test (push) Failing after 15s
This commit is contained in:
21
packages/excalidraw/components/main-menu/DefaultItems.scss
Normal file
21
packages/excalidraw/components/main-menu/DefaultItems.scss
Normal file
@@ -0,0 +1,21 @@
|
||||
.excalidraw {
|
||||
.ActiveFile {
|
||||
.ActiveFile__fileName {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 9.3em;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 1.15em;
|
||||
margin-inline-end: 0.3em;
|
||||
transform: scaleY(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
687
packages/excalidraw/components/main-menu/DefaultItems.tsx
Normal file
687
packages/excalidraw/components/main-menu/DefaultItems.tsx
Normal file
@@ -0,0 +1,687 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
import { THEME } from "@excalidraw/common";
|
||||
|
||||
import type { Theme } from "@excalidraw/element/types";
|
||||
|
||||
import {
|
||||
actionClearCanvas,
|
||||
actionLoadScene,
|
||||
actionSaveToActiveFile,
|
||||
actionShortcuts,
|
||||
actionToggleArrowBinding,
|
||||
actionToggleGridMode,
|
||||
actionToggleMidpointSnapping,
|
||||
actionToggleObjectsSnapMode,
|
||||
actionToggleSearchMenu,
|
||||
actionToggleStats,
|
||||
actionToggleTheme,
|
||||
actionToggleZenMode,
|
||||
} from "../../actions";
|
||||
import { actionToggleViewMode } from "../../actions/actionToggleViewMode";
|
||||
import { resolveInputDevice } from "../../appState";
|
||||
|
||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||
import { trackEvent } from "../../analytics";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
import { useSetAtom } from "../../editor-jotai";
|
||||
import { useI18n } from "../../i18n";
|
||||
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
||||
import {
|
||||
useExcalidrawSetAppState,
|
||||
useExcalidrawActionManager,
|
||||
useExcalidrawElements,
|
||||
useAppProps,
|
||||
useApp,
|
||||
} from "../App";
|
||||
import { openConfirmModal } from "../OverwriteConfirm/OverwriteConfirmState";
|
||||
import Trans from "../Trans";
|
||||
import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem";
|
||||
import DropdownMenuItemCheckbox from "../dropdownMenu/DropdownMenuItemCheckbox";
|
||||
import DropdownMenuItemContentRadio from "../dropdownMenu/DropdownMenuItemContentRadio";
|
||||
import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink";
|
||||
import DropdownMenuSub from "../dropdownMenu/DropdownMenuSub";
|
||||
import {
|
||||
GithubIcon,
|
||||
DiscordIcon,
|
||||
XBrandIcon,
|
||||
settingsIcon,
|
||||
emptyIcon,
|
||||
} from "../icons";
|
||||
import {
|
||||
boltIcon,
|
||||
DeviceDesktopIcon,
|
||||
ExportIcon,
|
||||
ExportImageIcon,
|
||||
HelpIcon,
|
||||
LoadIcon,
|
||||
MoonIcon,
|
||||
save,
|
||||
searchIcon,
|
||||
SunIcon,
|
||||
TrashIcon,
|
||||
usersIcon,
|
||||
} from "../icons";
|
||||
|
||||
import "./DefaultItems.scss";
|
||||
|
||||
import type { InputDevice } from "../../types";
|
||||
|
||||
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;
|
||||
/**
|
||||
* Controls the theme of this UI component only.
|
||||
* You should subscribe to `props.onThemeChange` and control the theme
|
||||
* upstream.
|
||||
*/
|
||||
theme: Theme | "system";
|
||||
}
|
||||
| {
|
||||
allowSystemTheme: false;
|
||||
},
|
||||
) => {
|
||||
const { t } = useI18n();
|
||||
const appState = useUIAppState();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
const shortcut = getShortcutFromShortcutName("toggleTheme");
|
||||
const appProps = useAppProps();
|
||||
|
||||
if (!actionManager.isActionEnabled(actionToggleTheme)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (props?.allowSystemTheme) {
|
||||
return (
|
||||
<DropdownMenuItemContentRadio
|
||||
name="theme"
|
||||
value={props.theme}
|
||||
onChange={(value: Theme | "system") => {
|
||||
if (appProps.onThemeChange) {
|
||||
appProps.onThemeChange(value);
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"MainMenu.DefaultItems.ToggleTheme: `<Excalidraw/> props.onThemeChange` must be defined to use system theme selection.",
|
||||
);
|
||||
}}
|
||||
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();
|
||||
|
||||
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.75rem" }}>
|
||||
<div
|
||||
data-testid="canvas-background-label"
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
marginBottom: "0.25rem",
|
||||
marginLeft: "0.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";
|
||||
|
||||
const PreferencesToggleToolLockItem = () => {
|
||||
const { t } = useI18n();
|
||||
const app = useApp();
|
||||
const appState = useUIAppState();
|
||||
|
||||
return (
|
||||
<DropdownMenuItemCheckbox
|
||||
checked={appState.activeTool.locked}
|
||||
shortcut={getShortcutFromShortcutName("toolLock")}
|
||||
onSelect={(event) => {
|
||||
app.toggleLock();
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{t("labels.preferences_toolLock")}
|
||||
</DropdownMenuItemCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
const PreferencesBoxSelectionModeItem = () => {
|
||||
const { t } = useI18n();
|
||||
const appState = useUIAppState();
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
|
||||
return (
|
||||
<DropdownMenuItemContentRadio<"contain" | "overlap">
|
||||
name="boxSelectionMode"
|
||||
icon={emptyIcon}
|
||||
value={appState.boxSelectionMode}
|
||||
onChange={(value) => {
|
||||
setAppState({
|
||||
boxSelectionMode: value,
|
||||
});
|
||||
}}
|
||||
choices={[
|
||||
{
|
||||
value: "contain",
|
||||
label: t("labels.boxSelectionContain"),
|
||||
ariaLabel: t("labels.boxSelectionContain"),
|
||||
},
|
||||
{
|
||||
value: "overlap",
|
||||
label: t("labels.boxSelectionOverlap"),
|
||||
ariaLabel: t("labels.boxSelectionOverlap"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{t("labels.boxSelectionMode")}
|
||||
</DropdownMenuItemContentRadio>
|
||||
);
|
||||
};
|
||||
|
||||
const PreferencesInputDeviceItem = () => {
|
||||
const { t } = useI18n();
|
||||
const appState = useUIAppState();
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
|
||||
return (
|
||||
<DropdownMenuItemContentRadio<Exclude<InputDevice, "auto">>
|
||||
name="inputDevice"
|
||||
icon={emptyIcon}
|
||||
// `auto` isn't offered yet; the radio shows what it resolves to
|
||||
value={resolveInputDevice(appState.inputDevice)}
|
||||
onChange={(value) => {
|
||||
setAppState({
|
||||
inputDevice: value,
|
||||
});
|
||||
}}
|
||||
choices={[
|
||||
{
|
||||
value: "trackpad",
|
||||
label: t("labels.inputDeviceTrackpad"),
|
||||
ariaLabel: t("labels.inputDeviceTrackpad"),
|
||||
},
|
||||
{
|
||||
value: "mouse",
|
||||
label: t("labels.inputDeviceMouse"),
|
||||
ariaLabel: t("labels.inputDeviceMouse"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{t("labels.inputDevice")}
|
||||
</DropdownMenuItemContentRadio>
|
||||
);
|
||||
};
|
||||
|
||||
const PreferencesToggleSnapModeItem = () => {
|
||||
const { t } = useI18n();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
const appState = useUIAppState();
|
||||
return (
|
||||
<DropdownMenuItemCheckbox
|
||||
checked={appState.objectsSnapModeEnabled}
|
||||
shortcut={getShortcutFromShortcutName("objectsSnapMode")}
|
||||
onSelect={(event) => {
|
||||
actionManager.executeAction(actionToggleObjectsSnapMode);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{t("buttons.objectsSnapMode")}
|
||||
</DropdownMenuItemCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
const PreferencesToggleArrowBindingItem = () => {
|
||||
const { t } = useI18n();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
const appState = useUIAppState();
|
||||
return (
|
||||
<DropdownMenuItemCheckbox
|
||||
checked={appState.bindingPreference === "enabled"}
|
||||
onSelect={(event) => {
|
||||
actionManager.executeAction(actionToggleArrowBinding);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{t("labels.arrowBinding")}
|
||||
</DropdownMenuItemCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
const PreferencesToggleMidpointSnappingItem = () => {
|
||||
const { t } = useI18n();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
const appState = useUIAppState();
|
||||
return (
|
||||
<DropdownMenuItemCheckbox
|
||||
checked={appState.isMidpointSnappingEnabled}
|
||||
onSelect={(event) => {
|
||||
actionManager.executeAction(actionToggleMidpointSnapping);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{t("labels.midpointSnapping")}
|
||||
</DropdownMenuItemCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
export const PreferencesToggleGridModeItem = () => {
|
||||
const { t } = useI18n();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
const appState = useUIAppState();
|
||||
|
||||
return (
|
||||
<DropdownMenuItemCheckbox
|
||||
checked={appState.gridModeEnabled}
|
||||
shortcut={getShortcutFromShortcutName("gridMode")}
|
||||
onSelect={(event) => {
|
||||
actionManager.executeAction(actionToggleGridMode);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{t("labels.toggleGrid")}
|
||||
</DropdownMenuItemCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
export const PreferencesToggleZenModeItem = () => {
|
||||
const { t } = useI18n();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
const appState = useUIAppState();
|
||||
return (
|
||||
<DropdownMenuItemCheckbox
|
||||
checked={appState.zenModeEnabled}
|
||||
shortcut={getShortcutFromShortcutName("zenMode")}
|
||||
onSelect={(event) => {
|
||||
actionManager.executeAction(actionToggleZenMode);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{t("buttons.zenMode")}
|
||||
</DropdownMenuItemCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
const PreferencesToggleViewModeItem = () => {
|
||||
const { t } = useI18n();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
const appState = useUIAppState();
|
||||
if (!actionManager.isActionEnabled(actionToggleViewMode)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<DropdownMenuItemCheckbox
|
||||
checked={appState.viewModeEnabled}
|
||||
shortcut={getShortcutFromShortcutName("viewMode")}
|
||||
onSelect={(event) => {
|
||||
actionManager.executeAction(actionToggleViewMode);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{t("labels.viewMode")}
|
||||
</DropdownMenuItemCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
const PreferencesToggleElementPropertiesItem = () => {
|
||||
const { t } = useI18n();
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
const appState = useUIAppState();
|
||||
return (
|
||||
<DropdownMenuItemCheckbox
|
||||
checked={appState.stats.open}
|
||||
shortcut={getShortcutFromShortcutName("stats")}
|
||||
onSelect={(event) => {
|
||||
actionManager.executeAction(actionToggleStats);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{t("stats.fullTitle")}
|
||||
</DropdownMenuItemCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
export const Preferences = ({
|
||||
children,
|
||||
additionalItems,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
additionalItems?: React.ReactNode;
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSub.Trigger icon={settingsIcon}>
|
||||
{t("labels.preferences")}
|
||||
</DropdownMenuSub.Trigger>
|
||||
<DropdownMenuSub.Content className="excalidraw-main-menu-preferences-submenu">
|
||||
{children || (
|
||||
<>
|
||||
<PreferencesBoxSelectionModeItem />
|
||||
<PreferencesInputDeviceItem />
|
||||
<PreferencesToggleToolLockItem />
|
||||
<PreferencesToggleSnapModeItem />
|
||||
<PreferencesToggleGridModeItem />
|
||||
<PreferencesToggleZenModeItem />
|
||||
<PreferencesToggleViewModeItem />
|
||||
<PreferencesToggleElementPropertiesItem />
|
||||
<PreferencesToggleArrowBindingItem />
|
||||
<PreferencesToggleMidpointSnappingItem />
|
||||
</>
|
||||
)}
|
||||
{additionalItems}
|
||||
</DropdownMenuSub.Content>
|
||||
</DropdownMenuSub>
|
||||
);
|
||||
};
|
||||
|
||||
Preferences.ToggleToolLock = PreferencesToggleToolLockItem;
|
||||
Preferences.BoxSelectionMode = PreferencesBoxSelectionModeItem;
|
||||
Preferences.InputDevice = PreferencesInputDeviceItem;
|
||||
Preferences.ToggleSnapMode = PreferencesToggleSnapModeItem;
|
||||
Preferences.ToggleArrowBinding = PreferencesToggleArrowBindingItem;
|
||||
Preferences.ToggleMidpointSnapping = PreferencesToggleMidpointSnappingItem;
|
||||
Preferences.ToggleGridMode = PreferencesToggleGridModeItem;
|
||||
Preferences.ToggleZenMode = PreferencesToggleZenModeItem;
|
||||
Preferences.ToggleViewMode = PreferencesToggleViewModeItem;
|
||||
Preferences.ToggleElementProperties = PreferencesToggleElementPropertiesItem;
|
||||
|
||||
Preferences.displayName = "Preferences";
|
||||
95
packages/excalidraw/components/main-menu/MainMenu.tsx
Normal file
95
packages/excalidraw/components/main-menu/MainMenu.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React from "react";
|
||||
|
||||
import { composeEventHandlers } from "@excalidraw/common";
|
||||
|
||||
import { useTunnels } from "../../context/tunnels";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
import { t } from "../../i18n";
|
||||
import {
|
||||
useAppProps,
|
||||
useEditorInterface,
|
||||
useExcalidrawSetAppState,
|
||||
} from "../App";
|
||||
import { UserList } from "../UserList";
|
||||
import DropdownMenu from "../dropdownMenu/DropdownMenu";
|
||||
import DropdownMenuSub from "../dropdownMenu/DropdownMenuSub";
|
||||
import { withInternalFallback } from "../hoc/withInternalFallback";
|
||||
import { HamburgerMenuIcon } from "../icons";
|
||||
|
||||
import * as DefaultItems from "./DefaultItems";
|
||||
|
||||
const MainMenu = Object.assign(
|
||||
withInternalFallback(
|
||||
"MainMenu",
|
||||
({
|
||||
children,
|
||||
onSelect,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* Called when any menu item is selected (clicked on).
|
||||
*/
|
||||
onSelect?: (event: Event) => void;
|
||||
}) => {
|
||||
const { MainMenuTunnel } = useTunnels();
|
||||
const editorInterface = useEditorInterface();
|
||||
const appState = useUIAppState();
|
||||
const appProps = useAppProps();
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
|
||||
return (
|
||||
<MainMenuTunnel.In>
|
||||
<DropdownMenu open={appState.openMenu === "canvas"}>
|
||||
<DropdownMenu.Trigger
|
||||
onToggle={() => {
|
||||
setAppState({
|
||||
openMenu: appState.openMenu === "canvas" ? null : "canvas",
|
||||
openPopup: null,
|
||||
openDialog: null,
|
||||
});
|
||||
}}
|
||||
data-testid="main-menu-trigger"
|
||||
className="main-menu-trigger"
|
||||
>
|
||||
{HamburgerMenuIcon}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
onClickOutside={() => setAppState({ openMenu: null })}
|
||||
onSelect={composeEventHandlers(onSelect, () => {
|
||||
setAppState({ openMenu: null });
|
||||
})}
|
||||
className="main-menu"
|
||||
align="start"
|
||||
>
|
||||
{children}
|
||||
{editorInterface.formFactor === "phone" &&
|
||||
appState.collaborators.size > 0 && (
|
||||
<fieldset className="UserList-Wrapper">
|
||||
<legend>{t("labels.collaborators")}</legend>
|
||||
<UserList
|
||||
mobile={true}
|
||||
collaborators={appState.collaborators}
|
||||
userToFollow={appProps.userToFollow?.socketId || null}
|
||||
currentUserControls={appProps.currentUserControls}
|
||||
/>
|
||||
</fieldset>
|
||||
)}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
</MainMenuTunnel.In>
|
||||
);
|
||||
},
|
||||
),
|
||||
{
|
||||
Trigger: DropdownMenu.Trigger,
|
||||
Item: DropdownMenu.Item,
|
||||
ItemLink: DropdownMenu.ItemLink,
|
||||
ItemCustom: DropdownMenu.ItemCustom,
|
||||
Group: DropdownMenu.Group,
|
||||
Separator: DropdownMenu.Separator,
|
||||
Sub: DropdownMenuSub,
|
||||
DefaultItems,
|
||||
},
|
||||
);
|
||||
|
||||
export default MainMenu;
|
||||
Reference in New Issue
Block a user