@excalidraw/excalidraw 0.17.1-3e334a6 → 0.17.1-4689a6b
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 +1 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-M7HSOQ7X.js → chunk-23CKV3WP.js} +3 -1
- package/dist/browser/dev/excalidraw-assets-dev/chunk-23CKV3WP.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-RWZVJAQU.js → chunk-7D5BMEAB.js} +2227 -1976
- package/dist/browser/dev/excalidraw-assets-dev/chunk-7D5BMEAB.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{en-R45KN4KN.js → en-W7TECCRB.js} +2 -2
- package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js → image-JKT6GXZD.js} +2 -2
- package/dist/browser/dev/index.css +20 -0
- package/dist/browser/dev/index.css.map +2 -2
- package/dist/browser/dev/index.js +766 -580
- package/dist/browser/dev/index.js.map +4 -4
- package/dist/browser/prod/excalidraw-assets/chunk-DWOM5R6H.js +55 -0
- package/dist/browser/prod/excalidraw-assets/{chunk-DIHRGRYX.js → chunk-SK23VHAR.js} +1 -1
- package/dist/browser/prod/excalidraw-assets/{en-H6IY7PV6.js → en-SMMH575S.js} +1 -1
- package/dist/browser/prod/excalidraw-assets/image-WDEQS5RL.js +1 -0
- package/dist/browser/prod/index.css +1 -1
- package/dist/browser/prod/index.js +22 -22
- package/dist/{prod/en-N2RZZLK5.json → dev/en-CVBEBUBY.json} +2 -0
- package/dist/dev/index.css +20 -0
- package/dist/dev/index.css.map +2 -2
- package/dist/dev/index.js +2379 -2069
- package/dist/dev/index.js.map +4 -4
- package/dist/excalidraw/actions/actionBoundText.js +4 -1
- package/dist/excalidraw/actions/actionCanvas.js +3 -1
- package/dist/excalidraw/actions/actionDuplicateSelection.js +4 -0
- package/dist/excalidraw/actions/actionExport.d.ts +1 -1
- package/dist/excalidraw/actions/actionFinalize.d.ts +1 -1
- package/dist/excalidraw/actions/actionFinalize.js +3 -3
- package/dist/excalidraw/actions/actionFlip.d.ts +3 -3
- package/dist/excalidraw/actions/actionFlip.js +6 -6
- package/dist/excalidraw/actions/actionGroup.js +4 -2
- package/dist/excalidraw/actions/actionHistory.js +3 -0
- package/dist/excalidraw/actions/actionZindex.d.ts +11 -11
- package/dist/excalidraw/analytics.js +1 -1
- package/dist/excalidraw/components/App.d.ts +13 -3
- package/dist/excalidraw/components/App.js +211 -81
- package/dist/excalidraw/components/CommandPalette/CommandPalette.js +24 -10
- package/dist/excalidraw/components/DarkModeToggle.js +3 -1
- package/dist/excalidraw/components/HelpDialog.js +2 -2
- package/dist/excalidraw/components/RadioGroup.d.ts +2 -1
- package/dist/excalidraw/components/RadioGroup.js +1 -1
- package/dist/excalidraw/components/TTDDialog/MermaidToExcalidraw.js +6 -2
- package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.d.ts +18 -0
- package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.js +9 -0
- package/dist/excalidraw/components/hyperlink/Hyperlink.js +3 -3
- package/dist/excalidraw/components/hyperlink/helpers.js +2 -3
- package/dist/excalidraw/components/icons.d.ts +3 -0
- package/dist/excalidraw/components/icons.js +5 -1
- package/dist/excalidraw/components/main-menu/DefaultItems.d.ts +12 -2
- package/dist/excalidraw/components/main-menu/DefaultItems.js +38 -7
- package/dist/excalidraw/constants.d.ts +0 -3
- package/dist/excalidraw/constants.js +0 -3
- package/dist/excalidraw/data/magic.js +2 -1
- package/dist/excalidraw/data/reconcile.d.ts +6 -0
- package/dist/excalidraw/data/reconcile.js +49 -0
- package/dist/excalidraw/data/restore.d.ts +3 -3
- package/dist/excalidraw/data/restore.js +5 -6
- package/dist/excalidraw/data/transform.d.ts +1 -1
- package/dist/excalidraw/data/transform.js +12 -3
- package/dist/excalidraw/element/binding.d.ts +22 -9
- package/dist/excalidraw/element/binding.js +403 -26
- package/dist/excalidraw/element/bounds.d.ts +0 -1
- package/dist/excalidraw/element/bounds.js +0 -3
- package/dist/excalidraw/element/collision.d.ts +14 -19
- package/dist/excalidraw/element/collision.js +36 -713
- package/dist/excalidraw/element/embeddable.js +18 -43
- package/dist/excalidraw/element/index.d.ts +0 -1
- package/dist/excalidraw/element/index.js +0 -1
- package/dist/excalidraw/element/linearElementEditor.d.ts +10 -10
- package/dist/excalidraw/element/linearElementEditor.js +6 -4
- package/dist/excalidraw/element/newElement.d.ts +1 -1
- package/dist/excalidraw/element/newElement.js +2 -1
- package/dist/excalidraw/element/textElement.d.ts +0 -1
- package/dist/excalidraw/element/textElement.js +0 -30
- package/dist/excalidraw/element/types.d.ts +17 -2
- package/dist/excalidraw/errors.d.ts +3 -0
- package/dist/excalidraw/errors.js +3 -0
- package/dist/excalidraw/fractionalIndex.d.ts +40 -0
- package/dist/excalidraw/fractionalIndex.js +241 -0
- package/dist/excalidraw/frame.d.ts +1 -1
- package/dist/excalidraw/hooks/useCreatePortalContainer.js +2 -1
- package/dist/excalidraw/locales/en.json +2 -0
- package/dist/excalidraw/renderer/helpers.js +2 -2
- package/dist/excalidraw/renderer/interactiveScene.js +1 -1
- package/dist/excalidraw/renderer/renderElement.js +3 -3
- package/dist/excalidraw/renderer/renderSnaps.js +2 -1
- package/dist/excalidraw/scene/Scene.d.ts +7 -6
- package/dist/excalidraw/scene/Scene.js +28 -13
- package/dist/excalidraw/scene/export.js +4 -3
- package/dist/excalidraw/types.d.ts +4 -3
- package/dist/excalidraw/utils.d.ts +1 -0
- package/dist/excalidraw/utils.js +1 -0
- package/dist/excalidraw/zindex.d.ts +2 -2
- package/dist/excalidraw/zindex.js +9 -13
- package/dist/{dev/en-N2RZZLK5.json → prod/en-CVBEBUBY.json} +2 -0
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +36 -36
- package/dist/utils/collision.d.ts +4 -0
- package/dist/utils/collision.js +48 -0
- package/dist/utils/geometry/geometry.d.ts +71 -0
- package/dist/utils/geometry/geometry.js +674 -0
- package/dist/utils/geometry/shape.d.ts +55 -0
- package/dist/utils/geometry/shape.js +149 -0
- package/package.json +2 -1
- package/dist/browser/dev/excalidraw-assets-dev/chunk-M7HSOQ7X.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/chunk-RWZVJAQU.js.map +0 -7
- package/dist/browser/prod/excalidraw-assets/chunk-LL4GORAM.js +0 -55
- package/dist/browser/prod/excalidraw-assets/image-EFCJDJH3.js +0 -1
- /package/dist/browser/dev/excalidraw-assets-dev/{en-R45KN4KN.js.map → en-W7TECCRB.js.map} +0 -0
- /package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js.map → image-JKT6GXZD.js.map} +0 -0
|
@@ -23,6 +23,8 @@ import { actionClearCanvas, actionLink } from "../../actions";
|
|
|
23
23
|
import { jotaiStore } from "../../jotai";
|
|
24
24
|
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
|
25
25
|
import * as defaultItems from "./defaultCommandPaletteItems";
|
|
26
|
+
import { trackEvent } from "../../analytics";
|
|
27
|
+
import { useStable } from "../../hooks/useStable";
|
|
26
28
|
import "./CommandPalette.scss";
|
|
27
29
|
const lastUsedPaletteItem = atom(null);
|
|
28
30
|
export const DEFAULT_CATEGORIES = {
|
|
@@ -71,11 +73,17 @@ export const CommandPalette = Object.assign((props) => {
|
|
|
71
73
|
if (isCommandPaletteToggleShortcut(event)) {
|
|
72
74
|
event.preventDefault();
|
|
73
75
|
event.stopPropagation();
|
|
74
|
-
setAppState((appState) =>
|
|
75
|
-
|
|
76
|
+
setAppState((appState) => {
|
|
77
|
+
const nextState = appState.openDialog?.name === "commandPalette"
|
|
76
78
|
? null
|
|
77
|
-
: { name: "commandPalette" }
|
|
78
|
-
|
|
79
|
+
: { name: "commandPalette" };
|
|
80
|
+
if (nextState) {
|
|
81
|
+
trackEvent("command_palette", "open", "shortcut");
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
openDialog: nextState,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
79
87
|
}
|
|
80
88
|
};
|
|
81
89
|
window.addEventListener(EVENT.KEYDOWN, commandPaletteShortcut, {
|
|
@@ -101,10 +109,18 @@ function CommandPaletteInner({ customCommandPaletteItems, }) {
|
|
|
101
109
|
const [lastUsed, setLastUsed] = useAtom(lastUsedPaletteItem);
|
|
102
110
|
const [allCommands, setAllCommands] = useState(null);
|
|
103
111
|
const inputRef = useRef(null);
|
|
112
|
+
const stableDeps = useStable({
|
|
113
|
+
uiAppState,
|
|
114
|
+
customCommandPaletteItems,
|
|
115
|
+
appProps,
|
|
116
|
+
});
|
|
104
117
|
useEffect(() => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
// these props change often and we don't want them to re-run the effect
|
|
119
|
+
// which would renew `allCommands`, cascading down and resetting state.
|
|
120
|
+
//
|
|
121
|
+
// This means that the commands won't update on appState/appProps changes
|
|
122
|
+
// while the command palette is open
|
|
123
|
+
const { uiAppState, customCommandPaletteItems, appProps } = stableDeps;
|
|
108
124
|
const getActionLabel = (action) => {
|
|
109
125
|
let label = "";
|
|
110
126
|
if (action.label) {
|
|
@@ -407,15 +423,13 @@ function CommandPaletteInner({ customCommandPaletteItems, }) {
|
|
|
407
423
|
null);
|
|
408
424
|
}
|
|
409
425
|
}, [
|
|
426
|
+
stableDeps,
|
|
410
427
|
app,
|
|
411
|
-
appProps,
|
|
412
|
-
uiAppState,
|
|
413
428
|
actionManager,
|
|
414
429
|
setAllCommands,
|
|
415
430
|
lastUsed?.label,
|
|
416
431
|
setLastUsed,
|
|
417
432
|
setAppState,
|
|
418
|
-
customCommandPaletteItems,
|
|
419
433
|
]);
|
|
420
434
|
const [commandSearch, setCommandSearch] = useState("");
|
|
421
435
|
const [currentCommand, setCurrentCommand] = useState(null);
|
|
@@ -7,7 +7,9 @@ import { THEME } from "../constants";
|
|
|
7
7
|
// but this could be added in the future.
|
|
8
8
|
export const DarkModeToggle = (props) => {
|
|
9
9
|
const title = props.title ||
|
|
10
|
-
(props.value ===
|
|
10
|
+
(props.value === THEME.DARK
|
|
11
|
+
? t("buttons.lightMode")
|
|
12
|
+
: t("buttons.darkMode"));
|
|
11
13
|
return (_jsx(ToolButton, { type: "icon", icon: props.value === THEME.LIGHT ? ICONS.MOON : ICONS.SUN, title: title, "aria-label": title, onClick: () => props.onChange(props.value === THEME.DARK ? THEME.LIGHT : THEME.DARK), "data-testid": "toggle-dark-mode" }));
|
|
12
14
|
};
|
|
13
15
|
const ICONS = {
|
|
@@ -5,11 +5,11 @@ import { KEYS } from "../keys";
|
|
|
5
5
|
import { Dialog } from "./Dialog";
|
|
6
6
|
import { getShortcutKey } from "../utils";
|
|
7
7
|
import "./HelpDialog.scss";
|
|
8
|
-
import { ExternalLinkIcon } from "./icons";
|
|
8
|
+
import { ExternalLinkIcon, GithubIcon, youtubeIcon } from "./icons";
|
|
9
9
|
import { probablySupportsClipboardBlob } from "../clipboard";
|
|
10
10
|
import { isDarwin, isFirefox, isWindows } from "../constants";
|
|
11
11
|
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
|
12
|
-
const Header = () => (_jsxs("div", { className: "HelpDialog__header", children: [_jsxs("a", { className: "HelpDialog__btn", href: "https://docs.excalidraw.com", target: "_blank", rel: "noopener noreferrer", children: [
|
|
12
|
+
const Header = () => (_jsxs("div", { className: "HelpDialog__header", children: [_jsxs("a", { className: "HelpDialog__btn", href: "https://docs.excalidraw.com", target: "_blank", rel: "noopener noreferrer", children: [_jsx("div", { className: "HelpDialog__link-icon", children: ExternalLinkIcon }), t("helpDialog.documentation")] }), _jsxs("a", { className: "HelpDialog__btn", href: "https://blog.excalidraw.com", target: "_blank", rel: "noopener noreferrer", children: [_jsx("div", { className: "HelpDialog__link-icon", children: ExternalLinkIcon }), t("helpDialog.blog")] }), _jsxs("a", { className: "HelpDialog__btn", href: "https://github.com/excalidraw/excalidraw/issues", target: "_blank", rel: "noopener noreferrer", children: [_jsx("div", { className: "HelpDialog__link-icon", children: GithubIcon }), t("helpDialog.github")] }), _jsxs("a", { className: "HelpDialog__btn", href: "https://youtube.com/@excalidraw", target: "_blank", rel: "noopener noreferrer", children: [_jsx("div", { className: "HelpDialog__link-icon", children: youtubeIcon }), "YouTube"] })] }));
|
|
13
13
|
const Section = (props) => (_jsxs(_Fragment, { children: [_jsx("h3", { children: props.title }), _jsx("div", { className: "HelpDialog__islands-container", children: props.children })] }));
|
|
14
14
|
const ShortcutIsland = (props) => (_jsxs("div", { className: `HelpDialog__island ${props.className}`, children: [_jsx("h4", { className: "HelpDialog__island-title", children: props.caption }), _jsx("div", { className: "HelpDialog__island-content", children: props.children })] }));
|
|
15
15
|
function* intersperse(as, delim) {
|
|
@@ -4,5 +4,5 @@ import "./RadioGroup.scss";
|
|
|
4
4
|
export const RadioGroup = function ({ onChange, value, choices, name, }) {
|
|
5
5
|
return (_jsx("div", { className: "RadioGroup", children: choices.map((choice) => (_jsxs("div", { className: clsx("RadioGroup__choice", {
|
|
6
6
|
active: choice.value === value,
|
|
7
|
-
}), children: [_jsx("input", { name: name, type: "radio", checked: choice.value === value, onChange: () => onChange(choice.value) }), choice.label] }, choice.
|
|
7
|
+
}), title: choice.ariaLabel, children: [_jsx("input", { name: name, type: "radio", checked: choice.value === value, onChange: () => onChange(choice.value), "aria-label": choice.ariaLabel }), choice.label] }, String(choice.value)))) }));
|
|
8
8
|
};
|
|
@@ -12,7 +12,7 @@ import { TTDDialogInput } from "./TTDDialogInput";
|
|
|
12
12
|
import { TTDDialogOutput } from "./TTDDialogOutput";
|
|
13
13
|
import { EditorLocalStorage } from "../../data/EditorLocalStorage";
|
|
14
14
|
import { EDITOR_LS_KEYS } from "../../constants";
|
|
15
|
-
import { debounce } from "../../utils";
|
|
15
|
+
import { debounce, isDevEnv } from "../../utils";
|
|
16
16
|
import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut";
|
|
17
17
|
const MERMAID_EXAMPLE = "flowchart TD\n A[Christmas] -->|Get money| B(Go shopping)\n B --> C{Let me think}\n C -->|One| D[Laptop]\n C -->|Two| E[iPhone]\n C -->|Three| F[Car]";
|
|
18
18
|
const debouncedSaveMermaidDefinition = debounce(saveMermaidDataToStorage, 300);
|
|
@@ -31,7 +31,11 @@ const MermaidToExcalidraw = ({ mermaidToExcalidrawLib, }) => {
|
|
|
31
31
|
mermaidToExcalidrawLib,
|
|
32
32
|
setError,
|
|
33
33
|
mermaidDefinition: deferredText,
|
|
34
|
-
}).catch(() => {
|
|
34
|
+
}).catch((err) => {
|
|
35
|
+
if (isDevEnv()) {
|
|
36
|
+
console.error("Failed to parse mermaid definition", err);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
35
39
|
debouncedSaveMermaidDefinition(deferredText);
|
|
36
40
|
}, [deferredText, mermaidToExcalidrawLib]);
|
|
37
41
|
useEffect(() => () => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
type Props<T> = {
|
|
3
|
+
value: T;
|
|
4
|
+
shortcut?: string;
|
|
5
|
+
choices: {
|
|
6
|
+
value: T;
|
|
7
|
+
label: React.ReactNode;
|
|
8
|
+
ariaLabel?: string;
|
|
9
|
+
}[];
|
|
10
|
+
onChange: (value: T) => void;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
name: string;
|
|
13
|
+
};
|
|
14
|
+
declare const DropdownMenuItemContentRadio: {
|
|
15
|
+
<T>({ value, shortcut, onChange, choices, children, name, }: Props<T>): JSX.Element;
|
|
16
|
+
displayName: string;
|
|
17
|
+
};
|
|
18
|
+
export default DropdownMenuItemContentRadio;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useDevice } from "../App";
|
|
3
|
+
import { RadioGroup } from "../RadioGroup";
|
|
4
|
+
const DropdownMenuItemContentRadio = ({ value, shortcut, onChange, choices, children, name, }) => {
|
|
5
|
+
const device = useDevice();
|
|
6
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "dropdown-menu-item-base dropdown-menu-item-bare", children: [_jsx("label", { className: "dropdown-menu-item__text", htmlFor: name, children: children }), _jsx(RadioGroup, { name: name, value: value, onChange: onChange, choices: choices })] }), shortcut && !device.editor.isMobile && (_jsx("div", { className: "dropdown-menu-item__shortcut dropdown-menu-item__shortcut--orphaned", children: shortcut }))] }));
|
|
7
|
+
};
|
|
8
|
+
DropdownMenuItemContentRadio.displayName = "DropdownMenuItemContentRadio";
|
|
9
|
+
export default DropdownMenuItemContentRadio;
|
|
@@ -10,9 +10,9 @@ import clsx from "clsx";
|
|
|
10
10
|
import { KEYS } from "../../keys";
|
|
11
11
|
import { EVENT, HYPERLINK_TOOLTIP_DELAY } from "../../constants";
|
|
12
12
|
import { getElementAbsoluteCoords } from "../../element/bounds";
|
|
13
|
-
import { getTooltipDiv, updateTooltipPosition } from "
|
|
13
|
+
import { getTooltipDiv, updateTooltipPosition } from "../../components/Tooltip";
|
|
14
14
|
import { getSelectedElements } from "../../scene";
|
|
15
|
-
import {
|
|
15
|
+
import { hitElementBoundingBox } from "../../element/collision";
|
|
16
16
|
import { isLocalLink, normalizeLink } from "../../data/url";
|
|
17
17
|
import "./Hyperlink.scss";
|
|
18
18
|
import { trackEvent } from "../../analytics";
|
|
@@ -252,7 +252,7 @@ const shouldHideLinkPopup = (element, elementsMap, appState, [clientX, clientY])
|
|
|
252
252
|
const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords({ clientX, clientY }, appState);
|
|
253
253
|
const threshold = 15 / appState.zoom.value;
|
|
254
254
|
// hitbox to prevent hiding when hovered in element bounding box
|
|
255
|
-
if (
|
|
255
|
+
if (hitElementBoundingBox(sceneX, sceneY, element, elementsMap)) {
|
|
256
256
|
return false;
|
|
257
257
|
}
|
|
258
258
|
const [x1, y1, x2] = getElementAbsoluteCoords(element, elementsMap);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MIME_TYPES } from "../../constants";
|
|
2
2
|
import { getElementAbsoluteCoords } from "../../element/bounds";
|
|
3
|
-
import {
|
|
3
|
+
import { hitElementBoundingBox } from "../../element/collision";
|
|
4
4
|
import { rotate } from "../../math";
|
|
5
5
|
import { DEFAULT_LINK_SIZE } from "../../renderer/renderElement";
|
|
6
6
|
export const EXTERNAL_LINK_IMG = document.createElement("img");
|
|
@@ -39,10 +39,9 @@ export const isPointHittingLink = (element, elementsMap, appState, [x, y], isMob
|
|
|
39
39
|
if (!element.link || appState.selectedElementIds[element.id]) {
|
|
40
40
|
return false;
|
|
41
41
|
}
|
|
42
|
-
const threshold = 4 / appState.zoom.value;
|
|
43
42
|
if (!isMobile &&
|
|
44
43
|
appState.viewModeEnabled &&
|
|
45
|
-
|
|
44
|
+
hitElementBoundingBox(x, y, element, elementsMap)) {
|
|
46
45
|
return true;
|
|
47
46
|
}
|
|
48
47
|
return isPointHittingLinkIcon(element, elementsMap, appState, [x, y]);
|
|
@@ -194,4 +194,7 @@ export declare const svgIcon: JSX.Element;
|
|
|
194
194
|
export declare const pngIcon: JSX.Element;
|
|
195
195
|
export declare const magnetIcon: JSX.Element;
|
|
196
196
|
export declare const coffeeIcon: JSX.Element;
|
|
197
|
+
export declare const DeviceDesktopIcon: JSX.Element;
|
|
198
|
+
export declare const arrowBarToLeftIcon: JSX.Element;
|
|
199
|
+
export declare const youtubeIcon: JSX.Element;
|
|
197
200
|
export {};
|
|
@@ -83,7 +83,7 @@ export const TrashIcon = createIcon(_jsx("path", { strokeWidth: "1.25", d: "M3.3
|
|
|
83
83
|
export const EmbedIcon = createIcon(_jsxs("g", { strokeWidth: "1.25", children: [_jsx("polyline", { points: "12 16 18 10 12 4" }), _jsx("polyline", { points: "8 4 2 10 8 16" })] }), modifiedTablerIconProps);
|
|
84
84
|
export const DuplicateIcon = createIcon(_jsxs("g", { strokeWidth: "1.25", children: [_jsx("path", { d: "M14.375 6.458H8.958a2.5 2.5 0 0 0-2.5 2.5v5.417a2.5 2.5 0 0 0 2.5 2.5h5.417a2.5 2.5 0 0 0 2.5-2.5V8.958a2.5 2.5 0 0 0-2.5-2.5Z" }), _jsx("path", { clipRule: "evenodd", d: "M11.667 3.125c.517 0 .986.21 1.325.55.34.338.55.807.55 1.325v1.458H8.333c-.485 0-.927.185-1.26.487-.343.312-.57.75-.609 1.24l-.005 5.357H5a1.87 1.87 0 0 1-1.326-.55 1.87 1.87 0 0 1-.549-1.325V5c0-.518.21-.987.55-1.326.338-.34.807-.549 1.325-.549h6.667Z" })] }), modifiedTablerIconProps);
|
|
85
85
|
export const MoonIcon = createIcon(_jsx("path", { clipRule: "evenodd", d: "M10 2.5h.328a6.25 6.25 0 0 0 6.6 10.372A7.5 7.5 0 1 1 10 2.493V2.5Z", stroke: "currentColor" }), modifiedTablerIconProps);
|
|
86
|
-
export const SunIcon = createIcon(_jsx("g", { stroke: "currentColor",
|
|
86
|
+
export const SunIcon = createIcon(_jsx("g", { stroke: "currentColor", strokeLinejoin: "round", children: _jsx("path", { d: "M10 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM10 4.167V2.5M14.167 5.833l1.166-1.166M15.833 10H17.5M14.167 14.167l1.166 1.166M10 15.833V17.5M5.833 14.167l-1.166 1.166M5 10H3.333M5.833 5.833 4.667 4.667" }) }), { ...modifiedTablerIconProps, strokeWidth: 1.5 });
|
|
87
87
|
export const HamburgerMenuIcon = createIcon(_jsxs("g", { strokeWidth: "1.5", children: [_jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), _jsx("line", { x1: "4", y1: "6", x2: "20", y2: "6" }), _jsx("line", { x1: "4", y1: "12", x2: "20", y2: "12" }), _jsx("line", { x1: "4", y1: "18", x2: "20", y2: "18" })] }), tablerIconProps);
|
|
88
88
|
export const ExportIcon = createIcon(_jsx("path", { strokeWidth: "1.25", d: "M3.333 14.167v1.666c0 .92.747 1.667 1.667 1.667h10c.92 0 1.667-.746 1.667-1.667v-1.666M5.833 9.167 10 13.333l4.167-4.166M10 3.333v10" }), modifiedTablerIconProps);
|
|
89
89
|
export const HelpIcon = createIcon(_jsxs("g", { strokeWidth: "1.5", children: [_jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), _jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("line", { x1: "12", y1: "17", x2: "12", y2: "17.01" }), _jsx("path", { d: "M12 13.5a1.5 1.5 0 0 1 1 -1.5a2.6 2.6 0 1 0 -3 -4" })] }), tablerIconProps);
|
|
@@ -240,3 +240,7 @@ export const svgIcon = createIcon(_jsxs("g", { strokeWidth: 1.25, children: [_js
|
|
|
240
240
|
export const pngIcon = createIcon(_jsxs("g", { strokeWidth: 1.25, children: [_jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), _jsx("path", { d: "M14 3v4a1 1 0 0 0 1 1h4" }), _jsx("path", { d: "M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4" }), _jsx("path", { d: "M20 15h-1a2 2 0 0 0 -2 2v2a2 2 0 0 0 2 2h1v-3" }), _jsx("path", { d: "M5 18h1.5a1.5 1.5 0 0 0 0 -3h-1.5v6" }), _jsx("path", { d: "M11 21v-6l3 6v-6" })] }), tablerIconProps);
|
|
241
241
|
export const magnetIcon = createIcon(_jsxs("g", { strokeWidth: 1.25, children: [_jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), _jsx("path", { d: "M4 13v-8a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v8a2 2 0 0 0 6 0v-8a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v8a8 8 0 0 1 -16 0" }), _jsx("path", { d: "M4 8l5 0" }), _jsx("path", { d: "M15 8l4 0" })] }), tablerIconProps);
|
|
242
242
|
export const coffeeIcon = createIcon(_jsxs("g", { strokeWidth: 1.25, children: [_jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), _jsx("path", { d: "M3 14c.83 .642 2.077 1.017 3.5 1c1.423 .017 2.67 -.358 3.5 -1c.83 -.642 2.077 -1.017 3.5 -1c1.423 -.017 2.67 .358 3.5 1" }), _jsx("path", { d: "M8 3a2.4 2.4 0 0 0 -1 2a2.4 2.4 0 0 0 1 2" }), _jsx("path", { d: "M12 3a2.4 2.4 0 0 0 -1 2a2.4 2.4 0 0 0 1 2" }), _jsx("path", { d: "M3 10h14v5a6 6 0 0 1 -6 6h-2a6 6 0 0 1 -6 -6v-5z" }), _jsx("path", { d: "M16.746 16.726a3 3 0 1 0 .252 -5.555" })] }), tablerIconProps);
|
|
243
|
+
export const DeviceDesktopIcon = createIcon(_jsxs("g", { stroke: "currentColor", children: [_jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), _jsx("path", { d: "M3 5a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-16a1 1 0 0 1-1-1v-10zM7 20h10M9 16v4M15 16v4" })] }), { ...tablerIconProps, strokeWidth: 1.5 });
|
|
244
|
+
// arrow-bar-to-left
|
|
245
|
+
export const arrowBarToLeftIcon = createIcon(_jsxs("g", { children: [_jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), _jsx("path", { d: "M10 12l10 0" }), _jsx("path", { d: "M10 12l4 4" }), _jsx("path", { d: "M10 12l4 -4" }), _jsx("path", { d: "M4 4l0 16" })] }), tablerIconProps);
|
|
246
|
+
export const youtubeIcon = createIcon(_jsxs("g", { children: [_jsx("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), _jsx("path", { d: "M2 8a4 4 0 0 1 4 -4h12a4 4 0 0 1 4 4v8a4 4 0 0 1 -4 4h-12a4 4 0 0 1 -4 -4v-8z" }), _jsx("path", { d: "M10 9l5 3l-5 3z" })] }), tablerIconProps);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
+
import type { Theme } from "../../element/types";
|
|
2
3
|
import "./DefaultItems.scss";
|
|
3
4
|
export declare const LoadScene: {
|
|
4
5
|
(): JSX.Element | null;
|
|
@@ -13,7 +14,9 @@ export declare const SaveAsImage: {
|
|
|
13
14
|
displayName: string;
|
|
14
15
|
};
|
|
15
16
|
export declare const CommandPalette: {
|
|
16
|
-
(
|
|
17
|
+
(opts?: {
|
|
18
|
+
className?: string;
|
|
19
|
+
}): JSX.Element;
|
|
17
20
|
displayName: string;
|
|
18
21
|
};
|
|
19
22
|
export declare const Help: {
|
|
@@ -25,7 +28,14 @@ export declare const ClearCanvas: {
|
|
|
25
28
|
displayName: string;
|
|
26
29
|
};
|
|
27
30
|
export declare const ToggleTheme: {
|
|
28
|
-
(
|
|
31
|
+
(props: {
|
|
32
|
+
allowSystemTheme: true;
|
|
33
|
+
theme: Theme | "system";
|
|
34
|
+
onSelect: (theme: Theme | "system") => void;
|
|
35
|
+
} | {
|
|
36
|
+
allowSystemTheme?: false | undefined;
|
|
37
|
+
onSelect?: ((theme: Theme) => void) | undefined;
|
|
38
|
+
}): JSX.Element | null;
|
|
29
39
|
displayName: string;
|
|
30
40
|
};
|
|
31
41
|
export declare const ChangeCanvasBackground: {
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
|
3
3
|
import { useI18n } from "../../i18n";
|
|
4
4
|
import { useExcalidrawSetAppState, useExcalidrawActionManager, useExcalidrawElements, useAppProps, } from "../App";
|
|
5
|
-
import { boltIcon, ExportIcon, ExportImageIcon, HelpIcon, LoadIcon, MoonIcon, save, SunIcon, TrashIcon, usersIcon, } from "../icons";
|
|
5
|
+
import { boltIcon, DeviceDesktopIcon, ExportIcon, ExportImageIcon, HelpIcon, LoadIcon, MoonIcon, save, SunIcon, TrashIcon, usersIcon, } from "../icons";
|
|
6
6
|
import { GithubIcon, DiscordIcon, XBrandIcon } from "../icons";
|
|
7
7
|
import DropdownMenuItem from "../dropdownMenu/DropdownMenuItem";
|
|
8
8
|
import DropdownMenuItemLink from "../dropdownMenu/DropdownMenuItemLink";
|
|
@@ -14,6 +14,9 @@ import { jotaiScope } from "../../jotai";
|
|
|
14
14
|
import { useUIAppState } from "../../context/ui-appState";
|
|
15
15
|
import { openConfirmModal } from "../OverwriteConfirm/OverwriteConfirmState";
|
|
16
16
|
import Trans from "../Trans";
|
|
17
|
+
import DropdownMenuItemContentRadio from "../dropdownMenu/DropdownMenuItemContentRadio";
|
|
18
|
+
import { THEME } from "../../constants";
|
|
19
|
+
import { trackEvent } from "../../analytics";
|
|
17
20
|
import "./DefaultItems.scss";
|
|
18
21
|
export const LoadScene = () => {
|
|
19
22
|
const { t } = useI18n();
|
|
@@ -51,10 +54,13 @@ export const SaveAsImage = () => {
|
|
|
51
54
|
return (_jsx(DropdownMenuItem, { icon: ExportImageIcon, "data-testid": "image-export-button", onSelect: () => setAppState({ openDialog: { name: "imageExport" } }), shortcut: getShortcutFromShortcutName("imageExport"), "aria-label": t("buttons.exportImage"), children: t("buttons.exportImage") }));
|
|
52
55
|
};
|
|
53
56
|
SaveAsImage.displayName = "SaveAsImage";
|
|
54
|
-
export const CommandPalette = () => {
|
|
57
|
+
export const CommandPalette = (opts) => {
|
|
55
58
|
const setAppState = useExcalidrawSetAppState();
|
|
56
59
|
const { t } = useI18n();
|
|
57
|
-
return (_jsx(DropdownMenuItem, { icon: boltIcon, "data-testid": "command-palette-button", onSelect: () =>
|
|
60
|
+
return (_jsx(DropdownMenuItem, { icon: boltIcon, "data-testid": "command-palette-button", onSelect: () => {
|
|
61
|
+
trackEvent("command_palette", "open", "menu");
|
|
62
|
+
setAppState({ openDialog: { name: "commandPalette" } });
|
|
63
|
+
}, shortcut: getShortcutFromShortcutName("commandPalette"), "aria-label": t("commandPalette.title"), className: opts?.className, children: t("commandPalette.title") }));
|
|
58
64
|
};
|
|
59
65
|
CommandPalette.displayName = "CommandPalette";
|
|
60
66
|
export const Help = () => {
|
|
@@ -73,20 +79,45 @@ export const ClearCanvas = () => {
|
|
|
73
79
|
return (_jsx(DropdownMenuItem, { icon: TrashIcon, onSelect: () => setActiveConfirmDialog("clearCanvas"), "data-testid": "clear-canvas-button", "aria-label": t("buttons.clearReset"), children: t("buttons.clearReset") }));
|
|
74
80
|
};
|
|
75
81
|
ClearCanvas.displayName = "ClearCanvas";
|
|
76
|
-
export const ToggleTheme = () => {
|
|
82
|
+
export const ToggleTheme = (props) => {
|
|
77
83
|
const { t } = useI18n();
|
|
78
84
|
const appState = useUIAppState();
|
|
79
85
|
const actionManager = useExcalidrawActionManager();
|
|
86
|
+
const shortcut = getShortcutFromShortcutName("toggleTheme");
|
|
80
87
|
if (!actionManager.isActionEnabled(actionToggleTheme)) {
|
|
81
88
|
return null;
|
|
82
89
|
}
|
|
90
|
+
if (props?.allowSystemTheme) {
|
|
91
|
+
return (_jsx(DropdownMenuItemContentRadio, { name: "theme", value: props.theme, onChange: (value) => props.onSelect(value), choices: [
|
|
92
|
+
{
|
|
93
|
+
value: THEME.LIGHT,
|
|
94
|
+
label: SunIcon,
|
|
95
|
+
ariaLabel: `${t("buttons.lightMode")} - ${shortcut}`,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
value: THEME.DARK,
|
|
99
|
+
label: MoonIcon,
|
|
100
|
+
ariaLabel: `${t("buttons.darkMode")} - ${shortcut}`,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
value: "system",
|
|
104
|
+
label: DeviceDesktopIcon,
|
|
105
|
+
ariaLabel: t("buttons.systemMode"),
|
|
106
|
+
},
|
|
107
|
+
], children: t("labels.theme") }));
|
|
108
|
+
}
|
|
83
109
|
return (_jsx(DropdownMenuItem, { onSelect: (event) => {
|
|
84
110
|
// do not close the menu when changing theme
|
|
85
111
|
event.preventDefault();
|
|
86
|
-
|
|
87
|
-
|
|
112
|
+
if (props?.onSelect) {
|
|
113
|
+
props.onSelect(appState.theme === THEME.DARK ? THEME.LIGHT : THEME.DARK);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
return actionManager.executeAction(actionToggleTheme);
|
|
117
|
+
}
|
|
118
|
+
}, icon: appState.theme === THEME.DARK ? SunIcon : MoonIcon, "data-testid": "toggle-dark-mode", shortcut: shortcut, "aria-label": appState.theme === THEME.DARK
|
|
88
119
|
? t("buttons.lightMode")
|
|
89
|
-
: t("buttons.darkMode"), children: appState.theme ===
|
|
120
|
+
: t("buttons.darkMode"), children: appState.theme === THEME.DARK
|
|
90
121
|
? t("buttons.lightMode")
|
|
91
122
|
: t("buttons.darkMode") }));
|
|
92
123
|
};
|
|
@@ -223,9 +223,6 @@ export declare const ROUNDNESS: {
|
|
|
223
223
|
readonly PROPORTIONAL_RADIUS: 2;
|
|
224
224
|
readonly ADAPTIVE_RADIUS: 3;
|
|
225
225
|
};
|
|
226
|
-
/** key containt id of precedeing elemnt id we use in reconciliation during
|
|
227
|
-
* collaboration */
|
|
228
|
-
export declare const PRECEDING_ELEMENT_KEY = "__precedingElement__";
|
|
229
226
|
export declare const ROUGHNESS: {
|
|
230
227
|
readonly architect: 0;
|
|
231
228
|
readonly artist: 1;
|
|
@@ -264,9 +264,6 @@ export const ROUNDNESS = {
|
|
|
264
264
|
// (see DEFAULT_ADAPTIVE_RADIUS constant)
|
|
265
265
|
ADAPTIVE_RADIUS: 3,
|
|
266
266
|
};
|
|
267
|
-
/** key containt id of precedeing elemnt id we use in reconciliation during
|
|
268
|
-
* collaboration */
|
|
269
|
-
export const PRECEDING_ELEMENT_KEY = "__precedingElement__";
|
|
270
267
|
export const ROUGHNESS = {
|
|
271
268
|
architect: 0,
|
|
272
269
|
artist: 1,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { THEME } from "../constants";
|
|
1
2
|
const SYSTEM_PROMPT = `You are a skilled front-end developer who builds interactive prototypes from wireframes, and is an expert at CSS Grid and Flex design.
|
|
2
3
|
Your role is to transform low-fidelity wireframes into working front-end HTML code.
|
|
3
4
|
|
|
@@ -19,7 +20,7 @@ If the wireframes, diagrams, or text is unclear or unreadable, refer to provided
|
|
|
19
20
|
Your goal is a production-ready prototype that brings the wireframes to life.
|
|
20
21
|
|
|
21
22
|
Please output JUST THE HTML file containing your best attempt at implementing the provided wireframes.`;
|
|
22
|
-
export async function diagramToHTML({ image, apiKey, text, theme =
|
|
23
|
+
export async function diagramToHTML({ image, apiKey, text, theme = THEME.LIGHT, }) {
|
|
23
24
|
const body = {
|
|
24
25
|
model: "gpt-4-vision-preview",
|
|
25
26
|
// 4096 are max output tokens allowed for `gpt-4-vision-preview` currently
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { OrderedExcalidrawElement } from "../element/types";
|
|
2
|
+
import { AppState } from "../types";
|
|
3
|
+
import { MakeBrand } from "../utility-types";
|
|
4
|
+
export type ReconciledExcalidrawElement = OrderedExcalidrawElement & MakeBrand<"ReconciledElement">;
|
|
5
|
+
export type RemoteExcalidrawElement = OrderedExcalidrawElement & MakeBrand<"RemoteExcalidrawElement">;
|
|
6
|
+
export declare const reconcileElements: (localElements: readonly OrderedExcalidrawElement[], remoteElements: readonly RemoteExcalidrawElement[], localAppState: AppState) => ReconciledExcalidrawElement[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { orderByFractionalIndex, syncInvalidIndices } from "../fractionalIndex";
|
|
2
|
+
import { arrayToMap } from "../utils";
|
|
3
|
+
const shouldDiscardRemoteElement = (localAppState, local, remote) => {
|
|
4
|
+
if (local &&
|
|
5
|
+
// local element is being edited
|
|
6
|
+
(local.id === localAppState.editingElement?.id ||
|
|
7
|
+
local.id === localAppState.resizingElement?.id ||
|
|
8
|
+
local.id === localAppState.draggingElement?.id ||
|
|
9
|
+
// local element is newer
|
|
10
|
+
local.version > remote.version ||
|
|
11
|
+
// resolve conflicting edits deterministically by taking the one with
|
|
12
|
+
// the lowest versionNonce
|
|
13
|
+
(local.version === remote.version &&
|
|
14
|
+
local.versionNonce < remote.versionNonce))) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
19
|
+
export const reconcileElements = (localElements, remoteElements, localAppState) => {
|
|
20
|
+
const localElementsMap = arrayToMap(localElements);
|
|
21
|
+
const reconciledElements = [];
|
|
22
|
+
const added = new Set();
|
|
23
|
+
// process remote elements
|
|
24
|
+
for (const remoteElement of remoteElements) {
|
|
25
|
+
if (!added.has(remoteElement.id)) {
|
|
26
|
+
const localElement = localElementsMap.get(remoteElement.id);
|
|
27
|
+
const discardRemoteElement = shouldDiscardRemoteElement(localAppState, localElement, remoteElement);
|
|
28
|
+
if (localElement && discardRemoteElement) {
|
|
29
|
+
reconciledElements.push(localElement);
|
|
30
|
+
added.add(localElement.id);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
reconciledElements.push(remoteElement);
|
|
34
|
+
added.add(remoteElement.id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// process remaining local elements
|
|
39
|
+
for (const localElement of localElements) {
|
|
40
|
+
if (!added.has(localElement.id)) {
|
|
41
|
+
reconciledElements.push(localElement);
|
|
42
|
+
added.add(localElement.id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const orderedElements = orderByFractionalIndex(reconciledElements);
|
|
46
|
+
// de-duplicate indices
|
|
47
|
+
syncInvalidIndices(orderedElements);
|
|
48
|
+
return orderedElements;
|
|
49
|
+
};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { ExcalidrawElement } from "../element/types";
|
|
1
|
+
import { ExcalidrawElement, OrderedExcalidrawElement } from "../element/types";
|
|
2
2
|
import { AppState, BinaryFiles, LibraryItem } from "../types";
|
|
3
3
|
import { ImportedDataState } from "./types";
|
|
4
4
|
type RestoredAppState = Omit<AppState, "offsetTop" | "offsetLeft" | "width" | "height">;
|
|
5
5
|
export declare const AllowedExcalidrawActiveTools: Record<AppState["activeTool"]["type"], boolean>;
|
|
6
6
|
export type RestoredDataState = {
|
|
7
|
-
elements:
|
|
7
|
+
elements: OrderedExcalidrawElement[];
|
|
8
8
|
appState: RestoredAppState;
|
|
9
9
|
files: BinaryFiles;
|
|
10
10
|
};
|
|
11
11
|
export declare const restoreElements: (elements: ImportedDataState["elements"], localElements: readonly ExcalidrawElement[] | null | undefined, opts?: {
|
|
12
12
|
refreshDimensions?: boolean;
|
|
13
13
|
repairBindings?: boolean;
|
|
14
|
-
} | undefined) =>
|
|
14
|
+
} | undefined) => OrderedExcalidrawElement[];
|
|
15
15
|
export declare const restoreAppState: (appState: ImportedDataState["appState"], localAppState: Partial<AppState> | null | undefined) => RestoredAppState;
|
|
16
16
|
export declare const restore: (data: Pick<ImportedDataState, "appState" | "elements" | "files"> | null, localAppState: Partial<AppState> | null | undefined, localElements: readonly ExcalidrawElement[] | null | undefined, elementsConfig?: {
|
|
17
17
|
refreshDimensions?: boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getNonDeletedElements, getNormalizedDimensions, isInvisiblySmallElement, refreshTextDimensions, } from "../element";
|
|
2
2
|
import { isTextElement, isUsingAdaptiveRadius } from "../element/typeChecks";
|
|
3
3
|
import { randomId } from "../random";
|
|
4
|
-
import { DEFAULT_FONT_FAMILY, DEFAULT_TEXT_ALIGN, DEFAULT_VERTICAL_ALIGN,
|
|
4
|
+
import { DEFAULT_FONT_FAMILY, DEFAULT_TEXT_ALIGN, DEFAULT_VERTICAL_ALIGN, FONT_FAMILY, ROUNDNESS, DEFAULT_SIDEBAR, DEFAULT_ELEMENT_PROPS, } from "../constants";
|
|
5
5
|
import { getDefaultAppState } from "../appState";
|
|
6
6
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
|
7
7
|
import { bumpVersion } from "../element/mutateElement";
|
|
@@ -9,6 +9,7 @@ import { getUpdatedTimestamp, updateActiveTool } from "../utils";
|
|
|
9
9
|
import { arrayToMap } from "../utils";
|
|
10
10
|
import { detectLineHeight, getContainerElement, getDefaultLineHeight, } from "../element/textElement";
|
|
11
11
|
import { normalizeLink } from "./url";
|
|
12
|
+
import { syncInvalidIndices } from "../fractionalIndex";
|
|
12
13
|
export const AllowedExcalidrawActiveTools = {
|
|
13
14
|
selection: true,
|
|
14
15
|
text: true,
|
|
@@ -46,6 +47,7 @@ const restoreElementWithProperties = (element, extra) => {
|
|
|
46
47
|
// newly added elements
|
|
47
48
|
version: element.version || 1,
|
|
48
49
|
versionNonce: element.versionNonce ?? 0,
|
|
50
|
+
index: element.index ?? null,
|
|
49
51
|
isDeleted: element.isDeleted ?? false,
|
|
50
52
|
id: element.id || randomId(),
|
|
51
53
|
fillStyle: element.fillStyle || DEFAULT_ELEMENT_PROPS.fillStyle,
|
|
@@ -85,9 +87,6 @@ const restoreElementWithProperties = (element, extra) => {
|
|
|
85
87
|
base.customData =
|
|
86
88
|
"customData" in extra ? extra.customData : element.customData;
|
|
87
89
|
}
|
|
88
|
-
if (PRECEDING_ELEMENT_KEY in element) {
|
|
89
|
-
base[PRECEDING_ELEMENT_KEY] = element[PRECEDING_ELEMENT_KEY];
|
|
90
|
-
}
|
|
91
90
|
return {
|
|
92
91
|
...base,
|
|
93
92
|
...getNormalizedDimensions(base),
|
|
@@ -275,7 +274,7 @@ localElements, opts) => {
|
|
|
275
274
|
// used to detect duplicate top-level element ids
|
|
276
275
|
const existingIds = new Set();
|
|
277
276
|
const localElementsMap = localElements ? arrayToMap(localElements) : null;
|
|
278
|
-
const restoredElements = (elements || []).reduce((elements, element) => {
|
|
277
|
+
const restoredElements = syncInvalidIndices((elements || []).reduce((elements, element) => {
|
|
279
278
|
// filtering out selection, which is legacy, no longer kept in elements,
|
|
280
279
|
// and causing issues if retained
|
|
281
280
|
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
|
|
@@ -293,7 +292,7 @@ localElements, opts) => {
|
|
|
293
292
|
}
|
|
294
293
|
}
|
|
295
294
|
return elements;
|
|
296
|
-
}, []);
|
|
295
|
+
}, []));
|
|
297
296
|
if (!opts?.repairBindings) {
|
|
298
297
|
return restoredElements;
|
|
299
298
|
}
|
|
@@ -78,4 +78,4 @@ export type ExcalidrawElementSkeleton = Extract<Exclude<ExcalidrawElement, Excal
|
|
|
78
78
|
} & Partial<ExcalidrawMagicFrameElement>);
|
|
79
79
|
export declare const convertToExcalidrawElements: (elementsSkeleton: ExcalidrawElementSkeleton[] | null, opts?: {
|
|
80
80
|
regenerateIds: boolean;
|
|
81
|
-
}) =>
|
|
81
|
+
}) => import("../element/types").OrderedExcalidrawElement[];
|
|
@@ -3,9 +3,10 @@ import { getCommonBounds, newElement, newLinearElement, redrawTextBoundingBox, }
|
|
|
3
3
|
import { bindLinearElement } from "../element/binding";
|
|
4
4
|
import { newFrameElement, newImageElement, newMagicFrameElement, newTextElement, } from "../element/newElement";
|
|
5
5
|
import { getDefaultLineHeight, measureText, normalizeText, } from "../element/textElement";
|
|
6
|
-
import { assertNever, cloneJSON, getFontString, toBrandedType } from "../utils";
|
|
6
|
+
import { arrayToMap, assertNever, cloneJSON, getFontString, toBrandedType, } from "../utils";
|
|
7
7
|
import { getSizeFromPoints } from "../points";
|
|
8
8
|
import { randomId } from "../random";
|
|
9
|
+
import { syncInvalidIndices } from "../fractionalIndex";
|
|
9
10
|
const DEFAULT_LINEAR_ELEMENT_PROPS = {
|
|
10
11
|
width: 100,
|
|
11
12
|
height: 0,
|
|
@@ -162,6 +163,14 @@ const bindLinearElementToElement = (linearElement, start, end, elementStore, ele
|
|
|
162
163
|
bindLinearElement(linearElement, endBoundElement, "end", elementsMap);
|
|
163
164
|
}
|
|
164
165
|
}
|
|
166
|
+
// Safe check to early return for single point
|
|
167
|
+
if (linearElement.points.length < 2) {
|
|
168
|
+
return {
|
|
169
|
+
linearElement,
|
|
170
|
+
startBoundElement,
|
|
171
|
+
endBoundElement,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
165
174
|
// Update start/end points by 0.5 so bindings don't overlap with start/end bound element coordinates.
|
|
166
175
|
const endPointIndex = linearElement.points.length - 1;
|
|
167
176
|
const delta = 0.5;
|
|
@@ -206,10 +215,10 @@ class ElementStore {
|
|
|
206
215
|
this.excalidrawElements.set(ele.id, ele);
|
|
207
216
|
};
|
|
208
217
|
getElements = () => {
|
|
209
|
-
return Array.from(this.excalidrawElements.values());
|
|
218
|
+
return syncInvalidIndices(Array.from(this.excalidrawElements.values()));
|
|
210
219
|
};
|
|
211
220
|
getElementsMap = () => {
|
|
212
|
-
return toBrandedType(this.
|
|
221
|
+
return toBrandedType(arrayToMap(this.getElements()));
|
|
213
222
|
};
|
|
214
223
|
getElement = (id) => {
|
|
215
224
|
return this.excalidrawElements.get(id);
|