@dfosco/storyboard-react 4.0.0-beta.34 → 4.0.0-beta.36
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.
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useSnapshotCapture — parent-side capture orchestration hook.
|
|
3
|
-
*
|
|
4
|
-
* Listens for snapshot-ready signals from an embedded iframe and
|
|
5
|
-
* provides a requestCapture() function that triggers a single capture
|
|
6
|
-
* of whatever the iframe is currently showing.
|
|
7
|
-
*
|
|
8
|
-
* Saves a single `snapshot` prop — overwritten every time.
|
|
9
|
-
* Only active in dev mode (when onUpdate is provided).
|
|
10
|
-
*/
|
|
11
|
-
import { useState, useEffect, useCallback, useRef } from 'react'
|
|
12
|
-
import { uploadImage } from '../canvasApi.js'
|
|
13
|
-
|
|
14
|
-
const CAPTURE_TIMEOUT = 3000
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Run a single capture request against the iframe.
|
|
18
|
-
* Returns the dataUrl or null on failure.
|
|
19
|
-
*/
|
|
20
|
-
function captureOnce(iframeContentWindow, requestId, listeners) {
|
|
21
|
-
return new Promise((resolve) => {
|
|
22
|
-
const timer = setTimeout(() => {
|
|
23
|
-
cleanup()
|
|
24
|
-
resolve(null)
|
|
25
|
-
}, CAPTURE_TIMEOUT)
|
|
26
|
-
|
|
27
|
-
function cleanup() {
|
|
28
|
-
clearTimeout(timer)
|
|
29
|
-
const idx = listeners.indexOf(handler)
|
|
30
|
-
if (idx !== -1) listeners.splice(idx, 1)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function handler(data) {
|
|
34
|
-
if (data.requestId !== requestId) return
|
|
35
|
-
cleanup()
|
|
36
|
-
if (data.error || !data.dataUrl) {
|
|
37
|
-
if (data.error) console.warn('[snapshot] Capture failed:', data.error)
|
|
38
|
-
resolve(null)
|
|
39
|
-
} else {
|
|
40
|
-
resolve(data.dataUrl)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
listeners.push(handler)
|
|
45
|
-
iframeContentWindow.postMessage({
|
|
46
|
-
type: 'storyboard:embed:capture',
|
|
47
|
-
requestId,
|
|
48
|
-
}, '*')
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function useSnapshotCapture({
|
|
53
|
-
iframeRef,
|
|
54
|
-
widgetId,
|
|
55
|
-
onUpdate,
|
|
56
|
-
showIframe,
|
|
57
|
-
}) {
|
|
58
|
-
const [iframeReady, setIframeReady] = useState(false)
|
|
59
|
-
const iframeReadyRef = useRef(false)
|
|
60
|
-
const capturingRef = useRef(false)
|
|
61
|
-
const requestIdCounter = useRef(0)
|
|
62
|
-
const captureGeneration = useRef(0)
|
|
63
|
-
const responseHandlers = useRef([])
|
|
64
|
-
// Track the iframe contentWindow to reset readiness on remount
|
|
65
|
-
const lastContentWindowRef = useRef(null)
|
|
66
|
-
|
|
67
|
-
// Reset ready state when iframe is unmounted/remounted
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
setIframeReady(false)
|
|
70
|
-
iframeReadyRef.current = false
|
|
71
|
-
}, [widgetId])
|
|
72
|
-
|
|
73
|
-
// Reset readiness when iframe is torn down so remount waits for new snapshot-ready
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (!showIframe) {
|
|
76
|
-
setIframeReady(false)
|
|
77
|
-
iframeReadyRef.current = false
|
|
78
|
-
lastContentWindowRef.current = null
|
|
79
|
-
}
|
|
80
|
-
}, [showIframe])
|
|
81
|
-
|
|
82
|
-
// Listen for postMessage events from the embedded iframe
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
if (!onUpdate) return
|
|
85
|
-
|
|
86
|
-
function handler(e) {
|
|
87
|
-
if (!iframeRef.current) return
|
|
88
|
-
if (e.source !== iframeRef.current.contentWindow) return
|
|
89
|
-
|
|
90
|
-
// Detect new iframe instance → reset readiness
|
|
91
|
-
if (e.source !== lastContentWindowRef.current) {
|
|
92
|
-
lastContentWindowRef.current = e.source
|
|
93
|
-
setIframeReady(false)
|
|
94
|
-
iframeReadyRef.current = false
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (e.data?.type === 'storyboard:embed:snapshot-ready') {
|
|
98
|
-
console.log(`[snapshot:${widgetId}] iframe ready`)
|
|
99
|
-
setIframeReady(true)
|
|
100
|
-
iframeReadyRef.current = true
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (e.data?.type === 'storyboard:embed:snapshot') {
|
|
104
|
-
for (const fn of responseHandlers.current) {
|
|
105
|
-
fn(e.data)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
window.addEventListener('message', handler)
|
|
111
|
-
return () => window.removeEventListener('message', handler)
|
|
112
|
-
}, [iframeRef, onUpdate])
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Capture a single snapshot of the current iframe state.
|
|
116
|
-
* Uploads and saves as `snapshot` prop, overwriting any previous value.
|
|
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}`)
|
|
120
|
-
if (!onUpdate) 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 {} }
|
|
124
|
-
|
|
125
|
-
capturingRef.current = true
|
|
126
|
-
const gen = ++captureGeneration.current
|
|
127
|
-
const cw = iframeRef.current.contentWindow
|
|
128
|
-
const base = (import.meta.env?.BASE_URL || '/').replace(/\/$/, '')
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
const reqId = ++requestIdCounter.current
|
|
132
|
-
const dataUrl = await captureOnce(cw, reqId, responseHandlers.current)
|
|
133
|
-
|
|
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 {} }
|
|
136
|
-
|
|
137
|
-
const filename = `snapshot-${widgetId}.webp`
|
|
138
|
-
console.log(`[snapshot:${widgetId}] uploading ${filename}`)
|
|
139
|
-
const result = await uploadImage(dataUrl, `snapshot-${widgetId}`, filename)
|
|
140
|
-
|
|
141
|
-
if (gen !== captureGeneration.current) { console.log(`[snapshot:${widgetId}] stale gen after upload`); return {} }
|
|
142
|
-
|
|
143
|
-
if (result?.filename) {
|
|
144
|
-
const cacheBust = `?v=${Date.now()}`
|
|
145
|
-
const url = `${base}/_storyboard/canvas/images/${result.filename}${cacheBust}`
|
|
146
|
-
const updates = { snapshot: url }
|
|
147
|
-
console.log(`[snapshot:${widgetId}] saved: ${url.slice(0, 60)}`)
|
|
148
|
-
onUpdate?.(updates)
|
|
149
|
-
return updates
|
|
150
|
-
}
|
|
151
|
-
return {}
|
|
152
|
-
} catch (err) {
|
|
153
|
-
console.warn('[snapshot] Capture failed:', err)
|
|
154
|
-
return {}
|
|
155
|
-
} finally {
|
|
156
|
-
capturingRef.current = false
|
|
157
|
-
}
|
|
158
|
-
}, [onUpdate, iframeRef, widgetId])
|
|
159
|
-
|
|
160
|
-
return { iframeReady, requestCapture }
|
|
161
|
-
}
|