@excalidraw/excalidraw 0.17.1-7441-4e2c539 → 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-HYNUJ3XL.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 +34707 -26
- 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 +38445 -39402
- package/dist/dev/index.js.map +4 -4
- package/dist/excalidraw/actions/actionAddToLibrary.d.ts +12 -12
- package/dist/excalidraw/actions/actionAlign.d.ts +6 -6
- package/dist/excalidraw/actions/actionAlign.js +2 -1
- package/dist/excalidraw/actions/actionBoundText.d.ts +8 -8
- package/dist/excalidraw/actions/actionBoundText.js +8 -8
- package/dist/excalidraw/actions/actionCanvas.d.ts +46 -46
- package/dist/excalidraw/actions/actionClipboard.d.ts +27 -27
- package/dist/excalidraw/actions/actionClipboard.js +9 -2
- package/dist/excalidraw/actions/actionDeleteSelected.d.ts +12 -12
- 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 +8 -8
- package/dist/excalidraw/actions/actionExport.d.ts +35 -35
- package/dist/excalidraw/actions/actionExport.js +4 -4
- package/dist/excalidraw/actions/actionFinalize.d.ts +7 -7
- 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 +13 -13
- package/dist/excalidraw/actions/actionFrame.js +1 -1
- package/dist/excalidraw/actions/actionGroup.d.ts +8 -8
- package/dist/excalidraw/actions/actionGroup.js +3 -2
- package/dist/excalidraw/actions/actionLinearEditor.d.ts +4 -4
- package/dist/excalidraw/actions/actionLinearEditor.js +1 -1
- package/dist/excalidraw/{element/Hyperlink.d.ts → actions/actionLink.d.ts} +28 -50
- package/dist/excalidraw/actions/actionLink.js +40 -0
- package/dist/excalidraw/actions/actionMenu.d.ts +11 -11
- package/dist/excalidraw/actions/actionNavigate.d.ts +8 -8
- package/dist/excalidraw/actions/actionNavigate.js +1 -1
- package/dist/excalidraw/actions/actionProperties.d.ts +64 -64
- package/dist/excalidraw/actions/actionProperties.js +32 -27
- package/dist/excalidraw/actions/actionSelectAll.d.ts +4 -4
- package/dist/excalidraw/actions/actionSelectAll.js +1 -1
- package/dist/excalidraw/actions/actionStyles.d.ts +6 -6
- package/dist/excalidraw/actions/actionStyles.js +4 -4
- package/dist/excalidraw/actions/actionToggleGridMode.d.ts +4 -4
- package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +4 -4
- package/dist/excalidraw/actions/actionToggleStats.d.ts +4 -4
- package/dist/excalidraw/actions/actionToggleViewMode.d.ts +4 -4
- package/dist/excalidraw/actions/actionToggleZenMode.d.ts +4 -4
- 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 +32 -17
- package/dist/excalidraw/components/App.js +474 -339
- 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 +16 -12
- 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/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/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/dropdownMenu/common.d.ts +1 -1
- 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 +8 -0
- package/dist/excalidraw/constants.js +10 -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 +11 -12
- 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 +35 -35
- 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/emitter.d.ts +5 -9
- package/dist/excalidraw/emitter.js +12 -12
- 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/index.d.ts +7 -3
- package/dist/excalidraw/index.js +14 -5
- 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 +11 -12
- package/dist/excalidraw/utility-types.d.ts +5 -0
- package/dist/excalidraw/utils.d.ts +25 -16
- package/dist/excalidraw/utils.js +52 -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 +45 -45
- package/dist/utils/export.d.ts +0 -6
- package/dist/utils/export.js +0 -6
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/withinBounds.js +2 -1
- 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-SUHLFFEF.js +0 -53449
- package/dist/browser/dev/excalidraw-assets-dev/chunk-SUHLFFEF.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/image-NOPDRTTM.css +0 -5797
- package/dist/browser/dev/excalidraw-assets-dev/image-NOPDRTTM.css.map +0 -7
- package/dist/browser/prod/excalidraw-assets/chunk-HE2P7BQ6.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-DZ6B4AID.js +0 -1
- package/dist/browser/prod/excalidraw-assets/image-J2QCCYAR.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-HYNUJ3XL.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,20 +170,52 @@ 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;
|
|
187
|
+
lastPointerMoveEvent = null;
|
|
169
188
|
lastViewportPosition = { x: 0, y: 0 };
|
|
170
|
-
|
|
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
|
+
});
|
|
171
208
|
onChangeEmitter = new Emitter();
|
|
172
209
|
onPointerDownEmitter = new Emitter();
|
|
173
210
|
onPointerUpEmitter = new Emitter();
|
|
174
211
|
onUserFollowEmitter = new Emitter();
|
|
175
212
|
onScrollChangeEmitter = new Emitter();
|
|
213
|
+
missingPointerEventCleanupEmitter = new Emitter();
|
|
214
|
+
onRemoveEventListenersEmitter = new Emitter();
|
|
176
215
|
constructor(props) {
|
|
177
216
|
super(props);
|
|
178
217
|
const defaultAppState = getDefaultAppState();
|
|
179
|
-
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;
|
|
180
219
|
this.state = {
|
|
181
220
|
...defaultAppState,
|
|
182
221
|
theme,
|
|
@@ -211,6 +250,7 @@ class App extends React.Component {
|
|
|
211
250
|
getSceneElements: this.getSceneElements,
|
|
212
251
|
getAppState: () => this.state,
|
|
213
252
|
getFiles: () => this.files,
|
|
253
|
+
getName: this.getName,
|
|
214
254
|
registerAction: (action) => {
|
|
215
255
|
this.actionManager.registerAction(action);
|
|
216
256
|
},
|
|
@@ -375,17 +415,20 @@ class App extends React.Component {
|
|
|
375
415
|
sceneY >= el.y + el.height / 3 &&
|
|
376
416
|
sceneY <= el.y + (2 * el.height) / 3);
|
|
377
417
|
}
|
|
418
|
+
updateEmbedValidationStatus = (element, status) => {
|
|
419
|
+
this.embedsValidationStatus.set(element.id, status);
|
|
420
|
+
ShapeCache.delete(element);
|
|
421
|
+
};
|
|
378
422
|
updateEmbeddables = () => {
|
|
379
423
|
const iframeLikes = new Set();
|
|
380
424
|
let updated = false;
|
|
381
425
|
this.scene.getNonDeletedElements().filter((element) => {
|
|
382
426
|
if (isEmbeddableElement(element)) {
|
|
383
427
|
iframeLikes.add(element.id);
|
|
384
|
-
if (element.
|
|
428
|
+
if (!this.embedsValidationStatus.has(element.id)) {
|
|
385
429
|
updated = true;
|
|
386
430
|
const validated = embeddableURLValidator(element.link, this.props.validateEmbeddable);
|
|
387
|
-
|
|
388
|
-
ShapeCache.delete(element);
|
|
431
|
+
this.updateEmbedValidationStatus(element, validated);
|
|
389
432
|
}
|
|
390
433
|
}
|
|
391
434
|
else if (isIframeElement(element)) {
|
|
@@ -409,9 +452,20 @@ class App extends React.Component {
|
|
|
409
452
|
const normalizedHeight = this.state.height;
|
|
410
453
|
const embeddableElements = this.scene
|
|
411
454
|
.getNonDeletedElements()
|
|
412
|
-
.filter((el) => (isEmbeddableElement(el) &&
|
|
455
|
+
.filter((el) => (isEmbeddableElement(el) &&
|
|
456
|
+
this.embedsValidationStatus.get(el.id) === true) ||
|
|
457
|
+
isIframeElement(el));
|
|
413
458
|
return (_jsx(_Fragment, { children: embeddableElements.map((el) => {
|
|
414
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
|
+
}
|
|
415
469
|
let src;
|
|
416
470
|
if (isIframeElement(el)) {
|
|
417
471
|
src = null;
|
|
@@ -551,8 +605,6 @@ class App extends React.Component {
|
|
|
551
605
|
else {
|
|
552
606
|
src = getEmbedLink(toValidURL(el.link || ""));
|
|
553
607
|
}
|
|
554
|
-
// console.log({ src });
|
|
555
|
-
const isVisible = isElementInViewport(el, normalizedWidth, normalizedHeight, this.state);
|
|
556
608
|
const isActive = this.state.activeEmbeddable?.element === el &&
|
|
557
609
|
this.state.activeEmbeddable?.state === "active";
|
|
558
610
|
const isHovered = this.state.activeEmbeddable?.element === el &&
|
|
@@ -564,7 +616,7 @@ class App extends React.Component {
|
|
|
564
616
|
? `translate(${x - this.state.offsetLeft}px, ${y - this.state.offsetTop}px) scale(${scale})`
|
|
565
617
|
: "none",
|
|
566
618
|
display: isVisible ? "block" : "none",
|
|
567
|
-
opacity: el
|
|
619
|
+
opacity: getRenderOpacity(el, getContainingFrame(el, this.scene.getNonDeletedElementsMap()), this.elementsPendingErasure),
|
|
568
620
|
["--embeddable-radius"]: `${getCornerRadius(Math.min(el.width, el.height), el)}px`,
|
|
569
621
|
}, children: _jsxs("div", {
|
|
570
622
|
//this is a hack that addresses isse with embedded excalidraw.com embeddable
|
|
@@ -656,16 +708,14 @@ class App extends React.Component {
|
|
|
656
708
|
scrollX: this.state.scrollX,
|
|
657
709
|
scrollY: this.state.scrollY,
|
|
658
710
|
zoom: this.state.zoom,
|
|
659
|
-
})) {
|
|
711
|
+
}, this.scene.getNonDeletedElementsMap())) {
|
|
660
712
|
// if frame not visible, don't render its name
|
|
661
713
|
return null;
|
|
662
714
|
}
|
|
663
715
|
const { x: x1, y: y1 } = sceneCoordsToViewportCoords({ sceneX: f.x, sceneY: f.y }, this.state);
|
|
664
716
|
const FRAME_NAME_EDIT_PADDING = 6;
|
|
665
717
|
const reset = () => {
|
|
666
|
-
|
|
667
|
-
mutateElement(f, { name: null });
|
|
668
|
-
}
|
|
718
|
+
mutateElement(f, { name: f.name?.trim() || null });
|
|
669
719
|
this.setState({ editingFrame: null });
|
|
670
720
|
};
|
|
671
721
|
let frameNameJSX;
|
|
@@ -676,7 +726,7 @@ class App extends React.Component {
|
|
|
676
726
|
mutateElement(f, {
|
|
677
727
|
name: e.target.value,
|
|
678
728
|
});
|
|
679
|
-
}, onBlur: () => reset(), onKeyDown: (event) => {
|
|
729
|
+
}, onFocus: (e) => e.target.select(), onBlur: () => reset(), onKeyDown: (event) => {
|
|
680
730
|
// for some inexplicable reason, `onBlur` triggered on ESC
|
|
681
731
|
// does not reset `state.editingFrame` despite being called,
|
|
682
732
|
// and we need to reset it here as well
|
|
@@ -737,11 +787,17 @@ class App extends React.Component {
|
|
|
737
787
|
}, children: frameNameJSX }, f.id));
|
|
738
788
|
});
|
|
739
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
|
+
}
|
|
740
796
|
render() {
|
|
741
797
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
742
798
|
const { renderTopRightUI, renderCustomStats } = this.props;
|
|
743
799
|
const versionNonce = this.scene.getVersionNonce();
|
|
744
|
-
const {
|
|
800
|
+
const { elementsMap, visibleElements } = this.renderer.getRenderableElements({
|
|
745
801
|
versionNonce,
|
|
746
802
|
zoom: this.state.zoom,
|
|
747
803
|
offsetLeft: this.state.offsetLeft,
|
|
@@ -753,6 +809,7 @@ class App extends React.Component {
|
|
|
753
809
|
editingElement: this.state.editingElement,
|
|
754
810
|
pendingImageElementId: this.state.pendingImageElementId,
|
|
755
811
|
});
|
|
812
|
+
const allElementsMap = this.scene.getNonDeletedElementsMap();
|
|
756
813
|
const shouldBlockPointerEvents = !(this.state.editingElement && isLinearElement(this.state.editingElement)) &&
|
|
757
814
|
(this.state.selectionElement ||
|
|
758
815
|
this.state.draggingElement ||
|
|
@@ -770,18 +827,18 @@ class App extends React.Component {
|
|
|
770
827
|
["--ui-pointerEvents"]: shouldBlockPointerEvents
|
|
771
828
|
? POINTER_EVENTS.disabled
|
|
772
829
|
: POINTER_EVENTS.enabled,
|
|
773
|
-
}, 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" &&
|
|
774
831
|
this.state.zenModeEnabled, UIOptions: this.props.UIOptions, onExportImage: this.onExportImage, renderWelcomeScreen: !this.state.isLoading &&
|
|
775
832
|
this.state.showWelcomeScreen &&
|
|
776
833
|
this.state.activeTool.type === "selection" &&
|
|
777
834
|
!this.state.zenModeEnabled &&
|
|
778
|
-
!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(
|
|
779
|
-
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 &&
|
|
780
837
|
selectedElements.length === 1 &&
|
|
781
|
-
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 &&
|
|
782
839
|
isIframeElement(firstSelectedElement) &&
|
|
783
840
|
firstSelectedElement.customData?.generationData
|
|
784
|
-
?.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: () => {
|
|
785
842
|
const iframe = this.getHTMLIFrameElement(firstSelectedElement);
|
|
786
843
|
if (iframe) {
|
|
787
844
|
try {
|
|
@@ -810,12 +867,14 @@ class App extends React.Component {
|
|
|
810
867
|
this.focusContainer();
|
|
811
868
|
callback?.();
|
|
812
869
|
});
|
|
813
|
-
} })), _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: {
|
|
814
871
|
imageCache: this.imageCache,
|
|
815
872
|
isExporting: false,
|
|
816
873
|
renderGrid: true,
|
|
817
874
|
canvasBackgroundColor: this.state.viewBackgroundColor,
|
|
818
|
-
|
|
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()] }) }) }) }) }) }) }) }));
|
|
819
878
|
}
|
|
820
879
|
focusContainer = () => {
|
|
821
880
|
this.excalidrawContainerRef.current?.focus();
|
|
@@ -837,7 +896,7 @@ class App extends React.Component {
|
|
|
837
896
|
trackEvent("export", type, "ui");
|
|
838
897
|
const fileHandle = await exportCanvas(type, elements, this.state, this.files, {
|
|
839
898
|
exportBackground: this.state.exportBackground,
|
|
840
|
-
name: this.
|
|
899
|
+
name: this.getName(),
|
|
841
900
|
viewBackgroundColor: this.state.viewBackgroundColor,
|
|
842
901
|
exportingFrame: opts.exportingFrame,
|
|
843
902
|
})
|
|
@@ -890,11 +949,7 @@ class App extends React.Component {
|
|
|
890
949
|
trackEvent("ai", "generate (missing key)", "d2c");
|
|
891
950
|
return;
|
|
892
951
|
}
|
|
893
|
-
const magicFrameChildren =
|
|
894
|
-
elements: this.scene.getNonDeletedElements(),
|
|
895
|
-
bounds: magicFrame,
|
|
896
|
-
type: "overlap",
|
|
897
|
-
}).filter((el) => !isMagicFrameElement(el));
|
|
952
|
+
const magicFrameChildren = getElementsOverlappingFrame(this.scene.getNonDeletedElements(), magicFrame).filter((el) => !isMagicFrameElement(el));
|
|
898
953
|
if (!magicFrameChildren.length) {
|
|
899
954
|
if (source === "button") {
|
|
900
955
|
this.setState({ errorMessage: "Cannot generate from an empty frame" });
|
|
@@ -1149,7 +1204,7 @@ class App extends React.Component {
|
|
|
1149
1204
|
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
|
|
1150
1205
|
let gridSize = actionResult?.appState?.gridSize || null;
|
|
1151
1206
|
const theme = actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
|
|
1152
|
-
|
|
1207
|
+
const name = actionResult?.appState?.name ?? this.state.name;
|
|
1153
1208
|
const errorMessage = actionResult?.appState?.errorMessage ?? this.state.errorMessage;
|
|
1154
1209
|
if (typeof this.props.viewModeEnabled !== "undefined") {
|
|
1155
1210
|
viewModeEnabled = this.props.viewModeEnabled;
|
|
@@ -1160,9 +1215,6 @@ class App extends React.Component {
|
|
|
1160
1215
|
if (typeof this.props.gridModeEnabled !== "undefined") {
|
|
1161
1216
|
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
|
|
1162
1217
|
}
|
|
1163
|
-
if (typeof this.props.name !== "undefined") {
|
|
1164
|
-
name = this.props.name;
|
|
1165
|
-
}
|
|
1166
1218
|
editingElement =
|
|
1167
1219
|
editingElement || actionResult.appState?.editingElement || null;
|
|
1168
1220
|
if (editingElement?.isDeleted) {
|
|
@@ -1413,14 +1465,16 @@ class App extends React.Component {
|
|
|
1413
1465
|
this.removeEventListeners();
|
|
1414
1466
|
this.scene.destroy();
|
|
1415
1467
|
this.library.destroy();
|
|
1416
|
-
this.
|
|
1417
|
-
this.
|
|
1468
|
+
this.laserTrails.stop();
|
|
1469
|
+
this.eraserTrail.stop();
|
|
1470
|
+
this.onChangeEmitter.clear();
|
|
1418
1471
|
ShapeCache.destroy();
|
|
1419
1472
|
SnapCache.destroy();
|
|
1420
1473
|
clearTimeout(touchTimeout);
|
|
1421
1474
|
isSomeElementSelected.clearCache();
|
|
1422
1475
|
selectGroupsForSelectedElements.clearCache();
|
|
1423
1476
|
touchTimeout = 0;
|
|
1477
|
+
document.documentElement.style.overscrollBehaviorX = "";
|
|
1424
1478
|
}
|
|
1425
1479
|
onResize = withBatchedUpdates(() => {
|
|
1426
1480
|
this.scene
|
|
@@ -1433,27 +1487,6 @@ class App extends React.Component {
|
|
|
1433
1487
|
}
|
|
1434
1488
|
this.setState({});
|
|
1435
1489
|
});
|
|
1436
|
-
removeEventListeners() {
|
|
1437
|
-
document.removeEventListener(EVENT.POINTER_UP, this.removePointer);
|
|
1438
|
-
document.removeEventListener(EVENT.COPY, this.onCopy);
|
|
1439
|
-
document.removeEventListener(EVENT.PASTE, this.pasteFromClipboard);
|
|
1440
|
-
document.removeEventListener(EVENT.CUT, this.onCut);
|
|
1441
|
-
this.excalidrawContainerRef.current?.removeEventListener(EVENT.WHEEL, this.onWheel);
|
|
1442
|
-
this.nearestScrollableContainer?.removeEventListener(EVENT.SCROLL, this.onScroll);
|
|
1443
|
-
document.removeEventListener(EVENT.KEYDOWN, this.onKeyDown, false);
|
|
1444
|
-
document.removeEventListener(EVENT.MOUSE_MOVE, this.updateCurrentCursorPosition, false);
|
|
1445
|
-
document.removeEventListener(EVENT.KEYUP, this.onKeyUp);
|
|
1446
|
-
window.removeEventListener(EVENT.RESIZE, this.onResize, false);
|
|
1447
|
-
window.removeEventListener(EVENT.UNLOAD, this.onUnload, false);
|
|
1448
|
-
window.removeEventListener(EVENT.BLUR, this.onBlur, false);
|
|
1449
|
-
this.excalidrawContainerRef.current?.removeEventListener(EVENT.DRAG_OVER, this.disableEvent, false);
|
|
1450
|
-
this.excalidrawContainerRef.current?.removeEventListener(EVENT.DROP, this.disableEvent, false);
|
|
1451
|
-
document.removeEventListener(EVENT.GESTURE_START, this.onGestureStart, false);
|
|
1452
|
-
document.removeEventListener(EVENT.GESTURE_CHANGE, this.onGestureChange, false);
|
|
1453
|
-
document.removeEventListener(EVENT.GESTURE_END, this.onGestureEnd, false);
|
|
1454
|
-
document.removeEventListener(EVENT.FULLSCREENCHANGE, this.onFullscreenChange);
|
|
1455
|
-
window.removeEventListener(EVENT.MESSAGE, this.onWindowMessage, false);
|
|
1456
|
-
}
|
|
1457
1490
|
/** generally invoked only if fullscreen was invoked programmatically */
|
|
1458
1491
|
onFullscreenChange = () => {
|
|
1459
1492
|
if (
|
|
@@ -1465,46 +1498,45 @@ class App extends React.Component {
|
|
|
1465
1498
|
});
|
|
1466
1499
|
}
|
|
1467
1500
|
};
|
|
1501
|
+
removeEventListeners() {
|
|
1502
|
+
this.onRemoveEventListenersEmitter.trigger();
|
|
1503
|
+
}
|
|
1468
1504
|
addEventListeners() {
|
|
1505
|
+
// remove first as we can add event listeners multiple times
|
|
1469
1506
|
this.removeEventListeners();
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
this.excalidrawContainerRef.current?.addEventListener(EVENT.WHEEL, this.onWheel, { passive: false });
|
|
1507
|
+
// -------------------------------------------------------------------------
|
|
1508
|
+
// view+edit mode listeners
|
|
1509
|
+
// -------------------------------------------------------------------------
|
|
1474
1510
|
if (this.props.handleKeyboardGlobally) {
|
|
1475
|
-
|
|
1511
|
+
this.onRemoveEventListenersEmitter.once(addEventListener(document, EVENT.KEYDOWN, this.onKeyDown, false));
|
|
1476
1512
|
}
|
|
1477
|
-
|
|
1478
|
-
document.addEventListener(EVENT.MOUSE_MOVE, this.updateCurrentCursorPosition)
|
|
1513
|
+
this.onRemoveEventListenersEmitter.once(addEventListener(this.excalidrawContainerRef.current, EVENT.WHEEL, this.onWheel, { passive: false }), addEventListener(window, EVENT.MESSAGE, this.onWindowMessage, false), addEventListener(document, EVENT.POINTER_UP, this.removePointer), // #3553
|
|
1514
|
+
addEventListener(document, EVENT.COPY, this.onCopy), addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }), addEventListener(document, EVENT.MOUSE_MOVE, this.updateCurrentCursorPosition),
|
|
1479
1515
|
// rerender text elements on font load to fix #637 && #1553
|
|
1480
|
-
document.fonts
|
|
1516
|
+
addEventListener(document.fonts, "loadingdone", (event) => {
|
|
1481
1517
|
const loadedFontFaces = event.fontfaces;
|
|
1482
1518
|
this.fonts.onFontsLoaded(loadedFontFaces);
|
|
1483
|
-
})
|
|
1519
|
+
}),
|
|
1484
1520
|
// Safari-only desktop pinch zoom
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1521
|
+
addEventListener(document, EVENT.GESTURE_START, this.onGestureStart, false), addEventListener(document, EVENT.GESTURE_CHANGE, this.onGestureChange, false), addEventListener(document, EVENT.GESTURE_END, this.onGestureEnd, false), addEventListener(window, EVENT.FOCUS, () => {
|
|
1522
|
+
this.maybeCleanupAfterMissingPointerUp(null);
|
|
1523
|
+
}));
|
|
1488
1524
|
if (this.state.viewModeEnabled) {
|
|
1489
1525
|
return;
|
|
1490
1526
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1527
|
+
// -------------------------------------------------------------------------
|
|
1528
|
+
// edit-mode listeners only
|
|
1529
|
+
// -------------------------------------------------------------------------
|
|
1530
|
+
this.onRemoveEventListenersEmitter.once(addEventListener(document, EVENT.FULLSCREENCHANGE, this.onFullscreenChange), addEventListener(document, EVENT.PASTE, this.pasteFromClipboard), addEventListener(document, EVENT.CUT, this.onCut), addEventListener(window, EVENT.RESIZE, this.onResize, false), addEventListener(window, EVENT.UNLOAD, this.onUnload, false), addEventListener(window, EVENT.BLUR, this.onBlur, false), addEventListener(this.excalidrawContainerRef.current, EVENT.DRAG_OVER, this.disableEvent, false), addEventListener(this.excalidrawContainerRef.current, EVENT.DROP, this.disableEvent, false));
|
|
1494
1531
|
if (this.props.detectScroll) {
|
|
1495
|
-
this.
|
|
1496
|
-
|
|
1497
|
-
}
|
|
1498
|
-
window.addEventListener(EVENT.RESIZE, this.onResize, false);
|
|
1499
|
-
window.addEventListener(EVENT.UNLOAD, this.onUnload, false);
|
|
1500
|
-
window.addEventListener(EVENT.BLUR, this.onBlur, false);
|
|
1501
|
-
this.excalidrawContainerRef.current?.addEventListener(EVENT.DRAG_OVER, this.disableEvent, false);
|
|
1502
|
-
this.excalidrawContainerRef.current?.addEventListener(EVENT.DROP, this.disableEvent, false);
|
|
1532
|
+
this.onRemoveEventListenersEmitter.once(addEventListener(getNearestScrollableContainer(this.excalidrawContainerRef.current), EVENT.SCROLL, this.onScroll));
|
|
1533
|
+
}
|
|
1503
1534
|
}
|
|
1504
1535
|
componentDidUpdate(prevProps, prevState) {
|
|
1505
1536
|
this.updateEmbeddables();
|
|
1506
|
-
|
|
1507
|
-
|
|
1537
|
+
const elements = this.scene.getElementsIncludingDeleted();
|
|
1538
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
1539
|
+
if (!this.state.showWelcomeScreen && !elements.length) {
|
|
1508
1540
|
this.setState({ showWelcomeScreen: true });
|
|
1509
1541
|
}
|
|
1510
1542
|
if (prevProps.UIOptions.dockedSidebarBreakpoint !==
|
|
@@ -1555,6 +1587,9 @@ class App extends React.Component {
|
|
|
1555
1587
|
if (prevProps.langCode !== this.props.langCode) {
|
|
1556
1588
|
this.updateLanguage();
|
|
1557
1589
|
}
|
|
1590
|
+
if (isEraserActive(prevState) && !isEraserActive(this.state)) {
|
|
1591
|
+
this.eraserTrail.endPath();
|
|
1592
|
+
}
|
|
1558
1593
|
if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) {
|
|
1559
1594
|
this.setState({ viewModeEnabled: !!this.props.viewModeEnabled });
|
|
1560
1595
|
}
|
|
@@ -1573,11 +1608,6 @@ class App extends React.Component {
|
|
|
1573
1608
|
gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
|
|
1574
1609
|
});
|
|
1575
1610
|
}
|
|
1576
|
-
if (this.props.name && prevProps.name !== this.props.name) {
|
|
1577
|
-
this.setState({
|
|
1578
|
-
name: this.props.name,
|
|
1579
|
-
});
|
|
1580
|
-
}
|
|
1581
1611
|
this.excalidrawContainerRef.current?.classList.toggle("theme--dark", this.state.theme === "dark");
|
|
1582
1612
|
if (this.state.editingLinearElement &&
|
|
1583
1613
|
!this.state.selectedElementIds[this.state.editingLinearElement.elementId]) {
|
|
@@ -1606,19 +1636,19 @@ class App extends React.Component {
|
|
|
1606
1636
|
multiElement != null &&
|
|
1607
1637
|
isBindingEnabled(this.state) &&
|
|
1608
1638
|
isBindingElement(multiElement, false)) {
|
|
1609
|
-
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);
|
|
1610
1640
|
}
|
|
1611
|
-
this.history.record(this.state,
|
|
1641
|
+
this.history.record(this.state, elements);
|
|
1612
1642
|
// Do not notify consumers if we're still loading the scene. Among other
|
|
1613
1643
|
// potential issues, this fixes a case where the tab isn't focused during
|
|
1614
1644
|
// init, which would trigger onChange with empty elements, which would then
|
|
1615
1645
|
// override whatever is in localStorage currently.
|
|
1616
1646
|
if (!this.state.isLoading) {
|
|
1617
|
-
this.props.onChange?.(
|
|
1618
|
-
this.onChangeEmitter.trigger(
|
|
1647
|
+
this.props.onChange?.(elements, this.state, this.files);
|
|
1648
|
+
this.onChangeEmitter.trigger(elements, this.state, this.files);
|
|
1619
1649
|
}
|
|
1620
1650
|
}
|
|
1621
|
-
renderInteractiveSceneCallback = ({ atLeastOneVisibleElement, scrollBars,
|
|
1651
|
+
renderInteractiveSceneCallback = ({ atLeastOneVisibleElement, scrollBars, elementsMap, }) => {
|
|
1622
1652
|
if (scrollBars) {
|
|
1623
1653
|
currentScrollBars = scrollBars;
|
|
1624
1654
|
}
|
|
@@ -1626,7 +1656,7 @@ class App extends React.Component {
|
|
|
1626
1656
|
// hide when editing text
|
|
1627
1657
|
isTextElement(this.state.editingElement)
|
|
1628
1658
|
? false
|
|
1629
|
-
: !atLeastOneVisibleElement &&
|
|
1659
|
+
: !atLeastOneVisibleElement && elementsMap.size > 0;
|
|
1630
1660
|
if (this.state.scrolledOutside !== scrolledOutside) {
|
|
1631
1661
|
this.setState({ scrolledOutside });
|
|
1632
1662
|
}
|
|
@@ -1664,9 +1694,8 @@ class App extends React.Component {
|
|
|
1664
1694
|
didTapTwice = false;
|
|
1665
1695
|
}
|
|
1666
1696
|
onTouchStart = (event) => {
|
|
1667
|
-
// fix for Apple Pencil Scribble
|
|
1668
|
-
|
|
1669
|
-
if (!isAndroid) {
|
|
1697
|
+
// fix for Apple Pencil Scribble (do not prevent for other devices)
|
|
1698
|
+
if (isIOS) {
|
|
1670
1699
|
event.preventDefault();
|
|
1671
1700
|
}
|
|
1672
1701
|
if (!didTapTwice) {
|
|
@@ -1687,9 +1716,6 @@ class App extends React.Component {
|
|
|
1687
1716
|
didTapTwice = false;
|
|
1688
1717
|
clearTimeout(tappedTwiceTimer);
|
|
1689
1718
|
}
|
|
1690
|
-
if (isAndroid) {
|
|
1691
|
-
event.preventDefault();
|
|
1692
|
-
}
|
|
1693
1719
|
if (event.touches.length === 2) {
|
|
1694
1720
|
this.setState({
|
|
1695
1721
|
selectedElementIds: makeNextSelectedElementIds({}, this.state),
|
|
@@ -1799,18 +1825,40 @@ class App extends React.Component {
|
|
|
1799
1825
|
});
|
|
1800
1826
|
}
|
|
1801
1827
|
else if (data.text) {
|
|
1802
|
-
const
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
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
|
+
});
|
|
1814
1862
|
}
|
|
1815
1863
|
return;
|
|
1816
1864
|
}
|
|
@@ -1846,15 +1894,20 @@ class App extends React.Component {
|
|
|
1846
1894
|
}), {
|
|
1847
1895
|
randomizeSeed: !opts.retainSeed,
|
|
1848
1896
|
});
|
|
1849
|
-
const
|
|
1897
|
+
const allElements = [
|
|
1850
1898
|
...this.scene.getElementsIncludingDeleted(),
|
|
1851
1899
|
...newElements,
|
|
1852
1900
|
];
|
|
1853
|
-
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);
|
|
1854
1907
|
newElements.forEach((newElement) => {
|
|
1855
1908
|
if (isTextElement(newElement) && isBoundToContainer(newElement)) {
|
|
1856
|
-
const container = getContainerElement(newElement);
|
|
1857
|
-
redrawTextBoundingBox(newElement, container);
|
|
1909
|
+
const container = getContainerElement(newElement, this.scene.getElementsMapIncludingDeleted());
|
|
1910
|
+
redrawTextBoundingBox(newElement, container, this.scene.getElementsMapIncludingDeleted());
|
|
1858
1911
|
}
|
|
1859
1912
|
});
|
|
1860
1913
|
if (opts.files) {
|
|
@@ -1909,7 +1962,14 @@ class App extends React.Component {
|
|
|
1909
1962
|
return { file: await ImageURLToFile(url) };
|
|
1910
1963
|
}
|
|
1911
1964
|
catch (error) {
|
|
1912
|
-
|
|
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 };
|
|
1913
1973
|
}
|
|
1914
1974
|
}));
|
|
1915
1975
|
let y = sceneY;
|
|
@@ -2377,7 +2437,7 @@ class App extends React.Component {
|
|
|
2377
2437
|
x: element.x + offsetX,
|
|
2378
2438
|
y: element.y + offsetY,
|
|
2379
2439
|
});
|
|
2380
|
-
updateBoundElements(element, {
|
|
2440
|
+
updateBoundElements(element, this.scene.getNonDeletedElementsMap(), {
|
|
2381
2441
|
simultaneouslyUpdated: selectedElements,
|
|
2382
2442
|
});
|
|
2383
2443
|
});
|
|
@@ -2395,7 +2455,7 @@ class App extends React.Component {
|
|
|
2395
2455
|
selectedElements[0].id) {
|
|
2396
2456
|
this.history.resumeRecording();
|
|
2397
2457
|
this.setState({
|
|
2398
|
-
editingLinearElement: new LinearElementEditor(selectedElement
|
|
2458
|
+
editingLinearElement: new LinearElementEditor(selectedElement),
|
|
2399
2459
|
});
|
|
2400
2460
|
}
|
|
2401
2461
|
}
|
|
@@ -2406,7 +2466,7 @@ class App extends React.Component {
|
|
|
2406
2466
|
if (!isTextElement(selectedElement)) {
|
|
2407
2467
|
container = selectedElement;
|
|
2408
2468
|
}
|
|
2409
|
-
const midPoint = getContainerCenter(selectedElement, this.state);
|
|
2469
|
+
const midPoint = getContainerCenter(selectedElement, this.state, this.scene.getNonDeletedElementsMap());
|
|
2410
2470
|
const sceneX = midPoint.x;
|
|
2411
2471
|
const sceneY = midPoint.y;
|
|
2412
2472
|
this.startTextEditing({
|
|
@@ -2520,9 +2580,10 @@ class App extends React.Component {
|
|
|
2520
2580
|
}
|
|
2521
2581
|
if (isArrowKey(event.key)) {
|
|
2522
2582
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
2583
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
2523
2584
|
isBindingEnabled(this.state)
|
|
2524
|
-
? bindOrUnbindSelectedElements(selectedElements)
|
|
2525
|
-
: unbindLinearElements(selectedElements);
|
|
2585
|
+
? bindOrUnbindSelectedElements(selectedElements, this.scene.getNonDeletedElements(), elementsMap)
|
|
2586
|
+
: unbindLinearElements(selectedElements, elementsMap);
|
|
2526
2587
|
this.setState({ suggestedBindings: [] });
|
|
2527
2588
|
}
|
|
2528
2589
|
});
|
|
@@ -2599,6 +2660,11 @@ class App extends React.Component {
|
|
|
2599
2660
|
// touchscreen
|
|
2600
2661
|
return gesture.pointers.size >= 2;
|
|
2601
2662
|
};
|
|
2663
|
+
getName = () => {
|
|
2664
|
+
return (this.state.name ||
|
|
2665
|
+
this.props.name ||
|
|
2666
|
+
`${t("labels.untitled")}-${getDateTime()}`);
|
|
2667
|
+
};
|
|
2602
2668
|
// fires only on Safari
|
|
2603
2669
|
onGestureStart = withBatchedUpdates((event) => {
|
|
2604
2670
|
event.preventDefault();
|
|
@@ -2651,11 +2717,13 @@ class App extends React.Component {
|
|
|
2651
2717
|
gesture.initialScale = null;
|
|
2652
2718
|
});
|
|
2653
2719
|
handleTextWysiwyg(element, { isExistingElement = false, }) {
|
|
2720
|
+
const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
|
2654
2721
|
const updateElement = (text, originalText, isDeleted) => {
|
|
2655
2722
|
this.scene.replaceAllElements([
|
|
2723
|
+
// Not sure why we include deleted elements as well hence using deleted elements map
|
|
2656
2724
|
...this.scene.getElementsIncludingDeleted().map((_element) => {
|
|
2657
2725
|
if (_element.id === element.id && isTextElement(_element)) {
|
|
2658
|
-
return updateTextElement(_element, {
|
|
2726
|
+
return updateTextElement(_element, getContainerElement(_element, elementsMap), elementsMap, {
|
|
2659
2727
|
text,
|
|
2660
2728
|
isDeleted,
|
|
2661
2729
|
originalText,
|
|
@@ -2681,7 +2749,7 @@ class App extends React.Component {
|
|
|
2681
2749
|
onChange: withBatchedUpdates((text) => {
|
|
2682
2750
|
updateElement(text, text, false);
|
|
2683
2751
|
if (isNonDeletedElement(element)) {
|
|
2684
|
-
updateBoundElements(element);
|
|
2752
|
+
updateBoundElements(element, elementsMap);
|
|
2685
2753
|
}
|
|
2686
2754
|
}),
|
|
2687
2755
|
onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
|
|
@@ -2757,7 +2825,7 @@ class App extends React.Component {
|
|
|
2757
2825
|
const elementWithHighestZIndex = allHitElements[allHitElements.length - 1];
|
|
2758
2826
|
// If we're hitting element with highest z-index only on its bounding box
|
|
2759
2827
|
// while also hitting other element figure, the latter should be considered.
|
|
2760
|
-
return isHittingElementBoundingBoxWithoutHittingElement(elementWithHighestZIndex, this.state, this.frameNameBoundsCache, x, y)
|
|
2828
|
+
return isHittingElementBoundingBoxWithoutHittingElement(elementWithHighestZIndex, this.state, this.frameNameBoundsCache, x, y, this.scene.getNonDeletedElementsMap())
|
|
2761
2829
|
? allHitElements[allHitElements.length - 2]
|
|
2762
2830
|
: elementWithHighestZIndex;
|
|
2763
2831
|
}
|
|
@@ -2774,13 +2842,14 @@ class App extends React.Component {
|
|
|
2774
2842
|
.filter((element) => (includeLockedElements || !element.locked) &&
|
|
2775
2843
|
(includeBoundTextElement ||
|
|
2776
2844
|
!(isTextElement(element) && element.containerId)));
|
|
2777
|
-
|
|
2845
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
2846
|
+
return getElementsAtPosition(elements, (element) => hitTest(element, this.state, this.frameNameBoundsCache, x, y, elementsMap)).filter((element) => {
|
|
2778
2847
|
// hitting a frame's element from outside the frame is not considered a hit
|
|
2779
|
-
const containingFrame = getContainingFrame(element);
|
|
2848
|
+
const containingFrame = getContainingFrame(element, elementsMap);
|
|
2780
2849
|
return containingFrame &&
|
|
2781
2850
|
this.state.frameRendering.enabled &&
|
|
2782
2851
|
this.state.frameRendering.clip
|
|
2783
|
-
? isCursorInFrame({ x, y }, containingFrame)
|
|
2852
|
+
? isCursorInFrame({ x, y }, containingFrame, elementsMap)
|
|
2784
2853
|
: true;
|
|
2785
2854
|
});
|
|
2786
2855
|
}
|
|
@@ -2789,7 +2858,7 @@ class App extends React.Component {
|
|
|
2789
2858
|
let parentCenterPosition = insertAtParentCenter &&
|
|
2790
2859
|
this.getTextWysiwygSnappedToCenterPosition(sceneX, sceneY, this.state, container);
|
|
2791
2860
|
if (container && parentCenterPosition) {
|
|
2792
|
-
const boundTextElementToContainer = getBoundTextElement(container);
|
|
2861
|
+
const boundTextElementToContainer = getBoundTextElement(container, this.scene.getNonDeletedElementsMap());
|
|
2793
2862
|
if (!boundTextElementToContainer) {
|
|
2794
2863
|
shouldBindToContainer = true;
|
|
2795
2864
|
}
|
|
@@ -2801,7 +2870,7 @@ class App extends React.Component {
|
|
|
2801
2870
|
existingTextElement = selectedElements[0];
|
|
2802
2871
|
}
|
|
2803
2872
|
else if (container) {
|
|
2804
|
-
existingTextElement = getBoundTextElement(selectedElements[0]);
|
|
2873
|
+
existingTextElement = getBoundTextElement(selectedElements[0], this.scene.getNonDeletedElementsMap());
|
|
2805
2874
|
}
|
|
2806
2875
|
else {
|
|
2807
2876
|
existingTextElement = this.getTextElementAtPosition(sceneX, sceneY);
|
|
@@ -2909,7 +2978,7 @@ class App extends React.Component {
|
|
|
2909
2978
|
this.state.editingLinearElement.elementId !== selectedElements[0].id)) {
|
|
2910
2979
|
this.history.resumeRecording();
|
|
2911
2980
|
this.setState({
|
|
2912
|
-
editingLinearElement: new LinearElementEditor(selectedElements[0]
|
|
2981
|
+
editingLinearElement: new LinearElementEditor(selectedElements[0]),
|
|
2913
2982
|
});
|
|
2914
2983
|
return;
|
|
2915
2984
|
}
|
|
@@ -2945,12 +3014,12 @@ class App extends React.Component {
|
|
|
2945
3014
|
});
|
|
2946
3015
|
return;
|
|
2947
3016
|
}
|
|
2948
|
-
const container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY);
|
|
3017
|
+
const container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
|
|
2949
3018
|
if (container) {
|
|
2950
3019
|
if (hasBoundTextElement(container) ||
|
|
2951
3020
|
!isTransparent(container.backgroundColor) ||
|
|
2952
|
-
isHittingElementNotConsideringBoundingBox(container, this.state, this.frameNameBoundsCache, [sceneX, sceneY])) {
|
|
2953
|
-
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());
|
|
2954
3023
|
sceneX = midPoint.x;
|
|
2955
3024
|
sceneY = midPoint.y;
|
|
2956
3025
|
}
|
|
@@ -2974,7 +3043,7 @@ class App extends React.Component {
|
|
|
2974
3043
|
}
|
|
2975
3044
|
return (element.link &&
|
|
2976
3045
|
index <= hitElementIndex &&
|
|
2977
|
-
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));
|
|
2978
3047
|
});
|
|
2979
3048
|
};
|
|
2980
3049
|
redirectToLink = (event, isTouchScreen) => {
|
|
@@ -2986,9 +3055,10 @@ class App extends React.Component {
|
|
|
2986
3055
|
return;
|
|
2987
3056
|
}
|
|
2988
3057
|
const lastPointerDownCoords = viewportCoordsToSceneCoords(this.lastPointerDownEvent, this.state);
|
|
2989
|
-
const
|
|
3058
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
3059
|
+
const lastPointerDownHittingLinkIcon = isPointHittingLink(this.hitLinkElement, elementsMap, this.state, [lastPointerDownCoords.x, lastPointerDownCoords.y], this.device.editor.isMobile);
|
|
2990
3060
|
const lastPointerUpCoords = viewportCoordsToSceneCoords(this.lastPointerUpEvent, this.state);
|
|
2991
|
-
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);
|
|
2992
3062
|
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
|
2993
3063
|
let url = this.hitLinkElement.link;
|
|
2994
3064
|
if (url) {
|
|
@@ -3014,13 +3084,15 @@ class App extends React.Component {
|
|
|
3014
3084
|
}
|
|
3015
3085
|
};
|
|
3016
3086
|
getTopLayerFrameAtSceneCoords = (sceneCoords) => {
|
|
3087
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
3017
3088
|
const frames = this.scene
|
|
3018
3089
|
.getNonDeletedFramesLikes()
|
|
3019
|
-
.filter((frame) => isCursorInFrame(sceneCoords, frame));
|
|
3090
|
+
.filter((frame) => isCursorInFrame(sceneCoords, frame, elementsMap));
|
|
3020
3091
|
return frames.length ? frames[frames.length - 1] : null;
|
|
3021
3092
|
};
|
|
3022
3093
|
handleCanvasPointerMove = (event) => {
|
|
3023
3094
|
this.savePointer(event.clientX, event.clientY, this.state.cursorButton);
|
|
3095
|
+
this.lastPointerMoveEvent = event.nativeEvent;
|
|
3024
3096
|
if (gesture.pointers.has(event.pointerId)) {
|
|
3025
3097
|
gesture.pointers.set(event.pointerId, {
|
|
3026
3098
|
x: event.clientX,
|
|
@@ -3087,7 +3159,7 @@ class App extends React.Component {
|
|
|
3087
3159
|
const { originOffset, snapLines } = getSnapLinesAtPointer(this.scene.getNonDeletedElements(), this.state, {
|
|
3088
3160
|
x: scenePointerX,
|
|
3089
3161
|
y: scenePointerY,
|
|
3090
|
-
}, event);
|
|
3162
|
+
}, event, this.scene.getNonDeletedElementsMap());
|
|
3091
3163
|
this.setState((prevState) => {
|
|
3092
3164
|
const nextSnapLines = updateStable(prevState.snapLines, snapLines);
|
|
3093
3165
|
const nextOriginOffset = prevState.originSnapOffset
|
|
@@ -3115,7 +3187,7 @@ class App extends React.Component {
|
|
|
3115
3187
|
}
|
|
3116
3188
|
if (this.state.editingLinearElement &&
|
|
3117
3189
|
!this.state.editingLinearElement.isDragging) {
|
|
3118
|
-
const editingLinearElement = LinearElementEditor.handlePointerMove(event, scenePointerX, scenePointerY, this.state);
|
|
3190
|
+
const editingLinearElement = LinearElementEditor.handlePointerMove(event, scenePointerX, scenePointerY, this.state, this.scene.getNonDeletedElementsMap());
|
|
3119
3191
|
if (editingLinearElement &&
|
|
3120
3192
|
editingLinearElement !== this.state.editingLinearElement) {
|
|
3121
3193
|
// Since we are reading from previous state which is not possible with
|
|
@@ -3217,7 +3289,7 @@ class App extends React.Component {
|
|
|
3217
3289
|
if (selectedElements.length === 1 &&
|
|
3218
3290
|
!isOverScrollBar &&
|
|
3219
3291
|
!this.state.editingLinearElement) {
|
|
3220
|
-
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());
|
|
3221
3293
|
if (elementWithTransformHandleType &&
|
|
3222
3294
|
elementWithTransformHandleType.transformHandleType) {
|
|
3223
3295
|
setCursor(this.interactiveCanvas, getCursorForResizingElement(elementWithTransformHandleType));
|
|
@@ -3241,7 +3313,7 @@ class App extends React.Component {
|
|
|
3241
3313
|
if (this.hitLinkElement &&
|
|
3242
3314
|
!this.state.selectedElementIds[this.hitLinkElement.id]) {
|
|
3243
3315
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
|
3244
|
-
showHyperlinkTooltip(this.hitLinkElement, this.state);
|
|
3316
|
+
showHyperlinkTooltip(this.hitLinkElement, this.state, this.scene.getNonDeletedElementsMap());
|
|
3245
3317
|
}
|
|
3246
3318
|
else {
|
|
3247
3319
|
hideHyperlinkToolip();
|
|
@@ -3292,34 +3364,49 @@ class App extends React.Component {
|
|
|
3292
3364
|
}
|
|
3293
3365
|
};
|
|
3294
3366
|
handleEraser = (event, pointerDownState, scenePointer) => {
|
|
3295
|
-
|
|
3296
|
-
|
|
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) {
|
|
3297
3373
|
if (element.locked) {
|
|
3298
3374
|
return;
|
|
3299
3375
|
}
|
|
3300
|
-
idsToUpdate.push(element.id);
|
|
3301
3376
|
if (event.altKey) {
|
|
3302
|
-
if (
|
|
3303
|
-
|
|
3304
|
-
pointerDownState.elementIdsToErase[element.id].erase = false;
|
|
3377
|
+
if (this.elementsPendingErasure.delete(element.id)) {
|
|
3378
|
+
didChange = true;
|
|
3305
3379
|
}
|
|
3306
3380
|
}
|
|
3307
|
-
else if (!
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
opacity: element.opacity,
|
|
3311
|
-
};
|
|
3381
|
+
else if (!this.elementsPendingErasure.has(element.id)) {
|
|
3382
|
+
didChange = true;
|
|
3383
|
+
this.elementsPendingErasure.add(element.id);
|
|
3312
3384
|
}
|
|
3313
|
-
|
|
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
|
+
}
|
|
3314
3402
|
};
|
|
3315
|
-
const idsToUpdate = [];
|
|
3316
3403
|
const distance = distance2d(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y, scenePointer.x, scenePointer.y);
|
|
3317
3404
|
const threshold = 10 / this.state.zoom.value;
|
|
3318
3405
|
const point = { ...pointerDownState.lastCoords };
|
|
3319
3406
|
let samplingInterval = 0;
|
|
3320
3407
|
while (samplingInterval <= distance) {
|
|
3321
3408
|
const hitElements = this.getElementsAtPosition(point.x, point.y);
|
|
3322
|
-
|
|
3409
|
+
processElements(hitElements);
|
|
3323
3410
|
// Exit since we reached current point
|
|
3324
3411
|
if (samplingInterval === distance) {
|
|
3325
3412
|
break;
|
|
@@ -3332,48 +3419,45 @@ class App extends React.Component {
|
|
|
3332
3419
|
point.x = nextX;
|
|
3333
3420
|
point.y = nextY;
|
|
3334
3421
|
}
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
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);
|
|
3346
3436
|
}
|
|
3347
|
-
}
|
|
3348
|
-
else {
|
|
3349
|
-
return newElementWith(ele, {
|
|
3350
|
-
opacity: ELEMENT_READY_TO_ERASE_OPACITY,
|
|
3351
|
-
});
|
|
3352
3437
|
}
|
|
3353
3438
|
}
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
pointerDownState.lastCoords.x = scenePointer.x;
|
|
3358
|
-
pointerDownState.lastCoords.y = scenePointer.y;
|
|
3439
|
+
this.elementsPendingErasure = new Set(this.elementsPendingErasure);
|
|
3440
|
+
this.onSceneUpdated();
|
|
3441
|
+
}
|
|
3359
3442
|
};
|
|
3360
3443
|
// set touch moving for mobile context menu
|
|
3361
3444
|
handleTouchMove = (event) => {
|
|
3362
3445
|
invalidateContextMenu = true;
|
|
3363
3446
|
};
|
|
3364
3447
|
handleHoverSelectedLinearElement(linearElementEditor, scenePointerX, scenePointerY) {
|
|
3365
|
-
const
|
|
3366
|
-
const
|
|
3448
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
3449
|
+
const element = LinearElementEditor.getElement(linearElementEditor.elementId, elementsMap);
|
|
3450
|
+
const boundTextElement = getBoundTextElement(element, elementsMap);
|
|
3367
3451
|
if (!element) {
|
|
3368
3452
|
return;
|
|
3369
3453
|
}
|
|
3370
3454
|
if (this.state.selectedLinearElement) {
|
|
3371
3455
|
let hoverPointIndex = -1;
|
|
3372
3456
|
let segmentMidPointHoveredCoords = null;
|
|
3373
|
-
if (isHittingElementNotConsideringBoundingBox(element, this.state, this.frameNameBoundsCache, [scenePointerX, scenePointerY])) {
|
|
3374
|
-
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);
|
|
3375
3459
|
segmentMidPointHoveredCoords =
|
|
3376
|
-
LinearElementEditor.getSegmentMidpointHitCoords(linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state);
|
|
3460
|
+
LinearElementEditor.getSegmentMidpointHitCoords(linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state, this.scene.getNonDeletedElementsMap());
|
|
3377
3461
|
if (hoverPointIndex >= 0 || segmentMidPointHoveredCoords) {
|
|
3378
3462
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
|
3379
3463
|
}
|
|
@@ -3382,11 +3466,11 @@ class App extends React.Component {
|
|
|
3382
3466
|
}
|
|
3383
3467
|
}
|
|
3384
3468
|
else if (shouldShowBoundingBox([element], this.state) &&
|
|
3385
|
-
isHittingElementBoundingBoxWithoutHittingElement(element, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY)) {
|
|
3469
|
+
isHittingElementBoundingBoxWithoutHittingElement(element, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, elementsMap)) {
|
|
3386
3470
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
|
|
3387
3471
|
}
|
|
3388
3472
|
else if (boundTextElement &&
|
|
3389
|
-
hitTest(boundTextElement, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY)) {
|
|
3473
|
+
hitTest(boundTextElement, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, this.scene.getNonDeletedElementsMap())) {
|
|
3390
3474
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
|
|
3391
3475
|
}
|
|
3392
3476
|
if (this.state.selectedLinearElement.hoverPointIndex !== hoverPointIndex) {
|
|
@@ -3411,6 +3495,7 @@ class App extends React.Component {
|
|
|
3411
3495
|
}
|
|
3412
3496
|
}
|
|
3413
3497
|
handleCanvasPointerDown = (event) => {
|
|
3498
|
+
this.maybeCleanupAfterMissingPointerUp(event.nativeEvent);
|
|
3414
3499
|
this.maybeUnfollowRemoteUser();
|
|
3415
3500
|
// since contextMenu options are potentially evaluated on each render,
|
|
3416
3501
|
// and an contextMenu action may depend on selection state, we must
|
|
@@ -3463,7 +3548,6 @@ class App extends React.Component {
|
|
|
3463
3548
|
selection.removeAllRanges();
|
|
3464
3549
|
}
|
|
3465
3550
|
this.maybeOpenContextMenuAfterPointerDownOnTouchDevices(event);
|
|
3466
|
-
this.maybeCleanupAfterMissingPointerUp(event);
|
|
3467
3551
|
//fires only once, if pen is detected, penMode is enabled
|
|
3468
3552
|
//the user can disable this by toggling the penMode button
|
|
3469
3553
|
if (!this.state.penDetected && event.pointerType === "pen") {
|
|
@@ -3493,9 +3577,47 @@ class App extends React.Component {
|
|
|
3493
3577
|
cursorButton: "down",
|
|
3494
3578
|
});
|
|
3495
3579
|
this.savePointer(event.clientX, event.clientY, "down");
|
|
3580
|
+
if (event.button === POINTER_BUTTON.ERASER &&
|
|
3581
|
+
this.state.activeTool.type !== TOOL_TYPE.eraser) {
|
|
3582
|
+
this.setState({
|
|
3583
|
+
activeTool: updateActiveTool(this.state, {
|
|
3584
|
+
type: TOOL_TYPE.eraser,
|
|
3585
|
+
lastActiveToolBeforeEraser: this.state.activeTool,
|
|
3586
|
+
}),
|
|
3587
|
+
}, () => {
|
|
3588
|
+
this.handleCanvasPointerDown(event);
|
|
3589
|
+
const onPointerUp = () => {
|
|
3590
|
+
unsubPointerUp();
|
|
3591
|
+
unsubCleanup?.();
|
|
3592
|
+
if (isEraserActive(this.state)) {
|
|
3593
|
+
this.setState({
|
|
3594
|
+
activeTool: updateActiveTool(this.state, {
|
|
3595
|
+
...(this.state.activeTool.lastActiveTool || {
|
|
3596
|
+
type: TOOL_TYPE.selection,
|
|
3597
|
+
}),
|
|
3598
|
+
lastActiveToolBeforeEraser: null,
|
|
3599
|
+
}),
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
};
|
|
3603
|
+
const unsubPointerUp = addEventListener(window, EVENT.POINTER_UP, onPointerUp, {
|
|
3604
|
+
once: true,
|
|
3605
|
+
});
|
|
3606
|
+
let unsubCleanup;
|
|
3607
|
+
// subscribe inside rAF lest it'd be triggered on the same pointerdown
|
|
3608
|
+
// if we start erasing while coming from blurred document since
|
|
3609
|
+
// we cleanup pointer events on focus
|
|
3610
|
+
requestAnimationFrame(() => {
|
|
3611
|
+
unsubCleanup =
|
|
3612
|
+
this.missingPointerEventCleanupEmitter.once(onPointerUp);
|
|
3613
|
+
});
|
|
3614
|
+
});
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3496
3617
|
// only handle left mouse button or touch
|
|
3497
3618
|
if (event.button !== POINTER_BUTTON.MAIN &&
|
|
3498
|
-
event.button !== POINTER_BUTTON.TOUCH
|
|
3619
|
+
event.button !== POINTER_BUTTON.TOUCH &&
|
|
3620
|
+
event.button !== POINTER_BUTTON.ERASER) {
|
|
3499
3621
|
return;
|
|
3500
3622
|
}
|
|
3501
3623
|
// don't select while panning
|
|
@@ -3566,7 +3688,7 @@ class App extends React.Component {
|
|
|
3566
3688
|
this.createFrameElementOnPointerDown(pointerDownState, this.state.activeTool.type);
|
|
3567
3689
|
}
|
|
3568
3690
|
else if (this.state.activeTool.type === "laser") {
|
|
3569
|
-
this.
|
|
3691
|
+
this.laserTrails.startPath(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y);
|
|
3570
3692
|
}
|
|
3571
3693
|
else if (this.state.activeTool.type !== "eraser" &&
|
|
3572
3694
|
this.state.activeTool.type !== "hand") {
|
|
@@ -3574,11 +3696,14 @@ class App extends React.Component {
|
|
|
3574
3696
|
}
|
|
3575
3697
|
this.props?.onPointerDown?.(this.state.activeTool, pointerDownState);
|
|
3576
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
|
+
}
|
|
3577
3702
|
const onPointerMove = this.onPointerMoveFromPointerDownHandler(pointerDownState);
|
|
3578
3703
|
const onPointerUp = this.onPointerUpFromPointerDownHandler(pointerDownState);
|
|
3579
3704
|
const onKeyDown = this.onKeyDownFromPointerDownHandler(pointerDownState);
|
|
3580
3705
|
const onKeyUp = this.onKeyUpFromPointerDownHandler(pointerDownState);
|
|
3581
|
-
|
|
3706
|
+
this.missingPointerEventCleanupEmitter.once((_event) => onPointerUp(_event || event.nativeEvent));
|
|
3582
3707
|
if (!this.state.viewModeEnabled || this.state.activeTool.type === "laser") {
|
|
3583
3708
|
window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
|
3584
3709
|
window.addEventListener(EVENT.POINTER_UP, onPointerUp);
|
|
@@ -3611,10 +3736,7 @@ class App extends React.Component {
|
|
|
3611
3736
|
!this.state.selectedElementIds[this.hitLinkElement.id]) {
|
|
3612
3737
|
if (clicklength < 300 &&
|
|
3613
3738
|
isIframeLikeElement(this.hitLinkElement) &&
|
|
3614
|
-
!isPointHittingLinkIcon(this.hitLinkElement, this.state, [
|
|
3615
|
-
scenePointer.x,
|
|
3616
|
-
scenePointer.y,
|
|
3617
|
-
])) {
|
|
3739
|
+
!isPointHittingLinkIcon(this.hitLinkElement, this.scene.getNonDeletedElementsMap(), this.state, [scenePointer.x, scenePointer.y])) {
|
|
3618
3740
|
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
|
3619
3741
|
}
|
|
3620
3742
|
else {
|
|
@@ -3655,14 +3777,15 @@ class App extends React.Component {
|
|
|
3655
3777
|
touchTimeout = 0;
|
|
3656
3778
|
invalidateContextMenu = false;
|
|
3657
3779
|
};
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3780
|
+
/**
|
|
3781
|
+
* pointerup may not fire in certian cases (user tabs away...), so in order
|
|
3782
|
+
* to properly cleanup pointerdown state, we need to fire any hanging
|
|
3783
|
+
* pointerup handlers manually
|
|
3784
|
+
*/
|
|
3785
|
+
maybeCleanupAfterMissingPointerUp = (event) => {
|
|
3786
|
+
lastPointerUp?.();
|
|
3787
|
+
this.missingPointerEventCleanupEmitter.trigger(event).clear();
|
|
3788
|
+
};
|
|
3666
3789
|
// Returns whether the event is a panning
|
|
3667
3790
|
handleCanvasPanUsingWheelOrSpaceDrag = (event) => {
|
|
3668
3791
|
if (!(gesture.pointers.size <= 1 &&
|
|
@@ -3676,7 +3799,9 @@ class App extends React.Component {
|
|
|
3676
3799
|
isPanning = true;
|
|
3677
3800
|
event.preventDefault();
|
|
3678
3801
|
let nextPastePrevented = false;
|
|
3679
|
-
const isLinux =
|
|
3802
|
+
const isLinux = typeof window === undefined
|
|
3803
|
+
? false
|
|
3804
|
+
: /Linux/.test(window.navigator.platform);
|
|
3680
3805
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.GRABBING);
|
|
3681
3806
|
let { clientX: lastX, clientY: lastY } = event;
|
|
3682
3807
|
const onPointerMove = withBatchedUpdatesThrottled((event) => {
|
|
@@ -3799,7 +3924,6 @@ class App extends React.Component {
|
|
|
3799
3924
|
boxSelection: {
|
|
3800
3925
|
hasOccurred: false,
|
|
3801
3926
|
},
|
|
3802
|
-
elementIdsToErase: {},
|
|
3803
3927
|
};
|
|
3804
3928
|
}
|
|
3805
3929
|
// Returns whether the event is a dragging a scrollbar
|
|
@@ -3818,9 +3942,9 @@ class App extends React.Component {
|
|
|
3818
3942
|
this.handlePointerMoveOverScrollbars(event, pointerDownState);
|
|
3819
3943
|
});
|
|
3820
3944
|
const onPointerUp = withBatchedUpdates(() => {
|
|
3945
|
+
lastPointerUp = null;
|
|
3821
3946
|
isDraggingScrollBar = false;
|
|
3822
3947
|
setCursorForShape(this.interactiveCanvas, this.state);
|
|
3823
|
-
lastPointerUp = null;
|
|
3824
3948
|
this.setState({
|
|
3825
3949
|
cursorButton: "up",
|
|
3826
3950
|
});
|
|
@@ -3850,9 +3974,10 @@ class App extends React.Component {
|
|
|
3850
3974
|
handleSelectionOnPointerDown = (event, pointerDownState) => {
|
|
3851
3975
|
if (this.state.activeTool.type === "selection") {
|
|
3852
3976
|
const elements = this.scene.getNonDeletedElements();
|
|
3977
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
3853
3978
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
3854
3979
|
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
|
3855
|
-
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());
|
|
3856
3981
|
if (elementWithTransformHandleType != null) {
|
|
3857
3982
|
this.setState({
|
|
3858
3983
|
resizingElement: elementWithTransformHandleType.element,
|
|
@@ -3866,7 +3991,7 @@ class App extends React.Component {
|
|
|
3866
3991
|
}
|
|
3867
3992
|
if (pointerDownState.resize.handleType) {
|
|
3868
3993
|
pointerDownState.resize.isResizing = true;
|
|
3869
|
-
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));
|
|
3870
3995
|
if (selectedElements.length === 1 &&
|
|
3871
3996
|
isLinearElement(selectedElements[0]) &&
|
|
3872
3997
|
selectedElements[0].points.length === 2) {
|
|
@@ -3876,7 +4001,7 @@ class App extends React.Component {
|
|
|
3876
4001
|
else {
|
|
3877
4002
|
if (this.state.selectedLinearElement) {
|
|
3878
4003
|
const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
|
|
3879
|
-
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);
|
|
3880
4005
|
if (ret.hitElement) {
|
|
3881
4006
|
pointerDownState.hit.element = ret.hitElement;
|
|
3882
4007
|
}
|
|
@@ -4057,7 +4182,7 @@ class App extends React.Component {
|
|
|
4057
4182
|
includeBoundTextElement: true,
|
|
4058
4183
|
});
|
|
4059
4184
|
// FIXME
|
|
4060
|
-
let container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY);
|
|
4185
|
+
let container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
|
|
4061
4186
|
if (hasBoundTextElement(element)) {
|
|
4062
4187
|
container = element;
|
|
4063
4188
|
sceneX = element.x + element.width / 2;
|
|
@@ -4115,7 +4240,7 @@ class App extends React.Component {
|
|
|
4115
4240
|
points: [[0, 0]],
|
|
4116
4241
|
pressures,
|
|
4117
4242
|
});
|
|
4118
|
-
const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene);
|
|
4243
|
+
const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
|
|
4119
4244
|
this.scene.addNewElement(element);
|
|
4120
4245
|
this.setState({
|
|
4121
4246
|
draggingElement: element,
|
|
@@ -4159,8 +4284,11 @@ class App extends React.Component {
|
|
|
4159
4284
|
if (!embedLink) {
|
|
4160
4285
|
return;
|
|
4161
4286
|
}
|
|
4162
|
-
if (embedLink.
|
|
4163
|
-
this.setToast({
|
|
4287
|
+
if (embedLink.error instanceof URIError) {
|
|
4288
|
+
this.setToast({
|
|
4289
|
+
message: t("toast.unrecognizedLinkFormat"),
|
|
4290
|
+
closable: true,
|
|
4291
|
+
});
|
|
4164
4292
|
}
|
|
4165
4293
|
const element = newEmbeddableElement({
|
|
4166
4294
|
type: "embeddable",
|
|
@@ -4178,7 +4306,6 @@ class App extends React.Component {
|
|
|
4178
4306
|
width: embedLink.intrinsicSize.w,
|
|
4179
4307
|
height: embedLink.intrinsicSize.h,
|
|
4180
4308
|
link,
|
|
4181
|
-
validated: null,
|
|
4182
4309
|
});
|
|
4183
4310
|
this.scene.replaceAllElements([
|
|
4184
4311
|
...this.scene.getElementsIncludingDeleted(),
|
|
@@ -4291,7 +4418,7 @@ class App extends React.Component {
|
|
|
4291
4418
|
mutateElement(element, {
|
|
4292
4419
|
points: [...element.points, [0, 0]],
|
|
4293
4420
|
});
|
|
4294
|
-
const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene);
|
|
4421
|
+
const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
|
|
4295
4422
|
this.scene.addNewElement(element);
|
|
4296
4423
|
this.setState({
|
|
4297
4424
|
draggingElement: element,
|
|
@@ -4336,7 +4463,6 @@ class App extends React.Component {
|
|
|
4336
4463
|
if (elementType === "embeddable") {
|
|
4337
4464
|
element = newEmbeddableElement({
|
|
4338
4465
|
type: "embeddable",
|
|
4339
|
-
validated: null,
|
|
4340
4466
|
...baseElementAttributes,
|
|
4341
4467
|
});
|
|
4342
4468
|
}
|
|
@@ -4392,7 +4518,7 @@ class App extends React.Component {
|
|
|
4392
4518
|
selectedElements,
|
|
4393
4519
|
}) &&
|
|
4394
4520
|
(recomputeAnyways || !SnapCache.getReferenceSnapPoints())) {
|
|
4395
|
-
SnapCache.setReferenceSnapPoints(getReferenceSnapPoints(this.scene.getNonDeletedElements(), selectedElements, this.state));
|
|
4521
|
+
SnapCache.setReferenceSnapPoints(getReferenceSnapPoints(this.scene.getNonDeletedElements(), selectedElements, this.state, this.scene.getNonDeletedElementsMap()));
|
|
4396
4522
|
}
|
|
4397
4523
|
}
|
|
4398
4524
|
maybeCacheVisibleGaps(event, selectedElements, recomputeAnyways = false) {
|
|
@@ -4402,7 +4528,7 @@ class App extends React.Component {
|
|
|
4402
4528
|
selectedElements,
|
|
4403
4529
|
}) &&
|
|
4404
4530
|
(recomputeAnyways || !SnapCache.getVisibleGaps())) {
|
|
4405
|
-
SnapCache.setVisibleGaps(getVisibleGaps(this.scene.getNonDeletedElements(), selectedElements, this.state));
|
|
4531
|
+
SnapCache.setVisibleGaps(getVisibleGaps(this.scene.getNonDeletedElements(), selectedElements, this.state, this.scene.getNonDeletedElementsMap()));
|
|
4406
4532
|
}
|
|
4407
4533
|
}
|
|
4408
4534
|
onKeyDownFromPointerDownHandler(pointerDownState) {
|
|
@@ -4445,7 +4571,7 @@ class App extends React.Component {
|
|
|
4445
4571
|
return;
|
|
4446
4572
|
}
|
|
4447
4573
|
if (this.state.activeTool.type === "laser") {
|
|
4448
|
-
this.
|
|
4574
|
+
this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
|
|
4449
4575
|
}
|
|
4450
4576
|
const [gridX, gridY] = getGridPoint(pointerCoords.x, pointerCoords.y, event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize);
|
|
4451
4577
|
// for arrows/lines, don't start dragging until a given threshold
|
|
@@ -4466,10 +4592,11 @@ class App extends React.Component {
|
|
|
4466
4592
|
return true;
|
|
4467
4593
|
}
|
|
4468
4594
|
}
|
|
4595
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
4469
4596
|
if (this.state.selectedLinearElement) {
|
|
4470
4597
|
const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
|
|
4471
|
-
if (LinearElementEditor.shouldAddMidpoint(this.state.selectedLinearElement, pointerCoords, this.state)) {
|
|
4472
|
-
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);
|
|
4473
4600
|
if (!ret) {
|
|
4474
4601
|
return;
|
|
4475
4602
|
}
|
|
@@ -4504,7 +4631,7 @@ class App extends React.Component {
|
|
|
4504
4631
|
}
|
|
4505
4632
|
const didDrag = LinearElementEditor.handlePointDragging(event, this.state, pointerCoords.x, pointerCoords.y, (element, pointsSceneCoords) => {
|
|
4506
4633
|
this.maybeSuggestBindingsForLinearElementAtCoords(element, pointsSceneCoords);
|
|
4507
|
-
}, linearElementEditor);
|
|
4634
|
+
}, linearElementEditor, this.scene.getNonDeletedElementsMap());
|
|
4508
4635
|
if (didDrag) {
|
|
4509
4636
|
pointerDownState.lastCoords.x = pointerCoords.x;
|
|
4510
4637
|
pointerDownState.lastCoords.y = pointerCoords.y;
|
|
@@ -4585,7 +4712,7 @@ class App extends React.Component {
|
|
|
4585
4712
|
// it snaps to its position if previously snapped already.
|
|
4586
4713
|
this.maybeCacheVisibleGaps(event, selectedElements);
|
|
4587
4714
|
this.maybeCacheReferenceSnapPoints(event, selectedElements);
|
|
4588
|
-
const { snapOffset, snapLines } = snapDraggedElements(
|
|
4715
|
+
const { snapOffset, snapLines } = snapDraggedElements(originalElements, dragOffset, this.state, event, this.scene.getNonDeletedElementsMap());
|
|
4589
4716
|
this.setState({ snapLines });
|
|
4590
4717
|
// when we're editing the name of a frame, we want the user to be
|
|
4591
4718
|
// able to select and interact with the text input
|
|
@@ -4703,7 +4830,7 @@ class App extends React.Component {
|
|
|
4703
4830
|
const elements = this.scene.getNonDeletedElements();
|
|
4704
4831
|
// box-select line editor points
|
|
4705
4832
|
if (this.state.editingLinearElement) {
|
|
4706
|
-
LinearElementEditor.handleBoxSelection(event, this.state, this.setState.bind(this));
|
|
4833
|
+
LinearElementEditor.handleBoxSelection(event, this.state, this.setState.bind(this), this.scene.getNonDeletedElementsMap());
|
|
4707
4834
|
// regular box-select
|
|
4708
4835
|
}
|
|
4709
4836
|
else {
|
|
@@ -4722,7 +4849,7 @@ class App extends React.Component {
|
|
|
4722
4849
|
shouldReuseSelection = false;
|
|
4723
4850
|
}
|
|
4724
4851
|
}
|
|
4725
|
-
const elementsWithinSelection = getElementsWithinSelection(elements, draggingElement);
|
|
4852
|
+
const elementsWithinSelection = getElementsWithinSelection(elements, draggingElement, this.scene.getNonDeletedElementsMap());
|
|
4726
4853
|
this.setState((prevState) => {
|
|
4727
4854
|
const nextSelectedElementIds = {
|
|
4728
4855
|
...(shouldReuseSelection && prevState.selectedElementIds),
|
|
@@ -4752,7 +4879,7 @@ class App extends React.Component {
|
|
|
4752
4879
|
// select linear element only when we haven't box-selected anything else
|
|
4753
4880
|
selectedLinearElement: elementsWithinSelection.length === 1 &&
|
|
4754
4881
|
isLinearElement(elementsWithinSelection[0])
|
|
4755
|
-
? new LinearElementEditor(elementsWithinSelection[0]
|
|
4882
|
+
? new LinearElementEditor(elementsWithinSelection[0])
|
|
4756
4883
|
: null,
|
|
4757
4884
|
showHyperlinkPopup: elementsWithinSelection.length === 1 &&
|
|
4758
4885
|
(elementsWithinSelection[0].link ||
|
|
@@ -4789,6 +4916,7 @@ class App extends React.Component {
|
|
|
4789
4916
|
}
|
|
4790
4917
|
onPointerUpFromPointerDownHandler(pointerDownState) {
|
|
4791
4918
|
return withBatchedUpdates((childEvent) => {
|
|
4919
|
+
this.removePointer(childEvent);
|
|
4792
4920
|
if (pointerDownState.eventListeners.onMove) {
|
|
4793
4921
|
pointerDownState.eventListeners.onMove.flush();
|
|
4794
4922
|
}
|
|
@@ -4815,6 +4943,7 @@ class App extends React.Component {
|
|
|
4815
4943
|
this.setState({
|
|
4816
4944
|
selectedElementsAreBeingDragged: false,
|
|
4817
4945
|
});
|
|
4946
|
+
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
4818
4947
|
// Handle end of dragging a point of a linear element, might close a loop
|
|
4819
4948
|
// and sets binding element
|
|
4820
4949
|
if (this.state.editingLinearElement) {
|
|
@@ -4824,7 +4953,7 @@ class App extends React.Component {
|
|
|
4824
4953
|
this.actionManager.executeAction(actionFinalize);
|
|
4825
4954
|
}
|
|
4826
4955
|
else {
|
|
4827
|
-
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);
|
|
4828
4957
|
if (editingLinearElement !== this.state.editingLinearElement) {
|
|
4829
4958
|
this.setState({
|
|
4830
4959
|
editingLinearElement,
|
|
@@ -4843,11 +4972,11 @@ class App extends React.Component {
|
|
|
4843
4972
|
}
|
|
4844
4973
|
}
|
|
4845
4974
|
else {
|
|
4846
|
-
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);
|
|
4847
4976
|
const { startBindingElement, endBindingElement } = linearElementEditor;
|
|
4848
4977
|
const element = this.scene.getElement(linearElementEditor.elementId);
|
|
4849
4978
|
if (isBindingElement(element)) {
|
|
4850
|
-
bindOrUnbindLinearElement(element, startBindingElement, endBindingElement);
|
|
4979
|
+
bindOrUnbindLinearElement(element, startBindingElement, endBindingElement, elementsMap);
|
|
4851
4980
|
}
|
|
4852
4981
|
if (linearElementEditor !== this.state.selectedLinearElement) {
|
|
4853
4982
|
this.setState({
|
|
@@ -4860,7 +4989,7 @@ class App extends React.Component {
|
|
|
4860
4989
|
}
|
|
4861
4990
|
}
|
|
4862
4991
|
}
|
|
4863
|
-
|
|
4992
|
+
this.missingPointerEventCleanupEmitter.clear();
|
|
4864
4993
|
window.removeEventListener(EVENT.POINTER_MOVE, pointerDownState.eventListeners.onMove);
|
|
4865
4994
|
window.removeEventListener(EVENT.POINTER_UP, pointerDownState.eventListeners.onUp);
|
|
4866
4995
|
window.removeEventListener(EVENT.KEYDOWN, pointerDownState.eventListeners.onKeyDown);
|
|
@@ -4868,6 +4997,7 @@ class App extends React.Component {
|
|
|
4868
4997
|
if (this.state.pendingImageElementId) {
|
|
4869
4998
|
this.setState({ pendingImageElementId: null });
|
|
4870
4999
|
}
|
|
5000
|
+
this.props?.onPointerUp?.(activeTool, pointerDownState);
|
|
4871
5001
|
this.onPointerUpEmitter.trigger(this.state.activeTool, pointerDownState, childEvent);
|
|
4872
5002
|
if (draggingElement?.type === "freedraw") {
|
|
4873
5003
|
const pointerCoords = viewportCoordsToSceneCoords(childEvent, this.state);
|
|
@@ -4934,7 +5064,7 @@ class App extends React.Component {
|
|
|
4934
5064
|
else if (pointerDownState.drag.hasOccurred && !multiElement) {
|
|
4935
5065
|
if (isBindingEnabled(this.state) &&
|
|
4936
5066
|
isBindingElement(draggingElement, false)) {
|
|
4937
|
-
maybeBindLinearElement(draggingElement, this.state, this.scene, pointerCoords);
|
|
5067
|
+
maybeBindLinearElement(draggingElement, this.state, this.scene, pointerCoords, elementsMap);
|
|
4938
5068
|
}
|
|
4939
5069
|
this.setState({ suggestedBindings: [], startBoundElement: null });
|
|
4940
5070
|
if (!activeTool.locked) {
|
|
@@ -4948,7 +5078,7 @@ class App extends React.Component {
|
|
|
4948
5078
|
...prevState.selectedElementIds,
|
|
4949
5079
|
[draggingElement.id]: true,
|
|
4950
5080
|
}, prevState),
|
|
4951
|
-
selectedLinearElement: new LinearElementEditor(draggingElement
|
|
5081
|
+
selectedLinearElement: new LinearElementEditor(draggingElement),
|
|
4952
5082
|
}));
|
|
4953
5083
|
}
|
|
4954
5084
|
else {
|
|
@@ -4981,15 +5111,16 @@ class App extends React.Component {
|
|
|
4981
5111
|
this.state.selectedLinearElement.isDragging) {
|
|
4982
5112
|
const linearElement = this.scene.getElement(this.state.selectedLinearElement.elementId);
|
|
4983
5113
|
if (linearElement?.frameId) {
|
|
4984
|
-
const frame = getContainingFrame(linearElement);
|
|
5114
|
+
const frame = getContainingFrame(linearElement, elementsMap);
|
|
4985
5115
|
if (frame && linearElement) {
|
|
4986
|
-
if (!elementOverlapsWithFrame(linearElement, frame)) {
|
|
5116
|
+
if (!elementOverlapsWithFrame(linearElement, frame, this.scene.getNonDeletedElementsMap())) {
|
|
4987
5117
|
// remove the linear element from all groups
|
|
4988
5118
|
// before removing it from the frame as well
|
|
4989
5119
|
mutateElement(linearElement, {
|
|
4990
5120
|
groupIds: [],
|
|
4991
5121
|
});
|
|
4992
|
-
|
|
5122
|
+
removeElementsFromFrame([linearElement], this.scene.getNonDeletedElementsMap());
|
|
5123
|
+
this.scene.informMutation();
|
|
4993
5124
|
}
|
|
4994
5125
|
}
|
|
4995
5126
|
}
|
|
@@ -4998,7 +5129,7 @@ class App extends React.Component {
|
|
|
4998
5129
|
// update the relationships between selected elements and frames
|
|
4999
5130
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords(sceneCoords);
|
|
5000
5131
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
|
5001
|
-
let nextElements = this.scene.
|
|
5132
|
+
let nextElements = this.scene.getElementsMapIncludingDeleted();
|
|
5002
5133
|
const updateGroupIdsAfterEditingGroup = (elements) => {
|
|
5003
5134
|
if (elements.length > 0) {
|
|
5004
5135
|
for (const element of elements) {
|
|
@@ -5041,8 +5172,8 @@ class App extends React.Component {
|
|
|
5041
5172
|
}
|
|
5042
5173
|
}
|
|
5043
5174
|
if (isFrameLikeElement(draggingElement)) {
|
|
5044
|
-
const elementsInsideFrame = getElementsInNewFrame(this.scene.getElementsIncludingDeleted(), draggingElement);
|
|
5045
|
-
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));
|
|
5046
5177
|
}
|
|
5047
5178
|
mutateElement(draggingElement, getNormalizedDimensions(draggingElement));
|
|
5048
5179
|
}
|
|
@@ -5061,7 +5192,7 @@ class App extends React.Component {
|
|
|
5061
5192
|
.getSelectedElements(this.state)
|
|
5062
5193
|
.filter((element) => isFrameLikeElement(element));
|
|
5063
5194
|
for (const frame of selectedFrames) {
|
|
5064
|
-
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);
|
|
5065
5196
|
}
|
|
5066
5197
|
this.scene.replaceAllElements(nextElements);
|
|
5067
5198
|
}
|
|
@@ -5075,28 +5206,28 @@ class App extends React.Component {
|
|
|
5075
5206
|
// the one we've hit
|
|
5076
5207
|
if (selectedELements.length === 1) {
|
|
5077
5208
|
this.setState({
|
|
5078
|
-
selectedLinearElement: new LinearElementEditor(hitElement
|
|
5209
|
+
selectedLinearElement: new LinearElementEditor(hitElement),
|
|
5079
5210
|
});
|
|
5080
5211
|
}
|
|
5081
5212
|
}
|
|
5082
|
-
|
|
5083
|
-
|
|
5213
|
+
const pointerStart = this.lastPointerDownEvent;
|
|
5214
|
+
const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
|
|
5215
|
+
if (isEraserActive(this.state) && pointerStart && pointerEnd) {
|
|
5216
|
+
this.eraserTrail.endPath();
|
|
5217
|
+
const draggedDistance = distance2d(pointerStart.clientX, pointerStart.clientY, pointerEnd.clientX, pointerEnd.clientY);
|
|
5084
5218
|
if (draggedDistance === 0) {
|
|
5085
5219
|
const scenePointer = viewportCoordsToSceneCoords({
|
|
5086
|
-
clientX:
|
|
5087
|
-
clientY:
|
|
5220
|
+
clientX: pointerEnd.clientX,
|
|
5221
|
+
clientY: pointerEnd.clientY,
|
|
5088
5222
|
}, this.state);
|
|
5089
5223
|
const hitElements = this.getElementsAtPosition(scenePointer.x, scenePointer.y);
|
|
5090
|
-
hitElements.forEach((hitElement) => (
|
|
5091
|
-
erase: true,
|
|
5092
|
-
opacity: hitElement.opacity,
|
|
5093
|
-
}));
|
|
5224
|
+
hitElements.forEach((hitElement) => this.elementsPendingErasure.add(hitElement.id));
|
|
5094
5225
|
}
|
|
5095
|
-
this.eraseElements(
|
|
5226
|
+
this.eraseElements();
|
|
5096
5227
|
return;
|
|
5097
5228
|
}
|
|
5098
|
-
else if (
|
|
5099
|
-
this.restoreReadyToEraseElements(
|
|
5229
|
+
else if (this.elementsPendingErasure.size) {
|
|
5230
|
+
this.restoreReadyToEraseElements();
|
|
5100
5231
|
}
|
|
5101
5232
|
if (hitElement &&
|
|
5102
5233
|
!pointerDownState.drag.hasOccurred &&
|
|
@@ -5148,7 +5279,7 @@ class App extends React.Component {
|
|
|
5148
5279
|
// set selectedLinearElement only if thats the only element selected
|
|
5149
5280
|
selectedLinearElement: newSelectedElements.length === 1 &&
|
|
5150
5281
|
isLinearElement(newSelectedElements[0])
|
|
5151
|
-
? new LinearElementEditor(newSelectedElements[0]
|
|
5282
|
+
? new LinearElementEditor(newSelectedElements[0])
|
|
5152
5283
|
: prevState.selectedLinearElement,
|
|
5153
5284
|
};
|
|
5154
5285
|
});
|
|
@@ -5202,7 +5333,7 @@ class App extends React.Component {
|
|
|
5202
5333
|
// Don't set `selectedLinearElement` if its same as the hitElement, this is mainly to prevent resetting the `hoverPointIndex` to -1.
|
|
5203
5334
|
// Future we should update the API to take care of setting the correct `hoverPointIndex` when initialized
|
|
5204
5335
|
prevState.selectedLinearElement?.elementId !== hitElement.id
|
|
5205
|
-
? new LinearElementEditor(hitElement
|
|
5336
|
+
? new LinearElementEditor(hitElement)
|
|
5206
5337
|
: prevState.selectedLinearElement,
|
|
5207
5338
|
}));
|
|
5208
5339
|
}
|
|
@@ -5210,7 +5341,7 @@ class App extends React.Component {
|
|
|
5210
5341
|
if (!pointerDownState.drag.hasOccurred &&
|
|
5211
5342
|
!this.state.isResizing &&
|
|
5212
5343
|
((hitElement &&
|
|
5213
|
-
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())) ||
|
|
5214
5345
|
(!hitElement &&
|
|
5215
5346
|
pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements))) {
|
|
5216
5347
|
if (this.state.editingLinearElement) {
|
|
@@ -5246,12 +5377,12 @@ class App extends React.Component {
|
|
|
5246
5377
|
this.history.resumeRecording();
|
|
5247
5378
|
}
|
|
5248
5379
|
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
|
5249
|
-
|
|
5250
|
-
? bindOrUnbindSelectedElements
|
|
5251
|
-
: unbindLinearElements
|
|
5380
|
+
isBindingEnabled(this.state)
|
|
5381
|
+
? bindOrUnbindSelectedElements(this.scene.getSelectedElements(this.state), this.scene.getNonDeletedElements(), elementsMap)
|
|
5382
|
+
: unbindLinearElements(this.scene.getSelectedElements(this.state), elementsMap);
|
|
5252
5383
|
}
|
|
5253
5384
|
if (activeTool.type === "laser") {
|
|
5254
|
-
this.
|
|
5385
|
+
this.laserTrails.endPath();
|
|
5255
5386
|
return;
|
|
5256
5387
|
}
|
|
5257
5388
|
if (!activeTool.locked && activeTool.type !== "freedraw") {
|
|
@@ -5281,52 +5412,27 @@ class App extends React.Component {
|
|
|
5281
5412
|
}
|
|
5282
5413
|
});
|
|
5283
5414
|
}
|
|
5284
|
-
restoreReadyToEraseElements = (
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
pointerDownState.elementIdsToErase[ele.id].erase) {
|
|
5288
|
-
return newElementWith(ele, {
|
|
5289
|
-
opacity: pointerDownState.elementIdsToErase[ele.id].opacity,
|
|
5290
|
-
});
|
|
5291
|
-
}
|
|
5292
|
-
else if (isBoundToContainer(ele) &&
|
|
5293
|
-
pointerDownState.elementIdsToErase[ele.containerId] &&
|
|
5294
|
-
pointerDownState.elementIdsToErase[ele.containerId].erase) {
|
|
5295
|
-
return newElementWith(ele, {
|
|
5296
|
-
opacity: pointerDownState.elementIdsToErase[ele.containerId].opacity,
|
|
5297
|
-
});
|
|
5298
|
-
}
|
|
5299
|
-
else if (ele.frameId &&
|
|
5300
|
-
pointerDownState.elementIdsToErase[ele.frameId] &&
|
|
5301
|
-
pointerDownState.elementIdsToErase[ele.frameId].erase) {
|
|
5302
|
-
return newElementWith(ele, {
|
|
5303
|
-
opacity: pointerDownState.elementIdsToErase[ele.frameId].opacity,
|
|
5304
|
-
});
|
|
5305
|
-
}
|
|
5306
|
-
return ele;
|
|
5307
|
-
});
|
|
5308
|
-
this.scene.replaceAllElements(elements);
|
|
5415
|
+
restoreReadyToEraseElements = () => {
|
|
5416
|
+
this.elementsPendingErasure = new Set();
|
|
5417
|
+
this.onSceneUpdated();
|
|
5309
5418
|
};
|
|
5310
|
-
eraseElements = (
|
|
5419
|
+
eraseElements = () => {
|
|
5420
|
+
let didChange = false;
|
|
5311
5421
|
const elements = this.scene.getElementsIncludingDeleted().map((ele) => {
|
|
5312
|
-
if (
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
pointerDownState.elementIdsToErase[ele.containerId] &&
|
|
5318
|
-
pointerDownState.elementIdsToErase[ele.containerId].erase) {
|
|
5319
|
-
return newElementWith(ele, { isDeleted: true });
|
|
5320
|
-
}
|
|
5321
|
-
else if (ele.frameId &&
|
|
5322
|
-
pointerDownState.elementIdsToErase[ele.frameId] &&
|
|
5323
|
-
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;
|
|
5324
5427
|
return newElementWith(ele, { isDeleted: true });
|
|
5325
5428
|
}
|
|
5326
5429
|
return ele;
|
|
5327
5430
|
});
|
|
5328
|
-
this.
|
|
5329
|
-
|
|
5431
|
+
this.elementsPendingErasure = new Set();
|
|
5432
|
+
if (didChange) {
|
|
5433
|
+
this.history.resumeRecording();
|
|
5434
|
+
this.scene.replaceAllElements(elements);
|
|
5435
|
+
}
|
|
5330
5436
|
};
|
|
5331
5437
|
initializeImage = async ({ imageFile, imageElement: _imageElement, showCursorImagePreview = false, }) => {
|
|
5332
5438
|
// at this point this should be guaranteed image file, but we do this check
|
|
@@ -5450,9 +5556,18 @@ class App extends React.Component {
|
|
|
5450
5556
|
// mustn't be larger than 128 px
|
|
5451
5557
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Basic_User_Interface/Using_URL_values_for_the_cursor_property
|
|
5452
5558
|
const cursorImageSizePx = 96;
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
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
|
+
}
|
|
5456
5571
|
let previewDataURL = await getDataURL(imagePreview);
|
|
5457
5572
|
// SVG cannot be resized via `resizeImageFile` so we resize by rendering to
|
|
5458
5573
|
// a small canvas
|
|
@@ -5607,7 +5722,7 @@ class App extends React.Component {
|
|
|
5607
5722
|
}
|
|
5608
5723
|
};
|
|
5609
5724
|
maybeSuggestBindingAtCursor = (pointerCoords) => {
|
|
5610
|
-
const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this.scene);
|
|
5725
|
+
const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
|
|
5611
5726
|
this.setState({
|
|
5612
5727
|
suggestedBindings: hoveredBindableElement != null ? [hoveredBindableElement] : [],
|
|
5613
5728
|
});
|
|
@@ -5622,7 +5737,7 @@ class App extends React.Component {
|
|
|
5622
5737
|
return;
|
|
5623
5738
|
}
|
|
5624
5739
|
const suggestedBindings = pointerCoords.reduce((acc, coords) => {
|
|
5625
|
-
const hoveredBindableElement = getHoveredElementForBinding(coords, this.scene);
|
|
5740
|
+
const hoveredBindableElement = getHoveredElementForBinding(coords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
|
|
5626
5741
|
if (hoveredBindableElement != null &&
|
|
5627
5742
|
!isLinearElementSimpleAndAlreadyBound(linearElement, oppositeBindingBoundElement?.id, hoveredBindableElement)) {
|
|
5628
5743
|
acc.push(hoveredBindableElement);
|
|
@@ -5635,7 +5750,7 @@ class App extends React.Component {
|
|
|
5635
5750
|
if (selectedElements.length > 50) {
|
|
5636
5751
|
return;
|
|
5637
5752
|
}
|
|
5638
|
-
const suggestedBindings = getEligibleElementsForBinding(selectedElements);
|
|
5753
|
+
const suggestedBindings = getEligibleElementsForBinding(selectedElements, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
|
|
5639
5754
|
this.setState({ suggestedBindings });
|
|
5640
5755
|
}
|
|
5641
5756
|
clearSelection(hitElement) {
|
|
@@ -5702,8 +5817,9 @@ class App extends React.Component {
|
|
|
5702
5817
|
return;
|
|
5703
5818
|
}
|
|
5704
5819
|
catch (error) {
|
|
5820
|
+
// Don't throw for image scene daa
|
|
5705
5821
|
if (error.name !== "EncodingError") {
|
|
5706
|
-
throw
|
|
5822
|
+
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
|
5707
5823
|
}
|
|
5708
5824
|
}
|
|
5709
5825
|
}
|
|
@@ -5764,7 +5880,32 @@ class App extends React.Component {
|
|
|
5764
5880
|
loadFileToCanvas = async (file, fileHandle) => {
|
|
5765
5881
|
file = await normalizeFile(file);
|
|
5766
5882
|
try {
|
|
5767
|
-
|
|
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
|
+
}
|
|
5768
5909
|
if (ret.type === MIME_TYPES.excalidraw) {
|
|
5769
5910
|
this.setState({ isLoading: true });
|
|
5770
5911
|
this.syncActionResult({
|
|
@@ -5791,15 +5932,6 @@ class App extends React.Component {
|
|
|
5791
5932
|
}
|
|
5792
5933
|
}
|
|
5793
5934
|
catch (error) {
|
|
5794
|
-
if (error instanceof ImageSceneDataError &&
|
|
5795
|
-
error.code === "IMAGE_NOT_CONTAINS_SCENE_DATA" &&
|
|
5796
|
-
!this.isToolSupported("image")) {
|
|
5797
|
-
this.setState({
|
|
5798
|
-
isLoading: false,
|
|
5799
|
-
errorMessage: t("errors.imageToolNotSupported"),
|
|
5800
|
-
});
|
|
5801
|
-
return;
|
|
5802
|
-
}
|
|
5803
5935
|
this.setState({ isLoading: false, errorMessage: error.message });
|
|
5804
5936
|
}
|
|
5805
5937
|
};
|
|
@@ -5836,7 +5968,7 @@ class App extends React.Component {
|
|
|
5836
5968
|
selectedElementIds: { [element.id]: true },
|
|
5837
5969
|
}, this.scene.getNonDeletedElements(), this.state, this),
|
|
5838
5970
|
selectedLinearElement: isLinearElement(element)
|
|
5839
|
-
? new LinearElementEditor(element
|
|
5971
|
+
? new LinearElementEditor(element)
|
|
5840
5972
|
: null,
|
|
5841
5973
|
}
|
|
5842
5974
|
: this.state),
|
|
@@ -5873,7 +6005,7 @@ class App extends React.Component {
|
|
|
5873
6005
|
}, {
|
|
5874
6006
|
x: gridX - pointerDownState.originInGrid.x,
|
|
5875
6007
|
y: gridY - pointerDownState.originInGrid.y,
|
|
5876
|
-
});
|
|
6008
|
+
}, this.scene.getNonDeletedElementsMap());
|
|
5877
6009
|
gridX += snapOffset.x;
|
|
5878
6010
|
gridY += snapOffset.y;
|
|
5879
6011
|
this.setState({
|
|
@@ -5887,7 +6019,7 @@ class App extends React.Component {
|
|
|
5887
6019
|
if (this.state.activeTool.type === TOOL_TYPE.frame ||
|
|
5888
6020
|
this.state.activeTool.type === TOOL_TYPE.magicframe) {
|
|
5889
6021
|
this.setState({
|
|
5890
|
-
elementsToHighlight: getElementsInResizingFrame(this.scene.getNonDeletedElements(), draggingElement, this.state),
|
|
6022
|
+
elementsToHighlight: getElementsInResizingFrame(this.scene.getNonDeletedElements(), draggingElement, this.state, this.scene.getNonDeletedElementsMap()),
|
|
5891
6023
|
});
|
|
5892
6024
|
}
|
|
5893
6025
|
}
|
|
@@ -5936,13 +6068,13 @@ class App extends React.Component {
|
|
|
5936
6068
|
snapLines,
|
|
5937
6069
|
});
|
|
5938
6070
|
}
|
|
5939
|
-
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])
|
|
5940
6072
|
? !shouldMaintainAspectRatio(event)
|
|
5941
|
-
: 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)) {
|
|
5942
6074
|
this.maybeSuggestBindingForAll(selectedElements);
|
|
5943
6075
|
const elementsToHighlight = new Set();
|
|
5944
6076
|
selectedFrames.forEach((frame) => {
|
|
5945
|
-
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));
|
|
5946
6078
|
});
|
|
5947
6079
|
this.setState({
|
|
5948
6080
|
elementsToHighlight: [...elementsToHighlight],
|
|
@@ -6075,7 +6207,7 @@ class App extends React.Component {
|
|
|
6075
6207
|
if (container) {
|
|
6076
6208
|
let elementCenterX = container.x + container.width / 2;
|
|
6077
6209
|
let elementCenterY = container.y + container.height / 2;
|
|
6078
|
-
const elementCenter = getContainerCenter(container, appState);
|
|
6210
|
+
const elementCenter = getContainerCenter(container, appState, this.scene.getNonDeletedElementsMap());
|
|
6079
6211
|
if (elementCenter) {
|
|
6080
6212
|
elementCenterX = elementCenter.x;
|
|
6081
6213
|
elementCenterY = elementCenter.y;
|
|
@@ -6160,18 +6292,21 @@ class App extends React.Component {
|
|
|
6160
6292
|
this.setAppState({});
|
|
6161
6293
|
}
|
|
6162
6294
|
}
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
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
|
+
},
|
|
6173
6307
|
},
|
|
6174
|
-
}
|
|
6175
|
-
}
|
|
6176
|
-
}
|
|
6308
|
+
});
|
|
6309
|
+
}
|
|
6310
|
+
};
|
|
6311
|
+
createTestHook();
|
|
6177
6312
|
export default App;
|