@dfosco/storyboard-core 3.0.0 → 3.1.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/core-ui.config.json +10 -2
- package/package.json +3 -1
- package/src/CanvasCreateMenu.svelte +65 -0
- package/src/CoreUIBar.svelte +197 -33
- package/src/InspectorPanel.svelte +58 -21
- package/src/SidePanel.svelte +185 -32
- package/src/bodyClasses.js +7 -3
- package/src/bodyClasses.test.js +28 -0
- package/src/canvas/server.js +14 -13
- package/src/comments/auth.js +74 -3
- package/src/comments/auth.test.js +108 -1
- package/src/comments/index.js +0 -9
- package/src/comments/ui/CommentWindow.svelte +14 -8
- package/src/comments/ui/Composer.svelte +3 -1
- package/src/comments/ui/index.js +15 -0
- package/src/inspector/highlighter.js +27 -0
- package/src/inspector/mouseMode.js +25 -2
- package/src/lib/components/ui/tooltip/tooltip-trigger.svelte +1 -0
- package/src/sidepanel.css +47 -14
- package/src/svelte-plugin-ui/components/Icon.svelte +5 -0
- package/src/svelte-plugin-ui/components/Viewfinder.svelte +50 -1
- package/src/viewfinder.js +5 -0
- package/src/vite/docs-handler.js +1 -1
- package/src/vite/server-plugin.js +73 -2
- package/src/workshop/features/createCanvas/CreateCanvasForm.svelte +13 -11
- package/src/workshop/features/createFlow/CreateFlowForm.svelte +1 -0
- package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +98 -56
- package/src/workshop/features/createPrototype/server.js +76 -38
- package/src/workshop/features/registry.js +2 -0
- package/src/workshop/ui/WorkshopPanel.svelte +1 -1
package/core-ui.config.json
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"trigger": "command",
|
|
17
17
|
"icon": "iconoir/key-command",
|
|
18
18
|
"meta": { "strokeWeight": 2 },
|
|
19
|
+
"default": true,
|
|
19
20
|
"modes": ["*"],
|
|
20
21
|
"actions": [
|
|
21
22
|
{ "type": "header", "label": "Command Menu" },
|
|
@@ -38,9 +39,10 @@
|
|
|
38
39
|
{ "type": "header", "label": "Create" },
|
|
39
40
|
{ "id": "workshop/create-prototype", "label": "New prototype", "type": "default", "modes": ["*"], "feature": "createPrototype" },
|
|
40
41
|
{ "id": "workshop/create-flow", "label": "New flow", "type": "default", "modes": ["*"], "feature": "createFlow" },
|
|
42
|
+
{ "id": "workshop/create-canvas", "label": "New canvas", "type": "default", "modes": ["*"], "feature": "createCanvas" },
|
|
41
43
|
{ "type": "footer", "label": "Supported in local development" }
|
|
42
44
|
]
|
|
43
|
-
},
|
|
45
|
+
},
|
|
44
46
|
"flows": {
|
|
45
47
|
"label": "Flows",
|
|
46
48
|
"ariaLabel": "Switch flow",
|
|
@@ -64,10 +66,16 @@
|
|
|
64
66
|
"inspector": {
|
|
65
67
|
"ariaLabel": "Inspect components",
|
|
66
68
|
"icon": "iconoir/square-dashed",
|
|
67
|
-
"excludeRoutes": ["^/$", "/viewfinder"],
|
|
69
|
+
"excludeRoutes": ["^/$", "/viewfinder", "/canvas/"],
|
|
68
70
|
"meta": { "strokeWeight": 2, "scale": 1.1 },
|
|
69
71
|
"modes": ["*"],
|
|
70
72
|
"sidepanel": "inspector"
|
|
71
73
|
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
"canvasToolbar": {
|
|
77
|
+
"icon": "iconoir/grid-plus",
|
|
78
|
+
"meta": { "strokeWeight": 2, "scale": 1.2 },
|
|
79
|
+
"menuWidth": "220px"
|
|
72
80
|
}
|
|
73
81
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dfosco/storyboard-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"exports": {
|
|
20
20
|
".": "./src/index.js",
|
|
21
21
|
"./core-ui.config.json": "./core-ui.config.json",
|
|
22
|
+
"./canvas/materializer": "./src/canvas/materializer.js",
|
|
22
23
|
"./vite/server": "./src/vite/server-plugin.js",
|
|
24
|
+
"./comments/svelte": "./src/comments/ui/index.js",
|
|
23
25
|
"./comments": "./src/comments/index.js",
|
|
24
26
|
"./comments/ui/comments.css": "./src/comments/ui/comment-layout.css",
|
|
25
27
|
"./comments/ui/comment-layout.css": "./src/comments/ui/comment-layout.css",
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CanvasCreateMenu — CoreUIBar dropdown for adding widgets to the active canvas.
|
|
3
|
+
Dispatches custom events to bridge Svelte → React.
|
|
4
|
+
Only visible when a canvas page is active.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { TriggerButton } from '$lib/components/ui/trigger-button/index.js'
|
|
9
|
+
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js'
|
|
10
|
+
import Icon from './svelte-plugin-ui/components/Icon.svelte'
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
config?: any
|
|
14
|
+
canvasName?: string
|
|
15
|
+
tabindex?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { config = {}, canvasName = '', tabindex }: Props = $props()
|
|
19
|
+
|
|
20
|
+
const widgetTypes = [
|
|
21
|
+
{ type: 'sticky-note', label: 'Sticky Note' },
|
|
22
|
+
{ type: 'markdown', label: 'Markdown' },
|
|
23
|
+
{ type: 'prototype', label: 'Prototype' },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
let menuOpen = $state(false)
|
|
27
|
+
|
|
28
|
+
function addWidget(type: string) {
|
|
29
|
+
document.dispatchEvent(new CustomEvent('storyboard:canvas:add-widget', {
|
|
30
|
+
detail: { type, canvasName }
|
|
31
|
+
}))
|
|
32
|
+
menuOpen = false
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<DropdownMenu.Root bind:open={menuOpen}>
|
|
37
|
+
<DropdownMenu.Trigger>
|
|
38
|
+
{#snippet child({ props })}
|
|
39
|
+
<TriggerButton
|
|
40
|
+
active={menuOpen}
|
|
41
|
+
size="icon-xl"
|
|
42
|
+
aria-label={config.ariaLabel || 'Add widget'}
|
|
43
|
+
{tabindex}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
{#if config.icon}
|
|
47
|
+
<Icon name={config.icon} size={16} {...(config.meta || {})} />
|
|
48
|
+
{:else}
|
|
49
|
+
+
|
|
50
|
+
{/if}
|
|
51
|
+
</TriggerButton>
|
|
52
|
+
{/snippet}
|
|
53
|
+
</DropdownMenu.Trigger>
|
|
54
|
+
|
|
55
|
+
<DropdownMenu.Content side="top" align="start" sideOffset={16} class="min-w-[180px]" style={config.menuWidth ? `width: ${config.menuWidth}` : ''}>
|
|
56
|
+
<DropdownMenu.Label>Add to canvas</DropdownMenu.Label>
|
|
57
|
+
{#each widgetTypes as wt (wt.type)}
|
|
58
|
+
<DropdownMenu.Item onclick={() => addWidget(wt.type)}>
|
|
59
|
+
{wt.label}
|
|
60
|
+
</DropdownMenu.Item>
|
|
61
|
+
{/each}
|
|
62
|
+
<DropdownMenu.Separator />
|
|
63
|
+
<div class="px-2 py-1.5 text-xs text-muted-foreground">Supported in local development</div>
|
|
64
|
+
</DropdownMenu.Content>
|
|
65
|
+
</DropdownMenu.Root>
|
package/src/CoreUIBar.svelte
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
let { basePath = '/' }: Props = $props()
|
|
27
27
|
|
|
28
28
|
let visible = $state(true)
|
|
29
|
+
// Hide the entire toolbar when loaded inside a prototype embed iframe
|
|
30
|
+
const isEmbed = typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('_sb_embed')
|
|
29
31
|
let commandMenuOpen = $state(false)
|
|
30
32
|
let ActionMenuButton: any = $state(null)
|
|
31
33
|
let navVersion = $state(0)
|
|
@@ -38,6 +40,15 @@
|
|
|
38
40
|
let commentsEnabled = $state(false)
|
|
39
41
|
let SidePanel: any = $state(null)
|
|
40
42
|
let toolbarEl: HTMLElement | null = $state(null)
|
|
43
|
+
let CanvasCreateMenu: any = $state(null)
|
|
44
|
+
let canvasActive = $state(false)
|
|
45
|
+
let activeCanvasName = $state('')
|
|
46
|
+
let canvasZoom = $state(100)
|
|
47
|
+
const canvasToolbarConfig = (coreUIConfig as any).canvasToolbar || {}
|
|
48
|
+
|
|
49
|
+
const ZOOM_STEP = 10
|
|
50
|
+
const ZOOM_MIN = 25
|
|
51
|
+
const ZOOM_MAX = 200
|
|
41
52
|
|
|
42
53
|
// Roving tabindex: only one button in the toolbar is tabbable at a time
|
|
43
54
|
let activeToolbarIndex = $state(-1)
|
|
@@ -150,7 +161,7 @@
|
|
|
150
161
|
visible = !visible
|
|
151
162
|
document.documentElement.classList.toggle('storyboard-chrome-hidden', !visible)
|
|
152
163
|
}
|
|
153
|
-
// Configurable shortcut to open the command menu
|
|
164
|
+
// Configurable shortcut to open the command menu (works even when hidden)
|
|
154
165
|
if (openKey && e.key === openKey && (e.metaKey || e.ctrlKey)) {
|
|
155
166
|
e.preventDefault()
|
|
156
167
|
commandMenuOpen = !commandMenuOpen
|
|
@@ -375,6 +386,17 @@
|
|
|
375
386
|
SidePanel = mod.default
|
|
376
387
|
}
|
|
377
388
|
} catch {}
|
|
389
|
+
|
|
390
|
+
// Load canvas create menu
|
|
391
|
+
try {
|
|
392
|
+
const mod = await import('./CanvasCreateMenu.svelte')
|
|
393
|
+
CanvasCreateMenu = mod.default
|
|
394
|
+
} catch {}
|
|
395
|
+
|
|
396
|
+
// Listen for canvas mount/unmount events (React↔Svelte bridge)
|
|
397
|
+
document.addEventListener('storyboard:canvas:mounted', handleCanvasMounted)
|
|
398
|
+
document.addEventListener('storyboard:canvas:unmounted', handleCanvasUnmounted)
|
|
399
|
+
document.addEventListener('storyboard:canvas:zoom-changed', handleZoomChanged)
|
|
378
400
|
})
|
|
379
401
|
|
|
380
402
|
onDestroy(() => {
|
|
@@ -382,8 +404,42 @@
|
|
|
382
404
|
if (bumpNav) window.removeEventListener('popstate', bumpNav)
|
|
383
405
|
if (origPushState) history.pushState = origPushState
|
|
384
406
|
if (origReplaceState) history.replaceState = origReplaceState
|
|
407
|
+
document.removeEventListener('storyboard:canvas:mounted', handleCanvasMounted)
|
|
408
|
+
document.removeEventListener('storyboard:canvas:unmounted', handleCanvasUnmounted)
|
|
409
|
+
document.removeEventListener('storyboard:canvas:zoom-changed', handleZoomChanged)
|
|
385
410
|
})
|
|
386
411
|
|
|
412
|
+
function handleCanvasMounted(e: Event) {
|
|
413
|
+
canvasActive = true
|
|
414
|
+
const detail = (e as CustomEvent).detail
|
|
415
|
+
activeCanvasName = detail?.name || ''
|
|
416
|
+
canvasZoom = detail?.zoom ?? 100
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function handleCanvasUnmounted() {
|
|
420
|
+
canvasActive = false
|
|
421
|
+
activeCanvasName = ''
|
|
422
|
+
canvasZoom = 100
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function handleZoomChanged(e: Event) {
|
|
426
|
+
canvasZoom = (e as CustomEvent).detail?.zoom ?? canvasZoom
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function canvasZoomIn() {
|
|
430
|
+
const next = Math.min(ZOOM_MAX, canvasZoom + ZOOM_STEP)
|
|
431
|
+
document.dispatchEvent(new CustomEvent('storyboard:canvas:set-zoom', { detail: { zoom: next } }))
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function canvasZoomOut() {
|
|
435
|
+
const next = Math.max(ZOOM_MIN, canvasZoom - ZOOM_STEP)
|
|
436
|
+
document.dispatchEvent(new CustomEvent('storyboard:canvas:set-zoom', { detail: { zoom: next } }))
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function canvasZoomReset() {
|
|
440
|
+
document.dispatchEvent(new CustomEvent('storyboard:canvas:set-zoom', { detail: { zoom: 100 } }))
|
|
441
|
+
}
|
|
442
|
+
|
|
387
443
|
// Flow info dialog state — driven by core/show-flow-info action
|
|
388
444
|
let flowDialogOpen = $state(false)
|
|
389
445
|
let flowName = $state('default')
|
|
@@ -398,48 +454,90 @@
|
|
|
398
454
|
}
|
|
399
455
|
</script>
|
|
400
456
|
|
|
401
|
-
{#if
|
|
457
|
+
{#if !isEmbed}
|
|
458
|
+
{#if visible && canvasActive && CanvasCreateMenu}
|
|
459
|
+
<div
|
|
460
|
+
class="fixed bottom-6 left-6 z-[9999] font-sans flex items-center gap-3"
|
|
461
|
+
role="toolbar"
|
|
462
|
+
aria-label="Canvas toolbar"
|
|
463
|
+
>
|
|
464
|
+
<Tooltip.Root>
|
|
465
|
+
<Tooltip.Trigger>
|
|
466
|
+
<CanvasCreateMenu config={canvasToolbarConfig} canvasName={activeCanvasName} tabindex={0} />
|
|
467
|
+
</Tooltip.Trigger>
|
|
468
|
+
<Tooltip.Content side="top">Add widget to canvas</Tooltip.Content>
|
|
469
|
+
</Tooltip.Root>
|
|
470
|
+
|
|
471
|
+
<div class="canvas-zoom-bar">
|
|
472
|
+
<button
|
|
473
|
+
class="canvas-zoom-btn"
|
|
474
|
+
onclick={canvasZoomOut}
|
|
475
|
+
disabled={canvasZoom <= ZOOM_MIN}
|
|
476
|
+
aria-label="Zoom out"
|
|
477
|
+
title="Zoom out"
|
|
478
|
+
>−</button>
|
|
479
|
+
<button
|
|
480
|
+
class="canvas-zoom-label"
|
|
481
|
+
onclick={canvasZoomReset}
|
|
482
|
+
aria-label="Reset zoom to 100%"
|
|
483
|
+
title="Reset to 100%"
|
|
484
|
+
>{canvasZoom}%</button>
|
|
485
|
+
<button
|
|
486
|
+
class="canvas-zoom-btn"
|
|
487
|
+
onclick={canvasZoomIn}
|
|
488
|
+
disabled={canvasZoom >= ZOOM_MAX}
|
|
489
|
+
aria-label="Zoom in"
|
|
490
|
+
title="Zoom in"
|
|
491
|
+
>+</button>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
{/if}
|
|
402
495
|
<div
|
|
403
496
|
id="storyboard-controls"
|
|
404
497
|
class="fixed bottom-6 right-6 z-[9999] font-sans flex items-end gap-3"
|
|
405
498
|
data-core-ui-bar
|
|
406
499
|
role="toolbar"
|
|
500
|
+
tabindex="0"
|
|
407
501
|
aria-label="Storyboard controls"
|
|
408
502
|
onkeydown={handleToolbarKeydown}
|
|
409
503
|
bind:this={toolbarEl}
|
|
410
504
|
>
|
|
411
|
-
{#
|
|
412
|
-
|
|
413
|
-
<Tooltip.
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
505
|
+
{#if visible}
|
|
506
|
+
{#each visibleMenus as menu, i (menu.key)}
|
|
507
|
+
<Tooltip.Root>
|
|
508
|
+
<Tooltip.Trigger>
|
|
509
|
+
{#if menu.sidepanel}
|
|
510
|
+
<TriggerButton
|
|
511
|
+
active={$sidePanelState.open && $sidePanelState.activeTab === menu.sidepanel}
|
|
512
|
+
size="icon-xl"
|
|
513
|
+
aria-label={menu.ariaLabel || menu.key}
|
|
514
|
+
tabindex={getTabindex(i)}
|
|
515
|
+
onfocus={() => { activeToolbarIndex = i }}
|
|
516
|
+
onclick={() => togglePanel(menu.sidepanel)}
|
|
517
|
+
>
|
|
518
|
+
<Icon name={menu.icon || menu.key} size={16} {...(menu.meta || {})} />
|
|
519
|
+
</TriggerButton>
|
|
520
|
+
{:else if menu.action}
|
|
521
|
+
<ActionMenuButton config={menu} tabindex={getTabindex(i)} />
|
|
522
|
+
{:else if menu.key === 'create'}
|
|
523
|
+
<CreateMenuButton features={createMenuFeatures} config={menu} tabindex={getTabindex(i)} />
|
|
524
|
+
{:else if menu.key === 'comments'}
|
|
525
|
+
<CommentsMenuButton config={menu} tabindex={getTabindex(i)} />
|
|
526
|
+
{/if}
|
|
527
|
+
</Tooltip.Trigger>
|
|
528
|
+
<Tooltip.Content side="top">{menu.ariaLabel || menu.key}</Tooltip.Content>
|
|
529
|
+
</Tooltip.Root>
|
|
530
|
+
{/each}
|
|
531
|
+
{/if}
|
|
436
532
|
{#if commandMenuConfig}
|
|
437
|
-
<
|
|
438
|
-
<Tooltip.
|
|
439
|
-
<
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
533
|
+
<div class={visible || commandMenuOpen ? '' : 'default-button-dimmed'}>
|
|
534
|
+
<Tooltip.Root>
|
|
535
|
+
<Tooltip.Trigger>
|
|
536
|
+
<CommandMenu {basePath} bind:open={commandMenuOpen} bind:flowDialogOpen {flowName} {flowJson} {flowError} shortcuts={shortcutsConfig} tabindex={getTabindex(commandMenuIndex)} icon={commandMenuConfig.icon} iconMeta={commandMenuConfig.meta} />
|
|
537
|
+
</Tooltip.Trigger>
|
|
538
|
+
<Tooltip.Content side="top">Command Menu</Tooltip.Content>
|
|
539
|
+
</Tooltip.Root>
|
|
540
|
+
</div>
|
|
443
541
|
{/if}
|
|
444
542
|
</div>
|
|
445
543
|
{/if}
|
|
@@ -448,3 +546,69 @@
|
|
|
448
546
|
<SidePanel onClose={() => focusToolbarItem(activeToolbarIndex < 0 ? toolbarItemCount - 1 : activeToolbarIndex)} />
|
|
449
547
|
{/if}
|
|
450
548
|
|
|
549
|
+
<style>
|
|
550
|
+
.canvas-zoom-bar {
|
|
551
|
+
display: flex;
|
|
552
|
+
align-items: center;
|
|
553
|
+
border-radius: 10px;
|
|
554
|
+
border: 1.5px solid var(--trigger-border, var(--color-slate-400));
|
|
555
|
+
background: var(--trigger-bg, var(--color-slate-100));
|
|
556
|
+
overflow: hidden;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.canvas-zoom-btn {
|
|
560
|
+
all: unset;
|
|
561
|
+
cursor: pointer;
|
|
562
|
+
display: flex;
|
|
563
|
+
align-items: center;
|
|
564
|
+
justify-content: center;
|
|
565
|
+
width: 36px;
|
|
566
|
+
height: 32px;
|
|
567
|
+
font-size: 16px;
|
|
568
|
+
font-weight: 600;
|
|
569
|
+
color: var(--trigger-text, var(--color-slate-600));
|
|
570
|
+
transition: background 120ms;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.canvas-zoom-btn:hover:not(:disabled) {
|
|
574
|
+
background: var(--trigger-bg-hover, var(--color-slate-300));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.canvas-zoom-btn:disabled {
|
|
578
|
+
opacity: 0.3;
|
|
579
|
+
cursor: default;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.canvas-zoom-label {
|
|
583
|
+
all: unset;
|
|
584
|
+
cursor: pointer;
|
|
585
|
+
display: flex;
|
|
586
|
+
align-items: center;
|
|
587
|
+
justify-content: center;
|
|
588
|
+
min-width: 48px;
|
|
589
|
+
height: 32px;
|
|
590
|
+
padding: 0 4px;
|
|
591
|
+
font-size: 11px;
|
|
592
|
+
font-weight: 600;
|
|
593
|
+
font-variant-numeric: tabular-nums;
|
|
594
|
+
color: var(--trigger-text, var(--color-slate-600));
|
|
595
|
+
border-left: 1.5px solid var(--trigger-border, var(--color-slate-400));
|
|
596
|
+
border-right: 1.5px solid var(--trigger-border, var(--color-slate-400));
|
|
597
|
+
transition: background 120ms;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.canvas-zoom-label:hover {
|
|
601
|
+
background: var(--trigger-bg-hover, var(--color-slate-300));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.default-button-dimmed {
|
|
605
|
+
opacity: 0.3;
|
|
606
|
+
transition: opacity 200ms;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.default-button-dimmed:hover,
|
|
610
|
+
.default-button-dimmed:focus-within {
|
|
611
|
+
opacity: 1;
|
|
612
|
+
}
|
|
613
|
+
</style>
|
|
614
|
+
|
|
@@ -34,6 +34,37 @@
|
|
|
34
34
|
/** @type {{ owner: string, name: string } | null} */
|
|
35
35
|
let repoInfo = $state(null)
|
|
36
36
|
|
|
37
|
+
/** @type {{ files: string[], sources: Record<string, string>, repo: { owner: string, name: string } | null } | null} */
|
|
38
|
+
let staticInspectorData = null
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Load the build-time static inspector JSON (production only).
|
|
42
|
+
* Cached after the first successful fetch.
|
|
43
|
+
*/
|
|
44
|
+
async function loadStaticData() {
|
|
45
|
+
if (staticInspectorData) return staticInspectorData
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(`${import.meta.env.BASE_URL}_storyboard/inspector.json`)
|
|
48
|
+
if (res.ok) {
|
|
49
|
+
staticInspectorData = await res.json()
|
|
50
|
+
return staticInspectorData
|
|
51
|
+
}
|
|
52
|
+
} catch {}
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fetch source file content — uses dev middleware in dev, static JSON in prod.
|
|
58
|
+
*/
|
|
59
|
+
async function fetchSourceContent(filePath) {
|
|
60
|
+
if (import.meta.env.DEV) {
|
|
61
|
+
const res = await fetch(`/_storyboard/docs/source?path=${encodeURIComponent(filePath)}`)
|
|
62
|
+
return res.ok ? (await res.json())?.content || '' : ''
|
|
63
|
+
}
|
|
64
|
+
const data = await loadStaticData()
|
|
65
|
+
return data?.sources?.[filePath] || ''
|
|
66
|
+
}
|
|
67
|
+
|
|
37
68
|
let mouseMode = null
|
|
38
69
|
|
|
39
70
|
const hasSelection = $derived(componentInfo !== null)
|
|
@@ -155,11 +186,8 @@
|
|
|
155
186
|
|
|
156
187
|
async function getHighlighter() {
|
|
157
188
|
if (highlighter) return highlighter
|
|
158
|
-
const {
|
|
159
|
-
highlighter = await
|
|
160
|
-
themes: ['github-dark'],
|
|
161
|
-
langs: ['jsx', 'tsx', 'javascript', 'typescript'],
|
|
162
|
-
})
|
|
189
|
+
const { createInspectorHighlighter } = await import('./inspector/highlighter.js')
|
|
190
|
+
highlighter = await createInspectorHighlighter()
|
|
163
191
|
return highlighter
|
|
164
192
|
}
|
|
165
193
|
|
|
@@ -234,10 +262,9 @@
|
|
|
234
262
|
sourceLoading = true
|
|
235
263
|
sourcePath = path
|
|
236
264
|
highlightedHtml = ''
|
|
237
|
-
|
|
238
|
-
.then(
|
|
239
|
-
|
|
240
|
-
sourceCode = data?.content || ''
|
|
265
|
+
fetchSourceContent(path)
|
|
266
|
+
.then(async (content) => {
|
|
267
|
+
sourceCode = content
|
|
241
268
|
// Try the selected component first, then walk up the chain
|
|
242
269
|
matchedLine = findComponentLine(sourceCode, componentInfo)
|
|
243
270
|
if (matchedLine < 0 && componentChain.length > 0) {
|
|
@@ -336,20 +363,30 @@
|
|
|
336
363
|
// Auto-start inspector mode
|
|
337
364
|
startInspecting()
|
|
338
365
|
|
|
339
|
-
// Pre-fetch file list and repo info
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
366
|
+
// Pre-fetch file list and repo info
|
|
367
|
+
if (import.meta.env.DEV) {
|
|
368
|
+
// Dev: use live middleware endpoints
|
|
369
|
+
try {
|
|
370
|
+
const [filesRes, repoRes] = await Promise.all([
|
|
371
|
+
fetch('/_storyboard/docs/files'),
|
|
372
|
+
fetch('/_storyboard/docs/repo'),
|
|
373
|
+
])
|
|
374
|
+
if (filesRes.ok) {
|
|
375
|
+
const data = await filesRes.json()
|
|
376
|
+
knownFiles = data.files || []
|
|
377
|
+
}
|
|
378
|
+
if (repoRes.ok) {
|
|
379
|
+
repoInfo = await repoRes.json()
|
|
380
|
+
}
|
|
381
|
+
} catch {}
|
|
382
|
+
} else {
|
|
383
|
+
// Production: load from static build-time JSON
|
|
384
|
+
const data = await loadStaticData()
|
|
385
|
+
if (data) {
|
|
347
386
|
knownFiles = data.files || []
|
|
387
|
+
repoInfo = data.repo || null
|
|
348
388
|
}
|
|
349
|
-
|
|
350
|
-
repoInfo = await repoRes.json()
|
|
351
|
-
}
|
|
352
|
-
} catch {}
|
|
389
|
+
}
|
|
353
390
|
})
|
|
354
391
|
|
|
355
392
|
onDestroy(() => {
|