@changfenhuang/dsh-genui 0.9.1

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 (106) hide show
  1. package/CHANGELOG.md +367 -0
  2. package/LICENSE +21 -0
  3. package/README.md +261 -0
  4. package/README.zh-CN.md +261 -0
  5. package/SKILL.md +115 -0
  6. package/cordis.patch.yml +9 -0
  7. package/demo-prompts.md +118 -0
  8. package/lib/assets/echarts.js +64 -0
  9. package/lib/assets/mermaid.js +3591 -0
  10. package/lib/assets/three.js +3844 -0
  11. package/lib/client.js +5 -0
  12. package/lib/index.js +1895 -0
  13. package/lib/invariant.js +19 -0
  14. package/lib/types/client/EChartNode.d.ts +4 -0
  15. package/lib/types/client/ErrorBoundary.d.ts +30 -0
  16. package/lib/types/client/GenuiBlock.d.ts +7 -0
  17. package/lib/types/client/PlotBlock.d.ts +44 -0
  18. package/lib/types/client/action-context.d.ts +7 -0
  19. package/lib/types/client/asset-echarts.d.ts +15 -0
  20. package/lib/types/client/asset-loader.d.ts +31 -0
  21. package/lib/types/client/asset-mermaid.d.ts +1 -0
  22. package/lib/types/client/asset-three.d.ts +1 -0
  23. package/lib/types/client/blocks/advanced.d.ts +104 -0
  24. package/lib/types/client/blocks/basic.d.ts +27 -0
  25. package/lib/types/client/blocks/charts.d.ts +38 -0
  26. package/lib/types/client/blocks/diagram/geometry.d.ts +73 -0
  27. package/lib/types/client/blocks/diagram/index.d.ts +5 -0
  28. package/lib/types/client/blocks/diagram/layout.d.ts +41 -0
  29. package/lib/types/client/blocks/diagram/theme.d.ts +35 -0
  30. package/lib/types/client/blocks/forms.d.ts +83 -0
  31. package/lib/types/client/blocks/render-node.d.ts +10 -0
  32. package/lib/types/client/blocks/state.d.ts +58 -0
  33. package/lib/types/client/dom-fence.d.ts +14 -0
  34. package/lib/types/client/echarts-lazy.d.ts +17 -0
  35. package/lib/types/client/fence-render.d.ts +58 -0
  36. package/lib/types/client/gallery.d.ts +9 -0
  37. package/lib/types/client/guard.d.ts +126 -0
  38. package/lib/types/client/index.d.ts +36 -0
  39. package/lib/types/client/interaction-store.d.ts +43 -0
  40. package/lib/types/client/mermaid-core.d.ts +8 -0
  41. package/lib/types/client/mermaid-lazy.d.ts +9 -0
  42. package/lib/types/client/mermaid-safe.d.ts +52 -0
  43. package/lib/types/client/panel-command.d.ts +29 -0
  44. package/lib/types/client/panel-store.d.ts +94 -0
  45. package/lib/types/client/panel.d.ts +15 -0
  46. package/lib/types/client/parse-partial.d.ts +37 -0
  47. package/lib/types/client/safe-math.d.ts +43 -0
  48. package/lib/types/client/scene3d-core.d.ts +21 -0
  49. package/lib/types/client/scene3d-lazy.d.ts +8 -0
  50. package/lib/types/client/spec.d.ts +610 -0
  51. package/lib/types/client/toolview.d.ts +7 -0
  52. package/lib/types/index.d.ts +1 -0
  53. package/lib/types/plugin/index.d.ts +25 -0
  54. package/lib/types/plugin/invariant.d.ts +15 -0
  55. package/lib/types/plugin/tool.d.ts +27 -0
  56. package/lib/types/shared/fence-repair.d.ts +67 -0
  57. package/package.json +121 -0
  58. package/src/client/EChartNode.tsx +242 -0
  59. package/src/client/ErrorBoundary.tsx +66 -0
  60. package/src/client/GenuiBlock.module.css +1263 -0
  61. package/src/client/GenuiBlock.tsx +166 -0
  62. package/src/client/PlotBlock.module.css +155 -0
  63. package/src/client/PlotBlock.tsx +431 -0
  64. package/src/client/action-context.ts +32 -0
  65. package/src/client/asset-echarts.ts +25 -0
  66. package/src/client/asset-loader.ts +79 -0
  67. package/src/client/asset-mermaid.ts +13 -0
  68. package/src/client/asset-three.ts +12 -0
  69. package/src/client/blocks/advanced.tsx +435 -0
  70. package/src/client/blocks/basic.tsx +116 -0
  71. package/src/client/blocks/charts.tsx +319 -0
  72. package/src/client/blocks/diagram/geometry.ts +151 -0
  73. package/src/client/blocks/diagram/index.tsx +284 -0
  74. package/src/client/blocks/diagram/layout.ts +199 -0
  75. package/src/client/blocks/diagram/theme.ts +108 -0
  76. package/src/client/blocks/forms.tsx +523 -0
  77. package/src/client/blocks/render-node.tsx +260 -0
  78. package/src/client/blocks/state.ts +61 -0
  79. package/src/client/dom-fence.tsx +641 -0
  80. package/src/client/echarts-lazy.ts +35 -0
  81. package/src/client/fence-render.tsx +230 -0
  82. package/src/client/gallery.ts +128 -0
  83. package/src/client/guard.ts +1450 -0
  84. package/src/client/index.tsx +195 -0
  85. package/src/client/interaction-store.ts +122 -0
  86. package/src/client/mermaid-core.ts +121 -0
  87. package/src/client/mermaid-lazy.ts +31 -0
  88. package/src/client/mermaid-safe.ts +122 -0
  89. package/src/client/panel-command.ts +119 -0
  90. package/src/client/panel-store.ts +471 -0
  91. package/src/client/panel.tsx +185 -0
  92. package/src/client/parse-partial.ts +162 -0
  93. package/src/client/safe-math.ts +270 -0
  94. package/src/client/scene3d-core.ts +192 -0
  95. package/src/client/scene3d-lazy.ts +26 -0
  96. package/src/client/spec.ts +767 -0
  97. package/src/client/toolview.tsx +66 -0
  98. package/src/css-modules.d.ts +5 -0
  99. package/src/index.ts +12 -0
  100. package/src/plugin/index.ts +173 -0
  101. package/src/plugin/invariant.ts +30 -0
  102. package/src/plugin/node-shims.d.ts +31 -0
  103. package/src/plugin/tool.ts +330 -0
  104. package/src/shared/fence-repair.ts +244 -0
  105. package/tsconfig.json +35 -0
  106. package/tsdown.config.ts +193 -0
