@widgetic/creator 0.3.50 → 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;
|
|
@@ -868,6 +880,8 @@
|
|
|
868
880
|
const LEFT_COL_DEFAULT_PERCENT = 58;
|
|
869
881
|
const LEFT_COL_MIN_PERCENT = 32;
|
|
870
882
|
const LEFT_COL_MAX_PERCENT = 72;
|
|
883
|
+
const COL_RESIZER_PX = 10;
|
|
884
|
+
const PREVIEW_COL_MIN_PX = 240;
|
|
871
885
|
/** Chat-first layout: preview column starts collapsed and opens on generate. */
|
|
872
886
|
let previewColumnCollapsed = true;
|
|
873
887
|
let lastAutoExpandForGenerating = false;
|
|
@@ -891,6 +905,23 @@
|
|
|
891
905
|
expandPreviewColumn();
|
|
892
906
|
}
|
|
893
907
|
|
|
908
|
+
function maxLeftColPercent(): number {
|
|
909
|
+
const twoCol =
|
|
910
|
+
typeof document !== 'undefined'
|
|
911
|
+
? (document.querySelector('.widget-details-two-col') as HTMLElement | null)
|
|
912
|
+
: null;
|
|
913
|
+
const width = twoCol?.clientWidth || detailsPanelWidth || DETAILS_PANEL_DEFAULT_WIDTH;
|
|
914
|
+
if (width <= 0) return LEFT_COL_MAX_PERCENT;
|
|
915
|
+
const maxLeftPx = Math.max(160, width - COL_RESIZER_PX - PREVIEW_COL_MIN_PX);
|
|
916
|
+
return Math.min(LEFT_COL_MAX_PERCENT, (maxLeftPx / width) * 100);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function clampLeftColPercent(percent: number): number {
|
|
920
|
+
const maxP = maxLeftColPercent();
|
|
921
|
+
const minP = Math.min(LEFT_COL_MIN_PERCENT, maxP);
|
|
922
|
+
return Math.min(maxP, Math.max(minP, percent));
|
|
923
|
+
}
|
|
924
|
+
|
|
894
925
|
function collapsePreviewColumn() {
|
|
895
926
|
previewColumnCollapsed = true;
|
|
896
927
|
if (widthBeforePreviewExpand > 0) {
|
|
@@ -911,10 +942,7 @@
|
|
|
911
942
|
const fittedWidth = Math.min(expandedWidth, Math.max(widthBeforePreviewExpand, maxRight - detailsPanelX));
|
|
912
943
|
detailsPanelWidth = Math.max(widthBeforePreviewExpand, fittedWidth);
|
|
913
944
|
const usable = Math.max(1, detailsPanelWidth);
|
|
914
|
-
leftColPercent =
|
|
915
|
-
LEFT_COL_MAX_PERCENT,
|
|
916
|
-
Math.max(LEFT_COL_MIN_PERCENT, (widthBeforePreviewExpand / usable) * 100),
|
|
917
|
-
);
|
|
945
|
+
leftColPercent = clampLeftColPercent((widthBeforePreviewExpand / usable) * 100);
|
|
918
946
|
console.log('[WidgetDetails] Preview column expanded', {
|
|
919
947
|
from: widthBeforePreviewExpand,
|
|
920
948
|
to: detailsPanelWidth,
|
|
@@ -989,35 +1017,39 @@
|
|
|
989
1017
|
|
|
990
1018
|
/** Load published CDN preview without Worker probe (safe for background panels). */
|
|
991
1019
|
async function loadPublishedPreviewForCurrentStep(): Promise<boolean> {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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;
|
|
1005
1037
|
}
|
|
1006
|
-
if (currentStep === 'embed'
|
|
1007
|
-
const reachable = await probePublishedArtifactUrl(embedPreviewUrl);
|
|
1008
|
-
publishedArtifactReachable = reachable;
|
|
1009
|
-
if (!reachable) {
|
|
1010
|
-
previewError = 'Published version is not available. Use Rebuild code.';
|
|
1011
|
-
previewLoading = false;
|
|
1012
|
-
return false;
|
|
1013
|
-
}
|
|
1038
|
+
if (currentStep === 'embed') {
|
|
1014
1039
|
_lastEmbedPreviewBase = null;
|
|
1015
|
-
applyPreviewUrlToIframe(
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1040
|
+
applyPreviewUrlToIframe(publishedUrl);
|
|
1041
|
+
} else {
|
|
1042
|
+
_lastEditPreviewBase = null;
|
|
1043
|
+
if (currentStep === 'edit') {
|
|
1044
|
+
switchPreviewUrlForStep('edit', { forceReload: true });
|
|
1045
|
+
} else {
|
|
1046
|
+
applyPreviewUrlToIframe(publishedUrl);
|
|
1047
|
+
}
|
|
1019
1048
|
}
|
|
1020
|
-
|
|
1049
|
+
previewBuildDeferred = false;
|
|
1050
|
+
previewError = null;
|
|
1051
|
+
previewLoading = false;
|
|
1052
|
+
return true;
|
|
1021
1053
|
}
|
|
1022
1054
|
|
|
1023
1055
|
/** Probe Worker / build-from-repo pipeline without resetting panel state. */
|
|
@@ -1060,7 +1092,7 @@
|
|
|
1060
1092
|
}
|
|
1061
1093
|
|
|
1062
1094
|
if (probeResult === 'not-found') {
|
|
1063
|
-
console.log('[WidgetDetails] Widget not on Worker — build-from-repo', {
|
|
1095
|
+
console.log('[WidgetDetails] Widget not on Worker — published artifact or build-from-repo', {
|
|
1064
1096
|
widgetId: capturedWidgetId.substring(0, 8),
|
|
1065
1097
|
hasJwt: !!getAuthJwt(),
|
|
1066
1098
|
});
|
|
@@ -1069,9 +1101,19 @@
|
|
|
1069
1101
|
previewError = null;
|
|
1070
1102
|
return;
|
|
1071
1103
|
}
|
|
1104
|
+
if (await loadPublishedPreviewForCurrentStep()) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1072
1107
|
const hasRepo = !!(normalizeRepositoryId(selectedWidget) || currentRepositoryId);
|
|
1073
1108
|
if (!hasRepo) {
|
|
1074
|
-
|
|
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
|
+
}
|
|
1075
1117
|
console.log('[WidgetDetails] No repository yet — skipping build-from-repo');
|
|
1076
1118
|
previewLoading = false;
|
|
1077
1119
|
previewError = 'NO_CODE_YET';
|
|
@@ -1124,10 +1166,7 @@
|
|
|
1124
1166
|
previewHydrating = false;
|
|
1125
1167
|
resetPreviewMessageBridge();
|
|
1126
1168
|
clearPendingRuntimeErrorOverlay();
|
|
1127
|
-
|
|
1128
|
-
if (!isWidgetLoading) {
|
|
1129
|
-
previewLoading = false;
|
|
1130
|
-
}
|
|
1169
|
+
previewLoading = true;
|
|
1131
1170
|
isWidgetLoading = false;
|
|
1132
1171
|
previewError = null;
|
|
1133
1172
|
previewRetryCount = 0;
|
|
@@ -1181,11 +1220,8 @@
|
|
|
1181
1220
|
return;
|
|
1182
1221
|
}
|
|
1183
1222
|
|
|
1184
|
-
// Brand-new widget (no repo): skip Worker HEAD / build-from-repo.
|
|
1185
|
-
// If the widget already has a GitLab repo, compile even without a cached commit SHA
|
|
1186
|
-
// (localStorage miss, failed retries, unpublished lastCpgCommitSha).
|
|
1187
1223
|
const hasRepo = !!(normalizeRepositoryId(selectedWidget) || currentRepositoryId);
|
|
1188
|
-
if (!lastCommitId && lastPublishedVersion === null && !hasRepo) {
|
|
1224
|
+
if (!lastCommitId && lastPublishedVersion === null && !widgetJsPath && !hasRepo) {
|
|
1189
1225
|
console.log('[WidgetDetails] No code yet — skipping Worker probe/build-from-repo', {
|
|
1190
1226
|
widgetId: widgetId?.substring(0, 8),
|
|
1191
1227
|
hasRepo,
|
|
@@ -1226,6 +1262,27 @@
|
|
|
1226
1262
|
/** Trigger build-from-repo with retries — for duplicate/returning widgets.
|
|
1227
1263
|
* Unlike loadWidget, this doesn't reset state. Just builds and updates preview.
|
|
1228
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
|
+
|
|
1229
1286
|
export async function triggerBuildFromRepo(options?: { force?: boolean }): Promise<void> {
|
|
1230
1287
|
const forceRebuild = options?.force === true;
|
|
1231
1288
|
if (!selectedWidgetId) return;
|
|
@@ -1321,9 +1378,7 @@
|
|
|
1321
1378
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1322
1379
|
if (isStaleRun()) return;
|
|
1323
1380
|
try {
|
|
1324
|
-
const buildData = await
|
|
1325
|
-
widgetId: capturedWidgetId
|
|
1326
|
-
});
|
|
1381
|
+
const buildData = await requestBuildFromRepo(capturedWidgetId);
|
|
1327
1382
|
if (isStaleRun()) return;
|
|
1328
1383
|
|
|
1329
1384
|
console.log('[WidgetDetails] triggerBuildFromRepo succeeded', { buildTimeMs: buildData?.data?.buildTimeMs ?? buildData?.buildTimeMs, attempt });
|
|
@@ -1397,24 +1452,17 @@
|
|
|
1397
1452
|
|
|
1398
1453
|
if (selectedWidgetId === capturedWidgetId) {
|
|
1399
1454
|
// For published widgets, try loading via API proxy (handles both R2 and CDN)
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
console.log('[WidgetDetails] triggerBuildFromRepo:
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
iframeSrc = proxyUrl;
|
|
1410
|
-
setupPreviewFallbackTimeout();
|
|
1411
|
-
syncPreviewLoadingWithIframe();
|
|
1412
|
-
return;
|
|
1413
|
-
}
|
|
1414
|
-
console.warn('[WidgetDetails] API proxy returned', probeRes.status, '— artifact not on R2, showing generate button');
|
|
1415
|
-
} catch (probeErr) {
|
|
1416
|
-
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;
|
|
1417
1464
|
}
|
|
1465
|
+
console.warn('[WidgetDetails] js_path artifact not reachable — showing Rebuild');
|
|
1418
1466
|
}
|
|
1419
1467
|
|
|
1420
1468
|
previewLoading = false;
|
|
@@ -1461,7 +1509,9 @@
|
|
|
1461
1509
|
}
|
|
1462
1510
|
|
|
1463
1511
|
export function hasValidPreview(): boolean {
|
|
1464
|
-
|
|
1512
|
+
// Iframe can already show a compiled preview while `previewLoading`/`previewError`
|
|
1513
|
+
// still lag (e.g. iteration rebuild). Screenshot only needs a live contentWindow.
|
|
1514
|
+
return !!previewIframe?.contentWindow;
|
|
1465
1515
|
}
|
|
1466
1516
|
|
|
1467
1517
|
export function getPreviewIframe(): HTMLIFrameElement | null {
|
|
@@ -1533,6 +1583,37 @@
|
|
|
1533
1583
|
notifyPreviewCompileErrorChanged();
|
|
1534
1584
|
}
|
|
1535
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
|
+
|
|
1536
1617
|
export function showNoCodeYetState(): void {
|
|
1537
1618
|
previewLoading = false;
|
|
1538
1619
|
previewError = 'NO_CODE_YET';
|
|
@@ -1556,6 +1637,8 @@
|
|
|
1556
1637
|
if (!target || pendingPreviewMessages.length === 0) return;
|
|
1557
1638
|
for (const message of pendingPreviewMessages) {
|
|
1558
1639
|
target.postMessage(message, '*');
|
|
1640
|
+
applyRichTextInPreviewIframe(message);
|
|
1641
|
+
applyDesignOverlayInPreviewIframe(message);
|
|
1559
1642
|
}
|
|
1560
1643
|
pendingPreviewMessages = [];
|
|
1561
1644
|
}
|
|
@@ -1590,19 +1673,183 @@
|
|
|
1590
1673
|
notifyPreviewCompileErrorChanged();
|
|
1591
1674
|
}
|
|
1592
1675
|
flushPendingPreviewMessages();
|
|
1593
|
-
|
|
1594
|
-
dispatch('previewLoaded', { step: currentStep });
|
|
1595
|
-
}
|
|
1676
|
+
dispatch('previewLoaded', { step: currentStep });
|
|
1596
1677
|
// Emit previewReady so the parent app can clear the "…loading preview…" status.
|
|
1597
1678
|
dispatch('previewReady', { widgetId: selectedWidgetId, step: currentStep });
|
|
1598
1679
|
// Fill available preview area once the widget confirms it is ready.
|
|
1599
1680
|
scheduleFitPreviewToArea();
|
|
1600
1681
|
}
|
|
1601
1682
|
|
|
1683
|
+
function looksLikeHtmlMarkup(value: unknown): value is string {
|
|
1684
|
+
return typeof value === 'string' && /<[a-z][\s\S]*>/i.test(value);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
/** Same-origin preview iframes (Vite /__widget_builder__ proxy): restore bold/italic after widgets set textContent. */
|
|
1688
|
+
function applyRichTextInPreviewIframe(message: Record<string, unknown>): void {
|
|
1689
|
+
if (message.type !== 'widgetic:update') return;
|
|
1690
|
+
const doc = previewIframe?.contentDocument;
|
|
1691
|
+
if (!doc) return;
|
|
1692
|
+
const apply = () => {
|
|
1693
|
+
try {
|
|
1694
|
+
const content = message.content as Record<string, unknown> | undefined;
|
|
1695
|
+
if (content) {
|
|
1696
|
+
for (const [key, value] of Object.entries(content)) {
|
|
1697
|
+
if (!looksLikeHtmlMarkup(value)) continue;
|
|
1698
|
+
doc.querySelectorAll(`[data-content="${CSS.escape(key)}"]`).forEach((el) => {
|
|
1699
|
+
if (el instanceof HTMLImageElement || el instanceof HTMLInputElement) return;
|
|
1700
|
+
el.innerHTML = value;
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
const items = message.contentItems as Array<Record<string, unknown>> | undefined;
|
|
1705
|
+
if (Array.isArray(items)) {
|
|
1706
|
+
for (const item of items) {
|
|
1707
|
+
const itemEl = item?.id
|
|
1708
|
+
? doc.querySelector(`[data-content-item="${CSS.escape(String(item.id))}"]`)
|
|
1709
|
+
: null;
|
|
1710
|
+
const scope = itemEl || doc;
|
|
1711
|
+
for (const [key, value] of Object.entries(item || {})) {
|
|
1712
|
+
if (key === 'id' || !looksLikeHtmlMarkup(value)) continue;
|
|
1713
|
+
scope.querySelectorAll(`[data-content="${CSS.escape(key)}"]`).forEach((el) => {
|
|
1714
|
+
if (el instanceof HTMLImageElement || el instanceof HTMLInputElement) return;
|
|
1715
|
+
el.innerHTML = value;
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
console.warn('[WidgetDetails] Rich-text preview overlay skipped:', error);
|
|
1722
|
+
}
|
|
1723
|
+
};
|
|
1724
|
+
requestAnimationFrame(() => {
|
|
1725
|
+
apply();
|
|
1726
|
+
setTimeout(apply, 50);
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
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
|
+
|
|
1602
1847
|
/** Send a postMessage to the widget preview iframe (for live property editing) */
|
|
1603
1848
|
export function sendMessageToPreview(message: Record<string, unknown>): void {
|
|
1604
1849
|
if (previewWidgetReady && previewIframe?.contentWindow) {
|
|
1605
1850
|
previewIframe.contentWindow.postMessage(message, '*');
|
|
1851
|
+
applyRichTextInPreviewIframe(message);
|
|
1852
|
+
applyDesignOverlayInPreviewIframe(message);
|
|
1606
1853
|
return;
|
|
1607
1854
|
}
|
|
1608
1855
|
pendingPreviewMessages.push(message);
|
|
@@ -1944,9 +2191,15 @@
|
|
|
1944
2191
|
}
|
|
1945
2192
|
} else {
|
|
1946
2193
|
previewHydrating = false;
|
|
1947
|
-
previewWidgetReady = true;
|
|
1948
2194
|
dispatch('previewReady', { widgetId: selectedWidgetId, step: currentStep });
|
|
1949
|
-
// Create
|
|
2195
|
+
// Create: wait for widgetic:ready so contentItems overlay is not sent before the widget listens.
|
|
2196
|
+
if (!previewWidgetReady) {
|
|
2197
|
+
if (previewReadyFallbackTimeout) clearTimeout(previewReadyFallbackTimeout);
|
|
2198
|
+
previewReadyFallbackTimeout = setTimeout(() => {
|
|
2199
|
+
console.warn('[WidgetDetails] Create step widgetic:ready fallback — applying overlay');
|
|
2200
|
+
markPreviewWidgetReady();
|
|
2201
|
+
}, 1500);
|
|
2202
|
+
}
|
|
1950
2203
|
scheduleFitPreviewToArea();
|
|
1951
2204
|
}
|
|
1952
2205
|
}
|
|
@@ -2124,9 +2377,7 @@
|
|
|
2124
2377
|
}
|
|
2125
2378
|
|
|
2126
2379
|
function clampPanelWidth(w: number): number {
|
|
2127
|
-
|
|
2128
|
-
const maxWidth = Math.min(DETAILS_PANEL_MAX_WIDTH, window.innerWidth - 24);
|
|
2129
|
-
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));
|
|
2130
2381
|
}
|
|
2131
2382
|
|
|
2132
2383
|
function clampPanelHeight(h: number): number {
|
|
@@ -2195,8 +2446,8 @@
|
|
|
2195
2446
|
}
|
|
2196
2447
|
|
|
2197
2448
|
/**
|
|
2198
|
-
* Park the panel
|
|
2199
|
-
*
|
|
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.
|
|
2200
2451
|
*/
|
|
2201
2452
|
export function positionBesideShape(shapeRect: {
|
|
2202
2453
|
left: number;
|
|
@@ -2217,41 +2468,20 @@
|
|
|
2217
2468
|
return;
|
|
2218
2469
|
}
|
|
2219
2470
|
|
|
2220
|
-
const
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
const availableWidth = Math.max(DETAILS_PANEL_MIN_WIDTH, shapeRect.viewportWidth - pad * 2);
|
|
2224
|
-
detailsPanelWidth = clampPanelWidth(Math.min(DETAILS_PANEL_MAX_WIDTH, availableWidth));
|
|
2225
|
-
detailsPanelHeight = clampPanelHeight(
|
|
2226
|
-
Math.min(DETAILS_PANEL_MAX_HEIGHT, shapeRect.viewportHeight - pad * 2),
|
|
2227
|
-
);
|
|
2228
|
-
|
|
2229
|
-
const shapeCenterX = shapeRect.left + shapeRect.width / 2;
|
|
2230
|
-
const viewCenterX = shapeRect.viewportLeft + shapeRect.viewportWidth / 2;
|
|
2231
|
-
const openOnRight = shapeCenterX < viewCenterX;
|
|
2232
|
-
if (openOnRight) {
|
|
2233
|
-
detailsPanelX = shapeRect.left + shapeRect.width + DETAILS_PANEL_SHAPE_GAP;
|
|
2234
|
-
if (detailsPanelX + detailsPanelWidth > viewRight - pad) {
|
|
2235
|
-
detailsPanelX = Math.max(viewLeft + pad, viewRight - pad - detailsPanelWidth);
|
|
2236
|
-
}
|
|
2237
|
-
} else {
|
|
2238
|
-
detailsPanelX = shapeRect.left - DETAILS_PANEL_SHAPE_GAP - detailsPanelWidth;
|
|
2239
|
-
if (detailsPanelX < viewLeft + pad) {
|
|
2240
|
-
detailsPanelX = viewLeft + pad;
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2471
|
+
const gap = DETAILS_PANEL_SHAPE_GAP;
|
|
2472
|
+
detailsPanelWidth = clampPanelWidth(DETAILS_PANEL_MAX_WIDTH);
|
|
2473
|
+
detailsPanelHeight = clampPanelHeight(DETAILS_PANEL_MAX_HEIGHT);
|
|
2243
2474
|
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
);
|
|
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;
|
|
2249
2479
|
console.log('[WidgetDetails] Positioned beside shape', {
|
|
2250
|
-
openOnRight,
|
|
2251
2480
|
x: Math.round(detailsPanelX),
|
|
2252
2481
|
y: Math.round(detailsPanelY),
|
|
2253
2482
|
w: detailsPanelWidth,
|
|
2254
2483
|
h: detailsPanelHeight,
|
|
2484
|
+
shapeLeft: Math.round(shapeRect.left),
|
|
2255
2485
|
});
|
|
2256
2486
|
}
|
|
2257
2487
|
|
|
@@ -2273,10 +2503,7 @@
|
|
|
2273
2503
|
const width = twoCol?.clientWidth || detailsPanelWidth || DETAILS_PANEL_DEFAULT_WIDTH;
|
|
2274
2504
|
if (width <= 0) return;
|
|
2275
2505
|
const deltaPercent = ((e.clientX - columnResizeStartX) / width) * 100;
|
|
2276
|
-
leftColPercent =
|
|
2277
|
-
LEFT_COL_MAX_PERCENT,
|
|
2278
|
-
Math.max(LEFT_COL_MIN_PERCENT, columnResizeStartPercent + deltaPercent)
|
|
2279
|
-
);
|
|
2506
|
+
leftColPercent = clampLeftColPercent(columnResizeStartPercent + deltaPercent);
|
|
2280
2507
|
// Keep preview iframe inside the shrinking right column (avoid clipped "cut off" widgets).
|
|
2281
2508
|
requestAnimationFrame(() => {
|
|
2282
2509
|
clampPreviewToArea();
|
|
@@ -2363,15 +2590,27 @@
|
|
|
2363
2590
|
window.removeEventListener('blur', stopFn);
|
|
2364
2591
|
}
|
|
2365
2592
|
|
|
2366
|
-
|
|
2593
|
+
let detailsResizeEdge: 'se' | 'sw' | 'e' | 'w' | 's' = 'se';
|
|
2594
|
+
let detailsResizeStartPanelX = 0;
|
|
2595
|
+
let detailsResizeStartPanelY = 0;
|
|
2596
|
+
|
|
2597
|
+
function startDetailsResize(e: MouseEvent, edge: 'se' | 'sw' | 'e' | 'w' | 's' = 'se') {
|
|
2367
2598
|
e.preventDefault();
|
|
2368
2599
|
e.stopPropagation();
|
|
2369
2600
|
isResizingDetails = true;
|
|
2601
|
+
detailsResizeEdge = edge;
|
|
2370
2602
|
detailsResizeStartX = e.clientX;
|
|
2371
2603
|
detailsResizeStartY = e.clientY;
|
|
2372
2604
|
detailsResizeStartWidth = detailsPanelWidth;
|
|
2373
2605
|
detailsResizeStartHeight = detailsPanelHeight || getDefaultDetailsPanelHeight();
|
|
2374
|
-
|
|
2606
|
+
detailsResizeStartPanelX = detailsPanelX;
|
|
2607
|
+
detailsResizeStartPanelY = detailsPanelY;
|
|
2608
|
+
const cursor =
|
|
2609
|
+
edge === 'e' || edge === 'w' ? 'ew-resize'
|
|
2610
|
+
: edge === 's' ? 'ns-resize'
|
|
2611
|
+
: edge === 'sw' ? 'nesw-resize'
|
|
2612
|
+
: 'nwse-resize';
|
|
2613
|
+
document.body.style.cursor = cursor;
|
|
2375
2614
|
document.body.style.userSelect = 'none';
|
|
2376
2615
|
window.addEventListener('mousemove', onDetailsResize);
|
|
2377
2616
|
addGlobalStopListeners(stopDetailsResize);
|
|
@@ -2381,10 +2620,46 @@
|
|
|
2381
2620
|
if (!isResizingDetails) return;
|
|
2382
2621
|
const deltaX = e.clientX - detailsResizeStartX;
|
|
2383
2622
|
const deltaY = e.clientY - detailsResizeStartY;
|
|
2384
|
-
const
|
|
2385
|
-
const
|
|
2386
|
-
|
|
2387
|
-
|
|
2623
|
+
const minW = DETAILS_PANEL_MIN_WIDTH;
|
|
2624
|
+
const minH = DETAILS_PANEL_MIN_HEIGHT;
|
|
2625
|
+
const maxRight = window.innerWidth - 8;
|
|
2626
|
+
const maxBottom = window.innerHeight - 8;
|
|
2627
|
+
|
|
2628
|
+
let nextW = detailsResizeStartWidth;
|
|
2629
|
+
let nextH = detailsResizeStartHeight;
|
|
2630
|
+
let nextX = detailsResizeStartPanelX;
|
|
2631
|
+
let nextY = detailsResizeStartPanelY;
|
|
2632
|
+
|
|
2633
|
+
if (detailsResizeEdge === 'e' || detailsResizeEdge === 'se') {
|
|
2634
|
+
nextW = detailsResizeStartWidth + deltaX;
|
|
2635
|
+
}
|
|
2636
|
+
if (detailsResizeEdge === 'w' || detailsResizeEdge === 'sw') {
|
|
2637
|
+
nextW = detailsResizeStartWidth - deltaX;
|
|
2638
|
+
nextX = detailsResizeStartPanelX + deltaX;
|
|
2639
|
+
}
|
|
2640
|
+
if (detailsResizeEdge === 's' || detailsResizeEdge === 'se' || detailsResizeEdge === 'sw') {
|
|
2641
|
+
nextH = detailsResizeStartHeight + deltaY;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
nextW = Math.max(minW, nextW);
|
|
2645
|
+
nextH = Math.max(minH, nextH);
|
|
2646
|
+
if (detailsResizeEdge === 'w' || detailsResizeEdge === 'sw') {
|
|
2647
|
+
nextX = detailsResizeStartPanelX + (detailsResizeStartWidth - nextW);
|
|
2648
|
+
if (nextX < 8) {
|
|
2649
|
+
nextW -= (8 - nextX);
|
|
2650
|
+
nextX = 8;
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
if (nextX + nextW > maxRight) nextW = maxRight - nextX;
|
|
2654
|
+
if (nextY + nextH > maxBottom) nextH = maxBottom - nextY;
|
|
2655
|
+
|
|
2656
|
+
detailsPanelWidth = nextW;
|
|
2657
|
+
detailsPanelHeight = nextH;
|
|
2658
|
+
detailsPanelX = nextX;
|
|
2659
|
+
detailsPanelY = nextY;
|
|
2660
|
+
if (!previewColumnCollapsed) {
|
|
2661
|
+
leftColPercent = clampLeftColPercent(leftColPercent);
|
|
2662
|
+
}
|
|
2388
2663
|
}
|
|
2389
2664
|
|
|
2390
2665
|
function stopDetailsResize() {
|
|
@@ -2403,14 +2678,16 @@
|
|
|
2403
2678
|
|
|
2404
2679
|
function clampDetailsPanelPosition(x: number, y: number): { x: number; y: number } {
|
|
2405
2680
|
if (typeof window === 'undefined') return { x, y };
|
|
2406
|
-
const pad = 8;
|
|
2407
2681
|
const panelH = detailsPanelHeight || getDefaultDetailsPanelHeight();
|
|
2408
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);
|
|
2409
2685
|
const minTop = detailsPanelTopOffset;
|
|
2410
|
-
const
|
|
2411
|
-
const
|
|
2686
|
+
const minX = minVisibleX - panelW;
|
|
2687
|
+
const maxX = window.innerWidth - minVisibleX;
|
|
2688
|
+
const maxTop = Math.max(minTop, window.innerHeight - minVisibleY);
|
|
2412
2689
|
return {
|
|
2413
|
-
x: Math.min(
|
|
2690
|
+
x: Math.min(maxX, Math.max(minX, x)),
|
|
2414
2691
|
y: Math.min(maxTop, Math.max(minTop, y)),
|
|
2415
2692
|
};
|
|
2416
2693
|
}
|
|
@@ -2847,16 +3124,28 @@
|
|
|
2847
3124
|
{#if selectedWidgetId}
|
|
2848
3125
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
2849
3126
|
<div
|
|
2850
|
-
class="widget-details-floating-
|
|
2851
|
-
|
|
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
|
|
2852
3143
|
{fillViewport
|
|
2853
|
-
? 'bg-white rounded-none border-x border-b border-t-0 ring-0 shadow-none'
|
|
2854
|
-
: 'bg-white/95 backdrop-blur-sm shadow-2xl rounded-xl border'}
|
|
3144
|
+
? 'bg-white rounded-none border-x border-b border-t-0 ring-0 shadow-none overflow-hidden'
|
|
3145
|
+
: 'bg-white/95 backdrop-blur-sm shadow-2xl rounded-xl border overflow-hidden'}
|
|
2855
3146
|
{isFocusedPanel
|
|
2856
3147
|
? (fillViewport ? 'border-blue-400' : 'border-blue-400 ring-2 ring-blue-200/80')
|
|
2857
3148
|
: 'border-gray-300 opacity-[0.97]'}"
|
|
2858
|
-
style="left: {detailsPanelX}px; top: {detailsPanelY}px; width: {detailsPanelWidth}px; z-index: {panelZIndex};
|
|
2859
|
-
height: {detailsPanelHeight ? detailsPanelHeight + 'px' : `calc(100vh - ${detailsPanelTopOffset + detailsPanelBottomMargin}px)`};"
|
|
2860
3149
|
onmousedown={handlePanelFocusRequest}
|
|
2861
3150
|
>
|
|
2862
3151
|
<!-- Widget Details Top Bar — title, steps, and close on one row -->
|
|
@@ -2866,7 +3155,7 @@
|
|
|
2866
3155
|
onpointerdown={startDetailsDrag}
|
|
2867
3156
|
>
|
|
2868
3157
|
<span
|
|
2869
|
-
class="widget-details-name pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 max-w-[
|
|
3158
|
+
class="widget-details-name pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 max-w-[28%] truncate text-sm font-bold text-gray-800"
|
|
2870
3159
|
title={selectedWidget?.name || ''}
|
|
2871
3160
|
>
|
|
2872
3161
|
{selectedWidget?.name || (isDraftConvertPanel ? 'Widget Draft' : 'Widget')}
|
|
@@ -2925,7 +3214,7 @@
|
|
|
2925
3214
|
</div>
|
|
2926
3215
|
</div>
|
|
2927
3216
|
|
|
2928
|
-
<div class="widget-details-debug-buttons flex items-center gap-1.5 shrink-0">
|
|
3217
|
+
<div class="widget-details-debug-buttons flex items-center gap-1.5 shrink-0 pr-1">
|
|
2929
3218
|
<button
|
|
2930
3219
|
type="button"
|
|
2931
3220
|
onclick={() => previewColumnCollapsed ? expandPreviewColumn() : collapsePreviewColumn()}
|
|
@@ -2939,23 +3228,13 @@
|
|
|
2939
3228
|
>
|
|
2940
3229
|
{previewColumnCollapsed ? 'Open Preview' : 'Close Preview'}
|
|
2941
3230
|
</button>
|
|
2942
|
-
<button
|
|
2943
|
-
type="button"
|
|
2944
|
-
onclick={handleCloseButtonClick}
|
|
2945
|
-
onmousedown={(e) => e.stopPropagation()}
|
|
2946
|
-
onpointerdown={(e) => e.stopPropagation()}
|
|
2947
|
-
class="widget-details-close-bt w-6 h-6 flex items-center justify-center rounded-lg hover:bg-gray-200 text-gray-500 hover:text-gray-700 cursor-pointer transition-colors text-sm"
|
|
2948
|
-
title="Close widget details"
|
|
2949
|
-
>
|
|
2950
|
-
✕
|
|
2951
|
-
</button>
|
|
2952
3231
|
</div>
|
|
2953
3232
|
</div>
|
|
2954
3233
|
</div>
|
|
2955
3234
|
|
|
2956
3235
|
<!-- Widget Details Content: Two-column layout -->
|
|
2957
|
-
<div class="widget-details-content-wrapper relative m-2.5 min-w-0 min-h-0 flex-1">
|
|
2958
|
-
<div class="widget-details-two-col flex h-full min-h-0 gap-0">
|
|
3236
|
+
<div class="widget-details-content-wrapper relative m-2.5 min-w-0 min-h-0 flex-1 overflow-x-hidden">
|
|
3237
|
+
<div class="widget-details-two-col flex h-full min-h-0 min-w-0 gap-0 overflow-x-hidden">
|
|
2959
3238
|
|
|
2960
3239
|
<!-- ═══ LEFT COLUMN: Step Content (scrollable) ═══ -->
|
|
2961
3240
|
<div
|
|
@@ -2988,24 +3267,28 @@
|
|
|
2988
3267
|
Commit: <span class="font-mono font-medium text-gray-600" title={lastCommitId || ''}>{lastCommitId ? lastCommitId.substring(0, 8) : '—'}</span>
|
|
2989
3268
|
</span>
|
|
2990
3269
|
{#if lastPublishedVersion !== null}
|
|
2991
|
-
<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>
|
|
2992
3271
|
{/if}
|
|
2993
3272
|
</div>
|
|
2994
3273
|
</div>
|
|
2995
3274
|
|
|
2996
3275
|
<!-- Scrollable step content -->
|
|
2997
3276
|
<div
|
|
2998
|
-
class="widget-details-left-content flex flex-col flex-1 min-h-0 relative rounded-b-lg
|
|
3277
|
+
class="widget-details-left-content flex flex-col flex-1 min-h-0 relative rounded-b-lg overflow-hidden overflow-x-hidden"
|
|
2999
3278
|
bind:this={contentContainerEl}
|
|
3000
3279
|
>
|
|
3001
3280
|
<div class="actions-panel widget-details-actions-panel relative flex flex-col flex-1 min-h-0">
|
|
3002
3281
|
|
|
3003
3282
|
<!-- Keep all steps mounted — switching tabs must not destroy chat / PropsEditor state -->
|
|
3004
|
-
<div class="widget-details-create-step flex flex-col flex-1 min-h-0 overflow-hidden
|
|
3005
|
-
<
|
|
3006
|
-
|
|
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>
|
|
3007
3290
|
</div>
|
|
3008
|
-
<div class="widget-details-edit-step flex flex-col h-full min-h-0 overflow-hidden {currentStep === 'edit' ? '' : 'hidden'}">
|
|
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'}">
|
|
3009
3292
|
<slot name="composition-editor" />
|
|
3010
3293
|
</div>
|
|
3011
3294
|
<div class="widget-details-embed-step flex flex-col gap-2 p-2 {currentStep === 'embed' ? '' : 'hidden'}">
|
|
@@ -3138,7 +3421,8 @@
|
|
|
3138
3421
|
<div class="widget-preview-fallback relative flex items-center justify-center h-full rounded-lg bg-gray-50 overflow-hidden">
|
|
3139
3422
|
{#if isGeneratingCode || previewLoading}
|
|
3140
3423
|
<GenerateLoader
|
|
3141
|
-
title={isGeneratingCode ? 'Generating code…' : '
|
|
3424
|
+
title={isGeneratingCode ? 'Generating code…' : 'Loading preview…'}
|
|
3425
|
+
subtitle=""
|
|
3142
3426
|
size="md"
|
|
3143
3427
|
/>
|
|
3144
3428
|
{:else if previewError === 'DRAFT_PANEL'}
|
|
@@ -3190,6 +3474,17 @@
|
|
|
3190
3474
|
</div>
|
|
3191
3475
|
{/if}
|
|
3192
3476
|
</div>
|
|
3477
|
+
{:else if lastGenerationFailed}
|
|
3478
|
+
<div class="widget-preview-generation-error flex flex-col items-center justify-center text-center p-4">
|
|
3479
|
+
<div class="text-3xl mb-2">⚠️</div>
|
|
3480
|
+
<div class="font-medium text-sm text-red-600 mb-1">Code generation failed</div>
|
|
3481
|
+
<div class="text-xs max-w-[220px] text-gray-600 mb-3">{lastGenerationError || 'Fix the prompt or attachments, then retry.'}</div>
|
|
3482
|
+
<button
|
|
3483
|
+
type="button"
|
|
3484
|
+
onclick={() => { dispatch('generateNow', { widgetId: selectedWidgetId }); }}
|
|
3485
|
+
class="generate-now-btn px-3 py-1.5 text-xs rounded border border-green-500 text-green-700 bg-white hover:bg-green-50 cursor-pointer font-medium"
|
|
3486
|
+
>▶ Retry</button>
|
|
3487
|
+
</div>
|
|
3193
3488
|
{:else if isLoadingDirectPreviewUrl || previewLoading}
|
|
3194
3489
|
<GenerateLoader title="Building Preview…" subtitle="Compiling widget code…" size="md" />
|
|
3195
3490
|
{:else}
|
|
@@ -3252,7 +3547,7 @@
|
|
|
3252
3547
|
</div> <!-- end of widget-details-content-wrapper -->
|
|
3253
3548
|
|
|
3254
3549
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
3255
|
-
<div class="absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize z-
|
|
3550
|
+
<div class="widget-details-resize-se absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize z-20 group" onmousedown={(e) => startDetailsResize(e, 'se')} title="Resize panel">
|
|
3256
3551
|
<svg class="w-3 h-3 absolute bottom-0.5 right-0.5 text-gray-400 group-hover:text-gray-600 transition-colors" viewBox="0 0 6 6" fill="currentColor">
|
|
3257
3552
|
<circle cx="5" cy="1" r="0.7"/>
|
|
3258
3553
|
<circle cx="3" cy="3" r="0.7"/>
|
|
@@ -3262,6 +3557,26 @@
|
|
|
3262
3557
|
<circle cx="5" cy="5" r="0.7"/>
|
|
3263
3558
|
</svg>
|
|
3264
3559
|
</div>
|
|
3560
|
+
{#if !fillViewport}
|
|
3561
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
3562
|
+
<div class="widget-details-resize-sw absolute bottom-0 left-0 w-4 h-4 cursor-nesw-resize z-20 group" onmousedown={(e) => startDetailsResize(e, 'sw')} title="Resize panel">
|
|
3563
|
+
<svg class="w-3 h-3 absolute bottom-0.5 left-0.5 text-gray-400 group-hover:text-gray-600 transition-colors rotate-90" viewBox="0 0 6 6" fill="currentColor">
|
|
3564
|
+
<circle cx="5" cy="1" r="0.7"/>
|
|
3565
|
+
<circle cx="3" cy="3" r="0.7"/>
|
|
3566
|
+
<circle cx="5" cy="3" r="0.7"/>
|
|
3567
|
+
<circle cx="1" cy="5" r="0.7"/>
|
|
3568
|
+
<circle cx="3" cy="5" r="0.7"/>
|
|
3569
|
+
<circle cx="5" cy="5" r="0.7"/>
|
|
3570
|
+
</svg>
|
|
3571
|
+
</div>
|
|
3572
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
3573
|
+
<div class="widget-details-resize-e absolute top-8 bottom-4 right-0 w-1.5 cursor-ew-resize z-20 hover:bg-blue-400/30" onmousedown={(e) => startDetailsResize(e, 'e')} title="Resize width"></div>
|
|
3574
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
3575
|
+
<div class="widget-details-resize-w absolute top-8 bottom-4 left-0 w-1.5 cursor-ew-resize z-20 hover:bg-blue-400/30" onmousedown={(e) => startDetailsResize(e, 'w')} title="Resize width"></div>
|
|
3576
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
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>
|
|
3578
|
+
{/if}
|
|
3579
|
+
</div>
|
|
3265
3580
|
</div>
|
|
3266
3581
|
{/if}
|
|
3267
3582
|
|