@dfosco/storyboard-react 4.0.0-beta.22 → 4.0.0-beta.24
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.24",
|
|
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.24",
|
|
7
|
+
"@dfosco/tiny-canvas": "4.0.0-beta.24",
|
|
8
8
|
"@neodrag/react": "^2.3.1",
|
|
9
9
|
"glob": "^11.0.0",
|
|
10
10
|
"jsonc-parser": "^3.3.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useRef, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react'
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
|
-
import { buildPrototypeIndex } from '@dfosco/storyboard-core'
|
|
3
|
+
import { buildPrototypeIndex, getFlag } from '@dfosco/storyboard-core'
|
|
4
4
|
import WidgetWrapper from './WidgetWrapper.jsx'
|
|
5
5
|
import { readProp, prototypeEmbedSchema } from './widgetProps.js'
|
|
6
6
|
import { getEmbedChromeVars } from './embedTheme.js'
|
|
@@ -9,6 +9,10 @@ import { useIframeQueue } from './useViewportEntry.js'
|
|
|
9
9
|
import styles from './PrototypeEmbed.module.css'
|
|
10
10
|
import overlayStyles from './embedOverlay.module.css'
|
|
11
11
|
|
|
12
|
+
function devLog(...args) {
|
|
13
|
+
try { if (getFlag('dev-logs')) console.log('[canvas:prototype-embed]', ...args) } catch { /* */ }
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
function formatName(name) {
|
|
13
17
|
return name
|
|
14
18
|
.replace(/[-_]/g, ' ')
|
|
@@ -72,26 +76,40 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
72
76
|
|
|
73
77
|
// Sequential iframe queue — prevents stampede when many embeds lack snapshots.
|
|
74
78
|
// Widgets with snapshots skip the queue entirely; others load one at a time.
|
|
75
|
-
const { ready: queueReady, releaseSlot } = useIframeQueue(hasSnapshot || isExternal)
|
|
79
|
+
const { ready: queueReady, releaseSlot } = useIframeQueue(hasSnapshot || isExternal, widgetId)
|
|
76
80
|
const [preloadIframe, setPreloadIframe] = useState(hasSnapshot || isExternal)
|
|
77
81
|
const [iframeLoaded, setIframeLoaded] = useState(false)
|
|
78
82
|
const [showIframe, setShowIframe] = useState(hasSnapshot || isExternal)
|
|
79
83
|
const [showSpinner, setShowSpinner] = useState(false)
|
|
80
84
|
const capturingRef = useRef(false)
|
|
81
85
|
|
|
86
|
+
devLog(widgetId, { hasSnapshot, isExternal, queueReady, preloadIframe, showIframe, iframeLoaded, src })
|
|
87
|
+
|
|
82
88
|
// Start loading when the queue grants this widget a slot
|
|
83
89
|
useEffect(() => {
|
|
84
90
|
if (queueReady && !preloadIframe) {
|
|
91
|
+
devLog(widgetId, 'queue ready → loading iframe')
|
|
85
92
|
setPreloadIframe(true)
|
|
86
93
|
setShowIframe(true)
|
|
87
94
|
}
|
|
88
95
|
}, [queueReady, preloadIframe])
|
|
89
96
|
|
|
90
|
-
// Release the queue slot once the iframe has loaded
|
|
97
|
+
// Release the queue slot once the iframe has loaded or user clicked to interact
|
|
91
98
|
useEffect(() => {
|
|
92
|
-
if (iframeLoaded)
|
|
99
|
+
if (iframeLoaded) {
|
|
100
|
+
devLog(widgetId, 'iframe loaded')
|
|
101
|
+
releaseSlot()
|
|
102
|
+
}
|
|
93
103
|
}, [iframeLoaded, releaseSlot])
|
|
94
104
|
|
|
105
|
+
// Click-to-interact: immediately start iframe and release queue slot for others
|
|
106
|
+
const activateIframe = useCallback(() => {
|
|
107
|
+
devLog(widgetId, 'user activated → jumping queue')
|
|
108
|
+
setShowIframe(true)
|
|
109
|
+
setPreloadIframe(true)
|
|
110
|
+
releaseSlot()
|
|
111
|
+
}, [releaseSlot])
|
|
112
|
+
|
|
95
113
|
// Show spinner only after 500ms of loading
|
|
96
114
|
useEffect(() => {
|
|
97
115
|
if (showIframe && !iframeLoaded && hasSnapshot) {
|
|
@@ -553,8 +571,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
553
571
|
}}
|
|
554
572
|
onClick={(e) => {
|
|
555
573
|
if (e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return
|
|
556
|
-
|
|
557
|
-
setPreloadIframe(true)
|
|
574
|
+
activateIframe()
|
|
558
575
|
enterInteractive()
|
|
559
576
|
}}
|
|
560
577
|
role="button"
|
|
@@ -563,8 +580,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
|
|
|
563
580
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
564
581
|
e.preventDefault()
|
|
565
582
|
e.stopPropagation()
|
|
566
|
-
|
|
567
|
-
setPreloadIframe(true)
|
|
583
|
+
activateIframe()
|
|
568
584
|
enterInteractive()
|
|
569
585
|
}
|
|
570
586
|
}}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Props: { storyId, exportName, width, height }
|
|
13
13
|
*/
|
|
14
14
|
import { forwardRef, useImperativeHandle, useRef, useCallback, useState, useEffect, useMemo } from 'react'
|
|
15
|
-
import { getStoryData } from '@dfosco/storyboard-core'
|
|
15
|
+
import { getStoryData, getFlag } from '@dfosco/storyboard-core'
|
|
16
16
|
import { createInspectorHighlighter } from '@dfosco/storyboard-core/inspector/highlighter'
|
|
17
17
|
import WidgetWrapper from './WidgetWrapper.jsx'
|
|
18
18
|
import ResizeHandle from './ResizeHandle.jsx'
|
|
@@ -21,6 +21,10 @@ import { useIframeQueue } from './useViewportEntry.js'
|
|
|
21
21
|
import styles from './StoryWidget.module.css'
|
|
22
22
|
import overlayStyles from './embedOverlay.module.css'
|
|
23
23
|
|
|
24
|
+
function devLog(...args) {
|
|
25
|
+
try { if (getFlag('dev-logs')) console.log('[canvas:story-widget]', ...args) } catch { /* */ }
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
function resolveStoryUrl(storyId, exportName) {
|
|
25
29
|
const story = getStoryData(storyId)
|
|
26
30
|
if (!story?._route) return null
|
|
@@ -114,26 +118,40 @@ export default forwardRef(function StoryWidget({ id: widgetId, props, onUpdate,
|
|
|
114
118
|
const hasSnapshot = !!currentSnapshot
|
|
115
119
|
|
|
116
120
|
// Sequential iframe queue — prevents stampede when many embeds lack snapshots.
|
|
117
|
-
const { ready: queueReady, releaseSlot } = useIframeQueue(hasSnapshot)
|
|
121
|
+
const { ready: queueReady, releaseSlot } = useIframeQueue(hasSnapshot, widgetId)
|
|
118
122
|
const [preloadIframe, setPreloadIframe] = useState(hasSnapshot)
|
|
119
123
|
const [iframeLoaded, setIframeLoaded] = useState(false)
|
|
120
124
|
const [showIframe, setShowIframe] = useState(hasSnapshot)
|
|
121
125
|
const [showSpinner, setShowSpinner] = useState(false)
|
|
122
126
|
const capturingRef = useRef(false)
|
|
123
127
|
|
|
128
|
+
devLog(widgetId, { hasSnapshot, queueReady, preloadIframe, showIframe, iframeLoaded, storyId })
|
|
129
|
+
|
|
124
130
|
// Start loading when the queue grants this widget a slot
|
|
125
131
|
useEffect(() => {
|
|
126
132
|
if (queueReady && !preloadIframe) {
|
|
133
|
+
devLog(widgetId, 'queue ready → loading iframe')
|
|
127
134
|
setPreloadIframe(true)
|
|
128
135
|
setShowIframe(true)
|
|
129
136
|
}
|
|
130
137
|
}, [queueReady, preloadIframe])
|
|
131
138
|
|
|
132
|
-
// Release the queue slot once the iframe has loaded
|
|
139
|
+
// Release the queue slot once the iframe has loaded or user clicked to interact
|
|
133
140
|
useEffect(() => {
|
|
134
|
-
if (iframeLoaded)
|
|
141
|
+
if (iframeLoaded) {
|
|
142
|
+
devLog(widgetId, 'iframe loaded')
|
|
143
|
+
releaseSlot()
|
|
144
|
+
}
|
|
135
145
|
}, [iframeLoaded, releaseSlot])
|
|
136
146
|
|
|
147
|
+
// Click-to-interact: immediately start iframe and release queue slot for others
|
|
148
|
+
const activateIframe = useCallback(() => {
|
|
149
|
+
devLog(widgetId, 'user activated → jumping queue')
|
|
150
|
+
setShowIframe(true)
|
|
151
|
+
setPreloadIframe(true)
|
|
152
|
+
releaseSlot()
|
|
153
|
+
}, [releaseSlot, widgetId])
|
|
154
|
+
|
|
137
155
|
// Show spinner only after 500ms of loading
|
|
138
156
|
useEffect(() => {
|
|
139
157
|
if (showIframe && !iframeLoaded && hasSnapshot) {
|
|
@@ -452,8 +470,7 @@ export default forwardRef(function StoryWidget({ id: widgetId, props, onUpdate,
|
|
|
452
470
|
}}
|
|
453
471
|
onClick={(e) => {
|
|
454
472
|
if (e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return
|
|
455
|
-
|
|
456
|
-
setPreloadIframe(true)
|
|
473
|
+
activateIframe()
|
|
457
474
|
enterInteractive()
|
|
458
475
|
}}
|
|
459
476
|
role="button"
|
|
@@ -462,8 +479,7 @@ export default forwardRef(function StoryWidget({ id: widgetId, props, onUpdate,
|
|
|
462
479
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
463
480
|
e.preventDefault()
|
|
464
481
|
e.stopPropagation()
|
|
465
|
-
|
|
466
|
-
setPreloadIframe(true)
|
|
482
|
+
activateIframe()
|
|
467
483
|
enterInteractive()
|
|
468
484
|
}
|
|
469
485
|
}}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef } from 'react'
|
|
2
|
+
import { getFlag } from '@dfosco/storyboard-core'
|
|
3
|
+
|
|
4
|
+
function devLog(...args) {
|
|
5
|
+
try { if (getFlag('dev-logs')) console.log('[canvas:iframe-queue]', ...args) } catch { /* flag system not initialized */ }
|
|
6
|
+
}
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* Sequential iframe loading queue.
|
|
@@ -19,13 +24,15 @@ let _active = false
|
|
|
19
24
|
function processQueue() {
|
|
20
25
|
if (_active || _queue.length === 0) return
|
|
21
26
|
_active = true
|
|
22
|
-
const { resolve } = _queue.shift()
|
|
27
|
+
const { resolve, label } = _queue.shift()
|
|
28
|
+
devLog(`slot granted → ${label} (${_queue.length} queued)`)
|
|
23
29
|
|
|
24
30
|
let released = false
|
|
25
31
|
const release = () => {
|
|
26
32
|
if (released) return
|
|
27
33
|
released = true
|
|
28
34
|
_active = false
|
|
35
|
+
devLog(`slot released ← ${label}`)
|
|
29
36
|
processQueue()
|
|
30
37
|
}
|
|
31
38
|
|
|
@@ -34,9 +41,10 @@ function processQueue() {
|
|
|
34
41
|
resolve(release)
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
function requestSlot() {
|
|
44
|
+
function requestSlot(label = '?') {
|
|
38
45
|
return new Promise((resolve) => {
|
|
39
|
-
_queue.push({ resolve })
|
|
46
|
+
_queue.push({ resolve, label })
|
|
47
|
+
devLog(`queued ${label} (position ${_queue.length})`)
|
|
40
48
|
processQueue()
|
|
41
49
|
})
|
|
42
50
|
}
|
|
@@ -49,9 +57,10 @@ function requestSlot() {
|
|
|
49
57
|
* it's this widget's turn.
|
|
50
58
|
*
|
|
51
59
|
* @param {boolean} hasUsableSnapshot - whether the widget has a working snapshot
|
|
52
|
-
* @
|
|
60
|
+
* @param {string} [label] - debug label for dev logs
|
|
61
|
+
* @returns {{ ready: boolean, releaseSlot: Function }}
|
|
53
62
|
*/
|
|
54
|
-
export function useIframeQueue(hasUsableSnapshot) {
|
|
63
|
+
export function useIframeQueue(hasUsableSnapshot, label = '?') {
|
|
55
64
|
const [ready, setReady] = useState(hasUsableSnapshot)
|
|
56
65
|
const releaseRef = useRef(null)
|
|
57
66
|
|
|
@@ -59,7 +68,7 @@ export function useIframeQueue(hasUsableSnapshot) {
|
|
|
59
68
|
if (hasUsableSnapshot || ready) return
|
|
60
69
|
|
|
61
70
|
let cancelled = false
|
|
62
|
-
requestSlot().then((release) => {
|
|
71
|
+
requestSlot(label).then((release) => {
|
|
63
72
|
if (cancelled) {
|
|
64
73
|
release()
|
|
65
74
|
return
|