project-graph-mcp 2.2.6 → 2.3.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 (150) hide show
  1. package/ARCHITECTURE.md +81 -0
  2. package/CHANGELOG.md +57 -0
  3. package/README.md +9 -4
  4. package/package.json +6 -13
  5. package/src/compact/expand.js +1 -1
  6. package/src/core/graph-builder.js +2 -2
  7. package/src/core/parser.js +2 -2
  8. package/src/network/server.js +1 -2
  9. package/vendor/symbiote-node/CHANGELOG.md +31 -0
  10. package/vendor/symbiote-node/LICENSE +21 -0
  11. package/vendor/symbiote-node/README.md +206 -0
  12. package/vendor/symbiote-node/canvas/AutoLayout.js +725 -0
  13. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +73 -0
  14. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +93 -0
  15. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +9 -0
  16. package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +962 -0
  17. package/vendor/symbiote-node/canvas/ConnectionRenderer.js +1468 -0
  18. package/vendor/symbiote-node/canvas/FlowSimulator.js +323 -0
  19. package/vendor/symbiote-node/canvas/ForceLayout.js +189 -0
  20. package/vendor/symbiote-node/canvas/ForceWorker.js +1325 -0
  21. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +97 -0
  22. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +176 -0
  23. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +12 -0
  24. package/vendor/symbiote-node/canvas/LODManager.js +88 -0
  25. package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +71 -0
  26. package/vendor/symbiote-node/canvas/Minimap/Minimap.js +207 -0
  27. package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +9 -0
  28. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +261 -0
  29. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +1840 -0
  30. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +22 -0
  31. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +97 -0
  32. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +132 -0
  33. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +21 -0
  34. package/vendor/symbiote-node/canvas/NodeViewManager.js +584 -0
  35. package/vendor/symbiote-node/canvas/PinExpansion.js +131 -0
  36. package/vendor/symbiote-node/canvas/PseudoConnection.js +80 -0
  37. package/vendor/symbiote-node/canvas/SubgraphManager.js +201 -0
  38. package/vendor/symbiote-node/canvas/SubgraphRouter.js +443 -0
  39. package/vendor/symbiote-node/canvas/ViewportActions.js +446 -0
  40. package/vendor/symbiote-node/core/Connection.js +45 -0
  41. package/vendor/symbiote-node/core/Editor.js +451 -0
  42. package/vendor/symbiote-node/core/Frame.js +31 -0
  43. package/vendor/symbiote-node/core/GraphMermaid.js +348 -0
  44. package/vendor/symbiote-node/core/GraphText.js +210 -0
  45. package/vendor/symbiote-node/core/Node.js +143 -0
  46. package/vendor/symbiote-node/core/Portal.js +104 -0
  47. package/vendor/symbiote-node/core/Socket.js +185 -0
  48. package/vendor/symbiote-node/core/SubgraphNode.js +125 -0
  49. package/vendor/symbiote-node/index.js +103 -0
  50. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +361 -0
  51. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +332 -0
  52. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +96 -0
  53. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +104 -0
  54. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +133 -0
  55. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +33 -0
  56. package/vendor/symbiote-node/interactions/ConnectFlow.js +307 -0
  57. package/vendor/symbiote-node/interactions/Drag.js +102 -0
  58. package/vendor/symbiote-node/interactions/Selector.js +132 -0
  59. package/vendor/symbiote-node/interactions/SnapGrid.js +65 -0
  60. package/vendor/symbiote-node/interactions/Zoom.js +140 -0
  61. package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +88 -0
  62. package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +254 -0
  63. package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +11 -0
  64. package/vendor/symbiote-node/layout/Layout/Layout.css.js +88 -0
  65. package/vendor/symbiote-node/layout/Layout/Layout.js +622 -0
  66. package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +25 -0
  67. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +293 -0
  68. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +467 -0
  69. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +33 -0
  70. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +46 -0
  71. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +102 -0
  72. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +6 -0
  73. package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +156 -0
  74. package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +250 -0
  75. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +379 -0
  76. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +263 -0
  77. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +20 -0
  78. package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +183 -0
  79. package/vendor/symbiote-node/layout/LayoutTree.js +246 -0
  80. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +43 -0
  81. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +89 -0
  82. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +14 -0
  83. package/vendor/symbiote-node/layout/index.js +16 -0
  84. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +61 -0
  85. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +79 -0
  86. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +19 -0
  87. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +41 -0
  88. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +24 -0
  89. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +16 -0
  90. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +65 -0
  91. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +29 -0
  92. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +13 -0
  93. package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +683 -0
  94. package/vendor/symbiote-node/node/GraphNode/GraphNode.js +92 -0
  95. package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +17 -0
  96. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +25 -0
  97. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +7 -0
  98. package/vendor/symbiote-node/node/PortItem/PortItem.css.js +90 -0
  99. package/vendor/symbiote-node/node/PortItem/PortItem.js +87 -0
  100. package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +10 -0
  101. package/vendor/symbiote-node/package.json +59 -0
  102. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +143 -0
  103. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +131 -0
  104. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +16 -0
  105. package/vendor/symbiote-node/plugins/History.js +384 -0
  106. package/vendor/symbiote-node/plugins/Readonly.js +59 -0
  107. package/vendor/symbiote-node/shapes/CircleShape.js +80 -0
  108. package/vendor/symbiote-node/shapes/CommentShape.js +35 -0
  109. package/vendor/symbiote-node/shapes/DiamondShape.js +115 -0
  110. package/vendor/symbiote-node/shapes/NodeShape.js +80 -0
  111. package/vendor/symbiote-node/shapes/PillShape.js +91 -0
  112. package/vendor/symbiote-node/shapes/RectShape.js +72 -0
  113. package/vendor/symbiote-node/shapes/SVGShape.js +494 -0
  114. package/vendor/symbiote-node/shapes/index.js +53 -0
  115. package/vendor/symbiote-node/themes/Palette.js +32 -0
  116. package/vendor/symbiote-node/themes/Skin.js +113 -0
  117. package/vendor/symbiote-node/themes/Theme.js +84 -0
  118. package/vendor/symbiote-node/themes/carbon.js +137 -0
  119. package/vendor/symbiote-node/themes/dark.js +137 -0
  120. package/vendor/symbiote-node/themes/ebook.js +138 -0
  121. package/vendor/symbiote-node/themes/grey.js +137 -0
  122. package/vendor/symbiote-node/themes/light.js +137 -0
  123. package/vendor/symbiote-node/themes/neon.js +138 -0
  124. package/vendor/symbiote-node/themes/pcb.js +273 -0
  125. package/vendor/symbiote-node/themes/synthwave.js +137 -0
  126. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +86 -0
  127. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +128 -0
  128. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +29 -0
  129. package/web/app.js +6 -5
  130. package/web/components/canvas-graph.js +1666 -0
  131. package/web/components/event-feed/CodeWidget.js +32 -0
  132. package/web/components/event-feed/EventWidget.js +97 -0
  133. package/web/components/event-feed/ListWidget.js +57 -0
  134. package/web/components/event-feed/MiniGraphWidget.js +69 -0
  135. package/web/dashboard.js +1 -1
  136. package/web/index.html +4 -0
  137. package/web/panels/ActionBoard/ActionBoard.js +1 -1
  138. package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -1
  139. package/web/panels/code-viewer.js +50 -15
  140. package/web/panels/dep-graph.js +2712 -7
  141. package/web/panels/file-tree.js +5 -2
  142. package/web/panels/live-monitor.js +75 -3
  143. package/web/style.css +33 -0
  144. package/docs/img/explorer-compact.jpg +0 -0
  145. package/docs/img/explorer-expanded.jpg +0 -0
  146. package/src/.contextignore +0 -22
  147. package/src/.project-graph-cache.json +0 -1
  148. package/src/compact/.project-graph-cache.json +0 -1
  149. package/web/.project-graph-cache.json +0 -1
  150. package/web/panels/SettingsPanel/.project-graph-cache.json +0 -1
