feat(app): add preferences to main menu (#10760)
feat(editor): add preferences to main menu
This commit is contained in:
@@ -77,6 +77,7 @@ export const AppMainMenu: React.FC<{
|
|||||||
</MainMenu.Item>
|
</MainMenu.Item>
|
||||||
)}
|
)}
|
||||||
<MainMenu.Separator />
|
<MainMenu.Separator />
|
||||||
|
<MainMenu.DefaultItems.Preferences />
|
||||||
<MainMenu.DefaultItems.ToggleTheme
|
<MainMenu.DefaultItems.ToggleTheme
|
||||||
allowSystemTheme
|
allowSystemTheme
|
||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ export type ShortcutName =
|
|||||||
| "saveScene"
|
| "saveScene"
|
||||||
| "imageExport"
|
| "imageExport"
|
||||||
| "commandPalette"
|
| "commandPalette"
|
||||||
| "searchMenu";
|
| "searchMenu"
|
||||||
|
| "toolLock";
|
||||||
|
|
||||||
const shortcutMap: Record<ShortcutName, string[]> = {
|
const shortcutMap: Record<ShortcutName, string[]> = {
|
||||||
toggleTheme: [getShortcutKey("Shift+Alt+D")],
|
toggleTheme: [getShortcutKey("Shift+Alt+D")],
|
||||||
@@ -117,6 +118,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
|
|||||||
toggleShortcuts: [getShortcutKey("?")],
|
toggleShortcuts: [getShortcutKey("?")],
|
||||||
searchMenu: [getShortcutKey("CtrlOrCmd+F")],
|
searchMenu: [getShortcutKey("CtrlOrCmd+F")],
|
||||||
wrapSelectionInFrame: [],
|
wrapSelectionInFrame: [],
|
||||||
|
toolLock: [getShortcutKey("Q")],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getShortcutFromShortcutName = (name: ShortcutName, idx = 0) => {
|
export const getShortcutFromShortcutName = (name: ShortcutName, idx = 0) => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import DropdownMenuItemLink from "./DropdownMenuItemLink";
|
|||||||
import MenuSeparator from "./DropdownMenuSeparator";
|
import MenuSeparator from "./DropdownMenuSeparator";
|
||||||
import DropdownMenuSub from "./DropdownMenuSub";
|
import DropdownMenuSub from "./DropdownMenuSub";
|
||||||
import DropdownMenuTrigger from "./DropdownMenuTrigger";
|
import DropdownMenuTrigger from "./DropdownMenuTrigger";
|
||||||
|
import DropdownMenuItemCheckbox from "./DropdownMenuItemCheckbox";
|
||||||
import {
|
import {
|
||||||
getMenuContentComponent,
|
getMenuContentComponent,
|
||||||
getMenuTriggerComponent,
|
getMenuTriggerComponent,
|
||||||
@@ -57,6 +58,7 @@ const DropdownMenu = ({
|
|||||||
DropdownMenu.Trigger = DropdownMenuTrigger;
|
DropdownMenu.Trigger = DropdownMenuTrigger;
|
||||||
DropdownMenu.Content = DropdownMenuContent;
|
DropdownMenu.Content = DropdownMenuContent;
|
||||||
DropdownMenu.Item = DropdownMenuItem;
|
DropdownMenu.Item = DropdownMenuItem;
|
||||||
|
DropdownMenu.ItemCheckbox = DropdownMenuItemCheckbox;
|
||||||
DropdownMenu.ItemLink = DropdownMenuItemLink;
|
DropdownMenu.ItemLink = DropdownMenuItemLink;
|
||||||
DropdownMenu.ItemCustom = DropdownMenuItemCustom;
|
DropdownMenu.ItemCustom = DropdownMenuItemCustom;
|
||||||
DropdownMenu.Group = DropdownMenuGroup;
|
DropdownMenu.Group = DropdownMenuGroup;
|
||||||
|
|||||||
@@ -16,6 +16,17 @@ import MenuItemContent from "./DropdownMenuItemContent";
|
|||||||
|
|
||||||
import type { JSX } from "react";
|
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<React.ButtonHTMLAttributes<HTMLButtonElement>, "onSelect">;
|
||||||
|
|
||||||
const DropdownMenuItem = ({
|
const DropdownMenuItem = ({
|
||||||
icon,
|
icon,
|
||||||
badge,
|
badge,
|
||||||
@@ -26,19 +37,7 @@ const DropdownMenuItem = ({
|
|||||||
selected,
|
selected,
|
||||||
onSelect,
|
onSelect,
|
||||||
...rest
|
...rest
|
||||||
}: {
|
}: 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<
|
|
||||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
||||||
"onSelect" | "onClick"
|
|
||||||
>) => {
|
|
||||||
const handleSelect = useHandleDropdownMenuItemSelect(onSelect);
|
const handleSelect = useHandleDropdownMenuItemSelect(onSelect);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { checkIcon, emptyIcon } from "../icons";
|
||||||
|
|
||||||
|
import DropdownMenuItem from "./DropdownMenuItem";
|
||||||
|
|
||||||
|
import type { DropdownMenuItemProps } from "./DropdownMenuItem";
|
||||||
|
|
||||||
|
const DropdownMenuItemCheckbox = (
|
||||||
|
props: Omit<DropdownMenuItemProps, "icon"> & { checked: boolean },
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem {...props} icon={props.checked ? checkIcon : emptyIcon} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropdownMenuItemCheckbox;
|
||||||
@@ -2408,3 +2408,20 @@ export const chevronRight = createIcon(
|
|||||||
</g>,
|
</g>,
|
||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// tabler-icons: adjustments-horizontal
|
||||||
|
export const settingsIcon = createIcon(
|
||||||
|
<g strokeWidth={1.25}>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M14 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||||
|
<path d="M4 6l8 0" />
|
||||||
|
<path d="M16 6l4 0" />
|
||||||
|
<path d="M8 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||||
|
<path d="M4 12l2 0" />
|
||||||
|
<path d="M10 12l10 0" />
|
||||||
|
<path d="M17 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
||||||
|
<path d="M4 18l11 0" />
|
||||||
|
<path d="M19 18l1 0" />
|
||||||
|
</g>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|||||||
@@ -9,9 +9,14 @@ import {
|
|||||||
actionLoadScene,
|
actionLoadScene,
|
||||||
actionSaveToActiveFile,
|
actionSaveToActiveFile,
|
||||||
actionShortcuts,
|
actionShortcuts,
|
||||||
|
actionToggleGridMode,
|
||||||
|
actionToggleObjectsSnapMode,
|
||||||
actionToggleSearchMenu,
|
actionToggleSearchMenu,
|
||||||
|
actionToggleStats,
|
||||||
actionToggleTheme,
|
actionToggleTheme,
|
||||||
|
actionToggleZenMode,
|
||||||
} from "../../actions";
|
} from "../../actions";
|
||||||
|
import { actionToggleViewMode } from "../../actions/actionToggleViewMode";
|
||||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||||
import { trackEvent } from "../../analytics";
|
import { trackEvent } from "../../analytics";
|
||||||
import { useUIAppState } from "../../context/ui-appState";
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
@@ -23,13 +28,16 @@ import {
|
|||||||
useExcalidrawActionManager,
|
useExcalidrawActionManager,
|
||||||
useExcalidrawElements,
|
useExcalidrawElements,
|
||||||
useAppProps,
|
useAppProps,
|
||||||
|
useApp,
|
||||||
} from "../App";
|
} from "../App";
|
||||||
import { openConfirmModal } from "../OverwriteConfirm/OverwriteConfirmState";
|
import { openConfirmModal } from "../OverwriteConfirm/OverwriteConfirmState";
|
||||||
import Trans from "../Trans";
|
import Trans from "../Trans";
|
||||||
import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem";
|
import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem";
|
||||||
|
import DropdownMenuItemCheckbox from "../dropdownMenu/DropdownMenuItemCheckbox";
|
||||||
import DropdownMenuItemContentRadio from "../dropdownMenu/DropdownMenuItemContentRadio";
|
import DropdownMenuItemContentRadio from "../dropdownMenu/DropdownMenuItemContentRadio";
|
||||||
import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink";
|
import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink";
|
||||||
import { GithubIcon, DiscordIcon, XBrandIcon } from "../icons";
|
import DropdownMenuSub from "../dropdownMenu/DropdownMenuSub";
|
||||||
|
import { GithubIcon, DiscordIcon, XBrandIcon, settingsIcon } from "../icons";
|
||||||
import {
|
import {
|
||||||
boltIcon,
|
boltIcon,
|
||||||
DeviceDesktopIcon,
|
DeviceDesktopIcon,
|
||||||
@@ -397,3 +405,152 @@ export const LiveCollaborationTrigger = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
LiveCollaborationTrigger.displayName = "LiveCollaborationTrigger";
|
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 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
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 || (
|
||||||
|
<>
|
||||||
|
<PreferencesToggleToolLockItem />
|
||||||
|
<PreferencesToggleSnapModeItem />
|
||||||
|
<PreferencesToggleGridModeItem />
|
||||||
|
<PreferencesToggleZenModeItem />
|
||||||
|
<PreferencesToggleViewModeItem />
|
||||||
|
<PreferencesToggleElementPropertiesItem />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{additionalItems}
|
||||||
|
</DropdownMenuSub.Content>
|
||||||
|
</DropdownMenuSub>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Preferences.ToggleToolLock = PreferencesToggleToolLockItem;
|
||||||
|
Preferences.ToggleSnapMode = PreferencesToggleSnapModeItem;
|
||||||
|
Preferences.ToggleGridMode = PreferencesToggleGridModeItem;
|
||||||
|
Preferences.ToggleZenMode = PreferencesToggleZenModeItem;
|
||||||
|
Preferences.ToggleViewMode = PreferencesToggleViewModeItem;
|
||||||
|
Preferences.ToggleElementProperties = PreferencesToggleElementPropertiesItem;
|
||||||
|
|
||||||
|
Preferences.displayName = "Preferences";
|
||||||
|
|||||||
@@ -171,7 +171,9 @@
|
|||||||
"linkToElement": "Link to object",
|
"linkToElement": "Link to object",
|
||||||
"wrapSelectionInFrame": "Wrap selection in frame",
|
"wrapSelectionInFrame": "Wrap selection in frame",
|
||||||
"tab": "Tab",
|
"tab": "Tab",
|
||||||
"shapeSwitch": "Switch shape"
|
"shapeSwitch": "Switch shape",
|
||||||
|
"preferences": "Preferences",
|
||||||
|
"preferences_toolLock": "Tool lock"
|
||||||
},
|
},
|
||||||
"elementLink": {
|
"elementLink": {
|
||||||
"title": "Link to object",
|
"title": "Link to object",
|
||||||
|
|||||||
Reference in New Issue
Block a user