@dfosco/storyboard 0.5.0-beta.38 → 0.5.0-beta.40
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
|
@@ -194,19 +194,23 @@ export default function PageSelector({ currentName, pages: initialPages, isLocal
|
|
|
194
194
|
return
|
|
195
195
|
}
|
|
196
196
|
const route = result?.route
|
|
197
|
+
const newName = result?.name || oldName
|
|
197
198
|
if (route) {
|
|
199
|
+
// Optimistic UI: replace the renamed entry in our local list so the
|
|
200
|
+
// selector reflects the change immediately (don't wait for HMR).
|
|
201
|
+
setPages(prev => prev.map(p => p.name === oldName
|
|
202
|
+
? { ...p, name: newName, route, title: trimmed }
|
|
203
|
+
: p
|
|
204
|
+
))
|
|
205
|
+
try { sessionStorage.setItem('sb-open-page-selector', '1') } catch { /* ignore */ }
|
|
206
|
+
|
|
207
|
+
// Navigate to the new URL immediately. The user is currently on the
|
|
208
|
+
// OLD route, which no longer resolves on the server — refreshing
|
|
209
|
+
// there would 404. Do a hard navigate so the SPA reloads and picks
|
|
210
|
+
// up the renamed canvas from the fresh virtual module.
|
|
198
211
|
const base = (import.meta.env?.BASE_URL || '/').replace(/\/$/, '')
|
|
199
212
|
const targetUrl = base + route
|
|
200
|
-
|
|
201
|
-
if (import.meta.hot) {
|
|
202
|
-
const timer = setTimeout(() => { window.location.href = targetUrl }, 3000)
|
|
203
|
-
import.meta.hot.on('vite:beforeFullReload', () => {
|
|
204
|
-
clearTimeout(timer)
|
|
205
|
-
sessionStorage.setItem('sb-pending-navigate', targetUrl)
|
|
206
|
-
})
|
|
207
|
-
} else {
|
|
208
|
-
setTimeout(() => { window.location.href = targetUrl }, 1000)
|
|
209
|
-
}
|
|
213
|
+
window.location.href = targetUrl
|
|
210
214
|
}
|
|
211
215
|
} catch (err) {
|
|
212
216
|
console.error('Failed to rename page:', err)
|
|
@@ -259,15 +263,11 @@ export default function PageSelector({ currentName, pages: initialPages, isLocal
|
|
|
259
263
|
|
|
260
264
|
try { sessionStorage.setItem('sb-open-page-selector', '1') } catch { /* ignore */ }
|
|
261
265
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
})
|
|
268
|
-
} else {
|
|
269
|
-
setTimeout(() => { window.location.href = targetUrl }, 1000)
|
|
270
|
-
}
|
|
266
|
+
// Hard navigate immediately. With the route map now reading `canvases`
|
|
267
|
+
// live, an SPA navigation would also work for the new canvas — but
|
|
268
|
+
// some refs (e.g. `_jsxModule`) only get filled in by a fresh module
|
|
269
|
+
// build, so a hard reload is the safer default.
|
|
270
|
+
window.location.href = targetUrl
|
|
271
271
|
} catch (err) {
|
|
272
272
|
console.error('Failed to duplicate page:', err)
|
|
273
273
|
}
|
|
@@ -363,18 +363,9 @@ export default function PageSelector({ currentName, pages: initialPages, isLocal
|
|
|
363
363
|
// Stash a flag so the page selector opens automatically on the new page
|
|
364
364
|
try { sessionStorage.setItem('sb-open-page-selector', '1') } catch { /* ignore */ }
|
|
365
365
|
|
|
366
|
-
// Navigate
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
window.location.href = targetUrl
|
|
370
|
-
}, 3000)
|
|
371
|
-
import.meta.hot.on('vite:beforeFullReload', () => {
|
|
372
|
-
clearTimeout(timer)
|
|
373
|
-
sessionStorage.setItem('sb-pending-navigate', targetUrl)
|
|
374
|
-
})
|
|
375
|
-
} else {
|
|
376
|
-
setTimeout(() => { window.location.href = targetUrl }, 1000)
|
|
377
|
-
}
|
|
366
|
+
// Navigate immediately. The server has already written the file; the
|
|
367
|
+
// route map will be rebuilt on hard reload.
|
|
368
|
+
window.location.href = targetUrl
|
|
378
369
|
} catch (err) {
|
|
379
370
|
console.error('Failed to create canvas page:', err)
|
|
380
371
|
setCreating(false)
|
|
@@ -216,6 +216,7 @@ const PromptWidget = forwardRef(function PromptWidget({ id, props, onUpdate, res
|
|
|
216
216
|
e.stopPropagation()
|
|
217
217
|
}, [handleSubmit])
|
|
218
218
|
|
|
219
|
+
// eslint-disable-next-line react-hooks/preserve-manual-memoization
|
|
219
220
|
const handleReset = useCallback(() => {
|
|
220
221
|
setExecStatus('idle')
|
|
221
222
|
setExecError('')
|
|
@@ -293,7 +293,7 @@ export default forwardRef(function TerminalWidget({ id, props, onUpdate, multiSe
|
|
|
293
293
|
// letters). Especially visible on consumer installs where fonts come
|
|
294
294
|
// over the network instead of from cache.
|
|
295
295
|
if (typeof document !== 'undefined' && document.fonts?.ready) {
|
|
296
|
-
try { await document.fonts.ready } catch {}
|
|
296
|
+
try { await document.fonts.ready } catch { /* font load failures are non-fatal */ }
|
|
297
297
|
if (disposed) return
|
|
298
298
|
}
|
|
299
299
|
|
|
@@ -52,43 +52,53 @@ class SectionErrorBoundary extends Component {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
// Canvas route resolution and group lookup happen live below
|
|
56
|
+
// (see matchCanvasRoute / getCanvasGroupMap) so that HMR rename/duplicate
|
|
57
|
+
// take effect without a page reload.
|
|
58
|
+
|
|
59
|
+
// Build a map from group name → array of { name, route, title } for page selector.
|
|
60
|
+
// Read live from `canvases` (HMR mutates it in place) so rename/duplicate are
|
|
61
|
+
// reflected immediately. Sorted by pageOrder from .meta.json when available.
|
|
62
|
+
function getCanvasGroupMap() {
|
|
63
|
+
const map = new Map()
|
|
64
|
+
for (const [name, data] of Object.entries(canvases || {})) {
|
|
65
|
+
const route = (data?._route || `/canvas/${name}`).replace(/\/+$/, '')
|
|
66
|
+
const group = data?._group
|
|
67
|
+
if (!group) continue
|
|
68
|
+
if (!map.has(group)) map.set(group, [])
|
|
69
|
+
map.get(group).push({
|
|
66
70
|
name,
|
|
67
71
|
route,
|
|
68
72
|
title: data?.title || name.split('/').pop(),
|
|
69
73
|
_canvasMeta: data?._canvasMeta || null,
|
|
70
74
|
})
|
|
71
75
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
})
|
|
76
|
+
for (const [, pages] of map) {
|
|
77
|
+
const pageOrder = pages[0]?._canvasMeta?.pageOrder
|
|
78
|
+
if (Array.isArray(pageOrder)) {
|
|
79
|
+
const orderMap = new Map()
|
|
80
|
+
pageOrder.forEach((entry, idx) => {
|
|
81
|
+
if (typeof entry === 'string' && !entry.startsWith('sep-')) orderMap.set(entry, idx)
|
|
82
|
+
})
|
|
83
|
+
pages.sort((a, b) => {
|
|
84
|
+
const ai = orderMap.has(a.name) ? orderMap.get(a.name) : Infinity
|
|
85
|
+
const bi = orderMap.has(b.name) ? orderMap.get(b.name) : Infinity
|
|
86
|
+
return ai - bi
|
|
87
|
+
})
|
|
88
|
+
}
|
|
86
89
|
}
|
|
90
|
+
return map
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
function matchCanvasRoute(pathname) {
|
|
90
94
|
const normalized = stripBasePath(pathname)
|
|
91
|
-
|
|
95
|
+
// Iterate `canvases` live so HMR rename/duplicate are reflected without
|
|
96
|
+
// a page reload (virtual-module HMR mutates the object in place).
|
|
97
|
+
for (const [name, data] of Object.entries(canvases || {})) {
|
|
98
|
+
const route = (data?._route || `/canvas/${name}`).replace(/\/+$/, '')
|
|
99
|
+
if (route === normalized) return name
|
|
100
|
+
}
|
|
101
|
+
return null
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
/**
|
|
@@ -219,6 +229,17 @@ function StoryboardProviderInner({ flowName, sceneName, recordName, recordParam,
|
|
|
219
229
|
return () => document.removeEventListener('storyboard:story-index-changed', handler)
|
|
220
230
|
}, [])
|
|
221
231
|
|
|
232
|
+
// Same pattern for canvases: HMR mutates the in-memory `canvases` object
|
|
233
|
+
// when files are added/renamed/removed. Re-run canvas route matching and
|
|
234
|
+
// sibling-page derivation when that happens so the SPA reflects the new
|
|
235
|
+
// state without requiring a hard reload (which would 404 on old URLs).
|
|
236
|
+
const [canvasIndexKey, setCanvasIndexKey] = useState(0)
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
const handler = () => setCanvasIndexKey((k) => k + 1)
|
|
239
|
+
document.addEventListener('storyboard:canvas-index-changed', handler)
|
|
240
|
+
return () => document.removeEventListener('storyboard:canvas-index-changed', handler)
|
|
241
|
+
}, [])
|
|
242
|
+
|
|
222
243
|
// Story route detection — matches current URL against registered story routes
|
|
223
244
|
// storyIndexKey forces re-evaluation when HMR mutates the stories object in place
|
|
224
245
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -228,8 +249,10 @@ function StoryboardProviderInner({ flowName, sceneName, recordName, recordParam,
|
|
|
228
249
|
[location.pathname, storyName],
|
|
229
250
|
)
|
|
230
251
|
|
|
231
|
-
// Canvas route detection — matches current URL against registered canvas routes
|
|
232
|
-
|
|
252
|
+
// Canvas route detection — matches current URL against registered canvas routes.
|
|
253
|
+
// canvasIndexKey forces re-evaluation when HMR mutates the canvases object in place
|
|
254
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
255
|
+
const canvasId = useMemo(() => matchCanvasRoute(location.pathname), [location.pathname, canvasIndexKey])
|
|
233
256
|
const isMissingCanvasRoute = useMemo(
|
|
234
257
|
() => isCanvasPath(location.pathname) && !canvasId && !storyName,
|
|
235
258
|
[location.pathname, canvasId, storyName],
|
|
@@ -400,8 +423,11 @@ function StoryboardProviderInner({ flowName, sceneName, recordName, recordParam,
|
|
|
400
423
|
const group = canvasData?._group
|
|
401
424
|
// Include the current canvas as a sibling even if it's the only page in its group,
|
|
402
425
|
// so the PageSelector can render and allow adding new pages.
|
|
426
|
+
// canvasIndexKey ensures siblingPages are re-derived on HMR
|
|
427
|
+
// eslint-disable-next-line no-unused-vars
|
|
428
|
+
const _hmrTick = canvasIndexKey
|
|
403
429
|
const siblingPages = group
|
|
404
|
-
?
|
|
430
|
+
? getCanvasGroupMap().get(group) || []
|
|
405
431
|
: [{ name: canvasId, route: canvasData?._route || `/canvas/${canvasId}`, title: canvasData?.title || canvasId.split('/').pop() }]
|
|
406
432
|
const canvasMeta = canvasData?._canvasMeta || null
|
|
407
433
|
const canvasValue = {
|