@tldraw/editor 3.9.0-internal.7f0e15f4f7d9 → 3.10.0-canary.d4dfa96478a4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/README.md +1 -1
- package/dist-cjs/index.d.ts +41 -229
- package/dist-cjs/index.js +1 -9
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +6 -33
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/LiveCollaborators.js +5 -0
- package/dist-cjs/lib/components/LiveCollaborators.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +0 -7
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultBrush.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCursor.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 +2 -2
- package/dist-cjs/lib/components/default-components/DefaultScribble.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +435 -308
- package/dist-cjs/lib/editor/Editor.js.map +3 -3
- package/dist-cjs/lib/editor/managers/TextManager.js +17 -23
- package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +7 -13
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/exports/FontEmbedder.js +2 -7
- package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/StyleEmbedder.js +1 -1
- package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/exportToSvg.js +2 -3
- package/dist-cjs/lib/exports/exportToSvg.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgJsx.js +1 -18
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/exports/parseCss.js +0 -1
- package/dist-cjs/lib/exports/parseCss.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -1
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
- package/dist-cjs/lib/options.js +1 -2
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +1 -1
- package/dist-cjs/lib/utils/dom.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 +41 -229
- package/dist-esm/index.mjs +1 -13
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +7 -34
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/LiveCollaborators.mjs +5 -0
- package/dist-esm/lib/components/LiveCollaborators.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +1 -8
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultBrush.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCursor.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 +2 -2
- package/dist-esm/lib/components/default-components/DefaultScribble.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +432 -312
- package/dist-esm/lib/editor/Editor.mjs.map +3 -3
- package/dist-esm/lib/editor/managers/TextManager.mjs +17 -23
- package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +7 -13
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/exports/FontEmbedder.mjs +2 -7
- package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/StyleEmbedder.mjs +1 -1
- package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/exportToSvg.mjs +2 -3
- package/dist-esm/lib/exports/exportToSvg.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs +2 -19
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/exports/parseCss.mjs +0 -1
- package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +1 -1
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
- package/dist-esm/lib/options.mjs +1 -2
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +1 -1
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +13 -127
- package/package.json +7 -10
- package/src/index.ts +2 -15
- package/src/lib/TldrawEditor.tsx +4 -52
- package/src/lib/components/LiveCollaborators.tsx +5 -0
- package/src/lib/components/Shape.tsx +1 -9
- package/src/lib/components/default-components/DefaultBrush.tsx +1 -0
- package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -0
- package/src/lib/components/default-components/DefaultCursor.tsx +1 -0
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +5 -3
- package/src/lib/components/default-components/DefaultScribble.tsx +1 -0
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +1 -0
- package/src/lib/editor/Editor.ts +561 -362
- package/src/lib/editor/managers/TextManager.ts +17 -42
- package/src/lib/editor/shapes/ShapeUtil.ts +32 -18
- package/src/lib/editor/types/emit-types.ts +0 -1
- package/src/lib/editor/types/external-content.ts +0 -1
- package/src/lib/exports/FontEmbedder.ts +1 -13
- package/src/lib/exports/StyleEmbedder.ts +1 -1
- package/src/lib/exports/exportToSvg.tsx +3 -4
- package/src/lib/exports/getSvgJsx.tsx +3 -22
- package/src/lib/exports/parseCss.ts +0 -1
- package/src/lib/hooks/useCanvasEvents.ts +1 -2
- package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +0 -1
- package/src/lib/hooks/usePassThroughWheelEvents.ts +1 -0
- package/src/lib/options.ts +0 -7
- package/src/lib/utils/dom.ts +1 -1
- package/src/version.ts +3 -3
- package/dist-cjs/lib/editor/managers/FontManager.js +0 -167
- package/dist-cjs/lib/editor/managers/FontManager.js.map +0 -7
- package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js +0 -48
- package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +0 -7
- package/dist-cjs/lib/hooks/useViewportHeight.js +0 -56
- package/dist-cjs/lib/hooks/useViewportHeight.js.map +0 -7
- package/dist-cjs/lib/utils/richText.js +0 -46
- package/dist-cjs/lib/utils/richText.js.map +0 -7
- package/dist-esm/lib/editor/managers/FontManager.mjs +0 -153
- package/dist-esm/lib/editor/managers/FontManager.mjs.map +0 -7
- package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs +0 -28
- package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +0 -7
- package/dist-esm/lib/hooks/useViewportHeight.mjs +0 -36
- package/dist-esm/lib/hooks/useViewportHeight.mjs.map +0 -7
- package/dist-esm/lib/utils/richText.mjs +0 -26
- package/dist-esm/lib/utils/richText.mjs.map +0 -7
- package/src/lib/editor/managers/FontManager.ts +0 -252
- package/src/lib/hooks/usePassThroughMouseOverEvents.ts +0 -29
- package/src/lib/hooks/useViewportHeight.ts +0 -37
- package/src/lib/utils/richText.ts +0 -72
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Atom,
|
|
3
|
-
EMPTY_ARRAY,
|
|
4
|
-
atom,
|
|
5
|
-
computed,
|
|
6
|
-
react,
|
|
7
|
-
transact,
|
|
8
|
-
unsafe__withoutCapture,
|
|
9
|
-
} from '@tldraw/state'
|
|
1
|
+
import { EMPTY_ARRAY, atom, computed, react, transact, unsafe__withoutCapture } from '@tldraw/state'
|
|
10
2
|
import {
|
|
11
3
|
ComputedCache,
|
|
12
4
|
RecordType,
|
|
@@ -42,7 +34,6 @@ import {
|
|
|
42
34
|
TLImageAsset,
|
|
43
35
|
TLInstance,
|
|
44
36
|
TLInstancePageState,
|
|
45
|
-
TLNoteShape,
|
|
46
37
|
TLPOINTER_ID,
|
|
47
38
|
TLPage,
|
|
48
39
|
TLPageId,
|
|
@@ -92,6 +83,7 @@ import {
|
|
|
92
83
|
structuredClone,
|
|
93
84
|
uniqueId,
|
|
94
85
|
} from '@tldraw/utils'
|
|
86
|
+
import { Number } from 'core-js'
|
|
95
87
|
import EventEmitter from 'eventemitter3'
|
|
96
88
|
import {
|
|
97
89
|
TLEditorSnapshot,
|
|
@@ -125,7 +117,7 @@ import { EASINGS } from '../primitives/easings'
|
|
|
125
117
|
import { Geometry2d } from '../primitives/geometry/Geometry2d'
|
|
126
118
|
import { Group2d } from '../primitives/geometry/Group2d'
|
|
127
119
|
import { intersectPolygonPolygon } from '../primitives/intersect'
|
|
128
|
-
import {
|
|
120
|
+
import { PI, approximately, areAnglesCompatible, clamp, pointInPolygon } from '../primitives/utils'
|
|
129
121
|
import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
|
|
130
122
|
import { dataUrlToFile } from '../utils/assets'
|
|
131
123
|
import { debugFlags } from '../utils/debug-flags'
|
|
@@ -138,7 +130,6 @@ import {
|
|
|
138
130
|
import { getIncrementedName } from '../utils/getIncrementedName'
|
|
139
131
|
import { isAccelKey } from '../utils/keyboard'
|
|
140
132
|
import { getReorderingShapesChanges } from '../utils/reorderShapes'
|
|
141
|
-
import { TLTextOptions, TiptapEditor } from '../utils/richText'
|
|
142
133
|
import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
|
|
143
134
|
import { BindingOnDeleteOptions, BindingUtil } from './bindings/BindingUtil'
|
|
144
135
|
import { bindingsIndex } from './derivations/bindingsIndex'
|
|
@@ -148,14 +139,13 @@ import { deriveShapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage
|
|
|
148
139
|
import { ClickManager } from './managers/ClickManager'
|
|
149
140
|
import { EdgeScrollManager } from './managers/EdgeScrollManager'
|
|
150
141
|
import { FocusManager } from './managers/FocusManager'
|
|
151
|
-
import { FontManager } from './managers/FontManager'
|
|
152
142
|
import { HistoryManager } from './managers/HistoryManager'
|
|
153
143
|
import { ScribbleManager } from './managers/ScribbleManager'
|
|
154
144
|
import { SnapManager } from './managers/SnapManager/SnapManager'
|
|
155
145
|
import { TextManager } from './managers/TextManager'
|
|
156
146
|
import { TickManager } from './managers/TickManager'
|
|
157
147
|
import { UserPreferencesManager } from './managers/UserPreferencesManager'
|
|
158
|
-
import { ShapeUtil, TLResizeMode } from './shapes/ShapeUtil'
|
|
148
|
+
import { ShapeUtil, TLGeometryOpts, TLResizeMode } from './shapes/ShapeUtil'
|
|
159
149
|
import { RootState } from './tools/RootState'
|
|
160
150
|
import { StateNode, TLStateNodeConstructor } from './tools/StateNode'
|
|
161
151
|
import { TLContent } from './types/clipboard-types'
|
|
@@ -235,10 +225,8 @@ export interface TLEditorOptions {
|
|
|
235
225
|
* Options for the editor's camera.
|
|
236
226
|
*/
|
|
237
227
|
cameraOptions?: Partial<TLCameraOptions>
|
|
238
|
-
textOptions?: TLTextOptions
|
|
239
228
|
options?: Partial<TldrawOptions>
|
|
240
229
|
licenseKey?: string
|
|
241
|
-
fontAssetUrls?: { [key: string]: string | undefined }
|
|
242
230
|
/**
|
|
243
231
|
* A predicate that should return true if the given shape should be hidden.
|
|
244
232
|
* @param shape - The shape to check.
|
|
@@ -275,13 +263,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
275
263
|
tools,
|
|
276
264
|
getContainer,
|
|
277
265
|
cameraOptions,
|
|
278
|
-
textOptions,
|
|
279
266
|
initialState,
|
|
280
267
|
autoFocus,
|
|
281
268
|
inferDarkMode,
|
|
282
269
|
options,
|
|
283
270
|
isShapeHidden,
|
|
284
|
-
fontAssetUrls,
|
|
285
271
|
}: TLEditorOptions) {
|
|
286
272
|
super()
|
|
287
273
|
|
|
@@ -305,16 +291,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
305
291
|
|
|
306
292
|
this._cameraOptions.set({ ...DEFAULT_CAMERA_OPTIONS, ...cameraOptions })
|
|
307
293
|
|
|
308
|
-
this._textOptions = atom('text options', textOptions ?? null)
|
|
309
|
-
|
|
310
294
|
this.user = new UserPreferencesManager(user ?? createTLUser(), inferDarkMode ?? false)
|
|
311
295
|
this.disposables.add(() => this.user.dispose())
|
|
312
296
|
|
|
313
297
|
this.getContainer = getContainer
|
|
314
298
|
|
|
315
299
|
this.textMeasure = new TextManager(this)
|
|
316
|
-
this.fonts = new FontManager(this, fontAssetUrls)
|
|
317
|
-
|
|
318
300
|
this._tickManager = new TickManager(this)
|
|
319
301
|
|
|
320
302
|
class NewRoot extends RootState {
|
|
@@ -853,13 +835,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
853
835
|
*/
|
|
854
836
|
readonly textMeasure: TextManager
|
|
855
837
|
|
|
856
|
-
/**
|
|
857
|
-
* A utility for managing the set of fonts that should be rendered in the document.
|
|
858
|
-
*
|
|
859
|
-
* @public
|
|
860
|
-
*/
|
|
861
|
-
readonly fonts: FontManager
|
|
862
|
-
|
|
863
838
|
/**
|
|
864
839
|
* A manager for the editor's environment.
|
|
865
840
|
*
|
|
@@ -2048,7 +2023,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2048
2023
|
*/
|
|
2049
2024
|
setEditingShape(shape: TLShapeId | TLShape | null): this {
|
|
2050
2025
|
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2051
|
-
this.setRichTextEditor(null)
|
|
2052
2026
|
if (id !== this.getEditingShapeId()) {
|
|
2053
2027
|
if (id) {
|
|
2054
2028
|
const shape = this.getShape(id)
|
|
@@ -2067,7 +2041,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2067
2041
|
this.run(
|
|
2068
2042
|
() => {
|
|
2069
2043
|
this._updateCurrentPageState({ editingShapeId: null })
|
|
2070
|
-
this._currentRichTextEditor.set(null)
|
|
2071
2044
|
},
|
|
2072
2045
|
{ history: 'ignore' }
|
|
2073
2046
|
)
|
|
@@ -2075,42 +2048,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2075
2048
|
return this
|
|
2076
2049
|
}
|
|
2077
2050
|
|
|
2078
|
-
// Rich text editor
|
|
2079
|
-
|
|
2080
|
-
private _currentRichTextEditor = atom('rich text editor', null as TiptapEditor | null)
|
|
2081
|
-
|
|
2082
|
-
/**
|
|
2083
|
-
* The current editing shape's text editor.
|
|
2084
|
-
*
|
|
2085
|
-
* @public
|
|
2086
|
-
*/
|
|
2087
|
-
@computed getRichTextEditor(): TiptapEditor | null {
|
|
2088
|
-
return this._currentRichTextEditor.get()
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
/**
|
|
2092
|
-
* Set the current editing shape's rich text editor.
|
|
2093
|
-
*
|
|
2094
|
-
* @example
|
|
2095
|
-
* ```ts
|
|
2096
|
-
* editor.setRichTextEditor(richTextEditorView)
|
|
2097
|
-
* ```
|
|
2098
|
-
*
|
|
2099
|
-
* @param textEditor - The text editor to set as the current editing shape's text editor.
|
|
2100
|
-
*
|
|
2101
|
-
* @public
|
|
2102
|
-
*/
|
|
2103
|
-
setRichTextEditor(textEditor: TiptapEditor | null) {
|
|
2104
|
-
// If the new editor is different from the current one, destroy the current one
|
|
2105
|
-
const current = this._currentRichTextEditor.__unsafe__getWithoutCapture()
|
|
2106
|
-
if (current !== textEditor) {
|
|
2107
|
-
current?.destroy()
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
this._currentRichTextEditor.set(textEditor)
|
|
2111
|
-
return this
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
2051
|
// Hovered
|
|
2115
2052
|
|
|
2116
2053
|
/**
|
|
@@ -2167,7 +2104,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2167
2104
|
@computed getHintingShapeIds() {
|
|
2168
2105
|
return this.getCurrentPageState().hintingShapeIds
|
|
2169
2106
|
}
|
|
2170
|
-
|
|
2171
2107
|
/**
|
|
2172
2108
|
* The editor's current hinting shapes.
|
|
2173
2109
|
*
|
|
@@ -2316,21 +2252,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2316
2252
|
return this
|
|
2317
2253
|
}
|
|
2318
2254
|
|
|
2319
|
-
private _textOptions: Atom<TLTextOptions | null>
|
|
2320
|
-
|
|
2321
|
-
/**
|
|
2322
|
-
* Get the current text options.
|
|
2323
|
-
*
|
|
2324
|
-
* @example
|
|
2325
|
-
* ```ts
|
|
2326
|
-
* editor.getTextOptions()
|
|
2327
|
-
* ```
|
|
2328
|
-
*
|
|
2329
|
-
* @public */
|
|
2330
|
-
getTextOptions() {
|
|
2331
|
-
return assertExists(this._textOptions.get(), 'Cannot use text without setting textOptions')
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
2255
|
/* --------------------- Camera --------------------- */
|
|
2335
2256
|
|
|
2336
2257
|
/** @internal */
|
|
@@ -4283,17 +4204,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4283
4204
|
|
|
4284
4205
|
/* --------------------- Shapes --------------------- */
|
|
4285
4206
|
|
|
4286
|
-
|
|
4287
|
-
private _getShapeGeometryCache(): ComputedCache<Geometry2d, TLShape> {
|
|
4288
|
-
return this.store.createComputedCache(
|
|
4289
|
-
'bounds',
|
|
4290
|
-
(shape) => {
|
|
4291
|
-
this.fonts.trackFontsForShape(shape)
|
|
4292
|
-
return this.getShapeUtil(shape).getGeometry(shape)
|
|
4293
|
-
},
|
|
4294
|
-
{ areRecordsEqual: (a, b) => a.props === b.props }
|
|
4295
|
-
)
|
|
4296
|
-
}
|
|
4207
|
+
private _shapeGeometryCaches: Record<string, ComputedCache<Geometry2d, TLShape>> = {}
|
|
4297
4208
|
|
|
4298
4209
|
/**
|
|
4299
4210
|
* Get the geometry of a shape.
|
|
@@ -4302,14 +4213,26 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4302
4213
|
* ```ts
|
|
4303
4214
|
* editor.getShapeGeometry(myShape)
|
|
4304
4215
|
* editor.getShapeGeometry(myShapeId)
|
|
4216
|
+
* editor.getShapeGeometry(myShapeId, { context: "arrow" })
|
|
4305
4217
|
* ```
|
|
4306
4218
|
*
|
|
4307
4219
|
* @param shape - The shape (or shape id) to get the geometry for.
|
|
4220
|
+
* @param opts - Additional options about the request for geometry. Passed to {@link ShapeUtil.getGeometry}.
|
|
4308
4221
|
*
|
|
4309
4222
|
* @public
|
|
4310
4223
|
*/
|
|
4311
|
-
getShapeGeometry<T extends Geometry2d>(shape: TLShape | TLShapeId): T {
|
|
4312
|
-
|
|
4224
|
+
getShapeGeometry<T extends Geometry2d>(shape: TLShape | TLShapeId, opts?: TLGeometryOpts): T {
|
|
4225
|
+
const context = opts?.context ?? 'none'
|
|
4226
|
+
if (!this._shapeGeometryCaches[context]) {
|
|
4227
|
+
this._shapeGeometryCaches[context] = this.store.createComputedCache(
|
|
4228
|
+
'bounds',
|
|
4229
|
+
(shape) => this.getShapeUtil(shape).getGeometry(shape, opts),
|
|
4230
|
+
{ areRecordsEqual: (a, b) => a.props === b.props }
|
|
4231
|
+
)
|
|
4232
|
+
}
|
|
4233
|
+
return this._shapeGeometryCaches[context].get(
|
|
4234
|
+
typeof shape === 'string' ? shape : shape.id
|
|
4235
|
+
)! as T
|
|
4313
4236
|
}
|
|
4314
4237
|
|
|
4315
4238
|
/** @internal */
|
|
@@ -4841,10 +4764,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4841
4764
|
// Check labels first
|
|
4842
4765
|
if (
|
|
4843
4766
|
this.isShapeOfType<TLFrameShape>(shape, 'frame') ||
|
|
4844
|
-
(this.isShapeOfType<TLArrowShape>(shape, 'arrow')
|
|
4845
|
-
((this.isShapeOfType<TLNoteShape>(shape, 'note') ||
|
|
4767
|
+
((this.isShapeOfType<TLArrowShape>(shape, 'arrow') ||
|
|
4846
4768
|
(this.isShapeOfType<TLGeoShape>(shape, 'geo') && shape.props.fill === 'none')) &&
|
|
4847
|
-
|
|
4769
|
+
shape.props.text.trim())
|
|
4848
4770
|
) {
|
|
4849
4771
|
for (const childGeometry of (geometry as Group2d).children) {
|
|
4850
4772
|
if (childGeometry.isLabel && childGeometry.isPointInBounds(pointInShapeSpace)) {
|
|
@@ -5769,14 +5691,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5769
5691
|
return this
|
|
5770
5692
|
}
|
|
5771
5693
|
|
|
5694
|
+
// Gets a shape partial that includes life cycle changes: on translate start, on translate, on translate end
|
|
5772
5695
|
private getChangesToTranslateShape(initialShape: TLShape, newShapeCoords: VecLike): TLShape {
|
|
5773
5696
|
let workingShape = initialShape
|
|
5774
5697
|
const util = this.getShapeUtil(initialShape)
|
|
5775
5698
|
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5699
|
+
const afterTranslateStart = util.onTranslateStart?.(workingShape)
|
|
5700
|
+
if (afterTranslateStart) {
|
|
5701
|
+
workingShape = applyPartialToRecordWithProps(workingShape, afterTranslateStart)
|
|
5702
|
+
}
|
|
5780
5703
|
|
|
5781
5704
|
workingShape = applyPartialToRecordWithProps(workingShape, {
|
|
5782
5705
|
id: initialShape.id,
|
|
@@ -5785,15 +5708,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5785
5708
|
y: newShapeCoords.y,
|
|
5786
5709
|
})
|
|
5787
5710
|
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5711
|
+
const afterTranslate = util.onTranslate?.(initialShape, workingShape)
|
|
5712
|
+
if (afterTranslate) {
|
|
5713
|
+
workingShape = applyPartialToRecordWithProps(workingShape, afterTranslate)
|
|
5714
|
+
}
|
|
5792
5715
|
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5716
|
+
const afterTranslateEnd = util.onTranslateEnd?.(initialShape, workingShape)
|
|
5717
|
+
if (afterTranslateEnd) {
|
|
5718
|
+
workingShape = applyPartialToRecordWithProps(workingShape, afterTranslateEnd)
|
|
5719
|
+
}
|
|
5797
5720
|
|
|
5798
5721
|
return workingShape
|
|
5799
5722
|
}
|
|
@@ -6192,6 +6115,37 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6192
6115
|
return this
|
|
6193
6116
|
}
|
|
6194
6117
|
|
|
6118
|
+
/**
|
|
6119
|
+
* @internal
|
|
6120
|
+
*/
|
|
6121
|
+
private collectShapesViaArrowBindings(info: {
|
|
6122
|
+
initialShapes: TLShape[]
|
|
6123
|
+
resultShapes: TLShape[]
|
|
6124
|
+
resultBounds: Box[]
|
|
6125
|
+
bindings: TLBinding[]
|
|
6126
|
+
visited: Set<TLShapeId>
|
|
6127
|
+
}) {
|
|
6128
|
+
const { initialShapes, resultShapes, resultBounds, bindings, visited } = info
|
|
6129
|
+
for (const binding of bindings) {
|
|
6130
|
+
for (const id of [binding.fromId, binding.toId]) {
|
|
6131
|
+
if (!visited.has(id)) {
|
|
6132
|
+
const aligningShape = initialShapes.find((s) => s.id === id)
|
|
6133
|
+
if (aligningShape && !visited.has(aligningShape.id)) {
|
|
6134
|
+
visited.add(aligningShape.id)
|
|
6135
|
+
const shapePageBounds = this.getShapePageBounds(aligningShape)
|
|
6136
|
+
if (!shapePageBounds) continue
|
|
6137
|
+
resultShapes.push(aligningShape)
|
|
6138
|
+
resultBounds.push(shapePageBounds)
|
|
6139
|
+
this.collectShapesViaArrowBindings({
|
|
6140
|
+
...info,
|
|
6141
|
+
bindings: this.getBindingsInvolvingShape(aligningShape, 'arrow'),
|
|
6142
|
+
})
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
|
|
6195
6149
|
/**
|
|
6196
6150
|
* Flip shape positions.
|
|
6197
6151
|
*
|
|
@@ -6207,47 +6161,74 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6207
6161
|
* @public
|
|
6208
6162
|
*/
|
|
6209
6163
|
flipShapes(shapes: TLShapeId[] | TLShape[], operation: 'horizontal' | 'vertical'): this {
|
|
6164
|
+
if (this.getIsReadonly()) return this
|
|
6165
|
+
|
|
6210
6166
|
const ids =
|
|
6211
6167
|
typeof shapes[0] === 'string'
|
|
6212
6168
|
? (shapes as TLShapeId[])
|
|
6213
6169
|
: (shapes as TLShape[]).map((s) => s.id)
|
|
6214
6170
|
|
|
6215
|
-
|
|
6171
|
+
// Collect a greedy list of shapes to flip
|
|
6172
|
+
const shapesToFlipFirstPass = compact(ids.map((id) => this.getShape(id)))
|
|
6216
6173
|
|
|
6217
|
-
|
|
6174
|
+
for (const shape of shapesToFlipFirstPass) {
|
|
6175
|
+
if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
|
|
6176
|
+
const childrenOfGroups = compact(
|
|
6177
|
+
this.getSortedChildIdsForParent(shape.id).map((id) => this.getShape(id))
|
|
6178
|
+
)
|
|
6179
|
+
shapesToFlipFirstPass.push(...childrenOfGroups)
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6218
6182
|
|
|
6219
|
-
|
|
6183
|
+
// exclude shapes that can't be flipped
|
|
6184
|
+
const shapesToFlip: {
|
|
6185
|
+
shape: TLShape
|
|
6186
|
+
localBounds: Box
|
|
6187
|
+
pageTransform: Mat
|
|
6188
|
+
isAspectRatioLocked: boolean
|
|
6189
|
+
}[] = []
|
|
6220
6190
|
|
|
6221
|
-
|
|
6222
|
-
shapesToFlip
|
|
6223
|
-
.map((shape) => {
|
|
6224
|
-
if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
|
|
6225
|
-
return this.getSortedChildIdsForParent(shape.id).map((id) => this.getShape(id))
|
|
6226
|
-
}
|
|
6191
|
+
const allBounds: Box[] = []
|
|
6227
6192
|
|
|
6228
|
-
|
|
6193
|
+
for (const shape of shapesToFlipFirstPass) {
|
|
6194
|
+
const util = this.getShapeUtil(shape)
|
|
6195
|
+
if (
|
|
6196
|
+
!util.canBeLaidOut(shape, {
|
|
6197
|
+
type: 'flip',
|
|
6198
|
+
shapes: shapesToFlipFirstPass,
|
|
6229
6199
|
})
|
|
6230
|
-
|
|
6231
|
-
|
|
6200
|
+
) {
|
|
6201
|
+
continue
|
|
6202
|
+
}
|
|
6203
|
+
|
|
6204
|
+
const pageBounds = this.getShapePageBounds(shape)
|
|
6205
|
+
const localBounds = this.getShapeGeometry(shape).bounds
|
|
6206
|
+
const pageTransform = this.getShapePageTransform(shape.id)
|
|
6207
|
+
if (!(pageBounds && localBounds && pageTransform)) continue
|
|
6208
|
+
shapesToFlip.push({
|
|
6209
|
+
shape,
|
|
6210
|
+
localBounds,
|
|
6211
|
+
pageTransform,
|
|
6212
|
+
isAspectRatioLocked: util.isAspectRatioLocked(shape),
|
|
6213
|
+
})
|
|
6214
|
+
allBounds.push(pageBounds)
|
|
6215
|
+
}
|
|
6232
6216
|
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
).center
|
|
6217
|
+
if (!shapesToFlip.length) return this
|
|
6218
|
+
|
|
6219
|
+
const scaleOriginPage = Box.Common(allBounds).center
|
|
6236
6220
|
|
|
6237
6221
|
this.run(() => {
|
|
6238
|
-
for (const shape of shapesToFlip) {
|
|
6239
|
-
const bounds = this.getShapeGeometry(shape).bounds
|
|
6240
|
-
const initialPageTransform = this.getShapePageTransform(shape.id)
|
|
6241
|
-
if (!initialPageTransform) continue
|
|
6222
|
+
for (const { shape, localBounds, pageTransform, isAspectRatioLocked } of shapesToFlip) {
|
|
6242
6223
|
this.resizeShape(
|
|
6243
6224
|
shape.id,
|
|
6244
6225
|
{ x: operation === 'horizontal' ? -1 : 1, y: operation === 'vertical' ? -1 : 1 },
|
|
6245
6226
|
{
|
|
6246
|
-
initialBounds:
|
|
6247
|
-
initialPageTransform,
|
|
6227
|
+
initialBounds: localBounds,
|
|
6228
|
+
initialPageTransform: pageTransform,
|
|
6248
6229
|
initialShape: shape,
|
|
6230
|
+
isAspectRatioLocked,
|
|
6249
6231
|
mode: 'scale_shape',
|
|
6250
|
-
isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
|
|
6251
6232
|
scaleOrigin: scaleOriginPage,
|
|
6252
6233
|
scaleAxisRotation: 0,
|
|
6253
6234
|
}
|
|
@@ -6284,21 +6265,58 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6284
6265
|
: (shapes as TLShape[]).map((s) => s.id)
|
|
6285
6266
|
if (this.getIsReadonly()) return this
|
|
6286
6267
|
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6268
|
+
// todo: this has a lot of extra code to handle stacking with custom gaps or auto gaps or other things like that. I don't think anyone has ever used this stuff.
|
|
6269
|
+
|
|
6270
|
+
// always fresh shapes
|
|
6271
|
+
const shapesToStackFirstPass = compact(ids.map((id) => this.getShape(id)))
|
|
6272
|
+
|
|
6273
|
+
const shapeClustersToStack: {
|
|
6274
|
+
shapes: TLShape[]
|
|
6275
|
+
pageBounds: Box
|
|
6276
|
+
}[] = []
|
|
6277
|
+
const allBounds: Box[] = []
|
|
6278
|
+
const visited = new Set<TLShapeId>()
|
|
6291
6279
|
|
|
6292
|
-
|
|
6280
|
+
for (const shape of shapesToStackFirstPass) {
|
|
6281
|
+
if (visited.has(shape.id)) continue
|
|
6282
|
+
visited.add(shape.id)
|
|
6283
|
+
|
|
6284
|
+
const shapePageBounds = this.getShapePageBounds(shape)
|
|
6285
|
+
if (!shapePageBounds) continue
|
|
6286
|
+
|
|
6287
|
+
if (
|
|
6288
|
+
!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
|
|
6289
|
+
type: 'stack',
|
|
6290
|
+
shapes: shapesToStackFirstPass,
|
|
6291
|
+
})
|
|
6292
|
+
) {
|
|
6293
|
+
continue
|
|
6294
|
+
}
|
|
6295
|
+
|
|
6296
|
+
const shapesMovingTogether = [shape]
|
|
6297
|
+
const boundsOfShapesMovingTogether: Box[] = [shapePageBounds]
|
|
6298
|
+
|
|
6299
|
+
this.collectShapesViaArrowBindings({
|
|
6300
|
+
bindings: this.getBindingsToShape(shape.id, 'arrow'),
|
|
6301
|
+
initialShapes: shapesToStackFirstPass,
|
|
6302
|
+
resultShapes: shapesMovingTogether,
|
|
6303
|
+
resultBounds: boundsOfShapesMovingTogether,
|
|
6304
|
+
visited,
|
|
6293
6305
|
})
|
|
6294
6306
|
|
|
6295
|
-
|
|
6307
|
+
const commonPageBounds = Box.Common(boundsOfShapesMovingTogether)
|
|
6308
|
+
if (!commonPageBounds) continue
|
|
6296
6309
|
|
|
6297
|
-
|
|
6310
|
+
shapeClustersToStack.push({
|
|
6311
|
+
shapes: shapesMovingTogether,
|
|
6312
|
+
pageBounds: commonPageBounds,
|
|
6313
|
+
})
|
|
6298
6314
|
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6315
|
+
allBounds.push(commonPageBounds)
|
|
6316
|
+
}
|
|
6317
|
+
|
|
6318
|
+
const len = shapeClustersToStack.length
|
|
6319
|
+
if ((gap === 0 && len < 3) || len < 2) return this
|
|
6302
6320
|
|
|
6303
6321
|
let val: 'x' | 'y'
|
|
6304
6322
|
let min: 'minX' | 'minY'
|
|
@@ -6317,46 +6335,45 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6317
6335
|
dim = 'height'
|
|
6318
6336
|
}
|
|
6319
6337
|
|
|
6320
|
-
let shapeGap: number
|
|
6338
|
+
let shapeGap: number = 0
|
|
6321
6339
|
|
|
6322
6340
|
if (gap === 0) {
|
|
6323
|
-
|
|
6341
|
+
// note: this is not used in the current tldraw.com; there we use a specified stack
|
|
6324
6342
|
|
|
6325
|
-
|
|
6343
|
+
const gaps: Record<number, number> = {}
|
|
6344
|
+
|
|
6345
|
+
shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])
|
|
6326
6346
|
|
|
6327
6347
|
// Collect all of the gaps between shapes. We want to find
|
|
6328
6348
|
// patterns (equal gaps between shapes) and use the most common
|
|
6329
6349
|
// one as the gap for all of the shapes.
|
|
6330
6350
|
for (let i = 0; i < len - 1; i++) {
|
|
6331
|
-
const
|
|
6332
|
-
const
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
const gap = nextBounds[min] - bounds[max]
|
|
6338
|
-
|
|
6339
|
-
const current = gaps.find((g) => g.gap === gap)
|
|
6340
|
-
|
|
6341
|
-
if (current) {
|
|
6342
|
-
current.count++
|
|
6343
|
-
} else {
|
|
6344
|
-
gaps.push({ gap, count: 1 })
|
|
6351
|
+
const currCluster = shapeClustersToStack[i]
|
|
6352
|
+
const nextCluster = shapeClustersToStack[i + 1]
|
|
6353
|
+
const gap = nextCluster.pageBounds[min] - currCluster.pageBounds[max]
|
|
6354
|
+
if (!gaps[gap]) {
|
|
6355
|
+
gaps[gap] = 0
|
|
6345
6356
|
}
|
|
6357
|
+
gaps[gap]++
|
|
6346
6358
|
}
|
|
6347
6359
|
|
|
6348
6360
|
// Which gap is the most common?
|
|
6349
|
-
let maxCount =
|
|
6350
|
-
|
|
6351
|
-
if (
|
|
6352
|
-
maxCount =
|
|
6353
|
-
shapeGap =
|
|
6361
|
+
let maxCount = 1
|
|
6362
|
+
for (const [gap, count] of Object.entries(gaps)) {
|
|
6363
|
+
if (count > maxCount) {
|
|
6364
|
+
maxCount = count
|
|
6365
|
+
shapeGap = parseFloat(gap)
|
|
6354
6366
|
}
|
|
6355
|
-
}
|
|
6367
|
+
}
|
|
6356
6368
|
|
|
6357
6369
|
// If there is no most-common gap, use the average gap.
|
|
6358
6370
|
if (maxCount === 1) {
|
|
6359
|
-
|
|
6371
|
+
let totalCount = 0
|
|
6372
|
+
for (const [gap, count] of Object.entries(gaps)) {
|
|
6373
|
+
shapeGap += parseFloat(gap) * count
|
|
6374
|
+
totalCount += count
|
|
6375
|
+
}
|
|
6376
|
+
shapeGap /= totalCount
|
|
6360
6377
|
}
|
|
6361
6378
|
} else {
|
|
6362
6379
|
// If a gap was provided, then use that instead.
|
|
@@ -6365,36 +6382,30 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6365
6382
|
|
|
6366
6383
|
const changes: TLShapePartial[] = []
|
|
6367
6384
|
|
|
6368
|
-
let v =
|
|
6369
|
-
|
|
6370
|
-
shapesToStack.forEach((shape, i) => {
|
|
6371
|
-
if (i === 0) return
|
|
6385
|
+
let v = shapeClustersToStack[0].pageBounds[max]
|
|
6372
6386
|
|
|
6373
|
-
|
|
6374
|
-
|
|
6387
|
+
for (let i = 1; i < shapeClustersToStack.length; i++) {
|
|
6388
|
+
const { shapes, pageBounds } = shapeClustersToStack[i]
|
|
6389
|
+
const delta = new Vec()
|
|
6390
|
+
delta[val] = v + shapeGap - pageBounds[val]
|
|
6375
6391
|
|
|
6376
|
-
const
|
|
6377
|
-
|
|
6378
|
-
? Vec.Rot(delta, -this.getShapePageTransform(parent)!.decompose().rotation)
|
|
6379
|
-
: delta
|
|
6392
|
+
for (const shape of shapes) {
|
|
6393
|
+
const shapeDelta = delta.clone()
|
|
6380
6394
|
|
|
6381
|
-
|
|
6395
|
+
// If the shape has another shape as its parent, and if the parent has a rotation, we need to rotate the counter-rotate delta
|
|
6396
|
+
// todo: ensure that the parent isn't being aligned together with its children
|
|
6397
|
+
const parent = this.getShapeParent(shape)
|
|
6398
|
+
if (parent) {
|
|
6399
|
+
const parentTransform = this.getShapePageTransform(parent)
|
|
6400
|
+
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
6401
|
+
}
|
|
6382
6402
|
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
...translateStartChanges,
|
|
6387
|
-
[val]: shape[val] + localDelta[val],
|
|
6388
|
-
}
|
|
6389
|
-
: {
|
|
6390
|
-
id: shape.id as any,
|
|
6391
|
-
type: shape.type,
|
|
6392
|
-
[val]: shape[val] + localDelta[val],
|
|
6393
|
-
}
|
|
6394
|
-
)
|
|
6403
|
+
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
6404
|
+
changes.push(this.getChangesToTranslateShape(shape, shapeDelta))
|
|
6405
|
+
}
|
|
6395
6406
|
|
|
6396
|
-
v += pageBounds[
|
|
6397
|
-
}
|
|
6407
|
+
v += pageBounds[dim] + shapeGap
|
|
6408
|
+
}
|
|
6398
6409
|
|
|
6399
6410
|
this.updateShapes(changes)
|
|
6400
6411
|
return this
|
|
@@ -6414,42 +6425,79 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6414
6425
|
* @param gap - The padding to apply to the packed shapes. Defaults to 16.
|
|
6415
6426
|
*/
|
|
6416
6427
|
packShapes(shapes: TLShapeId[] | TLShape[], gap: number): this {
|
|
6428
|
+
if (this.getIsReadonly()) return this
|
|
6429
|
+
|
|
6417
6430
|
const ids =
|
|
6418
6431
|
typeof shapes[0] === 'string'
|
|
6419
6432
|
? (shapes as TLShapeId[])
|
|
6420
6433
|
: (shapes as TLShape[]).map((s) => s.id)
|
|
6421
6434
|
|
|
6422
|
-
|
|
6423
|
-
|
|
6435
|
+
// Always fresh shapes
|
|
6436
|
+
const shapesToPackFirstPass = compact(ids.map((id) => this.getShape(id)))
|
|
6437
|
+
|
|
6438
|
+
const shapeClustersToPack: {
|
|
6439
|
+
shapes: TLShape[]
|
|
6440
|
+
pageBounds: Box
|
|
6441
|
+
nextPageBounds: Box
|
|
6442
|
+
}[] = []
|
|
6443
|
+
|
|
6444
|
+
const allBounds: Box[] = []
|
|
6445
|
+
const visited = new Set<TLShapeId>()
|
|
6446
|
+
|
|
6447
|
+
for (const shape of shapesToPackFirstPass) {
|
|
6448
|
+
if (visited.has(shape.id)) continue
|
|
6449
|
+
visited.add(shape.id)
|
|
6424
6450
|
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
.filter((shape): shape is TLShape => {
|
|
6428
|
-
if (!shape) return false
|
|
6451
|
+
const shapePageBounds = this.getShapePageBounds(shape)
|
|
6452
|
+
if (!shapePageBounds) continue
|
|
6429
6453
|
|
|
6430
|
-
|
|
6454
|
+
if (
|
|
6455
|
+
!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
|
|
6456
|
+
type: 'pack',
|
|
6457
|
+
shapes: shapesToPackFirstPass,
|
|
6458
|
+
})
|
|
6459
|
+
) {
|
|
6460
|
+
continue
|
|
6461
|
+
}
|
|
6462
|
+
|
|
6463
|
+
const shapesMovingTogether = [shape]
|
|
6464
|
+
const boundsOfShapesMovingTogether: Box[] = [shapePageBounds]
|
|
6465
|
+
|
|
6466
|
+
this.collectShapesViaArrowBindings({
|
|
6467
|
+
bindings: this.getBindingsToShape(shape.id, 'arrow'),
|
|
6468
|
+
initialShapes: shapesToPackFirstPass,
|
|
6469
|
+
resultShapes: shapesMovingTogether,
|
|
6470
|
+
resultBounds: boundsOfShapesMovingTogether,
|
|
6471
|
+
visited,
|
|
6431
6472
|
})
|
|
6432
|
-
const shapePageBounds: Record<string, Box> = {}
|
|
6433
|
-
const nextShapePageBounds: Record<string, Box> = {}
|
|
6434
6473
|
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6474
|
+
const commonPageBounds = Box.Common(boundsOfShapesMovingTogether)
|
|
6475
|
+
if (!commonPageBounds) continue
|
|
6476
|
+
|
|
6477
|
+
shapeClustersToPack.push({
|
|
6478
|
+
shapes: shapesMovingTogether,
|
|
6479
|
+
pageBounds: commonPageBounds,
|
|
6480
|
+
nextPageBounds: commonPageBounds.clone(),
|
|
6481
|
+
})
|
|
6438
6482
|
|
|
6439
|
-
|
|
6440
|
-
shape = shapesToPack[i]
|
|
6441
|
-
bounds = this.getShapePageBounds(shape)!
|
|
6442
|
-
shapePageBounds[shape.id] = bounds
|
|
6443
|
-
nextShapePageBounds[shape.id] = bounds.clone()
|
|
6444
|
-
area += bounds.width * bounds.height
|
|
6483
|
+
allBounds.push(commonPageBounds)
|
|
6445
6484
|
}
|
|
6446
6485
|
|
|
6447
|
-
|
|
6486
|
+
if (shapeClustersToPack.length < 2) return this
|
|
6487
|
+
|
|
6488
|
+
let area = 0
|
|
6489
|
+
for (const { pageBounds } of shapeClustersToPack) {
|
|
6490
|
+
area += pageBounds.width * pageBounds.height
|
|
6491
|
+
}
|
|
6492
|
+
|
|
6493
|
+
const commonBounds = Box.Common(allBounds)
|
|
6448
6494
|
|
|
6449
6495
|
const maxWidth = commonBounds.width
|
|
6450
6496
|
|
|
6451
|
-
// sort the
|
|
6452
|
-
|
|
6497
|
+
// sort the shape clusters by width and then height, descending
|
|
6498
|
+
shapeClustersToPack
|
|
6499
|
+
.sort((a, b) => a.pageBounds.width - b.pageBounds.width)
|
|
6500
|
+
.sort((a, b) => a.pageBounds.height - b.pageBounds.height)
|
|
6453
6501
|
|
|
6454
6502
|
// Start with is (sort of) the square of the area
|
|
6455
6503
|
const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth)
|
|
@@ -6462,85 +6510,69 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6462
6510
|
let space: Box
|
|
6463
6511
|
let last: Box
|
|
6464
6512
|
|
|
6465
|
-
for (
|
|
6466
|
-
shape = shapesToPack[i]
|
|
6467
|
-
bounds = nextShapePageBounds[shape.id]
|
|
6468
|
-
|
|
6513
|
+
for (const { nextPageBounds } of shapeClustersToPack) {
|
|
6469
6514
|
// starting at the back (smaller shapes)
|
|
6470
6515
|
for (let i = spaces.length - 1; i >= 0; i--) {
|
|
6471
6516
|
space = spaces[i]
|
|
6472
6517
|
|
|
6473
6518
|
// find a space that is big enough to contain the shape
|
|
6474
|
-
if (
|
|
6519
|
+
if (nextPageBounds.width > space.width || nextPageBounds.height > space.height) continue
|
|
6475
6520
|
|
|
6476
6521
|
// add the shape to its top-left corner
|
|
6477
|
-
|
|
6478
|
-
|
|
6522
|
+
nextPageBounds.x = space.x
|
|
6523
|
+
nextPageBounds.y = space.y
|
|
6479
6524
|
|
|
6480
|
-
height = Math.max(height,
|
|
6481
|
-
width = Math.max(width,
|
|
6525
|
+
height = Math.max(height, nextPageBounds.maxY)
|
|
6526
|
+
width = Math.max(width, nextPageBounds.maxX)
|
|
6482
6527
|
|
|
6483
|
-
if (
|
|
6528
|
+
if (nextPageBounds.width === space.width && nextPageBounds.height === space.height) {
|
|
6484
6529
|
// remove the space on a perfect fit
|
|
6485
6530
|
last = spaces.pop()!
|
|
6486
6531
|
if (i < spaces.length) spaces[i] = last
|
|
6487
|
-
} else if (
|
|
6532
|
+
} else if (nextPageBounds.height === space.height) {
|
|
6488
6533
|
// fit the shape into the space (width)
|
|
6489
|
-
space.x +=
|
|
6490
|
-
space.width -=
|
|
6491
|
-
} else if (
|
|
6534
|
+
space.x += nextPageBounds.width + gap
|
|
6535
|
+
space.width -= nextPageBounds.width + gap
|
|
6536
|
+
} else if (nextPageBounds.width === space.width) {
|
|
6492
6537
|
// fit the shape into the space (height)
|
|
6493
|
-
space.y +=
|
|
6494
|
-
space.height -=
|
|
6538
|
+
space.y += nextPageBounds.height + gap
|
|
6539
|
+
space.height -= nextPageBounds.height + gap
|
|
6495
6540
|
} else {
|
|
6496
6541
|
// split the space into two spaces
|
|
6497
6542
|
spaces.push(
|
|
6498
6543
|
new Box(
|
|
6499
|
-
space.x + (
|
|
6544
|
+
space.x + (nextPageBounds.width + gap),
|
|
6500
6545
|
space.y,
|
|
6501
|
-
space.width - (
|
|
6502
|
-
|
|
6546
|
+
space.width - (nextPageBounds.width + gap),
|
|
6547
|
+
nextPageBounds.height
|
|
6503
6548
|
)
|
|
6504
6549
|
)
|
|
6505
|
-
space.y +=
|
|
6506
|
-
space.height -=
|
|
6550
|
+
space.y += nextPageBounds.height + gap
|
|
6551
|
+
space.height -= nextPageBounds.height + gap
|
|
6507
6552
|
}
|
|
6508
6553
|
break
|
|
6509
6554
|
}
|
|
6510
6555
|
}
|
|
6511
6556
|
|
|
6512
|
-
const commonAfter = Box.Common(
|
|
6557
|
+
const commonAfter = Box.Common(shapeClustersToPack.map((s) => s.nextPageBounds))
|
|
6513
6558
|
const centerDelta = Vec.Sub(commonBounds.center, commonAfter.center)
|
|
6514
6559
|
|
|
6515
|
-
let nextBounds: Box
|
|
6516
|
-
|
|
6517
6560
|
const changes: TLShapePartial<any>[] = []
|
|
6518
6561
|
|
|
6519
|
-
for (
|
|
6520
|
-
|
|
6521
|
-
bounds = shapePageBounds[shape.id]
|
|
6522
|
-
nextBounds = nextShapePageBounds[shape.id]
|
|
6523
|
-
|
|
6524
|
-
const delta = Vec.Sub(nextBounds.point, bounds.point).add(centerDelta)
|
|
6525
|
-
const parentTransform = this.getShapeParentTransform(shape)
|
|
6526
|
-
if (parentTransform) delta.rot(-parentTransform.rotation())
|
|
6562
|
+
for (const { shapes, pageBounds, nextPageBounds } of shapeClustersToPack) {
|
|
6563
|
+
const delta = Vec.Sub(nextPageBounds.point, pageBounds.point).add(centerDelta)
|
|
6527
6564
|
|
|
6528
|
-
const
|
|
6529
|
-
|
|
6530
|
-
type: shape.type,
|
|
6531
|
-
x: shape.x + delta.x,
|
|
6532
|
-
y: shape.y + delta.y,
|
|
6533
|
-
}
|
|
6565
|
+
for (const shape of shapes) {
|
|
6566
|
+
const shapeDelta = delta.clone()
|
|
6534
6567
|
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6568
|
+
const parent = this.getShapeParent(shape)
|
|
6569
|
+
if (parent) {
|
|
6570
|
+
const parentTransform = this.getShapeParentTransform(shape)
|
|
6571
|
+
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
6572
|
+
}
|
|
6539
6573
|
|
|
6540
|
-
|
|
6541
|
-
changes.push(
|
|
6542
|
-
} else {
|
|
6543
|
-
changes.push(change)
|
|
6574
|
+
shapeDelta.add(shape)
|
|
6575
|
+
changes.push(this.getChangesToTranslateShape(shape, shapeDelta))
|
|
6544
6576
|
}
|
|
6545
6577
|
}
|
|
6546
6578
|
|
|
@@ -6565,32 +6597,78 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6565
6597
|
*
|
|
6566
6598
|
* @public
|
|
6567
6599
|
*/
|
|
6568
|
-
|
|
6569
6600
|
alignShapes(
|
|
6570
6601
|
shapes: TLShapeId[] | TLShape[],
|
|
6571
6602
|
operation: 'left' | 'center-horizontal' | 'right' | 'top' | 'center-vertical' | 'bottom'
|
|
6572
6603
|
): this {
|
|
6604
|
+
if (this.getIsReadonly()) return this
|
|
6605
|
+
|
|
6573
6606
|
const ids =
|
|
6574
6607
|
typeof shapes[0] === 'string'
|
|
6575
6608
|
? (shapes as TLShapeId[])
|
|
6576
6609
|
: (shapes as TLShape[]).map((s) => s.id)
|
|
6577
6610
|
|
|
6578
|
-
|
|
6579
|
-
|
|
6611
|
+
// Always get fresh shapes
|
|
6612
|
+
const shapesToAlignFirstPass = compact(ids.map((id) => this.getShape(id)))
|
|
6580
6613
|
|
|
6581
|
-
const
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
const
|
|
6614
|
+
const shapeClustersToAlign: {
|
|
6615
|
+
shapes: TLShape[]
|
|
6616
|
+
pageBounds: Box
|
|
6617
|
+
}[] = []
|
|
6618
|
+
const allBounds: Box[] = []
|
|
6619
|
+
const visited = new Set<TLShapeId>()
|
|
6586
6620
|
|
|
6587
|
-
const
|
|
6621
|
+
for (const shape of shapesToAlignFirstPass) {
|
|
6622
|
+
if (visited.has(shape.id)) continue
|
|
6623
|
+
visited.add(shape.id)
|
|
6588
6624
|
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
if (!pageBounds) return
|
|
6625
|
+
const shapePageBounds = this.getShapePageBounds(shape)
|
|
6626
|
+
if (!shapePageBounds) continue
|
|
6592
6627
|
|
|
6593
|
-
|
|
6628
|
+
if (
|
|
6629
|
+
!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
|
|
6630
|
+
type: 'align',
|
|
6631
|
+
shapes: shapesToAlignFirstPass,
|
|
6632
|
+
})
|
|
6633
|
+
) {
|
|
6634
|
+
continue
|
|
6635
|
+
}
|
|
6636
|
+
|
|
6637
|
+
// In this implementation, we want to create psuedo-groups out of shapes that
|
|
6638
|
+
// are moving together. At the moment shapes only move together if they're connected
|
|
6639
|
+
// by arrows. So let's say A -> B -> C -> D and A, B, and C are selected. If we're
|
|
6640
|
+
// aligning A, B, and C, then we want these to move together as one unit.
|
|
6641
|
+
|
|
6642
|
+
const shapesMovingTogether = [shape]
|
|
6643
|
+
const boundsOfShapesMovingTogether: Box[] = [shapePageBounds]
|
|
6644
|
+
|
|
6645
|
+
this.collectShapesViaArrowBindings({
|
|
6646
|
+
bindings: this.getBindingsToShape(shape.id, 'arrow'),
|
|
6647
|
+
initialShapes: shapesToAlignFirstPass,
|
|
6648
|
+
resultShapes: shapesMovingTogether,
|
|
6649
|
+
resultBounds: boundsOfShapesMovingTogether,
|
|
6650
|
+
visited,
|
|
6651
|
+
})
|
|
6652
|
+
|
|
6653
|
+
const commonPageBounds = Box.Common(boundsOfShapesMovingTogether)
|
|
6654
|
+
if (!commonPageBounds) continue
|
|
6655
|
+
|
|
6656
|
+
shapeClustersToAlign.push({
|
|
6657
|
+
shapes: shapesMovingTogether,
|
|
6658
|
+
pageBounds: commonPageBounds,
|
|
6659
|
+
})
|
|
6660
|
+
|
|
6661
|
+
allBounds.push(commonPageBounds)
|
|
6662
|
+
}
|
|
6663
|
+
|
|
6664
|
+
if (shapeClustersToAlign.length < 2) return this
|
|
6665
|
+
|
|
6666
|
+
const commonBounds = Box.Common(allBounds)
|
|
6667
|
+
|
|
6668
|
+
const changes: TLShapePartial[] = []
|
|
6669
|
+
|
|
6670
|
+
shapeClustersToAlign.forEach(({ shapes, pageBounds }) => {
|
|
6671
|
+
const delta = new Vec()
|
|
6594
6672
|
|
|
6595
6673
|
switch (operation) {
|
|
6596
6674
|
case 'top': {
|
|
@@ -6619,12 +6697,20 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6619
6697
|
}
|
|
6620
6698
|
}
|
|
6621
6699
|
|
|
6622
|
-
const
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6700
|
+
for (const shape of shapes) {
|
|
6701
|
+
const shapeDelta = delta.clone()
|
|
6702
|
+
|
|
6703
|
+
// If the shape has another shape as its parent, and if the parent has a rotation, we need to rotate the counter-rotate delta
|
|
6704
|
+
// todo: ensure that the parent isn't being aligned together with its children
|
|
6705
|
+
const parent = this.getShapeParent(shape)
|
|
6706
|
+
if (parent) {
|
|
6707
|
+
const parentTransform = this.getShapePageTransform(parent)
|
|
6708
|
+
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
6709
|
+
}
|
|
6626
6710
|
|
|
6627
|
-
|
|
6711
|
+
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
6712
|
+
changes.push(this.getChangesToTranslateShape(shape, shapeDelta))
|
|
6713
|
+
}
|
|
6628
6714
|
})
|
|
6629
6715
|
|
|
6630
6716
|
this.updateShapes(changes)
|
|
@@ -6646,65 +6732,137 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6646
6732
|
* @public
|
|
6647
6733
|
*/
|
|
6648
6734
|
distributeShapes(shapes: TLShapeId[] | TLShape[], operation: 'horizontal' | 'vertical'): this {
|
|
6735
|
+
if (this.getIsReadonly()) return this
|
|
6736
|
+
|
|
6649
6737
|
const ids =
|
|
6650
6738
|
typeof shapes[0] === 'string'
|
|
6651
6739
|
? (shapes as TLShapeId[])
|
|
6652
6740
|
: (shapes as TLShape[]).map((s) => s.id)
|
|
6653
6741
|
|
|
6654
|
-
|
|
6655
|
-
|
|
6742
|
+
// always fresh shapes
|
|
6743
|
+
const shapesToDistributeFirstPass = compact(ids.map((id) => this.getShape(id)))
|
|
6656
6744
|
|
|
6657
|
-
const
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6745
|
+
const shapeClustersToDistribute: {
|
|
6746
|
+
shapes: TLShape[]
|
|
6747
|
+
pageBounds: Box
|
|
6748
|
+
}[] = []
|
|
6749
|
+
|
|
6750
|
+
const allBounds: Box[] = []
|
|
6751
|
+
const visited = new Set<TLShapeId>()
|
|
6752
|
+
|
|
6753
|
+
for (const shape of shapesToDistributeFirstPass) {
|
|
6754
|
+
if (visited.has(shape.id)) continue
|
|
6755
|
+
visited.add(shape.id)
|
|
6756
|
+
|
|
6757
|
+
const shapePageBounds = this.getShapePageBounds(shape)
|
|
6758
|
+
if (!shapePageBounds) continue
|
|
6759
|
+
|
|
6760
|
+
if (
|
|
6761
|
+
!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
|
|
6762
|
+
type: 'distribute',
|
|
6763
|
+
shapes: shapesToDistributeFirstPass,
|
|
6764
|
+
})
|
|
6765
|
+
) {
|
|
6766
|
+
continue
|
|
6767
|
+
}
|
|
6768
|
+
|
|
6769
|
+
const shapesMovingTogether = [shape]
|
|
6770
|
+
const boundsOfShapesMovingTogether: Box[] = [shapePageBounds]
|
|
6771
|
+
|
|
6772
|
+
this.collectShapesViaArrowBindings({
|
|
6773
|
+
bindings: this.getBindingsToShape(shape.id, 'arrow'),
|
|
6774
|
+
initialShapes: shapesToDistributeFirstPass,
|
|
6775
|
+
resultShapes: shapesMovingTogether,
|
|
6776
|
+
resultBounds: boundsOfShapesMovingTogether,
|
|
6777
|
+
visited,
|
|
6778
|
+
})
|
|
6779
|
+
|
|
6780
|
+
const commonPageBounds = Box.Common(boundsOfShapesMovingTogether)
|
|
6781
|
+
if (!commonPageBounds) continue
|
|
6782
|
+
|
|
6783
|
+
shapeClustersToDistribute.push({
|
|
6784
|
+
shapes: shapesMovingTogether,
|
|
6785
|
+
pageBounds: commonPageBounds,
|
|
6786
|
+
})
|
|
6787
|
+
|
|
6788
|
+
allBounds.push(commonPageBounds)
|
|
6789
|
+
}
|
|
6790
|
+
|
|
6791
|
+
if (shapeClustersToDistribute.length < 3) return this
|
|
6662
6792
|
|
|
6663
6793
|
let val: 'x' | 'y'
|
|
6664
6794
|
let min: 'minX' | 'minY'
|
|
6665
6795
|
let max: 'maxX' | 'maxY'
|
|
6666
|
-
let mid: 'midX' | 'midY'
|
|
6667
6796
|
let dim: 'width' | 'height'
|
|
6668
6797
|
|
|
6669
6798
|
if (operation === 'horizontal') {
|
|
6670
6799
|
val = 'x'
|
|
6671
6800
|
min = 'minX'
|
|
6672
6801
|
max = 'maxX'
|
|
6673
|
-
mid = 'midX'
|
|
6674
6802
|
dim = 'width'
|
|
6675
6803
|
} else {
|
|
6676
6804
|
val = 'y'
|
|
6677
6805
|
min = 'minY'
|
|
6678
6806
|
max = 'maxY'
|
|
6679
|
-
mid = 'midY'
|
|
6680
6807
|
dim = 'height'
|
|
6681
6808
|
}
|
|
6682
6809
|
const changes: TLShapePartial[] = []
|
|
6683
6810
|
|
|
6684
|
-
|
|
6685
|
-
const
|
|
6686
|
-
(a, b) => pageBounds[a.id][min] - pageBounds[b.id][min]
|
|
6687
|
-
)[0]
|
|
6688
|
-
const last = shapesToDistribute.sort((a, b) => pageBounds[b.id][max] - pageBounds[a.id][max])[0]
|
|
6811
|
+
const first = shapeClustersToDistribute.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])[0]
|
|
6812
|
+
const last = shapeClustersToDistribute.sort((a, b) => b.pageBounds[max] - a.pageBounds[max])[0]
|
|
6689
6813
|
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6814
|
+
// If the first shape group is also the last shape group, distribute without it
|
|
6815
|
+
if (first === last) {
|
|
6816
|
+
const excludedShapeIds = new Set(first.shapes.map((s) => s.id))
|
|
6817
|
+
return this.distributeShapes(
|
|
6818
|
+
ids.filter((id) => !excludedShapeIds.has(id)),
|
|
6819
|
+
operation
|
|
6820
|
+
)
|
|
6821
|
+
}
|
|
6693
6822
|
|
|
6694
|
-
|
|
6823
|
+
const shapeClustersToMove = shapeClustersToDistribute
|
|
6695
6824
|
.filter((shape) => shape !== first && shape !== last)
|
|
6696
|
-
.sort((a, b) =>
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6825
|
+
.sort((a, b) => {
|
|
6826
|
+
if (a.pageBounds[min] === b.pageBounds[min]) {
|
|
6827
|
+
return a.shapes[0].id < b.shapes[0].id ? -1 : 1
|
|
6828
|
+
}
|
|
6829
|
+
return a.pageBounds[min] - b.pageBounds[min]
|
|
6830
|
+
})
|
|
6831
|
+
|
|
6832
|
+
// The gap is the amount of space "left over" between the first and last shape. This can be a negative number if the shapes are overlapping.
|
|
6833
|
+
const maxFirst = first.pageBounds[max]
|
|
6834
|
+
const range = last.pageBounds[min] - maxFirst
|
|
6835
|
+
const summedShapeDimensions = shapeClustersToMove.reduce((acc, s) => acc + s.pageBounds[dim], 0)
|
|
6836
|
+
const gap = (range - summedShapeDimensions) / (shapeClustersToMove.length + 1)
|
|
6700
6837
|
|
|
6838
|
+
for (let v = maxFirst + gap, i = 0; i < shapeClustersToMove.length; i++) {
|
|
6839
|
+
const { shapes, pageBounds } = shapeClustersToMove[i]
|
|
6840
|
+
const delta = new Vec()
|
|
6841
|
+
delta[val] = v - pageBounds[val]
|
|
6842
|
+
|
|
6843
|
+
// If for some reason the new position would be more than the maximum, we need to adjust the delta
|
|
6844
|
+
// This will likely throw off some of the other placements but hey, it's better than changing the common bounds
|
|
6845
|
+
if (v + pageBounds[dim] > last.pageBounds[max] - 1) {
|
|
6846
|
+
delta[val] = last.pageBounds[max] - pageBounds[max] - 1
|
|
6847
|
+
}
|
|
6848
|
+
|
|
6849
|
+
for (const shape of shapes) {
|
|
6850
|
+
const shapeDelta = delta.clone()
|
|
6851
|
+
|
|
6852
|
+
// If the shape has another shape as its parent, and if the parent has a rotation, we need to rotate the counter-rotate delta
|
|
6853
|
+
// todo: ensure that the parent isn't being aligned together with its children
|
|
6701
6854
|
const parent = this.getShapeParent(shape)
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6855
|
+
if (parent) {
|
|
6856
|
+
const parentTransform = this.getShapePageTransform(parent)
|
|
6857
|
+
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
6858
|
+
}
|
|
6705
6859
|
|
|
6706
|
-
|
|
6707
|
-
|
|
6860
|
+
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
6861
|
+
changes.push(this.getChangesToTranslateShape(shape, shapeDelta))
|
|
6862
|
+
}
|
|
6863
|
+
|
|
6864
|
+
v += pageBounds[dim] + gap
|
|
6865
|
+
}
|
|
6708
6866
|
|
|
6709
6867
|
this.updateShapes(changes)
|
|
6710
6868
|
return this
|
|
@@ -6731,65 +6889,106 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6731
6889
|
: (shapes as TLShape[]).map((s) => s.id)
|
|
6732
6890
|
|
|
6733
6891
|
if (this.getIsReadonly()) return this
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
const
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
}
|
|
6892
|
+
|
|
6893
|
+
// always fresh shapes, skip anything that isn't rotated 90 deg
|
|
6894
|
+
const shapesToStretchFirstPass = compact(ids.map((id) => this.getShape(id))).filter(
|
|
6895
|
+
(s) => this.getShapePageTransform(s)?.rotation() % (PI / 2) === 0
|
|
6896
|
+
)
|
|
6897
|
+
|
|
6898
|
+
const shapeClustersToStretch: {
|
|
6899
|
+
shapes: TLShape[]
|
|
6900
|
+
pageBounds: Box
|
|
6901
|
+
}[] = []
|
|
6902
|
+
|
|
6903
|
+
const allBounds: Box[] = []
|
|
6904
|
+
const visited = new Set<TLShapeId>()
|
|
6905
|
+
|
|
6906
|
+
for (const shape of shapesToStretchFirstPass) {
|
|
6907
|
+
if (visited.has(shape.id)) continue
|
|
6908
|
+
visited.add(shape.id)
|
|
6909
|
+
|
|
6910
|
+
const shapePageBounds = this.getShapePageBounds(shape)
|
|
6911
|
+
if (!shapePageBounds) continue
|
|
6912
|
+
|
|
6913
|
+
const shapesMovingTogether = [shape]
|
|
6914
|
+
const boundsOfShapesMovingTogether: Box[] = [shapePageBounds]
|
|
6915
|
+
|
|
6916
|
+
if (
|
|
6917
|
+
!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
|
|
6918
|
+
type: 'stretch',
|
|
6919
|
+
shapes: shapesToStretchFirstPass,
|
|
6763
6920
|
})
|
|
6764
|
-
|
|
6921
|
+
) {
|
|
6922
|
+
continue
|
|
6765
6923
|
}
|
|
6766
|
-
case 'horizontal': {
|
|
6767
|
-
this.run(() => {
|
|
6768
|
-
for (const shape of shapesToStretch) {
|
|
6769
|
-
const bounds = shapeBounds[shape.id]
|
|
6770
|
-
const pageBounds = shapePageBounds[shape.id]
|
|
6771
|
-
const pageRotation = this.getShapePageTransform(shape)!.rotation()
|
|
6772
|
-
if (pageRotation % PI2) continue
|
|
6773
|
-
const localOffset = new Vec(commonBounds.minX - pageBounds.minX, 0)
|
|
6774
|
-
const parentTransform = this.getShapeParentTransform(shape)
|
|
6775
|
-
if (parentTransform) localOffset.rot(-parentTransform.rotation())
|
|
6776
|
-
|
|
6777
|
-
const { x, y } = Vec.Add(localOffset, shape)
|
|
6778
|
-
this.updateShapes([{ id: shape.id, type: shape.type, x, y }])
|
|
6779
|
-
const scale = new Vec(commonBounds.width / pageBounds.width, 1)
|
|
6780
|
-
this.resizeShape(shape.id, scale, {
|
|
6781
|
-
initialBounds: bounds,
|
|
6782
|
-
scaleOrigin: new Vec(commonBounds.minX, pageBounds.center.y),
|
|
6783
|
-
isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
|
|
6784
|
-
scaleAxisRotation: 0,
|
|
6785
|
-
})
|
|
6786
|
-
}
|
|
6787
|
-
})
|
|
6788
6924
|
|
|
6789
|
-
|
|
6790
|
-
|
|
6925
|
+
this.collectShapesViaArrowBindings({
|
|
6926
|
+
bindings: this.getBindingsToShape(shape.id, 'arrow'),
|
|
6927
|
+
initialShapes: shapesToStretchFirstPass,
|
|
6928
|
+
resultShapes: shapesMovingTogether,
|
|
6929
|
+
resultBounds: boundsOfShapesMovingTogether,
|
|
6930
|
+
visited,
|
|
6931
|
+
})
|
|
6932
|
+
|
|
6933
|
+
const commonPageBounds = Box.Common(boundsOfShapesMovingTogether)
|
|
6934
|
+
if (!commonPageBounds) continue
|
|
6935
|
+
|
|
6936
|
+
shapeClustersToStretch.push({
|
|
6937
|
+
shapes: shapesMovingTogether,
|
|
6938
|
+
pageBounds: commonPageBounds,
|
|
6939
|
+
})
|
|
6940
|
+
|
|
6941
|
+
allBounds.push(commonPageBounds)
|
|
6791
6942
|
}
|
|
6792
6943
|
|
|
6944
|
+
if (shapeClustersToStretch.length < 2) return this
|
|
6945
|
+
|
|
6946
|
+
const commonBounds = Box.Common(allBounds)
|
|
6947
|
+
let val: 'x' | 'y'
|
|
6948
|
+
let min: 'minX' | 'minY'
|
|
6949
|
+
let dim: 'width' | 'height'
|
|
6950
|
+
|
|
6951
|
+
if (operation === 'horizontal') {
|
|
6952
|
+
val = 'x'
|
|
6953
|
+
min = 'minX'
|
|
6954
|
+
dim = 'width'
|
|
6955
|
+
} else {
|
|
6956
|
+
val = 'y'
|
|
6957
|
+
min = 'minY'
|
|
6958
|
+
dim = 'height'
|
|
6959
|
+
}
|
|
6960
|
+
|
|
6961
|
+
this.run(() => {
|
|
6962
|
+
shapeClustersToStretch.forEach(({ shapes, pageBounds }) => {
|
|
6963
|
+
const localOffset = new Vec()
|
|
6964
|
+
localOffset[val] = commonBounds[min] - pageBounds[min]
|
|
6965
|
+
|
|
6966
|
+
const scaleOrigin = pageBounds.center.clone()
|
|
6967
|
+
scaleOrigin[val] = commonBounds[min]
|
|
6968
|
+
|
|
6969
|
+
const scale = new Vec(1, 1)
|
|
6970
|
+
scale[val] = commonBounds[dim] / pageBounds[dim]
|
|
6971
|
+
|
|
6972
|
+
for (const shape of shapes) {
|
|
6973
|
+
// First translate
|
|
6974
|
+
const shapeLocalOffset = localOffset.clone()
|
|
6975
|
+
const parentTransform = this.getShapeParentTransform(shape)
|
|
6976
|
+
if (parentTransform) localOffset.rot(-parentTransform.rotation())
|
|
6977
|
+
shapeLocalOffset.add(shape)
|
|
6978
|
+
const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset)
|
|
6979
|
+
this.updateShape(changes)
|
|
6980
|
+
|
|
6981
|
+
// Then resize
|
|
6982
|
+
this.resizeShape(shape.id, scale, {
|
|
6983
|
+
initialBounds: this.getShapeGeometry(shape).bounds,
|
|
6984
|
+
scaleOrigin,
|
|
6985
|
+
isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
|
|
6986
|
+
scaleAxisRotation: 0,
|
|
6987
|
+
})
|
|
6988
|
+
}
|
|
6989
|
+
})
|
|
6990
|
+
})
|
|
6991
|
+
|
|
6793
6992
|
return this
|
|
6794
6993
|
}
|
|
6795
6994
|
|
|
@@ -7091,7 +7290,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7091
7290
|
* @example
|
|
7092
7291
|
* ```ts
|
|
7093
7292
|
* editor.createShape(myShape)
|
|
7094
|
-
* editor.createShape({ id: 'box1', type: 'text', props: {
|
|
7293
|
+
* editor.createShape({ id: 'box1', type: 'text', props: { text: "ok" } })
|
|
7095
7294
|
* ```
|
|
7096
7295
|
*
|
|
7097
7296
|
* @param shape - The shape (or shape partial) to create.
|
|
@@ -7109,7 +7308,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7109
7308
|
* @example
|
|
7110
7309
|
* ```ts
|
|
7111
7310
|
* editor.createShapes([myShape])
|
|
7112
|
-
* editor.createShapes([{ id: 'box1', type: 'text', props: {
|
|
7311
|
+
* editor.createShapes([{ id: 'box1', type: 'text', props: { text: "ok" } }])
|
|
7113
7312
|
* ```
|
|
7114
7313
|
*
|
|
7115
7314
|
* @param shapes - The shapes (or shape partials) to create.
|
|
@@ -10057,7 +10256,7 @@ function withIsolatedShapes<T>(
|
|
|
10057
10256
|
}
|
|
10058
10257
|
})
|
|
10059
10258
|
|
|
10060
|
-
editor.store.applyDiff(reverseRecordsDiff(changes))
|
|
10259
|
+
editor.store.applyDiff(reverseRecordsDiff(changes), { runCallbacks: false })
|
|
10061
10260
|
},
|
|
10062
10261
|
{ history: 'ignore' }
|
|
10063
10262
|
)
|