@dfosco/storyboard-react 4.2.0-beta.4 → 4.2.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.
Files changed (89) hide show
  1. package/package.json +10 -11
  2. package/src/AuthModal/AuthModal.jsx +6 -8
  3. package/src/BranchBar/BranchBar.jsx +20 -6
  4. package/src/BranchBar/BranchBar.module.css +13 -4
  5. package/src/BranchBar/useBranches.js +20 -6
  6. package/src/BranchBar/useBranches.test.js +68 -0
  7. package/src/CommandPalette/CommandPalette.jsx +480 -187
  8. package/src/CommandPalette/command-palette.css +142 -78
  9. package/src/Icon.jsx +157 -58
  10. package/src/Viewfinder.jsx +562 -207
  11. package/src/Viewfinder.module.css +434 -93
  12. package/src/Workspace.jsx +7 -0
  13. package/src/canvas/CanvasPage.bridge.test.jsx +14 -6
  14. package/src/canvas/CanvasPage.dragdrop.test.jsx +11 -7
  15. package/src/canvas/CanvasPage.jsx +739 -219
  16. package/src/canvas/CanvasPage.module.css +13 -15
  17. package/src/canvas/CanvasPage.multiselect.test.jsx +17 -6
  18. package/src/canvas/ConnectorLayer.jsx +121 -165
  19. package/src/canvas/ConnectorLayer.module.css +69 -0
  20. package/src/canvas/PageSelector.test.jsx +15 -6
  21. package/src/canvas/canvasApi.js +68 -2
  22. package/src/canvas/canvasReloadGuard.test.js +1 -1
  23. package/src/canvas/connectorGeometry.js +132 -0
  24. package/src/canvas/hotPoolDevLogs.js +25 -0
  25. package/src/canvas/useCanvas.js +1 -1
  26. package/src/canvas/useMarqueeSelect.js +30 -4
  27. package/src/canvas/widgets/CodePenEmbed.jsx +1 -0
  28. package/src/canvas/widgets/ComponentSetWidget.jsx +199 -0
  29. package/src/canvas/widgets/ComponentSetWidget.module.css +89 -0
  30. package/src/canvas/widgets/ComponentWidget.jsx +1 -0
  31. package/src/canvas/widgets/CropOverlay.jsx +219 -0
  32. package/src/canvas/widgets/CropOverlay.module.css +118 -0
  33. package/src/canvas/widgets/ExpandedPane.jsx +474 -0
  34. package/src/canvas/widgets/ExpandedPane.module.css +179 -0
  35. package/src/canvas/widgets/ExpandedPane.test.jsx +240 -0
  36. package/src/canvas/widgets/ExpandedPaneTopBar.jsx +111 -0
  37. package/src/canvas/widgets/ExpandedPaneTopBar.module.css +59 -0
  38. package/src/canvas/widgets/ExpandedPaneTopBar.test.jsx +45 -0
  39. package/src/canvas/widgets/FigmaEmbed.jsx +62 -47
  40. package/src/canvas/widgets/FigmaEmbed.module.css +61 -0
  41. package/src/canvas/widgets/ImageWidget.jsx +130 -9
  42. package/src/canvas/widgets/ImageWidget.module.css +30 -0
  43. package/src/canvas/widgets/LinkPreview.jsx +113 -5
  44. package/src/canvas/widgets/LinkPreview.module.css +127 -0
  45. package/src/canvas/widgets/MarkdownBlock.jsx +167 -17
  46. package/src/canvas/widgets/MarkdownBlock.module.css +148 -0
  47. package/src/canvas/widgets/PromptWidget.jsx +414 -0
  48. package/src/canvas/widgets/PromptWidget.module.css +273 -0
  49. package/src/canvas/widgets/PrototypeEmbed.jsx +77 -39
  50. package/src/canvas/widgets/PrototypeEmbed.module.css +117 -0
  51. package/src/canvas/widgets/PrototypeEmbed.test.jsx +2 -2
  52. package/src/canvas/widgets/ResizeHandle.jsx +17 -6
  53. package/src/canvas/widgets/StoryWidget.jsx +73 -15
  54. package/src/canvas/widgets/TerminalReadWidget.jsx +146 -0
  55. package/src/canvas/widgets/TerminalReadWidget.module.css +94 -0
  56. package/src/canvas/widgets/TerminalWidget.jsx +445 -67
  57. package/src/canvas/widgets/TerminalWidget.module.css +271 -8
  58. package/src/canvas/widgets/TilesWidget.jsx +300 -0
  59. package/src/canvas/widgets/TilesWidget.module.css +133 -0
  60. package/src/canvas/widgets/WidgetChrome.jsx +74 -153
  61. package/src/canvas/widgets/WidgetChrome.module.css +30 -1
  62. package/src/canvas/widgets/embedInteraction.test.jsx +24 -26
  63. package/src/canvas/widgets/expandUtils.js +560 -0
  64. package/src/canvas/widgets/expandUtils.test.js +155 -0
  65. package/src/canvas/widgets/index.js +9 -0
  66. package/src/canvas/widgets/snapshotDisplay.test.jsx +23 -71
  67. package/src/canvas/widgets/tilePool.js +23 -0
  68. package/src/canvas/widgets/tiles/diagonal-bl.png +0 -0
  69. package/src/canvas/widgets/tiles/diagonal-br.png +0 -0
  70. package/src/canvas/widgets/tiles/diagonal-tl.png +0 -0
  71. package/src/canvas/widgets/tiles/leaf.png +0 -0
  72. package/src/canvas/widgets/tiles/quarter-tl.png +0 -0
  73. package/src/canvas/widgets/tiles/quarter-tr.png +0 -0
  74. package/src/canvas/widgets/tiles/solid-a.png +0 -0
  75. package/src/canvas/widgets/tiles/solid-b.png +0 -0
  76. package/src/canvas/widgets/widgetConfig.js +55 -4
  77. package/src/canvas/widgets/widgetIcons.jsx +190 -0
  78. package/src/canvas/widgets/widgetProps.js +1 -0
  79. package/src/context.jsx +48 -20
  80. package/src/hooks/useConfig.js +14 -0
  81. package/src/hooks/usePrototypeReloadGuard.js +64 -0
  82. package/src/hooks/useSceneData.js +1 -0
  83. package/src/hooks/useThemeState.test.js +1 -1
  84. package/src/index.js +8 -2
  85. package/src/story/ComponentSetPage.jsx +186 -0
  86. package/src/story/ComponentSetPage.module.css +121 -0
  87. package/src/story/StoryPage.jsx +32 -2
  88. package/src/vite/data-plugin.js +363 -67
  89. package/src/vite/data-plugin.test.js +1 -1
