@tldiagram/core-ui 1.87.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 (272) hide show
  1. package/dist/App.d.ts +1 -0
  2. package/dist/api/client.d.ts +143 -0
  3. package/dist/api/transport-vscode.d.ts +8 -0
  4. package/dist/api/transport.d.ts +1 -0
  5. package/dist/components/CodePreviewPanel-vscode.d.ts +7 -0
  6. package/dist/components/CodePreviewPanel.d.ts +9 -0
  7. package/dist/components/ConfirmDialog.d.ts +12 -0
  8. package/dist/components/ConnectorPanel.d.ts +21 -0
  9. package/dist/components/ContextBoundaryElement.d.ts +11 -0
  10. package/dist/components/ContextNeighborElement.d.ts +29 -0
  11. package/dist/components/ContextStraightConnector.d.ts +4 -0
  12. package/dist/components/CrossBranchControls.d.ts +9 -0
  13. package/dist/components/DependenciesOnboarding.d.ts +5 -0
  14. package/dist/components/DrawingCanvas.d.ts +39 -0
  15. package/dist/components/ElementLibrary-vscode.d.ts +7 -0
  16. package/dist/components/ElementLibrary.d.ts +22 -0
  17. package/dist/components/ElementNode.d.ts +36 -0
  18. package/dist/components/ElementPanel.d.ts +25 -0
  19. package/dist/components/ExploreOnboarding.d.ts +5 -0
  20. package/dist/components/ExplorePageOnboarding.d.ts +5 -0
  21. package/dist/components/ExportModal.d.ts +16 -0
  22. package/dist/components/FloatingEdge.d.ts +9 -0
  23. package/dist/components/GitSourceLinker.d.ts +8 -0
  24. package/dist/components/HeaderContext.d.ts +16 -0
  25. package/dist/components/Icons.d.ts +95 -0
  26. package/dist/components/ImportModal.d.ts +10 -0
  27. package/dist/components/InlineElementAdder.d.ts +17 -0
  28. package/dist/components/LayoutSection.d.ts +7 -0
  29. package/dist/components/LocalSourceLinker.d.ts +8 -0
  30. package/dist/components/MiniZoomOnboarding.d.ts +5 -0
  31. package/dist/components/NavBreadcrumb.d.ts +6 -0
  32. package/dist/components/NodeBody.d.ts +12 -0
  33. package/dist/components/NodeContainer.d.ts +8 -0
  34. package/dist/components/NodeHoverCard.d.ts +10 -0
  35. package/dist/components/PanelHeader.d.ts +8 -0
  36. package/dist/components/PanelUI.d.ts +3 -0
  37. package/dist/components/ProxyConnectorEdge.d.ts +4 -0
  38. package/dist/components/ProxyConnectorPanel.d.ts +9 -0
  39. package/dist/components/SafeBackground.d.ts +13 -0
  40. package/dist/components/ScrollIndicatorWrapper.d.ts +8 -0
  41. package/dist/components/SetChildModal.d.ts +10 -0
  42. package/dist/components/SetParentModal.d.ts +10 -0
  43. package/dist/components/SlidingPanel.d.ts +16 -0
  44. package/dist/components/TagUpsert.d.ts +8 -0
  45. package/dist/components/TopMenuBar.d.ts +8 -0
  46. package/dist/components/ViewBezierConnector.d.ts +4 -0
  47. package/dist/components/ViewDrawMenu.d.ts +22 -0
  48. package/dist/components/ViewEditorEdgeLabelLayout.d.ts +16 -0
  49. package/dist/components/ViewEditorOnboarding.d.ts +5 -0
  50. package/dist/components/ViewExplorer/TagManager/ColorPicker.d.ts +7 -0
  51. package/dist/components/ViewExplorer/TagManager/GroupNamingPopover.d.ts +10 -0
  52. package/dist/components/ViewExplorer/TagManager/LayerItem.d.ts +27 -0
  53. package/dist/components/ViewExplorer/TagManager/TagItem.d.ts +25 -0
  54. package/dist/components/ViewExplorer/TagManager/index.d.ts +21 -0
  55. package/dist/components/ViewExplorer/ViewNavigator.d.ts +11 -0
  56. package/dist/components/ViewExplorer/ViewSearch.d.ts +8 -0
  57. package/dist/components/ViewExplorer/ViewTree.d.ts +18 -0
  58. package/dist/components/ViewExplorer/index.d.ts +31 -0
  59. package/dist/components/ViewExplorer/types.d.ts +11 -0
  60. package/dist/components/ViewExplorer/utils.d.ts +6 -0
  61. package/dist/components/ViewExplorer-vscode.d.ts +6 -0
  62. package/dist/components/ViewFloatingMenu-vscode.d.ts +27 -0
  63. package/dist/components/ViewFloatingMenu.d.ts +39 -0
  64. package/dist/components/ViewGridNode.d.ts +29 -0
  65. package/dist/components/ViewHeaderButton.d.ts +11 -0
  66. package/dist/components/ViewPanel.d.ts +18 -0
  67. package/dist/components/ViewsGridOnboarding.d.ts +5 -0
  68. package/dist/components/ZUI/ZUICanvas.d.ts +18 -0
  69. package/dist/components/ZUI/index.d.ts +2 -0
  70. package/dist/components/ZUI/layout.d.ts +18 -0
  71. package/dist/components/ZUI/proxy.d.ts +25 -0
  72. package/dist/components/ZUI/renderer.d.ts +30 -0
  73. package/dist/components/ZUI/types.d.ts +140 -0
  74. package/dist/components/ZUI/useZUIInteraction.d.ts +21 -0
  75. package/dist/config/runtime-vscode.d.ts +22 -0
  76. package/dist/config/runtime.d.ts +5 -0
  77. package/dist/constants/colors.d.ts +27 -0
  78. package/dist/constants/diagramColors.d.ts +1 -0
  79. package/dist/context/ThemeContext.d.ts +27 -0
  80. package/dist/crossBranch/graph.d.ts +13 -0
  81. package/dist/crossBranch/resolve.d.ts +22 -0
  82. package/dist/crossBranch/settings.d.ts +6 -0
  83. package/dist/crossBranch/store.d.ts +11 -0
  84. package/dist/crossBranch/types.d.ts +96 -0
  85. package/dist/demo/DemoPage.d.ts +9 -0
  86. package/dist/demo/seed.d.ts +9 -0
  87. package/dist/demo/store.d.ts +137 -0
  88. package/dist/demo/viewEditor.d.ts +26 -0
  89. package/dist/favicon.svg +35 -0
  90. package/dist/hooks/useSafeFitView.d.ts +16 -0
  91. package/dist/index.css +1 -0
  92. package/dist/index.d.ts +115 -0
  93. package/dist/index.js +19966 -0
  94. package/dist/lib/vscodeBridge-vscode.d.ts +13 -0
  95. package/dist/lib/vscodeBridge.d.ts +5 -0
  96. package/dist/logo-120.png +0 -0
  97. package/dist/logo-bw.png +0 -0
  98. package/dist/logo-bw.svg +15 -0
  99. package/dist/logo-text.svg +51 -0
  100. package/dist/logo.svg +35 -0
  101. package/dist/pages/AppearanceSettings.d.ts +3 -0
  102. package/dist/pages/Dependencies.d.ts +1 -0
  103. package/dist/pages/InfiniteZoom.d.ts +7 -0
  104. package/dist/pages/Settings.d.ts +7 -0
  105. package/dist/pages/ViewEditor/components/EditorMenus.d.ts +24 -0
  106. package/dist/pages/ViewEditor/components/EditorOverlays.d.ts +30 -0
  107. package/dist/pages/ViewEditor/components/EmptyCanvasState.d.ts +7 -0
  108. package/dist/pages/ViewEditor/context.d.ts +13 -0
  109. package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +201 -0
  110. package/dist/pages/ViewEditor/hooks/useDrawingEngine.d.ts +40 -0
  111. package/dist/pages/ViewEditor/hooks/useViewContextNeighbours.d.ts +20 -0
  112. package/dist/pages/ViewEditor/hooks/useViewData.d.ts +74 -0
  113. package/dist/pages/ViewEditor/index.d.ts +8 -0
  114. package/dist/pages/ViewEditor/utils.d.ts +14 -0
  115. package/dist/pages/Views.d.ts +6 -0
  116. package/dist/pages/ViewsGrid.d.ts +6 -0
  117. package/dist/pkg/importer/mermaid.d.ts +7 -0
  118. package/dist/pkg/importer/mermaid.test.d.ts +1 -0
  119. package/dist/platform/PlatformContext.d.ts +6 -0
  120. package/dist/platform/context.d.ts +3 -0
  121. package/dist/platform/local.d.ts +2 -0
  122. package/dist/platform/types.d.ts +17 -0
  123. package/dist/slots.d.ts +67 -0
  124. package/dist/theme.d.ts +2 -0
  125. package/dist/types/index.d.ts +193 -0
  126. package/dist/types/vscode-messages.d.ts +60 -0
  127. package/dist/utils/edgeDistribution.d.ts +34 -0
  128. package/dist/utils/githubApi.d.ts +4 -0
  129. package/dist/utils/githubCache.d.ts +17 -0
  130. package/dist/utils/ids.d.ts +2 -0
  131. package/dist/utils/technologyCatalog.d.ts +15 -0
  132. package/dist/utils/toast.d.ts +15 -0
  133. package/dist/utils/treesitter.d.ts +13 -0
  134. package/dist/utils/url.d.ts +12 -0
  135. package/package.json +159 -0
  136. package/src/App.tsx +141 -0
  137. package/src/api/client.ts +618 -0
  138. package/src/api/transport-vscode.ts +28 -0
  139. package/src/api/transport.ts +7 -0
  140. package/src/assets/logo-mark.svg +31 -0
  141. package/src/assets/logo-wordmark.svg +22 -0
  142. package/src/assets/logo.svg +35 -0
  143. package/src/components/CodePreviewPanel-vscode.tsx +85 -0
  144. package/src/components/CodePreviewPanel.tsx +384 -0
  145. package/src/components/ConfirmDialog.tsx +66 -0
  146. package/src/components/ConnectorPanel.tsx +403 -0
  147. package/src/components/ContextBoundaryElement.tsx +35 -0
  148. package/src/components/ContextNeighborElement.tsx +282 -0
  149. package/src/components/ContextStraightConnector.tsx +144 -0
  150. package/src/components/CrossBranchControls.tsx +105 -0
  151. package/src/components/DependenciesOnboarding.tsx +427 -0
  152. package/src/components/DrawingCanvas.tsx +391 -0
  153. package/src/components/ElementLibrary-vscode.tsx +9 -0
  154. package/src/components/ElementLibrary.tsx +512 -0
  155. package/src/components/ElementNode.tsx +1033 -0
  156. package/src/components/ElementPanel.tsx +928 -0
  157. package/src/components/ExploreOnboarding.tsx +347 -0
  158. package/src/components/ExplorePageOnboarding.tsx +383 -0
  159. package/src/components/ExportModal.tsx +132 -0
  160. package/src/components/FloatingEdge.tsx +115 -0
  161. package/src/components/GitSourceLinker.tsx +1053 -0
  162. package/src/components/HeaderContext.tsx +30 -0
  163. package/src/components/Icons.tsx +245 -0
  164. package/src/components/ImportModal.tsx +219 -0
  165. package/src/components/InlineElementAdder.tsx +216 -0
  166. package/src/components/LayoutSection.tsx +624 -0
  167. package/src/components/LocalSourceLinker.tsx +330 -0
  168. package/src/components/MiniZoomOnboarding.tsx +78 -0
  169. package/src/components/NavBreadcrumb.tsx +24 -0
  170. package/src/components/NodeBody.tsx +89 -0
  171. package/src/components/NodeContainer.tsx +58 -0
  172. package/src/components/NodeHoverCard.tsx +135 -0
  173. package/src/components/PanelHeader.tsx +36 -0
  174. package/src/components/PanelUI.tsx +24 -0
  175. package/src/components/ProxyConnectorEdge.tsx +169 -0
  176. package/src/components/ProxyConnectorPanel.tsx +130 -0
  177. package/src/components/SafeBackground.tsx +19 -0
  178. package/src/components/ScrollIndicatorWrapper.tsx +117 -0
  179. package/src/components/SetChildModal.tsx +191 -0
  180. package/src/components/SetParentModal.tsx +187 -0
  181. package/src/components/SlidingPanel.tsx +114 -0
  182. package/src/components/TagUpsert.tsx +142 -0
  183. package/src/components/TopMenuBar.tsx +380 -0
  184. package/src/components/ViewBezierConnector.tsx +143 -0
  185. package/src/components/ViewDrawMenu.tsx +270 -0
  186. package/src/components/ViewEditorEdgeLabelLayout.ts +189 -0
  187. package/src/components/ViewEditorOnboarding.tsx +445 -0
  188. package/src/components/ViewExplorer/TagManager/ColorPicker.tsx +49 -0
  189. package/src/components/ViewExplorer/TagManager/GroupNamingPopover.tsx +96 -0
  190. package/src/components/ViewExplorer/TagManager/LayerItem.tsx +228 -0
  191. package/src/components/ViewExplorer/TagManager/TagItem.tsx +242 -0
  192. package/src/components/ViewExplorer/TagManager/index.tsx +418 -0
  193. package/src/components/ViewExplorer/ViewNavigator.tsx +121 -0
  194. package/src/components/ViewExplorer/ViewSearch.tsx +33 -0
  195. package/src/components/ViewExplorer/ViewTree.tsx +98 -0
  196. package/src/components/ViewExplorer/index.tsx +384 -0
  197. package/src/components/ViewExplorer/types.ts +13 -0
  198. package/src/components/ViewExplorer/utils.ts +56 -0
  199. package/src/components/ViewExplorer-vscode.tsx +8 -0
  200. package/src/components/ViewFloatingMenu-vscode.tsx +248 -0
  201. package/src/components/ViewFloatingMenu.tsx +379 -0
  202. package/src/components/ViewGridNode.tsx +451 -0
  203. package/src/components/ViewHeaderButton.tsx +60 -0
  204. package/src/components/ViewPanel.tsx +162 -0
  205. package/src/components/ViewsGridOnboarding.tsx +400 -0
  206. package/src/components/ZUI/ZUICanvas.tsx +853 -0
  207. package/src/components/ZUI/index.ts +3 -0
  208. package/src/components/ZUI/layout.ts +323 -0
  209. package/src/components/ZUI/proxy.ts +278 -0
  210. package/src/components/ZUI/renderer.ts +1189 -0
  211. package/src/components/ZUI/types.ts +150 -0
  212. package/src/components/ZUI/useZUIInteraction.ts +720 -0
  213. package/src/config/runtime-vscode.ts +46 -0
  214. package/src/config/runtime.ts +30 -0
  215. package/src/constants/colors.ts +80 -0
  216. package/src/constants/diagramColors.ts +9 -0
  217. package/src/context/ThemeContext.tsx +158 -0
  218. package/src/crossBranch/graph.ts +207 -0
  219. package/src/crossBranch/resolve.ts +643 -0
  220. package/src/crossBranch/settings.ts +59 -0
  221. package/src/crossBranch/store.ts +71 -0
  222. package/src/crossBranch/types.ts +102 -0
  223. package/src/demo/DemoPage.tsx +184 -0
  224. package/src/demo/seed.ts +67 -0
  225. package/src/demo/store.ts +536 -0
  226. package/src/demo/viewEditor.ts +110 -0
  227. package/src/hooks/useSafeFitView.ts +60 -0
  228. package/src/index.css +309 -0
  229. package/src/index.ts +184 -0
  230. package/src/kafka-ss.png +0 -0
  231. package/src/lib/vscodeBridge-vscode.ts +27 -0
  232. package/src/lib/vscodeBridge.ts +7 -0
  233. package/src/main.tsx +46 -0
  234. package/src/pages/AppearanceSettings.tsx +135 -0
  235. package/src/pages/Dependencies.tsx +926 -0
  236. package/src/pages/InfiniteZoom.tsx +404 -0
  237. package/src/pages/Settings.tsx +91 -0
  238. package/src/pages/ViewEditor/EDGE_DISTRIBUTION.md +64 -0
  239. package/src/pages/ViewEditor/components/EditorMenus.tsx +112 -0
  240. package/src/pages/ViewEditor/components/EditorOverlays.tsx +172 -0
  241. package/src/pages/ViewEditor/components/EmptyCanvasState.tsx +42 -0
  242. package/src/pages/ViewEditor/context.tsx +21 -0
  243. package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +1349 -0
  244. package/src/pages/ViewEditor/hooks/useDrawingEngine.ts +127 -0
  245. package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +501 -0
  246. package/src/pages/ViewEditor/hooks/useViewData.ts +491 -0
  247. package/src/pages/ViewEditor/index.tsx +1366 -0
  248. package/src/pages/ViewEditor/utils.ts +88 -0
  249. package/src/pages/Views.tsx +171 -0
  250. package/src/pages/ViewsGrid.tsx +1310 -0
  251. package/src/pkg/importer/mermaid.test.ts +141 -0
  252. package/src/pkg/importer/mermaid.ts +76 -0
  253. package/src/platform/PlatformContext.tsx +17 -0
  254. package/src/platform/context.ts +9 -0
  255. package/src/platform/local.tsx +15 -0
  256. package/src/platform/types.ts +19 -0
  257. package/src/slots.ts +92 -0
  258. package/src/styles/editor-panels.css +66 -0
  259. package/src/styles/theme.css +56 -0
  260. package/src/theme.ts +336 -0
  261. package/src/types/index.ts +234 -0
  262. package/src/types/offline-ambient.d.ts +14 -0
  263. package/src/types/vscode-messages.ts +32 -0
  264. package/src/utils/edgeDistribution.ts +103 -0
  265. package/src/utils/githubApi.ts +121 -0
  266. package/src/utils/githubCache.ts +108 -0
  267. package/src/utils/ids.ts +9 -0
  268. package/src/utils/technologyCatalog.ts +143 -0
  269. package/src/utils/toast.ts +100 -0
  270. package/src/utils/treesitter.ts +147 -0
  271. package/src/utils/url.ts +72 -0
  272. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,384 @@
