@tldraw/tlschema 4.3.0-next.2d181ae353a2 → 4.3.0-next.40e4536afc8e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +82 -34
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/misc/TLOpacity.js +1 -5
- package/dist-cjs/misc/TLOpacity.js.map +2 -2
- package/dist-cjs/misc/TLRichText.js +5 -1
- package/dist-cjs/misc/TLRichText.js.map +2 -2
- package/dist-cjs/misc/b64Vecs.js +224 -0
- package/dist-cjs/misc/b64Vecs.js.map +7 -0
- package/dist-cjs/shapes/TLArrowShape.js +30 -13
- package/dist-cjs/shapes/TLArrowShape.js.map +2 -2
- package/dist-cjs/shapes/TLDrawShape.js +37 -4
- package/dist-cjs/shapes/TLDrawShape.js.map +2 -2
- package/dist-cjs/shapes/TLEmbedShape.js +17 -0
- package/dist-cjs/shapes/TLEmbedShape.js.map +2 -2
- package/dist-cjs/shapes/TLGeoShape.js +12 -1
- package/dist-cjs/shapes/TLGeoShape.js.map +2 -2
- package/dist-cjs/shapes/TLHighlightShape.js +29 -2
- package/dist-cjs/shapes/TLHighlightShape.js.map +2 -2
- package/dist-cjs/shapes/TLNoteShape.js +12 -1
- package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
- package/dist-cjs/shapes/TLTextShape.js +12 -1
- package/dist-cjs/shapes/TLTextShape.js.map +2 -2
- package/dist-cjs/store-migrations.js +14 -14
- package/dist-cjs/store-migrations.js.map +2 -2
- package/dist-esm/index.d.mts +82 -34
- package/dist-esm/index.mjs +5 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/misc/TLOpacity.mjs +1 -5
- package/dist-esm/misc/TLOpacity.mjs.map +2 -2
- package/dist-esm/misc/TLRichText.mjs +5 -1
- package/dist-esm/misc/TLRichText.mjs.map +2 -2
- package/dist-esm/misc/b64Vecs.mjs +204 -0
- package/dist-esm/misc/b64Vecs.mjs.map +7 -0
- package/dist-esm/shapes/TLArrowShape.mjs +30 -13
- package/dist-esm/shapes/TLArrowShape.mjs.map +2 -2
- package/dist-esm/shapes/TLDrawShape.mjs +37 -4
- package/dist-esm/shapes/TLDrawShape.mjs.map +2 -2
- package/dist-esm/shapes/TLEmbedShape.mjs +17 -0
- package/dist-esm/shapes/TLEmbedShape.mjs.map +2 -2
- package/dist-esm/shapes/TLGeoShape.mjs +12 -1
- package/dist-esm/shapes/TLGeoShape.mjs.map +2 -2
- package/dist-esm/shapes/TLHighlightShape.mjs +29 -2
- package/dist-esm/shapes/TLHighlightShape.mjs.map +2 -2
- package/dist-esm/shapes/TLNoteShape.mjs +12 -1
- package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
- package/dist-esm/shapes/TLTextShape.mjs +12 -1
- package/dist-esm/shapes/TLTextShape.mjs.map +2 -2
- package/dist-esm/store-migrations.mjs +14 -14
- package/dist-esm/store-migrations.mjs.map +2 -2
- package/package.json +8 -8
- package/src/__tests__/migrationTestUtils.ts +9 -3
- package/src/index.ts +3 -0
- package/src/migrations.test.ts +149 -1
- package/src/misc/TLOpacity.ts +1 -5
- package/src/misc/TLRichText.ts +6 -1
- package/src/misc/b64Vecs.ts +308 -0
- package/src/shapes/TLArrowShape.ts +36 -13
- package/src/shapes/TLDrawShape.ts +59 -12
- package/src/shapes/TLEmbedShape.ts +17 -0
- package/src/shapes/TLGeoShape.ts +14 -1
- package/src/shapes/TLHighlightShape.ts +37 -0
- package/src/shapes/TLNoteShape.ts +15 -1
- package/src/shapes/TLTextShape.ts +16 -2
- package/src/store-migrations.ts +15 -15
- package/src/assets/TLBookmarkAsset.test.ts +0 -96
- package/src/assets/TLImageAsset.test.ts +0 -213
- package/src/assets/TLVideoAsset.test.ts +0 -105
- package/src/bindings/TLArrowBinding.test.ts +0 -55
- package/src/misc/id-validator.test.ts +0 -50
- package/src/records/TLAsset.test.ts +0 -234
- package/src/records/TLBinding.test.ts +0 -22
- package/src/records/TLCamera.test.ts +0 -19
- package/src/records/TLDocument.test.ts +0 -35
- package/src/records/TLInstance.test.ts +0 -201
- package/src/records/TLPage.test.ts +0 -110
- package/src/records/TLPageState.test.ts +0 -228
- package/src/records/TLPointer.test.ts +0 -63
- package/src/records/TLPresence.test.ts +0 -190
- package/src/records/TLRecord.test.ts +0 -82
- package/src/records/TLShape.test.ts +0 -232
- package/src/shapes/ShapeWithCrop.test.ts +0 -18
- package/src/shapes/TLArrowShape.test.ts +0 -505
- package/src/shapes/TLBaseShape.test.ts +0 -142
- package/src/shapes/TLBookmarkShape.test.ts +0 -122
- package/src/shapes/TLDrawShape.test.ts +0 -177
- package/src/shapes/TLEmbedShape.test.ts +0 -286
- package/src/shapes/TLFrameShape.test.ts +0 -71
- package/src/shapes/TLGeoShape.test.ts +0 -247
- package/src/shapes/TLGroupShape.test.ts +0 -59
- package/src/shapes/TLHighlightShape.test.ts +0 -325
- package/src/shapes/TLImageShape.test.ts +0 -534
- package/src/shapes/TLLineShape.test.ts +0 -269
- package/src/shapes/TLNoteShape.test.ts +0 -1568
- package/src/shapes/TLTextShape.test.ts +0 -407
- package/src/shapes/TLVideoShape.test.ts +0 -112
- package/src/styles/TLColorStyle.test.ts +0 -439
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { T } from "@tldraw/validate";
|
|
2
|
-
const opacityValidator = T.
|
|
3
|
-
if (n < 0 || n > 1) {
|
|
4
|
-
throw new T.ValidationError("Opacity must be between 0 and 1");
|
|
5
|
-
}
|
|
6
|
-
});
|
|
2
|
+
const opacityValidator = T.unitInterval;
|
|
7
3
|
export {
|
|
8
4
|
opacityValidator
|
|
9
5
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/misc/TLOpacity.ts"],
|
|
4
|
-
"sourcesContent": ["import { T } from '@tldraw/validate'\n\n/**\n * A type representing opacity values in tldraw.\n *\n * Opacity values are numbers between 0 and 1, where 0 is fully transparent\n * and 1 is fully opaque. This type is used throughout the editor to control\n * the transparency of shapes, UI elements, and other visual components.\n *\n * @example\n * ```ts\n * const fullyOpaque: TLOpacityType = 1.0\n * const halfTransparent: TLOpacityType = 0.5\n * const fullyTransparent: TLOpacityType = 0.0\n * const quarterOpaque: TLOpacityType = 0.25\n * ```\n *\n * @public\n */\nexport type TLOpacityType = number\n\n/**\n * A validator for opacity values.\n *\n * This validator ensures that opacity values are numbers between 0 and 1 (inclusive).\n * Values outside this range will cause a validation error. The validator provides\n * runtime type checking for opacity properties throughout the editor.\n *\n * @param n - The number to validate as an opacity value\n * @throws T.ValidationError When the value is not between 0 and 1\n *\n * @example\n * ```ts\n * import { opacityValidator } from '@tldraw/tlschema'\n *\n * // Valid opacity values\n * try {\n * const validOpacity1 = opacityValidator.validate(0.5) // \u2713\n * const validOpacity2 = opacityValidator.validate(1.0) // \u2713\n * const validOpacity3 = opacityValidator.validate(0.0) // \u2713\n * } catch (error) {\n * console.error('Validation failed:', error.message)\n * }\n *\n * // Invalid opacity values\n * try {\n * opacityValidator.validate(-0.1) // \u2717 Throws error\n * opacityValidator.validate(1.5) // \u2717 Throws error\n * } catch (error) {\n * console.error('Invalid opacity:', error.message)\n * }\n * ```\n *\n * @public\n */\nexport const opacityValidator = T.
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAuDX,MAAM,mBAAmB,EAAE
|
|
4
|
+
"sourcesContent": ["import { T } from '@tldraw/validate'\n\n/**\n * A type representing opacity values in tldraw.\n *\n * Opacity values are numbers between 0 and 1, where 0 is fully transparent\n * and 1 is fully opaque. This type is used throughout the editor to control\n * the transparency of shapes, UI elements, and other visual components.\n *\n * @example\n * ```ts\n * const fullyOpaque: TLOpacityType = 1.0\n * const halfTransparent: TLOpacityType = 0.5\n * const fullyTransparent: TLOpacityType = 0.0\n * const quarterOpaque: TLOpacityType = 0.25\n * ```\n *\n * @public\n */\nexport type TLOpacityType = number\n\n/**\n * A validator for opacity values.\n *\n * This validator ensures that opacity values are numbers between 0 and 1 (inclusive).\n * Values outside this range will cause a validation error. The validator provides\n * runtime type checking for opacity properties throughout the editor.\n *\n * @param n - The number to validate as an opacity value\n * @throws T.ValidationError When the value is not between 0 and 1\n *\n * @example\n * ```ts\n * import { opacityValidator } from '@tldraw/tlschema'\n *\n * // Valid opacity values\n * try {\n * const validOpacity1 = opacityValidator.validate(0.5) // \u2713\n * const validOpacity2 = opacityValidator.validate(1.0) // \u2713\n * const validOpacity3 = opacityValidator.validate(0.0) // \u2713\n * } catch (error) {\n * console.error('Validation failed:', error.message)\n * }\n *\n * // Invalid opacity values\n * try {\n * opacityValidator.validate(-0.1) // \u2717 Throws error\n * opacityValidator.validate(1.5) // \u2717 Throws error\n * } catch (error) {\n * console.error('Invalid opacity:', error.message)\n * }\n * ```\n *\n * @public\n */\nexport const opacityValidator = T.unitInterval\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAuDX,MAAM,mBAAmB,EAAE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { T } from "@tldraw/validate";
|
|
2
|
-
const richTextValidator = T.object({
|
|
2
|
+
const richTextValidator = T.object({
|
|
3
|
+
type: T.string,
|
|
4
|
+
content: T.arrayOf(T.unknown),
|
|
5
|
+
attrs: T.any.optional()
|
|
6
|
+
});
|
|
3
7
|
function toRichText(text) {
|
|
4
8
|
const lines = text.split("\n");
|
|
5
9
|
const content = lines.map((text2) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/misc/TLRichText.ts"],
|
|
4
|
-
"sourcesContent": ["import { T } from '@tldraw/validate'\n\n/**\n * Validator for TLRichText objects that ensures they have the correct structure\n * for document-based rich text content. Validates a document with a type field\n * and an array of content blocks.\n *\n * @public\n * @example\n * ```ts\n * const richText = { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] }] }\n * const isValid = richTextValidator.check(richText) // true\n * ```\n */\nexport const richTextValidator = T.object({
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;
|
|
4
|
+
"sourcesContent": ["import { T } from '@tldraw/validate'\n\n/**\n * Validator for TLRichText objects that ensures they have the correct structure\n * for document-based rich text content. Validates a document with a type field\n * and an array of content blocks.\n *\n * @public\n * @example\n * ```ts\n * const richText = { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] }] }\n * const isValid = richTextValidator.check(richText) // true\n * ```\n */\n\nexport const richTextValidator = T.object({\n\ttype: T.string,\n\tcontent: T.arrayOf(T.unknown),\n\tattrs: T.any.optional(),\n})\n\n/**\n * Type representing rich text content in tldraw. Rich text follows a document-based\n * structure with a root document containing an array of content blocks (paragraphs,\n * text nodes, etc.). This enables formatted text with support for multiple paragraphs,\n * styling, and other rich content.\n *\n * @public\n * @example\n * ```ts\n * const richText: TLRichText = {\n * type: 'doc',\n * content: [\n * {\n * type: 'paragraph',\n * content: [{ type: 'text', text: 'Hello world!' }]\n * }\n * ]\n * }\n * ```\n */\nexport type TLRichText = T.TypeOf<typeof richTextValidator>\n\n/**\n * Converts a plain text string into a TLRichText object. Each line of the input\n * text becomes a separate paragraph in the rich text document. Empty lines are\n * preserved as empty paragraphs to maintain the original text structure.\n *\n * @param text - The plain text string to convert to rich text\n * @returns A TLRichText object with the text content structured as paragraphs\n * @public\n * @example\n * ```ts\n * const richText = toRichText('Hello\\nWorld')\n * // Returns:\n * // {\n * // type: 'doc',\n * // content: [\n * // { type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] },\n * // { type: 'paragraph', content: [{ type: 'text', text: 'World' }] }\n * // ]\n * // }\n *\n * const emptyLine = toRichText('Line 1\\n\\nLine 3')\n * // Creates three paragraphs, with the middle one being empty\n * ```\n */\nexport function toRichText(text: string): TLRichText {\n\tconst lines = text.split('\\n')\n\tconst content = lines.map((text) => {\n\t\tif (!text) {\n\t\t\treturn {\n\t\t\t\ttype: 'paragraph',\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\ttype: 'paragraph',\n\t\t\tcontent: [{ type: 'text', text }],\n\t\t}\n\t})\n\n\treturn {\n\t\ttype: 'doc',\n\t\tcontent,\n\t}\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAeX,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACzC,MAAM,EAAE;AAAA,EACR,SAAS,EAAE,QAAQ,EAAE,OAAO;AAAA,EAC5B,OAAO,EAAE,IAAI,SAAS;AACvB,CAAC;AAgDM,SAAS,WAAW,MAA0B;AACpD,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,QAAM,UAAU,MAAM,IAAI,CAACA,UAAS;AACnC,QAAI,CAACA,OAAM;AACV,aAAO;AAAA,QACN,MAAM;AAAA,MACP;AAAA,IACD;AAEA,WAAO;AAAA,MACN,MAAM;AAAA,MACN,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAAA,MAAK,CAAC;AAAA,IACjC;AAAA,EACD,CAAC;AAED,SAAO;AAAA,IACN,MAAM;AAAA,IACN;AAAA,EACD;AACD;",
|
|
6
6
|
"names": ["text"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const POINT_B64_LENGTH = 8;
|
|
2
|
+
const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3
|
+
const B64_LOOKUP = new Uint8Array(128);
|
|
4
|
+
for (let i = 0; i < 64; i++) {
|
|
5
|
+
B64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i;
|
|
6
|
+
}
|
|
7
|
+
const POW2 = new Float64Array(31);
|
|
8
|
+
for (let i = 0; i < 31; i++) {
|
|
9
|
+
POW2[i] = Math.pow(2, i - 15);
|
|
10
|
+
}
|
|
11
|
+
const POW2_SUBNORMAL = Math.pow(2, -14) / 1024;
|
|
12
|
+
const MANTISSA = new Float64Array(1024);
|
|
13
|
+
for (let i = 0; i < 1024; i++) {
|
|
14
|
+
MANTISSA[i] = 1 + i / 1024;
|
|
15
|
+
}
|
|
16
|
+
function uint16ArrayToBase64(uint16Array) {
|
|
17
|
+
const uint8Array = new Uint8Array(
|
|
18
|
+
uint16Array.buffer,
|
|
19
|
+
uint16Array.byteOffset,
|
|
20
|
+
uint16Array.byteLength
|
|
21
|
+
);
|
|
22
|
+
let result = "";
|
|
23
|
+
for (let i = 0; i < uint8Array.length; i += 3) {
|
|
24
|
+
const byte1 = uint8Array[i];
|
|
25
|
+
const byte2 = uint8Array[i + 1];
|
|
26
|
+
const byte3 = uint8Array[i + 2];
|
|
27
|
+
const bitmap = byte1 << 16 | byte2 << 8 | byte3;
|
|
28
|
+
result += BASE64_CHARS[bitmap >> 18 & 63] + BASE64_CHARS[bitmap >> 12 & 63] + BASE64_CHARS[bitmap >> 6 & 63] + BASE64_CHARS[bitmap & 63];
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
function base64ToUint16Array(base64) {
|
|
33
|
+
const numBytes = Math.floor(base64.length * 3 / 4);
|
|
34
|
+
const bytes = new Uint8Array(numBytes);
|
|
35
|
+
let byteIndex = 0;
|
|
36
|
+
for (let i = 0; i < base64.length; i += 4) {
|
|
37
|
+
const c0 = B64_LOOKUP[base64.charCodeAt(i)];
|
|
38
|
+
const c1 = B64_LOOKUP[base64.charCodeAt(i + 1)];
|
|
39
|
+
const c2 = B64_LOOKUP[base64.charCodeAt(i + 2)];
|
|
40
|
+
const c3 = B64_LOOKUP[base64.charCodeAt(i + 3)];
|
|
41
|
+
const bitmap = c0 << 18 | c1 << 12 | c2 << 6 | c3;
|
|
42
|
+
bytes[byteIndex++] = bitmap >> 16 & 255;
|
|
43
|
+
bytes[byteIndex++] = bitmap >> 8 & 255;
|
|
44
|
+
bytes[byteIndex++] = bitmap & 255;
|
|
45
|
+
}
|
|
46
|
+
return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
|
|
47
|
+
}
|
|
48
|
+
function float16BitsToNumber(bits) {
|
|
49
|
+
const sign = bits >> 15;
|
|
50
|
+
const exp = bits >> 10 & 31;
|
|
51
|
+
const frac = bits & 1023;
|
|
52
|
+
if (exp === 0) {
|
|
53
|
+
return sign ? -frac * POW2_SUBNORMAL : frac * POW2_SUBNORMAL;
|
|
54
|
+
}
|
|
55
|
+
if (exp === 31) {
|
|
56
|
+
return frac ? NaN : sign ? -Infinity : Infinity;
|
|
57
|
+
}
|
|
58
|
+
const magnitude = POW2[exp] * MANTISSA[frac];
|
|
59
|
+
return sign ? -magnitude : magnitude;
|
|
60
|
+
}
|
|
61
|
+
function numberToFloat16Bits(value) {
|
|
62
|
+
if (value === 0) return Object.is(value, -0) ? 32768 : 0;
|
|
63
|
+
if (!Number.isFinite(value)) {
|
|
64
|
+
if (Number.isNaN(value)) return 32256;
|
|
65
|
+
return value > 0 ? 31744 : 64512;
|
|
66
|
+
}
|
|
67
|
+
const sign = value < 0 ? 1 : 0;
|
|
68
|
+
value = Math.abs(value);
|
|
69
|
+
const exp = Math.floor(Math.log2(value));
|
|
70
|
+
let expBiased = exp + 15;
|
|
71
|
+
if (expBiased >= 31) {
|
|
72
|
+
return sign << 15 | 31744;
|
|
73
|
+
}
|
|
74
|
+
if (expBiased <= 0) {
|
|
75
|
+
const frac2 = Math.round(value * Math.pow(2, 14) * 1024);
|
|
76
|
+
return sign << 15 | frac2 & 1023;
|
|
77
|
+
}
|
|
78
|
+
const mantissa = value / Math.pow(2, exp) - 1;
|
|
79
|
+
let frac = Math.round(mantissa * 1024);
|
|
80
|
+
if (frac >= 1024) {
|
|
81
|
+
frac = 0;
|
|
82
|
+
expBiased++;
|
|
83
|
+
if (expBiased >= 31) {
|
|
84
|
+
return sign << 15 | 31744;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return sign << 15 | expBiased << 10 | frac;
|
|
88
|
+
}
|
|
89
|
+
class b64Vecs {
|
|
90
|
+
/**
|
|
91
|
+
* Encode a single point (x, y, z) to 8 base64 characters.
|
|
92
|
+
* Each coordinate is encoded as a Float16 value, resulting in 6 bytes total.
|
|
93
|
+
*
|
|
94
|
+
* @param x - The x coordinate
|
|
95
|
+
* @param y - The y coordinate
|
|
96
|
+
* @param z - The z coordinate
|
|
97
|
+
* @returns An 8-character base64 string representing the point
|
|
98
|
+
*/
|
|
99
|
+
static encodePoint(x, y, z) {
|
|
100
|
+
const xBits = numberToFloat16Bits(x);
|
|
101
|
+
const yBits = numberToFloat16Bits(y);
|
|
102
|
+
const zBits = numberToFloat16Bits(z);
|
|
103
|
+
const b0 = xBits & 255;
|
|
104
|
+
const b1 = xBits >> 8 & 255;
|
|
105
|
+
const b2 = yBits & 255;
|
|
106
|
+
const b3 = yBits >> 8 & 255;
|
|
107
|
+
const b4 = zBits & 255;
|
|
108
|
+
const b5 = zBits >> 8 & 255;
|
|
109
|
+
const bitmap1 = b0 << 16 | b1 << 8 | b2;
|
|
110
|
+
const bitmap2 = b3 << 16 | b4 << 8 | b5;
|
|
111
|
+
return BASE64_CHARS[bitmap1 >> 18 & 63] + BASE64_CHARS[bitmap1 >> 12 & 63] + BASE64_CHARS[bitmap1 >> 6 & 63] + BASE64_CHARS[bitmap1 & 63] + BASE64_CHARS[bitmap2 >> 18 & 63] + BASE64_CHARS[bitmap2 >> 12 & 63] + BASE64_CHARS[bitmap2 >> 6 & 63] + BASE64_CHARS[bitmap2 & 63];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Convert an array of VecModels to a base64 string for compact storage.
|
|
115
|
+
* Uses Float16 encoding for each coordinate (x, y, z). If a point's z value is
|
|
116
|
+
* undefined, it defaults to 0.5.
|
|
117
|
+
*
|
|
118
|
+
* @param points - An array of VecModel objects to encode
|
|
119
|
+
* @returns A base64-encoded string containing all points
|
|
120
|
+
*/
|
|
121
|
+
static encodePoints(points) {
|
|
122
|
+
const uint16s = new Uint16Array(points.length * 3);
|
|
123
|
+
for (let i = 0; i < points.length; i++) {
|
|
124
|
+
const p = points[i];
|
|
125
|
+
uint16s[i * 3] = numberToFloat16Bits(p.x);
|
|
126
|
+
uint16s[i * 3 + 1] = numberToFloat16Bits(p.y);
|
|
127
|
+
uint16s[i * 3 + 2] = numberToFloat16Bits(p.z ?? 0.5);
|
|
128
|
+
}
|
|
129
|
+
return uint16ArrayToBase64(uint16s);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Convert a base64 string back to an array of VecModels.
|
|
133
|
+
* Decodes Float16-encoded coordinates (x, y, z) from the base64 string.
|
|
134
|
+
*
|
|
135
|
+
* @param base64 - The base64-encoded string containing point data
|
|
136
|
+
* @returns An array of VecModel objects decoded from the string
|
|
137
|
+
*/
|
|
138
|
+
static decodePoints(base64) {
|
|
139
|
+
const uint16s = base64ToUint16Array(base64);
|
|
140
|
+
const result = [];
|
|
141
|
+
for (let i = 0; i < uint16s.length; i += 3) {
|
|
142
|
+
result.push({
|
|
143
|
+
x: float16BitsToNumber(uint16s[i]),
|
|
144
|
+
y: float16BitsToNumber(uint16s[i + 1]),
|
|
145
|
+
z: float16BitsToNumber(uint16s[i + 2])
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Decode a single point (8 base64 chars) starting at the given offset.
|
|
152
|
+
* Each point is encoded as 3 Float16 values (x, y, z) in 8 base64 characters.
|
|
153
|
+
*
|
|
154
|
+
* @param b64Points - The base64-encoded string containing point data
|
|
155
|
+
* @param charOffset - The character offset where the point starts (must be a multiple of 8)
|
|
156
|
+
* @returns A VecModel object with x, y, and z coordinates
|
|
157
|
+
* @internal
|
|
158
|
+
*/
|
|
159
|
+
static decodePointAt(b64Points, charOffset) {
|
|
160
|
+
const c0 = B64_LOOKUP[b64Points.charCodeAt(charOffset)];
|
|
161
|
+
const c1 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 1)];
|
|
162
|
+
const c2 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 2)];
|
|
163
|
+
const c3 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 3)];
|
|
164
|
+
const c4 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 4)];
|
|
165
|
+
const c5 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 5)];
|
|
166
|
+
const c6 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 6)];
|
|
167
|
+
const c7 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 7)];
|
|
168
|
+
const bitmap1 = c0 << 18 | c1 << 12 | c2 << 6 | c3;
|
|
169
|
+
const bitmap2 = c4 << 18 | c5 << 12 | c6 << 6 | c7;
|
|
170
|
+
const xBits = bitmap1 >> 16 & 255 | bitmap1 & 65280;
|
|
171
|
+
const yBits = bitmap1 & 255 | bitmap2 >> 8 & 65280;
|
|
172
|
+
const zBits = bitmap2 >> 8 & 255 | bitmap2 << 8 & 65280;
|
|
173
|
+
return {
|
|
174
|
+
x: float16BitsToNumber(xBits),
|
|
175
|
+
y: float16BitsToNumber(yBits),
|
|
176
|
+
z: float16BitsToNumber(zBits)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get the first point from a base64-encoded string of points.
|
|
181
|
+
*
|
|
182
|
+
* @param b64Points - The base64-encoded string containing point data
|
|
183
|
+
* @returns The first point as a VecModel, or null if the string is too short
|
|
184
|
+
* @public
|
|
185
|
+
*/
|
|
186
|
+
static decodeFirstPoint(b64Points) {
|
|
187
|
+
if (b64Points.length < POINT_B64_LENGTH) return null;
|
|
188
|
+
return b64Vecs.decodePointAt(b64Points, 0);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get the last point from a base64-encoded string of points.
|
|
192
|
+
*
|
|
193
|
+
* @param b64Points - The base64-encoded string containing point data
|
|
194
|
+
* @returns The last point as a VecModel, or null if the string is too short
|
|
195
|
+
*/
|
|
196
|
+
static decodeLastPoint(b64Points) {
|
|
197
|
+
if (b64Points.length < POINT_B64_LENGTH) return null;
|
|
198
|
+
return b64Vecs.decodePointAt(b64Points, b64Points.length - POINT_B64_LENGTH);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
export {
|
|
202
|
+
b64Vecs
|
|
203
|
+
};
|
|
204
|
+
//# sourceMappingURL=b64Vecs.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/misc/b64Vecs.ts"],
|
|
4
|
+
"sourcesContent": ["import { VecModel } from './geometry-types'\n\n// Each point = 3 Float16s = 6 bytes = 8 base64 chars\nconst POINT_B64_LENGTH = 8\n\n// O(1) lookup table for base64 decoding (maps char code -> 6-bit value)\nconst BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\nconst B64_LOOKUP = new Uint8Array(128)\nfor (let i = 0; i < 64; i++) {\n\tB64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i\n}\n\n// Precomputed powers of 2 for Float16 exponents (exp - 15, so indices 0-30 map to 2^-15 to 2^15)\nconst POW2 = new Float64Array(31)\nfor (let i = 0; i < 31; i++) {\n\tPOW2[i] = Math.pow(2, i - 15)\n}\nconst POW2_SUBNORMAL = Math.pow(2, -14) / 1024 // For subnormal numbers\n\n// Precomputed mantissa values: 1 + frac/1024 for all 1024 possible frac values\n// Avoids division in hot path\nconst MANTISSA = new Float64Array(1024)\nfor (let i = 0; i < 1024; i++) {\n\tMANTISSA[i] = 1 + i / 1024\n}\n\n/**\n * Convert a Uint16Array (containing Float16 bits) to base64.\n * Processes bytes in groups of 3 to produce 4 base64 characters.\n *\n * @internal\n */\nfunction uint16ArrayToBase64(uint16Array: Uint16Array): string {\n\tconst uint8Array = new Uint8Array(\n\t\tuint16Array.buffer,\n\t\tuint16Array.byteOffset,\n\t\tuint16Array.byteLength\n\t)\n\tlet result = ''\n\n\t// Process bytes in groups of 3 -> 4 base64 chars\n\tfor (let i = 0; i < uint8Array.length; i += 3) {\n\t\tconst byte1 = uint8Array[i]\n\t\tconst byte2 = uint8Array[i + 1] // Always exists for our use case (multiple of 6 bytes)\n\t\tconst byte3 = uint8Array[i + 2]\n\n\t\tconst bitmap = (byte1 << 16) | (byte2 << 8) | byte3\n\t\tresult +=\n\t\t\tBASE64_CHARS[(bitmap >> 18) & 63] +\n\t\t\tBASE64_CHARS[(bitmap >> 12) & 63] +\n\t\t\tBASE64_CHARS[(bitmap >> 6) & 63] +\n\t\t\tBASE64_CHARS[bitmap & 63]\n\t}\n\n\treturn result\n}\n\n/**\n * Convert a base64 string to Uint16Array containing Float16 bits.\n * The base64 string must have a length that is a multiple of 4.\n *\n * @param base64 - The base64-encoded string to decode\n * @returns A Uint16Array containing the decoded Float16 bit values\n * @public\n */\nfunction base64ToUint16Array(base64: string): Uint16Array {\n\t// Calculate exact number of bytes (4 base64 chars = 3 bytes)\n\tconst numBytes = Math.floor((base64.length * 3) / 4)\n\tconst bytes = new Uint8Array(numBytes)\n\tlet byteIndex = 0\n\n\t// Process in groups of 4 base64 characters\n\tfor (let i = 0; i < base64.length; i += 4) {\n\t\tconst c0 = B64_LOOKUP[base64.charCodeAt(i)]\n\t\tconst c1 = B64_LOOKUP[base64.charCodeAt(i + 1)]\n\t\tconst c2 = B64_LOOKUP[base64.charCodeAt(i + 2)]\n\t\tconst c3 = B64_LOOKUP[base64.charCodeAt(i + 3)]\n\n\t\tconst bitmap = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3\n\n\t\tbytes[byteIndex++] = (bitmap >> 16) & 255\n\t\tbytes[byteIndex++] = (bitmap >> 8) & 255\n\t\tbytes[byteIndex++] = bitmap & 255\n\t}\n\n\treturn new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2)\n}\n\n/**\n * Convert Float16 bits to a number using optimized lookup tables.\n * Handles normal numbers, subnormal numbers, zero, infinity, and NaN.\n *\n * @param bits - The 16-bit Float16 value to decode\n * @returns The decoded number value\n */\nfunction float16BitsToNumber(bits: number): number {\n\tconst sign = bits >> 15\n\tconst exp = (bits >> 10) & 0x1f\n\tconst frac = bits & 0x3ff\n\n\tif (exp === 0) {\n\t\t// Subnormal or zero - rare case\n\t\treturn sign ? -frac * POW2_SUBNORMAL : frac * POW2_SUBNORMAL\n\t}\n\tif (exp === 31) {\n\t\t// Infinity or NaN - very rare\n\t\treturn frac ? NaN : sign ? -Infinity : Infinity\n\t}\n\t// Normal case - two table lookups, one multiply, no division\n\tconst magnitude = POW2[exp] * MANTISSA[frac]\n\treturn sign ? -magnitude : magnitude\n}\n\n/**\n * Convert a number to Float16 bits.\n * Handles normal numbers, subnormal numbers, zero, infinity, and NaN.\n *\n * @param value - The number to encode as Float16\n * @returns The 16-bit Float16 representation of the number\n * @internal\n */\nfunction numberToFloat16Bits(value: number): number {\n\tif (value === 0) return Object.is(value, -0) ? 0x8000 : 0\n\tif (!Number.isFinite(value)) {\n\t\tif (Number.isNaN(value)) return 0x7e00\n\t\treturn value > 0 ? 0x7c00 : 0xfc00\n\t}\n\n\tconst sign = value < 0 ? 1 : 0\n\tvalue = Math.abs(value)\n\n\t// Find exponent and mantissa\n\tconst exp = Math.floor(Math.log2(value))\n\tlet expBiased = exp + 15\n\n\tif (expBiased >= 31) {\n\t\t// Overflow to infinity\n\t\treturn (sign << 15) | 0x7c00\n\t}\n\tif (expBiased <= 0) {\n\t\t// Subnormal or underflow\n\t\tconst frac = Math.round(value * Math.pow(2, 14) * 1024)\n\t\treturn (sign << 15) | (frac & 0x3ff)\n\t}\n\n\t// Normal number\n\tconst mantissa = value / Math.pow(2, exp) - 1\n\tlet frac = Math.round(mantissa * 1024)\n\n\t// Handle rounding overflow: if frac rounds to 1024, increment exponent\n\tif (frac >= 1024) {\n\t\tfrac = 0\n\t\texpBiased++\n\t\tif (expBiased >= 31) {\n\t\t\t// Overflow to infinity\n\t\t\treturn (sign << 15) | 0x7c00\n\t\t}\n\t}\n\n\treturn (sign << 15) | (expBiased << 10) | frac\n}\n\n/**\n * Utilities for encoding and decoding points using base64 and Float16 encoding.\n * Provides functions for converting between VecModel arrays and compact base64 strings,\n * as well as individual point encoding/decoding operations.\n *\n * @public\n */\nexport class b64Vecs {\n\t/**\n\t * Encode a single point (x, y, z) to 8 base64 characters.\n\t * Each coordinate is encoded as a Float16 value, resulting in 6 bytes total.\n\t *\n\t * @param x - The x coordinate\n\t * @param y - The y coordinate\n\t * @param z - The z coordinate\n\t * @returns An 8-character base64 string representing the point\n\t */\n\tstatic encodePoint(x: number, y: number, z: number): string {\n\t\tconst xBits = numberToFloat16Bits(x)\n\t\tconst yBits = numberToFloat16Bits(y)\n\t\tconst zBits = numberToFloat16Bits(z)\n\n\t\t// Convert Float16 bits to 6 bytes (little-endian)\n\t\tconst b0 = xBits & 0xff\n\t\tconst b1 = (xBits >> 8) & 0xff\n\t\tconst b2 = yBits & 0xff\n\t\tconst b3 = (yBits >> 8) & 0xff\n\t\tconst b4 = zBits & 0xff\n\t\tconst b5 = (zBits >> 8) & 0xff\n\n\t\t// Convert 6 bytes to 8 base64 chars\n\t\tconst bitmap1 = (b0 << 16) | (b1 << 8) | b2\n\t\tconst bitmap2 = (b3 << 16) | (b4 << 8) | b5\n\n\t\treturn (\n\t\t\tBASE64_CHARS[(bitmap1 >> 18) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap1 >> 12) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap1 >> 6) & 0x3f] +\n\t\t\tBASE64_CHARS[bitmap1 & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap2 >> 18) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap2 >> 12) & 0x3f] +\n\t\t\tBASE64_CHARS[(bitmap2 >> 6) & 0x3f] +\n\t\t\tBASE64_CHARS[bitmap2 & 0x3f]\n\t\t)\n\t}\n\n\t/**\n\t * Convert an array of VecModels to a base64 string for compact storage.\n\t * Uses Float16 encoding for each coordinate (x, y, z). If a point's z value is\n\t * undefined, it defaults to 0.5.\n\t *\n\t * @param points - An array of VecModel objects to encode\n\t * @returns A base64-encoded string containing all points\n\t */\n\tstatic encodePoints(points: VecModel[]): string {\n\t\tconst uint16s = new Uint16Array(points.length * 3)\n\t\tfor (let i = 0; i < points.length; i++) {\n\t\t\tconst p = points[i]\n\t\t\tuint16s[i * 3] = numberToFloat16Bits(p.x)\n\t\t\tuint16s[i * 3 + 1] = numberToFloat16Bits(p.y)\n\t\t\tuint16s[i * 3 + 2] = numberToFloat16Bits(p.z ?? 0.5)\n\t\t}\n\t\treturn uint16ArrayToBase64(uint16s)\n\t}\n\n\t/**\n\t * Convert a base64 string back to an array of VecModels.\n\t * Decodes Float16-encoded coordinates (x, y, z) from the base64 string.\n\t *\n\t * @param base64 - The base64-encoded string containing point data\n\t * @returns An array of VecModel objects decoded from the string\n\t */\n\tstatic decodePoints(base64: string): VecModel[] {\n\t\tconst uint16s = base64ToUint16Array(base64)\n\t\tconst result: VecModel[] = []\n\t\tfor (let i = 0; i < uint16s.length; i += 3) {\n\t\t\tresult.push({\n\t\t\t\tx: float16BitsToNumber(uint16s[i]),\n\t\t\t\ty: float16BitsToNumber(uint16s[i + 1]),\n\t\t\t\tz: float16BitsToNumber(uint16s[i + 2]),\n\t\t\t})\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Decode a single point (8 base64 chars) starting at the given offset.\n\t * Each point is encoded as 3 Float16 values (x, y, z) in 8 base64 characters.\n\t *\n\t * @param b64Points - The base64-encoded string containing point data\n\t * @param charOffset - The character offset where the point starts (must be a multiple of 8)\n\t * @returns A VecModel object with x, y, and z coordinates\n\t * @internal\n\t */\n\tstatic decodePointAt(b64Points: string, charOffset: number): VecModel {\n\t\t// Decode 8 base64 chars -> 6 bytes -> 3 Float16s using O(1) lookup\n\t\tconst c0 = B64_LOOKUP[b64Points.charCodeAt(charOffset)]\n\t\tconst c1 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 1)]\n\t\tconst c2 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 2)]\n\t\tconst c3 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 3)]\n\t\tconst c4 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 4)]\n\t\tconst c5 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 5)]\n\t\tconst c6 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 6)]\n\t\tconst c7 = B64_LOOKUP[b64Points.charCodeAt(charOffset + 7)]\n\n\t\t// 4 base64 chars -> 24 bits -> 3 bytes\n\t\tconst bitmap1 = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3\n\t\tconst bitmap2 = (c4 << 18) | (c5 << 12) | (c6 << 6) | c7\n\n\t\t// Extract Float16 bits directly (little-endian byte order)\n\t\t// bitmap1 = [byte0:8][byte1:8][byte2:8], bitmap2 = [byte3:8][byte4:8][byte5:8]\n\t\t// xBits = byte0 | (byte1 << 8), yBits = byte2 | (byte3 << 8), zBits = byte4 | (byte5 << 8)\n\t\tconst xBits = ((bitmap1 >> 16) & 0xff) | (bitmap1 & 0xff00)\n\t\tconst yBits = (bitmap1 & 0xff) | ((bitmap2 >> 8) & 0xff00)\n\t\tconst zBits = ((bitmap2 >> 8) & 0xff) | ((bitmap2 << 8) & 0xff00)\n\n\t\treturn {\n\t\t\tx: float16BitsToNumber(xBits),\n\t\t\ty: float16BitsToNumber(yBits),\n\t\t\tz: float16BitsToNumber(zBits),\n\t\t}\n\t}\n\n\t/**\n\t * Get the first point from a base64-encoded string of points.\n\t *\n\t * @param b64Points - The base64-encoded string containing point data\n\t * @returns The first point as a VecModel, or null if the string is too short\n\t * @public\n\t */\n\tstatic decodeFirstPoint(b64Points: string): VecModel | null {\n\t\tif (b64Points.length < POINT_B64_LENGTH) return null\n\t\treturn b64Vecs.decodePointAt(b64Points, 0)\n\t}\n\n\t/**\n\t * Get the last point from a base64-encoded string of points.\n\t *\n\t * @param b64Points - The base64-encoded string containing point data\n\t * @returns The last point as a VecModel, or null if the string is too short\n\t */\n\tstatic decodeLastPoint(b64Points: string): VecModel | null {\n\t\tif (b64Points.length < POINT_B64_LENGTH) return null\n\t\treturn b64Vecs.decodePointAt(b64Points, b64Points.length - POINT_B64_LENGTH)\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["frac"]
|
|
7
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createMigrationSequence } from "@tldraw/store";
|
|
2
|
+
import { structuredClone } from "@tldraw/utils";
|
|
2
3
|
import { T } from "@tldraw/validate";
|
|
3
4
|
import { richTextValidator, toRichText } from "../misc/TLRichText.mjs";
|
|
4
5
|
import { vecModelValidator } from "../misc/geometry-types.mjs";
|
|
@@ -63,7 +64,8 @@ const arrowShapeVersions = createShapePropsMigrationIds("arrow", {
|
|
|
63
64
|
ExtractBindings: 4,
|
|
64
65
|
AddScale: 5,
|
|
65
66
|
AddElbow: 6,
|
|
66
|
-
AddRichText: 7
|
|
67
|
+
AddRichText: 7,
|
|
68
|
+
AddRichTextAttrs: 8
|
|
67
69
|
});
|
|
68
70
|
function propsMigration(migration) {
|
|
69
71
|
return createPropsMigration("shape", "arrow", migration);
|
|
@@ -115,12 +117,13 @@ const arrowShapeMigrations = createMigrationSequence({
|
|
|
115
117
|
}),
|
|
116
118
|
{
|
|
117
119
|
id: arrowShapeVersions.ExtractBindings,
|
|
118
|
-
scope: "
|
|
119
|
-
up: (
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
scope: "storage",
|
|
121
|
+
up: (storage) => {
|
|
122
|
+
const updates = [];
|
|
123
|
+
for (const record of storage.values()) {
|
|
124
|
+
if (record.typeName !== "shape" || record.type !== "arrow") continue;
|
|
125
|
+
const arrow = record;
|
|
126
|
+
const newArrow = structuredClone(arrow);
|
|
124
127
|
const { start, end } = arrow.props;
|
|
125
128
|
if (start.type === "binding") {
|
|
126
129
|
const id = createBindingId();
|
|
@@ -138,10 +141,10 @@ const arrowShapeMigrations = createMigrationSequence({
|
|
|
138
141
|
isPrecise: start.isPrecise
|
|
139
142
|
}
|
|
140
143
|
};
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
updates.push([id, binding]);
|
|
145
|
+
newArrow.props.start = { x: 0, y: 0 };
|
|
143
146
|
} else {
|
|
144
|
-
delete
|
|
147
|
+
delete newArrow.props.start.type;
|
|
145
148
|
}
|
|
146
149
|
if (end.type === "binding") {
|
|
147
150
|
const id = createBindingId();
|
|
@@ -159,11 +162,15 @@ const arrowShapeMigrations = createMigrationSequence({
|
|
|
159
162
|
isPrecise: end.isPrecise
|
|
160
163
|
}
|
|
161
164
|
};
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
updates.push([id, binding]);
|
|
166
|
+
newArrow.props.end = { x: 0, y: 0 };
|
|
164
167
|
} else {
|
|
165
|
-
delete
|
|
168
|
+
delete newArrow.props.end.type;
|
|
166
169
|
}
|
|
170
|
+
updates.push([arrow.id, newArrow]);
|
|
171
|
+
}
|
|
172
|
+
for (const [id, record] of updates) {
|
|
173
|
+
storage.set(id, record);
|
|
167
174
|
}
|
|
168
175
|
}
|
|
169
176
|
},
|
|
@@ -197,6 +204,16 @@ const arrowShapeMigrations = createMigrationSequence({
|
|
|
197
204
|
// down: (props) => {
|
|
198
205
|
// delete props.richText
|
|
199
206
|
// },
|
|
207
|
+
}),
|
|
208
|
+
propsMigration({
|
|
209
|
+
id: arrowShapeVersions.AddRichTextAttrs,
|
|
210
|
+
up: (_props) => {
|
|
211
|
+
},
|
|
212
|
+
down: (props) => {
|
|
213
|
+
if (props.richText && "attrs" in props.richText) {
|
|
214
|
+
delete props.richText.attrs;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
200
217
|
})
|
|
201
218
|
]
|
|
202
219
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/shapes/TLArrowShape.ts"],
|
|
4
|
-
"sourcesContent": ["import { createMigrationSequence } from '@tldraw/store'\nimport { T } from '@tldraw/validate'\nimport { TLRichText, richTextValidator, toRichText } from '../misc/TLRichText'\nimport { VecModel, vecModelValidator } from '../misc/geometry-types'\nimport { createBindingId } from '../records/TLBinding'\nimport { TLShapeId, createShapePropsMigrationIds } from '../records/TLShape'\nimport { RecordProps, TLPropsMigration, createPropsMigration } from '../recordsWithProps'\nimport { StyleProp } from '../styles/StyleProp'\nimport {\n\tDefaultColorStyle,\n\tDefaultLabelColorStyle,\n\tTLDefaultColorStyle,\n} from '../styles/TLColorStyle'\nimport { DefaultDashStyle, TLDefaultDashStyle } from '../styles/TLDashStyle'\nimport { DefaultFillStyle, TLDefaultFillStyle } from '../styles/TLFillStyle'\nimport { DefaultFontStyle, TLDefaultFontStyle } from '../styles/TLFontStyle'\nimport { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle'\nimport { TLBaseShape } from './TLBaseShape'\n\nconst arrowKinds = ['arc', 'elbow'] as const\n/**\n * Style property for arrow shape kind, determining how the arrow is drawn.\n *\n * Arrows can be drawn as arcs (curved) or elbows (angled with straight segments).\n * This affects the visual appearance and behavior of arrow shapes.\n *\n * @example\n * ```ts\n * // Create an arrow with arc style (curved)\n * const arcArrow: TLArrowShape = {\n * // ... other properties\n * props: {\n * kind: 'arc',\n * // ... other props\n * }\n * }\n *\n * // Create an arrow with elbow style (angled)\n * const elbowArrow: TLArrowShape = {\n * // ... other properties\n * props: {\n * kind: 'elbow',\n * // ... other props\n * }\n * }\n * ```\n *\n * @public\n */\nexport const ArrowShapeKindStyle = StyleProp.defineEnum('tldraw:arrowKind', {\n\tdefaultValue: 'arc',\n\tvalues: arrowKinds,\n})\n\n/**\n * The type representing arrow shape kinds.\n *\n * @public\n */\nexport type TLArrowShapeKind = T.TypeOf<typeof ArrowShapeKindStyle>\n\nconst arrowheadTypes = [\n\t'arrow',\n\t'triangle',\n\t'square',\n\t'dot',\n\t'pipe',\n\t'diamond',\n\t'inverted',\n\t'bar',\n\t'none',\n] as const\n\n/**\n * Style property for the arrowhead at the start of an arrow.\n *\n * Defines the visual style of the arrowhead at the beginning of the arrow path.\n * Can be one of several predefined styles or none for no arrowhead.\n *\n * @example\n * ```ts\n * // Arrow with no start arrowhead but triangle end arrowhead\n * const arrow: TLArrowShape = {\n * // ... other properties\n * props: {\n * arrowheadStart: 'none',\n * arrowheadEnd: 'triangle',\n * // ... other props\n * }\n * }\n * ```\n *\n * @public\n */\nexport const ArrowShapeArrowheadStartStyle = StyleProp.defineEnum('tldraw:arrowheadStart', {\n\tdefaultValue: 'none',\n\tvalues: arrowheadTypes,\n})\n\n/**\n * Style property for the arrowhead at the end of an arrow.\n *\n * Defines the visual style of the arrowhead at the end of the arrow path.\n * Defaults to 'arrow' style, giving arrows their characteristic pointed appearance.\n *\n * @example\n * ```ts\n * // Arrow with different start and end arrowheads\n * const doubleArrow: TLArrowShape = {\n * // ... other properties\n * props: {\n * arrowheadStart: 'triangle',\n * arrowheadEnd: 'diamond',\n * // ... other props\n * }\n * }\n * ```\n *\n * @public\n */\nexport const ArrowShapeArrowheadEndStyle = StyleProp.defineEnum('tldraw:arrowheadEnd', {\n\tdefaultValue: 'arrow',\n\tvalues: arrowheadTypes,\n})\n\n/**\n * The type representing arrowhead styles for both start and end of arrows.\n *\n * @public\n */\nexport type TLArrowShapeArrowheadStyle = T.TypeOf<typeof ArrowShapeArrowheadStartStyle>\n\n/**\n * Properties specific to arrow shapes.\n *\n * Defines all the configurable aspects of an arrow shape, including visual styling,\n * geometry, text labeling, and positioning. Arrows can connect two points and\n * optionally display text labels.\n *\n * @example\n * ```ts\n * const arrowProps: TLArrowShapeProps = {\n * kind: 'arc',\n * labelColor: 'black',\n * color: 'blue',\n * fill: 'none',\n * dash: 'solid',\n * size: 'm',\n * arrowheadStart: 'none',\n * arrowheadEnd: 'arrow',\n * font: 'draw',\n * start: { x: 0, y: 0 },\n * end: { x: 100, y: 100 },\n * bend: 0.2,\n * richText: toRichText('Label'),\n * labelPosition: 0.5,\n * scale: 1,\n * elbowMidPoint: 0.5\n * }\n * ```\n *\n * @public\n */\nexport interface TLArrowShapeProps {\n\tkind: TLArrowShapeKind\n\tlabelColor: TLDefaultColorStyle\n\tcolor: TLDefaultColorStyle\n\tfill: TLDefaultFillStyle\n\tdash: TLDefaultDashStyle\n\tsize: TLDefaultSizeStyle\n\tarrowheadStart: TLArrowShapeArrowheadStyle\n\tarrowheadEnd: TLArrowShapeArrowheadStyle\n\tfont: TLDefaultFontStyle\n\tstart: VecModel\n\tend: VecModel\n\tbend: number\n\trichText: TLRichText\n\tlabelPosition: number\n\tscale: number\n\telbowMidPoint: number\n}\n\n/**\n * A complete arrow shape record.\n *\n * Combines the base shape interface with arrow-specific properties to create\n * a full arrow shape that can be stored and manipulated in the editor.\n *\n * @example\n * ```ts\n * const arrowShape: TLArrowShape = {\n * id: 'shape:arrow123',\n * typeName: 'shape',\n * type: 'arrow',\n * x: 100,\n * y: 200,\n * rotation: 0,\n * index: 'a1',\n * parentId: 'page:main',\n * isLocked: false,\n * opacity: 1,\n * props: {\n * kind: 'arc',\n * start: { x: 0, y: 0 },\n * end: { x: 150, y: 100 },\n * // ... other props\n * },\n * meta: {}\n * }\n * ```\n *\n * @public\n */\nexport type TLArrowShape = TLBaseShape<'arrow', TLArrowShapeProps>\n\n/**\n * Validation configuration for arrow shape properties.\n *\n * Defines the validators for each property of an arrow shape, ensuring that\n * arrow shape data is valid and conforms to the expected types and constraints.\n *\n * @example\n * ```ts\n * // The validators ensure proper typing and validation\n * const validator = T.object(arrowShapeProps)\n * const validArrowProps = validator.validate({\n * kind: 'arc',\n * start: { x: 0, y: 0 },\n * end: { x: 100, y: 50 },\n * // ... other required properties\n * })\n * ```\n *\n * @public\n */\nexport const arrowShapeProps: RecordProps<TLArrowShape> = {\n\tkind: ArrowShapeKindStyle,\n\tlabelColor: DefaultLabelColorStyle,\n\tcolor: DefaultColorStyle,\n\tfill: DefaultFillStyle,\n\tdash: DefaultDashStyle,\n\tsize: DefaultSizeStyle,\n\tarrowheadStart: ArrowShapeArrowheadStartStyle,\n\tarrowheadEnd: ArrowShapeArrowheadEndStyle,\n\tfont: DefaultFontStyle,\n\tstart: vecModelValidator,\n\tend: vecModelValidator,\n\tbend: T.number,\n\trichText: richTextValidator,\n\tlabelPosition: T.number,\n\tscale: T.nonZeroNumber,\n\telbowMidPoint: T.number,\n}\n\n/**\n * Migration version identifiers for arrow shape properties.\n *\n * These track the evolution of the arrow shape schema over time, with each\n * version representing a specific change to the arrow shape structure or properties.\n *\n * @example\n * ```ts\n * // Used internally for migration system\n * if (version < arrowShapeVersions.AddLabelColor) {\n * // Apply label color migration\n * }\n * ```\n *\n * @public\n */\nexport const arrowShapeVersions = createShapePropsMigrationIds('arrow', {\n\tAddLabelColor: 1,\n\tAddIsPrecise: 2,\n\tAddLabelPosition: 3,\n\tExtractBindings: 4,\n\tAddScale: 5,\n\tAddElbow: 6,\n\tAddRichText: 7,\n})\n\nfunction propsMigration(migration: TLPropsMigration) {\n\treturn createPropsMigration<TLArrowShape>('shape', 'arrow', migration)\n}\n\n/**\n * Complete migration sequence for arrow shapes.\n *\n * Defines all the migrations needed to transform arrow shape data from older\n * versions to the current version. Each migration handles a specific schema change,\n * ensuring backward compatibility and smooth data evolution.\n *\n * @public\n */\nexport const arrowShapeMigrations = createMigrationSequence({\n\tsequenceId: 'com.tldraw.shape.arrow',\n\tretroactive: false,\n\tsequence: [\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddLabelColor,\n\t\t\tup: (props) => {\n\t\t\t\tprops.labelColor = 'black'\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t}),\n\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddIsPrecise,\n\t\t\tup: ({ start, end }) => {\n\t\t\t\tif (start.type === 'binding') {\n\t\t\t\t\tstart.isPrecise = !(start.normalizedAnchor.x === 0.5 && start.normalizedAnchor.y === 0.5)\n\t\t\t\t}\n\t\t\t\tif (end.type === 'binding') {\n\t\t\t\t\tend.isPrecise = !(end.normalizedAnchor.x === 0.5 && end.normalizedAnchor.y === 0.5)\n\t\t\t\t}\n\t\t\t},\n\t\t\tdown: ({ start, end }) => {\n\t\t\t\tif (start.type === 'binding') {\n\t\t\t\t\tif (!start.isPrecise) {\n\t\t\t\t\t\tstart.normalizedAnchor = { x: 0.5, y: 0.5 }\n\t\t\t\t\t}\n\t\t\t\t\tdelete start.isPrecise\n\t\t\t\t}\n\t\t\t\tif (end.type === 'binding') {\n\t\t\t\t\tif (!end.isPrecise) {\n\t\t\t\t\t\tend.normalizedAnchor = { x: 0.5, y: 0.5 }\n\t\t\t\t\t}\n\t\t\t\t\tdelete end.isPrecise\n\t\t\t\t}\n\t\t\t},\n\t\t}),\n\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddLabelPosition,\n\t\t\tup: (props) => {\n\t\t\t\tprops.labelPosition = 0.5\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tdelete props.labelPosition\n\t\t\t},\n\t\t}),\n\n\t\t{\n\t\t\tid: arrowShapeVersions.ExtractBindings,\n\t\t\tscope: 'store',\n\t\t\tup: (oldStore) => {\n\t\t\t\ttype OldArrowTerminal =\n\t\t\t\t\t| {\n\t\t\t\t\t\t\ttype: 'point'\n\t\t\t\t\t\t\tx: number\n\t\t\t\t\t\t\ty: number\n\t\t\t\t\t }\n\t\t\t\t\t| {\n\t\t\t\t\t\t\ttype: 'binding'\n\t\t\t\t\t\t\tboundShapeId: TLShapeId\n\t\t\t\t\t\t\tnormalizedAnchor: VecModel\n\t\t\t\t\t\t\tisExact: boolean\n\t\t\t\t\t\t\tisPrecise: boolean\n\t\t\t\t\t }\n\t\t\t\t\t// new type:\n\t\t\t\t\t| { type?: undefined; x: number; y: number }\n\n\t\t\t\ttype OldArrow = TLBaseShape<'arrow', { start: OldArrowTerminal; end: OldArrowTerminal }>\n\n\t\t\t\tconst arrows = Object.values(oldStore).filter(\n\t\t\t\t\t(r: any): r is OldArrow => r.typeName === 'shape' && r.type === 'arrow'\n\t\t\t\t)\n\n\t\t\t\tfor (const arrow of arrows) {\n\t\t\t\t\tconst { start, end } = arrow.props\n\t\t\t\t\tif (start.type === 'binding') {\n\t\t\t\t\t\tconst id = createBindingId()\n\t\t\t\t\t\tconst binding = {\n\t\t\t\t\t\t\ttypeName: 'binding',\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\ttype: 'arrow',\n\t\t\t\t\t\t\tfromId: arrow.id,\n\t\t\t\t\t\t\ttoId: start.boundShapeId,\n\t\t\t\t\t\t\tmeta: {},\n\t\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\t\tterminal: 'start',\n\t\t\t\t\t\t\t\tnormalizedAnchor: start.normalizedAnchor,\n\t\t\t\t\t\t\t\tisExact: start.isExact,\n\t\t\t\t\t\t\t\tisPrecise: start.isPrecise,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\toldStore[id] = binding\n\t\t\t\t\t\tarrow.props.start = { x: 0, y: 0 }\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete arrow.props.start.type\n\t\t\t\t\t}\n\t\t\t\t\tif (end.type === 'binding') {\n\t\t\t\t\t\tconst id = createBindingId()\n\t\t\t\t\t\tconst binding = {\n\t\t\t\t\t\t\ttypeName: 'binding',\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\ttype: 'arrow',\n\t\t\t\t\t\t\tfromId: arrow.id,\n\t\t\t\t\t\t\ttoId: end.boundShapeId,\n\t\t\t\t\t\t\tmeta: {},\n\t\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\t\tterminal: 'end',\n\t\t\t\t\t\t\t\tnormalizedAnchor: end.normalizedAnchor,\n\t\t\t\t\t\t\t\tisExact: end.isExact,\n\t\t\t\t\t\t\t\tisPrecise: end.isPrecise,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\toldStore[id] = binding\n\t\t\t\t\t\tarrow.props.end = { x: 0, y: 0 }\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete arrow.props.end.type\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddScale,\n\t\t\tup: (props) => {\n\t\t\t\tprops.scale = 1\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tdelete props.scale\n\t\t\t},\n\t\t}),\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddElbow,\n\t\t\tup: (props) => {\n\t\t\t\tprops.kind = 'arc'\n\t\t\t\tprops.elbowMidPoint = 0.5\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tdelete props.kind\n\t\t\t\tdelete props.elbowMidPoint\n\t\t\t},\n\t\t}),\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddRichText,\n\t\t\tup: (props) => {\n\t\t\t\tprops.richText = toRichText(props.text)\n\t\t\t\tdelete props.text\n\t\t\t},\n\t\t\t// N.B. Explicitly no down state so that we force clients to update.\n\t\t\t// down: (props) => {\n\t\t\t// \tdelete props.richText\n\t\t\t// },\n\t\t}),\n\t],\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,+BAA+B;AACxC,SAAS,SAAS;AAClB,SAAqB,mBAAmB,kBAAkB;AAC1D,SAAmB,yBAAyB;AAC5C,SAAS,uBAAuB;AAChC,
|
|
4
|
+
"sourcesContent": ["import { createMigrationSequence } from '@tldraw/store'\nimport { structuredClone } from '@tldraw/utils'\nimport { T } from '@tldraw/validate'\nimport { TLRichText, richTextValidator, toRichText } from '../misc/TLRichText'\nimport { VecModel, vecModelValidator } from '../misc/geometry-types'\nimport { createBindingId } from '../records/TLBinding'\nimport { TLShape, TLShapeId, createShapePropsMigrationIds } from '../records/TLShape'\nimport { RecordProps, TLPropsMigration, createPropsMigration } from '../recordsWithProps'\nimport { StyleProp } from '../styles/StyleProp'\nimport {\n\tDefaultColorStyle,\n\tDefaultLabelColorStyle,\n\tTLDefaultColorStyle,\n} from '../styles/TLColorStyle'\nimport { DefaultDashStyle, TLDefaultDashStyle } from '../styles/TLDashStyle'\nimport { DefaultFillStyle, TLDefaultFillStyle } from '../styles/TLFillStyle'\nimport { DefaultFontStyle, TLDefaultFontStyle } from '../styles/TLFontStyle'\nimport { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle'\nimport { TLBaseShape } from './TLBaseShape'\n\nconst arrowKinds = ['arc', 'elbow'] as const\n/**\n * Style property for arrow shape kind, determining how the arrow is drawn.\n *\n * Arrows can be drawn as arcs (curved) or elbows (angled with straight segments).\n * This affects the visual appearance and behavior of arrow shapes.\n *\n * @example\n * ```ts\n * // Create an arrow with arc style (curved)\n * const arcArrow: TLArrowShape = {\n * // ... other properties\n * props: {\n * kind: 'arc',\n * // ... other props\n * }\n * }\n *\n * // Create an arrow with elbow style (angled)\n * const elbowArrow: TLArrowShape = {\n * // ... other properties\n * props: {\n * kind: 'elbow',\n * // ... other props\n * }\n * }\n * ```\n *\n * @public\n */\nexport const ArrowShapeKindStyle = StyleProp.defineEnum('tldraw:arrowKind', {\n\tdefaultValue: 'arc',\n\tvalues: arrowKinds,\n})\n\n/**\n * The type representing arrow shape kinds.\n *\n * @public\n */\nexport type TLArrowShapeKind = T.TypeOf<typeof ArrowShapeKindStyle>\n\nconst arrowheadTypes = [\n\t'arrow',\n\t'triangle',\n\t'square',\n\t'dot',\n\t'pipe',\n\t'diamond',\n\t'inverted',\n\t'bar',\n\t'none',\n] as const\n\n/**\n * Style property for the arrowhead at the start of an arrow.\n *\n * Defines the visual style of the arrowhead at the beginning of the arrow path.\n * Can be one of several predefined styles or none for no arrowhead.\n *\n * @example\n * ```ts\n * // Arrow with no start arrowhead but triangle end arrowhead\n * const arrow: TLArrowShape = {\n * // ... other properties\n * props: {\n * arrowheadStart: 'none',\n * arrowheadEnd: 'triangle',\n * // ... other props\n * }\n * }\n * ```\n *\n * @public\n */\nexport const ArrowShapeArrowheadStartStyle = StyleProp.defineEnum('tldraw:arrowheadStart', {\n\tdefaultValue: 'none',\n\tvalues: arrowheadTypes,\n})\n\n/**\n * Style property for the arrowhead at the end of an arrow.\n *\n * Defines the visual style of the arrowhead at the end of the arrow path.\n * Defaults to 'arrow' style, giving arrows their characteristic pointed appearance.\n *\n * @example\n * ```ts\n * // Arrow with different start and end arrowheads\n * const doubleArrow: TLArrowShape = {\n * // ... other properties\n * props: {\n * arrowheadStart: 'triangle',\n * arrowheadEnd: 'diamond',\n * // ... other props\n * }\n * }\n * ```\n *\n * @public\n */\nexport const ArrowShapeArrowheadEndStyle = StyleProp.defineEnum('tldraw:arrowheadEnd', {\n\tdefaultValue: 'arrow',\n\tvalues: arrowheadTypes,\n})\n\n/**\n * The type representing arrowhead styles for both start and end of arrows.\n *\n * @public\n */\nexport type TLArrowShapeArrowheadStyle = T.TypeOf<typeof ArrowShapeArrowheadStartStyle>\n\n/**\n * Properties specific to arrow shapes.\n *\n * Defines all the configurable aspects of an arrow shape, including visual styling,\n * geometry, text labeling, and positioning. Arrows can connect two points and\n * optionally display text labels.\n *\n * @example\n * ```ts\n * const arrowProps: TLArrowShapeProps = {\n * kind: 'arc',\n * labelColor: 'black',\n * color: 'blue',\n * fill: 'none',\n * dash: 'solid',\n * size: 'm',\n * arrowheadStart: 'none',\n * arrowheadEnd: 'arrow',\n * font: 'draw',\n * start: { x: 0, y: 0 },\n * end: { x: 100, y: 100 },\n * bend: 0.2,\n * richText: toRichText('Label'),\n * labelPosition: 0.5,\n * scale: 1,\n * elbowMidPoint: 0.5\n * }\n * ```\n *\n * @public\n */\nexport interface TLArrowShapeProps {\n\tkind: TLArrowShapeKind\n\tlabelColor: TLDefaultColorStyle\n\tcolor: TLDefaultColorStyle\n\tfill: TLDefaultFillStyle\n\tdash: TLDefaultDashStyle\n\tsize: TLDefaultSizeStyle\n\tarrowheadStart: TLArrowShapeArrowheadStyle\n\tarrowheadEnd: TLArrowShapeArrowheadStyle\n\tfont: TLDefaultFontStyle\n\tstart: VecModel\n\tend: VecModel\n\tbend: number\n\trichText: TLRichText\n\tlabelPosition: number\n\tscale: number\n\telbowMidPoint: number\n}\n\n/**\n * A complete arrow shape record.\n *\n * Combines the base shape interface with arrow-specific properties to create\n * a full arrow shape that can be stored and manipulated in the editor.\n *\n * @example\n * ```ts\n * const arrowShape: TLArrowShape = {\n * id: 'shape:arrow123',\n * typeName: 'shape',\n * type: 'arrow',\n * x: 100,\n * y: 200,\n * rotation: 0,\n * index: 'a1',\n * parentId: 'page:main',\n * isLocked: false,\n * opacity: 1,\n * props: {\n * kind: 'arc',\n * start: { x: 0, y: 0 },\n * end: { x: 150, y: 100 },\n * // ... other props\n * },\n * meta: {}\n * }\n * ```\n *\n * @public\n */\nexport type TLArrowShape = TLBaseShape<'arrow', TLArrowShapeProps>\n\n/**\n * Validation configuration for arrow shape properties.\n *\n * Defines the validators for each property of an arrow shape, ensuring that\n * arrow shape data is valid and conforms to the expected types and constraints.\n *\n * @example\n * ```ts\n * // The validators ensure proper typing and validation\n * const validator = T.object(arrowShapeProps)\n * const validArrowProps = validator.validate({\n * kind: 'arc',\n * start: { x: 0, y: 0 },\n * end: { x: 100, y: 50 },\n * // ... other required properties\n * })\n * ```\n *\n * @public\n */\nexport const arrowShapeProps: RecordProps<TLArrowShape> = {\n\tkind: ArrowShapeKindStyle,\n\tlabelColor: DefaultLabelColorStyle,\n\tcolor: DefaultColorStyle,\n\tfill: DefaultFillStyle,\n\tdash: DefaultDashStyle,\n\tsize: DefaultSizeStyle,\n\tarrowheadStart: ArrowShapeArrowheadStartStyle,\n\tarrowheadEnd: ArrowShapeArrowheadEndStyle,\n\tfont: DefaultFontStyle,\n\tstart: vecModelValidator,\n\tend: vecModelValidator,\n\tbend: T.number,\n\trichText: richTextValidator,\n\tlabelPosition: T.number,\n\tscale: T.nonZeroNumber,\n\telbowMidPoint: T.number,\n}\n\n/**\n * Migration version identifiers for arrow shape properties.\n *\n * These track the evolution of the arrow shape schema over time, with each\n * version representing a specific change to the arrow shape structure or properties.\n *\n * @example\n * ```ts\n * // Used internally for migration system\n * if (version < arrowShapeVersions.AddLabelColor) {\n * // Apply label color migration\n * }\n * ```\n *\n * @public\n */\nexport const arrowShapeVersions = createShapePropsMigrationIds('arrow', {\n\tAddLabelColor: 1,\n\tAddIsPrecise: 2,\n\tAddLabelPosition: 3,\n\tExtractBindings: 4,\n\tAddScale: 5,\n\tAddElbow: 6,\n\tAddRichText: 7,\n\tAddRichTextAttrs: 8,\n})\n\nfunction propsMigration(migration: TLPropsMigration) {\n\treturn createPropsMigration<TLArrowShape>('shape', 'arrow', migration)\n}\n\n/**\n * Complete migration sequence for arrow shapes.\n *\n * Defines all the migrations needed to transform arrow shape data from older\n * versions to the current version. Each migration handles a specific schema change,\n * ensuring backward compatibility and smooth data evolution.\n *\n * @public\n */\nexport const arrowShapeMigrations = createMigrationSequence({\n\tsequenceId: 'com.tldraw.shape.arrow',\n\tretroactive: false,\n\tsequence: [\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddLabelColor,\n\t\t\tup: (props) => {\n\t\t\t\tprops.labelColor = 'black'\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t}),\n\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddIsPrecise,\n\t\t\tup: ({ start, end }) => {\n\t\t\t\tif (start.type === 'binding') {\n\t\t\t\t\tstart.isPrecise = !(start.normalizedAnchor.x === 0.5 && start.normalizedAnchor.y === 0.5)\n\t\t\t\t}\n\t\t\t\tif (end.type === 'binding') {\n\t\t\t\t\tend.isPrecise = !(end.normalizedAnchor.x === 0.5 && end.normalizedAnchor.y === 0.5)\n\t\t\t\t}\n\t\t\t},\n\t\t\tdown: ({ start, end }) => {\n\t\t\t\tif (start.type === 'binding') {\n\t\t\t\t\tif (!start.isPrecise) {\n\t\t\t\t\t\tstart.normalizedAnchor = { x: 0.5, y: 0.5 }\n\t\t\t\t\t}\n\t\t\t\t\tdelete start.isPrecise\n\t\t\t\t}\n\t\t\t\tif (end.type === 'binding') {\n\t\t\t\t\tif (!end.isPrecise) {\n\t\t\t\t\t\tend.normalizedAnchor = { x: 0.5, y: 0.5 }\n\t\t\t\t\t}\n\t\t\t\t\tdelete end.isPrecise\n\t\t\t\t}\n\t\t\t},\n\t\t}),\n\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddLabelPosition,\n\t\t\tup: (props) => {\n\t\t\t\tprops.labelPosition = 0.5\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tdelete props.labelPosition\n\t\t\t},\n\t\t}),\n\n\t\t{\n\t\t\tid: arrowShapeVersions.ExtractBindings,\n\t\t\tscope: 'storage',\n\t\t\tup: (storage) => {\n\t\t\t\ttype OldArrowTerminal =\n\t\t\t\t\t| {\n\t\t\t\t\t\t\ttype: 'point'\n\t\t\t\t\t\t\tx: number\n\t\t\t\t\t\t\ty: number\n\t\t\t\t\t }\n\t\t\t\t\t| {\n\t\t\t\t\t\t\ttype: 'binding'\n\t\t\t\t\t\t\tboundShapeId: TLShapeId\n\t\t\t\t\t\t\tnormalizedAnchor: VecModel\n\t\t\t\t\t\t\tisExact: boolean\n\t\t\t\t\t\t\tisPrecise: boolean\n\t\t\t\t\t }\n\t\t\t\t\t// new type:\n\t\t\t\t\t| { type?: undefined; x: number; y: number }\n\n\t\t\t\ttype OldArrow = TLBaseShape<'arrow', { start: OldArrowTerminal; end: OldArrowTerminal }>\n\n\t\t\t\t// Collect all updates during iteration, then apply them after.\n\t\t\t\t// This avoids issues with live iterators (e.g., SQLite) where updating\n\t\t\t\t// records during iteration can cause them to be visited multiple times.\n\t\t\t\tconst updates: [string, unknown][] = []\n\n\t\t\t\tfor (const record of storage.values()) {\n\t\t\t\t\tif (record.typeName !== 'shape' || (record as TLShape).type !== 'arrow') continue\n\t\t\t\t\tconst arrow = record as OldArrow\n\t\t\t\t\tconst newArrow = structuredClone(arrow)\n\t\t\t\t\tconst { start, end } = arrow.props\n\t\t\t\t\tif (start.type === 'binding') {\n\t\t\t\t\t\tconst id = createBindingId()\n\t\t\t\t\t\tconst binding = {\n\t\t\t\t\t\t\ttypeName: 'binding',\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\ttype: 'arrow',\n\t\t\t\t\t\t\tfromId: arrow.id,\n\t\t\t\t\t\t\ttoId: start.boundShapeId,\n\t\t\t\t\t\t\tmeta: {},\n\t\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\t\tterminal: 'start',\n\t\t\t\t\t\t\t\tnormalizedAnchor: start.normalizedAnchor,\n\t\t\t\t\t\t\t\tisExact: start.isExact,\n\t\t\t\t\t\t\t\tisPrecise: start.isPrecise,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tupdates.push([id, binding])\n\t\t\t\t\t\tnewArrow.props.start = { x: 0, y: 0 }\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete newArrow.props.start.type\n\t\t\t\t\t}\n\t\t\t\t\tif (end.type === 'binding') {\n\t\t\t\t\t\tconst id = createBindingId()\n\t\t\t\t\t\tconst binding = {\n\t\t\t\t\t\t\ttypeName: 'binding',\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\ttype: 'arrow',\n\t\t\t\t\t\t\tfromId: arrow.id,\n\t\t\t\t\t\t\ttoId: end.boundShapeId,\n\t\t\t\t\t\t\tmeta: {},\n\t\t\t\t\t\t\tprops: {\n\t\t\t\t\t\t\t\tterminal: 'end',\n\t\t\t\t\t\t\t\tnormalizedAnchor: end.normalizedAnchor,\n\t\t\t\t\t\t\t\tisExact: end.isExact,\n\t\t\t\t\t\t\t\tisPrecise: end.isPrecise,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tupdates.push([id, binding])\n\t\t\t\t\t\tnewArrow.props.end = { x: 0, y: 0 }\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete newArrow.props.end.type\n\t\t\t\t\t}\n\t\t\t\t\tupdates.push([arrow.id, newArrow])\n\t\t\t\t}\n\n\t\t\t\tfor (const [id, record] of updates) {\n\t\t\t\t\tstorage.set(id, record as any)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddScale,\n\t\t\tup: (props) => {\n\t\t\t\tprops.scale = 1\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tdelete props.scale\n\t\t\t},\n\t\t}),\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddElbow,\n\t\t\tup: (props) => {\n\t\t\t\tprops.kind = 'arc'\n\t\t\t\tprops.elbowMidPoint = 0.5\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tdelete props.kind\n\t\t\t\tdelete props.elbowMidPoint\n\t\t\t},\n\t\t}),\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddRichText,\n\t\t\tup: (props) => {\n\t\t\t\tprops.richText = toRichText(props.text)\n\t\t\t\tdelete props.text\n\t\t\t},\n\t\t\t// N.B. Explicitly no down state so that we force clients to update.\n\t\t\t// down: (props) => {\n\t\t\t// \tdelete props.richText\n\t\t\t// },\n\t\t}),\n\t\tpropsMigration({\n\t\t\tid: arrowShapeVersions.AddRichTextAttrs,\n\t\t\tup: (_props) => {\n\t\t\t\t// noop - attrs is optional so old records are valid\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\t// Remove attrs from richText when migrating down\n\t\t\t\tif (props.richText && 'attrs' in props.richText) {\n\t\t\t\t\tdelete props.richText.attrs\n\t\t\t\t}\n\t\t\t},\n\t\t}),\n\t],\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,+BAA+B;AACxC,SAAS,uBAAuB;AAChC,SAAS,SAAS;AAClB,SAAqB,mBAAmB,kBAAkB;AAC1D,SAAmB,yBAAyB;AAC5C,SAAS,uBAAuB;AAChC,SAA6B,oCAAoC;AACjE,SAAwC,4BAA4B;AACpE,SAAS,iBAAiB;AAC1B;AAAA,EACC;AAAA,EACA;AAAA,OAEM;AACP,SAAS,wBAA4C;AACrD,SAAS,wBAA4C;AACrD,SAAS,wBAA4C;AACrD,SAAS,wBAA4C;AAGrD,MAAM,aAAa,CAAC,OAAO,OAAO;AA8B3B,MAAM,sBAAsB,UAAU,WAAW,oBAAoB;AAAA,EAC3E,cAAc;AAAA,EACd,QAAQ;AACT,CAAC;AASD,MAAM,iBAAiB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;AAuBO,MAAM,gCAAgC,UAAU,WAAW,yBAAyB;AAAA,EAC1F,cAAc;AAAA,EACd,QAAQ;AACT,CAAC;AAuBM,MAAM,8BAA8B,UAAU,WAAW,uBAAuB;AAAA,EACtF,cAAc;AAAA,EACd,QAAQ;AACT,CAAC;AAgHM,MAAM,kBAA6C;AAAA,EACzD,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,MAAM;AAAA,EACN,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM,EAAE;AAAA,EACR,UAAU;AAAA,EACV,eAAe,EAAE;AAAA,EACjB,OAAO,EAAE;AAAA,EACT,eAAe,EAAE;AAClB;AAkBO,MAAM,qBAAqB,6BAA6B,SAAS;AAAA,EACvE,eAAe;AAAA,EACf,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,UAAU;AAAA,EACV,aAAa;AAAA,EACb,kBAAkB;AACnB,CAAC;AAED,SAAS,eAAe,WAA6B;AACpD,SAAO,qBAAmC,SAAS,SAAS,SAAS;AACtE;AAWO,MAAM,uBAAuB,wBAAwB;AAAA,EAC3D,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,UAAU;AAAA,IACT,eAAe;AAAA,MACd,IAAI,mBAAmB;AAAA,MACvB,IAAI,CAAC,UAAU;AACd,cAAM,aAAa;AAAA,MACpB;AAAA,MACA,MAAM;AAAA,IACP,CAAC;AAAA,IAED,eAAe;AAAA,MACd,IAAI,mBAAmB;AAAA,MACvB,IAAI,CAAC,EAAE,OAAO,IAAI,MAAM;AACvB,YAAI,MAAM,SAAS,WAAW;AAC7B,gBAAM,YAAY,EAAE,MAAM,iBAAiB,MAAM,OAAO,MAAM,iBAAiB,MAAM;AAAA,QACtF;AACA,YAAI,IAAI,SAAS,WAAW;AAC3B,cAAI,YAAY,EAAE,IAAI,iBAAiB,MAAM,OAAO,IAAI,iBAAiB,MAAM;AAAA,QAChF;AAAA,MACD;AAAA,MACA,MAAM,CAAC,EAAE,OAAO,IAAI,MAAM;AACzB,YAAI,MAAM,SAAS,WAAW;AAC7B,cAAI,CAAC,MAAM,WAAW;AACrB,kBAAM,mBAAmB,EAAE,GAAG,KAAK,GAAG,IAAI;AAAA,UAC3C;AACA,iBAAO,MAAM;AAAA,QACd;AACA,YAAI,IAAI,SAAS,WAAW;AAC3B,cAAI,CAAC,IAAI,WAAW;AACnB,gBAAI,mBAAmB,EAAE,GAAG,KAAK,GAAG,IAAI;AAAA,UACzC;AACA,iBAAO,IAAI;AAAA,QACZ;AAAA,MACD;AAAA,IACD,CAAC;AAAA,IAED,eAAe;AAAA,MACd,IAAI,mBAAmB;AAAA,MACvB,IAAI,CAAC,UAAU;AACd,cAAM,gBAAgB;AAAA,MACvB;AAAA,MACA,MAAM,CAAC,UAAU;AAChB,eAAO,MAAM;AAAA,MACd;AAAA,IACD,CAAC;AAAA,IAED;AAAA,MACC,IAAI,mBAAmB;AAAA,MACvB,OAAO;AAAA,MACP,IAAI,CAAC,YAAY;AAsBhB,cAAM,UAA+B,CAAC;AAEtC,mBAAW,UAAU,QAAQ,OAAO,GAAG;AACtC,cAAI,OAAO,aAAa,WAAY,OAAmB,SAAS,QAAS;AACzE,gBAAM,QAAQ;AACd,gBAAM,WAAW,gBAAgB,KAAK;AACtC,gBAAM,EAAE,OAAO,IAAI,IAAI,MAAM;AAC7B,cAAI,MAAM,SAAS,WAAW;AAC7B,kBAAM,KAAK,gBAAgB;AAC3B,kBAAM,UAAU;AAAA,cACf,UAAU;AAAA,cACV;AAAA,cACA,MAAM;AAAA,cACN,QAAQ,MAAM;AAAA,cACd,MAAM,MAAM;AAAA,cACZ,MAAM,CAAC;AAAA,cACP,OAAO;AAAA,gBACN,UAAU;AAAA,gBACV,kBAAkB,MAAM;AAAA,gBACxB,SAAS,MAAM;AAAA,gBACf,WAAW,MAAM;AAAA,cAClB;AAAA,YACD;AAEA,oBAAQ,KAAK,CAAC,IAAI,OAAO,CAAC;AAC1B,qBAAS,MAAM,QAAQ,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,UACrC,OAAO;AACN,mBAAO,SAAS,MAAM,MAAM;AAAA,UAC7B;AACA,cAAI,IAAI,SAAS,WAAW;AAC3B,kBAAM,KAAK,gBAAgB;AAC3B,kBAAM,UAAU;AAAA,cACf,UAAU;AAAA,cACV;AAAA,cACA,MAAM;AAAA,cACN,QAAQ,MAAM;AAAA,cACd,MAAM,IAAI;AAAA,cACV,MAAM,CAAC;AAAA,cACP,OAAO;AAAA,gBACN,UAAU;AAAA,gBACV,kBAAkB,IAAI;AAAA,gBACtB,SAAS,IAAI;AAAA,gBACb,WAAW,IAAI;AAAA,cAChB;AAAA,YACD;AAEA,oBAAQ,KAAK,CAAC,IAAI,OAAO,CAAC;AAC1B,qBAAS,MAAM,MAAM,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,UACnC,OAAO;AACN,mBAAO,SAAS,MAAM,IAAI;AAAA,UAC3B;AACA,kBAAQ,KAAK,CAAC,MAAM,IAAI,QAAQ,CAAC;AAAA,QAClC;AAEA,mBAAW,CAAC,IAAI,MAAM,KAAK,SAAS;AACnC,kBAAQ,IAAI,IAAI,MAAa;AAAA,QAC9B;AAAA,MACD;AAAA,IACD;AAAA,IACA,eAAe;AAAA,MACd,IAAI,mBAAmB;AAAA,MACvB,IAAI,CAAC,UAAU;AACd,cAAM,QAAQ;AAAA,MACf;AAAA,MACA,MAAM,CAAC,UAAU;AAChB,eAAO,MAAM;AAAA,MACd;AAAA,IACD,CAAC;AAAA,IACD,eAAe;AAAA,MACd,IAAI,mBAAmB;AAAA,MACvB,IAAI,CAAC,UAAU;AACd,cAAM,OAAO;AACb,cAAM,gBAAgB;AAAA,MACvB;AAAA,MACA,MAAM,CAAC,UAAU;AAChB,eAAO,MAAM;AACb,eAAO,MAAM;AAAA,MACd;AAAA,IACD,CAAC;AAAA,IACD,eAAe;AAAA,MACd,IAAI,mBAAmB;AAAA,MACvB,IAAI,CAAC,UAAU;AACd,cAAM,WAAW,WAAW,MAAM,IAAI;AACtC,eAAO,MAAM;AAAA,MACd;AAAA;AAAA;AAAA;AAAA;AAAA,IAKD,CAAC;AAAA,IACD,eAAe;AAAA,MACd,IAAI,mBAAmB;AAAA,MACvB,IAAI,CAAC,WAAW;AAAA,MAEhB;AAAA,MACA,MAAM,CAAC,UAAU;AAEhB,YAAI,MAAM,YAAY,WAAW,MAAM,UAAU;AAChD,iBAAO,MAAM,SAAS;AAAA,QACvB;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AACD,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { T } from "@tldraw/validate";
|
|
2
|
-
import {
|
|
2
|
+
import { b64Vecs } from "../misc/b64Vecs.mjs";
|
|
3
3
|
import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from "../records/TLShape.mjs";
|
|
4
4
|
import { DefaultColorStyle } from "../styles/TLColorStyle.mjs";
|
|
5
5
|
import { DefaultDashStyle } from "../styles/TLDashStyle.mjs";
|
|
@@ -7,7 +7,7 @@ import { DefaultFillStyle } from "../styles/TLFillStyle.mjs";
|
|
|
7
7
|
import { DefaultSizeStyle } from "../styles/TLSizeStyle.mjs";
|
|
8
8
|
const DrawShapeSegment = T.object({
|
|
9
9
|
type: T.literalEnum("free", "straight"),
|
|
10
|
-
points: T.
|
|
10
|
+
points: T.string
|
|
11
11
|
});
|
|
12
12
|
const drawShapeProps = {
|
|
13
13
|
color: DefaultColorStyle,
|
|
@@ -18,11 +18,14 @@ const drawShapeProps = {
|
|
|
18
18
|
isComplete: T.boolean,
|
|
19
19
|
isClosed: T.boolean,
|
|
20
20
|
isPen: T.boolean,
|
|
21
|
-
scale: T.nonZeroNumber
|
|
21
|
+
scale: T.nonZeroNumber,
|
|
22
|
+
scaleX: T.nonZeroFiniteNumber,
|
|
23
|
+
scaleY: T.nonZeroFiniteNumber
|
|
22
24
|
};
|
|
23
25
|
const Versions = createShapePropsMigrationIds("draw", {
|
|
24
26
|
AddInPen: 1,
|
|
25
|
-
AddScale: 2
|
|
27
|
+
AddScale: 2,
|
|
28
|
+
Base64: 3
|
|
26
29
|
});
|
|
27
30
|
const drawShapeMigrations = createShapePropsMigrationSequence({
|
|
28
31
|
sequence: [
|
|
@@ -50,11 +53,41 @@ const drawShapeMigrations = createShapePropsMigrationSequence({
|
|
|
50
53
|
down: (props) => {
|
|
51
54
|
delete props.scale;
|
|
52
55
|
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: Versions.Base64,
|
|
59
|
+
up: (props) => {
|
|
60
|
+
props.segments = props.segments.map((segment) => {
|
|
61
|
+
return {
|
|
62
|
+
...segment,
|
|
63
|
+
// Only encode if points is an array (not already base64 string)
|
|
64
|
+
points: typeof segment.points === "string" ? segment.points : b64Vecs.encodePoints(segment.points)
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
props.scaleX = props.scaleX ?? 1;
|
|
68
|
+
props.scaleY = props.scaleY ?? 1;
|
|
69
|
+
},
|
|
70
|
+
down: (props) => {
|
|
71
|
+
props.segments = props.segments.map((segment) => ({
|
|
72
|
+
...segment,
|
|
73
|
+
// Only decode if points is a string (not already VecModel[])
|
|
74
|
+
points: Array.isArray(segment.points) ? segment.points : b64Vecs.decodePoints(segment.points)
|
|
75
|
+
}));
|
|
76
|
+
delete props.scaleX;
|
|
77
|
+
delete props.scaleY;
|
|
78
|
+
}
|
|
53
79
|
}
|
|
54
80
|
]
|
|
55
81
|
});
|
|
82
|
+
function compressLegacySegments(segments) {
|
|
83
|
+
return segments.map((segment) => ({
|
|
84
|
+
...segment,
|
|
85
|
+
points: b64Vecs.encodePoints(segment.points)
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
56
88
|
export {
|
|
57
89
|
DrawShapeSegment,
|
|
90
|
+
compressLegacySegments,
|
|
58
91
|
drawShapeMigrations,
|
|
59
92
|
drawShapeProps,
|
|
60
93
|
Versions as drawShapeVersions
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/shapes/TLDrawShape.ts"],
|
|
4
|
-
"sourcesContent": ["import { T } from '@tldraw/validate'\nimport { VecModel
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,
|
|
4
|
+
"sourcesContent": ["import { T } from '@tldraw/validate'\nimport { b64Vecs } from '../misc/b64Vecs'\nimport { VecModel } from '../misc/geometry-types'\nimport { createShapePropsMigrationIds, createShapePropsMigrationSequence } from '../records/TLShape'\nimport { RecordProps } from '../recordsWithProps'\nimport { DefaultColorStyle, TLDefaultColorStyle } from '../styles/TLColorStyle'\nimport { DefaultDashStyle, TLDefaultDashStyle } from '../styles/TLDashStyle'\nimport { DefaultFillStyle, TLDefaultFillStyle } from '../styles/TLFillStyle'\nimport { DefaultSizeStyle, TLDefaultSizeStyle } from '../styles/TLSizeStyle'\nimport { TLBaseShape } from './TLBaseShape'\n\n/**\n * A segment of a draw shape representing either freehand drawing or straight line segments.\n *\n * @public\n */\nexport interface TLDrawShapeSegment {\n\t/** Type of drawing segment - 'free' for freehand curves, 'straight' for line segments */\n\ttype: 'free' | 'straight'\n\t/** Base64-encoded points (x, y, z triplets stored as Float16) */\n\tpoints: string\n}\n\n/**\n * Validator for draw shape segments ensuring proper structure and data types.\n *\n * @public\n */\nexport const DrawShapeSegment: T.ObjectValidator<TLDrawShapeSegment> = T.object({\n\ttype: T.literalEnum('free', 'straight'),\n\tpoints: T.string,\n})\n\n/**\n * Properties for the draw shape, which represents freehand drawing and sketching.\n *\n * @public\n */\nexport interface TLDrawShapeProps {\n\t/** Color style for the drawing stroke */\n\tcolor: TLDefaultColorStyle\n\t/** Fill style for closed drawing shapes */\n\tfill: TLDefaultFillStyle\n\t/** Dash pattern style for the stroke */\n\tdash: TLDefaultDashStyle\n\t/** Size/thickness of the drawing stroke */\n\tsize: TLDefaultSizeStyle\n\t/** Array of segments that make up the complete drawing path */\n\tsegments: TLDrawShapeSegment[]\n\t/** Whether the drawing is complete (user finished drawing) */\n\tisComplete: boolean\n\t/** Whether the drawing path forms a closed shape */\n\tisClosed: boolean\n\t/** Whether this drawing was created with a pen/stylus device */\n\tisPen: boolean\n\t/** Scale factor applied to the drawing */\n\tscale: number\n\t/** Horizontal scale factor for lazy resize */\n\tscaleX: number\n\t/** Vertical scale factor for lazy resize */\n\tscaleY: number\n}\n\n/**\n * A draw shape represents freehand drawing, sketching, and pen input on the canvas.\n * Draw shapes are composed of segments that can be either smooth curves or straight lines.\n *\n * @public\n * @example\n * ```ts\n * const drawShape: TLDrawShape = {\n * id: createShapeId(),\n * typeName: 'shape',\n * type: 'draw',\n * x: 50,\n * y: 50,\n * rotation: 0,\n * index: 'a1',\n * parentId: 'page:page1',\n * isLocked: false,\n * opacity: 1,\n * props: {\n * color: 'black',\n * fill: 'none',\n * dash: 'solid',\n * size: 'm',\n * segments: [{\n * type: 'free',\n * points: [{ x: 0, y: 0, z: 0.5 }, { x: 20, y: 15, z: 0.6 }]\n * }],\n * isComplete: true,\n * isClosed: false,\n * isPen: false,\n * scale: 1\n * },\n * meta: {}\n * }\n * ```\n */\nexport type TLDrawShape = TLBaseShape<'draw', TLDrawShapeProps>\n\n/**\n * Validation schema for draw shape properties.\n *\n * @public\n * @example\n * ```ts\n * // Validate draw shape properties\n * const props = {\n * color: 'red',\n * fill: 'solid',\n * segments: [{ type: 'free', points: [] }],\n * isComplete: true\n * }\n * const isValid = drawShapeProps.color.isValid(props.color)\n * ```\n */\n/** @public */\nexport const drawShapeProps: RecordProps<TLDrawShape> = {\n\tcolor: DefaultColorStyle,\n\tfill: DefaultFillStyle,\n\tdash: DefaultDashStyle,\n\tsize: DefaultSizeStyle,\n\tsegments: T.arrayOf(DrawShapeSegment),\n\tisComplete: T.boolean,\n\tisClosed: T.boolean,\n\tisPen: T.boolean,\n\tscale: T.nonZeroNumber,\n\tscaleX: T.nonZeroFiniteNumber,\n\tscaleY: T.nonZeroFiniteNumber,\n}\n\nconst Versions = createShapePropsMigrationIds('draw', {\n\tAddInPen: 1,\n\tAddScale: 2,\n\tBase64: 3,\n})\n\n/**\n * Version identifiers for draw shape migrations.\n *\n * @public\n */\nexport { Versions as drawShapeVersions }\n\n/**\n * Migration sequence for draw shape properties across different schema versions.\n * Handles adding pen detection and scale properties to existing draw shapes.\n *\n * @public\n */\nexport const drawShapeMigrations = createShapePropsMigrationSequence({\n\tsequence: [\n\t\t{\n\t\t\tid: Versions.AddInPen,\n\t\t\tup: (props) => {\n\t\t\t\t// Rather than checking to see whether the shape is a pen at runtime,\n\t\t\t\t// from now on we're going to use the type of device reported to us\n\t\t\t\t// as well as the pressure data received; but for existing shapes we\n\t\t\t\t// need to check the pressure data to see if it's a pen or not.\n\n\t\t\t\tconst { points } = props.segments[0]\n\n\t\t\t\tif (points.length === 0) {\n\t\t\t\t\tprops.isPen = false\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tlet isPen = !(points[0].z === 0 || points[0].z === 0.5)\n\n\t\t\t\tif (points[1]) {\n\t\t\t\t\t// Double check if we have a second point (we probably should)\n\t\t\t\t\tisPen = isPen && !(points[1].z === 0 || points[1].z === 0.5)\n\t\t\t\t}\n\t\t\t\tprops.isPen = isPen\n\t\t\t},\n\t\t\tdown: 'retired',\n\t\t},\n\t\t{\n\t\t\tid: Versions.AddScale,\n\t\t\tup: (props) => {\n\t\t\t\tprops.scale = 1\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tdelete props.scale\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: Versions.Base64,\n\t\t\tup: (props) => {\n\t\t\t\tprops.segments = props.segments.map((segment: any) => {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...segment,\n\t\t\t\t\t\t// Only encode if points is an array (not already base64 string)\n\t\t\t\t\t\tpoints:\n\t\t\t\t\t\t\ttypeof segment.points === 'string'\n\t\t\t\t\t\t\t\t? segment.points\n\t\t\t\t\t\t\t\t: b64Vecs.encodePoints(segment.points),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tprops.scaleX = props.scaleX ?? 1\n\t\t\t\tprops.scaleY = props.scaleY ?? 1\n\t\t\t},\n\t\t\tdown: (props) => {\n\t\t\t\tprops.segments = props.segments.map((segment: any) => ({\n\t\t\t\t\t...segment,\n\t\t\t\t\t// Only decode if points is a string (not already VecModel[])\n\t\t\t\t\tpoints: Array.isArray(segment.points)\n\t\t\t\t\t\t? segment.points\n\t\t\t\t\t\t: b64Vecs.decodePoints(segment.points),\n\t\t\t\t}))\n\t\t\t\tdelete props.scaleX\n\t\t\t\tdelete props.scaleY\n\t\t\t},\n\t\t},\n\t],\n})\n\n/**\n * Compress legacy draw shape segments by converting VecModel[] points to base64 format.\n * This function is useful for converting old draw shape data to the new compressed format.\n *\n * @public\n */\nexport function compressLegacySegments(\n\tsegments: {\n\t\ttype: 'free' | 'straight'\n\t\tpoints: VecModel[]\n\t}[]\n): TLDrawShapeSegment[] {\n\treturn segments.map((segment) => ({\n\t\t...segment,\n\t\tpoints: b64Vecs.encodePoints(segment.points),\n\t}))\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,eAAe;AAExB,SAAS,8BAA8B,yCAAyC;AAEhF,SAAS,yBAA8C;AACvD,SAAS,wBAA4C;AACrD,SAAS,wBAA4C;AACrD,SAAS,wBAA4C;AAoB9C,MAAM,mBAA0D,EAAE,OAAO;AAAA,EAC/E,MAAM,EAAE,YAAY,QAAQ,UAAU;AAAA,EACtC,QAAQ,EAAE;AACX,CAAC;AAuFM,MAAM,iBAA2C;AAAA,EACvD,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,UAAU,EAAE,QAAQ,gBAAgB;AAAA,EACpC,YAAY,EAAE;AAAA,EACd,UAAU,EAAE;AAAA,EACZ,OAAO,EAAE;AAAA,EACT,OAAO,EAAE;AAAA,EACT,QAAQ,EAAE;AAAA,EACV,QAAQ,EAAE;AACX;AAEA,MAAM,WAAW,6BAA6B,QAAQ;AAAA,EACrD,UAAU;AAAA,EACV,UAAU;AAAA,EACV,QAAQ;AACT,CAAC;AAeM,MAAM,sBAAsB,kCAAkC;AAAA,EACpE,UAAU;AAAA,IACT;AAAA,MACC,IAAI,SAAS;AAAA,MACb,IAAI,CAAC,UAAU;AAMd,cAAM,EAAE,OAAO,IAAI,MAAM,SAAS,CAAC;AAEnC,YAAI,OAAO,WAAW,GAAG;AACxB,gBAAM,QAAQ;AACd;AAAA,QACD;AAEA,YAAI,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,EAAE,MAAM;AAEnD,YAAI,OAAO,CAAC,GAAG;AAEd,kBAAQ,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,EAAE,MAAM;AAAA,QACzD;AACA,cAAM,QAAQ;AAAA,MACf;AAAA,MACA,MAAM;AAAA,IACP;AAAA,IACA;AAAA,MACC,IAAI,SAAS;AAAA,MACb,IAAI,CAAC,UAAU;AACd,cAAM,QAAQ;AAAA,MACf;AAAA,MACA,MAAM,CAAC,UAAU;AAChB,eAAO,MAAM;AAAA,MACd;AAAA,IACD;AAAA,IACA;AAAA,MACC,IAAI,SAAS;AAAA,MACb,IAAI,CAAC,UAAU;AACd,cAAM,WAAW,MAAM,SAAS,IAAI,CAAC,YAAiB;AACrD,iBAAO;AAAA,YACN,GAAG;AAAA;AAAA,YAEH,QACC,OAAO,QAAQ,WAAW,WACvB,QAAQ,SACR,QAAQ,aAAa,QAAQ,MAAM;AAAA,UACxC;AAAA,QACD,CAAC;AACD,cAAM,SAAS,MAAM,UAAU;AAC/B,cAAM,SAAS,MAAM,UAAU;AAAA,MAChC;AAAA,MACA,MAAM,CAAC,UAAU;AAChB,cAAM,WAAW,MAAM,SAAS,IAAI,CAAC,aAAkB;AAAA,UACtD,GAAG;AAAA;AAAA,UAEH,QAAQ,MAAM,QAAQ,QAAQ,MAAM,IACjC,QAAQ,SACR,QAAQ,aAAa,QAAQ,MAAM;AAAA,QACvC,EAAE;AACF,eAAO,MAAM;AACb,eAAO,MAAM;AAAA,MACd;AAAA,IACD;AAAA,EACD;AACD,CAAC;AAQM,SAAS,uBACf,UAIuB;AACvB,SAAO,SAAS,IAAI,CAAC,aAAa;AAAA,IACjC,GAAG;AAAA,IACH,QAAQ,QAAQ,aAAa,QAAQ,MAAM;AAAA,EAC5C,EAAE;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|