@dfosco/storyboard-core 3.3.2 → 3.4.0
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/dist/storyboard-ui.css +9 -1
- package/dist/storyboard-ui.js +14659 -11413
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +1 -1
- package/scaffold/toolbar.config.json +2 -2
- package/src/CanvasCreateMenu.svelte +1 -1
- package/src/CanvasZoomControl.svelte +105 -0
- package/src/CommandMenu.svelte +87 -25
- package/src/CoreUIBar.svelte +350 -347
- package/src/CreateMenuButton.svelte +6 -2
- package/src/InspectorPanel.svelte +87 -37
- package/src/SidePanel.svelte +1 -1
- package/src/ThemeMenuButton.svelte +35 -3
- package/src/commandActions.js +14 -0
- package/src/core-ui-colors.css +30 -2
- package/src/devtools.js +7 -1
- package/src/index.js +10 -1
- package/src/inspector/fiberWalker.js +49 -6
- package/src/inspector/highlighter.js +143 -32
- package/src/lib/components/ui/button/button.svelte +1 -1
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
- package/src/lib/components/ui/trigger-button/trigger-button.svelte +31 -3
- package/src/modes.css +8 -0
- package/src/mountStoryboardCore.js +15 -1
- package/src/stores/themeStore.ts +66 -0
- package/src/svelte-plugin-ui/components/Viewfinder.svelte +16 -11
- package/src/toolRegistry.js +226 -0
- package/src/toolStateStore.js +180 -0
- package/src/toolStateStore.test.js +204 -0
- package/src/toolbarConfigStore.js +135 -0
- package/src/tools/handlers/canvasAddWidget.js +11 -0
- package/src/tools/handlers/canvasZoom.js +34 -0
- package/src/tools/handlers/comments.js +16 -0
- package/src/tools/handlers/create.js +39 -0
- package/src/tools/handlers/devtools.js +80 -0
- package/src/tools/handlers/docs.js +11 -0
- package/src/tools/handlers/featureFlags.js +21 -0
- package/src/tools/handlers/flows.js +59 -0
- package/src/tools/handlers/inspector.js +19 -0
- package/src/tools/handlers/theme.js +9 -0
- package/src/tools/registry.js +21 -0
- package/src/tools/surfaces/canvasToolbar.js +10 -0
- package/src/tools/surfaces/commandList.js +10 -0
- package/src/tools/surfaces/mainToolbar.js +11 -0
- package/src/tools/surfaces/registry.js +19 -0
- package/src/vite/server-plugin.js +36 -6
- package/toolbar.config.json +106 -48
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolbar Config Store — reactive toolbar configuration with layered overrides.
|
|
3
|
+
*
|
|
4
|
+
* Override priority (highest wins):
|
|
5
|
+
* core (toolbar.config.json) → custom (client repo) → prototype → user (future)
|
|
6
|
+
*
|
|
7
|
+
* Framework-agnostic (zero npm dependencies).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { deepMerge } from './loader.js'
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Internal state
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/** @type {object} Merged core + custom config (set once at startup) */
|
|
17
|
+
let _baseConfig = {}
|
|
18
|
+
|
|
19
|
+
/** @type {object|null} Active prototype toolbar overrides */
|
|
20
|
+
let _prototypeConfig = null
|
|
21
|
+
|
|
22
|
+
/** @type {object} Final merged config (base + prototype) */
|
|
23
|
+
let _mergedConfig = {}
|
|
24
|
+
|
|
25
|
+
/** @type {Set<Function>} */
|
|
26
|
+
const _listeners = new Set()
|
|
27
|
+
|
|
28
|
+
let _snapshotVersion = 0
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Initialization
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Set the base toolbar config (core defaults merged with client overrides).
|
|
36
|
+
* Called once at app startup by mountStoryboardCore.
|
|
37
|
+
*
|
|
38
|
+
* @param {object} config - Already-merged core + custom toolbar config
|
|
39
|
+
*/
|
|
40
|
+
export function initToolbarConfig(config) {
|
|
41
|
+
_baseConfig = config
|
|
42
|
+
_prototypeConfig = null
|
|
43
|
+
_recompute()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Prototype overrides
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Set toolbar overrides for the active prototype.
|
|
52
|
+
* Called on route change when entering a prototype that has a toolbar.config.json.
|
|
53
|
+
*
|
|
54
|
+
* @param {object|null} config - Prototype-level overrides, or null to clear
|
|
55
|
+
*/
|
|
56
|
+
export function setPrototypeToolbarConfig(config) {
|
|
57
|
+
_prototypeConfig = config || null
|
|
58
|
+
_recompute()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Clear prototype overrides (e.g. when navigating to viewfinder or a
|
|
63
|
+
* prototype without its own toolbar.config.json).
|
|
64
|
+
*/
|
|
65
|
+
export function clearPrototypeToolbarConfig() {
|
|
66
|
+
if (_prototypeConfig === null) return
|
|
67
|
+
_prototypeConfig = null
|
|
68
|
+
_recompute()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Access
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the current merged toolbar config.
|
|
77
|
+
*
|
|
78
|
+
* @returns {object}
|
|
79
|
+
*/
|
|
80
|
+
export function getToolbarConfig() {
|
|
81
|
+
return _mergedConfig
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Reactivity
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Subscribe to toolbar config changes. Compatible with Svelte stores.
|
|
90
|
+
*
|
|
91
|
+
* @param {Function} callback
|
|
92
|
+
* @returns {Function} Unsubscribe
|
|
93
|
+
*/
|
|
94
|
+
export function subscribeToToolbarConfig(callback) {
|
|
95
|
+
_listeners.add(callback)
|
|
96
|
+
callback(_mergedConfig)
|
|
97
|
+
return () => _listeners.delete(callback)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Snapshot for useSyncExternalStore.
|
|
102
|
+
*
|
|
103
|
+
* @returns {string}
|
|
104
|
+
*/
|
|
105
|
+
export function getToolbarConfigSnapshot() {
|
|
106
|
+
return String(_snapshotVersion)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Internal
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
function _recompute() {
|
|
114
|
+
_mergedConfig = _prototypeConfig
|
|
115
|
+
? deepMerge(_baseConfig, _prototypeConfig)
|
|
116
|
+
: _baseConfig
|
|
117
|
+
_snapshotVersion++
|
|
118
|
+
for (const cb of _listeners) {
|
|
119
|
+
try { cb(_mergedConfig) } catch (err) {
|
|
120
|
+
console.error('[storyboard] Error in toolbar config subscriber:', err)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Test helpers
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
export function _resetToolbarConfig() {
|
|
130
|
+
_baseConfig = {}
|
|
131
|
+
_prototypeConfig = null
|
|
132
|
+
_mergedConfig = {}
|
|
133
|
+
_listeners.clear()
|
|
134
|
+
_snapshotVersion = 0
|
|
135
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas add-widget tool module — dropdown menu for adding widgets to canvas.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the CanvasCreateMenu component. Only active on canvas pages.
|
|
5
|
+
*/
|
|
6
|
+
export const id = 'canvas-add-widget'
|
|
7
|
+
|
|
8
|
+
export async function component() {
|
|
9
|
+
const mod = await import('../../CanvasCreateMenu.svelte')
|
|
10
|
+
return mod.default
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas zoom tool module — zoom in/out/reset controls for canvas pages.
|
|
3
|
+
*
|
|
4
|
+
* Provides zoom actions via custom events (Svelte↔React bridge).
|
|
5
|
+
* Uses the unique "zoom-control" render type.
|
|
6
|
+
*/
|
|
7
|
+
export const id = 'canvas-zoom'
|
|
8
|
+
|
|
9
|
+
const ZOOM_STEP = 10
|
|
10
|
+
const ZOOM_MIN = 25
|
|
11
|
+
const ZOOM_MAX = 200
|
|
12
|
+
|
|
13
|
+
export async function handler() {
|
|
14
|
+
return {
|
|
15
|
+
zoomIn(currentZoom) {
|
|
16
|
+
const next = Math.min(ZOOM_MAX, currentZoom + ZOOM_STEP)
|
|
17
|
+
document.dispatchEvent(new CustomEvent('storyboard:canvas:set-zoom', { detail: { zoom: next } }))
|
|
18
|
+
},
|
|
19
|
+
zoomOut(currentZoom) {
|
|
20
|
+
const next = Math.max(ZOOM_MIN, currentZoom - ZOOM_STEP)
|
|
21
|
+
document.dispatchEvent(new CustomEvent('storyboard:canvas:set-zoom', { detail: { zoom: next } }))
|
|
22
|
+
},
|
|
23
|
+
zoomReset() {
|
|
24
|
+
document.dispatchEvent(new CustomEvent('storyboard:canvas:set-zoom', { detail: { zoom: 100 } }))
|
|
25
|
+
},
|
|
26
|
+
ZOOM_MIN,
|
|
27
|
+
ZOOM_MAX,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function component() {
|
|
32
|
+
const mod = await import('../../CanvasZoomControl.svelte')
|
|
33
|
+
return mod.default
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comments tool module — comment pins and annotation system.
|
|
3
|
+
*
|
|
4
|
+
* Guard: only mounts if comments are enabled in storyboard.config.json.
|
|
5
|
+
*/
|
|
6
|
+
export const id = 'comments'
|
|
7
|
+
|
|
8
|
+
export async function guard() {
|
|
9
|
+
const { isCommentsEnabled } = await import('../../comments/config.js')
|
|
10
|
+
return isCommentsEnabled()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function component() {
|
|
14
|
+
const mod = await import('../../CommentsMenuButton.svelte')
|
|
15
|
+
return mod.default
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create tool module — workshop prototype/flow/canvas creation.
|
|
3
|
+
*
|
|
4
|
+
* Loads the workshop feature registry and CreateMenuButton component.
|
|
5
|
+
* Guard: only mounts if at least one create feature has an overlay.
|
|
6
|
+
*/
|
|
7
|
+
export const id = 'create'
|
|
8
|
+
|
|
9
|
+
export async function setup(ctx) {
|
|
10
|
+
const { config } = ctx
|
|
11
|
+
const { features } = await import('../../workshop/features/registry.js')
|
|
12
|
+
|
|
13
|
+
const createActions = Array.isArray(config.actions) ? config.actions : []
|
|
14
|
+
const createFeatures = createActions
|
|
15
|
+
.filter(a => a.feature)
|
|
16
|
+
.map(a => {
|
|
17
|
+
const feat = features[a.feature]
|
|
18
|
+
if (!feat || !feat.overlayId || !feat.overlay) return null
|
|
19
|
+
return {
|
|
20
|
+
name: feat.name,
|
|
21
|
+
label: a.label || feat.label,
|
|
22
|
+
overlayId: feat.overlayId,
|
|
23
|
+
overlay: feat.overlay,
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
|
|
28
|
+
return { features: createFeatures }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function guard(ctx) {
|
|
32
|
+
const result = await setup(ctx)
|
|
33
|
+
return result.features.length > 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function component() {
|
|
37
|
+
const mod = await import('../../CreateMenuButton.svelte')
|
|
38
|
+
return mod.default
|
|
39
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devtools tool module — developer utilities submenu.
|
|
3
|
+
*
|
|
4
|
+
* Renders as a submenu in the command menu with:
|
|
5
|
+
* - Show flow info
|
|
6
|
+
* - Reset all params
|
|
7
|
+
* - Hide mode toggle
|
|
8
|
+
* - Logout (when authenticated)
|
|
9
|
+
*/
|
|
10
|
+
export const id = 'devtools'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} ctx
|
|
14
|
+
* @param {Function} ctx.showFlowInfoDialog - callback to open flow info dialog
|
|
15
|
+
*/
|
|
16
|
+
export async function handler(ctx) {
|
|
17
|
+
let loader = null
|
|
18
|
+
let hm = null
|
|
19
|
+
let commentsAuth = null
|
|
20
|
+
try { loader = await import('../../loader.js') } catch { /* optional */ }
|
|
21
|
+
try { hm = await import('../../hideMode.js') } catch { /* optional */ }
|
|
22
|
+
try { commentsAuth = await import('../../comments/auth.js') } catch { /* optional */ }
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
getChildren: () => {
|
|
26
|
+
const children = []
|
|
27
|
+
if (loader) {
|
|
28
|
+
children.push({
|
|
29
|
+
id: 'core/show-flow-info',
|
|
30
|
+
label: 'Show flow info',
|
|
31
|
+
type: 'default',
|
|
32
|
+
execute: () => {
|
|
33
|
+
const p = new URLSearchParams(window.location.search)
|
|
34
|
+
const name = p.get('flow') || p.get('scene') || 'default'
|
|
35
|
+
try {
|
|
36
|
+
const data = loader.loadFlow(name)
|
|
37
|
+
if (ctx.showFlowInfoDialog) {
|
|
38
|
+
ctx.showFlowInfoDialog(name, JSON.stringify(data, null, 2), null)
|
|
39
|
+
}
|
|
40
|
+
} catch (e) {
|
|
41
|
+
if (ctx.showFlowInfoDialog) {
|
|
42
|
+
ctx.showFlowInfoDialog(name, '', e.message)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
children.push({
|
|
49
|
+
id: 'core/reset-params',
|
|
50
|
+
label: 'Reset all params',
|
|
51
|
+
type: 'default',
|
|
52
|
+
execute: () => { window.location.hash = '' },
|
|
53
|
+
})
|
|
54
|
+
if (hm) {
|
|
55
|
+
children.push({
|
|
56
|
+
id: 'core/hide-mode',
|
|
57
|
+
label: 'Hide mode',
|
|
58
|
+
type: 'toggle',
|
|
59
|
+
active: hm.isHideMode(),
|
|
60
|
+
execute: () => {
|
|
61
|
+
if (hm.isHideMode()) hm.deactivateHideMode()
|
|
62
|
+
else hm.activateHideMode()
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
if (commentsAuth?.isAuthenticated()) {
|
|
67
|
+
children.push({
|
|
68
|
+
id: 'core/logout',
|
|
69
|
+
label: 'Logout (remove token)',
|
|
70
|
+
type: 'default',
|
|
71
|
+
execute: () => {
|
|
72
|
+
commentsAuth.clearToken()
|
|
73
|
+
console.log('[storyboard] Token removed')
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
return children
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs tool module — documentation sidepanel toggle.
|
|
3
|
+
*/
|
|
4
|
+
export const id = 'docs'
|
|
5
|
+
|
|
6
|
+
// No component needed — sidepanel tools use generic TriggerButton
|
|
7
|
+
|
|
8
|
+
export async function handler() {
|
|
9
|
+
const { togglePanel } = await import('../../stores/sidePanelStore.js')
|
|
10
|
+
return () => togglePanel('docs')
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Flags tool module — toggle feature flags submenu.
|
|
3
|
+
*
|
|
4
|
+
* Renders as a submenu in the command menu listing all declared flags.
|
|
5
|
+
*/
|
|
6
|
+
export const id = 'feature-flags'
|
|
7
|
+
|
|
8
|
+
export async function handler() {
|
|
9
|
+
const ff = await import('../../featureFlags.js')
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
getChildren: () =>
|
|
13
|
+
ff.getFlagKeys().map(key => ({
|
|
14
|
+
id: `flags/${key}`,
|
|
15
|
+
label: key,
|
|
16
|
+
type: 'toggle',
|
|
17
|
+
active: ff.getFlag(key),
|
|
18
|
+
execute: () => ff.toggleFlag(key),
|
|
19
|
+
})),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flows tool module — flow switcher action menu.
|
|
3
|
+
*
|
|
4
|
+
* Registers a command action handler that lists available flows
|
|
5
|
+
* for the current prototype and lets users switch between them.
|
|
6
|
+
*/
|
|
7
|
+
export const id = 'flows'
|
|
8
|
+
|
|
9
|
+
export async function handler(ctx) {
|
|
10
|
+
const loader = await import('../../loader.js')
|
|
11
|
+
const vf = await import('../../viewfinder.js')
|
|
12
|
+
const { basePath = '/' } = ctx
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
getChildren: () => {
|
|
16
|
+
let path = window.location.pathname
|
|
17
|
+
const base = basePath.replace(/\/+$/, '')
|
|
18
|
+
if (base && path.startsWith(base)) path = path.slice(base.length)
|
|
19
|
+
path = path.replace(/\/+$/, '') || '/'
|
|
20
|
+
const segments = path.split('/').filter(Boolean)
|
|
21
|
+
const proto = segments[0] || null
|
|
22
|
+
if (!proto) return []
|
|
23
|
+
|
|
24
|
+
const params = new URLSearchParams(window.location.search)
|
|
25
|
+
const explicit = params.get('flow') || params.get('scene')
|
|
26
|
+
let active
|
|
27
|
+
if (explicit) {
|
|
28
|
+
active = loader.resolveFlowName(proto, explicit)
|
|
29
|
+
} else {
|
|
30
|
+
const pageFlow = path === '/' ? 'index' : (path.split('/').pop() || 'index')
|
|
31
|
+
const scoped = loader.resolveFlowName(proto, pageFlow)
|
|
32
|
+
if (loader.flowExists(scoped)) active = scoped
|
|
33
|
+
else {
|
|
34
|
+
const protoFlow = loader.resolveFlowName(proto, proto)
|
|
35
|
+
active = loader.flowExists(protoFlow) ? protoFlow : 'default'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const flows = loader.getFlowsForPrototype(proto)
|
|
40
|
+
if (flows.length <= 1) return []
|
|
41
|
+
|
|
42
|
+
return flows.map(f => {
|
|
43
|
+
const meta = vf.getFlowMeta(f.key)
|
|
44
|
+
return {
|
|
45
|
+
id: f.key,
|
|
46
|
+
label: meta?.title || f.name,
|
|
47
|
+
type: 'radio',
|
|
48
|
+
active: f.key === active,
|
|
49
|
+
execute: () => { window.location.href = vf.resolveFlowRoute(f.key) },
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function component() {
|
|
57
|
+
const mod = await import('../../ActionMenuButton.svelte')
|
|
58
|
+
return mod.default
|
|
59
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inspector tool module — component inspector sidepanel.
|
|
3
|
+
*
|
|
4
|
+
* Auto-opens the panel if ?inspect= is in the URL.
|
|
5
|
+
*/
|
|
6
|
+
export const id = 'inspector'
|
|
7
|
+
|
|
8
|
+
export async function setup() {
|
|
9
|
+
const { openPanel } = await import('../../stores/sidePanelStore.js')
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const inspectParam = new URL(window.location.href).searchParams.get('inspect')
|
|
13
|
+
if (inspectParam) {
|
|
14
|
+
openPanel('inspector')
|
|
15
|
+
}
|
|
16
|
+
} catch { /* ignore */ }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// No component needed — sidepanel tools use generic TriggerButton
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool module registry — maps tool IDs to lazy-loaded handler modules.
|
|
3
|
+
*
|
|
4
|
+
* Each handler module exports: { id, component?, handler?, setup?, guard? }
|
|
5
|
+
* All imports are dynamic to enable code splitting.
|
|
6
|
+
*/
|
|
7
|
+
export const coreHandlers = {
|
|
8
|
+
create: () => import('./handlers/create.js'),
|
|
9
|
+
theme: () => import('./handlers/theme.js'),
|
|
10
|
+
comments: () => import('./handlers/comments.js'),
|
|
11
|
+
flows: () => import('./handlers/flows.js'),
|
|
12
|
+
docs: () => import('./handlers/docs.js'),
|
|
13
|
+
inspector: () => import('./handlers/inspector.js'),
|
|
14
|
+
devtools: () => import('./handlers/devtools.js'),
|
|
15
|
+
'feature-flags': () => import('./handlers/featureFlags.js'),
|
|
16
|
+
'canvas-add-widget': () => import('./handlers/canvasAddWidget.js'),
|
|
17
|
+
'canvas-zoom': () => import('./handlers/canvasZoom.js'),
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Keep legacy export name for backward compatibility
|
|
21
|
+
export const toolModules = coreHandlers
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas Toolbar surface — floating bar at bottom-left on canvas pages.
|
|
3
|
+
*
|
|
4
|
+
* Only visible when a canvas page is active. Supports menus and
|
|
5
|
+
* custom render types like zoom-control.
|
|
6
|
+
*/
|
|
7
|
+
export const id = 'canvas-toolbar'
|
|
8
|
+
export const label = 'Canvas Toolbar'
|
|
9
|
+
export const position = 'bottom-left'
|
|
10
|
+
export const renderTypes = ['menu', 'zoom-control']
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command List surface — items rendered inside the ⌘K command menu.
|
|
3
|
+
*
|
|
4
|
+
* Tools here appear as menu items, submenus, or links within
|
|
5
|
+
* the command palette. They don't render as standalone buttons.
|
|
6
|
+
*/
|
|
7
|
+
export const id = 'command-list'
|
|
8
|
+
export const label = 'Command Menu'
|
|
9
|
+
export const position = 'overlay'
|
|
10
|
+
export const renderTypes = ['link', 'submenu']
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Toolbar surface — the primary floating bar at bottom-right.
|
|
3
|
+
*
|
|
4
|
+
* Supports all standard render types. Tools appear in reverse JSON order
|
|
5
|
+
* (first in config = leftmost in toolbar). The command menu button is
|
|
6
|
+
* always the rightmost item and is not a tool.
|
|
7
|
+
*/
|
|
8
|
+
export const id = 'main-toolbar'
|
|
9
|
+
export const label = 'Main Toolbar'
|
|
10
|
+
export const position = 'bottom-right'
|
|
11
|
+
export const renderTypes = ['button', 'menu', 'sidepanel', 'separator']
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface registry — exports all available rendering surfaces.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces define WHERE a tool renders. Each surface has a position,
|
|
5
|
+
* supported render types, and rendering logic in CoreUIBar.
|
|
6
|
+
*
|
|
7
|
+
* To add a new surface:
|
|
8
|
+
* 1. Create a definition file in this directory
|
|
9
|
+
* 2. Export it from this registry
|
|
10
|
+
* 3. Add rendering logic in CoreUIBar.svelte
|
|
11
|
+
*/
|
|
12
|
+
export { id as mainToolbar } from './mainToolbar.js'
|
|
13
|
+
export { id as canvasToolbar } from './canvasToolbar.js'
|
|
14
|
+
export { id as commandList } from './commandList.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* All surface IDs for validation.
|
|
18
|
+
*/
|
|
19
|
+
export const SURFACE_IDS = ['main-toolbar', 'canvas-toolbar', 'command-list']
|
|
@@ -64,6 +64,7 @@ export default function storyboardServer() {
|
|
|
64
64
|
let root = ''
|
|
65
65
|
let base = '/'
|
|
66
66
|
let config = {}
|
|
67
|
+
let isDev = false
|
|
67
68
|
|
|
68
69
|
// Route handler registry — plugins register here during setup
|
|
69
70
|
const routeHandlers = new Map()
|
|
@@ -76,6 +77,7 @@ export default function storyboardServer() {
|
|
|
76
77
|
root = viteConfig.root
|
|
77
78
|
base = viteConfig.base || '/'
|
|
78
79
|
config = readConfig(root)
|
|
80
|
+
isDev = viteConfig.command === 'serve'
|
|
79
81
|
},
|
|
80
82
|
|
|
81
83
|
configureServer(server) {
|
|
@@ -178,12 +180,14 @@ export default function storyboardServer() {
|
|
|
178
180
|
transformIndexHtml() {
|
|
179
181
|
const tags = []
|
|
180
182
|
|
|
181
|
-
// Inject local dev flag
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
// Inject local dev flag only during dev server (not production builds)
|
|
184
|
+
if (isDev) {
|
|
185
|
+
tags.push({
|
|
186
|
+
tag: 'script',
|
|
187
|
+
children: 'window.__SB_LOCAL_DEV__=true',
|
|
188
|
+
injectTo: 'head',
|
|
189
|
+
})
|
|
190
|
+
}
|
|
187
191
|
|
|
188
192
|
// Inject base path so the inspector UI can resolve static assets
|
|
189
193
|
// (e.g. inspector.json) when deployed under a subpath
|
|
@@ -262,6 +266,32 @@ export default function storyboardServer() {
|
|
|
262
266
|
}),
|
|
263
267
|
})
|
|
264
268
|
|
|
269
|
+
// Emit README as static JSON so the docs panel works in deployed builds.
|
|
270
|
+
// Dev server serves this dynamically; production needs the static file.
|
|
271
|
+
let readmeContent = null
|
|
272
|
+
for (const candidate of ['README.md', 'readme.md', 'Readme.md']) {
|
|
273
|
+
try {
|
|
274
|
+
readmeContent = await fs.promises.readFile(path.join(root, candidate), 'utf-8')
|
|
275
|
+
break
|
|
276
|
+
} catch { /* try next */ }
|
|
277
|
+
}
|
|
278
|
+
if (readmeContent) {
|
|
279
|
+
this.emitFile({
|
|
280
|
+
type: 'asset',
|
|
281
|
+
fileName: '_storyboard/docs/readme',
|
|
282
|
+
source: JSON.stringify({ content: readmeContent, path: 'README.md' }),
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Emit repo info so the docs panel GitHub link works in deployed builds.
|
|
287
|
+
if (repo) {
|
|
288
|
+
this.emitFile({
|
|
289
|
+
type: 'asset',
|
|
290
|
+
fileName: '_storyboard/docs/repo',
|
|
291
|
+
source: JSON.stringify(repo),
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
|
|
265
295
|
// GitHub Pages uses Jekyll which ignores _-prefixed directories.
|
|
266
296
|
// Emit .nojekyll to ensure _storyboard/ is served.
|
|
267
297
|
this.emitFile({
|