@fieldnotes/core 0.50.7 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +683 -675
- package/dist/index.cjs +244 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -4
- package/dist/index.d.ts +21 -4
- package/dist/index.js +244 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -447,15 +447,32 @@ declare class HistoryStack {
|
|
|
447
447
|
private notifyChange;
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
-
interface ExportImageOptions {
|
|
450
|
+
interface ExportImageOptions extends ExportResourceOptions {
|
|
451
451
|
scale?: number;
|
|
452
452
|
padding?: number;
|
|
453
453
|
background?: string;
|
|
454
454
|
filter?: (element: CanvasElement) => boolean;
|
|
455
455
|
}
|
|
456
|
+
type ExportAssetErrorReason = 'load' | 'timeout' | 'encode';
|
|
457
|
+
interface ExportAssetError {
|
|
458
|
+
elementId: string;
|
|
459
|
+
src: string;
|
|
460
|
+
reason: ExportAssetErrorReason;
|
|
461
|
+
cause?: unknown;
|
|
462
|
+
}
|
|
463
|
+
interface ExportResourceOptions {
|
|
464
|
+
/** Maximum wait for each image asset. Defaults to 10 seconds. */
|
|
465
|
+
imageTimeoutMs?: number;
|
|
466
|
+
/** Maximum width or height of any allocated export canvas. Defaults to 16,384. */
|
|
467
|
+
maxDimension?: number;
|
|
468
|
+
/** Maximum pixel count of any allocated export canvas. Defaults to 67,108,864. */
|
|
469
|
+
maxPixels?: number;
|
|
470
|
+
/** Called when an image cannot be loaded or embedded. The export continues. */
|
|
471
|
+
onAssetError?: (error: ExportAssetError) => void;
|
|
472
|
+
}
|
|
456
473
|
declare function exportImage(store: ElementStore, options?: ExportImageOptions, layerManager?: LayerManager): Promise<Blob | null>;
|
|
457
474
|
|
|
458
|
-
interface ExportSvgOptions {
|
|
475
|
+
interface ExportSvgOptions extends ExportResourceOptions {
|
|
459
476
|
padding?: number;
|
|
460
477
|
background?: string;
|
|
461
478
|
filter?: (el: CanvasElement) => boolean;
|
|
@@ -1123,6 +1140,6 @@ declare class LaserTool implements Tool {
|
|
|
1123
1140
|
private notifyOptionsChange;
|
|
1124
1141
|
}
|
|
1125
1142
|
|
|
1126
|
-
declare const VERSION = "0.
|
|
1143
|
+
declare const VERSION = "0.51.0";
|
|
1127
1144
|
|
|
1128
|
-
export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
|
1145
|
+
export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportAssetError, type ExportAssetErrorReason, type ExportImageOptions, type ExportResourceOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
package/dist/index.d.ts
CHANGED
|
@@ -447,15 +447,32 @@ declare class HistoryStack {
|
|
|
447
447
|
private notifyChange;
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
-
interface ExportImageOptions {
|
|
450
|
+
interface ExportImageOptions extends ExportResourceOptions {
|
|
451
451
|
scale?: number;
|
|
452
452
|
padding?: number;
|
|
453
453
|
background?: string;
|
|
454
454
|
filter?: (element: CanvasElement) => boolean;
|
|
455
455
|
}
|
|
456
|
+
type ExportAssetErrorReason = 'load' | 'timeout' | 'encode';
|
|
457
|
+
interface ExportAssetError {
|
|
458
|
+
elementId: string;
|
|
459
|
+
src: string;
|
|
460
|
+
reason: ExportAssetErrorReason;
|
|
461
|
+
cause?: unknown;
|
|
462
|
+
}
|
|
463
|
+
interface ExportResourceOptions {
|
|
464
|
+
/** Maximum wait for each image asset. Defaults to 10 seconds. */
|
|
465
|
+
imageTimeoutMs?: number;
|
|
466
|
+
/** Maximum width or height of any allocated export canvas. Defaults to 16,384. */
|
|
467
|
+
maxDimension?: number;
|
|
468
|
+
/** Maximum pixel count of any allocated export canvas. Defaults to 67,108,864. */
|
|
469
|
+
maxPixels?: number;
|
|
470
|
+
/** Called when an image cannot be loaded or embedded. The export continues. */
|
|
471
|
+
onAssetError?: (error: ExportAssetError) => void;
|
|
472
|
+
}
|
|
456
473
|
declare function exportImage(store: ElementStore, options?: ExportImageOptions, layerManager?: LayerManager): Promise<Blob | null>;
|
|
457
474
|
|
|
458
|
-
interface ExportSvgOptions {
|
|
475
|
+
interface ExportSvgOptions extends ExportResourceOptions {
|
|
459
476
|
padding?: number;
|
|
460
477
|
background?: string;
|
|
461
478
|
filter?: (el: CanvasElement) => boolean;
|
|
@@ -1123,6 +1140,6 @@ declare class LaserTool implements Tool {
|
|
|
1123
1140
|
private notifyOptionsChange;
|
|
1124
1141
|
}
|
|
1125
1142
|
|
|
1126
|
-
declare const VERSION = "0.
|
|
1143
|
+
declare const VERSION = "0.51.0";
|
|
1127
1144
|
|
|
1128
|
-
export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
|
1145
|
+
export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportAssetError, type ExportAssetErrorReason, type ExportImageOptions, type ExportResourceOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
package/dist/index.js
CHANGED
|
@@ -152,6 +152,17 @@ function sanitizeAttributes(el, tag) {
|
|
|
152
152
|
|
|
153
153
|
// src/core/state-serializer.ts
|
|
154
154
|
var CURRENT_VERSION = 2;
|
|
155
|
+
var ELEMENT_TYPES = [
|
|
156
|
+
"stroke",
|
|
157
|
+
"note",
|
|
158
|
+
"arrow",
|
|
159
|
+
"image",
|
|
160
|
+
"html",
|
|
161
|
+
"text",
|
|
162
|
+
"shape",
|
|
163
|
+
"grid",
|
|
164
|
+
"template"
|
|
165
|
+
];
|
|
155
166
|
function exportState(elements, camera, layers = [], activeLayerId) {
|
|
156
167
|
const state = {
|
|
157
168
|
version: CURRENT_VERSION,
|
|
@@ -177,37 +188,42 @@ function parseState(json) {
|
|
|
177
188
|
return data;
|
|
178
189
|
}
|
|
179
190
|
function validateState(data) {
|
|
180
|
-
if (!data
|
|
191
|
+
if (!isRecord(data)) {
|
|
181
192
|
throw new Error("Invalid state: expected an object");
|
|
182
193
|
}
|
|
183
194
|
const obj = data;
|
|
184
|
-
if (
|
|
195
|
+
if (!Number.isInteger(obj["version"]) || obj["version"] < 1) {
|
|
185
196
|
throw new Error("Invalid state: missing or invalid version");
|
|
186
197
|
}
|
|
187
|
-
if (
|
|
198
|
+
if (obj["version"] > CURRENT_VERSION) {
|
|
199
|
+
throw new Error(`Invalid state: unsupported version ${String(obj["version"])}`);
|
|
200
|
+
}
|
|
201
|
+
if (!isRecord(obj["camera"])) {
|
|
188
202
|
throw new Error("Invalid state: missing camera");
|
|
189
203
|
}
|
|
190
204
|
const cam = obj["camera"];
|
|
191
|
-
if (!cam["position"]
|
|
205
|
+
if (!isRecord(cam["position"])) {
|
|
192
206
|
throw new Error("Invalid state: missing camera.position");
|
|
193
207
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
throw new Error("Invalid state: camera.position must have x and y numbers");
|
|
208
|
+
if (!isPoint(cam["position"])) {
|
|
209
|
+
throw new Error("Invalid state: camera.position must have finite x and y numbers");
|
|
197
210
|
}
|
|
198
|
-
if (
|
|
199
|
-
throw new Error("Invalid state:
|
|
211
|
+
if (!isFiniteNumber(cam["zoom"]) || cam["zoom"] <= 0) {
|
|
212
|
+
throw new Error("Invalid state: camera.zoom must be a positive finite number");
|
|
200
213
|
}
|
|
201
214
|
if (!Array.isArray(obj["elements"])) {
|
|
202
215
|
throw new Error("Invalid state: elements must be an array");
|
|
203
216
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
migrateElement(el);
|
|
217
|
+
if (obj["layers"] !== void 0 && !Array.isArray(obj["layers"])) {
|
|
218
|
+
throw new Error("Invalid state: layers must be an array");
|
|
207
219
|
}
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
220
|
+
const elements = obj["elements"];
|
|
221
|
+
const hasLayers = Array.isArray(obj["layers"]) && obj["layers"].length > 0;
|
|
222
|
+
for (const el of elements) {
|
|
223
|
+
if (!isRecord(el)) throw new Error("Invalid element: expected an object");
|
|
224
|
+
migrateElement(el, !hasLayers);
|
|
225
|
+
}
|
|
226
|
+
if (!hasLayers) {
|
|
211
227
|
obj["layers"] = [
|
|
212
228
|
{
|
|
213
229
|
id: "default-layer",
|
|
@@ -219,31 +235,73 @@ function validateState(data) {
|
|
|
219
235
|
}
|
|
220
236
|
];
|
|
221
237
|
}
|
|
238
|
+
const layers = obj["layers"];
|
|
239
|
+
const layerIds = /* @__PURE__ */ new Set();
|
|
240
|
+
for (const layer of layers) {
|
|
241
|
+
validateLayer(layer);
|
|
242
|
+
if (layerIds.has(layer.id)) throw new Error(`Invalid state: duplicate layer id "${layer.id}"`);
|
|
243
|
+
layerIds.add(layer.id);
|
|
244
|
+
}
|
|
245
|
+
const elementIds = /* @__PURE__ */ new Set();
|
|
246
|
+
for (const el of elements) {
|
|
247
|
+
validateElement(el);
|
|
248
|
+
if (elementIds.has(el.id)) throw new Error(`Invalid state: duplicate element id "${el.id}"`);
|
|
249
|
+
elementIds.add(el.id);
|
|
250
|
+
if (!layerIds.has(el.layerId)) {
|
|
251
|
+
throw new Error(`Invalid element "${el.id}": unknown layerId "${el.layerId}"`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (obj["activeLayerId"] !== void 0) {
|
|
255
|
+
if (typeof obj["activeLayerId"] !== "string" || !layerIds.has(obj["activeLayerId"])) {
|
|
256
|
+
throw new Error("Invalid state: activeLayerId must reference an existing layer");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
cleanBindings(elements);
|
|
222
260
|
}
|
|
223
|
-
var VALID_TYPES = /* @__PURE__ */ new Set([
|
|
224
|
-
"stroke",
|
|
225
|
-
"note",
|
|
226
|
-
"arrow",
|
|
227
|
-
"image",
|
|
228
|
-
"html",
|
|
229
|
-
"text",
|
|
230
|
-
"shape",
|
|
231
|
-
"grid",
|
|
232
|
-
"template"
|
|
233
|
-
]);
|
|
234
261
|
function validateElement(el) {
|
|
235
|
-
if (!el
|
|
262
|
+
if (!isRecord(el)) {
|
|
236
263
|
throw new Error("Invalid element: expected an object");
|
|
237
264
|
}
|
|
238
|
-
|
|
239
|
-
if (typeof obj["id"] !== "string") {
|
|
265
|
+
if (typeof el["id"] !== "string" || el["id"].length === 0) {
|
|
240
266
|
throw new Error("Invalid element: missing id");
|
|
241
267
|
}
|
|
242
|
-
if (
|
|
243
|
-
throw new Error(`Invalid element: unknown type "${String(
|
|
268
|
+
if (!isEnum(el["type"], ELEMENT_TYPES)) {
|
|
269
|
+
throw new Error(`Invalid element: unknown type "${String(el["type"])}"`);
|
|
270
|
+
}
|
|
271
|
+
if (!isFiniteNumber(el["zIndex"])) {
|
|
272
|
+
throw new Error(`Invalid element "${el["id"]}": missing or invalid zIndex`);
|
|
273
|
+
}
|
|
274
|
+
if (!isPoint(el["position"]) || typeof el["locked"] !== "boolean" || typeof el["layerId"] !== "string" || !isOptional(el["groupId"], isString) || !isOptional(el["rotation"], isFiniteNumber)) {
|
|
275
|
+
throw new Error(`Invalid element "${el["id"]}": invalid base fields or geometry`);
|
|
276
|
+
}
|
|
277
|
+
const valid = validateTypeFields(el, el["type"]);
|
|
278
|
+
if (!valid) throw new Error(`Invalid element "${el["id"]}": malformed ${el["type"]} data`);
|
|
279
|
+
}
|
|
280
|
+
function validateTypeFields(el, type) {
|
|
281
|
+
switch (type) {
|
|
282
|
+
case "stroke":
|
|
283
|
+
return Array.isArray(el["points"]) && el["points"].every(isStrokePoint) && isString(el["color"]) && isFiniteNumber(el["width"]) && isFiniteNumber(el["opacity"]) && isOptionalEnum(el["blendMode"], ["multiply"]);
|
|
284
|
+
case "note":
|
|
285
|
+
return isSize(el["size"]) && isString(el["text"]) && isString(el["backgroundColor"]) && isString(el["textColor"]) && isOptional(el["fontSize"], isFiniteNumber);
|
|
286
|
+
case "arrow":
|
|
287
|
+
return isPoint(el["from"]) && isPoint(el["to"]) && isFiniteNumber(el["bend"]) && isString(el["color"]) && isFiniteNumber(el["width"]) && isOptional(el["fromBinding"], isBinding) && isOptional(el["toBinding"], isBinding) && isOptional(el["cachedControlPoint"], isPoint) && isOptional(el["label"], isString) && isOptionalEnum(el["strokeStyle"], ["solid", "dashed", "dotted"]);
|
|
288
|
+
case "image":
|
|
289
|
+
return isSize(el["size"]) && isString(el["src"]);
|
|
290
|
+
case "html":
|
|
291
|
+
return isSize(el["size"]) && isOptional(el["domId"], isString) && isOptional(el["interactive"], isBoolean) && isOptional(el["htmlType"], isString) && isOptional(el["data"], isRecord);
|
|
292
|
+
case "text":
|
|
293
|
+
return isSize(el["size"]) && isString(el["text"]) && isFiniteNumber(el["fontSize"]) && isString(el["color"]) && isEnum(el["textAlign"], ["left", "center", "right"]);
|
|
294
|
+
case "shape":
|
|
295
|
+
return isEnum(el["shape"], ["rectangle", "ellipse", "line"]) && isSize(el["size"]) && isString(el["strokeColor"]) && isFiniteNumber(el["strokeWidth"]) && isString(el["fillColor"]) && isOptional(el["flip"], isBoolean);
|
|
296
|
+
case "grid":
|
|
297
|
+
return isEnum(el["gridType"], ["square", "hex"]) && isEnum(el["hexOrientation"], ["pointy", "flat"]) && isFiniteNumber(el["cellSize"]) && isString(el["strokeColor"]) && isFiniteNumber(el["strokeWidth"]) && isFiniteNumber(el["opacity"]);
|
|
298
|
+
case "template":
|
|
299
|
+
return isEnum(el["templateShape"], ["circle", "cone", "line", "square", "rectangle"]) && isFiniteNumber(el["radius"]) && isFiniteNumber(el["angle"]) && isOptional(el["width"], isFiniteNumber) && isString(el["fillColor"]) && isString(el["strokeColor"]) && isFiniteNumber(el["strokeWidth"]) && isFiniteNumber(el["opacity"]) && isOptional(el["feetPerCell"], isFiniteNumber) && isOptional(el["radiusFeet"], isFiniteNumber) && isOptionalEnum(el["renderStyle"], ["cells", "geometric"]);
|
|
244
300
|
}
|
|
245
|
-
|
|
246
|
-
|
|
301
|
+
}
|
|
302
|
+
function validateLayer(layer) {
|
|
303
|
+
if (!isRecord(layer) || typeof layer["id"] !== "string" || layer["id"].length === 0 || typeof layer["name"] !== "string" || typeof layer["visible"] !== "boolean" || typeof layer["locked"] !== "boolean" || !isFiniteNumber(layer["order"]) || !isFiniteNumber(layer["opacity"]) || layer["opacity"] < 0 || layer["opacity"] > 1) {
|
|
304
|
+
throw new Error("Invalid state: malformed layer");
|
|
247
305
|
}
|
|
248
306
|
}
|
|
249
307
|
function cleanBindings(elements) {
|
|
@@ -260,30 +318,63 @@ function cleanBindings(elements) {
|
|
|
260
318
|
}
|
|
261
319
|
}
|
|
262
320
|
}
|
|
263
|
-
function migrateElement(obj) {
|
|
264
|
-
if (
|
|
321
|
+
function migrateElement(obj, useDefaultLayer) {
|
|
322
|
+
if (obj["layerId"] === void 0 || useDefaultLayer && obj["layerId"] === "") {
|
|
265
323
|
obj["layerId"] = "default-layer";
|
|
266
324
|
}
|
|
267
|
-
if (obj["type"] === "arrow" &&
|
|
325
|
+
if (obj["type"] === "arrow" && obj["bend"] === void 0) {
|
|
268
326
|
obj["bend"] = 0;
|
|
269
327
|
}
|
|
270
328
|
if (obj["type"] === "stroke" && Array.isArray(obj["points"])) {
|
|
271
329
|
for (const pt of obj["points"]) {
|
|
272
|
-
if (
|
|
330
|
+
if (pt["pressure"] === void 0) {
|
|
273
331
|
pt["pressure"] = 0.5;
|
|
274
332
|
}
|
|
275
333
|
}
|
|
276
334
|
}
|
|
277
|
-
if (obj["type"] === "shape" &&
|
|
335
|
+
if (obj["type"] === "shape" && obj["shape"] === void 0) {
|
|
278
336
|
obj["shape"] = "rectangle";
|
|
279
337
|
}
|
|
280
|
-
if (obj["type"] === "note" &&
|
|
338
|
+
if (obj["type"] === "note" && obj["textColor"] === void 0) {
|
|
281
339
|
obj["textColor"] = "#000000";
|
|
282
340
|
}
|
|
283
341
|
if ((obj["type"] === "note" || obj["type"] === "text") && typeof obj["text"] === "string") {
|
|
284
342
|
obj["text"] = sanitizeNoteHtml(obj["text"]);
|
|
285
343
|
}
|
|
286
344
|
}
|
|
345
|
+
function isRecord(value) {
|
|
346
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
347
|
+
}
|
|
348
|
+
function isFiniteNumber(value) {
|
|
349
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
350
|
+
}
|
|
351
|
+
function isString(value) {
|
|
352
|
+
return typeof value === "string";
|
|
353
|
+
}
|
|
354
|
+
function isBoolean(value) {
|
|
355
|
+
return typeof value === "boolean";
|
|
356
|
+
}
|
|
357
|
+
function isOptional(value, validate) {
|
|
358
|
+
return value === void 0 || validate(value);
|
|
359
|
+
}
|
|
360
|
+
function isEnum(value, values) {
|
|
361
|
+
return typeof value === "string" && values.includes(value);
|
|
362
|
+
}
|
|
363
|
+
function isOptionalEnum(value, values) {
|
|
364
|
+
return value === void 0 || isEnum(value, values);
|
|
365
|
+
}
|
|
366
|
+
function isPoint(value) {
|
|
367
|
+
return isRecord(value) && isFiniteNumber(value["x"]) && isFiniteNumber(value["y"]);
|
|
368
|
+
}
|
|
369
|
+
function isSize(value) {
|
|
370
|
+
return isRecord(value) && isFiniteNumber(value["w"]) && isFiniteNumber(value["h"]);
|
|
371
|
+
}
|
|
372
|
+
function isStrokePoint(value) {
|
|
373
|
+
return isRecord(value) && isPoint(value) && isFiniteNumber(value["pressure"]);
|
|
374
|
+
}
|
|
375
|
+
function isBinding(value) {
|
|
376
|
+
return isRecord(value) && typeof value["elementId"] === "string";
|
|
377
|
+
}
|
|
287
378
|
|
|
288
379
|
// src/core/storage/local-storage-adapter.ts
|
|
289
380
|
var LocalStorageAdapter = class {
|
|
@@ -5265,6 +5356,9 @@ function renderTextOnCanvas(ctx, text) {
|
|
|
5265
5356
|
}
|
|
5266
5357
|
|
|
5267
5358
|
// src/canvas/export-image.ts
|
|
5359
|
+
var DEFAULT_IMAGE_TIMEOUT_MS = 1e4;
|
|
5360
|
+
var DEFAULT_MAX_DIMENSION = 16384;
|
|
5361
|
+
var DEFAULT_MAX_PIXELS = 67108864;
|
|
5268
5362
|
var center = (b) => ({ x: b.x + b.w / 2, y: b.y + b.h / 2 });
|
|
5269
5363
|
function getStrokeBounds(el) {
|
|
5270
5364
|
if (el.type !== "stroke") return null;
|
|
@@ -5374,12 +5468,53 @@ function renderGridForBounds(ctx, grid, bounds) {
|
|
|
5374
5468
|
);
|
|
5375
5469
|
}
|
|
5376
5470
|
}
|
|
5377
|
-
function
|
|
5471
|
+
function positiveOption(value, fallback, name) {
|
|
5472
|
+
const resolved = value ?? fallback;
|
|
5473
|
+
if (!Number.isFinite(resolved) || resolved <= 0) {
|
|
5474
|
+
throw new RangeError(`${name} must be a finite number greater than 0`);
|
|
5475
|
+
}
|
|
5476
|
+
return resolved;
|
|
5477
|
+
}
|
|
5478
|
+
function nonNegativeOption(value, fallback, name) {
|
|
5479
|
+
const resolved = value ?? fallback;
|
|
5480
|
+
if (!Number.isFinite(resolved) || resolved < 0) {
|
|
5481
|
+
throw new RangeError(`${name} must be a finite number greater than or equal to 0`);
|
|
5482
|
+
}
|
|
5483
|
+
return resolved;
|
|
5484
|
+
}
|
|
5485
|
+
function assertExportSize(width, height, options) {
|
|
5486
|
+
const maxDimension = positiveOption(options.maxDimension, DEFAULT_MAX_DIMENSION, "maxDimension");
|
|
5487
|
+
const maxPixels = positiveOption(options.maxPixels, DEFAULT_MAX_PIXELS, "maxPixels");
|
|
5488
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
5489
|
+
throw new RangeError("Export dimensions must be finite numbers greater than 0");
|
|
5490
|
+
}
|
|
5491
|
+
if (width > maxDimension || height > maxDimension) {
|
|
5492
|
+
throw new RangeError(
|
|
5493
|
+
`Export dimensions ${width}x${height} exceed the maximum dimension of ${maxDimension}`
|
|
5494
|
+
);
|
|
5495
|
+
}
|
|
5496
|
+
if (width * height > maxPixels) {
|
|
5497
|
+
throw new RangeError(
|
|
5498
|
+
`Export size ${width}x${height} exceeds the maximum of ${maxPixels} pixels`
|
|
5499
|
+
);
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
function validateExportResourceOptions(options) {
|
|
5503
|
+
positiveOption(options.imageTimeoutMs, DEFAULT_IMAGE_TIMEOUT_MS, "imageTimeoutMs");
|
|
5504
|
+
positiveOption(options.maxDimension, DEFAULT_MAX_DIMENSION, "maxDimension");
|
|
5505
|
+
positiveOption(options.maxPixels, DEFAULT_MAX_PIXELS, "maxPixels");
|
|
5506
|
+
}
|
|
5507
|
+
function loadImages(elements, options = {}) {
|
|
5378
5508
|
const imageElements = elements.filter(
|
|
5379
5509
|
(el) => el.type === "image" && "src" in el
|
|
5380
5510
|
);
|
|
5381
5511
|
const cache3 = /* @__PURE__ */ new Map();
|
|
5382
5512
|
if (imageElements.length === 0) return Promise.resolve(cache3);
|
|
5513
|
+
const timeoutMs = positiveOption(
|
|
5514
|
+
options.imageTimeoutMs,
|
|
5515
|
+
DEFAULT_IMAGE_TIMEOUT_MS,
|
|
5516
|
+
"imageTimeoutMs"
|
|
5517
|
+
);
|
|
5383
5518
|
return new Promise((resolve) => {
|
|
5384
5519
|
let remaining = imageElements.length;
|
|
5385
5520
|
const done = () => {
|
|
@@ -5389,19 +5524,41 @@ function loadImages(elements) {
|
|
|
5389
5524
|
for (const el of imageElements) {
|
|
5390
5525
|
const img = new Image();
|
|
5391
5526
|
img.crossOrigin = "anonymous";
|
|
5527
|
+
let settled = false;
|
|
5528
|
+
const timer = setTimeout(() => {
|
|
5529
|
+
if (settled) return;
|
|
5530
|
+
settled = true;
|
|
5531
|
+
img.onload = null;
|
|
5532
|
+
img.onerror = null;
|
|
5533
|
+
options.onAssetError?.({ elementId: el.id, src: el.src, reason: "timeout" });
|
|
5534
|
+
done();
|
|
5535
|
+
}, timeoutMs);
|
|
5536
|
+
const settle = () => {
|
|
5537
|
+
if (settled) return false;
|
|
5538
|
+
settled = true;
|
|
5539
|
+
clearTimeout(timer);
|
|
5540
|
+
img.onload = null;
|
|
5541
|
+
img.onerror = null;
|
|
5542
|
+
return true;
|
|
5543
|
+
};
|
|
5392
5544
|
img.onload = () => {
|
|
5545
|
+
if (!settle()) return;
|
|
5393
5546
|
cache3.set(el.id, img);
|
|
5394
5547
|
done();
|
|
5395
5548
|
};
|
|
5396
|
-
img.onerror =
|
|
5397
|
-
|
|
5398
|
-
|
|
5549
|
+
img.onerror = (cause) => {
|
|
5550
|
+
if (!settle()) return;
|
|
5551
|
+
options.onAssetError?.({ elementId: el.id, src: el.src, reason: "load", cause });
|
|
5552
|
+
done();
|
|
5553
|
+
};
|
|
5554
|
+
img.src = el.src;
|
|
5399
5555
|
}
|
|
5400
5556
|
});
|
|
5401
5557
|
}
|
|
5402
5558
|
async function exportImage(store, options = {}, layerManager) {
|
|
5403
|
-
const scale = options.scale
|
|
5404
|
-
const padding = options.padding
|
|
5559
|
+
const scale = positiveOption(options.scale, 2, "scale");
|
|
5560
|
+
const padding = nonNegativeOption(options.padding, 0, "padding");
|
|
5561
|
+
validateExportResourceOptions(options);
|
|
5405
5562
|
const background = options.background ?? "#ffffff";
|
|
5406
5563
|
const filter = options.filter;
|
|
5407
5564
|
const allElements = store.getAll();
|
|
@@ -5411,10 +5568,13 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
5411
5568
|
}
|
|
5412
5569
|
const bounds = computeBounds(visibleElements, padding);
|
|
5413
5570
|
if (!bounds) return null;
|
|
5414
|
-
const
|
|
5571
|
+
const width = Math.ceil(bounds.w * scale);
|
|
5572
|
+
const height = Math.ceil(bounds.h * scale);
|
|
5573
|
+
assertExportSize(width, height, options);
|
|
5574
|
+
const imageCache = await loadImages(visibleElements, options);
|
|
5415
5575
|
const canvas = document.createElement("canvas");
|
|
5416
|
-
canvas.width =
|
|
5417
|
-
canvas.height =
|
|
5576
|
+
canvas.width = width;
|
|
5577
|
+
canvas.height = height;
|
|
5418
5578
|
const ctx = canvas.getContext("2d");
|
|
5419
5579
|
if (!ctx) return null;
|
|
5420
5580
|
ctx.scale(scale, scale);
|
|
@@ -5597,14 +5757,17 @@ function emitImage(image, dataUri) {
|
|
|
5597
5757
|
const { w, h } = image.size;
|
|
5598
5758
|
return `<image href="${esc(href)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5599
5759
|
}
|
|
5600
|
-
function emitText(text, rasterScale) {
|
|
5760
|
+
function emitText(text, rasterScale, resourceOptions) {
|
|
5601
5761
|
if (!text.text) return "";
|
|
5602
5762
|
const { x, y } = text.position;
|
|
5603
5763
|
const { w, h } = text.size;
|
|
5604
5764
|
if (typeof document === "undefined") return "";
|
|
5765
|
+
const width = Math.max(1, Math.ceil(w * rasterScale));
|
|
5766
|
+
const height = Math.max(1, Math.ceil(h * rasterScale));
|
|
5767
|
+
assertExportSize(width, height, resourceOptions);
|
|
5605
5768
|
const canvas = document.createElement("canvas");
|
|
5606
|
-
canvas.width =
|
|
5607
|
-
canvas.height =
|
|
5769
|
+
canvas.width = width;
|
|
5770
|
+
canvas.height = height;
|
|
5608
5771
|
const ctx = canvas.getContext("2d");
|
|
5609
5772
|
if (!ctx) return "";
|
|
5610
5773
|
ctx.scale(rasterScale, rasterScale);
|
|
@@ -5619,13 +5782,16 @@ function emitText(text, rasterScale) {
|
|
|
5619
5782
|
if (!dataUri || !dataUri.startsWith("data:")) return "";
|
|
5620
5783
|
return `<image href="${esc(dataUri)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5621
5784
|
}
|
|
5622
|
-
function emitNote(note, rasterScale) {
|
|
5785
|
+
function emitNote(note, rasterScale, resourceOptions) {
|
|
5623
5786
|
const { x, y } = note.position;
|
|
5624
5787
|
const { w, h } = note.size;
|
|
5625
5788
|
if (typeof document === "undefined") return emitNotePlaceholder(note);
|
|
5789
|
+
const width = Math.max(1, Math.ceil(w * rasterScale));
|
|
5790
|
+
const height = Math.max(1, Math.ceil(h * rasterScale));
|
|
5791
|
+
assertExportSize(width, height, resourceOptions);
|
|
5626
5792
|
const canvas = document.createElement("canvas");
|
|
5627
|
-
canvas.width =
|
|
5628
|
-
canvas.height =
|
|
5793
|
+
canvas.width = width;
|
|
5794
|
+
canvas.height = height;
|
|
5629
5795
|
const ctx = canvas.getContext("2d");
|
|
5630
5796
|
if (!ctx) return emitNotePlaceholder(note);
|
|
5631
5797
|
ctx.scale(rasterScale, rasterScale);
|
|
@@ -5786,8 +5952,9 @@ function emitHexTemplate(t, grid) {
|
|
|
5786
5952
|
return `<path d="${d}" fill="${esc(t.fillColor)}" stroke="${esc(t.strokeColor)}" stroke-width="${n(t.strokeWidth)}" opacity="${n(t.opacity)}" />`;
|
|
5787
5953
|
}
|
|
5788
5954
|
async function exportSvg(store, options = {}, layerManager) {
|
|
5789
|
-
const padding = options.padding
|
|
5790
|
-
const rasterScale = options.rasterScale
|
|
5955
|
+
const padding = nonNegativeOption(options.padding, 0, "padding");
|
|
5956
|
+
const rasterScale = positiveOption(options.rasterScale, 2, "rasterScale");
|
|
5957
|
+
validateExportResourceOptions(options);
|
|
5791
5958
|
const filter = options.filter;
|
|
5792
5959
|
const allElements = store.getAll();
|
|
5793
5960
|
let visibleElements = layerManager ? allElements.filter((el) => layerManager.isLayerVisible(el.layerId)) : allElements;
|
|
@@ -5796,11 +5963,12 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
5796
5963
|
if (!bounds) {
|
|
5797
5964
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
5798
5965
|
}
|
|
5966
|
+
assertExportSize(Math.ceil(bounds.w), Math.ceil(bounds.h), options);
|
|
5799
5967
|
const remoteImages = visibleElements.filter(
|
|
5800
5968
|
(el) => el.type === "image" && !el.src.startsWith("data:")
|
|
5801
5969
|
);
|
|
5802
|
-
const imageCache = await loadImages(remoteImages);
|
|
5803
|
-
const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale);
|
|
5970
|
+
const imageCache = await loadImages(remoteImages, options);
|
|
5971
|
+
const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale, options);
|
|
5804
5972
|
const grids = visibleElements.filter((el) => el.type === "grid");
|
|
5805
5973
|
const firstGrid = grids[0];
|
|
5806
5974
|
let body = "";
|
|
@@ -5809,7 +5977,7 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
5809
5977
|
}
|
|
5810
5978
|
const layerBodies = /* @__PURE__ */ new Map();
|
|
5811
5979
|
for (const el of visibleElements) {
|
|
5812
|
-
const emitted = emitElement(el, imageDataUris, rasterScale, firstGrid, store);
|
|
5980
|
+
const emitted = emitElement(el, imageDataUris, rasterScale, firstGrid, store, options);
|
|
5813
5981
|
layerBodies.set(el.layerId, (layerBodies.get(el.layerId) ?? "") + emitted);
|
|
5814
5982
|
}
|
|
5815
5983
|
for (const [layerId, emitted] of layerBodies) {
|
|
@@ -5823,7 +5991,7 @@ async function exportSvg(store, options = {}, layerManager) {
|
|
|
5823
5991
|
}
|
|
5824
5992
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${n(bounds.w)}" height="${n(bounds.h)}" viewBox="${n(bounds.x)} ${n(bounds.y)} ${n(bounds.w)} ${n(bounds.h)}">${body}</svg>`;
|
|
5825
5993
|
}
|
|
5826
|
-
function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
5994
|
+
function emitElement(el, imageDataUris, rasterScale, firstGrid, store, resourceOptions) {
|
|
5827
5995
|
switch (el.type) {
|
|
5828
5996
|
case "stroke":
|
|
5829
5997
|
return withRotationSvg(el, emitStroke(el));
|
|
@@ -5834,9 +6002,9 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
|
5834
6002
|
case "image":
|
|
5835
6003
|
return withRotationSvg(el, emitImage(el, imageDataUris.get(el.id)));
|
|
5836
6004
|
case "text":
|
|
5837
|
-
return withRotationSvg(el, emitText(el, rasterScale));
|
|
6005
|
+
return withRotationSvg(el, emitText(el, rasterScale, resourceOptions));
|
|
5838
6006
|
case "note":
|
|
5839
|
-
return withRotationSvg(el, emitNote(el, rasterScale));
|
|
6007
|
+
return withRotationSvg(el, emitNote(el, rasterScale, resourceOptions));
|
|
5840
6008
|
case "template":
|
|
5841
6009
|
return emitTemplate(el, firstGrid);
|
|
5842
6010
|
case "grid":
|
|
@@ -5847,7 +6015,7 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
|
5847
6015
|
return "";
|
|
5848
6016
|
}
|
|
5849
6017
|
}
|
|
5850
|
-
function encodeImages(elements, imageCache, rasterScale) {
|
|
6018
|
+
function encodeImages(elements, imageCache, rasterScale, resourceOptions) {
|
|
5851
6019
|
const out = /* @__PURE__ */ new Map();
|
|
5852
6020
|
for (const el of elements) {
|
|
5853
6021
|
if (el.type !== "image") continue;
|
|
@@ -5857,16 +6025,24 @@ function encodeImages(elements, imageCache, rasterScale) {
|
|
|
5857
6025
|
}
|
|
5858
6026
|
const img = imageCache.get(el.id);
|
|
5859
6027
|
if (!img || typeof document === "undefined") continue;
|
|
6028
|
+
const width = Math.max(1, Math.ceil(el.size.w * rasterScale));
|
|
6029
|
+
const height = Math.max(1, Math.ceil(el.size.h * rasterScale));
|
|
6030
|
+
assertExportSize(width, height, resourceOptions);
|
|
5860
6031
|
const canvas = document.createElement("canvas");
|
|
5861
|
-
canvas.width =
|
|
5862
|
-
canvas.height =
|
|
6032
|
+
canvas.width = width;
|
|
6033
|
+
canvas.height = height;
|
|
5863
6034
|
const ctx = canvas.getContext("2d");
|
|
5864
|
-
if (!ctx)
|
|
6035
|
+
if (!ctx) {
|
|
6036
|
+
resourceOptions.onAssetError?.({ elementId: el.id, src: el.src, reason: "encode" });
|
|
6037
|
+
continue;
|
|
6038
|
+
}
|
|
5865
6039
|
try {
|
|
5866
6040
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
5867
6041
|
const uri = canvas.toDataURL();
|
|
5868
6042
|
if (uri.startsWith("data:")) out.set(el.id, uri);
|
|
5869
|
-
|
|
6043
|
+
else resourceOptions.onAssetError?.({ elementId: el.id, src: el.src, reason: "encode" });
|
|
6044
|
+
} catch (cause) {
|
|
6045
|
+
resourceOptions.onAssetError?.({ elementId: el.id, src: el.src, reason: "encode", cause });
|
|
5870
6046
|
}
|
|
5871
6047
|
}
|
|
5872
6048
|
return out;
|
|
@@ -10639,7 +10815,7 @@ var LaserTool = class {
|
|
|
10639
10815
|
};
|
|
10640
10816
|
|
|
10641
10817
|
// src/index.ts
|
|
10642
|
-
var VERSION = "0.
|
|
10818
|
+
var VERSION = "0.51.0";
|
|
10643
10819
|
export {
|
|
10644
10820
|
ArrowTool,
|
|
10645
10821
|
AutoSave,
|