@tldraw/editor 5.4.1 → 5.5.0-canary.003bf5951274
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 +37 -14
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +16 -12
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +42 -3
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +5 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +2 -5
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +3 -3
- package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +2 -2
- package/dist-cjs/lib/constants.js +2 -0
- package/dist-cjs/lib/constants.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +147 -157
- package/dist-cjs/lib/editor/Editor.js.map +3 -3
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js +2 -0
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +10 -6
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +4 -4
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +7 -3
- package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +14 -6
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +3 -3
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/exports/ExportDelay.js +10 -2
- package/dist-cjs/lib/exports/ExportDelay.js.map +2 -2
- package/dist-cjs/lib/exports/FontEmbedder.js +25 -11
- package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/StyleEmbedder.js +9 -4
- package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/fetchCache.js +3 -0
- package/dist-cjs/lib/exports/fetchCache.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgAsImage.js +10 -4
- package/dist-cjs/lib/exports/getSvgAsImage.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgJsx.js +18 -9
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/exports/parseCss.js +1 -1
- package/dist-cjs/lib/exports/parseCss.js.map +1 -1
- package/dist-cjs/lib/hooks/EditorComponentsContext.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +24 -0
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useIdentity.js +6 -2
- package/dist-cjs/lib/hooks/useIdentity.js.map +3 -3
- package/dist-cjs/lib/license/LicenseManager.js +9 -1
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/LicenseProvider.js +12 -4
- package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -5
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Point2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
- package/dist-cjs/lib/utils/debug-flags.js +0 -6
- package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
- package/dist-cjs/lib/utils/deepLinks.js +2 -1
- package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
- package/dist-cjs/lib/utils/sync/LocalIndexedDb.js +22 -24
- package/dist-cjs/lib/utils/sync/LocalIndexedDb.js.map +2 -2
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +81 -40
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +37 -14
- package/dist-esm/index.mjs +4 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +17 -13
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +42 -3
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +5 -1
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +2 -5
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +3 -3
- package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
- package/dist-esm/lib/constants.mjs +2 -0
- package/dist-esm/lib/constants.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +148 -157
- package/dist-esm/lib/editor/Editor.mjs.map +3 -3
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +2 -0
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +10 -6
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +4 -4
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +7 -3
- package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +14 -6
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +3 -3
- package/dist-esm/lib/exports/ExportDelay.mjs +11 -3
- package/dist-esm/lib/exports/ExportDelay.mjs.map +2 -2
- package/dist-esm/lib/exports/FontEmbedder.mjs +25 -11
- package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/StyleEmbedder.mjs +9 -4
- package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/fetchCache.mjs +3 -0
- package/dist-esm/lib/exports/fetchCache.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgAsImage.mjs +10 -4
- package/dist-esm/lib/exports/getSvgAsImage.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs +18 -9
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/exports/parseCss.mjs +1 -1
- package/dist-esm/lib/exports/parseCss.mjs.map +1 -1
- package/dist-esm/lib/hooks/EditorComponentsContext.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +24 -0
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useIdentity.mjs +7 -3
- package/dist-esm/lib/hooks/useIdentity.mjs.map +3 -3
- package/dist-esm/lib/license/LicenseManager.mjs +9 -1
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseProvider.mjs +13 -5
- package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +12 -6
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Point2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
- package/dist-esm/lib/utils/debug-flags.mjs +0 -6
- package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
- package/dist-esm/lib/utils/deepLinks.mjs +2 -1
- package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs +22 -24
- package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +81 -40
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +9 -1
- package/package.json +8 -11
- package/src/index.ts +5 -1
- package/src/lib/TldrawEditor.tsx +35 -21
- package/src/lib/components/MenuClickCapture.tsx +55 -4
- package/src/lib/components/default-components/DefaultCanvas.tsx +7 -1
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +13 -5
- package/src/lib/components/default-components/DefaultSpinner.tsx +4 -1
- package/src/lib/constants.ts +8 -0
- package/src/lib/editor/Editor.test.ts +20 -0
- package/src/lib/editor/Editor.ts +228 -262
- package/src/lib/editor/derivations/bindingsIndex.ts +3 -0
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +35 -4
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +13 -7
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +74 -0
- package/src/lib/editor/managers/TextManager/TextManager.ts +6 -5
- package/src/lib/editor/managers/ThemeManager/ThemeManager.test.ts +53 -0
- package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +9 -3
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +23 -8
- package/src/lib/editor/types/external-content.ts +5 -0
- package/src/lib/exports/ExportDelay.tsx +16 -4
- package/src/lib/exports/FontEmbedder.test.ts +99 -0
- package/src/lib/exports/FontEmbedder.ts +32 -11
- package/src/lib/exports/StyleEmbedder.ts +13 -4
- package/src/lib/exports/fetchCache.test.ts +22 -0
- package/src/lib/exports/fetchCache.ts +4 -0
- package/src/lib/exports/getSvgAsImage.ts +13 -5
- package/src/lib/exports/getSvgJsx.test.ts +43 -1
- package/src/lib/exports/getSvgJsx.tsx +28 -16
- package/src/lib/exports/parseCss.test.ts +4 -0
- package/src/lib/exports/parseCss.ts +2 -2
- package/src/lib/hooks/EditorComponentsContext.tsx +2 -1
- package/src/lib/hooks/useCanvasEvents.ts +42 -0
- package/src/lib/hooks/useIdentity.tsx +12 -1
- package/src/lib/license/LicenseManager.ts +13 -1
- package/src/lib/license/LicenseProvider.test.tsx +76 -0
- package/src/lib/license/LicenseProvider.tsx +19 -5
- package/src/lib/primitives/geometry/Arc2d.test.ts +9 -0
- package/src/lib/primitives/geometry/Arc2d.ts +1 -1
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +13 -0
- package/src/lib/primitives/geometry/CubicBezier2d.ts +1 -1
- package/src/lib/primitives/geometry/Edge2d.test.ts +21 -3
- package/src/lib/primitives/geometry/Edge2d.ts +1 -1
- package/src/lib/primitives/geometry/Group2d.test.ts +26 -5
- package/src/lib/primitives/geometry/Group2d.ts +18 -7
- package/src/lib/primitives/geometry/Point2d.test.ts +14 -3
- package/src/lib/primitives/geometry/Point2d.ts +1 -1
- package/src/lib/test/preserveFocus.test.tsx +81 -0
- package/src/lib/utils/debug-flags.ts +0 -6
- package/src/lib/utils/deepLinks.test.ts +15 -0
- package/src/lib/utils/deepLinks.ts +8 -3
- package/src/lib/utils/sync/LocalIndexedDb.test.ts +95 -0
- package/src/lib/utils/sync/LocalIndexedDb.ts +31 -27
- package/src/lib/utils/sync/TLLocalSyncClient.test.ts +257 -1
- package/src/lib/utils/sync/TLLocalSyncClient.ts +112 -54
- package/src/version.ts +3 -3
- package/dist-cjs/lib/primitives/geometry/geometry.bench.js +0 -133
- package/dist-cjs/lib/primitives/geometry/geometry.bench.js.map +0 -7
- package/dist-esm/lib/primitives/geometry/geometry.bench.mjs +0 -132
- package/dist-esm/lib/primitives/geometry/geometry.bench.mjs.map +0 -7
- package/src/lib/primitives/geometry/geometry.bench.ts +0 -179
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -110,6 +110,7 @@ import {
|
|
|
110
110
|
import {
|
|
111
111
|
DEFAULT_ANIMATION_OPTIONS,
|
|
112
112
|
DEFAULT_CAMERA_OPTIONS,
|
|
113
|
+
FRAME_MS_60HZ,
|
|
113
114
|
INTERNAL_POINTER_IDS,
|
|
114
115
|
LEFT_MOUSE_BUTTON,
|
|
115
116
|
MIDDLE_MOUSE_BUTTON,
|
|
@@ -340,6 +341,11 @@ export interface TLRenderingShape {
|
|
|
340
341
|
|
|
341
342
|
const RENDERING_SHAPES_SORT_CACHE_THRESHOLD = 100
|
|
342
343
|
|
|
344
|
+
const AXIS = {
|
|
345
|
+
horizontal: { val: 'x', min: 'minX', max: 'maxX', dim: 'width' },
|
|
346
|
+
vertical: { val: 'y', min: 'minY', max: 'maxY', dim: 'height' },
|
|
347
|
+
} as const
|
|
348
|
+
|
|
343
349
|
/** @public */
|
|
344
350
|
export class Editor extends EventEmitter<TLEventMap> {
|
|
345
351
|
readonly id = uniqueId()
|
|
@@ -752,7 +758,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
752
758
|
}
|
|
753
759
|
|
|
754
760
|
if (deleteBindingIds.length) {
|
|
755
|
-
this
|
|
761
|
+
// straight to the store: this cleanup must run even when deleteBindings would
|
|
762
|
+
// refuse (readonly), e.g. for a deletion that arrived from a remote peer
|
|
763
|
+
this.store.remove(deleteBindingIds)
|
|
756
764
|
}
|
|
757
765
|
},
|
|
758
766
|
},
|
|
@@ -1969,6 +1977,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1969
1977
|
* @public
|
|
1970
1978
|
**/
|
|
1971
1979
|
updateDocumentSettings(settings: Partial<TLDocument>): this {
|
|
1980
|
+
if (this.getIsReadonly()) return this
|
|
1972
1981
|
this.run(
|
|
1973
1982
|
() => {
|
|
1974
1983
|
this.store.put([{ ...this.getDocumentSettings(), ...settings }])
|
|
@@ -2196,10 +2205,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2196
2205
|
* @public
|
|
2197
2206
|
*/
|
|
2198
2207
|
select(...shapes: TLShapeId[] | TLShape[]): this {
|
|
2199
|
-
const ids =
|
|
2200
|
-
typeof shapes[0] === 'string'
|
|
2201
|
-
? (shapes as TLShapeId[])
|
|
2202
|
-
: (shapes as TLShape[]).map((shape) => shape.id)
|
|
2208
|
+
const ids = toShapeIds(shapes)
|
|
2203
2209
|
this.setSelectedShapes(ids)
|
|
2204
2210
|
return this
|
|
2205
2211
|
}
|
|
@@ -2215,10 +2221,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2215
2221
|
* @public
|
|
2216
2222
|
*/
|
|
2217
2223
|
deselect(...shapes: TLShapeId[] | TLShape[]): this {
|
|
2218
|
-
const ids =
|
|
2219
|
-
typeof shapes[0] === 'string'
|
|
2220
|
-
? (shapes as TLShapeId[])
|
|
2221
|
-
: (shapes as TLShape[]).map((shape) => shape.id)
|
|
2224
|
+
const ids = toShapeIds(shapes)
|
|
2222
2225
|
const selectedShapeIds = this.getSelectedShapeIds()
|
|
2223
2226
|
if (selectedShapeIds.length > 0 && ids.length > 0) {
|
|
2224
2227
|
this.setSelectedShapes(selectedShapeIds.filter((id) => !ids.includes(id)))
|
|
@@ -2263,10 +2266,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2263
2266
|
parentToSelectWithinId = this.getCurrentPageId()
|
|
2264
2267
|
}
|
|
2265
2268
|
|
|
2266
|
-
// Select all the unlocked shapes within the parent
|
|
2269
|
+
// Select all the unlocked shapes within the parent. Only the shape's own lock matters here:
|
|
2270
|
+
// selecting inside a locked frame or group is allowed, mutating is not.
|
|
2267
2271
|
const ids = this.getSortedChildIdsForParent(parentToSelectWithinId)
|
|
2268
2272
|
if (ids.length <= 0) return this
|
|
2269
|
-
this.setSelectedShapes(this.
|
|
2273
|
+
this.setSelectedShapes(ids.filter((id) => !this.getShape(id)?.isLocked))
|
|
2270
2274
|
return this
|
|
2271
2275
|
}
|
|
2272
2276
|
|
|
@@ -2287,12 +2291,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2287
2291
|
firstParentId &&
|
|
2288
2292
|
selectedShapeIds.every((shapeId) => this.getShape(shapeId)?.parentId === firstParentId) &&
|
|
2289
2293
|
!isPageId(firstParentId)
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2294
|
+
// Locked shapes (and children of locked containers) can't be selected by clicking or
|
|
2295
|
+
// select all, so traversal skips them too
|
|
2296
|
+
const filteredShapes = this.getCurrentPageShapes().filter(
|
|
2297
|
+
(shape) =>
|
|
2298
|
+
!this.isShapeOrAncestorLocked(shape) &&
|
|
2299
|
+
(isSelectedWithinContainer ? shape.parentId === firstParentId : isPageId(shape.parentId))
|
|
2300
|
+
)
|
|
2301
|
+
const readingOrderShapes = this._getShapesInReadingOrder(filteredShapes)
|
|
2296
2302
|
const currentShapeId: TLShapeId | undefined =
|
|
2297
2303
|
selectedShapeIds.length === 1
|
|
2298
2304
|
? selectedShapeIds[0]
|
|
@@ -2301,10 +2307,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2301
2307
|
let adjacentShapeId: TLShapeId
|
|
2302
2308
|
if (direction === 'next' || direction === 'prev') {
|
|
2303
2309
|
const shapeIds = readingOrderShapes.map((shape) => shape.id)
|
|
2310
|
+
// Every candidate can be filtered out (e.g. a locked shape is selected and nothing
|
|
2311
|
+
// else is unlocked); indexing an empty list would hand getShape undefined
|
|
2312
|
+
if (shapeIds.length === 0) return
|
|
2304
2313
|
|
|
2305
2314
|
const currentIndex = currentShapeId ? shapeIds.indexOf(currentShapeId) : -1
|
|
2315
|
+
// With no current index, stepping from -1 makes 'prev' land one shape
|
|
2316
|
+
// before the last; seed it from 0 so it wraps to the last shape. See #10559.
|
|
2317
|
+
const startIndex = currentIndex === -1 && direction === 'prev' ? 0 : currentIndex
|
|
2306
2318
|
const adjacentIndex =
|
|
2307
|
-
(
|
|
2319
|
+
(startIndex + (direction === 'next' ? 1 : -1) + shapeIds.length) % shapeIds.length
|
|
2308
2320
|
adjacentShapeId = shapeIds[adjacentIndex]
|
|
2309
2321
|
} else {
|
|
2310
2322
|
if (!currentShapeId) return
|
|
@@ -2446,8 +2458,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2446
2458
|
|
|
2447
2459
|
// Ok, now score that subset of shapes.
|
|
2448
2460
|
const lowestScoringShape = minBy(shapesInDirection, ({ center }) => {
|
|
2449
|
-
//
|
|
2450
|
-
|
|
2461
|
+
// Linear, not squared: the off-axis and diagonal penalties below are page units too, and
|
|
2462
|
+
// squared distance swamps them, so a nearer diagonal shape beats an aligned one.
|
|
2463
|
+
const distance = Vec.Dist(currentCenter, center)
|
|
2451
2464
|
|
|
2452
2465
|
// Distance along the primary axis.
|
|
2453
2466
|
const dirProp = ['left', 'right'].includes(direction) ? 'x' : 'y'
|
|
@@ -2457,9 +2470,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2457
2470
|
const offProp = ['left', 'right'].includes(direction) ? 'y' : 'x'
|
|
2458
2471
|
const offAxisDeviation = Math.abs(center[offProp] - currentCenter[offProp])
|
|
2459
2472
|
|
|
2460
|
-
//
|
|
2461
|
-
|
|
2462
|
-
const
|
|
2473
|
+
// atan2 gives -180..180, so 'up' is -90; normalize to 0..360 to match 270, and wrap the
|
|
2474
|
+
// deviation so that 350 is 10 away from 'right' (0), not 350.
|
|
2475
|
+
const angle = (Vec.Angle(currentCenter, center) * (180 / Math.PI) + 360) % 360
|
|
2476
|
+
const rawAngleDeviation = Math.abs(angle - directionToAngle[direction])
|
|
2477
|
+
const angleDeviation = Math.min(rawAngleDeviation, 360 - rawAngleDeviation)
|
|
2463
2478
|
|
|
2464
2479
|
// Calculate final score (lower is better).
|
|
2465
2480
|
// Weight factors to prioritize:
|
|
@@ -2964,10 +2979,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2964
2979
|
* @public
|
|
2965
2980
|
*/
|
|
2966
2981
|
setHintingShapes(shapes: TLShapeId[] | TLShape[]): this {
|
|
2967
|
-
const ids =
|
|
2968
|
-
typeof shapes[0] === 'string'
|
|
2969
|
-
? (shapes as TLShapeId[])
|
|
2970
|
-
: (shapes as TLShape[]).map((shape) => shape.id)
|
|
2982
|
+
const ids = toShapeIds(shapes)
|
|
2971
2983
|
// always ephemeral
|
|
2972
2984
|
this.run(
|
|
2973
2985
|
() => {
|
|
@@ -3014,10 +3026,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3014
3026
|
*/
|
|
3015
3027
|
setErasingShapes(shapes: TLShapeId[] | TLShape[]): this {
|
|
3016
3028
|
// copy before sorting: the caller may pass a store-owned (frozen) array
|
|
3017
|
-
const ids =
|
|
3018
|
-
typeof shapes[0] === 'string'
|
|
3019
|
-
? (shapes as TLShapeId[]).slice()
|
|
3020
|
-
: (shapes as TLShape[]).map((shape) => shape.id)
|
|
3029
|
+
const ids = toShapeIds(shapes).slice()
|
|
3021
3030
|
ids.sort() // sort the incoming ids
|
|
3022
3031
|
const erasingShapeIds = this.getErasingShapeIds()
|
|
3023
3032
|
this.run(
|
|
@@ -3591,6 +3600,20 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3591
3600
|
const { isLocked } = this._cameraOptions.__unsafe__getWithoutCapture()
|
|
3592
3601
|
if (isLocked && !opts?.force) return this
|
|
3593
3602
|
|
|
3603
|
+
// Resolve the zoom before building the Vec: Vec.Cast would default a missing z to 1,
|
|
3604
|
+
// and a missing z should keep the current zoom level instead
|
|
3605
|
+
const _point = new Vec(point.x, point.y, point.z ?? this.getZoomLevel())
|
|
3606
|
+
|
|
3607
|
+
// Reject non-finite values before anything else, so the call is a no-op rather than a
|
|
3608
|
+
// partial one. An animated move writes the camera from a 'tick' listener, and a listener
|
|
3609
|
+
// that throws stops TickManager scheduling the next frame, which kills every frame-driven
|
|
3610
|
+
// behavior for the rest of the session instead of surfacing the error to the caller.
|
|
3611
|
+
if (!Number.isFinite(_point.x) || !Number.isFinite(_point.y) || !Number.isFinite(_point.z)) {
|
|
3612
|
+
throw Error(
|
|
3613
|
+
`Editor.setCamera: expected finite values, got (${_point.x}, ${_point.y}, ${_point.z}).`
|
|
3614
|
+
)
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3594
3617
|
// Stop any camera animations
|
|
3595
3618
|
this.stopCameraAnimation()
|
|
3596
3619
|
|
|
@@ -3599,12 +3622,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3599
3622
|
this.stopFollowingUser()
|
|
3600
3623
|
}
|
|
3601
3624
|
|
|
3602
|
-
const _point = Vec.Cast(point)
|
|
3603
|
-
|
|
3604
|
-
if (!Number.isFinite(_point.x)) _point.x = 0
|
|
3605
|
-
if (!Number.isFinite(_point.y)) _point.y = 0
|
|
3606
|
-
if (_point.z === undefined || !Number.isFinite(_point.z)) point.z = this.getZoomLevel()
|
|
3607
|
-
|
|
3608
3625
|
const camera = this.getConstrainedCamera(_point, opts)
|
|
3609
3626
|
|
|
3610
3627
|
if (opts?.animation) {
|
|
@@ -3939,6 +3956,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3939
3956
|
easing(t: number): number
|
|
3940
3957
|
start: Box
|
|
3941
3958
|
end: Box
|
|
3959
|
+
opts: TLCameraMoveOptions
|
|
3942
3960
|
}
|
|
3943
3961
|
|
|
3944
3962
|
/** @internal */
|
|
@@ -3947,12 +3965,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3947
3965
|
|
|
3948
3966
|
this._viewportAnimation.elapsed += ms
|
|
3949
3967
|
|
|
3950
|
-
const { elapsed, easing, duration, start, end } = this._viewportAnimation
|
|
3968
|
+
const { elapsed, easing, duration, start, end, opts } = this._viewportAnimation
|
|
3951
3969
|
|
|
3952
3970
|
if (elapsed > duration) {
|
|
3953
3971
|
this.off('tick', this._animateViewport)
|
|
3954
3972
|
this._viewportAnimation = null
|
|
3955
|
-
|
|
3973
|
+
// Forward the caller's options, otherwise a forced move to a position outside the
|
|
3974
|
+
// constraints animates there and then snaps back on this last frame
|
|
3975
|
+
this._setCamera(
|
|
3976
|
+
new Vec(-end.x, -end.y, this.getViewportScreenBounds().width / end.width),
|
|
3977
|
+
opts
|
|
3978
|
+
)
|
|
3956
3979
|
return
|
|
3957
3980
|
}
|
|
3958
3981
|
|
|
@@ -4006,6 +4029,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4006
4029
|
easing,
|
|
4007
4030
|
start: viewportPageBounds.clone(),
|
|
4008
4031
|
end: targetViewportPage.clone(),
|
|
4032
|
+
opts: rest,
|
|
4009
4033
|
}
|
|
4010
4034
|
|
|
4011
4035
|
// If we ever get a "stop-camera-animation" event, we stop
|
|
@@ -4086,8 +4110,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4086
4110
|
newCy += center.y / newCz - center.y / cz
|
|
4087
4111
|
}
|
|
4088
4112
|
|
|
4089
|
-
// Apply friction
|
|
4090
|
-
currentSpeed *= 1 - friction
|
|
4113
|
+
// Apply friction per unit of elapsed time, not per tick, or a 120 Hz display decays twice as fast
|
|
4114
|
+
currentSpeed *= (1 - friction) ** (elapsed / FRAME_MS_60HZ)
|
|
4091
4115
|
if (currentSpeed < speedThreshold) {
|
|
4092
4116
|
cancel()
|
|
4093
4117
|
} else {
|
|
@@ -4130,6 +4154,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4130
4154
|
// If we're not on the same page, move to the page they're on
|
|
4131
4155
|
const isOnSamePage = presence.currentPageId === this.getCurrentPageId()
|
|
4132
4156
|
if (!isOnSamePage) {
|
|
4157
|
+
this.markHistoryStoppingPoint('change-page')
|
|
4133
4158
|
this.setCurrentPage(presence.currentPageId)
|
|
4134
4159
|
}
|
|
4135
4160
|
|
|
@@ -5227,7 +5252,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5227
5252
|
|
|
5228
5253
|
this.run(
|
|
5229
5254
|
() => {
|
|
5230
|
-
|
|
5255
|
+
// the asset store's remove is async; surface failures instead of leaving an unhandled rejection
|
|
5256
|
+
Promise.resolve(this.store.props.assets.remove?.(ids)).catch((err) =>
|
|
5257
|
+
console.error('Error while removing assets from the asset store:', err)
|
|
5258
|
+
)
|
|
5231
5259
|
this.store.remove(ids)
|
|
5232
5260
|
},
|
|
5233
5261
|
{ history: 'ignore' }
|
|
@@ -5689,10 +5717,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5689
5717
|
return
|
|
5690
5718
|
}
|
|
5691
5719
|
|
|
5692
|
-
const ids =
|
|
5693
|
-
typeof shapes[0] === 'string'
|
|
5694
|
-
? (shapes as TLShapeId[])
|
|
5695
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
5720
|
+
const ids = toShapeIds(shapes)
|
|
5696
5721
|
const freshShapes = compact(ids.map((id) => this.getShape(id)))
|
|
5697
5722
|
|
|
5698
5723
|
if (freshShapes.length === 1) {
|
|
@@ -5840,14 +5865,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5840
5865
|
*/
|
|
5841
5866
|
getSelectedShapeAtPoint(point: VecLike): TLShape | undefined {
|
|
5842
5867
|
const selectedShapeIds = this.getSelectedShapeIds()
|
|
5843
|
-
|
|
5868
|
+
if (selectedShapeIds.length === 0) return undefined
|
|
5869
|
+
const selectedShapeIdSet = new Set(selectedShapeIds)
|
|
5870
|
+
const margin = this.getHitTestMargin()
|
|
5844
5871
|
const sortedShapes = this.getCurrentPageShapesSorted()
|
|
5845
5872
|
|
|
5846
5873
|
// iterate from the top (highest z-index) to find the top-most matching shape
|
|
5847
5874
|
for (let i = sortedShapes.length - 1; i >= 0; i--) {
|
|
5848
5875
|
const shape = sortedShapes[i]
|
|
5849
5876
|
if (shape.type === 'group') continue
|
|
5850
|
-
if (!
|
|
5877
|
+
if (!selectedShapeIdSet.has(shape.id)) continue
|
|
5851
5878
|
if (
|
|
5852
5879
|
this.getShapeGeometry(shape).hitTestPoint(
|
|
5853
5880
|
this.getPointInShapeSpace(shape, point),
|
|
@@ -6018,9 +6045,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6018
6045
|
// other hits would be occluded by the shape.
|
|
6019
6046
|
return inMarginClosestToEdgeHit || shape
|
|
6020
6047
|
} else {
|
|
6021
|
-
// If the shape is bigger than the viewport, then skip it.
|
|
6022
|
-
if (this.getShapePageBounds(shape)!.contains(viewportPageBounds)) continue
|
|
6023
|
-
|
|
6024
6048
|
// If we're close to the edge of the shape, and if it's the closest edge among
|
|
6025
6049
|
// all the edges that we've gotten close to so far, then we will want to hit the
|
|
6026
6050
|
// shape unless we hit something else or closer in later iterations.
|
|
@@ -6041,6 +6065,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6041
6065
|
inMarginClosestToEdgeHit = shape
|
|
6042
6066
|
}
|
|
6043
6067
|
} else if (!inMarginClosestToEdgeHit) {
|
|
6068
|
+
// If the shape is bigger than the viewport, then skip it. (Only here: its
|
|
6069
|
+
// edges should still be hittable within the margin.)
|
|
6070
|
+
if (this.getShapePageBounds(shape)!.contains(viewportPageBounds)) continue
|
|
6071
|
+
|
|
6044
6072
|
// If we're not within margin distance to any edge, and if the
|
|
6045
6073
|
// shape is hollow, then we want to hit the shape with the
|
|
6046
6074
|
// smallest area. (There's a bug here with self-intersecting
|
|
@@ -6059,6 +6087,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6059
6087
|
// If the distance is less than the margin, return the shape as the hit.
|
|
6060
6088
|
// Use the editor's configurable hit test margin.
|
|
6061
6089
|
if (distance < this.getHitTestMargin()) {
|
|
6090
|
+
// An edge we already hit (above this shape) that is at least as close still wins,
|
|
6091
|
+
// matching the closest-edge rule used for hollow shapes
|
|
6092
|
+
if (inMarginClosestToEdgeHit && inMarginClosestToEdgeDistance <= distance) {
|
|
6093
|
+
return inMarginClosestToEdgeHit
|
|
6094
|
+
}
|
|
6062
6095
|
return shape
|
|
6063
6096
|
}
|
|
6064
6097
|
}
|
|
@@ -6423,8 +6456,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6423
6456
|
* @public
|
|
6424
6457
|
*/
|
|
6425
6458
|
reparentShapes(shapes: TLShapeId[] | TLShape[], parentId: TLParentId, insertIndex?: IndexKey) {
|
|
6426
|
-
const ids =
|
|
6427
|
-
typeof shapes[0] === 'string' ? (shapes as TLShapeId[]) : shapes.map((s) => (s as TLShape).id)
|
|
6459
|
+
const ids = toShapeIds(shapes)
|
|
6428
6460
|
if (ids.length === 0) return this
|
|
6429
6461
|
|
|
6430
6462
|
const changes: TLShapePartial[] = []
|
|
@@ -6773,6 +6805,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6773
6805
|
* binding, but the `type`, `toId`, and `fromId` must all be provided.
|
|
6774
6806
|
*/
|
|
6775
6807
|
createBindings<B extends TLBinding = TLBinding>(partials: TLBindingCreate<B>[]) {
|
|
6808
|
+
if (this.getIsReadonly()) return this
|
|
6809
|
+
|
|
6776
6810
|
const bindings: TLBinding[] = []
|
|
6777
6811
|
for (const partial of partials) {
|
|
6778
6812
|
const fromShape = this.getShape(partial.fromId)
|
|
@@ -6812,6 +6846,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6812
6846
|
* binding is skipped. The changes from the partial are merged into the existing record.
|
|
6813
6847
|
*/
|
|
6814
6848
|
updateBindings(partials: (TLBindingUpdate | null | undefined)[]) {
|
|
6849
|
+
if (this.getIsReadonly()) return this
|
|
6850
|
+
|
|
6815
6851
|
const updated: TLBinding[] = []
|
|
6816
6852
|
|
|
6817
6853
|
for (const partial of partials) {
|
|
@@ -6849,6 +6885,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6849
6885
|
* Delete several bindings by their IDs. If a binding ID doesn't exist, it's ignored.
|
|
6850
6886
|
*/
|
|
6851
6887
|
deleteBindings(bindings: (TLBinding | TLBindingId)[], { isolateShapes = false } = {}) {
|
|
6888
|
+
if (this.getIsReadonly()) return this
|
|
6889
|
+
return this._deleteBindings(bindings, { isolateShapes })
|
|
6890
|
+
}
|
|
6891
|
+
|
|
6892
|
+
/**
|
|
6893
|
+
* Unguarded so that withIsolatedShapes can transiently isolate shapes for copy and export in
|
|
6894
|
+
* readonly mode; the public deleteBindings is what readonly blocks.
|
|
6895
|
+
*
|
|
6896
|
+
* @internal
|
|
6897
|
+
*/
|
|
6898
|
+
_deleteBindings(bindings: (TLBinding | TLBindingId)[], { isolateShapes = false } = {}) {
|
|
6852
6899
|
const ids = bindings.map((binding) => (typeof binding === 'string' ? binding : binding.id))
|
|
6853
6900
|
if (isolateShapes) {
|
|
6854
6901
|
this.store.atomic(() => {
|
|
@@ -6923,10 +6970,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6923
6970
|
delta: number,
|
|
6924
6971
|
opts?: { center?: VecLike }
|
|
6925
6972
|
): this {
|
|
6926
|
-
const ids =
|
|
6927
|
-
typeof shapes[0] === 'string'
|
|
6928
|
-
? (shapes as TLShapeId[])
|
|
6929
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
6973
|
+
const ids = toShapeIds(shapes)
|
|
6930
6974
|
|
|
6931
6975
|
if (ids.length <= 0) return this
|
|
6932
6976
|
|
|
@@ -6973,6 +7017,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6973
7017
|
return workingShape
|
|
6974
7018
|
}
|
|
6975
7019
|
|
|
7020
|
+
// Counter-rotates the page-space delta into the parent's space; without that a child of a rotated
|
|
7021
|
+
// frame or group would move along its parent's axes instead of the page's.
|
|
7022
|
+
// todo: a shape laid out together with its own parent moves twice, once with the parent and once
|
|
7023
|
+
// on its own; the layout commands should skip shapes whose ancestors are also being laid out
|
|
7024
|
+
private getChangesToTranslateShapeByPageDelta(shape: TLShape, pageDelta: VecLike): TLShape {
|
|
7025
|
+
const localDelta = Vec.From(pageDelta).rot(-this.getShapeParentTransform(shape).rotation())
|
|
7026
|
+
return this.getChangesToTranslateShape(shape, localDelta.add(shape))
|
|
7027
|
+
}
|
|
7028
|
+
|
|
6976
7029
|
/**
|
|
6977
7030
|
* Move shapes by a delta.
|
|
6978
7031
|
*
|
|
@@ -6985,20 +7038,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6985
7038
|
* @param offset - The offset to apply to the shapes.
|
|
6986
7039
|
*/
|
|
6987
7040
|
nudgeShapes(shapes: TLShapeId[] | TLShape[], offset: VecLike): this {
|
|
6988
|
-
const ids =
|
|
6989
|
-
typeof shapes[0] === 'string'
|
|
6990
|
-
? (shapes as TLShapeId[])
|
|
6991
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7041
|
+
const ids = toShapeIds(shapes)
|
|
6992
7042
|
|
|
6993
7043
|
if (ids.length <= 0) return this
|
|
6994
7044
|
const changes: TLShapePartial[] = []
|
|
6995
7045
|
|
|
6996
7046
|
for (const id of ids) {
|
|
6997
7047
|
const shape = this.getShape(id)!
|
|
6998
|
-
|
|
6999
|
-
localDelta.rot(-this.getShapeParentTransform(shape).rotation())
|
|
7000
|
-
|
|
7001
|
-
changes.push(this.getChangesToTranslateShape(shape, localDelta.add(shape)))
|
|
7048
|
+
changes.push(this.getChangesToTranslateShapeByPageDelta(shape, offset))
|
|
7002
7049
|
}
|
|
7003
7050
|
|
|
7004
7051
|
this.updateShapes(changes)
|
|
@@ -7022,10 +7069,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7022
7069
|
*/
|
|
7023
7070
|
duplicateShapes(shapes: TLShapeId[] | TLShape[], offset?: VecLike): this {
|
|
7024
7071
|
this.run(() => {
|
|
7025
|
-
const _ids =
|
|
7026
|
-
typeof shapes[0] === 'string'
|
|
7027
|
-
? (shapes as TLShapeId[])
|
|
7028
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7072
|
+
const _ids = toShapeIds(shapes)
|
|
7029
7073
|
|
|
7030
7074
|
const ids = this._shouldIgnoreShapeLock ? _ids : this._getUnlockedShapeIds(_ids)
|
|
7031
7075
|
if (ids.length <= 0) return this
|
|
@@ -7167,10 +7211,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7167
7211
|
* @public
|
|
7168
7212
|
*/
|
|
7169
7213
|
moveShapesToPage(shapes: TLShapeId[] | TLShape[], pageId: TLPageId): this {
|
|
7170
|
-
const ids =
|
|
7171
|
-
typeof shapes[0] === 'string'
|
|
7172
|
-
? (shapes as TLShapeId[])
|
|
7173
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7214
|
+
const ids = toShapeIds(shapes)
|
|
7174
7215
|
|
|
7175
7216
|
if (ids.length === 0) return this
|
|
7176
7217
|
if (this.getIsReadonly()) return this
|
|
@@ -7231,10 +7272,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7231
7272
|
* @public
|
|
7232
7273
|
*/
|
|
7233
7274
|
toggleLock(shapes: TLShapeId[] | TLShape[]): this {
|
|
7234
|
-
const ids =
|
|
7235
|
-
typeof shapes[0] === 'string'
|
|
7236
|
-
? (shapes as TLShapeId[])
|
|
7237
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7275
|
+
const ids = toShapeIds(shapes)
|
|
7238
7276
|
|
|
7239
7277
|
if (this.getIsReadonly() || ids.length === 0) return this
|
|
7240
7278
|
|
|
@@ -7277,11 +7315,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7277
7315
|
* @public
|
|
7278
7316
|
*/
|
|
7279
7317
|
sendToBack(shapes: TLShapeId[] | TLShape[]): this {
|
|
7280
|
-
const ids =
|
|
7281
|
-
|
|
7282
|
-
? (shapes as TLShapeId[])
|
|
7283
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7284
|
-
const changes = getReorderingShapesChanges(this, 'toBack', ids as TLShapeId[], {
|
|
7318
|
+
const ids = toShapeIds(shapes)
|
|
7319
|
+
const changes = getReorderingShapesChanges(this, 'toBack', ids, {
|
|
7285
7320
|
considerAllShapes: true,
|
|
7286
7321
|
})
|
|
7287
7322
|
if (changes) this.updateShapes(changes)
|
|
@@ -7311,11 +7346,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7311
7346
|
* @public
|
|
7312
7347
|
*/
|
|
7313
7348
|
sendBackward(shapes: TLShapeId[] | TLShape[], opts: { considerAllShapes?: boolean } = {}): this {
|
|
7314
|
-
const ids =
|
|
7315
|
-
|
|
7316
|
-
? (shapes as TLShapeId[])
|
|
7317
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7318
|
-
const changes = getReorderingShapesChanges(this, 'backward', ids as TLShapeId[], opts)
|
|
7349
|
+
const ids = toShapeIds(shapes)
|
|
7350
|
+
const changes = getReorderingShapesChanges(this, 'backward', ids, opts)
|
|
7319
7351
|
if (changes) this.updateShapes(changes)
|
|
7320
7352
|
return this
|
|
7321
7353
|
}
|
|
@@ -7343,11 +7375,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7343
7375
|
* @public
|
|
7344
7376
|
*/
|
|
7345
7377
|
bringForward(shapes: TLShapeId[] | TLShape[], opts: { considerAllShapes?: boolean } = {}): this {
|
|
7346
|
-
const ids =
|
|
7347
|
-
|
|
7348
|
-
? (shapes as TLShapeId[])
|
|
7349
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7350
|
-
const changes = getReorderingShapesChanges(this, 'forward', ids as TLShapeId[], opts)
|
|
7378
|
+
const ids = toShapeIds(shapes)
|
|
7379
|
+
const changes = getReorderingShapesChanges(this, 'forward', ids, opts)
|
|
7351
7380
|
if (changes) this.updateShapes(changes)
|
|
7352
7381
|
return this
|
|
7353
7382
|
}
|
|
@@ -7366,11 +7395,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7366
7395
|
* @public
|
|
7367
7396
|
*/
|
|
7368
7397
|
bringToFront(shapes: TLShapeId[] | TLShape[]): this {
|
|
7369
|
-
const ids =
|
|
7370
|
-
|
|
7371
|
-
? (shapes as TLShapeId[])
|
|
7372
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7373
|
-
const changes = getReorderingShapesChanges(this, 'toFront', ids as TLShapeId[])
|
|
7398
|
+
const ids = toShapeIds(shapes)
|
|
7399
|
+
const changes = getReorderingShapesChanges(this, 'toFront', ids)
|
|
7374
7400
|
if (changes) this.updateShapes(changes)
|
|
7375
7401
|
return this
|
|
7376
7402
|
}
|
|
@@ -7386,10 +7412,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7386
7412
|
type: TLShapeUtilCanBeLaidOutOpts['type'],
|
|
7387
7413
|
opts?: { filterAxisAligned?: boolean }
|
|
7388
7414
|
): { clusters: { shapes: TLShape[]; pageBounds: Box }[]; allBounds: Box[] } {
|
|
7389
|
-
const ids =
|
|
7390
|
-
typeof shapes[0] === 'string'
|
|
7391
|
-
? (shapes as TLShapeId[])
|
|
7392
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7415
|
+
const ids = toShapeIds(shapes)
|
|
7393
7416
|
|
|
7394
7417
|
// always fresh shapes
|
|
7395
7418
|
let freshShapes = compact(ids.map((id) => this.getShape(id)))
|
|
@@ -7427,8 +7450,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7427
7450
|
const shapesMovingTogether = [shape]
|
|
7428
7451
|
const boundsOfShapesMovingTogether: Box[] = [shapePageBounds]
|
|
7429
7452
|
|
|
7453
|
+
// Seed with bindings in both directions, otherwise an arrow visited before the shapes it
|
|
7454
|
+
// binds ends up in a cluster of its own and the result depends on input order
|
|
7430
7455
|
this.collectShapesViaArrowBindings({
|
|
7431
|
-
bindings: this.
|
|
7456
|
+
bindings: this.getBindingsInvolvingShape(shape.id, 'arrow'),
|
|
7432
7457
|
initialShapes: freshShapes,
|
|
7433
7458
|
resultShapes: shapesMovingTogether,
|
|
7434
7459
|
resultBounds: boundsOfShapesMovingTogether,
|
|
@@ -7497,10 +7522,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7497
7522
|
flipShapes(shapes: TLShapeId[] | TLShape[], operation: 'horizontal' | 'vertical'): this {
|
|
7498
7523
|
if (this.getIsReadonly()) return this
|
|
7499
7524
|
|
|
7500
|
-
const ids =
|
|
7501
|
-
typeof shapes[0] === 'string'
|
|
7502
|
-
? (shapes as TLShapeId[])
|
|
7503
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7525
|
+
const ids = toShapeIds(shapes)
|
|
7504
7526
|
|
|
7505
7527
|
// Collect a greedy list of shapes to flip
|
|
7506
7528
|
const shapesToFlipFirstPass = compact(ids.map((id) => this.getShape(id)))
|
|
@@ -7603,22 +7625,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7603
7625
|
const len = shapeClustersToStack.length
|
|
7604
7626
|
if ((_gap === 0 && len < 3) || len < 2) return this
|
|
7605
7627
|
|
|
7606
|
-
|
|
7607
|
-
let min: 'minX' | 'minY'
|
|
7608
|
-
let max: 'maxX' | 'maxY'
|
|
7609
|
-
let dim: 'width' | 'height'
|
|
7610
|
-
|
|
7611
|
-
if (operation === 'horizontal') {
|
|
7612
|
-
val = 'x'
|
|
7613
|
-
min = 'minX'
|
|
7614
|
-
max = 'maxX'
|
|
7615
|
-
dim = 'width'
|
|
7616
|
-
} else {
|
|
7617
|
-
val = 'y'
|
|
7618
|
-
min = 'minY'
|
|
7619
|
-
max = 'maxY'
|
|
7620
|
-
dim = 'height'
|
|
7621
|
-
}
|
|
7628
|
+
const { val, min, max, dim } = AXIS[operation]
|
|
7622
7629
|
|
|
7623
7630
|
let shapeGap: number = 0
|
|
7624
7631
|
|
|
@@ -7676,17 +7683,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7676
7683
|
delta[val] = v + shapeGap - pageBounds[val]
|
|
7677
7684
|
|
|
7678
7685
|
for (const shape of shapes) {
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
// If the shape has another shape as its parent, and if the parent has a rotation, we need to rotate the counter-rotate delta
|
|
7682
|
-
// todo: ensure that the parent isn't being aligned together with its children
|
|
7683
|
-
const parent = this.getShapeParent(shape)
|
|
7684
|
-
if (parent) {
|
|
7685
|
-
shapeDelta.rot(-this.getShapePageTransform(parent).rotation())
|
|
7686
|
-
}
|
|
7687
|
-
|
|
7688
|
-
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
7689
|
-
changes.push(this.getChangesToTranslateShape(shape, shapeDelta))
|
|
7686
|
+
changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta))
|
|
7690
7687
|
}
|
|
7691
7688
|
|
|
7692
7689
|
v += pageBounds[dim] + shapeGap
|
|
@@ -7802,15 +7799,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7802
7799
|
const delta = Vec.Sub(nextPageBounds.point, pageBounds.point).add(centerDelta)
|
|
7803
7800
|
|
|
7804
7801
|
for (const shape of shapes) {
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
const parent = this.getShapeParent(shape)
|
|
7808
|
-
if (parent) {
|
|
7809
|
-
shapeDelta.rot(-this.getShapeParentTransform(shape).rotation())
|
|
7810
|
-
}
|
|
7811
|
-
|
|
7812
|
-
shapeDelta.add(shape)
|
|
7813
|
-
changes.push(this.getChangesToTranslateShape(shape, shapeDelta))
|
|
7802
|
+
changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta))
|
|
7814
7803
|
}
|
|
7815
7804
|
}
|
|
7816
7805
|
|
|
@@ -7890,17 +7879,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7890
7879
|
}
|
|
7891
7880
|
|
|
7892
7881
|
for (const shape of shapes) {
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
// If the shape has another shape as its parent, and if the parent has a rotation, we need to rotate the counter-rotate delta
|
|
7896
|
-
// todo: ensure that the parent isn't being aligned together with its children
|
|
7897
|
-
const parent = this.getShapeParent(shape)
|
|
7898
|
-
if (parent) {
|
|
7899
|
-
shapeDelta.rot(-this.getShapePageTransform(parent).rotation())
|
|
7900
|
-
}
|
|
7901
|
-
|
|
7902
|
-
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
7903
|
-
changes.push(this.getChangesToTranslateShape(shape, shapeDelta))
|
|
7882
|
+
changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta))
|
|
7904
7883
|
}
|
|
7905
7884
|
})
|
|
7906
7885
|
|
|
@@ -7929,22 +7908,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7929
7908
|
|
|
7930
7909
|
if (shapeClustersToDistribute.length < 3) return this
|
|
7931
7910
|
|
|
7932
|
-
|
|
7933
|
-
let min: 'minX' | 'minY'
|
|
7934
|
-
let max: 'maxX' | 'maxY'
|
|
7935
|
-
let dim: 'width' | 'height'
|
|
7936
|
-
|
|
7937
|
-
if (operation === 'horizontal') {
|
|
7938
|
-
val = 'x'
|
|
7939
|
-
min = 'minX'
|
|
7940
|
-
max = 'maxX'
|
|
7941
|
-
dim = 'width'
|
|
7942
|
-
} else {
|
|
7943
|
-
val = 'y'
|
|
7944
|
-
min = 'minY'
|
|
7945
|
-
max = 'maxY'
|
|
7946
|
-
dim = 'height'
|
|
7947
|
-
}
|
|
7911
|
+
const { val, min, max, dim } = AXIS[operation]
|
|
7948
7912
|
const changes: TLShapePartial[] = []
|
|
7949
7913
|
|
|
7950
7914
|
const first = shapeClustersToDistribute.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])[0]
|
|
@@ -7953,10 +7917,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7953
7917
|
// If the first shape group is also the last shape group, distribute without it
|
|
7954
7918
|
if (first === last) {
|
|
7955
7919
|
const excludedShapeIds = new Set(first.shapes.map((s) => s.id))
|
|
7956
|
-
const ids =
|
|
7957
|
-
typeof shapes[0] === 'string'
|
|
7958
|
-
? (shapes as TLShapeId[])
|
|
7959
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
7920
|
+
const ids = toShapeIds(shapes)
|
|
7960
7921
|
return this.distributeShapes(
|
|
7961
7922
|
ids.filter((id) => !excludedShapeIds.has(id)),
|
|
7962
7923
|
operation
|
|
@@ -7990,17 +7951,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7990
7951
|
}
|
|
7991
7952
|
|
|
7992
7953
|
for (const shape of shapes) {
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
// If the shape has another shape as its parent, and if the parent has a rotation, we need to rotate the counter-rotate delta
|
|
7996
|
-
// todo: ensure that the parent isn't being aligned together with its children
|
|
7997
|
-
const parent = this.getShapeParent(shape)
|
|
7998
|
-
if (parent) {
|
|
7999
|
-
shapeDelta.rot(-this.getShapePageTransform(parent).rotation())
|
|
8000
|
-
}
|
|
8001
|
-
|
|
8002
|
-
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
8003
|
-
changes.push(this.getChangesToTranslateShape(shape, shapeDelta))
|
|
7954
|
+
changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta))
|
|
8004
7955
|
}
|
|
8005
7956
|
|
|
8006
7957
|
v += pageBounds[dim] + gap
|
|
@@ -8036,24 +7987,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8036
7987
|
if (shapeClustersToStretch.length < 2) return this
|
|
8037
7988
|
|
|
8038
7989
|
const commonBounds = Box.Common(allBounds)
|
|
8039
|
-
|
|
8040
|
-
let min: 'minX' | 'minY'
|
|
8041
|
-
let dim: 'width' | 'height'
|
|
8042
|
-
|
|
8043
|
-
if (operation === 'horizontal') {
|
|
8044
|
-
val = 'x'
|
|
8045
|
-
min = 'minX'
|
|
8046
|
-
dim = 'width'
|
|
8047
|
-
} else {
|
|
8048
|
-
val = 'y'
|
|
8049
|
-
min = 'minY'
|
|
8050
|
-
dim = 'height'
|
|
8051
|
-
}
|
|
7990
|
+
const { val, min, dim } = AXIS[operation]
|
|
8052
7991
|
|
|
8053
7992
|
this.run(() => {
|
|
8054
7993
|
shapeClustersToStretch.forEach(({ shapes, pageBounds }) => {
|
|
8055
|
-
const
|
|
8056
|
-
|
|
7994
|
+
const pageOffset = new Vec()
|
|
7995
|
+
pageOffset[val] = commonBounds[min] - pageBounds[min]
|
|
8057
7996
|
|
|
8058
7997
|
const scaleOrigin = pageBounds.center.clone()
|
|
8059
7998
|
scaleOrigin[val] = commonBounds[min]
|
|
@@ -8063,11 +8002,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8063
8002
|
|
|
8064
8003
|
for (const shape of shapes) {
|
|
8065
8004
|
// First translate
|
|
8066
|
-
|
|
8067
|
-
shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation())
|
|
8068
|
-
shapeLocalOffset.add(shape)
|
|
8069
|
-
const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset)
|
|
8070
|
-
this.updateShape(changes)
|
|
8005
|
+
this.updateShape(this.getChangesToTranslateShapeByPageDelta(shape, pageOffset))
|
|
8071
8006
|
|
|
8072
8007
|
// Then resize
|
|
8073
8008
|
this.resizeShape(shape.id, scale, {
|
|
@@ -8120,7 +8055,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8120
8055
|
const scale = new Vec(scaleX, scaleY)
|
|
8121
8056
|
|
|
8122
8057
|
shapeClusters.forEach(({ shapes, pageBounds }) => {
|
|
8123
|
-
const
|
|
8058
|
+
const pageOffset = new Vec(
|
|
8124
8059
|
targetBounds.minX -
|
|
8125
8060
|
commonBounds.minX +
|
|
8126
8061
|
(pageBounds.minX - commonBounds.minX) * (scaleX - 1),
|
|
@@ -8134,11 +8069,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8134
8069
|
|
|
8135
8070
|
for (const shape of shapes) {
|
|
8136
8071
|
// First translate
|
|
8137
|
-
|
|
8138
|
-
shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation())
|
|
8139
|
-
shapeLocalOffset.add(shape)
|
|
8140
|
-
const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset)
|
|
8141
|
-
this.updateShape(changes)
|
|
8072
|
+
this.updateShape(this.getChangesToTranslateShapeByPageDelta(shape, pageOffset))
|
|
8142
8073
|
|
|
8143
8074
|
// Then resize
|
|
8144
8075
|
this.resizeShape(shape.id, scale, {
|
|
@@ -8783,6 +8714,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8783
8714
|
|
|
8784
8715
|
const animations: ShapeAnimation[] = []
|
|
8785
8716
|
|
|
8717
|
+
// Snapshot the lock override now: when this animation is started inside
|
|
8718
|
+
// editor.run(..., { ignoreShapeLock: true }), run() restores the flag before any tick
|
|
8719
|
+
// fires, so the final updateShapes below would refuse the locked shape and strand it
|
|
8720
|
+
const ignoreShapeLock = this._shouldIgnoreShapeLock
|
|
8721
|
+
|
|
8786
8722
|
let partial: TLShapePartial | null | undefined, result: ShapeAnimation
|
|
8787
8723
|
for (let i = 0, n = partials.length; i < n; i++) {
|
|
8788
8724
|
partial = partials[i]
|
|
@@ -8791,6 +8727,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8791
8727
|
const shape = this.getShape(partial.id)!
|
|
8792
8728
|
if (!shape) continue
|
|
8793
8729
|
|
|
8730
|
+
// Apply the same lock rule as updateShapes up front: the intermediate frames go through
|
|
8731
|
+
// _updateShapes, which doesn't check locks, so a locked shape would otherwise be moved by
|
|
8732
|
+
// every frame but the last and end up stranded at the penultimate one
|
|
8733
|
+
const unlocks = shape.isLocked && Object.hasOwn(partial, 'isLocked') && !partial.isLocked
|
|
8734
|
+
if (!ignoreShapeLock && !unlocks && this.isShapeOrAncestorLocked(shape)) {
|
|
8735
|
+
continue
|
|
8736
|
+
}
|
|
8737
|
+
|
|
8794
8738
|
result = {
|
|
8795
8739
|
start: structuredClone(shape),
|
|
8796
8740
|
end: applyPartialToRecordWithProps(structuredClone(shape), partial),
|
|
@@ -8811,7 +8755,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8811
8755
|
if (partialsToUpdate.length) {
|
|
8812
8756
|
// the regular update shapes also removes the shape from
|
|
8813
8757
|
// the animating shapes set
|
|
8814
|
-
this.updateShapes(partialsToUpdate)
|
|
8758
|
+
this.run(() => this.updateShapes(partialsToUpdate), { ignoreShapeLock })
|
|
8815
8759
|
}
|
|
8816
8760
|
|
|
8817
8761
|
this.off('tick', handleTick)
|
|
@@ -8878,18 +8822,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8878
8822
|
}
|
|
8879
8823
|
if (this.getIsReadonly()) return this
|
|
8880
8824
|
|
|
8881
|
-
const ids =
|
|
8882
|
-
typeof shapes[0] === 'string'
|
|
8883
|
-
? (shapes as TLShapeId[])
|
|
8884
|
-
: (shapes.map((s) => (s as TLShape).id) as TLShapeId[])
|
|
8885
|
-
|
|
8886
|
-
if (ids.length <= 1) return this
|
|
8825
|
+
const ids = toShapeIds(shapes)
|
|
8887
8826
|
|
|
8888
8827
|
const shapesToGroup = compact(
|
|
8889
8828
|
(this._shouldIgnoreShapeLock ? ids : this._getUnlockedShapeIds(ids)).map((id) =>
|
|
8890
8829
|
this.getShape(id)
|
|
8891
8830
|
)
|
|
8892
8831
|
)
|
|
8832
|
+
// Re-check after the lock filter: Box.Common of nothing is not a valid box and would throw
|
|
8833
|
+
if (shapesToGroup.length <= 1) return this
|
|
8834
|
+
|
|
8893
8835
|
const sortedShapeIds = shapesToGroup.sort(sortByIndex).map((s) => s.id)
|
|
8894
8836
|
const childBounds = compact(shapesToGroup.map((shape) => this.getShapePageBounds(shape)))
|
|
8895
8837
|
const pageBounds = Box.Common(childBounds)
|
|
@@ -8902,11 +8844,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8902
8844
|
|
|
8903
8845
|
const parentId = this.findCommonAncestor(shapesToGroup) ?? this.getCurrentPageId()
|
|
8904
8846
|
|
|
8905
|
-
//
|
|
8906
|
-
|
|
8847
|
+
// createShapes bails out when the page is full, so check first; otherwise the shapes get
|
|
8848
|
+
// reparented into a group that was never created and vanish from the page
|
|
8849
|
+
if (!this.canCreateShapes([groupId])) {
|
|
8850
|
+
alertMaxShapes(this)
|
|
8851
|
+
return this
|
|
8852
|
+
}
|
|
8907
8853
|
|
|
8908
|
-
// If
|
|
8909
|
-
if (!this.isIn('select.idle')) {
|
|
8854
|
+
// If the select tool is mid-interaction, cancel it (get back to idle) before grouping
|
|
8855
|
+
if (this.isIn('select') && !this.isIn('select.idle')) {
|
|
8910
8856
|
this.cancel()
|
|
8911
8857
|
}
|
|
8912
8858
|
|
|
@@ -8960,10 +8906,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8960
8906
|
if (this.getIsReadonly()) return this
|
|
8961
8907
|
|
|
8962
8908
|
const { select = true } = opts
|
|
8963
|
-
const ids =
|
|
8964
|
-
typeof shapes[0] === 'string'
|
|
8965
|
-
? (shapes as TLShapeId[])
|
|
8966
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
8909
|
+
const ids = toShapeIds(shapes)
|
|
8967
8910
|
|
|
8968
8911
|
const shapesToUngroup = compact(
|
|
8969
8912
|
(this._shouldIgnoreShapeLock ? ids : this._getUnlockedShapeIds(ids)).map((id) =>
|
|
@@ -8973,9 +8916,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8973
8916
|
|
|
8974
8917
|
if (shapesToUngroup.length === 0) return this
|
|
8975
8918
|
|
|
8976
|
-
//
|
|
8977
|
-
if (this.
|
|
8978
|
-
if (!this.isIn('select.idle')) {
|
|
8919
|
+
// If the select tool is mid-interaction, cancel it (get back to idle) before ungrouping
|
|
8920
|
+
if (this.isIn('select') && !this.isIn('select.idle')) {
|
|
8979
8921
|
this.cancel()
|
|
8980
8922
|
}
|
|
8981
8923
|
|
|
@@ -8998,10 +8940,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8998
8940
|
if (groups.length === 0) return this
|
|
8999
8941
|
|
|
9000
8942
|
this.run(() => {
|
|
9001
|
-
let group: TLGroupShape
|
|
9002
|
-
|
|
9003
8943
|
for (let i = 0, n = groups.length; i < n; i++) {
|
|
9004
|
-
group
|
|
8944
|
+
// Re-read the group: ungrouping an outer group earlier in this loop reparents the
|
|
8945
|
+
// inner ones, and the stale parentId would send their children into a group that's
|
|
8946
|
+
// about to be deleted
|
|
8947
|
+
const group = this.getShape<TLGroupShape>(groups[i].id)
|
|
8948
|
+
if (!group) continue
|
|
9005
8949
|
const childIds = this.getSortedChildIdsForParent(group.id)
|
|
9006
8950
|
|
|
9007
8951
|
for (let j = 0, n = childIds.length; j < n; j++) {
|
|
@@ -9128,7 +9072,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9128
9072
|
|
|
9129
9073
|
/** @internal */
|
|
9130
9074
|
private _getUnlockedShapeIds(ids: TLShapeId[]): TLShapeId[] {
|
|
9131
|
-
|
|
9075
|
+
// Match updateShapes, which also refuses shapes under a locked ancestor; otherwise a child
|
|
9076
|
+
// of a locked frame can't be moved but can still be deleted or duplicated
|
|
9077
|
+
return ids.filter((id) => !this.isShapeOrAncestorLocked(id))
|
|
9132
9078
|
}
|
|
9133
9079
|
|
|
9134
9080
|
/**
|
|
@@ -9152,8 +9098,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9152
9098
|
throw Error('Editor.deleteShapes: must provide an array of shapes or shapeIds')
|
|
9153
9099
|
}
|
|
9154
9100
|
|
|
9155
|
-
const shapeIds =
|
|
9156
|
-
typeof _ids[0] === 'string' ? (_ids as TLShapeId[]) : (_ids as TLShape[]).map((s) => s.id)
|
|
9101
|
+
const shapeIds = toShapeIds(_ids)
|
|
9157
9102
|
|
|
9158
9103
|
// Normally we don't want to delete locked shapes, but if the force option is set, we'll delete them anyway
|
|
9159
9104
|
const shapeIdsToDelete = this._shouldIgnoreShapeLock
|
|
@@ -9627,10 +9572,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9627
9572
|
*/
|
|
9628
9573
|
getContentFromCurrentPage(shapes: TLShapeId[] | TLShape[]): TLContent | undefined {
|
|
9629
9574
|
// todo: make this work with any page, not just the current page
|
|
9630
|
-
const ids =
|
|
9631
|
-
typeof shapes[0] === 'string'
|
|
9632
|
-
? (shapes as TLShapeId[])
|
|
9633
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
9575
|
+
const ids = toShapeIds(shapes)
|
|
9634
9576
|
|
|
9635
9577
|
if (!ids) return
|
|
9636
9578
|
if (ids.length === 0) return
|
|
@@ -9717,21 +9659,34 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9717
9659
|
!asset.props.src?.startsWith('data:video') &&
|
|
9718
9660
|
!asset.props.src?.startsWith('http')
|
|
9719
9661
|
) {
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9662
|
+
// If the asset can't be inlined (unresolvable src, fetch failure), fall through and
|
|
9663
|
+
// keep the original record; dropping it leaves the pasted shapes pointing at an
|
|
9664
|
+
// asset that doesn't exist
|
|
9665
|
+
try {
|
|
9666
|
+
const objectUrl = await this.store.props.assets.resolve(asset, {
|
|
9667
|
+
screenScale: 1,
|
|
9668
|
+
steppedScreenScale: 1,
|
|
9669
|
+
dpr: 1,
|
|
9670
|
+
networkEffectiveType: null,
|
|
9671
|
+
shouldResolveToOriginal: true,
|
|
9672
|
+
})
|
|
9673
|
+
if (objectUrl) {
|
|
9674
|
+
// fetch resolves on 4xx/5xx, so without this check a 404 error page would be
|
|
9675
|
+
// inlined as a data:text/html src
|
|
9676
|
+
const response = await fetch(objectUrl)
|
|
9677
|
+
if (response.ok) {
|
|
9678
|
+
const assetWithDataUrl = structuredClone(asset as TLImageAsset | TLVideoAsset)
|
|
9679
|
+
assetWithDataUrl.props.src = await FileHelpers.blobToDataUrl(await response.blob())
|
|
9680
|
+
assets.push(assetWithDataUrl)
|
|
9681
|
+
return
|
|
9682
|
+
}
|
|
9683
|
+
console.warn(`Could not inline asset ${asset.id}: fetch returned ${response.status}`)
|
|
9684
|
+
}
|
|
9685
|
+
} catch (err) {
|
|
9686
|
+
console.warn(`Could not inline asset ${asset.id}`, err)
|
|
9687
|
+
}
|
|
9734
9688
|
}
|
|
9689
|
+
assets.push(asset)
|
|
9735
9690
|
})
|
|
9736
9691
|
)
|
|
9737
9692
|
content.assets = assets
|
|
@@ -10153,12 +10108,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10153
10108
|
* @public
|
|
10154
10109
|
*/
|
|
10155
10110
|
async getSvgElement(shapes: TLShapeId[] | TLShape[], opts: TLSvgExportOptions = {}) {
|
|
10156
|
-
const ids =
|
|
10157
|
-
shapes.length === 0
|
|
10158
|
-
? this.getCurrentPageShapeIdsSorted()
|
|
10159
|
-
: typeof shapes[0] === 'string'
|
|
10160
|
-
? (shapes as TLShapeId[])
|
|
10161
|
-
: (shapes as TLShape[]).map((s) => s.id)
|
|
10111
|
+
const ids = shapes.length === 0 ? this.getCurrentPageShapeIdsSorted() : toShapeIds(shapes)
|
|
10162
10112
|
|
|
10163
10113
|
if (ids.length === 0) return undefined
|
|
10164
10114
|
|
|
@@ -11291,6 +11241,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11291
11241
|
inputs.setIsPointing(true)
|
|
11292
11242
|
inputs.setIsDragging(false)
|
|
11293
11243
|
|
|
11244
|
+
// A camera still animating under a held pointer would shift the page
|
|
11245
|
+
// point past the drag threshold and turn a click into a drag (#10706)
|
|
11246
|
+
this.stopCameraAnimation()
|
|
11247
|
+
|
|
11294
11248
|
// If pen mode is off, turn it on for direct-display pen input only (e.g. Apple
|
|
11295
11249
|
// Pencil on an iPad or a Surface Pen on a touchscreen). Indirect desktop tablet
|
|
11296
11250
|
// styluses still draw as pens, but should not auto-enable pen mode.
|
|
@@ -11485,9 +11439,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11485
11439
|
break
|
|
11486
11440
|
}
|
|
11487
11441
|
case 'keyboard': {
|
|
11488
|
-
//
|
|
11489
|
-
|
|
11490
|
-
|
|
11442
|
+
// Left and right modifier keys are the same key to us. `inputs.keys` stores
|
|
11443
|
+
// `code`, so normalize that: a `ShiftRight` left as-is would never match the
|
|
11444
|
+
// `ShiftLeft` that nudging checks or that `_releaseShiftKey` clears.
|
|
11445
|
+
if (info.code === 'ShiftRight') info.code = 'ShiftLeft'
|
|
11446
|
+
if (info.code === 'AltRight') info.code = 'AltLeft'
|
|
11491
11447
|
if (info.code === 'ControlRight') info.code = 'ControlLeft'
|
|
11492
11448
|
if (info.code === 'MetaRight') info.code = 'MetaLeft'
|
|
11493
11449
|
|
|
@@ -11693,6 +11649,16 @@ function pushShapeWithDescendants(editor: Editor, id: TLShapeId, result: TLShape
|
|
|
11693
11649
|
}
|
|
11694
11650
|
}
|
|
11695
11651
|
|
|
11652
|
+
/**
|
|
11653
|
+
* When `shapes` already holds ids this returns that same array, not a copy, so a caller that
|
|
11654
|
+
* mutates the result (for example to sort it) must copy it first.
|
|
11655
|
+
*/
|
|
11656
|
+
function toShapeIds(shapes: TLShapeId[] | TLShape[]): TLShapeId[] {
|
|
11657
|
+
return typeof shapes[0] === 'string'
|
|
11658
|
+
? (shapes as TLShapeId[])
|
|
11659
|
+
: (shapes as TLShape[]).map((shape) => shape.id)
|
|
11660
|
+
}
|
|
11661
|
+
|
|
11696
11662
|
/**
|
|
11697
11663
|
* Run `callback` in a world where all bindings from the shapes in `shapeIds` to shapes not in
|
|
11698
11664
|
* `shapeIds` are removed. This is useful when you want to duplicate/copy shapes without worrying
|
|
@@ -11730,7 +11696,7 @@ function withIsolatedShapes<T>(
|
|
|
11730
11696
|
}
|
|
11731
11697
|
}
|
|
11732
11698
|
|
|
11733
|
-
editor.
|
|
11699
|
+
editor._deleteBindings([...bindingsToRemove], { isolateShapes: true })
|
|
11734
11700
|
|
|
11735
11701
|
try {
|
|
11736
11702
|
result = Result.ok(callback(bindingsWithBoth))
|