@tldraw/tlschema 4.2.1 → 4.2.2
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/bindings/TLBaseBinding.js.map +2 -2
- package/dist-cjs/createTLSchema.js.map +2 -2
- package/dist-cjs/index.d.ts +242 -71
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/misc/TLOpacity.js +1 -5
- package/dist-cjs/misc/TLOpacity.js.map +2 -2
- package/dist-cjs/misc/TLRichText.js +5 -1
- package/dist-cjs/misc/TLRichText.js.map +2 -2
- package/dist-cjs/misc/b64Vecs.js +224 -0
- package/dist-cjs/misc/b64Vecs.js.map +7 -0
- package/dist-cjs/records/TLAsset.js.map +1 -1
- package/dist-cjs/records/TLBinding.js.map +2 -2
- package/dist-cjs/records/TLShape.js.map +2 -2
- package/dist-cjs/shapes/ShapeWithCrop.js.map +1 -1
- package/dist-cjs/shapes/TLArrowShape.js +26 -13
- package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
- package/dist-cjs/shapes/TLBaseShape.js.map +2 -2
- package/dist-cjs/shapes/TLDrawShape.js +37 -4
- package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
- package/dist-cjs/shapes/TLEmbedShape.js +17 -0
- package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
- package/dist-cjs/shapes/TLGeoShape.js +12 -1
- package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
- package/dist-cjs/shapes/TLHighlightShape.js +29 -2
- package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
- package/dist-cjs/shapes/TLNoteShape.js +12 -1
- package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
- package/dist-cjs/shapes/TLTextShape.js +12 -1
- package/dist-cjs/shapes/TLTextShape.js.map +2 -2
- package/dist-cjs/store-migrations.js +15 -15
- package/dist-cjs/store-migrations.js.map +2 -2
- package/dist-esm/bindings/TLBaseBinding.mjs.map +2 -2
- package/dist-esm/createTLSchema.mjs.map +2 -2
- package/dist-esm/index.d.mts +242 -71
- package/dist-esm/index.mjs +5 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/misc/TLOpacity.mjs +1 -5
- package/dist-esm/misc/TLOpacity.mjs.map +2 -2
- package/dist-esm/misc/TLRichText.mjs +5 -1
- package/dist-esm/misc/TLRichText.mjs.map +2 -2
- package/dist-esm/misc/b64Vecs.mjs +204 -0
- package/dist-esm/misc/b64Vecs.mjs.map +7 -0
- package/dist-esm/records/TLAsset.mjs.map +1 -1
- package/dist-esm/records/TLBinding.mjs.map +2 -2
- package/dist-esm/records/TLShape.mjs.map +2 -2
- package/dist-esm/shapes/TLArrowShape.mjs +26 -13
- package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
- package/dist-esm/shapes/TLBaseShape.mjs.map +2 -2
- package/dist-esm/shapes/TLDrawShape.mjs +37 -4
- package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
- package/dist-esm/shapes/TLEmbedShape.mjs +17 -0
- package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGeoShape.mjs +12 -1
- package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
- package/dist-esm/shapes/TLHighlightShape.mjs +29 -2
- package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
- package/dist-esm/shapes/TLNoteShape.mjs +12 -1
- package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
- package/dist-esm/shapes/TLTextShape.mjs +12 -1
- package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
- package/dist-esm/store-migrations.mjs +15 -15
- package/dist-esm/store-migrations.mjs.map +2 -2
- package/package.json +8 -8
- package/src/__tests__/migrationTestUtils.ts +9 -3
- package/src/bindings/TLBaseBinding.ts +25 -14
- package/src/createTLSchema.ts +8 -2
- package/src/index.ts +9 -0
- package/src/migrations.test.ts +149 -1
- package/src/misc/TLOpacity.ts +1 -5
- package/src/misc/TLRichText.ts +6 -1
- package/src/misc/b64Vecs.ts +308 -0
- package/src/records/TLAsset.ts +2 -2
- package/src/records/TLBinding.ts +65 -23
- package/src/records/TLShape.ts +100 -5
- package/src/shapes/ShapeWithCrop.ts +2 -2
- package/src/shapes/TLArrowShape.ts +28 -14
- package/src/shapes/TLBaseShape.ts +34 -10
- package/src/shapes/TLDrawShape.ts +59 -12
- package/src/shapes/TLEmbedShape.ts +17 -0
- package/src/shapes/TLGeoShape.ts +14 -1
- package/src/shapes/TLHighlightShape.ts +37 -0
- package/src/shapes/TLNoteShape.ts +15 -1
- package/src/shapes/TLTextShape.ts +16 -2
- package/src/store-migrations.ts +17 -16
- package/src/assets/TLBookmarkAsset.test.ts +0 -96
- package/src/assets/TLImageAsset.test.ts +0 -213
- package/src/assets/TLVideoAsset.test.ts +0 -105
- package/src/bindings/TLArrowBinding.test.ts +0 -55
- package/src/misc/id-validator.test.ts +0 -50
- package/src/records/TLAsset.test.ts +0 -234
- package/src/records/TLBinding.test.ts +0 -22
- package/src/records/TLCamera.test.ts +0 -19
- package/src/records/TLDocument.test.ts +0 -35
- package/src/records/TLInstance.test.ts +0 -201
- package/src/records/TLPage.test.ts +0 -110
- package/src/records/TLPageState.test.ts +0 -228
- package/src/records/TLPointer.test.ts +0 -63
- package/src/records/TLPresence.test.ts +0 -190
- package/src/records/TLRecord.test.ts +0 -70
- package/src/records/TLShape.test.ts +0 -232
- package/src/shapes/ShapeWithCrop.test.ts +0 -18
- package/src/shapes/TLArrowShape.test.ts +0 -505
- package/src/shapes/TLBaseShape.test.ts +0 -142
- package/src/shapes/TLBookmarkShape.test.ts +0 -122
- package/src/shapes/TLDrawShape.test.ts +0 -177
- package/src/shapes/TLEmbedShape.test.ts +0 -286
- package/src/shapes/TLFrameShape.test.ts +0 -71
- package/src/shapes/TLGeoShape.test.ts +0 -247
- package/src/shapes/TLGroupShape.test.ts +0 -59
- package/src/shapes/TLHighlightShape.test.ts +0 -325
- package/src/shapes/TLImageShape.test.ts +0 -534
- package/src/shapes/TLLineShape.test.ts +0 -269
- package/src/shapes/TLNoteShape.test.ts +0 -1568
- package/src/shapes/TLTextShape.test.ts +0 -407
- package/src/shapes/TLVideoShape.test.ts +0 -112
- package/src/styles/TLColorStyle.test.ts +0 -439
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createMigrationSequence } from '@tldraw/store'
|
|
2
|
+
import { structuredClone } from '@tldraw/utils'
|
|
2
3
|
import { T } from '@tldraw/validate'
|
|
3
4
|
import { TLRichText, richTextValidator, toRichText } from '../misc/TLRichText'
|
|
4
5
|
import { VecModel, vecModelValidator } from '../misc/geometry-types'
|
|
5
6
|
import { createBindingId } from '../records/TLBinding'
|
|
6
|
-
import { TLShapeId, createShapePropsMigrationIds } from '../records/TLShape'
|
|
7
|
+
import { TLShape, TLShapeId, createShapePropsMigrationIds } from '../records/TLShape'
|
|
7
8
|
import { RecordProps, TLPropsMigration, createPropsMigration } from '../recordsWithProps'
|
|
8
9
|
import { StyleProp } from '../styles/StyleProp'
|
|
9
10
|
import {
|
|
@@ -276,6 +277,7 @@ export const arrowShapeVersions = createShapePropsMigrationIds('arrow', {
|
|
|
276
277
|
AddScale: 5,
|
|
277
278
|
AddElbow: 6,
|
|
278
279
|
AddRichText: 7,
|
|
280
|
+
AddRichTextAttrs: 8,
|
|
279
281
|
})
|
|
280
282
|
|
|
281
283
|
function propsMigration(migration: TLPropsMigration) {
|
|
@@ -341,8 +343,8 @@ export const arrowShapeMigrations = createMigrationSequence({
|
|
|
341
343
|
|
|
342
344
|
{
|
|
343
345
|
id: arrowShapeVersions.ExtractBindings,
|
|
344
|
-
scope: '
|
|
345
|
-
up: (
|
|
346
|
+
scope: 'storage',
|
|
347
|
+
up: (storage) => {
|
|
346
348
|
type OldArrowTerminal =
|
|
347
349
|
| {
|
|
348
350
|
type: 'point'
|
|
@@ -361,11 +363,10 @@ export const arrowShapeMigrations = createMigrationSequence({
|
|
|
361
363
|
|
|
362
364
|
type OldArrow = TLBaseShape<'arrow', { start: OldArrowTerminal; end: OldArrowTerminal }>
|
|
363
365
|
|
|
364
|
-
const
|
|
365
|
-
(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
for (const arrow of arrows) {
|
|
366
|
+
for (const record of storage.values()) {
|
|
367
|
+
if (record.typeName !== 'shape' || (record as TLShape).type !== 'arrow') continue
|
|
368
|
+
const arrow = record as OldArrow
|
|
369
|
+
const newArrow = structuredClone(arrow)
|
|
369
370
|
const { start, end } = arrow.props
|
|
370
371
|
if (start.type === 'binding') {
|
|
371
372
|
const id = createBindingId()
|
|
@@ -384,10 +385,10 @@ export const arrowShapeMigrations = createMigrationSequence({
|
|
|
384
385
|
},
|
|
385
386
|
}
|
|
386
387
|
|
|
387
|
-
|
|
388
|
-
|
|
388
|
+
storage.set(id, binding as any)
|
|
389
|
+
newArrow.props.start = { x: 0, y: 0 }
|
|
389
390
|
} else {
|
|
390
|
-
delete
|
|
391
|
+
delete newArrow.props.start.type
|
|
391
392
|
}
|
|
392
393
|
if (end.type === 'binding') {
|
|
393
394
|
const id = createBindingId()
|
|
@@ -406,11 +407,12 @@ export const arrowShapeMigrations = createMigrationSequence({
|
|
|
406
407
|
},
|
|
407
408
|
}
|
|
408
409
|
|
|
409
|
-
|
|
410
|
-
|
|
410
|
+
storage.set(id, binding as any)
|
|
411
|
+
newArrow.props.end = { x: 0, y: 0 }
|
|
411
412
|
} else {
|
|
412
|
-
delete
|
|
413
|
+
delete newArrow.props.end.type
|
|
413
414
|
}
|
|
415
|
+
storage.set(arrow.id, newArrow)
|
|
414
416
|
}
|
|
415
417
|
},
|
|
416
418
|
},
|
|
@@ -445,5 +447,17 @@ export const arrowShapeMigrations = createMigrationSequence({
|
|
|
445
447
|
// delete props.richText
|
|
446
448
|
// },
|
|
447
449
|
}),
|
|
450
|
+
propsMigration({
|
|
451
|
+
id: arrowShapeVersions.AddRichTextAttrs,
|
|
452
|
+
up: (_props) => {
|
|
453
|
+
// noop - attrs is optional so old records are valid
|
|
454
|
+
},
|
|
455
|
+
down: (props) => {
|
|
456
|
+
// Remove attrs from richText when migrating down
|
|
457
|
+
if (props.richText && 'attrs' in props.richText) {
|
|
458
|
+
delete props.richText.attrs
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
}),
|
|
448
462
|
],
|
|
449
463
|
})
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { BaseRecord } from '@tldraw/store'
|
|
2
1
|
import { IndexKey, JsonObject } from '@tldraw/utils'
|
|
3
2
|
import { T } from '@tldraw/validate'
|
|
4
3
|
import { TLOpacityType, opacityValidator } from '../misc/TLOpacity'
|
|
@@ -9,18 +8,37 @@ import { TLParentId, TLShapeId } from '../records/TLShape'
|
|
|
9
8
|
* Base interface for all shapes in tldraw.
|
|
10
9
|
*
|
|
11
10
|
* This interface defines the common properties that all shapes share, regardless of their
|
|
12
|
-
* specific type. Every shape extends this base with additional type-specific properties.
|
|
11
|
+
* specific type. Every default shape extends this base with additional type-specific properties.
|
|
12
|
+
*
|
|
13
|
+
* Custom shapes should be defined by augmenting the TLGlobalShapePropsMap type and getting the shape type from the TLShape type.
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* ```ts
|
|
16
|
-
* // Define a
|
|
17
|
-
* interface
|
|
17
|
+
* // Define a default shape type
|
|
18
|
+
* interface TLArrowShape extends TLBaseShape<'arrow', {
|
|
19
|
+
* kind: TLArrowShapeKind
|
|
20
|
+
* labelColor: TLDefaultColorStyle
|
|
21
|
+
* color: TLDefaultColorStyle
|
|
22
|
+
* fill: TLDefaultFillStyle
|
|
23
|
+
* dash: TLDefaultDashStyle
|
|
24
|
+
* size: TLDefaultSizeStyle
|
|
25
|
+
* arrowheadStart: TLArrowShapeArrowheadStyle
|
|
26
|
+
* arrowheadEnd: TLArrowShapeArrowheadStyle
|
|
27
|
+
* font: TLDefaultFontStyle
|
|
28
|
+
* start: VecModel
|
|
29
|
+
* end: VecModel
|
|
30
|
+
* bend: number
|
|
31
|
+
* richText: TLRichText
|
|
32
|
+
* labelPosition: number
|
|
33
|
+
* scale: number
|
|
34
|
+
* elbowMidPoint: number
|
|
35
|
+
* }> {}
|
|
18
36
|
*
|
|
19
37
|
* // Create a shape instance
|
|
20
|
-
* const
|
|
38
|
+
* const arrowShape: TLArrowShape = {
|
|
21
39
|
* id: 'shape:abc123',
|
|
22
40
|
* typeName: 'shape',
|
|
23
|
-
* type: '
|
|
41
|
+
* type: 'arrow',
|
|
24
42
|
* x: 100,
|
|
25
43
|
* y: 200,
|
|
26
44
|
* rotation: 0,
|
|
@@ -29,8 +47,10 @@ import { TLParentId, TLShapeId } from '../records/TLShape'
|
|
|
29
47
|
* isLocked: false,
|
|
30
48
|
* opacity: 1,
|
|
31
49
|
* props: {
|
|
32
|
-
*
|
|
33
|
-
*
|
|
50
|
+
* kind: 'arc',
|
|
51
|
+
* start: { x: 0, y: 0 },
|
|
52
|
+
* end: { x: 100, y: 100 },
|
|
53
|
+
* // ... other props
|
|
34
54
|
* },
|
|
35
55
|
* meta: {}
|
|
36
56
|
* }
|
|
@@ -38,8 +58,12 @@ import { TLParentId, TLShapeId } from '../records/TLShape'
|
|
|
38
58
|
*
|
|
39
59
|
* @public
|
|
40
60
|
*/
|
|
41
|
-
export interface TLBaseShape<Type extends string, Props extends object>
|
|
42
|
-
extends BaseRecord<'shape', TLShapeId
|
|
61
|
+
export interface TLBaseShape<Type extends string, Props extends object> {
|
|
62
|
+
// using real `extends BaseRecord<'shape', TLShapeId>` introduces a circularity in the types
|
|
63
|
+
// and for that reason those "base members" have to be declared manually here
|
|
64
|
+
readonly id: TLShapeId
|
|
65
|
+
readonly typeName: 'shape'
|
|
66
|
+
|
|
43
67
|
type: Type
|
|
44
68
|
x: number
|
|
45
69
|
y: number
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { T } from '@tldraw/validate'
|
|
2
|
-
import {
|
|
2
|
+
import { b64Vecs } from '../misc/b64Vecs'
|
|
3
|
+
import { VecModel } from '../misc/geometry-types'
|
|
3
4
|
import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from '../records/TLShape'
|
|
4
5
|
import { RecordProps } from '../recordsWithProps'
|
|
5
6
|
import { DefaultColorStyle, TLDefaultColorStyle } from '../styles/TLColorStyle'
|
|
@@ -16,26 +17,18 @@ import { TLBaseShape } from './TLBaseShape'
|
|
|
16
17
|
export interface TLDrawShapeSegment {
|
|
17
18
|
/** Type of drawing segment - 'free' for freehand curves, 'straight' for line segments */
|
|
18
19
|
type: 'free' | 'straight'
|
|
19
|
-
/**
|
|
20
|
-
points:
|
|
20
|
+
/** Base64-encoded points (x, y, z triplets stored as Float16) */
|
|
21
|
+
points: string
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Validator for draw shape segments ensuring proper structure and data types.
|
|
25
26
|
*
|
|
26
27
|
* @public
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* const segment: TLDrawShapeSegment = {
|
|
30
|
-
* type: 'free',
|
|
31
|
-
* points: [{ x: 0, y: 0, z: 0.5 }, { x: 10, y: 10, z: 0.7 }]
|
|
32
|
-
* }
|
|
33
|
-
* const isValid = DrawShapeSegment.isValid(segment)
|
|
34
|
-
* ```
|
|
35
28
|
*/
|
|
36
29
|
export const DrawShapeSegment: T.ObjectValidator<TLDrawShapeSegment> = T.object({
|
|
37
30
|
type: T.literalEnum('free', 'straight'),
|
|
38
|
-
points: T.
|
|
31
|
+
points: T.string,
|
|
39
32
|
})
|
|
40
33
|
|
|
41
34
|
/**
|
|
@@ -62,6 +55,10 @@ export interface TLDrawShapeProps {
|
|
|
62
55
|
isPen: boolean
|
|
63
56
|
/** Scale factor applied to the drawing */
|
|
64
57
|
scale: number
|
|
58
|
+
/** Horizontal scale factor for lazy resize */
|
|
59
|
+
scaleX: number
|
|
60
|
+
/** Vertical scale factor for lazy resize */
|
|
61
|
+
scaleY: number
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
/**
|
|
@@ -118,6 +115,7 @@ export type TLDrawShape = TLBaseShape<'draw', TLDrawShapeProps>
|
|
|
118
115
|
* const isValid = drawShapeProps.color.isValid(props.color)
|
|
119
116
|
* ```
|
|
120
117
|
*/
|
|
118
|
+
/** @public */
|
|
121
119
|
export const drawShapeProps: RecordProps<TLDrawShape> = {
|
|
122
120
|
color: DefaultColorStyle,
|
|
123
121
|
fill: DefaultFillStyle,
|
|
@@ -128,11 +126,14 @@ export const drawShapeProps: RecordProps<TLDrawShape> = {
|
|
|
128
126
|
isClosed: T.boolean,
|
|
129
127
|
isPen: T.boolean,
|
|
130
128
|
scale: T.nonZeroNumber,
|
|
129
|
+
scaleX: T.nonZeroFiniteNumber,
|
|
130
|
+
scaleY: T.nonZeroFiniteNumber,
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
const Versions = createShapePropsMigrationIds('draw', {
|
|
134
134
|
AddInPen: 1,
|
|
135
135
|
AddScale: 2,
|
|
136
|
+
Base64: 3,
|
|
136
137
|
})
|
|
137
138
|
|
|
138
139
|
/**
|
|
@@ -184,5 +185,51 @@ export const drawShapeMigrations = createShapePropsMigrationSequence({
|
|
|
184
185
|
delete props.scale
|
|
185
186
|
},
|
|
186
187
|
},
|
|
188
|
+
{
|
|
189
|
+
id: Versions.Base64,
|
|
190
|
+
up: (props) => {
|
|
191
|
+
props.segments = props.segments.map((segment: any) => {
|
|
192
|
+
return {
|
|
193
|
+
...segment,
|
|
194
|
+
// Only encode if points is an array (not already base64 string)
|
|
195
|
+
points:
|
|
196
|
+
typeof segment.points === 'string'
|
|
197
|
+
? segment.points
|
|
198
|
+
: b64Vecs.encodePoints(segment.points),
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
props.scaleX = props.scaleX ?? 1
|
|
202
|
+
props.scaleY = props.scaleY ?? 1
|
|
203
|
+
},
|
|
204
|
+
down: (props) => {
|
|
205
|
+
props.segments = props.segments.map((segment: any) => ({
|
|
206
|
+
...segment,
|
|
207
|
+
// Only decode if points is a string (not already VecModel[])
|
|
208
|
+
points: Array.isArray(segment.points)
|
|
209
|
+
? segment.points
|
|
210
|
+
: b64Vecs.decodePoints(segment.points),
|
|
211
|
+
}))
|
|
212
|
+
delete props.scaleX
|
|
213
|
+
delete props.scaleY
|
|
214
|
+
},
|
|
215
|
+
},
|
|
187
216
|
],
|
|
188
217
|
})
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Compress legacy draw shape segments by converting VecModel[] points to base64 format.
|
|
221
|
+
* This function is useful for converting old draw shape data to the new compressed format.
|
|
222
|
+
*
|
|
223
|
+
* @public
|
|
224
|
+
*/
|
|
225
|
+
export function compressLegacySegments(
|
|
226
|
+
segments: {
|
|
227
|
+
type: 'free' | 'straight'
|
|
228
|
+
points: VecModel[]
|
|
229
|
+
}[]
|
|
230
|
+
): TLDrawShapeSegment[] {
|
|
231
|
+
return segments.map((segment) => ({
|
|
232
|
+
...segment,
|
|
233
|
+
points: b64Vecs.encodePoints(segment.points),
|
|
234
|
+
}))
|
|
235
|
+
}
|
|
@@ -10,6 +10,7 @@ const TLDRAW_APP_RE = /(^\/r\/[^/]+\/?$)/
|
|
|
10
10
|
const EMBED_DEFINITIONS = [
|
|
11
11
|
{
|
|
12
12
|
hostnames: ['beta.tldraw.com', 'tldraw.com', 'localhost:3000'],
|
|
13
|
+
canEditWhileLocked: true,
|
|
13
14
|
fromEmbedUrl: (url: string) => {
|
|
14
15
|
const urlObj = safeParseUrl(url)
|
|
15
16
|
if (urlObj && urlObj.pathname.match(TLDRAW_APP_RE)) {
|
|
@@ -20,6 +21,7 @@ const EMBED_DEFINITIONS = [
|
|
|
20
21
|
},
|
|
21
22
|
{
|
|
22
23
|
hostnames: ['figma.com'],
|
|
24
|
+
canEditWhileLocked: true,
|
|
23
25
|
fromEmbedUrl: (url: string) => {
|
|
24
26
|
const urlObj = safeParseUrl(url)
|
|
25
27
|
if (urlObj && urlObj.pathname.match(/^\/embed\/?$/)) {
|
|
@@ -33,6 +35,7 @@ const EMBED_DEFINITIONS = [
|
|
|
33
35
|
},
|
|
34
36
|
{
|
|
35
37
|
hostnames: ['google.*'],
|
|
38
|
+
canEditWhileLocked: true,
|
|
36
39
|
fromEmbedUrl: (url: string) => {
|
|
37
40
|
const urlObj = safeParseUrl(url)
|
|
38
41
|
if (!urlObj) return
|
|
@@ -48,6 +51,7 @@ const EMBED_DEFINITIONS = [
|
|
|
48
51
|
},
|
|
49
52
|
{
|
|
50
53
|
hostnames: ['val.town'],
|
|
54
|
+
canEditWhileLocked: true,
|
|
51
55
|
fromEmbedUrl: (url: string) => {
|
|
52
56
|
const urlObj = safeParseUrl(url)
|
|
53
57
|
// e.g. extract "steveruizok/mathFact" from https://www.val.town/v/steveruizok/mathFact
|
|
@@ -60,6 +64,7 @@ const EMBED_DEFINITIONS = [
|
|
|
60
64
|
},
|
|
61
65
|
{
|
|
62
66
|
hostnames: ['codesandbox.io'],
|
|
67
|
+
canEditWhileLocked: true,
|
|
63
68
|
fromEmbedUrl: (url: string) => {
|
|
64
69
|
const urlObj = safeParseUrl(url)
|
|
65
70
|
const matches = urlObj && urlObj.pathname.match(/\/embed\/([^/]+)\/?/)
|
|
@@ -71,6 +76,7 @@ const EMBED_DEFINITIONS = [
|
|
|
71
76
|
},
|
|
72
77
|
{
|
|
73
78
|
hostnames: ['codepen.io'],
|
|
79
|
+
canEditWhileLocked: true,
|
|
74
80
|
fromEmbedUrl: (url: string) => {
|
|
75
81
|
const CODEPEN_EMBED_REGEXP = /https:\/\/codepen.io\/([^/]+)\/embed\/([^/]+)/
|
|
76
82
|
const matches = url.match(CODEPEN_EMBED_REGEXP)
|
|
@@ -83,6 +89,7 @@ const EMBED_DEFINITIONS = [
|
|
|
83
89
|
},
|
|
84
90
|
{
|
|
85
91
|
hostnames: ['scratch.mit.edu'],
|
|
92
|
+
canEditWhileLocked: true,
|
|
86
93
|
fromEmbedUrl: (url: string) => {
|
|
87
94
|
const SCRATCH_EMBED_REGEXP = /https:\/\/scratch.mit.edu\/projects\/embed\/([^/]+)/
|
|
88
95
|
const matches = url.match(SCRATCH_EMBED_REGEXP)
|
|
@@ -95,6 +102,7 @@ const EMBED_DEFINITIONS = [
|
|
|
95
102
|
},
|
|
96
103
|
{
|
|
97
104
|
hostnames: ['*.youtube.com', 'youtube.com', 'youtu.be'],
|
|
105
|
+
canEditWhileLocked: true,
|
|
98
106
|
fromEmbedUrl: (url: string) => {
|
|
99
107
|
const urlObj = safeParseUrl(url)
|
|
100
108
|
if (!urlObj) return
|
|
@@ -111,6 +119,7 @@ const EMBED_DEFINITIONS = [
|
|
|
111
119
|
},
|
|
112
120
|
{
|
|
113
121
|
hostnames: ['calendar.google.*'],
|
|
122
|
+
canEditWhileLocked: true,
|
|
114
123
|
fromEmbedUrl: (url: string) => {
|
|
115
124
|
const urlObj = safeParseUrl(url)
|
|
116
125
|
const srcQs = urlObj?.searchParams.get('src')
|
|
@@ -129,6 +138,7 @@ const EMBED_DEFINITIONS = [
|
|
|
129
138
|
},
|
|
130
139
|
{
|
|
131
140
|
hostnames: ['docs.google.*'],
|
|
141
|
+
canEditWhileLocked: true,
|
|
132
142
|
fromEmbedUrl: (url: string) => {
|
|
133
143
|
const urlObj = safeParseUrl(url)
|
|
134
144
|
|
|
@@ -145,6 +155,7 @@ const EMBED_DEFINITIONS = [
|
|
|
145
155
|
},
|
|
146
156
|
{
|
|
147
157
|
hostnames: ['gist.github.com'],
|
|
158
|
+
canEditWhileLocked: true,
|
|
148
159
|
fromEmbedUrl: (url: string) => {
|
|
149
160
|
const urlObj = safeParseUrl(url)
|
|
150
161
|
if (urlObj && urlObj.pathname.match(/\/([^/]+)\/([^/]+)/)) {
|
|
@@ -156,6 +167,7 @@ const EMBED_DEFINITIONS = [
|
|
|
156
167
|
},
|
|
157
168
|
{
|
|
158
169
|
hostnames: ['replit.com'],
|
|
170
|
+
canEditWhileLocked: true,
|
|
159
171
|
fromEmbedUrl: (url: string) => {
|
|
160
172
|
const urlObj = safeParseUrl(url)
|
|
161
173
|
if (
|
|
@@ -171,6 +183,7 @@ const EMBED_DEFINITIONS = [
|
|
|
171
183
|
},
|
|
172
184
|
{
|
|
173
185
|
hostnames: ['felt.com'],
|
|
186
|
+
canEditWhileLocked: true,
|
|
174
187
|
fromEmbedUrl: (url: string) => {
|
|
175
188
|
const urlObj = safeParseUrl(url)
|
|
176
189
|
if (urlObj && urlObj.pathname.match(/^\/embed\/map\//)) {
|
|
@@ -182,6 +195,7 @@ const EMBED_DEFINITIONS = [
|
|
|
182
195
|
},
|
|
183
196
|
{
|
|
184
197
|
hostnames: ['open.spotify.com'],
|
|
198
|
+
canEditWhileLocked: true,
|
|
185
199
|
fromEmbedUrl: (url: string) => {
|
|
186
200
|
const urlObj = safeParseUrl(url)
|
|
187
201
|
if (urlObj && urlObj.pathname.match(/^\/embed\/(artist|album)\//)) {
|
|
@@ -192,6 +206,7 @@ const EMBED_DEFINITIONS = [
|
|
|
192
206
|
},
|
|
193
207
|
{
|
|
194
208
|
hostnames: ['vimeo.com', 'player.vimeo.com'],
|
|
209
|
+
canEditWhileLocked: true,
|
|
195
210
|
fromEmbedUrl: (url: string) => {
|
|
196
211
|
const urlObj = safeParseUrl(url)
|
|
197
212
|
if (urlObj && urlObj.hostname === 'player.vimeo.com') {
|
|
@@ -205,6 +220,7 @@ const EMBED_DEFINITIONS = [
|
|
|
205
220
|
},
|
|
206
221
|
{
|
|
207
222
|
hostnames: ['observablehq.com'],
|
|
223
|
+
canEditWhileLocked: true,
|
|
208
224
|
fromEmbedUrl: (url: string) => {
|
|
209
225
|
const urlObj = safeParseUrl(url)
|
|
210
226
|
if (urlObj && urlObj.pathname.match(/^\/embed\/@([^/]+)\/([^/]+)\/?$/)) {
|
|
@@ -219,6 +235,7 @@ const EMBED_DEFINITIONS = [
|
|
|
219
235
|
},
|
|
220
236
|
{
|
|
221
237
|
hostnames: ['desmos.com'],
|
|
238
|
+
canEditWhileLocked: true,
|
|
222
239
|
fromEmbedUrl: (url: string) => {
|
|
223
240
|
const urlObj = safeParseUrl(url)
|
|
224
241
|
if (
|
package/src/shapes/TLGeoShape.ts
CHANGED
|
@@ -193,6 +193,7 @@ const geoShapeVersions = createShapePropsMigrationIds('geo', {
|
|
|
193
193
|
MakeUrlsValid: 8,
|
|
194
194
|
AddScale: 9,
|
|
195
195
|
AddRichText: 10,
|
|
196
|
+
AddRichTextAttrs: 11,
|
|
196
197
|
})
|
|
197
198
|
|
|
198
199
|
/**
|
|
@@ -205,7 +206,7 @@ export { geoShapeVersions as geoShapeVersions }
|
|
|
205
206
|
/**
|
|
206
207
|
* Migration sequence for geo shape properties across different schema versions.
|
|
207
208
|
* Handles evolution of geo shapes including URL support, label colors, alignment changes,
|
|
208
|
-
*
|
|
209
|
+
* the transition from plain text to rich text, and support for attrs property on richText.
|
|
209
210
|
*
|
|
210
211
|
* @public
|
|
211
212
|
*/
|
|
@@ -305,5 +306,17 @@ export const geoShapeMigrations = createShapePropsMigrationSequence({
|
|
|
305
306
|
// delete props.richText
|
|
306
307
|
// },
|
|
307
308
|
},
|
|
309
|
+
{
|
|
310
|
+
id: geoShapeVersions.AddRichTextAttrs,
|
|
311
|
+
up: (_props) => {
|
|
312
|
+
// noop - attrs is optional so old records are valid
|
|
313
|
+
},
|
|
314
|
+
down: (props) => {
|
|
315
|
+
// Remove attrs from richText when migrating down
|
|
316
|
+
if (props.richText && 'attrs' in props.richText) {
|
|
317
|
+
delete props.richText.attrs
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
},
|
|
308
321
|
],
|
|
309
322
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { T } from '@tldraw/validate'
|
|
2
|
+
import { b64Vecs } from '../misc/b64Vecs'
|
|
2
3
|
import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from '../records/TLShape'
|
|
3
4
|
import { RecordProps } from '../recordsWithProps'
|
|
4
5
|
import { DefaultColorStyle, TLDefaultColorStyle } from '../styles/TLColorStyle'
|
|
@@ -36,6 +37,10 @@ export interface TLHighlightShapeProps {
|
|
|
36
37
|
isPen: boolean
|
|
37
38
|
/** Scale factor applied to the highlight shape for display */
|
|
38
39
|
scale: number
|
|
40
|
+
/** Horizontal scale factor for lazy resize */
|
|
41
|
+
scaleX: number
|
|
42
|
+
/** Vertical scale factor for lazy resize */
|
|
43
|
+
scaleY: number
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
/**
|
|
@@ -84,6 +89,7 @@ export type TLHighlightShape = TLBaseShape<'highlight', TLHighlightShapeProps>
|
|
|
84
89
|
* const validatedProps = validator.validate(someHighlightProps)
|
|
85
90
|
* ```
|
|
86
91
|
*/
|
|
92
|
+
/** @public */
|
|
87
93
|
export const highlightShapeProps: RecordProps<TLHighlightShape> = {
|
|
88
94
|
color: DefaultColorStyle,
|
|
89
95
|
size: DefaultSizeStyle,
|
|
@@ -91,10 +97,13 @@ export const highlightShapeProps: RecordProps<TLHighlightShape> = {
|
|
|
91
97
|
isComplete: T.boolean,
|
|
92
98
|
isPen: T.boolean,
|
|
93
99
|
scale: T.nonZeroNumber,
|
|
100
|
+
scaleX: T.nonZeroFiniteNumber,
|
|
101
|
+
scaleY: T.nonZeroFiniteNumber,
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
const Versions = createShapePropsMigrationIds('highlight', {
|
|
97
105
|
AddScale: 1,
|
|
106
|
+
Base64: 2,
|
|
98
107
|
})
|
|
99
108
|
|
|
100
109
|
/**
|
|
@@ -122,5 +131,33 @@ export const highlightShapeMigrations = createShapePropsMigrationSequence({
|
|
|
122
131
|
delete props.scale
|
|
123
132
|
},
|
|
124
133
|
},
|
|
134
|
+
{
|
|
135
|
+
id: Versions.Base64,
|
|
136
|
+
up: (props) => {
|
|
137
|
+
props.segments = props.segments.map((segment: any) => {
|
|
138
|
+
return {
|
|
139
|
+
...segment,
|
|
140
|
+
// Only encode if points is an array (not already base64 string)
|
|
141
|
+
points:
|
|
142
|
+
typeof segment.points === 'string'
|
|
143
|
+
? segment.points
|
|
144
|
+
: b64Vecs.encodePoints(segment.points),
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
props.scaleX = props.scaleX ?? 1
|
|
148
|
+
props.scaleY = props.scaleY ?? 1
|
|
149
|
+
},
|
|
150
|
+
down: (props) => {
|
|
151
|
+
props.segments = props.segments.map((segment: any) => ({
|
|
152
|
+
...segment,
|
|
153
|
+
// Only decode if points is a string (not already VecModel[])
|
|
154
|
+
points: Array.isArray(segment.points)
|
|
155
|
+
? segment.points
|
|
156
|
+
: b64Vecs.decodePoints(segment.points),
|
|
157
|
+
}))
|
|
158
|
+
delete props.scaleX
|
|
159
|
+
delete props.scaleY
|
|
160
|
+
},
|
|
161
|
+
},
|
|
125
162
|
],
|
|
126
163
|
})
|
|
@@ -142,6 +142,7 @@ const Versions = createShapePropsMigrationIds('note', {
|
|
|
142
142
|
AddScale: 7,
|
|
143
143
|
AddLabelColor: 8,
|
|
144
144
|
AddRichText: 9,
|
|
145
|
+
AddRichTextAttrs: 10,
|
|
145
146
|
})
|
|
146
147
|
|
|
147
148
|
/**
|
|
@@ -156,7 +157,8 @@ export { Versions as noteShapeVersions }
|
|
|
156
157
|
* Migration sequence for note shapes. Handles schema evolution over time by defining
|
|
157
158
|
* how to upgrade and downgrade note shape data between different versions. Includes
|
|
158
159
|
* migrations for URL properties, text alignment changes, vertical alignment addition,
|
|
159
|
-
* font size adjustments, scaling support, label color,
|
|
160
|
+
* font size adjustments, scaling support, label color, the transition from plain text to rich text,
|
|
161
|
+
* and support for attrs property on richText.
|
|
160
162
|
*
|
|
161
163
|
* @public
|
|
162
164
|
*/
|
|
@@ -251,5 +253,17 @@ export const noteShapeMigrations = createShapePropsMigrationSequence({
|
|
|
251
253
|
// delete props.richText
|
|
252
254
|
// },
|
|
253
255
|
},
|
|
256
|
+
{
|
|
257
|
+
id: Versions.AddRichTextAttrs,
|
|
258
|
+
up: (_props) => {
|
|
259
|
+
// noop - attrs is optional so old records are valid
|
|
260
|
+
},
|
|
261
|
+
down: (props) => {
|
|
262
|
+
// Remove attrs from richText when migrating down
|
|
263
|
+
if (props.richText && 'attrs' in props.richText) {
|
|
264
|
+
delete props.richText.attrs
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
},
|
|
254
268
|
],
|
|
255
269
|
})
|
|
@@ -104,6 +104,7 @@ const Versions = createShapePropsMigrationIds('text', {
|
|
|
104
104
|
RemoveJustify: 1,
|
|
105
105
|
AddTextAlign: 2,
|
|
106
106
|
AddRichText: 3,
|
|
107
|
+
AddRichTextAttrs: 4,
|
|
107
108
|
})
|
|
108
109
|
|
|
109
110
|
/**
|
|
@@ -116,8 +117,8 @@ const Versions = createShapePropsMigrationIds('text', {
|
|
|
116
117
|
* import { textShapeVersions } from '@tldraw/tlschema'
|
|
117
118
|
*
|
|
118
119
|
* // Check if shape data needs migration
|
|
119
|
-
* if (shapeVersion < textShapeVersions.
|
|
120
|
-
* // Apply rich text migration
|
|
120
|
+
* if (shapeVersion < textShapeVersions.AddRichTextAttrs) {
|
|
121
|
+
* // Apply rich text attrs migration
|
|
121
122
|
* }
|
|
122
123
|
* ```
|
|
123
124
|
*/
|
|
@@ -131,6 +132,7 @@ export { Versions as textShapeVersions }
|
|
|
131
132
|
* - RemoveJustify: Replaced 'justify' alignment with 'start'
|
|
132
133
|
* - AddTextAlign: Migrated from 'align' to 'textAlign' property
|
|
133
134
|
* - AddRichText: Converted plain text to rich text format
|
|
135
|
+
* - AddRichTextAttrs: Added support for attrs property on richText
|
|
134
136
|
*
|
|
135
137
|
* @public
|
|
136
138
|
*/
|
|
@@ -167,5 +169,17 @@ export const textShapeMigrations = createShapePropsMigrationSequence({
|
|
|
167
169
|
// delete props.richText
|
|
168
170
|
// },
|
|
169
171
|
},
|
|
172
|
+
{
|
|
173
|
+
id: Versions.AddRichTextAttrs,
|
|
174
|
+
up: (_props) => {
|
|
175
|
+
// noop - attrs is optional so old records are valid
|
|
176
|
+
},
|
|
177
|
+
down: (props) => {
|
|
178
|
+
// Remove attrs from richText when migrating down
|
|
179
|
+
if (props.richText && 'attrs' in props.richText) {
|
|
180
|
+
delete props.richText.attrs
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
},
|
|
170
184
|
],
|
|
171
185
|
})
|
package/src/store-migrations.ts
CHANGED
|
@@ -67,35 +67,36 @@ export const storeMigrations = createMigrationSequence({
|
|
|
67
67
|
sequence: [
|
|
68
68
|
{
|
|
69
69
|
id: Versions.RemoveCodeAndIconShapeTypes,
|
|
70
|
-
scope: '
|
|
71
|
-
up: (
|
|
72
|
-
for (const [id, record] of
|
|
70
|
+
scope: 'storage',
|
|
71
|
+
up: (storage) => {
|
|
72
|
+
for (const [id, record] of storage.entries()) {
|
|
73
73
|
if (
|
|
74
74
|
record.typeName === 'shape' &&
|
|
75
|
-
|
|
75
|
+
'type' in record &&
|
|
76
|
+
(record.type === 'icon' || record.type === 'code')
|
|
76
77
|
) {
|
|
77
|
-
delete
|
|
78
|
+
storage.delete(id)
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
},
|
|
81
82
|
},
|
|
82
83
|
{
|
|
83
84
|
id: Versions.AddInstancePresenceType,
|
|
84
|
-
scope: '
|
|
85
|
-
up(
|
|
85
|
+
scope: 'storage',
|
|
86
|
+
up(_storage) {
|
|
86
87
|
// noop
|
|
87
88
|
// there used to be a down migration for this but we made down migrations optional
|
|
88
|
-
// and we don't use them on
|
|
89
|
+
// and we don't use them on storage-level migrations so we can just remove it
|
|
89
90
|
},
|
|
90
91
|
},
|
|
91
92
|
{
|
|
92
93
|
// remove user and presence records and add pointer records
|
|
93
94
|
id: Versions.RemoveTLUserAndPresenceAndAddPointer,
|
|
94
|
-
scope: '
|
|
95
|
-
up: (
|
|
96
|
-
for (const [id, record] of
|
|
95
|
+
scope: 'storage',
|
|
96
|
+
up: (storage) => {
|
|
97
|
+
for (const [id, record] of storage.entries()) {
|
|
97
98
|
if (record.typeName.match(/^(user|user_presence)$/)) {
|
|
98
|
-
delete
|
|
99
|
+
storage.delete(id)
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
},
|
|
@@ -103,11 +104,11 @@ export const storeMigrations = createMigrationSequence({
|
|
|
103
104
|
{
|
|
104
105
|
// remove user document records
|
|
105
106
|
id: Versions.RemoveUserDocument,
|
|
106
|
-
scope: '
|
|
107
|
-
up: (
|
|
108
|
-
for (const [id, record] of
|
|
107
|
+
scope: 'storage',
|
|
108
|
+
up: (storage) => {
|
|
109
|
+
for (const [id, record] of storage.entries()) {
|
|
109
110
|
if (record.typeName.match('user_document')) {
|
|
110
|
-
delete
|
|
111
|
+
storage.delete(id)
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
},
|