diff --git a/excalidraw-app/components/AppMainMenu.tsx b/excalidraw-app/components/AppMainMenu.tsx
index e51d1763b8..a3f847385f 100644
--- a/excalidraw-app/components/AppMainMenu.tsx
+++ b/excalidraw-app/components/AppMainMenu.tsx
@@ -77,6 +77,7 @@ export const AppMainMenu: React.FC<{
)}
+
= {
toggleTheme: [getShortcutKey("Shift+Alt+D")],
@@ -117,6 +118,7 @@ const shortcutMap: Record = {
toggleShortcuts: [getShortcutKey("?")],
searchMenu: [getShortcutKey("CtrlOrCmd+F")],
wrapSelectionInFrame: [],
+ toolLock: [getShortcutKey("Q")],
};
export const getShortcutFromShortcutName = (name: ShortcutName, idx = 0) => {
diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx
index e5b5441efd..5b0ef39c3e 100644
--- a/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx
+++ b/packages/excalidraw/components/dropdownMenu/DropdownMenu.tsx
@@ -12,6 +12,7 @@ import DropdownMenuItemLink from "./DropdownMenuItemLink";
import MenuSeparator from "./DropdownMenuSeparator";
import DropdownMenuSub from "./DropdownMenuSub";
import DropdownMenuTrigger from "./DropdownMenuTrigger";
+import DropdownMenuItemCheckbox from "./DropdownMenuItemCheckbox";
import {
getMenuContentComponent,
getMenuTriggerComponent,
@@ -57,6 +58,7 @@ const DropdownMenu = ({
DropdownMenu.Trigger = DropdownMenuTrigger;
DropdownMenu.Content = DropdownMenuContent;
DropdownMenu.Item = DropdownMenuItem;
+DropdownMenu.ItemCheckbox = DropdownMenuItemCheckbox;
DropdownMenu.ItemLink = DropdownMenuItemLink;
DropdownMenu.ItemCustom = DropdownMenuItemCustom;
DropdownMenu.Group = DropdownMenuGroup;
diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenuItem.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenuItem.tsx
index 7cc10b95d4..55ffcd5844 100644
--- a/packages/excalidraw/components/dropdownMenu/DropdownMenuItem.tsx
+++ b/packages/excalidraw/components/dropdownMenu/DropdownMenuItem.tsx
@@ -16,6 +16,17 @@ import MenuItemContent from "./DropdownMenuItemContent";
import type { JSX } from "react";
+export type DropdownMenuItemProps = {
+ icon?: JSX.Element;
+ badge?: React.ReactNode;
+ value?: string | number | undefined;
+ onSelect?: (event: Event) => void;
+ children: React.ReactNode;
+ shortcut?: string;
+ selected?: boolean;
+ className?: string;
+} & Omit, "onSelect">;
+
const DropdownMenuItem = ({
icon,
badge,
@@ -26,19 +37,7 @@ const DropdownMenuItem = ({
selected,
onSelect,
...rest
-}: {
- icon?: JSX.Element;
- badge?: React.ReactNode;
- value?: string | number | undefined;
- onSelect?: (event: Event) => void;
- children: React.ReactNode;
- shortcut?: string;
- selected?: boolean;
- className?: string;
-} & Omit<
- React.ButtonHTMLAttributes,
- "onSelect" | "onClick"
->) => {
+}: DropdownMenuItemProps) => {
const handleSelect = useHandleDropdownMenuItemSelect(onSelect);
return (
diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenuItemCheckbox.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenuItemCheckbox.tsx
new file mode 100644
index 0000000000..c116e5bde7
--- /dev/null
+++ b/packages/excalidraw/components/dropdownMenu/DropdownMenuItemCheckbox.tsx
@@ -0,0 +1,15 @@
+import { checkIcon, emptyIcon } from "../icons";
+
+import DropdownMenuItem from "./DropdownMenuItem";
+
+import type { DropdownMenuItemProps } from "./DropdownMenuItem";
+
+const DropdownMenuItemCheckbox = (
+ props: Omit & { checked: boolean },
+) => {
+ return (
+
+ );
+};
+
+export default DropdownMenuItemCheckbox;
diff --git a/packages/excalidraw/components/icons.tsx b/packages/excalidraw/components/icons.tsx
index f5ce947d7a..6a7a934083 100644
--- a/packages/excalidraw/components/icons.tsx
+++ b/packages/excalidraw/components/icons.tsx
@@ -2408,3 +2408,20 @@ export const chevronRight = createIcon(
,
tablerIconProps,
);
+
+// tabler-icons: adjustments-horizontal
+export const settingsIcon = createIcon(
+
+
+
+
+
+
+
+
+
+
+
+ ,
+ tablerIconProps,
+);
diff --git a/packages/excalidraw/components/main-menu/DefaultItems.tsx b/packages/excalidraw/components/main-menu/DefaultItems.tsx
index 2a00a79a71..04cea3da48 100644
--- a/packages/excalidraw/components/main-menu/DefaultItems.tsx
+++ b/packages/excalidraw/components/main-menu/DefaultItems.tsx
@@ -9,9 +9,14 @@ import {
actionLoadScene,
actionSaveToActiveFile,
actionShortcuts,
+ actionToggleGridMode,
+ actionToggleObjectsSnapMode,
actionToggleSearchMenu,
+ actionToggleStats,
actionToggleTheme,
+ actionToggleZenMode,
} from "../../actions";
+import { actionToggleViewMode } from "../../actions/actionToggleViewMode";
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
import { trackEvent } from "../../analytics";
import { useUIAppState } from "../../context/ui-appState";
@@ -23,13 +28,16 @@ import {
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 { GithubIcon, DiscordIcon, XBrandIcon } from "../icons";
+import DropdownMenuSub from "../dropdownMenu/DropdownMenuSub";
+import { GithubIcon, DiscordIcon, XBrandIcon, settingsIcon } from "../icons";
import {
boltIcon,
DeviceDesktopIcon,
@@ -397,3 +405,152 @@ export const LiveCollaborationTrigger = ({
};
LiveCollaborationTrigger.displayName = "LiveCollaborationTrigger";
+
+const PreferencesToggleToolLockItem = () => {
+ const { t } = useI18n();
+ const app = useApp();
+ const appState = useUIAppState();
+
+ return (
+ {
+ app.toggleLock();
+ event.preventDefault();
+ }}
+ >
+ {t("labels.preferences_toolLock")}
+
+ );
+};
+
+const PreferencesToggleSnapModeItem = () => {
+ const { t } = useI18n();
+ const actionManager = useExcalidrawActionManager();
+ const appState = useUIAppState();
+ return (
+ {
+ actionManager.executeAction(actionToggleObjectsSnapMode);
+ event.preventDefault();
+ }}
+ >
+ {t("buttons.objectsSnapMode")}
+
+ );
+};
+
+export const PreferencesToggleGridModeItem = () => {
+ const { t } = useI18n();
+ const actionManager = useExcalidrawActionManager();
+ const appState = useUIAppState();
+
+ return (
+ {
+ actionManager.executeAction(actionToggleGridMode);
+ event.preventDefault();
+ }}
+ >
+ {t("labels.toggleGrid")}
+
+ );
+};
+
+export const PreferencesToggleZenModeItem = () => {
+ const { t } = useI18n();
+ const actionManager = useExcalidrawActionManager();
+ const appState = useUIAppState();
+ return (
+ {
+ actionManager.executeAction(actionToggleZenMode);
+ event.preventDefault();
+ }}
+ >
+ {t("buttons.zenMode")}
+
+ );
+};
+
+const PreferencesToggleViewModeItem = () => {
+ const { t } = useI18n();
+ const actionManager = useExcalidrawActionManager();
+ const appState = useUIAppState();
+ return (
+ {
+ actionManager.executeAction(actionToggleViewMode);
+ event.preventDefault();
+ }}
+ >
+ {t("labels.viewMode")}
+
+ );
+};
+
+const PreferencesToggleElementPropertiesItem = () => {
+ const { t } = useI18n();
+ const actionManager = useExcalidrawActionManager();
+ const appState = useUIAppState();
+ return (
+ {
+ actionManager.executeAction(actionToggleStats);
+ event.preventDefault();
+ }}
+ >
+ {t("stats.fullTitle")}
+
+ );
+};
+
+export const Preferences = ({
+ children,
+ additionalItems,
+}: {
+ children?: React.ReactNode;
+ additionalItems?: React.ReactNode;
+}) => {
+ const { t } = useI18n();
+ return (
+
+
+ {t("labels.preferences")}
+
+
+ {children || (
+ <>
+
+
+
+
+
+
+ >
+ )}
+ {additionalItems}
+
+
+ );
+};
+
+Preferences.ToggleToolLock = PreferencesToggleToolLockItem;
+Preferences.ToggleSnapMode = PreferencesToggleSnapModeItem;
+Preferences.ToggleGridMode = PreferencesToggleGridModeItem;
+Preferences.ToggleZenMode = PreferencesToggleZenModeItem;
+Preferences.ToggleViewMode = PreferencesToggleViewModeItem;
+Preferences.ToggleElementProperties = PreferencesToggleElementPropertiesItem;
+
+Preferences.displayName = "Preferences";
diff --git a/packages/excalidraw/locales/en.json b/packages/excalidraw/locales/en.json
index 765ff06cdd..182e59bb61 100644
--- a/packages/excalidraw/locales/en.json
+++ b/packages/excalidraw/locales/en.json
@@ -171,7 +171,9 @@
"linkToElement": "Link to object",
"wrapSelectionInFrame": "Wrap selection in frame",
"tab": "Tab",
- "shapeSwitch": "Switch shape"
+ "shapeSwitch": "Switch shape",
+ "preferences": "Preferences",
+ "preferences_toolLock": "Tool lock"
},
"elementLink": {
"title": "Link to object",