1
+ import React, { memo, useMemo, useState, useRef, useCallback } from 'react'
2
+ import {
3
+ Box,
4
+ Divider,
5
+ HStack,
6
+ VStack,
7
+ } from '@chakra-ui/react'
8
+ import '../../styles/editor-panels.css'
9
+ import SlidingPanel from '../SlidingPanel'
10
+ import PanelHeader from '../PanelHeader'
11
+ import { useViewEditorContext } from '../../pages/ViewEditor/context'
12
+ import type {
13
+ ViewTreeNode,
14
+ ViewConnector,
15
+ PlacedElement,
16
+ LibraryElement,
17
+ ViewLayer,
18
+ Tag,
19
+ } from '../../types'
20
+
21
+ import { buildTree, flattenTree } from './utils'
22
+ import { NavItem, TreeNode } from './types'
23
+ import { ViewNavigator } from './ViewNavigator'
24
+ import { ViewSearch } from './ViewSearch'
25
+ import { ViewTree } from './ViewTree'
26
+ import { TagManager } from './TagManager'
27
+
28
+ interface Props {
29
+ treeNodes: ViewTreeNode[]
30
+ linksMap: Record<number, ViewConnector[]>
31
+ viewElements: PlacedElement[]
32
+ onNavigate: (id: number) => void
33
+ onHoverZoom?: (elementId: number | null, type: 'in' | 'out' | null) => void
34
+ isOpen: boolean
35
+ onToggle: () => void
36
+ isMobile?: boolean
37
+ suppressed?: boolean
38
+ activeTags: string[]
39
+ setActiveTags: (tags: string[]) => void
40
+ hiddenLayerTags: string[]
41
+ setHiddenLayerTags: (tags: string[]) => void
42
+ availableTags: string[]
43
+ tagColors: Record<string, Tag>
44
+ selectedElement?: LibraryElement | null
45
+ onUpdateTags?: (elementId: number, tags: string[]) => Promise<void>
46
+ onCreateTag: (tag: string, color?: string) => Promise<void>
47
+ layers: ViewLayer[]
48
+ onHoverLayer: (tags: string[] | null, color?: string | null) => void
49
+ onCreateLayer: (name: string, tags: string[], color: string) => Promise<void>
50
+ onUpdateLayer: (layer: ViewLayer) => Promise<void>
51
+ onDeleteLayer: (id: number) => Promise<void>
52
+ }
53
+
54
+ function ViewExplorer({
55
+ treeNodes,
56
+ linksMap,
57
+ viewElements,
58
+ onNavigate,
59
+ onHoverZoom,
60
+ isOpen,
61
+ onToggle,
62
+ isMobile = false,
63
+ suppressed = false,
64
+ hiddenLayerTags,
65
+ setHiddenLayerTags,
66
+ availableTags,
67
+ tagColors,
68
+ selectedElement,
69
+ onUpdateTags,
70
+ onCreateTag,
71
+ layers,
72
+ onHoverLayer,
73
+ onCreateLayer,
74
+ onUpdateLayer,
75
+ onDeleteLayer,
76
+ }: Props) {
77
+ const { viewId } = useViewEditorContext()
78
+ const [query, setQuery] = useState('')
79
+ const [activeFilter, setActiveFilter] = useState<'out' | 'in' | null>(null)
80
+ const [focusedIdx, setFocusedIdx] = useState(-1)
81
+ const [bottomHeight, setBottomHeight] = useState<number | undefined>(undefined)
82
+
83
+ const containerRef = useRef<HTMLDivElement>(null)
84
+ const listRef = useRef<HTMLDivElement>(null)
85
+ const itemRefs = useRef<(HTMLDivElement | null)[]>([])
86
+
87
+ const onCanvasIds = useMemo(() => new Set(viewElements.map((o) => o.element_id)), [viewElements])
88
+
89
+ // --- Layer/Tag counts ---
90
+ const layerCounts = useMemo(() => {
91
+ const counts: Record<number, number> = {}
92
+ layers.forEach((layer) => {
93
+ counts[layer.id] = viewElements.filter((obj) =>
94
+ (obj.tags || []).some((t) => layer.tags.includes(t))
95
+ ).length
96
+ })
97
+ return counts
98
+ }, [layers, viewElements])
99
+
100
+ const tagCounts = useMemo(() => {
101
+ const counts: Record<string, number> = {}
102
+ availableTags.forEach((tag) => {
103
+ counts[tag] = viewElements.filter((obj) => (obj.tags || []).includes(tag)).length
104
+ })
105
+ return counts
106
+ }, [availableTags, viewElements])
107
+
108
+ // --- Navigation helpers ---
109
+ const handleNavigate = (id: number) => {
110
+ setActiveFilter(null)
111
+ onNavigate(id)
112
+ if (isMobile) onToggle()
113
+ }
114
+
115
+ const roots = useMemo(() => buildTree(treeNodes), [treeNodes])
116
+ const flat = useMemo(() => flattenTree(roots), [roots])
117
+
118
+ const parents = useMemo(() => {
119
+ if (viewId == null) return []
120
+ const diagMap = new Map<number, NavItem>()
121
+
122
+ // Find view path in tree to identify structural parent
123
+ const findViewPath = (nodes: TreeNode[], targetId: number, path: TreeNode[] = []): TreeNode[] | null => {
124
+ for (const node of nodes) {
125
+ if (node.id === targetId) return [...path, node]
126
+ const found = findViewPath(node.children, targetId, [...path, node])
127
+ if (found) return found
128
+ }
129
+ return null
130
+ }
131
+
132
+ const viewPath = findViewPath(roots, viewId)
133
+ const p = viewPath && viewPath.length > 1 ? viewPath[viewPath.length - 2] : null
134
+
135
+ if (p) {
136
+ diagMap.set(p.id, { id: p.id, name: p.name, subtitle: 'Parent View' })
137
+ }
138
+
139
+ return Array.from(diagMap.values())
140
+ }, [viewId, roots])
141
+
142
+ const children = useMemo(() => {
143
+ if (viewId == null) return []
144
+ const diagMap = new Map<number, NavItem>()
145
+ flat.forEach((n) => {
146
+ if (n.parent_view_id === viewId)
147
+ diagMap.set(n.id, { id: n.id, name: n.name, subtitle: 'Child View' })
148
+ })
149
+ const objMap = new Map(viewElements.map((o) => [o.element_id, o.name]))
150
+ Object.entries(linksMap).forEach(([elementIdStr, links]) => {
151
+ const elementId = Number(elementIdStr)
152
+ if (!Number.isFinite(elementId)) return
153
+ const isOnCanvas = onCanvasIds.has(elementId)
154
+ const objName = objMap.get(elementId) || 'Element'
155
+ links.forEach((link) => {
156
+ const existing = diagMap.get(link.to_view_id)
157
+ if (existing) {
158
+ if (!existing.elementId && isOnCanvas) existing.elementId = elementId
159
+ if (existing.subtitle === 'Child View') {
160
+ existing.subtitle = `Child View (Via ${objName})`
161
+ } else if (!existing.subtitle?.includes(objName)) {
162
+ existing.subtitle = `${existing.subtitle}, ${objName}`
163
+ }
164
+ } else {
165
+ diagMap.set(link.to_view_id, {
166
+ id: link.to_view_id,
167
+ name: link.to_view_name,
168
+ subtitle: `Via ${objName}`,
169
+ elementId: isOnCanvas ? elementId : undefined,
170
+ })
171
+ }
172
+ })
173
+ })
174
+ return Array.from(diagMap.values())
175
+ }, [viewId, flat, linksMap, viewElements, onCanvasIds])
176
+
177
+ const viewHoverMap = useMemo(() => {
178
+ const map = new Map<number, { elementId: number | undefined; type: 'in' | 'out' }>()
179
+ parents.forEach((p) => map.set(p.id, { elementId: p.elementId, type: 'out' }))
180
+ children.forEach((c) => map.set(c.id, { elementId: c.elementId, type: 'in' }))
181
+ return map
182
+ }, [parents, children])
183
+
184
+ const filteredByMode = useMemo(() => {
185
+ if (activeFilter === 'out') {
186
+ const parentIds = new Set(parents.map((p) => p.id))
187
+ const ancestors = new Set<number>()
188
+ const findAncestors = (id: number) => {
189
+ const node = treeNodes.find(n => n.id === id)
190
+ if (node?.parent_view_id) {
191
+ ancestors.add(node.parent_view_id)
192
+ findAncestors(node.parent_view_id)
193
+ }
194
+ }
195
+ if (viewId) findAncestors(viewId)
196
+ return flat.filter((n) => parentIds.has(n.id) || ancestors.has(n.id) || n.id === viewId)
197
+ }
198
+ if (activeFilter === 'in') {
199
+ const childIds = new Set(children.map((c) => c.id))
200
+ const subtree = new Set<number>()
201
+ const traverse = (nodes: TreeNode[]) => {
202
+ nodes.forEach(n => {
203
+ subtree.add(n.id)
204
+ traverse(n.children)
205
+ })
206
+ }
207
+ const current = flat.find(n => n.id === viewId)
208
+ if (current) traverse(current.children)
209
+ return flat.filter((n) => childIds.has(n.id) || subtree.has(n.id) || n.id === viewId)
210
+ }
211
+ return flat
212
+ }, [flat, parents, children, activeFilter, viewId, treeNodes])
213
+
214
+ const filtered = useMemo(() => {
215
+ if (!query.trim()) return filteredByMode
216
+ const q = query.toLowerCase()
217
+ return filteredByMode.filter((n) => n.name.toLowerCase().includes(q))
218
+ }, [filteredByMode, query])
219
+
220
+ const handleKeyDown = (e: React.KeyboardEvent) => {
221
+ if (filtered.length === 0) return
222
+ switch (e.key) {
223
+ case 'ArrowDown':
224
+ e.preventDefault(); setFocusedIdx((i) => Math.min(i + 1, filtered.length - 1)); break
225
+ case 'ArrowUp':
226
+ e.preventDefault(); setFocusedIdx((i) => Math.max(i - 1, 0)); break
227
+ case 'Enter':
228
+ if (focusedIdx >= 0 && filtered[focusedIdx]) { e.preventDefault(); handleNavigate(filtered[focusedIdx].id) }; break
229
+ case 'Escape':
230
+ e.preventDefault(); setActiveFilter(null); break
231
+ }
232
+ }
233
+
234
+ const handleDragStart = useCallback(
235
+ (e: React.MouseEvent) => {
236
+ e.preventDefault()
237
+ const dragStartY = e.clientY
238
+ const dragStartHeight = bottomHeight ?? (containerRef.current ? containerRef.current.offsetHeight / 2 : 240)
239
+ const onMouseMove = (ev: MouseEvent) => {
240
+ const delta = dragStartY - ev.clientY
241
+ const maxHeight = containerRef.current ? containerRef.current.offsetHeight - 120 : 500
242
+ setBottomHeight(Math.max(80, Math.min(maxHeight, dragStartHeight + delta)))
243
+ }
244
+ const onMouseUp = () => {
245
+ window.removeEventListener('mousemove', onMouseMove)
246
+ window.removeEventListener('mouseup', onMouseUp)
247
+ }
248
+ window.addEventListener('mousemove', onMouseMove)
249
+ window.addEventListener('mouseup', onMouseUp)
250
+ },
251
+ [bottomHeight]
252
+ )
253
+
254
+ const handleToggleTagOnElement = async (tag: string) => {
255
+ if (!selectedElement || !onUpdateTags) return
256
+ const currentTags = selectedElement.tags || []
257
+ const nextTags = currentTags.includes(tag)
258
+ ? currentTags.filter((t) => t !== tag)
259
+ : [...currentTags, tag]
260
+ await onUpdateTags(selectedElement.id, nextTags)
261
+ }
262
+
263
+ return (
264
+ <SlidingPanel
265
+ isOpen={isOpen && !suppressed}
266
+ onClose={onToggle}
267
+ panelKey="view-explorer"
268
+ side={isMobile ? 'left' : 'right'}
269
+ width="300px"
270
+ zIndex={1000}
271
+ hasBackdrop={isMobile}
272
+ >
273
+ <VStack ref={containerRef} align="stretch" spacing={0} h="full" overflow="hidden">
274
+ {/* Panel header */}
275
+ <PanelHeader title="Explorer" onClose={onToggle} hasCloseButton={isMobile} />
276
+
277
+ <ViewNavigator
278
+ parents={parents}
279
+ children={children}
280
+ activeFilter={activeFilter}
281
+ onFilterToggle={(type, items) => {
282
+ if (items.length === 0) return
283
+ if (items.length === 1) { handleNavigate(items[0].id); return }
284
+ setActiveFilter((prev) => (prev === type ? null : type))
285
+ }}
286
+ onHoverZoom={onHoverZoom}
287
+ />
288
+ <Divider borderColor="whiteAlpha.100" />
289
+
290
+ <ViewSearch query={query} setQuery={setQuery} activeFilter={activeFilter} />
291
+
292
+ <ViewTree
293
+ filtered={filtered}
294
+ viewId={viewId}
295
+ focusedIdx={focusedIdx}
296
+ itemRefs={itemRefs}
297
+ handleNavigate={handleNavigate}
298
+ onHoverZoom={onHoverZoom}
299
+ viewHoverMap={viewHoverMap}
300
+ handleKeyDown={handleKeyDown}
301
+ listRef={listRef}
302
+ />
303
+
304
+ {/* --- Drag handle --- */}
305
+ <Box
306
+ h="14px"
307
+ flexShrink={0}
308
+ cursor="row-resize"
309
+ position="relative"
310
+ onMouseDown={handleDragStart}
311
+ userSelect="none"
312
+ role="group"
313
+ aria-label="Resize panels"
314
+ transition="all 0.2s"
315
+ _hover={{ bg: 'whiteAlpha.100' }}
316
+ >
317
+ {/* Visual separator line */}
318
+ <Divider borderColor="whiteAlpha.200" />
319
+
320
+ {/* Draggable handle indicator */}
321
+ <Box
322
+ position="absolute"
323
+ top="50%"
324
+ left="50%"
325
+ transform="translate(-50%, -50%)"
326
+ w="40px"
327
+ h="4px"
328
+ rounded="full"
329
+ bg="whiteAlpha.300"
330
+ pointerEvents="none"
331
+ transition="all 0.2s cubic-bezier(0.4, 0, 0.2, 1)"
332
+ _groupHover={{
333
+ w: "48px",
334
+ bg: "whiteAlpha.500",
335
+ boxShadow: "0 0 10px rgba(255, 255, 255, 0.1)"
336
+ }}
337
+ />
338
+
339
+ {/* Subtle textured dots for "drag" affordance */}
340
+ <HStack
341
+ position="absolute"
342
+ top="50%"
343
+ left="50%"
344
+ transform="translate(-50%, -50%)"
345
+ spacing="4px"
346
+ pointerEvents="none"
347
+ opacity={0.3}
348
+ _groupHover={{ opacity: 0.6 }}
349
+ transition="opacity 0.2s"
350
+ >
351
+ {[0, 1, 2].map((i) => (
352
+ <Box key={i} w="2px" h="2px" rounded="full" bg="white" />
353
+ ))}
354
+ </HStack>
355
+ </Box>
356
+
357
+ <Box
358
+ height={bottomHeight !== undefined ? `${bottomHeight}px` : '50%'}
359
+ display="flex" flexDirection="column" overflow="hidden"
360
+ >
361
+ <TagManager
362
+ availableTags={availableTags}
363
+ tagColors={tagColors}
364
+ layers={layers}
365
+ viewElements={viewElements}
366
+ selectedElement={selectedElement || null}
367
+ hiddenLayerTags={hiddenLayerTags}
368
+ setHiddenLayerTags={setHiddenLayerTags}
369
+ onToggleTagOnElement={handleToggleTagOnElement}
370
+ onCreateTag={onCreateTag}
371
+ onHoverLayer={onHoverLayer}
372
+ onCreateLayer={onCreateLayer}
373
+ onUpdateLayer={onUpdateLayer}
374
+ onDeleteLayer={onDeleteLayer}
375
+ tagCounts={tagCounts}
376
+ layerCounts={layerCounts}
377
+ />
378
+ </Box>
379
+ </VStack>
380
+ </SlidingPanel>
381
+ )
382
+ }
383
+
384
+ export default memo(ViewExplorer)
@@ -0,0 +1,13 @@
1
+ import type { ViewTreeNode } from '../../types'
2
+
3
+ export interface TreeNode extends ViewTreeNode {
4
+ children: TreeNode[]
5
+ depth: number
6
+ }
7
+
8
+ export interface NavItem {
9
+ id: number
10
+ name: string
11
+ subtitle?: string
12
+ elementId?: number
13
+ }
@@ -0,0 +1,56 @@
1
+ import type { ViewTreeNode } from '../../types'
2
+ import type { TreeNode } from './types'
3
+
4
+ export const SWATCH_COLORS = [
5
+ '#F56565', '#ED8936', '#ECC94B', '#48BB78', '#38B2AC',
6
+ '#4299E1', '#667EEA', '#9F7AEA', '#ED64A6', '#A0AEC0',
7
+ ]
8
+
9
+ export function pickUnusedColor(usedColors: string[]): string {
10
+ const used = new Set(usedColors.map((c) => c.toLowerCase()))
11
+ const pool = SWATCH_COLORS.filter((c) => !used.has(c.toLowerCase()))
12
+ const source = pool.length > 0 ? pool : SWATCH_COLORS
13
+ return source[Math.floor(Math.random() * source.length)]
14
+ }
15
+
16
+ export function buildTree(nodes: ViewTreeNode[]): TreeNode[] {
17
+ const allNodes: ViewTreeNode[] = []
18
+ const traverseFlatten = (n: ViewTreeNode) => {
19
+ allNodes.push(n)
20
+ if (n.children) n.children.forEach(traverseFlatten)
21
+ }
22
+ nodes.forEach(traverseFlatten)
23
+
24
+ const map = new Map<number, TreeNode>()
25
+ allNodes.forEach((n) => map.set(n.id, { ...n, children: [], depth: 0 }))
26
+
27
+ const roots: TreeNode[] = []
28
+ map.forEach((node) => {
29
+ const pid = node.parent_view_id
30
+ if (pid !== null && map.has(pid)) {
31
+ map.get(pid)!.children.push(node)
32
+ } else {
33
+ roots.push(node)
34
+ }
35
+ })
36
+
37
+ const assignDepthAndSort = (node: TreeNode, depth: number) => {
38
+ node.depth = depth
39
+ node.children.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime())
40
+ node.children.forEach((c) => assignDepthAndSort(c, depth + 1))
41
+ }
42
+ roots.forEach((r) => assignDepthAndSort(r, 0))
43
+ roots.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime())
44
+
45
+ return roots
46
+ }
47
+
48
+ export function flattenTree(roots: TreeNode[]): TreeNode[] {
49
+ const result: TreeNode[] = []
50
+ const traverse = (node: TreeNode) => {
51
+ result.push(node)
52
+ node.children.forEach(traverse)
53
+ }
54
+ roots.forEach(traverse)
55
+ return result
56
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * VS Code variant of ViewExplorer.
3
+ * Diagram navigation is surfaced as a native VS Code tree view instead.
4
+ * This stub keeps the webview bundle lean.
5
+ */
6
+ export default function ViewExplorer() {
7
+ return null
8
+ }