@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
- }