@tldraw/editor 3.8.0-canary.c45a05a15c4a → 3.8.0-canary.c5d6281c3fdd
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 +167 -12
- package/dist-cjs/index.js +7 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +2 -5
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/config/createTLStore.js +4 -2
- package/dist-cjs/lib/config/createTLStore.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +95 -21
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/resizeScaled.js +66 -0
- package/dist-cjs/lib/editor/shapes/shared/resizeScaled.js.map +7 -0
- package/dist-cjs/lib/editor/types/SvgExportContext.js.map +2 -2
- package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
- package/dist-cjs/lib/exports/exportToSvg.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgAsImage.js +83 -0
- package/dist-cjs/lib/exports/getSvgAsImage.js.map +7 -0
- package/dist-cjs/lib/exports/getSvgJsx.js +16 -3
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/hooks/useLocalStore.js +1 -1
- package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
- package/dist-cjs/lib/options.js +2 -1
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/utils/browserCanvasMaxSize.js +75 -0
- package/dist-cjs/lib/utils/browserCanvasMaxSize.js.map +7 -0
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +3 -1
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +167 -12
- package/dist-esm/index.mjs +7 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +2 -5
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/config/createTLStore.mjs +4 -2
- package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +95 -21
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/resizeScaled.mjs +46 -0
- package/dist-esm/lib/editor/shapes/shared/resizeScaled.mjs.map +7 -0
- package/dist-esm/lib/editor/types/SvgExportContext.mjs.map +2 -2
- package/dist-esm/lib/exports/exportToSvg.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgAsImage.mjs +63 -0
- package/dist-esm/lib/exports/getSvgAsImage.mjs.map +7 -0
- package/dist-esm/lib/exports/getSvgJsx.mjs +16 -3
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/hooks/useLocalStore.mjs +1 -1
- package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
- package/dist-esm/lib/options.mjs +2 -1
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/utils/browserCanvasMaxSize.mjs +45 -0
- package/dist-esm/lib/utils/browserCanvasMaxSize.mjs.map +7 -0
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +3 -1
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +2 -1
- package/package.json +9 -7
- package/src/index.ts +6 -0
- package/src/lib/components/default-components/DefaultCanvas.tsx +2 -5
- package/src/lib/config/createTLStore.ts +4 -2
- package/src/lib/editor/Editor.ts +129 -39
- package/src/lib/editor/shapes/ShapeUtil.ts +30 -1
- package/src/lib/editor/shapes/shared/resizeScaled.ts +61 -0
- package/src/lib/editor/types/SvgExportContext.tsx +21 -0
- package/src/lib/editor/types/misc-types.ts +55 -2
- package/src/lib/exports/exportToSvg.tsx +2 -2
- package/src/lib/exports/getSvgAsImage.ts +92 -0
- package/src/lib/exports/getSvgJsx.tsx +17 -2
- package/src/lib/hooks/useLocalStore.ts +1 -1
- package/src/lib/options.ts +6 -0
- package/src/lib/utils/browserCanvasMaxSize.ts +65 -0
- package/src/lib/utils/sync/TLLocalSyncClient.ts +3 -1
- package/src/version.ts +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/options.ts"],
|
|
4
|
-
"sourcesContent": ["import { ComponentType, Fragment } from 'react'\n\n/**\n * Options for configuring tldraw. For defaults, see {@link defaultTldrawOptions}.\n *\n * @example\n * ```tsx\n * const options: Partial<TldrawOptions> = {\n * maxPages: 3,\n * maxShapesPerPage: 1000,\n * }\n *\n * function MyTldrawComponent() {\n * return <Tldraw options={options} />\n * }\n * ```\n *\n * @public\n */\nexport interface TldrawOptions {\n\treadonly maxShapesPerPage: number\n\treadonly maxFilesAtOnce: number\n\treadonly maxPages: number\n\treadonly animationMediumMs: number\n\treadonly followChaseViewportSnap: number\n\treadonly doubleClickDurationMs: number\n\treadonly multiClickDurationMs: number\n\treadonly coarseDragDistanceSquared: number\n\treadonly dragDistanceSquared: number\n\treadonly defaultSvgPadding: number\n\treadonly cameraSlideFriction: number\n\treadonly maxPointsPerDrawShape: number\n\treadonly gridSteps: readonly {\n\t\treadonly min: number\n\t\treadonly mid: number\n\t\treadonly step: number\n\t}[]\n\treadonly collaboratorInactiveTimeoutMs: number\n\treadonly collaboratorIdleTimeoutMs: number\n\treadonly collaboratorCheckIntervalMs: number\n\treadonly cameraMovingTimeoutMs: number\n\treadonly hitTestMargin: number\n\treadonly edgeScrollDelay: number\n\treadonly edgeScrollEaseDuration: number\n\treadonly edgeScrollSpeed: number\n\treadonly edgeScrollDistance: number\n\treadonly coarsePointerWidth: number\n\treadonly coarseHandleRadius: number\n\treadonly handleRadius: number\n\treadonly longPressDurationMs: number\n\treadonly textShadowLod: number\n\treadonly adjacentShapeMargin: number\n\treadonly flattenImageBoundsExpand: number\n\treadonly flattenImageBoundsPadding: number\n\treadonly laserDelayMs: number\n\treadonly maxExportDelayMs: number\n\t/**\n\t * How long should previews created by {@link Editor.createTemporaryAssetPreview} last before\n\t * they expire? Defaults to 3 minutes.\n\t */\n\treadonly temporaryAssetPreviewLifetimeMs: number\n\treadonly actionShortcutsLocation: 'menu' | 'toolbar' | 'swap'\n\treadonly createTextOnCanvasDoubleClick: boolean\n\t/**\n\t * The react provider to use when exporting an image. This is useful if your shapes depend on\n\t * external context providers. By default, this is `React.Fragment`.\n\t */\n\treadonly exportProvider: ComponentType<{ children: React.ReactNode }>\n}\n\n/** @public */\nexport const defaultTldrawOptions = {\n\tmaxShapesPerPage: 4000,\n\tmaxFilesAtOnce: 100,\n\tmaxPages: 40,\n\tanimationMediumMs: 320,\n\tfollowChaseViewportSnap: 2,\n\tdoubleClickDurationMs: 450,\n\tmultiClickDurationMs: 200,\n\tcoarseDragDistanceSquared: 36, // 6 squared\n\tdragDistanceSquared: 16, // 4 squared\n\tdefaultSvgPadding: 32,\n\tcameraSlideFriction: 0.09,\n\tmaxPointsPerDrawShape: 500,\n\tgridSteps: [\n\t\t{ min: -1, mid: 0.15, step: 64 },\n\t\t{ min: 0.05, mid: 0.375, step: 16 },\n\t\t{ min: 0.15, mid: 1, step: 4 },\n\t\t{ min: 0.7, mid: 2.5, step: 1 },\n\t],\n\tcollaboratorInactiveTimeoutMs: 60000,\n\tcollaboratorIdleTimeoutMs: 3000,\n\tcollaboratorCheckIntervalMs: 1200,\n\tcameraMovingTimeoutMs: 64,\n\thitTestMargin: 8,\n\tedgeScrollDelay: 200,\n\tedgeScrollEaseDuration: 200,\n\tedgeScrollSpeed: 25,\n\tedgeScrollDistance: 8,\n\tcoarsePointerWidth: 12,\n\tcoarseHandleRadius: 20,\n\thandleRadius: 12,\n\tlongPressDurationMs: 500,\n\ttextShadowLod: 0.35,\n\tadjacentShapeMargin: 10,\n\tflattenImageBoundsExpand: 64,\n\tflattenImageBoundsPadding: 16,\n\tlaserDelayMs: 1200,\n\tmaxExportDelayMs: 5000,\n\ttemporaryAssetPreviewLifetimeMs: 180000,\n\tactionShortcutsLocation: 'swap',\n\tcreateTextOnCanvasDoubleClick: true,\n\texportProvider: Fragment,\n} as const satisfies TldrawOptions\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAwC;
|
|
4
|
+
"sourcesContent": ["import { ComponentType, Fragment } from 'react'\n\n/**\n * Options for configuring tldraw. For defaults, see {@link defaultTldrawOptions}.\n *\n * @example\n * ```tsx\n * const options: Partial<TldrawOptions> = {\n * maxPages: 3,\n * maxShapesPerPage: 1000,\n * }\n *\n * function MyTldrawComponent() {\n * return <Tldraw options={options} />\n * }\n * ```\n *\n * @public\n */\nexport interface TldrawOptions {\n\treadonly maxShapesPerPage: number\n\treadonly maxFilesAtOnce: number\n\treadonly maxPages: number\n\treadonly animationMediumMs: number\n\treadonly followChaseViewportSnap: number\n\treadonly doubleClickDurationMs: number\n\treadonly multiClickDurationMs: number\n\treadonly coarseDragDistanceSquared: number\n\treadonly dragDistanceSquared: number\n\treadonly defaultSvgPadding: number\n\treadonly cameraSlideFriction: number\n\treadonly maxPointsPerDrawShape: number\n\treadonly gridSteps: readonly {\n\t\treadonly min: number\n\t\treadonly mid: number\n\t\treadonly step: number\n\t}[]\n\treadonly collaboratorInactiveTimeoutMs: number\n\treadonly collaboratorIdleTimeoutMs: number\n\treadonly collaboratorCheckIntervalMs: number\n\treadonly cameraMovingTimeoutMs: number\n\treadonly hitTestMargin: number\n\treadonly edgeScrollDelay: number\n\treadonly edgeScrollEaseDuration: number\n\treadonly edgeScrollSpeed: number\n\treadonly edgeScrollDistance: number\n\treadonly coarsePointerWidth: number\n\treadonly coarseHandleRadius: number\n\treadonly handleRadius: number\n\treadonly longPressDurationMs: number\n\treadonly textShadowLod: number\n\treadonly adjacentShapeMargin: number\n\treadonly flattenImageBoundsExpand: number\n\treadonly flattenImageBoundsPadding: number\n\treadonly laserDelayMs: number\n\treadonly maxExportDelayMs: number\n\t/**\n\t * How long should previews created by {@link Editor.createTemporaryAssetPreview} last before\n\t * they expire? Defaults to 3 minutes.\n\t */\n\treadonly temporaryAssetPreviewLifetimeMs: number\n\treadonly actionShortcutsLocation: 'menu' | 'toolbar' | 'swap'\n\treadonly createTextOnCanvasDoubleClick: boolean\n\t/**\n\t * The react provider to use when exporting an image. This is useful if your shapes depend on\n\t * external context providers. By default, this is `React.Fragment`.\n\t */\n\treadonly exportProvider: ComponentType<{ children: React.ReactNode }>\n\t/**\n\t * How should the note shape resize? By default it does not resize (except automatically based on its text content),\n\t * but you can set it to be user-resizable using scale.\n\t */\n\treadonly noteShapeResizeMode: 'none' | 'scale'\n}\n\n/** @public */\nexport const defaultTldrawOptions = {\n\tmaxShapesPerPage: 4000,\n\tmaxFilesAtOnce: 100,\n\tmaxPages: 40,\n\tanimationMediumMs: 320,\n\tfollowChaseViewportSnap: 2,\n\tdoubleClickDurationMs: 450,\n\tmultiClickDurationMs: 200,\n\tcoarseDragDistanceSquared: 36, // 6 squared\n\tdragDistanceSquared: 16, // 4 squared\n\tdefaultSvgPadding: 32,\n\tcameraSlideFriction: 0.09,\n\tmaxPointsPerDrawShape: 500,\n\tgridSteps: [\n\t\t{ min: -1, mid: 0.15, step: 64 },\n\t\t{ min: 0.05, mid: 0.375, step: 16 },\n\t\t{ min: 0.15, mid: 1, step: 4 },\n\t\t{ min: 0.7, mid: 2.5, step: 1 },\n\t],\n\tcollaboratorInactiveTimeoutMs: 60000,\n\tcollaboratorIdleTimeoutMs: 3000,\n\tcollaboratorCheckIntervalMs: 1200,\n\tcameraMovingTimeoutMs: 64,\n\thitTestMargin: 8,\n\tedgeScrollDelay: 200,\n\tedgeScrollEaseDuration: 200,\n\tedgeScrollSpeed: 25,\n\tedgeScrollDistance: 8,\n\tcoarsePointerWidth: 12,\n\tcoarseHandleRadius: 20,\n\thandleRadius: 12,\n\tlongPressDurationMs: 500,\n\ttextShadowLod: 0.35,\n\tadjacentShapeMargin: 10,\n\tflattenImageBoundsExpand: 64,\n\tflattenImageBoundsPadding: 16,\n\tlaserDelayMs: 1200,\n\tmaxExportDelayMs: 5000,\n\ttemporaryAssetPreviewLifetimeMs: 180000,\n\tactionShortcutsLocation: 'swap',\n\tcreateTextOnCanvasDoubleClick: true,\n\texportProvider: Fragment,\n\tnoteShapeResizeMode: 'none',\n} as const satisfies TldrawOptions\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAwC;AA4EjC,MAAM,uBAAuB;AAAA,EACnC,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,yBAAyB;AAAA,EACzB,uBAAuB;AAAA,EACvB,sBAAsB;AAAA,EACtB,2BAA2B;AAAA;AAAA,EAC3B,qBAAqB;AAAA;AAAA,EACrB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,uBAAuB;AAAA,EACvB,WAAW;AAAA,IACV,EAAE,KAAK,IAAI,KAAK,MAAM,MAAM,GAAG;AAAA,IAC/B,EAAE,KAAK,MAAM,KAAK,OAAO,MAAM,GAAG;AAAA,IAClC,EAAE,KAAK,MAAM,KAAK,GAAG,MAAM,EAAE;AAAA,IAC7B,EAAE,KAAK,KAAK,KAAK,KAAK,MAAM,EAAE;AAAA,EAC/B;AAAA,EACA,+BAA+B;AAAA,EAC/B,2BAA2B;AAAA,EAC3B,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,wBAAwB;AAAA,EACxB,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,0BAA0B;AAAA,EAC1B,2BAA2B;AAAA,EAC3B,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,iCAAiC;AAAA,EACjC,yBAAyB;AAAA,EACzB,+BAA+B;AAAA,EAC/B,gBAAgB;AAAA,EAChB,qBAAqB;AACtB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var browserCanvasMaxSize_exports = {};
|
|
30
|
+
__export(browserCanvasMaxSize_exports, {
|
|
31
|
+
clampToBrowserMaxCanvasSize: () => clampToBrowserMaxCanvasSize
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(browserCanvasMaxSize_exports);
|
|
34
|
+
var import_canvas_size = __toESM(require("canvas-size"));
|
|
35
|
+
let maxSizePromise = null;
|
|
36
|
+
function getBrowserCanvasMaxSize() {
|
|
37
|
+
if (!maxSizePromise) {
|
|
38
|
+
maxSizePromise = calculateBrowserCanvasMaxSize();
|
|
39
|
+
}
|
|
40
|
+
return maxSizePromise;
|
|
41
|
+
}
|
|
42
|
+
async function calculateBrowserCanvasMaxSize() {
|
|
43
|
+
const maxWidth = await import_canvas_size.default.maxWidth({ usePromise: true });
|
|
44
|
+
const maxHeight = await import_canvas_size.default.maxHeight({ usePromise: true });
|
|
45
|
+
const maxArea = await import_canvas_size.default.maxArea({ usePromise: true });
|
|
46
|
+
return {
|
|
47
|
+
maxWidth: maxWidth.width,
|
|
48
|
+
maxHeight: maxHeight.height,
|
|
49
|
+
maxArea: maxArea.width * maxArea.height
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const MAX_SAFE_CANVAS_DIMENSION = 8192;
|
|
53
|
+
const MAX_SAFE_CANVAS_AREA = 4096 * 4096;
|
|
54
|
+
async function clampToBrowserMaxCanvasSize(width, height) {
|
|
55
|
+
if (width <= MAX_SAFE_CANVAS_DIMENSION && height <= MAX_SAFE_CANVAS_DIMENSION && width * height <= MAX_SAFE_CANVAS_AREA) {
|
|
56
|
+
return [width, height];
|
|
57
|
+
}
|
|
58
|
+
const { maxWidth, maxHeight, maxArea } = await getBrowserCanvasMaxSize();
|
|
59
|
+
const aspectRatio = width / height;
|
|
60
|
+
if (width > maxWidth) {
|
|
61
|
+
width = maxWidth;
|
|
62
|
+
height = width / aspectRatio;
|
|
63
|
+
}
|
|
64
|
+
if (height > maxHeight) {
|
|
65
|
+
height = maxHeight;
|
|
66
|
+
width = height * aspectRatio;
|
|
67
|
+
}
|
|
68
|
+
if (width * height > maxArea) {
|
|
69
|
+
const ratio = Math.sqrt(maxArea / (width * height));
|
|
70
|
+
width *= ratio;
|
|
71
|
+
height *= ratio;
|
|
72
|
+
}
|
|
73
|
+
return [width, height];
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=browserCanvasMaxSize.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/utils/browserCanvasMaxSize.ts"],
|
|
4
|
+
"sourcesContent": ["import canvasSize from 'canvas-size'\n\n/** @internal */\nexport interface CanvasMaxSize {\n\tmaxWidth: number\n\tmaxHeight: number\n\tmaxArea: number\n}\n\nlet maxSizePromise: Promise<CanvasMaxSize> | null = null\n\nfunction getBrowserCanvasMaxSize() {\n\tif (!maxSizePromise) {\n\t\tmaxSizePromise = calculateBrowserCanvasMaxSize()\n\t}\n\n\treturn maxSizePromise\n}\n\nasync function calculateBrowserCanvasMaxSize(): Promise<CanvasMaxSize> {\n\tconst maxWidth = await canvasSize.maxWidth({ usePromise: true })\n\tconst maxHeight = await canvasSize.maxHeight({ usePromise: true })\n\tconst maxArea = await canvasSize.maxArea({ usePromise: true })\n\treturn {\n\t\tmaxWidth: maxWidth.width,\n\t\tmaxHeight: maxHeight.height,\n\t\tmaxArea: maxArea.width * maxArea.height,\n\t}\n}\n\n// https://github.com/jhildenbiddle/canvas-size?tab=readme-ov-file#test-results\nconst MAX_SAFE_CANVAS_DIMENSION = 8192\nconst MAX_SAFE_CANVAS_AREA = 4096 * 4096\n\n/** @internal */\nexport async function clampToBrowserMaxCanvasSize(width: number, height: number) {\n\tif (\n\t\twidth <= MAX_SAFE_CANVAS_DIMENSION &&\n\t\theight <= MAX_SAFE_CANVAS_DIMENSION &&\n\t\twidth * height <= MAX_SAFE_CANVAS_AREA\n\t) {\n\t\treturn [width, height]\n\t}\n\n\tconst { maxWidth, maxHeight, maxArea } = await getBrowserCanvasMaxSize()\n\tconst aspectRatio = width / height\n\n\tif (width > maxWidth) {\n\t\twidth = maxWidth\n\t\theight = width / aspectRatio\n\t}\n\n\tif (height > maxHeight) {\n\t\theight = maxHeight\n\t\twidth = height * aspectRatio\n\t}\n\n\tif (width * height > maxArea) {\n\t\tconst ratio = Math.sqrt(maxArea / (width * height))\n\t\twidth *= ratio\n\t\theight *= ratio\n\t}\n\n\treturn [width, height]\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAuB;AASvB,IAAI,iBAAgD;AAEpD,SAAS,0BAA0B;AAClC,MAAI,CAAC,gBAAgB;AACpB,qBAAiB,8BAA8B;AAAA,EAChD;AAEA,SAAO;AACR;AAEA,eAAe,gCAAwD;AACtE,QAAM,WAAW,MAAM,mBAAAA,QAAW,SAAS,EAAE,YAAY,KAAK,CAAC;AAC/D,QAAM,YAAY,MAAM,mBAAAA,QAAW,UAAU,EAAE,YAAY,KAAK,CAAC;AACjE,QAAM,UAAU,MAAM,mBAAAA,QAAW,QAAQ,EAAE,YAAY,KAAK,CAAC;AAC7D,SAAO;AAAA,IACN,UAAU,SAAS;AAAA,IACnB,WAAW,UAAU;AAAA,IACrB,SAAS,QAAQ,QAAQ,QAAQ;AAAA,EAClC;AACD;AAGA,MAAM,4BAA4B;AAClC,MAAM,uBAAuB,OAAO;AAGpC,eAAsB,4BAA4B,OAAe,QAAgB;AAChF,MACC,SAAS,6BACT,UAAU,6BACV,QAAQ,UAAU,sBACjB;AACD,WAAO,CAAC,OAAO,MAAM;AAAA,EACtB;AAEA,QAAM,EAAE,UAAU,WAAW,QAAQ,IAAI,MAAM,wBAAwB;AACvE,QAAM,cAAc,QAAQ;AAE5B,MAAI,QAAQ,UAAU;AACrB,YAAQ;AACR,aAAS,QAAQ;AAAA,EAClB;AAEA,MAAI,SAAS,WAAW;AACvB,aAAS;AACT,YAAQ,SAAS;AAAA,EAClB;AAEA,MAAI,QAAQ,SAAS,SAAS;AAC7B,UAAM,QAAQ,KAAK,KAAK,WAAW,QAAQ,OAAO;AAClD,aAAS;AACT,cAAU;AAAA,EACX;AAEA,SAAO,CAAC,OAAO,MAAM;AACtB;",
|
|
6
|
+
"names": ["canvasSize"]
|
|
7
|
+
}
|
|
@@ -150,7 +150,9 @@ class TLLocalSyncClient {
|
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
if (sessionStateSnapshot) {
|
|
153
|
-
(0, import_TLSessionStateSnapshot.loadSessionStateSnapshotIntoStore)(this.store, sessionStateSnapshot
|
|
153
|
+
(0, import_TLSessionStateSnapshot.loadSessionStateSnapshotIntoStore)(this.store, sessionStateSnapshot, {
|
|
154
|
+
forceOverwrite: true
|
|
155
|
+
});
|
|
154
156
|
}
|
|
155
157
|
}
|
|
156
158
|
this.channel.onmessage = ({ data: data2 }) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/utils/sync/TLLocalSyncClient.ts"],
|
|
4
|
-
"sourcesContent": ["import { Signal, transact } from '@tldraw/state'\nimport { RecordsDiff, SerializedSchema, UnknownRecord, squashRecordDiffs } from '@tldraw/store'\nimport { TLStore } from '@tldraw/tlschema'\nimport { assert } from '@tldraw/utils'\nimport {\n\tTAB_ID,\n\tTLSessionStateSnapshot,\n\tcreateSessionStateSnapshotSignal,\n\textractSessionStateFromLegacySnapshot,\n\tloadSessionStateSnapshotIntoStore,\n} from '../../config/TLSessionStateSnapshot'\nimport { LocalIndexedDb } from './LocalIndexedDb'\nimport { showCantReadFromIndexDbAlert, showCantWriteToIndexDbAlert } from './alerts'\n\n/** How should we debounce persists? */\nconst PERSIST_THROTTLE_MS = 350\n/** If we're in an error state, how long should we wait before retrying a write? */\nconst PERSIST_RETRY_THROTTLE_MS = 10_000\n\nconst UPDATE_INSTANCE_STATE = Symbol('UPDATE_INSTANCE_STATE')\n\n/**\n * IMPORTANT!!!\n *\n * This is just a quick-n-dirty temporary solution that will be replaced with the remote sync client\n * once it has the db integrated\n */\n\ninterface SyncMessage {\n\ttype: 'diff'\n\tstoreId: string\n\tchanges: RecordsDiff<UnknownRecord>\n\tschema: SerializedSchema\n}\n\n// Sent by new clients when they connect\n// If another client is on the channel with a newer schema version\n// It will\ninterface AnnounceMessage {\n\ttype: 'announce'\n\tschema: SerializedSchema\n}\n\ntype Message = SyncMessage | AnnounceMessage\n\ntype UnpackPromise<T> = T extends Promise<infer U> ? U : T\n\nconst msg = (msg: Message) => msg\n\n/** @internal */\nexport class BroadcastChannelMock {\n\tonmessage?: (e: MessageEvent) => void\n\tconstructor(_name: string) {\n\t\t// noop\n\t}\n\tpostMessage(_msg: Message) {\n\t\t// noop\n\t}\n\tclose() {\n\t\t// noop\n\t}\n}\n\nconst BC = typeof BroadcastChannel === 'undefined' ? BroadcastChannelMock : BroadcastChannel\n\n/** @internal */\nexport class TLLocalSyncClient {\n\tprivate disposables = new Set<() => void>()\n\tprivate diffQueue: Array<RecordsDiff<UnknownRecord> | typeof UPDATE_INSTANCE_STATE> = []\n\tprivate didDispose = false\n\tprivate shouldDoFullDBWrite = true\n\tprivate isReloading = false\n\treadonly persistenceKey: string\n\treadonly sessionId: string\n\treadonly serializedSchema: SerializedSchema\n\tprivate isDebugging = false\n\tprivate readonly documentTypes: ReadonlySet<string>\n\tprivate readonly $sessionStateSnapshot: Signal<TLSessionStateSnapshot | null>\n\t/** @internal */\n\treadonly db: LocalIndexedDb\n\n\tinitTime = Date.now()\n\tprivate debug(...args: any[]) {\n\t\tif (this.isDebugging) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.debug(...args)\n\t\t}\n\t}\n\tconstructor(\n\t\tpublic readonly store: TLStore,\n\t\t{\n\t\t\tpersistenceKey,\n\t\t\tsessionId = TAB_ID,\n\t\t\tonLoad,\n\t\t\tonLoadError,\n\t\t}: {\n\t\t\tpersistenceKey: string\n\t\t\tsessionId?: string\n\t\t\tonLoad(self: TLLocalSyncClient): void\n\t\t\tonLoadError(error: Error): void\n\t\t},\n\t\tpublic readonly channel = new BC(`tldraw-tab-sync-${persistenceKey}`)\n\t) {\n\t\tif (typeof window !== 'undefined') {\n\t\t\t;(window as any).tlsync = this\n\t\t}\n\t\tthis.persistenceKey = persistenceKey\n\t\tthis.sessionId = sessionId\n\t\tthis.db = new LocalIndexedDb(persistenceKey)\n\t\tthis.disposables.add(() => this.db.close())\n\n\t\tthis.serializedSchema = this.store.schema.serialize()\n\t\tthis.$sessionStateSnapshot = createSessionStateSnapshotSignal(this.store)\n\n\t\tthis.disposables.add(\n\t\t\t// Set up a subscription to changes from the store: When\n\t\t\t// the store changes (and if the change was made by the user)\n\t\t\t// then immediately send the diff to other tabs via postMessage\n\t\t\t// and schedule a persist.\n\t\t\tstore.listen(\n\t\t\t\t({ changes }) => {\n\t\t\t\t\tthis.diffQueue.push(changes)\n\t\t\t\t\tthis.channel.postMessage(\n\t\t\t\t\t\tmsg({\n\t\t\t\t\t\t\ttype: 'diff',\n\t\t\t\t\t\t\tstoreId: this.store.id,\n\t\t\t\t\t\t\tchanges,\n\t\t\t\t\t\t\tschema: this.serializedSchema,\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ source: 'user', scope: 'document' }\n\t\t\t)\n\t\t)\n\t\tthis.disposables.add(\n\t\t\tstore.listen(\n\t\t\t\t() => {\n\t\t\t\t\tthis.diffQueue.push(UPDATE_INSTANCE_STATE)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ scope: 'session' }\n\t\t\t)\n\t\t)\n\n\t\tthis.connect(onLoad, onLoadError)\n\n\t\tthis.documentTypes = new Set(\n\t\t\tObject.values(this.store.schema.types)\n\t\t\t\t.filter((t) => t.scope === 'document')\n\t\t\t\t.map((t) => t.typeName)\n\t\t)\n\t}\n\n\tprivate async connect(onLoad: (client: this) => void, onLoadError: (error: Error) => void) {\n\t\tthis.debug('connecting')\n\t\tlet data: UnpackPromise<ReturnType<LocalIndexedDb['load']>> | undefined\n\n\t\ttry {\n\t\t\tdata = await this.db.load({ sessionId: this.sessionId })\n\t\t} catch (error: any) {\n\t\t\tonLoadError(error)\n\t\t\tshowCantReadFromIndexDbAlert()\n\t\t\treturn\n\t\t}\n\n\t\tthis.debug('loaded data from store', data, 'didDispose', this.didDispose)\n\t\tif (this.didDispose) return\n\n\t\ttry {\n\t\t\tif (data) {\n\t\t\t\tconst documentSnapshot = Object.fromEntries(data.records.map((r) => [r.id, r]))\n\t\t\t\tconst sessionStateSnapshot =\n\t\t\t\t\tdata.sessionStateSnapshot ?? extractSessionStateFromLegacySnapshot(documentSnapshot)\n\t\t\t\tconst migrationResult = this.store.schema.migrateStoreSnapshot({\n\t\t\t\t\tstore: documentSnapshot,\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\tschema: data.schema ?? this.store.schema.serializeEarliestVersion(),\n\t\t\t\t})\n\n\t\t\t\tif (migrationResult.type === 'error') {\n\t\t\t\t\tconsole.error('failed to migrate store', migrationResult)\n\t\t\t\t\tonLoadError(new Error(`Failed to migrate store: ${migrationResult.reason}`))\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst records = Object.values(migrationResult.value).filter((r) =>\n\t\t\t\t\tthis.documentTypes.has(r.typeName)\n\t\t\t\t)\n\t\t\t\tif (records.length > 0) {\n\t\t\t\t\t// 3. Merge the changes into the REAL STORE\n\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t// Calling put will validate the records!\n\t\t\t\t\t\tthis.store.put(records, 'initialize')\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tif (sessionStateSnapshot) {\n\t\t\t\t\tloadSessionStateSnapshotIntoStore(this.store, sessionStateSnapshot)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.channel.onmessage = ({ data }) => {\n\t\t\t\tthis.debug('got message', data)\n\t\t\t\tconst msg = data as Message\n\t\t\t\t// if their schema is earlier than ours, we need to tell them so they can refresh\n\t\t\t\t// if their schema is later than ours, we need to refresh\n\t\t\t\tconst res = this.store.schema.getMigrationsSince(msg.schema)\n\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\t// we are older, refresh\n\t\t\t\t\t// but add a safety check to make sure we don't get in an infinite loop\n\t\t\t\t\tconst timeSinceInit = Date.now() - this.initTime\n\t\t\t\t\tif (timeSinceInit < 5000) {\n\t\t\t\t\t\t// This tab was just reloaded, but is out of date compared to other tabs.\n\t\t\t\t\t\t// Not expecting this to ever happen. It should only happen if we roll back a release that incremented\n\t\t\t\t\t\t// the schema version (which we should never do)\n\t\t\t\t\t\t// Or maybe during development if you have multiple local tabs open running the app on prod mode and you\n\t\t\t\t\t\t// check out an older commit. Dev server should be fine.\n\t\t\t\t\t\tonLoadError(new Error('Schema mismatch, please close other tabs and reload the page'))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tthis.debug('reloading')\n\t\t\t\t\tthis.isReloading = true\n\t\t\t\t\twindow?.location?.reload?.()\n\t\t\t\t\treturn\n\t\t\t\t} else if (res.value.length > 0) {\n\t\t\t\t\t// they are older, tell them to refresh and not write any more data\n\t\t\t\t\tthis.debug('telling them to reload')\n\t\t\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\t\t\t// schedule a full db write in case they wrote data anyway\n\t\t\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\t\t\tthis.persistIfNeeded()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// otherwise, all good, same version :)\n\t\t\t\tif (msg.type === 'diff') {\n\t\t\t\t\tthis.debug('applying diff')\n\t\t\t\t\ttransact(() => {\n\t\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t\tthis.store.applyDiff(msg.changes as any)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\tthis.disposables.add(() => {\n\t\t\t\tthis.channel.close()\n\t\t\t})\n\t\t\tonLoad(this)\n\t\t} catch (e: any) {\n\t\t\tthis.debug('error loading data from store', e)\n\t\t\tif (this.didDispose) return\n\t\t\tonLoadError(e)\n\t\t\treturn\n\t\t}\n\t}\n\n\tclose() {\n\t\tthis.debug('closing')\n\t\tthis.didDispose = true\n\t\tthis.disposables.forEach((d) => d())\n\t}\n\n\tprivate isPersisting = false\n\tprivate didLastWriteError = false\n\t// eslint-disable-next-line no-restricted-globals\n\tprivate scheduledPersistTimeout: ReturnType<typeof setTimeout> | null = null\n\n\t/**\n\t * Schedule a persist. Persists don't happen immediately: they are throttled to avoid writing too\n\t * often, and will retry if failed.\n\t *\n\t * @internal\n\t */\n\tprivate schedulePersist() {\n\t\tthis.debug('schedulePersist', this.scheduledPersistTimeout)\n\t\tif (this.scheduledPersistTimeout) return\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tthis.scheduledPersistTimeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.scheduledPersistTimeout = null\n\t\t\t\tthis.persistIfNeeded()\n\t\t\t},\n\t\t\tthis.didLastWriteError ? PERSIST_RETRY_THROTTLE_MS : PERSIST_THROTTLE_MS\n\t\t)\n\t}\n\n\t/**\n\t * Persist to IndexedDB only under certain circumstances:\n\t *\n\t * - If we're not already persisting\n\t * - If we're not reloading the page\n\t * - And we have something to persist (a full db write scheduled or changes in the diff queue)\n\t *\n\t * @internal\n\t */\n\tprivate persistIfNeeded() {\n\t\tthis.debug('persistIfNeeded', {\n\t\t\tisPersisting: this.isPersisting,\n\t\t\tisReloading: this.isReloading,\n\t\t\tshouldDoFullDBWrite: this.shouldDoFullDBWrite,\n\t\t\tdiffQueueLength: this.diffQueue.length,\n\t\t\tstoreIsPossiblyCorrupt: this.store.isPossiblyCorrupted(),\n\t\t})\n\n\t\t// if we've scheduled a persist for the future, that's no longer needed\n\t\tif (this.scheduledPersistTimeout) {\n\t\t\tclearTimeout(this.scheduledPersistTimeout)\n\t\t\tthis.scheduledPersistTimeout = null\n\t\t}\n\n\t\t// if a persist is already in progress, we don't need to do anything -\n\t\t// if there are still outstanding changes once it's finished, it'll\n\t\t// schedule another persist\n\t\tif (this.isPersisting) return\n\n\t\t// if we're reloading the page, it's because there's a newer client\n\t\t// present so lets not overwrite their changes\n\t\tif (this.isReloading) return\n\n\t\t// if the store is possibly corrupted, we don't want to persist\n\t\tif (this.store.isPossiblyCorrupted()) return\n\n\t\t// if we're scheduled for a full write or if we have changes outstanding, let's persist them!\n\t\tif (this.shouldDoFullDBWrite || this.diffQueue.length > 0) {\n\t\t\tthis.doPersist()\n\t\t}\n\t}\n\n\t/**\n\t * Actually persist to IndexedDB. If the write fails, then we'll retry with a full db write after\n\t * a short delay.\n\t */\n\tprivate async doPersist() {\n\t\tassert(!this.isPersisting, 'persist already in progress')\n\t\tif (this.didDispose) return\n\t\tthis.isPersisting = true\n\n\t\tthis.debug('doPersist start')\n\n\t\t// instantly empty the diff queue, but keep our own copy of it. this way\n\t\t// diffs that come in during the persist will still get tracked\n\t\tconst diffQueue = this.diffQueue\n\t\tthis.diffQueue = []\n\n\t\ttry {\n\t\t\tif (this.shouldDoFullDBWrite) {\n\t\t\t\tthis.shouldDoFullDBWrite = false\n\t\t\t\tawait this.db.storeSnapshot({\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsnapshot: this.store.serialize(),\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tconst diffs = squashRecordDiffs(\n\t\t\t\t\tdiffQueue.filter((d): d is RecordsDiff<UnknownRecord> => d !== UPDATE_INSTANCE_STATE)\n\t\t\t\t)\n\t\t\t\tawait this.db.storeChanges({\n\t\t\t\t\tchanges: diffs,\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t}\n\t\t\tthis.didLastWriteError = false\n\t\t} catch (e) {\n\t\t\t// set this.shouldDoFullDBWrite because we clear the diffQueue no matter what,\n\t\t\t// so if this is just a temporary error, we will still persist all changes\n\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\tthis.didLastWriteError = true\n\t\t\tconsole.error('failed to store changes in indexed db', e)\n\n\t\t\tshowCantWriteToIndexDbAlert()\n\t\t\tif (typeof window !== 'undefined') {\n\t\t\t\t// adios\n\t\t\t\twindow.location.reload()\n\t\t\t}\n\t\t}\n\n\t\tthis.isPersisting = false\n\t\tthis.debug('doPersist end')\n\n\t\t// changes might have come in between when we started the persist and\n\t\t// now. we request another persist so any new changes can get written\n\t\tthis.schedulePersist()\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { Signal, transact } from '@tldraw/state'\nimport { RecordsDiff, SerializedSchema, UnknownRecord, squashRecordDiffs } from '@tldraw/store'\nimport { TLStore } from '@tldraw/tlschema'\nimport { assert } from '@tldraw/utils'\nimport {\n\tTAB_ID,\n\tTLSessionStateSnapshot,\n\tcreateSessionStateSnapshotSignal,\n\textractSessionStateFromLegacySnapshot,\n\tloadSessionStateSnapshotIntoStore,\n} from '../../config/TLSessionStateSnapshot'\nimport { LocalIndexedDb } from './LocalIndexedDb'\nimport { showCantReadFromIndexDbAlert, showCantWriteToIndexDbAlert } from './alerts'\n\n/** How should we debounce persists? */\nconst PERSIST_THROTTLE_MS = 350\n/** If we're in an error state, how long should we wait before retrying a write? */\nconst PERSIST_RETRY_THROTTLE_MS = 10_000\n\nconst UPDATE_INSTANCE_STATE = Symbol('UPDATE_INSTANCE_STATE')\n\n/**\n * IMPORTANT!!!\n *\n * This is just a quick-n-dirty temporary solution that will be replaced with the remote sync client\n * once it has the db integrated\n */\n\ninterface SyncMessage {\n\ttype: 'diff'\n\tstoreId: string\n\tchanges: RecordsDiff<UnknownRecord>\n\tschema: SerializedSchema\n}\n\n// Sent by new clients when they connect\n// If another client is on the channel with a newer schema version\n// It will\ninterface AnnounceMessage {\n\ttype: 'announce'\n\tschema: SerializedSchema\n}\n\ntype Message = SyncMessage | AnnounceMessage\n\ntype UnpackPromise<T> = T extends Promise<infer U> ? U : T\n\nconst msg = (msg: Message) => msg\n\n/** @internal */\nexport class BroadcastChannelMock {\n\tonmessage?: (e: MessageEvent) => void\n\tconstructor(_name: string) {\n\t\t// noop\n\t}\n\tpostMessage(_msg: Message) {\n\t\t// noop\n\t}\n\tclose() {\n\t\t// noop\n\t}\n}\n\nconst BC = typeof BroadcastChannel === 'undefined' ? BroadcastChannelMock : BroadcastChannel\n\n/** @internal */\nexport class TLLocalSyncClient {\n\tprivate disposables = new Set<() => void>()\n\tprivate diffQueue: Array<RecordsDiff<UnknownRecord> | typeof UPDATE_INSTANCE_STATE> = []\n\tprivate didDispose = false\n\tprivate shouldDoFullDBWrite = true\n\tprivate isReloading = false\n\treadonly persistenceKey: string\n\treadonly sessionId: string\n\treadonly serializedSchema: SerializedSchema\n\tprivate isDebugging = false\n\tprivate readonly documentTypes: ReadonlySet<string>\n\tprivate readonly $sessionStateSnapshot: Signal<TLSessionStateSnapshot | null>\n\t/** @internal */\n\treadonly db: LocalIndexedDb\n\n\tinitTime = Date.now()\n\tprivate debug(...args: any[]) {\n\t\tif (this.isDebugging) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.debug(...args)\n\t\t}\n\t}\n\tconstructor(\n\t\tpublic readonly store: TLStore,\n\t\t{\n\t\t\tpersistenceKey,\n\t\t\tsessionId = TAB_ID,\n\t\t\tonLoad,\n\t\t\tonLoadError,\n\t\t}: {\n\t\t\tpersistenceKey: string\n\t\t\tsessionId?: string\n\t\t\tonLoad(self: TLLocalSyncClient): void\n\t\t\tonLoadError(error: Error): void\n\t\t},\n\t\tpublic readonly channel = new BC(`tldraw-tab-sync-${persistenceKey}`)\n\t) {\n\t\tif (typeof window !== 'undefined') {\n\t\t\t;(window as any).tlsync = this\n\t\t}\n\t\tthis.persistenceKey = persistenceKey\n\t\tthis.sessionId = sessionId\n\t\tthis.db = new LocalIndexedDb(persistenceKey)\n\t\tthis.disposables.add(() => this.db.close())\n\n\t\tthis.serializedSchema = this.store.schema.serialize()\n\t\tthis.$sessionStateSnapshot = createSessionStateSnapshotSignal(this.store)\n\n\t\tthis.disposables.add(\n\t\t\t// Set up a subscription to changes from the store: When\n\t\t\t// the store changes (and if the change was made by the user)\n\t\t\t// then immediately send the diff to other tabs via postMessage\n\t\t\t// and schedule a persist.\n\t\t\tstore.listen(\n\t\t\t\t({ changes }) => {\n\t\t\t\t\tthis.diffQueue.push(changes)\n\t\t\t\t\tthis.channel.postMessage(\n\t\t\t\t\t\tmsg({\n\t\t\t\t\t\t\ttype: 'diff',\n\t\t\t\t\t\t\tstoreId: this.store.id,\n\t\t\t\t\t\t\tchanges,\n\t\t\t\t\t\t\tschema: this.serializedSchema,\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ source: 'user', scope: 'document' }\n\t\t\t)\n\t\t)\n\t\tthis.disposables.add(\n\t\t\tstore.listen(\n\t\t\t\t() => {\n\t\t\t\t\tthis.diffQueue.push(UPDATE_INSTANCE_STATE)\n\t\t\t\t\tthis.schedulePersist()\n\t\t\t\t},\n\t\t\t\t{ scope: 'session' }\n\t\t\t)\n\t\t)\n\n\t\tthis.connect(onLoad, onLoadError)\n\n\t\tthis.documentTypes = new Set(\n\t\t\tObject.values(this.store.schema.types)\n\t\t\t\t.filter((t) => t.scope === 'document')\n\t\t\t\t.map((t) => t.typeName)\n\t\t)\n\t}\n\n\tprivate async connect(onLoad: (client: this) => void, onLoadError: (error: Error) => void) {\n\t\tthis.debug('connecting')\n\t\tlet data: UnpackPromise<ReturnType<LocalIndexedDb['load']>> | undefined\n\n\t\ttry {\n\t\t\tdata = await this.db.load({ sessionId: this.sessionId })\n\t\t} catch (error: any) {\n\t\t\tonLoadError(error)\n\t\t\tshowCantReadFromIndexDbAlert()\n\t\t\treturn\n\t\t}\n\n\t\tthis.debug('loaded data from store', data, 'didDispose', this.didDispose)\n\t\tif (this.didDispose) return\n\n\t\ttry {\n\t\t\tif (data) {\n\t\t\t\tconst documentSnapshot = Object.fromEntries(data.records.map((r) => [r.id, r]))\n\t\t\t\tconst sessionStateSnapshot =\n\t\t\t\t\tdata.sessionStateSnapshot ?? extractSessionStateFromLegacySnapshot(documentSnapshot)\n\t\t\t\tconst migrationResult = this.store.schema.migrateStoreSnapshot({\n\t\t\t\t\tstore: documentSnapshot,\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\tschema: data.schema ?? this.store.schema.serializeEarliestVersion(),\n\t\t\t\t})\n\n\t\t\t\tif (migrationResult.type === 'error') {\n\t\t\t\t\tconsole.error('failed to migrate store', migrationResult)\n\t\t\t\t\tonLoadError(new Error(`Failed to migrate store: ${migrationResult.reason}`))\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst records = Object.values(migrationResult.value).filter((r) =>\n\t\t\t\t\tthis.documentTypes.has(r.typeName)\n\t\t\t\t)\n\t\t\t\tif (records.length > 0) {\n\t\t\t\t\t// 3. Merge the changes into the REAL STORE\n\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t// Calling put will validate the records!\n\t\t\t\t\t\tthis.store.put(records, 'initialize')\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tif (sessionStateSnapshot) {\n\t\t\t\t\tloadSessionStateSnapshotIntoStore(this.store, sessionStateSnapshot, {\n\t\t\t\t\t\tforceOverwrite: true,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.channel.onmessage = ({ data }) => {\n\t\t\t\tthis.debug('got message', data)\n\t\t\t\tconst msg = data as Message\n\t\t\t\t// if their schema is earlier than ours, we need to tell them so they can refresh\n\t\t\t\t// if their schema is later than ours, we need to refresh\n\t\t\t\tconst res = this.store.schema.getMigrationsSince(msg.schema)\n\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\t// we are older, refresh\n\t\t\t\t\t// but add a safety check to make sure we don't get in an infinite loop\n\t\t\t\t\tconst timeSinceInit = Date.now() - this.initTime\n\t\t\t\t\tif (timeSinceInit < 5000) {\n\t\t\t\t\t\t// This tab was just reloaded, but is out of date compared to other tabs.\n\t\t\t\t\t\t// Not expecting this to ever happen. It should only happen if we roll back a release that incremented\n\t\t\t\t\t\t// the schema version (which we should never do)\n\t\t\t\t\t\t// Or maybe during development if you have multiple local tabs open running the app on prod mode and you\n\t\t\t\t\t\t// check out an older commit. Dev server should be fine.\n\t\t\t\t\t\tonLoadError(new Error('Schema mismatch, please close other tabs and reload the page'))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tthis.debug('reloading')\n\t\t\t\t\tthis.isReloading = true\n\t\t\t\t\twindow?.location?.reload?.()\n\t\t\t\t\treturn\n\t\t\t\t} else if (res.value.length > 0) {\n\t\t\t\t\t// they are older, tell them to refresh and not write any more data\n\t\t\t\t\tthis.debug('telling them to reload')\n\t\t\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\t\t\t// schedule a full db write in case they wrote data anyway\n\t\t\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\t\t\tthis.persistIfNeeded()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// otherwise, all good, same version :)\n\t\t\t\tif (msg.type === 'diff') {\n\t\t\t\t\tthis.debug('applying diff')\n\t\t\t\t\ttransact(() => {\n\t\t\t\t\t\tthis.store.mergeRemoteChanges(() => {\n\t\t\t\t\t\t\tthis.store.applyDiff(msg.changes as any)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.channel.postMessage({ type: 'announce', schema: this.serializedSchema })\n\t\t\tthis.disposables.add(() => {\n\t\t\t\tthis.channel.close()\n\t\t\t})\n\t\t\tonLoad(this)\n\t\t} catch (e: any) {\n\t\t\tthis.debug('error loading data from store', e)\n\t\t\tif (this.didDispose) return\n\t\t\tonLoadError(e)\n\t\t\treturn\n\t\t}\n\t}\n\n\tclose() {\n\t\tthis.debug('closing')\n\t\tthis.didDispose = true\n\t\tthis.disposables.forEach((d) => d())\n\t}\n\n\tprivate isPersisting = false\n\tprivate didLastWriteError = false\n\t// eslint-disable-next-line no-restricted-globals\n\tprivate scheduledPersistTimeout: ReturnType<typeof setTimeout> | null = null\n\n\t/**\n\t * Schedule a persist. Persists don't happen immediately: they are throttled to avoid writing too\n\t * often, and will retry if failed.\n\t *\n\t * @internal\n\t */\n\tprivate schedulePersist() {\n\t\tthis.debug('schedulePersist', this.scheduledPersistTimeout)\n\t\tif (this.scheduledPersistTimeout) return\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tthis.scheduledPersistTimeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.scheduledPersistTimeout = null\n\t\t\t\tthis.persistIfNeeded()\n\t\t\t},\n\t\t\tthis.didLastWriteError ? PERSIST_RETRY_THROTTLE_MS : PERSIST_THROTTLE_MS\n\t\t)\n\t}\n\n\t/**\n\t * Persist to IndexedDB only under certain circumstances:\n\t *\n\t * - If we're not already persisting\n\t * - If we're not reloading the page\n\t * - And we have something to persist (a full db write scheduled or changes in the diff queue)\n\t *\n\t * @internal\n\t */\n\tprivate persistIfNeeded() {\n\t\tthis.debug('persistIfNeeded', {\n\t\t\tisPersisting: this.isPersisting,\n\t\t\tisReloading: this.isReloading,\n\t\t\tshouldDoFullDBWrite: this.shouldDoFullDBWrite,\n\t\t\tdiffQueueLength: this.diffQueue.length,\n\t\t\tstoreIsPossiblyCorrupt: this.store.isPossiblyCorrupted(),\n\t\t})\n\n\t\t// if we've scheduled a persist for the future, that's no longer needed\n\t\tif (this.scheduledPersistTimeout) {\n\t\t\tclearTimeout(this.scheduledPersistTimeout)\n\t\t\tthis.scheduledPersistTimeout = null\n\t\t}\n\n\t\t// if a persist is already in progress, we don't need to do anything -\n\t\t// if there are still outstanding changes once it's finished, it'll\n\t\t// schedule another persist\n\t\tif (this.isPersisting) return\n\n\t\t// if we're reloading the page, it's because there's a newer client\n\t\t// present so lets not overwrite their changes\n\t\tif (this.isReloading) return\n\n\t\t// if the store is possibly corrupted, we don't want to persist\n\t\tif (this.store.isPossiblyCorrupted()) return\n\n\t\t// if we're scheduled for a full write or if we have changes outstanding, let's persist them!\n\t\tif (this.shouldDoFullDBWrite || this.diffQueue.length > 0) {\n\t\t\tthis.doPersist()\n\t\t}\n\t}\n\n\t/**\n\t * Actually persist to IndexedDB. If the write fails, then we'll retry with a full db write after\n\t * a short delay.\n\t */\n\tprivate async doPersist() {\n\t\tassert(!this.isPersisting, 'persist already in progress')\n\t\tif (this.didDispose) return\n\t\tthis.isPersisting = true\n\n\t\tthis.debug('doPersist start')\n\n\t\t// instantly empty the diff queue, but keep our own copy of it. this way\n\t\t// diffs that come in during the persist will still get tracked\n\t\tconst diffQueue = this.diffQueue\n\t\tthis.diffQueue = []\n\n\t\ttry {\n\t\t\tif (this.shouldDoFullDBWrite) {\n\t\t\t\tthis.shouldDoFullDBWrite = false\n\t\t\t\tawait this.db.storeSnapshot({\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsnapshot: this.store.serialize(),\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tconst diffs = squashRecordDiffs(\n\t\t\t\t\tdiffQueue.filter((d): d is RecordsDiff<UnknownRecord> => d !== UPDATE_INSTANCE_STATE)\n\t\t\t\t)\n\t\t\t\tawait this.db.storeChanges({\n\t\t\t\t\tchanges: diffs,\n\t\t\t\t\tschema: this.store.schema,\n\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\tsessionStateSnapshot: this.$sessionStateSnapshot.get(),\n\t\t\t\t})\n\t\t\t}\n\t\t\tthis.didLastWriteError = false\n\t\t} catch (e) {\n\t\t\t// set this.shouldDoFullDBWrite because we clear the diffQueue no matter what,\n\t\t\t// so if this is just a temporary error, we will still persist all changes\n\t\t\tthis.shouldDoFullDBWrite = true\n\t\t\tthis.didLastWriteError = true\n\t\t\tconsole.error('failed to store changes in indexed db', e)\n\n\t\t\tshowCantWriteToIndexDbAlert()\n\t\t\tif (typeof window !== 'undefined') {\n\t\t\t\t// adios\n\t\t\t\twindow.location.reload()\n\t\t\t}\n\t\t}\n\n\t\tthis.isPersisting = false\n\t\tthis.debug('doPersist end')\n\n\t\t// changes might have come in between when we started the persist and\n\t\t// now. we request another persist so any new changes can get written\n\t\tthis.schedulePersist()\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["msg", "data"]
|
|
7
7
|
}
|
package/dist-cjs/version.js
CHANGED
|
@@ -22,10 +22,10 @@ __export(version_exports, {
|
|
|
22
22
|
version: () => version
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(version_exports);
|
|
25
|
-
const version = "3.8.0-canary.
|
|
25
|
+
const version = "3.8.0-canary.c5d6281c3fdd";
|
|
26
26
|
const publishDates = {
|
|
27
27
|
major: "2024-09-13T14:36:29.063Z",
|
|
28
|
-
minor: "2025-01-
|
|
29
|
-
patch: "2025-01-
|
|
28
|
+
minor: "2025-01-28T16:50:53.939Z",
|
|
29
|
+
patch: "2025-01-28T16:50:53.939Z"
|
|
30
30
|
};
|
|
31
31
|
//# sourceMappingURL=version.js.map
|
package/dist-cjs/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.8.0-canary.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.8.0-canary.c5d6281c3fdd'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-01-28T16:50:53.939Z',\n\tpatch: '2025-01-28T16:50:53.939Z',\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/index.d.mts
CHANGED
|
@@ -68,6 +68,7 @@ import { TLPropsMigrations } from '@tldraw/tlschema';
|
|
|
68
68
|
import { TLRecord } from '@tldraw/tlschema';
|
|
69
69
|
import { TLScribble } from '@tldraw/tlschema';
|
|
70
70
|
import { TLShape } from '@tldraw/tlschema';
|
|
71
|
+
import { TLShapeCrop } from '@tldraw/tlschema';
|
|
71
72
|
import { TLShapeId } from '@tldraw/tlschema';
|
|
72
73
|
import { TLShapePartial } from '@tldraw/tlschema';
|
|
73
74
|
import { TLStore } from '@tldraw/tlschema';
|
|
@@ -536,6 +537,8 @@ export declare type BoxLike = Box | BoxModel;
|
|
|
536
537
|
*/
|
|
537
538
|
export declare function canonicalizeRotation(a: number): number;
|
|
538
539
|
|
|
540
|
+
/* Excluded from this release type: CanvasMaxSize */
|
|
541
|
+
|
|
539
542
|
/**
|
|
540
543
|
* Get the center of a circle from three points.
|
|
541
544
|
*
|
|
@@ -615,6 +618,8 @@ export declare function clamp(n: number, min: number, max: number): number;
|
|
|
615
618
|
*/
|
|
616
619
|
export declare function clampRadians(r: number): number;
|
|
617
620
|
|
|
621
|
+
/* Excluded from this release type: clampToBrowserMaxCanvasSize */
|
|
622
|
+
|
|
618
623
|
/** @public */
|
|
619
624
|
export declare class ClickManager {
|
|
620
625
|
editor: Editor;
|
|
@@ -880,6 +885,7 @@ export declare const defaultTldrawOptions: {
|
|
|
880
885
|
readonly maxPointsPerDrawShape: 500;
|
|
881
886
|
readonly maxShapesPerPage: 4000;
|
|
882
887
|
readonly multiClickDurationMs: 200;
|
|
888
|
+
readonly noteShapeResizeMode: "none";
|
|
883
889
|
readonly temporaryAssetPreviewLifetimeMs: 180000;
|
|
884
890
|
readonly textShadowLod: 0.35;
|
|
885
891
|
};
|
|
@@ -1113,6 +1119,14 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
1113
1119
|
getShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): ShapeUtil<S>;
|
|
1114
1120
|
getShapeUtil<S extends TLUnknownShape>(type: S['type']): ShapeUtil<S>;
|
|
1115
1121
|
getShapeUtil<T extends ShapeUtil>(type: T extends ShapeUtil<infer R> ? R['type'] : string): T;
|
|
1122
|
+
/**
|
|
1123
|
+
* Returns true if the editor has a shape util for the given shape / shape type.
|
|
1124
|
+
*
|
|
1125
|
+
* @param shape - A shape, shape partial, or shape type.
|
|
1126
|
+
*/
|
|
1127
|
+
hasShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): boolean;
|
|
1128
|
+
hasShapeUtil<S extends TLUnknownShape>(type: S['type']): boolean;
|
|
1129
|
+
hasShapeUtil<T extends ShapeUtil>(type: T extends ShapeUtil<infer R> ? R['type'] : string): boolean;
|
|
1116
1130
|
/**
|
|
1117
1131
|
* A map of shape utility classes (TLShapeUtils) by shape type.
|
|
1118
1132
|
*
|
|
@@ -2348,6 +2362,7 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
2348
2362
|
*/
|
|
2349
2363
|
getAsset<T extends TLAsset>(asset: T | T['id']): T | undefined;
|
|
2350
2364
|
resolveAssetUrl(assetId: null | TLAssetId, context: {
|
|
2365
|
+
dpr?: number;
|
|
2351
2366
|
screenScale?: number;
|
|
2352
2367
|
shouldResolveToOriginal?: boolean;
|
|
2353
2368
|
}): Promise<null | string>;
|
|
@@ -2355,7 +2370,10 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
2355
2370
|
* Upload an asset to the store's asset service, returning a URL that can be used to resolve the
|
|
2356
2371
|
* asset.
|
|
2357
2372
|
*/
|
|
2358
|
-
uploadAsset(asset: TLAsset, file: File, abortSignal?: AbortSignal): Promise<
|
|
2373
|
+
uploadAsset(asset: TLAsset, file: File, abortSignal?: AbortSignal): Promise<{
|
|
2374
|
+
meta?: JsonObject;
|
|
2375
|
+
src: string;
|
|
2376
|
+
}>;
|
|
2359
2377
|
private _getShapeGeometryCache;
|
|
2360
2378
|
/**
|
|
2361
2379
|
* Get the geometry of a shape.
|
|
@@ -3538,7 +3556,7 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
3538
3556
|
*
|
|
3539
3557
|
* @public
|
|
3540
3558
|
*/
|
|
3541
|
-
getSvgElement(shapes: TLShape[] | TLShapeId[], opts?:
|
|
3559
|
+
getSvgElement(shapes: TLShape[] | TLShapeId[], opts?: TLSvgExportOptions): Promise<{
|
|
3542
3560
|
height: number;
|
|
3543
3561
|
svg: SVGSVGElement;
|
|
3544
3562
|
width: number;
|
|
@@ -3553,13 +3571,27 @@ export declare class Editor extends EventEmitter<TLEventMap> {
|
|
|
3553
3571
|
*
|
|
3554
3572
|
* @public
|
|
3555
3573
|
*/
|
|
3556
|
-
getSvgString(shapes: TLShape[] | TLShapeId[], opts?:
|
|
3574
|
+
getSvgString(shapes: TLShape[] | TLShapeId[], opts?: TLSvgExportOptions): Promise<{
|
|
3557
3575
|
height: number;
|
|
3558
3576
|
svg: string;
|
|
3559
3577
|
width: number;
|
|
3560
3578
|
} | undefined>;
|
|
3561
3579
|
/** @deprecated Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead. */
|
|
3562
|
-
getSvg(shapes: TLShape[] | TLShapeId[], opts?:
|
|
3580
|
+
getSvg(shapes: TLShape[] | TLShapeId[], opts?: TLSvgExportOptions): Promise<SVGSVGElement | undefined>;
|
|
3581
|
+
/**
|
|
3582
|
+
* Get an exported image of the given shapes.
|
|
3583
|
+
*
|
|
3584
|
+
* @param shapes - The shapes (or shape ids) to export.
|
|
3585
|
+
* @param opts - Options for the export.
|
|
3586
|
+
*
|
|
3587
|
+
* @returns A blob of the image.
|
|
3588
|
+
* @public
|
|
3589
|
+
*/
|
|
3590
|
+
toImage(shapes: TLShape[] | TLShapeId[], opts?: TLImageExportOptions): Promise<{
|
|
3591
|
+
blob: Blob;
|
|
3592
|
+
height: number;
|
|
3593
|
+
width: number;
|
|
3594
|
+
}>;
|
|
3563
3595
|
/**
|
|
3564
3596
|
* The app's current input state.
|
|
3565
3597
|
*
|
|
@@ -4073,6 +4105,15 @@ export declare function getPolygonVertices(width: number, height: number, sides:
|
|
|
4073
4105
|
/** @public */
|
|
4074
4106
|
export declare function getSnapshot(store: TLStore): TLEditorSnapshot;
|
|
4075
4107
|
|
|
4108
|
+
/** @public */
|
|
4109
|
+
export declare function getSvgAsImage(svgString: string, options: {
|
|
4110
|
+
height: number;
|
|
4111
|
+
pixelRatio?: number;
|
|
4112
|
+
quality?: number;
|
|
4113
|
+
type: 'jpeg' | 'png' | 'webp';
|
|
4114
|
+
width: number;
|
|
4115
|
+
}): Promise<Blob | null>;
|
|
4116
|
+
|
|
4076
4117
|
/**
|
|
4077
4118
|
* Turn an array of points into a path of quadradic curves.
|
|
4078
4119
|
*
|
|
@@ -4652,6 +4693,23 @@ export declare interface ResizeBoxOptions {
|
|
|
4652
4693
|
maxHeight?: number;
|
|
4653
4694
|
}
|
|
4654
4695
|
|
|
4696
|
+
/**
|
|
4697
|
+
* Resize a shape that has a scale prop.
|
|
4698
|
+
*
|
|
4699
|
+
* @param shape - The shape to resize
|
|
4700
|
+
* @param info - The resize info
|
|
4701
|
+
*
|
|
4702
|
+
* @public */
|
|
4703
|
+
export declare function resizeScaled(shape: TLBaseShape<any, {
|
|
4704
|
+
scale: number;
|
|
4705
|
+
}>, { initialBounds, scaleX, scaleY, newPoint, handle }: TLResizeInfo<any>): {
|
|
4706
|
+
props: {
|
|
4707
|
+
scale: number;
|
|
4708
|
+
};
|
|
4709
|
+
x: number;
|
|
4710
|
+
y: number;
|
|
4711
|
+
};
|
|
4712
|
+
|
|
4655
4713
|
/** @public */
|
|
4656
4714
|
export declare const ROTATE_CORNER_TO_SELECTION_CORNER: {
|
|
4657
4715
|
readonly bottom_left_rotate: "bottom_left";
|
|
@@ -5023,6 +5081,15 @@ export declare abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknown
|
|
|
5023
5081
|
* @public
|
|
5024
5082
|
*/
|
|
5025
5083
|
onBeforeUpdate?(prev: Shape, next: Shape): Shape | void;
|
|
5084
|
+
/**
|
|
5085
|
+
* A callback called when a shape changes from a crop.
|
|
5086
|
+
*
|
|
5087
|
+
* @param shape - The shape at the start of the crop.
|
|
5088
|
+
* @param info - Info about the crop.
|
|
5089
|
+
* @returns A change to apply to the shape, or void.
|
|
5090
|
+
* @public
|
|
5091
|
+
*/
|
|
5092
|
+
onCrop?(shape: Shape, info: TLCropInfo<Shape>): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void;
|
|
5026
5093
|
/**
|
|
5027
5094
|
* A callback called when some other shapes are dragged over this one.
|
|
5028
5095
|
*
|
|
@@ -5404,10 +5471,27 @@ export declare interface SvgExportContext {
|
|
|
5404
5471
|
* method depending on your use-case.
|
|
5405
5472
|
*/
|
|
5406
5473
|
waitUntil(promise: Promise<void>): void;
|
|
5474
|
+
/**
|
|
5475
|
+
* Resolve an asset URL in the context of this export. Supply the asset ID and the width in
|
|
5476
|
+
* shape-pixels it'll be displayed at, and this will resolve the asset according to the export
|
|
5477
|
+
* options.
|
|
5478
|
+
*/
|
|
5479
|
+
resolveAssetUrl(assetId: TLAssetId, width: number): Promise<null | string>;
|
|
5407
5480
|
/**
|
|
5408
5481
|
* Whether the export should be in dark mode.
|
|
5409
5482
|
*/
|
|
5410
5483
|
readonly isDarkMode: boolean;
|
|
5484
|
+
/**
|
|
5485
|
+
* The scale of the export - how much CSS pixels will be scaled up/down by.
|
|
5486
|
+
*/
|
|
5487
|
+
readonly scale: number;
|
|
5488
|
+
/**
|
|
5489
|
+
* Use this value to optionally downscale images in the export. If we're exporting directly to
|
|
5490
|
+
* an SVG, this will usually be null, and you shouldn't downscale images. If the export is to a
|
|
5491
|
+
* raster format like PNG, this will be the number of raster pixels in the resulting bitmap per
|
|
5492
|
+
* CSS pixel in the resulting SVG.
|
|
5493
|
+
*/
|
|
5494
|
+
readonly pixelRatio: null | number;
|
|
5411
5495
|
}
|
|
5412
5496
|
|
|
5413
5497
|
/** @public */
|
|
@@ -5663,6 +5747,24 @@ export declare interface TLContent {
|
|
|
5663
5747
|
schema: SerializedSchema;
|
|
5664
5748
|
}
|
|
5665
5749
|
|
|
5750
|
+
/**
|
|
5751
|
+
* Info about a crop.
|
|
5752
|
+
* @param handle - The handle being dragged.
|
|
5753
|
+
* @param change - The distance the handle is moved.
|
|
5754
|
+
* @param initialShape - The shape at the start of the resize.
|
|
5755
|
+
* @public
|
|
5756
|
+
*/
|
|
5757
|
+
export declare interface TLCropInfo<T extends TLShape> {
|
|
5758
|
+
handle: SelectionHandle;
|
|
5759
|
+
change: Vec;
|
|
5760
|
+
crop: TLShapeCrop;
|
|
5761
|
+
uncroppedSize: {
|
|
5762
|
+
h: number;
|
|
5763
|
+
w: number;
|
|
5764
|
+
};
|
|
5765
|
+
initialShape: T;
|
|
5766
|
+
}
|
|
5767
|
+
|
|
5666
5768
|
/** @public */
|
|
5667
5769
|
export declare interface TLCursorProps {
|
|
5668
5770
|
className?: string;
|
|
@@ -5915,6 +6017,11 @@ export declare interface TldrawOptions {
|
|
|
5915
6017
|
readonly exportProvider: ComponentType<{
|
|
5916
6018
|
children: React.ReactNode;
|
|
5917
6019
|
}>;
|
|
6020
|
+
/**
|
|
6021
|
+
* How should the note shape resize? By default it does not resize (except automatically based on its text content),
|
|
6022
|
+
* but you can set it to be user-resizable using scale.
|
|
6023
|
+
*/
|
|
6024
|
+
readonly noteShapeResizeMode: 'none' | 'scale';
|
|
5918
6025
|
}
|
|
5919
6026
|
|
|
5920
6027
|
/** @public */
|
|
@@ -6103,6 +6210,9 @@ export declare type TLEventName = 'cancel' | 'complete' | 'interrupt' | 'tick' |
|
|
|
6103
6210
|
/** @public */
|
|
6104
6211
|
export declare type TLExitEventHandler = (info: any, to: string) => void;
|
|
6105
6212
|
|
|
6213
|
+
/** @public */
|
|
6214
|
+
export declare type TLExportType = 'jpeg' | 'png' | 'svg' | 'webp';
|
|
6215
|
+
|
|
6106
6216
|
/** @public */
|
|
6107
6217
|
export declare type TLExternalAssetContent = {
|
|
6108
6218
|
assetId?: TLAssetId;
|
|
@@ -6209,15 +6319,16 @@ export declare interface TLHistoryMark {
|
|
|
6209
6319
|
}
|
|
6210
6320
|
|
|
6211
6321
|
/** @public */
|
|
6212
|
-
export declare interface TLImageExportOptions {
|
|
6213
|
-
|
|
6214
|
-
|
|
6322
|
+
export declare interface TLImageExportOptions extends TLSvgExportOptions {
|
|
6323
|
+
/**
|
|
6324
|
+
* If the export is being converted to a lossy bitmap format (e.g. jpeg), this is the quality of
|
|
6325
|
+
* the export. This is a number between 0 and 1.
|
|
6326
|
+
*/
|
|
6215
6327
|
quality?: number;
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
preserveAspectRatio?: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
|
|
6328
|
+
/**
|
|
6329
|
+
* The format to export as. Defaults to 'png'.
|
|
6330
|
+
*/
|
|
6331
|
+
format?: TLExportType;
|
|
6221
6332
|
}
|
|
6222
6333
|
|
|
6223
6334
|
/** @public */
|
|
@@ -6721,6 +6832,50 @@ export declare type TLStoreWithStatus = {
|
|
|
6721
6832
|
readonly store: TLStore;
|
|
6722
6833
|
};
|
|
6723
6834
|
|
|
6835
|
+
/** @public */
|
|
6836
|
+
export declare interface TLSvgExportOptions {
|
|
6837
|
+
/**
|
|
6838
|
+
* The bounding box, in page coordinates, of the area being exported.
|
|
6839
|
+
*/
|
|
6840
|
+
bounds?: Box;
|
|
6841
|
+
/**
|
|
6842
|
+
* The logical scale of the export. This scales the resulting size of the SVG being generated.
|
|
6843
|
+
*/
|
|
6844
|
+
scale?: number;
|
|
6845
|
+
/**
|
|
6846
|
+
* When exporting an SVG, the expected pixel ratio of the export will be passed in to
|
|
6847
|
+
* {@link @tldraw/tlschema#TLAssetStore.resolve} as the `dpr` property, so that assets can be
|
|
6848
|
+
* downscaled to the appropriate resolution.
|
|
6849
|
+
*
|
|
6850
|
+
* When exporting to a bitmap image format, the size of the resulting image will be multiplied
|
|
6851
|
+
* by this number.
|
|
6852
|
+
*
|
|
6853
|
+
* For SVG exports, this defaults to undefined - which means we'll request original-quality
|
|
6854
|
+
* assets. For bitmap exports, this defaults to 2.
|
|
6855
|
+
*/
|
|
6856
|
+
pixelRatio?: number;
|
|
6857
|
+
/**
|
|
6858
|
+
* Should the background color be included in the export? If false, the generated image will be
|
|
6859
|
+
* transparent (if exporting to a format that supports transparency).
|
|
6860
|
+
*/
|
|
6861
|
+
background?: boolean;
|
|
6862
|
+
/**
|
|
6863
|
+
* How much padding to include around the bounds of exports? Defaults to 32px.
|
|
6864
|
+
*/
|
|
6865
|
+
padding?: number;
|
|
6866
|
+
/**
|
|
6867
|
+
* Should the export be rendered in dark mode (true) or light mode (false)? Defaults to the
|
|
6868
|
+
* current instance's dark mode setting.
|
|
6869
|
+
*/
|
|
6870
|
+
darkMode?: boolean;
|
|
6871
|
+
/**
|
|
6872
|
+
* The
|
|
6873
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio | `preserveAspectRatio` }
|
|
6874
|
+
* attribute of the SVG element.
|
|
6875
|
+
*/
|
|
6876
|
+
preserveAspectRatio?: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
|
|
6877
|
+
}
|
|
6878
|
+
|
|
6724
6879
|
/**
|
|
6725
6880
|
* @public
|
|
6726
6881
|
* @deprecated use {@link TLImageExportOptions} instead
|
package/dist-esm/index.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
useStateTracking,
|
|
24
24
|
useValue
|
|
25
25
|
} from "@tldraw/state-react";
|
|
26
|
+
import { resizeScaled } from "./lib/editor/shapes/shared/resizeScaled.mjs";
|
|
26
27
|
import { LocalIndexedDb, Table } from "./lib/utils/sync/LocalIndexedDb.mjs";
|
|
27
28
|
export * from "@tldraw/store";
|
|
28
29
|
export * from "@tldraw/tlschema";
|
|
@@ -142,6 +143,7 @@ import {
|
|
|
142
143
|
import {
|
|
143
144
|
EVENT_NAME_MAP
|
|
144
145
|
} from "./lib/editor/types/event-types.mjs";
|
|
146
|
+
import { getSvgAsImage } from "./lib/exports/getSvgAsImage.mjs";
|
|
145
147
|
import { tlenv } from "./lib/globals/environment.mjs";
|
|
146
148
|
import { tlmenus } from "./lib/globals/menus.mjs";
|
|
147
149
|
import { tltime } from "./lib/globals/time.mjs";
|
|
@@ -250,6 +252,7 @@ import {
|
|
|
250
252
|
SharedStyleMap
|
|
251
253
|
} from "./lib/utils/SharedStylesMap.mjs";
|
|
252
254
|
import { dataUrlToFile, getDefaultCdnBaseUrl } from "./lib/utils/assets.mjs";
|
|
255
|
+
import { clampToBrowserMaxCanvasSize } from "./lib/utils/browserCanvasMaxSize.mjs";
|
|
253
256
|
import {
|
|
254
257
|
debugFlags,
|
|
255
258
|
featureFlags
|
|
@@ -285,7 +288,7 @@ function debugEnableLicensing() {
|
|
|
285
288
|
}
|
|
286
289
|
registerTldrawLibraryVersion(
|
|
287
290
|
"@tldraw/editor",
|
|
288
|
-
"3.8.0-canary.
|
|
291
|
+
"3.8.0-canary.c5d6281c3fdd",
|
|
289
292
|
"esm"
|
|
290
293
|
);
|
|
291
294
|
export {
|
|
@@ -377,6 +380,7 @@ export {
|
|
|
377
380
|
centerOfCircleFromThreePoints,
|
|
378
381
|
clamp,
|
|
379
382
|
clampRadians,
|
|
383
|
+
clampToBrowserMaxCanvasSize,
|
|
380
384
|
clockwiseAngleDist,
|
|
381
385
|
computed,
|
|
382
386
|
coreShapes,
|
|
@@ -407,6 +411,7 @@ export {
|
|
|
407
411
|
getPolygonVertices,
|
|
408
412
|
getRotationSnapshot,
|
|
409
413
|
getSnapshot,
|
|
414
|
+
getSvgAsImage,
|
|
410
415
|
getSvgPathFromPoints,
|
|
411
416
|
getUserPreferences,
|
|
412
417
|
hardReset,
|
|
@@ -443,6 +448,7 @@ export {
|
|
|
443
448
|
refreshPage,
|
|
444
449
|
releasePointerCapture,
|
|
445
450
|
resizeBox,
|
|
451
|
+
resizeScaled,
|
|
446
452
|
rotateSelectionHandle,
|
|
447
453
|
runtime,
|
|
448
454
|
sanitizeId,
|