@ibodr/schema 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6687 -0
- package/dist/index.mjs +4084 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4084 @@
|
|
|
1
|
+
import { structuredClone, sortByIndex, objectMapFromEntries, getIndices, objectMapEntries, registerDrawLibraryVersion, mapObjectMapValues, uniqueId, safeParseUrl, assert, filterEntries, objectMapValues, annotateError } from '@ibodr/utils';
|
|
2
|
+
import { T } from '@ibodr/validate';
|
|
3
|
+
import { createMigrationIds, createRecordMigrationSequence, createMigrationSequence, createRecordType, StoreSchema, RecordType } from '@ibodr/store';
|
|
4
|
+
import { computed } from '@ibodr/state';
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
function idValidator(prefix) {
|
|
8
|
+
return T.string.refine((id) => {
|
|
9
|
+
if (!id.startsWith(`${prefix}:`)) {
|
|
10
|
+
throw new Error(`${prefix} ID must start with "${prefix}:"`);
|
|
11
|
+
}
|
|
12
|
+
return id;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/assets/DrBaseAsset.ts
|
|
17
|
+
var assetIdValidator = idValidator("asset");
|
|
18
|
+
function createAssetValidator(type, props, meta) {
|
|
19
|
+
const propsValidator = props instanceof T.Validator ? props : props ? T.object(props) : T.jsonValue;
|
|
20
|
+
return T.object({
|
|
21
|
+
id: assetIdValidator,
|
|
22
|
+
typeName: T.literal("asset"),
|
|
23
|
+
type: T.literal(type),
|
|
24
|
+
props: propsValidator,
|
|
25
|
+
meta: meta ? T.object(meta) : T.jsonValue
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
var bookmarkAssetProps = {
|
|
29
|
+
title: T.string,
|
|
30
|
+
description: T.string,
|
|
31
|
+
image: T.string,
|
|
32
|
+
favicon: T.string,
|
|
33
|
+
src: T.srcUrl.nullable()
|
|
34
|
+
};
|
|
35
|
+
createAssetValidator(
|
|
36
|
+
"bookmark",
|
|
37
|
+
T.object(bookmarkAssetProps)
|
|
38
|
+
);
|
|
39
|
+
var Versions = createMigrationIds("com.draw.asset.bookmark", {
|
|
40
|
+
MakeUrlsValid: 1,
|
|
41
|
+
AddFavicon: 2
|
|
42
|
+
});
|
|
43
|
+
var bookmarkAssetMigrations = createRecordMigrationSequence({
|
|
44
|
+
sequenceId: "com.draw.asset.bookmark",
|
|
45
|
+
recordType: "asset",
|
|
46
|
+
filter: (asset) => asset.type === "bookmark",
|
|
47
|
+
sequence: [
|
|
48
|
+
{
|
|
49
|
+
id: Versions.MakeUrlsValid,
|
|
50
|
+
up: (asset) => {
|
|
51
|
+
if (!T.srcUrl.isValid(asset.props.src)) {
|
|
52
|
+
asset.props.src = "";
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
down: (_asset) => {
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: Versions.AddFavicon,
|
|
60
|
+
up: (asset) => {
|
|
61
|
+
if (!T.srcUrl.isValid(asset.props.favicon)) {
|
|
62
|
+
asset.props.favicon = "";
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
down: (asset) => {
|
|
66
|
+
delete asset.props.favicon;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
var imageAssetProps = {
|
|
72
|
+
w: T.number,
|
|
73
|
+
h: T.number,
|
|
74
|
+
name: T.string,
|
|
75
|
+
isAnimated: T.boolean,
|
|
76
|
+
mimeType: T.string.nullable(),
|
|
77
|
+
src: T.srcUrl.nullable(),
|
|
78
|
+
fileSize: T.nonZeroNumber.optional(),
|
|
79
|
+
pixelRatio: T.positiveNumber.optional()
|
|
80
|
+
};
|
|
81
|
+
createAssetValidator(
|
|
82
|
+
"image",
|
|
83
|
+
T.object(imageAssetProps)
|
|
84
|
+
);
|
|
85
|
+
var Versions2 = createMigrationIds("com.draw.asset.image", {
|
|
86
|
+
AddIsAnimated: 1,
|
|
87
|
+
RenameWidthHeight: 2,
|
|
88
|
+
MakeUrlsValid: 3,
|
|
89
|
+
AddFileSize: 4,
|
|
90
|
+
MakeFileSizeOptional: 5,
|
|
91
|
+
AddPixelRatio: 6
|
|
92
|
+
});
|
|
93
|
+
var imageAssetMigrations = createRecordMigrationSequence({
|
|
94
|
+
sequenceId: "com.draw.asset.image",
|
|
95
|
+
recordType: "asset",
|
|
96
|
+
filter: (asset) => asset.type === "image",
|
|
97
|
+
sequence: [
|
|
98
|
+
{
|
|
99
|
+
id: Versions2.AddIsAnimated,
|
|
100
|
+
up: (asset) => {
|
|
101
|
+
asset.props.isAnimated = false;
|
|
102
|
+
},
|
|
103
|
+
down: (asset) => {
|
|
104
|
+
delete asset.props.isAnimated;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: Versions2.RenameWidthHeight,
|
|
109
|
+
up: (asset) => {
|
|
110
|
+
asset.props.w = asset.props.width;
|
|
111
|
+
asset.props.h = asset.props.height;
|
|
112
|
+
delete asset.props.width;
|
|
113
|
+
delete asset.props.height;
|
|
114
|
+
},
|
|
115
|
+
down: (asset) => {
|
|
116
|
+
asset.props.width = asset.props.w;
|
|
117
|
+
asset.props.height = asset.props.h;
|
|
118
|
+
delete asset.props.w;
|
|
119
|
+
delete asset.props.h;
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: Versions2.MakeUrlsValid,
|
|
124
|
+
up: (asset) => {
|
|
125
|
+
if (!T.srcUrl.isValid(asset.props.src)) {
|
|
126
|
+
asset.props.src = "";
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
down: (_asset) => {
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: Versions2.AddFileSize,
|
|
134
|
+
up: (asset) => {
|
|
135
|
+
asset.props.fileSize = -1;
|
|
136
|
+
},
|
|
137
|
+
down: (asset) => {
|
|
138
|
+
delete asset.props.fileSize;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: Versions2.MakeFileSizeOptional,
|
|
143
|
+
up: (asset) => {
|
|
144
|
+
if (asset.props.fileSize === -1) {
|
|
145
|
+
asset.props.fileSize = void 0;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
down: (asset) => {
|
|
149
|
+
if (asset.props.fileSize === void 0) {
|
|
150
|
+
asset.props.fileSize = -1;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: Versions2.AddPixelRatio,
|
|
156
|
+
up: (_asset) => {
|
|
157
|
+
},
|
|
158
|
+
down: (asset) => {
|
|
159
|
+
delete asset.props.pixelRatio;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
});
|
|
164
|
+
var videoAssetProps = {
|
|
165
|
+
w: T.number,
|
|
166
|
+
h: T.number,
|
|
167
|
+
name: T.string,
|
|
168
|
+
isAnimated: T.boolean,
|
|
169
|
+
mimeType: T.string.nullable(),
|
|
170
|
+
src: T.srcUrl.nullable(),
|
|
171
|
+
fileSize: T.number.optional()
|
|
172
|
+
};
|
|
173
|
+
createAssetValidator(
|
|
174
|
+
"video",
|
|
175
|
+
T.object(videoAssetProps)
|
|
176
|
+
);
|
|
177
|
+
var Versions3 = createMigrationIds("com.draw.asset.video", {
|
|
178
|
+
AddIsAnimated: 1,
|
|
179
|
+
RenameWidthHeight: 2,
|
|
180
|
+
MakeUrlsValid: 3,
|
|
181
|
+
AddFileSize: 4,
|
|
182
|
+
MakeFileSizeOptional: 5
|
|
183
|
+
});
|
|
184
|
+
var videoAssetMigrations = createRecordMigrationSequence({
|
|
185
|
+
sequenceId: "com.draw.asset.video",
|
|
186
|
+
recordType: "asset",
|
|
187
|
+
filter: (asset) => asset.type === "video",
|
|
188
|
+
sequence: [
|
|
189
|
+
{
|
|
190
|
+
id: Versions3.AddIsAnimated,
|
|
191
|
+
up: (asset) => {
|
|
192
|
+
asset.props.isAnimated = false;
|
|
193
|
+
},
|
|
194
|
+
down: (asset) => {
|
|
195
|
+
delete asset.props.isAnimated;
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: Versions3.RenameWidthHeight,
|
|
200
|
+
up: (asset) => {
|
|
201
|
+
asset.props.w = asset.props.width;
|
|
202
|
+
asset.props.h = asset.props.height;
|
|
203
|
+
delete asset.props.width;
|
|
204
|
+
delete asset.props.height;
|
|
205
|
+
},
|
|
206
|
+
down: (asset) => {
|
|
207
|
+
asset.props.width = asset.props.w;
|
|
208
|
+
asset.props.height = asset.props.h;
|
|
209
|
+
delete asset.props.w;
|
|
210
|
+
delete asset.props.h;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: Versions3.MakeUrlsValid,
|
|
215
|
+
up: (asset) => {
|
|
216
|
+
if (!T.srcUrl.isValid(asset.props.src)) {
|
|
217
|
+
asset.props.src = "";
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
down: (_asset) => {
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: Versions3.AddFileSize,
|
|
225
|
+
up: (asset) => {
|
|
226
|
+
asset.props.fileSize = -1;
|
|
227
|
+
},
|
|
228
|
+
down: (asset) => {
|
|
229
|
+
delete asset.props.fileSize;
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: Versions3.MakeFileSizeOptional,
|
|
234
|
+
up: (asset) => {
|
|
235
|
+
if (asset.props.fileSize === -1) {
|
|
236
|
+
asset.props.fileSize = void 0;
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
down: (asset) => {
|
|
240
|
+
if (asset.props.fileSize === void 0) {
|
|
241
|
+
asset.props.fileSize = -1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
});
|
|
247
|
+
var vecModelValidator = T.object({
|
|
248
|
+
x: T.number,
|
|
249
|
+
y: T.number,
|
|
250
|
+
z: T.number.optional()
|
|
251
|
+
});
|
|
252
|
+
var boxModelValidator = T.object({
|
|
253
|
+
x: T.number,
|
|
254
|
+
y: T.number,
|
|
255
|
+
w: T.number,
|
|
256
|
+
h: T.number
|
|
257
|
+
});
|
|
258
|
+
var opacityValidator = T.unitInterval;
|
|
259
|
+
|
|
260
|
+
// src/shapes/DrBaseShape.ts
|
|
261
|
+
var parentIdValidator = T.string.refine((id) => {
|
|
262
|
+
if (!id.startsWith("page:") && !id.startsWith("shape:")) {
|
|
263
|
+
throw new Error('Parent ID must start with "page:" or "shape:"');
|
|
264
|
+
}
|
|
265
|
+
return id;
|
|
266
|
+
});
|
|
267
|
+
var shapeIdValidator = idValidator("shape");
|
|
268
|
+
function createShapeValidator(type, props, meta) {
|
|
269
|
+
return T.object({
|
|
270
|
+
id: shapeIdValidator,
|
|
271
|
+
typeName: T.literal("shape"),
|
|
272
|
+
x: T.number,
|
|
273
|
+
y: T.number,
|
|
274
|
+
rotation: T.number,
|
|
275
|
+
index: T.indexKey,
|
|
276
|
+
parentId: parentIdValidator,
|
|
277
|
+
type: T.literal(type),
|
|
278
|
+
isLocked: T.boolean,
|
|
279
|
+
opacity: opacityValidator,
|
|
280
|
+
props: props ? T.object(props) : T.jsonValue,
|
|
281
|
+
meta: meta ? T.object(meta) : T.jsonValue
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/bindings/DrBaseBinding.ts
|
|
286
|
+
var bindingIdValidator = idValidator("binding");
|
|
287
|
+
function createBindingValidator(type, props, meta) {
|
|
288
|
+
return T.object({
|
|
289
|
+
id: bindingIdValidator,
|
|
290
|
+
typeName: T.literal("binding"),
|
|
291
|
+
type: T.literal(type),
|
|
292
|
+
fromId: shapeIdValidator,
|
|
293
|
+
toId: shapeIdValidator,
|
|
294
|
+
props: props ? T.object(props) : T.jsonValue,
|
|
295
|
+
meta: meta ? T.object(meta) : T.jsonValue
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/records/DrBinding.ts
|
|
300
|
+
createMigrationIds("com.draw.binding", {});
|
|
301
|
+
var rootBindingMigrations = createRecordMigrationSequence({
|
|
302
|
+
sequenceId: "com.draw.binding",
|
|
303
|
+
recordType: "binding",
|
|
304
|
+
sequence: []
|
|
305
|
+
});
|
|
306
|
+
function isBinding(record) {
|
|
307
|
+
if (!record) return false;
|
|
308
|
+
return record.typeName === "binding";
|
|
309
|
+
}
|
|
310
|
+
function isBindingId(id) {
|
|
311
|
+
if (!id) return false;
|
|
312
|
+
return id.startsWith("binding:");
|
|
313
|
+
}
|
|
314
|
+
function createBindingId(id) {
|
|
315
|
+
return `binding:${id ?? uniqueId()}`;
|
|
316
|
+
}
|
|
317
|
+
function createBindingPropsMigrationSequence(migrations) {
|
|
318
|
+
return migrations;
|
|
319
|
+
}
|
|
320
|
+
function createBindingPropsMigrationIds(bindingType, ids) {
|
|
321
|
+
return mapObjectMapValues(ids, (_k, v) => `com.draw.binding.${bindingType}/${v}`);
|
|
322
|
+
}
|
|
323
|
+
function createBindingRecordType(bindings) {
|
|
324
|
+
return createRecordType("binding", {
|
|
325
|
+
scope: "document",
|
|
326
|
+
validator: T.model(
|
|
327
|
+
"binding",
|
|
328
|
+
T.union(
|
|
329
|
+
"type",
|
|
330
|
+
mapObjectMapValues(
|
|
331
|
+
bindings,
|
|
332
|
+
(type, { props, meta }) => createBindingValidator(type, props, meta)
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
}).withDefaultProperties(() => ({
|
|
337
|
+
meta: {}
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
var richTextValidator = T.object({
|
|
341
|
+
type: T.string,
|
|
342
|
+
content: T.arrayOf(T.unknown),
|
|
343
|
+
attrs: T.any.optional()
|
|
344
|
+
});
|
|
345
|
+
function toRichText(text) {
|
|
346
|
+
const lines = text.split("\n");
|
|
347
|
+
const content = lines.map((text2) => {
|
|
348
|
+
if (!text2) {
|
|
349
|
+
return {
|
|
350
|
+
type: "paragraph"
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
type: "paragraph",
|
|
355
|
+
content: [{ type: "text", text: text2 }]
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
return {
|
|
359
|
+
type: "doc",
|
|
360
|
+
content
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
var StyleProp = class _StyleProp {
|
|
364
|
+
/** @internal */
|
|
365
|
+
constructor(id, defaultValue, type) {
|
|
366
|
+
this.id = id;
|
|
367
|
+
this.defaultValue = defaultValue;
|
|
368
|
+
this.type = type;
|
|
369
|
+
}
|
|
370
|
+
id;
|
|
371
|
+
defaultValue;
|
|
372
|
+
type;
|
|
373
|
+
/**
|
|
374
|
+
* Define a new {@link StyleProp}.
|
|
375
|
+
*
|
|
376
|
+
* @param uniqueId - Each StyleProp must have a unique ID. We recommend you prefix this with
|
|
377
|
+
* your app/library name.
|
|
378
|
+
* @param options -
|
|
379
|
+
* - `defaultValue`: The default value for this style prop.
|
|
380
|
+
*
|
|
381
|
+
* - `type`: Optionally, describe what type of data you expect for this style prop.
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```ts
|
|
385
|
+
* import {T} from '@ibodr/validate'
|
|
386
|
+
* import {StyleProp} from '@ibodr/schema'
|
|
387
|
+
*
|
|
388
|
+
* const MyLineWidthProp = StyleProp.define('myApp:lineWidth', {
|
|
389
|
+
* defaultValue: 1,
|
|
390
|
+
* type: T.number,
|
|
391
|
+
* })
|
|
392
|
+
* ```
|
|
393
|
+
* @public
|
|
394
|
+
*/
|
|
395
|
+
static define(uniqueId4, options) {
|
|
396
|
+
const { defaultValue, type = T.any } = options;
|
|
397
|
+
return new _StyleProp(uniqueId4, defaultValue, type);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Define a new {@link StyleProp} as a list of possible values.
|
|
401
|
+
*
|
|
402
|
+
* @param uniqueId - Each StyleProp must have a unique ID. We recommend you prefix this with
|
|
403
|
+
* your app/library name.
|
|
404
|
+
* @param options -
|
|
405
|
+
* - `defaultValue`: The default value for this style prop.
|
|
406
|
+
*
|
|
407
|
+
* - `values`: An array of possible values of this style prop.
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* ```ts
|
|
411
|
+
* import {StyleProp} from '@ibodr/schema'
|
|
412
|
+
*
|
|
413
|
+
* const MySizeProp = StyleProp.defineEnum('myApp:size', {
|
|
414
|
+
* defaultValue: 'medium',
|
|
415
|
+
* values: ['small', 'medium', 'large'],
|
|
416
|
+
* })
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
static defineEnum(uniqueId4, options) {
|
|
420
|
+
const { defaultValue, values } = options;
|
|
421
|
+
return new EnumStyleProp(uniqueId4, defaultValue, values);
|
|
422
|
+
}
|
|
423
|
+
setDefaultValue(value) {
|
|
424
|
+
this.defaultValue = value;
|
|
425
|
+
}
|
|
426
|
+
validate(value) {
|
|
427
|
+
return this.type.validate(value);
|
|
428
|
+
}
|
|
429
|
+
validateUsingKnownGoodVersion(prevValue, newValue) {
|
|
430
|
+
if (this.type.validateUsingKnownGoodVersion) {
|
|
431
|
+
return this.type.validateUsingKnownGoodVersion(prevValue, newValue);
|
|
432
|
+
} else {
|
|
433
|
+
return this.validate(newValue);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
var EnumStyleProp = class extends StyleProp {
|
|
438
|
+
/** @internal */
|
|
439
|
+
constructor(id, defaultValue, values) {
|
|
440
|
+
super(id, defaultValue, T.literalEnum(...values));
|
|
441
|
+
this.values = [...values];
|
|
442
|
+
}
|
|
443
|
+
values;
|
|
444
|
+
/**
|
|
445
|
+
* Add new values to this enum style prop at runtime. This is useful for extending
|
|
446
|
+
* the built-in styles with custom values (e.g. adding custom colors). Be sure to
|
|
447
|
+
* also modify the associated types.
|
|
448
|
+
*
|
|
449
|
+
* @param newValues - The new values to add.
|
|
450
|
+
*
|
|
451
|
+
* @public
|
|
452
|
+
*/
|
|
453
|
+
addValues(...newValues) {
|
|
454
|
+
for (const v of newValues) {
|
|
455
|
+
if (!this.values.includes(v)) {
|
|
456
|
+
this.values.push(v);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
this.type = T.literalEnum(...this.values);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Remove values from this enum style prop at runtime. This is useful for narrowing
|
|
463
|
+
* the built-in styles with custom values (e.g. adding custom colors). Be sure to
|
|
464
|
+
* also modify the associated types.
|
|
465
|
+
*
|
|
466
|
+
* @param valuesToRemove - The values to remove.
|
|
467
|
+
*
|
|
468
|
+
* @public
|
|
469
|
+
*/
|
|
470
|
+
removeValues(...valuesToRemove) {
|
|
471
|
+
for (const v of valuesToRemove) {
|
|
472
|
+
if (this.values.includes(v)) {
|
|
473
|
+
this.values.splice(this.values.indexOf(v), 1);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
this.type = T.literalEnum(...this.values);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// src/records/DrShape.ts
|
|
481
|
+
var rootShapeVersions = createMigrationIds("com.draw.shape", {
|
|
482
|
+
AddIsLocked: 1,
|
|
483
|
+
HoistOpacity: 2,
|
|
484
|
+
AddMeta: 3,
|
|
485
|
+
AddWhite: 4
|
|
486
|
+
});
|
|
487
|
+
var rootShapeMigrations = createRecordMigrationSequence({
|
|
488
|
+
sequenceId: "com.draw.shape",
|
|
489
|
+
recordType: "shape",
|
|
490
|
+
sequence: [
|
|
491
|
+
{
|
|
492
|
+
id: rootShapeVersions.AddIsLocked,
|
|
493
|
+
up: (record) => {
|
|
494
|
+
record.isLocked = false;
|
|
495
|
+
},
|
|
496
|
+
down: (record) => {
|
|
497
|
+
delete record.isLocked;
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
id: rootShapeVersions.HoistOpacity,
|
|
502
|
+
up: (record) => {
|
|
503
|
+
record.opacity = Number(record.props.opacity ?? "1");
|
|
504
|
+
delete record.props.opacity;
|
|
505
|
+
},
|
|
506
|
+
down: (record) => {
|
|
507
|
+
const opacity = record.opacity;
|
|
508
|
+
delete record.opacity;
|
|
509
|
+
record.props.opacity = opacity < 0.175 ? "0.1" : opacity < 0.375 ? "0.25" : opacity < 0.625 ? "0.5" : opacity < 0.875 ? "0.75" : "1";
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
id: rootShapeVersions.AddMeta,
|
|
514
|
+
up: (record) => {
|
|
515
|
+
record.meta = {};
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
id: rootShapeVersions.AddWhite,
|
|
520
|
+
up: (_record) => {
|
|
521
|
+
},
|
|
522
|
+
down: (record) => {
|
|
523
|
+
if (record.props.color === "white") {
|
|
524
|
+
record.props.color = "black";
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
]
|
|
529
|
+
});
|
|
530
|
+
function isShape(record) {
|
|
531
|
+
if (!record) return false;
|
|
532
|
+
return record.typeName === "shape";
|
|
533
|
+
}
|
|
534
|
+
function isShapeId(id) {
|
|
535
|
+
if (!id) return false;
|
|
536
|
+
return id.startsWith("shape:");
|
|
537
|
+
}
|
|
538
|
+
function createShapeId(id) {
|
|
539
|
+
return `shape:${id ?? uniqueId()}`;
|
|
540
|
+
}
|
|
541
|
+
function getShapePropKeysByStyle(props) {
|
|
542
|
+
const propKeysByStyle = /* @__PURE__ */ new Map();
|
|
543
|
+
for (const [key, prop] of Object.entries(props)) {
|
|
544
|
+
if (prop instanceof StyleProp) {
|
|
545
|
+
if (propKeysByStyle.has(prop)) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
`Duplicate style prop ${prop.id}. Each style prop can only be used once within a shape.`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
propKeysByStyle.set(prop, key);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return propKeysByStyle;
|
|
554
|
+
}
|
|
555
|
+
function createShapePropsMigrationSequence(migrations) {
|
|
556
|
+
return migrations;
|
|
557
|
+
}
|
|
558
|
+
function createShapePropsMigrationIds(shapeType, ids) {
|
|
559
|
+
return mapObjectMapValues(ids, (_k, v) => `com.draw.shape.${shapeType}/${v}`);
|
|
560
|
+
}
|
|
561
|
+
function createShapeRecordType(shapes) {
|
|
562
|
+
return createRecordType("shape", {
|
|
563
|
+
scope: "document",
|
|
564
|
+
validator: T.model(
|
|
565
|
+
"shape",
|
|
566
|
+
T.union(
|
|
567
|
+
"type",
|
|
568
|
+
mapObjectMapValues(
|
|
569
|
+
shapes,
|
|
570
|
+
(type, { props, meta }) => createShapeValidator(type, props, meta)
|
|
571
|
+
)
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
}).withDefaultProperties(() => ({
|
|
575
|
+
x: 0,
|
|
576
|
+
y: 0,
|
|
577
|
+
rotation: 0,
|
|
578
|
+
isLocked: false,
|
|
579
|
+
opacity: 1,
|
|
580
|
+
meta: {}
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
function processPropsMigrations(typeName, records) {
|
|
584
|
+
const result = [];
|
|
585
|
+
for (const [subType, { migrations }] of Object.entries(records)) {
|
|
586
|
+
const sequenceId = `com.draw.${typeName}.${subType}`;
|
|
587
|
+
if (!migrations) {
|
|
588
|
+
result.push(
|
|
589
|
+
createMigrationSequence({
|
|
590
|
+
sequenceId,
|
|
591
|
+
retroactive: true,
|
|
592
|
+
sequence: []
|
|
593
|
+
})
|
|
594
|
+
);
|
|
595
|
+
} else if ("sequenceId" in migrations) {
|
|
596
|
+
assert(
|
|
597
|
+
sequenceId === migrations.sequenceId,
|
|
598
|
+
`sequenceId mismatch for ${subType} ${RecordType} migrations. Expected '${sequenceId}', got '${migrations.sequenceId}'`
|
|
599
|
+
);
|
|
600
|
+
result.push(migrations);
|
|
601
|
+
} else if ("sequence" in migrations) {
|
|
602
|
+
result.push(
|
|
603
|
+
createMigrationSequence({
|
|
604
|
+
sequenceId,
|
|
605
|
+
retroactive: true,
|
|
606
|
+
sequence: migrations.sequence.map(
|
|
607
|
+
(m) => "id" in m ? createPropsMigration(typeName, subType, m) : m
|
|
608
|
+
)
|
|
609
|
+
})
|
|
610
|
+
);
|
|
611
|
+
} else {
|
|
612
|
+
result.push(
|
|
613
|
+
createMigrationSequence({
|
|
614
|
+
sequenceId,
|
|
615
|
+
retroactive: true,
|
|
616
|
+
sequence: Object.keys(migrations.migrators).map((k) => Number(k)).sort((a, b) => a - b).map(
|
|
617
|
+
(version) => ({
|
|
618
|
+
id: `${sequenceId}/${version}`,
|
|
619
|
+
scope: "record",
|
|
620
|
+
filter: (r) => r.typeName === typeName && r.type === subType,
|
|
621
|
+
up: (record) => {
|
|
622
|
+
const result2 = migrations.migrators[version].up(record);
|
|
623
|
+
if (result2) {
|
|
624
|
+
return result2;
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
down: (record) => {
|
|
628
|
+
const result2 = migrations.migrators[version].down(record);
|
|
629
|
+
if (result2) {
|
|
630
|
+
return result2;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
})
|
|
634
|
+
)
|
|
635
|
+
})
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return result;
|
|
640
|
+
}
|
|
641
|
+
function createPropsMigration(typeName, subType, m) {
|
|
642
|
+
return {
|
|
643
|
+
id: m.id,
|
|
644
|
+
dependsOn: m.dependsOn,
|
|
645
|
+
scope: "record",
|
|
646
|
+
filter: (r) => r.typeName === typeName && r.type === subType,
|
|
647
|
+
up: (record) => {
|
|
648
|
+
const result = m.up(record.props);
|
|
649
|
+
if (result) {
|
|
650
|
+
record.props = result;
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
down: typeof m.down === "function" ? (record) => {
|
|
654
|
+
const result = m.down(record.props);
|
|
655
|
+
if (result) {
|
|
656
|
+
record.props = result;
|
|
657
|
+
}
|
|
658
|
+
} : void 0
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/styles/DrColorStyle.ts
|
|
663
|
+
var defaultColorNames = [
|
|
664
|
+
"black",
|
|
665
|
+
"grey",
|
|
666
|
+
"light-violet",
|
|
667
|
+
"violet",
|
|
668
|
+
"blue",
|
|
669
|
+
"light-blue",
|
|
670
|
+
"yellow",
|
|
671
|
+
"orange",
|
|
672
|
+
"green",
|
|
673
|
+
"light-green",
|
|
674
|
+
"light-red",
|
|
675
|
+
"red",
|
|
676
|
+
"white"
|
|
677
|
+
];
|
|
678
|
+
var DefaultColorStyle = StyleProp.defineEnum("draw:color", {
|
|
679
|
+
defaultValue: "black",
|
|
680
|
+
values: defaultColorNames
|
|
681
|
+
});
|
|
682
|
+
var DefaultLabelColorStyle = StyleProp.defineEnum("draw:labelColor", {
|
|
683
|
+
defaultValue: "black",
|
|
684
|
+
values: defaultColorNames
|
|
685
|
+
});
|
|
686
|
+
function registerColorsFromThemes(definitions) {
|
|
687
|
+
const colorNames = /* @__PURE__ */ new Set();
|
|
688
|
+
for (const def of Object.values(definitions)) {
|
|
689
|
+
for (const colorPalette of [def.colors.light, def.colors.dark]) {
|
|
690
|
+
for (const [key, value] of Object.entries(colorPalette)) {
|
|
691
|
+
if (typeof value === "object" && value !== null) {
|
|
692
|
+
colorNames.add(key);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (colorNames.size > 0) {
|
|
698
|
+
DefaultColorStyle.addValues(...colorNames);
|
|
699
|
+
DefaultLabelColorStyle.addValues(...colorNames);
|
|
700
|
+
}
|
|
701
|
+
const toRemove = DefaultColorStyle.values.filter((v) => !colorNames.has(v));
|
|
702
|
+
if (toRemove.length > 0) {
|
|
703
|
+
DefaultColorStyle.removeValues(...toRemove);
|
|
704
|
+
DefaultLabelColorStyle.removeValues(...toRemove);
|
|
705
|
+
}
|
|
706
|
+
{
|
|
707
|
+
for (const def of Object.values(definitions)) {
|
|
708
|
+
for (const color of colorNames) {
|
|
709
|
+
if (!(color in def.colors.light)) {
|
|
710
|
+
console.warn(
|
|
711
|
+
`Theme '${def.id}' light palette is missing color '${color}'. Shapes using this color won't render correctly.`
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
if (!(color in def.colors.dark)) {
|
|
715
|
+
console.warn(
|
|
716
|
+
`Theme '${def.id}' dark palette is missing color '${color}'. Shapes using this color won't render correctly.`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// src/styles/DrDashStyle.ts
|
|
725
|
+
var DefaultDashStyle = StyleProp.defineEnum("draw:dash", {
|
|
726
|
+
defaultValue: "draw",
|
|
727
|
+
values: ["draw", "solid", "dashed", "dotted", "none"]
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
// src/styles/DrFillStyle.ts
|
|
731
|
+
var DefaultFillStyle = StyleProp.defineEnum("draw:fill", {
|
|
732
|
+
defaultValue: "none",
|
|
733
|
+
values: ["none", "semi", "solid", "pattern", "fill", "lined-fill"]
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// src/styles/DrFontStyle.ts
|
|
737
|
+
var DefaultFontStyle = StyleProp.defineEnum("draw:font", {
|
|
738
|
+
defaultValue: "draw",
|
|
739
|
+
values: ["draw", "sans", "serif", "mono"]
|
|
740
|
+
});
|
|
741
|
+
var DefaultFontFamilies = {
|
|
742
|
+
draw: "'draw_draw', sans-serif",
|
|
743
|
+
sans: "'draw_sans', sans-serif",
|
|
744
|
+
serif: "'draw_serif', serif",
|
|
745
|
+
mono: "'draw_mono', monospace"
|
|
746
|
+
};
|
|
747
|
+
function isFontEntry(value) {
|
|
748
|
+
return typeof value === "object" && value !== null && "fontFamily" in value;
|
|
749
|
+
}
|
|
750
|
+
function registerFontsFromThemes(definitions) {
|
|
751
|
+
const fontNames = /* @__PURE__ */ new Set();
|
|
752
|
+
for (const def of Object.values(definitions)) {
|
|
753
|
+
if (!def.fonts) continue;
|
|
754
|
+
for (const [key, value] of Object.entries(def.fonts)) {
|
|
755
|
+
if (isFontEntry(value)) {
|
|
756
|
+
fontNames.add(key);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const toAdd = [...fontNames].filter((v) => !DefaultFontStyle.values.includes(v));
|
|
761
|
+
if (toAdd.length > 0) {
|
|
762
|
+
DefaultFontStyle.addValues(...toAdd);
|
|
763
|
+
}
|
|
764
|
+
const toRemove = DefaultFontStyle.values.filter((v) => !fontNames.has(v));
|
|
765
|
+
if (toRemove.length > 0) {
|
|
766
|
+
DefaultFontStyle.removeValues(...toRemove);
|
|
767
|
+
}
|
|
768
|
+
{
|
|
769
|
+
for (const def of Object.values(definitions)) {
|
|
770
|
+
for (const font of fontNames) {
|
|
771
|
+
if (!def.fonts || !(font in def.fonts)) {
|
|
772
|
+
console.warn(
|
|
773
|
+
`Theme '${def.id}' is missing font '${font}'. Shapes using this font won't render correctly in this theme.`
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/styles/DrSizeStyle.ts
|
|
782
|
+
var DefaultSizeStyle = StyleProp.defineEnum("draw:size", {
|
|
783
|
+
defaultValue: "m",
|
|
784
|
+
values: ["s", "m", "l", "xl"]
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// src/shapes/DrArrowShape.ts
|
|
788
|
+
var arrowKinds = ["arc", "elbow"];
|
|
789
|
+
var ArrowShapeKindStyle = StyleProp.defineEnum("draw:arrowKind", {
|
|
790
|
+
defaultValue: "arc",
|
|
791
|
+
values: arrowKinds
|
|
792
|
+
});
|
|
793
|
+
var arrowheadTypes = [
|
|
794
|
+
"arrow",
|
|
795
|
+
"triangle",
|
|
796
|
+
"square",
|
|
797
|
+
"dot",
|
|
798
|
+
"pipe",
|
|
799
|
+
"diamond",
|
|
800
|
+
"inverted",
|
|
801
|
+
"bar",
|
|
802
|
+
"none"
|
|
803
|
+
];
|
|
804
|
+
var ArrowShapeArrowheadStartStyle = StyleProp.defineEnum("draw:arrowheadStart", {
|
|
805
|
+
defaultValue: "none",
|
|
806
|
+
values: arrowheadTypes
|
|
807
|
+
});
|
|
808
|
+
var ArrowShapeArrowheadEndStyle = StyleProp.defineEnum("draw:arrowheadEnd", {
|
|
809
|
+
defaultValue: "arrow",
|
|
810
|
+
values: arrowheadTypes
|
|
811
|
+
});
|
|
812
|
+
var arrowShapeProps = {
|
|
813
|
+
kind: ArrowShapeKindStyle,
|
|
814
|
+
labelColor: DefaultLabelColorStyle,
|
|
815
|
+
color: DefaultColorStyle,
|
|
816
|
+
fill: DefaultFillStyle,
|
|
817
|
+
dash: DefaultDashStyle,
|
|
818
|
+
size: DefaultSizeStyle,
|
|
819
|
+
arrowheadStart: ArrowShapeArrowheadStartStyle,
|
|
820
|
+
arrowheadEnd: ArrowShapeArrowheadEndStyle,
|
|
821
|
+
font: DefaultFontStyle,
|
|
822
|
+
start: vecModelValidator,
|
|
823
|
+
end: vecModelValidator,
|
|
824
|
+
bend: T.number,
|
|
825
|
+
richText: richTextValidator,
|
|
826
|
+
labelPosition: T.number,
|
|
827
|
+
scale: T.nonZeroNumber,
|
|
828
|
+
elbowMidPoint: T.number
|
|
829
|
+
};
|
|
830
|
+
var arrowShapeVersions = createShapePropsMigrationIds("arrow", {
|
|
831
|
+
AddLabelColor: 1,
|
|
832
|
+
AddIsPrecise: 2,
|
|
833
|
+
AddLabelPosition: 3,
|
|
834
|
+
ExtractBindings: 4,
|
|
835
|
+
AddScale: 5,
|
|
836
|
+
AddElbow: 6,
|
|
837
|
+
AddRichText: 7,
|
|
838
|
+
AddRichTextAttrs: 8
|
|
839
|
+
});
|
|
840
|
+
function propsMigration(migration) {
|
|
841
|
+
return createPropsMigration("shape", "arrow", migration);
|
|
842
|
+
}
|
|
843
|
+
var arrowShapeMigrations = createMigrationSequence({
|
|
844
|
+
sequenceId: "com.draw.shape.arrow",
|
|
845
|
+
retroactive: false,
|
|
846
|
+
sequence: [
|
|
847
|
+
propsMigration({
|
|
848
|
+
id: arrowShapeVersions.AddLabelColor,
|
|
849
|
+
up: (props) => {
|
|
850
|
+
props.labelColor = "black";
|
|
851
|
+
},
|
|
852
|
+
down: "retired"
|
|
853
|
+
}),
|
|
854
|
+
propsMigration({
|
|
855
|
+
id: arrowShapeVersions.AddIsPrecise,
|
|
856
|
+
up: ({ start, end }) => {
|
|
857
|
+
if (start.type === "binding") {
|
|
858
|
+
start.isPrecise = !(start.normalizedAnchor.x === 0.5 && start.normalizedAnchor.y === 0.5);
|
|
859
|
+
}
|
|
860
|
+
if (end.type === "binding") {
|
|
861
|
+
end.isPrecise = !(end.normalizedAnchor.x === 0.5 && end.normalizedAnchor.y === 0.5);
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
down: ({ start, end }) => {
|
|
865
|
+
if (start.type === "binding") {
|
|
866
|
+
if (!start.isPrecise) {
|
|
867
|
+
start.normalizedAnchor = { x: 0.5, y: 0.5 };
|
|
868
|
+
}
|
|
869
|
+
delete start.isPrecise;
|
|
870
|
+
}
|
|
871
|
+
if (end.type === "binding") {
|
|
872
|
+
if (!end.isPrecise) {
|
|
873
|
+
end.normalizedAnchor = { x: 0.5, y: 0.5 };
|
|
874
|
+
}
|
|
875
|
+
delete end.isPrecise;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}),
|
|
879
|
+
propsMigration({
|
|
880
|
+
id: arrowShapeVersions.AddLabelPosition,
|
|
881
|
+
up: (props) => {
|
|
882
|
+
props.labelPosition = 0.5;
|
|
883
|
+
},
|
|
884
|
+
down: (props) => {
|
|
885
|
+
delete props.labelPosition;
|
|
886
|
+
}
|
|
887
|
+
}),
|
|
888
|
+
{
|
|
889
|
+
id: arrowShapeVersions.ExtractBindings,
|
|
890
|
+
scope: "storage",
|
|
891
|
+
up: (storage) => {
|
|
892
|
+
const updates = [];
|
|
893
|
+
for (const record of storage.values()) {
|
|
894
|
+
if (record.typeName !== "shape" || record.type !== "arrow") continue;
|
|
895
|
+
const arrow = record;
|
|
896
|
+
const newArrow = structuredClone(arrow);
|
|
897
|
+
const { start, end } = arrow.props;
|
|
898
|
+
if (start.type === "binding") {
|
|
899
|
+
const id = createBindingId();
|
|
900
|
+
const binding = {
|
|
901
|
+
typeName: "binding",
|
|
902
|
+
id,
|
|
903
|
+
type: "arrow",
|
|
904
|
+
fromId: arrow.id,
|
|
905
|
+
toId: start.boundShapeId,
|
|
906
|
+
meta: {},
|
|
907
|
+
props: {
|
|
908
|
+
terminal: "start",
|
|
909
|
+
normalizedAnchor: start.normalizedAnchor,
|
|
910
|
+
isExact: start.isExact,
|
|
911
|
+
isPrecise: start.isPrecise
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
updates.push([id, binding]);
|
|
915
|
+
newArrow.props.start = { x: 0, y: 0 };
|
|
916
|
+
} else {
|
|
917
|
+
delete newArrow.props.start.type;
|
|
918
|
+
}
|
|
919
|
+
if (end.type === "binding") {
|
|
920
|
+
const id = createBindingId();
|
|
921
|
+
const binding = {
|
|
922
|
+
typeName: "binding",
|
|
923
|
+
id,
|
|
924
|
+
type: "arrow",
|
|
925
|
+
fromId: arrow.id,
|
|
926
|
+
toId: end.boundShapeId,
|
|
927
|
+
meta: {},
|
|
928
|
+
props: {
|
|
929
|
+
terminal: "end",
|
|
930
|
+
normalizedAnchor: end.normalizedAnchor,
|
|
931
|
+
isExact: end.isExact,
|
|
932
|
+
isPrecise: end.isPrecise
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
updates.push([id, binding]);
|
|
936
|
+
newArrow.props.end = { x: 0, y: 0 };
|
|
937
|
+
} else {
|
|
938
|
+
delete newArrow.props.end.type;
|
|
939
|
+
}
|
|
940
|
+
updates.push([arrow.id, newArrow]);
|
|
941
|
+
}
|
|
942
|
+
for (const [id, record] of updates) {
|
|
943
|
+
storage.set(id, record);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
},
|
|
947
|
+
propsMigration({
|
|
948
|
+
id: arrowShapeVersions.AddScale,
|
|
949
|
+
up: (props) => {
|
|
950
|
+
props.scale = 1;
|
|
951
|
+
},
|
|
952
|
+
down: (props) => {
|
|
953
|
+
delete props.scale;
|
|
954
|
+
}
|
|
955
|
+
}),
|
|
956
|
+
propsMigration({
|
|
957
|
+
id: arrowShapeVersions.AddElbow,
|
|
958
|
+
up: (props) => {
|
|
959
|
+
props.kind = "arc";
|
|
960
|
+
props.elbowMidPoint = 0.5;
|
|
961
|
+
},
|
|
962
|
+
down: (props) => {
|
|
963
|
+
delete props.kind;
|
|
964
|
+
delete props.elbowMidPoint;
|
|
965
|
+
}
|
|
966
|
+
}),
|
|
967
|
+
propsMigration({
|
|
968
|
+
id: arrowShapeVersions.AddRichText,
|
|
969
|
+
up: (props) => {
|
|
970
|
+
props.richText = toRichText(props.text);
|
|
971
|
+
delete props.text;
|
|
972
|
+
}
|
|
973
|
+
// N.B. Explicitly no down state so that we force clients to update.
|
|
974
|
+
// down: (props) => {
|
|
975
|
+
// delete props.richText
|
|
976
|
+
// },
|
|
977
|
+
}),
|
|
978
|
+
propsMigration({
|
|
979
|
+
id: arrowShapeVersions.AddRichTextAttrs,
|
|
980
|
+
up: (_props) => {
|
|
981
|
+
},
|
|
982
|
+
down: (props) => {
|
|
983
|
+
if (props.richText && "attrs" in props.richText) {
|
|
984
|
+
delete props.richText.attrs;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
})
|
|
988
|
+
]
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
// src/bindings/DrArrowBinding.ts
|
|
992
|
+
var ElbowArrowSnap = T.literalEnum("center", "edge-point", "edge", "none");
|
|
993
|
+
var arrowBindingProps = {
|
|
994
|
+
terminal: T.literalEnum("start", "end"),
|
|
995
|
+
normalizedAnchor: vecModelValidator,
|
|
996
|
+
isExact: T.boolean,
|
|
997
|
+
isPrecise: T.boolean,
|
|
998
|
+
snap: ElbowArrowSnap
|
|
999
|
+
};
|
|
1000
|
+
var arrowBindingVersions = createBindingPropsMigrationIds("arrow", {
|
|
1001
|
+
AddSnap: 1
|
|
1002
|
+
});
|
|
1003
|
+
var arrowBindingMigrations = createBindingPropsMigrationSequence({
|
|
1004
|
+
sequence: [
|
|
1005
|
+
{ dependsOn: [arrowShapeVersions.ExtractBindings] },
|
|
1006
|
+
{
|
|
1007
|
+
id: arrowBindingVersions.AddSnap,
|
|
1008
|
+
up: (props) => {
|
|
1009
|
+
props.snap = "none";
|
|
1010
|
+
},
|
|
1011
|
+
down: (props) => {
|
|
1012
|
+
delete props.snap;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
]
|
|
1016
|
+
});
|
|
1017
|
+
var cameraValidator = T.model(
|
|
1018
|
+
"camera",
|
|
1019
|
+
T.object({
|
|
1020
|
+
typeName: T.literal("camera"),
|
|
1021
|
+
id: idValidator("camera"),
|
|
1022
|
+
x: T.number,
|
|
1023
|
+
y: T.number,
|
|
1024
|
+
z: T.number,
|
|
1025
|
+
meta: T.jsonValue
|
|
1026
|
+
})
|
|
1027
|
+
);
|
|
1028
|
+
var cameraVersions = createMigrationIds("com.draw.camera", {
|
|
1029
|
+
AddMeta: 1
|
|
1030
|
+
});
|
|
1031
|
+
var cameraMigrations = createRecordMigrationSequence({
|
|
1032
|
+
sequenceId: "com.draw.camera",
|
|
1033
|
+
recordType: "camera",
|
|
1034
|
+
sequence: [
|
|
1035
|
+
{
|
|
1036
|
+
id: cameraVersions.AddMeta,
|
|
1037
|
+
up: (record) => {
|
|
1038
|
+
record.meta = {};
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
]
|
|
1042
|
+
});
|
|
1043
|
+
var CameraRecordType = createRecordType("camera", {
|
|
1044
|
+
validator: cameraValidator,
|
|
1045
|
+
scope: "session"
|
|
1046
|
+
}).withDefaultProperties(
|
|
1047
|
+
() => ({
|
|
1048
|
+
x: 0,
|
|
1049
|
+
y: 0,
|
|
1050
|
+
z: 1,
|
|
1051
|
+
meta: {}
|
|
1052
|
+
})
|
|
1053
|
+
);
|
|
1054
|
+
var TL_CURSOR_TYPES = /* @__PURE__ */ new Set([
|
|
1055
|
+
"none",
|
|
1056
|
+
"default",
|
|
1057
|
+
"pointer",
|
|
1058
|
+
"cross",
|
|
1059
|
+
"grab",
|
|
1060
|
+
"rotate",
|
|
1061
|
+
"grabbing",
|
|
1062
|
+
"resize-edge",
|
|
1063
|
+
"resize-corner",
|
|
1064
|
+
"text",
|
|
1065
|
+
"move",
|
|
1066
|
+
"ew-resize",
|
|
1067
|
+
"ns-resize",
|
|
1068
|
+
"nesw-resize",
|
|
1069
|
+
"nwse-resize",
|
|
1070
|
+
"nesw-rotate",
|
|
1071
|
+
"nwse-rotate",
|
|
1072
|
+
"swne-rotate",
|
|
1073
|
+
"senw-rotate",
|
|
1074
|
+
"zoom-in",
|
|
1075
|
+
"zoom-out"
|
|
1076
|
+
]);
|
|
1077
|
+
var cursorTypeValidator = T.setEnum(TL_CURSOR_TYPES);
|
|
1078
|
+
var cursorValidator = T.object({
|
|
1079
|
+
type: cursorTypeValidator,
|
|
1080
|
+
rotation: T.number
|
|
1081
|
+
});
|
|
1082
|
+
var TL_CANVAS_UI_COLOR_TYPES = /* @__PURE__ */ new Set([
|
|
1083
|
+
"accent",
|
|
1084
|
+
"white",
|
|
1085
|
+
"black",
|
|
1086
|
+
"selection-stroke",
|
|
1087
|
+
"selection-fill",
|
|
1088
|
+
"laser",
|
|
1089
|
+
"muted-1"
|
|
1090
|
+
]);
|
|
1091
|
+
var canvasUiColorTypeValidator = T.setEnum(TL_CANVAS_UI_COLOR_TYPES);
|
|
1092
|
+
|
|
1093
|
+
// src/misc/DrScribble.ts
|
|
1094
|
+
var TL_SCRIBBLE_STATES = /* @__PURE__ */ new Set([
|
|
1095
|
+
"starting",
|
|
1096
|
+
"paused",
|
|
1097
|
+
"active",
|
|
1098
|
+
"complete",
|
|
1099
|
+
"stopping"
|
|
1100
|
+
]);
|
|
1101
|
+
var scribbleValidator = T.object({
|
|
1102
|
+
id: T.string,
|
|
1103
|
+
points: T.arrayOf(vecModelValidator),
|
|
1104
|
+
size: T.positiveNumber,
|
|
1105
|
+
color: canvasUiColorTypeValidator,
|
|
1106
|
+
opacity: T.number,
|
|
1107
|
+
state: T.setEnum(TL_SCRIBBLE_STATES),
|
|
1108
|
+
delay: T.number,
|
|
1109
|
+
shrink: T.number,
|
|
1110
|
+
taper: T.boolean
|
|
1111
|
+
});
|
|
1112
|
+
var pageIdValidator = idValidator("page");
|
|
1113
|
+
var pageValidator = T.model(
|
|
1114
|
+
"page",
|
|
1115
|
+
T.object({
|
|
1116
|
+
typeName: T.literal("page"),
|
|
1117
|
+
id: pageIdValidator,
|
|
1118
|
+
name: T.string,
|
|
1119
|
+
index: T.indexKey,
|
|
1120
|
+
meta: T.jsonValue
|
|
1121
|
+
})
|
|
1122
|
+
);
|
|
1123
|
+
var pageVersions = createMigrationIds("com.draw.page", {
|
|
1124
|
+
AddMeta: 1
|
|
1125
|
+
});
|
|
1126
|
+
var pageMigrations = createRecordMigrationSequence({
|
|
1127
|
+
sequenceId: "com.draw.page",
|
|
1128
|
+
recordType: "page",
|
|
1129
|
+
sequence: [
|
|
1130
|
+
{
|
|
1131
|
+
id: pageVersions.AddMeta,
|
|
1132
|
+
up: (record) => {
|
|
1133
|
+
record.meta = {};
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
]
|
|
1137
|
+
});
|
|
1138
|
+
var PageRecordType = createRecordType("page", {
|
|
1139
|
+
validator: pageValidator,
|
|
1140
|
+
scope: "document"
|
|
1141
|
+
}).withDefaultProperties(() => ({
|
|
1142
|
+
meta: {}
|
|
1143
|
+
}));
|
|
1144
|
+
function isPageId(id) {
|
|
1145
|
+
return PageRecordType.isId(id);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// src/records/DrInstance.ts
|
|
1149
|
+
var shouldKeyBePreservedBetweenSessions = {
|
|
1150
|
+
// This object defines keys that should be preserved across calls to loadSnapshot()
|
|
1151
|
+
id: false,
|
|
1152
|
+
// meta
|
|
1153
|
+
typeName: false,
|
|
1154
|
+
// meta
|
|
1155
|
+
currentPageId: false,
|
|
1156
|
+
// does not preserve because who knows if the page still exists
|
|
1157
|
+
opacityForNextShape: false,
|
|
1158
|
+
// does not preserve because it's a temporary state
|
|
1159
|
+
stylesForNextShape: false,
|
|
1160
|
+
// does not preserve because it's a temporary state
|
|
1161
|
+
followingUserId: false,
|
|
1162
|
+
// does not preserve because it's a temporary state
|
|
1163
|
+
highlightedUserIds: false,
|
|
1164
|
+
// does not preserve because it's a temporary state
|
|
1165
|
+
brush: false,
|
|
1166
|
+
// does not preserve because it's a temporary state
|
|
1167
|
+
cursor: false,
|
|
1168
|
+
// does not preserve because it's a temporary state
|
|
1169
|
+
scribbles: false,
|
|
1170
|
+
// does not preserve because it's a temporary state
|
|
1171
|
+
isFocusMode: true,
|
|
1172
|
+
// preserves because it's a user preference
|
|
1173
|
+
isDebugMode: true,
|
|
1174
|
+
// preserves because it's a user preference
|
|
1175
|
+
isToolLocked: true,
|
|
1176
|
+
// preserves because it's a user preference
|
|
1177
|
+
exportBackground: true,
|
|
1178
|
+
// preserves because it's a user preference
|
|
1179
|
+
screenBounds: true,
|
|
1180
|
+
// preserves because it's capturing the user's screen state
|
|
1181
|
+
insets: true,
|
|
1182
|
+
// preserves because it's capturing the user's screen state
|
|
1183
|
+
zoomBrush: false,
|
|
1184
|
+
// does not preserve because it's a temporary state
|
|
1185
|
+
chatMessage: false,
|
|
1186
|
+
// does not preserve because it's a temporary state
|
|
1187
|
+
isChatting: false,
|
|
1188
|
+
// does not preserve because it's a temporary state
|
|
1189
|
+
isPenMode: false,
|
|
1190
|
+
// does not preserve because it's a temporary state
|
|
1191
|
+
isGridMode: true,
|
|
1192
|
+
// preserves because it's a user preference
|
|
1193
|
+
isFocused: true,
|
|
1194
|
+
// preserves because obviously
|
|
1195
|
+
devicePixelRatio: true,
|
|
1196
|
+
// preserves because it captures the user's screen state
|
|
1197
|
+
isCoarsePointer: true,
|
|
1198
|
+
// preserves because it captures the user's screen state
|
|
1199
|
+
isHoveringCanvas: false,
|
|
1200
|
+
// does not preserve because it's a temporary state
|
|
1201
|
+
openMenus: false,
|
|
1202
|
+
// does not preserve because it's a temporary state
|
|
1203
|
+
isChangingStyle: false,
|
|
1204
|
+
// does not preserve because it's a temporary state
|
|
1205
|
+
isReadonly: true,
|
|
1206
|
+
// preserves because it's a config option
|
|
1207
|
+
meta: false,
|
|
1208
|
+
// does not preserve because who knows what's in there, leave it up to sdk users to save and reinstate
|
|
1209
|
+
duplicateProps: false,
|
|
1210
|
+
//
|
|
1211
|
+
cameraState: false
|
|
1212
|
+
// does not preserve because it's a temporary state
|
|
1213
|
+
};
|
|
1214
|
+
function pluckPreservingValues(val) {
|
|
1215
|
+
return val ? filterEntries(val, (key) => {
|
|
1216
|
+
return shouldKeyBePreservedBetweenSessions[key];
|
|
1217
|
+
}) : null;
|
|
1218
|
+
}
|
|
1219
|
+
idValidator("instance");
|
|
1220
|
+
function createInstanceRecordType(stylesById) {
|
|
1221
|
+
const stylesForNextShapeValidators = {};
|
|
1222
|
+
for (const [id, style] of stylesById) {
|
|
1223
|
+
stylesForNextShapeValidators[id] = T.optional(style);
|
|
1224
|
+
}
|
|
1225
|
+
const instanceTypeValidator = T.model(
|
|
1226
|
+
"instance",
|
|
1227
|
+
T.object({
|
|
1228
|
+
typeName: T.literal("instance"),
|
|
1229
|
+
id: idValidator("instance"),
|
|
1230
|
+
currentPageId: pageIdValidator,
|
|
1231
|
+
followingUserId: T.string.nullable(),
|
|
1232
|
+
brush: boxModelValidator.nullable(),
|
|
1233
|
+
opacityForNextShape: opacityValidator,
|
|
1234
|
+
stylesForNextShape: T.object(stylesForNextShapeValidators),
|
|
1235
|
+
cursor: cursorValidator,
|
|
1236
|
+
scribbles: T.arrayOf(scribbleValidator),
|
|
1237
|
+
isFocusMode: T.boolean,
|
|
1238
|
+
isDebugMode: T.boolean,
|
|
1239
|
+
isToolLocked: T.boolean,
|
|
1240
|
+
exportBackground: T.boolean,
|
|
1241
|
+
screenBounds: boxModelValidator,
|
|
1242
|
+
insets: T.arrayOf(T.boolean),
|
|
1243
|
+
zoomBrush: boxModelValidator.nullable(),
|
|
1244
|
+
isPenMode: T.boolean,
|
|
1245
|
+
isGridMode: T.boolean,
|
|
1246
|
+
chatMessage: T.string,
|
|
1247
|
+
isChatting: T.boolean,
|
|
1248
|
+
highlightedUserIds: T.arrayOf(T.string),
|
|
1249
|
+
isFocused: T.boolean,
|
|
1250
|
+
devicePixelRatio: T.number,
|
|
1251
|
+
isCoarsePointer: T.boolean,
|
|
1252
|
+
isHoveringCanvas: T.boolean.nullable(),
|
|
1253
|
+
openMenus: T.arrayOf(T.string),
|
|
1254
|
+
isChangingStyle: T.boolean,
|
|
1255
|
+
isReadonly: T.boolean,
|
|
1256
|
+
meta: T.jsonValue,
|
|
1257
|
+
duplicateProps: T.object({
|
|
1258
|
+
shapeIds: T.arrayOf(idValidator("shape")),
|
|
1259
|
+
offset: T.object({
|
|
1260
|
+
x: T.number,
|
|
1261
|
+
y: T.number
|
|
1262
|
+
})
|
|
1263
|
+
}).nullable(),
|
|
1264
|
+
cameraState: T.literalEnum("idle", "moving")
|
|
1265
|
+
})
|
|
1266
|
+
);
|
|
1267
|
+
return createRecordType("instance", {
|
|
1268
|
+
validator: instanceTypeValidator,
|
|
1269
|
+
scope: "session",
|
|
1270
|
+
ephemeralKeys: {
|
|
1271
|
+
currentPageId: false,
|
|
1272
|
+
meta: false,
|
|
1273
|
+
followingUserId: true,
|
|
1274
|
+
opacityForNextShape: true,
|
|
1275
|
+
stylesForNextShape: true,
|
|
1276
|
+
brush: true,
|
|
1277
|
+
cursor: true,
|
|
1278
|
+
scribbles: true,
|
|
1279
|
+
isFocusMode: true,
|
|
1280
|
+
isDebugMode: true,
|
|
1281
|
+
isToolLocked: true,
|
|
1282
|
+
exportBackground: true,
|
|
1283
|
+
screenBounds: true,
|
|
1284
|
+
insets: true,
|
|
1285
|
+
zoomBrush: true,
|
|
1286
|
+
isPenMode: true,
|
|
1287
|
+
isGridMode: true,
|
|
1288
|
+
chatMessage: true,
|
|
1289
|
+
isChatting: true,
|
|
1290
|
+
highlightedUserIds: true,
|
|
1291
|
+
isFocused: true,
|
|
1292
|
+
devicePixelRatio: true,
|
|
1293
|
+
isCoarsePointer: true,
|
|
1294
|
+
isHoveringCanvas: true,
|
|
1295
|
+
openMenus: true,
|
|
1296
|
+
isChangingStyle: true,
|
|
1297
|
+
isReadonly: true,
|
|
1298
|
+
duplicateProps: true,
|
|
1299
|
+
cameraState: true
|
|
1300
|
+
}
|
|
1301
|
+
}).withDefaultProperties(
|
|
1302
|
+
() => ({
|
|
1303
|
+
followingUserId: null,
|
|
1304
|
+
opacityForNextShape: 1,
|
|
1305
|
+
stylesForNextShape: {},
|
|
1306
|
+
brush: null,
|
|
1307
|
+
scribbles: [],
|
|
1308
|
+
cursor: {
|
|
1309
|
+
type: "default",
|
|
1310
|
+
rotation: 0
|
|
1311
|
+
},
|
|
1312
|
+
isFocusMode: false,
|
|
1313
|
+
exportBackground: false,
|
|
1314
|
+
isDebugMode: false,
|
|
1315
|
+
isToolLocked: false,
|
|
1316
|
+
screenBounds: { x: 0, y: 0, w: 1080, h: 720 },
|
|
1317
|
+
insets: [false, false, false, false],
|
|
1318
|
+
zoomBrush: null,
|
|
1319
|
+
isGridMode: false,
|
|
1320
|
+
isPenMode: false,
|
|
1321
|
+
chatMessage: "",
|
|
1322
|
+
isChatting: false,
|
|
1323
|
+
highlightedUserIds: [],
|
|
1324
|
+
isFocused: false,
|
|
1325
|
+
devicePixelRatio: typeof window === "undefined" ? 1 : window.devicePixelRatio,
|
|
1326
|
+
isCoarsePointer: false,
|
|
1327
|
+
isHoveringCanvas: null,
|
|
1328
|
+
openMenus: [],
|
|
1329
|
+
isChangingStyle: false,
|
|
1330
|
+
isReadonly: false,
|
|
1331
|
+
meta: {},
|
|
1332
|
+
duplicateProps: null,
|
|
1333
|
+
cameraState: "idle"
|
|
1334
|
+
})
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
var instanceVersions = createMigrationIds("com.draw.instance", {
|
|
1338
|
+
AddTransparentExportBgs: 1,
|
|
1339
|
+
RemoveDialog: 2,
|
|
1340
|
+
AddToolLockMode: 3,
|
|
1341
|
+
RemoveExtraPropsForNextShape: 4,
|
|
1342
|
+
AddLabelColor: 5,
|
|
1343
|
+
AddFollowingUserId: 6,
|
|
1344
|
+
RemoveAlignJustify: 7,
|
|
1345
|
+
AddZoom: 8,
|
|
1346
|
+
AddVerticalAlign: 9,
|
|
1347
|
+
AddScribbleDelay: 10,
|
|
1348
|
+
RemoveUserId: 11,
|
|
1349
|
+
AddIsPenModeAndIsGridMode: 12,
|
|
1350
|
+
HoistOpacity: 13,
|
|
1351
|
+
AddChat: 14,
|
|
1352
|
+
AddHighlightedUserIds: 15,
|
|
1353
|
+
ReplacePropsForNextShapeWithStylesForNextShape: 16,
|
|
1354
|
+
AddMeta: 17,
|
|
1355
|
+
RemoveCursorColor: 18,
|
|
1356
|
+
AddLonelyProperties: 19,
|
|
1357
|
+
ReadOnlyReadonly: 20,
|
|
1358
|
+
AddHoveringCanvas: 21,
|
|
1359
|
+
AddScribbles: 22,
|
|
1360
|
+
AddInset: 23,
|
|
1361
|
+
AddDuplicateProps: 24,
|
|
1362
|
+
RemoveCanMoveCamera: 25,
|
|
1363
|
+
AddCameraState: 26
|
|
1364
|
+
});
|
|
1365
|
+
var instanceMigrations = createRecordMigrationSequence({
|
|
1366
|
+
sequenceId: "com.draw.instance",
|
|
1367
|
+
recordType: "instance",
|
|
1368
|
+
sequence: [
|
|
1369
|
+
{
|
|
1370
|
+
id: instanceVersions.AddTransparentExportBgs,
|
|
1371
|
+
up: (instance) => {
|
|
1372
|
+
return { ...instance, exportBackground: true };
|
|
1373
|
+
}
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
id: instanceVersions.RemoveDialog,
|
|
1377
|
+
up: ({ dialog: _, ...instance }) => {
|
|
1378
|
+
return instance;
|
|
1379
|
+
}
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
id: instanceVersions.AddToolLockMode,
|
|
1383
|
+
up: (instance) => {
|
|
1384
|
+
return { ...instance, isToolLocked: false };
|
|
1385
|
+
}
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
id: instanceVersions.RemoveExtraPropsForNextShape,
|
|
1389
|
+
up: ({ propsForNextShape, ...instance }) => {
|
|
1390
|
+
return {
|
|
1391
|
+
...instance,
|
|
1392
|
+
propsForNextShape: Object.fromEntries(
|
|
1393
|
+
Object.entries(propsForNextShape).filter(
|
|
1394
|
+
([key]) => [
|
|
1395
|
+
"color",
|
|
1396
|
+
"labelColor",
|
|
1397
|
+
"dash",
|
|
1398
|
+
"fill",
|
|
1399
|
+
"size",
|
|
1400
|
+
"font",
|
|
1401
|
+
"align",
|
|
1402
|
+
"verticalAlign",
|
|
1403
|
+
"icon",
|
|
1404
|
+
"geo",
|
|
1405
|
+
"arrowheadStart",
|
|
1406
|
+
"arrowheadEnd",
|
|
1407
|
+
"spline"
|
|
1408
|
+
].includes(key)
|
|
1409
|
+
)
|
|
1410
|
+
)
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
id: instanceVersions.AddLabelColor,
|
|
1416
|
+
up: ({ propsForNextShape, ...instance }) => {
|
|
1417
|
+
return {
|
|
1418
|
+
...instance,
|
|
1419
|
+
propsForNextShape: {
|
|
1420
|
+
...propsForNextShape,
|
|
1421
|
+
labelColor: "black"
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
id: instanceVersions.AddFollowingUserId,
|
|
1428
|
+
up: (instance) => {
|
|
1429
|
+
return { ...instance, followingUserId: null };
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
id: instanceVersions.RemoveAlignJustify,
|
|
1434
|
+
up: (instance) => {
|
|
1435
|
+
let newAlign = instance.propsForNextShape.align;
|
|
1436
|
+
if (newAlign === "justify") {
|
|
1437
|
+
newAlign = "start";
|
|
1438
|
+
}
|
|
1439
|
+
return {
|
|
1440
|
+
...instance,
|
|
1441
|
+
propsForNextShape: {
|
|
1442
|
+
...instance.propsForNextShape,
|
|
1443
|
+
align: newAlign
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
id: instanceVersions.AddZoom,
|
|
1450
|
+
up: (instance) => {
|
|
1451
|
+
return { ...instance, zoomBrush: null };
|
|
1452
|
+
}
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
id: instanceVersions.AddVerticalAlign,
|
|
1456
|
+
up: (instance) => {
|
|
1457
|
+
return {
|
|
1458
|
+
...instance,
|
|
1459
|
+
propsForNextShape: {
|
|
1460
|
+
...instance.propsForNextShape,
|
|
1461
|
+
verticalAlign: "middle"
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
},
|
|
1466
|
+
{
|
|
1467
|
+
id: instanceVersions.AddScribbleDelay,
|
|
1468
|
+
up: (instance) => {
|
|
1469
|
+
if (instance.scribble !== null) {
|
|
1470
|
+
return { ...instance, scribble: { ...instance.scribble, delay: 0 } };
|
|
1471
|
+
}
|
|
1472
|
+
return { ...instance };
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
id: instanceVersions.RemoveUserId,
|
|
1477
|
+
up: ({ userId: _, ...instance }) => {
|
|
1478
|
+
return instance;
|
|
1479
|
+
}
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
id: instanceVersions.AddIsPenModeAndIsGridMode,
|
|
1483
|
+
up: (instance) => {
|
|
1484
|
+
return { ...instance, isPenMode: false, isGridMode: false };
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
id: instanceVersions.HoistOpacity,
|
|
1489
|
+
up: ({ propsForNextShape: { opacity, ...propsForNextShape }, ...instance }) => {
|
|
1490
|
+
return { ...instance, opacityForNextShape: Number(opacity ?? "1"), propsForNextShape };
|
|
1491
|
+
}
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
id: instanceVersions.AddChat,
|
|
1495
|
+
up: (instance) => {
|
|
1496
|
+
return { ...instance, chatMessage: "", isChatting: false };
|
|
1497
|
+
}
|
|
1498
|
+
},
|
|
1499
|
+
{
|
|
1500
|
+
id: instanceVersions.AddHighlightedUserIds,
|
|
1501
|
+
up: (instance) => {
|
|
1502
|
+
return { ...instance, highlightedUserIds: [] };
|
|
1503
|
+
}
|
|
1504
|
+
},
|
|
1505
|
+
{
|
|
1506
|
+
id: instanceVersions.ReplacePropsForNextShapeWithStylesForNextShape,
|
|
1507
|
+
up: ({ propsForNextShape: _, ...instance }) => {
|
|
1508
|
+
return { ...instance, stylesForNextShape: {} };
|
|
1509
|
+
}
|
|
1510
|
+
},
|
|
1511
|
+
{
|
|
1512
|
+
id: instanceVersions.AddMeta,
|
|
1513
|
+
up: (record) => {
|
|
1514
|
+
return {
|
|
1515
|
+
...record,
|
|
1516
|
+
meta: {}
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
id: instanceVersions.RemoveCursorColor,
|
|
1522
|
+
up: (record) => {
|
|
1523
|
+
const { color: _, ...cursor } = record.cursor;
|
|
1524
|
+
return {
|
|
1525
|
+
...record,
|
|
1526
|
+
cursor
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
id: instanceVersions.AddLonelyProperties,
|
|
1532
|
+
up: (record) => {
|
|
1533
|
+
return {
|
|
1534
|
+
...record,
|
|
1535
|
+
canMoveCamera: true,
|
|
1536
|
+
isFocused: false,
|
|
1537
|
+
devicePixelRatio: 1,
|
|
1538
|
+
isCoarsePointer: false,
|
|
1539
|
+
openMenus: [],
|
|
1540
|
+
isChangingStyle: false,
|
|
1541
|
+
isReadOnly: false
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
id: instanceVersions.ReadOnlyReadonly,
|
|
1547
|
+
up: ({ isReadOnly: _isReadOnly, ...record }) => {
|
|
1548
|
+
return {
|
|
1549
|
+
...record,
|
|
1550
|
+
isReadonly: _isReadOnly
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
id: instanceVersions.AddHoveringCanvas,
|
|
1556
|
+
up: (record) => {
|
|
1557
|
+
return {
|
|
1558
|
+
...record,
|
|
1559
|
+
isHoveringCanvas: null
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
id: instanceVersions.AddScribbles,
|
|
1565
|
+
up: ({ scribble: _, ...record }) => {
|
|
1566
|
+
return {
|
|
1567
|
+
...record,
|
|
1568
|
+
scribbles: []
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
id: instanceVersions.AddInset,
|
|
1574
|
+
up: (record) => {
|
|
1575
|
+
return {
|
|
1576
|
+
...record,
|
|
1577
|
+
insets: [false, false, false, false]
|
|
1578
|
+
};
|
|
1579
|
+
},
|
|
1580
|
+
down: ({ insets: _, ...record }) => {
|
|
1581
|
+
return {
|
|
1582
|
+
...record
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
id: instanceVersions.AddDuplicateProps,
|
|
1588
|
+
up: (record) => {
|
|
1589
|
+
return {
|
|
1590
|
+
...record,
|
|
1591
|
+
duplicateProps: null
|
|
1592
|
+
};
|
|
1593
|
+
},
|
|
1594
|
+
down: ({ duplicateProps: _, ...record }) => {
|
|
1595
|
+
return {
|
|
1596
|
+
...record
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
},
|
|
1600
|
+
{
|
|
1601
|
+
id: instanceVersions.RemoveCanMoveCamera,
|
|
1602
|
+
up: ({ canMoveCamera: _, ...record }) => {
|
|
1603
|
+
return {
|
|
1604
|
+
...record
|
|
1605
|
+
};
|
|
1606
|
+
},
|
|
1607
|
+
down: (instance) => {
|
|
1608
|
+
return { ...instance, canMoveCamera: true };
|
|
1609
|
+
}
|
|
1610
|
+
},
|
|
1611
|
+
{
|
|
1612
|
+
id: instanceVersions.AddCameraState,
|
|
1613
|
+
up: (record) => {
|
|
1614
|
+
return { ...record, cameraState: "idle" };
|
|
1615
|
+
},
|
|
1616
|
+
down: ({ cameraState: _, ...record }) => {
|
|
1617
|
+
return record;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
]
|
|
1621
|
+
});
|
|
1622
|
+
var DrINSTANCE_ID = "instance:instance";
|
|
1623
|
+
var instancePageStateValidator = T.model(
|
|
1624
|
+
"instance_page_state",
|
|
1625
|
+
T.object({
|
|
1626
|
+
typeName: T.literal("instance_page_state"),
|
|
1627
|
+
id: idValidator("instance_page_state"),
|
|
1628
|
+
pageId: pageIdValidator,
|
|
1629
|
+
selectedShapeIds: T.arrayOf(shapeIdValidator),
|
|
1630
|
+
hintingShapeIds: T.arrayOf(shapeIdValidator),
|
|
1631
|
+
erasingShapeIds: T.arrayOf(shapeIdValidator),
|
|
1632
|
+
hoveredShapeId: shapeIdValidator.nullable(),
|
|
1633
|
+
editingShapeId: shapeIdValidator.nullable(),
|
|
1634
|
+
croppingShapeId: shapeIdValidator.nullable(),
|
|
1635
|
+
focusedGroupId: shapeIdValidator.nullable(),
|
|
1636
|
+
meta: T.jsonValue
|
|
1637
|
+
})
|
|
1638
|
+
);
|
|
1639
|
+
var instancePageStateVersions = createMigrationIds("com.draw.instance_page_state", {
|
|
1640
|
+
AddCroppingId: 1,
|
|
1641
|
+
RemoveInstanceIdAndCameraId: 2,
|
|
1642
|
+
AddMeta: 3,
|
|
1643
|
+
RenameProperties: 4,
|
|
1644
|
+
RenamePropertiesAgain: 5
|
|
1645
|
+
});
|
|
1646
|
+
var instancePageStateMigrations = createRecordMigrationSequence({
|
|
1647
|
+
sequenceId: "com.draw.instance_page_state",
|
|
1648
|
+
recordType: "instance_page_state",
|
|
1649
|
+
sequence: [
|
|
1650
|
+
{
|
|
1651
|
+
id: instancePageStateVersions.AddCroppingId,
|
|
1652
|
+
up(instance) {
|
|
1653
|
+
instance.croppingShapeId = null;
|
|
1654
|
+
}
|
|
1655
|
+
},
|
|
1656
|
+
{
|
|
1657
|
+
id: instancePageStateVersions.RemoveInstanceIdAndCameraId,
|
|
1658
|
+
up(instance) {
|
|
1659
|
+
delete instance.instanceId;
|
|
1660
|
+
delete instance.cameraId;
|
|
1661
|
+
}
|
|
1662
|
+
},
|
|
1663
|
+
{
|
|
1664
|
+
id: instancePageStateVersions.AddMeta,
|
|
1665
|
+
up: (record) => {
|
|
1666
|
+
record.meta = {};
|
|
1667
|
+
}
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
id: instancePageStateVersions.RenameProperties,
|
|
1671
|
+
// this migration is cursed: it was written wrong and doesn't do anything.
|
|
1672
|
+
// rather than replace it, I've added another migration below that fixes it.
|
|
1673
|
+
up: (_record) => {
|
|
1674
|
+
},
|
|
1675
|
+
down: (_record) => {
|
|
1676
|
+
}
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
id: instancePageStateVersions.RenamePropertiesAgain,
|
|
1680
|
+
up: (record) => {
|
|
1681
|
+
record.selectedShapeIds = record.selectedIds;
|
|
1682
|
+
delete record.selectedIds;
|
|
1683
|
+
record.hintingShapeIds = record.hintingIds;
|
|
1684
|
+
delete record.hintingIds;
|
|
1685
|
+
record.erasingShapeIds = record.erasingIds;
|
|
1686
|
+
delete record.erasingIds;
|
|
1687
|
+
record.hoveredShapeId = record.hoveredId;
|
|
1688
|
+
delete record.hoveredId;
|
|
1689
|
+
record.editingShapeId = record.editingId;
|
|
1690
|
+
delete record.editingId;
|
|
1691
|
+
record.croppingShapeId = record.croppingShapeId ?? record.croppingId ?? null;
|
|
1692
|
+
delete record.croppingId;
|
|
1693
|
+
record.focusedGroupId = record.focusLayerId;
|
|
1694
|
+
delete record.focusLayerId;
|
|
1695
|
+
},
|
|
1696
|
+
down: (record) => {
|
|
1697
|
+
record.selectedIds = record.selectedShapeIds;
|
|
1698
|
+
delete record.selectedShapeIds;
|
|
1699
|
+
record.hintingIds = record.hintingShapeIds;
|
|
1700
|
+
delete record.hintingShapeIds;
|
|
1701
|
+
record.erasingIds = record.erasingShapeIds;
|
|
1702
|
+
delete record.erasingShapeIds;
|
|
1703
|
+
record.hoveredId = record.hoveredShapeId;
|
|
1704
|
+
delete record.hoveredShapeId;
|
|
1705
|
+
record.editingId = record.editingShapeId;
|
|
1706
|
+
delete record.editingShapeId;
|
|
1707
|
+
record.croppingId = record.croppingShapeId;
|
|
1708
|
+
delete record.croppingShapeId;
|
|
1709
|
+
record.focusLayerId = record.focusedGroupId;
|
|
1710
|
+
delete record.focusedGroupId;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
]
|
|
1714
|
+
});
|
|
1715
|
+
var InstancePageStateRecordType = createRecordType(
|
|
1716
|
+
"instance_page_state",
|
|
1717
|
+
{
|
|
1718
|
+
validator: instancePageStateValidator,
|
|
1719
|
+
scope: "session",
|
|
1720
|
+
ephemeralKeys: {
|
|
1721
|
+
pageId: false,
|
|
1722
|
+
selectedShapeIds: false,
|
|
1723
|
+
editingShapeId: false,
|
|
1724
|
+
croppingShapeId: false,
|
|
1725
|
+
meta: false,
|
|
1726
|
+
hintingShapeIds: true,
|
|
1727
|
+
erasingShapeIds: true,
|
|
1728
|
+
hoveredShapeId: true,
|
|
1729
|
+
focusedGroupId: true
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
).withDefaultProperties(
|
|
1733
|
+
() => ({
|
|
1734
|
+
editingShapeId: null,
|
|
1735
|
+
croppingShapeId: null,
|
|
1736
|
+
selectedShapeIds: [],
|
|
1737
|
+
hoveredShapeId: null,
|
|
1738
|
+
erasingShapeIds: [],
|
|
1739
|
+
hintingShapeIds: [],
|
|
1740
|
+
focusedGroupId: null,
|
|
1741
|
+
meta: {}
|
|
1742
|
+
})
|
|
1743
|
+
);
|
|
1744
|
+
var pointerValidator = T.model(
|
|
1745
|
+
"pointer",
|
|
1746
|
+
T.object({
|
|
1747
|
+
typeName: T.literal("pointer"),
|
|
1748
|
+
id: idValidator("pointer"),
|
|
1749
|
+
x: T.number,
|
|
1750
|
+
y: T.number,
|
|
1751
|
+
lastActivityTimestamp: T.number,
|
|
1752
|
+
meta: T.jsonValue
|
|
1753
|
+
})
|
|
1754
|
+
);
|
|
1755
|
+
var pointerVersions = createMigrationIds("com.draw.pointer", {
|
|
1756
|
+
AddMeta: 1
|
|
1757
|
+
});
|
|
1758
|
+
var pointerMigrations = createRecordMigrationSequence({
|
|
1759
|
+
sequenceId: "com.draw.pointer",
|
|
1760
|
+
recordType: "pointer",
|
|
1761
|
+
sequence: [
|
|
1762
|
+
{
|
|
1763
|
+
id: pointerVersions.AddMeta,
|
|
1764
|
+
up: (record) => {
|
|
1765
|
+
record.meta = {};
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
]
|
|
1769
|
+
});
|
|
1770
|
+
var PointerRecordType = createRecordType("pointer", {
|
|
1771
|
+
validator: pointerValidator,
|
|
1772
|
+
scope: "session"
|
|
1773
|
+
}).withDefaultProperties(
|
|
1774
|
+
() => ({
|
|
1775
|
+
x: 0,
|
|
1776
|
+
y: 0,
|
|
1777
|
+
lastActivityTimestamp: 0,
|
|
1778
|
+
meta: {}
|
|
1779
|
+
})
|
|
1780
|
+
);
|
|
1781
|
+
var DrPOINTER_ID = PointerRecordType.createId("pointer");
|
|
1782
|
+
var instancePresenceValidator = T.model(
|
|
1783
|
+
"instance_presence",
|
|
1784
|
+
T.object({
|
|
1785
|
+
typeName: T.literal("instance_presence"),
|
|
1786
|
+
id: idValidator("instance_presence"),
|
|
1787
|
+
userId: T.string,
|
|
1788
|
+
userName: T.string,
|
|
1789
|
+
lastActivityTimestamp: T.number.nullable(),
|
|
1790
|
+
followingUserId: T.string.nullable(),
|
|
1791
|
+
cursor: T.object({
|
|
1792
|
+
x: T.number,
|
|
1793
|
+
y: T.number,
|
|
1794
|
+
type: cursorTypeValidator,
|
|
1795
|
+
rotation: T.number
|
|
1796
|
+
}).nullable(),
|
|
1797
|
+
color: T.string,
|
|
1798
|
+
camera: T.object({
|
|
1799
|
+
x: T.number,
|
|
1800
|
+
y: T.number,
|
|
1801
|
+
z: T.number
|
|
1802
|
+
}).nullable(),
|
|
1803
|
+
screenBounds: boxModelValidator.nullable(),
|
|
1804
|
+
selectedShapeIds: T.arrayOf(idValidator("shape")),
|
|
1805
|
+
currentPageId: idValidator("page"),
|
|
1806
|
+
brush: boxModelValidator.nullable(),
|
|
1807
|
+
scribbles: T.arrayOf(scribbleValidator),
|
|
1808
|
+
chatMessage: T.string,
|
|
1809
|
+
meta: T.jsonValue
|
|
1810
|
+
})
|
|
1811
|
+
);
|
|
1812
|
+
var instancePresenceVersions = createMigrationIds("com.draw.instance_presence", {
|
|
1813
|
+
AddScribbleDelay: 1,
|
|
1814
|
+
RemoveInstanceId: 2,
|
|
1815
|
+
AddChatMessage: 3,
|
|
1816
|
+
AddMeta: 4,
|
|
1817
|
+
RenameSelectedShapeIds: 5,
|
|
1818
|
+
NullableCameraCursor: 6
|
|
1819
|
+
});
|
|
1820
|
+
var instancePresenceMigrations = createRecordMigrationSequence({
|
|
1821
|
+
sequenceId: "com.draw.instance_presence",
|
|
1822
|
+
recordType: "instance_presence",
|
|
1823
|
+
sequence: [
|
|
1824
|
+
{
|
|
1825
|
+
id: instancePresenceVersions.AddScribbleDelay,
|
|
1826
|
+
up: (instance) => {
|
|
1827
|
+
if (instance.scribble !== null) {
|
|
1828
|
+
instance.scribble.delay = 0;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
},
|
|
1832
|
+
{
|
|
1833
|
+
id: instancePresenceVersions.RemoveInstanceId,
|
|
1834
|
+
up: (instance) => {
|
|
1835
|
+
delete instance.instanceId;
|
|
1836
|
+
}
|
|
1837
|
+
},
|
|
1838
|
+
{
|
|
1839
|
+
id: instancePresenceVersions.AddChatMessage,
|
|
1840
|
+
up: (instance) => {
|
|
1841
|
+
instance.chatMessage = "";
|
|
1842
|
+
}
|
|
1843
|
+
},
|
|
1844
|
+
{
|
|
1845
|
+
id: instancePresenceVersions.AddMeta,
|
|
1846
|
+
up: (record) => {
|
|
1847
|
+
record.meta = {};
|
|
1848
|
+
}
|
|
1849
|
+
},
|
|
1850
|
+
{
|
|
1851
|
+
id: instancePresenceVersions.RenameSelectedShapeIds,
|
|
1852
|
+
up: (_record) => {
|
|
1853
|
+
}
|
|
1854
|
+
},
|
|
1855
|
+
{
|
|
1856
|
+
id: instancePresenceVersions.NullableCameraCursor,
|
|
1857
|
+
up: (_record) => {
|
|
1858
|
+
},
|
|
1859
|
+
down: (record) => {
|
|
1860
|
+
if (record.camera === null) {
|
|
1861
|
+
record.camera = { x: 0, y: 0, z: 1 };
|
|
1862
|
+
}
|
|
1863
|
+
if (record.lastActivityTimestamp === null) {
|
|
1864
|
+
record.lastActivityTimestamp = 0;
|
|
1865
|
+
}
|
|
1866
|
+
if (record.cursor === null) {
|
|
1867
|
+
record.cursor = { type: "default", x: 0, y: 0, rotation: 0 };
|
|
1868
|
+
}
|
|
1869
|
+
if (record.screenBounds === null) {
|
|
1870
|
+
record.screenBounds = { x: 0, y: 0, w: 1, h: 1 };
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
]
|
|
1875
|
+
});
|
|
1876
|
+
var InstancePresenceRecordType = createRecordType(
|
|
1877
|
+
"instance_presence",
|
|
1878
|
+
{
|
|
1879
|
+
validator: instancePresenceValidator,
|
|
1880
|
+
scope: "presence"
|
|
1881
|
+
}
|
|
1882
|
+
).withDefaultProperties(() => ({
|
|
1883
|
+
lastActivityTimestamp: null,
|
|
1884
|
+
followingUserId: null,
|
|
1885
|
+
color: "#FF0000",
|
|
1886
|
+
camera: null,
|
|
1887
|
+
cursor: null,
|
|
1888
|
+
screenBounds: null,
|
|
1889
|
+
selectedShapeIds: [],
|
|
1890
|
+
brush: null,
|
|
1891
|
+
scribbles: [],
|
|
1892
|
+
chatMessage: "",
|
|
1893
|
+
meta: {}
|
|
1894
|
+
}));
|
|
1895
|
+
|
|
1896
|
+
// src/createPresenceStateDerivation.ts
|
|
1897
|
+
function createPresenceStateDerivation($user, opts) {
|
|
1898
|
+
const { instanceId, getUserPresence: _getUserPresence } = opts ?? {};
|
|
1899
|
+
const getUserPresence = _getUserPresence ?? getDefaultUserPresence;
|
|
1900
|
+
return (store) => {
|
|
1901
|
+
return computed("instancePresence", () => {
|
|
1902
|
+
const user = $user.get();
|
|
1903
|
+
if (!user) return null;
|
|
1904
|
+
const state = getUserPresence(store, user);
|
|
1905
|
+
if (!state) return null;
|
|
1906
|
+
return InstancePresenceRecordType.create({
|
|
1907
|
+
...state,
|
|
1908
|
+
id: instanceId ?? InstancePresenceRecordType.createId(store.id)
|
|
1909
|
+
});
|
|
1910
|
+
});
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
function getDefaultUserPresence(store, user) {
|
|
1914
|
+
const instance = store.get(DrINSTANCE_ID);
|
|
1915
|
+
const pageState = store.get(InstancePageStateRecordType.createId(instance?.currentPageId));
|
|
1916
|
+
const camera = store.get(CameraRecordType.createId(instance?.currentPageId));
|
|
1917
|
+
const pointer = store.get(DrPOINTER_ID);
|
|
1918
|
+
if (!pageState || !instance || !camera || !pointer) {
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
return {
|
|
1922
|
+
selectedShapeIds: pageState.selectedShapeIds,
|
|
1923
|
+
brush: instance.brush,
|
|
1924
|
+
scribbles: instance.scribbles,
|
|
1925
|
+
userId: user.id,
|
|
1926
|
+
userName: user.name,
|
|
1927
|
+
followingUserId: instance.followingUserId,
|
|
1928
|
+
camera: {
|
|
1929
|
+
x: camera.x,
|
|
1930
|
+
y: camera.y,
|
|
1931
|
+
z: camera.z
|
|
1932
|
+
},
|
|
1933
|
+
color: user.color || "#FF0000",
|
|
1934
|
+
currentPageId: instance.currentPageId,
|
|
1935
|
+
cursor: {
|
|
1936
|
+
x: pointer.x,
|
|
1937
|
+
y: pointer.y,
|
|
1938
|
+
rotation: instance.cursor.rotation,
|
|
1939
|
+
type: instance.cursor.type
|
|
1940
|
+
},
|
|
1941
|
+
lastActivityTimestamp: pointer.lastActivityTimestamp,
|
|
1942
|
+
screenBounds: instance.screenBounds,
|
|
1943
|
+
chatMessage: instance.chatMessage,
|
|
1944
|
+
meta: {}
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
var assetVersions = createMigrationIds("com.draw.asset", {
|
|
1948
|
+
AddMeta: 1
|
|
1949
|
+
});
|
|
1950
|
+
var assetMigrations = createRecordMigrationSequence({
|
|
1951
|
+
sequenceId: "com.draw.asset",
|
|
1952
|
+
recordType: "asset",
|
|
1953
|
+
sequence: [
|
|
1954
|
+
{
|
|
1955
|
+
id: assetVersions.AddMeta,
|
|
1956
|
+
up: (record) => {
|
|
1957
|
+
record.meta = {};
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
]
|
|
1961
|
+
});
|
|
1962
|
+
function createAssetRecordType(assets) {
|
|
1963
|
+
return createRecordType("asset", {
|
|
1964
|
+
scope: "document",
|
|
1965
|
+
validator: T.model(
|
|
1966
|
+
"asset",
|
|
1967
|
+
T.union(
|
|
1968
|
+
"type",
|
|
1969
|
+
mapObjectMapValues(
|
|
1970
|
+
assets,
|
|
1971
|
+
(type, { props, meta }) => createAssetValidator(type, props, meta)
|
|
1972
|
+
)
|
|
1973
|
+
)
|
|
1974
|
+
)
|
|
1975
|
+
}).withDefaultProperties(() => ({
|
|
1976
|
+
meta: {}
|
|
1977
|
+
}));
|
|
1978
|
+
}
|
|
1979
|
+
var AssetRecordType = createRecordType("asset", {
|
|
1980
|
+
scope: "document"
|
|
1981
|
+
}).withDefaultProperties(() => ({
|
|
1982
|
+
meta: {}
|
|
1983
|
+
}));
|
|
1984
|
+
function createAssetPropsMigrationSequence(migrations) {
|
|
1985
|
+
return migrations;
|
|
1986
|
+
}
|
|
1987
|
+
function createAssetPropsMigrationIds(assetType, ids) {
|
|
1988
|
+
return mapObjectMapValues(ids, (_k, v) => `com.draw.asset.${assetType}/${v}`);
|
|
1989
|
+
}
|
|
1990
|
+
function createCustomRecordType(typeName, config) {
|
|
1991
|
+
return createRecordType(typeName, {
|
|
1992
|
+
scope: config.scope,
|
|
1993
|
+
validator: config.validator
|
|
1994
|
+
}).withDefaultProperties(config.createDefaultProperties ?? (() => ({})));
|
|
1995
|
+
}
|
|
1996
|
+
function processCustomRecordMigrations(records) {
|
|
1997
|
+
const result = [];
|
|
1998
|
+
for (const [typeName, config] of Object.entries(records)) {
|
|
1999
|
+
const sequenceId = `com.draw.${typeName}`;
|
|
2000
|
+
const { migrations } = config;
|
|
2001
|
+
if (!migrations) {
|
|
2002
|
+
result.push(
|
|
2003
|
+
createMigrationSequence({
|
|
2004
|
+
sequenceId,
|
|
2005
|
+
retroactive: true,
|
|
2006
|
+
sequence: []
|
|
2007
|
+
})
|
|
2008
|
+
);
|
|
2009
|
+
} else if ("sequenceId" in migrations) {
|
|
2010
|
+
assert(
|
|
2011
|
+
sequenceId === migrations.sequenceId,
|
|
2012
|
+
`sequenceId mismatch for ${typeName} custom record migrations. Expected '${sequenceId}', got '${migrations.sequenceId}'`
|
|
2013
|
+
);
|
|
2014
|
+
result.push(migrations);
|
|
2015
|
+
} else if ("sequence" in migrations) {
|
|
2016
|
+
result.push(
|
|
2017
|
+
createMigrationSequence({
|
|
2018
|
+
sequenceId,
|
|
2019
|
+
retroactive: true,
|
|
2020
|
+
sequence: migrations.sequence.map((m) => {
|
|
2021
|
+
if (!("id" in m)) return m;
|
|
2022
|
+
return {
|
|
2023
|
+
id: m.id,
|
|
2024
|
+
dependsOn: m.dependsOn,
|
|
2025
|
+
scope: "record",
|
|
2026
|
+
filter: (r) => r.typeName === typeName,
|
|
2027
|
+
up: (record) => {
|
|
2028
|
+
const result2 = m.up(record);
|
|
2029
|
+
if (result2) return result2;
|
|
2030
|
+
},
|
|
2031
|
+
down: typeof m.down === "function" ? (record) => {
|
|
2032
|
+
const result2 = m.down(record);
|
|
2033
|
+
if (result2) return result2;
|
|
2034
|
+
} : void 0
|
|
2035
|
+
};
|
|
2036
|
+
})
|
|
2037
|
+
})
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
return result;
|
|
2042
|
+
}
|
|
2043
|
+
function createCustomRecordMigrationIds(recordType, ids) {
|
|
2044
|
+
return mapObjectMapValues(ids, (_k, v) => `com.draw.${recordType}/${v}`);
|
|
2045
|
+
}
|
|
2046
|
+
function createCustomRecordMigrationSequence(migrations) {
|
|
2047
|
+
return migrations;
|
|
2048
|
+
}
|
|
2049
|
+
function createCustomRecordId(typeName, id) {
|
|
2050
|
+
return `${typeName}:${id ?? uniqueId()}`;
|
|
2051
|
+
}
|
|
2052
|
+
function isCustomRecordId(typeName, id) {
|
|
2053
|
+
if (!id) return false;
|
|
2054
|
+
return id.startsWith(`${typeName}:`);
|
|
2055
|
+
}
|
|
2056
|
+
function isCustomRecord(typeName, record) {
|
|
2057
|
+
if (!record) return false;
|
|
2058
|
+
return record.typeName === typeName;
|
|
2059
|
+
}
|
|
2060
|
+
var documentValidator = T.model(
|
|
2061
|
+
"document",
|
|
2062
|
+
T.object({
|
|
2063
|
+
typeName: T.literal("document"),
|
|
2064
|
+
id: T.literal("document:document"),
|
|
2065
|
+
gridSize: T.number,
|
|
2066
|
+
name: T.string,
|
|
2067
|
+
meta: T.jsonValue
|
|
2068
|
+
})
|
|
2069
|
+
);
|
|
2070
|
+
function isDocument(record) {
|
|
2071
|
+
if (!record) return false;
|
|
2072
|
+
return record.typeName === "document";
|
|
2073
|
+
}
|
|
2074
|
+
var documentVersions = createMigrationIds("com.draw.document", {
|
|
2075
|
+
AddName: 1,
|
|
2076
|
+
AddMeta: 2
|
|
2077
|
+
});
|
|
2078
|
+
var documentMigrations = createRecordMigrationSequence({
|
|
2079
|
+
sequenceId: "com.draw.document",
|
|
2080
|
+
recordType: "document",
|
|
2081
|
+
sequence: [
|
|
2082
|
+
{
|
|
2083
|
+
id: documentVersions.AddName,
|
|
2084
|
+
up: (document) => {
|
|
2085
|
+
document.name = "";
|
|
2086
|
+
},
|
|
2087
|
+
down: (document) => {
|
|
2088
|
+
delete document.name;
|
|
2089
|
+
}
|
|
2090
|
+
},
|
|
2091
|
+
{
|
|
2092
|
+
id: documentVersions.AddMeta,
|
|
2093
|
+
up: (record) => {
|
|
2094
|
+
record.meta = {};
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
]
|
|
2098
|
+
});
|
|
2099
|
+
var DocumentRecordType = createRecordType("document", {
|
|
2100
|
+
validator: documentValidator,
|
|
2101
|
+
scope: "document"
|
|
2102
|
+
}).withDefaultProperties(
|
|
2103
|
+
() => ({
|
|
2104
|
+
gridSize: 10,
|
|
2105
|
+
name: "",
|
|
2106
|
+
meta: {}
|
|
2107
|
+
})
|
|
2108
|
+
);
|
|
2109
|
+
var DrDOCUMENT_ID = DocumentRecordType.createId("document");
|
|
2110
|
+
var userIdValidator = idValidator("user");
|
|
2111
|
+
var userVersions = createMigrationIds("com.draw.user", {
|
|
2112
|
+
Initial: 1
|
|
2113
|
+
});
|
|
2114
|
+
var userMigrations = createRecordMigrationSequence({
|
|
2115
|
+
sequenceId: "com.draw.user",
|
|
2116
|
+
recordType: "user",
|
|
2117
|
+
sequence: [
|
|
2118
|
+
{
|
|
2119
|
+
id: userVersions.Initial,
|
|
2120
|
+
up: (_record) => {
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
]
|
|
2124
|
+
});
|
|
2125
|
+
function createUserRecordType(config) {
|
|
2126
|
+
const metaConfig = config?.meta;
|
|
2127
|
+
const metaValidator = metaConfig ? T.object(
|
|
2128
|
+
Object.fromEntries(
|
|
2129
|
+
Object.entries(metaConfig).map(([key, v]) => [
|
|
2130
|
+
key,
|
|
2131
|
+
new T.Validator((value) => {
|
|
2132
|
+
if (value === void 0) return void 0;
|
|
2133
|
+
return v.validate(value);
|
|
2134
|
+
})
|
|
2135
|
+
])
|
|
2136
|
+
)
|
|
2137
|
+
).allowUnknownProperties() : T.jsonValue;
|
|
2138
|
+
const validator = T.model(
|
|
2139
|
+
"user",
|
|
2140
|
+
T.object({
|
|
2141
|
+
typeName: T.literal("user"),
|
|
2142
|
+
id: userIdValidator,
|
|
2143
|
+
name: T.string,
|
|
2144
|
+
color: T.string,
|
|
2145
|
+
imageUrl: T.string,
|
|
2146
|
+
meta: metaValidator
|
|
2147
|
+
})
|
|
2148
|
+
);
|
|
2149
|
+
return createRecordType("user", {
|
|
2150
|
+
validator,
|
|
2151
|
+
scope: "document"
|
|
2152
|
+
}).withDefaultProperties(() => ({
|
|
2153
|
+
name: "",
|
|
2154
|
+
color: "",
|
|
2155
|
+
imageUrl: "",
|
|
2156
|
+
meta: {}
|
|
2157
|
+
}));
|
|
2158
|
+
}
|
|
2159
|
+
T.model(
|
|
2160
|
+
"user",
|
|
2161
|
+
T.object({
|
|
2162
|
+
typeName: T.literal("user"),
|
|
2163
|
+
id: userIdValidator,
|
|
2164
|
+
name: T.string,
|
|
2165
|
+
color: T.string,
|
|
2166
|
+
imageUrl: T.string,
|
|
2167
|
+
meta: T.jsonValue
|
|
2168
|
+
})
|
|
2169
|
+
);
|
|
2170
|
+
var UserRecordType = createUserRecordType();
|
|
2171
|
+
function isUserId(id) {
|
|
2172
|
+
return UserRecordType.isId(id);
|
|
2173
|
+
}
|
|
2174
|
+
function createUserId(id) {
|
|
2175
|
+
return UserRecordType.createId(id);
|
|
2176
|
+
}
|
|
2177
|
+
var bookmarkShapeProps = {
|
|
2178
|
+
w: T.nonZeroNumber,
|
|
2179
|
+
h: T.nonZeroNumber,
|
|
2180
|
+
assetId: assetIdValidator.nullable(),
|
|
2181
|
+
url: T.linkUrl
|
|
2182
|
+
};
|
|
2183
|
+
var Versions4 = createShapePropsMigrationIds("bookmark", {
|
|
2184
|
+
NullAssetId: 1,
|
|
2185
|
+
MakeUrlsValid: 2
|
|
2186
|
+
});
|
|
2187
|
+
var bookmarkShapeMigrations = createShapePropsMigrationSequence({
|
|
2188
|
+
sequence: [
|
|
2189
|
+
{
|
|
2190
|
+
id: Versions4.NullAssetId,
|
|
2191
|
+
up: (props) => {
|
|
2192
|
+
if (props.assetId === void 0) {
|
|
2193
|
+
props.assetId = null;
|
|
2194
|
+
}
|
|
2195
|
+
},
|
|
2196
|
+
down: "retired"
|
|
2197
|
+
},
|
|
2198
|
+
{
|
|
2199
|
+
id: Versions4.MakeUrlsValid,
|
|
2200
|
+
up: (props) => {
|
|
2201
|
+
if (!T.linkUrl.isValid(props.url)) {
|
|
2202
|
+
props.url = "";
|
|
2203
|
+
}
|
|
2204
|
+
},
|
|
2205
|
+
down: (_props) => {
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
]
|
|
2209
|
+
});
|
|
2210
|
+
var FIRST_POINT_B64_LENGTH = 16;
|
|
2211
|
+
var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
2212
|
+
var B64_LOOKUP = new Uint8Array(128);
|
|
2213
|
+
for (let i = 0; i < 64; i++) {
|
|
2214
|
+
B64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i;
|
|
2215
|
+
}
|
|
2216
|
+
var POW2 = new Float64Array(31);
|
|
2217
|
+
for (let i = 0; i < 31; i++) {
|
|
2218
|
+
POW2[i] = Math.pow(2, i - 15);
|
|
2219
|
+
}
|
|
2220
|
+
var POW2_SUBNORMAL = Math.pow(2, -14) / 1024;
|
|
2221
|
+
var MANTISSA = new Float64Array(1024);
|
|
2222
|
+
for (let i = 0; i < 1024; i++) {
|
|
2223
|
+
MANTISSA[i] = 1 + i / 1024;
|
|
2224
|
+
}
|
|
2225
|
+
function nativeGetFloat16(dataView, offset) {
|
|
2226
|
+
return dataView.getFloat16(offset, true);
|
|
2227
|
+
}
|
|
2228
|
+
function fallbackGetFloat16(dataView, offset) {
|
|
2229
|
+
return float16BitsToNumber(dataView.getUint16(offset, true));
|
|
2230
|
+
}
|
|
2231
|
+
var getFloat16 = typeof DataView.prototype.getFloat16 === "function" ? nativeGetFloat16 : fallbackGetFloat16;
|
|
2232
|
+
function nativeSetFloat16(dataView, offset, value) {
|
|
2233
|
+
dataView.setFloat16(offset, value, true);
|
|
2234
|
+
}
|
|
2235
|
+
function fallbackSetFloat16(dataView, offset, value) {
|
|
2236
|
+
dataView.setUint16(offset, numberToFloat16Bits(value), true);
|
|
2237
|
+
}
|
|
2238
|
+
var setFloat16 = typeof DataView.prototype.setFloat16 === "function" ? nativeSetFloat16 : fallbackSetFloat16;
|
|
2239
|
+
function nativeBase64ToUint8Array(base64) {
|
|
2240
|
+
return Uint8Array.fromBase64(base64);
|
|
2241
|
+
}
|
|
2242
|
+
function fallbackBase64ToUint8Array(base64) {
|
|
2243
|
+
const numBytes = Math.floor(base64.length * 3 / 4);
|
|
2244
|
+
const bytes = new Uint8Array(numBytes);
|
|
2245
|
+
let byteIndex = 0;
|
|
2246
|
+
for (let i = 0; i < base64.length; i += 4) {
|
|
2247
|
+
const c0 = B64_LOOKUP[base64.charCodeAt(i)];
|
|
2248
|
+
const c1 = B64_LOOKUP[base64.charCodeAt(i + 1)];
|
|
2249
|
+
const c2 = B64_LOOKUP[base64.charCodeAt(i + 2)];
|
|
2250
|
+
const c3 = B64_LOOKUP[base64.charCodeAt(i + 3)];
|
|
2251
|
+
const bitmap = c0 << 18 | c1 << 12 | c2 << 6 | c3;
|
|
2252
|
+
bytes[byteIndex++] = bitmap >> 16 & 255;
|
|
2253
|
+
bytes[byteIndex++] = bitmap >> 8 & 255;
|
|
2254
|
+
bytes[byteIndex++] = bitmap & 255;
|
|
2255
|
+
}
|
|
2256
|
+
return bytes;
|
|
2257
|
+
}
|
|
2258
|
+
function nativeUint8ArrayToBase64(uint8Array) {
|
|
2259
|
+
return uint8Array.toBase64();
|
|
2260
|
+
}
|
|
2261
|
+
function fallbackUint8ArrayToBase64(uint8Array) {
|
|
2262
|
+
assert(uint8Array.length % 3 === 0, "Uint8Array length must be a multiple of 3");
|
|
2263
|
+
let result = "";
|
|
2264
|
+
for (let i = 0; i < uint8Array.length; i += 3) {
|
|
2265
|
+
const byte1 = uint8Array[i];
|
|
2266
|
+
const byte2 = uint8Array[i + 1];
|
|
2267
|
+
const byte3 = uint8Array[i + 2];
|
|
2268
|
+
const bitmap = byte1 << 16 | byte2 << 8 | byte3;
|
|
2269
|
+
result += BASE64_CHARS[bitmap >> 18 & 63] + BASE64_CHARS[bitmap >> 12 & 63] + BASE64_CHARS[bitmap >> 6 & 63] + BASE64_CHARS[bitmap & 63];
|
|
2270
|
+
}
|
|
2271
|
+
return result;
|
|
2272
|
+
}
|
|
2273
|
+
var uint8ArrayToBase64 = typeof Uint8Array.prototype.toBase64 === "function" ? nativeUint8ArrayToBase64 : fallbackUint8ArrayToBase64;
|
|
2274
|
+
var base64ToUint8Array = typeof Uint8Array.fromBase64 === "function" ? nativeBase64ToUint8Array : fallbackBase64ToUint8Array;
|
|
2275
|
+
function float16BitsToNumber(bits) {
|
|
2276
|
+
const sign = bits >> 15;
|
|
2277
|
+
const exp = bits >> 10 & 31;
|
|
2278
|
+
const frac = bits & 1023;
|
|
2279
|
+
if (exp === 0) {
|
|
2280
|
+
return sign ? -frac * POW2_SUBNORMAL : frac * POW2_SUBNORMAL;
|
|
2281
|
+
}
|
|
2282
|
+
if (exp === 31) {
|
|
2283
|
+
return frac ? NaN : sign ? -Infinity : Infinity;
|
|
2284
|
+
}
|
|
2285
|
+
const magnitude = POW2[exp] * MANTISSA[frac];
|
|
2286
|
+
return sign ? -magnitude : magnitude;
|
|
2287
|
+
}
|
|
2288
|
+
function numberToFloat16Bits(value) {
|
|
2289
|
+
if (value === 0) return Object.is(value, -0) ? 32768 : 0;
|
|
2290
|
+
if (!Number.isFinite(value)) {
|
|
2291
|
+
if (Number.isNaN(value)) return 32256;
|
|
2292
|
+
return value > 0 ? 31744 : 64512;
|
|
2293
|
+
}
|
|
2294
|
+
const sign = value < 0 ? 1 : 0;
|
|
2295
|
+
value = Math.abs(value);
|
|
2296
|
+
const exp = Math.floor(Math.log2(value));
|
|
2297
|
+
let expBiased = exp + 15;
|
|
2298
|
+
if (expBiased >= 31) {
|
|
2299
|
+
return sign << 15 | 31744;
|
|
2300
|
+
}
|
|
2301
|
+
if (expBiased <= 0) {
|
|
2302
|
+
const frac2 = Math.round(value * Math.pow(2, 14) * 1024);
|
|
2303
|
+
return sign << 15 | frac2 & 1023;
|
|
2304
|
+
}
|
|
2305
|
+
const mantissa = value / Math.pow(2, exp) - 1;
|
|
2306
|
+
let frac = Math.round(mantissa * 1024);
|
|
2307
|
+
if (frac >= 1024) {
|
|
2308
|
+
frac = 0;
|
|
2309
|
+
expBiased++;
|
|
2310
|
+
if (expBiased >= 31) {
|
|
2311
|
+
return sign << 15 | 31744;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
return sign << 15 | expBiased << 10 | frac;
|
|
2315
|
+
}
|
|
2316
|
+
var b64Vecs = class {
|
|
2317
|
+
/**
|
|
2318
|
+
* Encode a single point (x, y, z) to 8 base64 characters using legacy Float16 encoding.
|
|
2319
|
+
* Each coordinate is encoded as a Float16 value, resulting in 6 bytes total.
|
|
2320
|
+
*
|
|
2321
|
+
* @param x - The x coordinate
|
|
2322
|
+
* @param y - The y coordinate
|
|
2323
|
+
* @param z - The z coordinate
|
|
2324
|
+
* @returns An 8-character base64 string representing the point
|
|
2325
|
+
* @internal
|
|
2326
|
+
*/
|
|
2327
|
+
static _legacyEncodePoint(x, y, z) {
|
|
2328
|
+
const buffer = new Uint8Array(6);
|
|
2329
|
+
const dataView = new DataView(buffer.buffer);
|
|
2330
|
+
setFloat16(dataView, 0, x);
|
|
2331
|
+
setFloat16(dataView, 2, y);
|
|
2332
|
+
setFloat16(dataView, 4, z);
|
|
2333
|
+
return uint8ArrayToBase64(buffer);
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Convert an array of VecModels to a base64 string using legacy Float16 encoding.
|
|
2337
|
+
* Uses Float16 encoding for each coordinate (x, y, z). If a point's z value is
|
|
2338
|
+
* undefined, it defaults to 0.5.
|
|
2339
|
+
*
|
|
2340
|
+
* @param points - An array of VecModel objects to encode
|
|
2341
|
+
* @returns A base64-encoded string containing all points
|
|
2342
|
+
* @internal Used only for migrations from legacy format
|
|
2343
|
+
*/
|
|
2344
|
+
static _legacyEncodePoints(points) {
|
|
2345
|
+
if (points.length === 0) return "";
|
|
2346
|
+
const buffer = new Uint8Array(points.length * 6);
|
|
2347
|
+
const dataView = new DataView(buffer.buffer);
|
|
2348
|
+
for (let i = 0; i < points.length; i++) {
|
|
2349
|
+
const p = points[i];
|
|
2350
|
+
const offset = i * 6;
|
|
2351
|
+
setFloat16(dataView, offset, p.x);
|
|
2352
|
+
setFloat16(dataView, offset + 2, p.y);
|
|
2353
|
+
setFloat16(dataView, offset + 4, p.z ?? 0.5);
|
|
2354
|
+
}
|
|
2355
|
+
return uint8ArrayToBase64(buffer);
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* Convert a legacy base64 string back to an array of VecModels.
|
|
2359
|
+
* Decodes Float16-encoded coordinates (x, y, z) from the base64 string.
|
|
2360
|
+
*
|
|
2361
|
+
* @param base64 - The base64-encoded string containing point data
|
|
2362
|
+
* @returns An array of VecModel objects decoded from the string
|
|
2363
|
+
* @internal Used only for migrations from legacy format
|
|
2364
|
+
*/
|
|
2365
|
+
static _legacyDecodePoints(base64) {
|
|
2366
|
+
const bytes = base64ToUint8Array(base64);
|
|
2367
|
+
const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2368
|
+
const result = [];
|
|
2369
|
+
for (let offset = 0; offset < bytes.length; offset += 6) {
|
|
2370
|
+
result.push({
|
|
2371
|
+
x: getFloat16(dataView, offset),
|
|
2372
|
+
y: getFloat16(dataView, offset + 2),
|
|
2373
|
+
z: getFloat16(dataView, offset + 4)
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
return result;
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Encode an array of VecModels using delta encoding for improved precision.
|
|
2380
|
+
* The first point is stored as Float32 (high precision for absolute position),
|
|
2381
|
+
* subsequent points are stored as Float16 deltas from the previous point.
|
|
2382
|
+
* This provides full precision for the starting position and excellent precision
|
|
2383
|
+
* for deltas between consecutive points (which are typically small values).
|
|
2384
|
+
*
|
|
2385
|
+
* Format:
|
|
2386
|
+
* - First point: 3 Float32 values = 12 bytes = 16 base64 chars
|
|
2387
|
+
* - Delta points: 3 Float16 values each = 6 bytes = 8 base64 chars each
|
|
2388
|
+
*
|
|
2389
|
+
* @param points - An array of VecModel objects to encode
|
|
2390
|
+
* @returns A base64-encoded string containing delta-encoded points
|
|
2391
|
+
* @public
|
|
2392
|
+
*/
|
|
2393
|
+
static encodePoints(points) {
|
|
2394
|
+
if (points.length === 0) return "";
|
|
2395
|
+
const firstPointBytes = 12;
|
|
2396
|
+
const deltaBytes = (points.length - 1) * 6;
|
|
2397
|
+
const totalBytes = firstPointBytes + deltaBytes;
|
|
2398
|
+
const buffer = new Uint8Array(totalBytes);
|
|
2399
|
+
const dataView = new DataView(buffer.buffer);
|
|
2400
|
+
const first = points[0];
|
|
2401
|
+
dataView.setFloat32(0, first.x, true);
|
|
2402
|
+
dataView.setFloat32(4, first.y, true);
|
|
2403
|
+
dataView.setFloat32(8, first.z ?? 0.5, true);
|
|
2404
|
+
let prevX = first.x;
|
|
2405
|
+
let prevY = first.y;
|
|
2406
|
+
let prevZ = first.z ?? 0.5;
|
|
2407
|
+
for (let i = 1; i < points.length; i++) {
|
|
2408
|
+
const p = points[i];
|
|
2409
|
+
const z = p.z ?? 0.5;
|
|
2410
|
+
const offset = firstPointBytes + (i - 1) * 6;
|
|
2411
|
+
setFloat16(dataView, offset, p.x - prevX);
|
|
2412
|
+
setFloat16(dataView, offset + 2, p.y - prevY);
|
|
2413
|
+
setFloat16(dataView, offset + 4, z - prevZ);
|
|
2414
|
+
prevX = p.x;
|
|
2415
|
+
prevY = p.y;
|
|
2416
|
+
prevZ = z;
|
|
2417
|
+
}
|
|
2418
|
+
return uint8ArrayToBase64(buffer);
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Decode a delta-encoded base64 string back to an array of absolute VecModels.
|
|
2422
|
+
* The first point is stored as Float32 (high precision), subsequent points are
|
|
2423
|
+
* Float16 deltas that are accumulated to reconstruct absolute positions.
|
|
2424
|
+
*
|
|
2425
|
+
* @param base64 - The base64-encoded string containing delta-encoded point data
|
|
2426
|
+
* @returns An array of VecModel objects with absolute coordinates
|
|
2427
|
+
* @public
|
|
2428
|
+
*/
|
|
2429
|
+
static decodePoints(base64) {
|
|
2430
|
+
if (base64.length === 0) return [];
|
|
2431
|
+
const bytes = base64ToUint8Array(base64);
|
|
2432
|
+
const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2433
|
+
const result = [];
|
|
2434
|
+
let x = dataView.getFloat32(0, true);
|
|
2435
|
+
let y = dataView.getFloat32(4, true);
|
|
2436
|
+
let z = dataView.getFloat32(8, true);
|
|
2437
|
+
result.push({ x, y, z });
|
|
2438
|
+
const firstPointBytes = 12;
|
|
2439
|
+
for (let offset = firstPointBytes; offset < bytes.length; offset += 6) {
|
|
2440
|
+
x += getFloat16(dataView, offset);
|
|
2441
|
+
y += getFloat16(dataView, offset + 2);
|
|
2442
|
+
z += getFloat16(dataView, offset + 4);
|
|
2443
|
+
result.push({ x, y, z });
|
|
2444
|
+
}
|
|
2445
|
+
return result;
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Get the first point from a delta-encoded base64 string.
|
|
2449
|
+
* The first point is stored as Float32 for full precision.
|
|
2450
|
+
*
|
|
2451
|
+
* @param b64Points - The delta-encoded base64 string
|
|
2452
|
+
* @returns The first point as a VecModel, or null if the string is too short
|
|
2453
|
+
* @public
|
|
2454
|
+
*/
|
|
2455
|
+
static decodeFirstPoint(b64Points) {
|
|
2456
|
+
if (b64Points.length < FIRST_POINT_B64_LENGTH) return null;
|
|
2457
|
+
const bytes = base64ToUint8Array(b64Points.slice(0, FIRST_POINT_B64_LENGTH));
|
|
2458
|
+
const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2459
|
+
return {
|
|
2460
|
+
x: dataView.getFloat32(0, true),
|
|
2461
|
+
y: dataView.getFloat32(4, true),
|
|
2462
|
+
z: dataView.getFloat32(8, true)
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Get the last point from a delta-encoded base64 string.
|
|
2467
|
+
* Requires decoding all points to accumulate deltas.
|
|
2468
|
+
*
|
|
2469
|
+
* @param b64Points - The delta-encoded base64 string
|
|
2470
|
+
* @returns The last point as a VecModel, or null if the string is too short
|
|
2471
|
+
* @public
|
|
2472
|
+
*/
|
|
2473
|
+
static decodeLastPoint(b64Points) {
|
|
2474
|
+
if (b64Points.length < FIRST_POINT_B64_LENGTH) return null;
|
|
2475
|
+
const bytes = base64ToUint8Array(b64Points);
|
|
2476
|
+
const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2477
|
+
let x = dataView.getFloat32(0, true);
|
|
2478
|
+
let y = dataView.getFloat32(4, true);
|
|
2479
|
+
let z = dataView.getFloat32(8, true);
|
|
2480
|
+
const firstPointBytes = 12;
|
|
2481
|
+
for (let offset = firstPointBytes; offset < bytes.length; offset += 6) {
|
|
2482
|
+
x += getFloat16(dataView, offset);
|
|
2483
|
+
y += getFloat16(dataView, offset + 2);
|
|
2484
|
+
z += getFloat16(dataView, offset + 4);
|
|
2485
|
+
}
|
|
2486
|
+
return { x, y, z };
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
|
|
2490
|
+
// src/shapes/DrDrawShape.ts
|
|
2491
|
+
var DrawShapeSegment = T.object({
|
|
2492
|
+
type: T.literalEnum("free", "straight"),
|
|
2493
|
+
path: T.string
|
|
2494
|
+
});
|
|
2495
|
+
var drawShapeProps = {
|
|
2496
|
+
color: DefaultColorStyle,
|
|
2497
|
+
fill: DefaultFillStyle,
|
|
2498
|
+
dash: DefaultDashStyle,
|
|
2499
|
+
size: DefaultSizeStyle,
|
|
2500
|
+
segments: T.arrayOf(DrawShapeSegment),
|
|
2501
|
+
isComplete: T.boolean,
|
|
2502
|
+
isClosed: T.boolean,
|
|
2503
|
+
isPen: T.boolean,
|
|
2504
|
+
scale: T.nonZeroNumber,
|
|
2505
|
+
scaleX: T.nonZeroFiniteNumber,
|
|
2506
|
+
scaleY: T.nonZeroFiniteNumber
|
|
2507
|
+
};
|
|
2508
|
+
var Versions5 = createShapePropsMigrationIds("draw", {
|
|
2509
|
+
AddInPen: 1,
|
|
2510
|
+
AddScale: 2,
|
|
2511
|
+
Base64: 3,
|
|
2512
|
+
LegacyPointsConversion: 4
|
|
2513
|
+
});
|
|
2514
|
+
var drawShapeMigrations = createShapePropsMigrationSequence({
|
|
2515
|
+
sequence: [
|
|
2516
|
+
{
|
|
2517
|
+
id: Versions5.AddInPen,
|
|
2518
|
+
up: (props) => {
|
|
2519
|
+
const { points } = props.segments[0];
|
|
2520
|
+
if (points.length === 0) {
|
|
2521
|
+
props.isPen = false;
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
let isPen = !(points[0].z === 0 || points[0].z === 0.5);
|
|
2525
|
+
if (points[1]) {
|
|
2526
|
+
isPen = isPen && !(points[1].z === 0 || points[1].z === 0.5);
|
|
2527
|
+
}
|
|
2528
|
+
props.isPen = isPen;
|
|
2529
|
+
},
|
|
2530
|
+
down: "retired"
|
|
2531
|
+
},
|
|
2532
|
+
{
|
|
2533
|
+
id: Versions5.AddScale,
|
|
2534
|
+
up: (props) => {
|
|
2535
|
+
props.scale = 1;
|
|
2536
|
+
},
|
|
2537
|
+
down: (props) => {
|
|
2538
|
+
delete props.scale;
|
|
2539
|
+
}
|
|
2540
|
+
},
|
|
2541
|
+
{
|
|
2542
|
+
id: Versions5.Base64,
|
|
2543
|
+
up: (props) => {
|
|
2544
|
+
props.segments = props.segments.map((segment) => {
|
|
2545
|
+
if (segment.path !== void 0) return segment;
|
|
2546
|
+
const { points, ...rest } = segment;
|
|
2547
|
+
const vecModels = Array.isArray(points) ? points : b64Vecs._legacyDecodePoints(points);
|
|
2548
|
+
return {
|
|
2549
|
+
...rest,
|
|
2550
|
+
path: b64Vecs.encodePoints(vecModels)
|
|
2551
|
+
};
|
|
2552
|
+
});
|
|
2553
|
+
props.scaleX = props.scaleX ?? 1;
|
|
2554
|
+
props.scaleY = props.scaleY ?? 1;
|
|
2555
|
+
},
|
|
2556
|
+
down: (props) => {
|
|
2557
|
+
props.segments = props.segments.map((segment) => {
|
|
2558
|
+
const { path, ...rest } = segment;
|
|
2559
|
+
return {
|
|
2560
|
+
...rest,
|
|
2561
|
+
points: b64Vecs.decodePoints(path)
|
|
2562
|
+
};
|
|
2563
|
+
});
|
|
2564
|
+
delete props.scaleX;
|
|
2565
|
+
delete props.scaleY;
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2568
|
+
{
|
|
2569
|
+
id: Versions5.LegacyPointsConversion,
|
|
2570
|
+
up: (props) => {
|
|
2571
|
+
props.segments = props.segments.map((segment) => {
|
|
2572
|
+
if (segment.path !== void 0) return segment;
|
|
2573
|
+
const { points, ...rest } = segment;
|
|
2574
|
+
const vecModels = Array.isArray(points) ? points : b64Vecs._legacyDecodePoints(points);
|
|
2575
|
+
return {
|
|
2576
|
+
...rest,
|
|
2577
|
+
path: b64Vecs.encodePoints(vecModels)
|
|
2578
|
+
};
|
|
2579
|
+
});
|
|
2580
|
+
},
|
|
2581
|
+
down: (_props) => {
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
]
|
|
2585
|
+
});
|
|
2586
|
+
function compressLegacySegments(segments) {
|
|
2587
|
+
return segments.map((segment) => ({
|
|
2588
|
+
type: segment.type,
|
|
2589
|
+
path: b64Vecs.encodePoints(segment.points)
|
|
2590
|
+
}));
|
|
2591
|
+
}
|
|
2592
|
+
var DRAW_APP_RE = /(^\/r\/[^/]+\/?$)/;
|
|
2593
|
+
var EMBED_DEFINITIONS = [
|
|
2594
|
+
{
|
|
2595
|
+
hostnames: ["beta.draw.com", "tldraw.com", "localhost:3000"],
|
|
2596
|
+
canEditWhileLocked: true,
|
|
2597
|
+
fromEmbedUrl: (url) => {
|
|
2598
|
+
const urlObj = safeParseUrl(url);
|
|
2599
|
+
if (urlObj && urlObj.pathname.match(DRAW_APP_RE)) {
|
|
2600
|
+
return url;
|
|
2601
|
+
}
|
|
2602
|
+
return;
|
|
2603
|
+
}
|
|
2604
|
+
},
|
|
2605
|
+
{
|
|
2606
|
+
hostnames: ["figma.com"],
|
|
2607
|
+
canEditWhileLocked: true,
|
|
2608
|
+
fromEmbedUrl: (url) => {
|
|
2609
|
+
const urlObj = safeParseUrl(url);
|
|
2610
|
+
if (urlObj && urlObj.pathname.match(/^\/embed\/?$/)) {
|
|
2611
|
+
const outUrl = urlObj.searchParams.get("url");
|
|
2612
|
+
if (outUrl) {
|
|
2613
|
+
return outUrl;
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
return;
|
|
2617
|
+
}
|
|
2618
|
+
},
|
|
2619
|
+
{
|
|
2620
|
+
hostnames: ["google.*"],
|
|
2621
|
+
canEditWhileLocked: true,
|
|
2622
|
+
fromEmbedUrl: (url) => {
|
|
2623
|
+
const urlObj = safeParseUrl(url);
|
|
2624
|
+
if (!urlObj) return;
|
|
2625
|
+
const matches = urlObj.pathname.match(/^\/maps\/embed\/v1\/view\/?$/);
|
|
2626
|
+
if (matches && urlObj.searchParams.has("center") && urlObj.searchParams.get("zoom")) {
|
|
2627
|
+
const zoom = urlObj.searchParams.get("zoom");
|
|
2628
|
+
const [lat, lon] = urlObj.searchParams.get("center").split(",");
|
|
2629
|
+
return `https://www.google.com/maps/@${lat},${lon},${zoom}z`;
|
|
2630
|
+
}
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
hostnames: ["val.town"],
|
|
2636
|
+
canEditWhileLocked: true,
|
|
2637
|
+
fromEmbedUrl: (url) => {
|
|
2638
|
+
const urlObj = safeParseUrl(url);
|
|
2639
|
+
const matches = urlObj && urlObj.pathname.match(/\/embed\/(.+)\/?/);
|
|
2640
|
+
if (matches) {
|
|
2641
|
+
return `https://www.val.town/v/${matches[1]}`;
|
|
2642
|
+
}
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2645
|
+
},
|
|
2646
|
+
{
|
|
2647
|
+
hostnames: ["codesandbox.io"],
|
|
2648
|
+
canEditWhileLocked: true,
|
|
2649
|
+
fromEmbedUrl: (url) => {
|
|
2650
|
+
const urlObj = safeParseUrl(url);
|
|
2651
|
+
const matches = urlObj && urlObj.pathname.match(/\/embed\/([^/]+)\/?/);
|
|
2652
|
+
if (matches) {
|
|
2653
|
+
return `https://codesandbox.io/s/${matches[1]}`;
|
|
2654
|
+
}
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
hostnames: ["codepen.io"],
|
|
2660
|
+
canEditWhileLocked: true,
|
|
2661
|
+
fromEmbedUrl: (url) => {
|
|
2662
|
+
const CODEPEN_EMBED_REGEXP = /https:\/\/codepen.io\/([^/]+)\/embed\/([^/]+)/;
|
|
2663
|
+
const matches = url.match(CODEPEN_EMBED_REGEXP);
|
|
2664
|
+
if (matches) {
|
|
2665
|
+
const [_, user, id] = matches;
|
|
2666
|
+
return `https://codepen.io/${user}/pen/${id}`;
|
|
2667
|
+
}
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
},
|
|
2671
|
+
{
|
|
2672
|
+
hostnames: ["scratch.mit.edu"],
|
|
2673
|
+
canEditWhileLocked: true,
|
|
2674
|
+
fromEmbedUrl: (url) => {
|
|
2675
|
+
const SCRATCH_EMBED_REGEXP = /https:\/\/scratch.mit.edu\/projects\/embed\/([^/]+)/;
|
|
2676
|
+
const matches = url.match(SCRATCH_EMBED_REGEXP);
|
|
2677
|
+
if (matches) {
|
|
2678
|
+
const [_, id] = matches;
|
|
2679
|
+
return `https://scratch.mit.edu/projects/${id}`;
|
|
2680
|
+
}
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
},
|
|
2684
|
+
{
|
|
2685
|
+
hostnames: ["*.youtube.com", "youtube.com", "youtu.be"],
|
|
2686
|
+
canEditWhileLocked: true,
|
|
2687
|
+
fromEmbedUrl: (url) => {
|
|
2688
|
+
const urlObj = safeParseUrl(url);
|
|
2689
|
+
if (!urlObj) return;
|
|
2690
|
+
const hostname = urlObj.hostname.replace(/^www./, "");
|
|
2691
|
+
if (hostname === "youtube.com") {
|
|
2692
|
+
const matches = urlObj.pathname.match(/^\/embed\/([^/]+)\/?/);
|
|
2693
|
+
if (matches) {
|
|
2694
|
+
return `https://www.youtube.com/watch?v=${matches[1]}`;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
},
|
|
2700
|
+
{
|
|
2701
|
+
hostnames: ["calendar.google.*"],
|
|
2702
|
+
canEditWhileLocked: true,
|
|
2703
|
+
fromEmbedUrl: (url) => {
|
|
2704
|
+
const urlObj = safeParseUrl(url);
|
|
2705
|
+
const srcQs = urlObj?.searchParams.get("src");
|
|
2706
|
+
if (urlObj?.pathname.match(/\/calendar\/embed/) && srcQs) {
|
|
2707
|
+
urlObj.pathname = "/calendar/u/0";
|
|
2708
|
+
const keys = Array.from(urlObj.searchParams.keys());
|
|
2709
|
+
for (const key of keys) {
|
|
2710
|
+
urlObj.searchParams.delete(key);
|
|
2711
|
+
}
|
|
2712
|
+
urlObj.searchParams.set("cid", srcQs);
|
|
2713
|
+
return urlObj.href;
|
|
2714
|
+
}
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
},
|
|
2718
|
+
{
|
|
2719
|
+
hostnames: ["docs.google.*"],
|
|
2720
|
+
canEditWhileLocked: true,
|
|
2721
|
+
fromEmbedUrl: (url) => {
|
|
2722
|
+
const urlObj = safeParseUrl(url);
|
|
2723
|
+
if (urlObj?.pathname.match(/^\/presentation/) && urlObj?.pathname.match(/\/embed\/?$/)) {
|
|
2724
|
+
urlObj.pathname = urlObj.pathname.replace(/\/embed$/, "/pub");
|
|
2725
|
+
const keys = Array.from(urlObj.searchParams.keys());
|
|
2726
|
+
for (const key of keys) {
|
|
2727
|
+
urlObj.searchParams.delete(key);
|
|
2728
|
+
}
|
|
2729
|
+
return urlObj.href;
|
|
2730
|
+
}
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
},
|
|
2734
|
+
{
|
|
2735
|
+
hostnames: ["gist.github.com"],
|
|
2736
|
+
canEditWhileLocked: true,
|
|
2737
|
+
fromEmbedUrl: (url) => {
|
|
2738
|
+
const urlObj = safeParseUrl(url);
|
|
2739
|
+
if (urlObj && urlObj.pathname.match(/\/([^/]+)\/([^/]+)/)) {
|
|
2740
|
+
if (!url.split("/").pop()) return;
|
|
2741
|
+
return url;
|
|
2742
|
+
}
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2745
|
+
},
|
|
2746
|
+
{
|
|
2747
|
+
hostnames: ["replit.com"],
|
|
2748
|
+
canEditWhileLocked: true,
|
|
2749
|
+
fromEmbedUrl: (url) => {
|
|
2750
|
+
const urlObj = safeParseUrl(url);
|
|
2751
|
+
if (urlObj && urlObj.pathname.match(/\/@([^/]+)\/([^/]+)/) && urlObj.searchParams.has("embed")) {
|
|
2752
|
+
urlObj.searchParams.delete("embed");
|
|
2753
|
+
return urlObj.href;
|
|
2754
|
+
}
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
},
|
|
2758
|
+
{
|
|
2759
|
+
hostnames: ["felt.com"],
|
|
2760
|
+
canEditWhileLocked: true,
|
|
2761
|
+
fromEmbedUrl: (url) => {
|
|
2762
|
+
const urlObj = safeParseUrl(url);
|
|
2763
|
+
if (urlObj && urlObj.pathname.match(/^\/embed\/map\//)) {
|
|
2764
|
+
urlObj.pathname = urlObj.pathname.replace(/^\/embed/, "");
|
|
2765
|
+
return urlObj.href;
|
|
2766
|
+
}
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
},
|
|
2770
|
+
{
|
|
2771
|
+
hostnames: ["open.spotify.com"],
|
|
2772
|
+
canEditWhileLocked: true,
|
|
2773
|
+
fromEmbedUrl: (url) => {
|
|
2774
|
+
const urlObj = safeParseUrl(url);
|
|
2775
|
+
if (urlObj && urlObj.pathname.match(/^\/embed\/(artist|album)\//)) {
|
|
2776
|
+
return urlObj.origin + urlObj.pathname.replace(/^\/embed/, "");
|
|
2777
|
+
}
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
},
|
|
2781
|
+
{
|
|
2782
|
+
hostnames: ["vimeo.com", "player.vimeo.com"],
|
|
2783
|
+
canEditWhileLocked: true,
|
|
2784
|
+
fromEmbedUrl: (url) => {
|
|
2785
|
+
const urlObj = safeParseUrl(url);
|
|
2786
|
+
if (urlObj && urlObj.hostname === "player.vimeo.com") {
|
|
2787
|
+
const matches = urlObj.pathname.match(/^\/video\/([^/]+)\/?$/);
|
|
2788
|
+
if (matches) {
|
|
2789
|
+
return "https://vimeo.com/" + matches[1];
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
},
|
|
2795
|
+
{
|
|
2796
|
+
hostnames: ["observablehq.com"],
|
|
2797
|
+
canEditWhileLocked: true,
|
|
2798
|
+
fromEmbedUrl: (url) => {
|
|
2799
|
+
const urlObj = safeParseUrl(url);
|
|
2800
|
+
if (urlObj && urlObj.pathname.match(/^\/embed\/@([^/]+)\/([^/]+)\/?$/)) {
|
|
2801
|
+
return `${urlObj.origin}${urlObj.pathname.replace("/embed", "")}#cell-*`;
|
|
2802
|
+
}
|
|
2803
|
+
if (urlObj && urlObj.pathname.match(/^\/embed\/([^/]+)\/?$/)) {
|
|
2804
|
+
return `${urlObj.origin}${urlObj.pathname.replace("/embed", "/d")}#cell-*`;
|
|
2805
|
+
}
|
|
2806
|
+
return;
|
|
2807
|
+
}
|
|
2808
|
+
},
|
|
2809
|
+
{
|
|
2810
|
+
hostnames: ["desmos.com"],
|
|
2811
|
+
canEditWhileLocked: true,
|
|
2812
|
+
fromEmbedUrl: (url) => {
|
|
2813
|
+
const urlObj = safeParseUrl(url);
|
|
2814
|
+
if (urlObj && urlObj.hostname === "www.desmos.com" && urlObj.pathname.match(/^\/calculator\/([^/]+)\/?$/) && urlObj.search === "?embed" && urlObj.hash === "") {
|
|
2815
|
+
return url.replace("?embed", "");
|
|
2816
|
+
}
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
];
|
|
2821
|
+
var embedShapeProps = {
|
|
2822
|
+
w: T.nonZeroNumber,
|
|
2823
|
+
h: T.nonZeroNumber,
|
|
2824
|
+
url: T.string
|
|
2825
|
+
};
|
|
2826
|
+
var Versions6 = createShapePropsMigrationIds("embed", {
|
|
2827
|
+
GenOriginalUrlInEmbed: 1,
|
|
2828
|
+
RemoveDoesResize: 2,
|
|
2829
|
+
RemoveTmpOldUrl: 3,
|
|
2830
|
+
RemovePermissionOverrides: 4
|
|
2831
|
+
});
|
|
2832
|
+
var embedShapeMigrations = createShapePropsMigrationSequence({
|
|
2833
|
+
sequence: [
|
|
2834
|
+
{
|
|
2835
|
+
id: Versions6.GenOriginalUrlInEmbed,
|
|
2836
|
+
// add tmpOldUrl property
|
|
2837
|
+
up: (props) => {
|
|
2838
|
+
try {
|
|
2839
|
+
const url = props.url;
|
|
2840
|
+
const host = new URL(url).host.replace("www.", "");
|
|
2841
|
+
let originalUrl;
|
|
2842
|
+
for (const localEmbedDef of EMBED_DEFINITIONS) {
|
|
2843
|
+
if (localEmbedDef.hostnames.includes(host)) {
|
|
2844
|
+
try {
|
|
2845
|
+
originalUrl = localEmbedDef.fromEmbedUrl(url);
|
|
2846
|
+
} catch (err) {
|
|
2847
|
+
console.warn(err);
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
props.tmpOldUrl = props.url;
|
|
2852
|
+
props.url = originalUrl ?? "";
|
|
2853
|
+
} catch {
|
|
2854
|
+
props.url = "";
|
|
2855
|
+
props.tmpOldUrl = props.url;
|
|
2856
|
+
}
|
|
2857
|
+
},
|
|
2858
|
+
down: "retired"
|
|
2859
|
+
},
|
|
2860
|
+
{
|
|
2861
|
+
id: Versions6.RemoveDoesResize,
|
|
2862
|
+
up: (props) => {
|
|
2863
|
+
delete props.doesResize;
|
|
2864
|
+
},
|
|
2865
|
+
down: "retired"
|
|
2866
|
+
},
|
|
2867
|
+
{
|
|
2868
|
+
id: Versions6.RemoveTmpOldUrl,
|
|
2869
|
+
up: (props) => {
|
|
2870
|
+
delete props.tmpOldUrl;
|
|
2871
|
+
},
|
|
2872
|
+
down: "retired"
|
|
2873
|
+
},
|
|
2874
|
+
{
|
|
2875
|
+
id: Versions6.RemovePermissionOverrides,
|
|
2876
|
+
up: (props) => {
|
|
2877
|
+
delete props.overridePermissions;
|
|
2878
|
+
},
|
|
2879
|
+
down: "retired"
|
|
2880
|
+
}
|
|
2881
|
+
]
|
|
2882
|
+
});
|
|
2883
|
+
var frameShapeProps = {
|
|
2884
|
+
w: T.nonZeroNumber,
|
|
2885
|
+
h: T.nonZeroNumber,
|
|
2886
|
+
name: T.string,
|
|
2887
|
+
// because shape colors are an option, we don't want them to be picked up by the editor as a
|
|
2888
|
+
// style prop by default, so instead of a proper style we just supply an equivalent validator.
|
|
2889
|
+
// Check `FrameShapeUtil.configure` for how we replace this with the original
|
|
2890
|
+
// `DefaultColorStyle` style when the option is turned on.
|
|
2891
|
+
// We delegate to DefaultColorStyle.validate so custom colors from themes are
|
|
2892
|
+
// picked up automatically.
|
|
2893
|
+
color: { validate: (v) => DefaultColorStyle.validate(v) }
|
|
2894
|
+
};
|
|
2895
|
+
var Versions7 = createShapePropsMigrationIds("frame", {
|
|
2896
|
+
AddColorProp: 1
|
|
2897
|
+
});
|
|
2898
|
+
var frameShapeMigrations = createShapePropsMigrationSequence({
|
|
2899
|
+
sequence: [
|
|
2900
|
+
{
|
|
2901
|
+
id: Versions7.AddColorProp,
|
|
2902
|
+
up: (props) => {
|
|
2903
|
+
props.color = "black";
|
|
2904
|
+
},
|
|
2905
|
+
down: (props) => {
|
|
2906
|
+
delete props.color;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
]
|
|
2910
|
+
});
|
|
2911
|
+
|
|
2912
|
+
// src/styles/DrHorizontalAlignStyle.ts
|
|
2913
|
+
var DefaultHorizontalAlignStyle = StyleProp.defineEnum("draw:horizontalAlign", {
|
|
2914
|
+
defaultValue: "middle",
|
|
2915
|
+
values: ["start", "middle", "end", "start-legacy", "end-legacy", "middle-legacy"]
|
|
2916
|
+
});
|
|
2917
|
+
|
|
2918
|
+
// src/styles/DrVerticalAlignStyle.ts
|
|
2919
|
+
var DefaultVerticalAlignStyle = StyleProp.defineEnum("draw:verticalAlign", {
|
|
2920
|
+
defaultValue: "middle",
|
|
2921
|
+
values: ["start", "middle", "end"]
|
|
2922
|
+
});
|
|
2923
|
+
|
|
2924
|
+
// src/shapes/DrGeoShape.ts
|
|
2925
|
+
var GeoShapeGeoStyle = StyleProp.defineEnum("draw:geo", {
|
|
2926
|
+
defaultValue: "rectangle",
|
|
2927
|
+
values: [
|
|
2928
|
+
"cloud",
|
|
2929
|
+
"rectangle",
|
|
2930
|
+
"ellipse",
|
|
2931
|
+
"triangle",
|
|
2932
|
+
"diamond",
|
|
2933
|
+
"pentagon",
|
|
2934
|
+
"hexagon",
|
|
2935
|
+
"octagon",
|
|
2936
|
+
"star",
|
|
2937
|
+
"rhombus",
|
|
2938
|
+
"rhombus-2",
|
|
2939
|
+
"oval",
|
|
2940
|
+
"trapezoid",
|
|
2941
|
+
"arrow-right",
|
|
2942
|
+
"arrow-left",
|
|
2943
|
+
"arrow-up",
|
|
2944
|
+
"arrow-down",
|
|
2945
|
+
"x-box",
|
|
2946
|
+
"check-box",
|
|
2947
|
+
"heart"
|
|
2948
|
+
]
|
|
2949
|
+
});
|
|
2950
|
+
var geoShapeProps = {
|
|
2951
|
+
geo: GeoShapeGeoStyle,
|
|
2952
|
+
dash: DefaultDashStyle,
|
|
2953
|
+
url: T.linkUrl,
|
|
2954
|
+
w: T.nonZeroNumber,
|
|
2955
|
+
h: T.nonZeroNumber,
|
|
2956
|
+
growY: T.positiveNumber,
|
|
2957
|
+
scale: T.nonZeroNumber,
|
|
2958
|
+
// Text properties
|
|
2959
|
+
labelColor: DefaultLabelColorStyle,
|
|
2960
|
+
color: DefaultColorStyle,
|
|
2961
|
+
fill: DefaultFillStyle,
|
|
2962
|
+
size: DefaultSizeStyle,
|
|
2963
|
+
font: DefaultFontStyle,
|
|
2964
|
+
align: DefaultHorizontalAlignStyle,
|
|
2965
|
+
verticalAlign: DefaultVerticalAlignStyle,
|
|
2966
|
+
richText: richTextValidator
|
|
2967
|
+
};
|
|
2968
|
+
var geoShapeVersions = createShapePropsMigrationIds("geo", {
|
|
2969
|
+
AddUrlProp: 1,
|
|
2970
|
+
AddLabelColor: 2,
|
|
2971
|
+
RemoveJustify: 3,
|
|
2972
|
+
AddCheckBox: 4,
|
|
2973
|
+
AddVerticalAlign: 5,
|
|
2974
|
+
MigrateLegacyAlign: 6,
|
|
2975
|
+
AddCloud: 7,
|
|
2976
|
+
MakeUrlsValid: 8,
|
|
2977
|
+
AddScale: 9,
|
|
2978
|
+
AddRichText: 10,
|
|
2979
|
+
AddRichTextAttrs: 11
|
|
2980
|
+
});
|
|
2981
|
+
var geoShapeMigrations = createShapePropsMigrationSequence({
|
|
2982
|
+
sequence: [
|
|
2983
|
+
{
|
|
2984
|
+
id: geoShapeVersions.AddUrlProp,
|
|
2985
|
+
up: (props) => {
|
|
2986
|
+
props.url = "";
|
|
2987
|
+
},
|
|
2988
|
+
down: "retired"
|
|
2989
|
+
},
|
|
2990
|
+
{
|
|
2991
|
+
id: geoShapeVersions.AddLabelColor,
|
|
2992
|
+
up: (props) => {
|
|
2993
|
+
props.labelColor = "black";
|
|
2994
|
+
},
|
|
2995
|
+
down: "retired"
|
|
2996
|
+
},
|
|
2997
|
+
{
|
|
2998
|
+
id: geoShapeVersions.RemoveJustify,
|
|
2999
|
+
up: (props) => {
|
|
3000
|
+
if (props.align === "justify") {
|
|
3001
|
+
props.align = "start";
|
|
3002
|
+
}
|
|
3003
|
+
},
|
|
3004
|
+
down: "retired"
|
|
3005
|
+
},
|
|
3006
|
+
{
|
|
3007
|
+
id: geoShapeVersions.AddCheckBox,
|
|
3008
|
+
up: (_props) => {
|
|
3009
|
+
},
|
|
3010
|
+
down: "retired"
|
|
3011
|
+
},
|
|
3012
|
+
{
|
|
3013
|
+
id: geoShapeVersions.AddVerticalAlign,
|
|
3014
|
+
up: (props) => {
|
|
3015
|
+
props.verticalAlign = "middle";
|
|
3016
|
+
},
|
|
3017
|
+
down: "retired"
|
|
3018
|
+
},
|
|
3019
|
+
{
|
|
3020
|
+
id: geoShapeVersions.MigrateLegacyAlign,
|
|
3021
|
+
up: (props) => {
|
|
3022
|
+
let newAlign;
|
|
3023
|
+
switch (props.align) {
|
|
3024
|
+
case "start":
|
|
3025
|
+
newAlign = "start-legacy";
|
|
3026
|
+
break;
|
|
3027
|
+
case "end":
|
|
3028
|
+
newAlign = "end-legacy";
|
|
3029
|
+
break;
|
|
3030
|
+
default:
|
|
3031
|
+
newAlign = "middle-legacy";
|
|
3032
|
+
break;
|
|
3033
|
+
}
|
|
3034
|
+
props.align = newAlign;
|
|
3035
|
+
},
|
|
3036
|
+
down: "retired"
|
|
3037
|
+
},
|
|
3038
|
+
{
|
|
3039
|
+
id: geoShapeVersions.AddCloud,
|
|
3040
|
+
up: (_props) => {
|
|
3041
|
+
},
|
|
3042
|
+
down: "retired"
|
|
3043
|
+
},
|
|
3044
|
+
{
|
|
3045
|
+
id: geoShapeVersions.MakeUrlsValid,
|
|
3046
|
+
up: (props) => {
|
|
3047
|
+
if (!T.linkUrl.isValid(props.url)) {
|
|
3048
|
+
props.url = "";
|
|
3049
|
+
}
|
|
3050
|
+
},
|
|
3051
|
+
down: (_props) => {
|
|
3052
|
+
}
|
|
3053
|
+
},
|
|
3054
|
+
{
|
|
3055
|
+
id: geoShapeVersions.AddScale,
|
|
3056
|
+
up: (props) => {
|
|
3057
|
+
props.scale = 1;
|
|
3058
|
+
},
|
|
3059
|
+
down: (props) => {
|
|
3060
|
+
delete props.scale;
|
|
3061
|
+
}
|
|
3062
|
+
},
|
|
3063
|
+
{
|
|
3064
|
+
id: geoShapeVersions.AddRichText,
|
|
3065
|
+
up: (props) => {
|
|
3066
|
+
props.richText = toRichText(props.text);
|
|
3067
|
+
delete props.text;
|
|
3068
|
+
}
|
|
3069
|
+
// N.B. Explicitly no down state so that we force clients to update.
|
|
3070
|
+
// down: (props) => {
|
|
3071
|
+
// delete props.richText
|
|
3072
|
+
// },
|
|
3073
|
+
},
|
|
3074
|
+
{
|
|
3075
|
+
id: geoShapeVersions.AddRichTextAttrs,
|
|
3076
|
+
up: (_props) => {
|
|
3077
|
+
},
|
|
3078
|
+
down: (props) => {
|
|
3079
|
+
if (props.richText && "attrs" in props.richText) {
|
|
3080
|
+
delete props.richText.attrs;
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
]
|
|
3085
|
+
});
|
|
3086
|
+
|
|
3087
|
+
// src/shapes/DrGroupShape.ts
|
|
3088
|
+
var groupShapeProps = {};
|
|
3089
|
+
var groupShapeMigrations = createShapePropsMigrationSequence({ sequence: [] });
|
|
3090
|
+
var highlightShapeProps = {
|
|
3091
|
+
color: DefaultColorStyle,
|
|
3092
|
+
size: DefaultSizeStyle,
|
|
3093
|
+
segments: T.arrayOf(DrawShapeSegment),
|
|
3094
|
+
isComplete: T.boolean,
|
|
3095
|
+
isPen: T.boolean,
|
|
3096
|
+
scale: T.nonZeroNumber,
|
|
3097
|
+
scaleX: T.nonZeroFiniteNumber,
|
|
3098
|
+
scaleY: T.nonZeroFiniteNumber
|
|
3099
|
+
};
|
|
3100
|
+
var Versions8 = createShapePropsMigrationIds("highlight", {
|
|
3101
|
+
AddScale: 1,
|
|
3102
|
+
Base64: 2,
|
|
3103
|
+
LegacyPointsConversion: 3
|
|
3104
|
+
});
|
|
3105
|
+
var highlightShapeMigrations = createShapePropsMigrationSequence({
|
|
3106
|
+
sequence: [
|
|
3107
|
+
{
|
|
3108
|
+
id: Versions8.AddScale,
|
|
3109
|
+
up: (props) => {
|
|
3110
|
+
props.scale = 1;
|
|
3111
|
+
},
|
|
3112
|
+
down: (props) => {
|
|
3113
|
+
delete props.scale;
|
|
3114
|
+
}
|
|
3115
|
+
},
|
|
3116
|
+
{
|
|
3117
|
+
id: Versions8.Base64,
|
|
3118
|
+
up: (props) => {
|
|
3119
|
+
props.segments = props.segments.map((segment) => {
|
|
3120
|
+
if (segment.path !== void 0) return segment;
|
|
3121
|
+
const { points, ...rest } = segment;
|
|
3122
|
+
const vecModels = Array.isArray(points) ? points : b64Vecs._legacyDecodePoints(points);
|
|
3123
|
+
return {
|
|
3124
|
+
...rest,
|
|
3125
|
+
path: b64Vecs.encodePoints(vecModels)
|
|
3126
|
+
};
|
|
3127
|
+
});
|
|
3128
|
+
props.scaleX = props.scaleX ?? 1;
|
|
3129
|
+
props.scaleY = props.scaleY ?? 1;
|
|
3130
|
+
},
|
|
3131
|
+
down: (props) => {
|
|
3132
|
+
props.segments = props.segments.map((segment) => {
|
|
3133
|
+
const { path, ...rest } = segment;
|
|
3134
|
+
return {
|
|
3135
|
+
...rest,
|
|
3136
|
+
points: b64Vecs.decodePoints(path)
|
|
3137
|
+
};
|
|
3138
|
+
});
|
|
3139
|
+
delete props.scaleX;
|
|
3140
|
+
delete props.scaleY;
|
|
3141
|
+
}
|
|
3142
|
+
},
|
|
3143
|
+
{
|
|
3144
|
+
id: Versions8.LegacyPointsConversion,
|
|
3145
|
+
up: (props) => {
|
|
3146
|
+
props.segments = props.segments.map((segment) => {
|
|
3147
|
+
if (segment.path !== void 0) return segment;
|
|
3148
|
+
const { points, ...rest } = segment;
|
|
3149
|
+
const vecModels = Array.isArray(points) ? points : b64Vecs._legacyDecodePoints(points);
|
|
3150
|
+
return {
|
|
3151
|
+
...rest,
|
|
3152
|
+
path: b64Vecs.encodePoints(vecModels)
|
|
3153
|
+
};
|
|
3154
|
+
});
|
|
3155
|
+
},
|
|
3156
|
+
down: (_props) => {
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
]
|
|
3160
|
+
});
|
|
3161
|
+
var ImageShapeCrop = T.object({
|
|
3162
|
+
topLeft: vecModelValidator,
|
|
3163
|
+
bottomRight: vecModelValidator,
|
|
3164
|
+
isCircle: T.boolean.optional()
|
|
3165
|
+
});
|
|
3166
|
+
var imageShapeProps = {
|
|
3167
|
+
w: T.nonZeroNumber,
|
|
3168
|
+
h: T.nonZeroNumber,
|
|
3169
|
+
playing: T.boolean,
|
|
3170
|
+
url: T.linkUrl,
|
|
3171
|
+
assetId: assetIdValidator.nullable(),
|
|
3172
|
+
crop: ImageShapeCrop.nullable(),
|
|
3173
|
+
flipX: T.boolean,
|
|
3174
|
+
flipY: T.boolean,
|
|
3175
|
+
altText: T.string
|
|
3176
|
+
};
|
|
3177
|
+
var Versions9 = createShapePropsMigrationIds("image", {
|
|
3178
|
+
AddUrlProp: 1,
|
|
3179
|
+
AddCropProp: 2,
|
|
3180
|
+
MakeUrlsValid: 3,
|
|
3181
|
+
AddFlipProps: 4,
|
|
3182
|
+
AddAltText: 5
|
|
3183
|
+
});
|
|
3184
|
+
var imageShapeMigrations = createShapePropsMigrationSequence({
|
|
3185
|
+
sequence: [
|
|
3186
|
+
{
|
|
3187
|
+
id: Versions9.AddUrlProp,
|
|
3188
|
+
up: (props) => {
|
|
3189
|
+
props.url = "";
|
|
3190
|
+
},
|
|
3191
|
+
down: "retired"
|
|
3192
|
+
},
|
|
3193
|
+
{
|
|
3194
|
+
id: Versions9.AddCropProp,
|
|
3195
|
+
up: (props) => {
|
|
3196
|
+
props.crop = null;
|
|
3197
|
+
},
|
|
3198
|
+
down: (props) => {
|
|
3199
|
+
delete props.crop;
|
|
3200
|
+
}
|
|
3201
|
+
},
|
|
3202
|
+
{
|
|
3203
|
+
id: Versions9.MakeUrlsValid,
|
|
3204
|
+
up: (props) => {
|
|
3205
|
+
if (!T.linkUrl.isValid(props.url)) {
|
|
3206
|
+
props.url = "";
|
|
3207
|
+
}
|
|
3208
|
+
},
|
|
3209
|
+
down: (_props) => {
|
|
3210
|
+
}
|
|
3211
|
+
},
|
|
3212
|
+
{
|
|
3213
|
+
id: Versions9.AddFlipProps,
|
|
3214
|
+
up: (props) => {
|
|
3215
|
+
props.flipX = false;
|
|
3216
|
+
props.flipY = false;
|
|
3217
|
+
},
|
|
3218
|
+
down: (props) => {
|
|
3219
|
+
delete props.flipX;
|
|
3220
|
+
delete props.flipY;
|
|
3221
|
+
}
|
|
3222
|
+
},
|
|
3223
|
+
{
|
|
3224
|
+
id: Versions9.AddAltText,
|
|
3225
|
+
up: (props) => {
|
|
3226
|
+
props.altText = "";
|
|
3227
|
+
},
|
|
3228
|
+
down: (props) => {
|
|
3229
|
+
delete props.altText;
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
]
|
|
3233
|
+
});
|
|
3234
|
+
var LineShapeSplineStyle = StyleProp.defineEnum("draw:spline", {
|
|
3235
|
+
defaultValue: "line",
|
|
3236
|
+
values: ["cubic", "line"]
|
|
3237
|
+
});
|
|
3238
|
+
var lineShapePointValidator = T.object({
|
|
3239
|
+
id: T.string,
|
|
3240
|
+
index: T.indexKey,
|
|
3241
|
+
x: T.number,
|
|
3242
|
+
y: T.number
|
|
3243
|
+
});
|
|
3244
|
+
var lineShapeProps = {
|
|
3245
|
+
color: DefaultColorStyle,
|
|
3246
|
+
dash: DefaultDashStyle,
|
|
3247
|
+
size: DefaultSizeStyle,
|
|
3248
|
+
spline: LineShapeSplineStyle,
|
|
3249
|
+
points: T.dict(T.string, lineShapePointValidator),
|
|
3250
|
+
scale: T.nonZeroNumber
|
|
3251
|
+
};
|
|
3252
|
+
var lineShapeVersions = createShapePropsMigrationIds("line", {
|
|
3253
|
+
AddSnapHandles: 1,
|
|
3254
|
+
RemoveExtraHandleProps: 2,
|
|
3255
|
+
HandlesToPoints: 3,
|
|
3256
|
+
PointIndexIds: 4,
|
|
3257
|
+
AddScale: 5
|
|
3258
|
+
});
|
|
3259
|
+
var lineShapeMigrations = createShapePropsMigrationSequence({
|
|
3260
|
+
sequence: [
|
|
3261
|
+
{
|
|
3262
|
+
id: lineShapeVersions.AddSnapHandles,
|
|
3263
|
+
up: (props) => {
|
|
3264
|
+
for (const handle of Object.values(props.handles)) {
|
|
3265
|
+
handle.canSnap = true;
|
|
3266
|
+
}
|
|
3267
|
+
},
|
|
3268
|
+
down: "retired"
|
|
3269
|
+
},
|
|
3270
|
+
{
|
|
3271
|
+
id: lineShapeVersions.RemoveExtraHandleProps,
|
|
3272
|
+
up: (props) => {
|
|
3273
|
+
props.handles = objectMapFromEntries(
|
|
3274
|
+
Object.values(props.handles).map((handle) => [
|
|
3275
|
+
handle.index,
|
|
3276
|
+
{
|
|
3277
|
+
x: handle.x,
|
|
3278
|
+
y: handle.y
|
|
3279
|
+
}
|
|
3280
|
+
])
|
|
3281
|
+
);
|
|
3282
|
+
},
|
|
3283
|
+
down: (props) => {
|
|
3284
|
+
const handles = Object.entries(props.handles).map(([index, handle]) => ({ index, ...handle })).sort(sortByIndex);
|
|
3285
|
+
props.handles = Object.fromEntries(
|
|
3286
|
+
handles.map((handle, i) => {
|
|
3287
|
+
const id = i === 0 ? "start" : i === handles.length - 1 ? "end" : `handle:${handle.index}`;
|
|
3288
|
+
return [
|
|
3289
|
+
id,
|
|
3290
|
+
{
|
|
3291
|
+
id,
|
|
3292
|
+
type: "vertex",
|
|
3293
|
+
canBind: false,
|
|
3294
|
+
canSnap: true,
|
|
3295
|
+
index: handle.index,
|
|
3296
|
+
x: handle.x,
|
|
3297
|
+
y: handle.y
|
|
3298
|
+
}
|
|
3299
|
+
];
|
|
3300
|
+
})
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
3303
|
+
},
|
|
3304
|
+
{
|
|
3305
|
+
id: lineShapeVersions.HandlesToPoints,
|
|
3306
|
+
up: (props) => {
|
|
3307
|
+
const sortedHandles = Object.entries(props.handles).map(([index, { x, y }]) => ({ x, y, index })).sort(sortByIndex);
|
|
3308
|
+
props.points = sortedHandles.map(({ x, y }) => ({ x, y }));
|
|
3309
|
+
delete props.handles;
|
|
3310
|
+
},
|
|
3311
|
+
down: (props) => {
|
|
3312
|
+
const indices = getIndices(props.points.length);
|
|
3313
|
+
props.handles = Object.fromEntries(
|
|
3314
|
+
props.points.map((handle, i) => {
|
|
3315
|
+
const index = indices[i];
|
|
3316
|
+
return [
|
|
3317
|
+
index,
|
|
3318
|
+
{
|
|
3319
|
+
x: handle.x,
|
|
3320
|
+
y: handle.y
|
|
3321
|
+
}
|
|
3322
|
+
];
|
|
3323
|
+
})
|
|
3324
|
+
);
|
|
3325
|
+
delete props.points;
|
|
3326
|
+
}
|
|
3327
|
+
},
|
|
3328
|
+
{
|
|
3329
|
+
id: lineShapeVersions.PointIndexIds,
|
|
3330
|
+
up: (props) => {
|
|
3331
|
+
const indices = getIndices(props.points.length);
|
|
3332
|
+
props.points = Object.fromEntries(
|
|
3333
|
+
props.points.map((point, i) => {
|
|
3334
|
+
const id = indices[i];
|
|
3335
|
+
return [
|
|
3336
|
+
id,
|
|
3337
|
+
{
|
|
3338
|
+
id,
|
|
3339
|
+
index: id,
|
|
3340
|
+
x: point.x,
|
|
3341
|
+
y: point.y
|
|
3342
|
+
}
|
|
3343
|
+
];
|
|
3344
|
+
})
|
|
3345
|
+
);
|
|
3346
|
+
},
|
|
3347
|
+
down: (props) => {
|
|
3348
|
+
const sortedHandles = Object.values(props.points).sort(sortByIndex);
|
|
3349
|
+
props.points = sortedHandles.map(({ x, y }) => ({ x, y }));
|
|
3350
|
+
}
|
|
3351
|
+
},
|
|
3352
|
+
{
|
|
3353
|
+
id: lineShapeVersions.AddScale,
|
|
3354
|
+
up: (props) => {
|
|
3355
|
+
props.scale = 1;
|
|
3356
|
+
},
|
|
3357
|
+
down: (props) => {
|
|
3358
|
+
delete props.scale;
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
]
|
|
3362
|
+
});
|
|
3363
|
+
var noteShapeProps = {
|
|
3364
|
+
color: DefaultColorStyle,
|
|
3365
|
+
labelColor: DefaultLabelColorStyle,
|
|
3366
|
+
size: DefaultSizeStyle,
|
|
3367
|
+
font: DefaultFontStyle,
|
|
3368
|
+
fontSizeAdjustment: T.positiveNumber.nullable(),
|
|
3369
|
+
align: DefaultHorizontalAlignStyle,
|
|
3370
|
+
verticalAlign: DefaultVerticalAlignStyle,
|
|
3371
|
+
growY: T.positiveNumber,
|
|
3372
|
+
url: T.linkUrl,
|
|
3373
|
+
richText: richTextValidator,
|
|
3374
|
+
scale: T.nonZeroNumber,
|
|
3375
|
+
textFirstEditedBy: T.string.nullable()
|
|
3376
|
+
};
|
|
3377
|
+
var Versions10 = createShapePropsMigrationIds("note", {
|
|
3378
|
+
AddUrlProp: 1,
|
|
3379
|
+
RemoveJustify: 2,
|
|
3380
|
+
MigrateLegacyAlign: 3,
|
|
3381
|
+
AddVerticalAlign: 4,
|
|
3382
|
+
MakeUrlsValid: 5,
|
|
3383
|
+
AddFontSizeAdjustment: 6,
|
|
3384
|
+
AddScale: 7,
|
|
3385
|
+
AddLabelColor: 8,
|
|
3386
|
+
AddRichText: 9,
|
|
3387
|
+
AddRichTextAttrs: 10,
|
|
3388
|
+
AddFirstEditedBy: 11,
|
|
3389
|
+
MakeFontSizeAdjustmentRatio: 12
|
|
3390
|
+
});
|
|
3391
|
+
var noteShapeMigrations = createShapePropsMigrationSequence({
|
|
3392
|
+
sequence: [
|
|
3393
|
+
{
|
|
3394
|
+
id: Versions10.AddUrlProp,
|
|
3395
|
+
up: (props) => {
|
|
3396
|
+
props.url = "";
|
|
3397
|
+
},
|
|
3398
|
+
down: "retired"
|
|
3399
|
+
},
|
|
3400
|
+
{
|
|
3401
|
+
id: Versions10.RemoveJustify,
|
|
3402
|
+
up: (props) => {
|
|
3403
|
+
if (props.align === "justify") {
|
|
3404
|
+
props.align = "start";
|
|
3405
|
+
}
|
|
3406
|
+
},
|
|
3407
|
+
down: "retired"
|
|
3408
|
+
},
|
|
3409
|
+
{
|
|
3410
|
+
id: Versions10.MigrateLegacyAlign,
|
|
3411
|
+
up: (props) => {
|
|
3412
|
+
switch (props.align) {
|
|
3413
|
+
case "start":
|
|
3414
|
+
props.align = "start-legacy";
|
|
3415
|
+
return;
|
|
3416
|
+
case "end":
|
|
3417
|
+
props.align = "end-legacy";
|
|
3418
|
+
return;
|
|
3419
|
+
default:
|
|
3420
|
+
props.align = "middle-legacy";
|
|
3421
|
+
return;
|
|
3422
|
+
}
|
|
3423
|
+
},
|
|
3424
|
+
down: "retired"
|
|
3425
|
+
},
|
|
3426
|
+
{
|
|
3427
|
+
id: Versions10.AddVerticalAlign,
|
|
3428
|
+
up: (props) => {
|
|
3429
|
+
props.verticalAlign = "middle";
|
|
3430
|
+
},
|
|
3431
|
+
down: "retired"
|
|
3432
|
+
},
|
|
3433
|
+
{
|
|
3434
|
+
id: Versions10.MakeUrlsValid,
|
|
3435
|
+
up: (props) => {
|
|
3436
|
+
if (!T.linkUrl.isValid(props.url)) {
|
|
3437
|
+
props.url = "";
|
|
3438
|
+
}
|
|
3439
|
+
},
|
|
3440
|
+
down: (_props) => {
|
|
3441
|
+
}
|
|
3442
|
+
},
|
|
3443
|
+
{
|
|
3444
|
+
id: Versions10.AddFontSizeAdjustment,
|
|
3445
|
+
up: (props) => {
|
|
3446
|
+
props.fontSizeAdjustment = 0;
|
|
3447
|
+
},
|
|
3448
|
+
down: (props) => {
|
|
3449
|
+
delete props.fontSizeAdjustment;
|
|
3450
|
+
}
|
|
3451
|
+
},
|
|
3452
|
+
{
|
|
3453
|
+
id: Versions10.AddScale,
|
|
3454
|
+
up: (props) => {
|
|
3455
|
+
props.scale = 1;
|
|
3456
|
+
},
|
|
3457
|
+
down: (props) => {
|
|
3458
|
+
delete props.scale;
|
|
3459
|
+
}
|
|
3460
|
+
},
|
|
3461
|
+
{
|
|
3462
|
+
id: Versions10.AddLabelColor,
|
|
3463
|
+
up: (props) => {
|
|
3464
|
+
props.labelColor = "black";
|
|
3465
|
+
},
|
|
3466
|
+
down: (props) => {
|
|
3467
|
+
delete props.labelColor;
|
|
3468
|
+
}
|
|
3469
|
+
},
|
|
3470
|
+
{
|
|
3471
|
+
id: Versions10.AddRichText,
|
|
3472
|
+
up: (props) => {
|
|
3473
|
+
props.richText = toRichText(props.text);
|
|
3474
|
+
delete props.text;
|
|
3475
|
+
}
|
|
3476
|
+
// N.B. Explicitly no down state so that we force clients to update.
|
|
3477
|
+
// down: (props) => {
|
|
3478
|
+
// delete props.richText
|
|
3479
|
+
// },
|
|
3480
|
+
},
|
|
3481
|
+
{
|
|
3482
|
+
id: Versions10.AddRichTextAttrs,
|
|
3483
|
+
up: (_props) => {
|
|
3484
|
+
},
|
|
3485
|
+
down: (props) => {
|
|
3486
|
+
if (props.richText && "attrs" in props.richText) {
|
|
3487
|
+
delete props.richText.attrs;
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
},
|
|
3491
|
+
{
|
|
3492
|
+
id: Versions10.AddFirstEditedBy,
|
|
3493
|
+
up: (props) => {
|
|
3494
|
+
props.textFirstEditedBy = null;
|
|
3495
|
+
},
|
|
3496
|
+
down: (props) => {
|
|
3497
|
+
delete props.textFirstEditedBy;
|
|
3498
|
+
}
|
|
3499
|
+
},
|
|
3500
|
+
{
|
|
3501
|
+
id: Versions10.MakeFontSizeAdjustmentRatio,
|
|
3502
|
+
up: (props) => {
|
|
3503
|
+
props.fontSizeAdjustment = props.fontSizeAdjustment === 0 ? 1 : null;
|
|
3504
|
+
},
|
|
3505
|
+
down: (props) => {
|
|
3506
|
+
props.fontSizeAdjustment = 0;
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
]
|
|
3510
|
+
});
|
|
3511
|
+
|
|
3512
|
+
// src/styles/DrTextAlignStyle.ts
|
|
3513
|
+
var DefaultTextAlignStyle = StyleProp.defineEnum("draw:textAlign", {
|
|
3514
|
+
defaultValue: "start",
|
|
3515
|
+
values: ["start", "middle", "end"]
|
|
3516
|
+
});
|
|
3517
|
+
|
|
3518
|
+
// src/shapes/DrTextShape.ts
|
|
3519
|
+
var textShapeProps = {
|
|
3520
|
+
color: DefaultColorStyle,
|
|
3521
|
+
size: DefaultSizeStyle,
|
|
3522
|
+
font: DefaultFontStyle,
|
|
3523
|
+
textAlign: DefaultTextAlignStyle,
|
|
3524
|
+
w: T.nonZeroNumber,
|
|
3525
|
+
richText: richTextValidator,
|
|
3526
|
+
scale: T.nonZeroNumber,
|
|
3527
|
+
autoSize: T.boolean
|
|
3528
|
+
};
|
|
3529
|
+
var Versions11 = createShapePropsMigrationIds("text", {
|
|
3530
|
+
RemoveJustify: 1,
|
|
3531
|
+
AddTextAlign: 2,
|
|
3532
|
+
AddRichText: 3,
|
|
3533
|
+
AddRichTextAttrs: 4
|
|
3534
|
+
});
|
|
3535
|
+
var textShapeMigrations = createShapePropsMigrationSequence({
|
|
3536
|
+
sequence: [
|
|
3537
|
+
{
|
|
3538
|
+
id: Versions11.RemoveJustify,
|
|
3539
|
+
up: (props) => {
|
|
3540
|
+
if (props.align === "justify") {
|
|
3541
|
+
props.align = "start";
|
|
3542
|
+
}
|
|
3543
|
+
},
|
|
3544
|
+
down: "retired"
|
|
3545
|
+
},
|
|
3546
|
+
{
|
|
3547
|
+
id: Versions11.AddTextAlign,
|
|
3548
|
+
up: (props) => {
|
|
3549
|
+
props.textAlign = props.align;
|
|
3550
|
+
delete props.align;
|
|
3551
|
+
},
|
|
3552
|
+
down: (props) => {
|
|
3553
|
+
props.align = props.textAlign;
|
|
3554
|
+
delete props.textAlign;
|
|
3555
|
+
}
|
|
3556
|
+
},
|
|
3557
|
+
{
|
|
3558
|
+
id: Versions11.AddRichText,
|
|
3559
|
+
up: (props) => {
|
|
3560
|
+
props.richText = toRichText(props.text);
|
|
3561
|
+
delete props.text;
|
|
3562
|
+
}
|
|
3563
|
+
// N.B. Explicitly no down state so that we force clients to update.
|
|
3564
|
+
// down: (props) => {
|
|
3565
|
+
// delete props.richText
|
|
3566
|
+
// },
|
|
3567
|
+
},
|
|
3568
|
+
{
|
|
3569
|
+
id: Versions11.AddRichTextAttrs,
|
|
3570
|
+
up: (_props) => {
|
|
3571
|
+
},
|
|
3572
|
+
down: (props) => {
|
|
3573
|
+
if (props.richText && "attrs" in props.richText) {
|
|
3574
|
+
delete props.richText.attrs;
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
]
|
|
3579
|
+
});
|
|
3580
|
+
var videoShapeProps = {
|
|
3581
|
+
w: T.nonZeroNumber,
|
|
3582
|
+
h: T.nonZeroNumber,
|
|
3583
|
+
time: T.number,
|
|
3584
|
+
playing: T.boolean,
|
|
3585
|
+
autoplay: T.boolean,
|
|
3586
|
+
url: T.linkUrl,
|
|
3587
|
+
assetId: assetIdValidator.nullable(),
|
|
3588
|
+
altText: T.string
|
|
3589
|
+
};
|
|
3590
|
+
var Versions12 = createShapePropsMigrationIds("video", {
|
|
3591
|
+
AddUrlProp: 1,
|
|
3592
|
+
MakeUrlsValid: 2,
|
|
3593
|
+
AddAltText: 3,
|
|
3594
|
+
AddAutoplay: 4
|
|
3595
|
+
});
|
|
3596
|
+
var videoShapeMigrations = createShapePropsMigrationSequence({
|
|
3597
|
+
sequence: [
|
|
3598
|
+
{
|
|
3599
|
+
id: Versions12.AddUrlProp,
|
|
3600
|
+
up: (props) => {
|
|
3601
|
+
props.url = "";
|
|
3602
|
+
},
|
|
3603
|
+
down: "retired"
|
|
3604
|
+
},
|
|
3605
|
+
{
|
|
3606
|
+
id: Versions12.MakeUrlsValid,
|
|
3607
|
+
up: (props) => {
|
|
3608
|
+
if (!T.linkUrl.isValid(props.url)) {
|
|
3609
|
+
props.url = "";
|
|
3610
|
+
}
|
|
3611
|
+
},
|
|
3612
|
+
down: (_props) => {
|
|
3613
|
+
}
|
|
3614
|
+
},
|
|
3615
|
+
{
|
|
3616
|
+
id: Versions12.AddAltText,
|
|
3617
|
+
up: (props) => {
|
|
3618
|
+
props.altText = "";
|
|
3619
|
+
},
|
|
3620
|
+
down: (props) => {
|
|
3621
|
+
delete props.altText;
|
|
3622
|
+
}
|
|
3623
|
+
},
|
|
3624
|
+
{
|
|
3625
|
+
id: Versions12.AddAutoplay,
|
|
3626
|
+
up: (props) => {
|
|
3627
|
+
props.autoplay = true;
|
|
3628
|
+
},
|
|
3629
|
+
down: (props) => {
|
|
3630
|
+
delete props.autoplay;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
]
|
|
3634
|
+
});
|
|
3635
|
+
var Versions13 = createMigrationIds("com.draw.store", {
|
|
3636
|
+
RemoveCodeAndIconShapeTypes: 1,
|
|
3637
|
+
AddInstancePresenceType: 2,
|
|
3638
|
+
RemoveTLUserAndPresenceAndAddPointer: 3,
|
|
3639
|
+
RemoveUserDocument: 4,
|
|
3640
|
+
FixIndexKeys: 5
|
|
3641
|
+
});
|
|
3642
|
+
var storeMigrations = createMigrationSequence({
|
|
3643
|
+
sequenceId: "com.draw.store",
|
|
3644
|
+
retroactive: false,
|
|
3645
|
+
sequence: [
|
|
3646
|
+
{
|
|
3647
|
+
id: Versions13.RemoveCodeAndIconShapeTypes,
|
|
3648
|
+
scope: "storage",
|
|
3649
|
+
up: (storage) => {
|
|
3650
|
+
for (const [id, record] of storage.entries()) {
|
|
3651
|
+
if (record.typeName === "shape" && "type" in record && (record.type === "icon" || record.type === "code")) {
|
|
3652
|
+
storage.delete(id);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
},
|
|
3657
|
+
{
|
|
3658
|
+
id: Versions13.AddInstancePresenceType,
|
|
3659
|
+
scope: "storage",
|
|
3660
|
+
up(_storage) {
|
|
3661
|
+
}
|
|
3662
|
+
},
|
|
3663
|
+
{
|
|
3664
|
+
// remove user and presence records and add pointer records
|
|
3665
|
+
id: Versions13.RemoveTLUserAndPresenceAndAddPointer,
|
|
3666
|
+
scope: "storage",
|
|
3667
|
+
up: (storage) => {
|
|
3668
|
+
for (const [id, record] of storage.entries()) {
|
|
3669
|
+
if (record.typeName.match(/^(user|user_presence)$/)) {
|
|
3670
|
+
storage.delete(id);
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
},
|
|
3675
|
+
{
|
|
3676
|
+
// remove user document records
|
|
3677
|
+
id: Versions13.RemoveUserDocument,
|
|
3678
|
+
scope: "storage",
|
|
3679
|
+
up: (storage) => {
|
|
3680
|
+
for (const [id, record] of storage.entries()) {
|
|
3681
|
+
if (record.typeName.match("user_document")) {
|
|
3682
|
+
storage.delete(id);
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
},
|
|
3687
|
+
{
|
|
3688
|
+
id: Versions13.FixIndexKeys,
|
|
3689
|
+
scope: "record",
|
|
3690
|
+
up: (record) => {
|
|
3691
|
+
if (["shape", "page"].includes(record.typeName) && "index" in record) {
|
|
3692
|
+
const recordWithIndex = record;
|
|
3693
|
+
if (recordWithIndex.index.endsWith("0") && recordWithIndex.index !== "a0") {
|
|
3694
|
+
recordWithIndex.index = recordWithIndex.index.slice(0, -1) + getNRandomBase62Digits(3);
|
|
3695
|
+
}
|
|
3696
|
+
if (record.typeName === "shape" && recordWithIndex.type === "line") {
|
|
3697
|
+
const lineShape = recordWithIndex;
|
|
3698
|
+
for (const [_, point] of objectMapEntries(lineShape.props.points)) {
|
|
3699
|
+
if (point.index.endsWith("0") && point.index !== "a0") {
|
|
3700
|
+
point.index = point.index.slice(0, -1) + getNRandomBase62Digits(3);
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
},
|
|
3706
|
+
down: () => {
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
]
|
|
3710
|
+
});
|
|
3711
|
+
var BASE_62_DIGITS_WITHOUT_ZERO = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
3712
|
+
var getRandomBase62Digit = () => {
|
|
3713
|
+
return BASE_62_DIGITS_WITHOUT_ZERO.charAt(
|
|
3714
|
+
Math.floor(Math.random() * BASE_62_DIGITS_WITHOUT_ZERO.length)
|
|
3715
|
+
);
|
|
3716
|
+
};
|
|
3717
|
+
var getNRandomBase62Digits = (n) => {
|
|
3718
|
+
return Array.from({ length: n }, getRandomBase62Digit).join("");
|
|
3719
|
+
};
|
|
3720
|
+
function redactRecordForErrorReporting(record) {
|
|
3721
|
+
if (record.typeName === "asset") {
|
|
3722
|
+
if ("src" in record) {
|
|
3723
|
+
record.src = "<redacted>";
|
|
3724
|
+
}
|
|
3725
|
+
if ("src" in record.props) {
|
|
3726
|
+
record.props.src = "<redacted>";
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
function createCachedUserResolve(resolveFn) {
|
|
3731
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3732
|
+
return (userId) => {
|
|
3733
|
+
let signal = cache.get(userId);
|
|
3734
|
+
if (!signal) {
|
|
3735
|
+
signal = computed("resolve-user-" + userId, () => resolveFn(userId));
|
|
3736
|
+
cache.set(userId, signal);
|
|
3737
|
+
}
|
|
3738
|
+
return signal;
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
function onValidationFailure({
|
|
3742
|
+
error,
|
|
3743
|
+
phase,
|
|
3744
|
+
record,
|
|
3745
|
+
recordBefore
|
|
3746
|
+
}) {
|
|
3747
|
+
const isExistingValidationIssue = (
|
|
3748
|
+
// if we're initializing the store for the first time, we should
|
|
3749
|
+
// allow invalid records so people can load old buggy data:
|
|
3750
|
+
phase === "initialize"
|
|
3751
|
+
);
|
|
3752
|
+
annotateError(error, {
|
|
3753
|
+
tags: {
|
|
3754
|
+
origin: "store.validateRecord",
|
|
3755
|
+
storePhase: phase,
|
|
3756
|
+
isExistingValidationIssue
|
|
3757
|
+
},
|
|
3758
|
+
extras: {
|
|
3759
|
+
recordBefore: recordBefore ? redactRecordForErrorReporting(structuredClone(recordBefore)) : void 0,
|
|
3760
|
+
recordAfter: redactRecordForErrorReporting(structuredClone(record))
|
|
3761
|
+
}
|
|
3762
|
+
});
|
|
3763
|
+
throw error;
|
|
3764
|
+
}
|
|
3765
|
+
function getDefaultPages() {
|
|
3766
|
+
return [
|
|
3767
|
+
PageRecordType.create({
|
|
3768
|
+
id: "page:page",
|
|
3769
|
+
name: "Page 1",
|
|
3770
|
+
index: "a1",
|
|
3771
|
+
meta: {}
|
|
3772
|
+
})
|
|
3773
|
+
];
|
|
3774
|
+
}
|
|
3775
|
+
function createIntegrityChecker(store) {
|
|
3776
|
+
const $pageIds = store.query.ids("page");
|
|
3777
|
+
const $pageStates = store.query.records("instance_page_state");
|
|
3778
|
+
const ensureStoreIsUsable = () => {
|
|
3779
|
+
if (!store.has(DrDOCUMENT_ID)) {
|
|
3780
|
+
store.put([DocumentRecordType.create({ id: DrDOCUMENT_ID, name: store.props.defaultName })]);
|
|
3781
|
+
return ensureStoreIsUsable();
|
|
3782
|
+
}
|
|
3783
|
+
if (!store.has(DrPOINTER_ID)) {
|
|
3784
|
+
store.put([PointerRecordType.create({ id: DrPOINTER_ID })]);
|
|
3785
|
+
return ensureStoreIsUsable();
|
|
3786
|
+
}
|
|
3787
|
+
const pageIds = $pageIds.get();
|
|
3788
|
+
if (pageIds.size === 0) {
|
|
3789
|
+
store.put(getDefaultPages());
|
|
3790
|
+
return ensureStoreIsUsable();
|
|
3791
|
+
}
|
|
3792
|
+
const getFirstPageId = () => [...pageIds].map((id) => store.get(id)).sort(sortByIndex)[0].id;
|
|
3793
|
+
const instanceState = store.get(DrINSTANCE_ID);
|
|
3794
|
+
if (!instanceState) {
|
|
3795
|
+
store.put([
|
|
3796
|
+
store.schema.types.instance.create({
|
|
3797
|
+
id: DrINSTANCE_ID,
|
|
3798
|
+
currentPageId: getFirstPageId(),
|
|
3799
|
+
exportBackground: true
|
|
3800
|
+
})
|
|
3801
|
+
]);
|
|
3802
|
+
return ensureStoreIsUsable();
|
|
3803
|
+
} else if (!pageIds.has(instanceState.currentPageId)) {
|
|
3804
|
+
store.put([{ ...instanceState, currentPageId: getFirstPageId() }]);
|
|
3805
|
+
return ensureStoreIsUsable();
|
|
3806
|
+
}
|
|
3807
|
+
const missingPageStateIds = /* @__PURE__ */ new Set();
|
|
3808
|
+
const missingCameraIds = /* @__PURE__ */ new Set();
|
|
3809
|
+
for (const id of pageIds) {
|
|
3810
|
+
const pageStateId = InstancePageStateRecordType.createId(id);
|
|
3811
|
+
const pageState = store.get(pageStateId);
|
|
3812
|
+
if (!pageState) {
|
|
3813
|
+
missingPageStateIds.add(pageStateId);
|
|
3814
|
+
}
|
|
3815
|
+
const cameraId = CameraRecordType.createId(id);
|
|
3816
|
+
if (!store.has(cameraId)) {
|
|
3817
|
+
missingCameraIds.add(cameraId);
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
if (missingPageStateIds.size > 0) {
|
|
3821
|
+
store.put(
|
|
3822
|
+
[...missingPageStateIds].map(
|
|
3823
|
+
(id) => InstancePageStateRecordType.create({
|
|
3824
|
+
id,
|
|
3825
|
+
pageId: InstancePageStateRecordType.parseId(id)
|
|
3826
|
+
})
|
|
3827
|
+
)
|
|
3828
|
+
);
|
|
3829
|
+
}
|
|
3830
|
+
if (missingCameraIds.size > 0) {
|
|
3831
|
+
store.put([...missingCameraIds].map((id) => CameraRecordType.create({ id })));
|
|
3832
|
+
}
|
|
3833
|
+
const pageStates = $pageStates.get();
|
|
3834
|
+
for (const pageState of pageStates) {
|
|
3835
|
+
if (!pageIds.has(pageState.pageId)) {
|
|
3836
|
+
store.remove([pageState.id]);
|
|
3837
|
+
continue;
|
|
3838
|
+
}
|
|
3839
|
+
if (pageState.croppingShapeId && !store.has(pageState.croppingShapeId)) {
|
|
3840
|
+
store.put([{ ...pageState, croppingShapeId: null }]);
|
|
3841
|
+
return ensureStoreIsUsable();
|
|
3842
|
+
}
|
|
3843
|
+
if (pageState.focusedGroupId && !store.has(pageState.focusedGroupId)) {
|
|
3844
|
+
store.put([{ ...pageState, focusedGroupId: null }]);
|
|
3845
|
+
return ensureStoreIsUsable();
|
|
3846
|
+
}
|
|
3847
|
+
if (pageState.hoveredShapeId && !store.has(pageState.hoveredShapeId)) {
|
|
3848
|
+
store.put([{ ...pageState, hoveredShapeId: null }]);
|
|
3849
|
+
return ensureStoreIsUsable();
|
|
3850
|
+
}
|
|
3851
|
+
const filteredSelectedIds = pageState.selectedShapeIds.filter((id) => store.has(id));
|
|
3852
|
+
if (filteredSelectedIds.length !== pageState.selectedShapeIds.length) {
|
|
3853
|
+
store.put([{ ...pageState, selectedShapeIds: filteredSelectedIds }]);
|
|
3854
|
+
return ensureStoreIsUsable();
|
|
3855
|
+
}
|
|
3856
|
+
const filteredHintingIds = pageState.hintingShapeIds.filter((id) => store.has(id));
|
|
3857
|
+
if (filteredHintingIds.length !== pageState.hintingShapeIds.length) {
|
|
3858
|
+
store.put([{ ...pageState, hintingShapeIds: filteredHintingIds }]);
|
|
3859
|
+
return ensureStoreIsUsable();
|
|
3860
|
+
}
|
|
3861
|
+
const filteredErasingIds = pageState.erasingShapeIds.filter((id) => store.has(id));
|
|
3862
|
+
if (filteredErasingIds.length !== pageState.erasingShapeIds.length) {
|
|
3863
|
+
store.put([{ ...pageState, erasingShapeIds: filteredErasingIds }]);
|
|
3864
|
+
return ensureStoreIsUsable();
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
};
|
|
3868
|
+
return ensureStoreIsUsable;
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
// src/createDrSchema.ts
|
|
3872
|
+
var defaultShapeSchemas = {
|
|
3873
|
+
arrow: { migrations: arrowShapeMigrations, props: arrowShapeProps },
|
|
3874
|
+
bookmark: { migrations: bookmarkShapeMigrations, props: bookmarkShapeProps },
|
|
3875
|
+
draw: { migrations: drawShapeMigrations, props: drawShapeProps },
|
|
3876
|
+
embed: { migrations: embedShapeMigrations, props: embedShapeProps },
|
|
3877
|
+
frame: { migrations: frameShapeMigrations, props: frameShapeProps },
|
|
3878
|
+
geo: { migrations: geoShapeMigrations, props: geoShapeProps },
|
|
3879
|
+
group: { migrations: groupShapeMigrations, props: groupShapeProps },
|
|
3880
|
+
highlight: { migrations: highlightShapeMigrations, props: highlightShapeProps },
|
|
3881
|
+
image: { migrations: imageShapeMigrations, props: imageShapeProps },
|
|
3882
|
+
line: { migrations: lineShapeMigrations, props: lineShapeProps },
|
|
3883
|
+
note: { migrations: noteShapeMigrations, props: noteShapeProps },
|
|
3884
|
+
text: { migrations: textShapeMigrations, props: textShapeProps },
|
|
3885
|
+
video: { migrations: videoShapeMigrations, props: videoShapeProps }
|
|
3886
|
+
};
|
|
3887
|
+
var defaultBindingSchemas = {
|
|
3888
|
+
arrow: { migrations: arrowBindingMigrations, props: arrowBindingProps }
|
|
3889
|
+
};
|
|
3890
|
+
var defaultAssetSchemas = {
|
|
3891
|
+
image: { migrations: imageAssetMigrations, props: imageAssetProps },
|
|
3892
|
+
video: { migrations: videoAssetMigrations, props: videoAssetProps },
|
|
3893
|
+
bookmark: { migrations: bookmarkAssetMigrations, props: bookmarkAssetProps }
|
|
3894
|
+
};
|
|
3895
|
+
function createDrSchema({
|
|
3896
|
+
shapes = defaultShapeSchemas,
|
|
3897
|
+
bindings = defaultBindingSchemas,
|
|
3898
|
+
assets = defaultAssetSchemas,
|
|
3899
|
+
user,
|
|
3900
|
+
records = {},
|
|
3901
|
+
migrations
|
|
3902
|
+
} = {}) {
|
|
3903
|
+
const stylesById = /* @__PURE__ */ new Map();
|
|
3904
|
+
for (const shape of objectMapValues(shapes)) {
|
|
3905
|
+
for (const style of getShapePropKeysByStyle(shape.props ?? {}).keys()) {
|
|
3906
|
+
if (stylesById.has(style.id) && stylesById.get(style.id) !== style) {
|
|
3907
|
+
throw new Error(`Multiple StyleProp instances with the same id: ${style.id}`);
|
|
3908
|
+
}
|
|
3909
|
+
stylesById.set(style.id, style);
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
const ShapeRecordType = createShapeRecordType(shapes);
|
|
3913
|
+
const BindingRecordType = createBindingRecordType(bindings);
|
|
3914
|
+
const _AssetRecordType = createAssetRecordType(assets);
|
|
3915
|
+
const InstanceRecordType = createInstanceRecordType(stylesById);
|
|
3916
|
+
const CustomUserRecordType = user ? createUserRecordType(user) : UserRecordType;
|
|
3917
|
+
const builtInTypeNames = /* @__PURE__ */ new Set([
|
|
3918
|
+
"asset",
|
|
3919
|
+
"binding",
|
|
3920
|
+
"camera",
|
|
3921
|
+
"document",
|
|
3922
|
+
"instance",
|
|
3923
|
+
"instance_page_state",
|
|
3924
|
+
"page",
|
|
3925
|
+
"instance_presence",
|
|
3926
|
+
"pointer",
|
|
3927
|
+
"shape",
|
|
3928
|
+
"store",
|
|
3929
|
+
"user"
|
|
3930
|
+
]);
|
|
3931
|
+
const customRecordTypes = {};
|
|
3932
|
+
for (const [typeName, config] of Object.entries(records)) {
|
|
3933
|
+
if (builtInTypeNames.has(typeName)) {
|
|
3934
|
+
throw new Error(
|
|
3935
|
+
`Custom record type name '${typeName}' conflicts with tldraw's built-in record type of that name. Choose a different name instead.`
|
|
3936
|
+
);
|
|
3937
|
+
}
|
|
3938
|
+
customRecordTypes[typeName] = createCustomRecordType(typeName, config);
|
|
3939
|
+
}
|
|
3940
|
+
return StoreSchema.create(
|
|
3941
|
+
{
|
|
3942
|
+
asset: _AssetRecordType,
|
|
3943
|
+
binding: BindingRecordType,
|
|
3944
|
+
camera: CameraRecordType,
|
|
3945
|
+
document: DocumentRecordType,
|
|
3946
|
+
instance: InstanceRecordType,
|
|
3947
|
+
instance_page_state: InstancePageStateRecordType,
|
|
3948
|
+
page: PageRecordType,
|
|
3949
|
+
instance_presence: InstancePresenceRecordType,
|
|
3950
|
+
pointer: PointerRecordType,
|
|
3951
|
+
shape: ShapeRecordType,
|
|
3952
|
+
user: CustomUserRecordType,
|
|
3953
|
+
...customRecordTypes
|
|
3954
|
+
},
|
|
3955
|
+
{
|
|
3956
|
+
migrations: [
|
|
3957
|
+
storeMigrations,
|
|
3958
|
+
assetMigrations,
|
|
3959
|
+
cameraMigrations,
|
|
3960
|
+
documentMigrations,
|
|
3961
|
+
instanceMigrations,
|
|
3962
|
+
instancePageStateMigrations,
|
|
3963
|
+
pageMigrations,
|
|
3964
|
+
instancePresenceMigrations,
|
|
3965
|
+
pointerMigrations,
|
|
3966
|
+
rootShapeMigrations,
|
|
3967
|
+
userMigrations,
|
|
3968
|
+
...processPropsMigrations("asset", assets),
|
|
3969
|
+
...processPropsMigrations("shape", shapes),
|
|
3970
|
+
...processPropsMigrations("binding", bindings),
|
|
3971
|
+
...processCustomRecordMigrations(records),
|
|
3972
|
+
...user?.migrations ?? [],
|
|
3973
|
+
...migrations ?? []
|
|
3974
|
+
],
|
|
3975
|
+
onValidationFailure,
|
|
3976
|
+
createIntegrityChecker
|
|
3977
|
+
}
|
|
3978
|
+
);
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3981
|
+
// src/misc/DrHandle.ts
|
|
3982
|
+
var TL_HANDLE_TYPES = /* @__PURE__ */ new Set(["vertex", "virtual", "create", "clone"]);
|
|
3983
|
+
|
|
3984
|
+
// src/translations/languages.ts
|
|
3985
|
+
var LANGUAGES = [
|
|
3986
|
+
{ locale: "id", label: "Bahasa Indonesia" },
|
|
3987
|
+
{ locale: "ms", label: "Bahasa Melayu" },
|
|
3988
|
+
{ locale: "ca", label: "Catal\xE0" },
|
|
3989
|
+
{ locale: "cs", label: "\u010Ce\u0161tina" },
|
|
3990
|
+
{ locale: "da", label: "Danish" },
|
|
3991
|
+
{ locale: "de", label: "Deutsch" },
|
|
3992
|
+
{ locale: "en", label: "English" },
|
|
3993
|
+
{ locale: "es", label: "Espa\xF1ol" },
|
|
3994
|
+
{ locale: "tl", label: "Filipino" },
|
|
3995
|
+
{ locale: "fr", label: "Fran\xE7ais" },
|
|
3996
|
+
{ locale: "gl", label: "Galego" },
|
|
3997
|
+
{ locale: "hr", label: "Hrvatski" },
|
|
3998
|
+
{ locale: "it", label: "Italiano" },
|
|
3999
|
+
{ locale: "hu", label: "Magyar" },
|
|
4000
|
+
{ locale: "nl", label: "Nederlands" },
|
|
4001
|
+
{ locale: "no", label: "Norwegian" },
|
|
4002
|
+
{ locale: "pl", label: "Polski" },
|
|
4003
|
+
{ locale: "pt-br", label: "Portugu\xEAs - Brasil" },
|
|
4004
|
+
{ locale: "pt-pt", label: "Portugu\xEAs - Europeu" },
|
|
4005
|
+
{ locale: "ro", label: "Rom\xE2n\u0103" },
|
|
4006
|
+
{ locale: "sl", label: "Sloven\u0161\u010Dina" },
|
|
4007
|
+
{ locale: "so", label: "Somali" },
|
|
4008
|
+
{ locale: "fi", label: "Suomi" },
|
|
4009
|
+
{ locale: "sv", label: "Svenska" },
|
|
4010
|
+
{ locale: "vi", label: "Ti\u1EBFng Vi\u1EC7t" },
|
|
4011
|
+
{ locale: "tr", label: "T\xFCrk\xE7e" },
|
|
4012
|
+
{ locale: "el", label: "\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC" },
|
|
4013
|
+
{ locale: "ru", label: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439" },
|
|
4014
|
+
{ locale: "uk", label: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430" },
|
|
4015
|
+
{ locale: "he", label: "\u05E2\u05D1\u05E8\u05D9\u05EA" },
|
|
4016
|
+
{ locale: "ur", label: "\u0627\u0631\u062F\u0648" },
|
|
4017
|
+
{ locale: "ar", label: "\u0639\u0631\u0628\u064A" },
|
|
4018
|
+
{ locale: "fa", label: "\u0641\u0627\u0631\u0633\u06CC" },
|
|
4019
|
+
{ locale: "ne", label: "\u0928\u0947\u092A\u093E\u0932\u0940" },
|
|
4020
|
+
{ locale: "mr", label: "\u092E\u0930\u093E\u0920\u0940" },
|
|
4021
|
+
{ locale: "hi-in", label: "\u0939\u093F\u0928\u094D\u0926\u0940" },
|
|
4022
|
+
{ locale: "bn", label: "\u09AC\u09BE\u0982\u09B2\u09BE" },
|
|
4023
|
+
{ locale: "pa", label: "\u0A2A\u0A70\u0A1C\u0A3E\u0A2C\u0A40" },
|
|
4024
|
+
{ locale: "gu-in", label: "\u0A97\u0AC1\u0A9C\u0AB0\u0ABE\u0AA4\u0AC0" },
|
|
4025
|
+
{ locale: "ta", label: "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD" },
|
|
4026
|
+
{ locale: "te", label: "\u0C24\u0C46\u0C32\u0C41\u0C17\u0C41" },
|
|
4027
|
+
{ locale: "kn", label: "\u0C95\u0CA8\u0CCD\u0CA8\u0CA1" },
|
|
4028
|
+
{ locale: "ml", label: "\u0D2E\u0D32\u0D2F\u0D3E\u0D33\u0D02" },
|
|
4029
|
+
{ locale: "th", label: "\u0E20\u0E32\u0E29\u0E32\u0E44\u0E17\u0E22" },
|
|
4030
|
+
{ locale: "km-kh", label: "\u1797\u17B6\u179F\u17B6\u1781\u17D2\u1798\u17C2\u179A" },
|
|
4031
|
+
{ locale: "ko-kr", label: "\uD55C\uAD6D\uC5B4" },
|
|
4032
|
+
{ locale: "ja", label: "\u65E5\u672C\u8A9E" },
|
|
4033
|
+
{ locale: "zh-cn", label: "\u7B80\u4F53\u4E2D\u6587" },
|
|
4034
|
+
{ locale: "zh-tw", label: "\u7E41\u9AD4\u4E2D\u6587 (\u53F0\u7063)" }
|
|
4035
|
+
];
|
|
4036
|
+
|
|
4037
|
+
// src/translations/translations.ts
|
|
4038
|
+
function getDefaultTranslationLocale() {
|
|
4039
|
+
const locales = typeof window !== "undefined" && window.navigator ? window.navigator.languages ?? ["en"] : ["en"];
|
|
4040
|
+
return _getDefaultTranslationLocale(locales);
|
|
4041
|
+
}
|
|
4042
|
+
function _getDefaultTranslationLocale(locales) {
|
|
4043
|
+
for (const locale of locales) {
|
|
4044
|
+
const supportedLocale = getSupportedLocale(locale);
|
|
4045
|
+
if (supportedLocale) {
|
|
4046
|
+
return supportedLocale;
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
return "en";
|
|
4050
|
+
}
|
|
4051
|
+
var DEFAULT_LOCALE_REGIONS = {
|
|
4052
|
+
zh: "zh-cn",
|
|
4053
|
+
pt: "pt-br",
|
|
4054
|
+
ko: "ko-kr",
|
|
4055
|
+
hi: "hi-in"
|
|
4056
|
+
};
|
|
4057
|
+
function getSupportedLocale(locale) {
|
|
4058
|
+
const exactMatch = LANGUAGES.find((t) => t.locale === locale.toLowerCase());
|
|
4059
|
+
if (exactMatch) {
|
|
4060
|
+
return exactMatch.locale;
|
|
4061
|
+
}
|
|
4062
|
+
const [language, region] = locale.split(/[-_]/).map((s) => s.toLowerCase());
|
|
4063
|
+
if (region) {
|
|
4064
|
+
const languageMatch = LANGUAGES.find((t) => t.locale === language);
|
|
4065
|
+
if (languageMatch) {
|
|
4066
|
+
return languageMatch.locale;
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
if (language in DEFAULT_LOCALE_REGIONS) {
|
|
4070
|
+
return DEFAULT_LOCALE_REGIONS[language];
|
|
4071
|
+
}
|
|
4072
|
+
return null;
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
// src/index.ts
|
|
4076
|
+
registerDrawLibraryVersion(
|
|
4077
|
+
"@ibodr/schema",
|
|
4078
|
+
"0.0.0",
|
|
4079
|
+
"esm"
|
|
4080
|
+
);
|
|
4081
|
+
|
|
4082
|
+
export { ArrowShapeArrowheadEndStyle, ArrowShapeArrowheadStartStyle, ArrowShapeKindStyle, AssetRecordType, CameraRecordType, DefaultColorStyle, DefaultDashStyle, DefaultFillStyle, DefaultFontFamilies, DefaultFontStyle, DefaultHorizontalAlignStyle, DefaultSizeStyle, DefaultTextAlignStyle, DefaultVerticalAlignStyle, DocumentRecordType, DrDOCUMENT_ID, DrINSTANCE_ID, DrPOINTER_ID, ElbowArrowSnap, EnumStyleProp, GeoShapeGeoStyle, ImageShapeCrop, InstancePageStateRecordType, InstancePresenceRecordType, LANGUAGES, LineShapeSplineStyle, PageRecordType, PointerRecordType, StyleProp, TL_CANVAS_UI_COLOR_TYPES, TL_CURSOR_TYPES, TL_HANDLE_TYPES, TL_SCRIBBLE_STATES, UserRecordType, arrowBindingMigrations, arrowBindingProps, arrowBindingVersions, arrowShapeMigrations, arrowShapeProps, arrowShapeVersions, assetIdValidator, assetMigrations, b64Vecs, bindingIdValidator, bookmarkAssetMigrations, bookmarkAssetProps, bookmarkShapeMigrations, bookmarkShapeProps, boxModelValidator, canvasUiColorTypeValidator, compressLegacySegments, createAssetPropsMigrationIds, createAssetPropsMigrationSequence, createAssetRecordType, createAssetValidator, createBindingId, createBindingPropsMigrationIds, createBindingPropsMigrationSequence, createBindingValidator, createCachedUserResolve, createCustomRecordId, createCustomRecordMigrationIds, createCustomRecordMigrationSequence, createDrSchema, createPresenceStateDerivation, createShapeId, createShapePropsMigrationIds, createShapePropsMigrationSequence, createShapeValidator, createUserId, createUserRecordType, defaultAssetSchemas, defaultBindingSchemas, defaultShapeSchemas, drawShapeMigrations, drawShapeProps, embedShapeMigrations, embedShapeProps, frameShapeMigrations, frameShapeProps, geoShapeMigrations, geoShapeProps, getDefaultTranslationLocale, getDefaultUserPresence, getShapePropKeysByStyle, groupShapeMigrations, groupShapeProps, highlightShapeMigrations, highlightShapeProps, idValidator, imageAssetMigrations, imageAssetProps, imageShapeMigrations, imageShapeProps, isBinding, isBindingId, isCustomRecord, isCustomRecordId, isDocument, isFontEntry, isPageId, isShape, isShapeId, isUserId, lineShapeMigrations, lineShapeProps, noteShapeMigrations, noteShapeProps, opacityValidator, pageIdValidator, parentIdValidator, pluckPreservingValues, registerColorsFromThemes, registerFontsFromThemes, richTextValidator, rootBindingMigrations, rootShapeMigrations, scribbleValidator, shapeIdValidator, textShapeMigrations, textShapeProps, toRichText, userIdValidator, vecModelValidator, videoAssetMigrations, videoAssetProps, videoShapeMigrations, videoShapeProps };
|
|
4083
|
+
//# sourceMappingURL=index.mjs.map
|
|
4084
|
+
//# sourceMappingURL=index.mjs.map
|