@@ -0,0 +1,146 @@
1
+ import { useRef, useEffect, useState } from 'react'
2
+ import { readProp, schemas } from './widgetProps.js'
3
+ import styles from './TerminalReadWidget.module.css'
4
+
5
+ const terminalSchema = schemas['terminal']
6
+
7
+ let Convert = null
8
+ let ansiLoadAttempted = false
9
+
10
+ async function getConverter() {
11
+ if (Convert) return new Convert({ fg: '#e6edf3', bg: '#0d1117', newline: true })
12
+ if (ansiLoadAttempted) return null
13
+ ansiLoadAttempted = true
14
+ try {
15
+ const mod = await import(/* @vite-ignore */ 'ansi-to-html')
16
+ Convert = mod.default || mod
17
+ return new Convert({ fg: '#e6edf3', bg: '#0d1117', newline: true })
18
+ } catch {
19
+ return null
20
+ }
21
+ }
22
+
23
+ function stripAnsi(text) {
24
+ // eslint-disable-next-line no-control-regex
25
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
26
+ }
27
+
28
+ function getBaseUrl() {
29
+ const base = (typeof import.meta !== 'undefined' && import.meta.env?.BASE_URL) || '/'
30
+ return base.endsWith('/') ? base : base + '/'
31
+ }
32
+
33
+ function getCanvasId() {
34
+ return window.__storyboardCanvasBridgeState?.canvasId || null
35
+ }
36
+
37
+ function isProduction() {
38
+ return typeof import.meta !== 'undefined' && import.meta.env?.PROD
39
+ }
40
+
41
+ export default function TerminalReadWidget({ id, props }) {
42
+ const width = readProp(props, 'width', terminalSchema)
43
+ const height = readProp(props, 'height', terminalSchema)
44
+ const prettyName = props?.prettyName || '...'
45
+
46
+ const [content, setContent] = useState(null)
47
+ const [html, setHtml] = useState(null)
48
+ const [failed, setFailed] = useState(false)
49
+ const contentRef = useRef(null)
50
+
51
+ useEffect(() => {
52
+ let cancelled = false
53
+ async function fetchSnapshot() {
54
+ const baseUrl = getBaseUrl()
55
+ const canvasId = getCanvasId()
56
+ if (!canvasId) { setFailed(true); return }
57
+
58
+ const urls = isProduction()
59
+ ? [
60
+ // New flat format: <widgetId>.snapshot.json
61
+ `${baseUrl}_storyboard/terminal-snapshots/${id}.snapshot.json`,
62
+ // Legacy nested format: <canvasDir>/<widgetId>.json
63
+ `${baseUrl}_storyboard/terminal-snapshots/${canvasId.replace(/\//g, '--')}/${id}.json`,
64
+ ]
65
+ : [
66
+ `${baseUrl}_storyboard/canvas/terminal-snapshot/${id}`,
67
+ `${baseUrl}_storyboard/terminal-snapshots/${id}.snapshot.json`,
68
+ `${baseUrl}_storyboard/terminal-snapshots/${canvasId.replace(/\//g, '--')}/${id}.json`,
69
+ ]
70
+
71
+ for (const url of urls) {
72
+ try {
73
+ const res = await fetch(url)
74
+ if (!res.ok) continue
75
+ const data = await res.json()
76
+ if (cancelled) return
77
+ const text = data.paneContent || data.content || data.output || ''
78
+ setContent(text)
79
+
80
+ const converter = await getConverter()
81
+ if (cancelled) return
82
+ if (converter) {
83
+ setHtml(converter.toHtml(text))
84
+ } else {
85
+ setContent(stripAnsi(text))
86
+ }
87
+ return
88
+ } catch {
89
+ continue
90
+ }
91
+ }
92
+ if (!cancelled) setFailed(true)
93
+ }
94
+ fetchSnapshot()
95
+ return () => { cancelled = true }
96
+ }, [id])
97
+
98
+ // Auto-scroll to bottom
99
+ useEffect(() => {
100
+ if (contentRef.current) {
101
+ contentRef.current.scrollTop = contentRef.current.scrollHeight
102
+ }
103
+ }, [html, content])
104
+
105
+ const titleLabel = `terminal · ${prettyName}`
106
+
107
+ return (
108
+ <div className={styles.container}>
109
+ <div className={`tc-drag-handle ${styles.titleBar}`}>
110
+ <span>{titleLabel}</span>
111
+ <span className={styles.readOnlyBadge}>read only</span>
112
+ </div>
113
+ <div
114
+ ref={contentRef}
115
+ className={styles.content}
116
+ style={{
117
+ ...(typeof width === 'number' ? { width: `${width}px` } : undefined),
118
+ ...(typeof height === 'number' ? { height: `${height}px` } : undefined),
119
+ }}
120
+ >
121
+ {failed && (
122
+ <div className={styles.placeholder}>
123
+ <span className={styles.placeholderTitle}>Terminal session · {prettyName}</span>
124
+ <span className={styles.placeholderSub}>No captured output available</span>
125
+ </div>
126
+ )}
127
+ {!failed && content === null && (
128
+ <div className={styles.placeholder}>
129
+ <span className={styles.placeholderSub}>Loading…</span>
130
+ </div>
131
+ )}
132
+ {!failed && html && (
133
+ <pre
134
+ style={{ margin: 0, whiteSpace: 'pre', fontFamily: 'inherit', fontSize: 'inherit', lineHeight: 'inherit' }}
135
+ dangerouslySetInnerHTML={{ __html: html }}
136
+ />
137
+ )}
138
+ {!failed && content !== null && !html && (
139
+ <pre style={{ margin: 0, whiteSpace: 'pre', fontFamily: 'inherit', fontSize: 'inherit', lineHeight: 'inherit' }}>
140
+ {content}
141
+ </pre>
142
+ )}
143
+ </div>
144
+ </div>
145
+ )
146
+ }
@@ -0,0 +1,94 @@
1
+ .container {
2
+ position: relative;
3
+ display: flex;
4
+ flex-direction: column;
5
+ border-radius: var(--base-size-16, 16px);
6
+ }
7
+
8
+ :global(.tc-drag-surface):has(.container) {
9
+ border-radius: 16px;
10
+ }
11
+
12
+ .titleBar {
13
+ position: absolute;
14
+ top: -28px;
15
+ left: 4px;
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 8px;
19
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
20
+ font-size: 11px;
21
+ color: #8b949e;
22
+ user-select: none;
23
+ white-space: nowrap;
24
+ z-index: 2;
25
+ cursor: grab;
26
+ padding: 2px 6px;
27
+ border-radius: 4px;
28
+ }
29
+
30
+ [data-widget-selected] .titleBar {
31
+ color: var(--borderColor-accent-emphasis, #0969da);
32
+ }
33
+
34
+ .readOnlyBadge {
35
+ font-size: 10px;
36
+ color: #6e7681;
37
+ background: rgba(110, 118, 129, 0.15);
38
+ padding: 1px 6px;
39
+ border-radius: 4px;
40
+ font-weight: 500;
41
+ }
42
+
43
+ .content {
44
+ background: #0d1117;
45
+ color: #e6edf3;
46
+ font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', monospace;
47
+ font-size: 13px;
48
+ line-height: 17px;
49
+ padding: 12px;
50
+ overflow-y: auto;
51
+ white-space: pre;
52
+ flex: 1;
53
+ border: 1px solid var(--borderColor-default, #30363d);
54
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.24);
55
+ border-radius: var(--base-size-16, 16px);
56
+ box-sizing: border-box;
57
+ }
58
+
59
+ .content::-webkit-scrollbar {
60
+ width: 6px;
61
+ }
62
+
63
+ .content::-webkit-scrollbar-thumb {
64
+ background: rgba(255, 255, 255, 0.15);
65
+ border-radius: 3px;
66
+ }
67
+
68
+ .content::-webkit-scrollbar-track {
69
+ background: transparent;
70
+ }
71
+
72
+ .placeholder {
73
+ display: flex;
74
+ flex-direction: column;
75
+ align-items: center;
76
+ justify-content: center;
77
+ gap: 8px;
78
+ height: 100%;
79
+ color: #8b949e;
80
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
81
+ font-size: 13px;
82
+ text-align: center;
83
+ user-select: none;
84
+ }
85
+
86
+ .placeholderTitle {
87
+ color: #e6edf3;
88
+ font-weight: 500;
89
+ }
90
+
91
+ .placeholderSub {
92
+ color: #6e7681;
93
+ font-size: 12px;
94
+ }