@widgetic/creator 0.3.58 → 0.3.59
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
|
@@ -447,6 +447,7 @@
|
|
|
447
447
|
*/
|
|
448
448
|
import SelectorDropdown from './components/SelectorDropdown.svelte';
|
|
449
449
|
import type { DropdownItem } from './components/SelectorDropdown.svelte';
|
|
450
|
+
import WidgetShapeLivePreviews from './components/WidgetShapeLivePreviews.svelte';
|
|
450
451
|
|
|
451
452
|
// WIDGET DETAILS COMPONENT — lazy-loaded to reduce peak build memory
|
|
452
453
|
let WidgetDetails: any;
|
|
@@ -473,6 +474,10 @@
|
|
|
473
474
|
|
|
474
475
|
let WidgeticCanvas: any;
|
|
475
476
|
let canvasRef: any;
|
|
477
|
+
/** HTML iframe overlays over Fabric WidgetShapes (W3). */
|
|
478
|
+
let widgetShapeLivePreviewsRef: { sync?: () => void } | null = null;
|
|
479
|
+
/** Selected WidgetShape id for chrome highlight (from Fabric selection). */
|
|
480
|
+
let canvasSelectedWidgetShapeId: string | null = null;
|
|
476
481
|
let widgetDetailsModulePromise: Promise<any> | null = null;
|
|
477
482
|
|
|
478
483
|
function loadWidgetDetailsModule(): Promise<any> {
|
|
@@ -8996,6 +9001,48 @@ Requirements:
|
|
|
8996
9001
|
return resolvePublishedWidgetUrl(widgetId, version, jsPath);
|
|
8997
9002
|
}
|
|
8998
9003
|
|
|
9004
|
+
/** Widget Builder origin for canvas shape iframes (mirrors WidgetDetails). */
|
|
9005
|
+
function widgetBuilderPreviewBaseUrl(): string {
|
|
9006
|
+
const publicBase = (
|
|
9007
|
+
widgetBuilderUrl ||
|
|
9008
|
+
import.meta.env.VITE_WIDGET_BUILDER_URL ||
|
|
9009
|
+
import.meta.env.PUBLIC_WIDGET_BUILDER_URL ||
|
|
9010
|
+
'https://wbuilder.widgetic.com'
|
|
9011
|
+
).replace(/\/+$/, '');
|
|
9012
|
+
if (import.meta.env.DEV && publicBase.includes('wbuilder.widgetic.com')) {
|
|
9013
|
+
return '/__widget_builder__';
|
|
9014
|
+
}
|
|
9015
|
+
return publicBase;
|
|
9016
|
+
}
|
|
9017
|
+
|
|
9018
|
+
/**
|
|
9019
|
+
* Live preview URL for a canvas WidgetShape overlay.
|
|
9020
|
+
* Prefer published CDN when available; else Worker /preview when code exists.
|
|
9021
|
+
* Returns null → screenshot-only fallback on the Fabric shape.
|
|
9022
|
+
*/
|
|
9023
|
+
function resolveWidgetShapeLivePreviewUrl(widgetId: string): string | null {
|
|
9024
|
+
if (!widgetId) return null;
|
|
9025
|
+
const published = getPanelEditPreviewUrl(widgetId);
|
|
9026
|
+
if (published) return published;
|
|
9027
|
+
|
|
9028
|
+
const widget = getWidgetById(widgetId);
|
|
9029
|
+
if (!widget) return null;
|
|
9030
|
+
const ps = getPanelDisplayState(widgetId);
|
|
9031
|
+
const hasCode = !!(ps.lastCommitId || loadCommitIdForWidget(widgetId));
|
|
9032
|
+
if (!hasCode) return null;
|
|
9033
|
+
|
|
9034
|
+
return `${widgetBuilderPreviewBaseUrl()}/preview/${widgetId}`;
|
|
9035
|
+
}
|
|
9036
|
+
|
|
9037
|
+
function syncWidgetShapeLivePreviews(): void {
|
|
9038
|
+
try {
|
|
9039
|
+
canvasSelectedWidgetShapeId = canvasRef?.getSelectedWidgetShapeId?.() ?? null;
|
|
9040
|
+
widgetShapeLivePreviewsRef?.sync?.();
|
|
9041
|
+
} catch (err) {
|
|
9042
|
+
console.warn('[CreatorApp] WidgetShape live preview sync failed', err);
|
|
9043
|
+
}
|
|
9044
|
+
}
|
|
9045
|
+
|
|
8999
9046
|
function getPanelEmbedPreviewUrl(widgetId: string): string | null {
|
|
9000
9047
|
const base = getPanelEditPreviewUrl(widgetId);
|
|
9001
9048
|
if (!base) return null;
|
|
@@ -11298,6 +11345,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
11298
11345
|
if (selectedWidget?.id && !didForceCreateNewWidget) {
|
|
11299
11346
|
placeSelectedWidgetShapeOnCanvas();
|
|
11300
11347
|
}
|
|
11348
|
+
syncWidgetShapeLivePreviews();
|
|
11301
11349
|
return;
|
|
11302
11350
|
}
|
|
11303
11351
|
const { loaded, widgetIds: widgetIdsOnCanvas } = await tryLoadCanvasState();
|
|
@@ -11306,6 +11354,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
11306
11354
|
if (!isCanvasStateLoaded) {
|
|
11307
11355
|
scheduleCanvasHydrateRetry('canvas ready before canvasId');
|
|
11308
11356
|
}
|
|
11357
|
+
syncWidgetShapeLivePreviews();
|
|
11309
11358
|
return;
|
|
11310
11359
|
}
|
|
11311
11360
|
await syncWidgetsWithCanvas(widgetIdsOnCanvas);
|
|
@@ -11318,6 +11367,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
11318
11367
|
} else if (isConvertOrDraftPanelOpen()) {
|
|
11319
11368
|
// console.log('[CreatorApp] Skip primary shape select — convert/draft panel is open');
|
|
11320
11369
|
}
|
|
11370
|
+
syncWidgetShapeLivePreviews();
|
|
11321
11371
|
}
|
|
11322
11372
|
|
|
11323
11373
|
/**
|
|
@@ -12550,6 +12600,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12550
12600
|
onWidgetRename={handleWidgetRename}
|
|
12551
12601
|
onRefreshScreenshot={handleRefreshScreenshot}
|
|
12552
12602
|
onSetPrimaryWidget={handleSetPrimaryWidget}
|
|
12603
|
+
onAfterRender={syncWidgetShapeLivePreviews}
|
|
12553
12604
|
getWidgetName={getWidgetNameById}
|
|
12554
12605
|
isWidgetKnown={isWidgetKnownById}
|
|
12555
12606
|
onNonWidgetSelected={() => {
|
|
@@ -12574,6 +12625,24 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12574
12625
|
<!-- FLOATING PANELS OVERLAY (pointer-events-none container, each panel is pointer-events-auto) -->
|
|
12575
12626
|
<div class="floating-panels-overlay absolute inset-0 z-2003 pointer-events-none">
|
|
12576
12627
|
|
|
12628
|
+
<!-- W3: live iframe + drag bar on WidgetShapes (iframe interactive; body drag locked) -->
|
|
12629
|
+
<WidgetShapeLivePreviews
|
|
12630
|
+
bind:this={widgetShapeLivePreviewsRef}
|
|
12631
|
+
getRects={() => canvasRef?.listWidgetShapeScreenRects?.() ?? []}
|
|
12632
|
+
resolveUrl={resolveWidgetShapeLivePreviewUrl}
|
|
12633
|
+
selectedWidgetId={canvasSelectedWidgetShapeId}
|
|
12634
|
+
onTranslate={(widgetId, dx, dy) => {
|
|
12635
|
+
canvasRef?.translateWidgetShapeByScreen?.(widgetId, dx, dy);
|
|
12636
|
+
}}
|
|
12637
|
+
onSelectShape={(widgetId) => {
|
|
12638
|
+
canvasRef?.selectWidgetShapeById?.(widgetId);
|
|
12639
|
+
canvasSelectedWidgetShapeId = widgetId;
|
|
12640
|
+
}}
|
|
12641
|
+
onOpenChat={(widgetId) => {
|
|
12642
|
+
void openWidgetDetailsPanel(widgetId);
|
|
12643
|
+
}}
|
|
12644
|
+
/>
|
|
12645
|
+
|
|
12577
12646
|
<!-- ═══ PANEL 2: Widgets Panel — debug + showWidgetsList (site passes false) ═══ -->
|
|
12578
12647
|
{#if debugMode && showWidgetsList}
|
|
12579
12648
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
@@ -763,7 +763,11 @@
|
|
|
763
763
|
}
|
|
764
764
|
let showPreviewModal = false;
|
|
765
765
|
let previewModalRef: HTMLDivElement | null = null;
|
|
766
|
-
let widgetPreviewComponentRef: {
|
|
766
|
+
let widgetPreviewComponentRef: {
|
|
767
|
+
getResizeLimits?: () => { maxWidth: number; maxHeight: number };
|
|
768
|
+
fitToContainer?: () => void;
|
|
769
|
+
resetPreviewPosition?: () => void;
|
|
770
|
+
} | undefined;
|
|
767
771
|
let previewIframe: HTMLIFrameElement | null = null;
|
|
768
772
|
let isLoadingDirectPreviewUrl = false;
|
|
769
773
|
let previewFetchRetryCount = 0;
|
|
@@ -925,27 +929,18 @@
|
|
|
925
929
|
const LEFT_COL_MAX_PERCENT = 62;
|
|
926
930
|
const COL_RESIZER_PX = 10;
|
|
927
931
|
const PREVIEW_COL_MIN_PX = 420;
|
|
928
|
-
/** Chat-first
|
|
932
|
+
/** Chat-first: panel preview column is hidden (W3 canvas live preview is primary). */
|
|
933
|
+
const SHOW_PANEL_PREVIEW_COLUMN = false;
|
|
934
|
+
/** Chat-first layout: preview column stays collapsed until the user opens it (W3 canvas shows live preview). */
|
|
929
935
|
let previewColumnCollapsed = true;
|
|
930
936
|
let lastAutoExpandForGenerating = false;
|
|
931
937
|
let widthBeforePreviewExpand = 0;
|
|
932
|
-
|
|
933
|
-
lastAutoExpandForGenerating = true;
|
|
934
|
-
if (previewColumnCollapsed) expandPreviewColumn();
|
|
935
|
-
}
|
|
938
|
+
// Do not auto-expand the right preview column — canvas WidgetShape iframes are the live preview.
|
|
936
939
|
$: if (!isGeneratingCode) {
|
|
937
940
|
lastAutoExpandForGenerating = false;
|
|
938
941
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
showWidgetDetails
|
|
942
|
-
&& !isDraftConvertPanel
|
|
943
|
-
&& previewColumnCollapsed
|
|
944
|
-
&& !hasAutoOpenedPreview
|
|
945
|
-
&& (hasWidgetCode || !!iframeSrc)
|
|
946
|
-
) {
|
|
947
|
-
hasAutoOpenedPreview = true;
|
|
948
|
-
expandPreviewColumn();
|
|
942
|
+
$: if (!SHOW_PANEL_PREVIEW_COLUMN) {
|
|
943
|
+
previewColumnCollapsed = true;
|
|
949
944
|
}
|
|
950
945
|
|
|
951
946
|
function maxLeftColPercent(): number {
|
|
@@ -974,6 +969,7 @@
|
|
|
974
969
|
}
|
|
975
970
|
|
|
976
971
|
function expandPreviewColumn() {
|
|
972
|
+
if (!SHOW_PANEL_PREVIEW_COLUMN) return;
|
|
977
973
|
if (!previewColumnCollapsed) return;
|
|
978
974
|
previewColumnCollapsed = false;
|
|
979
975
|
leftColPercent = clampLeftColPercent(LEFT_COL_DEFAULT_PERCENT);
|
|
@@ -982,7 +978,25 @@
|
|
|
982
978
|
detailsPanelX = Math.max(8, window.innerWidth - 8 - detailsPanelWidth);
|
|
983
979
|
}
|
|
984
980
|
console.log('[WidgetDetails] Open Preview — panel width', detailsPanelWidth);
|
|
985
|
-
tick().then(() => requestAnimationFrame(() =>
|
|
981
|
+
tick().then(() => requestAnimationFrame(async () => {
|
|
982
|
+
// Load panel preview only when the user opens the column (avoid wasted Worker/CDN requests).
|
|
983
|
+
if (selectedWidgetId && !iframeSrc && !showPreviewModal) {
|
|
984
|
+
try {
|
|
985
|
+
if (await loadPublishedPreviewForCurrentStep()) {
|
|
986
|
+
fitPreviewToArea();
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (lastCommitId || lastPublishedVersion !== null || normalizeRepositoryId(selectedWidget) || currentRepositoryId) {
|
|
990
|
+
previewLoading = true;
|
|
991
|
+
await runPreviewLoadPipeline(selectedWidgetId);
|
|
992
|
+
previewLoadedForWidgetId = selectedWidgetId;
|
|
993
|
+
}
|
|
994
|
+
} catch (err) {
|
|
995
|
+
console.warn('[WidgetDetails] Deferred preview load on expand failed', err);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
fitPreviewToArea();
|
|
999
|
+
}));
|
|
986
1000
|
}
|
|
987
1001
|
|
|
988
1002
|
let detailsPanelX = -1;
|
|
@@ -1256,6 +1270,14 @@
|
|
|
1256
1270
|
return;
|
|
1257
1271
|
}
|
|
1258
1272
|
|
|
1273
|
+
// Chat-first: skip panel Worker/CDN preview while the right column is closed (W3 canvas overlays handle live view).
|
|
1274
|
+
if (previewColumnCollapsed && !showPreviewModal) {
|
|
1275
|
+
previewLoading = false;
|
|
1276
|
+
previewError = null;
|
|
1277
|
+
console.log('[WidgetDetails] Preview column collapsed — skipping panel preview network');
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1259
1281
|
if (await loadPublishedPreviewForCurrentStep()) {
|
|
1260
1282
|
return;
|
|
1261
1283
|
}
|
|
@@ -2019,7 +2041,8 @@
|
|
|
2019
2041
|
stopPreviewTokenAutoRefresh();
|
|
2020
2042
|
// console.log('[TokenAutoRefresh] Started - checking every', TOKEN_CHECK_INTERVAL_MS / 1000, 'seconds');
|
|
2021
2043
|
previewTokenRefreshInterval = setInterval(async () => {
|
|
2022
|
-
const isPreviewVisible = showPreviewModal
|
|
2044
|
+
const isPreviewVisible = showPreviewModal
|
|
2045
|
+
|| (showWidgetDetails && !previewColumnCollapsed && inlinePanelView === 'preview');
|
|
2023
2046
|
if (!isPreviewVisible || !lastPreviewUrlFetchTime || !directPreviewUrl) return;
|
|
2024
2047
|
const tokenAge = Date.now() - lastPreviewUrlFetchTime;
|
|
2025
2048
|
if (tokenAge >= PREVIEW_TOKEN_REFRESH_MS) {
|
|
@@ -2536,7 +2559,9 @@
|
|
|
2536
2559
|
);
|
|
2537
2560
|
return;
|
|
2538
2561
|
}
|
|
2539
|
-
detailsPanelWidth = clampPanelWidth(
|
|
2562
|
+
detailsPanelWidth = clampPanelWidth(
|
|
2563
|
+
previewColumnCollapsed ? DETAILS_PANEL_MIN_WIDTH : DETAILS_PANEL_DEFAULT_WIDTH
|
|
2564
|
+
);
|
|
2540
2565
|
detailsPanelHeight = getDefaultDetailsPanelHeight();
|
|
2541
2566
|
}
|
|
2542
2567
|
|
|
@@ -2583,7 +2608,8 @@
|
|
|
2583
2608
|
}
|
|
2584
2609
|
|
|
2585
2610
|
/**
|
|
2586
|
-
*
|
|
2611
|
+
* Park the panel beside a canvas widget shape: right if the shape is left of
|
|
2612
|
+
* viewport center, otherwise left. Chat-first width when preview is collapsed.
|
|
2587
2613
|
*/
|
|
2588
2614
|
export function positionBesideShape(shapeRect: {
|
|
2589
2615
|
left: number;
|
|
@@ -2596,32 +2622,55 @@
|
|
|
2596
2622
|
viewportHeight: number;
|
|
2597
2623
|
} | null): void {
|
|
2598
2624
|
if (typeof window === 'undefined') return;
|
|
2625
|
+
previewColumnCollapsed = true;
|
|
2599
2626
|
resetDetailsPanelSize();
|
|
2600
2627
|
_hasBeenOpenedOnce = true;
|
|
2601
2628
|
if (!shapeRect) {
|
|
2602
2629
|
centerDetailsPanel();
|
|
2603
|
-
|
|
2630
|
+
console.log('[WidgetDetails] No shape rect — centered floating panel', detailsPanelWidth, detailsPanelHeight);
|
|
2604
2631
|
return;
|
|
2605
2632
|
}
|
|
2606
2633
|
|
|
2607
|
-
|
|
2608
|
-
|
|
2634
|
+
const pad = 8;
|
|
2635
|
+
const viewLeft = shapeRect.viewportLeft;
|
|
2636
|
+
const viewRight = shapeRect.viewportLeft + shapeRect.viewportWidth;
|
|
2637
|
+
const availableWidth = Math.max(DETAILS_PANEL_MIN_WIDTH, shapeRect.viewportWidth - pad * 2);
|
|
2638
|
+
detailsPanelWidth = clampPanelWidth(
|
|
2639
|
+
previewColumnCollapsed
|
|
2640
|
+
? DETAILS_PANEL_MIN_WIDTH
|
|
2641
|
+
: Math.min(DETAILS_PANEL_MAX_WIDTH, availableWidth)
|
|
2642
|
+
);
|
|
2643
|
+
detailsPanelHeight = clampPanelHeight(
|
|
2644
|
+
Math.min(DETAILS_PANEL_MAX_HEIGHT, shapeRect.viewportHeight - pad * 2),
|
|
2645
|
+
);
|
|
2609
2646
|
|
|
2610
|
-
const
|
|
2611
|
-
const
|
|
2612
|
-
const
|
|
2613
|
-
|
|
2647
|
+
const shapeCenterX = shapeRect.left + shapeRect.width / 2;
|
|
2648
|
+
const viewCenterX = shapeRect.viewportLeft + shapeRect.viewportWidth / 2;
|
|
2649
|
+
const openOnRight = shapeCenterX < viewCenterX;
|
|
2650
|
+
if (openOnRight) {
|
|
2651
|
+
detailsPanelX = shapeRect.left + shapeRect.width + DETAILS_PANEL_SHAPE_GAP;
|
|
2652
|
+
if (detailsPanelX + detailsPanelWidth > viewRight - pad) {
|
|
2653
|
+
detailsPanelX = Math.max(viewLeft + pad, viewRight - pad - detailsPanelWidth);
|
|
2654
|
+
}
|
|
2655
|
+
} else {
|
|
2656
|
+
detailsPanelX = shapeRect.left - DETAILS_PANEL_SHAPE_GAP - detailsPanelWidth;
|
|
2657
|
+
if (detailsPanelX < viewLeft + pad) {
|
|
2658
|
+
detailsPanelX = viewLeft + pad;
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2614
2661
|
|
|
2615
|
-
const
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
console.log('[WidgetDetails]
|
|
2662
|
+
const maxY = window.innerHeight - detailsPanelHeight - pad;
|
|
2663
|
+
detailsPanelY = Math.min(
|
|
2664
|
+
maxY,
|
|
2665
|
+
Math.max(detailsPanelTopOffset, shapeRect.top),
|
|
2666
|
+
);
|
|
2667
|
+
console.log('[WidgetDetails] Positioned beside shape', {
|
|
2668
|
+
openOnRight,
|
|
2621
2669
|
x: Math.round(detailsPanelX),
|
|
2622
2670
|
y: Math.round(detailsPanelY),
|
|
2623
2671
|
w: detailsPanelWidth,
|
|
2624
2672
|
h: detailsPanelHeight,
|
|
2673
|
+
previewCollapsed: previewColumnCollapsed,
|
|
2625
2674
|
});
|
|
2626
2675
|
}
|
|
2627
2676
|
|
|
@@ -2668,6 +2717,8 @@
|
|
|
2668
2717
|
let _hasBeenOpenedOnce = false;
|
|
2669
2718
|
export function openWidgetDetails() {
|
|
2670
2719
|
if (showWidgetDetails) return;
|
|
2720
|
+
previewColumnCollapsed = true;
|
|
2721
|
+
hasAutoOpenedPreview = false;
|
|
2671
2722
|
resetDetailsPanelSize();
|
|
2672
2723
|
if (fillViewport || (!_hasBeenOpenedOnce && (detailsPanelX < 0 || detailsPanelY < 0))) {
|
|
2673
2724
|
centerDetailsPanel();
|
|
@@ -2676,7 +2727,7 @@
|
|
|
2676
2727
|
showWidgetDetails = true;
|
|
2677
2728
|
dispatch('widgetDetailsToggle', { open: true });
|
|
2678
2729
|
|
|
2679
|
-
// Clamp preview after DOM is laid out
|
|
2730
|
+
// Clamp preview after DOM is laid out (no-op while column collapsed)
|
|
2680
2731
|
tick().then(() => {
|
|
2681
2732
|
requestAnimationFrame(() => {
|
|
2682
2733
|
fitPreviewToArea();
|
|
@@ -2959,6 +3010,7 @@
|
|
|
2959
3010
|
/** Expand preview to fill the available panel (fit-to-area). */
|
|
2960
3011
|
function fitPreviewToArea() {
|
|
2961
3012
|
widgetPreviewComponentRef?.fitToContainer?.();
|
|
3013
|
+
widgetPreviewComponentRef?.resetPreviewPosition?.();
|
|
2962
3014
|
}
|
|
2963
3015
|
|
|
2964
3016
|
// Clamp preview to available stage inside WidgetPreview (handles + dimension badge padding)
|
|
@@ -3064,8 +3116,8 @@
|
|
|
3064
3116
|
previewLoading = false;
|
|
3065
3117
|
}
|
|
3066
3118
|
|
|
3067
|
-
// Stop token auto-refresh when no preview is visible
|
|
3068
|
-
$: if (!showPreviewModal && !(showWidgetDetails && inlinePanelView === 'preview')) {
|
|
3119
|
+
// Stop token auto-refresh when no preview is visible (collapsed column counts as hidden)
|
|
3120
|
+
$: if (!showPreviewModal && !(showWidgetDetails && !previewColumnCollapsed && inlinePanelView === 'preview')) {
|
|
3069
3121
|
stopPreviewTokenAutoRefresh();
|
|
3070
3122
|
}
|
|
3071
3123
|
|
|
@@ -3447,6 +3499,7 @@
|
|
|
3447
3499
|
class="widget-details-debug-buttons relative z-[1] ml-auto flex items-center shrink-0"
|
|
3448
3500
|
bind:this={widgetDetailsDebugButtonsEl}
|
|
3449
3501
|
>
|
|
3502
|
+
{#if SHOW_PANEL_PREVIEW_COLUMN}
|
|
3450
3503
|
<HoverTooltip text={previewColumnCollapsed ? 'Open live preview' : 'Close live preview'}>
|
|
3451
3504
|
<button
|
|
3452
3505
|
type="button"
|
|
@@ -3474,6 +3527,7 @@
|
|
|
3474
3527
|
{/if}
|
|
3475
3528
|
</button>
|
|
3476
3529
|
</HoverTooltip>
|
|
3530
|
+
{/if}
|
|
3477
3531
|
</div>
|
|
3478
3532
|
</div>
|
|
3479
3533
|
</div>
|
|
@@ -3561,7 +3615,7 @@
|
|
|
3561
3615
|
|
|
3562
3616
|
</div> <!-- end of widget-details-left-col -->
|
|
3563
3617
|
|
|
3564
|
-
{#if previewColumnCollapsed}
|
|
3618
|
+
{#if SHOW_PANEL_PREVIEW_COLUMN && previewColumnCollapsed}
|
|
3565
3619
|
<div class="widget-details-preview-expand-strip flex items-center justify-center w-8 shrink-0 border-l border-gray-200 bg-gray-50">
|
|
3566
3620
|
<button
|
|
3567
3621
|
type="button"
|
|
@@ -3572,7 +3626,7 @@
|
|
|
3572
3626
|
<span class="widget-details-preview-expand-label inline-block rotate-180" style="writing-mode: vertical-rl;">Preview</span>
|
|
3573
3627
|
</button>
|
|
3574
3628
|
</div>
|
|
3575
|
-
{:else}
|
|
3629
|
+
{:else if SHOW_PANEL_PREVIEW_COLUMN}
|
|
3576
3630
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
3577
3631
|
<div
|
|
3578
3632
|
class="widget-details-col-resizer w-2.5 shrink-0 cursor-col-resize relative z-20 group"
|
|
@@ -3645,6 +3699,7 @@
|
|
|
3645
3699
|
mode="controlled"
|
|
3646
3700
|
enableResize={true}
|
|
3647
3701
|
resizeMode="concentric"
|
|
3702
|
+
defaultStageAlign="center"
|
|
3648
3703
|
autoFitStage={showPreviewModal}
|
|
3649
3704
|
src={iframeSrc || getFallbackPreviewUrl()}
|
|
3650
3705
|
displayUrl={directPreviewUrl || iframeSrc}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Live iframe + chrome over Fabric WidgetShapes (W3).
|
|
4
|
+
* Body drag is locked on the shape; move via the top drag bar so the iframe stays interactive.
|
|
5
|
+
*/
|
|
6
|
+
let className = '';
|
|
7
|
+
export { className as class };
|
|
8
|
+
export let style = '';
|
|
9
|
+
|
|
10
|
+
export let enabled = true;
|
|
11
|
+
/** Screen-space boxes for widget shapes currently on the canvas. */
|
|
12
|
+
export let getRects:
|
|
13
|
+
| (() => Array<{ widgetId: string; left: number; top: number; width: number; height: number }>)
|
|
14
|
+
| null = null;
|
|
15
|
+
/** Resolve a live preview URL for a widget (CDN published or Worker preview). */
|
|
16
|
+
export let resolveUrl: ((widgetId: string) => string | null) | null = null;
|
|
17
|
+
/** Currently selected WidgetShape id (highlights chrome). */
|
|
18
|
+
export let selectedWidgetId: string | null = null;
|
|
19
|
+
/** Translate shape by screen-pixel delta. */
|
|
20
|
+
export let onTranslate:
|
|
21
|
+
| ((widgetId: string, dxScreen: number, dyScreen: number) => void)
|
|
22
|
+
| null = null;
|
|
23
|
+
/** Select the Fabric WidgetShape (e.g. when pressing the drag bar). */
|
|
24
|
+
export let onSelectShape: ((widgetId: string) => void) | null = null;
|
|
25
|
+
/** Open Widget Details / chat for this widget. */
|
|
26
|
+
export let onOpenChat: ((widgetId: string) => void) | null = null;
|
|
27
|
+
|
|
28
|
+
const DRAG_BAR_H = 28;
|
|
29
|
+
const BAR_SHAPE_GAP = 4;
|
|
30
|
+
|
|
31
|
+
type ShapeRect = {
|
|
32
|
+
widgetId: string;
|
|
33
|
+
left: number;
|
|
34
|
+
top: number;
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
src: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let overlays: ShapeRect[] = [];
|
|
41
|
+
let draggingWidgetId: string | null = null;
|
|
42
|
+
|
|
43
|
+
export function sync(): void {
|
|
44
|
+
if (!enabled || !getRects || !resolveUrl) {
|
|
45
|
+
overlays = [];
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const next: ShapeRect[] = [];
|
|
49
|
+
for (const rect of getRects() || []) {
|
|
50
|
+
if (!rect?.widgetId) continue;
|
|
51
|
+
const src = resolveUrl(rect.widgetId);
|
|
52
|
+
if (!src) continue;
|
|
53
|
+
next.push({
|
|
54
|
+
widgetId: rect.widgetId,
|
|
55
|
+
left: rect.left,
|
|
56
|
+
top: rect.top,
|
|
57
|
+
width: rect.width,
|
|
58
|
+
height: rect.height,
|
|
59
|
+
src,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
overlays = next;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function startShapeDrag(event: PointerEvent, widgetId: string) {
|
|
66
|
+
const target = event.target as HTMLElement | null;
|
|
67
|
+
if (target?.closest('button, a, input')) return;
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
event.stopPropagation();
|
|
70
|
+
onSelectShape?.(widgetId);
|
|
71
|
+
draggingWidgetId = widgetId;
|
|
72
|
+
const startX = event.clientX;
|
|
73
|
+
const startY = event.clientY;
|
|
74
|
+
let lastX = startX;
|
|
75
|
+
let lastY = startY;
|
|
76
|
+
|
|
77
|
+
function onMove(e: PointerEvent) {
|
|
78
|
+
const dx = e.clientX - lastX;
|
|
79
|
+
const dy = e.clientY - lastY;
|
|
80
|
+
lastX = e.clientX;
|
|
81
|
+
lastY = e.clientY;
|
|
82
|
+
if (dx === 0 && dy === 0) return;
|
|
83
|
+
onTranslate?.(widgetId, dx, dy);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function endDrag() {
|
|
87
|
+
draggingWidgetId = null;
|
|
88
|
+
document.removeEventListener('pointermove', onMove);
|
|
89
|
+
document.removeEventListener('pointerup', endDrag);
|
|
90
|
+
document.removeEventListener('pointercancel', endDrag);
|
|
91
|
+
document.body.style.removeProperty('user-select');
|
|
92
|
+
document.body.style.removeProperty('cursor');
|
|
93
|
+
console.log('[WidgetShapeLivePreviews] Drag end', widgetId.substring(0, 8));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
document.body.style.userSelect = 'none';
|
|
97
|
+
document.body.style.cursor = 'grabbing';
|
|
98
|
+
document.addEventListener('pointermove', onMove);
|
|
99
|
+
document.addEventListener('pointerup', endDrag);
|
|
100
|
+
document.addEventListener('pointercancel', endDrag);
|
|
101
|
+
console.log('[WidgetShapeLivePreviews] Drag start', widgetId.substring(0, 8));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function handleOpenChat(event: MouseEvent, widgetId: string) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
event.stopPropagation();
|
|
107
|
+
onSelectShape?.(widgetId);
|
|
108
|
+
onOpenChat?.(widgetId);
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
{#if enabled && overlays.length > 0}
|
|
113
|
+
<div class="widget-shape-live-previews-ct pointer-events-none {className}" {style}>
|
|
114
|
+
{#each overlays as overlay (overlay.widgetId)}
|
|
115
|
+
{@const isSelected = selectedWidgetId === overlay.widgetId}
|
|
116
|
+
{@const isDragging = draggingWidgetId === overlay.widgetId}
|
|
117
|
+
{@const chromeTop = overlay.top - DRAG_BAR_H - BAR_SHAPE_GAP}
|
|
118
|
+
<div
|
|
119
|
+
class="widget-shape-live-chrome-ct fixed"
|
|
120
|
+
style="left: {overlay.left}px; top: {chromeTop}px; width: {overlay.width}px; height: {DRAG_BAR_H + BAR_SHAPE_GAP + overlay.height}px; z-index: 1;"
|
|
121
|
+
>
|
|
122
|
+
<!-- Drag bar sits between rotation control and the shape top stroke -->
|
|
123
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
124
|
+
<div
|
|
125
|
+
class="widget-shape-drag-bar pointer-events-auto absolute left-0 right-0 top-0 flex h-7 items-center gap-1 rounded-t-md border border-b-0 bg-white/95 px-1.5 shadow-sm select-none
|
|
126
|
+
{isSelected ? 'border-blue-400' : 'border-gray-300'}"
|
|
127
|
+
style="height: {DRAG_BAR_H}px; cursor: grab;"
|
|
128
|
+
title="Drag widget"
|
|
129
|
+
onpointerdown={(e) => startShapeDrag(e, overlay.widgetId)}
|
|
130
|
+
>
|
|
131
|
+
<span class="widget-shape-drag-grip shrink-0 text-xs leading-none text-gray-400" aria-hidden="true">⠿</span>
|
|
132
|
+
{#if isSelected}
|
|
133
|
+
<span
|
|
134
|
+
class="widget-shape-edit-mode-badge shrink-0 rounded-full bg-amber-200 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-amber-900"
|
|
135
|
+
>Edit mode</span>
|
|
136
|
+
{/if}
|
|
137
|
+
<div class="widget-shape-drag-bar-actions ml-auto flex items-center gap-0.5">
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
class="widget-shape-open-chat-bt flex h-6 items-center gap-1 rounded px-1.5 text-[10px] font-medium text-gray-600 hover:bg-gray-100 hover:text-forest-green cursor-pointer"
|
|
141
|
+
title="Open chat"
|
|
142
|
+
onclick={(e) => handleOpenChat(e, overlay.widgetId)}
|
|
143
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
144
|
+
>
|
|
145
|
+
Chat
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<div
|
|
151
|
+
class="widget-shape-live-preview-frame pointer-events-none absolute left-0 right-0 overflow-visible
|
|
152
|
+
{isSelected ? 'ring-2 ring-blue-400/80 rounded-sm' : ''}"
|
|
153
|
+
style="top: {DRAG_BAR_H + BAR_SHAPE_GAP}px; height: {overlay.height}px;"
|
|
154
|
+
>
|
|
155
|
+
<!-- Inset so Fabric corner/rotate handles stay clickable around the edges -->
|
|
156
|
+
<iframe
|
|
157
|
+
class="widget-shape-live-preview-iframe pointer-events-auto absolute border-0 bg-white"
|
|
158
|
+
class:pointer-events-none={isDragging}
|
|
159
|
+
style="left: 8px; top: 8px; right: 8px; bottom: 8px; width: calc(100% - 16px); height: calc(100% - 16px);"
|
|
160
|
+
title="Live widget preview"
|
|
161
|
+
src={overlay.src}
|
|
162
|
+
tabindex="-1"
|
|
163
|
+
loading="lazy"
|
|
164
|
+
sandbox="allow-scripts allow-same-origin allow-forms"
|
|
165
|
+
></iframe>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
{/each}
|
|
169
|
+
</div>
|
|
170
|
+
{/if}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: Props & {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const WidgetShapeLivePreviews: $$__sveltets_2_IsomorphicComponent<{
|
|
15
|
+
/**
|
|
16
|
+
* Live iframe + chrome over Fabric WidgetShapes (W3).
|
|
17
|
+
* Body drag is locked on the shape; move via the top drag bar so the iframe stays interactive.
|
|
18
|
+
*/ class?: string;
|
|
19
|
+
style?: string;
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
/** Screen-space boxes for widget shapes currently on the canvas. */ getRects?: (() => Array<{
|
|
22
|
+
widgetId: string;
|
|
23
|
+
left: number;
|
|
24
|
+
top: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
}>) | null;
|
|
28
|
+
/** Resolve a live preview URL for a widget (CDN published or Worker preview). */ resolveUrl?: ((widgetId: string) => string | null) | null;
|
|
29
|
+
/** Currently selected WidgetShape id (highlights chrome). */ selectedWidgetId?: string | null;
|
|
30
|
+
/** Translate shape by screen-pixel delta. */ onTranslate?: ((widgetId: string, dxScreen: number, dyScreen: number) => void) | null;
|
|
31
|
+
/** Select the Fabric WidgetShape (e.g. when pressing the drag bar). */ onSelectShape?: ((widgetId: string) => void) | null;
|
|
32
|
+
/** Open Widget Details / chat for this widget. */ onOpenChat?: ((widgetId: string) => void) | null;
|
|
33
|
+
sync?: () => void;
|
|
34
|
+
}, {
|
|
35
|
+
[evt: string]: CustomEvent<any>;
|
|
36
|
+
}, {}, {
|
|
37
|
+
sync: () => void;
|
|
38
|
+
}, string>;
|
|
39
|
+
type WidgetShapeLivePreviews = InstanceType<typeof WidgetShapeLivePreviews>;
|
|
40
|
+
export default WidgetShapeLivePreviews;
|