@excalidraw/excalidraw 0.17.1-c0b80a0 → 0.17.1-c329470
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/dist/browser/dev/excalidraw-assets-dev/{chunk-JGDL4H2X.js → chunk-3DLVY5XU.js} +8272 -6864
- package/dist/browser/dev/excalidraw-assets-dev/chunk-3DLVY5XU.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-V7NFEZA6.js → chunk-NOAEU4NM.js} +9 -2
- package/dist/browser/dev/excalidraw-assets-dev/chunk-NOAEU4NM.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{en-ZSVWGT55.js → en-7IBTMWBG.js} +2 -2
- package/dist/browser/dev/excalidraw-assets-dev/{image-RJG3J34Y.js → image-N5AC7SEK.js} +2 -6
- package/dist/browser/dev/index.css +85 -50
- package/dist/browser/dev/index.css.map +3 -3
- package/dist/browser/dev/index.js +4375 -3766
- package/dist/browser/dev/index.js.map +4 -4
- package/dist/browser/prod/excalidraw-assets/{chunk-LDVEIXGO.js → chunk-7CSIPVOW.js} +2 -2
- package/dist/browser/prod/excalidraw-assets/chunk-TX3BU7T2.js +47 -0
- package/dist/browser/prod/excalidraw-assets/{en-UPNEHLDS.js → en-LOGQBETY.js} +1 -1
- package/dist/browser/prod/excalidraw-assets/image-3V4U7GZE.js +1 -0
- package/dist/browser/prod/index.css +1 -1
- package/dist/browser/prod/index.js +40 -40
- package/dist/dev/index.css +85 -50
- package/dist/dev/index.css.map +3 -3
- package/dist/dev/index.js +8688 -6706
- package/dist/dev/index.js.map +4 -4
- package/dist/{prod/locales/en-ZXYG7GCR.json → dev/locales/en-V6KXFSCK.json} +8 -1
- package/dist/excalidraw/actions/actionAlign.d.ts +7 -6
- package/dist/excalidraw/actions/actionAlign.js +14 -14
- package/dist/excalidraw/actions/actionClipboard.d.ts +7 -3
- package/dist/excalidraw/actions/actionDeleteSelected.d.ts +7 -3
- package/dist/excalidraw/actions/actionDeleteSelected.js +103 -34
- package/dist/excalidraw/actions/actionDuplicateSelection.js +105 -95
- package/dist/excalidraw/actions/actionFlip.js +16 -7
- package/dist/excalidraw/actions/actionFrame.d.ts +493 -0
- package/dist/excalidraw/actions/actionFrame.js +45 -2
- package/dist/excalidraw/actions/actionGroup.js +6 -4
- package/dist/excalidraw/actions/actionProperties.js +145 -116
- package/dist/excalidraw/actions/actionSelectAll.js +4 -3
- package/dist/excalidraw/actions/shortcuts.d.ts +1 -1
- package/dist/excalidraw/actions/shortcuts.js +1 -0
- package/dist/excalidraw/actions/types.d.ts +1 -1
- package/dist/excalidraw/align.d.ts +2 -1
- package/dist/excalidraw/align.js +15 -6
- package/dist/excalidraw/clipboard.d.ts +27 -5
- package/dist/excalidraw/clipboard.js +55 -28
- package/dist/excalidraw/components/Actions.d.ts +2 -1
- package/dist/excalidraw/components/Actions.js +4 -2
- package/dist/excalidraw/components/ActiveConfirmDialog.d.ts +1 -1
- package/dist/excalidraw/components/ActiveConfirmDialog.js +2 -3
- package/dist/excalidraw/components/App.d.ts +1 -0
- package/dist/excalidraw/components/App.js +216 -111
- package/dist/excalidraw/components/ColorPicker/ColorInput.js +2 -3
- package/dist/excalidraw/components/ColorPicker/ColorPicker.js +2 -3
- package/dist/excalidraw/components/ColorPicker/CustomColorList.js +1 -1
- package/dist/excalidraw/components/ColorPicker/Picker.js +1 -1
- package/dist/excalidraw/components/ColorPicker/PickerColorList.js +1 -1
- package/dist/excalidraw/components/ColorPicker/ShadeList.js +1 -1
- package/dist/excalidraw/components/ColorPicker/colorPickerUtils.d.ts +1 -1
- package/dist/excalidraw/components/ColorPicker/colorPickerUtils.js +1 -1
- package/dist/excalidraw/components/CommandPalette/CommandPalette.js +3 -3
- package/dist/excalidraw/components/ConfirmDialog.js +17 -5
- package/dist/excalidraw/components/Dialog.js +2 -3
- package/dist/excalidraw/components/EyeDropper.d.ts +1 -1
- package/dist/excalidraw/components/EyeDropper.js +1 -1
- package/dist/excalidraw/components/IconPicker.d.ts +2 -2
- package/dist/excalidraw/components/IconPicker.js +56 -53
- package/dist/excalidraw/components/LayerUI.js +6 -6
- package/dist/excalidraw/components/LibraryMenu.d.ts +2 -16
- package/dist/excalidraw/components/LibraryMenu.js +70 -28
- package/dist/excalidraw/components/LibraryMenuHeaderContent.js +4 -5
- package/dist/excalidraw/components/MobileMenu.js +1 -1
- package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirm.js +2 -3
- package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.d.ts +1 -1
- package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.js +2 -3
- package/dist/excalidraw/components/Range.d.ts +9 -0
- package/dist/excalidraw/components/Range.js +24 -0
- package/dist/excalidraw/components/SearchMenu.d.ts +1 -1
- package/dist/excalidraw/components/SearchMenu.js +3 -4
- package/dist/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
- package/dist/excalidraw/components/Sidebar/Sidebar.js +2 -3
- package/dist/excalidraw/components/Stats/Collapsible.d.ts +2 -1
- package/dist/excalidraw/components/Stats/Collapsible.js +2 -2
- package/dist/excalidraw/components/Stats/Dimension.js +94 -8
- package/dist/excalidraw/components/Stats/MultiDimension.js +8 -5
- package/dist/excalidraw/components/Stats/Position.js +63 -3
- package/dist/excalidraw/components/Stats/index.js +21 -4
- package/dist/excalidraw/components/Stats/utils.d.ts +1 -1
- package/dist/excalidraw/components/Stats/utils.js +2 -55
- package/dist/excalidraw/components/TTDDialog/TTDDialog.js +1 -1
- package/dist/excalidraw/components/ToolButton.js +4 -9
- package/dist/excalidraw/components/hoc/withInternalFallback.js +3 -3
- package/dist/excalidraw/components/hyperlink/Hyperlink.js +6 -12
- package/dist/excalidraw/components/icons.d.ts +9 -0
- package/dist/excalidraw/components/icons.js +4 -4
- package/dist/excalidraw/components/main-menu/DefaultItems.js +2 -3
- package/dist/excalidraw/constants.d.ts +5 -1
- package/dist/excalidraw/constants.js +9 -1
- package/dist/excalidraw/context/tunnels.d.ts +2 -1
- package/dist/excalidraw/context/tunnels.js +3 -1
- package/dist/excalidraw/data/blob.d.ts +1 -0
- package/dist/excalidraw/data/blob.js +7 -3
- package/dist/excalidraw/data/filesystem.d.ts +2 -1
- package/dist/excalidraw/data/filesystem.js +1 -0
- package/dist/excalidraw/data/image.d.ts +0 -6
- package/dist/excalidraw/data/image.js +1 -43
- package/dist/excalidraw/data/index.js +6 -6
- package/dist/excalidraw/data/library.d.ts +9 -3
- package/dist/excalidraw/data/library.js +43 -6
- package/dist/excalidraw/data/restore.js +26 -8
- package/dist/excalidraw/data/url.d.ts +0 -1
- package/dist/excalidraw/data/url.js +2 -4
- package/dist/excalidraw/editor-jotai.d.ts +56 -0
- package/dist/excalidraw/editor-jotai.js +8 -0
- package/dist/excalidraw/element/binding.d.ts +9 -6
- package/dist/excalidraw/element/binding.js +124 -44
- package/dist/excalidraw/element/bounds.js +10 -0
- package/dist/excalidraw/element/cropElement.d.ts +5 -0
- package/dist/excalidraw/element/cropElement.js +28 -1
- package/dist/excalidraw/element/dragElements.js +13 -7
- package/dist/excalidraw/element/elbowArrow.d.ts +16 -0
- package/dist/excalidraw/element/elbowArrow.js +1268 -0
- package/dist/excalidraw/element/embeddable.js +4 -5
- package/dist/excalidraw/element/flowchart.d.ts +1 -1
- package/dist/excalidraw/element/flowchart.js +25 -9
- package/dist/excalidraw/element/heading.d.ts +5 -1
- package/dist/excalidraw/element/heading.js +5 -1
- package/dist/excalidraw/element/image.js +19 -5
- package/dist/excalidraw/element/linearElementEditor.d.ts +9 -10
- package/dist/excalidraw/element/linearElementEditor.js +97 -38
- package/dist/excalidraw/element/mutateElement.d.ts +3 -1
- package/dist/excalidraw/element/mutateElement.js +31 -4
- package/dist/excalidraw/element/newElement.d.ts +8 -12
- package/dist/excalidraw/element/newElement.js +36 -21
- package/dist/excalidraw/element/resizeElements.d.ts +20 -5
- package/dist/excalidraw/element/resizeElements.js +593 -361
- package/dist/excalidraw/element/sortElements.js +1 -4
- package/dist/excalidraw/element/types.d.ts +23 -1
- package/dist/excalidraw/fonts/Fonts.d.ts +0 -16
- package/dist/excalidraw/fonts/Fonts.js +6 -31
- package/dist/excalidraw/frame.d.ts +11 -5
- package/dist/excalidraw/frame.js +146 -35
- package/dist/excalidraw/groups.js +3 -0
- package/dist/excalidraw/hooks/useLibraryItemSvg.d.ts +1 -1
- package/dist/excalidraw/hooks/useLibraryItemSvg.js +2 -3
- package/dist/excalidraw/hooks/useScrollPosition.js +1 -1
- package/dist/excalidraw/i18n.js +3 -4
- package/dist/excalidraw/index.js +3 -4
- package/dist/excalidraw/locales/en.json +8 -1
- package/dist/excalidraw/renderer/interactiveScene.js +43 -32
- package/dist/excalidraw/renderer/staticScene.js +6 -4
- package/dist/excalidraw/renderer/staticSvgScene.js +1 -1
- package/dist/excalidraw/scene/Shape.js +40 -17
- package/dist/excalidraw/scene/comparisons.d.ts +0 -477
- package/dist/excalidraw/scene/comparisons.js +0 -37
- package/dist/excalidraw/scene/export.d.ts +7 -0
- package/dist/excalidraw/scene/export.js +107 -43
- package/dist/excalidraw/scene/index.d.ts +1 -1
- package/dist/excalidraw/scene/index.js +1 -1
- package/dist/excalidraw/scene/selection.js +4 -1
- package/dist/excalidraw/types.d.ts +15 -0
- package/dist/excalidraw/utility-types.d.ts +1 -0
- package/dist/excalidraw/utils.d.ts +8 -1
- package/dist/excalidraw/utils.js +9 -0
- package/dist/excalidraw/visualdebug.d.ts +8 -1
- package/dist/excalidraw/visualdebug.js +3 -0
- package/dist/math/line.d.ts +19 -0
- package/dist/math/line.js +32 -3
- package/dist/math/point.d.ts +10 -0
- package/dist/math/point.js +12 -1
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +29 -44
- package/dist/{dev/locales/en-ZXYG7GCR.json → prod/locales/en-V6KXFSCK.json} +8 -1
- package/package.json +5 -2
- package/dist/browser/dev/excalidraw-assets-dev/chunk-JGDL4H2X.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/chunk-V7NFEZA6.js.map +0 -7
- package/dist/browser/prod/excalidraw-assets/chunk-S2XKB3DE.js +0 -62
- package/dist/browser/prod/excalidraw-assets/image-OFI2YYMP.js +0 -1
- package/dist/excalidraw/element/routing.d.ts +0 -12
- package/dist/excalidraw/element/routing.js +0 -642
- package/dist/excalidraw/jotai.d.ts +0 -34
- package/dist/excalidraw/jotai.js +0 -18
- /package/dist/browser/dev/excalidraw-assets-dev/{en-ZSVWGT55.js.map → en-7IBTMWBG.js.map} +0 -0
- /package/dist/browser/dev/excalidraw-assets-dev/{image-RJG3J34Y.js.map → image-N5AC7SEK.js.map} +0 -0
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useCallback, useState } from "react";
|
|
3
3
|
import { t } from "../i18n";
|
|
4
4
|
import Trans from "./Trans";
|
|
5
|
-
import {
|
|
5
|
+
import { useAtom } from "../editor-jotai";
|
|
6
6
|
import { useApp, useExcalidrawSetAppState } from "./App";
|
|
7
7
|
import { saveLibraryAsJSON } from "../data/json";
|
|
8
8
|
import { libraryItemsAtom } from "../data/library";
|
|
@@ -10,7 +10,6 @@ import { DotsIcon, ExportIcon, LoadIcon, publishIcon, TrashIcon, } from "./icons
|
|
|
10
10
|
import { ToolButton } from "./ToolButton";
|
|
11
11
|
import { fileOpen } from "../data/filesystem";
|
|
12
12
|
import { muteFSAbortError } from "../utils";
|
|
13
|
-
import { useAtom } from "jotai";
|
|
14
13
|
import ConfirmDialog from "./ConfirmDialog";
|
|
15
14
|
import PublishLibrary from "./PublishLibrary";
|
|
16
15
|
import { Dialog } from "./Dialog";
|
|
@@ -21,8 +20,8 @@ import clsx from "clsx";
|
|
|
21
20
|
import { useLibraryCache } from "../hooks/useLibraryItemSvg";
|
|
22
21
|
const getSelectedItems = (libraryItems, selectedItems) => libraryItems.filter((item) => selectedItems.includes(item.id));
|
|
23
22
|
export const LibraryDropdownMenuButton = ({ setAppState, selectedItems, library, onRemoveFromLibrary, resetLibrary, onSelectItems, appState, className, }) => {
|
|
24
|
-
const [libraryItemsData] = useAtom(libraryItemsAtom
|
|
25
|
-
const [isLibraryMenuOpen, setIsLibraryMenuOpen] = useAtom(isLibraryMenuOpenAtom
|
|
23
|
+
const [libraryItemsData] = useAtom(libraryItemsAtom);
|
|
24
|
+
const [isLibraryMenuOpen, setIsLibraryMenuOpen] = useAtom(isLibraryMenuOpenAtom);
|
|
26
25
|
const renderRemoveLibAlert = () => {
|
|
27
26
|
const content = selectedItems.length
|
|
28
27
|
? t("alerts.removeItemsFromsLibrary", { count: selectedItems.length })
|
|
@@ -109,7 +108,7 @@ export const LibraryDropdownMenu = ({ selectedItems, onSelectItems, className, }
|
|
|
109
108
|
const { clearLibraryCache, deleteItemsFromLibraryCache } = useLibraryCache();
|
|
110
109
|
const appState = useUIAppState();
|
|
111
110
|
const setAppState = useExcalidrawSetAppState();
|
|
112
|
-
const [libraryItemsData] = useAtom(libraryItemsAtom
|
|
111
|
+
const [libraryItemsData] = useAtom(libraryItemsAtom);
|
|
113
112
|
const removeFromLibrary = async (libraryItems) => {
|
|
114
113
|
const nextItems = libraryItems.filter((item) => !selectedItems.includes(item.id));
|
|
115
114
|
library.setLibrary(nextItems).catch(() => {
|
|
@@ -36,7 +36,7 @@ export const MobileMenu = ({ appState, elements, actionManager, setAppState, onL
|
|
|
36
36
|
}, children: _jsxs(Island, { padding: 0, children: [appState.openMenu === "shape" &&
|
|
37
37
|
!appState.viewModeEnabled &&
|
|
38
38
|
appState.openDialog?.name !== "elementLinkSelector" &&
|
|
39
|
-
showSelectedShapeActions(appState, elements) ? (_jsx(Section, { className: "App-mobile-menu", heading: "selectedShapeActions", children: _jsx(SelectedShapeActions, { appState: appState, elementsMap: app.scene.getNonDeletedElementsMap(), renderAction: actionManager.renderAction }) })) : null, _jsxs("footer", { className: "App-toolbar", children: [renderAppToolbar(), appState.scrolledOutside &&
|
|
39
|
+
showSelectedShapeActions(appState, elements) ? (_jsx(Section, { className: "App-mobile-menu", heading: "selectedShapeActions", children: _jsx(SelectedShapeActions, { appState: appState, elementsMap: app.scene.getNonDeletedElementsMap(), renderAction: actionManager.renderAction, app: app }) })) : null, _jsxs("footer", { className: "App-toolbar", children: [renderAppToolbar(), appState.scrolledOutside &&
|
|
40
40
|
!appState.openMenu &&
|
|
41
41
|
!appState.openSidebar && (_jsx("button", { type: "button", className: "scroll-back-to-content", onClick: () => {
|
|
42
42
|
setAppState((appState) => ({
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useAtom } from "jotai";
|
|
3
2
|
import { useTunnels } from "../../context/tunnels";
|
|
4
|
-
import {
|
|
3
|
+
import { useAtom } from "../../editor-jotai";
|
|
5
4
|
import { Dialog } from "../Dialog";
|
|
6
5
|
import { withInternalFallback } from "../hoc/withInternalFallback";
|
|
7
6
|
import { overwriteConfirmStateAtom } from "./OverwriteConfirmState";
|
|
@@ -11,7 +10,7 @@ import { Actions, Action } from "./OverwriteConfirmActions";
|
|
|
11
10
|
import "./OverwriteConfirm.scss";
|
|
12
11
|
const OverwriteConfirmDialog = Object.assign(withInternalFallback("OverwriteConfirmDialog", ({ children }) => {
|
|
13
12
|
const { OverwriteConfirmDialogTunnel } = useTunnels();
|
|
14
|
-
const [overwriteConfirmState, setState] = useAtom(overwriteConfirmStateAtom
|
|
13
|
+
const [overwriteConfirmState, setState] = useAtom(overwriteConfirmStateAtom);
|
|
15
14
|
if (!overwriteConfirmState.active) {
|
|
16
15
|
return null;
|
|
17
16
|
}
|
|
@@ -11,7 +11,7 @@ export type OverwriteConfirmState = {
|
|
|
11
11
|
} | {
|
|
12
12
|
active: false;
|
|
13
13
|
};
|
|
14
|
-
export declare const overwriteConfirmStateAtom: import("jotai").PrimitiveAtom<OverwriteConfirmState> & {
|
|
14
|
+
export declare const overwriteConfirmStateAtom: import("jotai/vanilla/atom").PrimitiveAtom<OverwriteConfirmState> & {
|
|
15
15
|
init: OverwriteConfirmState;
|
|
16
16
|
};
|
|
17
17
|
export declare function openConfirmModal({ title, description, actionLabel, color, }: {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { atom } from "jotai";
|
|
2
|
-
import { jotaiStore } from "../../jotai";
|
|
1
|
+
import { atom, editorJotaiStore } from "../../editor-jotai";
|
|
3
2
|
export const overwriteConfirmStateAtom = atom({
|
|
4
3
|
active: false,
|
|
5
4
|
});
|
|
6
5
|
export async function openConfirmModal({ title, description, actionLabel, color, }) {
|
|
7
6
|
return new Promise((resolve) => {
|
|
8
|
-
|
|
7
|
+
editorJotaiStore.set(overwriteConfirmStateAtom, {
|
|
9
8
|
active: true,
|
|
10
9
|
onConfirm: () => resolve(true),
|
|
11
10
|
onClose: () => resolve(false),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import "./Range.scss";
|
|
3
|
+
export type RangeProps = {
|
|
4
|
+
updateData: (value: number) => void;
|
|
5
|
+
appState: any;
|
|
6
|
+
elements: any;
|
|
7
|
+
testId?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const Range: ({ updateData, appState, elements, testId, }: RangeProps) => JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useEffect } from "react";
|
|
3
|
+
import { getFormValue } from "../actions/actionProperties";
|
|
4
|
+
import { t } from "../i18n";
|
|
5
|
+
import "./Range.scss";
|
|
6
|
+
export const Range = ({ updateData, appState, elements, testId, }) => {
|
|
7
|
+
const rangeRef = React.useRef(null);
|
|
8
|
+
const valueRef = React.useRef(null);
|
|
9
|
+
const value = getFormValue(elements, appState, (element) => element.opacity, true, appState.currentItemOpacity);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (rangeRef.current && valueRef.current) {
|
|
12
|
+
const rangeElement = rangeRef.current;
|
|
13
|
+
const valueElement = valueRef.current;
|
|
14
|
+
const inputWidth = rangeElement.offsetWidth;
|
|
15
|
+
const thumbWidth = 15; // 15 is the width of the thumb
|
|
16
|
+
const position = (value / 100) * (inputWidth - thumbWidth) + thumbWidth / 2;
|
|
17
|
+
valueElement.style.left = `${position}px`;
|
|
18
|
+
rangeElement.style.background = `linear-gradient(to right, var(--color-slider-track) 0%, var(--color-slider-track) ${value}%, var(--button-bg) ${value}%, var(--button-bg) 100%)`;
|
|
19
|
+
}
|
|
20
|
+
}, [value]);
|
|
21
|
+
return (_jsxs("label", { className: "control-label", children: [t("labels.opacity"), _jsxs("div", { className: "range-wrapper", children: [_jsx("input", { ref: rangeRef, type: "range", min: "0", max: "100", step: "10", onChange: (event) => {
|
|
22
|
+
updateData(+event.target.value);
|
|
23
|
+
}, value: value, className: "range-input", "data-testid": testId }), _jsx("div", { className: "value-bubble", ref: valueRef, children: value !== 0 ? value : null }), _jsx("div", { className: "zero-label", children: "0" })] })] }));
|
|
24
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import "./SearchMenu.scss";
|
|
3
|
-
export declare const searchItemInFocusAtom: import("jotai").PrimitiveAtom<number | null> & {
|
|
3
|
+
export declare const searchItemInFocusAtom: import("jotai/vanilla/atom").PrimitiveAtom<number | null> & {
|
|
4
4
|
init: number | null;
|
|
5
5
|
};
|
|
6
6
|
export declare const SearchMenu: () => JSX.Element;
|
|
@@ -10,8 +10,7 @@ import { measureText } from "../element/textElement";
|
|
|
10
10
|
import { addEventListener, getFontString } from "../utils";
|
|
11
11
|
import { KEYS } from "../keys";
|
|
12
12
|
import clsx from "clsx";
|
|
13
|
-
import { atom, useAtom } from "jotai";
|
|
14
|
-
import { jotaiScope } from "../jotai";
|
|
13
|
+
import { atom, useAtom } from "../editor-jotai";
|
|
15
14
|
import { t } from "../i18n";
|
|
16
15
|
import { isElementCompletelyInViewport } from "../element/sizeHelpers";
|
|
17
16
|
import { randomInteger } from "../random";
|
|
@@ -26,7 +25,7 @@ export const SearchMenu = () => {
|
|
|
26
25
|
const app = useApp();
|
|
27
26
|
const setAppState = useExcalidrawSetAppState();
|
|
28
27
|
const searchInputRef = useRef(null);
|
|
29
|
-
const [inputValue, setInputValue] = useAtom(searchQueryAtom
|
|
28
|
+
const [inputValue, setInputValue] = useAtom(searchQueryAtom);
|
|
30
29
|
const searchQuery = inputValue.trim();
|
|
31
30
|
const [isSearching, setIsSearching] = useState(false);
|
|
32
31
|
const [searchMatches, setSearchMatches] = useState({
|
|
@@ -35,7 +34,7 @@ export const SearchMenu = () => {
|
|
|
35
34
|
});
|
|
36
35
|
const searchedQueryRef = useRef(null);
|
|
37
36
|
const lastSceneNonceRef = useRef(undefined);
|
|
38
|
-
const [focusIndex, setFocusIndex] = useAtom(searchItemInFocusAtom
|
|
37
|
+
const [focusIndex, setFocusIndex] = useAtom(searchItemInFocusAtom);
|
|
39
38
|
const elementsMap = app.scene.getNonDeletedElementsMap();
|
|
40
39
|
useEffect(() => {
|
|
41
40
|
if (isSearching) {
|
|
@@ -8,7 +8,7 @@ import "./Sidebar.scss";
|
|
|
8
8
|
*
|
|
9
9
|
* Since we can only render one Sidebar at a time, we can use a simple flag.
|
|
10
10
|
*/
|
|
11
|
-
export declare const isSidebarDockedAtom: import("jotai").PrimitiveAtom<boolean> & {
|
|
11
|
+
export declare const isSidebarDockedAtom: import("jotai/vanilla/atom").PrimitiveAtom<boolean> & {
|
|
12
12
|
init: boolean;
|
|
13
13
|
};
|
|
14
14
|
export declare const SidebarInner: React.ForwardRefExoticComponent<Omit<{
|
|
@@ -2,8 +2,7 @@ import { createElement as _createElement } from "react";
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useLayoutEffect, useRef, useState, forwardRef, useImperativeHandle, useCallback, } from "react";
|
|
4
4
|
import { Island } from "../Island";
|
|
5
|
-
import { atom, useSetAtom } from "jotai";
|
|
6
|
-
import { jotaiScope } from "../../jotai";
|
|
5
|
+
import { atom, useSetAtom } from "../../editor-jotai";
|
|
7
6
|
import { SidebarPropsContext } from "./common";
|
|
8
7
|
import { SidebarHeader } from "./SidebarHeader";
|
|
9
8
|
import clsx from "clsx";
|
|
@@ -33,7 +32,7 @@ export const SidebarInner = forwardRef(({ name, children, onDock, docked, classN
|
|
|
33
32
|
console.warn("Sidebar: `docked` must be set when `onDock` is supplied for the sidebar to be user-dockable. To hide this message, either pass `docked` or remove `onDock`");
|
|
34
33
|
}
|
|
35
34
|
const setAppState = useExcalidrawSetAppState();
|
|
36
|
-
const setIsSidebarDockedAtom = useSetAtom(isSidebarDockedAtom
|
|
35
|
+
const setIsSidebarDockedAtom = useSetAtom(isSidebarDockedAtom);
|
|
37
36
|
useLayoutEffect(() => {
|
|
38
37
|
setIsSidebarDockedAtom(!!docked);
|
|
39
38
|
return () => {
|
|
@@ -4,6 +4,7 @@ interface CollapsibleProps {
|
|
|
4
4
|
open: boolean;
|
|
5
5
|
openTrigger: () => void;
|
|
6
6
|
children: React.ReactNode;
|
|
7
|
+
className?: string;
|
|
7
8
|
}
|
|
8
|
-
declare const Collapsible: ({ label, open, openTrigger, children, }: CollapsibleProps) => JSX.Element;
|
|
9
|
+
declare const Collapsible: ({ label, open, openTrigger, children, className, }: CollapsibleProps) => JSX.Element;
|
|
9
10
|
export default Collapsible;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { InlineIcon } from "../InlineIcon";
|
|
3
3
|
import { collapseDownIcon, collapseUpIcon } from "../icons";
|
|
4
|
-
const Collapsible = ({ label, open, openTrigger, children, }) => {
|
|
4
|
+
const Collapsible = ({ label, open, openTrigger, children, className, }) => {
|
|
5
5
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
|
|
6
6
|
cursor: "pointer",
|
|
7
7
|
display: "flex",
|
|
8
8
|
justifyContent: "space-between",
|
|
9
9
|
alignItems: "center",
|
|
10
|
-
}, onClick: openTrigger, children: [label, _jsx(InlineIcon, { icon: open ? collapseUpIcon : collapseDownIcon })] }), open && (_jsx("div", { style: { display: "flex", flexDirection: "column" }, children: children }))] }));
|
|
10
|
+
}, className: className, onClick: openTrigger, children: [label, _jsx(InlineIcon, { icon: open ? collapseUpIcon : collapseDownIcon })] }), open && (_jsx("div", { style: { display: "flex", flexDirection: "column" }, children: children }))] }));
|
|
11
11
|
};
|
|
12
12
|
export default Collapsible;
|
|
@@ -1,18 +1,87 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import DragInput from "./DragInput";
|
|
3
|
-
import { getStepSizedValue, isPropertyEditable
|
|
3
|
+
import { getStepSizedValue, isPropertyEditable } from "./utils";
|
|
4
4
|
import { MIN_WIDTH_OR_HEIGHT } from "../../constants";
|
|
5
|
+
import { resizeSingleElement } from "../../element/resizeElements";
|
|
6
|
+
import { isImageElement } from "../../element/typeChecks";
|
|
7
|
+
import { MINIMAL_CROP_SIZE, getUncroppedWidthAndHeight, } from "../../element/cropElement";
|
|
8
|
+
import { mutateElement } from "../../element/mutateElement";
|
|
9
|
+
import { clamp, round } from "../../../math";
|
|
5
10
|
const STEP_SIZE = 10;
|
|
6
11
|
const _shouldKeepAspectRatio = (element) => {
|
|
7
12
|
return element.type === "image";
|
|
8
13
|
};
|
|
9
|
-
const handleDimensionChange = ({ accumulatedChange, originalElements, shouldKeepAspectRatio, shouldChangeByStepSize, nextValue, property, scene, }) => {
|
|
14
|
+
const handleDimensionChange = ({ accumulatedChange, originalElements, originalElementsMap, shouldKeepAspectRatio, shouldChangeByStepSize, nextValue, property, originalAppState, instantChange, scene, }) => {
|
|
10
15
|
const elementsMap = scene.getNonDeletedElementsMap();
|
|
11
|
-
const elements = scene.getNonDeletedElements();
|
|
12
16
|
const origElement = originalElements[0];
|
|
13
|
-
|
|
17
|
+
const latestElement = elementsMap.get(origElement.id);
|
|
18
|
+
if (origElement && latestElement) {
|
|
14
19
|
const keepAspectRatio = shouldKeepAspectRatio || _shouldKeepAspectRatio(origElement);
|
|
15
20
|
const aspectRatio = origElement.width / origElement.height;
|
|
21
|
+
if (originalAppState.croppingElementId === origElement.id) {
|
|
22
|
+
const element = elementsMap.get(origElement.id);
|
|
23
|
+
if (!element || !isImageElement(element) || !element.crop) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const crop = element.crop;
|
|
27
|
+
let nextCrop = { ...crop };
|
|
28
|
+
const isFlippedByX = element.scale[0] === -1;
|
|
29
|
+
const isFlippedByY = element.scale[1] === -1;
|
|
30
|
+
const { width: uncroppedWidth, height: uncroppedHeight } = getUncroppedWidthAndHeight(element);
|
|
31
|
+
const naturalToUncroppedWidthRatio = crop.naturalWidth / uncroppedWidth;
|
|
32
|
+
const naturalToUncroppedHeightRatio = crop.naturalHeight / uncroppedHeight;
|
|
33
|
+
const MAX_POSSIBLE_WIDTH = isFlippedByX
|
|
34
|
+
? crop.width + crop.x
|
|
35
|
+
: crop.naturalWidth - crop.x;
|
|
36
|
+
const MAX_POSSIBLE_HEIGHT = isFlippedByY
|
|
37
|
+
? crop.height + crop.y
|
|
38
|
+
: crop.naturalHeight - crop.y;
|
|
39
|
+
const MIN_WIDTH = MINIMAL_CROP_SIZE * naturalToUncroppedWidthRatio;
|
|
40
|
+
const MIN_HEIGHT = MINIMAL_CROP_SIZE * naturalToUncroppedHeightRatio;
|
|
41
|
+
if (nextValue !== undefined) {
|
|
42
|
+
if (property === "width") {
|
|
43
|
+
const nextValueInNatural = nextValue * naturalToUncroppedWidthRatio;
|
|
44
|
+
const nextCropWidth = clamp(nextValueInNatural, MIN_WIDTH, MAX_POSSIBLE_WIDTH);
|
|
45
|
+
nextCrop = {
|
|
46
|
+
...nextCrop,
|
|
47
|
+
width: nextCropWidth,
|
|
48
|
+
x: isFlippedByX ? crop.x + crop.width - nextCropWidth : crop.x,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
else if (property === "height") {
|
|
52
|
+
const nextValueInNatural = nextValue * naturalToUncroppedHeightRatio;
|
|
53
|
+
const nextCropHeight = clamp(nextValueInNatural, MIN_HEIGHT, MAX_POSSIBLE_HEIGHT);
|
|
54
|
+
nextCrop = {
|
|
55
|
+
...nextCrop,
|
|
56
|
+
height: nextCropHeight,
|
|
57
|
+
y: isFlippedByY ? crop.y + crop.height - nextCropHeight : crop.y,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
mutateElement(element, {
|
|
61
|
+
crop: nextCrop,
|
|
62
|
+
width: nextCrop.width / (crop.naturalWidth / uncroppedWidth),
|
|
63
|
+
height: nextCrop.height / (crop.naturalHeight / uncroppedHeight),
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const changeInWidth = property === "width" ? instantChange : 0;
|
|
68
|
+
const changeInHeight = property === "height" ? instantChange : 0;
|
|
69
|
+
const nextCropWidth = clamp(crop.width + changeInWidth, MIN_WIDTH, MAX_POSSIBLE_WIDTH);
|
|
70
|
+
const nextCropHeight = clamp(crop.height + changeInHeight, MIN_WIDTH, MAX_POSSIBLE_HEIGHT);
|
|
71
|
+
nextCrop = {
|
|
72
|
+
...crop,
|
|
73
|
+
x: isFlippedByX ? crop.x + crop.width - nextCropWidth : crop.x,
|
|
74
|
+
y: isFlippedByY ? crop.y + crop.height - nextCropHeight : crop.y,
|
|
75
|
+
width: nextCropWidth,
|
|
76
|
+
height: nextCropHeight,
|
|
77
|
+
};
|
|
78
|
+
mutateElement(element, {
|
|
79
|
+
crop: nextCrop,
|
|
80
|
+
width: nextCrop.width / (crop.naturalWidth / uncroppedWidth),
|
|
81
|
+
height: nextCrop.height / (crop.naturalHeight / uncroppedHeight),
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
16
85
|
if (nextValue !== undefined) {
|
|
17
86
|
const nextWidth = Math.max(property === "width"
|
|
18
87
|
? nextValue
|
|
@@ -24,7 +93,9 @@ const handleDimensionChange = ({ accumulatedChange, originalElements, shouldKeep
|
|
|
24
93
|
: keepAspectRatio
|
|
25
94
|
? nextValue / aspectRatio
|
|
26
95
|
: origElement.height, MIN_WIDTH_OR_HEIGHT);
|
|
27
|
-
|
|
96
|
+
resizeSingleElement(nextWidth, nextHeight, latestElement, origElement, elementsMap, originalElementsMap, property === "width" ? "e" : "s", {
|
|
97
|
+
shouldMaintainAspectRatio: keepAspectRatio,
|
|
98
|
+
});
|
|
28
99
|
return;
|
|
29
100
|
}
|
|
30
101
|
const changeInWidth = property === "width" ? accumulatedChange : 0;
|
|
@@ -57,12 +128,27 @@ const handleDimensionChange = ({ accumulatedChange, originalElements, shouldKeep
|
|
|
57
128
|
}
|
|
58
129
|
nextHeight = Math.max(MIN_WIDTH_OR_HEIGHT, nextHeight);
|
|
59
130
|
nextWidth = Math.max(MIN_WIDTH_OR_HEIGHT, nextWidth);
|
|
60
|
-
|
|
131
|
+
resizeSingleElement(nextWidth, nextHeight, latestElement, origElement, elementsMap, originalElementsMap, property === "width" ? "e" : "s", {
|
|
132
|
+
shouldMaintainAspectRatio: keepAspectRatio,
|
|
133
|
+
});
|
|
61
134
|
}
|
|
62
135
|
};
|
|
63
136
|
const DimensionDragInput = ({ property, element, scene, appState, }) => {
|
|
64
|
-
|
|
65
|
-
|
|
137
|
+
let value = round(property === "width" ? element.width : element.height, 2);
|
|
138
|
+
if (appState.croppingElementId &&
|
|
139
|
+
appState.croppingElementId === element.id &&
|
|
140
|
+
isImageElement(element) &&
|
|
141
|
+
element.crop) {
|
|
142
|
+
const { width: uncroppedWidth, height: uncroppedHeight } = getUncroppedWidthAndHeight(element);
|
|
143
|
+
if (property === "width") {
|
|
144
|
+
const ratio = uncroppedWidth / element.crop.naturalWidth;
|
|
145
|
+
value = round(element.crop.width * ratio, 2);
|
|
146
|
+
}
|
|
147
|
+
if (property === "height") {
|
|
148
|
+
const ratio = uncroppedHeight / element.crop.naturalHeight;
|
|
149
|
+
value = round(element.crop.height * ratio, 2);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
66
152
|
return (_jsx(DragInput, { label: property === "width" ? "W" : "H", elements: [element], dragInputCallback: handleDimensionChange, value: value, editable: isPropertyEditable(element, property), scene: scene, appState: appState, property: property }));
|
|
67
153
|
};
|
|
68
154
|
export default DimensionDragInput;
|
|
@@ -3,11 +3,11 @@ import { useMemo } from "react";
|
|
|
3
3
|
import { getCommonBounds, isTextElement } from "../../element";
|
|
4
4
|
import { updateBoundElements } from "../../element/binding";
|
|
5
5
|
import { mutateElement } from "../../element/mutateElement";
|
|
6
|
-
import { rescalePointsInElement } from "../../element/resizeElements";
|
|
6
|
+
import { rescalePointsInElement, resizeSingleElement, } from "../../element/resizeElements";
|
|
7
7
|
import { getBoundTextElement, handleBindTextResize, } from "../../element/textElement";
|
|
8
8
|
import DragInput from "./DragInput";
|
|
9
9
|
import { getAtomicUnits, getStepSizedValue, isPropertyEditable } from "./utils";
|
|
10
|
-
import { getElementsInAtomicUnit
|
|
10
|
+
import { getElementsInAtomicUnit } from "./utils";
|
|
11
11
|
import { MIN_WIDTH_OR_HEIGHT } from "../../constants";
|
|
12
12
|
import { pointFrom } from "../../../math";
|
|
13
13
|
const STEP_SIZE = 10;
|
|
@@ -64,7 +64,6 @@ const resizeGroup = (nextWidth, nextHeight, initialHeight, aspectRatio, anchor,
|
|
|
64
64
|
};
|
|
65
65
|
const handleDimensionChange = ({ accumulatedChange, originalElements, originalElementsMap, originalAppState, shouldChangeByStepSize, nextValue, scene, property, }) => {
|
|
66
66
|
const elementsMap = scene.getNonDeletedElementsMap();
|
|
67
|
-
const elements = scene.getNonDeletedElements();
|
|
68
67
|
const atomicUnits = getAtomicUnits(originalElements, originalAppState);
|
|
69
68
|
if (nextValue !== undefined) {
|
|
70
69
|
for (const atomicUnit of atomicUnits) {
|
|
@@ -109,7 +108,9 @@ const handleDimensionChange = ({ accumulatedChange, originalElements, originalEl
|
|
|
109
108
|
}
|
|
110
109
|
nextWidth = Math.max(MIN_WIDTH_OR_HEIGHT, nextWidth);
|
|
111
110
|
nextHeight = Math.max(MIN_WIDTH_OR_HEIGHT, nextHeight);
|
|
112
|
-
|
|
111
|
+
resizeSingleElement(nextWidth, nextHeight, latestElement, origElement, elementsMap, originalElementsMap, property === "width" ? "e" : "s", {
|
|
112
|
+
shouldInformMutation: false,
|
|
113
|
+
});
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
}
|
|
@@ -176,7 +177,9 @@ const handleDimensionChange = ({ accumulatedChange, originalElements, originalEl
|
|
|
176
177
|
}
|
|
177
178
|
nextWidth = Math.max(MIN_WIDTH_OR_HEIGHT, nextWidth);
|
|
178
179
|
nextHeight = Math.max(MIN_WIDTH_OR_HEIGHT, nextHeight);
|
|
179
|
-
|
|
180
|
+
resizeSingleElement(nextWidth, nextHeight, latestElement, origElement, elementsMap, originalElementsMap, property === "width" ? "e" : "s", {
|
|
181
|
+
shouldInformMutation: false,
|
|
182
|
+
});
|
|
180
183
|
}
|
|
181
184
|
}
|
|
182
185
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import StatsDragInput from "./DragInput";
|
|
3
3
|
import { getStepSizedValue, moveElement } from "./utils";
|
|
4
|
-
import { pointFrom, pointRotateRads } from "../../../math";
|
|
4
|
+
import { clamp, pointFrom, pointRotateRads, round } from "../../../math";
|
|
5
|
+
import { isImageElement } from "../../element/typeChecks";
|
|
6
|
+
import { getFlipAdjustedCropPosition, getUncroppedWidthAndHeight, } from "../../element/cropElement";
|
|
7
|
+
import { mutateElement } from "../../element/mutateElement";
|
|
5
8
|
const STEP_SIZE = 10;
|
|
6
|
-
const handlePositionChange = ({ accumulatedChange, originalElements, originalElementsMap, shouldChangeByStepSize, nextValue, property, scene, }) => {
|
|
9
|
+
const handlePositionChange = ({ accumulatedChange, instantChange, originalElements, originalElementsMap, shouldChangeByStepSize, nextValue, property, scene, originalAppState, }) => {
|
|
7
10
|
const elementsMap = scene.getNonDeletedElementsMap();
|
|
8
11
|
const elements = scene.getNonDeletedElements();
|
|
9
12
|
const origElement = originalElements[0];
|
|
@@ -12,6 +15,55 @@ const handlePositionChange = ({ accumulatedChange, originalElements, originalEle
|
|
|
12
15
|
origElement.y + origElement.height / 2,
|
|
13
16
|
];
|
|
14
17
|
const [topLeftX, topLeftY] = pointRotateRads(pointFrom(origElement.x, origElement.y), pointFrom(cx, cy), origElement.angle);
|
|
18
|
+
if (originalAppState.croppingElementId === origElement.id) {
|
|
19
|
+
const element = elementsMap.get(origElement.id);
|
|
20
|
+
if (!element || !isImageElement(element) || !element.crop) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const crop = element.crop;
|
|
24
|
+
let nextCrop = crop;
|
|
25
|
+
const isFlippedByX = element.scale[0] === -1;
|
|
26
|
+
const isFlippedByY = element.scale[1] === -1;
|
|
27
|
+
const { width: uncroppedWidth, height: uncroppedHeight } = getUncroppedWidthAndHeight(element);
|
|
28
|
+
if (nextValue !== undefined) {
|
|
29
|
+
if (property === "x") {
|
|
30
|
+
const nextValueInNatural = nextValue * (crop.naturalWidth / uncroppedWidth);
|
|
31
|
+
if (isFlippedByX) {
|
|
32
|
+
nextCrop = {
|
|
33
|
+
...crop,
|
|
34
|
+
x: clamp(crop.naturalWidth - nextValueInNatural - crop.width, 0, crop.naturalWidth - crop.width),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
nextCrop = {
|
|
39
|
+
...crop,
|
|
40
|
+
x: clamp(nextValue * (crop.naturalWidth / uncroppedWidth), 0, crop.naturalWidth - crop.width),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (property === "y") {
|
|
45
|
+
nextCrop = {
|
|
46
|
+
...crop,
|
|
47
|
+
y: clamp(nextValue * (crop.naturalHeight / uncroppedHeight), 0, crop.naturalHeight - crop.height),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
mutateElement(element, {
|
|
51
|
+
crop: nextCrop,
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const changeInX = (property === "x" ? instantChange : 0) * (isFlippedByX ? -1 : 1);
|
|
56
|
+
const changeInY = (property === "y" ? instantChange : 0) * (isFlippedByY ? -1 : 1);
|
|
57
|
+
nextCrop = {
|
|
58
|
+
...crop,
|
|
59
|
+
x: clamp(crop.x + changeInX, 0, crop.naturalWidth - crop.width),
|
|
60
|
+
y: clamp(crop.y + changeInY, 0, crop.naturalHeight - crop.height),
|
|
61
|
+
};
|
|
62
|
+
mutateElement(element, {
|
|
63
|
+
crop: nextCrop,
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
15
67
|
if (nextValue !== undefined) {
|
|
16
68
|
const newTopLeftX = property === "x" ? nextValue : topLeftX;
|
|
17
69
|
const newTopLeftY = property === "y" ? nextValue : topLeftY;
|
|
@@ -34,7 +86,15 @@ const handlePositionChange = ({ accumulatedChange, originalElements, originalEle
|
|
|
34
86
|
};
|
|
35
87
|
const Position = ({ property, element, elementsMap, scene, appState, }) => {
|
|
36
88
|
const [topLeftX, topLeftY] = pointRotateRads(pointFrom(element.x, element.y), pointFrom(element.x + element.width / 2, element.y + element.height / 2), element.angle);
|
|
37
|
-
|
|
89
|
+
let value = round(property === "x" ? topLeftX : topLeftY, 2);
|
|
90
|
+
if (appState.croppingElementId === element.id &&
|
|
91
|
+
isImageElement(element) &&
|
|
92
|
+
element.crop) {
|
|
93
|
+
const flipAdjustedPosition = getFlipAdjustedCropPosition(element);
|
|
94
|
+
if (flipAdjustedPosition) {
|
|
95
|
+
value = round(property === "x" ? flipAdjustedPosition.x : flipAdjustedPosition.y, 2);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
38
98
|
return (_jsx(StatsDragInput, { label: property === "x" ? "X" : "Y", elements: [element], dragInputCallback: handlePositionChange, scene: scene, value: value, property: property, appState: appState }));
|
|
39
99
|
};
|
|
40
100
|
export default Position;
|
|
@@ -18,11 +18,14 @@ import Collapsible from "./Collapsible";
|
|
|
18
18
|
import { useExcalidrawAppState, useExcalidrawSetAppState } from "../App";
|
|
19
19
|
import { getAtomicUnits } from "./utils";
|
|
20
20
|
import { STATS_PANELS } from "../../constants";
|
|
21
|
-
import { isElbowArrow } from "../../element/typeChecks";
|
|
21
|
+
import { isElbowArrow, isImageElement } from "../../element/typeChecks";
|
|
22
22
|
import CanvasGrid from "./CanvasGrid";
|
|
23
23
|
import clsx from "clsx";
|
|
24
24
|
import "./Stats.scss";
|
|
25
25
|
import { isGridModeEnabled } from "../../snapping";
|
|
26
|
+
import { getUncroppedWidthAndHeight } from "../../element/cropElement";
|
|
27
|
+
import { round } from "../../../math";
|
|
28
|
+
import { frameAndChildrenSelectedTogether } from "../../frame";
|
|
26
29
|
const STATS_TIMEOUT = 50;
|
|
27
30
|
export const Stats = (props) => {
|
|
28
31
|
const appState = useExcalidrawAppState();
|
|
@@ -50,6 +53,10 @@ export const StatsInner = memo(({ app, onClose, renderCustomStats, selectedEleme
|
|
|
50
53
|
const setAppState = useExcalidrawSetAppState();
|
|
51
54
|
const singleElement = selectedElements.length === 1 ? selectedElements[0] : null;
|
|
52
55
|
const multipleElements = selectedElements.length > 1 ? selectedElements : null;
|
|
56
|
+
const cropMode = appState.croppingElementId && isImageElement(singleElement);
|
|
57
|
+
const unCroppedDimension = cropMode
|
|
58
|
+
? getUncroppedWidthAndHeight(singleElement)
|
|
59
|
+
: null;
|
|
53
60
|
const [sceneDimension, setSceneDimension] = useState({
|
|
54
61
|
width: 0,
|
|
55
62
|
height: 0,
|
|
@@ -68,6 +75,9 @@ export const StatsInner = memo(({ app, onClose, renderCustomStats, selectedEleme
|
|
|
68
75
|
const atomicUnits = useMemo(() => {
|
|
69
76
|
return getAtomicUnits(selectedElements, appState);
|
|
70
77
|
}, [selectedElements, appState]);
|
|
78
|
+
const _frameAndChildrenSelectedTogether = useMemo(() => {
|
|
79
|
+
return frameAndChildrenSelectedTogether(selectedElements);
|
|
80
|
+
}, [selectedElements]);
|
|
71
81
|
return (_jsx("div", { className: "exc-stats", children: _jsxs(Island, { padding: 3, children: [_jsxs("div", { className: "title", children: [_jsx("h2", { children: t("stats.title") }), _jsx("div", { className: "close", onClick: onClose, children: CloseIcon })] }), _jsxs(Collapsible, { label: _jsx("h3", { children: t("stats.generalStats") }), open: !!(appState.stats.panels & STATS_PANELS.generalStats), openTrigger: () => setAppState((state) => {
|
|
72
82
|
return {
|
|
73
83
|
stats: {
|
|
@@ -75,7 +85,7 @@ export const StatsInner = memo(({ app, onClose, renderCustomStats, selectedEleme
|
|
|
75
85
|
panels: state.stats.panels ^ STATS_PANELS.generalStats,
|
|
76
86
|
},
|
|
77
87
|
};
|
|
78
|
-
}), children: [_jsxs(StatsRows, { children: [_jsx(StatsRow, { heading: true, children: t("stats.scene") }), _jsxs(StatsRow, { columns: 2, children: [_jsx("div", { children: t("stats.shapes") }), _jsx("div", { children: elements.length })] }), _jsxs(StatsRow, { columns: 2, children: [_jsx("div", { children: t("stats.width") }), _jsx("div", { children: sceneDimension.width })] }), _jsxs(StatsRow, { columns: 2, children: [_jsx("div", { children: t("stats.height") }), _jsx("div", { children: sceneDimension.height })] }), gridModeEnabled && (_jsxs(_Fragment, { children: [_jsx(StatsRow, { heading: true, children: "Canvas" }), _jsx(StatsRow, { children: _jsx(CanvasGrid, { property: "gridStep", scene: scene, appState: appState, setAppState: setAppState }) })] }))] }), renderCustomStats?.(elements, appState)] }), selectedElements.length > 0 && (_jsx("div", { id: "elementStats", style: {
|
|
88
|
+
}), children: [_jsxs(StatsRows, { children: [_jsx(StatsRow, { heading: true, children: t("stats.scene") }), _jsxs(StatsRow, { columns: 2, children: [_jsx("div", { children: t("stats.shapes") }), _jsx("div", { children: elements.length })] }), _jsxs(StatsRow, { columns: 2, children: [_jsx("div", { children: t("stats.width") }), _jsx("div", { children: sceneDimension.width })] }), _jsxs(StatsRow, { columns: 2, children: [_jsx("div", { children: t("stats.height") }), _jsx("div", { children: sceneDimension.height })] }), gridModeEnabled && (_jsxs(_Fragment, { children: [_jsx(StatsRow, { heading: true, children: "Canvas" }), _jsx(StatsRow, { children: _jsx(CanvasGrid, { property: "gridStep", scene: scene, appState: appState, setAppState: setAppState }) })] }))] }), renderCustomStats?.(elements, appState)] }), !_frameAndChildrenSelectedTogether && selectedElements.length > 0 && (_jsx("div", { id: "elementStats", style: {
|
|
79
89
|
marginTop: 12,
|
|
80
90
|
}, children: _jsx(Collapsible, { label: _jsx("h3", { children: t("stats.elementProperties") }), open: !!(appState.stats.panels & STATS_PANELS.elementProperties), openTrigger: () => setAppState((state) => {
|
|
81
91
|
return {
|
|
@@ -84,11 +94,18 @@ export const StatsInner = memo(({ app, onClose, renderCustomStats, selectedEleme
|
|
|
84
94
|
panels: state.stats.panels ^ STATS_PANELS.elementProperties,
|
|
85
95
|
},
|
|
86
96
|
};
|
|
87
|
-
}), children: _jsxs(StatsRows, { children: [singleElement && (_jsxs(_Fragment, { children: [
|
|
97
|
+
}), children: _jsxs(StatsRows, { children: [singleElement && (_jsxs(_Fragment, { children: [cropMode && (_jsx(StatsRow, { heading: true, children: t("labels.unCroppedDimension") })), appState.croppingElementId &&
|
|
98
|
+
isImageElement(singleElement) &&
|
|
99
|
+
unCroppedDimension && (_jsxs(StatsRow, { columns: 2, children: [_jsx("div", { children: t("stats.width") }), _jsx("div", { children: round(unCroppedDimension.width, 2) })] })), appState.croppingElementId &&
|
|
100
|
+
isImageElement(singleElement) &&
|
|
101
|
+
unCroppedDimension && (_jsxs(StatsRow, { columns: 2, children: [_jsx("div", { children: t("stats.height") }), _jsx("div", { children: round(unCroppedDimension.height, 2) })] })), _jsx(StatsRow, { heading: true, "data-testid": "stats-element-type", children: appState.croppingElementId
|
|
102
|
+
? t("labels.imageCropping")
|
|
103
|
+
: t(`element.${singleElement.type}`) }), _jsx(StatsRow, { children: _jsx(Position, { element: singleElement, property: "x", elementsMap: elementsMap, scene: scene, appState: appState }) }), _jsx(StatsRow, { children: _jsx(Position, { element: singleElement, property: "y", elementsMap: elementsMap, scene: scene, appState: appState }) }), _jsx(StatsRow, { children: _jsx(Dimension, { property: "width", element: singleElement, scene: scene, appState: appState }) }), _jsx(StatsRow, { children: _jsx(Dimension, { property: "height", element: singleElement, scene: scene, appState: appState }) }), !isElbowArrow(singleElement) && (_jsx(StatsRow, { children: _jsx(Angle, { property: "angle", element: singleElement, scene: scene, appState: appState }) })), _jsx(StatsRow, { children: _jsx(FontSize, { property: "fontSize", element: singleElement, scene: scene, appState: appState }) })] })), multipleElements && (_jsxs(_Fragment, { children: [elementsAreInSameGroup(multipleElements) && (_jsx(StatsRow, { heading: true, children: t("element.group") })), _jsxs(StatsRow, { columns: 2, style: { margin: "0.3125rem 0" }, children: [_jsx("div", { children: t("stats.shapes") }), _jsx("div", { children: selectedElements.length })] }), _jsx(StatsRow, { children: _jsx(MultiPosition, { property: "x", elements: multipleElements, elementsMap: elementsMap, atomicUnits: atomicUnits, scene: scene, appState: appState }) }), _jsx(StatsRow, { children: _jsx(MultiPosition, { property: "y", elements: multipleElements, elementsMap: elementsMap, atomicUnits: atomicUnits, scene: scene, appState: appState }) }), _jsx(StatsRow, { children: _jsx(MultiDimension, { property: "width", elements: multipleElements, elementsMap: elementsMap, atomicUnits: atomicUnits, scene: scene, appState: appState }) }), _jsx(StatsRow, { children: _jsx(MultiDimension, { property: "height", elements: multipleElements, elementsMap: elementsMap, atomicUnits: atomicUnits, scene: scene, appState: appState }) }), _jsx(StatsRow, { children: _jsx(MultiAngle, { property: "angle", elements: multipleElements, scene: scene, appState: appState }) }), _jsx(StatsRow, { children: _jsx(MultiFontSize, { property: "fontSize", elements: multipleElements, scene: scene, appState: appState, elementsMap: elementsMap }) })] }))] }) }) }))] }) }));
|
|
88
104
|
}, (prev, next) => {
|
|
89
105
|
return (prev.sceneNonce === next.sceneNonce &&
|
|
90
106
|
prev.selectedElements === next.selectedElements &&
|
|
91
107
|
prev.appState.stats.panels === next.appState.stats.panels &&
|
|
92
108
|
prev.gridModeEnabled === next.gridModeEnabled &&
|
|
93
|
-
prev.appState.gridStep === next.appState.gridStep
|
|
109
|
+
prev.appState.gridStep === next.appState.gridStep &&
|
|
110
|
+
prev.appState.croppingElementId === next.appState.croppingElementId);
|
|
94
111
|
});
|
|
@@ -14,7 +14,6 @@ export declare const newOrigin: (x1: number, y1: number, w1: number, h1: number,
|
|
|
14
14
|
x: number;
|
|
15
15
|
y: number;
|
|
16
16
|
};
|
|
17
|
-
export declare const resizeElement: (nextWidth: number, nextHeight: number, keepAspectRatio: boolean, origElement: ExcalidrawElement, elementsMap: NonDeletedSceneElementsMap, elements: readonly NonDeletedExcalidrawElement[], scene: Scene, shouldInformMutation?: boolean) => void;
|
|
18
17
|
export declare const moveElement: (newTopLeftX: number, newTopLeftY: number, originalElement: ExcalidrawElement, elementsMap: NonDeletedSceneElementsMap, elements: readonly NonDeletedExcalidrawElement[], scene: Scene, originalElementsMap: ElementsMap, shouldInformMutation?: boolean) => void;
|
|
19
18
|
export declare const getAtomicUnits: (targetElements: readonly ExcalidrawElement[], appState: AppState) => AtomicUnit[];
|
|
20
19
|
export declare const updateBindings: (latestElement: ExcalidrawElement, elementsMap: NonDeletedSceneElementsMap, elements: readonly NonDeletedExcalidrawElement[], scene: Scene, options?: {
|
|
@@ -23,4 +22,5 @@ export declare const updateBindings: (latestElement: ExcalidrawElement, elements
|
|
|
23
22
|
width: number;
|
|
24
23
|
height: number;
|
|
25
24
|
};
|
|
25
|
+
zoom?: AppState["zoom"];
|
|
26
26
|
}) => void;
|