@widgetic/creator 0.3.51 → 0.3.53
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.
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
import { get } from 'svelte/store';
|
|
15
15
|
import { Button, showToast, GenerateLoader } from '@widgetic/design-system/components';
|
|
16
16
|
import {
|
|
17
|
-
AgentApi
|
|
17
|
+
AgentApi,
|
|
18
|
+
WidgetBuilderApi
|
|
18
19
|
} from '@widgetic/api-sdk';
|
|
19
20
|
import { maxCharsFromId } from '../constants.js';
|
|
20
21
|
import {
|
|
@@ -182,6 +183,21 @@
|
|
|
182
183
|
return `${base}/preview/${widgetId}`;
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
/** CDN/js_path may point at the source widget after duplicate — never rewrite the id. */
|
|
187
|
+
function publishedHtmlUrlFromJsPath(jsPath: string | null | undefined): string | null {
|
|
188
|
+
if (!jsPath) return null;
|
|
189
|
+
let resolved = String(jsPath).replace(/\/+$/, '')
|
|
190
|
+
.replace(/\/widget\.js$/i, '/widget.html')
|
|
191
|
+
.replace(/\.js$/i, '.html');
|
|
192
|
+
if (!/\.html$/i.test(resolved)) {
|
|
193
|
+
resolved = `${resolved}/widget.html`;
|
|
194
|
+
}
|
|
195
|
+
if (resolved.startsWith('/')) {
|
|
196
|
+
resolved = `${cdnPublicBaseUrl}${resolved}`;
|
|
197
|
+
}
|
|
198
|
+
return resolved;
|
|
199
|
+
}
|
|
200
|
+
|
|
185
201
|
/** Map public preview URLs to same-origin fetch paths in dev (iframe embed). */
|
|
186
202
|
function toPreviewFetchUrl(url: string): string {
|
|
187
203
|
if (!import.meta.env.DEV) return url;
|
|
@@ -269,7 +285,7 @@
|
|
|
269
285
|
{ id: 'embed', label: 'Embed', number: 3 },
|
|
270
286
|
];
|
|
271
287
|
|
|
272
|
-
$: hasWidgetCode = !!lastCommitId || lastPublishedVersion !== null;
|
|
288
|
+
$: hasWidgetCode = !!lastCommitId || lastPublishedVersion !== null || !!widgetJsPath;
|
|
273
289
|
|
|
274
290
|
/** Convert UX v2: draft panels (synthetic ids) have no widget/repo yet —
|
|
275
291
|
* show a friendly draft hint instead of the repo-setup loader. */
|
|
@@ -278,14 +294,10 @@
|
|
|
278
294
|
previewError = 'DRAFT_PANEL';
|
|
279
295
|
}
|
|
280
296
|
|
|
281
|
-
/**
|
|
297
|
+
/** Duplicate still has source code — keep the loader, never flash "No Code Yet" as a compile failure. */
|
|
282
298
|
$: if (hasWidgetCode && previewError === 'NO_CODE_YET') {
|
|
283
|
-
previewError =
|
|
284
|
-
|
|
285
|
-
: 'Failed to compile widget code from repository.';
|
|
286
|
-
if (lastPublishedVersion !== null) {
|
|
287
|
-
publishedArtifactReachable = false;
|
|
288
|
-
}
|
|
299
|
+
previewError = null;
|
|
300
|
+
if (!iframeSrc) previewLoading = true;
|
|
289
301
|
}
|
|
290
302
|
$: canEditStep = lastPublishedVersion !== null && !isGeneratingCode;
|
|
291
303
|
$: canEmbedStep = lastPublishedVersion !== null && !isGeneratingCode;
|
|
@@ -647,7 +659,7 @@
|
|
|
647
659
|
|
|
648
660
|
function isCdnArtifactMissingError(message: string | null): boolean {
|
|
649
661
|
if (!message) return false;
|
|
650
|
-
return /artifact not found|artifact missing|not available|CDN|404
|
|
662
|
+
return /artifact not found|artifact missing|not available|CDN|404|not found|requested resource|not publicly accessible/i.test(message);
|
|
651
663
|
}
|
|
652
664
|
$: if (currentStep === 'edit') {
|
|
653
665
|
const editSource = resolvePreviewUrlForStep('edit') ?? '';
|
|
@@ -857,8 +869,8 @@
|
|
|
857
869
|
$: detailsPanelTopOffset = panelTopOffset ?? legacyTopOffset;
|
|
858
870
|
$: detailsPanelBottomMargin = panelBottomMargin ?? legacyBottomMargin;
|
|
859
871
|
|
|
860
|
-
const DETAILS_PANEL_DEFAULT_WIDTH =
|
|
861
|
-
const DETAILS_PANEL_MAX_WIDTH =
|
|
872
|
+
const DETAILS_PANEL_DEFAULT_WIDTH = 800;
|
|
873
|
+
const DETAILS_PANEL_MAX_WIDTH = 800;
|
|
862
874
|
const DETAILS_PANEL_MAX_HEIGHT = 800;
|
|
863
875
|
const DETAILS_PANEL_MIN_WIDTH = 320;
|
|
864
876
|
const DETAILS_PANEL_MIN_HEIGHT = 400;
|
|
@@ -1005,35 +1017,39 @@
|
|
|
1005
1017
|
|
|
1006
1018
|
/** Load published CDN preview without Worker probe (safe for background panels). */
|
|
1007
1019
|
async function loadPublishedPreviewForCurrentStep(): Promise<boolean> {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1020
|
+
const widgetPath = publishedHtmlUrlFromJsPath(
|
|
1021
|
+
widgetJsPath
|
|
1022
|
+
|| ((selectedWidget as { jsPath?: string | null; js_path?: string | null } | null)?.jsPath
|
|
1023
|
+
?? (selectedWidget as { js_path?: string | null } | null)?.js_path)
|
|
1024
|
+
|| null
|
|
1025
|
+
);
|
|
1026
|
+
const publishedUrl =
|
|
1027
|
+
currentStep === 'embed'
|
|
1028
|
+
? embedPreviewUrl
|
|
1029
|
+
: (widgetPath || editPreviewUrl);
|
|
1030
|
+
if (!publishedUrl) return false;
|
|
1031
|
+
const reachable = await probePublishedArtifactUrl(publishedUrl);
|
|
1032
|
+
publishedArtifactReachable = reachable;
|
|
1033
|
+
if (!reachable) {
|
|
1034
|
+
console.warn('[WidgetDetails] Published artifact not reachable, falling through to repo compile', publishedUrl);
|
|
1035
|
+
previewLoading = false;
|
|
1036
|
+
return false;
|
|
1021
1037
|
}
|
|
1022
|
-
if (currentStep === 'embed'
|
|
1023
|
-
const reachable = await probePublishedArtifactUrl(embedPreviewUrl);
|
|
1024
|
-
publishedArtifactReachable = reachable;
|
|
1025
|
-
if (!reachable) {
|
|
1026
|
-
previewError = 'Published version is not available. Use Rebuild code.';
|
|
1027
|
-
previewLoading = false;
|
|
1028
|
-
return false;
|
|
1029
|
-
}
|
|
1038
|
+
if (currentStep === 'embed') {
|
|
1030
1039
|
_lastEmbedPreviewBase = null;
|
|
1031
|
-
applyPreviewUrlToIframe(
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1040
|
+
applyPreviewUrlToIframe(publishedUrl);
|
|
1041
|
+
} else {
|
|
1042
|
+
_lastEditPreviewBase = null;
|
|
1043
|
+
if (currentStep === 'edit') {
|
|
1044
|
+
switchPreviewUrlForStep('edit', { forceReload: true });
|
|
1045
|
+
} else {
|
|
1046
|
+
applyPreviewUrlToIframe(publishedUrl);
|
|
1047
|
+
}
|
|
1035
1048
|
}
|
|
1036
|
-
|
|
1049
|
+
previewBuildDeferred = false;
|
|
1050
|
+
previewError = null;
|
|
1051
|
+
previewLoading = false;
|
|
1052
|
+
return true;
|
|
1037
1053
|
}
|
|
1038
1054
|
|
|
1039
1055
|
/** Probe Worker / build-from-repo pipeline without resetting panel state. */
|
|
@@ -1076,7 +1092,7 @@
|
|
|
1076
1092
|
}
|
|
1077
1093
|
|
|
1078
1094
|
if (probeResult === 'not-found') {
|
|
1079
|
-
console.log('[WidgetDetails] Widget not on Worker — build-from-repo', {
|
|
1095
|
+
console.log('[WidgetDetails] Widget not on Worker — published artifact or build-from-repo', {
|
|
1080
1096
|
widgetId: capturedWidgetId.substring(0, 8),
|
|
1081
1097
|
hasJwt: !!getAuthJwt(),
|
|
1082
1098
|
});
|
|
@@ -1085,9 +1101,19 @@
|
|
|
1085
1101
|
previewError = null;
|
|
1086
1102
|
return;
|
|
1087
1103
|
}
|
|
1104
|
+
if (await loadPublishedPreviewForCurrentStep()) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1088
1107
|
const hasRepo = !!(normalizeRepositoryId(selectedWidget) || currentRepositoryId);
|
|
1089
1108
|
if (!hasRepo) {
|
|
1090
|
-
|
|
1109
|
+
const fallbackUrl = publishedHtmlUrlFromJsPath(widgetJsPath);
|
|
1110
|
+
if (fallbackUrl) {
|
|
1111
|
+
console.log('[WidgetDetails] No repo yet — loading js_path artifact', fallbackUrl);
|
|
1112
|
+
previewError = null;
|
|
1113
|
+
applyPreviewUrlToIframe(fallbackUrl);
|
|
1114
|
+
previewLoading = false;
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1091
1117
|
console.log('[WidgetDetails] No repository yet — skipping build-from-repo');
|
|
1092
1118
|
previewLoading = false;
|
|
1093
1119
|
previewError = 'NO_CODE_YET';
|
|
@@ -1140,10 +1166,7 @@
|
|
|
1140
1166
|
previewHydrating = false;
|
|
1141
1167
|
resetPreviewMessageBridge();
|
|
1142
1168
|
clearPendingRuntimeErrorOverlay();
|
|
1143
|
-
|
|
1144
|
-
if (!isWidgetLoading) {
|
|
1145
|
-
previewLoading = false;
|
|
1146
|
-
}
|
|
1169
|
+
previewLoading = true;
|
|
1147
1170
|
isWidgetLoading = false;
|
|
1148
1171
|
previewError = null;
|
|
1149
1172
|
previewRetryCount = 0;
|
|
@@ -1197,11 +1220,8 @@
|
|
|
1197
1220
|
return;
|
|
1198
1221
|
}
|
|
1199
1222
|
|
|
1200
|
-
// Brand-new widget (no repo): skip Worker HEAD / build-from-repo.
|
|
1201
|
-
// If the widget already has a GitLab repo, compile even without a cached commit SHA
|
|
1202
|
-
// (localStorage miss, failed retries, unpublished lastCpgCommitSha).
|
|
1203
1223
|
const hasRepo = !!(normalizeRepositoryId(selectedWidget) || currentRepositoryId);
|
|
1204
|
-
if (!lastCommitId && lastPublishedVersion === null && !hasRepo) {
|
|
1224
|
+
if (!lastCommitId && lastPublishedVersion === null && !widgetJsPath && !hasRepo) {
|
|
1205
1225
|
console.log('[WidgetDetails] No code yet — skipping Worker probe/build-from-repo', {
|
|
1206
1226
|
widgetId: widgetId?.substring(0, 8),
|
|
1207
1227
|
hasRepo,
|
|
@@ -1242,6 +1262,27 @@
|
|
|
1242
1262
|
/** Trigger build-from-repo with retries — for duplicate/returning widgets.
|
|
1243
1263
|
* Unlike loadWidget, this doesn't reset state. Just builds and updates preview.
|
|
1244
1264
|
* When force is true, always recompiles from GitLab (evicts Worker cache) even if preview exists. */
|
|
1265
|
+
async function requestBuildFromRepo(widgetId: string): Promise<any> {
|
|
1266
|
+
const anyClient = agentClient as any;
|
|
1267
|
+
if (typeof anyClient.agentWidgetBuilderBuildFromRepoWidgetIdPost === 'function') {
|
|
1268
|
+
return anyClient.agentWidgetBuilderBuildFromRepoWidgetIdPost({ widgetId });
|
|
1269
|
+
}
|
|
1270
|
+
const cfg = anyClient.configuration || {};
|
|
1271
|
+
const builderBasePath = String(cfg.basePath || '').replace(/\/v1\/?$/, '') || undefined;
|
|
1272
|
+
const builder = new WidgetBuilderApi({
|
|
1273
|
+
basePath: builderBasePath,
|
|
1274
|
+
accessToken: cfg.accessToken,
|
|
1275
|
+
middleware: cfg.middleware,
|
|
1276
|
+
headers: cfg.headers,
|
|
1277
|
+
});
|
|
1278
|
+
console.log('[WidgetDetails] buildWidgetFromRepo', {
|
|
1279
|
+
widgetId: widgetId.substring(0, 8),
|
|
1280
|
+
builderBasePath,
|
|
1281
|
+
sdkPath: '/v1/agent/widget-builder/build-from-repo/{widgetId}',
|
|
1282
|
+
});
|
|
1283
|
+
return builder.buildWidgetFromRepo({ widgetId });
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1245
1286
|
export async function triggerBuildFromRepo(options?: { force?: boolean }): Promise<void> {
|
|
1246
1287
|
const forceRebuild = options?.force === true;
|
|
1247
1288
|
if (!selectedWidgetId) return;
|
|
@@ -1337,9 +1378,7 @@
|
|
|
1337
1378
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1338
1379
|
if (isStaleRun()) return;
|
|
1339
1380
|
try {
|
|
1340
|
-
const buildData = await
|
|
1341
|
-
widgetId: capturedWidgetId
|
|
1342
|
-
});
|
|
1381
|
+
const buildData = await requestBuildFromRepo(capturedWidgetId);
|
|
1343
1382
|
if (isStaleRun()) return;
|
|
1344
1383
|
|
|
1345
1384
|
console.log('[WidgetDetails] triggerBuildFromRepo succeeded', { buildTimeMs: buildData?.data?.buildTimeMs ?? buildData?.buildTimeMs, attempt });
|
|
@@ -1413,24 +1452,17 @@
|
|
|
1413
1452
|
|
|
1414
1453
|
if (selectedWidgetId === capturedWidgetId) {
|
|
1415
1454
|
// For published widgets, try loading via API proxy (handles both R2 and CDN)
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
console.log('[WidgetDetails] triggerBuildFromRepo:
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
iframeSrc = proxyUrl;
|
|
1426
|
-
setupPreviewFallbackTimeout();
|
|
1427
|
-
syncPreviewLoadingWithIframe();
|
|
1428
|
-
return;
|
|
1429
|
-
}
|
|
1430
|
-
console.warn('[WidgetDetails] API proxy returned', probeRes.status, '— artifact not on R2, showing generate button');
|
|
1431
|
-
} catch (probeErr) {
|
|
1432
|
-
console.warn('[WidgetDetails] API proxy probe failed:', probeErr);
|
|
1455
|
+
const sourceArtifactUrl = publishedHtmlUrlFromJsPath(widgetJsPath);
|
|
1456
|
+
if (sourceArtifactUrl) {
|
|
1457
|
+
console.log('[WidgetDetails] triggerBuildFromRepo: using js_path artifact (not new-id proxy)', sourceArtifactUrl);
|
|
1458
|
+
const reachable = await probePublishedArtifactUrl(sourceArtifactUrl);
|
|
1459
|
+
if (reachable) {
|
|
1460
|
+
previewError = null;
|
|
1461
|
+
previewLoading = true;
|
|
1462
|
+
applyPreviewUrlToIframe(sourceArtifactUrl);
|
|
1463
|
+
return;
|
|
1433
1464
|
}
|
|
1465
|
+
console.warn('[WidgetDetails] js_path artifact not reachable — showing Rebuild');
|
|
1434
1466
|
}
|
|
1435
1467
|
|
|
1436
1468
|
previewLoading = false;
|
|
@@ -1551,6 +1583,37 @@
|
|
|
1551
1583
|
notifyPreviewCompileErrorChanged();
|
|
1552
1584
|
}
|
|
1553
1585
|
|
|
1586
|
+
/** Duplicate/fork fallback: keep published iframe if it loaded; otherwise compile from repo. */
|
|
1587
|
+
export async function retryPreviewIfEmpty(): Promise<void> {
|
|
1588
|
+
if (iframeSrc && !previewError) {
|
|
1589
|
+
console.log('[WidgetDetails] retryPreviewIfEmpty skipped — preview already loaded');
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
previewLoading = true;
|
|
1593
|
+
previewError = null;
|
|
1594
|
+
try {
|
|
1595
|
+
if (await loadPublishedPreviewForCurrentStep()) return;
|
|
1596
|
+
const hasRepo = !!(normalizeRepositoryId(selectedWidget) || currentRepositoryId);
|
|
1597
|
+
if (hasRepo) {
|
|
1598
|
+
console.log('[WidgetDetails] retryPreviewIfEmpty — compiling from repository');
|
|
1599
|
+
await triggerBuildFromRepo();
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
const fallbackUrl = publishedHtmlUrlFromJsPath(widgetJsPath);
|
|
1603
|
+
if (fallbackUrl) {
|
|
1604
|
+
applyPreviewUrlToIframe(fallbackUrl);
|
|
1605
|
+
previewLoading = false;
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
previewLoading = false;
|
|
1609
|
+
previewError = 'NO_CODE_YET';
|
|
1610
|
+
console.warn('[WidgetDetails] retryPreviewIfEmpty — no published artifact and no repo');
|
|
1611
|
+
} catch (err) {
|
|
1612
|
+
previewLoading = false;
|
|
1613
|
+
console.warn('[WidgetDetails] retryPreviewIfEmpty failed:', err);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1554
1617
|
export function showNoCodeYetState(): void {
|
|
1555
1618
|
previewLoading = false;
|
|
1556
1619
|
previewError = 'NO_CODE_YET';
|
|
@@ -1574,6 +1637,8 @@
|
|
|
1574
1637
|
if (!target || pendingPreviewMessages.length === 0) return;
|
|
1575
1638
|
for (const message of pendingPreviewMessages) {
|
|
1576
1639
|
target.postMessage(message, '*');
|
|
1640
|
+
applyRichTextInPreviewIframe(message);
|
|
1641
|
+
applyDesignOverlayInPreviewIframe(message);
|
|
1577
1642
|
}
|
|
1578
1643
|
pendingPreviewMessages = [];
|
|
1579
1644
|
}
|
|
@@ -1662,11 +1727,129 @@
|
|
|
1662
1727
|
});
|
|
1663
1728
|
}
|
|
1664
1729
|
|
|
1730
|
+
function extractCssFontFamily(raw: string | undefined): string | null {
|
|
1731
|
+
if (!raw) return null;
|
|
1732
|
+
const quoted = raw.match(/["']([^"']+)["']/);
|
|
1733
|
+
if (quoted?.[1]) return quoted[1].trim();
|
|
1734
|
+
const first = raw.split(',')[0]?.trim();
|
|
1735
|
+
if (!first || first === 'sans-serif' || first === 'serif' || first === 'system-ui') return null;
|
|
1736
|
+
return first.replace(/^["']|["']$/g, '');
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
function ensureGoogleFontInPreviewDoc(doc: Document, family: string): void {
|
|
1740
|
+
if (!family || /^(Inter|system-ui|sans-serif|serif)$/i.test(family)) return;
|
|
1741
|
+
const id = `widgetic-gf-${family.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}`;
|
|
1742
|
+
if (doc.getElementById(id)) return;
|
|
1743
|
+
const link = doc.createElement('link');
|
|
1744
|
+
link.id = id;
|
|
1745
|
+
link.rel = 'stylesheet';
|
|
1746
|
+
link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap`;
|
|
1747
|
+
doc.head?.appendChild(link);
|
|
1748
|
+
console.log('[WidgetDetails] Loaded Google Font in preview iframe:', family);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
/** Same-origin preview: apply CSS vars, Google Fonts, title/subtitle, tile aspect-ratio. */
|
|
1752
|
+
function applyDesignOverlayInPreviewIframe(message: Record<string, unknown>): void {
|
|
1753
|
+
if (message.type !== 'widgetic:update') return;
|
|
1754
|
+
const design = message.design as Record<string, string> | undefined;
|
|
1755
|
+
if (!design) return;
|
|
1756
|
+
const doc = previewIframe?.contentDocument;
|
|
1757
|
+
if (!doc?.documentElement) {
|
|
1758
|
+
console.log('[WidgetDetails] Design overlay skipped — preview iframe not same-origin');
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
const apply = () => {
|
|
1762
|
+
try {
|
|
1763
|
+
for (const [key, value] of Object.entries(design)) {
|
|
1764
|
+
if (typeof value !== 'string' || !value) continue;
|
|
1765
|
+
doc.documentElement.style.setProperty(`--${key}`, value);
|
|
1766
|
+
doc.body?.style.setProperty(`--${key}`, value);
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
const families = new Set<string>();
|
|
1770
|
+
for (const [key, value] of Object.entries(design)) {
|
|
1771
|
+
if (typeof value !== 'string') continue;
|
|
1772
|
+
if (/Family$/i.test(key) || (/font/i.test(key) && !/Weight$/i.test(key))) {
|
|
1773
|
+
const family = extractCssFontFamily(value);
|
|
1774
|
+
if (family) families.add(family);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
families.forEach((family) => ensureGoogleFontInPreviewDoc(doc, family));
|
|
1778
|
+
|
|
1779
|
+
const titleFamily =
|
|
1780
|
+
design.titleFontFamily ||
|
|
1781
|
+
extractCssFontFamily(design.titleFont) ||
|
|
1782
|
+
design.headingFontFamily ||
|
|
1783
|
+
extractCssFontFamily(design.headingFont);
|
|
1784
|
+
const titleWeight = design.titleFontWeight || design.headingFontWeight || '';
|
|
1785
|
+
const subtitleFamily =
|
|
1786
|
+
design.subtitleFontFamily ||
|
|
1787
|
+
extractCssFontFamily(design.subtitleFont) ||
|
|
1788
|
+
design.descriptionFontFamily ||
|
|
1789
|
+
extractCssFontFamily(design.descriptionFont);
|
|
1790
|
+
const subtitleWeight = design.subtitleFontWeight || design.descriptionFontWeight || '';
|
|
1791
|
+
|
|
1792
|
+
if (titleFamily || titleWeight) {
|
|
1793
|
+
const titleStack = titleFamily ? `"${titleFamily}", sans-serif` : '';
|
|
1794
|
+
doc.querySelectorAll(
|
|
1795
|
+
'h1, h2, h3, .widget-title, [data-content="title"], [data-content="widgetTitle"], [data-content="heading"]'
|
|
1796
|
+
).forEach((el) => {
|
|
1797
|
+
if (!(el instanceof HTMLElement)) return;
|
|
1798
|
+
if (titleStack) el.style.setProperty('font-family', titleStack, 'important');
|
|
1799
|
+
if (titleWeight) el.style.setProperty('font-weight', titleWeight, 'important');
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
if (subtitleFamily || subtitleWeight) {
|
|
1803
|
+
const subStack = subtitleFamily ? `"${subtitleFamily}", sans-serif` : '';
|
|
1804
|
+
const subtitleEls = doc.querySelectorAll(
|
|
1805
|
+
'.widget-subtitle, .gallery-subtitle, [data-content="subtitle"], [data-content="description"], [data-content="widgetSubtitle"]'
|
|
1806
|
+
);
|
|
1807
|
+
const applySub = (el: HTMLElement) => {
|
|
1808
|
+
if (subStack) el.style.setProperty('font-family', subStack, 'important');
|
|
1809
|
+
if (subtitleWeight) el.style.setProperty('font-weight', subtitleWeight, 'important');
|
|
1810
|
+
};
|
|
1811
|
+
if (subtitleEls.length > 0) {
|
|
1812
|
+
subtitleEls.forEach((el) => {
|
|
1813
|
+
if (el instanceof HTMLElement) applySub(el);
|
|
1814
|
+
});
|
|
1815
|
+
} else {
|
|
1816
|
+
const firstParagraph = doc.querySelector('p');
|
|
1817
|
+
if (firstParagraph instanceof HTMLElement) applySub(firstParagraph);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
const aspectEntry = Object.entries(design).find(([key, value]) =>
|
|
1822
|
+
/aspect/i.test(key) && typeof value === 'string' && /\d+\s*\/\s*\d+/.test(value)
|
|
1823
|
+
);
|
|
1824
|
+
if (aspectEntry) {
|
|
1825
|
+
const cssRatio = aspectEntry[1].replace(/\s+/g, '');
|
|
1826
|
+
doc.querySelectorAll('img, [class*="aspect-"], .gallery-tile, .tile').forEach((el) => {
|
|
1827
|
+
if (!(el instanceof HTMLElement)) return;
|
|
1828
|
+
el.style.aspectRatio = cssRatio;
|
|
1829
|
+
el.style.objectFit = 'cover';
|
|
1830
|
+
if (el instanceof HTMLImageElement) {
|
|
1831
|
+
el.style.width = '100%';
|
|
1832
|
+
el.style.height = 'auto';
|
|
1833
|
+
}
|
|
1834
|
+
});
|
|
1835
|
+
console.log('[WidgetDetails] Applied tile aspect-ratio overlay:', cssRatio);
|
|
1836
|
+
}
|
|
1837
|
+
} catch (error) {
|
|
1838
|
+
console.warn('[WidgetDetails] Design overlay skipped:', error);
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
requestAnimationFrame(() => {
|
|
1842
|
+
apply();
|
|
1843
|
+
setTimeout(apply, 50);
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1665
1847
|
/** Send a postMessage to the widget preview iframe (for live property editing) */
|
|
1666
1848
|
export function sendMessageToPreview(message: Record<string, unknown>): void {
|
|
1667
1849
|
if (previewWidgetReady && previewIframe?.contentWindow) {
|
|
1668
1850
|
previewIframe.contentWindow.postMessage(message, '*');
|
|
1669
1851
|
applyRichTextInPreviewIframe(message);
|
|
1852
|
+
applyDesignOverlayInPreviewIframe(message);
|
|
1670
1853
|
return;
|
|
1671
1854
|
}
|
|
1672
1855
|
pendingPreviewMessages.push(message);
|
|
@@ -2194,9 +2377,7 @@
|
|
|
2194
2377
|
}
|
|
2195
2378
|
|
|
2196
2379
|
function clampPanelWidth(w: number): number {
|
|
2197
|
-
|
|
2198
|
-
const maxWidth = Math.min(DETAILS_PANEL_MAX_WIDTH, window.innerWidth - 24);
|
|
2199
|
-
return Math.max(DETAILS_PANEL_MIN_WIDTH, Math.min(w, maxWidth));
|
|
2380
|
+
return Math.max(DETAILS_PANEL_MIN_WIDTH, Math.min(w, DETAILS_PANEL_MAX_WIDTH));
|
|
2200
2381
|
}
|
|
2201
2382
|
|
|
2202
2383
|
function clampPanelHeight(h: number): number {
|
|
@@ -2265,8 +2446,8 @@
|
|
|
2265
2446
|
}
|
|
2266
2447
|
|
|
2267
2448
|
/**
|
|
2268
|
-
* Park the panel
|
|
2269
|
-
*
|
|
2449
|
+
* Park the panel to the right of a canvas widget shape. May overlay the
|
|
2450
|
+
* shape Props panel. Can hang halfway outside the viewport.
|
|
2270
2451
|
*/
|
|
2271
2452
|
export function positionBesideShape(shapeRect: {
|
|
2272
2453
|
left: number;
|
|
@@ -2287,41 +2468,20 @@
|
|
|
2287
2468
|
return;
|
|
2288
2469
|
}
|
|
2289
2470
|
|
|
2290
|
-
const
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
const availableWidth = Math.max(DETAILS_PANEL_MIN_WIDTH, shapeRect.viewportWidth - pad * 2);
|
|
2294
|
-
detailsPanelWidth = clampPanelWidth(Math.min(DETAILS_PANEL_MAX_WIDTH, availableWidth));
|
|
2295
|
-
detailsPanelHeight = clampPanelHeight(
|
|
2296
|
-
Math.min(DETAILS_PANEL_MAX_HEIGHT, shapeRect.viewportHeight - pad * 2),
|
|
2297
|
-
);
|
|
2298
|
-
|
|
2299
|
-
const shapeCenterX = shapeRect.left + shapeRect.width / 2;
|
|
2300
|
-
const viewCenterX = shapeRect.viewportLeft + shapeRect.viewportWidth / 2;
|
|
2301
|
-
const openOnRight = shapeCenterX < viewCenterX;
|
|
2302
|
-
if (openOnRight) {
|
|
2303
|
-
detailsPanelX = shapeRect.left + shapeRect.width + DETAILS_PANEL_SHAPE_GAP;
|
|
2304
|
-
if (detailsPanelX + detailsPanelWidth > viewRight - pad) {
|
|
2305
|
-
detailsPanelX = Math.max(viewLeft + pad, viewRight - pad - detailsPanelWidth);
|
|
2306
|
-
}
|
|
2307
|
-
} else {
|
|
2308
|
-
detailsPanelX = shapeRect.left - DETAILS_PANEL_SHAPE_GAP - detailsPanelWidth;
|
|
2309
|
-
if (detailsPanelX < viewLeft + pad) {
|
|
2310
|
-
detailsPanelX = viewLeft + pad;
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2471
|
+
const gap = DETAILS_PANEL_SHAPE_GAP;
|
|
2472
|
+
detailsPanelWidth = clampPanelWidth(DETAILS_PANEL_MAX_WIDTH);
|
|
2473
|
+
detailsPanelHeight = clampPanelHeight(DETAILS_PANEL_MAX_HEIGHT);
|
|
2313
2474
|
|
|
2314
|
-
const
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
);
|
|
2475
|
+
const x = shapeRect.left + shapeRect.width + gap;
|
|
2476
|
+
const pos = clampDetailsPanelPosition(x, Math.max(detailsPanelTopOffset, shapeRect.top));
|
|
2477
|
+
detailsPanelX = pos.x;
|
|
2478
|
+
detailsPanelY = pos.y;
|
|
2319
2479
|
console.log('[WidgetDetails] Positioned beside shape', {
|
|
2320
|
-
openOnRight,
|
|
2321
2480
|
x: Math.round(detailsPanelX),
|
|
2322
2481
|
y: Math.round(detailsPanelY),
|
|
2323
2482
|
w: detailsPanelWidth,
|
|
2324
2483
|
h: detailsPanelHeight,
|
|
2484
|
+
shapeLeft: Math.round(shapeRect.left),
|
|
2325
2485
|
});
|
|
2326
2486
|
}
|
|
2327
2487
|
|
|
@@ -2518,14 +2678,16 @@
|
|
|
2518
2678
|
|
|
2519
2679
|
function clampDetailsPanelPosition(x: number, y: number): { x: number; y: number } {
|
|
2520
2680
|
if (typeof window === 'undefined') return { x, y };
|
|
2521
|
-
const pad = 8;
|
|
2522
2681
|
const panelH = detailsPanelHeight || getDefaultDetailsPanelHeight();
|
|
2523
2682
|
const panelW = detailsPanelWidth || DETAILS_PANEL_DEFAULT_WIDTH;
|
|
2683
|
+
const minVisibleX = Math.max(120, panelW * 0.5);
|
|
2684
|
+
const minVisibleY = Math.max(160, panelH * 0.5);
|
|
2524
2685
|
const minTop = detailsPanelTopOffset;
|
|
2525
|
-
const
|
|
2526
|
-
const
|
|
2686
|
+
const minX = minVisibleX - panelW;
|
|
2687
|
+
const maxX = window.innerWidth - minVisibleX;
|
|
2688
|
+
const maxTop = Math.max(minTop, window.innerHeight - minVisibleY);
|
|
2527
2689
|
return {
|
|
2528
|
-
x: Math.min(
|
|
2690
|
+
x: Math.min(maxX, Math.max(minX, x)),
|
|
2529
2691
|
y: Math.min(maxTop, Math.max(minTop, y)),
|
|
2530
2692
|
};
|
|
2531
2693
|
}
|
|
@@ -2962,16 +3124,28 @@
|
|
|
2962
3124
|
{#if selectedWidgetId}
|
|
2963
3125
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
2964
3126
|
<div
|
|
2965
|
-
class="widget-details-floating-
|
|
2966
|
-
|
|
3127
|
+
class="widget-details-floating-shell pointer-events-none fixed overflow-visible {showWidgetDetails ? '' : 'hidden'}"
|
|
3128
|
+
style="left: {detailsPanelX}px; top: {detailsPanelY}px; width: {detailsPanelWidth}px; z-index: {panelZIndex};
|
|
3129
|
+
height: {detailsPanelHeight ? detailsPanelHeight + 'px' : `calc(100vh - ${detailsPanelTopOffset + detailsPanelBottomMargin}px)`};"
|
|
3130
|
+
>
|
|
3131
|
+
<button
|
|
3132
|
+
type="button"
|
|
3133
|
+
onclick={handleCloseButtonClick}
|
|
3134
|
+
onmousedown={(e) => e.stopPropagation()}
|
|
3135
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
3136
|
+
class="widget-details-close-bt pointer-events-auto absolute -top-2.5 -right-2.5 z-30 w-7 h-7 flex items-center justify-center rounded-full bg-white border border-gray-300 shadow-md hover:bg-red-50 hover:border-red-300 hover:text-red-600 text-gray-500 cursor-pointer transition-colors text-sm"
|
|
3137
|
+
title="Close widget details"
|
|
3138
|
+
>
|
|
3139
|
+
✕
|
|
3140
|
+
</button>
|
|
3141
|
+
<div
|
|
3142
|
+
class="widget-details-floating-panel pointer-events-auto relative flex flex-col h-full w-full
|
|
2967
3143
|
{fillViewport
|
|
2968
3144
|
? 'bg-white rounded-none border-x border-b border-t-0 ring-0 shadow-none overflow-hidden'
|
|
2969
|
-
: 'bg-white/95 backdrop-blur-sm shadow-2xl rounded-xl border overflow-
|
|
3145
|
+
: 'bg-white/95 backdrop-blur-sm shadow-2xl rounded-xl border overflow-hidden'}
|
|
2970
3146
|
{isFocusedPanel
|
|
2971
3147
|
? (fillViewport ? 'border-blue-400' : 'border-blue-400 ring-2 ring-blue-200/80')
|
|
2972
3148
|
: 'border-gray-300 opacity-[0.97]'}"
|
|
2973
|
-
style="left: {detailsPanelX}px; top: {detailsPanelY}px; width: {detailsPanelWidth}px; z-index: {panelZIndex};
|
|
2974
|
-
height: {detailsPanelHeight ? detailsPanelHeight + 'px' : `calc(100vh - ${detailsPanelTopOffset + detailsPanelBottomMargin}px)`};"
|
|
2975
3149
|
onmousedown={handlePanelFocusRequest}
|
|
2976
3150
|
>
|
|
2977
3151
|
<!-- Widget Details Top Bar — title, steps, and close on one row -->
|
|
@@ -3058,17 +3232,6 @@
|
|
|
3058
3232
|
</div>
|
|
3059
3233
|
</div>
|
|
3060
3234
|
|
|
3061
|
-
<button
|
|
3062
|
-
type="button"
|
|
3063
|
-
onclick={handleCloseButtonClick}
|
|
3064
|
-
onmousedown={(e) => e.stopPropagation()}
|
|
3065
|
-
onpointerdown={(e) => e.stopPropagation()}
|
|
3066
|
-
class="widget-details-close-bt absolute -top-2.5 -right-2.5 z-30 w-7 h-7 flex items-center justify-center rounded-full bg-white border border-gray-300 shadow-md hover:bg-red-50 hover:border-red-300 hover:text-red-600 text-gray-500 cursor-pointer transition-colors text-sm"
|
|
3067
|
-
title="Close widget details"
|
|
3068
|
-
>
|
|
3069
|
-
✕
|
|
3070
|
-
</button>
|
|
3071
|
-
|
|
3072
3235
|
<!-- Widget Details Content: Two-column layout -->
|
|
3073
3236
|
<div class="widget-details-content-wrapper relative m-2.5 min-w-0 min-h-0 flex-1 overflow-x-hidden">
|
|
3074
3237
|
<div class="widget-details-two-col flex h-full min-h-0 min-w-0 gap-0 overflow-x-hidden">
|
|
@@ -3104,7 +3267,7 @@
|
|
|
3104
3267
|
Commit: <span class="font-mono font-medium text-gray-600" title={lastCommitId || ''}>{lastCommitId ? lastCommitId.substring(0, 8) : '—'}</span>
|
|
3105
3268
|
</span>
|
|
3106
3269
|
{#if lastPublishedVersion !== null}
|
|
3107
|
-
<a href="/test-widget?widgetId={selectedWidgetId}&version={lastPublishedVersion}" target="_blank" rel="noopener noreferrer" class="text-[10px] px-1.5 py-0.5 rounded bg-emerald-
|
|
3270
|
+
<a href="/test-widget?widgetId={selectedWidgetId}&version={lastPublishedVersion}" target="_blank" rel="noopener noreferrer" class="text-[10px] px-1.5 py-0.5 rounded bg-emerald-600 border border-emerald-700 text-white hover:bg-emerald-700 transition-colors whitespace-nowrap cursor-pointer" title="Open published widget v{lastPublishedVersion}">Live: v{lastPublishedVersion} ↗</a>
|
|
3108
3271
|
{/if}
|
|
3109
3272
|
</div>
|
|
3110
3273
|
</div>
|
|
@@ -3117,9 +3280,13 @@
|
|
|
3117
3280
|
<div class="actions-panel widget-details-actions-panel relative flex flex-col flex-1 min-h-0">
|
|
3118
3281
|
|
|
3119
3282
|
<!-- Keep all steps mounted — switching tabs must not destroy chat / PropsEditor state -->
|
|
3120
|
-
<div class="widget-details-create-step flex flex-col flex-1 min-h-0
|
|
3121
|
-
<
|
|
3122
|
-
|
|
3283
|
+
<div class="widget-details-create-step flex flex-col flex-1 min-h-0 overflow-hidden overflow-x-hidden {currentStep === 'create' ? '' : 'hidden'}">
|
|
3284
|
+
<div class="widget-details-create-chat-ct flex flex-col flex-1 min-h-0 overflow-y-auto overflow-x-hidden scrollbar-thin gap-2 p-2">
|
|
3285
|
+
<slot name="code-generation" />
|
|
3286
|
+
</div>
|
|
3287
|
+
<div class="widget-details-create-publish-ct shrink-0 px-2 pb-2">
|
|
3288
|
+
<slot name="publish-widget" />
|
|
3289
|
+
</div>
|
|
3123
3290
|
</div>
|
|
3124
3291
|
<div class="widget-details-edit-step flex flex-col h-full min-h-0 overflow-y-auto overflow-x-hidden scrollbar-thin {currentStep === 'edit' ? '' : 'hidden'}">
|
|
3125
3292
|
<slot name="composition-editor" />
|
|
@@ -3254,7 +3421,7 @@
|
|
|
3254
3421
|
<div class="widget-preview-fallback relative flex items-center justify-center h-full rounded-lg bg-gray-50 overflow-hidden">
|
|
3255
3422
|
{#if isGeneratingCode || previewLoading}
|
|
3256
3423
|
<GenerateLoader
|
|
3257
|
-
title={isGeneratingCode ? 'Generating code…' : '
|
|
3424
|
+
title={isGeneratingCode ? 'Generating code…' : 'Loading preview…'}
|
|
3258
3425
|
subtitle=""
|
|
3259
3426
|
size="md"
|
|
3260
3427
|
/>
|
|
@@ -3410,6 +3577,7 @@
|
|
|
3410
3577
|
<div class="widget-details-resize-s absolute bottom-0 left-4 right-4 h-1.5 cursor-ns-resize z-20 hover:bg-blue-400/30" onmousedown={(e) => startDetailsResize(e, 's')} title="Resize height"></div>
|
|
3411
3578
|
{/if}
|
|
3412
3579
|
</div>
|
|
3580
|
+
</div>
|
|
3413
3581
|
{/if}
|
|
3414
3582
|
|
|
3415
3583
|
<style>
|
|
@@ -135,6 +135,7 @@ declare const WidgetDetails: $$__sveltets_2_IsomorphicComponent<{
|
|
|
135
135
|
setPreviewLoadingState?: (val: boolean) => void;
|
|
136
136
|
getPreviewError?: () => string | null;
|
|
137
137
|
setPreviewBuildError?: (message: string | null) => void;
|
|
138
|
+
retryPreviewIfEmpty?: () => Promise<void>;
|
|
138
139
|
showNoCodeYetState?: () => void;
|
|
139
140
|
startPreviewLoadingPublic?: () => void;
|
|
140
141
|
sendMessageToPreview?: (message: Record<string, unknown>) => void;
|
|
@@ -224,6 +225,7 @@ declare const WidgetDetails: $$__sveltets_2_IsomorphicComponent<{
|
|
|
224
225
|
setPreviewLoadingState: (val: boolean) => void;
|
|
225
226
|
getPreviewError: () => string | null;
|
|
226
227
|
setPreviewBuildError: (message: string | null) => void;
|
|
228
|
+
retryPreviewIfEmpty: () => Promise<void>;
|
|
227
229
|
showNoCodeYetState: () => void;
|
|
228
230
|
startPreviewLoadingPublic: () => void;
|
|
229
231
|
sendMessageToPreview: (message: Record<string, unknown>) => void;
|
package/dist/pageHelpers.js
CHANGED
|
@@ -20,6 +20,9 @@ export function sanitizePreviewBuildError(raw) {
|
|
|
20
20
|
if (/failed to fetch|fetch failed/i.test(text)) {
|
|
21
21
|
return 'Could not reach the preview builder. Try Rebuild.';
|
|
22
22
|
}
|
|
23
|
+
if (/is not a function/i.test(text)) {
|
|
24
|
+
return 'Preview rebuild is unavailable in this session. Try Rebuild or reload the page.';
|
|
25
|
+
}
|
|
23
26
|
if (looksLikeHtmlErrorPage(text) || /error code:\s*1042/i.test(text)) {
|
|
24
27
|
return 'Preview builder is unavailable. The widget code was saved — use Rebuild to compile it.';
|
|
25
28
|
}
|
|
@@ -33,7 +36,7 @@ export function isInfrastructurePreviewError(raw) {
|
|
|
33
36
|
return false;
|
|
34
37
|
if (looksLikeHtmlErrorPage(raw))
|
|
35
38
|
return true;
|
|
36
|
-
return /preview builder is unavailable|could not reach the preview builder|unexpected error page/i.test(sanitizePreviewBuildError(raw));
|
|
39
|
+
return /preview builder is unavailable|could not reach the preview builder|unexpected error page|is not a function|requested resource was not found|rebuild is unavailable/i.test(sanitizePreviewBuildError(raw));
|
|
37
40
|
}
|
|
38
41
|
export function normalizeErrorMessage(rawError) {
|
|
39
42
|
let extracted = '';
|