@excalidraw/excalidraw 0.17.1-7500-ac247a0 → 0.17.1-a38e82f
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 +52 -2
- package/dist/browser/dev/excalidraw-assets-dev/chunk-5VWQDKDR.js +20279 -0
- package/dist/browser/dev/excalidraw-assets-dev/chunk-5VWQDKDR.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-2W5GQUR4.js → chunk-IM4WTX2M.js} +12 -6
- package/dist/browser/dev/excalidraw-assets-dev/chunk-IM4WTX2M.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js → en-IOBA4CS2.js} +4 -2
- package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css +6 -0
- package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{image-5TVMINCA.js → image-VKDAL6BQ.js} +2 -4
- package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js +161 -0
- package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js.map +7 -0
- package/dist/browser/dev/index.css +189 -129
- package/dist/browser/dev/index.css.map +3 -3
- package/dist/browser/dev/index.js +34708 -37
- package/dist/browser/dev/index.js.map +4 -4
- package/dist/browser/prod/excalidraw-assets/chunk-LIG3S5TN.js +11 -0
- package/dist/browser/prod/excalidraw-assets/chunk-N2C5DK3B.js +55 -0
- package/dist/browser/prod/excalidraw-assets/en-WFZVQ7I6.js +1 -0
- package/dist/browser/prod/excalidraw-assets/image-4AT7LYMR.js +1 -0
- package/dist/browser/prod/excalidraw-assets/image-X66R2EM5.css +1 -0
- package/dist/browser/prod/excalidraw-assets/roundRect-2ACQK4DA.js +1 -0
- package/dist/browser/prod/index.css +1 -1
- package/dist/browser/prod/index.js +203 -1
- package/dist/{prod/en-RLIAOBCI.json → dev/en-TDNWCAOT.json} +9 -5
- package/dist/dev/index.css +189 -129
- package/dist/dev/index.css.map +3 -3
- package/dist/dev/index.js +39078 -40080
- package/dist/dev/index.js.map +4 -4
- package/dist/excalidraw/actions/actionAddToLibrary.d.ts +15 -15
- package/dist/excalidraw/actions/actionAlign.d.ts +6 -6
- package/dist/excalidraw/actions/actionAlign.js +2 -1
- package/dist/excalidraw/actions/actionBoundText.d.ts +10 -10
- package/dist/excalidraw/actions/actionBoundText.js +8 -8
- package/dist/excalidraw/actions/actionCanvas.d.ts +58 -58
- package/dist/excalidraw/actions/actionClipboard.d.ts +34 -34
- package/dist/excalidraw/actions/actionClipboard.js +9 -2
- package/dist/excalidraw/actions/actionDeleteSelected.d.ts +15 -15
- package/dist/excalidraw/actions/actionDeleteSelected.js +3 -2
- package/dist/excalidraw/actions/actionDistribute.d.ts +2 -2
- package/dist/excalidraw/actions/actionDistribute.js +1 -1
- package/dist/excalidraw/actions/actionDuplicateSelection.d.ts +1 -1
- package/dist/excalidraw/actions/actionDuplicateSelection.js +4 -3
- package/dist/excalidraw/actions/actionElementLock.d.ts +10 -10
- package/dist/excalidraw/actions/actionExport.d.ts +43 -43
- package/dist/excalidraw/actions/actionExport.js +4 -4
- package/dist/excalidraw/actions/actionFinalize.d.ts +9 -9
- package/dist/excalidraw/actions/actionFinalize.js +7 -6
- package/dist/excalidraw/actions/actionFlip.d.ts +2 -2
- package/dist/excalidraw/actions/actionFlip.js +11 -11
- package/dist/excalidraw/actions/actionFrame.d.ts +16 -16
- package/dist/excalidraw/actions/actionFrame.js +1 -1
- package/dist/excalidraw/actions/actionGroup.d.ts +10 -10
- package/dist/excalidraw/actions/actionGroup.js +3 -2
- package/dist/excalidraw/actions/actionLinearEditor.d.ts +5 -5
- package/dist/excalidraw/actions/actionLinearEditor.js +1 -1
- package/dist/excalidraw/{element/Hyperlink.d.ts → actions/actionLink.d.ts} +29 -51
- package/dist/excalidraw/actions/actionLink.js +40 -0
- package/dist/excalidraw/actions/actionMenu.d.ts +13 -13
- package/dist/excalidraw/actions/actionNavigate.d.ts +10 -10
- package/dist/excalidraw/actions/actionNavigate.js +1 -1
- package/dist/excalidraw/actions/actionProperties.d.ts +77 -77
- package/dist/excalidraw/actions/actionProperties.js +32 -27
- package/dist/excalidraw/actions/actionSelectAll.d.ts +5 -5
- package/dist/excalidraw/actions/actionSelectAll.js +1 -1
- package/dist/excalidraw/actions/actionStyles.d.ts +7 -7
- package/dist/excalidraw/actions/actionStyles.js +4 -4
- package/dist/excalidraw/actions/actionToggleGridMode.d.ts +5 -5
- package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +5 -5
- package/dist/excalidraw/actions/actionToggleStats.d.ts +5 -5
- package/dist/excalidraw/actions/actionToggleViewMode.d.ts +5 -5
- package/dist/excalidraw/actions/actionToggleZenMode.d.ts +5 -5
- package/dist/excalidraw/actions/index.d.ts +1 -1
- package/dist/excalidraw/actions/index.js +1 -1
- package/dist/excalidraw/actions/manager.js +2 -1
- package/dist/excalidraw/align.d.ts +2 -2
- package/dist/excalidraw/align.js +2 -2
- package/dist/excalidraw/animated-trail.d.ts +33 -0
- package/dist/excalidraw/animated-trail.js +96 -0
- package/dist/excalidraw/animation-frame-handler.d.ts +16 -0
- package/dist/excalidraw/animation-frame-handler.js +55 -0
- package/dist/excalidraw/appState.d.ts +1 -1
- package/dist/excalidraw/appState.js +1 -3
- package/dist/excalidraw/clipboard.js +5 -5
- package/dist/excalidraw/components/Actions.d.ts +3 -3
- package/dist/excalidraw/components/Actions.js +18 -7
- package/dist/excalidraw/components/App.d.ts +23 -16
- package/dist/excalidraw/components/App.js +387 -272
- package/dist/excalidraw/components/Button.d.ts +1 -1
- package/dist/excalidraw/components/FilledButton.d.ts +2 -2
- package/dist/excalidraw/components/FilledButton.js +27 -3
- package/dist/excalidraw/components/FollowMode/FollowMode.js +1 -1
- package/dist/excalidraw/components/ImageExportDialog.d.ts +2 -1
- package/dist/excalidraw/components/ImageExportDialog.js +17 -13
- package/dist/excalidraw/components/JSONExportDialog.js +1 -1
- package/dist/excalidraw/components/{LaserTool/LaserPointerButton.d.ts → LaserPointerButton.d.ts} +1 -1
- package/dist/excalidraw/components/{LaserTool/LaserPointerButton.js → LaserPointerButton.js} +2 -2
- package/dist/excalidraw/components/LayerUI.js +3 -3
- package/dist/excalidraw/components/MobileMenu.js +1 -1
- package/dist/excalidraw/components/ProjectName.d.ts +0 -1
- package/dist/excalidraw/components/ProjectName.js +1 -1
- package/dist/excalidraw/components/PublishLibrary.js +1 -1
- package/dist/excalidraw/components/SVGLayer.d.ts +8 -0
- package/dist/excalidraw/components/SVGLayer.js +20 -0
- package/dist/excalidraw/components/ShareableLinkDialog.js +10 -10
- package/dist/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
- package/dist/excalidraw/components/Stack.d.ts +2 -2
- package/dist/excalidraw/components/TTDDialog/common.js +10 -1
- package/dist/excalidraw/components/TextField.d.ts +5 -2
- package/dist/excalidraw/components/TextField.js +6 -3
- package/dist/excalidraw/components/Toast.d.ts +3 -2
- package/dist/excalidraw/components/Toast.js +2 -2
- package/dist/excalidraw/components/ToolButton.js +2 -1
- package/dist/excalidraw/components/canvases/InteractiveCanvas.d.ts +2 -2
- package/dist/excalidraw/components/canvases/InteractiveCanvas.js +6 -5
- package/dist/excalidraw/components/canvases/StaticCanvas.d.ts +4 -3
- package/dist/excalidraw/components/canvases/StaticCanvas.js +7 -5
- package/dist/excalidraw/components/dropdownMenu/DropdownMenuContent.js +22 -2
- package/dist/excalidraw/components/hyperlink/Hyperlink.d.ts +19 -0
- package/dist/excalidraw/{element → components/hyperlink}/Hyperlink.js +40 -115
- package/dist/excalidraw/components/hyperlink/helpers.d.ts +7 -0
- package/dist/excalidraw/components/hyperlink/helpers.js +49 -0
- package/dist/excalidraw/components/icons.d.ts +2 -1
- package/dist/excalidraw/components/icons.js +2 -1
- package/dist/excalidraw/components/live-collaboration/LiveCollaborationTrigger.js +3 -2
- package/dist/excalidraw/components/main-menu/DefaultItems.js +5 -2
- package/dist/excalidraw/constants.d.ts +6 -0
- package/dist/excalidraw/constants.js +6 -0
- package/dist/excalidraw/data/blob.js +13 -14
- package/dist/excalidraw/data/filesystem.d.ts +1 -1
- package/dist/excalidraw/data/index.d.ts +2 -1
- package/dist/excalidraw/data/index.js +20 -16
- package/dist/excalidraw/data/json.d.ts +1 -1
- package/dist/excalidraw/data/json.js +5 -3
- package/dist/excalidraw/data/resave.d.ts +1 -1
- package/dist/excalidraw/data/resave.js +2 -2
- package/dist/excalidraw/data/restore.js +8 -13
- package/dist/excalidraw/data/transform.js +13 -9
- package/dist/excalidraw/distribute.d.ts +2 -2
- package/dist/excalidraw/distribute.js +2 -2
- package/dist/excalidraw/element/ElementCanvasButtons.d.ts +3 -2
- package/dist/excalidraw/element/ElementCanvasButtons.js +4 -4
- package/dist/excalidraw/element/binding.d.ts +9 -9
- package/dist/excalidraw/element/binding.js +61 -59
- package/dist/excalidraw/element/bounds.d.ts +5 -5
- package/dist/excalidraw/element/bounds.js +29 -32
- package/dist/excalidraw/element/collision.d.ts +11 -11
- package/dist/excalidraw/element/collision.js +49 -46
- package/dist/excalidraw/element/containerCache.d.ts +11 -0
- package/dist/excalidraw/element/containerCache.js +14 -0
- package/dist/excalidraw/element/dragElements.js +10 -19
- package/dist/excalidraw/element/embeddable.d.ts +12 -13
- package/dist/excalidraw/element/embeddable.js +17 -27
- package/dist/excalidraw/element/image.js +1 -2
- package/dist/excalidraw/element/index.d.ts +0 -1
- package/dist/excalidraw/element/index.js +0 -1
- package/dist/excalidraw/element/linearElementEditor.d.ts +36 -36
- package/dist/excalidraw/element/linearElementEditor.js +79 -80
- package/dist/excalidraw/element/newElement.d.ts +4 -6
- package/dist/excalidraw/element/newElement.js +11 -16
- package/dist/excalidraw/element/resizeElements.d.ts +6 -6
- package/dist/excalidraw/element/resizeElements.js +40 -46
- package/dist/excalidraw/element/resizeTest.d.ts +3 -3
- package/dist/excalidraw/element/resizeTest.js +4 -4
- package/dist/excalidraw/element/sizeHelpers.d.ts +2 -2
- package/dist/excalidraw/element/sizeHelpers.js +2 -2
- package/dist/excalidraw/element/textElement.d.ts +18 -20
- package/dist/excalidraw/element/textElement.js +80 -111
- package/dist/excalidraw/element/textWysiwyg.d.ts +1 -6
- package/dist/excalidraw/element/textWysiwyg.js +15 -37
- package/dist/excalidraw/element/transformHandles.d.ts +4 -4
- package/dist/excalidraw/element/transformHandles.js +6 -6
- package/dist/excalidraw/element/typeChecks.js +4 -1
- package/dist/excalidraw/element/types.d.ts +24 -11
- package/dist/excalidraw/frame.d.ts +26 -20
- package/dist/excalidraw/frame.js +157 -84
- package/dist/excalidraw/groups.d.ts +3 -3
- package/dist/excalidraw/groups.js +11 -3
- package/dist/excalidraw/history.d.ts +1 -1
- package/dist/excalidraw/hooks/useLibraryItemSvg.js +1 -1
- package/dist/excalidraw/index.d.ts +8 -9
- package/dist/excalidraw/index.js +15 -11
- package/dist/excalidraw/laser-trails.d.ts +19 -0
- package/dist/excalidraw/laser-trails.js +95 -0
- package/dist/excalidraw/locales/en.json +9 -5
- package/dist/excalidraw/reactUtils.d.ts +14 -0
- package/dist/excalidraw/reactUtils.js +45 -0
- package/dist/excalidraw/renderer/helpers.d.ts +13 -0
- package/dist/excalidraw/renderer/helpers.js +39 -0
- package/dist/excalidraw/renderer/interactiveScene.d.ts +20 -0
- package/dist/excalidraw/renderer/{renderScene.js → interactiveScene.js} +199 -474
- package/dist/excalidraw/renderer/renderElement.d.ts +6 -6
- package/dist/excalidraw/renderer/renderElement.js +54 -366
- package/dist/excalidraw/renderer/staticScene.d.ts +11 -0
- package/dist/excalidraw/renderer/staticScene.js +205 -0
- package/dist/excalidraw/renderer/staticSvgScene.d.ts +5 -0
- package/dist/excalidraw/renderer/staticSvgScene.js +385 -0
- package/dist/excalidraw/scene/Fonts.js +2 -1
- package/dist/excalidraw/scene/Renderer.d.ts +1 -1
- package/dist/excalidraw/scene/Renderer.js +32 -20
- package/dist/excalidraw/scene/Scene.d.ts +10 -9
- package/dist/excalidraw/scene/Scene.js +45 -21
- package/dist/excalidraw/scene/Shape.d.ts +3 -1
- package/dist/excalidraw/scene/Shape.js +7 -5
- package/dist/excalidraw/scene/ShapeCache.d.ts +2 -1
- package/dist/excalidraw/scene/ShapeCache.js +1 -0
- package/dist/excalidraw/scene/comparisons.js +2 -1
- package/dist/excalidraw/scene/export.d.ts +3 -0
- package/dist/excalidraw/scene/export.js +20 -40
- package/dist/excalidraw/scene/index.d.ts +0 -1
- package/dist/excalidraw/scene/index.js +0 -1
- package/dist/excalidraw/scene/scrollbars.d.ts +1 -1
- package/dist/excalidraw/scene/scrollbars.js +1 -1
- package/dist/excalidraw/scene/selection.d.ts +5 -5
- package/dist/excalidraw/scene/selection.js +16 -14
- package/dist/excalidraw/scene/types.d.ts +11 -5
- package/dist/excalidraw/snapping.d.ts +7 -7
- package/dist/excalidraw/snapping.js +21 -20
- package/dist/excalidraw/types.d.ts +10 -11
- package/dist/excalidraw/utility-types.d.ts +5 -0
- package/dist/excalidraw/utils.d.ts +18 -15
- package/dist/excalidraw/utils.js +37 -45
- package/dist/{dev/en-RLIAOBCI.json → prod/en-TDNWCAOT.json} +9 -5
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +42 -42
- package/dist/utils/bbox.d.ts +2 -2
- package/dist/utils/export.d.ts +3 -3
- package/dist/utils/export.js +3 -13
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +2 -2
- package/dist/utils/withinBounds.d.ts +1 -1
- package/dist/utils/withinBounds.js +5 -2
- package/package.json +4 -4
- package/dist/browser/dev/excalidraw-assets-dev/chunk-2W5GQUR4.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/chunk-KGZXLFLR.js +0 -53497
- package/dist/browser/dev/excalidraw-assets-dev/chunk-KGZXLFLR.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/image-3MFRCKYM.css +0 -5797
- package/dist/browser/dev/excalidraw-assets-dev/image-3MFRCKYM.css.map +0 -7
- package/dist/browser/prod/excalidraw-assets/chunk-4YN2HN3S.js +0 -257
- package/dist/browser/prod/excalidraw-assets/chunk-OWLL6VOG.js +0 -11
- package/dist/browser/prod/excalidraw-assets/en-ERQOR3OC.js +0 -1
- package/dist/browser/prod/excalidraw-assets/image-LTLHTTSE.js +0 -1
- package/dist/browser/prod/excalidraw-assets/image-QBL334OA.css +0 -1
- package/dist/excalidraw/components/LaserTool/LaserPathManager.d.ts +0 -28
- package/dist/excalidraw/components/LaserTool/LaserPathManager.js +0 -225
- package/dist/excalidraw/components/LaserTool/LaserTool.d.ts +0 -8
- package/dist/excalidraw/components/LaserTool/LaserTool.js +0 -15
- package/dist/excalidraw/renderer/renderScene.d.ts +0 -25
- package/dist/excalidraw/vite.config.d.mts +0 -2
- package/dist/excalidraw/vite.config.mjs +0 -13
- /package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js.map → en-IOBA4CS2.js.map} +0 -0
- /package/dist/browser/dev/excalidraw-assets-dev/{image-5TVMINCA.js.map → image-VKDAL6BQ.js.map} +0 -0
|
@@ -11,11 +11,11 @@ import { actions } from "../actions/register";
|
|
|
11
11
|
import { trackEvent } from "../analytics";
|
|
12
12
|
import { getDefaultAppState, isEraserActive, isHandToolActive, } from "../appState";
|
|
13
13
|
import { copyTextToSystemClipboard, parseClipboard, } from "../clipboard";
|
|
14
|
-
import { APP_NAME, CURSOR_TYPE, DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT, DEFAULT_VERTICAL_ALIGN, DRAGGING_THRESHOLD,
|
|
14
|
+
import { APP_NAME, CURSOR_TYPE, DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT, DEFAULT_VERTICAL_ALIGN, DRAGGING_THRESHOLD, ELEMENT_SHIFT_TRANSLATE_AMOUNT, ELEMENT_TRANSLATE_AMOUNT, ENV, EVENT, FRAME_STYLE, GRID_SIZE, IMAGE_MIME_TYPES, IMAGE_RENDER_TIMEOUT, isBrave, LINE_CONFIRM_THRESHOLD, MAX_ALLOWED_FILE_BYTES, MIME_TYPES, MQ_MAX_HEIGHT_LANDSCAPE, MQ_MAX_WIDTH_LANDSCAPE, MQ_MAX_WIDTH_PORTRAIT, MQ_RIGHT_SIDEBAR_MIN_WIDTH, POINTER_BUTTON, ROUNDNESS, SCROLL_TIMEOUT, TAP_TWICE_TIMEOUT, TEXT_TO_CENTER_SNAP_THRESHOLD, THEME, THEME_FILTER, TOUCH_CTX_MENU_TIMEOUT, VERTICAL_ALIGN, YOUTUBE_STATES, ZOOM_STEP, POINTER_EVENTS, TOOL_TYPE, EDITOR_LS_KEYS, isIOS, } from "../constants";
|
|
15
15
|
import { exportCanvas, loadFromBlob } from "../data";
|
|
16
16
|
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
|
17
17
|
import { restore, restoreElements } from "../data/restore";
|
|
18
|
-
import { dragNewElement, dragSelectedElements, duplicateElement, getCommonBounds, getCursorForResizingElement, getDragOffsetXY, getElementWithTransformHandleType, getNormalizedDimensions, getResizeArrowDirection, getResizeOffsetXY, getLockedLinearCursorAlignSize, getTransformHandleTypeFromCoords, hitTest, isHittingElementBoundingBoxWithoutHittingElement, isInvisiblySmallElement, isNonDeletedElement, isTextElement, newElement, newLinearElement, newTextElement, newImageElement,
|
|
18
|
+
import { dragNewElement, dragSelectedElements, duplicateElement, getCommonBounds, getCursorForResizingElement, getDragOffsetXY, getElementWithTransformHandleType, getNormalizedDimensions, getResizeArrowDirection, getResizeOffsetXY, getLockedLinearCursorAlignSize, getTransformHandleTypeFromCoords, hitTest, isHittingElementBoundingBoxWithoutHittingElement, isInvisiblySmallElement, isNonDeletedElement, isTextElement, newElement, newLinearElement, newTextElement, newImageElement, transformElements, updateTextElement, redrawTextBoundingBox, } from "../element";
|
|
19
19
|
import { bindOrUnbindLinearElement, bindOrUnbindSelectedElements, fixBindingsAfterDeletion, fixBindingsAfterDuplication, getEligibleElementsForBinding, getHoveredElementForBinding, isBindingEnabled, isLinearElementSimpleAndAlreadyBound, maybeBindLinearElement, shouldEnableBindingForPointerEvent, unbindLinearElements, updateBoundElements, } from "../element/binding";
|
|
20
20
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
|
21
21
|
import { mutateElement, newElementWith } from "../element/mutateElement";
|
|
@@ -28,12 +28,12 @@ import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n";
|
|
|
28
28
|
import { CODES, shouldResizeFromCenter, shouldMaintainAspectRatio, shouldRotateWithDiscreteAngle, isArrowKey, KEYS, } from "../keys";
|
|
29
29
|
import { isElementInViewport } from "../element/sizeHelpers";
|
|
30
30
|
import { distance2d, getCornerRadius, getGridPoint, isPathALoop, } from "../math";
|
|
31
|
-
import { calculateScrollCenter, getElementsAtPosition, getElementsWithinSelection, getNormalizedZoom, getSelectedElements, hasBackground,
|
|
31
|
+
import { calculateScrollCenter, getElementsAtPosition, getElementsWithinSelection, getNormalizedZoom, getSelectedElements, hasBackground, isSomeElementSelected, } from "../scene";
|
|
32
32
|
import Scene from "../scene/Scene";
|
|
33
33
|
import { getStateForZoom } from "../scene/zoom";
|
|
34
34
|
import { findShapeByKey } from "../shapes";
|
|
35
|
-
import { debounce, distance, getFontString, getNearestScrollableContainer, isInputLike, isToolIcon, isWritableElement, sceneCoordsToViewportCoords, tupleToCoors, viewportCoordsToSceneCoords,
|
|
36
|
-
import { createSrcDoc, embeddableURLValidator,
|
|
35
|
+
import { debounce, distance, getFontString, getNearestScrollableContainer, isInputLike, isToolIcon, isWritableElement, sceneCoordsToViewportCoords, tupleToCoors, viewportCoordsToSceneCoords, wrapEvent, updateObject, updateActiveTool, getShortcutKey, isTransparent, easeToValuesRAF, muteFSAbortError, isTestEnv, easeOut, updateStable, addEventListener, normalizeEOL, getDateTime, } from "../utils";
|
|
36
|
+
import { createSrcDoc, embeddableURLValidator, maybeParseEmbedSrc, getEmbedLink, } from "../element/embeddable";
|
|
37
37
|
import { ContextMenu, CONTEXT_MENU_SEPARATOR, } from "./ContextMenu";
|
|
38
38
|
import LayerUI from "./LayerUI";
|
|
39
39
|
import { Toast } from "./Toast";
|
|
@@ -44,12 +44,12 @@ import throttle from "lodash.throttle";
|
|
|
44
44
|
import { fileOpen } from "../data/filesystem";
|
|
45
45
|
import { bindTextToShapeAfterDuplication, getApproxMinLineHeight, getApproxMinLineWidth, getBoundTextElement, getContainerCenter, getContainerElement, getDefaultLineHeight, getLineHeightInPx, getTextBindableContainerAtPosition, isMeasureTextSupported, isValidTextContainer, } from "../element/textElement";
|
|
46
46
|
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
|
47
|
-
import { showHyperlinkTooltip, hideHyperlinkToolip, Hyperlink,
|
|
47
|
+
import { showHyperlinkTooltip, hideHyperlinkToolip, Hyperlink, } from "../components/hyperlink/Hyperlink";
|
|
48
48
|
import { isLocalLink, normalizeLink, toValidURL } from "../data/url";
|
|
49
49
|
import { shouldShowBoundingBox } from "../element/transformHandles";
|
|
50
50
|
import { actionUnlockAllElements } from "../actions/actionElementLock";
|
|
51
51
|
import { Fonts } from "../scene/Fonts";
|
|
52
|
-
import { getFrameChildren, isCursorInFrame, bindElementsToFramesAfterDuplication, addElementsToFrame, replaceAllElementsInFrame, removeElementsFromFrame, getElementsInResizingFrame, getElementsInNewFrame, getContainingFrame, elementOverlapsWithFrame, updateFrameMembershipOfSelectedElements, isElementInFrame, getFrameLikeTitle, } from "../frame";
|
|
52
|
+
import { getFrameChildren, isCursorInFrame, bindElementsToFramesAfterDuplication, addElementsToFrame, replaceAllElementsInFrame, removeElementsFromFrame, getElementsInResizingFrame, getElementsInNewFrame, getContainingFrame, elementOverlapsWithFrame, updateFrameMembershipOfSelectedElements, isElementInFrame, getFrameLikeTitle, getElementsOverlappingFrame, filterElementsEligibleAsFrameChildren, } from "../frame";
|
|
53
53
|
import { excludeElementsInFramesFromSelection, makeNextSelectedElementIds, } from "../scene/selection";
|
|
54
54
|
import { actionPaste } from "../actions/actionClipboard";
|
|
55
55
|
import { actionRemoveAllElementsFromFrame, actionSelectAllElementsInFrame, } from "../actions/actionFrame";
|
|
@@ -66,18 +66,25 @@ import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
|
|
66
66
|
import { StaticCanvas, InteractiveCanvas } from "./canvases";
|
|
67
67
|
import { Renderer } from "../scene/Renderer";
|
|
68
68
|
import { ShapeCache } from "../scene/ShapeCache";
|
|
69
|
-
import {
|
|
70
|
-
import { LaserPathManager } from "./LaserTool/LaserPathManager";
|
|
69
|
+
import { SVGLayer } from "./SVGLayer";
|
|
71
70
|
import { setEraserCursor, setCursor, resetCursor, setCursorForShape, } from "../cursor";
|
|
72
71
|
import { Emitter } from "../emitter";
|
|
73
72
|
import { ElementCanvasButtons } from "../element/ElementCanvasButtons";
|
|
74
73
|
import { diagramToHTML } from "../data/magic";
|
|
75
|
-
import {
|
|
74
|
+
import { exportToBlob } from "../../utils/export";
|
|
76
75
|
import { COLOR_PALETTE } from "../colors";
|
|
77
76
|
import { ElementCanvasButton } from "./MagicButton";
|
|
78
77
|
import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
|
|
79
78
|
import { EditorLocalStorage } from "../data/EditorLocalStorage";
|
|
80
79
|
import FollowMode from "./FollowMode/FollowMode";
|
|
80
|
+
import { AnimationFrameHandler } from "../animation-frame-handler";
|
|
81
|
+
import { AnimatedTrail } from "../animated-trail";
|
|
82
|
+
import { LaserTrails } from "../laser-trails";
|
|
83
|
+
import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
|
|
84
|
+
import { getRenderOpacity } from "../renderer/renderElement";
|
|
85
|
+
import { textWysiwyg } from "../element/textWysiwyg";
|
|
86
|
+
import { isOverScrollBars } from "../scene/scrollbars";
|
|
87
|
+
import { isPointHittingLink, isPointHittingLinkIcon, } from "./hyperlink/helpers";
|
|
81
88
|
const AppContext = React.createContext(null);
|
|
82
89
|
const AppPropsContext = React.createContext(null);
|
|
83
90
|
const deviceContextInitialValue = {
|
|
@@ -163,12 +170,41 @@ class App extends React.Component {
|
|
|
163
170
|
files = {};
|
|
164
171
|
imageCache = new Map();
|
|
165
172
|
iFrameRefs = new Map();
|
|
173
|
+
/**
|
|
174
|
+
* Indicates whether the embeddable's url has been validated for rendering.
|
|
175
|
+
* If value not set, indicates that the validation is pending.
|
|
176
|
+
* Initially or on url change the flag is not reset so that we can guarantee
|
|
177
|
+
* the validation came from a trusted source (the editor).
|
|
178
|
+
**/
|
|
179
|
+
embedsValidationStatus = new Map();
|
|
180
|
+
/** embeds that have been inserted to DOM (as a perf optim, we don't want to
|
|
181
|
+
* insert to DOM before user initially scrolls to them) */
|
|
182
|
+
initializedEmbeds = new Set();
|
|
183
|
+
elementsPendingErasure = new Set();
|
|
166
184
|
hitLinkElement;
|
|
167
185
|
lastPointerDownEvent = null;
|
|
168
186
|
lastPointerUpEvent = null;
|
|
169
187
|
lastPointerMoveEvent = null;
|
|
170
188
|
lastViewportPosition = { x: 0, y: 0 };
|
|
171
|
-
|
|
189
|
+
animationFrameHandler = new AnimationFrameHandler();
|
|
190
|
+
laserTrails = new LaserTrails(this.animationFrameHandler, this);
|
|
191
|
+
eraserTrail = new AnimatedTrail(this.animationFrameHandler, this, {
|
|
192
|
+
streamline: 0.2,
|
|
193
|
+
size: 5,
|
|
194
|
+
keepHead: true,
|
|
195
|
+
sizeMapping: (c) => {
|
|
196
|
+
const DECAY_TIME = 200;
|
|
197
|
+
const DECAY_LENGTH = 10;
|
|
198
|
+
const t = Math.max(0, 1 - (performance.now() - c.pressure) / DECAY_TIME);
|
|
199
|
+
const l = (DECAY_LENGTH -
|
|
200
|
+
Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) /
|
|
201
|
+
DECAY_LENGTH;
|
|
202
|
+
return Math.min(easeOut(l), easeOut(t));
|
|
203
|
+
},
|
|
204
|
+
fill: () => this.state.theme === THEME.LIGHT
|
|
205
|
+
? "rgba(0, 0, 0, 0.2)"
|
|
206
|
+
: "rgba(255, 255, 255, 0.2)",
|
|
207
|
+
});
|
|
172
208
|
onChangeEmitter = new Emitter();
|
|
173
209
|
onPointerDownEmitter = new Emitter();
|
|
174
210
|
onPointerUpEmitter = new Emitter();
|
|
@@ -179,7 +215,7 @@ class App extends React.Component {
|
|
|
179
215
|
constructor(props) {
|
|
180
216
|
super(props);
|
|
181
217
|
const defaultAppState = getDefaultAppState();
|
|
182
|
-
const { excalidrawAPI, viewModeEnabled = false, zenModeEnabled = false, gridModeEnabled = false, objectsSnapModeEnabled = false, theme = defaultAppState.theme, name =
|
|
218
|
+
const { excalidrawAPI, viewModeEnabled = false, zenModeEnabled = false, gridModeEnabled = false, objectsSnapModeEnabled = false, theme = defaultAppState.theme, name = `${t("labels.untitled")}-${getDateTime()}`, } = props;
|
|
183
219
|
this.state = {
|
|
184
220
|
...defaultAppState,
|
|
185
221
|
theme,
|
|
@@ -214,6 +250,7 @@ class App extends React.Component {
|
|
|
214
250
|
getSceneElements: this.getSceneElements,
|
|
215
251
|
getAppState: () => this.state,
|
|
216
252
|
getFiles: () => this.files,
|
|
253
|
+
getName: this.getName,
|
|
217
254
|
registerAction: (action) => {
|
|
218
255
|
this.actionManager.registerAction(action);
|
|
219
256
|
},
|
|
@@ -378,17 +415,20 @@ class App extends React.Component {
|
|
|
378
415
|
sceneY >= el.y + el.height / 3 &&
|
|
379
416
|
sceneY <= el.y + (2 * el.height) / 3);
|
|
380
417
|
}
|
|
418
|
+
updateEmbedValidationStatus = (element, status) => {
|
|
419
|
+
this.embedsValidationStatus.set(element.id, status);
|
|
420
|
+
ShapeCache.delete(element);
|
|
421
|
+
};
|
|
381
422
|
updateEmbeddables = () => {
|
|
382
423
|
const iframeLikes = new Set();
|
|
383
424
|
let updated = false;
|
|
384
425
|
this.scene.getNonDeletedElements().filter((element) => {
|
|
385
426
|
if (isEmbeddableElement(element)) {
|
|
386
427
|
iframeLikes.add(element.id);
|
|
387
|
-
if (element.
|
|
428
|
+
if (!this.embedsValidationStatus.has(element.id)) {
|
|
388
429
|
updated = true;
|
|
389
430
|
const validated = embeddableURLValidator(element.link, this.props.validateEmbeddable);
|
|
390
|
-
|
|
391
|
-
ShapeCache.delete(element);
|
|
431
|
+
this.updateEmbedValidationStatus(element, validated);
|
|
392
432
|
}
|
|
393
433
|
}
|
|
394
434
|
else if (isIframeElement(element)) {
|
|
@@ -412,9 +452,20 @@ class App extends React.Component {
|
|
|
412
452
|
const normalizedHeight = this.state.height;
|
|
413
453
|
const embeddableElements = this.scene
|
|
414
454
|
.getNonDeletedElements()
|
|
415
|
-
.filter((el) => (isEmbeddableElement(el) &&
|
|
455
|
+
.filter((el) => (isEmbeddableElement(el) &&
|
|
456
|
+
this.embedsValidationStatus.get(el.id) === true) ||
|
|
457
|
+
isIframeElement(el));
|
|
416
458
|
return (_jsx(_Fragment, { children: embeddableElements.map((el) => {
|
|
417
459
|
const { x, y } = sceneCoordsToViewportCoords({ sceneX: el.x, sceneY: el.y }, this.state);
|
|
460
|
+
const isVisible = isElementInViewport(el, normalizedWidth, normalizedHeight, this.state, this.scene.getNonDeletedElementsMap());
|
|
461
|
+
const hasBeenInitialized = this.initializedEmbeds.has(el.id);
|
|
462
|
+
if (isVisible && !hasBeenInitialized) {
|
|
463
|
+
this.initializedEmbeds.add(el.id);
|
|
464
|
+
}
|
|
465
|
+
const shouldRender = isVisible || hasBeenInitialized;
|
|
466
|
+
if (!shouldRender) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
418
469
|
let src;
|
|
419
470
|
if (isIframeElement(el)) {
|
|
420
471
|
src = null;
|
|
@@ -554,8 +605,6 @@ class App extends React.Component {
|
|
|
554
605
|
else {
|
|
555
606
|
src = getEmbedLink(toValidURL(el.link || ""));
|
|
556
607
|
}
|
|
557
|
-
// console.log({ src });
|
|
558
|
-
const isVisible = isElementInViewport(el, normalizedWidth, normalizedHeight, this.state);
|
|
559
608
|
const isActive = this.state.activeEmbeddable?.element === el &&
|
|
560
609
|
this.state.activeEmbeddable?.state === "active";
|
|
561
610
|
const isHovered = this.state.activeEmbeddable?.element === el &&
|
|
@@ -567,7 +616,7 @@ class App extends React.Component {
|
|
|
567
616
|
? `translate(${x - this.state.offsetLeft}px, ${y - this.state.offsetTop}px) scale(${scale})`
|
|
568
617
|
: "none",
|
|
569
618
|
display: isVisible ? "block" : "none",
|
|
570
|
-
opacity: el
|
|
619
|
+
opacity: getRenderOpacity(el, getContainingFrame(el, this.scene.getNonDeletedElementsMap()), this.elementsPendingErasure),
|
|
571
620
|
["--embeddable-radius"]: `${getCornerRadius(Math.min(el.width, el.height), el)}px`,
|
|
572
621
|
}, children: _jsxs("div", {
|
|
573
622
|
//this is a hack that addresses isse with embedded excalidraw.com embeddable
|
|
@@ -659,16 +708,14 @@ class App extends React.Component {
|
|
|
659
708
|
scrollX: this.state.scrollX,
|
|
660
709
|
scrollY: this.state.scrollY,
|
|
661
710
|
zoom: this.state.zoom,
|
|
662
|
-
})) {
|
|
711
|
+
}, this.scene.getNonDeletedElementsMap())) {
|
|
663
712
|
// if frame not visible, don't render its name
|
|
664
713
|
return null;
|
|
665
714
|
}
|
|
666
715
|
const { x: x1, y: y1 } = sceneCoordsToViewportCoords({ sceneX: f.x, sceneY: f.y }, this.state);
|
|
667
716
|
const FRAME_NAME_EDIT_PADDING = 6;
|
|
668
717
|
const reset = () => {
|
|
669
|
-
|
|
670
|
-
mutateElement(f, { name: null });
|
|
671
|
-
}
|
|
718
|
+
mutateElement(f, { name: f.name?.trim() || null });
|
|
672
719
|
this.setState({ editingFrame: null });
|
|
673
720
|
};
|
|
674
721
|
let frameNameJSX;
|
|
@@ -679,7 +726,7 @@ class App extends React.Component {
|
|
|
679
726
|
mutateElement(f, {
|
|
680
727
|
name: e.target.value,
|
|
681
728
|
});
|
|
682
|
-
}, onBlur: () => reset(), onKeyDown: (event) => {
|
|
729
|
+
}, onFocus: (e) => e.target.select(), onBlur: () => reset(), onKeyDown: (event) => {
|
|
683
730
|
// for some inexplicable reason, `onBlur` triggered on ESC
|
|
684
731
|
// does not reset `state.editingFrame` despite being called,
|
|
685
732
|
// and we need to reset it here as well
|
|
@@ -740,11 +787,17 @@ class App extends React.Component {
|
|
|
740
787
|
}, children: frameNameJSX }, f.id));
|
|
741
788
|
});
|
|
742
789
|
};
|
|
790
|
+
toggleOverscrollBehavior(event) {
|
|
791
|
+
// when pointer inside editor, disable overscroll behavior to prevent
|
|
792
|
+
// panning to trigger history back/forward on MacOS Chrome
|
|
793
|
+
document.documentElement.style.overscrollBehaviorX =
|
|
794
|
+
event.type === "pointerenter" ? "none" : "auto";
|
|
795
|
+
}
|
|
743
796
|
render() {
|
|
744
797
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
745
798
|
const { renderTopRightUI, renderCustomStats } = this.props;
|
|
746
799
|
const versionNonce = this.scene.getVersionNonce();
|
|
747
|
-
const {
|
|
800
|
+
const { elementsMap, visibleElements } = this.renderer.getRenderableElements({
|
|
748
801
|
versionNonce,
|
|
749
802
|
zoom: this.state.zoom,
|
|
750
803
|
offsetLeft: this.state.offsetLeft,
|
|
@@ -756,6 +809,7 @@ class App extends React.Component {
|
|
|
756
809
|
editingElement: this.state.editingElement,
|
|
757
810
|
pendingImageElementId: this.state.pendingImageElementId,
|
|
758
811
|
});
|
|
812
|
+
const allElementsMap = this.scene.getNonDeletedElementsMap();
|
|
759
813
|
const shouldBlockPointerEvents = !(this.state.editingElement && isLinearElement(this.state.editingElement)) &&
|
|
760
814
|
(this.state.selectionElement ||
|
|
761
815
|
this.state.draggingElement ||
|
|
@@ -773,18 +827,18 @@ class App extends React.Component {
|
|
|
773
827
|
["--ui-pointerEvents"]: shouldBlockPointerEvents
|
|
774
828
|
? POINTER_EVENTS.disabled
|
|
775
829
|
: POINTER_EVENTS.enabled,
|
|
776
|
-
}, ref: this.excalidrawContainerRef, onDrop: this.handleAppOnDrop, tabIndex: 0, onKeyDown: this.props.handleKeyboardGlobally ? undefined : this.onKeyDown, children: _jsx(AppContext.Provider, { value: this, children: _jsx(AppPropsContext.Provider, { value: this.props, children: _jsx(ExcalidrawContainerContext.Provider, { value: this.excalidrawContainerValue, children: _jsx(DeviceContext.Provider, { value: this.device, children: _jsx(ExcalidrawSetAppStateContext.Provider, { value: this.setAppState, children: _jsx(ExcalidrawAppStateContext.Provider, { value: this.state, children: _jsxs(ExcalidrawElementsContext.Provider, { value: this.scene.getNonDeletedElements(), children: [_jsxs(ExcalidrawActionManagerContext.Provider, { value: this.actionManager, children: [_jsx(LayerUI, { canvas: this.canvas, appState: this.state, files: this.files, setAppState: this.setAppState, actionManager: this.actionManager, elements: this.scene.getNonDeletedElements(), onLockToggle: this.toggleLock, onPenModeToggle: this.togglePenMode, onHandToolToggle: this.onHandToolToggle, langCode: getLanguage().code, renderTopRightUI: renderTopRightUI, renderCustomStats: renderCustomStats, showExitZenModeBtn: typeof this.props?.zenModeEnabled === "undefined" &&
|
|
830
|
+
}, ref: this.excalidrawContainerRef, onDrop: this.handleAppOnDrop, tabIndex: 0, onKeyDown: this.props.handleKeyboardGlobally ? undefined : this.onKeyDown, onPointerEnter: this.toggleOverscrollBehavior, onPointerLeave: this.toggleOverscrollBehavior, children: _jsx(AppContext.Provider, { value: this, children: _jsx(AppPropsContext.Provider, { value: this.props, children: _jsx(ExcalidrawContainerContext.Provider, { value: this.excalidrawContainerValue, children: _jsx(DeviceContext.Provider, { value: this.device, children: _jsx(ExcalidrawSetAppStateContext.Provider, { value: this.setAppState, children: _jsx(ExcalidrawAppStateContext.Provider, { value: this.state, children: _jsxs(ExcalidrawElementsContext.Provider, { value: this.scene.getNonDeletedElements(), children: [_jsxs(ExcalidrawActionManagerContext.Provider, { value: this.actionManager, children: [_jsx(LayerUI, { canvas: this.canvas, appState: this.state, files: this.files, setAppState: this.setAppState, actionManager: this.actionManager, elements: this.scene.getNonDeletedElements(), onLockToggle: this.toggleLock, onPenModeToggle: this.togglePenMode, onHandToolToggle: this.onHandToolToggle, langCode: getLanguage().code, renderTopRightUI: renderTopRightUI, renderCustomStats: renderCustomStats, showExitZenModeBtn: typeof this.props?.zenModeEnabled === "undefined" &&
|
|
777
831
|
this.state.zenModeEnabled, UIOptions: this.props.UIOptions, onExportImage: this.onExportImage, renderWelcomeScreen: !this.state.isLoading &&
|
|
778
832
|
this.state.showWelcomeScreen &&
|
|
779
833
|
this.state.activeTool.type === "selection" &&
|
|
780
834
|
!this.state.zenModeEnabled &&
|
|
781
|
-
!this.scene.getElementsIncludingDeleted().length, app: this, isCollaborating: this.props.isCollaborating, openAIKey: this.OPENAI_KEY, isOpenAIKeyPersisted: this.OPENAI_KEY_IS_PERSISTED, onOpenAIAPIKeyChange: this.onOpenAIKeyChange, onMagicSettingsConfirm: this.onMagicSettingsConfirm, children: this.props.children }), _jsx("div", { className: "excalidraw-textEditorContainer" }), _jsx("div", { className: "excalidraw-contextMenuContainer" }), _jsx("div", { className: "excalidraw-eye-dropper-container" }), _jsx(
|
|
782
|
-
this.state.showHyperlinkPopup && (_jsx(Hyperlink, { element: firstSelectedElement, setAppState: this.setAppState, onLinkOpen: this.props.onLinkOpen, setToast: this.setToast }, firstSelectedElement.id)), this.props.aiEnabled !== false &&
|
|
835
|
+
!this.scene.getElementsIncludingDeleted().length, app: this, isCollaborating: this.props.isCollaborating, openAIKey: this.OPENAI_KEY, isOpenAIKeyPersisted: this.OPENAI_KEY_IS_PERSISTED, onOpenAIAPIKeyChange: this.onOpenAIKeyChange, onMagicSettingsConfirm: this.onMagicSettingsConfirm, children: this.props.children }), _jsx("div", { className: "excalidraw-textEditorContainer" }), _jsx("div", { className: "excalidraw-contextMenuContainer" }), _jsx("div", { className: "excalidraw-eye-dropper-container" }), _jsx(SVGLayer, { trails: [this.laserTrails, this.eraserTrail] }), selectedElements.length === 1 &&
|
|
836
|
+
this.state.showHyperlinkPopup && (_jsx(Hyperlink, { element: firstSelectedElement, elementsMap: allElementsMap, setAppState: this.setAppState, onLinkOpen: this.props.onLinkOpen, setToast: this.setToast, updateEmbedValidationStatus: this.updateEmbedValidationStatus }, firstSelectedElement.id)), this.props.aiEnabled !== false &&
|
|
783
837
|
selectedElements.length === 1 &&
|
|
784
|
-
isMagicFrameElement(firstSelectedElement) && (_jsx(ElementCanvasButtons, { element: firstSelectedElement, children: _jsx(ElementCanvasButton, { title: t("labels.convertToCode"), icon: MagicIcon, checked: false, onChange: () => this.onMagicFrameGenerate(firstSelectedElement, "button") }) })), selectedElements.length === 1 &&
|
|
838
|
+
isMagicFrameElement(firstSelectedElement) && (_jsx(ElementCanvasButtons, { element: firstSelectedElement, elementsMap: elementsMap, children: _jsx(ElementCanvasButton, { title: t("labels.convertToCode"), icon: MagicIcon, checked: false, onChange: () => this.onMagicFrameGenerate(firstSelectedElement, "button") }) })), selectedElements.length === 1 &&
|
|
785
839
|
isIframeElement(firstSelectedElement) &&
|
|
786
840
|
firstSelectedElement.customData?.generationData
|
|
787
|
-
?.status === "done" && (_jsxs(ElementCanvasButtons, { element: firstSelectedElement, children: [_jsx(ElementCanvasButton, { title: t("labels.copySource"), icon: copyIcon, checked: false, onChange: () => this.onIframeSrcCopy(firstSelectedElement) }), _jsx(ElementCanvasButton, { title: "Enter fullscreen", icon: fullscreenIcon, checked: false, onChange: () => {
|
|
841
|
+
?.status === "done" && (_jsxs(ElementCanvasButtons, { element: firstSelectedElement, elementsMap: elementsMap, children: [_jsx(ElementCanvasButton, { title: t("labels.copySource"), icon: copyIcon, checked: false, onChange: () => this.onIframeSrcCopy(firstSelectedElement) }), _jsx(ElementCanvasButton, { title: "Enter fullscreen", icon: fullscreenIcon, checked: false, onChange: () => {
|
|
788
842
|
const iframe = this.getHTMLIFrameElement(firstSelectedElement);
|
|
789
843
|
if (iframe) {
|
|
790
844
|
try {
|
|
@@ -813,12 +867,14 @@ class App extends React.Component {
|
|
|
813
867
|
this.focusContainer();
|
|
814
868
|
callback?.();
|
|
815
869
|
});
|
|
816
|
-
} })), _jsx(StaticCanvas, { canvas: this.canvas, rc: this.rc,
|
|
870
|
+
} })), _jsx(StaticCanvas, { canvas: this.canvas, rc: this.rc, elementsMap: elementsMap, allElementsMap: allElementsMap, visibleElements: visibleElements, versionNonce: versionNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, renderConfig: {
|
|
817
871
|
imageCache: this.imageCache,
|
|
818
872
|
isExporting: false,
|
|
819
873
|
renderGrid: true,
|
|
820
874
|
canvasBackgroundColor: this.state.viewBackgroundColor,
|
|
821
|
-
|
|
875
|
+
embedsValidationStatus: this.embedsValidationStatus,
|
|
876
|
+
elementsPendingErasure: this.elementsPendingErasure,
|
|
877
|
+
} }), _jsx(InteractiveCanvas, { containerRef: this.excalidrawContainerRef, canvas: this.interactiveCanvas, elementsMap: elementsMap, visibleElements: visibleElements, selectedElements: selectedElements, versionNonce: versionNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, renderInteractiveSceneCallback: this.renderInteractiveSceneCallback, handleCanvasRef: this.handleInteractiveCanvasRef, onContextMenu: this.handleCanvasContextMenu, onPointerMove: this.handleCanvasPointerMove, onPointerUp: this.handleCanvasPointerUp, onPointerCancel: this.removePointer, onTouchMove: this.handleTouchMove, onPointerDown: this.handleCanvasPointerDown, onDoubleClick: this.handleCanvasDoubleClick }), this.state.userToFollow && (_jsx(FollowMode, { width: this.state.width, height: this.state.height, userToFollow: this.state.userToFollow, onDisconnect: this.maybeUnfollowRemoteUser })), this.renderFrameNames()] }), this.renderEmbeddables()] }) }) }) }) }) }) }) }));
|
|
822
878
|
}
|
|
823
879
|
focusContainer = () => {
|
|
824
880
|
this.excalidrawContainerRef.current?.focus();
|
|
@@ -840,7 +896,7 @@ class App extends React.Component {
|
|
|
840
896
|
trackEvent("export", type, "ui");
|
|
841
897
|
const fileHandle = await exportCanvas(type, elements, this.state, this.files, {
|
|
842
898
|
exportBackground: this.state.exportBackground,
|
|
843
|
-
name: this.
|
|
899
|
+
name: this.getName(),
|
|
844
900
|
viewBackgroundColor: this.state.viewBackgroundColor,
|
|
845
901
|
exportingFrame: opts.exportingFrame,
|
|
846
902
|
})
|
|
@@ -893,11 +949,7 @@ class App extends React.Component {
|
|
|
893
949
|
trackEvent("ai", "generate (missing key)", "d2c");
|
|
894
950
|
return;
|
|
895
951
|
}
|
|
896
|
-
const magicFrameChildren =
|
|
897
|
-
elements: this.scene.getNonDeletedElements(),
|
|
898
|
-
bounds: magicFrame,
|
|
899
|
-
type: "overlap",
|
|
900
|
-
}).filter((el) => !isMagicFrameElement(el));
|
|
952
|
+
const magicFrameChildren = getElementsOverlappingFrame(this.scene.getNonDeletedElements(), magicFrame).filter((el) => !isMagicFrameElement(el));
|
|
901
953
|
if (!magicFrameChildren.length) {
|
|
902
954
|
if (source === "button") {
|
|
903
955
|
this.setState({ errorMessage: "Cannot generate from an empty frame" });
|
|
@@ -1152,7 +1204,7 @@ class App extends React.Component {
|
|
|
1152
1204
|
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
|
|
1153
1205
|
let gridSize = actionResult?.appState?.gridSize || null;
|
|
1154
1206
|
const theme = actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
|
|
1155
|
-
|
|
1207
|
+
const name = actionResult?.appState?.name ?? this.state.name;
|
|
1156
1208
|
const errorMessage = actionResult?.appState?.errorMessage ?? this.state.errorMessage;
|
|
1157
1209
|
if (typeof this.props.viewModeEnabled !== "undefined") {
|
|
1158
1210
|
viewModeEnabled = this.props.viewModeEnabled;
|
|
@@ -1163,9 +1215,6 @@ class App extends React.Component {
|
|
|
1163
1215
|
if (typeof this.props.gridModeEnabled !== "undefined") {
|
|
1164
1216
|
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
|
|
1165
1217
|
}
|
|
1166
|
-
if (typeof this.props.name !== "undefined") {
|
|
1167
|
-
name = this.props.name;
|
|
1168
|
-
}
|
|
1169
1218
|
editingElement =
|
|
1170
1219
|
editingElement || actionResult.appState?.editingElement || null;
|
|
1171
1220
|
if (editingElement?.isDeleted) {
|
|
@@ -1342,7 +1391,6 @@ class App extends React.Component {
|
|
|
1342
1391
|
return false;
|
|
1343
1392
|
};
|
|
1344
1393
|
async componentDidMount() {
|
|
1345
|
-
console.log("HELLO IS");
|
|
1346
1394
|
this.unmounted = false;
|
|
1347
1395
|
this.excalidrawContainerValue.container =
|
|
1348
1396
|
this.excalidrawContainerRef.current;
|
|
@@ -1417,7 +1465,8 @@ class App extends React.Component {
|
|
|
1417
1465
|
this.removeEventListeners();
|
|
1418
1466
|
this.scene.destroy();
|
|
1419
1467
|
this.library.destroy();
|
|
1420
|
-
this.
|
|
1468
|
+
this.laserTrails.stop();
|
|
1469
|
+
this.eraserTrail.stop();
|
|
1421
1470
|
this.onChangeEmitter.clear();
|
|
1422
1471
|
ShapeCache.destroy();
|
|
1423
1472
|
SnapCache.destroy();
|
|
@@ -1425,6 +1474,7 @@ class App extends React.Component {
|
|
|
1425
1474
|
isSomeElementSelected.clearCache();
|
|
1426
1475
|
selectGroupsForSelectedElements.clearCache();
|
|
1427
1476
|
touchTimeout = 0;
|
|
1477
|
+
document.documentElement.style.overscrollBehaviorX = "";
|
|
1428
1478
|
}
|
|
1429
1479
|
onResize = withBatchedUpdates(() => {
|
|
1430
1480
|
this.scene
|
|
@@ -1484,8 +1534,9 @@ class App extends React.Component {
|
|
|
1484
1534
|
}
|
|
1485
1535
|
componentDidUpdate(prevProps, prevState) {
|
|
1486
1536
|
this.updateEmbeddables();
|
|
1487
|
-
|
|
1488
|
-
|
|
1537
|
+
const elements = this.scene.getElementsIncludingDeleted();
|
|
1538
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
1539
|
+
if (!this.state.showWelcomeScreen && !elements.length) {
|
|
1489
1540
|
this.setState({ showWelcomeScreen: true });
|
|
1490
1541
|
}
|
|
1491
1542
|
if (prevProps.UIOptions.dockedSidebarBreakpoint !==
|
|
@@ -1536,6 +1587,9 @@ class App extends React.Component {
|
|
|
1536
1587
|
if (prevProps.langCode !== this.props.langCode) {
|
|
1537
1588
|
this.updateLanguage();
|
|
1538
1589
|
}
|
|
1590
|
+
if (isEraserActive(prevState) && !isEraserActive(this.state)) {
|
|
1591
|
+
this.eraserTrail.endPath();
|
|
1592
|
+
}
|
|
1539
1593
|
if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) {
|
|
1540
1594
|
this.setState({ viewModeEnabled: !!this.props.viewModeEnabled });
|
|
1541
1595
|
}
|
|
@@ -1554,11 +1608,6 @@ class App extends React.Component {
|
|
|
1554
1608
|
gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
|
|
1555
1609
|
});
|
|
1556
1610
|
}
|
|
1557
|
-
if (this.props.name && prevProps.name !== this.props.name) {
|
|
1558
|
-
this.setState({
|
|
1559
|
-
name: this.props.name,
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
1611
|
this.excalidrawContainerRef.current?.classList.toggle("theme--dark", this.state.theme === "dark");
|
|
1563
1612
|
if (this.state.editingLinearElement &&
|
|
1564
1613
|
!this.state.selectedElementIds[this.state.editingLinearElement.elementId]) {
|
|
@@ -1587,19 +1636,19 @@ class App extends React.Component {
|
|
|
1587
1636
|
multiElement != null &&
|
|
1588
1637
|
isBindingEnabled(this.state) &&
|
|
1589
1638
|
isBindingElement(multiElement, false)) {
|
|
1590
|
-
maybeBindLinearElement(multiElement, this.state, this.scene, tupleToCoors(LinearElementEditor.getPointAtIndexGlobalCoordinates(multiElement, -1)));
|
|
1639
|
+
maybeBindLinearElement(multiElement, this.state, this.scene, tupleToCoors(LinearElementEditor.getPointAtIndexGlobalCoordinates(multiElement, -1, elementsMap)), elementsMap);
|
|
1591
1640
|
}
|
|
1592
|
-
this.history.record(this.state,
|
|
1641
|
+
this.history.record(this.state, elements);
|
|
1593
1642
|
// Do not notify consumers if we're still loading the scene. Among other
|
|
1594
1643
|
// potential issues, this fixes a case where the tab isn't focused during
|
|
1595
1644
|
// init, which would trigger onChange with empty elements, which would then
|
|
1596
1645
|
// override whatever is in localStorage currently.
|
|
1597
1646
|
if (!this.state.isLoading) {
|
|
1598
|
-
this.props.onChange?.(
|
|
1599
|
-
this.onChangeEmitter.trigger(
|
|
1647
|
+
this.props.onChange?.(elements, this.state, this.files);
|
|
1648
|
+
this.onChangeEmitter.trigger(elements, this.state, this.files);
|
|
1600
1649
|
}
|
|
1601
1650
|
}
|
|
1602
|
-
renderInteractiveSceneCallback = ({ atLeastOneVisibleElement, scrollBars,
|
|
1651
|
+
renderInteractiveSceneCallback = ({ atLeastOneVisibleElement, scrollBars, elementsMap, }) => {
|
|
1603
1652
|
if (scrollBars) {
|
|
1604
1653
|
currentScrollBars = scrollBars;
|
|
1605
1654
|
}
|
|
@@ -1607,7 +1656,7 @@ class App extends React.Component {
|
|
|
1607
1656
|
// hide when editing text
|
|
1608
1657
|
isTextElement(this.state.editingElement)
|
|
1609
1658
|
? false
|
|
1610
|
-
: !atLeastOneVisibleElement &&
|
|
1659
|
+
: !atLeastOneVisibleElement && elementsMap.size > 0;
|
|
1611
1660
|
if (this.state.scrolledOutside !== scrolledOutside) {
|
|
1612
1661
|
this.setState({ scrolledOutside });
|
|
1613
1662
|
}
|
|
@@ -1776,18 +1825,40 @@ class App extends React.Component {
|
|
|
1776
1825
|
});
|
|
1777
1826
|
}
|
|
1778
1827
|
else if (data.text) {
|
|
1779
|
-
const
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1828
|
+
const nonEmptyLines = normalizeEOL(data.text)
|
|
1829
|
+
.split(/\n+/)
|
|
1830
|
+
.map((s) => s.trim())
|
|
1831
|
+
.filter(Boolean);
|
|
1832
|
+
const embbeddableUrls = nonEmptyLines
|
|
1833
|
+
.map((str) => maybeParseEmbedSrc(str))
|
|
1834
|
+
.filter((string) => {
|
|
1835
|
+
return (embeddableURLValidator(string, this.props.validateEmbeddable) &&
|
|
1836
|
+
(/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(string) ||
|
|
1837
|
+
getEmbedLink(string)?.type === "video"));
|
|
1838
|
+
});
|
|
1839
|
+
if (!IS_PLAIN_PASTE &&
|
|
1840
|
+
embbeddableUrls.length > 0 &&
|
|
1841
|
+
// if there were non-embeddable text (lines) mixed in with embeddable
|
|
1842
|
+
// urls, ignore and paste as text
|
|
1843
|
+
embbeddableUrls.length === nonEmptyLines.length) {
|
|
1844
|
+
const embeddables = [];
|
|
1845
|
+
for (const url of embbeddableUrls) {
|
|
1846
|
+
const prevEmbeddable = embeddables[embeddables.length - 1];
|
|
1847
|
+
const embeddable = this.insertEmbeddableElement({
|
|
1848
|
+
sceneX: prevEmbeddable
|
|
1849
|
+
? prevEmbeddable.x + prevEmbeddable.width + 20
|
|
1850
|
+
: sceneX,
|
|
1851
|
+
sceneY,
|
|
1852
|
+
link: normalizeLink(url),
|
|
1853
|
+
});
|
|
1854
|
+
if (embeddable) {
|
|
1855
|
+
embeddables.push(embeddable);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
if (embeddables.length) {
|
|
1859
|
+
this.setState({
|
|
1860
|
+
selectedElementIds: Object.fromEntries(embeddables.map((embeddable) => [embeddable.id, true])),
|
|
1861
|
+
});
|
|
1791
1862
|
}
|
|
1792
1863
|
return;
|
|
1793
1864
|
}
|
|
@@ -1823,15 +1894,20 @@ class App extends React.Component {
|
|
|
1823
1894
|
}), {
|
|
1824
1895
|
randomizeSeed: !opts.retainSeed,
|
|
1825
1896
|
});
|
|
1826
|
-
const
|
|
1897
|
+
const allElements = [
|
|
1827
1898
|
...this.scene.getElementsIncludingDeleted(),
|
|
1828
1899
|
...newElements,
|
|
1829
1900
|
];
|
|
1830
|
-
this.
|
|
1901
|
+
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({ x, y });
|
|
1902
|
+
if (topLayerFrame) {
|
|
1903
|
+
const eligibleElements = filterElementsEligibleAsFrameChildren(newElements, topLayerFrame);
|
|
1904
|
+
addElementsToFrame(allElements, eligibleElements, topLayerFrame);
|
|
1905
|
+
}
|
|
1906
|
+
this.scene.replaceAllElements(allElements);
|
|
1831
1907
|
newElements.forEach((newElement) => {
|
|
1832
1908
|
if (isTextElement(newElement) && isBoundToContainer(newElement)) {
|
|
1833
|
-
const container = getContainerElement(newElement);
|
|
1834
|
-
redrawTextBoundingBox(newElement, container);
|
|
1909
|
+
const container = getContainerElement(newElement, this.scene.getElementsMapIncludingDeleted());
|
|
1910
|
+
redrawTextBoundingBox(newElement, container, this.scene.getElementsMapIncludingDeleted());
|
|
1835
1911
|
}
|
|
1836
1912
|
});
|
|
1837
1913
|
if (opts.files) {
|
|
@@ -1886,7 +1962,14 @@ class App extends React.Component {
|
|
|
1886
1962
|
return { file: await ImageURLToFile(url) };
|
|
1887
1963
|
}
|
|
1888
1964
|
catch (error) {
|
|
1889
|
-
|
|
1965
|
+
let errorMessage = error.message;
|
|
1966
|
+
if (error.cause === "FETCH_ERROR") {
|
|
1967
|
+
errorMessage = t("errors.failedToFetchImage");
|
|
1968
|
+
}
|
|
1969
|
+
else if (error.cause === "UNSUPPORTED") {
|
|
1970
|
+
errorMessage = t("errors.unsupportedFileType");
|
|
1971
|
+
}
|
|
1972
|
+
return { errorMessage };
|
|
1890
1973
|
}
|
|
1891
1974
|
}));
|
|
1892
1975
|
let y = sceneY;
|
|
@@ -2354,7 +2437,7 @@ class App extends React.Component {
|
|
|
2354
2437
|
x: element.x + offsetX,
|
|
2355
2438
|
y: element.y + offsetY,
|
|
2356
2439
|
});
|
|
2357
|
-
updateBoundElements(element, {
|
|
2440
|
+
updateBoundElements(element, this.scene.getNonDeletedElementsMap(), {
|
|
2358
2441
|
simultaneouslyUpdated: selectedElements,
|
|
2359
2442
|
});
|
|
2360
2443
|
});
|
|
@@ -2372,7 +2455,7 @@ class App extends React.Component {
|
|
|
2372
2455
|
selectedElements[0].id) {
|
|
2373
2456
|
this.history.resumeRecording();
|
|
2374
2457
|
this.setState({
|
|
2375
|
-
editingLinearElement: new LinearElementEditor(selectedElement
|
|
2458
|
+
editingLinearElement: new LinearElementEditor(selectedElement),
|
|
2376
2459
|
});
|
|
2377
2460
|
}
|
|
2378
2461
|
}
|
|
@@ -2383,7 +2466,7 @@ class App extends React.Component {
|
|
|
2383
2466
|
if (!isTextElement(selectedElement)) {
|
|
2384
2467
|
container = selectedElement;
|
|
2385
2468
|
}
|
|
2386
|
-
const midPoint = getContainerCenter(selectedElement, this.state);
|
|
2469
|
+
const midPoint = getContainerCenter(selectedElement, this.state, this.scene.getNonDeletedElementsMap());
|
|
2387
2470
|
const sceneX = midPoint.x;
|
|
2388
2471
|
const sceneY = midPoint.y;
|
|
2389
2472
|
this.startTextEditing({
|
|
@@ -2497,9 +2580,10 @@ class App extends React.Component {
|
|
|
2497
2580
|
}
|
|
2498
2581
|
if (isArrowKey(event.key)) {
|
|
2499
2582
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
2583
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
2500
2584
|
isBindingEnabled(this.state)
|
|
2501
|
-
? bindOrUnbindSelectedElements(selectedElements)
|
|
2502
|
-
: unbindLinearElements(selectedElements);
|
|
2585
|
+
? bindOrUnbindSelectedElements(selectedElements, this.scene.getNonDeletedElements(), elementsMap)
|
|
2586
|
+
: unbindLinearElements(selectedElements, elementsMap);
|
|
2503
2587
|
this.setState({ suggestedBindings: [] });
|
|
2504
2588
|
}
|
|
2505
2589
|
});
|
|
@@ -2576,6 +2660,11 @@ class App extends React.Component {
|
|
|
2576
2660
|
// touchscreen
|
|
2577
2661
|
return gesture.pointers.size >= 2;
|
|
2578
2662
|
};
|
|
2663
|
+
getName = () => {
|
|
2664
|
+
return (this.state.name ||
|
|
2665
|
+
this.props.name ||
|
|
2666
|
+
`${t("labels.untitled")}-${getDateTime()}`);
|
|
2667
|
+
};
|
|
2579
2668
|
// fires only on Safari
|
|
2580
2669
|
onGestureStart = withBatchedUpdates((event) => {
|
|
2581
2670
|
event.preventDefault();
|
|
@@ -2628,11 +2717,13 @@ class App extends React.Component {
|
|
|
2628
2717
|
gesture.initialScale = null;
|
|
2629
2718
|
});
|
|
2630
2719
|
handleTextWysiwyg(element, { isExistingElement = false, }) {
|
|
2720
|
+
const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
|
2631
2721
|
const updateElement = (text, originalText, isDeleted) => {
|
|
2632
2722
|
this.scene.replaceAllElements([
|
|
2723
|
+
// Not sure why we include deleted elements as well hence using deleted elements map
|
|
2633
2724
|
...this.scene.getElementsIncludingDeleted().map((_element) => {
|
|
2634
2725
|
if (_element.id === element.id && isTextElement(_element)) {
|
|
2635
|
-
return updateTextElement(_element, {
|
|
2726
|
+
return updateTextElement(_element, getContainerElement(_element, elementsMap), elementsMap, {
|
|
2636
2727
|
text,
|
|
2637
2728
|
isDeleted,
|
|
2638
2729
|
originalText,
|
|
@@ -2658,7 +2749,7 @@ class App extends React.Component {
|
|
|
2658
2749
|
onChange: withBatchedUpdates((text) => {
|
|
2659
2750
|
updateElement(text, text, false);
|
|
2660
2751
|
if (isNonDeletedElement(element)) {
|
|
2661
|
-
updateBoundElements(element);
|
|
2752
|
+
updateBoundElements(element, elementsMap);
|
|
2662
2753
|
}
|
|
2663
2754
|
}),
|
|
2664
2755
|
onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
|
|
@@ -2734,7 +2825,7 @@ class App extends React.Component {
|
|
|
2734
2825
|
const elementWithHighestZIndex = allHitElements[allHitElements.length - 1];
|
|
2735
2826
|
// If we're hitting element with highest z-index only on its bounding box
|
|
2736
2827
|
// while also hitting other element figure, the latter should be considered.
|
|
2737
|
-
return isHittingElementBoundingBoxWithoutHittingElement(elementWithHighestZIndex, this.state, this.frameNameBoundsCache, x, y)
|
|
2828
|
+
return isHittingElementBoundingBoxWithoutHittingElement(elementWithHighestZIndex, this.state, this.frameNameBoundsCache, x, y, this.scene.getNonDeletedElementsMap())
|
|
2738
2829
|
? allHitElements[allHitElements.length - 2]
|
|
2739
2830
|
: elementWithHighestZIndex;
|
|
2740
2831
|
}
|
|
@@ -2751,13 +2842,14 @@ class App extends React.Component {
|
|
|
2751
2842
|
.filter((element) => (includeLockedElements || !element.locked) &&
|
|
2752
2843
|
(includeBoundTextElement ||
|
|
2753
2844
|
!(isTextElement(element) && element.containerId)));
|
|
2754
|
-
|
|
2845
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
2846
|
+
return getElementsAtPosition(elements, (element) => hitTest(element, this.state, this.frameNameBoundsCache, x, y, elementsMap)).filter((element) => {
|
|
2755
2847
|
// hitting a frame's element from outside the frame is not considered a hit
|
|
2756
|
-
const containingFrame = getContainingFrame(element);
|
|
2848
|
+
const containingFrame = getContainingFrame(element, elementsMap);
|
|
2757
2849
|
return containingFrame &&
|
|
2758
2850
|
this.state.frameRendering.enabled &&
|
|
2759
2851
|
this.state.frameRendering.clip
|
|
2760
|
-
? isCursorInFrame({ x, y }, containingFrame)
|
|
2852
|
+
? isCursorInFrame({ x, y }, containingFrame, elementsMap)
|
|
2761
2853
|
: true;
|
|
2762
2854
|
});
|
|
2763
2855
|
}
|
|
@@ -2766,7 +2858,7 @@ class App extends React.Component {
|
|
|
2766
2858
|
let parentCenterPosition = insertAtParentCenter &&
|
|
2767
2859
|
this.getTextWysiwygSnappedToCenterPosition(sceneX, sceneY, this.state, container);
|
|
2768
2860
|
if (container && parentCenterPosition) {
|
|
2769
|
-
const boundTextElementToContainer = getBoundTextElement(container);
|
|
2861
|
+
const boundTextElementToContainer = getBoundTextElement(container, this.scene.getNonDeletedElementsMap());
|
|
2770
2862
|
if (!boundTextElementToContainer) {
|
|
2771
2863
|
shouldBindToContainer = true;
|
|
2772
2864
|
}
|
|
@@ -2778,7 +2870,7 @@ class App extends React.Component {
|
|
|
2778
2870
|
existingTextElement = selectedElements[0];
|
|
2779
2871
|
}
|
|
2780
2872
|
else if (container) {
|
|
2781
|
-
existingTextElement = getBoundTextElement(selectedElements[0]);
|
|
2873
|
+
existingTextElement = getBoundTextElement(selectedElements[0], this.scene.getNonDeletedElementsMap());
|
|
2782
2874
|
}
|
|
2783
2875
|
else {
|
|
2784
2876
|
existingTextElement = this.getTextElementAtPosition(sceneX, sceneY);
|
|
@@ -2886,7 +2978,7 @@ class App extends React.Component {
|
|
|
2886
2978
|
this.state.editingLinearElement.elementId !== selectedElements[0].id)) {
|
|
2887
2979
|
this.history.resumeRecording();
|
|
2888
2980
|
this.setState({
|
|
2889
|
-
editingLinearElement: new LinearElementEditor(selectedElements[0]
|
|
2981
|
+
editingLinearElement: new LinearElementEditor(selectedElements[0]),
|
|
2890
2982
|
});
|
|
2891
2983
|
return;
|
|
2892
2984
|
}
|
|
@@ -2922,12 +3014,12 @@ class App extends React.Component {
|
|
|
2922
3014
|
});
|
|
2923
3015
|
return;
|
|
2924
3016
|
}
|
|
2925
|
-
const container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY);
|
|
3017
|
+
const container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
|
|
2926
3018
|
if (container) {
|
|
2927
3019
|
if (hasBoundTextElement(container) ||
|
|
2928
3020
|
!isTransparent(container.backgroundColor) ||
|
|
2929
|
-
isHittingElementNotConsideringBoundingBox(container, this.state, this.frameNameBoundsCache, [sceneX, sceneY])) {
|
|
2930
|
-
const midPoint = getContainerCenter(container, this.state);
|
|
3021
|
+
isHittingElementNotConsideringBoundingBox(container, this.state, this.frameNameBoundsCache, [sceneX, sceneY], this.scene.getNonDeletedElementsMap())) {
|
|
3022
|
+
const midPoint = getContainerCenter(container, this.state, this.scene.getNonDeletedElementsMap());
|
|
2931
3023
|
sceneX = midPoint.x;
|
|
2932
3024
|
sceneY = midPoint.y;
|
|
2933
3025
|
}
|
|
@@ -2951,7 +3043,7 @@ class App extends React.Component {
|
|
|
2951
3043
|
}
|
|
2952
3044
|
return (element.link &&
|
|
2953
3045
|
index <= hitElementIndex &&
|
|
2954
|
-
isPointHittingLink(element, this.state, [scenePointer.x, scenePointer.y], this.device.editor.isMobile));
|
|
3046
|
+
isPointHittingLink(element, this.scene.getNonDeletedElementsMap(), this.state, [scenePointer.x, scenePointer.y], this.device.editor.isMobile));
|
|
2955
3047
|
});
|
|
2956
3048
|
};
|
|
2957
3049
|
redirectToLink = (event, isTouchScreen) => {
|
|
@@ -2963,9 +3055,10 @@ class App extends React.Component {
|
|
|
2963
3055
|
return;
|
|
2964
3056
|
}
|
|
2965
3057
|
const lastPointerDownCoords = viewportCoordsToSceneCoords(this.lastPointerDownEvent, this.state);
|
|
2966
|
-
const
|
|
3058
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
3059
|
+
const lastPointerDownHittingLinkIcon = isPointHittingLink(this.hitLinkElement, elementsMap, this.state, [lastPointerDownCoords.x, lastPointerDownCoords.y], this.device.editor.isMobile);
|
|
2967
3060
|
const lastPointerUpCoords = viewportCoordsToSceneCoords(this.lastPointerUpEvent, this.state);
|
|
2968
|
-
const lastPointerUpHittingLinkIcon = isPointHittingLink(this.hitLinkElement, this.state, [lastPointerUpCoords.x, lastPointerUpCoords.y], this.device.editor.isMobile);
|
|
3061
|
+
const lastPointerUpHittingLinkIcon = isPointHittingLink(this.hitLinkElement, elementsMap, this.state, [lastPointerUpCoords.x, lastPointerUpCoords.y], this.device.editor.isMobile);
|
|
2969
3062
|
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
|
2970
3063
|
let url = this.hitLinkElement.link;
|
|
2971
3064
|
if (url) {
|
|
@@ -2991,9 +3084,10 @@ class App extends React.Component {
|
|
|
2991
3084
|
}
|
|
2992
3085
|
};
|
|
2993
3086
|
getTopLayerFrameAtSceneCoords = (sceneCoords) => {
|
|
3087
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
2994
3088
|
const frames = this.scene
|
|
2995
3089
|
.getNonDeletedFramesLikes()
|
|
2996
|
-
.filter((frame) => isCursorInFrame(sceneCoords, frame));
|
|
3090
|
+
.filter((frame) => isCursorInFrame(sceneCoords, frame, elementsMap));
|
|
2997
3091
|
return frames.length ? frames[frames.length - 1] : null;
|
|
2998
3092
|
};
|
|
2999
3093
|
handleCanvasPointerMove = (event) => {
|
|
@@ -3065,7 +3159,7 @@ class App extends React.Component {
|
|
|
3065
3159
|
const { originOffset, snapLines } = getSnapLinesAtPointer(this.scene.getNonDeletedElements(), this.state, {
|
|
3066
3160
|
x: scenePointerX,
|
|
3067
3161
|
y: scenePointerY,
|
|
3068
|
-
}, event);
|
|
3162
|
+
}, event, this.scene.getNonDeletedElementsMap());
|
|
3069
3163
|
this.setState((prevState) => {
|
|
3070
3164
|
const nextSnapLines = updateStable(prevState.snapLines, snapLines);
|
|
3071
3165
|
const nextOriginOffset = prevState.originSnapOffset
|
|
@@ -3093,7 +3187,7 @@ class App extends React.Component {
|
|
|
3093
3187
|
}
|
|
3094
3188
|
if (this.state.editingLinearElement &&
|
|
3095
3189
|
!this.state.editingLinearElement.isDragging) {
|
|
3096
|
-
const editingLinearElement = LinearElementEditor.handlePointerMove(event, scenePointerX, scenePointerY, this.state);
|
|
3190
|
+
const editingLinearElement = LinearElementEditor.handlePointerMove(event, scenePointerX, scenePointerY, this.state, this.scene.getNonDeletedElementsMap());
|
|
3097
3191
|
if (editingLinearElement &&
|
|
3098
3192
|
editingLinearElement !== this.state.editingLinearElement) {
|
|
3099
3193
|
// Since we are reading from previous state which is not possible with
|
|
@@ -3195,7 +3289,7 @@ class App extends React.Component {
|
|
|
3195
3289
|
if (selectedElements.length === 1 &&
|
|
3196
3290
|
!isOverScrollBar &&
|
|
3197
3291
|
!this.state.editingLinearElement) {
|
|
3198
|
-
const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, scenePointerX, scenePointerY, this.state.zoom, event.pointerType);
|
|
3292
|
+
const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, scenePointerX, scenePointerY, this.state.zoom, event.pointerType, this.scene.getNonDeletedElementsMap());
|
|
3199
3293
|
if (elementWithTransformHandleType &&
|
|
3200
3294
|
elementWithTransformHandleType.transformHandleType) {
|
|
3201
3295
|
setCursor(this.interactiveCanvas, getCursorForResizingElement(elementWithTransformHandleType));
|
|
@@ -3219,7 +3313,7 @@ class App extends React.Component {
|
|
|
3219
3313
|
if (this.hitLinkElement &&
|
|
3220
3314
|
!this.state.selectedElementIds[this.hitLinkElement.id]) {
|
|
3221
3315
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
|
3222
|
-
showHyperlinkTooltip(this.hitLinkElement, this.state);
|
|
3316
|
+
showHyperlinkTooltip(this.hitLinkElement, this.state, this.scene.getNonDeletedElementsMap());
|
|
3223
3317
|
}
|
|
3224
3318
|
else {
|
|
3225
3319
|
hideHyperlinkToolip();
|
|
@@ -3270,34 +3364,49 @@ class App extends React.Component {
|
|
|
3270
3364
|
}
|
|
3271
3365
|
};
|
|
3272
3366
|
handleEraser = (event, pointerDownState, scenePointer) => {
|
|
3273
|
-
|
|
3274
|
-
|
|
3367
|
+
this.eraserTrail.addPointToPath(scenePointer.x, scenePointer.y);
|
|
3368
|
+
let didChange = false;
|
|
3369
|
+
const processedGroups = new Set();
|
|
3370
|
+
const nonDeletedElements = this.scene.getNonDeletedElements();
|
|
3371
|
+
const processElements = (elements) => {
|
|
3372
|
+
for (const element of elements) {
|
|
3275
3373
|
if (element.locked) {
|
|
3276
3374
|
return;
|
|
3277
3375
|
}
|
|
3278
|
-
idsToUpdate.push(element.id);
|
|
3279
3376
|
if (event.altKey) {
|
|
3280
|
-
if (
|
|
3281
|
-
|
|
3282
|
-
pointerDownState.elementIdsToErase[element.id].erase = false;
|
|
3377
|
+
if (this.elementsPendingErasure.delete(element.id)) {
|
|
3378
|
+
didChange = true;
|
|
3283
3379
|
}
|
|
3284
3380
|
}
|
|
3285
|
-
else if (!
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
opacity: element.opacity,
|
|
3289
|
-
};
|
|
3381
|
+
else if (!this.elementsPendingErasure.has(element.id)) {
|
|
3382
|
+
didChange = true;
|
|
3383
|
+
this.elementsPendingErasure.add(element.id);
|
|
3290
3384
|
}
|
|
3291
|
-
|
|
3385
|
+
// (un)erase groups atomically
|
|
3386
|
+
if (didChange && element.groupIds?.length) {
|
|
3387
|
+
const shallowestGroupId = element.groupIds.at(-1);
|
|
3388
|
+
if (!processedGroups.has(shallowestGroupId)) {
|
|
3389
|
+
processedGroups.add(shallowestGroupId);
|
|
3390
|
+
const elems = getElementsInGroup(nonDeletedElements, shallowestGroupId);
|
|
3391
|
+
for (const elem of elems) {
|
|
3392
|
+
if (event.altKey) {
|
|
3393
|
+
this.elementsPendingErasure.delete(elem.id);
|
|
3394
|
+
}
|
|
3395
|
+
else {
|
|
3396
|
+
this.elementsPendingErasure.add(elem.id);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3292
3402
|
};
|
|
3293
|
-
const idsToUpdate = [];
|
|
3294
3403
|
const distance = distance2d(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y, scenePointer.x, scenePointer.y);
|
|
3295
3404
|
const threshold = 10 / this.state.zoom.value;
|
|
3296
3405
|
const point = { ...pointerDownState.lastCoords };
|
|
3297
3406
|
let samplingInterval = 0;
|
|
3298
3407
|
while (samplingInterval <= distance) {
|
|
3299
3408
|
const hitElements = this.getElementsAtPosition(point.x, point.y);
|
|
3300
|
-
|
|
3409
|
+
processElements(hitElements);
|
|
3301
3410
|
// Exit since we reached current point
|
|
3302
3411
|
if (samplingInterval === distance) {
|
|
3303
3412
|
break;
|
|
@@ -3310,48 +3419,45 @@ class App extends React.Component {
|
|
|
3310
3419
|
point.x = nextX;
|
|
3311
3420
|
point.y = nextY;
|
|
3312
3421
|
}
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3422
|
+
pointerDownState.lastCoords.x = scenePointer.x;
|
|
3423
|
+
pointerDownState.lastCoords.y = scenePointer.y;
|
|
3424
|
+
if (didChange) {
|
|
3425
|
+
for (const element of this.scene.getNonDeletedElements()) {
|
|
3426
|
+
if (isBoundToContainer(element) &&
|
|
3427
|
+
(this.elementsPendingErasure.has(element.id) ||
|
|
3428
|
+
this.elementsPendingErasure.has(element.containerId))) {
|
|
3429
|
+
if (event.altKey) {
|
|
3430
|
+
this.elementsPendingErasure.delete(element.id);
|
|
3431
|
+
this.elementsPendingErasure.delete(element.containerId);
|
|
3432
|
+
}
|
|
3433
|
+
else {
|
|
3434
|
+
this.elementsPendingErasure.add(element.id);
|
|
3435
|
+
this.elementsPendingErasure.add(element.containerId);
|
|
3324
3436
|
}
|
|
3325
|
-
}
|
|
3326
|
-
else {
|
|
3327
|
-
return newElementWith(ele, {
|
|
3328
|
-
opacity: ELEMENT_READY_TO_ERASE_OPACITY,
|
|
3329
|
-
});
|
|
3330
3437
|
}
|
|
3331
3438
|
}
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
pointerDownState.lastCoords.x = scenePointer.x;
|
|
3336
|
-
pointerDownState.lastCoords.y = scenePointer.y;
|
|
3439
|
+
this.elementsPendingErasure = new Set(this.elementsPendingErasure);
|
|
3440
|
+
this.onSceneUpdated();
|
|
3441
|
+
}
|
|
3337
3442
|
};
|
|
3338
3443
|
// set touch moving for mobile context menu
|
|
3339
3444
|
handleTouchMove = (event) => {
|
|
3340
3445
|
invalidateContextMenu = true;
|
|
3341
3446
|
};
|
|
3342
3447
|
handleHoverSelectedLinearElement(linearElementEditor, scenePointerX, scenePointerY) {
|
|
3343
|
-
const
|
|
3344
|
-
const
|
|
3448
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
3449
|
+
const element = LinearElementEditor.getElement(linearElementEditor.elementId, elementsMap);
|
|
3450
|
+
const boundTextElement = getBoundTextElement(element, elementsMap);
|
|
3345
3451
|
if (!element) {
|
|
3346
3452
|
return;
|
|
3347
3453
|
}
|
|
3348
3454
|
if (this.state.selectedLinearElement) {
|
|
3349
3455
|
let hoverPointIndex = -1;
|
|
3350
3456
|
let segmentMidPointHoveredCoords = null;
|
|
3351
|
-
if (isHittingElementNotConsideringBoundingBox(element, this.state, this.frameNameBoundsCache, [scenePointerX, scenePointerY])) {
|
|
3352
|
-
hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(element, this.state.zoom, scenePointerX, scenePointerY);
|
|
3457
|
+
if (isHittingElementNotConsideringBoundingBox(element, this.state, this.frameNameBoundsCache, [scenePointerX, scenePointerY], elementsMap)) {
|
|
3458
|
+
hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(element, elementsMap, this.state.zoom, scenePointerX, scenePointerY);
|
|
3353
3459
|
segmentMidPointHoveredCoords =
|
|
3354
|
-
LinearElementEditor.getSegmentMidpointHitCoords(linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state);
|
|
3460
|
+
LinearElementEditor.getSegmentMidpointHitCoords(linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state, this.scene.getNonDeletedElementsMap());
|
|
3355
3461
|
if (hoverPointIndex >= 0 || segmentMidPointHoveredCoords) {
|
|
3356
3462
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
|
3357
3463
|
}
|
|
@@ -3360,11 +3466,11 @@ class App extends React.Component {
|
|
|
3360
3466
|
}
|
|
3361
3467
|
}
|
|
3362
3468
|
else if (shouldShowBoundingBox([element], this.state) &&
|
|
3363
|
-
isHittingElementBoundingBoxWithoutHittingElement(element, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY)) {
|
|
3469
|
+
isHittingElementBoundingBoxWithoutHittingElement(element, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, elementsMap)) {
|
|
3364
3470
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
|
|
3365
3471
|
}
|
|
3366
3472
|
else if (boundTextElement &&
|
|
3367
|
-
hitTest(boundTextElement, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY)) {
|
|
3473
|
+
hitTest(boundTextElement, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, this.scene.getNonDeletedElementsMap())) {
|
|
3368
3474
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
|
|
3369
3475
|
}
|
|
3370
3476
|
if (this.state.selectedLinearElement.hoverPointIndex !== hoverPointIndex) {
|
|
@@ -3582,7 +3688,7 @@ class App extends React.Component {
|
|
|
3582
3688
|
this.createFrameElementOnPointerDown(pointerDownState, this.state.activeTool.type);
|
|
3583
3689
|
}
|
|
3584
3690
|
else if (this.state.activeTool.type === "laser") {
|
|
3585
|
-
this.
|
|
3691
|
+
this.laserTrails.startPath(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y);
|
|
3586
3692
|
}
|
|
3587
3693
|
else if (this.state.activeTool.type !== "eraser" &&
|
|
3588
3694
|
this.state.activeTool.type !== "hand") {
|
|
@@ -3590,6 +3696,9 @@ class App extends React.Component {
|
|
|
3590
3696
|
}
|
|
3591
3697
|
this.props?.onPointerDown?.(this.state.activeTool, pointerDownState);
|
|
3592
3698
|
this.onPointerDownEmitter.trigger(this.state.activeTool, pointerDownState, event);
|
|
3699
|
+
if (this.state.activeTool.type === "eraser") {
|
|
3700
|
+
this.eraserTrail.startPath(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y);
|
|
3701
|
+
}
|
|
3593
3702
|
const onPointerMove = this.onPointerMoveFromPointerDownHandler(pointerDownState);
|
|
3594
3703
|
const onPointerUp = this.onPointerUpFromPointerDownHandler(pointerDownState);
|
|
3595
3704
|
const onKeyDown = this.onKeyDownFromPointerDownHandler(pointerDownState);
|
|
@@ -3627,10 +3736,7 @@ class App extends React.Component {
|
|
|
3627
3736
|
!this.state.selectedElementIds[this.hitLinkElement.id]) {
|
|
3628
3737
|
if (clicklength < 300 &&
|
|
3629
3738
|
isIframeLikeElement(this.hitLinkElement) &&
|
|
3630
|
-
!isPointHittingLinkIcon(this.hitLinkElement, this.state, [
|
|
3631
|
-
scenePointer.x,
|
|
3632
|
-
scenePointer.y,
|
|
3633
|
-
])) {
|
|
3739
|
+
!isPointHittingLinkIcon(this.hitLinkElement, this.scene.getNonDeletedElementsMap(), this.state, [scenePointer.x, scenePointer.y])) {
|
|
3634
3740
|
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
|
3635
3741
|
}
|
|
3636
3742
|
else {
|
|
@@ -3693,7 +3799,9 @@ class App extends React.Component {
|
|
|
3693
3799
|
isPanning = true;
|
|
3694
3800
|
event.preventDefault();
|
|
3695
3801
|
let nextPastePrevented = false;
|
|
3696
|
-
const isLinux =
|
|
3802
|
+
const isLinux = typeof window === undefined
|
|
3803
|
+
? false
|
|
3804
|
+
: /Linux/.test(window.navigator.platform);
|
|
3697
3805
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.GRABBING);
|
|
3698
3806
|
let { clientX: lastX, clientY: lastY } = event;
|
|
3699
3807
|
const onPointerMove = withBatchedUpdatesThrottled((event) => {
|
|
@@ -3816,7 +3924,6 @@ class App extends React.Component {
|
|
|
3816
3924
|
boxSelection: {
|
|
3817
3925
|
hasOccurred: false,
|
|
3818
3926
|
},
|
|
3819
|
-
elementIdsToErase: {},
|
|
3820
3927
|
};
|
|
3821
3928
|
}
|
|
3822
3929
|
// Returns whether the event is a dragging a scrollbar
|
|
@@ -3867,9 +3974,10 @@ class App extends React.Component {
|
|
|
3867
3974
|
handleSelectionOnPointerDown = (event, pointerDownState) => {
|
|
3868
3975
|
if (this.state.activeTool.type === "selection") {
|
|
3869
3976
|
const elements = this.scene.getNonDeletedElements();
|
|
3977
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
3870
3978
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
3871
3979
|
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
|
3872
|
-
const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType);
|
|
3980
|
+
const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType, this.scene.getNonDeletedElementsMap());
|
|
3873
3981
|
if (elementWithTransformHandleType != null) {
|
|
3874
3982
|
this.setState({
|
|
3875
3983
|
resizingElement: elementWithTransformHandleType.element,
|
|
@@ -3883,7 +3991,7 @@ class App extends React.Component {
|
|
|
3883
3991
|
}
|
|
3884
3992
|
if (pointerDownState.resize.handleType) {
|
|
3885
3993
|
pointerDownState.resize.isResizing = true;
|
|
3886
|
-
pointerDownState.resize.offset = tupleToCoors(getResizeOffsetXY(pointerDownState.resize.handleType, selectedElements, pointerDownState.origin.x, pointerDownState.origin.y));
|
|
3994
|
+
pointerDownState.resize.offset = tupleToCoors(getResizeOffsetXY(pointerDownState.resize.handleType, selectedElements, elementsMap, pointerDownState.origin.x, pointerDownState.origin.y));
|
|
3887
3995
|
if (selectedElements.length === 1 &&
|
|
3888
3996
|
isLinearElement(selectedElements[0]) &&
|
|
3889
3997
|
selectedElements[0].points.length === 2) {
|
|
@@ -3893,7 +4001,7 @@ class App extends React.Component {
|
|
|
3893
4001
|
else {
|
|
3894
4002
|
if (this.state.selectedLinearElement) {
|
|
3895
4003
|
const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
|
|
3896
|
-
const ret = LinearElementEditor.handlePointerDown(event, this.state, this.history, pointerDownState.origin, linearElementEditor);
|
|
4004
|
+
const ret = LinearElementEditor.handlePointerDown(event, this.state, this.history, pointerDownState.origin, linearElementEditor, this.scene.getNonDeletedElements(), elementsMap);
|
|
3897
4005
|
if (ret.hitElement) {
|
|
3898
4006
|
pointerDownState.hit.element = ret.hitElement;
|
|
3899
4007
|
}
|
|
@@ -4074,7 +4182,7 @@ class App extends React.Component {
|
|
|
4074
4182
|
includeBoundTextElement: true,
|
|
4075
4183
|
});
|
|
4076
4184
|
// FIXME
|
|
4077
|
-
let container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY);
|
|
4185
|
+
let container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
|
|
4078
4186
|
if (hasBoundTextElement(element)) {
|
|
4079
4187
|
container = element;
|
|
4080
4188
|
sceneX = element.x + element.width / 2;
|
|
@@ -4132,7 +4240,7 @@ class App extends React.Component {
|
|
|
4132
4240
|
points: [[0, 0]],
|
|
4133
4241
|
pressures,
|
|
4134
4242
|
});
|
|
4135
|
-
const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene);
|
|
4243
|
+
const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
|
|
4136
4244
|
this.scene.addNewElement(element);
|
|
4137
4245
|
this.setState({
|
|
4138
4246
|
draggingElement: element,
|
|
@@ -4176,8 +4284,11 @@ class App extends React.Component {
|
|
|
4176
4284
|
if (!embedLink) {
|
|
4177
4285
|
return;
|
|
4178
4286
|
}
|
|
4179
|
-
if (embedLink.
|
|
4180
|
-
this.setToast({
|
|
4287
|
+
if (embedLink.error instanceof URIError) {
|
|
4288
|
+
this.setToast({
|
|
4289
|
+
message: t("toast.unrecognizedLinkFormat"),
|
|
4290
|
+
closable: true,
|
|
4291
|
+
});
|
|
4181
4292
|
}
|
|
4182
4293
|
const element = newEmbeddableElement({
|
|
4183
4294
|
type: "embeddable",
|
|
@@ -4195,7 +4306,6 @@ class App extends React.Component {
|
|
|
4195
4306
|
width: embedLink.intrinsicSize.w,
|
|
4196
4307
|
height: embedLink.intrinsicSize.h,
|
|
4197
4308
|
link,
|
|
4198
|
-
validated: null,
|
|
4199
4309
|
});
|
|
4200
4310
|
this.scene.replaceAllElements([
|
|
4201
4311
|
...this.scene.getElementsIncludingDeleted(),
|
|
@@ -4308,7 +4418,7 @@ class App extends React.Component {
|
|
|
4308
4418
|
mutateElement(element, {
|
|
4309
4419
|
points: [...element.points, [0, 0]],
|
|
4310
4420
|
});
|
|
4311
|
-
const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene);
|
|
4421
|
+
const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
|
|
4312
4422
|
this.scene.addNewElement(element);
|
|
4313
4423
|
this.setState({
|
|
4314
4424
|
draggingElement: element,
|
|
@@ -4353,7 +4463,6 @@ class App extends React.Component {
|
|
|
4353
4463
|
if (elementType === "embeddable") {
|
|
4354
4464
|
element = newEmbeddableElement({
|
|
4355
4465
|
type: "embeddable",
|
|
4356
|
-
validated: null,
|
|
4357
4466
|
...baseElementAttributes,
|
|
4358
4467
|
});
|
|
4359
4468
|
}
|
|
@@ -4409,7 +4518,7 @@ class App extends React.Component {
|
|
|
4409
4518
|
selectedElements,
|
|
4410
4519
|
}) &&
|
|
4411
4520
|
(recomputeAnyways || !SnapCache.getReferenceSnapPoints())) {
|
|
4412
|
-
SnapCache.setReferenceSnapPoints(getReferenceSnapPoints(this.scene.getNonDeletedElements(), selectedElements, this.state));
|
|
4521
|
+
SnapCache.setReferenceSnapPoints(getReferenceSnapPoints(this.scene.getNonDeletedElements(), selectedElements, this.state, this.scene.getNonDeletedElementsMap()));
|
|
4413
4522
|
}
|
|
4414
4523
|
}
|
|
4415
4524
|
maybeCacheVisibleGaps(event, selectedElements, recomputeAnyways = false) {
|
|
@@ -4419,7 +4528,7 @@ class App extends React.Component {
|
|
|
4419
4528
|
selectedElements,
|
|
4420
4529
|
}) &&
|
|
4421
4530
|
(recomputeAnyways || !SnapCache.getVisibleGaps())) {
|
|
4422
|
-
SnapCache.setVisibleGaps(getVisibleGaps(this.scene.getNonDeletedElements(), selectedElements, this.state));
|
|
4531
|
+
SnapCache.setVisibleGaps(getVisibleGaps(this.scene.getNonDeletedElements(), selectedElements, this.state, this.scene.getNonDeletedElementsMap()));
|
|
4423
4532
|
}
|
|
4424
4533
|
}
|
|
4425
4534
|
onKeyDownFromPointerDownHandler(pointerDownState) {
|
|
@@ -4462,7 +4571,7 @@ class App extends React.Component {
|
|
|
4462
4571
|
return;
|
|
4463
4572
|
}
|
|
4464
4573
|
if (this.state.activeTool.type === "laser") {
|
|
4465
|
-
this.
|
|
4574
|
+
this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
|
|
4466
4575
|
}
|
|
4467
4576
|
const [gridX, gridY] = getGridPoint(pointerCoords.x, pointerCoords.y, event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize);
|
|
4468
4577
|
// for arrows/lines, don't start dragging until a given threshold
|
|
@@ -4483,10 +4592,11 @@ class App extends React.Component {
|
|
|
4483
4592
|
return true;
|
|
4484
4593
|
}
|
|
4485
4594
|
}
|
|
4595
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
4486
4596
|
if (this.state.selectedLinearElement) {
|
|
4487
4597
|
const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
|
|
4488
|
-
if (LinearElementEditor.shouldAddMidpoint(this.state.selectedLinearElement, pointerCoords, this.state)) {
|
|
4489
|
-
const ret = LinearElementEditor.addMidpoint(this.state.selectedLinearElement, pointerCoords, this.state, !event[KEYS.CTRL_OR_CMD]);
|
|
4598
|
+
if (LinearElementEditor.shouldAddMidpoint(this.state.selectedLinearElement, pointerCoords, this.state, elementsMap)) {
|
|
4599
|
+
const ret = LinearElementEditor.addMidpoint(this.state.selectedLinearElement, pointerCoords, this.state, !event[KEYS.CTRL_OR_CMD], elementsMap);
|
|
4490
4600
|
if (!ret) {
|
|
4491
4601
|
return;
|
|
4492
4602
|
}
|
|
@@ -4521,7 +4631,7 @@ class App extends React.Component {
|
|
|
4521
4631
|
}
|
|
4522
4632
|
const didDrag = LinearElementEditor.handlePointDragging(event, this.state, pointerCoords.x, pointerCoords.y, (element, pointsSceneCoords) => {
|
|
4523
4633
|
this.maybeSuggestBindingsForLinearElementAtCoords(element, pointsSceneCoords);
|
|
4524
|
-
}, linearElementEditor);
|
|
4634
|
+
}, linearElementEditor, this.scene.getNonDeletedElementsMap());
|
|
4525
4635
|
if (didDrag) {
|
|
4526
4636
|
pointerDownState.lastCoords.x = pointerCoords.x;
|
|
4527
4637
|
pointerDownState.lastCoords.y = pointerCoords.y;
|
|
@@ -4602,7 +4712,7 @@ class App extends React.Component {
|
|
|
4602
4712
|
// it snaps to its position if previously snapped already.
|
|
4603
4713
|
this.maybeCacheVisibleGaps(event, selectedElements);
|
|
4604
4714
|
this.maybeCacheReferenceSnapPoints(event, selectedElements);
|
|
4605
|
-
const { snapOffset, snapLines } = snapDraggedElements(
|
|
4715
|
+
const { snapOffset, snapLines } = snapDraggedElements(originalElements, dragOffset, this.state, event, this.scene.getNonDeletedElementsMap());
|
|
4606
4716
|
this.setState({ snapLines });
|
|
4607
4717
|
// when we're editing the name of a frame, we want the user to be
|
|
4608
4718
|
// able to select and interact with the text input
|
|
@@ -4720,7 +4830,7 @@ class App extends React.Component {
|
|
|
4720
4830
|
const elements = this.scene.getNonDeletedElements();
|
|
4721
4831
|
// box-select line editor points
|
|
4722
4832
|
if (this.state.editingLinearElement) {
|
|
4723
|
-
LinearElementEditor.handleBoxSelection(event, this.state, this.setState.bind(this));
|
|
4833
|
+
LinearElementEditor.handleBoxSelection(event, this.state, this.setState.bind(this), this.scene.getNonDeletedElementsMap());
|
|
4724
4834
|
// regular box-select
|
|
4725
4835
|
}
|
|
4726
4836
|
else {
|
|
@@ -4739,7 +4849,7 @@ class App extends React.Component {
|
|
|
4739
4849
|
shouldReuseSelection = false;
|
|
4740
4850
|
}
|
|
4741
4851
|
}
|
|
4742
|
-
const elementsWithinSelection = getElementsWithinSelection(elements, draggingElement);
|
|
4852
|
+
const elementsWithinSelection = getElementsWithinSelection(elements, draggingElement, this.scene.getNonDeletedElementsMap());
|
|
4743
4853
|
this.setState((prevState) => {
|
|
4744
4854
|
const nextSelectedElementIds = {
|
|
4745
4855
|
...(shouldReuseSelection && prevState.selectedElementIds),
|
|
@@ -4769,7 +4879,7 @@ class App extends React.Component {
|
|
|
4769
4879
|
// select linear element only when we haven't box-selected anything else
|
|
4770
4880
|
selectedLinearElement: elementsWithinSelection.length === 1 &&
|
|
4771
4881
|
isLinearElement(elementsWithinSelection[0])
|
|
4772
|
-
? new LinearElementEditor(elementsWithinSelection[0]
|
|
4882
|
+
? new LinearElementEditor(elementsWithinSelection[0])
|
|
4773
4883
|
: null,
|
|
4774
4884
|
showHyperlinkPopup: elementsWithinSelection.length === 1 &&
|
|
4775
4885
|
(elementsWithinSelection[0].link ||
|
|
@@ -4833,6 +4943,7 @@ class App extends React.Component {
|
|
|
4833
4943
|
this.setState({
|
|
4834
4944
|
selectedElementsAreBeingDragged: false,
|
|
4835
4945
|
});
|
|
4946
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
4836
4947
|
// Handle end of dragging a point of a linear element, might close a loop
|
|
4837
4948
|
// and sets binding element
|
|
4838
4949
|
if (this.state.editingLinearElement) {
|
|
@@ -4842,7 +4953,7 @@ class App extends React.Component {
|
|
|
4842
4953
|
this.actionManager.executeAction(actionFinalize);
|
|
4843
4954
|
}
|
|
4844
4955
|
else {
|
|
4845
|
-
const editingLinearElement = LinearElementEditor.handlePointerUp(childEvent, this.state.editingLinearElement, this.state);
|
|
4956
|
+
const editingLinearElement = LinearElementEditor.handlePointerUp(childEvent, this.state.editingLinearElement, this.state, this.scene.getNonDeletedElements(), elementsMap);
|
|
4846
4957
|
if (editingLinearElement !== this.state.editingLinearElement) {
|
|
4847
4958
|
this.setState({
|
|
4848
4959
|
editingLinearElement,
|
|
@@ -4861,11 +4972,11 @@ class App extends React.Component {
|
|
|
4861
4972
|
}
|
|
4862
4973
|
}
|
|
4863
4974
|
else {
|
|
4864
|
-
const linearElementEditor = LinearElementEditor.handlePointerUp(childEvent, this.state.selectedLinearElement, this.state);
|
|
4975
|
+
const linearElementEditor = LinearElementEditor.handlePointerUp(childEvent, this.state.selectedLinearElement, this.state, this.scene.getNonDeletedElements(), elementsMap);
|
|
4865
4976
|
const { startBindingElement, endBindingElement } = linearElementEditor;
|
|
4866
4977
|
const element = this.scene.getElement(linearElementEditor.elementId);
|
|
4867
4978
|
if (isBindingElement(element)) {
|
|
4868
|
-
bindOrUnbindLinearElement(element, startBindingElement, endBindingElement);
|
|
4979
|
+
bindOrUnbindLinearElement(element, startBindingElement, endBindingElement, elementsMap);
|
|
4869
4980
|
}
|
|
4870
4981
|
if (linearElementEditor !== this.state.selectedLinearElement) {
|
|
4871
4982
|
this.setState({
|
|
@@ -4886,6 +4997,7 @@ class App extends React.Component {
|
|
|
4886
4997
|
if (this.state.pendingImageElementId) {
|
|
4887
4998
|
this.setState({ pendingImageElementId: null });
|
|
4888
4999
|
}
|
|
5000
|
+
this.props?.onPointerUp?.(activeTool, pointerDownState);
|
|
4889
5001
|
this.onPointerUpEmitter.trigger(this.state.activeTool, pointerDownState, childEvent);
|
|
4890
5002
|
if (draggingElement?.type === "freedraw") {
|
|
4891
5003
|
const pointerCoords = viewportCoordsToSceneCoords(childEvent, this.state);
|
|
@@ -4952,7 +5064,7 @@ class App extends React.Component {
|
|
|
4952
5064
|
else if (pointerDownState.drag.hasOccurred && !multiElement) {
|
|
4953
5065
|
if (isBindingEnabled(this.state) &&
|
|
4954
5066
|
isBindingElement(draggingElement, false)) {
|
|
4955
|
-
maybeBindLinearElement(draggingElement, this.state, this.scene, pointerCoords);
|
|
5067
|
+
maybeBindLinearElement(draggingElement, this.state, this.scene, pointerCoords, elementsMap);
|
|
4956
5068
|
}
|
|
4957
5069
|
this.setState({ suggestedBindings: [], startBoundElement: null });
|
|
4958
5070
|
if (!activeTool.locked) {
|
|
@@ -4966,7 +5078,7 @@ class App extends React.Component {
|
|
|
4966
5078
|
...prevState.selectedElementIds,
|
|
4967
5079
|
[draggingElement.id]: true,
|
|
4968
5080
|
}, prevState),
|
|
4969
|
-
selectedLinearElement: new LinearElementEditor(draggingElement
|
|
5081
|
+
selectedLinearElement: new LinearElementEditor(draggingElement),
|
|
4970
5082
|
}));
|
|
4971
5083
|
}
|
|
4972
5084
|
else {
|
|
@@ -4999,15 +5111,16 @@ class App extends React.Component {
|
|
|
4999
5111
|
this.state.selectedLinearElement.isDragging) {
|
|
5000
5112
|
const linearElement = this.scene.getElement(this.state.selectedLinearElement.elementId);
|
|
5001
5113
|
if (linearElement?.frameId) {
|
|
5002
|
-
const frame = getContainingFrame(linearElement);
|
|
5114
|
+
const frame = getContainingFrame(linearElement, elementsMap);
|
|
5003
5115
|
if (frame && linearElement) {
|
|
5004
|
-
if (!elementOverlapsWithFrame(linearElement, frame)) {
|
|
5116
|
+
if (!elementOverlapsWithFrame(linearElement, frame, this.scene.getNonDeletedElementsMap())) {
|
|
5005
5117
|
// remove the linear element from all groups
|
|
5006
5118
|
// before removing it from the frame as well
|
|
5007
5119
|
mutateElement(linearElement, {
|
|
5008
5120
|
groupIds: [],
|
|
5009
5121
|
});
|
|
5010
|
-
|
|
5122
|
+
removeElementsFromFrame([linearElement], this.scene.getNonDeletedElementsMap());
|
|
5123
|
+
this.scene.informMutation();
|
|
5011
5124
|
}
|
|
5012
5125
|
}
|
|
5013
5126
|
}
|
|
@@ -5016,7 +5129,7 @@ class App extends React.Component {
|
|
|
5016
5129
|
// update the relationships between selected elements and frames
|
|
5017
5130
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords(sceneCoords);
|
|
5018
5131
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
5019
|
-
let nextElements = this.scene.
|
|
5132
|
+
let nextElements = this.scene.getElementsMapIncludingDeleted();
|
|
5020
5133
|
const updateGroupIdsAfterEditingGroup = (elements) => {
|
|
5021
5134
|
if (elements.length > 0) {
|
|
5022
5135
|
for (const element of elements) {
|
|
@@ -5059,8 +5172,8 @@ class App extends React.Component {
|
|
|
5059
5172
|
}
|
|
5060
5173
|
}
|
|
5061
5174
|
if (isFrameLikeElement(draggingElement)) {
|
|
5062
|
-
const elementsInsideFrame = getElementsInNewFrame(this.scene.getElementsIncludingDeleted(), draggingElement);
|
|
5063
|
-
this.scene.replaceAllElements(addElementsToFrame(this.scene.
|
|
5175
|
+
const elementsInsideFrame = getElementsInNewFrame(this.scene.getElementsIncludingDeleted(), draggingElement, this.scene.getNonDeletedElementsMap());
|
|
5176
|
+
this.scene.replaceAllElements(addElementsToFrame(this.scene.getElementsMapIncludingDeleted(), elementsInsideFrame, draggingElement));
|
|
5064
5177
|
}
|
|
5065
5178
|
mutateElement(draggingElement, getNormalizedDimensions(draggingElement));
|
|
5066
5179
|
}
|
|
@@ -5079,7 +5192,7 @@ class App extends React.Component {
|
|
|
5079
5192
|
.getSelectedElements(this.state)
|
|
5080
5193
|
.filter((element) => isFrameLikeElement(element));
|
|
5081
5194
|
for (const frame of selectedFrames) {
|
|
5082
|
-
nextElements = replaceAllElementsInFrame(nextElements, getElementsInResizingFrame(this.scene.getElementsIncludingDeleted(), frame, this.state), frame, this
|
|
5195
|
+
nextElements = replaceAllElementsInFrame(nextElements, getElementsInResizingFrame(this.scene.getElementsIncludingDeleted(), frame, this.state, elementsMap), frame, this);
|
|
5083
5196
|
}
|
|
5084
5197
|
this.scene.replaceAllElements(nextElements);
|
|
5085
5198
|
}
|
|
@@ -5093,13 +5206,14 @@ class App extends React.Component {
|
|
|
5093
5206
|
// the one we've hit
|
|
5094
5207
|
if (selectedELements.length === 1) {
|
|
5095
5208
|
this.setState({
|
|
5096
|
-
selectedLinearElement: new LinearElementEditor(hitElement
|
|
5209
|
+
selectedLinearElement: new LinearElementEditor(hitElement),
|
|
5097
5210
|
});
|
|
5098
5211
|
}
|
|
5099
5212
|
}
|
|
5100
5213
|
const pointerStart = this.lastPointerDownEvent;
|
|
5101
5214
|
const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
|
|
5102
5215
|
if (isEraserActive(this.state) && pointerStart && pointerEnd) {
|
|
5216
|
+
this.eraserTrail.endPath();
|
|
5103
5217
|
const draggedDistance = distance2d(pointerStart.clientX, pointerStart.clientY, pointerEnd.clientX, pointerEnd.clientY);
|
|
5104
5218
|
if (draggedDistance === 0) {
|
|
5105
5219
|
const scenePointer = viewportCoordsToSceneCoords({
|
|
@@ -5107,16 +5221,13 @@ class App extends React.Component {
|
|
|
5107
5221
|
clientY: pointerEnd.clientY,
|
|
5108
5222
|
}, this.state);
|
|
5109
5223
|
const hitElements = this.getElementsAtPosition(scenePointer.x, scenePointer.y);
|
|
5110
|
-
hitElements.forEach((hitElement) => (
|
|
5111
|
-
erase: true,
|
|
5112
|
-
opacity: hitElement.opacity,
|
|
5113
|
-
}));
|
|
5224
|
+
hitElements.forEach((hitElement) => this.elementsPendingErasure.add(hitElement.id));
|
|
5114
5225
|
}
|
|
5115
|
-
this.eraseElements(
|
|
5226
|
+
this.eraseElements();
|
|
5116
5227
|
return;
|
|
5117
5228
|
}
|
|
5118
|
-
else if (
|
|
5119
|
-
this.restoreReadyToEraseElements(
|
|
5229
|
+
else if (this.elementsPendingErasure.size) {
|
|
5230
|
+
this.restoreReadyToEraseElements();
|
|
5120
5231
|
}
|
|
5121
5232
|
if (hitElement &&
|
|
5122
5233
|
!pointerDownState.drag.hasOccurred &&
|
|
@@ -5168,7 +5279,7 @@ class App extends React.Component {
|
|
|
5168
5279
|
// set selectedLinearElement only if thats the only element selected
|
|
5169
5280
|
selectedLinearElement: newSelectedElements.length === 1 &&
|
|
5170
5281
|
isLinearElement(newSelectedElements[0])
|
|
5171
|
-
? new LinearElementEditor(newSelectedElements[0]
|
|
5282
|
+
? new LinearElementEditor(newSelectedElements[0])
|
|
5172
5283
|
: prevState.selectedLinearElement,
|
|
5173
5284
|
};
|
|
5174
5285
|
});
|
|
@@ -5222,7 +5333,7 @@ class App extends React.Component {
|
|
|
5222
5333
|
// Don't set `selectedLinearElement` if its same as the hitElement, this is mainly to prevent resetting the `hoverPointIndex` to -1.
|
|
5223
5334
|
// Future we should update the API to take care of setting the correct `hoverPointIndex` when initialized
|
|
5224
5335
|
prevState.selectedLinearElement?.elementId !== hitElement.id
|
|
5225
|
-
? new LinearElementEditor(hitElement
|
|
5336
|
+
? new LinearElementEditor(hitElement)
|
|
5226
5337
|
: prevState.selectedLinearElement,
|
|
5227
5338
|
}));
|
|
5228
5339
|
}
|
|
@@ -5230,7 +5341,7 @@ class App extends React.Component {
|
|
|
5230
5341
|
if (!pointerDownState.drag.hasOccurred &&
|
|
5231
5342
|
!this.state.isResizing &&
|
|
5232
5343
|
((hitElement &&
|
|
5233
|
-
isHittingElementBoundingBoxWithoutHittingElement(hitElement, this.state, this.frameNameBoundsCache, pointerDownState.origin.x, pointerDownState.origin.y)) ||
|
|
5344
|
+
isHittingElementBoundingBoxWithoutHittingElement(hitElement, this.state, this.frameNameBoundsCache, pointerDownState.origin.x, pointerDownState.origin.y, this.scene.getNonDeletedElementsMap())) ||
|
|
5234
5345
|
(!hitElement &&
|
|
5235
5346
|
pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements))) {
|
|
5236
5347
|
if (this.state.editingLinearElement) {
|
|
@@ -5266,12 +5377,12 @@ class App extends React.Component {
|
|
|
5266
5377
|
this.history.resumeRecording();
|
|
5267
5378
|
}
|
|
5268
5379
|
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
|
5269
|
-
|
|
5270
|
-
? bindOrUnbindSelectedElements
|
|
5271
|
-
: unbindLinearElements
|
|
5380
|
+
isBindingEnabled(this.state)
|
|
5381
|
+
? bindOrUnbindSelectedElements(this.scene.getSelectedElements(this.state), this.scene.getNonDeletedElements(), elementsMap)
|
|
5382
|
+
: unbindLinearElements(this.scene.getSelectedElements(this.state), elementsMap);
|
|
5272
5383
|
}
|
|
5273
5384
|
if (activeTool.type === "laser") {
|
|
5274
|
-
this.
|
|
5385
|
+
this.laserTrails.endPath();
|
|
5275
5386
|
return;
|
|
5276
5387
|
}
|
|
5277
5388
|
if (!activeTool.locked && activeTool.type !== "freedraw") {
|
|
@@ -5301,52 +5412,27 @@ class App extends React.Component {
|
|
|
5301
5412
|
}
|
|
5302
5413
|
});
|
|
5303
5414
|
}
|
|
5304
|
-
restoreReadyToEraseElements = (
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
pointerDownState.elementIdsToErase[ele.id].erase) {
|
|
5308
|
-
return newElementWith(ele, {
|
|
5309
|
-
opacity: pointerDownState.elementIdsToErase[ele.id].opacity,
|
|
5310
|
-
});
|
|
5311
|
-
}
|
|
5312
|
-
else if (isBoundToContainer(ele) &&
|
|
5313
|
-
pointerDownState.elementIdsToErase[ele.containerId] &&
|
|
5314
|
-
pointerDownState.elementIdsToErase[ele.containerId].erase) {
|
|
5315
|
-
return newElementWith(ele, {
|
|
5316
|
-
opacity: pointerDownState.elementIdsToErase[ele.containerId].opacity,
|
|
5317
|
-
});
|
|
5318
|
-
}
|
|
5319
|
-
else if (ele.frameId &&
|
|
5320
|
-
pointerDownState.elementIdsToErase[ele.frameId] &&
|
|
5321
|
-
pointerDownState.elementIdsToErase[ele.frameId].erase) {
|
|
5322
|
-
return newElementWith(ele, {
|
|
5323
|
-
opacity: pointerDownState.elementIdsToErase[ele.frameId].opacity,
|
|
5324
|
-
});
|
|
5325
|
-
}
|
|
5326
|
-
return ele;
|
|
5327
|
-
});
|
|
5328
|
-
this.scene.replaceAllElements(elements);
|
|
5415
|
+
restoreReadyToEraseElements = () => {
|
|
5416
|
+
this.elementsPendingErasure = new Set();
|
|
5417
|
+
this.onSceneUpdated();
|
|
5329
5418
|
};
|
|
5330
|
-
eraseElements = (
|
|
5419
|
+
eraseElements = () => {
|
|
5420
|
+
let didChange = false;
|
|
5331
5421
|
const elements = this.scene.getElementsIncludingDeleted().map((ele) => {
|
|
5332
|
-
if (
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
pointerDownState.elementIdsToErase[ele.containerId] &&
|
|
5338
|
-
pointerDownState.elementIdsToErase[ele.containerId].erase) {
|
|
5339
|
-
return newElementWith(ele, { isDeleted: true });
|
|
5340
|
-
}
|
|
5341
|
-
else if (ele.frameId &&
|
|
5342
|
-
pointerDownState.elementIdsToErase[ele.frameId] &&
|
|
5343
|
-
pointerDownState.elementIdsToErase[ele.frameId].erase) {
|
|
5422
|
+
if (this.elementsPendingErasure.has(ele.id) ||
|
|
5423
|
+
(ele.frameId && this.elementsPendingErasure.has(ele.frameId)) ||
|
|
5424
|
+
(isBoundToContainer(ele) &&
|
|
5425
|
+
this.elementsPendingErasure.has(ele.containerId))) {
|
|
5426
|
+
didChange = true;
|
|
5344
5427
|
return newElementWith(ele, { isDeleted: true });
|
|
5345
5428
|
}
|
|
5346
5429
|
return ele;
|
|
5347
5430
|
});
|
|
5348
|
-
this.
|
|
5349
|
-
|
|
5431
|
+
this.elementsPendingErasure = new Set();
|
|
5432
|
+
if (didChange) {
|
|
5433
|
+
this.history.resumeRecording();
|
|
5434
|
+
this.scene.replaceAllElements(elements);
|
|
5435
|
+
}
|
|
5350
5436
|
};
|
|
5351
5437
|
initializeImage = async ({ imageFile, imageElement: _imageElement, showCursorImagePreview = false, }) => {
|
|
5352
5438
|
// at this point this should be guaranteed image file, but we do this check
|
|
@@ -5470,9 +5556,18 @@ class App extends React.Component {
|
|
|
5470
5556
|
// mustn't be larger than 128 px
|
|
5471
5557
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Basic_User_Interface/Using_URL_values_for_the_cursor_property
|
|
5472
5558
|
const cursorImageSizePx = 96;
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5559
|
+
let imagePreview;
|
|
5560
|
+
try {
|
|
5561
|
+
imagePreview = await resizeImageFile(imageFile, {
|
|
5562
|
+
maxWidthOrHeight: cursorImageSizePx,
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
5565
|
+
catch (e) {
|
|
5566
|
+
if (e.cause === "UNSUPPORTED") {
|
|
5567
|
+
throw new Error(t("errors.unsupportedFileType"));
|
|
5568
|
+
}
|
|
5569
|
+
throw e;
|
|
5570
|
+
}
|
|
5476
5571
|
let previewDataURL = await getDataURL(imagePreview);
|
|
5477
5572
|
// SVG cannot be resized via `resizeImageFile` so we resize by rendering to
|
|
5478
5573
|
// a small canvas
|
|
@@ -5627,7 +5722,7 @@ class App extends React.Component {
|
|
|
5627
5722
|
}
|
|
5628
5723
|
};
|
|
5629
5724
|
maybeSuggestBindingAtCursor = (pointerCoords) => {
|
|
5630
|
-
const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this.scene);
|
|
5725
|
+
const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
|
|
5631
5726
|
this.setState({
|
|
5632
5727
|
suggestedBindings: hoveredBindableElement != null ? [hoveredBindableElement] : [],
|
|
5633
5728
|
});
|
|
@@ -5642,7 +5737,7 @@ class App extends React.Component {
|
|
|
5642
5737
|
return;
|
|
5643
5738
|
}
|
|
5644
5739
|
const suggestedBindings = pointerCoords.reduce((acc, coords) => {
|
|
5645
|
-
const hoveredBindableElement = getHoveredElementForBinding(coords, this.scene);
|
|
5740
|
+
const hoveredBindableElement = getHoveredElementForBinding(coords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
|
|
5646
5741
|
if (hoveredBindableElement != null &&
|
|
5647
5742
|
!isLinearElementSimpleAndAlreadyBound(linearElement, oppositeBindingBoundElement?.id, hoveredBindableElement)) {
|
|
5648
5743
|
acc.push(hoveredBindableElement);
|
|
@@ -5655,7 +5750,7 @@ class App extends React.Component {
|
|
|
5655
5750
|
if (selectedElements.length > 50) {
|
|
5656
5751
|
return;
|
|
5657
5752
|
}
|
|
5658
|
-
const suggestedBindings = getEligibleElementsForBinding(selectedElements);
|
|
5753
|
+
const suggestedBindings = getEligibleElementsForBinding(selectedElements, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
|
|
5659
5754
|
this.setState({ suggestedBindings });
|
|
5660
5755
|
}
|
|
5661
5756
|
clearSelection(hitElement) {
|
|
@@ -5722,8 +5817,9 @@ class App extends React.Component {
|
|
|
5722
5817
|
return;
|
|
5723
5818
|
}
|
|
5724
5819
|
catch (error) {
|
|
5820
|
+
// Don't throw for image scene daa
|
|
5725
5821
|
if (error.name !== "EncodingError") {
|
|
5726
|
-
throw
|
|
5822
|
+
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
|
5727
5823
|
}
|
|
5728
5824
|
}
|
|
5729
5825
|
}
|
|
@@ -5784,7 +5880,32 @@ class App extends React.Component {
|
|
|
5784
5880
|
loadFileToCanvas = async (file, fileHandle) => {
|
|
5785
5881
|
file = await normalizeFile(file);
|
|
5786
5882
|
try {
|
|
5787
|
-
|
|
5883
|
+
let ret;
|
|
5884
|
+
try {
|
|
5885
|
+
ret = await loadSceneOrLibraryFromBlob(file, this.state, this.scene.getElementsIncludingDeleted(), fileHandle);
|
|
5886
|
+
}
|
|
5887
|
+
catch (error) {
|
|
5888
|
+
const imageSceneDataError = error instanceof ImageSceneDataError;
|
|
5889
|
+
if (imageSceneDataError &&
|
|
5890
|
+
error.code === "IMAGE_NOT_CONTAINS_SCENE_DATA" &&
|
|
5891
|
+
!this.isToolSupported("image")) {
|
|
5892
|
+
this.setState({
|
|
5893
|
+
isLoading: false,
|
|
5894
|
+
errorMessage: t("errors.imageToolNotSupported"),
|
|
5895
|
+
});
|
|
5896
|
+
return;
|
|
5897
|
+
}
|
|
5898
|
+
const errorMessage = imageSceneDataError
|
|
5899
|
+
? t("alerts.cannotRestoreFromImage")
|
|
5900
|
+
: t("alerts.couldNotLoadInvalidFile");
|
|
5901
|
+
this.setState({
|
|
5902
|
+
isLoading: false,
|
|
5903
|
+
errorMessage,
|
|
5904
|
+
});
|
|
5905
|
+
}
|
|
5906
|
+
if (!ret) {
|
|
5907
|
+
return;
|
|
5908
|
+
}
|
|
5788
5909
|
if (ret.type === MIME_TYPES.excalidraw) {
|
|
5789
5910
|
this.setState({ isLoading: true });
|
|
5790
5911
|
this.syncActionResult({
|
|
@@ -5811,15 +5932,6 @@ class App extends React.Component {
|
|
|
5811
5932
|
}
|
|
5812
5933
|
}
|
|
5813
5934
|
catch (error) {
|
|
5814
|
-
if (error instanceof ImageSceneDataError &&
|
|
5815
|
-
error.code === "IMAGE_NOT_CONTAINS_SCENE_DATA" &&
|
|
5816
|
-
!this.isToolSupported("image")) {
|
|
5817
|
-
this.setState({
|
|
5818
|
-
isLoading: false,
|
|
5819
|
-
errorMessage: t("errors.imageToolNotSupported"),
|
|
5820
|
-
});
|
|
5821
|
-
return;
|
|
5822
|
-
}
|
|
5823
5935
|
this.setState({ isLoading: false, errorMessage: error.message });
|
|
5824
5936
|
}
|
|
5825
5937
|
};
|
|
@@ -5856,7 +5968,7 @@ class App extends React.Component {
|
|
|
5856
5968
|
selectedElementIds: { [element.id]: true },
|
|
5857
5969
|
}, this.scene.getNonDeletedElements(), this.state, this),
|
|
5858
5970
|
selectedLinearElement: isLinearElement(element)
|
|
5859
|
-
? new LinearElementEditor(element
|
|
5971
|
+
? new LinearElementEditor(element)
|
|
5860
5972
|
: null,
|
|
5861
5973
|
}
|
|
5862
5974
|
: this.state),
|
|
@@ -5893,7 +6005,7 @@ class App extends React.Component {
|
|
|
5893
6005
|
}, {
|
|
5894
6006
|
x: gridX - pointerDownState.originInGrid.x,
|
|
5895
6007
|
y: gridY - pointerDownState.originInGrid.y,
|
|
5896
|
-
});
|
|
6008
|
+
}, this.scene.getNonDeletedElementsMap());
|
|
5897
6009
|
gridX += snapOffset.x;
|
|
5898
6010
|
gridY += snapOffset.y;
|
|
5899
6011
|
this.setState({
|
|
@@ -5907,7 +6019,7 @@ class App extends React.Component {
|
|
|
5907
6019
|
if (this.state.activeTool.type === TOOL_TYPE.frame ||
|
|
5908
6020
|
this.state.activeTool.type === TOOL_TYPE.magicframe) {
|
|
5909
6021
|
this.setState({
|
|
5910
|
-
elementsToHighlight: getElementsInResizingFrame(this.scene.getNonDeletedElements(), draggingElement, this.state),
|
|
6022
|
+
elementsToHighlight: getElementsInResizingFrame(this.scene.getNonDeletedElements(), draggingElement, this.state, this.scene.getNonDeletedElementsMap()),
|
|
5911
6023
|
});
|
|
5912
6024
|
}
|
|
5913
6025
|
}
|
|
@@ -5956,13 +6068,13 @@ class App extends React.Component {
|
|
|
5956
6068
|
snapLines,
|
|
5957
6069
|
});
|
|
5958
6070
|
}
|
|
5959
|
-
if (transformElements(pointerDownState, transformHandleType, selectedElements,
|
|
6071
|
+
if (transformElements(pointerDownState.originalElements, transformHandleType, selectedElements, this.scene.getElementsMapIncludingDeleted(), shouldRotateWithDiscreteAngle(event), shouldResizeFromCenter(event), selectedElements.length === 1 && isImageElement(selectedElements[0])
|
|
5960
6072
|
? !shouldMaintainAspectRatio(event)
|
|
5961
|
-
: shouldMaintainAspectRatio(event), resizeX, resizeY, pointerDownState.resize.center.x, pointerDownState.resize.center.y
|
|
6073
|
+
: shouldMaintainAspectRatio(event), resizeX, resizeY, pointerDownState.resize.center.x, pointerDownState.resize.center.y)) {
|
|
5962
6074
|
this.maybeSuggestBindingForAll(selectedElements);
|
|
5963
6075
|
const elementsToHighlight = new Set();
|
|
5964
6076
|
selectedFrames.forEach((frame) => {
|
|
5965
|
-
getElementsInResizingFrame(this.scene.getNonDeletedElements(), frame, this.state).forEach((element) => elementsToHighlight.add(element));
|
|
6077
|
+
getElementsInResizingFrame(this.scene.getNonDeletedElements(), frame, this.state, this.scene.getNonDeletedElementsMap()).forEach((element) => elementsToHighlight.add(element));
|
|
5966
6078
|
});
|
|
5967
6079
|
this.setState({
|
|
5968
6080
|
elementsToHighlight: [...elementsToHighlight],
|
|
@@ -6095,7 +6207,7 @@ class App extends React.Component {
|
|
|
6095
6207
|
if (container) {
|
|
6096
6208
|
let elementCenterX = container.x + container.width / 2;
|
|
6097
6209
|
let elementCenterY = container.y + container.height / 2;
|
|
6098
|
-
const elementCenter = getContainerCenter(container, appState);
|
|
6210
|
+
const elementCenter = getContainerCenter(container, appState, this.scene.getNonDeletedElementsMap());
|
|
6099
6211
|
if (elementCenter) {
|
|
6100
6212
|
elementCenterX = elementCenter.x;
|
|
6101
6213
|
elementCenterY = elementCenter.y;
|
|
@@ -6180,18 +6292,21 @@ class App extends React.Component {
|
|
|
6180
6292
|
this.setAppState({});
|
|
6181
6293
|
}
|
|
6182
6294
|
}
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6295
|
+
export const createTestHook = () => {
|
|
6296
|
+
if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
|
|
6297
|
+
window.h = window.h || {};
|
|
6298
|
+
Object.defineProperties(window.h, {
|
|
6299
|
+
elements: {
|
|
6300
|
+
configurable: true,
|
|
6301
|
+
get() {
|
|
6302
|
+
return this.app?.scene.getElementsIncludingDeleted();
|
|
6303
|
+
},
|
|
6304
|
+
set(elements) {
|
|
6305
|
+
return this.app?.scene.replaceAllElements(elements);
|
|
6306
|
+
},
|
|
6193
6307
|
},
|
|
6194
|
-
}
|
|
6195
|
-
}
|
|
6196
|
-
}
|
|
6308
|
+
});
|
|
6309
|
+
}
|
|
6310
|
+
};
|
|
6311
|
+
createTestHook();
|
|
6197
6312
|
export default App;
|