symbiote-ui 0.3.0-alpha.4
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/CHANGELOG.md +46 -0
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/canvas/AutoLayout.js +731 -0
- package/canvas/Breadcrumb/Breadcrumb.css.js +75 -0
- package/canvas/Breadcrumb/Breadcrumb.js +96 -0
- package/canvas/Breadcrumb/Breadcrumb.tpl.js +7 -0
- package/canvas/CanvasConnectionRenderer.js +971 -0
- package/canvas/CanvasGraph/CanvasGraph.css.js +29 -0
- package/canvas/CanvasGraph/CanvasGraph.js +1697 -0
- package/canvas/CanvasGraph/CanvasGraphDrawState.js +280 -0
- package/canvas/CanvasGraph/CanvasGraphGeometry.js +194 -0
- package/canvas/CanvasViewport.js +550 -0
- package/canvas/ConnectionRenderer.js +1283 -0
- package/canvas/FlowSimulator.js +326 -0
- package/canvas/ForceLayout.js +226 -0
- package/canvas/ForceWorker.js +1303 -0
- package/canvas/FrameManager.js +223 -0
- package/canvas/GraphExplorerShell/GraphExplorerShell.css.js +136 -0
- package/canvas/GraphExplorerShell/GraphExplorerShell.js +129 -0
- package/canvas/GraphExplorerShell/GraphExplorerShell.tpl.js +12 -0
- package/canvas/GraphTabs/GraphTabs.css.js +101 -0
- package/canvas/GraphTabs/GraphTabs.js +189 -0
- package/canvas/GraphTabs/GraphTabs.tpl.js +12 -0
- package/canvas/LODManager.js +88 -0
- package/canvas/Minimap/Minimap.css.js +73 -0
- package/canvas/Minimap/Minimap.js +210 -0
- package/canvas/Minimap/Minimap.tpl.js +7 -0
- package/canvas/NodeCanvas/NodeCanvas.css.js +398 -0
- package/canvas/NodeCanvas/NodeCanvas.js +1499 -0
- package/canvas/NodeCanvas/NodeCanvas.tpl.js +22 -0
- package/canvas/NodeSearch/NodeSearch.css.js +97 -0
- package/canvas/NodeSearch/NodeSearch.js +140 -0
- package/canvas/NodeSearch/NodeSearch.tpl.js +25 -0
- package/canvas/NodeViewManager.js +748 -0
- package/canvas/PcbRouteDiagnostics.js +463 -0
- package/canvas/PcbRouter.js +1127 -0
- package/canvas/PinExpansion.js +134 -0
- package/canvas/PseudoConnection.js +84 -0
- package/canvas/SelectionSync.js +163 -0
- package/canvas/SubgraphManager.js +203 -0
- package/canvas/SubgraphRouter.js +452 -0
- package/canvas/ViewportActions.js +473 -0
- package/canvas/graph-explorer.js +339 -0
- package/canvas/graph-layout.js +148 -0
- package/canvas/graph-model.js +68 -0
- package/canvas/html-in-canvas.js +202 -0
- package/canvas/project-graph-builder.js +440 -0
- package/canvas/project-graph-model.js +183 -0
- package/chat/ChatComposer/ChatComposer.css.js +652 -0
- package/chat/ChatComposer/ChatComposer.js +304 -0
- package/chat/ChatList/ChatList.css.js +102 -0
- package/chat/ChatList/ChatList.js +99 -0
- package/chat/ChatList/ChatList.tpl.js +20 -0
- package/chat/ChatListItem/ChatListItem.css.js +117 -0
- package/chat/ChatListItem/ChatListItem.js +32 -0
- package/chat/ChatListItem/ChatListItem.tpl.js +17 -0
- package/chat/ChatMessageItem/ChatMessageItem.css.js +628 -0
- package/chat/ChatMessageItem/ChatMessageItem.js +156 -0
- package/chat/ChatSidebar/ChatSidebar.css.js +150 -0
- package/chat/ChatSidebar/ChatSidebar.js +230 -0
- package/chat/ChatSidebar/ChatSidebar.tpl.js +18 -0
- package/chat/ChatSidebar/constants.js +11 -0
- package/chat/ChatSidebarItem/ChatSidebarItem.css.js +445 -0
- package/chat/ChatSidebarItem/ChatSidebarItem.js +304 -0
- package/chat/ChatTranscript/ChatTranscript.css.js +90 -0
- package/chat/ChatTranscript/ChatTranscript.js +244 -0
- package/chat/chat-context.js +123 -0
- package/chat/message-model.js +156 -0
- package/cli.js +20 -0
- package/control/Button/Button.css.js +93 -0
- package/control/Button/Button.js +78 -0
- package/control/Button/Button.tpl.js +3 -0
- package/control/Field/Field.css.js +91 -0
- package/control/Field/Field.js +17 -0
- package/control/Field/Field.tpl.js +3 -0
- package/core/Connection.js +47 -0
- package/core/Editor.js +449 -0
- package/core/Frame.js +33 -0
- package/core/GraphMermaid.js +348 -0
- package/core/GraphText.js +228 -0
- package/core/Node.js +145 -0
- package/core/Portal.js +106 -0
- package/core/Socket.js +187 -0
- package/core/SubgraphNode.js +121 -0
- package/core/base-path.js +55 -0
- package/core/dom-utils.js +14 -0
- package/core/index.js +18 -0
- package/core/local-cache.js +26 -0
- package/core/state-sync.js +227 -0
- package/custom-elements.json +6380 -0
- package/discover.js +240 -0
- package/display/Badge/Badge.css.js +44 -0
- package/display/Badge/Badge.js +17 -0
- package/display/Badge/Badge.tpl.js +3 -0
- package/display/Banner/Banner.css.js +61 -0
- package/display/Banner/Banner.js +17 -0
- package/display/Banner/Banner.tpl.js +3 -0
- package/display/CodeBlock/CodeBlock.css.js +194 -0
- package/display/CodeBlock/CodeBlock.js +220 -0
- package/display/CodeBlock/CodeBlock.tpl.js +11 -0
- package/display/DataTable/DataTable.css.js +101 -0
- package/display/DataTable/DataTable.js +136 -0
- package/display/DataTable/DataTable.tpl.js +13 -0
- package/display/EmptyState/EmptyState.css.js +33 -0
- package/display/EmptyState/EmptyState.js +17 -0
- package/display/EmptyState/EmptyState.tpl.js +3 -0
- package/display/EventFeed/EventFeed.css.js +145 -0
- package/display/EventFeed/EventFeed.js +64 -0
- package/display/EventFeed/EventFeed.tpl.js +14 -0
- package/display/EventFeed/EventFeedItem.js +116 -0
- package/display/EventFeed/EventFeedItem.tpl.js +22 -0
- package/display/LoadingOverlay/LoadingOverlay.css.js +91 -0
- package/display/LoadingOverlay/LoadingOverlay.js +48 -0
- package/display/LoadingOverlay/LoadingOverlay.tpl.js +12 -0
- package/display/Metric/Metric.css.js +60 -0
- package/display/Metric/Metric.js +17 -0
- package/display/Metric/Metric.tpl.js +6 -0
- package/display/OutputGraphPreview/OutputGraphPreview.css.js +122 -0
- package/display/OutputGraphPreview/OutputGraphPreview.js +89 -0
- package/display/OutputGraphPreview/OutputGraphPreview.tpl.js +13 -0
- package/display/OutputListPreview/OutputListPreview.css.js +109 -0
- package/display/OutputListPreview/OutputListPreview.js +77 -0
- package/display/OutputListPreview/OutputListPreview.tpl.js +13 -0
- package/display/SourceEditor/SourceEditor.css.js +39 -0
- package/display/SourceEditor/SourceEditor.js +129 -0
- package/display/SourceEditor/SourceEditor.tpl.js +10 -0
- package/display/SourceViewer/SourceViewer.css.js +80 -0
- package/display/SourceViewer/SourceViewer.js +418 -0
- package/display/SourceViewer/SourceViewer.tpl.js +17 -0
- package/display/StatusRibbon/StatusRibbon.css.js +73 -0
- package/display/StatusRibbon/StatusRibbon.js +87 -0
- package/display/StatusRibbon/StatusRibbon.tpl.js +7 -0
- package/display/event-feed-adapter.js +72 -0
- package/display/format-utils.js +29 -0
- package/display/highlight.js +659 -0
- package/display/icons.js +37 -0
- package/display/markdown-formatter.js +60 -0
- package/display/network-approval-page.js +487 -0
- package/display/output-preview.js +261 -0
- package/effects/CellBg/CellBg.css.js +33 -0
- package/effects/CellBg/CellBg.js +410 -0
- package/effects/CellBg/CellBg.tpl.js +5 -0
- package/graph/canvas-adapter.js +223 -0
- package/graph/graph-algorithms.js +31 -0
- package/graph/index.js +46 -0
- package/graph/model.js +176 -0
- package/graph/project-graph-build.js +66 -0
- package/graph/project-graph-metadata.js +253 -0
- package/graph/project-package.js +128 -0
- package/graph/project-runtime.js +116 -0
- package/graph/project-transaction.js +284 -0
- package/graph/skeleton-utils.js +84 -0
- package/graph/theme-contract.js +36 -0
- package/graph/transaction-parser.js +56 -0
- package/icons/MaterialSymbols.js +69 -0
- package/icons/material-symbols-outlined-400.ttf +0 -0
- package/icons/material-symbols.css +24 -0
- package/index.js +95 -0
- package/inspector/InspectorPanel/InspectorPanel.css.js +375 -0
- package/inspector/InspectorPanel/InspectorPanel.js +368 -0
- package/inspector/InspectorPanel/InspectorPanel.tpl.js +96 -0
- package/inspector/TemplatePreview/TemplatePreview.css.js +104 -0
- package/inspector/TemplatePreview/TemplatePreview.js +145 -0
- package/inspector/TemplatePreview/TemplatePreview.tpl.js +33 -0
- package/interactions/ConnectFlow.js +304 -0
- package/interactions/Drag.js +104 -0
- package/interactions/Selector.js +133 -0
- package/interactions/SnapGrid.js +66 -0
- package/interactions/Zoom.js +139 -0
- package/layout/ActionZone/ActionZone.css.js +88 -0
- package/layout/ActionZone/ActionZone.js +261 -0
- package/layout/ActionZone/ActionZone.tpl.js +11 -0
- package/layout/CrossLayoutPortalBridge/CrossLayoutPortalBridge.js +255 -0
- package/layout/Layout/Layout.css.js +91 -0
- package/layout/Layout/Layout.js +637 -0
- package/layout/Layout/Layout.tpl.js +27 -0
- package/layout/LayoutNode/LayoutNode.css.js +302 -0
- package/layout/LayoutNode/LayoutNode.js +509 -0
- package/layout/LayoutNode/LayoutNode.tpl.js +39 -0
- package/layout/LayoutPreview/LayoutPreview.css.js +46 -0
- package/layout/LayoutPreview/LayoutPreview.js +102 -0
- package/layout/LayoutPreview/LayoutPreview.tpl.js +6 -0
- package/layout/LayoutRouter/LayoutRouter.js +274 -0
- package/layout/LayoutRouter/SectionRegistry.js +135 -0
- package/layout/LayoutRouter/routerSync.js +250 -0
- package/layout/LayoutSidebar/LayoutSidebar.css.js +411 -0
- package/layout/LayoutSidebar/LayoutSidebar.js +368 -0
- package/layout/LayoutSidebar/LayoutSidebar.tpl.js +26 -0
- package/layout/LayoutSidebar/SidebarSection.css.js +20 -0
- package/layout/LayoutSidebar/SidebarSection.js +184 -0
- package/layout/LayoutSidebar/SidebarSection.tpl.js +22 -0
- package/layout/LayoutTree.js +373 -0
- package/layout/PanelMenu/PanelMenu.css.js +43 -0
- package/layout/PanelMenu/PanelMenu.js +95 -0
- package/layout/PanelMenu/PanelMenu.tpl.js +17 -0
- package/layout/ProjectTabs/ProjectTabs.css.js +188 -0
- package/layout/ProjectTabs/ProjectTabs.js +77 -0
- package/layout/ProjectTabs/ProjectTabs.tpl.js +15 -0
- package/layout/index.js +40 -0
- package/list/ListDetailShell/ListDetailShell.css.js +128 -0
- package/list/ListDetailShell/ListDetailShell.js +72 -0
- package/list/ListDetailShell/ListDetailShell.tpl.js +36 -0
- package/list/ListItem/ListItem.css.js +111 -0
- package/list/ListItem/ListItem.js +66 -0
- package/list/ListItem/ListItem.tpl.js +18 -0
- package/locale/index.js +503 -0
- package/manifest/component-registry.js +2446 -0
- package/manifest/graph-schema.js +285 -0
- package/manifest/index.js +6 -0
- package/manifest/project-schema-catalog.js +246 -0
- package/manifest/rule-catalog.js +201 -0
- package/manifest/theme-catalog.js +2149 -0
- package/manifest/ui-schema-catalog.js +334 -0
- package/menu/ContextMenu/ContextMenu.css.js +61 -0
- package/menu/ContextMenu/ContextMenu.js +82 -0
- package/menu/ContextMenu/ContextMenu.tpl.js +19 -0
- package/navigation/QuickOpen/QuickOpen.css.js +92 -0
- package/navigation/QuickOpen/QuickOpen.js +185 -0
- package/navigation/QuickOpen/QuickOpen.tpl.js +15 -0
- package/navigation/quick-open-utils.js +101 -0
- package/node/CtrlItem/CtrlItem.css.js +41 -0
- package/node/CtrlItem/CtrlItem.js +24 -0
- package/node/CtrlItem/CtrlItem.tpl.js +17 -0
- package/node/GraphFrame/GraphFrame.css.js +66 -0
- package/node/GraphFrame/GraphFrame.js +32 -0
- package/node/GraphFrame/GraphFrame.tpl.js +13 -0
- package/node/GraphNode/GraphNode.css.js +815 -0
- package/node/GraphNode/GraphNode.js +173 -0
- package/node/GraphNode/GraphNode.tpl.js +33 -0
- package/node/NodeCallout/NodeCallout.css.js +91 -0
- package/node/NodeCallout/NodeCallout.js +281 -0
- package/node/NodeCallout/NodeCallout.tpl.js +8 -0
- package/node/NodeSocket/NodeSocket.css.js +68 -0
- package/node/NodeSocket/NodeSocket.js +26 -0
- package/node/NodeSocket/NodeSocket.tpl.js +7 -0
- package/node/PortItem/PortItem.css.js +93 -0
- package/node/PortItem/PortItem.js +87 -0
- package/node/PortItem/PortItem.tpl.js +10 -0
- package/package.json +165 -0
- package/palette/PaletteBrowser/PaletteBrowser.css.js +143 -0
- package/palette/PaletteBrowser/PaletteBrowser.js +152 -0
- package/palette/PaletteBrowser/PaletteBrowser.tpl.js +23 -0
- package/plugins/History.js +408 -0
- package/plugins/Readonly.js +60 -0
- package/rules/symbiote-3x.json +170 -0
- package/schemas/component-descriptor-v1.json +91 -0
- package/schemas/component-descriptor-v2.json +145 -0
- package/schemas/graph-model-v1.json +179 -0
- package/schemas/graph-v1.json +91 -0
- package/schemas/project-package-v1.json +102 -0
- package/schemas/project-transaction-v1.json +114 -0
- package/schemas/runtime-ui-v1.json +80 -0
- package/schemas/theme-rule-block-v1.json +73 -0
- package/shapes/CircleShape.js +79 -0
- package/shapes/CommentShape.js +35 -0
- package/shapes/DiamondShape.js +130 -0
- package/shapes/NodeShape.js +79 -0
- package/shapes/PillShape.js +91 -0
- package/shapes/RectShape.js +84 -0
- package/shapes/SVGShape.js +525 -0
- package/shapes/index.js +63 -0
- package/surface/Card/Card.css.js +57 -0
- package/surface/Card/Card.js +17 -0
- package/surface/Card/Card.tpl.js +3 -0
- package/themes/Palette.js +30 -0
- package/themes/Skin.js +113 -0
- package/themes/Theme.js +82 -0
- package/themes/carbon.js +135 -0
- package/themes/dark.js +140 -0
- package/themes/default-dark.js +714 -0
- package/themes/default-provider.css +635 -0
- package/themes/default-provider.js +718 -0
- package/themes/ebook.js +136 -0
- package/themes/grey.js +137 -0
- package/themes/light.js +139 -0
- package/themes/neon.js +138 -0
- package/themes/pcb.js +273 -0
- package/themes/synthwave.js +138 -0
- package/tokens/base.json +29 -0
- package/tokens/themes/carbon.json +11 -0
- package/tokens/themes/dark.json +12 -0
- package/tokens/themes/default-dark.json +1543 -0
- package/tokens/themes/default-provider.json +1543 -0
- package/tokens/themes/ebook.json +11 -0
- package/tokens/themes/grey.json +11 -0
- package/tokens/themes/light.json +12 -0
- package/tokens/themes/neon.json +11 -0
- package/tokens/themes/pcb.json +11 -0
- package/tokens/themes/synthwave.json +11 -0
- package/toolbar/QuickToolbar/QuickToolbar.css.js +152 -0
- package/toolbar/QuickToolbar/QuickToolbar.js +529 -0
- package/toolbar/QuickToolbar/QuickToolbar.tpl.js +34 -0
- package/tree/TreePanel/TreePanel.css.js +112 -0
- package/tree/TreePanel/TreePanel.js +147 -0
- package/tree/TreePanel/TreePanel.tpl.js +18 -0
- package/tree/TreeView/TreeView.css.js +122 -0
- package/tree/TreeView/TreeView.js +365 -0
- package/tree/TreeView/TreeView.tpl.js +10 -0
- package/ui/dialogs.js +221 -0
- package/ui/host-adapters.js +114 -0
- package/ui/index.js +660 -0
- package/ui/locale.js +50 -0
- package/ui/overlay-stack.js +89 -0
- package/ui/shared-styles.js +26 -0
- package/webmcp.js +37 -0
- package/xr/deep-graph.js +646 -0
- package/xr/emulation.js +198 -0
- package/xr/gesture.js +228 -0
- package/xr/html-canvas-renderer.js +472 -0
- package/xr/index.js +15 -0
- package/xr/layout-projection.js +1046 -0
- package/xr/panel-frame.js +128 -0
- package/xr/panel-host.js +267 -0
- package/xr/pointer.js +258 -0
- package/xr/scene-controller.js +242 -0
- package/xr/spatial-scene.js +212 -0
- package/xr/theme-bridge.js +105 -0
- package/xr/three-webxr-adapter.js +3439 -0
- package/xr/webgl-layer-renderer.js +419 -0
- package/xr/webxr.js +679 -0
- package/xr/workbench.js +516 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
export const HTML_IN_CANVAS_RENDERER_NAME = 'html-in-canvas';
|
|
2
|
+
|
|
3
|
+
export const HTML_IN_CANVAS_APIS = Object.freeze({
|
|
4
|
+
layoutSubtreeAttribute: 'layoutsubtree',
|
|
5
|
+
paintEvent: 'paint',
|
|
6
|
+
canvas2dDraw: 'drawElementImage',
|
|
7
|
+
elementCapture: 'captureElementImage',
|
|
8
|
+
webglTextureUpload: 'texElementImage2D',
|
|
9
|
+
webgpuTextureCopy: 'copyElementImageToTexture',
|
|
10
|
+
elementTransform: 'getElementTransform',
|
|
11
|
+
requestPaint: 'requestPaint',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const HTML_IN_CANVAS_RENDERER = Object.freeze({
|
|
15
|
+
name: HTML_IN_CANVAS_RENDERER_NAME,
|
|
16
|
+
status: 'experimental',
|
|
17
|
+
specifier: 'symbiote-ui/ui',
|
|
18
|
+
description: 'Experimental DOM-to-canvas renderer adapter for packaged Chromium hosts and origin-trial browsers.',
|
|
19
|
+
modes: ['canvas2d', 'offscreen2d', 'webgl', 'webgpu'],
|
|
20
|
+
fallback: 'dom-overlay',
|
|
21
|
+
requiredCanvasAttribute: HTML_IN_CANVAS_APIS.layoutSubtreeAttribute,
|
|
22
|
+
capabilities: [
|
|
23
|
+
'dom-content-in-canvas',
|
|
24
|
+
'interactive-canvas-ui',
|
|
25
|
+
'accessible-canvas-ui',
|
|
26
|
+
'text-selection',
|
|
27
|
+
'native-form-controls',
|
|
28
|
+
'offscreen-worker-snapshots',
|
|
29
|
+
'canvas-texture-upload',
|
|
30
|
+
'feature-detected-fallback',
|
|
31
|
+
],
|
|
32
|
+
apis: HTML_IN_CANVAS_APIS,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function hasFn(source, name) {
|
|
36
|
+
return typeof source?.[name] === 'function';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getPrototype(target, name) {
|
|
40
|
+
return target?.[name]?.prototype || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function supportsLayoutSubtree(target) {
|
|
44
|
+
let canvasProto = getPrototype(target, 'HTMLCanvasElement');
|
|
45
|
+
if (canvasProto && 'layoutSubtree' in canvasProto) return true;
|
|
46
|
+
let canvas = target?.document?.createElement?.('canvas');
|
|
47
|
+
return Boolean(canvas && 'layoutSubtree' in canvas);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getHtmlInCanvasSupport(target = globalThis) {
|
|
51
|
+
let canvas2dProto = getPrototype(target, 'CanvasRenderingContext2D');
|
|
52
|
+
let offscreen2dProto = getPrototype(target, 'OffscreenCanvasRenderingContext2D');
|
|
53
|
+
let webglProto = getPrototype(target, 'WebGLRenderingContext');
|
|
54
|
+
let webgl2Proto = getPrototype(target, 'WebGL2RenderingContext');
|
|
55
|
+
let gpuQueueProto = getPrototype(target, 'GPUQueue');
|
|
56
|
+
let canvasProto = getPrototype(target, 'HTMLCanvasElement');
|
|
57
|
+
let offscreenCanvasProto = getPrototype(target, 'OffscreenCanvas');
|
|
58
|
+
|
|
59
|
+
let canvas2d = hasFn(canvas2dProto, HTML_IN_CANVAS_APIS.canvas2dDraw);
|
|
60
|
+
let offscreen2d = hasFn(offscreen2dProto, HTML_IN_CANVAS_APIS.canvas2dDraw);
|
|
61
|
+
let webgl = hasFn(webglProto, HTML_IN_CANVAS_APIS.webglTextureUpload) ||
|
|
62
|
+
hasFn(webgl2Proto, HTML_IN_CANVAS_APIS.webglTextureUpload);
|
|
63
|
+
let webgpu = hasFn(gpuQueueProto, HTML_IN_CANVAS_APIS.webgpuTextureCopy);
|
|
64
|
+
let elementCapture = hasFn(canvasProto, HTML_IN_CANVAS_APIS.elementCapture);
|
|
65
|
+
let elementTransform = hasFn(canvasProto, HTML_IN_CANVAS_APIS.elementTransform) ||
|
|
66
|
+
hasFn(offscreenCanvasProto, HTML_IN_CANVAS_APIS.elementTransform);
|
|
67
|
+
let requestPaint = hasFn(canvasProto, HTML_IN_CANVAS_APIS.requestPaint);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
name: HTML_IN_CANVAS_RENDERER_NAME,
|
|
71
|
+
status: 'experimental',
|
|
72
|
+
supported: canvas2d || offscreen2d || webgl || webgpu,
|
|
73
|
+
fallback: HTML_IN_CANVAS_RENDERER.fallback,
|
|
74
|
+
modes: {
|
|
75
|
+
canvas2d,
|
|
76
|
+
offscreen2d,
|
|
77
|
+
webgl,
|
|
78
|
+
webgpu,
|
|
79
|
+
},
|
|
80
|
+
apis: {
|
|
81
|
+
...HTML_IN_CANVAS_APIS,
|
|
82
|
+
canvas2dDrawAvailable: canvas2d,
|
|
83
|
+
offscreen2dDrawAvailable: offscreen2d,
|
|
84
|
+
webglTextureUploadAvailable: webgl,
|
|
85
|
+
webgpuTextureCopyAvailable: webgpu,
|
|
86
|
+
elementCaptureAvailable: elementCapture,
|
|
87
|
+
elementTransformAvailable: elementTransform,
|
|
88
|
+
requestPaintAvailable: requestPaint,
|
|
89
|
+
layoutSubtreeAvailable: supportsLayoutSubtree(target),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function setupHtmlInCanvas(canvas) {
|
|
95
|
+
if (!canvas || typeof canvas.setAttribute !== 'function') return false;
|
|
96
|
+
canvas.setAttribute(HTML_IN_CANVAS_APIS.layoutSubtreeAttribute, '');
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function requestHtmlInCanvasPaint(canvas) {
|
|
101
|
+
if (hasFn(canvas, HTML_IN_CANVAS_APIS.requestPaint)) {
|
|
102
|
+
canvas.requestPaint();
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function getHtmlInCanvasChangedElements(event) {
|
|
109
|
+
if (!event || !Array.isArray(event.changedElements)) return [];
|
|
110
|
+
return event.changedElements;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function captureHtmlElementImage(canvas, element) {
|
|
114
|
+
if (!hasFn(canvas, HTML_IN_CANVAS_APIS.elementCapture)) {
|
|
115
|
+
return { captured: false, reason: 'unsupported' };
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
captured: true,
|
|
119
|
+
elementImage: canvas[HTML_IN_CANVAS_APIS.elementCapture](element),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function closeHtmlElementImage(elementImage) {
|
|
124
|
+
if (!hasFn(elementImage, 'close')) return false;
|
|
125
|
+
elementImage.close();
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function drawHtmlElement2d(ctx, element, options = {}) {
|
|
130
|
+
if (!hasFn(ctx, HTML_IN_CANVAS_APIS.canvas2dDraw)) {
|
|
131
|
+
return { rendered: false, mode: 'canvas2d', reason: 'unsupported' };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let args = Array.isArray(options.rect)
|
|
135
|
+
? [element, ...options.rect.map((value) => Number(value))]
|
|
136
|
+
: [element, Number(options.x ?? 0), Number(options.y ?? 0)];
|
|
137
|
+
if (options.width != null && options.height != null) {
|
|
138
|
+
args.push(Number(options.width), Number(options.height));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let transform = ctx[HTML_IN_CANVAS_APIS.canvas2dDraw](...args);
|
|
142
|
+
if (options.syncTransform !== false && transform && element?.style) {
|
|
143
|
+
element.style.transform = transform.toString();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { rendered: true, mode: 'canvas2d', transform };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function uploadHtmlElementToWebGLTexture(gl, element, options = {}) {
|
|
150
|
+
if (!hasFn(gl, HTML_IN_CANVAS_APIS.webglTextureUpload)) {
|
|
151
|
+
return { rendered: false, mode: 'webgl', reason: 'unsupported' };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let target = options.target ?? gl.TEXTURE_2D;
|
|
155
|
+
let level = options.level ?? 0;
|
|
156
|
+
let internalFormat = options.internalFormat ?? gl.RGBA;
|
|
157
|
+
let format = options.format ?? gl.RGBA;
|
|
158
|
+
let type = options.type ?? gl.UNSIGNED_BYTE;
|
|
159
|
+
gl[HTML_IN_CANVAS_APIS.webglTextureUpload](target, level, internalFormat, format, type, element);
|
|
160
|
+
return { rendered: true, mode: 'webgl' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function copyHtmlElementToWebGPUTexture(queue, element, options = {}) {
|
|
164
|
+
if (!hasFn(queue, HTML_IN_CANVAS_APIS.webgpuTextureCopy)) {
|
|
165
|
+
return { rendered: false, mode: 'webgpu', reason: 'unsupported' };
|
|
166
|
+
}
|
|
167
|
+
if (!options.destination) {
|
|
168
|
+
return { rendered: false, mode: 'webgpu', reason: 'missing-destination' };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let args = [element, options.destination];
|
|
172
|
+
if (options.copySize) args.push(options.copySize);
|
|
173
|
+
queue[HTML_IN_CANVAS_APIS.webgpuTextureCopy](...args);
|
|
174
|
+
return { rendered: true, mode: 'webgpu' };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function getHtmlElementCanvasTransform(canvas, element, matrix) {
|
|
178
|
+
if (!hasFn(canvas, HTML_IN_CANVAS_APIS.elementTransform)) return null;
|
|
179
|
+
return canvas[HTML_IN_CANVAS_APIS.elementTransform](element, matrix) || null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function createHtmlInCanvasAdapter(options = {}) {
|
|
183
|
+
let target = options.globalThis || globalThis;
|
|
184
|
+
let support = getHtmlInCanvasSupport(target);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
...HTML_IN_CANVAS_RENDERER,
|
|
188
|
+
support,
|
|
189
|
+
canRender(mode = 'canvas2d') {
|
|
190
|
+
return Boolean(support.modes[mode]);
|
|
191
|
+
},
|
|
192
|
+
setupCanvas: setupHtmlInCanvas,
|
|
193
|
+
requestPaint: requestHtmlInCanvasPaint,
|
|
194
|
+
getChangedElements: getHtmlInCanvasChangedElements,
|
|
195
|
+
captureElementImage: captureHtmlElementImage,
|
|
196
|
+
closeElementImage: closeHtmlElementImage,
|
|
197
|
+
draw2d: drawHtmlElement2d,
|
|
198
|
+
uploadWebGLTexture: uploadHtmlElementToWebGLTexture,
|
|
199
|
+
copyWebGPUTexture: copyHtmlElementToWebGPUTexture,
|
|
200
|
+
getElementTransform: getHtmlElementCanvasTransform,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { NodeEditor } from '../core/Editor.js'
|
|
2
|
+
import { Node } from '../core/Node.js'
|
|
3
|
+
import { SubgraphNode } from '../core/SubgraphNode.js'
|
|
4
|
+
import { Connection } from '../core/Connection.js'
|
|
5
|
+
import { Socket, Input, Output } from '../core/Socket.js'
|
|
6
|
+
import { computeAutoLayout } from './AutoLayout.js'
|
|
7
|
+
import { baseName, collectSkeletonFiles, dirOf, resolveImport } from '../graph/skeleton-utils.js'
|
|
8
|
+
|
|
9
|
+
// ── Socket types (for wire coloring) ──
|
|
10
|
+
const S_IMPORT = new Socket('import')
|
|
11
|
+
S_IMPORT.color = 'var(--sn-dot-input)'
|
|
12
|
+
const S_EXPORT = new Socket('export')
|
|
13
|
+
S_EXPORT.color = 'var(--sn-dot-output)'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build a file-level graph from skeleton data.
|
|
17
|
+
* Each file becomes a Node, each import relationship becomes a Connection.
|
|
18
|
+
*
|
|
19
|
+
* @param {object} skeleton - skeleton from get_skeleton
|
|
20
|
+
* @returns {{ editor: NodeEditor, fileMap: Map<string, string> }}
|
|
21
|
+
*/
|
|
22
|
+
function buildFileGraph(skeleton) {
|
|
23
|
+
const editor = new NodeEditor()
|
|
24
|
+
const fileMap = new Map() // filePath → nodeId
|
|
25
|
+
const dirMap = new Map() // dirPath → nodeId (hub nodes)
|
|
26
|
+
|
|
27
|
+
const { files, assetFiles } = collectSkeletonFiles(skeleton)
|
|
28
|
+
|
|
29
|
+
if (files.size === 0) return { editor, fileMap }
|
|
30
|
+
|
|
31
|
+
// Group files by directory
|
|
32
|
+
const dirFiles = new Map()
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
const dir = dirOf(file)
|
|
35
|
+
if (!dirFiles.has(dir)) dirFiles.set(dir, [])
|
|
36
|
+
dirFiles.get(dir).push(file)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Phase 2 candidate: create directory hub nodes when LOD zoom expansion is ready.
|
|
40
|
+
// Hub nodes without connections create disconnected groups — skip for now
|
|
41
|
+
|
|
42
|
+
// Create file nodes (standard HTML nodes with icons)
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
const dir = dirOf(file)
|
|
45
|
+
const label = baseName(file)
|
|
46
|
+
const isAsset = assetFiles.has(file)
|
|
47
|
+
const node = new Node(label, {
|
|
48
|
+
type: isAsset ? 'asset' : 'file',
|
|
49
|
+
category: isAsset ? 'asset' : 'file',
|
|
50
|
+
})
|
|
51
|
+
node.params = { path: file, dir }
|
|
52
|
+
|
|
53
|
+
// Every file has one output (exports) and one input (imports)
|
|
54
|
+
node.addOutput('out', new Output(S_EXPORT, ''))
|
|
55
|
+
node.addInput('in', new Input(S_IMPORT, ''))
|
|
56
|
+
|
|
57
|
+
editor.addNode(node)
|
|
58
|
+
fileMap.set(file, node.id)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Build import edges from skeleton.I (file-level import map)
|
|
62
|
+
// skeleton.I[file] = [source1, source2, ...]
|
|
63
|
+
const edgesAdded = new Set()
|
|
64
|
+
for (const [srcFile, sources] of Object.entries(skeleton.I || {})) {
|
|
65
|
+
const srcId = fileMap.get(srcFile)
|
|
66
|
+
if (!srcId) continue
|
|
67
|
+
|
|
68
|
+
for (const impPath of sources) {
|
|
69
|
+
// Skip node builtins and external packages
|
|
70
|
+
if (impPath.startsWith('node:') || (!impPath.startsWith('.') && !impPath.startsWith('/'))) continue
|
|
71
|
+
|
|
72
|
+
const targetFile = resolveImport(impPath, srcFile, files)
|
|
73
|
+
if (!targetFile) continue
|
|
74
|
+
|
|
75
|
+
const tgtId = fileMap.get(targetFile)
|
|
76
|
+
if (!tgtId || tgtId === srcId) continue
|
|
77
|
+
|
|
78
|
+
const edgeKey = `${srcId}->${tgtId}`
|
|
79
|
+
if (edgesAdded.has(edgeKey)) continue
|
|
80
|
+
edgesAdded.add(edgeKey)
|
|
81
|
+
|
|
82
|
+
const srcNode = editor.getNode(srcId)
|
|
83
|
+
const tgtNode = editor.getNode(tgtId)
|
|
84
|
+
try {
|
|
85
|
+
const conn = new Connection(srcNode, 'out', tgtNode, 'in')
|
|
86
|
+
// Phase 3: tag cross-directory connections as "via"
|
|
87
|
+
const srcDir = dirOf(srcFile)
|
|
88
|
+
const tgtDir = dirOf(targetFile)
|
|
89
|
+
if (srcDir !== tgtDir) {
|
|
90
|
+
conn._via = true
|
|
91
|
+
conn._srcDir = srcDir
|
|
92
|
+
conn._tgtDir = tgtDir
|
|
93
|
+
}
|
|
94
|
+
editor.addConnection(conn)
|
|
95
|
+
} catch {
|
|
96
|
+
// Skip invalid connections
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Hub node: find node with highest connectivity → module category
|
|
102
|
+
const connCounts = new Map()
|
|
103
|
+
for (const conn of editor.getConnections()) {
|
|
104
|
+
connCounts.set(conn.from, (connCounts.get(conn.from) || 0) + 1)
|
|
105
|
+
connCounts.set(conn.to, (connCounts.get(conn.to) || 0) + 1)
|
|
106
|
+
}
|
|
107
|
+
let maxConns = 0
|
|
108
|
+
let hubId = null
|
|
109
|
+
for (const [nodeId, count] of connCounts) {
|
|
110
|
+
if (count > maxConns) {
|
|
111
|
+
maxConns = count
|
|
112
|
+
hubId = nodeId
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (hubId) {
|
|
116
|
+
const hubNode = editor.getNode(hubId)
|
|
117
|
+
if (hubNode && hubNode.options) {
|
|
118
|
+
hubNode.options.category = 'module'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Build Reverse ID Lookup ──
|
|
123
|
+
const idToPath = new Map()
|
|
124
|
+
for (const [path, id] of fileMap.entries()) idToPath.set(id, path)
|
|
125
|
+
|
|
126
|
+
return { editor, fileMap, dirMap, dirFiles, idToPath }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build a hierarchical SubgraphNode graph:
|
|
131
|
+
* Level 0: directories (SubgraphNode)
|
|
132
|
+
* Level 1: files inside directories (SubgraphNode or Node)
|
|
133
|
+
* Level 2: functions/exports inside files (Node)
|
|
134
|
+
*
|
|
135
|
+
* @param {object} skeleton
|
|
136
|
+
* @returns {{ editor: NodeEditor, fileMap: Map<string, string> }}
|
|
137
|
+
*/
|
|
138
|
+
function buildStructuredGraph(skeleton) {
|
|
139
|
+
const editor = new NodeEditor()
|
|
140
|
+
const fileMap = new Map()
|
|
141
|
+
const symbolMap = new Map()
|
|
142
|
+
const L = skeleton.L || {} // legend: abbreviation → full name
|
|
143
|
+
const N = skeleton.n || {} // classes: className → { f, m, ... }
|
|
144
|
+
|
|
145
|
+
// Build class-name set for classification
|
|
146
|
+
const classNames = new Set(Object.keys(N))
|
|
147
|
+
// Map file → set of class names defined in it
|
|
148
|
+
const fileClasses = new Map()
|
|
149
|
+
for (const [className, data] of Object.entries(N)) {
|
|
150
|
+
if (data.f) {
|
|
151
|
+
if (!fileClasses.has(data.f)) fileClasses.set(data.f, new Set())
|
|
152
|
+
fileClasses.get(data.f).add(className)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const { files, assetFiles } = collectSkeletonFiles(skeleton)
|
|
157
|
+
|
|
158
|
+
if (files.size === 0) return { editor, fileMap }
|
|
159
|
+
|
|
160
|
+
// Group files by directory
|
|
161
|
+
const dirFiles = new Map()
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
const dir = dirOf(file)
|
|
164
|
+
if (!dirFiles.has(dir)) dirFiles.set(dir, [])
|
|
165
|
+
dirFiles.get(dir).push(file)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Classify a file based on its content and name
|
|
170
|
+
* @param {string} file
|
|
171
|
+
* @returns {string} category
|
|
172
|
+
*/
|
|
173
|
+
function classifyFile(file) {
|
|
174
|
+
if (assetFiles.has(file)) return 'asset'
|
|
175
|
+
const name = baseName(file).toLowerCase()
|
|
176
|
+
const classes = fileClasses.get(file)
|
|
177
|
+
if (classes && classes.size > 0) return 'class'
|
|
178
|
+
if (name === 'index.js' || name === 'index.mjs') return 'module'
|
|
179
|
+
if (name.includes('test') || name.includes('spec')) return 'control'
|
|
180
|
+
if (name.includes('config') || name.includes('.json')) return 'data'
|
|
181
|
+
return 'file'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolve export abbreviation to full name
|
|
186
|
+
* @param {string} abbr
|
|
187
|
+
* @returns {string}
|
|
188
|
+
*/
|
|
189
|
+
function resolveName(abbr) {
|
|
190
|
+
return L[abbr] || abbr
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── Phase 1: Create all Directory SubgraphNodes (without nesting yet) ──
|
|
194
|
+
const dirNodeMap = new Map() // dirPath → nodeId
|
|
195
|
+
const dirSubgraphs = new Map() // dirPath → SubgraphNode instance
|
|
196
|
+
|
|
197
|
+
// Sort directories by depth (shortest first) so parents are created before children
|
|
198
|
+
const sortedDirs = [...dirFiles.keys()].sort((a, b) => {
|
|
199
|
+
const dA = a.split('/').filter(Boolean).length
|
|
200
|
+
const dB = b.split('/').filter(Boolean).length
|
|
201
|
+
return dA - dB || a.localeCompare(b)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
for (const dir of sortedDirs) {
|
|
205
|
+
const dirFileList = dirFiles.get(dir)
|
|
206
|
+
|
|
207
|
+
// Root directory './' is NOT a node — its contents go directly into the root editor
|
|
208
|
+
const isRoot = (dir === './')
|
|
209
|
+
const targetEditor = isRoot ? editor : null
|
|
210
|
+
|
|
211
|
+
let dirSubgraph = null
|
|
212
|
+
let innerEditor
|
|
213
|
+
|
|
214
|
+
if (isRoot) {
|
|
215
|
+
innerEditor = editor
|
|
216
|
+
} else {
|
|
217
|
+
const dirLabel = dir.replace(/\/$/, '').split('/').pop() || 'root'
|
|
218
|
+
dirSubgraph = new SubgraphNode(dirLabel, {
|
|
219
|
+
category: 'directory',
|
|
220
|
+
})
|
|
221
|
+
dirSubgraph.params = { path: dir, isDirectory: true }
|
|
222
|
+
dirSubgraph.addOutput('out', new Output(S_EXPORT, ''))
|
|
223
|
+
dirSubgraph.addInput('in', new Input(S_IMPORT, ''))
|
|
224
|
+
innerEditor = dirSubgraph.getInnerEditor()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ── File nodes inside this directory ──
|
|
228
|
+
for (const file of dirFileList) {
|
|
229
|
+
const fileLabel = baseName(file)
|
|
230
|
+
const exports = skeleton.X?.[file] || []
|
|
231
|
+
const fileCategory = classifyFile(file)
|
|
232
|
+
const classes = fileClasses.get(file)
|
|
233
|
+
|
|
234
|
+
let fileNode
|
|
235
|
+
if (exports.length > 0) {
|
|
236
|
+
fileNode = new SubgraphNode(fileLabel, {
|
|
237
|
+
category: fileCategory,
|
|
238
|
+
})
|
|
239
|
+
fileNode.params = { path: file, dir, calculatedHeight: 60 + exports.length * 50 }
|
|
240
|
+
|
|
241
|
+
const fileInnerEditor = fileNode.getInnerEditor()
|
|
242
|
+
for (const abbr of exports) {
|
|
243
|
+
const abbrId = typeof abbr === 'object' ? abbr.id : abbr
|
|
244
|
+
const fullName = resolveName(abbrId)
|
|
245
|
+
const isClass = classes && classes.has(fullName)
|
|
246
|
+
const fnNode = new Node(fullName, {
|
|
247
|
+
type: isClass ? 'class' : 'function',
|
|
248
|
+
category: isClass ? 'class' : 'function',
|
|
249
|
+
})
|
|
250
|
+
fnNode.params = { name: fullName, file }
|
|
251
|
+
symbolMap.set(fnNode.id, fnNode.params)
|
|
252
|
+
fileInnerEditor.addNode(fnNode)
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
fileNode = new Node(fileLabel, {
|
|
256
|
+
type: 'file',
|
|
257
|
+
category: fileCategory,
|
|
258
|
+
})
|
|
259
|
+
fileNode.params = { path: file, dir }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
fileNode.addOutput('out', new Output(S_EXPORT, ''))
|
|
263
|
+
fileNode.addInput('in', new Input(S_IMPORT, ''))
|
|
264
|
+
|
|
265
|
+
innerEditor.addNode(fileNode)
|
|
266
|
+
fileMap.set(file, fileNode.id)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── File-level import edges within this directory ──
|
|
270
|
+
const edgesAdded = new Set()
|
|
271
|
+
for (const [srcFile, sources] of Object.entries(skeleton.I || {})) {
|
|
272
|
+
const srcId = fileMap.get(srcFile)
|
|
273
|
+
if (!srcId) continue
|
|
274
|
+
const srcDir = dirOf(srcFile)
|
|
275
|
+
if (srcDir !== dir) continue
|
|
276
|
+
|
|
277
|
+
for (const impPath of sources) {
|
|
278
|
+
if (impPath.startsWith('node:') || (!impPath.startsWith('.') && !impPath.startsWith('/'))) continue
|
|
279
|
+
const targetFile = resolveImport(impPath, srcFile, files)
|
|
280
|
+
if (!targetFile) continue
|
|
281
|
+
|
|
282
|
+
const tgtId = fileMap.get(targetFile)
|
|
283
|
+
if (!tgtId || tgtId === srcId) continue
|
|
284
|
+
if (dirOf(targetFile) !== dir) continue
|
|
285
|
+
|
|
286
|
+
const edgeKey = `${srcId}->${tgtId}`
|
|
287
|
+
if (edgesAdded.has(edgeKey)) continue
|
|
288
|
+
edgesAdded.add(edgeKey)
|
|
289
|
+
|
|
290
|
+
const srcNode = innerEditor.getNode(srcId)
|
|
291
|
+
const tgtNode = innerEditor.getNode(tgtId)
|
|
292
|
+
if (srcNode && tgtNode) {
|
|
293
|
+
try {
|
|
294
|
+
innerEditor.addConnection(new Connection(srcNode, 'out', tgtNode, 'in'))
|
|
295
|
+
} catch { /* skip */ }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (dirSubgraph) {
|
|
301
|
+
dirSubgraphs.set(dir, dirSubgraph)
|
|
302
|
+
dirNodeMap.set(dir, dirSubgraph.id)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ── Phase 2: Nest child directories inside parent directories ──
|
|
307
|
+
// Root './' is not a node, so its children go directly into root editor.
|
|
308
|
+
for (const dir of sortedDirs) {
|
|
309
|
+
if (dir === './') continue // root dir contents already in root editor
|
|
310
|
+
const dirSubgraph = dirSubgraphs.get(dir)
|
|
311
|
+
if (!dirSubgraph) continue
|
|
312
|
+
|
|
313
|
+
// Find parent directory
|
|
314
|
+
const segments = dir.replace(/\/$/, '').split('/')
|
|
315
|
+
segments.pop()
|
|
316
|
+
|
|
317
|
+
let parentDir = null
|
|
318
|
+
while (segments.length > 0) {
|
|
319
|
+
const candidate = segments.join('/') + '/'
|
|
320
|
+
if (dirSubgraphs.has(candidate)) {
|
|
321
|
+
parentDir = candidate
|
|
322
|
+
break
|
|
323
|
+
}
|
|
324
|
+
segments.pop()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (parentDir) {
|
|
328
|
+
// Nest inside parent's inner editor
|
|
329
|
+
const parentSubgraph = dirSubgraphs.get(parentDir)
|
|
330
|
+
parentSubgraph.getInnerEditor().addNode(dirSubgraph)
|
|
331
|
+
} else {
|
|
332
|
+
// No parent (or parent is './') → add to root editor
|
|
333
|
+
editor.addNode(dirSubgraph)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ── Cross-directory edges ──
|
|
338
|
+
// Edges between directories that share the same parent go into that parent's inner editor.
|
|
339
|
+
// Edges between top-level directories go into the root editor.
|
|
340
|
+
const crossEdges = new Set()
|
|
341
|
+
for (const [srcFile, sources] of Object.entries(skeleton.I || {})) {
|
|
342
|
+
const srcDir = dirOf(srcFile)
|
|
343
|
+
const srcDirId = dirNodeMap.get(srcDir)
|
|
344
|
+
if (!srcDirId) continue
|
|
345
|
+
|
|
346
|
+
for (const impPath of sources) {
|
|
347
|
+
if (impPath.startsWith('node:') || (!impPath.startsWith('.') && !impPath.startsWith('/'))) continue
|
|
348
|
+
const targetFile = resolveImport(impPath, srcFile, files)
|
|
349
|
+
if (!targetFile) continue
|
|
350
|
+
|
|
351
|
+
const tgtDir = dirOf(targetFile)
|
|
352
|
+
if (tgtDir === srcDir) continue
|
|
353
|
+
|
|
354
|
+
const tgtDirId = dirNodeMap.get(tgtDir)
|
|
355
|
+
if (!tgtDirId || tgtDirId === srcDirId) continue
|
|
356
|
+
|
|
357
|
+
const edgeKey = `${srcDirId}->${tgtDirId}`
|
|
358
|
+
if (crossEdges.has(edgeKey)) continue
|
|
359
|
+
crossEdges.add(edgeKey)
|
|
360
|
+
|
|
361
|
+
// Find the common parent editor that contains BOTH directory nodes
|
|
362
|
+
// Walk up both paths to find shared ancestor
|
|
363
|
+
const srcSegments = srcDir.replace(/\/$/, '').split('/')
|
|
364
|
+
const tgtSegments = tgtDir.replace(/\/$/, '').split('/')
|
|
365
|
+
|
|
366
|
+
// Find common prefix
|
|
367
|
+
let commonLen = 0
|
|
368
|
+
while (commonLen < srcSegments.length && commonLen < tgtSegments.length &&
|
|
369
|
+
srcSegments[commonLen] === tgtSegments[commonLen]) {
|
|
370
|
+
commonLen++
|
|
371
|
+
}
|
|
372
|
+
const commonPath = commonLen > 0 ? srcSegments.slice(0, commonLen).join('/') + '/' : null
|
|
373
|
+
|
|
374
|
+
// The editor that holds both nodes is the common parent's inner editor,
|
|
375
|
+
// or the root editor if they share no parent.
|
|
376
|
+
let targetEditor = editor // default: root editor
|
|
377
|
+
if (commonPath && dirSubgraphs.has(commonPath)) {
|
|
378
|
+
targetEditor = dirSubgraphs.get(commonPath).getInnerEditor()
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const srcNode = targetEditor.getNode(srcDirId)
|
|
382
|
+
const tgtNode = targetEditor.getNode(tgtDirId)
|
|
383
|
+
if (srcNode && tgtNode) {
|
|
384
|
+
try {
|
|
385
|
+
targetEditor.addConnection(new Connection(srcNode, 'out', tgtNode, 'in'))
|
|
386
|
+
} catch { /* skip */ }
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ── Pre-compute inner positions for drill-down (recursive) ──
|
|
392
|
+
const symbolNodes = [] // Track internal symbol nodes for idToPath linking
|
|
393
|
+
|
|
394
|
+
function computeInnerPositions(subgraph) {
|
|
395
|
+
if (!subgraph._isSubgraph) return
|
|
396
|
+
const inner = subgraph.getInnerEditor()
|
|
397
|
+
const innerPos = computeAutoLayout(inner, { nodeHeight: 80, gapY: 100 })
|
|
398
|
+
subgraph.setInnerPositions(innerPos)
|
|
399
|
+
|
|
400
|
+
let minX = 0, maxX = 260
|
|
401
|
+
let minY = 0, maxY = 60
|
|
402
|
+
|
|
403
|
+
for (const pos of Object.values(innerPos)) {
|
|
404
|
+
if (pos.x < minX) minX = pos.x
|
|
405
|
+
if (pos.x + 260 > maxX) maxX = pos.x + 260
|
|
406
|
+
if (pos.y < minY) minY = pos.y
|
|
407
|
+
if (pos.y + 60 > maxY) maxY = pos.y + 60
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
subgraph.params = subgraph.params || {}
|
|
411
|
+
subgraph.params.calculatedWidth = maxX - minX + 60
|
|
412
|
+
subgraph.params.calculatedHeight = maxY - minY + 100
|
|
413
|
+
|
|
414
|
+
for (const childNode of inner.getNodes()) {
|
|
415
|
+
if (childNode._isSubgraph) {
|
|
416
|
+
computeInnerPositions(childNode)
|
|
417
|
+
// If it's a file node (has params.path and no isDirectory), collect symbols
|
|
418
|
+
if (childNode.params?.path && !childNode.params?.isDirectory) {
|
|
419
|
+
const fileInner = childNode.getInnerEditor()
|
|
420
|
+
for (const fnNode of fileInner.getNodes()) {
|
|
421
|
+
symbolNodes.push({ id: fnNode.id, file: childNode.params.path })
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
for (const rootNode of editor.getNodes()) {
|
|
429
|
+
computeInnerPositions(rootNode)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ── Build Reverse ID Lookup ──
|
|
433
|
+
const idToPath = new Map()
|
|
434
|
+
for (const [path, id] of fileMap.entries()) idToPath.set(id, path)
|
|
435
|
+
for (const [path, id] of dirNodeMap.entries()) idToPath.set(id, path)
|
|
436
|
+
for (const node of symbolNodes) idToPath.set(node.id, node.file)
|
|
437
|
+
|
|
438
|
+
return { editor, fileMap, dirFiles, dirNodeMap, idToPath, symbolMap }
|
|
439
|
+
}
|
|
440
|
+
export { buildFileGraph, buildStructuredGraph }
|