@@ -0,0 +1,361 @@
1
+ /**
2
+ * InspectorPanel styles
3
+ * @module symbiote-node/inspector/InspectorPanel.css
4
+ */
5
+ import { css } from '@symbiotejs/symbiote';
6
+
7
+ export const styles = css`
8
+ inspector-panel {
9
+ position: absolute;
10
+ top: 0;
11
+ right: 0;
12
+ bottom: 0;
13
+ width: 280px;
14
+ background: var(--sn-node-bg, #2a2a3e);
15
+ border-left: 1px solid var(--sn-node-border, rgba(255,255,255,0.08));
16
+ display: flex;
17
+ flex-direction: column;
18
+ z-index: 100;
19
+ font-family: var(--sn-font, 'Inter', sans-serif);
20
+ color: var(--sn-text, #d4d4d4);
21
+ overflow-y: auto;
22
+ transition: transform 0.2s ease;
23
+
24
+ &[hidden] {
25
+ display: none;
26
+ }
27
+
28
+ & .insp-resize-handle {
29
+ position: absolute;
30
+ top: 0;
31
+ left: -2px;
32
+ width: 5px;
33
+ height: 100%;
34
+ cursor: col-resize;
35
+ z-index: 110;
36
+ transition: background 0.15s;
37
+
38
+ &:hover, &.dragging {
39
+ background: var(--sn-node-selected, #4a9eff);
40
+ opacity: 0.5;
41
+ }
42
+ }
43
+
44
+ & .insp-header {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 8px;
48
+ padding: 12px 16px;
49
+ font-size: 14px;
50
+ font-weight: 600;
51
+ border-bottom: 1px solid var(--sn-node-border, rgba(255,255,255,0.08));
52
+ background: var(--sn-node-bg, #2a2a3e);
53
+ }
54
+
55
+ & .insp-header .material-symbols-outlined {
56
+ font-size: 18px;
57
+ opacity: 0.7;
58
+ }
59
+
60
+ & .insp-body {
61
+ flex: 1;
62
+ padding: 44px 16px 12px;
63
+ }
64
+
65
+ & .insp-empty {
66
+ display: flex;
67
+ flex-direction: column;
68
+ align-items: center;
69
+ gap: 8px;
70
+ padding: 40px 0;
71
+ color: var(--sn-text-dim, #888);
72
+ font-size: 13px;
73
+
74
+ &[hidden] {
75
+ display: none;
76
+ }
77
+ }
78
+
79
+ & .insp-empty .material-symbols-outlined {
80
+ font-size: 32px;
81
+ opacity: 0.4;
82
+ }
83
+
84
+ & .insp-field {
85
+ margin-bottom: 12px;
86
+ }
87
+
88
+ & .insp-field label {
89
+ display: block;
90
+ font-size: 11px;
91
+ font-weight: 600;
92
+ text-transform: uppercase;
93
+ color: var(--sn-text-dim, #888);
94
+ margin-bottom: 4px;
95
+ letter-spacing: 0.5px;
96
+ }
97
+
98
+ & .insp-value {
99
+ font-size: 13px;
100
+ padding: 6px 8px;
101
+ background: color-mix(in srgb, currentColor 4%, transparent);
102
+ border-radius: 4px;
103
+ }
104
+
105
+ & .insp-tag {
106
+ display: inline-block;
107
+ padding: 2px 8px;
108
+ font-size: 11px;
109
+ border-radius: 4px;
110
+ background: color-mix(in srgb, var(--sn-cat-server, #5cb8ff) 15%, transparent);
111
+ color: var(--sn-cat-server, #5cb8ff);
112
+ }
113
+
114
+ & .insp-mono {
115
+ font-family: 'SF Mono', 'Fira Code', monospace;
116
+ font-size: 11px;
117
+ opacity: 0.6;
118
+ }
119
+
120
+ & .insp-section {
121
+ margin-top: 16px;
122
+ }
123
+
124
+ & .insp-section-title {
125
+ display: flex;
126
+ align-items: center;
127
+ gap: 6px;
128
+ font-size: 12px;
129
+ font-weight: 600;
130
+ color: var(--sn-text-dim, #888);
131
+ margin-bottom: 8px;
132
+ text-transform: uppercase;
133
+ letter-spacing: 0.5px;
134
+ }
135
+
136
+ & .insp-section-title .material-symbols-outlined {
137
+ font-size: 16px;
138
+ opacity: 0.6;
139
+ }
140
+ }
141
+
142
+ .insp-port {
143
+ display: flex;
144
+ align-items: center;
145
+ gap: 8px;
146
+ padding: 4px 8px;
147
+ font-size: 12px;
148
+ border-radius: 4px;
149
+ margin-bottom: 2px;
150
+ }
151
+
152
+ .insp-port:hover {
153
+ background: color-mix(in srgb, currentColor 4%, transparent);
154
+ }
155
+
156
+ .insp-port-dot {
157
+ width: 8px;
158
+ height: 8px;
159
+ border-radius: 50%;
160
+ background: var(--sn-cat-server, #5cb8ff);
161
+ flex-shrink: 0;
162
+ }
163
+
164
+ .insp-port-label {
165
+ flex: 1;
166
+ }
167
+
168
+ .insp-port-type {
169
+ font-size: 10px;
170
+ color: var(--sn-text-dim, #888);
171
+ font-family: 'SF Mono', 'Fira Code', monospace;
172
+ }
173
+
174
+ .insp-ctrl {
175
+ margin-bottom: 12px;
176
+ }
177
+
178
+ .insp-ctrl-label {
179
+ display: block;
180
+ font-size: 11px;
181
+ font-weight: 600;
182
+ text-transform: uppercase;
183
+ color: var(--sn-text-dim, #888);
184
+ margin-bottom: 4px;
185
+ letter-spacing: 0.5px;
186
+ }
187
+
188
+ .insp-ctrl-input-el,
189
+ .insp-ctrl-select {
190
+ width: 100%;
191
+ padding: 6px 8px;
192
+ font-size: 12px;
193
+ font-family: 'SF Mono', 'Fira Code', monospace;
194
+ color: var(--sn-text, #d4d4d4);
195
+ background: color-mix(in srgb, currentColor 6%, transparent);
196
+ border: 1px solid rgba(255,255,255,0.06);
197
+ border-radius: 4px;
198
+ outline: none;
199
+ box-sizing: border-box;
200
+ transition: border-color 0.15s;
201
+
202
+ &:focus {
203
+ border-color: var(--sn-node-selected, #4a9eff);
204
+ }
205
+ }
206
+
207
+ .insp-ctrl-textarea {
208
+ width: 100%;
209
+ padding: 6px 8px;
210
+ font-size: 11px;
211
+ font-family: 'SF Mono', 'Fira Code', monospace;
212
+ color: var(--sn-text, #d4d4d4);
213
+ background: color-mix(in srgb, currentColor 6%, transparent);
214
+ border: 1px solid rgba(255,255,255,0.06);
215
+ border-radius: 4px;
216
+ outline: none;
217
+ resize: vertical;
218
+ min-height: 80px;
219
+ box-sizing: border-box;
220
+ line-height: 1.4;
221
+ transition: border-color 0.15s;
222
+
223
+ &:focus {
224
+ border-color: var(--sn-node-selected, #4a9eff);
225
+ }
226
+ }
227
+
228
+ .insp-ctrl-select {
229
+ appearance: none;
230
+ cursor: pointer;
231
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'%3E%3Cpath fill='%23888' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
232
+ background-repeat: no-repeat;
233
+ background-position: right 6px center;
234
+ padding-right: 24px;
235
+
236
+ & option {
237
+ background: #2a2a3e;
238
+ color: #d4d4d4;
239
+ }
240
+ }
241
+
242
+ .insp-ctrl-toggle {
243
+ position: relative;
244
+ display: inline-block;
245
+ width: 36px;
246
+ height: 20px;
247
+ cursor: pointer;
248
+
249
+ & input {
250
+ opacity: 0;
251
+ width: 0;
252
+ height: 0;
253
+ }
254
+
255
+ & .insp-ctrl-slider {
256
+ position: absolute;
257
+ inset: 0;
258
+ background: rgba(255,255,255,0.1);
259
+ border-radius: 10px;
260
+ transition: background 0.2s;
261
+
262
+ &::before {
263
+ content: '';
264
+ position: absolute;
265
+ width: 14px;
266
+ height: 14px;
267
+ left: 3px;
268
+ bottom: 3px;
269
+ background: #aaa;
270
+ border-radius: 50%;
271
+ transition: transform 0.2s, background 0.2s;
272
+ }
273
+ }
274
+
275
+ & input:checked + .insp-ctrl-slider {
276
+ background: var(--sn-node-selected, #4a9eff);
277
+
278
+ &::before {
279
+ transform: translateX(16px);
280
+ background: white;
281
+ }
282
+ }
283
+ }
284
+
285
+ .insp-enter-btn {
286
+ display: flex;
287
+ align-items: center;
288
+ justify-content: center;
289
+ gap: 8px;
290
+ width: 100%;
291
+ padding: 10px 16px;
292
+ margin-top: 12px;
293
+ border: 1px solid rgba(167, 139, 250, 0.3);
294
+ border-radius: 8px;
295
+ background: linear-gradient(135deg, rgba(167, 139, 250, 0.12) 0%, rgba(109, 40, 217, 0.08) 100%);
296
+ color: var(--sn-subgraph-accent, #a78bfa);
297
+ font-family: var(--sn-font, 'Inter', sans-serif);
298
+ font-size: 13px;
299
+ font-weight: 500;
300
+ cursor: pointer;
301
+ transition: background 0.15s, border-color 0.15s, transform 0.1s;
302
+ }
303
+
304
+ .insp-enter-btn:hover {
305
+ background: linear-gradient(135deg, rgba(167, 139, 250, 0.22) 0%, rgba(109, 40, 217, 0.15) 100%);
306
+ border-color: rgba(167, 139, 250, 0.5);
307
+ }
308
+
309
+ .insp-enter-btn:active {
310
+ transform: scale(0.97);
311
+ }
312
+
313
+ .insp-enter-btn .material-symbols-outlined {
314
+ font-size: 18px;
315
+ }
316
+
317
+ .insp-fire {
318
+ padding: 12px 16px;
319
+ }
320
+
321
+ .insp-fire-btn {
322
+ display: flex;
323
+ align-items: center;
324
+ justify-content: center;
325
+ gap: 6px;
326
+ width: 100%;
327
+ padding: 10px 16px;
328
+ border: 1px solid rgba(76, 175, 80, 0.4);
329
+ border-radius: 8px;
330
+ background: linear-gradient(135deg, rgba(76, 175, 80, 0.15) 0%, rgba(46, 125, 50, 0.10) 100%);
331
+ color: #66bb6a;
332
+ font-family: var(--sn-font, 'Inter', sans-serif);
333
+ font-size: 13px;
334
+ font-weight: 600;
335
+ cursor: pointer;
336
+ transition: background 0.15s, border-color 0.15s, transform 0.1s;
337
+
338
+ &:hover {
339
+ background: linear-gradient(135deg, rgba(76, 175, 80, 0.25) 0%, rgba(46, 125, 50, 0.18) 100%);
340
+ border-color: rgba(76, 175, 80, 0.6);
341
+ }
342
+
343
+ &:active {
344
+ transform: scale(0.97);
345
+ }
346
+
347
+ .material-symbols-outlined {
348
+ font-size: 20px;
349
+ }
350
+ }
351
+
352
+ .insp-template-preview {
353
+ padding: 0 16px 12px;
354
+ border-top: 1px solid rgba(255,255,255,0.06);
355
+ margin-top: 8px;
356
+
357
+ &[hidden] {
358
+ display: none;
359
+ }
360
+ }
361
+ `;
@@ -0,0 +1,332 @@
1
+ /**
2
+ * InspectorPanel — side panel showing selected node properties
3
+ *
4
+ * Displays node label, type, category, inputs/outputs/controls.
5
+ * Updates when selection changes. Shows "No selection" when nothing selected.
6
+ *
7
+ * @module symbiote-node/inspector/InspectorPanel
8
+ */
9
+
10
+ import Symbiote from '@symbiotejs/symbiote';
11
+ import { template, inspPortItemTemplate, inspCtrlItemTemplate } from './InspectorPanel.tpl.js';
12
+ import { styles } from './InspectorPanel.css.js';
13
+ import '../TemplatePreview/TemplatePreview.js';
14
+
15
+ export class InspectorPanel extends Symbiote {
16
+ init$ = {
17
+ visible: false,
18
+ nodeLabel: '',
19
+ nodeType: '',
20
+ nodeCategory: '',
21
+ nodeId: '',
22
+ inputsList: [],
23
+ outputsList: [],
24
+ controlsList: [],
25
+ hasSelection: false,
26
+ isFireable: false,
27
+ isSubgraph: false,
28
+ isTemplateBuilder: false,
29
+ innerNodeCount: 0,
30
+ onFire: () => {
31
+ if (this._currentNodeId) {
32
+ this.dispatchEvent(new CustomEvent('node-fire', {
33
+ detail: { nodeId: this._currentNodeId },
34
+ bubbles: true,
35
+ composed: true,
36
+ }));
37
+ }
38
+ },
39
+ onEnterSubgraph: () => {
40
+ if (this._canvas && this._currentNodeId) {
41
+ this._canvas.drillDown(this._currentNodeId);
42
+ }
43
+ },
44
+ };
45
+
46
+ /** @type {*} */
47
+ _canvas = null;
48
+
49
+ /** @type {string|null} */
50
+ _currentNodeId = null;
51
+
52
+ /**
53
+ * Show inspector for a node
54
+ * @param {*} node - Node instance or node-like object
55
+ */
56
+ inspect(node) {
57
+ if (!node) {
58
+ this.clear();
59
+ return;
60
+ }
61
+
62
+ const inputs = Object.entries(node.inputs).map(([key, port]) => ({
63
+ key,
64
+ label: port.label || key,
65
+ socketType: port.socket?.name || 'any',
66
+ }));
67
+
68
+ const outputs = Object.entries(node.outputs).map(([key, port]) => ({
69
+ key,
70
+ label: port.label || key,
71
+ socketType: port.socket?.name || 'any',
72
+ }));
73
+
74
+ const controls = Object.entries(node.controls).map(([key, ctrl]) => ({
75
+ key,
76
+ label: ctrl.label || key,
77
+ value: ctrl.value ?? '',
78
+ type: ctrl.type || 'text',
79
+ options: (ctrl.options || []).join(','),
80
+ }));
81
+
82
+ const isSubgraph = !!node._isSubgraph;
83
+ let innerNodeCount = 0;
84
+ if (isSubgraph && node.innerEditor) {
85
+ innerNodeCount = node.innerEditor.getNodes().length;
86
+ }
87
+
88
+ this._currentNodeId = node.id;
89
+
90
+ // Check if node is fireable (inject or trigger with testData)
91
+ const driver = node.driver || node._driver;
92
+ const isFireable = !!(driver?.fireable) ||
93
+ node.type === 'debug/inject' ||
94
+ (node.category === 'trigger' || node.category === 'queue');
95
+
96
+ this.set$({
97
+ nodeLabel: node.label,
98
+ nodeType: node.type || 'default',
99
+ nodeCategory: node.category || 'default',
100
+ nodeId: node.id,
101
+ inputsList: inputs,
102
+ outputsList: outputs,
103
+ controlsList: controls,
104
+ hasSelection: true,
105
+ visible: true,
106
+ isFireable,
107
+ isSubgraph,
108
+ isTemplateBuilder: node.type === 'transform/template-builder' || node.type === 'transform/template',
109
+ innerNodeCount,
110
+ });
111
+
112
+ // Populate template-preview with current template value
113
+ if (node.type === 'transform/template-builder' || node.type === 'transform/template') {
114
+ requestAnimationFrame(() => {
115
+ /** @type {*} */
116
+ const preview = this.querySelector('template-preview');
117
+ if (preview && node.params?.template) {
118
+ preview.$.template = node.params.template;
119
+ }
120
+ });
121
+ }
122
+ }
123
+
124
+ /** Clear inspector */
125
+ clear() {
126
+ this._currentNodeId = null;
127
+ this.set$({
128
+ hasSelection: false,
129
+ nodeLabel: '',
130
+ nodeType: '',
131
+ nodeCategory: '',
132
+ nodeId: '',
133
+ inputsList: [],
134
+ outputsList: [],
135
+ controlsList: [],
136
+ isFireable: false,
137
+ isSubgraph: false,
138
+ isTemplateBuilder: false,
139
+ innerNodeCount: 0,
140
+ });
141
+ }
142
+
143
+ renderCallback() {
144
+ this.sub('visible', (val) => {
145
+ this.toggleAttribute('hidden', !val);
146
+ });
147
+
148
+ // Listen for control value changes from InspCtrlItem
149
+ this.addEventListener('ctrl-change', (/** @type {CustomEvent} */ e) => {
150
+ const { key, value } = e.detail;
151
+ if (this._currentNodeId && this._canvas) {
152
+ const editor = this._canvas._editor;
153
+ if (editor) {
154
+ const node = editor.getNode(this._currentNodeId);
155
+ if (node && node.controls[key]) {
156
+ node.controls[key].setValue(value);
157
+ }
158
+ // Also update params for serialization
159
+ if (node && node.params) {
160
+ node.params[key] = value;
161
+ }
162
+ // Update template-preview when template field changes
163
+ if (key === 'template') {
164
+ const preview = this.querySelector('template-preview');
165
+ if (preview) preview.$.template = value;
166
+ }
167
+ }
168
+ }
169
+ });
170
+
171
+ this.sub('hasSelection', (val) => {
172
+ /** @type {HTMLElement} */
173
+ const empty = this.querySelector('.insp-empty');
174
+ /** @type {HTMLElement} */
175
+ const content = this.querySelector('.insp-content');
176
+ if (empty) empty.hidden = val;
177
+ if (content) content.hidden = !val;
178
+ });
179
+
180
+ this.sub('isSubgraph', (val) => {
181
+ /** @type {HTMLElement} */
182
+ const sgSection = this.querySelector('.insp-subgraph');
183
+ if (sgSection) sgSection.hidden = !val;
184
+ });
185
+
186
+ // Resize drag handle
187
+ const STORAGE_KEY = 'sn-inspector-width';
188
+ const handle = this.querySelector('.insp-resize-handle');
189
+
190
+ // Restore saved width
191
+ const saved = localStorage.getItem(STORAGE_KEY);
192
+ if (saved) this.style.width = saved + 'px';
193
+
194
+ if (handle) {
195
+ let startX = 0;
196
+ let startW = 0;
197
+
198
+ /** @param {PointerEvent} e */
199
+ const onMove = (e) => {
200
+ const delta = startX - e.clientX;
201
+ const newWidth = Math.max(200, Math.min(600, startW + delta));
202
+ this.style.width = newWidth + 'px';
203
+ };
204
+
205
+ const onUp = () => {
206
+ handle.classList.remove('dragging');
207
+ localStorage.setItem(STORAGE_KEY, String(this.offsetWidth));
208
+ document.removeEventListener('pointermove', onMove);
209
+ document.removeEventListener('pointerup', onUp);
210
+ };
211
+
212
+ handle.addEventListener('pointerdown', (/** @type {PointerEvent} */ e) => {
213
+ e.preventDefault();
214
+ e.stopPropagation();
215
+ startX = e.clientX;
216
+ startW = this.offsetWidth;
217
+ handle.classList.add('dragging');
218
+ document.addEventListener('pointermove', onMove);
219
+ document.addEventListener('pointerup', onUp);
220
+ });
221
+ }
222
+ }
223
+ }
224
+
225
+ // Port item for itemize
226
+ class InspPortItem extends Symbiote {
227
+ init$ = {
228
+ key: '',
229
+ label: '',
230
+ socketType: '',
231
+ };
232
+ }
233
+
234
+ InspPortItem.template = inspPortItemTemplate;
235
+ InspPortItem.reg('insp-port-item');
236
+
237
+ // Control item for itemize — renders editable form controls
238
+ class InspCtrlItem extends Symbiote {
239
+ init$ = {
240
+ key: '',
241
+ label: '',
242
+ value: '',
243
+ type: 'text',
244
+ options: '',
245
+ };
246
+
247
+ renderCallback() {
248
+ /** @type {HTMLElement} */
249
+ const container = this.querySelector('.insp-ctrl-input');
250
+ if (!container) return;
251
+
252
+ this.sub('type', (type) => {
253
+ this._renderControl(container, type);
254
+ });
255
+ }
256
+
257
+ /**
258
+ * Render the appropriate control element
259
+ * @param {HTMLElement} container
260
+ * @param {string} type
261
+ */
262
+ _renderControl(container, type) {
263
+ container.innerHTML = '';
264
+
265
+ if (type === 'textarea') {
266
+ const el = document.createElement('textarea');
267
+ el.className = 'insp-ctrl-textarea';
268
+ el.value = this.$.value || '';
269
+ el.rows = 6;
270
+ el.spellcheck = false;
271
+ el.addEventListener('input', () => this._emitChange(el.value));
272
+ container.appendChild(el);
273
+ } else if (type === 'boolean') {
274
+ const label = document.createElement('label');
275
+ label.className = 'insp-ctrl-toggle';
276
+ const el = document.createElement('input');
277
+ el.type = 'checkbox';
278
+ el.checked = this.$.value === true || this.$.value === 'true';
279
+ el.addEventListener('change', () => this._emitChange(el.checked));
280
+ const slider = document.createElement('span');
281
+ slider.className = 'insp-ctrl-slider';
282
+ label.appendChild(el);
283
+ label.appendChild(slider);
284
+ container.appendChild(label);
285
+ } else if (type === 'select') {
286
+ const el = document.createElement('select');
287
+ el.className = 'insp-ctrl-select';
288
+ const opts = typeof this.$.options === 'string'
289
+ ? this.$.options.split(',').map((s) => s.trim()).filter(Boolean)
290
+ : [];
291
+ for (const opt of opts) {
292
+ const option = document.createElement('option');
293
+ option.value = opt;
294
+ option.textContent = opt;
295
+ if (opt === String(this.$.value)) option.selected = true;
296
+ el.appendChild(option);
297
+ }
298
+ el.addEventListener('change', () => this._emitChange(el.value));
299
+ container.appendChild(el);
300
+ } else {
301
+ // text / number
302
+ const el = document.createElement('input');
303
+ el.className = 'insp-ctrl-input-el';
304
+ el.type = type === 'number' ? 'number' : 'text';
305
+ el.value = this.$.value ?? '';
306
+ el.spellcheck = false;
307
+ el.addEventListener('input', () => {
308
+ this._emitChange(type === 'number' ? Number(el.value) : el.value);
309
+ });
310
+ container.appendChild(el);
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Emit change event to parent InspectorPanel
316
+ * @param {*} value
317
+ */
318
+ _emitChange(value) {
319
+ this.$.value = value;
320
+ this.dispatchEvent(new CustomEvent('ctrl-change', {
321
+ bubbles: true,
322
+ detail: { key: this.$.key, value },
323
+ }));
324
+ }
325
+ }
326
+
327
+ InspCtrlItem.template = inspCtrlItemTemplate;
328
+ InspCtrlItem.reg('insp-ctrl-item');
329
+
330
+ InspectorPanel.template = template;
331
+ InspectorPanel.rootStyles = styles;
332
+ InspectorPanel.reg('inspector-panel');