@widgetic/creator 0.3.49 → 0.3.50
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.
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
ondblclick={() => startEditing()}
|
|
66
66
|
>
|
|
67
67
|
<input
|
|
68
|
-
class="editable-name-input font-semibold rounded px-1 py-0.5 text-sm outline-none transition-colors {isEditing ? 'border border-blue-400 bg-white focus:ring-1 focus:ring-blue-500' : 'border border-transparent bg-transparent cursor-default'}"
|
|
69
|
-
style="
|
|
68
|
+
class="editable-name-input min-w-0 w-full overflow-hidden text-ellipsis whitespace-nowrap font-semibold rounded px-1 py-0.5 text-sm outline-none transition-colors {isEditing ? 'border border-blue-400 bg-white focus:ring-1 focus:ring-blue-500' : 'border border-transparent bg-transparent cursor-default'}"
|
|
69
|
+
style="flex: 1 1 0%; {!isEditing ? 'pointer-events: none;' : ''}"
|
|
70
70
|
type="text"
|
|
71
71
|
value={isEditing ? inputValue : (name || placeholder)}
|
|
72
72
|
readonly={!isEditing}
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
import { userSession as userSessionStore } from '../stores/userSession.js';
|
|
32
32
|
|
|
33
33
|
import { WidgetPreview } from '@widgetic/editor';
|
|
34
|
-
import EditableName from './EditableName.svelte';
|
|
35
34
|
|
|
36
35
|
const dispatch = createEventDispatcher();
|
|
37
36
|
|
|
@@ -272,6 +271,13 @@
|
|
|
272
271
|
|
|
273
272
|
$: hasWidgetCode = !!lastCommitId || lastPublishedVersion !== null;
|
|
274
273
|
|
|
274
|
+
/** Convert UX v2: draft panels (synthetic ids) have no widget/repo yet —
|
|
275
|
+
* show a friendly draft hint instead of the repo-setup loader. */
|
|
276
|
+
$: isDraftConvertPanel = !!selectedWidgetId?.startsWith('draft_convert_');
|
|
277
|
+
$: if (isDraftConvertPanel && previewError === 'NO_CODE_YET') {
|
|
278
|
+
previewError = 'DRAFT_PANEL';
|
|
279
|
+
}
|
|
280
|
+
|
|
275
281
|
/** Published widgets must never show the draft "No Code Yet" empty state. */
|
|
276
282
|
$: if (hasWidgetCode && previewError === 'NO_CODE_YET') {
|
|
277
283
|
previewError = lastPublishedVersion !== null
|
|
@@ -851,14 +857,71 @@
|
|
|
851
857
|
$: detailsPanelTopOffset = panelTopOffset ?? legacyTopOffset;
|
|
852
858
|
$: detailsPanelBottomMargin = panelBottomMargin ?? legacyBottomMargin;
|
|
853
859
|
|
|
854
|
-
const DETAILS_PANEL_DEFAULT_WIDTH =
|
|
855
|
-
const
|
|
856
|
-
const
|
|
860
|
+
const DETAILS_PANEL_DEFAULT_WIDTH = 600;
|
|
861
|
+
const DETAILS_PANEL_MAX_WIDTH = 600;
|
|
862
|
+
const DETAILS_PANEL_MAX_HEIGHT = 800;
|
|
863
|
+
const DETAILS_PANEL_MIN_WIDTH = 320;
|
|
864
|
+
const DETAILS_PANEL_MIN_HEIGHT = 400;
|
|
857
865
|
const DETAILS_PANEL_FILL_INSET = 16;
|
|
866
|
+
const DETAILS_PANEL_SHAPE_GAP = 12;
|
|
858
867
|
/** Left column share of the two-col layout (chat / PropsEditor). */
|
|
859
868
|
const LEFT_COL_DEFAULT_PERCENT = 58;
|
|
860
869
|
const LEFT_COL_MIN_PERCENT = 32;
|
|
861
870
|
const LEFT_COL_MAX_PERCENT = 72;
|
|
871
|
+
/** Chat-first layout: preview column starts collapsed and opens on generate. */
|
|
872
|
+
let previewColumnCollapsed = true;
|
|
873
|
+
let lastAutoExpandForGenerating = false;
|
|
874
|
+
let widthBeforePreviewExpand = 0;
|
|
875
|
+
$: if (isGeneratingCode && !lastAutoExpandForGenerating) {
|
|
876
|
+
lastAutoExpandForGenerating = true;
|
|
877
|
+
if (previewColumnCollapsed) expandPreviewColumn();
|
|
878
|
+
}
|
|
879
|
+
$: if (!isGeneratingCode) {
|
|
880
|
+
lastAutoExpandForGenerating = false;
|
|
881
|
+
}
|
|
882
|
+
/** Open the live preview column once when this widget already has code (or a loaded iframe). */
|
|
883
|
+
$: if (
|
|
884
|
+
showWidgetDetails
|
|
885
|
+
&& !isDraftConvertPanel
|
|
886
|
+
&& previewColumnCollapsed
|
|
887
|
+
&& !hasAutoOpenedPreview
|
|
888
|
+
&& (hasWidgetCode || !!iframeSrc)
|
|
889
|
+
) {
|
|
890
|
+
hasAutoOpenedPreview = true;
|
|
891
|
+
expandPreviewColumn();
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function collapsePreviewColumn() {
|
|
895
|
+
previewColumnCollapsed = true;
|
|
896
|
+
if (widthBeforePreviewExpand > 0) {
|
|
897
|
+
detailsPanelWidth = widthBeforePreviewExpand;
|
|
898
|
+
widthBeforePreviewExpand = 0;
|
|
899
|
+
leftColPercent = LEFT_COL_DEFAULT_PERCENT;
|
|
900
|
+
}
|
|
901
|
+
console.log('[WidgetDetails] Preview column minimized, restored width', detailsPanelWidth);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function expandPreviewColumn() {
|
|
905
|
+
if (!previewColumnCollapsed) return;
|
|
906
|
+
widthBeforePreviewExpand = detailsPanelWidth;
|
|
907
|
+
previewColumnCollapsed = false;
|
|
908
|
+
const extraWidth = 420;
|
|
909
|
+
const maxRight = typeof window !== 'undefined' ? window.innerWidth - 8 : detailsPanelWidth;
|
|
910
|
+
const expandedWidth = widthBeforePreviewExpand + extraWidth;
|
|
911
|
+
const fittedWidth = Math.min(expandedWidth, Math.max(widthBeforePreviewExpand, maxRight - detailsPanelX));
|
|
912
|
+
detailsPanelWidth = Math.max(widthBeforePreviewExpand, fittedWidth);
|
|
913
|
+
const usable = Math.max(1, detailsPanelWidth);
|
|
914
|
+
leftColPercent = Math.min(
|
|
915
|
+
LEFT_COL_MAX_PERCENT,
|
|
916
|
+
Math.max(LEFT_COL_MIN_PERCENT, (widthBeforePreviewExpand / usable) * 100),
|
|
917
|
+
);
|
|
918
|
+
console.log('[WidgetDetails] Preview column expanded', {
|
|
919
|
+
from: widthBeforePreviewExpand,
|
|
920
|
+
to: detailsPanelWidth,
|
|
921
|
+
leftColPercent,
|
|
922
|
+
});
|
|
923
|
+
tick().then(() => requestAnimationFrame(() => fitPreviewToArea()));
|
|
924
|
+
}
|
|
862
925
|
|
|
863
926
|
let detailsPanelX = -1;
|
|
864
927
|
let detailsPanelY = -1;
|
|
@@ -959,6 +1022,12 @@
|
|
|
959
1022
|
|
|
960
1023
|
/** Probe Worker / build-from-repo pipeline without resetting panel state. */
|
|
961
1024
|
async function runPreviewLoadPipeline(widgetId: string): Promise<void> {
|
|
1025
|
+
if (iframeSrc && previewLoadedForWidgetId === widgetId) {
|
|
1026
|
+
previewBuildDeferred = false;
|
|
1027
|
+
previewLoading = false;
|
|
1028
|
+
console.log('[WidgetDetails] Preview already loaded, skipping pipeline:', widgetId.substring(0, 8));
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
962
1031
|
if (shouldDeferPreviewNetworkWork()) {
|
|
963
1032
|
console.log('[WidgetDetails] Deferring preview load (background panel):', widgetId.substring(0, 8));
|
|
964
1033
|
previewBuildDeferred = true;
|
|
@@ -1026,6 +1095,11 @@
|
|
|
1026
1095
|
/** Resume preview after a background panel receives focus. */
|
|
1027
1096
|
export async function resumeDeferredPreviewLoad(): Promise<void> {
|
|
1028
1097
|
if (!previewBuildDeferred || !selectedWidgetId) return;
|
|
1098
|
+
if (iframeSrc && previewLoadedForWidgetId === selectedWidgetId) {
|
|
1099
|
+
previewBuildDeferred = false;
|
|
1100
|
+
console.log('[WidgetDetails] Skip deferred preview — already loaded for', selectedWidgetId.substring(0, 8));
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1029
1103
|
previewBuildDeferred = false;
|
|
1030
1104
|
await runPreviewLoadPipeline(selectedWidgetId);
|
|
1031
1105
|
}
|
|
@@ -1107,10 +1181,11 @@
|
|
|
1107
1181
|
return;
|
|
1108
1182
|
}
|
|
1109
1183
|
|
|
1110
|
-
// Brand-new widget (no
|
|
1111
|
-
//
|
|
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).
|
|
1112
1187
|
const hasRepo = !!(normalizeRepositoryId(selectedWidget) || currentRepositoryId);
|
|
1113
|
-
if (!lastCommitId && lastPublishedVersion === null) {
|
|
1188
|
+
if (!lastCommitId && lastPublishedVersion === null && !hasRepo) {
|
|
1114
1189
|
console.log('[WidgetDetails] No code yet — skipping Worker probe/build-from-repo', {
|
|
1115
1190
|
widgetId: widgetId?.substring(0, 8),
|
|
1116
1191
|
hasRepo,
|
|
@@ -2043,20 +2118,30 @@
|
|
|
2043
2118
|
// ═══════════════════════════════════════════════════════════════════════
|
|
2044
2119
|
|
|
2045
2120
|
function getDefaultDetailsPanelHeight(): number {
|
|
2046
|
-
if (typeof window === 'undefined') return
|
|
2047
|
-
|
|
2121
|
+
if (typeof window === 'undefined') return DETAILS_PANEL_MAX_HEIGHT;
|
|
2122
|
+
const available = window.innerHeight - detailsPanelTopOffset - detailsPanelBottomMargin;
|
|
2123
|
+
return Math.max(DETAILS_PANEL_MIN_HEIGHT, Math.min(DETAILS_PANEL_MAX_HEIGHT, available));
|
|
2048
2124
|
}
|
|
2049
2125
|
|
|
2050
2126
|
function clampPanelWidth(w: number): number {
|
|
2051
|
-
if (typeof window === 'undefined') return w;
|
|
2052
|
-
const maxWidth = window.innerWidth -
|
|
2127
|
+
if (typeof window === 'undefined') return Math.min(DETAILS_PANEL_MAX_WIDTH, w);
|
|
2128
|
+
const maxWidth = Math.min(DETAILS_PANEL_MAX_WIDTH, window.innerWidth - 24);
|
|
2053
2129
|
return Math.max(DETAILS_PANEL_MIN_WIDTH, Math.min(w, maxWidth));
|
|
2054
2130
|
}
|
|
2055
2131
|
|
|
2132
|
+
function clampPanelHeight(h: number): number {
|
|
2133
|
+
if (typeof window === 'undefined') return Math.min(DETAILS_PANEL_MAX_HEIGHT, h);
|
|
2134
|
+
const available = window.innerHeight - detailsPanelTopOffset - 16;
|
|
2135
|
+
return Math.max(
|
|
2136
|
+
DETAILS_PANEL_MIN_HEIGHT,
|
|
2137
|
+
Math.min(h, Math.min(DETAILS_PANEL_MAX_HEIGHT, available)),
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2056
2141
|
function resetDetailsPanelSize() {
|
|
2057
2142
|
if (typeof window === 'undefined') {
|
|
2058
2143
|
detailsPanelWidth = DETAILS_PANEL_DEFAULT_WIDTH;
|
|
2059
|
-
detailsPanelHeight =
|
|
2144
|
+
detailsPanelHeight = DETAILS_PANEL_MAX_HEIGHT;
|
|
2060
2145
|
return;
|
|
2061
2146
|
}
|
|
2062
2147
|
if (fillViewport) {
|
|
@@ -2071,7 +2156,7 @@
|
|
|
2071
2156
|
return;
|
|
2072
2157
|
}
|
|
2073
2158
|
detailsPanelWidth = clampPanelWidth(DETAILS_PANEL_DEFAULT_WIDTH);
|
|
2074
|
-
detailsPanelHeight =
|
|
2159
|
+
detailsPanelHeight = getDefaultDetailsPanelHeight();
|
|
2075
2160
|
}
|
|
2076
2161
|
|
|
2077
2162
|
export function centerDetailsPanel() {
|
|
@@ -2083,10 +2168,93 @@
|
|
|
2083
2168
|
return;
|
|
2084
2169
|
}
|
|
2085
2170
|
const cascade = panelCascadeIndex * 32;
|
|
2086
|
-
detailsPanelX = Math.max(
|
|
2171
|
+
detailsPanelX = Math.max(8, Math.round((window.innerWidth - detailsPanelWidth) / 2) + cascade);
|
|
2087
2172
|
detailsPanelY = detailsPanelTopOffset + cascade;
|
|
2088
2173
|
}
|
|
2089
2174
|
|
|
2175
|
+
/** Keep the panel on-screen after a browser resize. Does not reset to default 600×800. */
|
|
2176
|
+
export function keepPanelInViewport() {
|
|
2177
|
+
if (typeof window === 'undefined') return;
|
|
2178
|
+
if (fillViewport) {
|
|
2179
|
+
resetDetailsPanelSize();
|
|
2180
|
+
detailsPanelX = DETAILS_PANEL_FILL_INSET;
|
|
2181
|
+
detailsPanelY = detailsPanelTopOffset;
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const maxW = Math.max(DETAILS_PANEL_MIN_WIDTH, window.innerWidth - 16);
|
|
2185
|
+
const maxH = Math.max(
|
|
2186
|
+
DETAILS_PANEL_MIN_HEIGHT,
|
|
2187
|
+
window.innerHeight - detailsPanelTopOffset - 16,
|
|
2188
|
+
);
|
|
2189
|
+
if (detailsPanelWidth > maxW) detailsPanelWidth = maxW;
|
|
2190
|
+
const currentH = detailsPanelHeight || getDefaultDetailsPanelHeight();
|
|
2191
|
+
if (currentH > maxH) detailsPanelHeight = maxH;
|
|
2192
|
+
const pos = clampDetailsPanelPosition(detailsPanelX, detailsPanelY);
|
|
2193
|
+
detailsPanelX = pos.x;
|
|
2194
|
+
detailsPanelY = pos.y;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
/**
|
|
2198
|
+
* Park the panel beside a canvas widget shape: right if the shape is left of
|
|
2199
|
+
* the canvas viewport center, otherwise left. Caps at 600×800 (or smaller).
|
|
2200
|
+
*/
|
|
2201
|
+
export function positionBesideShape(shapeRect: {
|
|
2202
|
+
left: number;
|
|
2203
|
+
top: number;
|
|
2204
|
+
width: number;
|
|
2205
|
+
height: number;
|
|
2206
|
+
viewportLeft: number;
|
|
2207
|
+
viewportTop: number;
|
|
2208
|
+
viewportWidth: number;
|
|
2209
|
+
viewportHeight: number;
|
|
2210
|
+
} | null): void {
|
|
2211
|
+
if (typeof window === 'undefined') return;
|
|
2212
|
+
resetDetailsPanelSize();
|
|
2213
|
+
_hasBeenOpenedOnce = true;
|
|
2214
|
+
if (!shapeRect) {
|
|
2215
|
+
centerDetailsPanel();
|
|
2216
|
+
console.log('[WidgetDetails] No shape rect — centered floating panel', detailsPanelWidth, detailsPanelHeight);
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
const pad = 8;
|
|
2221
|
+
const viewLeft = shapeRect.viewportLeft;
|
|
2222
|
+
const viewRight = shapeRect.viewportLeft + shapeRect.viewportWidth;
|
|
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
|
+
}
|
|
2243
|
+
|
|
2244
|
+
const maxY = window.innerHeight - detailsPanelHeight - pad;
|
|
2245
|
+
detailsPanelY = Math.min(
|
|
2246
|
+
maxY,
|
|
2247
|
+
Math.max(detailsPanelTopOffset, shapeRect.top),
|
|
2248
|
+
);
|
|
2249
|
+
console.log('[WidgetDetails] Positioned beside shape', {
|
|
2250
|
+
openOnRight,
|
|
2251
|
+
x: Math.round(detailsPanelX),
|
|
2252
|
+
y: Math.round(detailsPanelY),
|
|
2253
|
+
w: detailsPanelWidth,
|
|
2254
|
+
h: detailsPanelHeight,
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2090
2258
|
function startColumnResize(e: MouseEvent) {
|
|
2091
2259
|
e.preventDefault();
|
|
2092
2260
|
e.stopPropagation();
|
|
@@ -2134,7 +2302,7 @@
|
|
|
2134
2302
|
export function openWidgetDetails() {
|
|
2135
2303
|
if (showWidgetDetails) return;
|
|
2136
2304
|
resetDetailsPanelSize();
|
|
2137
|
-
if (fillViewport || !_hasBeenOpenedOnce
|
|
2305
|
+
if (fillViewport || (!_hasBeenOpenedOnce && (detailsPanelX < 0 || detailsPanelY < 0))) {
|
|
2138
2306
|
centerDetailsPanel();
|
|
2139
2307
|
_hasBeenOpenedOnce = true;
|
|
2140
2308
|
}
|
|
@@ -2152,7 +2320,6 @@
|
|
|
2152
2320
|
if (selectedWidgetId) {
|
|
2153
2321
|
if (iframeSrc && previewLoadedForWidgetId === selectedWidgetId) {
|
|
2154
2322
|
console.log('[WidgetDetails] Reopened with existing preview, no reload needed');
|
|
2155
|
-
dispatch('buildFromRepoSuccess', { widgetId: selectedWidgetId, source: 'cached-preview' });
|
|
2156
2323
|
return;
|
|
2157
2324
|
}
|
|
2158
2325
|
loadWidget(selectedWidgetId);
|
|
@@ -2162,6 +2329,10 @@
|
|
|
2162
2329
|
export function closeWidgetDetails(options?: { silent?: boolean }) {
|
|
2163
2330
|
showWidgetDetails = false;
|
|
2164
2331
|
showPreviewModal = false;
|
|
2332
|
+
// Next open should auto-expand preview again if the widget already has code.
|
|
2333
|
+
hasAutoOpenedPreview = false;
|
|
2334
|
+
previewColumnCollapsed = true;
|
|
2335
|
+
widthBeforePreviewExpand = 0;
|
|
2165
2336
|
if (!options?.silent) {
|
|
2166
2337
|
dispatch('widgetDetailsToggle', { open: false, showWidgetDetails: false });
|
|
2167
2338
|
}
|
|
@@ -2691,19 +2862,18 @@
|
|
|
2691
2862
|
<!-- Widget Details Top Bar — title, steps, and close on one row -->
|
|
2692
2863
|
<div class="widget-details-header-ct flex flex-col shrink-0 bg-gray-100 border-b border-gray-200">
|
|
2693
2864
|
<div
|
|
2694
|
-
class="widget-details-drag-bar relative flex items-center gap-2 px-3 py-1.5 cursor-grab active:cursor-grabbing select-none min-h-[40px] touch-none"
|
|
2865
|
+
class="widget-details-drag-bar relative flex items-center justify-end gap-2 px-3 py-1.5 cursor-grab active:cursor-grabbing select-none min-h-[40px] touch-none flex-nowrap overflow-hidden"
|
|
2695
2866
|
onpointerdown={startDetailsDrag}
|
|
2696
2867
|
>
|
|
2697
|
-
<
|
|
2698
|
-
|
|
2699
|
-
{
|
|
2700
|
-
|
|
2701
|
-
{
|
|
2702
|
-
</
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
<div class="widget-details-step-indicator flex items-center gap-1 flex-wrap justify-center">
|
|
2868
|
+
<span
|
|
2869
|
+
class="widget-details-name pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 max-w-[22%] truncate text-xs font-medium text-gray-600"
|
|
2870
|
+
title={selectedWidget?.name || ''}
|
|
2871
|
+
>
|
|
2872
|
+
{selectedWidget?.name || (isDraftConvertPanel ? 'Widget Draft' : 'Widget')}
|
|
2873
|
+
</span>
|
|
2874
|
+
<!-- Step Indicator — centered; name is compact on the left (read-only, no rename) -->
|
|
2875
|
+
<div class="widget-details-step-indicator-row pointer-events-none absolute left-1/2 -translate-x-1/2 flex justify-center px-1">
|
|
2876
|
+
<div class="widget-details-step-indicator pointer-events-auto flex items-center gap-1 flex-nowrap justify-center whitespace-nowrap">
|
|
2707
2877
|
<!-- svelte-ignore a11y_consider_explicit_label -->
|
|
2708
2878
|
<button
|
|
2709
2879
|
type="button"
|
|
@@ -2713,7 +2883,7 @@
|
|
|
2713
2883
|
? 'bg-blue-100 text-blue-700 border border-blue-300 cursor-pointer'
|
|
2714
2884
|
: 'text-gray-600 hover:bg-gray-200 border border-transparent cursor-pointer'}"
|
|
2715
2885
|
>
|
|
2716
|
-
<span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold {currentStep === 'create' ? 'bg-blue-600 text-white' : 'bg-gray-300 text-gray-700'}">1</span>
|
|
2886
|
+
<span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold leading-none {currentStep === 'create' ? 'bg-blue-600 text-white' : 'bg-gray-300 text-gray-700'}">1</span>
|
|
2717
2887
|
<span class="step-label">Create</span>
|
|
2718
2888
|
</button>
|
|
2719
2889
|
<div class="step-connector w-4 h-px {canEditStep ? 'bg-gray-400' : 'bg-gray-200'}"></div>
|
|
@@ -2731,7 +2901,7 @@
|
|
|
2731
2901
|
? 'text-gray-600 hover:bg-gray-200 border border-transparent cursor-pointer'
|
|
2732
2902
|
: 'text-gray-300 border border-transparent cursor-default pointer-events-none'}"
|
|
2733
2903
|
>
|
|
2734
|
-
<span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold {currentStep === 'edit' ? 'bg-blue-600 text-white' : canEditStep ? 'bg-gray-300 text-gray-700' : 'bg-gray-200 text-gray-400'}">2</span>
|
|
2904
|
+
<span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold leading-none {currentStep === 'edit' ? 'bg-blue-600 text-white' : canEditStep ? 'bg-gray-300 text-gray-700' : 'bg-gray-200 text-gray-400'}">2</span>
|
|
2735
2905
|
<span class="step-label">Edit</span>
|
|
2736
2906
|
</button>
|
|
2737
2907
|
<div class="step-connector w-4 h-px {canEmbedStep ? 'bg-gray-400' : 'bg-gray-200'}"></div>
|
|
@@ -2749,13 +2919,26 @@
|
|
|
2749
2919
|
? 'text-gray-600 hover:bg-gray-200 border border-transparent cursor-pointer'
|
|
2750
2920
|
: 'text-gray-300 border border-transparent cursor-default pointer-events-none'}"
|
|
2751
2921
|
>
|
|
2752
|
-
<span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold {currentStep === 'embed' ? 'bg-blue-600 text-white' : canEmbedStep ? 'bg-gray-300 text-gray-700' : 'bg-gray-200 text-gray-400'}">3</span>
|
|
2922
|
+
<span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold leading-none {currentStep === 'embed' ? 'bg-blue-600 text-white' : canEmbedStep ? 'bg-gray-300 text-gray-700' : 'bg-gray-200 text-gray-400'}">3</span>
|
|
2753
2923
|
<span class="step-label">Embed</span>
|
|
2754
2924
|
</button>
|
|
2755
2925
|
</div>
|
|
2756
2926
|
</div>
|
|
2757
2927
|
|
|
2758
2928
|
<div class="widget-details-debug-buttons flex items-center gap-1.5 shrink-0">
|
|
2929
|
+
<button
|
|
2930
|
+
type="button"
|
|
2931
|
+
onclick={() => previewColumnCollapsed ? expandPreviewColumn() : collapsePreviewColumn()}
|
|
2932
|
+
onmousedown={(e) => e.stopPropagation()}
|
|
2933
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
2934
|
+
class="widget-details-preview-toggle-bt h-6 min-w-[6.75rem] px-2 flex items-center justify-center rounded-lg text-[11px] font-medium cursor-pointer transition-colors whitespace-nowrap
|
|
2935
|
+
{previewColumnCollapsed
|
|
2936
|
+
? 'bg-white border border-gray-300 text-gray-600 hover:bg-gray-100'
|
|
2937
|
+
: 'bg-blue-50 border border-blue-300 text-blue-700 hover:bg-blue-100'}"
|
|
2938
|
+
title={previewColumnCollapsed ? 'Open live preview column' : 'Close live preview column'}
|
|
2939
|
+
>
|
|
2940
|
+
{previewColumnCollapsed ? 'Open Preview' : 'Close Preview'}
|
|
2941
|
+
</button>
|
|
2759
2942
|
<button
|
|
2760
2943
|
type="button"
|
|
2761
2944
|
onclick={handleCloseButtonClick}
|
|
@@ -2777,7 +2960,7 @@
|
|
|
2777
2960
|
<!-- ═══ LEFT COLUMN: Step Content (scrollable) ═══ -->
|
|
2778
2961
|
<div
|
|
2779
2962
|
class="widget-details-left-col flex flex-col min-w-0 min-h-0 relative"
|
|
2780
|
-
style="width: {leftColPercent}%; flex: 0 0 {leftColPercent}%;"
|
|
2963
|
+
style="width: {previewColumnCollapsed ? 100 : leftColPercent}%; flex: 0 0 {previewColumnCollapsed ? 100 : leftColPercent}%;"
|
|
2781
2964
|
>
|
|
2782
2965
|
<div class="widget-details-left-border absolute inset-y-0 left-0 right-0 border border-gray-300 rounded-lg pointer-events-none z-10"></div>
|
|
2783
2966
|
|
|
@@ -2785,16 +2968,13 @@
|
|
|
2785
2968
|
<!-- Sticky Widget Info Bar (always visible above scrollable content) -->
|
|
2786
2969
|
<div class="widget-info-sticky-bar flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 rounded-t-lg shrink-0 gap-2 min-w-0 z-5">
|
|
2787
2970
|
<div class="widget-info-name flex items-center min-w-0 gap-1 text-sm text-gray-700">
|
|
2788
|
-
<EditableName
|
|
2789
|
-
name={selectedWidget?.name || ''}
|
|
2790
|
-
placeholder="Unnamed"
|
|
2791
|
-
tooltipText="Double-click to rename"
|
|
2792
|
-
class="flex-1 min-w-0"
|
|
2793
|
-
on:rename={({ detail }) => dispatch('widgetRename', { widgetId: selectedWidgetId, newName: detail.newName })}
|
|
2794
|
-
/>
|
|
2795
2971
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
2796
2972
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
2797
|
-
<span
|
|
2973
|
+
<span
|
|
2974
|
+
class="widget-id-label text-[11px] font-mono text-gray-500 cursor-pointer hover:text-gray-700 truncate"
|
|
2975
|
+
title="Click to copy widget ID"
|
|
2976
|
+
onclick={() => { navigator.clipboard.writeText(selectedWidgetId); showToast('success', 'Widget ID copied', { duration: 2000 }); }}
|
|
2977
|
+
>Widget id: {selectedWidgetId}</span>
|
|
2798
2978
|
</div>
|
|
2799
2979
|
<div class="widget-info-meta flex items-center gap-2 shrink-0">
|
|
2800
2980
|
{#if saveStatus === 'saving'}
|
|
@@ -2815,13 +2995,13 @@
|
|
|
2815
2995
|
|
|
2816
2996
|
<!-- Scrollable step content -->
|
|
2817
2997
|
<div
|
|
2818
|
-
class="widget-details-left-content flex flex-col flex-1 min-h-0 relative rounded-b-lg {currentStep === 'edit' ? 'overflow-hidden' : 'overflow-
|
|
2998
|
+
class="widget-details-left-content flex flex-col flex-1 min-h-0 relative rounded-b-lg {currentStep === 'edit' ? 'overflow-hidden' : 'overflow-hidden'}"
|
|
2819
2999
|
bind:this={contentContainerEl}
|
|
2820
3000
|
>
|
|
2821
3001
|
<div class="actions-panel widget-details-actions-panel relative flex flex-col flex-1 min-h-0">
|
|
2822
3002
|
|
|
2823
3003
|
<!-- Keep all steps mounted — switching tabs must not destroy chat / PropsEditor state -->
|
|
2824
|
-
<div class="widget-details-create-step flex flex-col gap-2 p-2 {currentStep === 'create' ? '' : 'hidden'}">
|
|
3004
|
+
<div class="widget-details-create-step flex flex-col flex-1 min-h-0 overflow-hidden gap-2 p-2 {currentStep === 'create' ? '' : 'hidden'}">
|
|
2825
3005
|
<slot name="code-generation" />
|
|
2826
3006
|
<slot name="publish-widget" />
|
|
2827
3007
|
</div>
|
|
@@ -2838,6 +3018,18 @@
|
|
|
2838
3018
|
|
|
2839
3019
|
</div> <!-- end of widget-details-left-col -->
|
|
2840
3020
|
|
|
3021
|
+
{#if previewColumnCollapsed}
|
|
3022
|
+
<div class="widget-details-preview-expand-strip flex items-center justify-center w-8 shrink-0 border-l border-gray-200 bg-gray-50">
|
|
3023
|
+
<button
|
|
3024
|
+
type="button"
|
|
3025
|
+
onclick={expandPreviewColumn}
|
|
3026
|
+
class="widget-details-preview-expand-bt h-full w-full text-[10px] font-semibold text-gray-500 hover:text-forest-green hover:bg-gray-100 cursor-pointer"
|
|
3027
|
+
title="Show live preview"
|
|
3028
|
+
>
|
|
3029
|
+
<span class="widget-details-preview-expand-label inline-block rotate-180" style="writing-mode: vertical-rl;">Preview</span>
|
|
3030
|
+
</button>
|
|
3031
|
+
</div>
|
|
3032
|
+
{:else}
|
|
2841
3033
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
2842
3034
|
<div
|
|
2843
3035
|
class="widget-details-col-resizer w-2.5 shrink-0 cursor-col-resize relative z-20 group"
|
|
@@ -2949,7 +3141,14 @@
|
|
|
2949
3141
|
title={isGeneratingCode ? 'Generating code…' : 'Building preview…'}
|
|
2950
3142
|
size="md"
|
|
2951
3143
|
/>
|
|
2952
|
-
{:else if previewError === '
|
|
3144
|
+
{:else if previewError === 'DRAFT_PANEL'}
|
|
3145
|
+
<div class="widget-preview-draft-hint flex flex-col items-center justify-center text-center p-4">
|
|
3146
|
+
<div class="text-3xl mb-2">🎨</div>
|
|
3147
|
+
<div class="font-medium text-sm text-gray-700 mb-1">Sketch draft</div>
|
|
3148
|
+
<div class="text-xs max-w-[240px] text-gray-500 mb-1">Your sketch is still on the canvas — keep editing it freely.</div>
|
|
3149
|
+
<div class="text-xs max-w-[240px] text-gray-500">Send the prompt in chat to create the widget and generate code.</div>
|
|
3150
|
+
</div>
|
|
3151
|
+
{:else if previewError === 'NO_CODE_YET' && !hasWidgetCode}
|
|
2953
3152
|
<div class="widget-preview-no-code flex flex-col items-center justify-center text-center p-4">
|
|
2954
3153
|
{#if !normalizeRepositoryId(selectedWidget) && repositorySetupStatus !== 'failed'}
|
|
2955
3154
|
<GenerateLoader
|
|
@@ -3031,6 +3230,7 @@
|
|
|
3031
3230
|
|
|
3032
3231
|
</div> <!-- end of widget-details-right-content -->
|
|
3033
3232
|
</div> <!-- end of widget-details-right-col -->
|
|
3233
|
+
{/if}
|
|
3034
3234
|
|
|
3035
3235
|
</div> <!-- end of widget-details-two-col -->
|
|
3036
3236
|
|
|
@@ -3067,7 +3267,7 @@
|
|
|
3067
3267
|
|
|
3068
3268
|
<style>
|
|
3069
3269
|
.widget-details-left-content {
|
|
3070
|
-
overflow
|
|
3270
|
+
overflow: hidden;
|
|
3071
3271
|
overflow-x: hidden;
|
|
3072
3272
|
scrollbar-width: thin;
|
|
3073
3273
|
scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
|
|
@@ -3124,4 +3324,17 @@
|
|
|
3124
3324
|
:global(.widget-details-right-content .widget-preview-wrapper) {
|
|
3125
3325
|
overflow: visible !important;
|
|
3126
3326
|
}
|
|
3327
|
+
|
|
3328
|
+
.step-number {
|
|
3329
|
+
line-height: 1;
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
/* Tab focus: hide DS/site orange ring inside Widget Details (buttons + links). */
|
|
3333
|
+
.widget-details-floating-panel :global(button:focus),
|
|
3334
|
+
.widget-details-floating-panel :global(button:focus-visible),
|
|
3335
|
+
.widget-details-floating-panel :global(a:focus),
|
|
3336
|
+
.widget-details-floating-panel :global(a:focus-visible) {
|
|
3337
|
+
outline: none !important;
|
|
3338
|
+
box-shadow: none !important;
|
|
3339
|
+
}
|
|
3127
3340
|
</style>
|
|
@@ -147,12 +147,22 @@ declare const WidgetDetails: $$__sveltets_2_IsomorphicComponent<{
|
|
|
147
147
|
startTokenAutoRefreshPublic?: () => void;
|
|
148
148
|
fetchDirectPreviewUrlPublic?: () => Promise<string | null>;
|
|
149
149
|
centerDetailsPanel?: () => void;
|
|
150
|
+
keepPanelInViewport?: () => void;
|
|
151
|
+
positionBesideShape?: (shapeRect: {
|
|
152
|
+
left: number;
|
|
153
|
+
top: number;
|
|
154
|
+
width: number;
|
|
155
|
+
height: number;
|
|
156
|
+
viewportLeft: number;
|
|
157
|
+
viewportTop: number;
|
|
158
|
+
viewportWidth: number;
|
|
159
|
+
viewportHeight: number;
|
|
160
|
+
} | null) => void;
|
|
150
161
|
openWidgetDetails?: () => void;
|
|
151
162
|
closeWidgetDetails?: (options?: {
|
|
152
163
|
silent?: boolean;
|
|
153
164
|
}) => void;
|
|
154
165
|
}, {
|
|
155
|
-
widgetRename: CustomEvent<any>;
|
|
156
166
|
fixErrors: CustomEvent<any>;
|
|
157
167
|
retryRepository: CustomEvent<any>;
|
|
158
168
|
generateNow: CustomEvent<any>;
|
|
@@ -226,6 +236,17 @@ declare const WidgetDetails: $$__sveltets_2_IsomorphicComponent<{
|
|
|
226
236
|
startTokenAutoRefreshPublic: () => void;
|
|
227
237
|
fetchDirectPreviewUrlPublic: () => Promise<string | null>;
|
|
228
238
|
centerDetailsPanel: () => void;
|
|
239
|
+
keepPanelInViewport: () => void;
|
|
240
|
+
positionBesideShape: (shapeRect: {
|
|
241
|
+
left: number;
|
|
242
|
+
top: number;
|
|
243
|
+
width: number;
|
|
244
|
+
height: number;
|
|
245
|
+
viewportLeft: number;
|
|
246
|
+
viewportTop: number;
|
|
247
|
+
viewportWidth: number;
|
|
248
|
+
viewportHeight: number;
|
|
249
|
+
} | null) => void;
|
|
229
250
|
openWidgetDetails: () => void;
|
|
230
251
|
closeWidgetDetails: (options?: {
|
|
231
252
|
silent?: boolean;
|
|
@@ -19,6 +19,10 @@ export function streamOperationUpdates(operationId, options) {
|
|
|
19
19
|
let abortController = null;
|
|
20
20
|
let reader = null;
|
|
21
21
|
const cleanup = () => {
|
|
22
|
+
if (watchdogTimer) {
|
|
23
|
+
clearTimeout(watchdogTimer);
|
|
24
|
+
watchdogTimer = null;
|
|
25
|
+
}
|
|
22
26
|
try {
|
|
23
27
|
reader?.cancel();
|
|
24
28
|
}
|
|
@@ -42,6 +46,18 @@ export function streamOperationUpdates(operationId, options) {
|
|
|
42
46
|
else
|
|
43
47
|
reject(value);
|
|
44
48
|
};
|
|
49
|
+
// Watchdog: if no frame arrives for a while (proxy dropped the stream,
|
|
50
|
+
// server stalled), give up on SSE and let the caller fall back to
|
|
51
|
+
// polling GET /operations/:id — the terminal event then always lands.
|
|
52
|
+
const WATCHDOG_TIMEOUT_MS = 45000;
|
|
53
|
+
let watchdogTimer = null;
|
|
54
|
+
const armWatchdog = () => {
|
|
55
|
+
if (watchdogTimer)
|
|
56
|
+
clearTimeout(watchdogTimer);
|
|
57
|
+
watchdogTimer = setTimeout(() => {
|
|
58
|
+
settle('reject', new Error('Operation stream idle — falling back to polling'));
|
|
59
|
+
}, WATCHDOG_TIMEOUT_MS);
|
|
60
|
+
};
|
|
45
61
|
const run = async () => {
|
|
46
62
|
abortController = new AbortController();
|
|
47
63
|
// Propagate caller aborts into the fetch request.
|
|
@@ -68,12 +84,15 @@ export function streamOperationUpdates(operationId, options) {
|
|
|
68
84
|
reader = response.body.getReader();
|
|
69
85
|
const decoder = new TextDecoder();
|
|
70
86
|
let buffer = '';
|
|
87
|
+
armWatchdog();
|
|
71
88
|
while (true) {
|
|
72
89
|
const { done, value } = await reader.read();
|
|
73
90
|
if (done)
|
|
74
91
|
break;
|
|
92
|
+
armWatchdog();
|
|
75
93
|
buffer += decoder.decode(value, { stream: true });
|
|
76
|
-
// SSE frames are `data: {...}\n\n
|
|
94
|
+
// SSE frames are `data: {...}\n\n`; keepalive comment frames carry
|
|
95
|
+
// no data line and are skipped.
|
|
77
96
|
let frameEnd = buffer.indexOf('\n\n');
|
|
78
97
|
while (frameEnd !== -1) {
|
|
79
98
|
const frame = buffer.slice(0, frameEnd);
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@widgetic/creator",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.50",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@sveltejs/kit": "^2.20.0",
|
|
7
7
|
"svelte": "^5.25.0",
|
|
8
|
-
"@widgetic/api-sdk": "
|
|
9
|
-
"@widgetic/canvas": "^0.5.
|
|
10
|
-
"@widgetic/cache-layer": "^0.1.
|
|
11
|
-
"@widgetic/chat": "^0.1.
|
|
12
|
-
"@widgetic/design-system": "
|
|
13
|
-
"@widgetic/editor": "^4.0.
|
|
14
|
-
"@widgetic/file-browser": "^0.5.
|
|
8
|
+
"@widgetic/api-sdk": "^1.0.10",
|
|
9
|
+
"@widgetic/canvas": "^0.5.4",
|
|
10
|
+
"@widgetic/cache-layer": "^0.1.2",
|
|
11
|
+
"@widgetic/chat": "^0.1.4",
|
|
12
|
+
"@widgetic/design-system": "^0.5.8",
|
|
13
|
+
"@widgetic/editor": "^4.0.1",
|
|
14
|
+
"@widgetic/file-browser": "^0.5.17"
|
|
15
15
|
},
|
|
16
16
|
"svelte": "./dist/index.js",
|
|
17
17
|
"module": "./dist/index.js",
|
|
@@ -89,13 +89,13 @@
|
|
|
89
89
|
"vite": "^6.2.6",
|
|
90
90
|
"vite-imagetools": "^7.0.4",
|
|
91
91
|
"vitest": "^2.0.0",
|
|
92
|
-
"@widgetic/api-sdk": "
|
|
93
|
-
"@widgetic/canvas": "^0.5.
|
|
94
|
-
"@widgetic/cache-layer": "^0.1.
|
|
95
|
-
"@widgetic/chat": "^0.1.
|
|
96
|
-
"@widgetic/design-system": "
|
|
97
|
-
"@widgetic/editor": "^4.0.
|
|
98
|
-
"@widgetic/file-browser": "^0.5.
|
|
92
|
+
"@widgetic/api-sdk": "^1.0.10",
|
|
93
|
+
"@widgetic/canvas": "^0.5.4",
|
|
94
|
+
"@widgetic/cache-layer": "^0.1.2",
|
|
95
|
+
"@widgetic/chat": "^0.1.4",
|
|
96
|
+
"@widgetic/design-system": "^0.5.8",
|
|
97
|
+
"@widgetic/editor": "^4.0.1",
|
|
98
|
+
"@widgetic/file-browser": "^0.5.17"
|
|
99
99
|
},
|
|
100
100
|
"dependencies": {
|
|
101
101
|
"@codesandbox/sandpack-react": "^2.19.9",
|