@tldraw/editor 5.4.0-next.ebc95cf01337 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +67 -1
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +7 -2
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +6 -8
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/default-components/CanvasOverlays.js +1 -3
- package/dist-cjs/lib/components/default-components/CanvasOverlays.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +1 -1
- package/dist-cjs/lib/config/TLUserPreferences.js +6 -2
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/config/createTLStore.js +1 -1
- package/dist-cjs/lib/config/createTLStore.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +164 -280
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +3 -3
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +8 -8
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +2 -1
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +79 -6
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +7 -0
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +0 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +1 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +6 -3
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +12 -1
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/OverlayManager.js +3 -7
- package/dist-cjs/lib/editor/overlays/OverlayManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +0 -1
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +3 -1
- package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +1 -0
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +1 -1
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
- package/dist-cjs/lib/editor/tools/StateNode.js +3 -2
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/globals/editors.js +57 -0
- package/dist-cjs/lib/globals/editors.js.map +7 -0
- package/dist-cjs/lib/globals/menus.js +9 -4
- package/dist-cjs/lib/globals/menus.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -0
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +39 -9
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useLocalStore.js +10 -6
- package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +1 -0
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePeerIds.js +2 -1
- package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +9 -2
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/LicenseProvider.js +16 -3
- package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
- package/dist-cjs/lib/license/useLicenseManagerState.js +5 -4
- package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +2 -1
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +4 -3
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/utils.js +2 -1
- package/dist-cjs/lib/primitives/utils.js.map +2 -2
- package/dist-cjs/lib/utils/deepLinks.js +5 -1
- package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
- package/dist-cjs/lib/utils/rotation.js +1 -9
- package/dist-cjs/lib/utils/rotation.js.map +2 -2
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +17 -0
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
- package/dist-cjs/lib/utils/sync/alerts.js +1 -1
- package/dist-cjs/lib/utils/sync/alerts.js.map +1 -1
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +67 -1
- package/dist-esm/index.mjs +9 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +9 -4
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +6 -8
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/CanvasOverlays.mjs +1 -3
- package/dist-esm/lib/components/default-components/CanvasOverlays.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +1 -1
- package/dist-esm/lib/config/TLUserPreferences.mjs +6 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/config/createTLStore.mjs +1 -1
- package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +172 -281
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +3 -3
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +9 -9
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +2 -1
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +81 -8
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +7 -0
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +0 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -3
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +6 -3
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +12 -1
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/OverlayManager.mjs +3 -7
- package/dist-esm/lib/editor/overlays/OverlayManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +0 -1
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +3 -1
- package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +1 -0
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +1 -1
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs +3 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/globals/editors.mjs +37 -0
- package/dist-esm/lib/globals/editors.mjs.map +7 -0
- package/dist-esm/lib/globals/menus.mjs +9 -4
- package/dist-esm/lib/globals/menus.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -0
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +40 -10
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useLocalStore.mjs +11 -7
- package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +1 -0
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePeerIds.mjs +2 -1
- package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +9 -2
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseProvider.mjs +16 -3
- package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
- package/dist-esm/lib/license/useLicenseManagerState.mjs +5 -4
- package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +2 -1
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +4 -3
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/utils.mjs +2 -1
- package/dist-esm/lib/primitives/utils.mjs.map +2 -2
- package/dist-esm/lib/utils/deepLinks.mjs +5 -1
- package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
- package/dist-esm/lib/utils/rotation.mjs +1 -9
- package/dist-esm/lib/utils/rotation.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +17 -0
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/alerts.mjs +1 -1
- package/dist-esm/lib/utils/sync/alerts.mjs.map +1 -1
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +13 -7
- package/src/index.ts +6 -1
- package/src/lib/TldrawEditor.tsx +16 -4
- package/src/lib/components/MenuClickCapture.tsx +19 -18
- package/src/lib/components/default-components/CanvasOverlays.tsx +1 -3
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
- package/src/lib/config/TLUserPreferences.test.ts +18 -1
- package/src/lib/config/TLUserPreferences.ts +8 -4
- package/src/lib/config/createTLStore.ts +1 -1
- package/src/lib/editor/Editor.ts +241 -359
- package/src/lib/editor/managers/ClickManager/ClickManager.ts +5 -3
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +9 -9
- package/src/lib/editor/managers/FontManager/FontManager.ts +2 -1
- package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +199 -3
- package/src/lib/editor/managers/InputsManager/InputsManager.ts +128 -9
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +12 -1
- package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +0 -2
- package/src/lib/editor/managers/TextManager/TextManager.ts +2 -6
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +13 -0
- package/src/lib/editor/managers/TickManager/TickManager.ts +9 -3
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +64 -3
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +18 -1
- package/src/lib/editor/overlays/OverlayManager.ts +3 -7
- package/src/lib/editor/overlays/strokeShapeIndicators.ts +0 -1
- package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +5 -1
- package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +3 -0
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -1
- package/src/lib/editor/tools/StateNode.test.ts +64 -0
- package/src/lib/editor/tools/StateNode.ts +12 -2
- package/src/lib/globals/editors.test.ts +76 -0
- package/src/lib/globals/editors.ts +53 -0
- package/src/lib/globals/menus.test.ts +33 -0
- package/src/lib/globals/menus.ts +11 -4
- package/src/lib/hooks/useCanvasEvents.ts +2 -0
- package/src/lib/hooks/useDocumentEvents.ts +60 -16
- package/src/lib/hooks/useLocalStore.ts +16 -8
- package/src/lib/hooks/usePassThroughWheelEvents.ts +3 -0
- package/src/lib/hooks/usePeerIds.ts +2 -1
- package/src/lib/license/LicenseManager.test.ts +160 -0
- package/src/lib/license/LicenseManager.ts +13 -4
- package/src/lib/license/LicenseProvider.tsx +24 -3
- package/src/lib/license/useLicenseManagerState.ts +8 -6
- package/src/lib/primitives/Box.test.ts +9 -1
- package/src/lib/primitives/Box.ts +3 -1
- package/src/lib/primitives/Vec.test.ts +10 -0
- package/src/lib/primitives/Vec.ts +4 -4
- package/src/lib/primitives/geometry/Arc2d.test.ts +226 -3
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +173 -3
- package/src/lib/primitives/geometry/CubicSpline2d.test.ts +160 -3
- package/src/lib/primitives/geometry/Ellipse2d.test.ts +243 -3
- package/src/lib/primitives/geometry/Geometry2d.test.ts +553 -1
- package/src/lib/primitives/geometry/Group2d.test.ts +398 -3
- package/src/lib/primitives/geometry/Polyline2d.test.ts +207 -0
- package/src/lib/primitives/geometry/Stadium2d.test.ts +191 -3
- package/src/lib/primitives/utils.test.ts +10 -1
- package/src/lib/primitives/utils.ts +4 -1
- package/src/lib/test/useCanvasEvents.test.tsx +74 -0
- package/src/lib/test/useDocumentEvents.test.tsx +107 -0
- package/src/lib/test/usePassThroughWheelEvents.test.tsx +25 -2
- package/src/lib/utils/deepLinks.test.ts +15 -0
- package/src/lib/utils/deepLinks.ts +7 -1
- package/src/lib/utils/rotation.ts +4 -12
- package/src/lib/utils/sync/TLLocalSyncClient.test.ts +56 -0
- package/src/lib/utils/sync/TLLocalSyncClient.ts +24 -0
- package/src/lib/utils/sync/alerts.ts +1 -1
- package/src/version.ts +3 -3
- package/src/lib/primitives/geometry/Polyline.test.ts +0 -5
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
Result,
|
|
73
73
|
ZERO_INDEX_KEY,
|
|
74
74
|
annotateError,
|
|
75
|
+
areArraysShallowEqual,
|
|
75
76
|
assert,
|
|
76
77
|
assertExists,
|
|
77
78
|
bind,
|
|
@@ -118,8 +119,10 @@ import {
|
|
|
118
119
|
import { getOwnerWindow } from '../exports/domUtils'
|
|
119
120
|
import { exportToSvg } from '../exports/exportToSvg'
|
|
120
121
|
import { getSvgAsImageWithOptions, trimSvgToContent } from '../exports/getSvgAsImage'
|
|
122
|
+
import { registerMountedEditor, unregisterMountedEditor } from '../globals/editors'
|
|
121
123
|
import { tlmenus } from '../globals/menus'
|
|
122
124
|
import { tltime } from '../globals/time'
|
|
125
|
+
import { LicenseManager } from '../license/LicenseManager'
|
|
123
126
|
import { TldrawOptions, defaultTldrawOptions } from '../options'
|
|
124
127
|
import { Box, BoxLike } from '../primitives/Box'
|
|
125
128
|
import { EASINGS } from '../primitives/easings'
|
|
@@ -127,7 +130,13 @@ import { Geometry2d } from '../primitives/geometry/Geometry2d'
|
|
|
127
130
|
import { Group2d } from '../primitives/geometry/Group2d'
|
|
128
131
|
import { intersectPolygonPolygon } from '../primitives/intersect'
|
|
129
132
|
import { Mat, MatLike } from '../primitives/Mat'
|
|
130
|
-
import {
|
|
133
|
+
import {
|
|
134
|
+
HALF_PI,
|
|
135
|
+
approximately,
|
|
136
|
+
areAnglesCompatible,
|
|
137
|
+
clamp,
|
|
138
|
+
pointInPolygon,
|
|
139
|
+
} from '../primitives/utils'
|
|
131
140
|
import { Vec, VecLike } from '../primitives/Vec'
|
|
132
141
|
import { areShapesContentEqual } from '../utils/areShapesContentEqual'
|
|
133
142
|
import { dataUrlToFile } from '../utils/assets'
|
|
@@ -185,6 +194,7 @@ import { TLHistoryBatchOptions } from './types/history-types'
|
|
|
185
194
|
import {
|
|
186
195
|
OptionalKeys,
|
|
187
196
|
RequiredKeys,
|
|
197
|
+
TLCameraConstraints,
|
|
188
198
|
TLCameraMoveOptions,
|
|
189
199
|
TLCameraOptions,
|
|
190
200
|
TLGetShapeAtPointOptions,
|
|
@@ -543,6 +553,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
543
553
|
nextPageState.editingShapeId = null
|
|
544
554
|
}
|
|
545
555
|
|
|
556
|
+
if (
|
|
557
|
+
prevPageState.croppingShapeId &&
|
|
558
|
+
shapesNoLongerInPage.has(prevPageState.croppingShapeId)
|
|
559
|
+
) {
|
|
560
|
+
if (!nextPageState) nextPageState = { ...prevPageState }
|
|
561
|
+
nextPageState.croppingShapeId = null
|
|
562
|
+
}
|
|
563
|
+
|
|
546
564
|
const hintingShapeIds = prevPageState.hintingShapeIds.filter(
|
|
547
565
|
(id) => !shapesNoLongerInPage.has(id)
|
|
548
566
|
)
|
|
@@ -828,10 +846,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
828
846
|
|
|
829
847
|
if (prev?.selectedShapeIds !== next?.selectedShapeIds) {
|
|
830
848
|
// ensure that descendants and ancestors are not selected at the same time
|
|
849
|
+
const selectedShapeIds = new Set(next.selectedShapeIds)
|
|
831
850
|
const filtered = next.selectedShapeIds.filter((id) => {
|
|
832
851
|
let parentId = this.getShape(id)?.parentId
|
|
833
852
|
while (isShapeId(parentId)) {
|
|
834
|
-
if (
|
|
853
|
+
if (selectedShapeIds.has(parentId)) {
|
|
835
854
|
return false
|
|
836
855
|
}
|
|
837
856
|
parentId = this.getShape(parentId)?.parentId
|
|
@@ -918,10 +937,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
918
937
|
|
|
919
938
|
this.on('mount', () => {
|
|
920
939
|
this._isMounted.set(true)
|
|
940
|
+
registerMountedEditor(this)
|
|
921
941
|
})
|
|
922
942
|
|
|
923
943
|
this.on('unmount', () => {
|
|
924
944
|
this._isMounted.set(false)
|
|
945
|
+
unregisterMountedEditor(this)
|
|
925
946
|
})
|
|
926
947
|
|
|
927
948
|
this.timers.requestAnimationFrame(() => {
|
|
@@ -934,7 +955,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
934
955
|
const mode = this.store.props.collaboration.mode
|
|
935
956
|
this.disposables.add(
|
|
936
957
|
react('update collaboration mode', () => {
|
|
937
|
-
|
|
958
|
+
const isReadonly = mode.get() === 'readonly'
|
|
959
|
+
// only track `mode`, and keep the sync out of the user's undo history
|
|
960
|
+
unsafe__withoutCapture(() =>
|
|
961
|
+
this._updateInstanceState({ isReadonly }, { history: 'ignore' })
|
|
962
|
+
)
|
|
938
963
|
})
|
|
939
964
|
)
|
|
940
965
|
}
|
|
@@ -973,6 +998,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
973
998
|
|
|
974
999
|
readonly options: TldrawOptions
|
|
975
1000
|
|
|
1001
|
+
/**
|
|
1002
|
+
* The license manager whose feature flags apply to this editor. Assigned by `<TldrawEditor />`
|
|
1003
|
+
* when it creates the editor, so that UI rendered outside the editor tree can resolve the
|
|
1004
|
+
* editor's license through the editor instance. Undefined for editors created directly.
|
|
1005
|
+
*
|
|
1006
|
+
* @internal
|
|
1007
|
+
*/
|
|
1008
|
+
licenseManager?: LicenseManager
|
|
1009
|
+
|
|
976
1010
|
readonly contextId = uniqueId()
|
|
977
1011
|
|
|
978
1012
|
/**
|
|
@@ -1209,6 +1243,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1209
1243
|
if (this._isMounted.get()) {
|
|
1210
1244
|
this.emit('unmount')
|
|
1211
1245
|
}
|
|
1246
|
+
// The unmount listener above normally handles this; unregister again in case a listener
|
|
1247
|
+
// threw or was removed, so a disposed editor never lingers in `tleditors`.
|
|
1248
|
+
unregisterMountedEditor(this)
|
|
1212
1249
|
|
|
1213
1250
|
// Stop any in-progress camera animations and following before
|
|
1214
1251
|
// running disposables, so their cleanup listeners fire first
|
|
@@ -1374,7 +1411,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1374
1411
|
getShapeUtil(arg: string | { type: string }) {
|
|
1375
1412
|
const type = typeof arg === 'string' ? arg : arg.type
|
|
1376
1413
|
const shapeUtil = getOwnProperty(this.shapeUtils, type)
|
|
1377
|
-
|
|
1414
|
+
// hot path: avoid building the message (and the assert wrapper) on every successful call
|
|
1415
|
+
if (!shapeUtil) throw new Error(`No shape util found for type "${type}"`)
|
|
1378
1416
|
return shapeUtil
|
|
1379
1417
|
}
|
|
1380
1418
|
|
|
@@ -2451,9 +2489,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2451
2489
|
const selectedShapes = this.getSelectedShapes()
|
|
2452
2490
|
if (!selectedShapes.length) return
|
|
2453
2491
|
const selectedShape = selectedShapes[0]
|
|
2454
|
-
const children =
|
|
2455
|
-
.map((id) => this.getShape(id))
|
|
2456
|
-
|
|
2492
|
+
const children = compact(
|
|
2493
|
+
this.getSortedChildIdsForParent(selectedShape.id).map((id) => this.getShape(id))
|
|
2494
|
+
)
|
|
2457
2495
|
const sortedChildren = this._getShapesInReadingOrder(children)
|
|
2458
2496
|
if (sortedChildren.length === 0) return
|
|
2459
2497
|
this._selectShapesAndZoom([sortedChildren[0].id])
|
|
@@ -2554,20 +2592,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2554
2592
|
* @internal
|
|
2555
2593
|
*/
|
|
2556
2594
|
getShapesSharedRotation(shapeIds: TLShapeId[]) {
|
|
2557
|
-
let foundFirst = false // annoying but we can't use an i===0 check because we need to skip over undefineds
|
|
2558
2595
|
let rotation = 0
|
|
2559
2596
|
for (let i = 0, n = shapeIds.length; i < n; i++) {
|
|
2560
|
-
const
|
|
2561
|
-
if (
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
}
|
|
2567
|
-
} else {
|
|
2568
|
-
// First rotation found
|
|
2569
|
-
foundFirst = true
|
|
2570
|
-
rotation = pageTransform.rotation()
|
|
2597
|
+
const pageRotation = this.getShapePageTransform(shapeIds[i]).rotation()
|
|
2598
|
+
if (i === 0) {
|
|
2599
|
+
rotation = pageRotation
|
|
2600
|
+
} else if (pageRotation !== rotation) {
|
|
2601
|
+
// There are at least 2 different rotations, so the common rotation is zero
|
|
2602
|
+
return 0
|
|
2571
2603
|
}
|
|
2572
2604
|
}
|
|
2573
2605
|
|
|
@@ -2599,7 +2631,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2599
2631
|
|
|
2600
2632
|
if (shapeIds.length === 1) {
|
|
2601
2633
|
const bounds = this.getShapeGeometry(shapeIds[0]).bounds.clone()
|
|
2602
|
-
const pageTransform = this.getShapePageTransform(shapeIds[0])
|
|
2634
|
+
const pageTransform = this.getShapePageTransform(shapeIds[0])
|
|
2603
2635
|
bounds.point = pageTransform.applyToPoint(bounds.point)
|
|
2604
2636
|
return bounds
|
|
2605
2637
|
}
|
|
@@ -2607,11 +2639,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2607
2639
|
// need to 'un-rotate' all the outlines of the existing nodes so we can fit them inside a box
|
|
2608
2640
|
const boxFromRotatedVertices = Box.FromPoints(
|
|
2609
2641
|
shapeIds
|
|
2610
|
-
.flatMap((id) =>
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
return pageTransform.applyToPoints(this.getShapeGeometry(id).bounds.corners)
|
|
2614
|
-
})
|
|
2642
|
+
.flatMap((id) =>
|
|
2643
|
+
this.getShapePageTransform(id).applyToPoints(this.getShapeGeometry(id).bounds.corners)
|
|
2644
|
+
)
|
|
2615
2645
|
.map((p) => p.rot(-selectionRotation))
|
|
2616
2646
|
)
|
|
2617
2647
|
// now position box so that it's top-left corner is in the right place
|
|
@@ -2638,9 +2668,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2638
2668
|
@computed getSelectionRotatedScreenBounds(): Box | undefined {
|
|
2639
2669
|
const bounds = this.getSelectionRotatedPageBounds()
|
|
2640
2670
|
if (!bounds) return undefined
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2671
|
+
// Don't use pageToScreen here: it reads the screen bounds without capturing them, so this
|
|
2672
|
+
// computed would never invalidate when the container moves
|
|
2673
|
+
const screenBounds = this.getViewportScreenBounds()
|
|
2674
|
+
const { x: cx, y: cy, z: zoom } = this.getCamera()
|
|
2675
|
+
return new Box(
|
|
2676
|
+
(bounds.x + cx) * zoom + screenBounds.x,
|
|
2677
|
+
(bounds.y + cy) * zoom + screenBounds.y,
|
|
2678
|
+
bounds.width * zoom,
|
|
2679
|
+
bounds.height * zoom
|
|
2680
|
+
)
|
|
2644
2681
|
}
|
|
2645
2682
|
|
|
2646
2683
|
// Focus Group
|
|
@@ -2780,57 +2817,38 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2780
2817
|
setEditingShape(shape: TLShapeId | TLShape | null): this {
|
|
2781
2818
|
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2782
2819
|
|
|
2783
|
-
|
|
2784
|
-
|
|
2820
|
+
// id was provided but the next editing shape was not editable or didn't exist, so do nothing
|
|
2821
|
+
if (id && !this.canEditShape(id)) return this
|
|
2822
|
+
|
|
2823
|
+
this.run(() => {
|
|
2824
|
+
// Clean up the previous editing shape. This runs outside the history-ignored batch below,
|
|
2825
|
+
// otherwise document changes made by onEditEnd (e.g. deleting an empty text shape) are
|
|
2826
|
+
// never recorded and leave a phantom undo entry whose redo resurrects the shape.
|
|
2827
|
+
const prevEditingShapeId = this.getEditingShapeId()
|
|
2828
|
+
if (prevEditingShapeId) {
|
|
2829
|
+
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2830
|
+
if (prevEditingShape) {
|
|
2831
|
+
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2785
2835
|
this.run(
|
|
2786
2836
|
() => {
|
|
2787
|
-
// Clean up the previous editing shape
|
|
2788
|
-
const prevEditingShapeId = this.getEditingShapeId()
|
|
2789
|
-
if (prevEditingShapeId) {
|
|
2790
|
-
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2791
|
-
if (prevEditingShape) {
|
|
2792
|
-
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2793
|
-
}
|
|
2794
|
-
}
|
|
2795
|
-
|
|
2796
2837
|
// Clean up the editing shape state and rich text editor
|
|
2797
2838
|
this._updateCurrentPageState({ editingShapeId: null })
|
|
2798
2839
|
this._currentRichTextEditor.set(null)
|
|
2799
|
-
},
|
|
2800
|
-
{ history: 'ignore' }
|
|
2801
|
-
)
|
|
2802
2840
|
|
|
2803
|
-
|
|
2804
|
-
}
|
|
2841
|
+
if (!id) return
|
|
2805
2842
|
|
|
2806
|
-
|
|
2807
|
-
|
|
2843
|
+
this.select(id)
|
|
2844
|
+
this._updateCurrentPageState({ editingShapeId: id })
|
|
2808
2845
|
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2816
|
-
if (prevEditingShape) {
|
|
2817
|
-
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
|
|
2821
|
-
// Clean up the editing shape state and rich text editor
|
|
2822
|
-
this._updateCurrentPageState({ editingShapeId: null })
|
|
2823
|
-
this._currentRichTextEditor.set(null)
|
|
2824
|
-
|
|
2825
|
-
// Set the new editing shape
|
|
2826
|
-
this.select(id)
|
|
2827
|
-
this._updateCurrentPageState({ editingShapeId: id })
|
|
2828
|
-
|
|
2829
|
-
const nextEditingShape = this.getShape(id)! // shape should be there because canEditShape checked it. Possible small chance that onEditEnd deleted it?
|
|
2830
|
-
this.getShapeUtil(nextEditingShape).onEditStart?.(nextEditingShape)
|
|
2831
|
-
},
|
|
2832
|
-
{ history: 'ignore' }
|
|
2833
|
-
)
|
|
2846
|
+
const nextEditingShape = this.getShape(id)! // shape should be there because canEditShape checked it. Possible small chance that onEditEnd deleted it?
|
|
2847
|
+
this.getShapeUtil(nextEditingShape).onEditStart?.(nextEditingShape)
|
|
2848
|
+
},
|
|
2849
|
+
{ history: 'ignore' }
|
|
2850
|
+
)
|
|
2851
|
+
})
|
|
2834
2852
|
|
|
2835
2853
|
return this
|
|
2836
2854
|
}
|
|
@@ -2995,26 +3013,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2995
3013
|
* @public
|
|
2996
3014
|
*/
|
|
2997
3015
|
setErasingShapes(shapes: TLShapeId[] | TLShape[]): this {
|
|
3016
|
+
// copy before sorting: the caller may pass a store-owned (frozen) array
|
|
2998
3017
|
const ids =
|
|
2999
3018
|
typeof shapes[0] === 'string'
|
|
3000
|
-
? (shapes as TLShapeId[])
|
|
3019
|
+
? (shapes as TLShapeId[]).slice()
|
|
3001
3020
|
: (shapes as TLShape[]).map((shape) => shape.id)
|
|
3002
3021
|
ids.sort() // sort the incoming ids
|
|
3003
3022
|
const erasingShapeIds = this.getErasingShapeIds()
|
|
3004
3023
|
this.run(
|
|
3005
3024
|
() => {
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
// presuming the current ids are also sorted, check each item to see if it's the same;
|
|
3009
|
-
// if we find any unequal, then we know the new ids are different.
|
|
3010
|
-
for (let i = 0; i < ids.length; i++) {
|
|
3011
|
-
if (ids[i] !== erasingShapeIds[i]) {
|
|
3012
|
-
this._updateCurrentPageState({ erasingShapeIds: ids })
|
|
3013
|
-
break
|
|
3014
|
-
}
|
|
3015
|
-
}
|
|
3016
|
-
} else {
|
|
3017
|
-
// if the ids are a different length, then we know they're different.
|
|
3025
|
+
// the current ids are also sorted, so a shallow comparison tells us whether they changed
|
|
3026
|
+
if (!areArraysShallowEqual(ids, erasingShapeIds)) {
|
|
3018
3027
|
this._updateCurrentPageState({ erasingShapeIds: ids })
|
|
3019
3028
|
}
|
|
3020
3029
|
},
|
|
@@ -3051,6 +3060,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3051
3060
|
if (!_shape) return false
|
|
3052
3061
|
const util = this.getShapeUtil(_shape)
|
|
3053
3062
|
if (!util.canCrop(_shape)) return false
|
|
3063
|
+
if (this.getIsReadonly()) return false
|
|
3054
3064
|
if (this.isShapeOrAncestorLocked(_shape)) return false
|
|
3055
3065
|
return true
|
|
3056
3066
|
}
|
|
@@ -3130,11 +3140,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3130
3140
|
const collaborators = this.getCollaborators()
|
|
3131
3141
|
let leaderPresence = null as null | TLInstancePresence
|
|
3132
3142
|
while (targetUserId && !visited.includes(targetUserId)) {
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3143
|
+
const nextPresence = collaborators.find((c) => c.userId === targetUserId)
|
|
3144
|
+
// Stop at the last resolvable presence, otherwise a leader whose own leader has left
|
|
3145
|
+
// (or whose presence hasn't arrived yet) can't be followed at all
|
|
3146
|
+
if (!nextPresence) break
|
|
3147
|
+
leaderPresence = nextPresence
|
|
3148
|
+
targetUserId = nextPresence.followingUserId ?? null
|
|
3149
|
+
visited.push(nextPresence.userId)
|
|
3138
3150
|
}
|
|
3139
3151
|
return leaderPresence
|
|
3140
3152
|
}
|
|
@@ -3252,44 +3264,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3252
3264
|
*
|
|
3253
3265
|
* @public */
|
|
3254
3266
|
getInitialZoom() {
|
|
3255
|
-
|
|
3256
|
-
// If no camera constraints are provided, the default zoom is 100%
|
|
3257
|
-
if (!cameraOptions.constraints) return 1
|
|
3258
|
-
|
|
3259
|
-
// When defaultZoom is default, the default zoom is 100%
|
|
3260
|
-
if (cameraOptions.constraints.initialZoom === 'default') return 1
|
|
3261
|
-
|
|
3262
|
-
const { zx, zy } = getCameraFitXFitY(this, cameraOptions)
|
|
3263
|
-
|
|
3264
|
-
switch (cameraOptions.constraints.initialZoom) {
|
|
3265
|
-
case 'fit-min': {
|
|
3266
|
-
return Math.max(zx, zy)
|
|
3267
|
-
}
|
|
3268
|
-
case 'fit-max': {
|
|
3269
|
-
return Math.min(zx, zy)
|
|
3270
|
-
}
|
|
3271
|
-
case 'fit-x': {
|
|
3272
|
-
return zx
|
|
3273
|
-
}
|
|
3274
|
-
case 'fit-y': {
|
|
3275
|
-
return zy
|
|
3276
|
-
}
|
|
3277
|
-
case 'fit-min-100': {
|
|
3278
|
-
return Math.min(1, Math.max(zx, zy))
|
|
3279
|
-
}
|
|
3280
|
-
case 'fit-max-100': {
|
|
3281
|
-
return Math.min(1, Math.min(zx, zy))
|
|
3282
|
-
}
|
|
3283
|
-
case 'fit-x-100': {
|
|
3284
|
-
return Math.min(1, zx)
|
|
3285
|
-
}
|
|
3286
|
-
case 'fit-y-100': {
|
|
3287
|
-
return Math.min(1, zy)
|
|
3288
|
-
}
|
|
3289
|
-
default: {
|
|
3290
|
-
throw exhaustiveSwitchError(cameraOptions.constraints.initialZoom)
|
|
3291
|
-
}
|
|
3292
|
-
}
|
|
3267
|
+
return this._getFitZoom(this.getCameraOptions().constraints?.initialZoom ?? 'default')
|
|
3293
3268
|
}
|
|
3294
3269
|
|
|
3295
3270
|
/**
|
|
@@ -3302,16 +3277,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3302
3277
|
*
|
|
3303
3278
|
* @public */
|
|
3304
3279
|
getBaseZoom() {
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
if (!cameraOptions.constraints) return 1
|
|
3280
|
+
return this._getFitZoom(this.getCameraOptions().constraints?.baseZoom ?? 'default')
|
|
3281
|
+
}
|
|
3308
3282
|
|
|
3309
|
-
|
|
3310
|
-
|
|
3283
|
+
private _getFitZoom(fit: TLCameraConstraints['initialZoom']): number {
|
|
3284
|
+
const cameraOptions = this.getCameraOptions()
|
|
3285
|
+
if (!cameraOptions.constraints || fit === 'default') return 1
|
|
3311
3286
|
|
|
3312
3287
|
const { zx, zy } = getCameraFitXFitY(this, cameraOptions)
|
|
3313
3288
|
|
|
3314
|
-
switch (
|
|
3289
|
+
switch (fit) {
|
|
3315
3290
|
case 'fit-min': {
|
|
3316
3291
|
return Math.max(zx, zy)
|
|
3317
3292
|
}
|
|
@@ -3337,7 +3312,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3337
3312
|
return Math.min(1, zy)
|
|
3338
3313
|
}
|
|
3339
3314
|
default: {
|
|
3340
|
-
throw exhaustiveSwitchError(
|
|
3315
|
+
throw exhaustiveSwitchError(fit)
|
|
3341
3316
|
}
|
|
3342
3317
|
}
|
|
3343
3318
|
}
|
|
@@ -3374,7 +3349,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3374
3349
|
...this._cameraOptions.__unsafe__getWithoutCapture(),
|
|
3375
3350
|
...opts,
|
|
3376
3351
|
})
|
|
3377
|
-
|
|
3352
|
+
// `undefined < 1` is false, so an explicit `zoomSteps: undefined` would otherwise get through
|
|
3353
|
+
// and make every later camera move throw
|
|
3354
|
+
if (!next.zoomSteps || next.zoomSteps.length < 1) next.zoomSteps = [1]
|
|
3378
3355
|
this._cameraOptions.set(next)
|
|
3379
3356
|
this.setCamera(this.getCamera())
|
|
3380
3357
|
return this
|
|
@@ -3420,8 +3397,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3420
3397
|
const { constraints } = cameraOptions
|
|
3421
3398
|
|
|
3422
3399
|
// Clamp padding to half the viewport size on either dimension
|
|
3423
|
-
const
|
|
3424
|
-
const
|
|
3400
|
+
const px = Math.min(constraints.padding.x, vsb.w / 2)
|
|
3401
|
+
const py = Math.min(constraints.padding.y, vsb.h / 2)
|
|
3425
3402
|
|
|
3426
3403
|
// Expand the bounds by the padding
|
|
3427
3404
|
const bounds = Box.From(cameraOptions.constraints.bounds)
|
|
@@ -3490,14 +3467,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3490
3467
|
}
|
|
3491
3468
|
case 'inside': {
|
|
3492
3469
|
// When below fit zoom, constrain the camera so that the bounds stay completely within the viewport
|
|
3493
|
-
if (z < zx) x = clamp(x, minX, (vsb.w - px) / z - bounds.w)
|
|
3470
|
+
if (z < zx) x = clamp(x, minX, (vsb.w - px) / z - bounds.w - bounds.x)
|
|
3494
3471
|
// When above fit zoom, keep the bounds within padding distance of the viewport edge
|
|
3495
3472
|
else x = clamp(x, minX + freeW, minX)
|
|
3496
3473
|
break
|
|
3497
3474
|
}
|
|
3498
3475
|
case 'outside': {
|
|
3499
3476
|
// Constrain the camera so that the bounds never leaves the viewport
|
|
3500
|
-
x = clamp(x, px / z - bounds.w, (vsb.w - px) / z)
|
|
3477
|
+
x = clamp(x, px / z - bounds.w - bounds.x, (vsb.w - px) / z - bounds.x)
|
|
3501
3478
|
break
|
|
3502
3479
|
}
|
|
3503
3480
|
case 'free': {
|
|
@@ -3522,12 +3499,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3522
3499
|
break
|
|
3523
3500
|
}
|
|
3524
3501
|
case 'inside': {
|
|
3525
|
-
if (z < zy) y = clamp(y, minY, (vsb.h - py) / z - bounds.h)
|
|
3502
|
+
if (z < zy) y = clamp(y, minY, (vsb.h - py) / z - bounds.h - bounds.y)
|
|
3526
3503
|
else y = clamp(y, minY + freeH, minY)
|
|
3527
3504
|
break
|
|
3528
3505
|
}
|
|
3529
3506
|
case 'outside': {
|
|
3530
|
-
y = clamp(y, py / z - bounds.h, (vsb.h - py) / z)
|
|
3507
|
+
y = clamp(y, py / z - bounds.h - bounds.y, (vsb.h - py) / z - bounds.y)
|
|
3531
3508
|
break
|
|
3532
3509
|
}
|
|
3533
3510
|
case 'free': {
|
|
@@ -3982,9 +3959,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3982
3959
|
const remaining = duration - elapsed
|
|
3983
3960
|
const t = easing(1 - remaining / duration)
|
|
3984
3961
|
|
|
3985
|
-
const left = start.minX
|
|
3986
|
-
const top = start.minY
|
|
3987
|
-
const right = start.maxX
|
|
3962
|
+
const left = lerp(start.minX, end.minX, t)
|
|
3963
|
+
const top = lerp(start.minY, end.minY, t)
|
|
3964
|
+
const right = lerp(start.maxX, end.maxX, t)
|
|
3988
3965
|
|
|
3989
3966
|
this._setCamera(new Vec(-left, -top, this.getViewportScreenBounds().width / (right - left)), {
|
|
3990
3967
|
force: true,
|
|
@@ -4099,9 +4076,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4099
4076
|
let newCy = cy + dy
|
|
4100
4077
|
let newCz = cz
|
|
4101
4078
|
|
|
4102
|
-
// animate zoom if z direction is passed in
|
|
4079
|
+
// animate zoom if z direction is passed in. Use an exponential factor so that a single
|
|
4080
|
+
// long frame can't drive the zoom to zero or negative (which would give NaN coordinates)
|
|
4103
4081
|
if (dirZ !== 0) {
|
|
4104
|
-
newCz = cz * (
|
|
4082
|
+
newCz = cz * Math.exp(dirZ * currentSpeed * elapsed)
|
|
4105
4083
|
// Adjust x/y to keep the viewport center fixed while zooming
|
|
4106
4084
|
const center = this.getViewportScreenCenter()
|
|
4107
4085
|
newCx += center.x / newCz - center.x / cz
|
|
@@ -4686,7 +4664,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4686
4664
|
let nextIndex = this.options.maxShapesPerPage * 2
|
|
4687
4665
|
let nextBackgroundIndex = this.options.maxShapesPerPage
|
|
4688
4666
|
|
|
4689
|
-
const erasingShapeIds = this.getErasingShapeIds()
|
|
4667
|
+
const erasingShapeIds = new Set(this.getErasingShapeIds())
|
|
4690
4668
|
|
|
4691
4669
|
const addShapeById = (id: TLShapeId, opacity: number, isAncestorErasing: boolean) => {
|
|
4692
4670
|
const shape = this.getShape(id)
|
|
@@ -4694,7 +4672,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4694
4672
|
|
|
4695
4673
|
if (this.isShapeHidden(shape)) {
|
|
4696
4674
|
// process children just in case they are overriding the hidden state
|
|
4697
|
-
const isErasing = isAncestorErasing || erasingShapeIds.
|
|
4675
|
+
const isErasing = isAncestorErasing || erasingShapeIds.has(id)
|
|
4698
4676
|
for (const childId of this.getSortedChildIdsForParent(id)) {
|
|
4699
4677
|
addShapeById(childId, opacity, isErasing)
|
|
4700
4678
|
}
|
|
@@ -4706,7 +4684,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4706
4684
|
const util = this.getShapeUtil(shape)
|
|
4707
4685
|
|
|
4708
4686
|
if (useEditorState) {
|
|
4709
|
-
isShapeErasing = !isAncestorErasing && erasingShapeIds.
|
|
4687
|
+
isShapeErasing = !isAncestorErasing && erasingShapeIds.has(id)
|
|
4710
4688
|
if (isShapeErasing) {
|
|
4711
4689
|
opacity *= 0.32
|
|
4712
4690
|
}
|
|
@@ -5465,11 +5443,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5465
5443
|
/** @internal */
|
|
5466
5444
|
@computed private _getShapePageBoundsCache(): ComputedCache<Box, TLShape> {
|
|
5467
5445
|
return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
|
|
5468
|
-
const pageTransform = this.getShapePageTransform(shape)
|
|
5469
|
-
if (!pageTransform) return undefined
|
|
5470
|
-
|
|
5471
5446
|
return Box.FromPoints(
|
|
5472
|
-
|
|
5447
|
+
this.getShapePageTransform(shape).applyToPoints(this.getShapeGeometry(shape).boundsVertices)
|
|
5473
5448
|
)
|
|
5474
5449
|
})
|
|
5475
5450
|
}
|
|
@@ -5608,7 +5583,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5608
5583
|
if (pageMask) {
|
|
5609
5584
|
if (pageMask.length === 0) return undefined
|
|
5610
5585
|
const { corners } = pageBounds
|
|
5611
|
-
|
|
5586
|
+
// the mask may have fewer than four points (e.g. a triangular clip path)
|
|
5587
|
+
if (
|
|
5588
|
+
pageMask.length === corners.length &&
|
|
5589
|
+
corners.every((p, i) => Vec.Equals(p, pageMask[i]))
|
|
5590
|
+
) {
|
|
5591
|
+
return pageBounds.clone()
|
|
5592
|
+
}
|
|
5612
5593
|
const intersection = intersectPolygonPolygon(pageMask, corners)
|
|
5613
5594
|
if (!intersection) return
|
|
5614
5595
|
return Box.FromPoints(intersection)
|
|
@@ -5936,17 +5917,19 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5936
5917
|
|
|
5937
5918
|
const pointInShapeSpace = this.getPointInShapeSpace(shape, point)
|
|
5938
5919
|
|
|
5939
|
-
// Check labels first
|
|
5920
|
+
// Check labels first. Only group geometries can carry a label child; a frame-like
|
|
5921
|
+
// shape util may still return a plain geometry.
|
|
5940
5922
|
const shapeUtil = this.getShapeUtil(shape)
|
|
5941
5923
|
const isShapeFrameLike = this.isShapeFrameLike(shape)
|
|
5942
5924
|
if (
|
|
5943
|
-
|
|
5944
|
-
(
|
|
5945
|
-
this.isShapeOfType(shape, '
|
|
5946
|
-
|
|
5947
|
-
|
|
5925
|
+
isGroup &&
|
|
5926
|
+
(isShapeFrameLike ||
|
|
5927
|
+
((this.isShapeOfType(shape, 'note') ||
|
|
5928
|
+
this.isShapeOfType(shape, 'arrow') ||
|
|
5929
|
+
(this.isShapeOfType(shape, 'geo') && shape.props.fill === 'none')) &&
|
|
5930
|
+
shapeUtil.getText(shape)?.trim()))
|
|
5948
5931
|
) {
|
|
5949
|
-
for (const childGeometry of
|
|
5932
|
+
for (const childGeometry of geometry.children) {
|
|
5950
5933
|
if (childGeometry.isLabel && childGeometry.isPointInBounds(pointInShapeSpace)) {
|
|
5951
5934
|
return shape
|
|
5952
5935
|
}
|
|
@@ -6223,9 +6206,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6223
6206
|
if (!freshShape) return new Vec(0, 0)
|
|
6224
6207
|
if (isPageId(freshShape.parentId)) return Vec.From(point)
|
|
6225
6208
|
|
|
6226
|
-
|
|
6227
|
-
if (!parentTransform) return Vec.From(point)
|
|
6228
|
-
return parentTransform.clone().invert().applyToPoint(point)
|
|
6209
|
+
return this.getShapePageTransform(freshShape.parentId).clone().invert().applyToPoint(point)
|
|
6229
6210
|
}
|
|
6230
6211
|
|
|
6231
6212
|
/**
|
|
@@ -6392,26 +6373,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6392
6373
|
* @public
|
|
6393
6374
|
*/
|
|
6394
6375
|
isShapeInPage(shape: TLShape | TLShapeId, pageId = this.getCurrentPageId()): boolean {
|
|
6395
|
-
|
|
6396
|
-
const shapeToCheck = this.getShape(id)
|
|
6397
|
-
if (!shapeToCheck) return false
|
|
6398
|
-
|
|
6399
|
-
let shapeIsInPage = false
|
|
6400
|
-
|
|
6401
|
-
if (shapeToCheck.parentId === pageId) {
|
|
6402
|
-
shapeIsInPage = true
|
|
6403
|
-
} else {
|
|
6404
|
-
let parent = this.getShape(shapeToCheck.parentId)
|
|
6405
|
-
isInPageSearch: while (parent) {
|
|
6406
|
-
if (parent.parentId === pageId) {
|
|
6407
|
-
shapeIsInPage = true
|
|
6408
|
-
break isInPageSearch
|
|
6409
|
-
}
|
|
6410
|
-
parent = this.getShape(parent.parentId)
|
|
6411
|
-
}
|
|
6412
|
-
}
|
|
6413
|
-
|
|
6414
|
-
return shapeIsInPage
|
|
6376
|
+
return this.getAncestorPageId(shape) === pageId
|
|
6415
6377
|
}
|
|
6416
6378
|
|
|
6417
6379
|
/**
|
|
@@ -6522,13 +6484,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6522
6484
|
for (let i = 0; i < shapesToReparent.length; i++) {
|
|
6523
6485
|
const shape = shapesToReparent[i]
|
|
6524
6486
|
|
|
6525
|
-
const pageTransform = this.getShapePageTransform(shape)
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
const pagePoint = pageTransform.point()
|
|
6529
|
-
if (!pagePoint) continue
|
|
6530
|
-
|
|
6531
|
-
const newPoint = invertedParentTransform.applyToPoint(pagePoint)
|
|
6487
|
+
const pageTransform = this.getShapePageTransform(shape)
|
|
6488
|
+
const newPoint = invertedParentTransform.applyToPoint(pageTransform.point())
|
|
6532
6489
|
const newRotation = pageTransform.rotation() - parentPageRotation
|
|
6533
6490
|
|
|
6534
6491
|
if (shape.id === parentId) {
|
|
@@ -6629,7 +6586,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6629
6586
|
*/
|
|
6630
6587
|
getShapeAndDescendantIds(ids: TLShapeId[]): Set<TLShapeId> {
|
|
6631
6588
|
const shapeIds = new Set<TLShapeId>()
|
|
6632
|
-
for (const shape of ids.map((id) => this.getShape(id)
|
|
6589
|
+
for (const shape of compact(ids.map((id) => this.getShape(id))).sort(sortByIndex)) {
|
|
6633
6590
|
shapeIds.add(shape.id)
|
|
6634
6591
|
this.visitDescendants(shape, (descendantId) => {
|
|
6635
6592
|
shapeIds.add(descendantId)
|
|
@@ -6653,6 +6610,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6653
6610
|
const draggingShapes = compact(droppingShapes.map((s) => this.getShape(s))).filter(
|
|
6654
6611
|
(s) => !s.isLocked && !this.isShapeHidden(s)
|
|
6655
6612
|
)
|
|
6613
|
+
// Descendants of the dragged shapes can't be the target, otherwise dragging a frame out of
|
|
6614
|
+
// its parent reports a nested child as the target and the parent never sees the drag leave
|
|
6615
|
+
const excludedIds = this.getShapeAndDescendantIds(draggingShapes.map((s) => s.id))
|
|
6656
6616
|
|
|
6657
6617
|
const maybeDraggingOverShapes = this.getShapesAtPoint(point, {
|
|
6658
6618
|
hitInside: true,
|
|
@@ -6662,7 +6622,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6662
6622
|
!droppingShapes.includes(s) &&
|
|
6663
6623
|
!s.isLocked &&
|
|
6664
6624
|
!this.isShapeHidden(s) &&
|
|
6665
|
-
!
|
|
6625
|
+
!excludedIds.has(s.id)
|
|
6666
6626
|
)
|
|
6667
6627
|
|
|
6668
6628
|
for (const maybeDraggingOverShape of maybeDraggingOverShapes) {
|
|
@@ -7036,8 +6996,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7036
6996
|
for (const id of ids) {
|
|
7037
6997
|
const shape = this.getShape(id)!
|
|
7038
6998
|
const localDelta = Vec.From(offset)
|
|
7039
|
-
|
|
7040
|
-
if (parentTransform) localDelta.rot(-parentTransform.rotation())
|
|
6999
|
+
localDelta.rot(-this.getShapeParentTransform(shape).rotation())
|
|
7041
7000
|
|
|
7042
7001
|
changes.push(this.getChangesToTranslateShape(shape, localDelta.add(shape)))
|
|
7043
7002
|
}
|
|
@@ -7107,7 +7066,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7107
7066
|
let ox = 0
|
|
7108
7067
|
let oy = 0
|
|
7109
7068
|
|
|
7110
|
-
|
|
7069
|
+
// Only offset the roots of the duplicated tree: a descendant that was also passed in
|
|
7070
|
+
// follows its duplicated parent, so offsetting it too would move it twice
|
|
7071
|
+
if (
|
|
7072
|
+
offset &&
|
|
7073
|
+
initialIds.has(originalId) &&
|
|
7074
|
+
!shapeIdSet.has(originalShape.parentId as TLShapeId)
|
|
7075
|
+
) {
|
|
7111
7076
|
const parentTransform = this.getShapeParentTransform(originalShape)
|
|
7112
7077
|
const vec = new Vec(offset.x, offset.y).rot(-parentTransform!.rotation())
|
|
7113
7078
|
ox = vec.x
|
|
@@ -7273,27 +7238,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7273
7238
|
|
|
7274
7239
|
if (this.getIsReadonly() || ids.length === 0) return this
|
|
7275
7240
|
|
|
7276
|
-
let allLocked = true
|
|
7277
|
-
allUnlocked = true
|
|
7241
|
+
let allLocked = true
|
|
7278
7242
|
const shapesToToggle: TLShape[] = []
|
|
7279
7243
|
for (const id of ids) {
|
|
7280
7244
|
const shape = this.getShape(id)
|
|
7281
7245
|
if (shape) {
|
|
7282
7246
|
shapesToToggle.push(shape)
|
|
7283
|
-
if (shape.isLocked)
|
|
7284
|
-
allUnlocked = false
|
|
7285
|
-
} else {
|
|
7286
|
-
allLocked = false
|
|
7287
|
-
}
|
|
7247
|
+
if (!shape.isLocked) allLocked = false
|
|
7288
7248
|
}
|
|
7289
7249
|
}
|
|
7290
7250
|
this.run(() => {
|
|
7291
|
-
if (
|
|
7292
|
-
this.updateShapes(
|
|
7293
|
-
shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
|
|
7294
|
-
)
|
|
7295
|
-
this.setSelectedShapes([])
|
|
7296
|
-
} else if (allLocked) {
|
|
7251
|
+
if (allLocked) {
|
|
7297
7252
|
this.updateShapes(
|
|
7298
7253
|
shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: false }))
|
|
7299
7254
|
)
|
|
@@ -7301,6 +7256,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7301
7256
|
this.updateShapes(
|
|
7302
7257
|
shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
|
|
7303
7258
|
)
|
|
7259
|
+
this.setSelectedShapes([])
|
|
7304
7260
|
}
|
|
7305
7261
|
})
|
|
7306
7262
|
|
|
@@ -7440,9 +7396,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7440
7396
|
|
|
7441
7397
|
// optionally filter to axis-aligned shapes (rotation is a multiple of 90 degrees)
|
|
7442
7398
|
if (opts?.filterAxisAligned) {
|
|
7443
|
-
freshShapes = freshShapes.filter(
|
|
7444
|
-
|
|
7445
|
-
|
|
7399
|
+
freshShapes = freshShapes.filter((s) => {
|
|
7400
|
+
// Page rotations come back through atan2 on a composed matrix, so a 270° shape can land
|
|
7401
|
+
// a hair either side of the multiple; an exact === 0 check drops it
|
|
7402
|
+
const remainder = Math.abs(this.getShapePageTransform(s).rotation() % HALF_PI)
|
|
7403
|
+
return Math.min(remainder, HALF_PI - remainder) < 1e-9
|
|
7404
|
+
})
|
|
7446
7405
|
}
|
|
7447
7406
|
|
|
7448
7407
|
const clusters: { shapes: TLShape[]; pageBounds: Box }[] = []
|
|
@@ -7663,13 +7622,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7663
7622
|
|
|
7664
7623
|
let shapeGap: number = 0
|
|
7665
7624
|
|
|
7625
|
+
// Stack in spatial order rather than input (z) order, otherwise shapes swap places
|
|
7626
|
+
shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])
|
|
7627
|
+
|
|
7666
7628
|
if (_gap === 0) {
|
|
7667
7629
|
// note: this is not used in the current tldraw.com; there we use a specified stack
|
|
7668
7630
|
|
|
7669
7631
|
const gaps: Record<number, number> = {}
|
|
7670
7632
|
|
|
7671
|
-
shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])
|
|
7672
|
-
|
|
7673
7633
|
// Collect all of the gaps between shapes. We want to find
|
|
7674
7634
|
// patterns (equal gaps between shapes) and use the most common
|
|
7675
7635
|
// one as the gap for all of the shapes.
|
|
@@ -7722,8 +7682,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7722
7682
|
// todo: ensure that the parent isn't being aligned together with its children
|
|
7723
7683
|
const parent = this.getShapeParent(shape)
|
|
7724
7684
|
if (parent) {
|
|
7725
|
-
|
|
7726
|
-
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
7685
|
+
shapeDelta.rot(-this.getShapePageTransform(parent).rotation())
|
|
7727
7686
|
}
|
|
7728
7687
|
|
|
7729
7688
|
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
@@ -7773,10 +7732,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7773
7732
|
|
|
7774
7733
|
const maxWidth = commonBounds.width
|
|
7775
7734
|
|
|
7776
|
-
// sort the shape clusters by width and then height, descending
|
|
7735
|
+
// sort the shape clusters by width and then height, descending: potpack fills each row's
|
|
7736
|
+
// right-hand space with the shapes that follow, so they must be no taller than the row
|
|
7777
7737
|
shapeClustersToPack
|
|
7778
|
-
.sort((a, b) =>
|
|
7779
|
-
.sort((a, b) =>
|
|
7738
|
+
.sort((a, b) => b.pageBounds.width - a.pageBounds.width)
|
|
7739
|
+
.sort((a, b) => b.pageBounds.height - a.pageBounds.height)
|
|
7780
7740
|
|
|
7781
7741
|
// Start with is (sort of) the square of the area
|
|
7782
7742
|
const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth)
|
|
@@ -7846,8 +7806,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7846
7806
|
|
|
7847
7807
|
const parent = this.getShapeParent(shape)
|
|
7848
7808
|
if (parent) {
|
|
7849
|
-
|
|
7850
|
-
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
7809
|
+
shapeDelta.rot(-this.getShapeParentTransform(shape).rotation())
|
|
7851
7810
|
}
|
|
7852
7811
|
|
|
7853
7812
|
shapeDelta.add(shape)
|
|
@@ -7937,8 +7896,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7937
7896
|
// todo: ensure that the parent isn't being aligned together with its children
|
|
7938
7897
|
const parent = this.getShapeParent(shape)
|
|
7939
7898
|
if (parent) {
|
|
7940
|
-
|
|
7941
|
-
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
7899
|
+
shapeDelta.rot(-this.getShapePageTransform(parent).rotation())
|
|
7942
7900
|
}
|
|
7943
7901
|
|
|
7944
7902
|
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
@@ -8038,8 +7996,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8038
7996
|
// todo: ensure that the parent isn't being aligned together with its children
|
|
8039
7997
|
const parent = this.getShapeParent(shape)
|
|
8040
7998
|
if (parent) {
|
|
8041
|
-
|
|
8042
|
-
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
7999
|
+
shapeDelta.rot(-this.getShapePageTransform(parent).rotation())
|
|
8043
8000
|
}
|
|
8044
8001
|
|
|
8045
8002
|
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
@@ -8107,8 +8064,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8107
8064
|
for (const shape of shapes) {
|
|
8108
8065
|
// First translate
|
|
8109
8066
|
const shapeLocalOffset = localOffset.clone()
|
|
8110
|
-
|
|
8111
|
-
if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation())
|
|
8067
|
+
shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation())
|
|
8112
8068
|
shapeLocalOffset.add(shape)
|
|
8113
8069
|
const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset)
|
|
8114
8070
|
this.updateShape(changes)
|
|
@@ -8179,8 +8135,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8179
8135
|
for (const shape of shapes) {
|
|
8180
8136
|
// First translate
|
|
8181
8137
|
const shapeLocalOffset = localOffset.clone()
|
|
8182
|
-
|
|
8183
|
-
if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation())
|
|
8138
|
+
shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation())
|
|
8184
8139
|
shapeLocalOffset.add(shape)
|
|
8185
8140
|
const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset)
|
|
8186
8141
|
this.updateShape(changes)
|
|
@@ -8244,12 +8199,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8244
8199
|
const pageTransform = opts.initialPageTransform
|
|
8245
8200
|
? Mat.Cast(opts.initialPageTransform)
|
|
8246
8201
|
: this.getShapePageTransform(id)
|
|
8247
|
-
if (!pageTransform) return null
|
|
8248
8202
|
|
|
8249
8203
|
const pageRotation = pageTransform.rotation()
|
|
8250
8204
|
|
|
8251
|
-
if (pageRotation == null) return null
|
|
8252
|
-
|
|
8253
8205
|
const scaleAxisRotation = opts.scaleAxisRotation ?? pageRotation
|
|
8254
8206
|
|
|
8255
8207
|
const initialBounds = opts.initialBounds ?? this.getShapeGeometry(id).bounds
|
|
@@ -8486,7 +8438,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8486
8438
|
)
|
|
8487
8439
|
|
|
8488
8440
|
// now calculate how far away the shape is from where it needs to be
|
|
8489
|
-
const pageTransform = this.getShapePageTransform(id)
|
|
8441
|
+
const pageTransform = this.getShapePageTransform(id)
|
|
8490
8442
|
// We need to use the local bounds center transformed to page space, not the axis-aligned
|
|
8491
8443
|
// page bounds center. This is because the page bounds are axis-aligned and their center
|
|
8492
8444
|
// changes when the rotation changes, but we want to use the same reference point as
|
|
@@ -8494,7 +8446,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8494
8446
|
const currentLocalBounds = this.getShapeGeometry(id).bounds
|
|
8495
8447
|
const currentPageCenter = Mat.applyToPoint(pageTransform, currentLocalBounds.center)
|
|
8496
8448
|
const shapePageTransformOrigin = pageTransform.point()
|
|
8497
|
-
if (!currentPageCenter || !shapePageTransformOrigin) return this
|
|
8498
8449
|
const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter)
|
|
8499
8450
|
|
|
8500
8451
|
// and finally figure out what the shape's new position should be
|
|
@@ -8882,10 +8833,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8882
8833
|
|
|
8883
8834
|
updates.push({
|
|
8884
8835
|
...end,
|
|
8885
|
-
x: start.x
|
|
8886
|
-
y: start.y
|
|
8887
|
-
opacity: start.opacity
|
|
8888
|
-
rotation: start.rotation
|
|
8836
|
+
x: lerp(start.x, end.x, t),
|
|
8837
|
+
y: lerp(start.y, end.y, t),
|
|
8838
|
+
opacity: lerp(start.opacity, end.opacity, t),
|
|
8839
|
+
rotation: lerp(start.rotation, end.rotation, t),
|
|
8889
8840
|
props: this.getShapeUtil(end).getInterpolatedProps?.(start, end, t) ?? end.props,
|
|
8890
8841
|
})
|
|
8891
8842
|
}
|
|
@@ -9101,7 +9052,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9101
9052
|
* @public
|
|
9102
9053
|
*/
|
|
9103
9054
|
updateShapes<T extends TLShape>(partials: (TLShapePartial<T> | null | undefined)[]) {
|
|
9104
|
-
const compactedPartials: TLShapePartial<T>[] =
|
|
9055
|
+
const compactedPartials: TLShapePartial<T>[] = []
|
|
9105
9056
|
|
|
9106
9057
|
for (let i = 0, n = partials.length; i < n; i++) {
|
|
9107
9058
|
const partial = partials[i]
|
|
@@ -9247,26 +9198,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9247
9198
|
/* --------------------- Styles --------------------- */
|
|
9248
9199
|
|
|
9249
9200
|
/**
|
|
9250
|
-
*
|
|
9201
|
+
* Groups have no styles of their own: a style read or write on a selection applies to the
|
|
9202
|
+
* non-group shapes beneath each group, however deeply nested. Returns those shapes.
|
|
9251
9203
|
*
|
|
9252
9204
|
* @internal
|
|
9253
9205
|
*/
|
|
9254
|
-
private
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
}
|
|
9265
|
-
} else {
|
|
9266
|
-
for (const [style, propKey] of this.styleProps[shape.type]) {
|
|
9267
|
-
sharedStyleMap.applyValue(style, getOwnProperty(shape.props, propKey))
|
|
9206
|
+
private _getStyleableShapes(shapes: TLShape[]): TLShape[] {
|
|
9207
|
+
const result: TLShape[] = []
|
|
9208
|
+
const visit = (shape: TLShape) => {
|
|
9209
|
+
if (this.isShapeOfType(shape, 'group')) {
|
|
9210
|
+
for (const childId of this.getSortedChildIdsForParent(shape.id)) {
|
|
9211
|
+
const child = this.getShape(childId)
|
|
9212
|
+
if (child) visit(child)
|
|
9213
|
+
}
|
|
9214
|
+
} else {
|
|
9215
|
+
result.push(shape)
|
|
9268
9216
|
}
|
|
9269
9217
|
}
|
|
9218
|
+
for (const shape of shapes) visit(shape)
|
|
9219
|
+
return result
|
|
9270
9220
|
}
|
|
9271
9221
|
|
|
9272
9222
|
/**
|
|
@@ -9276,11 +9226,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9276
9226
|
*/
|
|
9277
9227
|
@computed
|
|
9278
9228
|
private _getSelectionSharedStyles(): ReadonlySharedStyleMap {
|
|
9279
|
-
const selectedShapes = this.getSelectedShapes()
|
|
9280
|
-
|
|
9281
9229
|
const sharedStyles = new SharedStyleMap()
|
|
9282
|
-
for (const
|
|
9283
|
-
|
|
9230
|
+
for (const shape of this._getStyleableShapes(this.getSelectedShapes())) {
|
|
9231
|
+
for (const [style, propKey] of this.styleProps[shape.type]) {
|
|
9232
|
+
sharedStyles.applyValue(style, getOwnProperty(shape.props, propKey))
|
|
9233
|
+
}
|
|
9284
9234
|
}
|
|
9285
9235
|
|
|
9286
9236
|
return sharedStyles
|
|
@@ -9338,18 +9288,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9338
9288
|
if (!currentTool) return styles
|
|
9339
9289
|
|
|
9340
9290
|
if (currentTool.shapeType) {
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
!(this.getShapeUtil('frame')!.options as any).showColors
|
|
9344
|
-
) {
|
|
9345
|
-
for (const style of this.styleProps[currentTool.shapeType].keys()) {
|
|
9346
|
-
if (style.id === 'tldraw:color') continue
|
|
9347
|
-
styles.applyValue(style, this.getStyleForNextShape(style))
|
|
9348
|
-
}
|
|
9349
|
-
} else {
|
|
9350
|
-
for (const style of this.styleProps[currentTool.shapeType].keys()) {
|
|
9351
|
-
styles.applyValue(style, this.getStyleForNextShape(style))
|
|
9352
|
-
}
|
|
9291
|
+
for (const style of this.styleProps[currentTool.shapeType].keys()) {
|
|
9292
|
+
styles.applyValue(style, this.getStyleForNextShape(style))
|
|
9353
9293
|
}
|
|
9354
9294
|
}
|
|
9355
9295
|
|
|
@@ -9365,27 +9305,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9365
9305
|
*/
|
|
9366
9306
|
@computed getSharedOpacity(): SharedStyle<number> {
|
|
9367
9307
|
if (this.isIn('select') && this.getSelectedShapeIds().length > 0) {
|
|
9368
|
-
const shapesToCheck: TLShape[] = []
|
|
9369
|
-
const addShape = (shapeId: TLShapeId) => {
|
|
9370
|
-
const shape = this.getShape(shapeId)
|
|
9371
|
-
if (!shape) return
|
|
9372
|
-
// For groups, ignore the opacity of the group shape and instead include
|
|
9373
|
-
// the opacity of the group's children. These are the shapes that would have
|
|
9374
|
-
// their opacity changed if the user called `setOpacity` on the current selection.
|
|
9375
|
-
if (this.isShapeOfType(shape, 'group')) {
|
|
9376
|
-
for (const childId of this.getSortedChildIdsForParent(shape.id)) {
|
|
9377
|
-
addShape(childId)
|
|
9378
|
-
}
|
|
9379
|
-
} else {
|
|
9380
|
-
shapesToCheck.push(shape)
|
|
9381
|
-
}
|
|
9382
|
-
}
|
|
9383
|
-
for (const shapeId of this.getSelectedShapeIds()) {
|
|
9384
|
-
addShape(shapeId)
|
|
9385
|
-
}
|
|
9386
|
-
|
|
9387
9308
|
let opacity: number | null = null
|
|
9388
|
-
for (const shape of
|
|
9309
|
+
for (const shape of this._getStyleableShapes(this.getSelectedShapes())) {
|
|
9389
9310
|
if (opacity === null) {
|
|
9390
9311
|
opacity = shape.opacity
|
|
9391
9312
|
} else if (opacity !== shape.opacity) {
|
|
@@ -9428,33 +9349,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9428
9349
|
const selectedShapes = this.getSelectedShapes()
|
|
9429
9350
|
|
|
9430
9351
|
if (selectedShapes.length > 0) {
|
|
9431
|
-
const shapesToUpdate: TLShape[] = []
|
|
9432
|
-
|
|
9433
|
-
// We can have many deep levels of grouped shape
|
|
9434
|
-
// Making a recursive function to look through all the levels
|
|
9435
|
-
const addShapeById = (shape: TLShape) => {
|
|
9436
|
-
if (this.isShapeOfType(shape, 'group')) {
|
|
9437
|
-
const childIds = this.getSortedChildIdsForParent(shape)
|
|
9438
|
-
for (const childId of childIds) {
|
|
9439
|
-
addShapeById(this.getShape(childId)!)
|
|
9440
|
-
}
|
|
9441
|
-
} else {
|
|
9442
|
-
shapesToUpdate.push(shape)
|
|
9443
|
-
}
|
|
9444
|
-
}
|
|
9445
|
-
|
|
9446
|
-
for (const id of selectedShapes) {
|
|
9447
|
-
addShapeById(id)
|
|
9448
|
-
}
|
|
9449
|
-
|
|
9450
9352
|
this.updateShapes(
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
}
|
|
9457
|
-
})
|
|
9353
|
+
this._getStyleableShapes(selectedShapes).map((shape) => ({
|
|
9354
|
+
id: shape.id,
|
|
9355
|
+
type: shape.type,
|
|
9356
|
+
opacity,
|
|
9357
|
+
}))
|
|
9458
9358
|
)
|
|
9459
9359
|
}
|
|
9460
9360
|
|
|
@@ -9508,43 +9408,19 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9508
9408
|
const selectedShapes = this.getSelectedShapes()
|
|
9509
9409
|
|
|
9510
9410
|
if (selectedShapes.length > 0) {
|
|
9511
|
-
const updates:
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
if (this.isShapeOfType(shape, 'group')) {
|
|
9521
|
-
const childIds = this.getSortedChildIdsForParent(shape.id)
|
|
9522
|
-
for (const childId of childIds) {
|
|
9523
|
-
addShapeById(this.getShape(childId)!)
|
|
9524
|
-
}
|
|
9525
|
-
} else {
|
|
9526
|
-
const util = this.getShapeUtil(shape)
|
|
9527
|
-
const stylePropKey = this.styleProps[shape.type].get(style)
|
|
9528
|
-
if (stylePropKey) {
|
|
9529
|
-
const shapePartial: TLShapePartial = {
|
|
9530
|
-
id: shape.id,
|
|
9531
|
-
type: shape.type,
|
|
9532
|
-
props: { [stylePropKey]: value },
|
|
9533
|
-
}
|
|
9534
|
-
updates.push({
|
|
9535
|
-
util,
|
|
9536
|
-
originalShape: shape,
|
|
9537
|
-
updatePartial: shapePartial,
|
|
9538
|
-
})
|
|
9539
|
-
}
|
|
9411
|
+
const updates: TLShapePartial[] = []
|
|
9412
|
+
for (const shape of this._getStyleableShapes(selectedShapes)) {
|
|
9413
|
+
const stylePropKey = this.styleProps[shape.type].get(style)
|
|
9414
|
+
if (stylePropKey) {
|
|
9415
|
+
updates.push({
|
|
9416
|
+
id: shape.id,
|
|
9417
|
+
type: shape.type,
|
|
9418
|
+
props: { [stylePropKey]: value },
|
|
9419
|
+
})
|
|
9540
9420
|
}
|
|
9541
9421
|
}
|
|
9542
9422
|
|
|
9543
|
-
|
|
9544
|
-
addShapeById(shape)
|
|
9545
|
-
}
|
|
9546
|
-
|
|
9547
|
-
this.updateShapes(updates.map(({ updatePartial }) => updatePartial))
|
|
9423
|
+
this.updateShapes(updates)
|
|
9548
9424
|
}
|
|
9549
9425
|
|
|
9550
9426
|
return this
|
|
@@ -10076,7 +9952,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10076
9952
|
if (shapeIdMap.has(newShape.parentId)) {
|
|
10077
9953
|
newShape.parentId = shapeIdMap.get(oldShape.parentId)!
|
|
10078
9954
|
} else {
|
|
10079
|
-
rootShapeIds.push(newShape.id)
|
|
10080
9955
|
// newShape.parentId = pasteParentId
|
|
10081
9956
|
newShape.index = index
|
|
10082
9957
|
index = getIndexAbove(index)
|
|
@@ -10119,10 +9994,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10119
9994
|
(asset.type === 'video' && asset.props.src?.startsWith('data:video'))
|
|
10120
9995
|
) {
|
|
10121
9996
|
// it's src is a base64 image or video; we need to create a new asset without the src,
|
|
10122
|
-
// then create a new asset from the original src. So we
|
|
10123
|
-
//
|
|
10124
|
-
|
|
10125
|
-
asset
|
|
9997
|
+
// then create a new asset from the original src. So we keep the original asset for the
|
|
9998
|
+
// upload and create a copy with its src removed. Copy rather than mutate: when no
|
|
9999
|
+
// migration applies, migrateStoreSnapshot hands back the caller's own record objects.
|
|
10000
|
+
assetsToUpdate.push(asset as TLImageAsset | TLVideoAsset)
|
|
10001
|
+
const assetWithoutSrc = structuredClone(asset as TLImageAsset | TLVideoAsset)
|
|
10002
|
+
assetWithoutSrc.props.src = null
|
|
10003
|
+
assetsToCreate.push(assetWithoutSrc)
|
|
10004
|
+
continue
|
|
10126
10005
|
}
|
|
10127
10006
|
|
|
10128
10007
|
// Add the asset to the list of assets to create
|
|
@@ -11117,7 +10996,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11117
10996
|
if (info.name === 'cancel' || info.name === 'complete') {
|
|
11118
10997
|
this.inputs.setIsDragging(false)
|
|
11119
10998
|
|
|
11120
|
-
|
|
10999
|
+
// A pan owned by a held spacebar outlives the cancelled interaction;
|
|
11000
|
+
// key_up ends it. Otherwise Escape mid-pan stops the camera until
|
|
11001
|
+
// the user releases and re-presses Space (#10446).
|
|
11002
|
+
if (this.inputs.getIsPanning() && !this.inputs.keys.has('Space')) {
|
|
11121
11003
|
this.inputs.setIsPanning(false)
|
|
11122
11004
|
this.inputs.setIsSpacebarPanning(false)
|
|
11123
11005
|
this.setCursor({ type: this._prevCursor, rotation: 0 })
|
|
@@ -11871,10 +11753,10 @@ function withIsolatedShapes<T>(
|
|
|
11871
11753
|
|
|
11872
11754
|
function getCameraFitXFitY(editor: Editor, cameraOptions: TLCameraOptions) {
|
|
11873
11755
|
if (!cameraOptions.constraints) throw Error('Should have constraints here')
|
|
11874
|
-
const {
|
|
11875
|
-
padding: { x: px, y: py },
|
|
11876
|
-
} = cameraOptions.constraints
|
|
11877
11756
|
const vsb = editor.getViewportScreenBounds()
|
|
11757
|
+
// Clamp padding to half the viewport size on either dimension, as getConstrainedCamera does
|
|
11758
|
+
const px = Math.min(cameraOptions.constraints.padding.x, vsb.w / 2)
|
|
11759
|
+
const py = Math.min(cameraOptions.constraints.padding.y, vsb.h / 2)
|
|
11878
11760
|
const bounds = Box.From(cameraOptions.constraints.bounds)
|
|
11879
11761
|
const zx = (vsb.w - px * 2) / bounds.w
|
|
11880
11762
|
const zy = (vsb.h - py * 2) / bounds.h
|