@widgetic/creator 0.3.66 → 0.3.67

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.
@@ -529,6 +529,8 @@
529
529
  onMount(async () => {
530
530
  if (canvasModulePromise) {
531
531
  const canvasModule = await canvasModulePromise;
532
+ // Installed before the canvas mounts so the first save already externalizes
533
+ installCanvasThumbnailStore(canvasModule);
532
534
  WidgeticCanvas = canvasModule.Canvas;
533
535
  }
534
536
  installLongTaskFreezeReporter();
@@ -590,7 +592,10 @@
590
592
  saveWidgetDraft,
591
593
  readWidgetDraft,
592
594
  deleteWidgetDraft,
593
- copyWidgetDraft
595
+ copyWidgetDraft,
596
+ saveShapeThumbnail,
597
+ readShapeThumbnail,
598
+ canPersistShapeThumbnails
594
599
  } from './utils/creatorDraftStorage';
595
600
 
596
601
  /**
@@ -1410,6 +1415,76 @@
1410
1415
  return dataUrls.slice(0, 1);
1411
1416
  }
1412
1417
 
1418
+ /**
1419
+ * Keeps widget screenshots out of the canvas JSON.
1420
+ *
1421
+ * Fabric serialized each widget shape's screenshot inline as base64, so an
1422
+ * autosave stringified megabytes of image data and wrote it three times over —
1423
+ * the main-thread block the freeze reports kept naming. The canvas now stores a
1424
+ * reference and asks for the bytes back through this store: the local copy
1425
+ * first, then the widget's persisted thumbnail so a fresh browser still paints
1426
+ * the shape.
1427
+ */
1428
+ function installCanvasThumbnailStore(canvasModule: any): void {
1429
+ if (typeof canvasModule?.setShapeImageStore !== 'function') return;
1430
+ canvasModule.setShapeImageStore({
1431
+ save(ref: string, dataUrl: string) {
1432
+ // Refusing here tells the canvas to keep the bytes inline, so a save
1433
+ // before the owner is known cannot drop a screenshot on the floor.
1434
+ if (!canPersistShapeThumbnails()) throw new Error('No cache owner yet');
1435
+ saveShapeThumbnail(ref, dataUrl);
1436
+ },
1437
+ async load(ref: string): Promise<string | null> {
1438
+ const local = await readShapeThumbnail(ref);
1439
+ if (local) return local;
1440
+ const widgetId = ref.startsWith('widget:') ? ref.slice('widget:'.length) : null;
1441
+ if (!widgetId) return null;
1442
+ return await waitForWidgetThumbnailUrl(widgetId);
1443
+ }
1444
+ });
1445
+ }
1446
+
1447
+ /**
1448
+ * The canvas often loads before the widget list does, so a reference with no
1449
+ * local copy has nothing to resolve to yet. Give the list a few seconds to
1450
+ * arrive rather than leaving the shape blank until the next screenshot.
1451
+ */
1452
+ async function waitForWidgetThumbnailUrl(widgetId: string): Promise<string | null> {
1453
+ for (let attempt = 0; attempt < 12; attempt++) {
1454
+ const widget = getWidgetById(widgetId) as any;
1455
+ const url = widget?.thumbnailUrl || widget?.thumbnail_url;
1456
+ if (url) return url;
1457
+ // The canvas widget list is slim and often omits the thumbnail, so ask the
1458
+ // API for this one widget rather than leaving the shape blank.
1459
+ if (widget) return await fetchWidgetThumbnailUrl(widgetId);
1460
+ await new Promise((resolve) => setTimeout(resolve, 500));
1461
+ }
1462
+ return null;
1463
+ }
1464
+
1465
+ /** One-widget lookup for the persisted thumbnail, cached per widget. */
1466
+ const thumbnailUrlRequests = new Map<string, Promise<string | null>>();
1467
+
1468
+ function fetchWidgetThumbnailUrl(widgetId: string): Promise<string | null> {
1469
+ const pending = thumbnailUrlRequests.get(widgetId);
1470
+ if (pending) return pending;
1471
+ const request = (async () => {
1472
+ if (!widgetsClient) return null;
1473
+ try {
1474
+ const response = (await widgetsClient.getWidgetsBatch({
1475
+ getWidgetsBatchRequest: { widgetIds: [widgetId] }
1476
+ })) as { widgets?: any[]; data?: { widgets?: any[] } };
1477
+ const fetched = (response?.widgets ?? response?.data?.widgets ?? [])[0];
1478
+ return fetched?.thumbnailUrl ?? fetched?.thumbnail_url ?? null;
1479
+ } catch (error) {
1480
+ console.warn('[CreatorApp] thumbnail lookup failed', widgetId.slice(0, 8), error);
1481
+ return null;
1482
+ }
1483
+ })();
1484
+ thumbnailUrlRequests.set(widgetId, request);
1485
+ return request;
1486
+ }
1487
+
1413
1488
  async function persistWidgetListThumbnail(widgetId: string, imageDataUrl: string, isCurrent = () => true): Promise<void> {
1414
1489
  if (!widgetsClient || !widgetId || !imageDataUrl || !isCurrent()) return;
1415
1490
  try {
@@ -12,6 +12,11 @@ export declare function readAllWidgetDrafts(): Promise<Record<string, WidgetDraf
12
12
  export declare function copyWidgetDraft(sourceWidgetId: string, targetWidgetId: string): Promise<void>;
13
13
  export declare function saveCanvasState(canvasId: string | null, json: string): void;
14
14
  export declare function readCanvasState(canvasId: string | null): Promise<string | null>;
15
+ /** False before an owner is known — nothing can be persisted yet. */
16
+ export declare function canPersistShapeThumbnails(): boolean;
17
+ export declare function saveShapeThumbnail(ref: string, dataUrl: string): void;
18
+ export declare function readShapeThumbnail(ref: string): Promise<string | null>;
19
+ export declare function deleteShapeThumbnail(ref: string): Promise<void>;
15
20
  export declare function saveActiveGeneration(widgetId: string, operationId: string): void;
16
21
  export declare function readActiveGeneration(widgetId: string): Promise<string | null>;
17
22
  export declare function clearActiveGeneration(widgetId: string): Promise<void>;
@@ -11,6 +11,7 @@ import { deleteEntity, getCacheOwner, readAllEntitiesForKind, readEntity, writeE
11
11
  const DRAFT_KIND = 'creatorDrafts';
12
12
  const CANVAS_KIND = 'creatorCanvasState';
13
13
  const GENERATION_KIND = 'creatorActiveGeneration';
14
+ const SHAPE_THUMBNAIL_KIND = 'creatorShapeThumbnails';
14
15
  function ownerIdOrNull() {
15
16
  return getCacheOwner();
16
17
  }
@@ -56,7 +57,9 @@ export function saveCanvasState(canvasId, json) {
56
57
  const ownerId = ownerIdOrNull();
57
58
  if (!ownerId)
58
59
  return;
59
- void writeEntity(ownerId, CANVAS_KIND, canvasId ?? 'default', json).catch((error) => {
60
+ // Wrapped: readEntity discards non-object payloads, so a bare string never
61
+ // came back and this durable fallback silently read as empty.
62
+ void writeEntity(ownerId, CANVAS_KIND, canvasId ?? 'default', { json }).catch((error) => {
60
63
  console.warn('[creatorDraftStorage] Failed to persist canvas state:', error);
61
64
  });
62
65
  }
@@ -64,20 +67,54 @@ export async function readCanvasState(canvasId) {
64
67
  const ownerId = ownerIdOrNull();
65
68
  if (!ownerId)
66
69
  return null;
67
- return readEntity(ownerId, CANVAS_KIND, canvasId ?? 'default');
70
+ const record = await readEntity(ownerId, CANVAS_KIND, canvasId ?? 'default');
71
+ return record?.json ?? null;
72
+ }
73
+ // ─── Widget shape thumbnails ─────────────────────────────────────
74
+ //
75
+ // Widget screenshots used to be serialized inline in the canvas JSON, which
76
+ // made every autosave stringify megabytes of base64 on the main thread. They
77
+ // live here instead, keyed by the reference the canvas puts on the shape.
78
+ /** False before an owner is known — nothing can be persisted yet. */
79
+ export function canPersistShapeThumbnails() {
80
+ return !!ownerIdOrNull();
81
+ }
82
+ export function saveShapeThumbnail(ref, dataUrl) {
83
+ const ownerId = ownerIdOrNull();
84
+ if (!ownerId || !ref || !dataUrl)
85
+ return;
86
+ // Wrapped in an object: readEntity only hands back object payloads, so a bare
87
+ // string round-trips as null.
88
+ void writeEntity(ownerId, SHAPE_THUMBNAIL_KIND, ref, { dataUrl }).catch((error) => {
89
+ console.warn('[creatorDraftStorage] Failed to persist shape thumbnail:', ref, error);
90
+ });
91
+ }
92
+ export async function readShapeThumbnail(ref) {
93
+ const ownerId = ownerIdOrNull();
94
+ if (!ownerId || !ref)
95
+ return null;
96
+ const record = await readEntity(ownerId, SHAPE_THUMBNAIL_KIND, ref);
97
+ return record?.dataUrl ?? null;
98
+ }
99
+ export async function deleteShapeThumbnail(ref) {
100
+ const ownerId = ownerIdOrNull();
101
+ if (!ownerId || !ref)
102
+ return;
103
+ await deleteEntity(ownerId, SHAPE_THUMBNAIL_KIND, ref);
68
104
  }
69
105
  // ─── Active generation id ────────────────────────────────────────
70
106
  export function saveActiveGeneration(widgetId, operationId) {
71
107
  const ownerId = ownerIdOrNull();
72
108
  if (!ownerId)
73
109
  return;
74
- void writeEntity(ownerId, GENERATION_KIND, widgetId, operationId);
110
+ void writeEntity(ownerId, GENERATION_KIND, widgetId, { operationId });
75
111
  }
76
112
  export async function readActiveGeneration(widgetId) {
77
113
  const ownerId = ownerIdOrNull();
78
114
  if (!ownerId)
79
115
  return null;
80
- return readEntity(ownerId, GENERATION_KIND, widgetId);
116
+ const record = await readEntity(ownerId, GENERATION_KIND, widgetId);
117
+ return record?.operationId ?? null;
81
118
  }
82
119
  export async function clearActiveGeneration(widgetId) {
83
120
  const ownerId = ownerIdOrNull();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widgetic/creator",
3
- "version": "0.3.66",
3
+ "version": "0.3.67",
4
4
  "type": "module",
5
5
  "peerDependencies": {
6
6
  "@sveltejs/kit": "^2.20.0",