project-graph-mcp 2.2.6 → 2.3.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.
- package/ARCHITECTURE.md +81 -0
- package/CHANGELOG.md +57 -0
- package/README.md +9 -4
- package/package.json +4 -13
- package/project-graph-mcp-2.3.0.tgz +0 -0
- package/src/compact/expand.js +1 -1
- package/src/core/graph-builder.js +2 -2
- package/src/core/parser.js +2 -2
- package/src/network/server.js +1 -2
- package/src/network/web-server.js +1 -1
- package/vendor/symbiote-node/CHANGELOG.md +31 -0
- package/vendor/symbiote-node/LICENSE +21 -0
- package/vendor/symbiote-node/README.md +206 -0
- package/vendor/symbiote-node/canvas/AutoLayout.js +725 -0
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +73 -0
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +93 -0
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +9 -0
- package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +962 -0
- package/vendor/symbiote-node/canvas/ConnectionRenderer.js +1468 -0
- package/vendor/symbiote-node/canvas/FlowSimulator.js +323 -0
- package/vendor/symbiote-node/canvas/ForceLayout.js +189 -0
- package/vendor/symbiote-node/canvas/ForceWorker.js +1325 -0
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +97 -0
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +176 -0
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +12 -0
- package/vendor/symbiote-node/canvas/LODManager.js +88 -0
- package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +71 -0
- package/vendor/symbiote-node/canvas/Minimap/Minimap.js +207 -0
- package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +9 -0
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +261 -0
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +1840 -0
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +22 -0
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +97 -0
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +132 -0
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +21 -0
- package/vendor/symbiote-node/canvas/NodeViewManager.js +584 -0
- package/vendor/symbiote-node/canvas/PinExpansion.js +131 -0
- package/vendor/symbiote-node/canvas/PseudoConnection.js +80 -0
- package/vendor/symbiote-node/canvas/SubgraphManager.js +201 -0
- package/vendor/symbiote-node/canvas/SubgraphRouter.js +443 -0
- package/vendor/symbiote-node/canvas/ViewportActions.js +446 -0
- package/vendor/symbiote-node/core/Connection.js +45 -0
- package/vendor/symbiote-node/core/Editor.js +451 -0
- package/vendor/symbiote-node/core/Frame.js +31 -0
- package/vendor/symbiote-node/core/GraphMermaid.js +348 -0
- package/vendor/symbiote-node/core/GraphText.js +210 -0
- package/vendor/symbiote-node/core/Node.js +143 -0
- package/vendor/symbiote-node/core/Portal.js +104 -0
- package/vendor/symbiote-node/core/Socket.js +185 -0
- package/vendor/symbiote-node/core/SubgraphNode.js +125 -0
- package/vendor/symbiote-node/index.js +103 -0
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +361 -0
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +332 -0
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +96 -0
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +104 -0
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +133 -0
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +33 -0
- package/vendor/symbiote-node/interactions/ConnectFlow.js +307 -0
- package/vendor/symbiote-node/interactions/Drag.js +102 -0
- package/vendor/symbiote-node/interactions/Selector.js +132 -0
- package/vendor/symbiote-node/interactions/SnapGrid.js +65 -0
- package/vendor/symbiote-node/interactions/Zoom.js +140 -0
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +88 -0
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +254 -0
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +11 -0
- package/vendor/symbiote-node/layout/Layout/Layout.css.js +88 -0
- package/vendor/symbiote-node/layout/Layout/Layout.js +622 -0
- package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +25 -0
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +293 -0
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +467 -0
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +33 -0
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +46 -0
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +102 -0
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +6 -0
- package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +156 -0
- package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +250 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +379 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +263 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +20 -0
- package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +183 -0
- package/vendor/symbiote-node/layout/LayoutTree.js +246 -0
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +43 -0
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +89 -0
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +14 -0
- package/vendor/symbiote-node/layout/index.js +16 -0
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +61 -0
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +79 -0
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +19 -0
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +41 -0
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +24 -0
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +16 -0
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +65 -0
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +29 -0
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +13 -0
- package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +683 -0
- package/vendor/symbiote-node/node/GraphNode/GraphNode.js +92 -0
- package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +17 -0
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +25 -0
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +7 -0
- package/vendor/symbiote-node/node/PortItem/PortItem.css.js +90 -0
- package/vendor/symbiote-node/node/PortItem/PortItem.js +87 -0
- package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +10 -0
- package/vendor/symbiote-node/package.json +59 -0
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +143 -0
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +131 -0
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +16 -0
- package/vendor/symbiote-node/plugins/History.js +384 -0
- package/vendor/symbiote-node/plugins/Readonly.js +59 -0
- package/vendor/symbiote-node/shapes/CircleShape.js +80 -0
- package/vendor/symbiote-node/shapes/CommentShape.js +35 -0
- package/vendor/symbiote-node/shapes/DiamondShape.js +115 -0
- package/vendor/symbiote-node/shapes/NodeShape.js +80 -0
- package/vendor/symbiote-node/shapes/PillShape.js +91 -0
- package/vendor/symbiote-node/shapes/RectShape.js +72 -0
- package/vendor/symbiote-node/shapes/SVGShape.js +494 -0
- package/vendor/symbiote-node/shapes/index.js +53 -0
- package/vendor/symbiote-node/themes/Palette.js +32 -0
- package/vendor/symbiote-node/themes/Skin.js +113 -0
- package/vendor/symbiote-node/themes/Theme.js +84 -0
- package/vendor/symbiote-node/themes/carbon.js +137 -0
- package/vendor/symbiote-node/themes/dark.js +137 -0
- package/vendor/symbiote-node/themes/ebook.js +138 -0
- package/vendor/symbiote-node/themes/grey.js +137 -0
- package/vendor/symbiote-node/themes/light.js +137 -0
- package/vendor/symbiote-node/themes/neon.js +138 -0
- package/vendor/symbiote-node/themes/pcb.js +273 -0
- package/vendor/symbiote-node/themes/synthwave.js +137 -0
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +86 -0
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +128 -0
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +29 -0
- package/web/app.js +9 -5
- package/web/components/canvas-graph.js +1705 -0
- package/web/components/code-block.js +1 -1
- package/web/components/event-feed/CodeWidget.js +32 -0
- package/web/components/event-feed/EventWidget.js +97 -0
- package/web/components/event-feed/ListWidget.js +57 -0
- package/web/components/event-feed/MiniGraphWidget.js +159 -0
- package/web/components/follow-ribbon.js +134 -0
- package/web/dashboard.js +1 -1
- package/web/follow-controller.js +241 -0
- package/web/index.html +4 -0
- package/web/panels/ActionBoard/ActionBoard.js +1 -1
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -1
- package/web/panels/code-viewer.js +50 -15
- package/web/panels/dep-graph.js +2691 -7
- package/web/panels/file-tree.js +5 -2
- package/web/panels/live-monitor.js +75 -3
- package/web/style.css +39 -0
- package/docs/img/explorer-compact.jpg +0 -0
- package/docs/img/explorer-expanded.jpg +0 -0
- package/src/.contextignore +0 -22
- package/src/.project-graph-cache.json +0 -1
- package/src/compact/.project-graph-cache.json +0 -1
- package/web/.project-graph-cache.json +0 -1
- package/web/panels/SettingsPanel/.project-graph-cache.json +0 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SidebarSection — itemize sub-component for sidebar section items
|
|
3
|
+
*
|
|
4
|
+
* Renders a master panel entry with icon, label.
|
|
5
|
+
* Edit mode: shows visibility toggle + drag handle.
|
|
6
|
+
* Shows sub-panel list with close buttons for non-master panels.
|
|
7
|
+
*
|
|
8
|
+
* @module symbiote-node/layout/LayoutSidebar/SidebarSection
|
|
9
|
+
*/
|
|
10
|
+
import Symbiote, { html } from '@symbiotejs/symbiote';
|
|
11
|
+
import { navigate } from '../LayoutRouter/LayoutRouter.js';
|
|
12
|
+
|
|
13
|
+
export class SidebarSection extends Symbiote {
|
|
14
|
+
isoMode = true;
|
|
15
|
+
|
|
16
|
+
init$ = {
|
|
17
|
+
sectionId: '',
|
|
18
|
+
icon: 'dashboard',
|
|
19
|
+
label: '',
|
|
20
|
+
isActive: false,
|
|
21
|
+
isVisible: true,
|
|
22
|
+
isDisabled: false,
|
|
23
|
+
eyeIcon: 'visibility',
|
|
24
|
+
hasSubPanels: false,
|
|
25
|
+
isExpanded: false,
|
|
26
|
+
subPanels: [],
|
|
27
|
+
|
|
28
|
+
onSectionClick: () => {
|
|
29
|
+
if (!this.$.isVisible || this.$.isDisabled) return;
|
|
30
|
+
navigate(this.$.sectionId);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
onExpandToggle: (e) => {
|
|
34
|
+
e.stopPropagation();
|
|
35
|
+
if (!this.$.hasSubPanels) return;
|
|
36
|
+
this.$.isExpanded = !this.$.isExpanded;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
onToggleVisibility: (e) => {
|
|
40
|
+
e.stopPropagation();
|
|
41
|
+
const sidebar = this.closest('layout-sidebar');
|
|
42
|
+
if (sidebar) sidebar.toggleVisibility(this.$.sectionId);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
renderCallback() {
|
|
47
|
+
this.sub('isActive', (val) => {
|
|
48
|
+
this.toggleAttribute('data-active', val);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.sub('isExpanded', (val) => {
|
|
52
|
+
this.toggleAttribute('data-expanded', val);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
this.sub('isVisible', (val) => {
|
|
56
|
+
this.toggleAttribute('data-hidden', !val);
|
|
57
|
+
this.$.eyeIcon = val ? 'visibility' : 'visibility_off';
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.sub('isDisabled', (val) => {
|
|
61
|
+
this.toggleAttribute('data-disabled', val);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
this.sub('subPanels', (panels) => {
|
|
65
|
+
const has = panels && panels.length > 0;
|
|
66
|
+
this.$.hasSubPanels = has;
|
|
67
|
+
this.toggleAttribute('data-has-sub', has);
|
|
68
|
+
// Collapse if no sub-panels
|
|
69
|
+
if (!has) this.$.isExpanded = false;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Drag support
|
|
73
|
+
this.setAttribute('draggable', 'true');
|
|
74
|
+
|
|
75
|
+
this.addEventListener('dragstart', (e) => {
|
|
76
|
+
const sidebar = this.closest('layout-sidebar');
|
|
77
|
+
if (!sidebar?.hasAttribute('edit-mode')) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const sections = Array.from(this.parentElement.children);
|
|
82
|
+
const idx = sections.indexOf(this);
|
|
83
|
+
e.dataTransfer.setData('text/plain', String(idx));
|
|
84
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
85
|
+
this.setAttribute('data-dragging', '');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
this.addEventListener('dragend', () => {
|
|
89
|
+
this.removeAttribute('data-dragging');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
this.addEventListener('dragover', (e) => {
|
|
93
|
+
const sidebar = this.closest('layout-sidebar');
|
|
94
|
+
if (!sidebar?.hasAttribute('edit-mode')) return;
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
e.dataTransfer.dropEffect = 'move';
|
|
97
|
+
this.setAttribute('data-dragover', '');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this.addEventListener('dragleave', () => {
|
|
101
|
+
this.removeAttribute('data-dragover');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
this.addEventListener('drop', (e) => {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
this.removeAttribute('data-dragover');
|
|
107
|
+
const sidebar = this.closest('layout-sidebar');
|
|
108
|
+
if (!sidebar?.hasAttribute('edit-mode')) return;
|
|
109
|
+
|
|
110
|
+
const fromIdx = parseInt(e.dataTransfer.getData('text/plain'), 10);
|
|
111
|
+
const sections = Array.from(this.parentElement.children);
|
|
112
|
+
const toIdx = sections.indexOf(this);
|
|
113
|
+
if (fromIdx !== toIdx) {
|
|
114
|
+
sidebar.moveSection(fromIdx, toIdx);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
SidebarSection.template = html`
|
|
121
|
+
<div class="sec-drag-handle">
|
|
122
|
+
<span class="material-symbols-outlined">drag_indicator</span>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="sec-item" ${{ onclick: 'onSectionClick' }}>
|
|
125
|
+
<span class="material-symbols-outlined sec-icon" ${{ textContent: 'icon' }}></span>
|
|
126
|
+
<span class="sec-label" ${{ textContent: 'label' }}></span>
|
|
127
|
+
<span class="material-symbols-outlined sec-expand" ${{ onclick: 'onExpandToggle' }}>chevron_right</span>
|
|
128
|
+
</div>
|
|
129
|
+
<button class="sec-eye" ${{ onclick: 'onToggleVisibility' }}>
|
|
130
|
+
<span class="material-symbols-outlined" ${{ textContent: 'eyeIcon' }}></span>
|
|
131
|
+
</button>
|
|
132
|
+
<div class="sec-sub-panels" itemize="subPanels" item-tag="sidebar-sub-item"></div>
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
SidebarSection.reg('sidebar-section');
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* SidebarSubItem — sub-panel entry inside a section
|
|
139
|
+
* Shows close button for non-master panels (isMaster=false)
|
|
140
|
+
*/
|
|
141
|
+
export class SidebarSubItem extends Symbiote {
|
|
142
|
+
isoMode = true;
|
|
143
|
+
|
|
144
|
+
init$ = {
|
|
145
|
+
title: '',
|
|
146
|
+
icon: 'web_asset',
|
|
147
|
+
panelId: '',
|
|
148
|
+
isMaster: false,
|
|
149
|
+
|
|
150
|
+
onClose: (e) => {
|
|
151
|
+
e.stopPropagation();
|
|
152
|
+
const panelId = this.$.panelId;
|
|
153
|
+
if (!panelId) return;
|
|
154
|
+
|
|
155
|
+
// Find the panel-layout and call joinPanels
|
|
156
|
+
const sidebar = this.closest('layout-sidebar');
|
|
157
|
+
if (sidebar) {
|
|
158
|
+
sidebar.dispatchEvent(new CustomEvent('panel-close', {
|
|
159
|
+
bubbles: true,
|
|
160
|
+
detail: { panelId },
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
renderCallback() {
|
|
167
|
+
this.sub('isMaster', (val) => {
|
|
168
|
+
this.toggleAttribute('data-master', val);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
SidebarSubItem.template = html`
|
|
174
|
+
<div class="sub-panel-item">
|
|
175
|
+
<span class="material-symbols-outlined" ${{ textContent: 'icon' }}></span>
|
|
176
|
+
<span ${{ textContent: 'title' }}></span>
|
|
177
|
+
<button class="sub-panel-close" ${{ onclick: 'onClose' }}>
|
|
178
|
+
<span class="material-symbols-outlined">close</span>
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
SidebarSubItem.reg('sidebar-sub-item');
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview BSP (Binary Space Partitioning) Layout Tree
|
|
3
|
+
* Implements Blender-style area splitting/joining mechanics.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {'horizontal' | 'vertical'} SplitDirection
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} PanelNode
|
|
12
|
+
* @property {string} id - Unique node ID
|
|
13
|
+
* @property {'panel'} type - Node type
|
|
14
|
+
* @property {string} panelType - Panel content type (e.g., 'viewport', 'timeline')
|
|
15
|
+
* @property {boolean} [collapsed] - Whether panel is collapsed
|
|
16
|
+
* @property {Object} [panelState] - Panel-specific state
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} SplitNode
|
|
21
|
+
* @property {string} id - Unique node ID
|
|
22
|
+
* @property {'split'} type - Node type
|
|
23
|
+
* @property {SplitDirection} direction - Split direction
|
|
24
|
+
* @property {number} ratio - Split ratio (0-1), size of first child
|
|
25
|
+
* @property {LayoutNode} first - First child node
|
|
26
|
+
* @property {LayoutNode} second - Second child node
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {PanelNode | SplitNode} LayoutNode
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
let idCounter = 0;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate unique node ID
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
export function generateId() {
|
|
40
|
+
return `node_${++idCounter}_${Date.now().toString(36)}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a panel node
|
|
45
|
+
* @param {string} panelType - Panel content type
|
|
46
|
+
* @param {Object} [panelState] - Initial panel state
|
|
47
|
+
* @returns {PanelNode}
|
|
48
|
+
*/
|
|
49
|
+
export function createPanel(panelType, panelState = {}) {
|
|
50
|
+
return {
|
|
51
|
+
id: generateId(),
|
|
52
|
+
type: 'panel',
|
|
53
|
+
panelType,
|
|
54
|
+
panelState,
|
|
55
|
+
collapsed: false
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a split node
|
|
61
|
+
* @param {SplitDirection} direction - Split direction
|
|
62
|
+
* @param {LayoutNode} first - First child
|
|
63
|
+
* @param {LayoutNode} second - Second child
|
|
64
|
+
* @param {number} [ratio=0.5] - Split ratio
|
|
65
|
+
* @returns {SplitNode}
|
|
66
|
+
*/
|
|
67
|
+
export function createSplit(direction, first, second, ratio = 0.5) {
|
|
68
|
+
return {
|
|
69
|
+
id: generateId(),
|
|
70
|
+
type: 'split',
|
|
71
|
+
direction,
|
|
72
|
+
ratio,
|
|
73
|
+
first,
|
|
74
|
+
second
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find a node by ID in the tree
|
|
80
|
+
* @param {LayoutNode} root - Root node
|
|
81
|
+
* @param {string} id - Node ID to find
|
|
82
|
+
* @returns {LayoutNode | null}
|
|
83
|
+
*/
|
|
84
|
+
export function findNode(root, id) {
|
|
85
|
+
if (root.id === id) return root;
|
|
86
|
+
if (root.type === 'split') {
|
|
87
|
+
return findNode(root.first, id) || findNode(root.second, id);
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Find parent of a node
|
|
94
|
+
* @param {LayoutNode} root - Root node
|
|
95
|
+
* @param {string} id - Child node ID
|
|
96
|
+
* @returns {{ parent: SplitNode, which: 'first' | 'second' } | null}
|
|
97
|
+
*/
|
|
98
|
+
export function findParent(root, id) {
|
|
99
|
+
if (root.type !== 'split') return null;
|
|
100
|
+
|
|
101
|
+
if (root.first.id === id) return { parent: root, which: 'first' };
|
|
102
|
+
if (root.second.id === id) return { parent: root, which: 'second' };
|
|
103
|
+
|
|
104
|
+
return findParent(root.first, id) || findParent(root.second, id);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Split a panel into two
|
|
109
|
+
* @param {LayoutNode} root - Root node
|
|
110
|
+
* @param {string} panelId - Panel ID to split
|
|
111
|
+
* @param {SplitDirection} direction - Split direction
|
|
112
|
+
* @param {number} [ratio=0.5] - Split ratio
|
|
113
|
+
* @param {string} [newPanelType] - Type for new panel (defaults to same as original)
|
|
114
|
+
* @returns {LayoutNode} - New root node
|
|
115
|
+
*/
|
|
116
|
+
export function splitPanel(root, panelId, direction, ratio = 0.5, newPanelType) {
|
|
117
|
+
const node = findNode(root, panelId);
|
|
118
|
+
if (!node || node.type !== 'panel') {
|
|
119
|
+
console.warn(`Cannot split: panel ${panelId} not found`);
|
|
120
|
+
return root;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const newPanel = createPanel(newPanelType || node.panelType);
|
|
124
|
+
const splitNode = createSplit(direction, node, newPanel, ratio);
|
|
125
|
+
|
|
126
|
+
// If splitting the root
|
|
127
|
+
if (root.id === panelId) {
|
|
128
|
+
return splitNode;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Find parent and replace
|
|
132
|
+
const parentInfo = findParent(root, panelId);
|
|
133
|
+
if (parentInfo) {
|
|
134
|
+
parentInfo.parent[parentInfo.which] = splitNode;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return root;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Join two panels (remove one panel and its parent split)
|
|
142
|
+
* @param {LayoutNode} root - Root node
|
|
143
|
+
* @param {string} panelToRemove - Panel ID to remove
|
|
144
|
+
* @returns {LayoutNode} - New root node
|
|
145
|
+
*/
|
|
146
|
+
export function joinPanels(root, panelToRemove) {
|
|
147
|
+
const parentInfo = findParent(root, panelToRemove);
|
|
148
|
+
if (!parentInfo) {
|
|
149
|
+
// Trying to remove root - not allowed
|
|
150
|
+
console.warn('Cannot join: panel is root');
|
|
151
|
+
return root;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const { parent, which } = parentInfo;
|
|
155
|
+
const survivor = which === 'first' ? parent.second : parent.first;
|
|
156
|
+
|
|
157
|
+
// If parent is root, survivor becomes new root
|
|
158
|
+
const grandparentInfo = findParent(root, parent.id);
|
|
159
|
+
if (!grandparentInfo) {
|
|
160
|
+
return survivor;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Replace parent with survivor in grandparent
|
|
164
|
+
grandparentInfo.parent[grandparentInfo.which] = survivor;
|
|
165
|
+
return root;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Update split ratio
|
|
170
|
+
* @param {LayoutNode} root - Root node
|
|
171
|
+
* @param {string} splitId - Split node ID
|
|
172
|
+
* @param {number} ratio - New ratio (0-1)
|
|
173
|
+
* @returns {LayoutNode} - Same root (mutated)
|
|
174
|
+
*/
|
|
175
|
+
export function resizeSplit(root, splitId, ratio) {
|
|
176
|
+
const node = findNode(root, splitId);
|
|
177
|
+
if (!node || node.type !== 'split') {
|
|
178
|
+
console.warn(`Cannot resize: split ${splitId} not found`);
|
|
179
|
+
return root;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
node.ratio = Math.max(0.1, Math.min(0.9, ratio));
|
|
183
|
+
return root;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Serialize layout to JSON string
|
|
188
|
+
* @param {LayoutNode} root - Root node
|
|
189
|
+
* @returns {string}
|
|
190
|
+
*/
|
|
191
|
+
export function serialize(root) {
|
|
192
|
+
return JSON.stringify(root);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Deserialize layout from JSON string
|
|
197
|
+
* @param {string} json - JSON string
|
|
198
|
+
* @returns {LayoutNode}
|
|
199
|
+
*/
|
|
200
|
+
export function deserialize(json) {
|
|
201
|
+
return JSON.parse(json);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Clone a layout tree (deep copy)
|
|
206
|
+
* @param {LayoutNode} root - Root node
|
|
207
|
+
* @returns {LayoutNode}
|
|
208
|
+
*/
|
|
209
|
+
export function clone(root) {
|
|
210
|
+
return deserialize(serialize(root));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get all panel nodes in tree
|
|
215
|
+
* @param {LayoutNode} root - Root node
|
|
216
|
+
* @returns {PanelNode[]}
|
|
217
|
+
*/
|
|
218
|
+
export function getAllPanels(root) {
|
|
219
|
+
if (root.type === 'panel') return [root];
|
|
220
|
+
return [...getAllPanels(root.first), ...getAllPanels(root.second)];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Update a node's properties by ID
|
|
225
|
+
* @param {LayoutNode} root - Root node
|
|
226
|
+
* @param {string} nodeId - Node ID to update
|
|
227
|
+
* @param {Object} updates - Properties to update
|
|
228
|
+
* @returns {boolean} - True if node was found and updated
|
|
229
|
+
*/
|
|
230
|
+
export function updateNode(root, nodeId, updates) {
|
|
231
|
+
const node = findNode(root, nodeId);
|
|
232
|
+
if (!node) return false;
|
|
233
|
+
Object.assign(node, updates);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get neighbor panel IDs for a panel
|
|
239
|
+
* @param {LayoutNode} root - Root node
|
|
240
|
+
* @param {string} panelId - Panel ID
|
|
241
|
+
* @returns {{ left?: string, right?: string, top?: string, bottom?: string }}
|
|
242
|
+
*/
|
|
243
|
+
export function getNeighbors(root, panelId) {
|
|
244
|
+
// TODO: Implement neighbor detection for join preview
|
|
245
|
+
return {};
|
|
246
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { css } from '@symbiotejs/symbiote';
|
|
2
|
+
|
|
3
|
+
export const styles = css`
|
|
4
|
+
panel-menu {
|
|
5
|
+
position: fixed;
|
|
6
|
+
z-index: 1000;
|
|
7
|
+
pointer-events: none;
|
|
8
|
+
|
|
9
|
+
.menu-container {
|
|
10
|
+
pointer-events: auto;
|
|
11
|
+
background: var(--bg-popup, #2a2a2a);
|
|
12
|
+
border: 1px solid var(--border-popup, #444);
|
|
13
|
+
border-radius: 6px;
|
|
14
|
+
box-shadow: 0 4px 12px var(--sn-shadow-color, rgba(0, 0, 0, 0.4));
|
|
15
|
+
min-width: 160px;
|
|
16
|
+
padding: 4px 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.menu-item {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: 8px;
|
|
23
|
+
padding: 8px 12px;
|
|
24
|
+
cursor: pointer;
|
|
25
|
+
color: var(--text-main, #e0e0e0);
|
|
26
|
+
font-size: 0.85rem;
|
|
27
|
+
transition: background 0.1s;
|
|
28
|
+
|
|
29
|
+
&:hover {
|
|
30
|
+
background: var(--bg-hover, #3a3a3a);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&[active] {
|
|
34
|
+
color: var(--accent, #4a9eff);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.material-symbols-outlined {
|
|
38
|
+
font-size: 18px;
|
|
39
|
+
opacity: 0.8;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview PanelMenu - Dropdown menu for panel type selection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import Symbiote from '@symbiotejs/symbiote';
|
|
6
|
+
import { template } from './PanelMenu.tpl.js';
|
|
7
|
+
import { styles } from './PanelMenu.css.js';
|
|
8
|
+
|
|
9
|
+
export class PanelMenu extends Symbiote {
|
|
10
|
+
static isoMode = true;
|
|
11
|
+
|
|
12
|
+
init$ = {
|
|
13
|
+
visible: false,
|
|
14
|
+
panelId: '',
|
|
15
|
+
items: [],
|
|
16
|
+
currentType: '',
|
|
17
|
+
|
|
18
|
+
onItemClick: (e) => {
|
|
19
|
+
const type = e.target.closest('[data-type]')?.dataset.type;
|
|
20
|
+
if (type) {
|
|
21
|
+
this.dispatchEvent(new CustomEvent('panel-type-select', {
|
|
22
|
+
bubbles: true,
|
|
23
|
+
composed: true,
|
|
24
|
+
detail: { panelId: this.$.panelId, type }
|
|
25
|
+
}));
|
|
26
|
+
this.hide();
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Show menu at position
|
|
33
|
+
* @param {number} x
|
|
34
|
+
* @param {number} y
|
|
35
|
+
* @param {string} panelId
|
|
36
|
+
* @param {string} currentType
|
|
37
|
+
* @param {Array<{type: string, title: string, icon: string}>} items
|
|
38
|
+
*/
|
|
39
|
+
show(x, y, panelId, currentType, items) {
|
|
40
|
+
this.$.panelId = panelId;
|
|
41
|
+
this.$.currentType = currentType;
|
|
42
|
+
|
|
43
|
+
// Transform items to include isActive flag for template binding
|
|
44
|
+
this.$.items = items.map(item => ({
|
|
45
|
+
...item,
|
|
46
|
+
icon: item.icon || 'dashboard',
|
|
47
|
+
title: item.title || item.type,
|
|
48
|
+
isActive: item.type === currentType
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
this.style.left = `${x}px`;
|
|
52
|
+
this.style.top = `${y}px`;
|
|
53
|
+
this.$.visible = true;
|
|
54
|
+
|
|
55
|
+
// Close on outside click
|
|
56
|
+
if (typeof setTimeout !== 'undefined') {
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
if (typeof document !== 'undefined') {
|
|
59
|
+
document.addEventListener('pointerdown', this._onOutsideClick);
|
|
60
|
+
}
|
|
61
|
+
}, 0);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
hide() {
|
|
66
|
+
this.$.visible = false;
|
|
67
|
+
if (typeof document !== 'undefined') {
|
|
68
|
+
document.removeEventListener('pointerdown', this._onOutsideClick);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_onOutsideClick = (e) => {
|
|
73
|
+
if (!this.contains(e.target)) {
|
|
74
|
+
this.hide();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
disconnectedCallback() {
|
|
79
|
+
if (typeof document !== 'undefined') {
|
|
80
|
+
document.removeEventListener('pointerdown', this._onOutsideClick);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
PanelMenu.template = template;
|
|
86
|
+
PanelMenu.rootStyles = styles;
|
|
87
|
+
|
|
88
|
+
PanelMenu.reg('panel-menu');
|
|
89
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { html } from '@symbiotejs/symbiote';
|
|
2
|
+
|
|
3
|
+
export const template = html`
|
|
4
|
+
<div class="menu-container" ${{ '@hidden': '!visible' }}>
|
|
5
|
+
<div class="menu-items" itemize="items">
|
|
6
|
+
<template>
|
|
7
|
+
<div class="menu-item" ${{ onclick: '^onItemClick', '@data-type': 'type', '@active': 'isActive' }}>
|
|
8
|
+
<span class="material-symbols-outlined">{{icon}}</span>
|
|
9
|
+
<span>{{title}}</span>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
`;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Layout module exports
|
|
3
|
+
* Blender-style panel layout system for Symbiote.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { Layout } from './Layout/Layout.js';
|
|
7
|
+
export { LayoutNode } from './LayoutNode/LayoutNode.js';
|
|
8
|
+
export { ActionZone } from './ActionZone/ActionZone.js';
|
|
9
|
+
export { LayoutPreview } from './LayoutPreview/LayoutPreview.js';
|
|
10
|
+
export { LayoutSidebar } from './LayoutSidebar/LayoutSidebar.js';
|
|
11
|
+
export * as LayoutTree from './LayoutTree.js';
|
|
12
|
+
export {
|
|
13
|
+
navigate, updateParams, parseQuery, buildHash, buildQuery,
|
|
14
|
+
getRoute, setDefaultPanel,
|
|
15
|
+
} from './LayoutRouter/LayoutRouter.js';
|
|
16
|
+
export { syncWithRouter } from './LayoutRouter/routerSync.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextMenu styles
|
|
3
|
+
* @module symbiote-node/menu/ContextMenu.css
|
|
4
|
+
*/
|
|
5
|
+
import { css } from '@symbiotejs/symbiote';
|
|
6
|
+
|
|
7
|
+
export const styles = css`
|
|
8
|
+
context-menu {
|
|
9
|
+
position: absolute;
|
|
10
|
+
inset: 0;
|
|
11
|
+
z-index: 200;
|
|
12
|
+
pointer-events: none;
|
|
13
|
+
|
|
14
|
+
&[hidden] {
|
|
15
|
+
display: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
& .sn-ctx-backdrop {
|
|
19
|
+
position: absolute;
|
|
20
|
+
inset: 0;
|
|
21
|
+
pointer-events: all;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
& .sn-ctx-menu {
|
|
25
|
+
position: absolute;
|
|
26
|
+
pointer-events: all;
|
|
27
|
+
min-width: 160px;
|
|
28
|
+
background: var(--sn-ctx-bg, #1e1e3a);
|
|
29
|
+
border: 1px solid var(--sn-ctx-border, #3a3a6a);
|
|
30
|
+
border-radius: 8px;
|
|
31
|
+
box-shadow: 0 8px 24px var(--sn-shadow-color, rgba(0, 0, 0, 0.5));
|
|
32
|
+
padding: 4px;
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.sn-ctx-btn {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: 8px;
|
|
41
|
+
width: 100%;
|
|
42
|
+
padding: 8px 12px;
|
|
43
|
+
border: none;
|
|
44
|
+
background: transparent;
|
|
45
|
+
color: var(--sn-ctx-color, #e0e0e0);
|
|
46
|
+
font-family: var(--sn-font, 'Inter', sans-serif);
|
|
47
|
+
font-size: 13px;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
border-radius: 4px;
|
|
50
|
+
transition: background 0.1s;
|
|
51
|
+
|
|
52
|
+
&:hover {
|
|
53
|
+
background: var(--sn-ctx-hover, rgba(74, 158, 255, 0.15));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.sn-ctx-icon {
|
|
58
|
+
font-size: 18px;
|
|
59
|
+
opacity: 0.7;
|
|
60
|
+
}
|
|
61
|
+
`;
|