@@ -0,0 +1,192 @@
1
+ /**
2
+ * three.js scene renderer — the HEAVY half. This module imports three and is
3
+ * bundled ONLY into the lazy asset bundle `lib/assets/three.js`, loaded on
4
+ * demand by the scene3d-lazy loader when a spec contains a `scene3d` node;
5
+ * the main client bundle stays small.
6
+ *
7
+ * Security: the model only supplies white-listed primitive shapes, numeric
8
+ * transforms, and colors. No textures, no external URLs, no scripts, no
9
+ * custom shaders — everything is geometry + material colors constructed
10
+ * locally. The renderer mounts into a caller-owned container and returns a
11
+ * disposer that tears down the WebGL context.
12
+ * @module @changfenhuang/dsh-genui/client/scene3d-core
13
+ */
14
+ import type { GenuiScene3D } from './spec.ts'
15
+
16
+ let threePromise: Promise<typeof import('three')> | null = null
17
+
18
+ function loadThree(): Promise<typeof import('three')> {
19
+ threePromise ??= import('three')
20
+ return threePromise
21
+ }
22
+
23
+ /** Shape constructors we accept, mapped to a size-normalized factory. */
24
+ type ShapeKind = 'box' | 'sphere' | 'cone' | 'cylinder' | 'torus'
25
+
26
+ function geometryFor(
27
+ THREE: typeof import('three'),
28
+ shape: ShapeKind,
29
+ size: number | [number, number, number] | undefined,
30
+ ): import('three').BufferGeometry {
31
+ const s = size ?? 1
32
+ switch (shape) {
33
+ case 'box': {
34
+ const [w, h, d] = Array.isArray(s) ? s : [s, s, s]
35
+ return new THREE.BoxGeometry(w, h, d)
36
+ }
37
+ case 'sphere': {
38
+ const r = Array.isArray(s) ? s[0] : s
39
+ return new THREE.SphereGeometry(r, 32, 24)
40
+ }
41
+ case 'cone': {
42
+ const r = Array.isArray(s) ? s[0] : s
43
+ return new THREE.ConeGeometry(r, Array.isArray(s) ? s[1] : r * 2, 32)
44
+ }
45
+ case 'cylinder': {
46
+ const r = Array.isArray(s) ? s[0] : s
47
+ return new THREE.CylinderGeometry(r, r, Array.isArray(s) ? s[1] : r * 2, 32)
48
+ }
49
+ case 'torus': {
50
+ const r = Array.isArray(s) ? s[0] : s
51
+ return new THREE.TorusGeometry(r, Array.isArray(s) ? s[1] : r * 0.4, 16, 48)
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Mount a GenUI 3D scene into `container`.
58
+ * @param container - the DOM node to host the WebGL canvas.
59
+ * @param scene - the declarative scene spec.
60
+ * @returns a disposer that removes the renderer and its context.
61
+ */
62
+ export async function mountScene(container: HTMLElement, scene: GenuiScene3D): Promise<() => void> {
63
+ const THREE = await loadThree()
64
+ const width = container.clientWidth || 420
65
+ const height = 240
66
+
67
+ const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
68
+ renderer.setSize(width, height)
69
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
70
+ container.appendChild(renderer.domElement)
71
+
72
+ const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 100)
73
+ camera.position.set(3, 2.4, 4)
74
+ camera.lookAt(0, 0, 0)
75
+
76
+ const threeScene = new THREE.Scene()
77
+ if (scene.background !== undefined) {
78
+ try { threeScene.background = new THREE.Color(scene.background) } catch { /* keep transparent */ }
79
+ }
80
+
81
+ const ambient = new THREE.AmbientLight(0xffffff, scene.ambient ?? 0.8)
82
+ threeScene.add(ambient)
83
+ const key = new THREE.DirectionalLight(0xffffff, 1.6)
84
+ key.position.set(4, 6, 3)
85
+ threeScene.add(key)
86
+ const rim = new THREE.DirectionalLight(0x6ea8ff, 0.6)
87
+ rim.position.set(-4, -2, -4)
88
+ threeScene.add(rim)
89
+
90
+ // Wireframe-ish ground grid for spatial reference.
91
+ const grid = new THREE.GridHelper(8, 16, 0x3a4152, 0x232a38)
92
+ threeScene.add(grid)
93
+
94
+ for (const mesh of scene.meshes) {
95
+ const geo = geometryFor(THREE, mesh.shape, mesh.size)
96
+ const color = new THREE.Color(mesh.color ?? '#6ea8ff')
97
+ const mat = new THREE.MeshStandardMaterial({ color, roughness: 0.35, metalness: 0.15 })
98
+ const obj = new THREE.Mesh(geo, mat)
99
+ const p = mesh.position ?? [0, 0, 0]
100
+ obj.position.set(p[0] ?? 0, p[1] ?? 0, p[2] ?? 0)
101
+ const r = mesh.rotation ?? [0, 0, 0]
102
+ obj.rotation.set(r[0] ?? 0, r[1] ?? 0, r[2] ?? 0)
103
+ const sc = mesh.scale ?? 1
104
+ if (typeof sc === 'number') obj.scale.setScalar(sc)
105
+ else obj.scale.set(sc[0] ?? 1, sc[1] ?? 1, sc[2] ?? 1)
106
+ threeScene.add(obj)
107
+ }
108
+
109
+ // Orbit: simple drag-to-rotate, wheel to zoom (no external controls dep).
110
+ // EVENT-DRIVEN rendering: one render after init, one per orbit change —
111
+ // a static scene never runs a requestAnimationFrame loop, so an idle
112
+ // scene costs zero GPU/battery. Pointer capture keeps all drag events on
113
+ // the canvas (no window listeners, nothing to leak on unmount).
114
+ let isDragging = false
115
+ let prevX = 0
116
+ let prevY = 0
117
+ let theta = 0.8
118
+ let phi = 0.6
119
+ let radius = 5.5
120
+ const orbit = (): void => {
121
+ camera.position.set(
122
+ radius * Math.sin(phi) * Math.cos(theta),
123
+ radius * Math.cos(phi),
124
+ radius * Math.sin(phi) * Math.sin(theta),
125
+ )
126
+ camera.lookAt(0, 0, 0)
127
+ renderer.render(threeScene, camera)
128
+ }
129
+
130
+ const onPointerDown = (e: PointerEvent): void => {
131
+ isDragging = true
132
+ prevX = e.clientX
133
+ prevY = e.clientY
134
+ try { canvas.setPointerCapture(e.pointerId) } catch { /* jsdom / capture unsupported */ }
135
+ }
136
+ const onPointerMove = (e: PointerEvent): void => {
137
+ if (!isDragging) return
138
+ theta -= (e.clientX - prevX) * 0.01
139
+ phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi + (e.clientY - prevY) * 0.01))
140
+ prevX = e.clientX
141
+ prevY = e.clientY
142
+ orbit()
143
+ }
144
+ const onPointerEnd = (e: PointerEvent): void => {
145
+ if (!isDragging) return
146
+ isDragging = false
147
+ try { canvas.releasePointerCapture(e.pointerId) } catch { /* jsdom */ }
148
+ }
149
+ const onWheel = (e: WheelEvent): void => {
150
+ e.preventDefault()
151
+ radius = Math.max(2.5, Math.min(14, radius + e.deltaY * 0.005))
152
+ orbit()
153
+ }
154
+ const canvas = renderer.domElement
155
+ canvas.addEventListener('pointerdown', onPointerDown)
156
+ canvas.addEventListener('pointermove', onPointerMove)
157
+ canvas.addEventListener('pointerup', onPointerEnd)
158
+ canvas.addEventListener('pointercancel', onPointerEnd)
159
+ canvas.addEventListener('wheel', onWheel, { passive: false })
160
+
161
+ // Container-size sync: the panel is resizable and the window reflows, so a
162
+ // fixed mount-time width drifts. Event-driven like the orbit — a resize
163
+ // renders exactly once, an idle scene still costs nothing.
164
+ let lastWidth = width
165
+ const observer = typeof ResizeObserver !== 'undefined'
166
+ ? new ResizeObserver(() => {
167
+ const w = container.clientWidth || 420
168
+ if (w === lastWidth) return
169
+ lastWidth = w
170
+ renderer.setSize(w, height)
171
+ camera.aspect = w / height
172
+ camera.updateProjectionMatrix()
173
+ renderer.render(threeScene, camera)
174
+ })
175
+ : null
176
+ observer?.observe(container)
177
+
178
+ orbit() // initial render; afterwards only interactions re-render.
179
+
180
+ return () => {
181
+ observer?.disconnect()
182
+ canvas.removeEventListener('pointerdown', onPointerDown)
183
+ canvas.removeEventListener('pointermove', onPointerMove)
184
+ canvas.removeEventListener('pointerup', onPointerEnd)
185
+ canvas.removeEventListener('pointercancel', onPointerEnd)
186
+ canvas.removeEventListener('wheel', onWheel)
187
+ renderer.dispose()
188
+ if (renderer.domElement.parentElement === container) {
189
+ container.removeChild(renderer.domElement)
190
+ }
191
+ }
192
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Runtime loader for the three.js scene renderer. The heavy three bundle
3
+ * ships as a separate asset (`lib/assets/three.js`, served by the plugin's
4
+ * own HTTP route) and is fetched ONLY when a spec contains a `scene3d` node.
5
+ * On a host that does not serve the asset the load rejects and the
6
+ * Scene3DNode shows its error hint.
7
+ * @module @changfenhuang/dsh-genui/client/scene3d-lazy
8
+ */
9
+ import { loadGenuiAsset } from './asset-loader.ts'
10
+ import type { GenuiScene3D } from './spec.ts'
11
+
12
+ /** The engine surface registered by the three asset bundle. */
13
+ interface ThreeAssetApi {
14
+ mountScene: (container: HTMLElement, scene: GenuiScene3D) => Promise<() => void>
15
+ }
16
+
17
+ /**
18
+ * Mount a GenUI 3D scene into `container` (engine loaded on demand).
19
+ * @param container - the DOM node to host the WebGL canvas.
20
+ * @param scene - the declarative scene spec.
21
+ * @returns a disposer that removes the renderer and its context.
22
+ */
23
+ export async function mountScene(container: HTMLElement, scene: GenuiScene3D): Promise<() => void> {
24
+ const api = await loadGenuiAsset<ThreeAssetApi>('three')
25
+ return api.mountScene(container, scene)
26
+ }