@dfosco/storyboard-react 4.0.0-beta.32 → 4.0.0-beta.34
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/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dfosco/storyboard-react",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@dfosco/storyboard-core": "4.0.0-beta.
|
|
7
|
-
"@dfosco/tiny-canvas": "4.0.0-beta.
|
|
6
|
+
"@dfosco/storyboard-core": "4.0.0-beta.34",
|
|
7
|
+
"@dfosco/tiny-canvas": "4.0.0-beta.34",
|
|
8
8
|
"@neodrag/react": "^2.3.1",
|
|
9
9
|
"glob": "^11.0.0",
|
|
10
10
|
"jsonc-parser": "^3.3.1",
|
|
@@ -83,13 +83,20 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
83
83
|
const scale = zoom / 100
|
|
84
84
|
|
|
85
85
|
const [editing, setEditing] = useState(false)
|
|
86
|
-
const [interactive,
|
|
87
|
-
const [showIframe,
|
|
88
|
-
const [iframeLoaded,
|
|
86
|
+
const [interactive, _setInteractive] = useState(false)
|
|
87
|
+
const [showIframe, _setShowIframe] = useState(false)
|
|
88
|
+
const [iframeLoaded, _setIframeLoaded] = useState(false)
|
|
89
89
|
const [expanded, setExpanded] = useState(false)
|
|
90
90
|
const [filter, setFilter] = useState('')
|
|
91
|
-
const [canvasTheme,
|
|
92
|
-
const [brokenSnaps,
|
|
91
|
+
const [canvasTheme, _setCanvasTheme] = useState('light')
|
|
92
|
+
const [brokenSnaps, _setBrokenSnaps] = useState({})
|
|
93
|
+
|
|
94
|
+
// ── Debug logging wrappers (temporary) ──
|
|
95
|
+
const setInteractive = (v) => { console.log(`[embed:${widgetId}] setInteractive →`, v); _setInteractive(v) }
|
|
96
|
+
const setShowIframe = (v) => { if (v) console.trace(`[embed:${widgetId}] setShowIframe → TRUE`); else console.log(`[embed:${widgetId}] setShowIframe → false`); _setShowIframe(v) }
|
|
97
|
+
const setIframeLoaded = (v) => { console.log(`[embed:${widgetId}] iframeLoaded →`, v); _setIframeLoaded(v) }
|
|
98
|
+
const setCanvasTheme = (v) => { console.log(`[embed:${widgetId}] canvasTheme →`, v); _setCanvasTheme(v) }
|
|
99
|
+
const setBrokenSnaps = (fn) => { console.log(`[embed:${widgetId}] setBrokenSnaps`); _setBrokenSnaps(fn) }
|
|
93
100
|
|
|
94
101
|
const inputRef = useRef(null)
|
|
95
102
|
const filterRef = useRef(null)
|
|
@@ -114,6 +121,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
114
121
|
|
|
115
122
|
// Single snapshot — backward compat reads snapshotLight/snapshotDark if snapshot is missing
|
|
116
123
|
const hasSnap = !isExternal && !!(snapshot && snapshot.includes(widgetId) && !brokenSnaps[snapshot])
|
|
124
|
+
console.log(`[embed:${widgetId}] render: hasSnap=${hasSnap}, showIframe=${showIframe}, interactive=${interactive}, canvasTheme=${canvasTheme}, snapshot=${snapshot ? snapshot.slice(0, 50) : 'null'}, broken=${!!brokenSnaps[snapshot]}`)
|
|
117
125
|
|
|
118
126
|
const iframeSrc = useMemo(() => {
|
|
119
127
|
if (!rawSrc) return ''
|
|
@@ -247,8 +255,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
247
255
|
}, [showIframe])
|
|
248
256
|
|
|
249
257
|
// Exit interactive mode when clicking outside the embed.
|
|
250
|
-
//
|
|
251
|
-
// snapshots in the background with the iframe hidden but still mounted.
|
|
258
|
+
// Captures a snapshot in the background, then unmounts the iframe.
|
|
252
259
|
useEffect(() => {
|
|
253
260
|
if (!interactive || expanded) return
|
|
254
261
|
function handlePointerDown(e) {
|
|
@@ -280,8 +287,6 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
280
287
|
})
|
|
281
288
|
}, 0)
|
|
282
289
|
} else if (isExternal && showIframe) {
|
|
283
|
-
// External embeds (e.g. Figma) are slow to reload — keep the
|
|
284
|
-
// iframe mounted for 2 min so re-entering is instant.
|
|
285
290
|
const session = ++exitSessionRef.current
|
|
286
291
|
clearTimeout(teardownTimerRef.current)
|
|
287
292
|
teardownTimerRef.current = setTimeout(() => {
|
|
@@ -303,25 +308,27 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
303
308
|
}), [])
|
|
304
309
|
|
|
305
310
|
// On canvas theme change, enqueue a background snapshot refresh.
|
|
306
|
-
//
|
|
307
|
-
// Uses
|
|
308
|
-
|
|
311
|
+
// Only fires for true user-initiated theme changes (not page load).
|
|
312
|
+
// Uses mountTime to ignore the initial theme resolution that happens
|
|
313
|
+
// within the first 3 seconds of mount.
|
|
314
|
+
const mountTimeRef = useRef(Date.now())
|
|
309
315
|
const refreshMetaRef = useRef(null)
|
|
310
316
|
const hasSnapRef = useRef(hasSnap)
|
|
311
317
|
hasSnapRef.current = hasSnap
|
|
312
318
|
useEffect(() => {
|
|
313
|
-
|
|
314
|
-
|
|
319
|
+
// Ignore theme changes during initial page load (first 3 seconds)
|
|
320
|
+
const elapsed = Date.now() - mountTimeRef.current
|
|
321
|
+
if (elapsed < 3000) { console.log(`[embed:${widgetId}] theme-effect: skip page-load (${elapsed}ms)`); return }
|
|
322
|
+
if (isExternal || !onUpdate || interactive) { console.log(`[embed:${widgetId}] theme-effect: skip (ext=${isExternal}, noUpdate=${!onUpdate}, interactive=${interactive})`); return }
|
|
323
|
+
if (!hasSnapRef.current) { console.log(`[embed:${widgetId}] theme-effect: skip (no snap)`); return }
|
|
324
|
+
console.log(`[embed:${widgetId}] theme-effect: enqueue refresh, hasSnapRef=${hasSnapRef.current}`)
|
|
315
325
|
const rect = embedRef.current?.getBoundingClientRect()
|
|
316
326
|
enqueueRefresh(widgetId, ({ revealOrder, batchStart }) => {
|
|
317
|
-
// Re-check hasSnap at callback time — snapshot may have been
|
|
318
|
-
// marked broken (404) between enqueue and execution.
|
|
319
327
|
if (!hasSnapRef.current) return Promise.resolve(false)
|
|
320
328
|
return new Promise((resolve) => {
|
|
321
329
|
refreshMetaRef.current = { revealOrder, batchStart, resolve }
|
|
322
330
|
captureOnReadyRef.current = true
|
|
323
331
|
setShowIframe(true)
|
|
324
|
-
// Safety timeout — report failure so retry pass picks it up
|
|
325
332
|
setTimeout(() => { refreshMetaRef.current = null; resolve(false) }, 10000)
|
|
326
333
|
})
|
|
327
334
|
}, rect ? { x: rect.left, y: rect.top } : undefined)
|
|
@@ -357,7 +364,6 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
357
364
|
}
|
|
358
365
|
meta.resolve(!!snap)
|
|
359
366
|
}
|
|
360
|
-
// Wait for our reveal slot in the wave
|
|
361
367
|
const elapsed = Date.now() - meta.batchStart
|
|
362
368
|
const targetTime = meta.revealOrder * REVEAL_INTERVAL
|
|
363
369
|
const wait = Math.max(0, targetTime - elapsed)
|
|
@@ -444,6 +450,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
444
450
|
const chromeVars = useMemo(() => getEmbedChromeVars(canvasTheme), [canvasTheme])
|
|
445
451
|
|
|
446
452
|
const enterInteractive = useCallback(() => {
|
|
453
|
+
console.log(`[embed:${widgetId}] enterInteractive`)
|
|
447
454
|
exitSessionRef.current++
|
|
448
455
|
clearTimeout(teardownTimerRef.current)
|
|
449
456
|
cancelRefresh(widgetId)
|
|
@@ -598,7 +605,12 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
598
605
|
className={styles.snapshotImage}
|
|
599
606
|
alt={`${prototypeTitle} snapshot`}
|
|
600
607
|
draggable={false}
|
|
601
|
-
onError={() =>
|
|
608
|
+
onError={() => {
|
|
609
|
+
console.log(`[embed:${widgetId}] snapshot img onError: ${snapshot?.slice(0, 60)}`)
|
|
610
|
+
setBrokenSnaps(prev => ({ ...prev, [snapshot]: true }))
|
|
611
|
+
// Clear the broken snapshot from widget data so we stop trying
|
|
612
|
+
onUpdate?.({ snapshot: '' })
|
|
613
|
+
}}
|
|
602
614
|
/>
|
|
603
615
|
)}
|
|
604
616
|
|
|
@@ -616,7 +628,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
616
628
|
transition: 'opacity 150ms ease',
|
|
617
629
|
...(iframeLoaded ? {} : { opacity: 0 }),
|
|
618
630
|
}}
|
|
619
|
-
onLoad={() => setIframeLoaded(true)}
|
|
631
|
+
onLoad={() => { console.log(`[embed:${widgetId}] iframe onLoad`); setIframeLoaded(true) }}
|
|
620
632
|
title={`${prototypeTitle} prototype`}
|
|
621
633
|
sandbox="allow-same-origin allow-scripts allow-forms allow-popups"
|
|
622
634
|
/>
|
|
@@ -31,6 +31,7 @@ export const REVEAL_INTERVAL = 200
|
|
|
31
31
|
* @param {{ x: number, y: number }} [pos] — spatial position for wave ordering
|
|
32
32
|
*/
|
|
33
33
|
export function enqueueRefresh(widgetId, fn, pos) {
|
|
34
|
+
console.log(`[refreshQueue] enqueue: ${widgetId}, queueLen=${queue.length}`)
|
|
34
35
|
const existing = queue.findIndex(item => item.widgetId === widgetId)
|
|
35
36
|
if (existing !== -1) queue.splice(existing, 1)
|
|
36
37
|
|
|
@@ -69,11 +70,13 @@ function scheduleDrain() {
|
|
|
69
70
|
|
|
70
71
|
function onTaskDone(success, item) {
|
|
71
72
|
batchDone++
|
|
73
|
+
console.log(`[refreshQueue] taskDone: ${item.widgetId}, success=${success}, done=${batchDone}/${batchTotal}, retry=${item.isRetry}`)
|
|
72
74
|
if (!success && !item.isRetry) {
|
|
73
75
|
batchFailed.push(item)
|
|
74
76
|
}
|
|
75
77
|
// When batch is complete, re-enqueue failures for one retry
|
|
76
78
|
if (batchDone >= batchTotal && batchFailed.length > 0) {
|
|
79
|
+
console.log(`[refreshQueue] batch complete, retrying ${batchFailed.length} failed`)
|
|
77
80
|
const retries = batchFailed.splice(0)
|
|
78
81
|
for (const failed of retries) {
|
|
79
82
|
failed.isRetry = true
|
|
@@ -95,6 +95,7 @@ export function useSnapshotCapture({
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
if (e.data?.type === 'storyboard:embed:snapshot-ready') {
|
|
98
|
+
console.log(`[snapshot:${widgetId}] iframe ready`)
|
|
98
99
|
setIframeReady(true)
|
|
99
100
|
iframeReadyRef.current = true
|
|
100
101
|
}
|
|
@@ -115,10 +116,11 @@ export function useSnapshotCapture({
|
|
|
115
116
|
* Uploads and saves as `snapshot` prop, overwriting any previous value.
|
|
116
117
|
*/
|
|
117
118
|
const requestCapture = useCallback(async ({ force = false } = {}) => {
|
|
119
|
+
console.log(`[snapshot:${widgetId}] requestCapture: force=${force}, hasContentWindow=${!!iframeRef.current?.contentWindow}, capturing=${capturingRef.current}, ready=${iframeReadyRef.current}`)
|
|
118
120
|
if (!onUpdate) return {}
|
|
119
|
-
if (!iframeRef.current?.contentWindow) return {}
|
|
120
|
-
if (capturingRef.current) return {}
|
|
121
|
-
if (!force && !iframeReadyRef.current) return {}
|
|
121
|
+
if (!iframeRef.current?.contentWindow) { console.log(`[snapshot:${widgetId}] requestCapture: no contentWindow`); return {} }
|
|
122
|
+
if (capturingRef.current) { console.log(`[snapshot:${widgetId}] requestCapture: already capturing`); return {} }
|
|
123
|
+
if (!force && !iframeReadyRef.current) { console.log(`[snapshot:${widgetId}] requestCapture: not ready`); return {} }
|
|
122
124
|
|
|
123
125
|
capturingRef.current = true
|
|
124
126
|
const gen = ++captureGeneration.current
|
|
@@ -129,18 +131,20 @@ export function useSnapshotCapture({
|
|
|
129
131
|
const reqId = ++requestIdCounter.current
|
|
130
132
|
const dataUrl = await captureOnce(cw, reqId, responseHandlers.current)
|
|
131
133
|
|
|
132
|
-
if (gen !== captureGeneration.current) return {}
|
|
133
|
-
if (!dataUrl) return {}
|
|
134
|
+
if (gen !== captureGeneration.current) { console.log(`[snapshot:${widgetId}] stale gen after capture`); return {} }
|
|
135
|
+
if (!dataUrl) { console.log(`[snapshot:${widgetId}] captureOnce returned null`); return {} }
|
|
134
136
|
|
|
135
137
|
const filename = `snapshot-${widgetId}.webp`
|
|
138
|
+
console.log(`[snapshot:${widgetId}] uploading ${filename}`)
|
|
136
139
|
const result = await uploadImage(dataUrl, `snapshot-${widgetId}`, filename)
|
|
137
140
|
|
|
138
|
-
if (gen !== captureGeneration.current) return {}
|
|
141
|
+
if (gen !== captureGeneration.current) { console.log(`[snapshot:${widgetId}] stale gen after upload`); return {} }
|
|
139
142
|
|
|
140
143
|
if (result?.filename) {
|
|
141
144
|
const cacheBust = `?v=${Date.now()}`
|
|
142
145
|
const url = `${base}/_storyboard/canvas/images/${result.filename}${cacheBust}`
|
|
143
146
|
const updates = { snapshot: url }
|
|
147
|
+
console.log(`[snapshot:${widgetId}] saved: ${url.slice(0, 60)}`)
|
|
144
148
|
onUpdate?.(updates)
|
|
145
149
|
return updates
|
|
146
150
|
}
|