@widgetic/creator 0.3.51 → 0.3.54
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
|
@@ -111,6 +111,21 @@
|
|
|
111
111
|
|
|
112
112
|
function notifyHostWidgetName(widget: { id?: string | null; name?: string | null } | null | undefined): void {
|
|
113
113
|
if (!embedded || !widget?.id) return;
|
|
114
|
+
const primaryId = (canvasPrimaryWidgetId || '').trim();
|
|
115
|
+
if (!primaryId) {
|
|
116
|
+
console.log('[CreatorApp] Skip host title — primary widget unknown');
|
|
117
|
+
emitCanvasWidgetsList('skip-host-title');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (widget.id !== primaryId) {
|
|
121
|
+
console.log(
|
|
122
|
+
'[CreatorApp] Skip host title — not primary',
|
|
123
|
+
widget.id.substring(0, 8),
|
|
124
|
+
primaryId.substring(0, 8)
|
|
125
|
+
);
|
|
126
|
+
emitCanvasWidgetsList('skip-host-title');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
114
129
|
const name = (widget.name || '').trim() || 'Untitled Widget';
|
|
115
130
|
console.log('[CreatorApp] Notify host widget name:', widget.id, name);
|
|
116
131
|
onWidgetRenamed?.({ widgetId: widget.id, name });
|
|
@@ -142,9 +157,14 @@
|
|
|
142
157
|
const HOST_OPEN_WIDGET_DETAILS_EVENT = 'widgetic:host-open-widget-details';
|
|
143
158
|
const HOST_DUPLICATE_WIDGET_EVENT = 'widgetic:host-duplicate-widget';
|
|
144
159
|
const HOST_DELETE_WIDGET_EVENT = 'widgetic:host-delete-widget';
|
|
160
|
+
const HOST_SAVE_CANVAS_EVENT = 'widgetic:host-save-canvas';
|
|
161
|
+
const HOST_SET_PRIMARY_WIDGET_EVENT = 'widgetic:host-set-primary-widget';
|
|
162
|
+
const HOST_REQUEST_CANVAS_WIDGETS_EVENT = 'widgetic:host-request-canvas-widgets';
|
|
163
|
+
const HOST_MOVE_CANVAS_FOLDER_EVENT = 'widgetic:host-move-canvas-folder';
|
|
145
164
|
const CREATOR_DEBUG_CHANGED_EVENT = 'widgetic:creator-debug-changed';
|
|
146
165
|
const CREATOR_WIREFRAME_CHANGED_EVENT = 'widgetic:creator-wireframe-changed';
|
|
147
166
|
const CREATOR_ADVANCED_CHANGED_EVENT = 'widgetic:creator-advanced-changed';
|
|
167
|
+
const CREATOR_CANVAS_WIDGETS_EVENT = 'widgetic:creator-canvas-widgets';
|
|
148
168
|
|
|
149
169
|
/** Site AppHeader rename — embed is a second Svelte runtime, so mount props do not update. */
|
|
150
170
|
async function applyHostWidgetName(widgetId: string | null | undefined, newName: string): Promise<void> {
|
|
@@ -246,7 +266,7 @@
|
|
|
246
266
|
/**
|
|
247
267
|
* WIDGETIC EDITOR — PropsEditor for design/content property editing
|
|
248
268
|
*/
|
|
249
|
-
import { PropsEditor, setFileBrowserUploadConfig } from '@widgetic/editor';
|
|
269
|
+
import { PropsEditor, setFileBrowserUploadConfig, setGoogleFontsApiKey } from '@widgetic/editor';
|
|
250
270
|
|
|
251
271
|
// API root: embedded hosts pass apiUrl; standalone uses VITE_API_URL / PUBLIC_API_URL.
|
|
252
272
|
// If unset, @widgetic/api-sdk falls back to BASE_PATH = dev-api.widgetic.com (OpenAPI default).
|
|
@@ -549,6 +569,7 @@
|
|
|
549
569
|
let linkedWidgetIds: Set<string> = new Set();
|
|
550
570
|
function refreshLinkedWidgetIds() {
|
|
551
571
|
linkedWidgetIds = canvasRef?.getLinkedWidgetIds?.() ?? new Set();
|
|
572
|
+
emitCanvasWidgetsList('linked-ids');
|
|
552
573
|
}
|
|
553
574
|
|
|
554
575
|
// Delete Widget Shape confirmation dialog state
|
|
@@ -606,6 +627,7 @@
|
|
|
606
627
|
if (!widgetsClient || !hasValidToken) return;
|
|
607
628
|
try {
|
|
608
629
|
console.log('[WidgetCreator] Deleting widget from DB:', widgetId);
|
|
630
|
+
closeWidgetDetailsPanel(widgetId);
|
|
609
631
|
await widgetsClient.deleteWidget({ id: widgetId });
|
|
610
632
|
widgets = widgets.filter((w: any) => w.id !== widgetId);
|
|
611
633
|
if (selectedWidgetId === widgetId) {
|
|
@@ -734,6 +756,9 @@
|
|
|
734
756
|
updateLastUserMessageAttachments?: (attachments: Array<{ id?: string; url: string; file_type?: string; file_name?: string | null }>) => void;
|
|
735
757
|
} | null = null;
|
|
736
758
|
let chatMethodsByWidgetId: Record<string, NonNullable<typeof chatMethods>> = {};
|
|
759
|
+
/** Conversation `completed` flag per widget — ActionBar Complete / Completed. */
|
|
760
|
+
let conversationCompletedByWidgetId: Record<string, boolean> = {};
|
|
761
|
+
let conversationsByWidgetId: Record<string, any[]> = {};
|
|
737
762
|
let pendingChatDraftText = '';
|
|
738
763
|
let chatDraftRestoredForWidgetId: string | null = null;
|
|
739
764
|
/**
|
|
@@ -951,15 +976,8 @@
|
|
|
951
976
|
file_size: attachment.fileSize,
|
|
952
977
|
display_order: i + 1,
|
|
953
978
|
}));
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
codegenWidgetId,
|
|
957
|
-
content,
|
|
958
|
-
undefined,
|
|
959
|
-
persistFileAttachments,
|
|
960
|
-
);
|
|
961
|
-
}
|
|
962
|
-
// Pass upload IDs / images explicitly — clearDraft already wiped Chat's local attachments.
|
|
979
|
+
// Chat.onSaveMessage already persisted this turn. generateCode keeps one
|
|
980
|
+
// ensureCodegenUserMessagePersisted after imagesToSend is finalized.
|
|
963
981
|
await generateCode({
|
|
964
982
|
prompt: content,
|
|
965
983
|
fromChat: true,
|
|
@@ -1046,6 +1064,86 @@
|
|
|
1046
1064
|
currentChatConversationId = conversationId;
|
|
1047
1065
|
}
|
|
1048
1066
|
console.log('[Chat] Active conversation changed for', widgetId.substring(0, 8), ':', conversationId || 'none');
|
|
1067
|
+
const conv = (conversationsByWidgetId[widgetId] || []).find((c) => c.id === conversationId);
|
|
1068
|
+
if (conv) {
|
|
1069
|
+
const completed = !!(conv.completed ?? conv.is_completed ?? conv.isCompleted);
|
|
1070
|
+
conversationCompletedByWidgetId = { ...conversationCompletedByWidgetId, [widgetId]: completed };
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function isConversationRecordCompleted(conversation: any): boolean {
|
|
1075
|
+
return !!(conversation?.completed ?? conversation?.is_completed ?? conversation?.isCompleted);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function isUuid(value: string | null | undefined): value is string {
|
|
1079
|
+
return !!value && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function resolvePersistableConversationId(widgetId: string, preferredId?: string | null): string | null {
|
|
1083
|
+
const knownIds = (conversationsByWidgetId[widgetId] || [])
|
|
1084
|
+
.map((c) => c.id)
|
|
1085
|
+
.filter((id) => isUuid(id));
|
|
1086
|
+
const fromChat = getChatMethodsForWidget(widgetId)?.getConversationId?.();
|
|
1087
|
+
const fromFocus = widgetId === focusedPanelWidgetId ? currentChatConversationId : null;
|
|
1088
|
+
const preferredKnown = [preferredId, fromChat, fromFocus].find((id) => id && knownIds.includes(id));
|
|
1089
|
+
if (preferredKnown) return preferredKnown;
|
|
1090
|
+
if (knownIds[0]) return knownIds[0];
|
|
1091
|
+
if (isUuid(preferredId)) return preferredId;
|
|
1092
|
+
if (isUuid(fromChat)) return fromChat;
|
|
1093
|
+
return isUuid(fromFocus) ? fromFocus : null;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
async function handleChatCompletionToggle(
|
|
1097
|
+
widgetId: string,
|
|
1098
|
+
detail?: { conversationId?: string | null; completed?: boolean },
|
|
1099
|
+
) {
|
|
1100
|
+
const next = typeof detail?.completed === 'boolean'
|
|
1101
|
+
? detail.completed
|
|
1102
|
+
: !conversationCompletedByWidgetId[widgetId];
|
|
1103
|
+
conversationCompletedByWidgetId = { ...conversationCompletedByWidgetId, [widgetId]: next };
|
|
1104
|
+
let conversationId = resolvePersistableConversationId(widgetId, detail?.conversationId);
|
|
1105
|
+
console.log('[Chat] Complete toggle', widgetId.substring(0, 8), next ? 'completed' : 'active', conversationId);
|
|
1106
|
+
if (!conversationsClient) {
|
|
1107
|
+
console.warn('[Chat] Complete toggle skipped persist — missing conversations client');
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
try {
|
|
1111
|
+
if (!conversationId) {
|
|
1112
|
+
const created = await handleCreateConversation(widgetId, 'Widget conversation');
|
|
1113
|
+
conversationId = created?.id || null;
|
|
1114
|
+
console.log('[Chat] Complete created conversation for persist', conversationId);
|
|
1115
|
+
}
|
|
1116
|
+
if (!conversationId || !isUuid(conversationId)) {
|
|
1117
|
+
throw new Error('No persistable conversation id');
|
|
1118
|
+
}
|
|
1119
|
+
try {
|
|
1120
|
+
await conversationsClient.updateConversation({
|
|
1121
|
+
conversationId,
|
|
1122
|
+
updateConversationRequest: { completed: next },
|
|
1123
|
+
});
|
|
1124
|
+
} catch (updateError) {
|
|
1125
|
+
const listed = await handleLoadConversations(widgetId);
|
|
1126
|
+
const fallbackId = (listed || []).map((c) => c.id).find((id) => isUuid(id) && id !== conversationId);
|
|
1127
|
+
if (!fallbackId) throw updateError;
|
|
1128
|
+
console.warn('[Chat] Complete retry with loaded conversation', fallbackId);
|
|
1129
|
+
conversationId = fallbackId;
|
|
1130
|
+
await conversationsClient.updateConversation({
|
|
1131
|
+
conversationId,
|
|
1132
|
+
updateConversationRequest: { completed: next },
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
conversationsByWidgetId = {
|
|
1136
|
+
...conversationsByWidgetId,
|
|
1137
|
+
[widgetId]: (conversationsByWidgetId[widgetId] || []).map((c) =>
|
|
1138
|
+
c.id === conversationId ? { ...c, completed: next } : c,
|
|
1139
|
+
),
|
|
1140
|
+
};
|
|
1141
|
+
console.log('[Chat] Conversation marked', next ? 'completed' : 'active', conversationId);
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
console.error('[Chat] Failed to persist conversation completed:', error);
|
|
1144
|
+
showToast('error', 'Could not update conversation status');
|
|
1145
|
+
conversationCompletedByWidgetId = { ...conversationCompletedByWidgetId, [widgetId]: !next };
|
|
1146
|
+
}
|
|
1049
1147
|
}
|
|
1050
1148
|
|
|
1051
1149
|
/** Sync unsent chat draft + attachments to the durable per-widget draft (or clear when empty). */
|
|
@@ -1196,12 +1294,19 @@
|
|
|
1196
1294
|
if (compressedPersistImages.length === 0 && persistFileItems.length === 0) {
|
|
1197
1295
|
console.warn('[Codegen] Persist user message has no attachment URLs for widget', widgetId?.substring(0, 8));
|
|
1198
1296
|
}
|
|
1199
|
-
const
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1297
|
+
const fileHasImage = persistFileItems.some((att) => {
|
|
1298
|
+
const type = String(att.file_type || '');
|
|
1299
|
+
const url = String(att.url || '');
|
|
1300
|
+
return type.startsWith('image/') || url.startsWith('data:image/') || /\.(png|jpe?g|gif|webp|svg)(\?|$)/i.test(url);
|
|
1301
|
+
});
|
|
1302
|
+
const imagePersistItems = fileHasImage
|
|
1303
|
+
? []
|
|
1304
|
+
: compressedPersistImages.map((url, i) => ({
|
|
1305
|
+
url,
|
|
1306
|
+
file_type: guessAttachmentFileType(url, 'design-screenshot.png', 'image/png'),
|
|
1307
|
+
file_name: 'design-screenshot.png',
|
|
1308
|
+
display_order: i + 1,
|
|
1309
|
+
}));
|
|
1205
1310
|
const persistAttachments = [...persistFileItems, ...imagePersistItems].map((att, i) => ({
|
|
1206
1311
|
...att,
|
|
1207
1312
|
display_order: i + 1,
|
|
@@ -1227,8 +1332,11 @@
|
|
|
1227
1332
|
.map((a: { url?: string }) => a.url)
|
|
1228
1333
|
.filter((url: string | undefined): url is string => !!url),
|
|
1229
1334
|
);
|
|
1335
|
+
const existingHasRemoteImage = [...existingUrls].some((url) => /^https?:\/\//i.test(url));
|
|
1230
1336
|
const missingPersist = persistAttachments.filter((att) => {
|
|
1231
1337
|
if (att.url && existingUrls.has(att.url)) return false;
|
|
1338
|
+
if (existingHasRemoteImage && typeof att.url === 'string' && att.url.startsWith('data:')) return false;
|
|
1339
|
+
if (existingHasRemoteImage && String(att.file_type || '').startsWith('image/')) return false;
|
|
1232
1340
|
return true;
|
|
1233
1341
|
});
|
|
1234
1342
|
let savedAttachmentCount = targetUser?.attachments?.length || 0;
|
|
@@ -1366,6 +1474,13 @@
|
|
|
1366
1474
|
});
|
|
1367
1475
|
const json = await response.raw.json();
|
|
1368
1476
|
const conversations = normalizeConversationRecords(json);
|
|
1477
|
+
conversationsByWidgetId = { ...conversationsByWidgetId, [contextId]: conversations };
|
|
1478
|
+
if (conversations[0]) {
|
|
1479
|
+
conversationCompletedByWidgetId = {
|
|
1480
|
+
...conversationCompletedByWidgetId,
|
|
1481
|
+
[contextId]: isConversationRecordCompleted(conversations[0]),
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1369
1484
|
console.log('[Chat→Backend] Loaded conversations for widget:', contextId, conversations.length);
|
|
1370
1485
|
return conversations;
|
|
1371
1486
|
} catch (error) {
|
|
@@ -1481,6 +1596,15 @@
|
|
|
1481
1596
|
const conversation = json?.data || json;
|
|
1482
1597
|
const conversationId = conversation?.id || conversation?.conversation_id || conversation?.conversationId;
|
|
1483
1598
|
console.log('[Chat→Backend] Created conversation:', conversationId);
|
|
1599
|
+
if (conversationId && contextId) {
|
|
1600
|
+
conversationsByWidgetId = {
|
|
1601
|
+
...conversationsByWidgetId,
|
|
1602
|
+
[contextId]: [
|
|
1603
|
+
{ ...conversation, id: conversationId },
|
|
1604
|
+
...(conversationsByWidgetId[contextId] || []).filter((c) => c.id !== conversationId),
|
|
1605
|
+
],
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1484
1608
|
return conversationId ? { ...conversation, id: conversationId } : conversation;
|
|
1485
1609
|
} catch (error) {
|
|
1486
1610
|
console.error('[Chat→Backend] Failed to create conversation:', error);
|
|
@@ -1763,6 +1887,11 @@
|
|
|
1763
1887
|
let publishingWidgetId: string | null = null;
|
|
1764
1888
|
let publishWidgetStatus: string | null = null;
|
|
1765
1889
|
|
|
1890
|
+
$: if (isPublishing && !publishingWidgetId) {
|
|
1891
|
+
console.warn('[Publish] Clearing stuck isPublishing without publishingWidgetId');
|
|
1892
|
+
isPublishing = false;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1766
1895
|
// Cross-tab operation guards — detect when another tab is generating/publishing on the same widget
|
|
1767
1896
|
let isGeneratingInOtherTab = false;
|
|
1768
1897
|
let isPublishingInOtherTab = false;
|
|
@@ -1979,6 +2108,50 @@
|
|
|
1979
2108
|
return null;
|
|
1980
2109
|
}
|
|
1981
2110
|
|
|
2111
|
+
/** If the widget only has titleFont, expose subtitleFont in Edit (same FontIC). */
|
|
2112
|
+
function ensureSubtitleFontInDesignSchema(schema: unknown): unknown {
|
|
2113
|
+
if (!schema) return schema;
|
|
2114
|
+
try {
|
|
2115
|
+
const clone = JSON.parse(JSON.stringify(schema));
|
|
2116
|
+
const groups = Array.isArray(clone) ? clone : clone.componentGroups;
|
|
2117
|
+
if (!Array.isArray(groups) || groups.length === 0) return schema;
|
|
2118
|
+
const allProps = groups.flatMap((group: { components?: Array<{ properties?: unknown[] }> }) =>
|
|
2119
|
+
(group.components || []).flatMap((component) => component.properties || [])
|
|
2120
|
+
);
|
|
2121
|
+
const hasSubtitle = allProps.some((prop: { name?: string; id?: string }) =>
|
|
2122
|
+
/subtitleFont/i.test(String(prop?.name || prop?.id || ''))
|
|
2123
|
+
);
|
|
2124
|
+
if (hasSubtitle) return clone;
|
|
2125
|
+
const titleProp = allProps.find((prop: { name?: string; id?: string; inputController?: { type?: string } }) => {
|
|
2126
|
+
const key = String(prop?.name || prop?.id || '');
|
|
2127
|
+
const type = String(prop?.inputController?.type || '').toLowerCase();
|
|
2128
|
+
return type === 'font' && /titleFont|^font$/i.test(key);
|
|
2129
|
+
});
|
|
2130
|
+
if (!titleProp || typeof titleProp !== 'object') return clone;
|
|
2131
|
+
const firstComponent = groups[0]?.components?.[0];
|
|
2132
|
+
if (!firstComponent) return clone;
|
|
2133
|
+
firstComponent.properties = Array.isArray(firstComponent.properties) ? firstComponent.properties : [];
|
|
2134
|
+
const subtitleProp = {
|
|
2135
|
+
...JSON.parse(JSON.stringify(titleProp)),
|
|
2136
|
+
id: 'subtitleFont',
|
|
2137
|
+
name: 'subtitleFont',
|
|
2138
|
+
description: 'Subtitle Font',
|
|
2139
|
+
};
|
|
2140
|
+
const titleIndex = firstComponent.properties.findIndex(
|
|
2141
|
+
(prop: { name?: string; id?: string }) =>
|
|
2142
|
+
(prop?.name || prop?.id) === ((titleProp as { name?: string; id?: string }).name
|
|
2143
|
+
|| (titleProp as { name?: string; id?: string }).id)
|
|
2144
|
+
);
|
|
2145
|
+
if (titleIndex >= 0) firstComponent.properties.splice(titleIndex + 1, 0, subtitleProp);
|
|
2146
|
+
else firstComponent.properties.push(subtitleProp);
|
|
2147
|
+
console.log('[CompositionEditor] Added subtitleFont FontIC next to titleFont');
|
|
2148
|
+
return Array.isArray(schema) ? clone : clone;
|
|
2149
|
+
} catch (error) {
|
|
2150
|
+
console.warn('[CompositionEditor] subtitleFont schema inject skipped:', error);
|
|
2151
|
+
return schema;
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
|
|
1982
2155
|
function normalizeContentSchemaFromApiResponse(contentRes: unknown): any {
|
|
1983
2156
|
if (!contentRes) return null;
|
|
1984
2157
|
const payload = (contentRes as { data?: unknown })?.data ?? contentRes;
|
|
@@ -2108,14 +2281,20 @@
|
|
|
2108
2281
|
}
|
|
2109
2282
|
|
|
2110
2283
|
/** Load design & content schemas for a widget — per-panel; never wipes other panels. */
|
|
2111
|
-
async function loadWidgetSchemas(widgetId: string): Promise<void> {
|
|
2284
|
+
async function loadWidgetSchemas(widgetId: string, options?: { force?: boolean }): Promise<void> {
|
|
2112
2285
|
if (!widgetsClient || !hasValidToken) {
|
|
2113
2286
|
patchPanelSnapshot(widgetId, { compositionSchemasLoading: false, schemaLoadStartedAt: null });
|
|
2114
2287
|
return;
|
|
2115
2288
|
}
|
|
2116
2289
|
|
|
2290
|
+
if (options?.force) {
|
|
2291
|
+
widgetSchemaCache.delete(widgetId);
|
|
2292
|
+
cancelPanelSchemaLoad(widgetId);
|
|
2293
|
+
abandonSchemaLoadAttempt(widgetId);
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2117
2296
|
const existingLoad = schemaLoadPromises.get(widgetId);
|
|
2118
|
-
if (existingLoad) {
|
|
2297
|
+
if (existingLoad && !options?.force) {
|
|
2119
2298
|
const snap = getPanelState(widgetId);
|
|
2120
2299
|
if (!isStalePanelSchemaLoading(widgetId, snap)) return existingLoad;
|
|
2121
2300
|
cancelPanelSchemaLoad(widgetId);
|
|
@@ -2131,7 +2310,7 @@
|
|
|
2131
2310
|
|| existing.compositionContentSchema
|
|
2132
2311
|
);
|
|
2133
2312
|
|
|
2134
|
-
if (hasCachedSchemas) {
|
|
2313
|
+
if (hasCachedSchemas && !options?.force) {
|
|
2135
2314
|
const designSchema = cached?.design ?? existing.compositionDesignSchema;
|
|
2136
2315
|
const contentSchema = cached?.content ?? existing.compositionContentSchema;
|
|
2137
2316
|
const schemaError = cached?.error ?? existing.compositionSchemasError;
|
|
@@ -2226,7 +2405,9 @@
|
|
|
2226
2405
|
'Content schema',
|
|
2227
2406
|
);
|
|
2228
2407
|
|
|
2229
|
-
designSchema =
|
|
2408
|
+
designSchema = ensureSubtitleFontInDesignSchema(
|
|
2409
|
+
normalizeDesignSchemaFromApiResponse(designRes)
|
|
2410
|
+
);
|
|
2230
2411
|
contentSchema = normalizeContentSchemaFromApiResponse(contentRes);
|
|
2231
2412
|
|
|
2232
2413
|
if (!designSchema && !contentSchema) {
|
|
@@ -2330,7 +2511,7 @@
|
|
|
2330
2511
|
} else {
|
|
2331
2512
|
patchPanelSnapshot(widgetId, { liveDesignValues: defaults });
|
|
2332
2513
|
}
|
|
2333
|
-
getWidgetDetailsRef(widgetId)?.sendMessageToPreview({ type: 'widgetic:update', design: defaults });
|
|
2514
|
+
getWidgetDetailsRef(widgetId)?.sendMessageToPreview({ type: 'widgetic:update', design: flattenDesignValuesForPreview(defaults) });
|
|
2334
2515
|
console.log('[Create Preview] Applied design schema defaults:', Object.keys(defaults));
|
|
2335
2516
|
}
|
|
2336
2517
|
const contentSchema = getPanelDisplayState(widgetId).compositionContentSchema;
|
|
@@ -2468,15 +2649,22 @@
|
|
|
2468
2649
|
const versionedUrl = `${cdnBase}/widgets/${widgetId}/v${version}/widget.html`;
|
|
2469
2650
|
|
|
2470
2651
|
if (jsPath) {
|
|
2652
|
+
const trimmed = jsPath.replace(/\/+$/, '');
|
|
2653
|
+
let resolved = trimmed
|
|
2654
|
+
.replace(/\/widget\.js$/i, '/widget.html')
|
|
2655
|
+
.replace(/\.js$/i, '.html');
|
|
2656
|
+
if (!/\.html$/i.test(resolved)) {
|
|
2657
|
+
resolved = `${resolved}/widget.html`;
|
|
2658
|
+
}
|
|
2659
|
+
if (resolved.startsWith('/')) {
|
|
2660
|
+
resolved = `${cdnBase}${resolved}`;
|
|
2661
|
+
}
|
|
2471
2662
|
const jsPathVersionMatch = jsPath.match(/\/v(\d+)(?:\/|$)/);
|
|
2472
2663
|
const jsPathVersion = jsPathVersionMatch ? parseInt(jsPathVersionMatch[1], 10) : null;
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
if (resolved.startsWith('/')) {
|
|
2478
|
-
resolved = `${cdnBase}${resolved}`;
|
|
2479
|
-
}
|
|
2664
|
+
const jsPathWidgetId = (resolved.match(/\/widgets\/([^/]+)\//i) || [])[1] || null;
|
|
2665
|
+
const pointsAtOtherWidget = !!(jsPathWidgetId && jsPathWidgetId !== widgetId);
|
|
2666
|
+
if (pointsAtOtherWidget || jsPathVersion === version || /\/widgets\//i.test(resolved)) {
|
|
2667
|
+
console.log('[Publish] Using js_path artifact URL', resolved, pointsAtOtherWidget ? '(source widget)' : '');
|
|
2480
2668
|
return resolved;
|
|
2481
2669
|
}
|
|
2482
2670
|
}
|
|
@@ -2507,6 +2695,68 @@
|
|
|
2507
2695
|
let suppressNextBuildFromRepoChatMessage = false;
|
|
2508
2696
|
let _isRestoringCheckpoint = false;
|
|
2509
2697
|
|
|
2698
|
+
function mapFontWeightToCss(weight: string | undefined): string {
|
|
2699
|
+
const name = String(weight || 'Regular').toLowerCase().replace(/[\s_-]/g, '');
|
|
2700
|
+
const table: Record<string, string> = {
|
|
2701
|
+
thin: '100', hairline: '100',
|
|
2702
|
+
extralight: '200', ultralight: '200',
|
|
2703
|
+
light: '300',
|
|
2704
|
+
regular: '400', normal: '400',
|
|
2705
|
+
medium: '500',
|
|
2706
|
+
semibold: '600', demibold: '600',
|
|
2707
|
+
bold: '700',
|
|
2708
|
+
extrabold: '800', ultrabold: '800',
|
|
2709
|
+
black: '900', heavy: '900',
|
|
2710
|
+
};
|
|
2711
|
+
if (/^\d{3}$/.test(name)) return name;
|
|
2712
|
+
return table[name] || '400';
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
function mapAspectRatioToCss(raw: string): string {
|
|
2716
|
+
const key = raw.trim().toLowerCase();
|
|
2717
|
+
const table: Record<string, string> = {
|
|
2718
|
+
square: '1/1',
|
|
2719
|
+
'1:1': '1/1',
|
|
2720
|
+
'1/1': '1/1',
|
|
2721
|
+
landscape: '4/3',
|
|
2722
|
+
'4:3': '4/3',
|
|
2723
|
+
'4/3': '4/3',
|
|
2724
|
+
wide: '16/9',
|
|
2725
|
+
'16:9': '16/9',
|
|
2726
|
+
'16/9': '16/9',
|
|
2727
|
+
portrait: '3/4',
|
|
2728
|
+
'3:4': '3/4',
|
|
2729
|
+
'3/4': '3/4',
|
|
2730
|
+
};
|
|
2731
|
+
return table[key] || raw;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
/** CSS custom properties for widgetic:update — FontIC objects and dropdown {value} become strings. */
|
|
2735
|
+
function flattenDesignValuesForPreview(design: Record<string, unknown>): Record<string, string> {
|
|
2736
|
+
const out: Record<string, string> = {};
|
|
2737
|
+
for (const [key, value] of Object.entries(design || {})) {
|
|
2738
|
+
if (value && typeof value === 'object' && 'family' in (value as Record<string, unknown>)) {
|
|
2739
|
+
const font = value as { family?: string; weight?: string };
|
|
2740
|
+
const family = String(font.family || 'Inter');
|
|
2741
|
+
out[key] = `"${family}", sans-serif`;
|
|
2742
|
+
out[`${key}Family`] = family;
|
|
2743
|
+
out[`${key}Weight`] = mapFontWeightToCss(font.weight);
|
|
2744
|
+
continue;
|
|
2745
|
+
}
|
|
2746
|
+
if (value && typeof value === 'object' && 'value' in (value as Record<string, unknown>)) {
|
|
2747
|
+
out[key] = mapAspectRatioToCss(String((value as { value?: unknown }).value ?? ''));
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2750
|
+
if (typeof value === 'string') {
|
|
2751
|
+
out[key] = /aspect/i.test(key) ? mapAspectRatioToCss(value) : value;
|
|
2752
|
+
continue;
|
|
2753
|
+
}
|
|
2754
|
+
if (value == null) continue;
|
|
2755
|
+
out[key] = String(value);
|
|
2756
|
+
}
|
|
2757
|
+
return out;
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2510
2760
|
/** Handle property change from PropsEditor — send to widget preview iframe */
|
|
2511
2761
|
function handleEditorPropertyChange(event: CustomEvent<{ schema: string; propertyId: string; value: unknown; opacity?: number; itemId?: string }>) {
|
|
2512
2762
|
const { schema, propertyId, value, itemId } = event.detail;
|
|
@@ -2516,7 +2766,7 @@
|
|
|
2516
2766
|
liveDesignValues = { ...liveDesignValues, [propertyId]: value };
|
|
2517
2767
|
getWidgetDetailsRef(focusedPanelWidgetId)?.sendMessageToPreview({
|
|
2518
2768
|
type: 'widgetic:update',
|
|
2519
|
-
design: liveDesignValues
|
|
2769
|
+
design: flattenDesignValuesForPreview(liveDesignValues)
|
|
2520
2770
|
});
|
|
2521
2771
|
} else if (schema === 'content') {
|
|
2522
2772
|
if (itemId) {
|
|
@@ -2699,11 +2949,16 @@
|
|
|
2699
2949
|
|
|
2700
2950
|
/** Push composition overlay to a specific panel preview iframe. */
|
|
2701
2951
|
function applyPanelCompositionToPreview(widgetId: string): void {
|
|
2702
|
-
const ps = getPanelDisplayState(widgetId);
|
|
2703
2952
|
const panelRef = getWidgetDetailsRef(widgetId);
|
|
2704
2953
|
if (!panelRef) return;
|
|
2954
|
+
const step = widgetId === focusedPanelWidgetId ? widgetDetailsStep : 'create';
|
|
2955
|
+
if (step === 'create') {
|
|
2956
|
+
console.log('[Preview] Skip composition overlay on Create step', widgetId?.substring(0, 8));
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
const ps = getPanelDisplayState(widgetId);
|
|
2705
2960
|
if (Object.keys(ps.liveDesignValues).length > 0) {
|
|
2706
|
-
panelRef.sendMessageToPreview({ type: 'widgetic:update', design: ps.liveDesignValues });
|
|
2961
|
+
panelRef.sendMessageToPreview({ type: 'widgetic:update', design: flattenDesignValuesForPreview(ps.liveDesignValues as Record<string, unknown>) });
|
|
2707
2962
|
}
|
|
2708
2963
|
if (Object.keys(ps.liveContentValues).length > 0) {
|
|
2709
2964
|
panelRef.sendMessageToPreview({ type: 'widgetic:update', content: ps.liveContentValues });
|
|
@@ -2965,6 +3220,10 @@
|
|
|
2965
3220
|
/** Send current composition design/content values to the preview iframe via postMessage.
|
|
2966
3221
|
* Called on: composition select, iframe ready, and step switch to Edit/Embed. */
|
|
2967
3222
|
function applyCompositionToPreview() {
|
|
3223
|
+
if (widgetDetailsStep === 'create') {
|
|
3224
|
+
console.log('[Preview] Skip composition overlay on Create step');
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
2968
3227
|
if (Object.keys(liveDesignValues).length > 0) {
|
|
2969
3228
|
widgetDetails?.sendMessageToPreview({ type: 'widgetic:update', design: liveDesignValues });
|
|
2970
3229
|
}
|
|
@@ -3438,19 +3697,21 @@
|
|
|
3438
3697
|
}
|
|
3439
3698
|
|
|
3440
3699
|
async function handleHostDuplicateWidgetEvent(): Promise<void> {
|
|
3441
|
-
const widgetId = resolveHostTargetWidgetId();
|
|
3442
|
-
if (!widgetId ||
|
|
3700
|
+
const widgetId = canvasPrimaryWidgetId || resolveHostTargetWidgetId();
|
|
3701
|
+
if (!widgetId || isDraftConvertId(widgetId)) {
|
|
3443
3702
|
showToast('warning', 'Select a widget first', {
|
|
3444
|
-
description: '
|
|
3703
|
+
description: 'Duplicate from the header copies the widget in the title.',
|
|
3445
3704
|
});
|
|
3446
3705
|
return;
|
|
3447
3706
|
}
|
|
3448
|
-
|
|
3449
|
-
if (typeof canvasRef.duplicateSelectedObjects === 'function') {
|
|
3450
|
-
|
|
3451
|
-
|
|
3707
|
+
console.log('[CreatorApp] Host Duplicate — primary widget', widgetId.substring(0, 8));
|
|
3708
|
+
if (canvasRef?.selectWidgetShape && typeof canvasRef.duplicateSelectedObjects === 'function') {
|
|
3709
|
+
if (canvasRef.selectWidgetShape(widgetId)) {
|
|
3710
|
+
await canvasRef.duplicateSelectedObjects();
|
|
3711
|
+
return;
|
|
3712
|
+
}
|
|
3452
3713
|
}
|
|
3453
|
-
|
|
3714
|
+
await handleWidgetShapeDuplicated(widgetId, null);
|
|
3454
3715
|
}
|
|
3455
3716
|
|
|
3456
3717
|
async function handleHostDeleteWidgetEvent(): Promise<void> {
|
|
@@ -3470,6 +3731,15 @@
|
|
|
3470
3731
|
}
|
|
3471
3732
|
}
|
|
3472
3733
|
|
|
3734
|
+
function emitCreatorSaveState(dirty: boolean): void {
|
|
3735
|
+
if (typeof window === 'undefined' || !embedded) return;
|
|
3736
|
+
window.dispatchEvent(new CustomEvent('widgetic:creator-save-state', { detail: { dirty } }));
|
|
3737
|
+
}
|
|
3738
|
+
|
|
3739
|
+
function handleHostSaveCanvasEvent(): void {
|
|
3740
|
+
canvasRef?.saveNow?.();
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3473
3743
|
async function persistPrimaryWidgetId(widgetId: string) {
|
|
3474
3744
|
if (!widgetId) return;
|
|
3475
3745
|
canvasPrimaryWidgetId = widgetId;
|
|
@@ -3485,7 +3755,6 @@
|
|
|
3485
3755
|
}
|
|
3486
3756
|
}
|
|
3487
3757
|
const widget = getWidgetById(widgetId);
|
|
3488
|
-
if (widget) notifyHostWidgetName(widget);
|
|
3489
3758
|
if (embedded && typeof window !== 'undefined') {
|
|
3490
3759
|
try {
|
|
3491
3760
|
const url = new URL(window.location.href);
|
|
@@ -3497,12 +3766,110 @@
|
|
|
3497
3766
|
console.warn('[CreatorApp] Failed to sync primary widget_id in URL:', err);
|
|
3498
3767
|
}
|
|
3499
3768
|
}
|
|
3769
|
+
if (widget) notifyHostWidgetName(widget);
|
|
3770
|
+
emitCanvasWidgetsList('primary-changed');
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
function emitCanvasWidgetsList(reason: string) {
|
|
3774
|
+
if (!embedded || typeof window === 'undefined') return;
|
|
3775
|
+
const ids = canvasRef?.getLinkedWidgetIds?.() ?? linkedWidgetIds ?? new Set<string>();
|
|
3776
|
+
const items: { id: string; name: string }[] = [];
|
|
3777
|
+
for (const id of ids) {
|
|
3778
|
+
const widget = getWidgetById(id);
|
|
3779
|
+
items.push({
|
|
3780
|
+
id,
|
|
3781
|
+
name: (widget?.name || '').trim() || 'Untitled Widget'
|
|
3782
|
+
});
|
|
3783
|
+
}
|
|
3784
|
+
if (canvasPrimaryWidgetId && !items.some((item) => item.id === canvasPrimaryWidgetId)) {
|
|
3785
|
+
const primary = getWidgetById(canvasPrimaryWidgetId);
|
|
3786
|
+
if (primary?.id) {
|
|
3787
|
+
items.unshift({
|
|
3788
|
+
id: primary.id,
|
|
3789
|
+
name: (primary.name || '').trim() || 'Untitled Widget'
|
|
3790
|
+
});
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
const primaryWidgetId = canvasPrimaryWidgetId || '';
|
|
3794
|
+
console.log('[CreatorApp] Emit canvas widgets for header', reason, items.length, primaryWidgetId);
|
|
3795
|
+
window.dispatchEvent(new CustomEvent(CREATOR_CANVAS_WIDGETS_EVENT, {
|
|
3796
|
+
detail: { widgets: items, primaryWidgetId }
|
|
3797
|
+
}));
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
function handleHostRequestCanvasWidgetsEvent() {
|
|
3801
|
+
emitCanvasWidgetsList('host-request');
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3804
|
+
function handleHostSetPrimaryWidgetEvent(event: Event) {
|
|
3805
|
+
const widgetId = (event as CustomEvent).detail?.widgetId;
|
|
3806
|
+
if (!widgetId || typeof widgetId !== 'string') {
|
|
3807
|
+
console.warn('[CreatorApp] Host set primary ignored — missing widgetId');
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
console.log('[CreatorApp] Host set primary widget', widgetId.substring(0, 8));
|
|
3811
|
+
canvasRef?.setPrimaryWidgetShape?.(widgetId);
|
|
3812
|
+
canvasRef?.selectWidgetShape?.(widgetId);
|
|
3813
|
+
void persistPrimaryWidgetId(widgetId);
|
|
3500
3814
|
}
|
|
3501
3815
|
|
|
3502
3816
|
function handleSetPrimaryWidget(widgetId: string) {
|
|
3503
3817
|
void persistPrimaryWidgetId(widgetId);
|
|
3504
3818
|
}
|
|
3505
3819
|
|
|
3820
|
+
async function hydrateCanvasPrimaryWidget(linkedIds?: string[]): Promise<string | null> {
|
|
3821
|
+
const targetCanvasId = (typeof canvasId === 'string' && canvasId.trim()) ? canvasId.trim() : null;
|
|
3822
|
+
if (!targetCanvasId) return canvasPrimaryWidgetId || null;
|
|
3823
|
+
let primaryId = await resolvePrimaryWidgetIdForCanvas(targetCanvasId);
|
|
3824
|
+
if (!primaryId) {
|
|
3825
|
+
const fromLinks = (linkedIds || []).filter((id) => id && !isDraftConvertId(id) && !String(id).startsWith('draft'));
|
|
3826
|
+
const fromList = widgets
|
|
3827
|
+
.filter((w: any) => {
|
|
3828
|
+
const cid = w.canvasId || w.canvas_id;
|
|
3829
|
+
return cid === targetCanvasId && w.id && !isDraftConvertId(w.id);
|
|
3830
|
+
})
|
|
3831
|
+
.map((w: any) => w.id);
|
|
3832
|
+
primaryId = fromLinks[0] || fromList[0] || null;
|
|
3833
|
+
if (primaryId) {
|
|
3834
|
+
console.log('[CreatorApp] Healing empty primary_widget_id →', primaryId.substring(0, 8));
|
|
3835
|
+
await persistPrimaryWidgetId(primaryId);
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
if (primaryId) {
|
|
3839
|
+
const widget = getWidgetById(primaryId);
|
|
3840
|
+
if (widget) notifyHostWidgetName(widget);
|
|
3841
|
+
emitCanvasWidgetsList('hydrate-primary');
|
|
3842
|
+
}
|
|
3843
|
+
return primaryId || canvasPrimaryWidgetId || null;
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
async function handleHostMoveCanvasFolderEvent(event: Event) {
|
|
3847
|
+
const folderId = (event as CustomEvent).detail?.folderId;
|
|
3848
|
+
if (!folderId || typeof folderId !== 'string' || !widgetsClient) return;
|
|
3849
|
+
const targetCanvasId = (typeof canvasId === 'string' && canvasId.trim()) ? canvasId.trim() : null;
|
|
3850
|
+
let primaryId = canvasPrimaryWidgetId || (targetCanvasId ? await resolvePrimaryWidgetIdForCanvas(targetCanvasId) : null);
|
|
3851
|
+
if (!primaryId) {
|
|
3852
|
+
primaryId = await hydrateCanvasPrimaryWidget();
|
|
3853
|
+
}
|
|
3854
|
+
const ids = new Set<string>();
|
|
3855
|
+
if (primaryId) ids.add(primaryId);
|
|
3856
|
+
for (const w of widgets) {
|
|
3857
|
+
const cid = (w as any).canvasId || (w as any).canvas_id;
|
|
3858
|
+
if (targetCanvasId && cid === targetCanvasId && w.id) ids.add(w.id);
|
|
3859
|
+
}
|
|
3860
|
+
console.log('[CreatorApp] Move canvas widgets to folder', folderId, [...ids].map((id) => id.substring(0, 8)));
|
|
3861
|
+
for (const id of ids) {
|
|
3862
|
+
try {
|
|
3863
|
+
await widgetsClient.updateWidget({
|
|
3864
|
+
id,
|
|
3865
|
+
updateWidgetRequest: { projectId: folderId, projectFolder: folderId }
|
|
3866
|
+
});
|
|
3867
|
+
} catch (err) {
|
|
3868
|
+
console.warn('[CreatorApp] Failed to move widget to folder', id.substring(0, 8), err);
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3506
3873
|
// Preview Modal State Variables
|
|
3507
3874
|
let previewIframe: HTMLIFrameElement | null = null;
|
|
3508
3875
|
const { DEV: isDevMode } = import.meta.env;
|
|
@@ -4007,18 +4374,11 @@
|
|
|
4007
4374
|
const { loaded, widgetIds: widgetIdsOnCanvas } = await tryLoadCanvasState();
|
|
4008
4375
|
await syncWidgetsWithCanvas(widgetIdsOnCanvas);
|
|
4009
4376
|
await maybeAutoRestoreWidgetShapes();
|
|
4010
|
-
// Backfill widget name + thumbnail on shapes that were saved before those
|
|
4011
|
-
// fields existed on WidgetShape (fixes "Widget" placeholder on old canvases).
|
|
4012
4377
|
backfillWidgetShapeMetadata();
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
selectedWidgetId;
|
|
4018
|
-
if (primaryId && !isConvertOrDraftPanelOpen()) {
|
|
4019
|
-
canvasRef.setPrimaryWidgetShape?.(primaryId);
|
|
4020
|
-
canvasRef.selectWidgetShape(primaryId);
|
|
4021
|
-
}
|
|
4378
|
+
const primaryId = await hydrateCanvasPrimaryWidget(widgetIdsOnCanvas);
|
|
4379
|
+
if (loaded && canvasRef && primaryId && !isConvertOrDraftPanelOpen()) {
|
|
4380
|
+
canvasRef.setPrimaryWidgetShape?.(primaryId);
|
|
4381
|
+
canvasRef.selectWidgetShape(primaryId);
|
|
4022
4382
|
}
|
|
4023
4383
|
} catch (error) {
|
|
4024
4384
|
console.error('Widget Creator: Load canvases error:', error);
|
|
@@ -5282,20 +5642,19 @@
|
|
|
5282
5642
|
}
|
|
5283
5643
|
}
|
|
5284
5644
|
|
|
5285
|
-
/** After hydrate,
|
|
5286
|
-
*
|
|
5287
|
-
* ("Newer code than Live") can detect unpublished code even without recent codegen locally. */
|
|
5645
|
+
/** After hydrate, probe GitLab HEAD only when this widget has no codegen commit yet.
|
|
5646
|
+
* Do not promote HEAD over lastCommitId — that marks a new publish version on every Details open. */
|
|
5288
5647
|
async function reconcileWidgetHeadCommit(widgetId: string): Promise<void> {
|
|
5289
5648
|
const currentPanelLastCommit = getPanelState(widgetId).lastCommitId;
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5649
|
+
if (currentPanelLastCommit) {
|
|
5650
|
+
console.log(
|
|
5651
|
+
`[Version] Skip HEAD probe for "${widgetId}": lastCommitId already ${currentPanelLastCommit.substring(0, 8)}`
|
|
5652
|
+
);
|
|
5293
5653
|
return;
|
|
5294
5654
|
}
|
|
5295
5655
|
const headSha = await fetchRepoHeadCommitSha(widgetId);
|
|
5296
5656
|
if (!headSha) return;
|
|
5297
|
-
|
|
5298
|
-
console.log(`[Version] Repo HEAD probe for "${widgetId}": ${headSha.substring(0, 8)} (was ${currentPanelLastCommit?.substring(0, 8) ?? 'null'})`);
|
|
5657
|
+
console.log(`[Version] Repo HEAD probe for "${widgetId}": ${headSha.substring(0, 8)} (was null)`);
|
|
5299
5658
|
patchPanelSnapshot(widgetId, { lastCommitId: headSha, hasGeneratedCode: true });
|
|
5300
5659
|
saveCommitIdForWidget(widgetId, headSha);
|
|
5301
5660
|
if (widgetId === focusedPanelWidgetId) {
|
|
@@ -5568,9 +5927,11 @@
|
|
|
5568
5927
|
// Ensure we have an id
|
|
5569
5928
|
const newWidget = response.data || response; // handle envelope or direct
|
|
5570
5929
|
if (newWidget?.id) {
|
|
5571
|
-
// Optimistically add to list and select
|
|
5572
5930
|
widgets = [{ ...newWidget }, ...widgets];
|
|
5573
5931
|
selectWidget(newWidget);
|
|
5932
|
+
if (!canvasPrimaryWidgetId) {
|
|
5933
|
+
void persistPrimaryWidgetId(newWidget.id);
|
|
5934
|
+
}
|
|
5574
5935
|
authStatus = 'Widget created.';
|
|
5575
5936
|
} else {
|
|
5576
5937
|
authStatus = 'Widget created but response missing id.';
|
|
@@ -6553,16 +6914,6 @@ Requirements:
|
|
|
6553
6914
|
: 'Preparing code generation...',
|
|
6554
6915
|
});
|
|
6555
6916
|
|
|
6556
|
-
// Persist chat attachments before repo/codegen so reopen + failed GLM still show the file.
|
|
6557
|
-
if (!options.compileFixOnly) {
|
|
6558
|
-
await ensureCodegenUserMessagePersisted(
|
|
6559
|
-
generationWidgetId,
|
|
6560
|
-
prompt,
|
|
6561
|
-
options.promptImagesOverride,
|
|
6562
|
-
options.persistFileAttachments,
|
|
6563
|
-
);
|
|
6564
|
-
}
|
|
6565
|
-
|
|
6566
6917
|
// Keep preview tab active so user sees the "Generating..." overlay.
|
|
6567
6918
|
// Console logs are still accessible via the Console tab button.
|
|
6568
6919
|
getWidgetDetailsRef(generationWidgetId)?.setInlinePanelView('preview');
|
|
@@ -6929,6 +7280,11 @@ Requirements:
|
|
|
6929
7280
|
}
|
|
6930
7281
|
})();
|
|
6931
7282
|
}
|
|
7283
|
+
if (generationWidgetId) {
|
|
7284
|
+
console.log('[CompositionEditor] Reloading schemas after codegen', generationWidgetId.substring(0, 8));
|
|
7285
|
+
void loadWidgetSchemas(generationWidgetId, { force: true });
|
|
7286
|
+
}
|
|
7287
|
+
|
|
6932
7288
|
const completedRepoId = resolveRepositoryIdForWidget(generationWidgetId);
|
|
6933
7289
|
if (completedRepoId) {
|
|
6934
7290
|
stampWidgetRepositoryId(generationWidgetId, completedRepoId);
|
|
@@ -7160,7 +7516,11 @@ Requirements:
|
|
|
7160
7516
|
commitSha?: string | null
|
|
7161
7517
|
): void {
|
|
7162
7518
|
const jsPath = resolvePublishedJsPath(widgetId, version, artifactUrl);
|
|
7163
|
-
const
|
|
7519
|
+
const prevSnap = getPanelState(widgetId);
|
|
7520
|
+
const normalizedCommit =
|
|
7521
|
+
normalizeCommitSha(commitSha)
|
|
7522
|
+
|| normalizeCommitSha(prevSnap.lastCommitId)
|
|
7523
|
+
|| normalizeCommitSha(loadCommitIdForWidget(widgetId));
|
|
7164
7524
|
|
|
7165
7525
|
widgets = widgets.map((w) =>
|
|
7166
7526
|
w.id === widgetId
|
|
@@ -7177,7 +7537,6 @@ Requirements:
|
|
|
7177
7537
|
: w
|
|
7178
7538
|
);
|
|
7179
7539
|
|
|
7180
|
-
const prevSnap = getPanelState(widgetId);
|
|
7181
7540
|
const optimisticHistoryEntry = normalizedCommit
|
|
7182
7541
|
? {
|
|
7183
7542
|
version,
|
|
@@ -7208,6 +7567,9 @@ Requirements:
|
|
|
7208
7567
|
saveCommitIdForWidget(widgetId, normalizedCommit);
|
|
7209
7568
|
}
|
|
7210
7569
|
|
|
7570
|
+
console.log('[CompositionEditor] Reloading schemas after publish', widgetId.substring(0, 8));
|
|
7571
|
+
void loadWidgetSchemas(widgetId, { force: true });
|
|
7572
|
+
|
|
7211
7573
|
if (selectedWidgetId === widgetId) {
|
|
7212
7574
|
lastPublishedVersion = version;
|
|
7213
7575
|
selectedWidgetJsPath = jsPath;
|
|
@@ -7402,6 +7764,16 @@ Requirements:
|
|
|
7402
7764
|
|
|
7403
7765
|
// Restore session on mount with expiration check
|
|
7404
7766
|
onMount(() => {
|
|
7767
|
+
const googleFontsKey =
|
|
7768
|
+
(typeof import.meta !== 'undefined' &&
|
|
7769
|
+
(import.meta.env?.PUBLIC_GOOGLE_FONTS_API_KEY || import.meta.env?.VITE_GOOGLE_FONTS_API_KEY)) ||
|
|
7770
|
+
'';
|
|
7771
|
+
if (googleFontsKey) {
|
|
7772
|
+
setGoogleFontsApiKey(String(googleFontsKey));
|
|
7773
|
+
console.log('[CreatorApp] Google Fonts API key set for FontIC');
|
|
7774
|
+
} else {
|
|
7775
|
+
console.warn('[CreatorApp] No PUBLIC_GOOGLE_FONTS_API_KEY / VITE_GOOGLE_FONTS_API_KEY — FontIC uses fallback catalog');
|
|
7776
|
+
}
|
|
7405
7777
|
setWebsocketApiUrl(apiRoot ?? null);
|
|
7406
7778
|
if (embedded) {
|
|
7407
7779
|
syncSessionFromSiteAuth();
|
|
@@ -7551,6 +7923,10 @@ Requirements:
|
|
|
7551
7923
|
window.removeEventListener(HOST_OPEN_WIDGET_DETAILS_EVENT, handleHostOpenWidgetDetailsEvent);
|
|
7552
7924
|
window.removeEventListener(HOST_DUPLICATE_WIDGET_EVENT, handleHostDuplicateWidgetEvent);
|
|
7553
7925
|
window.removeEventListener(HOST_DELETE_WIDGET_EVENT, handleHostDeleteWidgetEvent);
|
|
7926
|
+
window.removeEventListener(HOST_SAVE_CANVAS_EVENT, handleHostSaveCanvasEvent);
|
|
7927
|
+
window.removeEventListener(HOST_SET_PRIMARY_WIDGET_EVENT, handleHostSetPrimaryWidgetEvent);
|
|
7928
|
+
window.removeEventListener(HOST_REQUEST_CANVAS_WIDGETS_EVENT, handleHostRequestCanvasWidgetsEvent);
|
|
7929
|
+
window.removeEventListener(HOST_MOVE_CANVAS_FOLDER_EVENT, handleHostMoveCanvasFolderEvent);
|
|
7554
7930
|
for (const wId of Object.keys(generatingWidgetIds)) {
|
|
7555
7931
|
setCrossTabGenerating(false, wId);
|
|
7556
7932
|
}
|
|
@@ -7580,9 +7956,14 @@ Requirements:
|
|
|
7580
7956
|
window.addEventListener(HOST_OPEN_WIDGET_DETAILS_EVENT, handleHostOpenWidgetDetailsEvent);
|
|
7581
7957
|
window.addEventListener(HOST_DUPLICATE_WIDGET_EVENT, handleHostDuplicateWidgetEvent);
|
|
7582
7958
|
window.addEventListener(HOST_DELETE_WIDGET_EVENT, handleHostDeleteWidgetEvent);
|
|
7959
|
+
window.addEventListener(HOST_SAVE_CANVAS_EVENT, handleHostSaveCanvasEvent);
|
|
7960
|
+
window.addEventListener(HOST_SET_PRIMARY_WIDGET_EVENT, handleHostSetPrimaryWidgetEvent);
|
|
7961
|
+
window.addEventListener(HOST_REQUEST_CANVAS_WIDGETS_EVENT, handleHostRequestCanvasWidgetsEvent);
|
|
7962
|
+
window.addEventListener(HOST_MOVE_CANVAS_FOLDER_EVENT, handleHostMoveCanvasFolderEvent);
|
|
7583
7963
|
emitCreatorDebugChanged();
|
|
7584
7964
|
emitCreatorWireframeChanged();
|
|
7585
7965
|
emitCreatorAdvancedChanged();
|
|
7966
|
+
emitCreatorSaveState(false);
|
|
7586
7967
|
});
|
|
7587
7968
|
|
|
7588
7969
|
// Capture-phase Escape: close lightbox before Canvas can deselect → close WidgetDetails
|
|
@@ -7620,8 +8001,6 @@ Requirements:
|
|
|
7620
8001
|
if (file) {
|
|
7621
8002
|
const base64 = await processImageFile(file);
|
|
7622
8003
|
if (base64) {
|
|
7623
|
-
promptImages = [...promptImages, base64];
|
|
7624
|
-
savePromptImagesToSession();
|
|
7625
8004
|
chatMethods?.addImageAttachment?.(base64, file.name || 'pasted-image.png');
|
|
7626
8005
|
showToast('success', 'Image pasted to prompt', { duration: 2000 });
|
|
7627
8006
|
}
|
|
@@ -8425,14 +8804,20 @@ Requirements:
|
|
|
8425
8804
|
}
|
|
8426
8805
|
|
|
8427
8806
|
function positionOpenDetailsPanel(widgetId: string): void {
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8807
|
+
const apply = () => {
|
|
8808
|
+
let rect = canvasRef?.getWidgetShapeScreenRect?.(widgetId) ?? null;
|
|
8809
|
+
if (!rect) {
|
|
8810
|
+
const objectId = draftConvertObjectIds.get(widgetId);
|
|
8811
|
+
if (objectId && canvasRef?.getObjectScreenRect) {
|
|
8812
|
+
rect = canvasRef.getObjectScreenRect(objectId);
|
|
8813
|
+
}
|
|
8433
8814
|
}
|
|
8815
|
+
getWidgetDetailsRef(widgetId)?.positionBesideShape?.(rect);
|
|
8816
|
+
};
|
|
8817
|
+
apply();
|
|
8818
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
8819
|
+
requestAnimationFrame(apply);
|
|
8434
8820
|
}
|
|
8435
|
-
getWidgetDetailsRef(widgetId)?.positionBesideShape?.(rect);
|
|
8436
8821
|
}
|
|
8437
8822
|
|
|
8438
8823
|
async function openWidgetDetailsPanel(widgetId: string): Promise<void> {
|
|
@@ -9235,20 +9620,11 @@ Requirements:
|
|
|
9235
9620
|
const effectiveCanvasId = getEffectiveCanvasId();
|
|
9236
9621
|
const panelIsPublishing = isPublishing && publishingWidgetId === widgetId;
|
|
9237
9622
|
const anotherPanelIsPublishing = isPublishing && publishingWidgetId !== null && publishingWidgetId !== widgetId;
|
|
9238
|
-
const stuckGlobalPublishing = isPublishing && !publishingWidgetId;
|
|
9239
9623
|
const nextVersion = (ps.lastPublishedVersion ?? 0) + 1;
|
|
9240
9624
|
|
|
9241
9625
|
if (panelIsPublishing) {
|
|
9242
9626
|
return { disabled: true, label: 'Publishing...', reason: 'Publishing in progress…', showDisabledHint: false };
|
|
9243
9627
|
}
|
|
9244
|
-
if (stuckGlobalPublishing) {
|
|
9245
|
-
return {
|
|
9246
|
-
disabled: true,
|
|
9247
|
-
label: getPublishButtonLabelFallback(ps),
|
|
9248
|
-
reason: 'Publish state is busy — try again in a moment.',
|
|
9249
|
-
showDisabledHint: true,
|
|
9250
|
-
};
|
|
9251
|
-
}
|
|
9252
9628
|
if (anotherPanelIsPublishing) {
|
|
9253
9629
|
return {
|
|
9254
9630
|
disabled: true,
|
|
@@ -9257,7 +9633,7 @@ Requirements:
|
|
|
9257
9633
|
showDisabledHint: true,
|
|
9258
9634
|
};
|
|
9259
9635
|
}
|
|
9260
|
-
if (
|
|
9636
|
+
if (checkCrossTabPublishing(widgetId)) {
|
|
9261
9637
|
return {
|
|
9262
9638
|
disabled: true,
|
|
9263
9639
|
label: getPublishButtonLabelFallback(ps),
|
|
@@ -9319,6 +9695,16 @@ Requirements:
|
|
|
9319
9695
|
const unpublishedAhead = hasUnpublishedChangesForPanel(widgetId)
|
|
9320
9696
|
|| !!(ps.lastCommitId && liveSha && !commitsMatch(ps.lastCommitId, liveSha));
|
|
9321
9697
|
|
|
9698
|
+
// Known up-to-date: HEAD matches live. Must win over leftover pendingPublish flags.
|
|
9699
|
+
if (headSha && liveSha && commitsMatch(headSha, liveSha)) {
|
|
9700
|
+
return {
|
|
9701
|
+
disabled: true,
|
|
9702
|
+
label: 'No new version to publish',
|
|
9703
|
+
reason: `Latest commit (${formatCommitShaShort(headSha)}) is already live at v${ps.lastPublishedVersion}.`,
|
|
9704
|
+
showDisabledHint: false,
|
|
9705
|
+
};
|
|
9706
|
+
}
|
|
9707
|
+
|
|
9322
9708
|
if (pendingPublish || unpublishedAhead) {
|
|
9323
9709
|
console.log('[Publish] Gate: unpublished HEAD — enable', widgetId.substring(0, 8), {
|
|
9324
9710
|
pendingPublish,
|
|
@@ -9335,16 +9721,6 @@ Requirements:
|
|
|
9335
9721
|
};
|
|
9336
9722
|
}
|
|
9337
9723
|
|
|
9338
|
-
// Known up-to-date: HEAD matches live.
|
|
9339
|
-
if (headSha && liveSha && commitsMatch(headSha, liveSha)) {
|
|
9340
|
-
return {
|
|
9341
|
-
disabled: true,
|
|
9342
|
-
label: 'No new version to publish',
|
|
9343
|
-
reason: `Latest commit (${formatCommitShaShort(headSha)}) is already live at v${ps.lastPublishedVersion}.`,
|
|
9344
|
-
showDisabledHint: false,
|
|
9345
|
-
};
|
|
9346
|
-
}
|
|
9347
|
-
|
|
9348
9724
|
// After a live version exists, enable vN+1 when HEAD is proven different.
|
|
9349
9725
|
if (headSha && liveSha && !commitsMatch(headSha, liveSha)) {
|
|
9350
9726
|
return {
|
|
@@ -10178,8 +10554,8 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
10178
10554
|
}
|
|
10179
10555
|
}
|
|
10180
10556
|
|
|
10181
|
-
// Keep address-bar widget_id
|
|
10182
|
-
if (typeof window !== 'undefined' && embedded) {
|
|
10557
|
+
// Keep address-bar widget_id on the canvas primary (duplicate/select must not steal it).
|
|
10558
|
+
if (typeof window !== 'undefined' && embedded && canvasPrimaryWidgetId && widgetId === canvasPrimaryWidgetId) {
|
|
10183
10559
|
try {
|
|
10184
10560
|
const url = new URL(window.location.href);
|
|
10185
10561
|
if (url.searchParams.get('widget_id') !== widgetId) {
|
|
@@ -10229,7 +10605,7 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
10229
10605
|
id: sourceWidgetId,
|
|
10230
10606
|
duplicateWidgetRequest: { canvas_id: canvasId } as any
|
|
10231
10607
|
});
|
|
10232
|
-
|
|
10608
|
+
let newWidget = response?.data;
|
|
10233
10609
|
|
|
10234
10610
|
if (!newWidget?.id) {
|
|
10235
10611
|
console.error('[WidgetCreator] duplicateWidget returned no data', response);
|
|
@@ -10255,37 +10631,94 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
10255
10631
|
}
|
|
10256
10632
|
|
|
10257
10633
|
// Re-link the cloned canvas shape to the new widget (also clears _isDuplicating on clone)
|
|
10258
|
-
|
|
10634
|
+
if (clonedObject) {
|
|
10635
|
+
canvasRef?.linkObjectToWidget(clonedObject, newWidget.id, newWidget.name ?? null);
|
|
10636
|
+
} else if (canvasRef?.addWidgetShape) {
|
|
10637
|
+
canvasRef.addWidgetShape(newWidget.id, newWidget.name, 0, 1, null);
|
|
10638
|
+
}
|
|
10259
10639
|
setTimeout(() => {
|
|
10260
10640
|
const json = canvasRef?.saveCanvas();
|
|
10261
10641
|
if (json) handleCanvasAutoSave(json);
|
|
10262
10642
|
}, 500);
|
|
10263
10643
|
|
|
10264
|
-
// Add to beginning of list (newest first) and auto-select
|
|
10265
10644
|
widgets = [{ ...newWidget }, ...widgets];
|
|
10266
10645
|
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10646
|
+
const sourceWidget = getWidgetById(sourceWidgetId);
|
|
10647
|
+
const sourceJsPath =
|
|
10648
|
+
((sourceWidget as { jsPath?: string | null; js_path?: string | null } | null)?.jsPath
|
|
10649
|
+
?? (sourceWidget as { js_path?: string | null } | null)?.js_path)
|
|
10650
|
+
?? null;
|
|
10651
|
+
const duplicateJsPath =
|
|
10652
|
+
sourceJsPath
|
|
10653
|
+
?? ((newWidget as { jsPath?: string | null; js_path?: string | null }).jsPath
|
|
10654
|
+
?? (newWidget as { js_path?: string | null }).js_path);
|
|
10655
|
+
if (sourceWidget && duplicateJsPath) {
|
|
10656
|
+
Object.assign(newWidget, {
|
|
10657
|
+
jsPath: duplicateJsPath,
|
|
10658
|
+
js_path: duplicateJsPath,
|
|
10659
|
+
version: (newWidget as { version?: number }).version ?? (sourceWidget as { version?: number }).version,
|
|
10660
|
+
isDraft: (newWidget as { isDraft?: boolean }).isDraft
|
|
10661
|
+
?? (sourceWidget as { isDraft?: boolean; is_draft?: boolean }).isDraft
|
|
10662
|
+
?? (sourceWidget as { is_draft?: boolean }).is_draft,
|
|
10663
|
+
lastCpgCommitSha:
|
|
10664
|
+
(newWidget as { lastCpgCommitSha?: string | null }).lastCpgCommitSha
|
|
10665
|
+
?? (sourceWidget as { lastCpgCommitSha?: string | null }).lastCpgCommitSha,
|
|
10666
|
+
lastPublishCommitSha:
|
|
10667
|
+
(newWidget as { lastPublishCommitSha?: string | null }).lastPublishCommitSha
|
|
10668
|
+
?? (sourceWidget as { lastPublishCommitSha?: string | null; last_publish_commit_sha?: string | null }).lastPublishCommitSha
|
|
10669
|
+
?? (sourceWidget as { last_publish_commit_sha?: string | null }).last_publish_commit_sha,
|
|
10670
|
+
});
|
|
10671
|
+
widgets = widgets.map((w) => (w.id === newWidget.id ? { ...newWidget } : w));
|
|
10672
|
+
}
|
|
10673
|
+
|
|
10674
|
+
const sourceCommit = loadCommitIdForWidget(sourceWidgetId);
|
|
10675
|
+
if (sourceCommit) {
|
|
10676
|
+
saveCommitIdForWidget(newWidget.id, sourceCommit);
|
|
10677
|
+
}
|
|
10678
|
+
|
|
10679
|
+
const sourceHasCode =
|
|
10680
|
+
!!normalizeRepositoryId(newWidget)
|
|
10681
|
+
|| !!((newWidget as { jsPath?: string | null; js_path?: string | null }).jsPath
|
|
10682
|
+
?? (newWidget as { js_path?: string | null }).js_path)
|
|
10683
|
+
|| resolvePublishedVersionFromWidget(newWidget) !== null
|
|
10684
|
+
|| !!sourceCommit;
|
|
10685
|
+
|
|
10686
|
+
if (!canvasPrimaryWidgetId) {
|
|
10687
|
+
canvasPrimaryWidgetId = sourceWidgetId;
|
|
10688
|
+
}
|
|
10689
|
+
|
|
10690
|
+
// Duplicate stays a sibling on the canvas — do not make it the primary widget.
|
|
10691
|
+
await selectWidget(newWidget, { skipPlaceShape: true });
|
|
10692
|
+
notifyHostWidgetName(getWidgetById(canvasPrimaryWidgetId) ?? getWidgetById(sourceWidgetId));
|
|
10271
10693
|
|
|
10272
|
-
const newRepoId = normalizeRepositoryId(newWidget);
|
|
10273
10694
|
showToast('success', 'Widget duplicated', {
|
|
10274
|
-
description:
|
|
10695
|
+
description: sourceHasCode
|
|
10696
|
+
? `"${newWidget.name}" created with existing code`
|
|
10697
|
+
: `"${newWidget.name}" created`
|
|
10275
10698
|
});
|
|
10276
10699
|
|
|
10277
|
-
// Re-fetch widget from API to ensure repo_id and other fields are current
|
|
10278
10700
|
setTimeout(() => handleWidgetDataChanged(newWidget.id), 1500);
|
|
10279
10701
|
|
|
10280
|
-
|
|
10281
|
-
|
|
10702
|
+
if (sourceHasCode) {
|
|
10703
|
+
await openWidgetDetailsPanel(newWidget.id);
|
|
10704
|
+
await tick();
|
|
10705
|
+
getWidgetDetailsRef(newWidget.id)?.startPreviewLoadingPublic();
|
|
10706
|
+
try {
|
|
10707
|
+
await getWidgetDetailsRef(newWidget.id)?.retryPreviewIfEmpty?.();
|
|
10708
|
+
} catch (previewErr) {
|
|
10709
|
+
console.warn('[WidgetCreator] Duplicate preview fallback failed:', previewErr);
|
|
10710
|
+
}
|
|
10711
|
+
return;
|
|
10712
|
+
}
|
|
10713
|
+
|
|
10714
|
+
pendingDuplicateBuildWidgetId = newWidget.id;
|
|
10282
10715
|
setTimeout(async () => {
|
|
10283
10716
|
if (selectedWidgetId !== newWidget.id) return;
|
|
10284
10717
|
pendingDuplicateBuildWidgetId = null;
|
|
10285
10718
|
try {
|
|
10286
10719
|
await handleWidgetDataChanged(newWidget.id);
|
|
10287
10720
|
const freshWidget = getWidgetById(newWidget.id);
|
|
10288
|
-
let repoId = freshWidget ? normalizeRepositoryId(freshWidget) :
|
|
10721
|
+
let repoId = freshWidget ? normalizeRepositoryId(freshWidget) : normalizeRepositoryId(newWidget);
|
|
10289
10722
|
|
|
10290
10723
|
if (!repoId) {
|
|
10291
10724
|
console.log('[WidgetCreator] Duplicate has no repo after re-fetch, creating one...');
|
|
@@ -10333,6 +10766,8 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
10333
10766
|
|
|
10334
10767
|
if (!result.proceed) return false;
|
|
10335
10768
|
|
|
10769
|
+
closeWidgetDetailsPanel(widgetId);
|
|
10770
|
+
|
|
10336
10771
|
if (result.deleteFromDb) {
|
|
10337
10772
|
await deleteWidgetFromDb(widgetId);
|
|
10338
10773
|
} else {
|
|
@@ -10360,7 +10795,8 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
10360
10795
|
if (selectedWidget?.id === widgetId) {
|
|
10361
10796
|
selectedWidget = { ...selectedWidget, name: newName };
|
|
10362
10797
|
}
|
|
10363
|
-
|
|
10798
|
+
notifyHostWidgetName({ id: widgetId, name: newName });
|
|
10799
|
+
emitCanvasWidgetsList('widget-renamed');
|
|
10364
10800
|
showToast('success', `Widget renamed to "${newName}"`);
|
|
10365
10801
|
} catch (error) {
|
|
10366
10802
|
console.error('[WidgetCreator] Failed to rename widget:', error);
|
|
@@ -10407,10 +10843,7 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
10407
10843
|
}
|
|
10408
10844
|
await syncWidgetsWithCanvas(widgetIdsOnCanvas);
|
|
10409
10845
|
await maybeAutoRestoreWidgetShapes();
|
|
10410
|
-
const primaryId =
|
|
10411
|
-
canvasPrimaryWidgetId ||
|
|
10412
|
-
canvasRef.getPrimaryWidgetId?.() ||
|
|
10413
|
-
selectedWidgetId;
|
|
10846
|
+
const primaryId = await hydrateCanvasPrimaryWidget(widgetIdsOnCanvas);
|
|
10414
10847
|
if (primaryId && canvasRef && !isConvertOrDraftPanelOpen()) {
|
|
10415
10848
|
canvasRef.setPrimaryWidgetShape?.(primaryId);
|
|
10416
10849
|
const found = canvasRef.selectWidgetShape(primaryId);
|
|
@@ -10607,6 +11040,7 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
10607
11040
|
const widgetIdsOnCanvas = extractWidgetIdsFromCanvasContent(parsed);
|
|
10608
11041
|
await canvasRef.loadCanvas(saved);
|
|
10609
11042
|
markCanvasStateLoaded();
|
|
11043
|
+
canvasApiLoadError = null;
|
|
10610
11044
|
isLoadCooldownActive = true;
|
|
10611
11045
|
setTimeout(() => { isLoadCooldownActive = false; }, 3000);
|
|
10612
11046
|
lastSavedCanvasObjectCount = objectCount;
|
|
@@ -10690,6 +11124,7 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
10690
11124
|
|
|
10691
11125
|
lastSavedCanvasObjectCount = objectCount;
|
|
10692
11126
|
try { sessionStorage.setItem(CANVAS_OBJ_COUNT_KEY, String(objectCount)); } catch {}
|
|
11127
|
+
emitCreatorSaveState(false);
|
|
10693
11128
|
|
|
10694
11129
|
// Save to durable L1 fallback (always, even with 0 objects for legitimate clears)
|
|
10695
11130
|
saveCanvasState(canvasId, json);
|
|
@@ -10928,6 +11363,13 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
10928
11363
|
// Add to widgets list so getWidgetById resolves the real widget from now on.
|
|
10929
11364
|
widgets = [{ ...newWidget }, ...widgets.filter((w) => w.id !== draftId)];
|
|
10930
11365
|
|
|
11366
|
+
if (canvasId) {
|
|
11367
|
+
const existingPrimary = await resolvePrimaryWidgetIdForCanvas(canvasId);
|
|
11368
|
+
if (!existingPrimary) {
|
|
11369
|
+
await persistPrimaryWidgetId(widgetId);
|
|
11370
|
+
}
|
|
11371
|
+
}
|
|
11372
|
+
|
|
10931
11373
|
// Replace the grouped sketch with a WidgetShape screenshot. On failure
|
|
10932
11374
|
// fall back to linking the sketch object to the widget id.
|
|
10933
11375
|
if (canvasRef && canvasObjectId) {
|
|
@@ -11560,6 +12002,7 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
11560
12002
|
}}
|
|
11561
12003
|
onConvertToWidget={handleConvertToWidget}
|
|
11562
12004
|
onWidgetImageClick={handleWidgetImageClick}
|
|
12005
|
+
onCanvasDirty={() => emitCreatorSaveState(true)}
|
|
11563
12006
|
onWidgetShapeDuplicated={handleWidgetShapeDuplicated}
|
|
11564
12007
|
onWidgetShapeDeleting={handleWidgetShapeDeleting}
|
|
11565
12008
|
onWidgetRename={handleWidgetRename}
|
|
@@ -11998,21 +12441,15 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
11998
12441
|
detail.repoHeadCommitSha ||
|
|
11999
12442
|
detail.repo_head_commit_sha ||
|
|
12000
12443
|
null;
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
saveCommitIdForWidget(detail.widgetId, repoHead);
|
|
12004
|
-
}
|
|
12005
|
-
// Always persist hasGeneratedCode on the snapshot so Publish re-renders
|
|
12006
|
-
// even when Worker cache hits without a SHA and the panel is unfocused.
|
|
12444
|
+
// Rebuild is the same saved tree — do not promote GitLab HEAD into lastCommitId
|
|
12445
|
+
// or Publish will offer vN+1 without a new codegen commit.
|
|
12007
12446
|
patchPanelSnapshot(detail.widgetId, {
|
|
12008
12447
|
panelPreviewCompileError: null,
|
|
12009
12448
|
hasGeneratedCode: true,
|
|
12010
|
-
...(hasSha ? { lastCommitId: repoHead } : {}),
|
|
12011
12449
|
});
|
|
12012
12450
|
if (detail.widgetId === panelWidgetId) {
|
|
12013
12451
|
hasGeneratedCode = true;
|
|
12014
|
-
if (
|
|
12015
|
-
if (!hasSha) {
|
|
12452
|
+
if (!getPanelState(detail.widgetId).lastCommitId && !loadCommitIdForWidget(detail.widgetId)) {
|
|
12016
12453
|
void reconcileWidgetHeadCommit(detail.widgetId);
|
|
12017
12454
|
}
|
|
12018
12455
|
if (isPanelFocused && generateCodeStatus?.includes('preview build failed')) {
|
|
@@ -12273,6 +12710,8 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
|
|
|
12273
12710
|
onRetryMessage={(content) => handleChatRetryMessage(content, panelWidgetId)}
|
|
12274
12711
|
onUploadChatFile={handleChatUploadFile}
|
|
12275
12712
|
onTakeScreenshot={() => handleChatScreenshot(panelWidgetId)}
|
|
12713
|
+
isCompleted={!!conversationCompletedByWidgetId[panelWidgetId]}
|
|
12714
|
+
onCompletionToggle={(detail) => handleChatCompletionToggle(panelWidgetId, detail)}
|
|
12276
12715
|
onLoadConversations={handleLoadConversations}
|
|
12277
12716
|
onLoadMessages={(conversationId) => handleLoadMessages(conversationId, panelWidgetId)}
|
|
12278
12717
|
onCreateConversation={handleCreateConversation}
|
|
@@ -12384,8 +12823,8 @@ class={`publish-widget-btn flex justify-center items-center border text-white w-
|
|
|
12384
12823
|
${isPublishing && publishingWidgetId === panelWidgetId
|
|
12385
12824
|
? 'bg-emerald-500 border-emerald-500 opacity-80 cursor-not-allowed'
|
|
12386
12825
|
: isPublishDisabledForPanel(panelWidgetId)
|
|
12387
|
-
? 'bg-emerald-600 border-emerald-600 opacity-
|
|
12388
|
-
: 'bg-emerald-600 border-emerald-600 hover:bg-emerald-700 hover:border-emerald-700 cursor-pointer'
|
|
12826
|
+
? 'bg-emerald-600 border-emerald-600 opacity-40 cursor-not-allowed'
|
|
12827
|
+
: '!opacity-100 bg-emerald-600 border-emerald-600 hover:bg-emerald-700 hover:border-emerald-700 cursor-pointer'
|
|
12389
12828
|
} rounded-lg`}
|
|
12390
12829
|
>
|
|
12391
12830
|
{#if isPublishing && publishingWidgetId === panelWidgetId && publishWidgetStatus === 'Building...'}
|
|
@@ -12408,9 +12847,9 @@ class={`publish-widget-btn flex justify-center items-center border text-white w-
|
|
|
12408
12847
|
{/if}
|
|
12409
12848
|
</Button>
|
|
12410
12849
|
|
|
12411
|
-
{#if isPublishDisabledForPanel(panelWidgetId)
|
|
12850
|
+
{#if isPublishDisabledForPanel(panelWidgetId)}
|
|
12412
12851
|
<p class="publish-widget-disabled-hint text-[10px] text-center text-emerald-800/80 leading-snug px-1">
|
|
12413
|
-
{getPublishDisabledReasonForPanel(panelWidgetId)}
|
|
12852
|
+
{getPublishDisabledReasonForPanel(panelWidgetId) || getPublishButtonLabelForPanel(panelWidgetId)}
|
|
12414
12853
|
</p>
|
|
12415
12854
|
{/if}
|
|
12416
12855
|
|