@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.
package/dist/CreatorApp.svelte
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|