@oh-just-another/editor 0.61.0 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +139 -0
- package/README.md +18 -15
- package/dist/.tsbuildinfo +1 -1
- package/dist/Diagram.d.ts +22 -1
- package/dist/Diagram.d.ts.map +1 -1
- package/dist/Diagram.js +151 -114
- package/dist/Diagram.js.map +1 -1
- package/dist/brand/brand-logo.d.ts +11 -0
- package/dist/brand/brand-logo.d.ts.map +1 -0
- package/dist/brand/brand-logo.js +11 -0
- package/dist/brand/brand-logo.js.map +1 -0
- package/dist/brand/logo.generated.d.ts +4 -0
- package/dist/brand/logo.generated.d.ts.map +1 -0
- package/dist/brand/logo.generated.js +5 -0
- package/dist/brand/logo.generated.js.map +1 -0
- package/dist/file-actions.d.ts +38 -7
- package/dist/file-actions.d.ts.map +1 -1
- package/dist/file-actions.js +167 -17
- package/dist/file-actions.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/png-export.d.ts +7 -1
- package/dist/png-export.d.ts.map +1 -1
- package/dist/png-export.js +13 -49
- package/dist/png-export.js.map +1 -1
- package/dist/preferences-storage.d.ts +18 -0
- package/dist/preferences-storage.d.ts.map +1 -0
- package/dist/preferences-storage.js +63 -0
- package/dist/preferences-storage.js.map +1 -0
- package/dist/scene-subset.d.ts +20 -0
- package/dist/scene-subset.d.ts.map +1 -0
- package/dist/scene-subset.js +68 -0
- package/dist/scene-subset.js.map +1 -0
- package/package.json +16 -14
package/dist/Diagram.d.ts
CHANGED
|
@@ -87,18 +87,32 @@ export interface DiagramProps {
|
|
|
87
87
|
*/
|
|
88
88
|
readonly hideDrawingPanel?: boolean;
|
|
89
89
|
/**
|
|
90
|
-
* Show the built-in minimap: a scene overview + viewport rect, docked
|
|
90
|
+
* Show the built-in minimap at startup: a scene overview + viewport rect, docked
|
|
91
91
|
* bottom-right above the zoom controls. Click / drag it to pan. Hidden in
|
|
92
92
|
* zen mode along with the rest of the chrome. Off by default.
|
|
93
93
|
*/
|
|
94
94
|
readonly minimap?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Brand cell at the start of the top bar. Defaults to the built-in
|
|
97
|
+
* `BrandLogo` (light / dark artwork from `assets/logo*.svg`); pass a node
|
|
98
|
+
* to replace it, or `null` to drop the cell.
|
|
99
|
+
*/
|
|
100
|
+
readonly logo?: ReactNode;
|
|
95
101
|
readonly renderTopBarLeft?: () => ReactNode;
|
|
96
102
|
readonly renderTopBarCenter?: () => ReactNode;
|
|
97
103
|
readonly renderTopBarRight?: () => ReactNode;
|
|
98
104
|
readonly renderBottomBarLeft?: () => ReactNode;
|
|
99
105
|
readonly renderBottomBarCenter?: () => ReactNode;
|
|
100
106
|
readonly renderBottomBarRight?: () => ReactNode;
|
|
107
|
+
/** Extra rows appended to the main menu's top level (after a separator). */
|
|
101
108
|
readonly renderMainMenuExtras?: () => ReactNode;
|
|
109
|
+
/** Extra rows inside the main menu's Board › submenu, right after Export (e.g. import / export formats). */
|
|
110
|
+
readonly renderBoardMenuExtras?: () => ReactNode;
|
|
111
|
+
/**
|
|
112
|
+
* Extra rows inside Board › Export, after the built-in PNG / SVG entries —
|
|
113
|
+
* for host export formats (JSON, Mermaid, …). Render `MainMenu.Item`s.
|
|
114
|
+
*/
|
|
115
|
+
readonly renderExportMenuExtras?: () => ReactNode;
|
|
102
116
|
/** Called when user clicks the "Import" button in the Library panel. */
|
|
103
117
|
readonly onImportTemplates?: () => void;
|
|
104
118
|
/**
|
|
@@ -128,6 +142,13 @@ export interface DiagramProps {
|
|
|
128
142
|
* on reload).
|
|
129
143
|
*/
|
|
130
144
|
readonly persistTheme?: boolean | string;
|
|
145
|
+
/**
|
|
146
|
+
* Persist the per-user editor preferences (`EditorPreferences`: object
|
|
147
|
+
* snapping, size readouts, wheel mode — the canvas menu's check rows) in
|
|
148
|
+
* `localStorage`. Pass `true` for the default key `"diagram-preferences"`,
|
|
149
|
+
* or a custom key string. Omit to keep them in memory for the session.
|
|
150
|
+
*/
|
|
151
|
+
readonly persistPreferences?: boolean | string;
|
|
131
152
|
/**
|
|
132
153
|
* URL for the "GitHub" link in the Help menu. Omit to use the
|
|
133
154
|
* project's repository; pass your own, or `null` to hide the link
|
package/dist/Diagram.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Diagram.d.ts","sourceRoot":"","sources":["../src/Diagram.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,aAAa,EAClB,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"Diagram.d.ts","sourceRoot":"","sources":["../src/Diagram.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,aAAa,EAClB,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAmGf,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAa,MAAM,wBAAwB,CAAC;AAMnG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,OAAO,EAGL,KAAK,KAAK,EAEV,KAAK,SAAS,EACf,MAAM,wBAAwB,CAAC;AAMhC,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAE,KAAK,QAAQ,EAAmB,MAAM,4BAA4B,CAAC;AAC5E,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACvB,MAAM,gBAAgB,CAAC;AAexB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,qFAAqF;IACrF,QAAQ,CAAC,YAAY,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAEhD,QAAQ,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAE3C,QAAQ,CAAC,aAAa,EAAE,MAAM,UAAU,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAE7C,QAAQ,CAAC,YAAY,EAAE,MAAM,WAAW,CAAC,SAAS,CAAC,CAAC;IACpD,QAAQ,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IAE1D,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;IAE1B,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAE3B,QAAQ,CAAC,YAAY,CAAC,EAAE,KAAK,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC;IAK5B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;KAAE,CAAC;IAC3E,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAGxB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACvD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAClD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAG9D,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IAGnE,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IAGtC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAG3B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,SAAS,CAAC;IAC5C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,SAAS,CAAC;IAC9C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,SAAS,CAAC;IAC7C,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,SAAS,CAAC;IAC/C,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,SAAS,CAAC;IACjD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,SAAS,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,SAAS,CAAC;IAChD,4GAA4G;IAC5G,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,SAAS,CAAC;IACjD;;;OAGG;IACH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,SAAS,CAAC;IAClD,wEAAwE;IACxE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAGxC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpD;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,KAAK,IAAI,CAAC;IACtE;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAG/C;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGvC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAClD;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAG9C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEvD,eAAO,MAAM,OAAO,qGAyblB,CAAC"}
|
package/dist/Diagram.js
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
|
|
3
|
-
import { Clipboard, Copy, Delete, Download, FileDown, FileUp, Grid3x3, Grip,
|
|
4
|
-
import { BottomBar, BottomSheet, ButtonGroup, ContextMenu, DEFAULT_CONTEXT_MENU, DEFAULT_VERTICAL_TOOLBAR, DiagramRoot, CommandPalette, DiagramSurface, HelpButton, HelpDialog, IconButton, DrawingPanel, LibraryPanel, MainMenu, Minimap, ResetToContentButton, LinkHoverPopup, LinkDropShapeMenu, LinkCaptionEditor, SelectionFloatingPanel, SearchOverlay, StatsPanel, TextEditorOverlay, FrameNameEditorOverlay, PortalContainerProvider, ToastHost, Toolbar,
|
|
3
|
+
import { Ban, Clipboard, Command, Copy, Delete, Download, Expand, Eye, FileDown, FileUp, Grid3x3, Grip, ImageDown, Keyboard, LayoutDashboard, Magnet, Map as MapIcon, Maximize, Minus, Monitor, Moon, Mouse, MousePointer, Pencil, Plus, Redo2, RotateCcw, Ruler, Scissors, Search, Shrink, SlidersHorizontal, Sun, Undo2, ZoomIn, } from "lucide-react";
|
|
4
|
+
import { BottomBar, BottomSheet, ButtonGroup, ContextMenu, DEFAULT_CONTEXT_MENU, DEFAULT_VERTICAL_TOOLBAR, DiagramRoot, CommandPalette, DiagramSurface, HelpButton, HelpDialog, IconButton, DrawingPanel, LibraryPanel, MainMenu, Minimap, ResetToContentButton, LinkHoverPopup, LinkBadges, StickyReactions, LinkDropShapeMenu, LinkCaptionEditor, SelectionFloatingPanel, SearchOverlay, StatsPanel, TextEditorOverlay, FrameNameEditorOverlay, PortalContainerProvider, ToastHost, Toolbar, TooltipProvider, TopBar, UILayer, ZenModeProvider, useDiagramOptional, useHelpDialogHotkey, useMobileLayout, usePalettePlacement, useEditorSelector, useFullscreen, useZenMode, CONTROL_ICON, ROW_ICON, FileDropOverlay, } from "@oh-just-another/react-ui";
|
|
5
|
+
import { BrandLogo } from "./brand/brand-logo.js";
|
|
6
|
+
import { diagramFileDropHandler } from "@oh-just-another/importers";
|
|
5
7
|
/**
|
|
6
8
|
* Lucide icon sizing — `MENU_ICON_SIZE` is for in-row icons of
|
|
7
|
-
* `MainMenu.Item`, `
|
|
8
|
-
* Grid toggles, `BUTTON_ICON_SIZE` is for `IconButton` slot
|
|
9
|
+
* `MainMenu.Item`, `BUTTON_ICON_SIZE` is for `IconButton` slot
|
|
9
10
|
* children (library, zoom, fit). All share `BUTTON_ICON_STROKE`
|
|
10
11
|
* for visual consistency with the toolbar.
|
|
11
12
|
*/
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const BUTTON_ICON_SIZE = 16;
|
|
15
|
-
const BUTTON_ICON_STROKE = 1.75;
|
|
16
|
-
const menuIcon = { size: MENU_ICON_SIZE, strokeWidth: BUTTON_ICON_STROKE };
|
|
17
|
-
const toggleIcon = { size: TOGGLE_ICON_SIZE, strokeWidth: BUTTON_ICON_STROKE };
|
|
18
|
-
const buttonIcon = { size: BUTTON_ICON_SIZE, strokeWidth: BUTTON_ICON_STROKE };
|
|
13
|
+
const menuIcon = ROW_ICON;
|
|
14
|
+
const buttonIcon = CONTROL_ICON;
|
|
19
15
|
/** Default target for the Help-menu "GitHub" link (overridable / hideable via the `repositoryUrl` prop). */
|
|
20
16
|
const DEFAULT_REPOSITORY_URL = "https://github.com/oh-just-another/diagram";
|
|
17
|
+
import { defaultActionRegistry } from "@oh-just-another/state";
|
|
18
|
+
import { DEFAULT_PREFERENCES_STORAGE_KEY, bindPreferencesPersistence, } from "./preferences-storage.js";
|
|
21
19
|
import { formatHotkey } from "@oh-just-another/state";
|
|
22
20
|
import { hydrateScene, isText, } from "@oh-just-another/scene";
|
|
23
21
|
import { WasmTextShaper } from "@oh-just-another/text-wasm";
|
|
@@ -33,7 +31,7 @@ import { useThemedPortalContainer } from "./themed-portal-container.js";
|
|
|
33
31
|
import { downloadScene, downloadSvg, downloadPng, openSceneFile, copySceneAsImage, registerFileActions, setFileActionNotifier, } from "./file-actions.js";
|
|
34
32
|
import { isEditableTarget } from "./dom-focus";
|
|
35
33
|
export const Diagram = forwardRef(function Diagram(props, ref) {
|
|
36
|
-
const { initialScene, initialTool = "select", grid, snap, templates, fileDropHandlers, layoutKinds, animationAdapters, onReady, onSceneChange, onSelectionChange, capabilities: capabilityOverrides, workerFactory, hideTopBar, hideBottomBar, hideToolbar, hideLibraryButton, hideMainMenu, hideZoomControls, hideResetToContent, hideHelpButton, hideContextMenu, hideSelectionPanel, hideDrawingPanel, minimap, renderTopBarLeft, renderTopBarCenter, renderTopBarRight, renderBottomBarLeft, renderBottomBarCenter, renderBottomBarRight, renderMainMenuExtras, onImportTemplates, theme: themeProp, defaultTheme = "system", onThemeChange, persistTheme, repositoryUrl, onConfirm, onNotify, className, style, } = props;
|
|
34
|
+
const { initialScene, initialTool = "select", grid, snap, templates, fileDropHandlers, layoutKinds, animationAdapters, onReady, onSceneChange, onSelectionChange, capabilities: capabilityOverrides, workerFactory, hideTopBar, hideBottomBar, hideToolbar, hideLibraryButton, hideMainMenu, hideZoomControls, hideResetToContent, hideHelpButton, hideContextMenu, hideSelectionPanel, hideDrawingPanel, minimap, logo = _jsx(BrandLogo, {}), renderTopBarLeft, renderTopBarCenter, renderTopBarRight, renderBottomBarLeft, renderBottomBarCenter, renderBottomBarRight, renderMainMenuExtras, renderBoardMenuExtras, renderExportMenuExtras, onImportTemplates, theme: themeProp, defaultTheme = "system", onThemeChange, persistTheme, persistPreferences, repositoryUrl, onConfirm, onNotify, className, style, } = props;
|
|
37
35
|
// Theme: controlled when `themeProp` is provided, otherwise
|
|
38
36
|
// self-managed via `internalTheme`. The lazy `useState` initializer
|
|
39
37
|
// reads from `localStorage` once when `persistTheme` is on.
|
|
@@ -173,6 +171,9 @@ export const Diagram = forwardRef(function Diagram(props, ref) {
|
|
|
173
171
|
// --- Editor wiring ---
|
|
174
172
|
const [editor, setEditor] = useState(null);
|
|
175
173
|
const handleReady = useCallback((e) => {
|
|
174
|
+
// Built-in diagram import (native JSON, Excalidraw, Mermaid, …) first,
|
|
175
|
+
// then the host's handlers.
|
|
176
|
+
e.registerFileDropHandler(diagramFileDropHandler);
|
|
176
177
|
if (fileDropHandlers) {
|
|
177
178
|
for (const handler of fileDropHandlers)
|
|
178
179
|
e.registerFileDropHandler(handler);
|
|
@@ -180,6 +181,30 @@ export const Diagram = forwardRef(function Diagram(props, ref) {
|
|
|
180
181
|
setEditor(e);
|
|
181
182
|
onReady?.(e);
|
|
182
183
|
}, [fileDropHandlers, onReady]);
|
|
184
|
+
// Minimap visibility: the `minimap` prop seeds it; the zoom menu's
|
|
185
|
+
// "Hide / Show minimap" row and the `M` key toggle it at runtime.
|
|
186
|
+
const [minimapVisible, setMinimapVisible] = useState(minimap === true);
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
setMinimapVisible(minimap === true);
|
|
189
|
+
}, [minimap]);
|
|
190
|
+
const toggleMinimap = useCallback(() => {
|
|
191
|
+
setMinimapVisible((v) => !v);
|
|
192
|
+
}, []);
|
|
193
|
+
// Fullscreen target: the editor root (chrome + canvas), see the zoom menu.
|
|
194
|
+
const rootRef = useRef(null);
|
|
195
|
+
// Per-user preferences: load once the editor exists, then mirror changes.
|
|
196
|
+
const preferencesKey = useMemo(() => {
|
|
197
|
+
if (persistPreferences === true)
|
|
198
|
+
return DEFAULT_PREFERENCES_STORAGE_KEY;
|
|
199
|
+
if (typeof persistPreferences === "string")
|
|
200
|
+
return persistPreferences;
|
|
201
|
+
return null;
|
|
202
|
+
}, [persistPreferences]);
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
if (!editor || !preferencesKey)
|
|
205
|
+
return undefined;
|
|
206
|
+
return bindPreferencesPersistence(editor, preferencesKey);
|
|
207
|
+
}, [editor, preferencesKey]);
|
|
183
208
|
useEffect(() => {
|
|
184
209
|
if (!editor)
|
|
185
210
|
return undefined;
|
|
@@ -249,6 +274,11 @@ export const Diagram = forwardRef(function Diagram(props, ref) {
|
|
|
249
274
|
ed.cancelFlowchart();
|
|
250
275
|
e.preventDefault();
|
|
251
276
|
}
|
|
277
|
+
// `M` — toggle the minimap (zoom menu parity).
|
|
278
|
+
if (e.key.toLowerCase() === "m" && !e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) {
|
|
279
|
+
toggleMinimap();
|
|
280
|
+
e.preventDefault();
|
|
281
|
+
}
|
|
252
282
|
};
|
|
253
283
|
const onKeyUp = (e) => {
|
|
254
284
|
sync(e);
|
|
@@ -275,7 +305,7 @@ export const Diagram = forwardRef(function Diagram(props, ref) {
|
|
|
275
305
|
ed.setSnapSuppressed(false);
|
|
276
306
|
ed.setTransformModifiers({ alt: false, shift: false });
|
|
277
307
|
};
|
|
278
|
-
}, [editor]);
|
|
308
|
+
}, [editor, toggleMinimap]);
|
|
279
309
|
useImperativeHandle(ref, () => ({
|
|
280
310
|
editor,
|
|
281
311
|
capabilities: profile,
|
|
@@ -309,7 +339,7 @@ export const Diagram = forwardRef(function Diagram(props, ref) {
|
|
|
309
339
|
if (profile.renderer === "webgl2" && profile.wasmText && !wasmTextSettled && sceneHasText) {
|
|
310
340
|
return _jsx("div", { className: className, style: style });
|
|
311
341
|
}
|
|
312
|
-
return (_jsx(PortalContainerProvider, { container: portalContainer, children: _jsx(ToastHost, { children: _jsx(TooltipProvider, { children: _jsx("div", { className: className, "data-diagram-root": true, ...(theme === "system" ? {} : { "data-theme": theme }), style: {
|
|
342
|
+
return (_jsx(PortalContainerProvider, { container: portalContainer, children: _jsx(ToastHost, { children: _jsx(TooltipProvider, { children: _jsx("div", { ref: rootRef, className: className, "data-diagram-root": true, ...(theme === "system" ? {} : { "data-theme": theme }), style: {
|
|
313
343
|
position: "relative",
|
|
314
344
|
width: "100%",
|
|
315
345
|
height: "100%",
|
|
@@ -317,7 +347,7 @@ export const Diagram = forwardRef(function Diagram(props, ref) {
|
|
|
317
347
|
...style,
|
|
318
348
|
}, children: _jsx(DiagramRoot, { initialScene: seed, initialTool: initialTool, onReady: handleReady, renderer: profile.renderer, ...(profile.renderer === "offscreen"
|
|
319
349
|
? { workerFactory: workerFactory ?? createRenderWorker }
|
|
320
|
-
: {}), ...(wasmShaper ? { textShaper: wasmShaper } : {}), ...(wasmRaster ? { rasterizer: wasmRaster } : {}), children: _jsx(ZenModeProvider, { children: _jsx(EditorShell, { hideTopBar: hideTopBar, hideBottomBar: hideBottomBar, hideToolbar: hideToolbar, hideLibraryButton: hideLibraryButton, hideMainMenu: hideMainMenu, hideZoomControls: hideZoomControls, hideResetToContent: hideResetToContent, hideHelpButton: hideHelpButton, hideContextMenu: hideContextMenu, hideSelectionPanel: hideSelectionPanel, hideDrawingPanel: hideDrawingPanel,
|
|
350
|
+
: {}), ...(wasmShaper ? { textShaper: wasmShaper } : {}), ...(wasmRaster ? { rasterizer: wasmRaster } : {}), children: _jsx(ZenModeProvider, { children: _jsx(EditorShell, { hideTopBar: hideTopBar, hideBottomBar: hideBottomBar, hideToolbar: hideToolbar, hideLibraryButton: hideLibraryButton, hideMainMenu: hideMainMenu, logo: logo, hideZoomControls: hideZoomControls, hideResetToContent: hideResetToContent, hideHelpButton: hideHelpButton, hideContextMenu: hideContextMenu, hideSelectionPanel: hideSelectionPanel, hideDrawingPanel: hideDrawingPanel, minimapVisible: minimapVisible, onToggleMinimap: toggleMinimap, rootRef: rootRef, renderTopBarLeft: renderTopBarLeft, renderTopBarCenter: renderTopBarCenter, renderTopBarRight: renderTopBarRight, renderBottomBarLeft: renderBottomBarLeft, renderBottomBarCenter: renderBottomBarCenter, renderBottomBarRight: renderBottomBarRight, renderMainMenuExtras: renderMainMenuExtras, renderBoardMenuExtras: renderBoardMenuExtras, renderExportMenuExtras: renderExportMenuExtras, onImportTemplates: onImportTemplates, repositoryUrl: repositoryUrl, onConfirm: onConfirm, onNotify: onNotify, theme: theme, changeTheme: changeTheme }) }) }) }) }) }) }));
|
|
321
351
|
});
|
|
322
352
|
// Match the primary public name (`<Editor>`) in DevTools / stack traces,
|
|
323
353
|
// even though the internal forwardRef function is named `Diagram`.
|
|
@@ -328,11 +358,12 @@ Diagram.displayName = "Editor";
|
|
|
328
358
|
* `useHelpDialogHotkey`) resolve correctly. Composes the
|
|
329
359
|
* canvas-surface + ui-layer overlay + side panels into one tree.
|
|
330
360
|
*/
|
|
331
|
-
const EditorShell = ({ hideTopBar, hideBottomBar, hideToolbar, hideLibraryButton, hideMainMenu, hideZoomControls, hideResetToContent, hideHelpButton, hideContextMenu, hideSelectionPanel, hideDrawingPanel,
|
|
361
|
+
const EditorShell = ({ hideTopBar, hideBottomBar, hideToolbar, hideLibraryButton, hideMainMenu, logo, hideZoomControls, hideResetToContent, hideHelpButton, hideContextMenu, hideSelectionPanel, hideDrawingPanel, minimapVisible, onToggleMinimap, rootRef, renderTopBarLeft, renderTopBarCenter, renderTopBarRight, renderBottomBarLeft, renderBottomBarCenter, renderBottomBarRight, renderMainMenuExtras, renderBoardMenuExtras, renderExportMenuExtras, onImportTemplates, repositoryUrl, onConfirm, onNotify, theme, changeTheme, }) => {
|
|
332
362
|
const editor = useDiagramOptional();
|
|
333
363
|
// Zen mode (⌥Z): hide every chrome surface for focused work, leaving the
|
|
334
364
|
// canvas + the observational overlays (search, stats, command palette).
|
|
335
365
|
const { zen } = useZenMode();
|
|
366
|
+
const fullscreen = useFullscreen(rootRef);
|
|
336
367
|
// Omitted → project repo; explicit string → that URL; null → no link.
|
|
337
368
|
const repositoryHref = repositoryUrl === undefined ? DEFAULT_REPOSITORY_URL : repositoryUrl;
|
|
338
369
|
// Native dialogs by default; hosts can route through their own UI.
|
|
@@ -349,11 +380,20 @@ const EditorShell = ({ hideTopBar, hideBottomBar, hideToolbar, hideLibraryButton
|
|
|
349
380
|
registerFileActions();
|
|
350
381
|
setFileActionNotifier(notify);
|
|
351
382
|
}, [notify]);
|
|
352
|
-
// Subscribe to
|
|
353
|
-
// the
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
383
|
+
// Subscribe ONLY to the Grid / Snap toggle VALUES, not the scene
|
|
384
|
+
// identity: the scene reference flips on every frame of a drag, and a
|
|
385
|
+
// whole-shell re-render (menus, toolbars, HelpDialog) per frame makes
|
|
386
|
+
// moving elements visibly sluggish. The selectors return primitives,
|
|
387
|
+
// so `Object.is` skips re-renders until a toggle actually changes.
|
|
388
|
+
useEditorSelector((e) => gridSelection(e), "lines", "scene");
|
|
389
|
+
useEditorSelector((e) => snapSelection(e), "on");
|
|
390
|
+
// Preference switches in the View / Preferences submenus.
|
|
391
|
+
const showObjectSize = useEditorSelector((e) => e.preferences.showObjectSize, true);
|
|
392
|
+
const snapObjects = useEditorSelector((e) => e.preferences.snapObjects, true);
|
|
393
|
+
const suggestObjectSize = useEditorSelector((e) => e.preferences.suggestObjectSize, true);
|
|
394
|
+
const wheelMode = useEditorSelector((e) => e.preferences.wheelMode, "auto");
|
|
395
|
+
const [fileDragging, setFileDragging] = useState(false);
|
|
396
|
+
const paletteDropHandlers = usePalettePlacement({ onFileDrag: setFileDragging });
|
|
357
397
|
// Touch / narrow screens: the library opens as a bottom sheet instead of
|
|
358
398
|
// a left overlay (which would cover the whole small canvas).
|
|
359
399
|
const mobile = useMobileLayout();
|
|
@@ -361,6 +401,13 @@ const EditorShell = ({ hideTopBar, hideBottomBar, hideToolbar, hideLibraryButton
|
|
|
361
401
|
// toggle and closed via its ✕. Starts closed; no dock / pin.
|
|
362
402
|
const [libraryOpen, setLibraryOpen] = useState(false);
|
|
363
403
|
const [helpOpen, setHelpOpen] = useState(false);
|
|
404
|
+
// Export content switches (sticky reactions / tags / author) — session
|
|
405
|
+
// state seeded by EXPORT_CONTENT_DEFAULTS inside the export helpers;
|
|
406
|
+
// the Export… submenu checkboxes flip them per run.
|
|
407
|
+
const [exportContent, setExportContent] = useState({});
|
|
408
|
+
const toggleExportContent = (key) => {
|
|
409
|
+
setExportContent((cur) => ({ ...cur, [key]: !(cur?.[key] ?? true) }));
|
|
410
|
+
};
|
|
364
411
|
useHelpDialogHotkey(() => {
|
|
365
412
|
setHelpOpen((v) => !v);
|
|
366
413
|
});
|
|
@@ -369,51 +416,37 @@ const EditorShell = ({ hideTopBar, hideBottomBar, hideToolbar, hideLibraryButton
|
|
|
369
416
|
// The library overlays the canvas (no reflow); the toolbar floats just
|
|
370
417
|
// to its right when open, else near the edge. On mobile the library is a
|
|
371
418
|
// bottom sheet, so the toolbar never shifts.
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const toolbarLeft = !mobile && libraryOpen
|
|
419
|
+
// Dock inset from the edge, or past the open library (its inset + width)
|
|
420
|
+
// with a flyout gap — all CSS tokens so hosts retune them in one place.
|
|
421
|
+
const toolbarLeft = !mobile && libraryOpen
|
|
422
|
+
? "var(--du-bar-inset) + var(--du-side-panel-w) + var(--du-flyout-gap)"
|
|
423
|
+
: "var(--du-dock-inset)";
|
|
375
424
|
// Items for the vertical creation dock: an optional templates-library
|
|
376
425
|
// toggle on top (hidden with `hideLibraryButton`), then the standard
|
|
377
426
|
// creation tools.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
active: libraryOpen,
|
|
387
|
-
onClick: () => {
|
|
388
|
-
setLibraryOpen((v) => !v);
|
|
389
|
-
},
|
|
427
|
+
// The template library's only toolbar entry point is the "More shapes"
|
|
428
|
+
// row inside the Shapes and lines flyout (`hideLibraryButton` removes
|
|
429
|
+
// it). No standalone library toggle in the dock.
|
|
430
|
+
const toolbarItems = useMemo(() => DEFAULT_VERTICAL_TOOLBAR.map((item) => item.kind === "shapes-flyout" && !hideLibraryButton
|
|
431
|
+
? {
|
|
432
|
+
...item,
|
|
433
|
+
onMoreShapes: () => {
|
|
434
|
+
setLibraryOpen(true);
|
|
390
435
|
},
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return (_jsxs("div", { style: { position: "absolute", inset: 0 }, onDragEnter: paletteDropHandlers.onDragEnter, onDragOver: paletteDropHandlers.onDragOver, onDragLeave: paletteDropHandlers.onDragLeave, onDrop: paletteDropHandlers.onDrop, children: [_jsxs("div", { style: {
|
|
436
|
+
}
|
|
437
|
+
: item), [hideLibraryButton]);
|
|
438
|
+
return (_jsxs("div", { style: { position: "absolute", inset: 0 }, onDragEnter: paletteDropHandlers.onDragEnter, onDragOver: paletteDropHandlers.onDragOver, onDragLeave: paletteDropHandlers.onDragLeave, onDrop: paletteDropHandlers.onDrop, children: [_jsx(FileDropOverlay, { active: fileDragging }), _jsxs("div", { style: {
|
|
395
439
|
position: "absolute",
|
|
396
440
|
top: 0,
|
|
397
441
|
bottom: 0,
|
|
398
442
|
left: 0,
|
|
399
443
|
right: 0,
|
|
400
|
-
}, children: [_jsx(DiagramSurface, { style: { position: "absolute", inset: 0 } }), _jsx(TextEditorOverlay, {}), _jsx(FrameNameEditorOverlay, {}), _jsx(LinkHoverPopup, {}), _jsx(LinkDropShapeMenu, {}), _jsx(LinkCaptionEditor, {}), !hideContextMenu && _jsx(ContextMenu, { items: DEFAULT_CONTEXT_MENU })] }), !hideToolbar && !zen ? (_jsx(
|
|
401
|
-
|
|
402
|
-
//
|
|
403
|
-
// the
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
top: "50%",
|
|
407
|
-
left: `calc(env(safe-area-inset-left, 0px) + ${toolbarLeft}px)`,
|
|
408
|
-
transform: "translateY(-50%)",
|
|
409
|
-
zIndex: 60,
|
|
410
|
-
} })) : null, _jsxs(UILayer, { children: [!hideTopBar && !zen && (_jsx(TopBar, { left: _jsxs(ButtonGroup, { ariaLabel: "Logo and main menu", children: [_jsx("span", { "aria-label": "Diagram", title: "Diagram", className: "du-icon-button", style: {
|
|
411
|
-
minWidth: 36,
|
|
412
|
-
padding: "0 10px",
|
|
413
|
-
cursor: "default",
|
|
414
|
-
fontWeight: 600,
|
|
415
|
-
letterSpacing: 0.5,
|
|
416
|
-
}, children: "\u2317" }), !hideMainMenu && (_jsxs(MainMenu, { children: [_jsxs(MainMenu.Group, { title: "File", children: [_jsx(MainMenu.Item, { icon: _jsx(FileUp, { ...menuIcon }), onClick: () => {
|
|
444
|
+
}, children: [_jsx(DiagramSurface, { style: { position: "absolute", inset: 0 } }), _jsx(TextEditorOverlay, {}), _jsx(FrameNameEditorOverlay, {}), _jsx(LinkHoverPopup, {}), _jsx(LinkBadges, {}), _jsx(StickyReactions, {}), _jsx(LinkDropShapeMenu, {}), _jsx(LinkCaptionEditor, {}), !hideContextMenu && _jsx(ContextMenu, { items: DEFAULT_CONTEXT_MENU })] }), !hideToolbar && !zen ? (_jsx("div", { className: "du-dock", style: {
|
|
445
|
+
// Floats just to the right of the library when it's open (else
|
|
446
|
+
// near the edge). `env(safe-area-inset-left)` is 0 on desktop,
|
|
447
|
+
// clears the notch in mobile landscape.
|
|
448
|
+
left: `calc(env(safe-area-inset-left, 0px) + ${toolbarLeft})`,
|
|
449
|
+
}, children: _jsx(Toolbar, { orientation: "vertical", items: toolbarItems }) })) : null, _jsxs(UILayer, { children: [!hideTopBar && !zen && (_jsx(TopBar, { left: _jsxs(ButtonGroup, { ariaLabel: "Logo and main menu", children: [logo !== null && _jsx("span", { className: "du-icon-button du-brand", children: logo }), !hideMainMenu && (_jsxs(MainMenu, { children: [_jsxs(MainMenu.Submenu, { icon: _jsx(LayoutDashboard, { ...menuIcon }), label: "Board", children: [_jsx(MainMenu.Item, { icon: _jsx(FileUp, { ...menuIcon }), onClick: () => {
|
|
417
450
|
if (editor)
|
|
418
451
|
openSceneFile(editor);
|
|
419
452
|
}, shortcut: formatHotkey({ key: "O", meta: true }), children: "Open\u2026" }), _jsx(MainMenu.Item, { icon: _jsx(FileDown, { ...menuIcon }), onClick: () => {
|
|
@@ -422,10 +455,16 @@ const EditorShell = ({ hideTopBar, hideBottomBar, hideToolbar, hideLibraryButton
|
|
|
422
455
|
}, disabled: !editor, shortcut: formatHotkey({ key: "S", meta: true }), children: "Save as JSON" }), _jsx(MainMenu.Item, { icon: _jsx(Copy, { ...menuIcon }), onClick: () => {
|
|
423
456
|
if (editor)
|
|
424
457
|
void copySceneAsImage(editor);
|
|
425
|
-
}, disabled: !editor, shortcut: formatHotkey({ key: "C", shift: true, alt: true }), children: "Copy as image" }), _jsxs(MainMenu.Submenu, { icon: _jsx(Download, { ...menuIcon }), label: "Export
|
|
458
|
+
}, disabled: !editor, shortcut: formatHotkey({ key: "C", shift: true, alt: true }), children: "Copy as image" }), _jsxs(MainMenu.Submenu, { icon: _jsx(Download, { ...menuIcon }), label: "Export", disabled: !editor, children: [_jsx(MainMenu.Item, { icon: _jsx(ImageDown, { ...menuIcon }), onClick: () => editor && void downloadPng(editor, "transparent", exportContent), disabled: !editor, children: "PNG (transparent)" }), _jsx(MainMenu.Item, { icon: _jsx(ImageDown, { ...menuIcon }), onClick: () => editor && void downloadPng(editor, "color", exportContent), disabled: !editor, shortcut: formatHotkey({ key: "E", meta: true, shift: true }), children: "PNG (with background)" }), _jsx(MainMenu.Item, { icon: _jsx(ImageDown, { ...menuIcon }), onClick: () => editor && void downloadPng(editor, "color-and-grid", exportContent), disabled: !editor, children: "PNG (with background + grid)" }), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.Item, { icon: _jsx(Download, { ...menuIcon }), onClick: () => {
|
|
426
459
|
if (editor)
|
|
427
|
-
downloadSvg(editor.scene);
|
|
428
|
-
}, disabled: !editor, children: "SVG" })
|
|
460
|
+
downloadSvg(editor.scene, exportContent);
|
|
461
|
+
}, disabled: !editor, children: "SVG" }), renderExportMenuExtras?.(), _jsx(MainMenu.Separator, {}), _jsxs(MainMenu.Group, { title: "Include in export", children: [_jsx(MainMenu.Item, { keepOpen: true, onClick: () => {
|
|
462
|
+
toggleExportContent("stickyReactions");
|
|
463
|
+
}, checked: exportContent?.stickyReactions !== false, children: "Sticky reactions" }), _jsx(MainMenu.Item, { keepOpen: true, onClick: () => {
|
|
464
|
+
toggleExportContent("stickyTags");
|
|
465
|
+
}, checked: exportContent?.stickyTags !== false, children: "Sticky tags" }), _jsx(MainMenu.Item, { keepOpen: true, onClick: () => {
|
|
466
|
+
toggleExportContent("stickyAuthor");
|
|
467
|
+
}, checked: exportContent?.stickyAuthor !== false, children: "Sticky author" })] })] }), renderBoardMenuExtras?.(), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.Item, { icon: _jsx(MapIcon, { ...menuIcon }), onClick: () => editor?.goToStartView(), disabled: (editor?.startView ?? null) === null, children: "Start view" }), _jsx(MainMenu.Item, { icon: _jsx(MapIcon, { ...menuIcon }), onClick: () => editor?.setCurrentViewAsStart(), disabled: !editor, children: "Set current view as start" }), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.Item, { icon: _jsx(RotateCcw, { ...menuIcon }), onClick: () => {
|
|
429
468
|
if (!editor)
|
|
430
469
|
return;
|
|
431
470
|
if (confirmDialog("Reset canvas? This clears all shapes.")) {
|
|
@@ -436,48 +475,29 @@ const EditorShell = ({ hideTopBar, hideBottomBar, hideToolbar, hideLibraryButton
|
|
|
436
475
|
// DEFAULT_VIEWPORT has it disabled.
|
|
437
476
|
editor.clear();
|
|
438
477
|
}
|
|
439
|
-
}, disabled: !editor, children: "Reset canvas" })] }),
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
{ value: "off", label: "Off", icon: _jsx(Minus, { ...toggleIcon }) },
|
|
454
|
-
] }) }), _jsx(MainMenu.Separator, {}), _jsxs(MainMenu.Group, { title: "Help", children: [_jsx(MainMenu.Item, { icon: _jsx(HelpCircle, { ...menuIcon }), shortcut: "?", onClick: () => {
|
|
455
|
-
setHelpOpen(true);
|
|
456
|
-
}, children: "Hotkeys" }), repositoryHref ? (_jsx(MainMenu.ItemLink, { href: repositoryHref, external: true, children: "GitHub" })) : null] }), renderMainMenuExtras ? (_jsxs(_Fragment, { children: [_jsx(MainMenu.Separator, {}), renderMainMenuExtras()] })) : null] })), renderTopBarLeft ? renderTopBarLeft() : null] }), center: renderTopBarCenter?.(), right: _jsx(ButtonGroup, { ariaLabel: "Top bar actions", children: renderTopBarRight ? renderTopBarRight() : null }) })), !hideBottomBar && !zen && (_jsx(BottomBar, { left: renderBottomBarLeft ? renderBottomBarLeft() : null, center: renderBottomBarCenter ? (renderBottomBarCenter()) : !hideResetToContent ? (_jsx(ResetToContentButton, {})) : null, right: renderBottomBarRight ? (renderBottomBarRight()) : !hideZoomControls ? (
|
|
478
|
+
}, disabled: !editor, children: "Reset canvas" })] }), _jsxs(MainMenu.Submenu, { icon: _jsx(Pencil, { ...menuIcon }), label: "Edit", children: [_jsx(MainMenu.Item, { icon: _jsx(Undo2, { ...menuIcon }), shortcut: "\u2318Z", onClick: () => editor?.undo(), disabled: !editor, children: "Undo" }), _jsx(MainMenu.Item, { icon: _jsx(Redo2, { ...menuIcon }), shortcut: "\u21E7\u2318Z", onClick: () => editor?.redo(), disabled: !editor, children: "Redo" }), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.Item, { icon: _jsx(Scissors, { ...menuIcon }), shortcut: "\u2318X", onClick: () => editor?.cutSelected(), disabled: !editor, children: "Cut" }), _jsx(MainMenu.Item, { icon: _jsx(Copy, { ...menuIcon }), shortcut: "\u2318C", onClick: () => editor?.copySelected(), disabled: !editor, children: "Copy" }), _jsx(MainMenu.Item, { icon: _jsx(Clipboard, { ...menuIcon }), shortcut: "\u2318V", onClick: () => editor?.paste(), disabled: !editor, children: "Paste" }), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.Item, { icon: _jsx(MousePointer, { ...menuIcon }), shortcut: "\u2318A", onClick: () => editor?.selectAll(), disabled: !editor, children: "Select all" }), _jsx(MainMenu.Item, { icon: _jsx(Delete, { ...menuIcon }), shortcut: "\u232B", onClick: () => editor?.deleteSelected(), disabled: !editor, children: "Delete selected" }), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.Item, { icon: _jsx(Command, { ...menuIcon }), shortcut: "\u2318K", onClick: () => {
|
|
479
|
+
if (editor) {
|
|
480
|
+
defaultActionRegistry.dispatch("open-command-palette", { editor });
|
|
481
|
+
}
|
|
482
|
+
}, disabled: !editor, children: "Commands" }), _jsx(MainMenu.Item, { icon: _jsx(Search, { ...menuIcon }), shortcut: "\u2318F", onClick: () => {
|
|
483
|
+
if (editor)
|
|
484
|
+
defaultActionRegistry.dispatch("open-search", { editor });
|
|
485
|
+
}, disabled: !editor, children: "Find" })] }), _jsxs(MainMenu.Submenu, { icon: _jsx(Eye, { ...menuIcon }), label: "View", children: [_jsxs(MainMenu.Submenu, { icon: _jsx(Grid3x3, { ...menuIcon }), label: "Grid", children: [GRID_OPTIONS.map((opt) => (_jsx(MainMenu.Item, { icon: opt.icon, active: gridSelection(editor) === opt.value, onClick: () => {
|
|
486
|
+
applyGridSelection(editor, opt.value);
|
|
487
|
+
}, children: opt.label }, opt.value))), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.Item, { icon: _jsx(Magnet, { ...menuIcon }), keepOpen: true, onClick: () => editor?.setSnapToGrid(!editor.snapToGridEnabled), checked: snapSelection(editor) === "on", children: "Snap to grid" })] }), _jsx(MainMenu.Item, { icon: _jsx(Ruler, { ...menuIcon }), keepOpen: true, onClick: () => editor?.setPreferences({ showObjectSize: !showObjectSize }), checked: showObjectSize, children: "Object dimensions" }), _jsx(MainMenu.Item, { icon: _jsx(MapIcon, { ...menuIcon }), keepOpen: true, onClick: onToggleMinimap, checked: minimapVisible, children: "Minimap" }), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.Submenu, { icon: _jsx(Sun, { ...menuIcon }), label: "Theme", children: THEME_OPTIONS.map((opt) => (_jsx(MainMenu.Item, { icon: opt.icon, active: theme === opt.value, onClick: () => {
|
|
488
|
+
changeTheme(opt.value);
|
|
489
|
+
}, children: opt.label }, opt.value))) }), fullscreen.supported ? (_jsxs(_Fragment, { children: [_jsx(MainMenu.Separator, {}), _jsx(MainMenu.Item, { icon: fullscreen.active ? (_jsx(Shrink, { ...menuIcon })) : (_jsx(Expand, { ...menuIcon })), onClick: fullscreen.toggle, children: fullscreen.active ? "Exit full screen" : "Enter full screen" })] })) : null] }), _jsxs(MainMenu.Submenu, { icon: _jsx(SlidersHorizontal, { ...menuIcon }), label: "Preferences", children: [_jsx(MainMenu.Submenu, { icon: _jsx(Mouse, { ...menuIcon }), label: "Mouse or trackpad", children: WHEEL_MODE_OPTIONS.map((opt) => (_jsx(MainMenu.Item, { active: wheelMode === opt.value, onClick: () => editor?.setPreferences({ wheelMode: opt.value }), children: opt.label }, opt.value))) }), _jsx(MainMenu.Item, { icon: _jsx(Magnet, { ...menuIcon }), keepOpen: true, onClick: () => editor?.setPreferences({ snapObjects: !snapObjects }), checked: snapObjects, children: "Snap objects" }), _jsx(MainMenu.Item, { icon: _jsx(Ruler, { ...menuIcon }), keepOpen: true, onClick: () => editor?.setPreferences({ suggestObjectSize: !suggestObjectSize }), checked: suggestObjectSize, children: "Suggest object size" })] }), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.Item, { icon: _jsx(Keyboard, { ...menuIcon }), shortcut: "?", onClick: () => {
|
|
490
|
+
setHelpOpen(true);
|
|
491
|
+
}, children: "Hotkeys" }), repositoryHref ? (_jsx(MainMenu.ItemLink, { href: repositoryHref, external: true, children: "GitHub" })) : null, renderMainMenuExtras ? (_jsxs(_Fragment, { children: [_jsx(MainMenu.Separator, {}), renderMainMenuExtras()] })) : null] })), renderTopBarLeft ? renderTopBarLeft() : null] }), center: renderTopBarCenter?.(), right: _jsx(ButtonGroup, { ariaLabel: "Top bar actions", children: renderTopBarRight ? renderTopBarRight() : null }) })), !hideBottomBar && !zen && (_jsx(BottomBar, { left: renderBottomBarLeft ? renderBottomBarLeft() : null, center: renderBottomBarCenter ? (renderBottomBarCenter()) : !hideResetToContent ? (_jsx(ResetToContentButton, {})) : null, right: renderBottomBarRight ? (renderBottomBarRight()) : !hideZoomControls ? (
|
|
457
492
|
// Help sits inside the zoom pill group, right next to it.
|
|
458
|
-
_jsx(ZoomControls, { trailing: !hideHelpButton ? _jsx(HelpButton, {}) : undefined })) : !hideHelpButton ? (_jsx(HelpButton, {})) : null })), zen ? null : mobile ? (libraryOpen ? (_jsx(BottomSheet, { snapPoints: [0, 60, 92], defaultValue: 60, style: { pointerEvents: "auto" }, onChange: (vh) => {
|
|
493
|
+
_jsx(ZoomControls, { trailing: !hideHelpButton ? _jsx(HelpButton, {}) : undefined, fullscreen: fullscreen, minimapVisible: minimapVisible, onToggleMinimap: onToggleMinimap })) : !hideHelpButton ? (_jsx(HelpButton, {})) : null })), zen ? null : mobile ? (libraryOpen ? (_jsx(BottomSheet, { snapPoints: [0, 60, 92], defaultValue: 60, style: { pointerEvents: "auto" }, onChange: (vh) => {
|
|
459
494
|
if (vh <= 0)
|
|
460
495
|
setLibraryOpen(false);
|
|
461
496
|
}, children: _jsx(LibraryPanel, { open: true, sheet: true, onClose: () => {
|
|
462
497
|
setLibraryOpen(false);
|
|
463
|
-
}, ...(onImportTemplates ? { onImport: onImportTemplates } : {}) }) })) : null) : (_jsx(LibraryPanel, { open: libraryOpen, side: "left",
|
|
498
|
+
}, ...(onImportTemplates ? { onImport: onImportTemplates } : {}) }) })) : null) : (_jsx(LibraryPanel, { open: libraryOpen, side: "left", onClose: () => {
|
|
464
499
|
setLibraryOpen(false);
|
|
465
|
-
}, ...(onImportTemplates ? { onImport: onImportTemplates } : {}) }))] }), minimap && !zen && (_jsx("div", {
|
|
466
|
-
position: "absolute",
|
|
467
|
-
right: "var(--du-bar-inset, 12px)",
|
|
468
|
-
bottom: "calc(var(--du-bar-inset, 12px) + 52px)",
|
|
469
|
-
border: "1px solid var(--du-border, #d0d0d0)",
|
|
470
|
-
borderRadius: 6,
|
|
471
|
-
background: "var(--du-surface, #fff)",
|
|
472
|
-
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
|
|
473
|
-
overflow: "hidden",
|
|
474
|
-
zIndex: 40,
|
|
475
|
-
}, children: _jsx(Minimap, {}) })), !hideDrawingPanel && !zen && (_jsx("div", { style: {
|
|
476
|
-
position: "absolute",
|
|
477
|
-
top: "calc(var(--du-bar-inset, 12px) + 52px)",
|
|
478
|
-
right: "var(--du-bar-inset, 12px)",
|
|
479
|
-
zIndex: 40,
|
|
480
|
-
}, children: _jsx(DrawingPanel, {}) })), !hideSelectionPanel && !zen && _jsx(SelectionFloatingPanel, {}), _jsx(HelpDialog, { open: helpOpen, onClose: () => {
|
|
500
|
+
}, ...(onImportTemplates ? { onImport: onImportTemplates } : {}) }))] }), minimapVisible && !zen && (_jsx("div", { className: "du-minimap-dock", children: _jsx(Minimap, {}) })), !hideDrawingPanel && !zen && (_jsx("div", { className: "du-tool-options-dock", children: _jsx(DrawingPanel, {}) })), !hideSelectionPanel && !zen && _jsx(SelectionFloatingPanel, {}), _jsx(HelpDialog, { open: helpOpen, onClose: () => {
|
|
481
501
|
setHelpOpen(false);
|
|
482
502
|
} }), _jsx(CommandPalette, {}), _jsx(SearchOverlay, {}), _jsx(StatsPanel, {}), void editor] }));
|
|
483
503
|
};
|
|
@@ -500,34 +520,51 @@ const ZOOM_FIT_HOTKEY = formatHotkey({ alt: true, key: "1" });
|
|
|
500
520
|
* `trailing` lets the host append extra controls inside the same pill
|
|
501
521
|
* group (e.g. the Help button, so it sits right next to zoom).
|
|
502
522
|
*/
|
|
503
|
-
|
|
523
|
+
/** Zoom presets of the zoom menu (fractions of 100 %). */
|
|
524
|
+
const ZOOM_PRESETS = [0.5, 0.7, 1, 4, 12, 20];
|
|
525
|
+
const ZoomControls = ({ trailing, fullscreen, minimapVisible, onToggleMinimap, }) => {
|
|
504
526
|
const editor = useDiagramOptional();
|
|
505
|
-
//
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
return undefined;
|
|
510
|
-
return editor.subscribe(() => {
|
|
511
|
-
force((n) => n + 1);
|
|
512
|
-
});
|
|
513
|
-
}, [editor]);
|
|
527
|
+
// Subscribe to the zoom VALUE only — a whole-editor subscription would
|
|
528
|
+
// re-render these buttons on every frame of an element drag.
|
|
529
|
+
const zoom = useEditorSelector((e) => e.scene.viewport.zoom, 1);
|
|
530
|
+
const showObjectSize = useEditorSelector((e) => e.preferences.showObjectSize, true);
|
|
514
531
|
if (!editor)
|
|
515
532
|
return null;
|
|
516
|
-
const
|
|
533
|
+
const setShowObjectSize = (next) => {
|
|
534
|
+
editor.setPreferences({ showObjectSize: next });
|
|
535
|
+
};
|
|
517
536
|
return (_jsxs(ButtonGroup, { ariaLabel: "Zoom", children: [_jsx(IconButton, { label: `Zoom out (${ZOOM_OUT_HOTKEY})`, onClick: () => {
|
|
518
537
|
editor.zoomOut();
|
|
519
|
-
}, children: _jsx(Minus, { ...buttonIcon }) }),
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
538
|
+
}, children: _jsx(Minus, { ...buttonIcon }) }), _jsxs(MainMenu, { ariaLabel: "Zoom menu", placement: "top-end", triggerClassName: "du-icon-button du-icon-button-flat du-zoom-trigger", trigger: _jsxs(_Fragment, { children: [Math.round(zoom * 100), "%"] }), children: [fullscreen.supported ? (_jsx(MainMenu.Item, { icon: fullscreen.active ? _jsx(Shrink, { ...menuIcon }) : _jsx(Expand, { ...menuIcon }), onClick: fullscreen.toggle, children: fullscreen.active ? "Exit full screen" : "Enter full screen" })) : null, _jsx(MainMenu.Item, { icon: _jsx(MapIcon, { ...menuIcon }), shortcut: "M", onClick: onToggleMinimap, children: minimapVisible ? "Hide minimap" : "Show minimap" }), _jsx(MainMenu.Submenu, { icon: _jsx(Grid3x3, { ...menuIcon }), label: "Grid", children: GRID_OPTIONS.map((opt) => (_jsx(MainMenu.Item, { icon: opt.icon, active: gridSelection(editor) === opt.value, onClick: () => {
|
|
539
|
+
applyGridSelection(editor, opt.value);
|
|
540
|
+
}, children: opt.label }, opt.value))) }), _jsx(MainMenu.Item, { icon: _jsx(Ruler, { ...menuIcon }), keepOpen: true, onClick: () => {
|
|
541
|
+
setShowObjectSize(!showObjectSize);
|
|
542
|
+
}, checked: showObjectSize, children: "Object dimensions" }), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.Item, { icon: _jsx(Maximize, { ...menuIcon }), shortcut: ZOOM_FIT_HOTKEY, onClick: () => {
|
|
543
|
+
editor.zoomToFit();
|
|
544
|
+
}, children: "Fit to screen" }), ZOOM_PRESETS.map((level) => (_jsx(MainMenu.Item, { icon: _jsx(ZoomIn, { ...menuIcon }), ...(level === 1 ? { shortcut: ZOOM_RESET_HOTKEY } : {}), onClick: () => {
|
|
545
|
+
editor.setZoom(level);
|
|
546
|
+
}, children: `${String(Math.round(level * 100))}%` }, level)))] }), _jsx(IconButton, { label: `Zoom in (${ZOOM_IN_HOTKEY})`, onClick: () => {
|
|
526
547
|
editor.zoomIn();
|
|
527
548
|
}, children: _jsx(Plus, { ...buttonIcon }) }), _jsx(IconButton, { label: `Fit to screen (${ZOOM_FIT_HOTKEY})`, onClick: () => {
|
|
528
549
|
editor.zoomToFit();
|
|
529
550
|
}, children: _jsx(Maximize, { ...buttonIcon }) }), trailing] }));
|
|
530
551
|
};
|
|
552
|
+
/** Grid submenu rows (reference labels), mapped onto the grid toggle values. */
|
|
553
|
+
const GRID_OPTIONS = [
|
|
554
|
+
{ value: "off", label: "None", icon: _jsx(Ban, { ...menuIcon }) },
|
|
555
|
+
{ value: "lines", label: "Line grid", icon: _jsx(Grid3x3, { ...menuIcon }) },
|
|
556
|
+
{ value: "dots", label: "Dot grid", icon: _jsx(Grip, { ...menuIcon }) },
|
|
557
|
+
];
|
|
558
|
+
const THEME_OPTIONS = [
|
|
559
|
+
{ value: "light", label: "Light", icon: _jsx(Sun, { ...menuIcon }) },
|
|
560
|
+
{ value: "dark", label: "Dark", icon: _jsx(Moon, { ...menuIcon }) },
|
|
561
|
+
{ value: "system", label: "System", icon: _jsx(Monitor, { ...menuIcon }) },
|
|
562
|
+
];
|
|
563
|
+
const WHEEL_MODE_OPTIONS = [
|
|
564
|
+
{ value: "auto", label: "Auto-detect" },
|
|
565
|
+
{ value: "mouse", label: "Mouse" },
|
|
566
|
+
{ value: "trackpad", label: "Trackpad" },
|
|
567
|
+
];
|
|
531
568
|
// --- Grid toggle helpers ----------------------------------------------------
|
|
532
569
|
/** Translate the `grid` / `snap` props into a partial settings override. */
|
|
533
570
|
const buildHostSettings = (gridEnabled, gridStyle, snap) => ({
|