@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,46 @@
1
+ /**
2
+ * VS Code webview runtime configuration.
3
+ *
4
+ * Replaces runtime.ts for the vscode webview build:
5
+ * - Reads server URL from window.__TLD_SERVER_URL__ instead of import.meta.env
6
+ * - No Capacitor dependency (isNativeApp = false)
7
+ */
8
+
9
+ declare global {
10
+ interface Window {
11
+ __TLD_SERVER_URL__?: string
12
+ }
13
+ }
14
+
15
+ function trimTrailingSlash(value: string): string {
16
+ return value.replace(/\/+$/, '')
17
+ }
18
+
19
+ const serverUrl = trimTrailingSlash(window.__TLD_SERVER_URL__ ?? 'https://tldiagram.com')
20
+
21
+ export const appBase = '/app/'
22
+ export const routerBasename = undefined
23
+ export const isNativeApp = false
24
+
25
+ export const apiBase = `${serverUrl}/api`
26
+
27
+ export function apiUrl(path: string): string {
28
+ return `${apiBase}${path.startsWith('/') ? path : `/${path}`}`
29
+ }
30
+
31
+ export function oauthGoogleStartUrl(): string {
32
+ return apiUrl('/auth/oauth/google')
33
+ }
34
+
35
+ export function oauthGithubStartUrl(): string {
36
+ return apiUrl('/auth/oauth/github')
37
+ }
38
+
39
+ export function oauthAppleStartUrl(): string {
40
+ return apiUrl('/auth/oauth/apple')
41
+ }
42
+
43
+ // Web OAuth client ID - also used as serverClientId for native Google Sign-In
44
+ export const googleClientId = '945690634753-lcrtd97c5hnqdo5shkoaetstmtrqbk5t.apps.googleusercontent.com'
45
+
46
+ export const turnstileSiteKey = '0x4AAAAAACyQUcIpN2Yuy8-a'
@@ -0,0 +1,30 @@
1
+ const DEFAULT_WEB_BASE = "/"
2
+
3
+ function trimTrailingSlash(value: string): string {
4
+ return value.replace(/\/+$/, "")
5
+ }
6
+
7
+ function trim(value: string | undefined): string | undefined {
8
+ if (!value) return undefined
9
+ const cleaned = value.trim()
10
+ return cleaned.length > 0 ? cleaned : undefined
11
+ }
12
+
13
+ const configuredApiBase = trim(import.meta.env.VITE_API_BASE_URL)
14
+
15
+ export const appBase = trim(import.meta.env.VITE_APP_BASE) ?? DEFAULT_WEB_BASE
16
+ export const routerBasename = (() => {
17
+ const configuredBase = trim(import.meta.env.VITE_ROUTER_BASENAME)
18
+ if (configuredBase && configuredBase !== "/") return configuredBase
19
+ return undefined
20
+ })()
21
+
22
+ export const isNativeApp = false
23
+
24
+ export const apiBase = trimTrailingSlash(
25
+ configuredApiBase ?? "/api",
26
+ )
27
+
28
+ export function apiUrl(path: string): string {
29
+ return `${apiBase}${path.startsWith("/") ? path : `/${path}`}`
30
+ }
@@ -0,0 +1,80 @@
1
+ // ── Accent color ────────────────────────────────────────────────────────────
2
+ // The interactive brand color. Drives edges, selection highlights, drawing
3
+ // stroke default, and focus indicators. Users can customize via Appearance
4
+ // settings; the chosen value is stored in localStorage and exposed as the
5
+ // CSS variable --accent on :root.
6
+
7
+ export const ACCENT_DEFAULT = '#63b3ed'
8
+
9
+ export const ACCENT_OPTIONS: { name: string; value: string }[] = [
10
+ { name: 'Blue', value: '#63b3ed' },
11
+ { name: 'Teal', value: '#4fd1c5' },
12
+ { name: 'Purple', value: '#b794f4' },
13
+ { name: 'Green', value: '#68d391' },
14
+ { name: 'Orange', value: '#f6ad55' },
15
+ { name: 'Pink', value: '#f687b3' },
16
+ { name: 'White', value: '#ffffff' },
17
+ { name: 'Black', value: '#000000' },
18
+ { name: 'Red', value: '#f56565' },
19
+ { name: 'Yellow', value: '#ecc94b' },
20
+ ]
21
+
22
+ // ── Background color ─────────────────────────────────────────────────────────
23
+ export const BACKGROUND_DEFAULT = '#0d121e'
24
+
25
+ export const BACKGROUND_OPTIONS: { name: string; value: string }[] = [
26
+ { name: 'Midnight', value: '#0d121e' }, // Original dark blue/purplish
27
+ { name: 'Deep Sea', value: '#0a192f' },
28
+ { name: 'Obsidian', value: '#0f172a' },
29
+ { name: 'Coal', value: '#111111' },
30
+ { name: 'Space', value: '#0b0e14' },
31
+ { name: 'Asphalt', value: '#1a202c' },
32
+ { name: 'Dark', value: '#1e1e1e' },
33
+ { name: 'Ebony', value: '#121212' },
34
+ { name: 'Pitch', value: '#000000' },
35
+ { name: 'Charcoal', value: '#242424' },
36
+ ]
37
+
38
+ // ── Element color ────────────────────────────────────────────────────────────
39
+ export const ELEMENT_DEFAULT = '#2d3748'
40
+
41
+ export const ELEMENT_OPTIONS: { name: string; value: string }[] = [
42
+ { name: 'Slate', value: '#2d3748' },
43
+ { name: 'Navy', value: '#1a365d' },
44
+ { name: 'Deep Purple', value: '#322659' },
45
+ { name: 'Dark Grey', value: '#171923' },
46
+ { name: 'Steel', value: '#4a5568' },
47
+ { name: 'Cobalt', value: '#2c5282' },
48
+ { name: 'Forest', value: '#22543d' },
49
+ { name: 'Rust', value: '#742a2a' },
50
+ { name: 'Bronze', value: '#744210' },
51
+ { name: 'Amethyst', value: '#553c9a' },
52
+ ]
53
+
54
+ // ── View hierarchy ───────────────────────────────────────────────────────────
55
+ // Blue = parent / zoom-out direction (equivalent to Chakra blue.400)
56
+ // Teal = child / zoom-in direction (equivalent to Chakra teal.400)
57
+
58
+ export const PARENT_VIEW_COLOR = '#63b3ed'
59
+ export const CHILD_VIEW_COLOR = '#4fd1c5'
60
+
61
+ export const PARENT_VIEW_BG = 'rgba(99,179,237,0.12)'
62
+ export const PARENT_VIEW_BORDER = 'rgba(99,179,237,0.25)'
63
+ export const CHILD_VIEW_BG = 'rgba(79,209,197,0.12)'
64
+ export const CHILD_VIEW_BORDER = 'rgba(79,209,197,0.25)'
65
+
66
+ // ── Drawing toolbar palette ───────────────────────────────────────────────────
67
+ export const DRAWING_COLORS = ['#63b3ed', '#f56565', '#48bb78', '#ecc94b', '#ed64a6', '#ffffff']
68
+
69
+ // ── Utility ───────────────────────────────────────────────────────────────────
70
+ /**
71
+ * Convert a 6-char hex color to `rgba(r, g, b, alpha)`.
72
+ * Used to compute dynamic shadows from the accent value.
73
+ */
74
+ export function hexToRgba(hex: string, alpha: number): string {
75
+ const h = hex.replace('#', '')
76
+ const r = parseInt(h.substring(0, 2), 16)
77
+ const g = parseInt(h.substring(2, 4), 16)
78
+ const b = parseInt(h.substring(4, 6), 16)
79
+ return `rgba(${r},${g},${b},${alpha})`
80
+ }
@@ -0,0 +1,9 @@
1
+ // Re-exports - all values have moved to constants/colors.ts.
2
+ export {
3
+ PARENT_VIEW_COLOR as PARENT_DIAGRAM_COLOR,
4
+ CHILD_VIEW_COLOR as CHILD_DIAGRAM_COLOR,
5
+ PARENT_VIEW_BG as PARENT_DIAGRAM_BG,
6
+ PARENT_VIEW_BORDER as PARENT_DIAGRAM_BORDER,
7
+ CHILD_VIEW_BG as CHILD_DIAGRAM_BG,
8
+ CHILD_VIEW_BORDER as CHILD_DIAGRAM_BORDER,
9
+ } from './colors'
@@ -0,0 +1,158 @@
1
+ /* eslint-disable react-refresh/only-export-components */
2
+ import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
3
+ import { ACCENT_DEFAULT, BACKGROUND_DEFAULT, ELEMENT_DEFAULT, hexToRgba } from '../constants/colors'
4
+ import { api } from '../api/client'
5
+
6
+ const ACCENT_KEY = 'diag:accent-color'
7
+ const BG_KEY = 'diag:background-color'
8
+ const ELEMENT_COLOR_KEY = 'diag:element-color'
9
+
10
+ /** Convert hex to "r,g,b" triplet for use in rgba(var(--rgb), alpha). */
11
+ function toRgbTriplet(hex: string): string {
12
+ const rgba = hexToRgba(hex, 1)
13
+ // hexToRgba returns "rgba(r,g,b,1)" - extract "r,g,b"
14
+ return rgba.slice(5, -3)
15
+ }
16
+
17
+ function applyAccentVars(hex: string) {
18
+ document.documentElement.style.setProperty('--accent', hex)
19
+ document.documentElement.style.setProperty('--accent-rgb', toRgbTriplet(hex))
20
+ }
21
+
22
+ function applyBgVars(hex: string) {
23
+ document.documentElement.style.setProperty('--bg-main', hex)
24
+ document.documentElement.style.setProperty('--bg-main-rgb', toRgbTriplet(hex))
25
+
26
+ // Also derive canvas background (slightly darker)
27
+ // For simplicity, we just use the same or a hardcoded variant for now,
28
+ // but we could use color-mix if we wanted true derivation.
29
+ // document.documentElement.style.setProperty('--bg-canvas', `color-mix(in srgb, ${hex}, black 20%)`)
30
+ }
31
+
32
+ function applyElementVars(hex: string) {
33
+ document.documentElement.style.setProperty('--bg-element', hex)
34
+ document.documentElement.style.setProperty('--bg-element-rgb', toRgbTriplet(hex))
35
+ }
36
+
37
+ interface ThemeContextValue {
38
+ accent: string
39
+ setAccent: (value: string) => void
40
+ background: string
41
+ setBackground: (value: string) => void
42
+ elementColor: string
43
+ setElementColor: (value: string) => void
44
+ }
45
+
46
+ const ThemeContext = createContext<ThemeContextValue>({
47
+ accent: ACCENT_DEFAULT,
48
+ setAccent: () => { },
49
+ background: BACKGROUND_DEFAULT,
50
+ setBackground: () => { },
51
+ elementColor: ELEMENT_DEFAULT,
52
+ setElementColor: () => { },
53
+ })
54
+
55
+ export function ThemeProvider({
56
+ children,
57
+ isAuthenticated,
58
+ defaultAccent,
59
+ defaultBackground,
60
+ defaultElementColor,
61
+ storagePrefix,
62
+ }: {
63
+ children: ReactNode
64
+ isAuthenticated?: boolean
65
+ defaultAccent?: string
66
+ defaultBackground?: string
67
+ defaultElementColor?: string
68
+ storagePrefix?: string
69
+ }) {
70
+ const accentKey = storagePrefix ? `${storagePrefix}:accent-color` : ACCENT_KEY
71
+ const bgKey = storagePrefix ? `${storagePrefix}:background-color` : BG_KEY
72
+ const elementKey = storagePrefix ? `${storagePrefix}:element-color` : ELEMENT_COLOR_KEY
73
+
74
+ const [accent, setAccentState] = useState<string>(
75
+ () => localStorage.getItem(accentKey) ?? defaultAccent ?? ACCENT_DEFAULT,
76
+ )
77
+ const [background, setBackgroundState] = useState<string>(
78
+ () => localStorage.getItem(bgKey) ?? defaultBackground ?? BACKGROUND_DEFAULT,
79
+ )
80
+ const [elementColor, setElementColorState] = useState<string>(
81
+ () => localStorage.getItem(elementKey) ?? defaultElementColor ?? ELEMENT_DEFAULT,
82
+ )
83
+
84
+ // Apply CSS vars whenever accent or background changes
85
+ useEffect(() => {
86
+ applyAccentVars(accent)
87
+ }, [accent])
88
+
89
+ useEffect(() => {
90
+ applyBgVars(background)
91
+ }, [background])
92
+
93
+ useEffect(() => {
94
+ applyElementVars(elementColor)
95
+ }, [elementColor])
96
+
97
+ // Fetch server preferences only when authenticated and NOT in namespaced/demo mode
98
+ useEffect(() => {
99
+ if (!isAuthenticated || storagePrefix) return
100
+ api.user.getPreferences().then((prefs) => {
101
+ if (prefs.accent_color && prefs.accent_color !== accent) {
102
+ localStorage.setItem(accentKey, prefs.accent_color)
103
+ setAccentState(prefs.accent_color)
104
+ }
105
+ if (prefs.background_color && prefs.background_color !== background) {
106
+ localStorage.setItem(bgKey, prefs.background_color)
107
+ setBackgroundState(prefs.background_color)
108
+ }
109
+ if (prefs.element_color && prefs.element_color !== elementColor) {
110
+ localStorage.setItem(elementKey, prefs.element_color)
111
+ setElementColorState(prefs.element_color)
112
+ }
113
+ }).catch(() => { })
114
+ }, [isAuthenticated, storagePrefix]) // eslint-disable-line react-hooks/exhaustive-deps
115
+
116
+ function setAccent(value: string) {
117
+ localStorage.setItem(accentKey, value)
118
+ setAccentState(value)
119
+ if (!storagePrefix) {
120
+ api.user.updatePreferences({ accent_color: value }).catch(() => { })
121
+ }
122
+ }
123
+
124
+ function setBackground(value: string) {
125
+ localStorage.setItem(bgKey, value)
126
+ setBackgroundState(value)
127
+ if (!storagePrefix) {
128
+ api.user.updatePreferences({ background_color: value }).catch(() => { })
129
+ }
130
+ }
131
+
132
+ function setElementColor(value: string) {
133
+ localStorage.setItem(elementKey, value)
134
+ setElementColorState(value)
135
+ if (!storagePrefix) {
136
+ api.user.updatePreferences({ element_color: value }).catch(() => { })
137
+ }
138
+ }
139
+
140
+ return (
141
+ <ThemeContext.Provider value={{ accent, setAccent, background, setBackground, elementColor, setElementColor }}>
142
+ {children}
143
+ </ThemeContext.Provider>
144
+ )
145
+ }
146
+
147
+ export function useTheme() {
148
+ return useContext(ThemeContext)
149
+ }
150
+
151
+ /**
152
+ * Backward compatibility alias
153
+ * @deprecated Use useTheme() instead
154
+ */
155
+ export function useAccentColor() {
156
+ const { accent, setAccent } = useTheme()
157
+ return { accent, setAccent }
158
+ }
@@ -0,0 +1,207 @@
1
+ import type { Connector, ExploreData, PlacedElement, ViewTreeNode } from '../types'
2
+ import type { GraphPlacementRef, WorkspaceGraphSnapshot } from './types'
3
+
4
+ function cloneViewTree(nodes: ViewTreeNode[]): ViewTreeNode[] {
5
+ return nodes.map((node) => ({
6
+ ...node,
7
+ children: cloneViewTree(node.children ?? []),
8
+ }))
9
+ }
10
+
11
+ function flattenTree(nodes: ViewTreeNode[], out: ViewTreeNode[] = []): ViewTreeNode[] {
12
+ for (const node of nodes) {
13
+ out.push(node)
14
+ flattenTree(node.children ?? [], out)
15
+ }
16
+ return out
17
+ }
18
+
19
+ function collectDescendants(viewById: Record<number, ViewTreeNode>, parentId: number): number[] {
20
+ const result: number[] = []
21
+ const visit = (viewId: number) => {
22
+ result.push(viewId)
23
+ const node = viewById[viewId]
24
+ for (const child of node?.children ?? []) visit(child.id)
25
+ }
26
+ visit(parentId)
27
+ return result
28
+ }
29
+
30
+ function viewNameOf(viewById: Record<number, ViewTreeNode>, viewId: number): string {
31
+ return viewById[viewId]?.name ?? `View ${viewId}`
32
+ }
33
+
34
+ function deepCloneExploreData(data: ExploreData): ExploreData {
35
+ return {
36
+ tree: cloneViewTree(data.tree ?? []),
37
+ navigations: [...(data.navigations ?? [])],
38
+ views: Object.fromEntries(
39
+ Object.entries(data.views ?? {}).map(([key, value]) => [
40
+ key,
41
+ {
42
+ placements: [...(value.placements ?? [])],
43
+ connectors: [...(value.connectors ?? [])],
44
+ },
45
+ ]),
46
+ ),
47
+ }
48
+ }
49
+
50
+ export function buildWorkspaceGraphSnapshot(data: ExploreData): WorkspaceGraphSnapshot {
51
+ const tree = cloneViewTree(data.tree ?? [])
52
+ const flatTree = flattenTree(tree)
53
+ const viewById = Object.fromEntries(flatTree.map((view) => [view.id, view])) as Record<number, ViewTreeNode>
54
+
55
+ const placementsByViewId: Record<number, PlacedElement[]> = {}
56
+ const connectorsByViewId: Record<number, Connector[]> = {}
57
+ const placementsByElementId: Record<number, GraphPlacementRef[]> = {}
58
+
59
+ for (const view of flatTree) {
60
+ const content = data.views?.[String(view.id)]
61
+ const placements = [...(content?.placements ?? [])]
62
+ const connectors = [...(content?.connectors ?? [])]
63
+ placementsByViewId[view.id] = placements
64
+ connectorsByViewId[view.id] = connectors
65
+
66
+ for (const placement of placements) {
67
+ placementsByElementId[placement.element_id] ??= []
68
+ placementsByElementId[placement.element_id].push({
69
+ viewId: view.id,
70
+ viewName: view.name,
71
+ element: placement,
72
+ })
73
+ }
74
+ }
75
+
76
+ const childViewIdByOwnerElementId = flatTree.reduce<Record<number, number>>((acc, view) => {
77
+ if (view.owner_element_id != null) acc[view.owner_element_id] = view.id
78
+ return acc
79
+ }, {})
80
+
81
+ const ancestorsByViewId = flatTree.reduce<Record<number, number[]>>((acc, view) => {
82
+ const lineage: number[] = []
83
+ let cursor: ViewTreeNode | undefined = view
84
+ while (cursor) {
85
+ lineage.push(cursor.id)
86
+ cursor = cursor.parent_view_id != null ? viewById[cursor.parent_view_id] : undefined
87
+ }
88
+ acc[view.id] = lineage.reverse()
89
+ return acc
90
+ }, {})
91
+
92
+ const descendantsByViewId = flatTree.reduce<Record<number, number[]>>((acc, view) => {
93
+ acc[view.id] = collectDescendants(viewById, view.id)
94
+ return acc
95
+ }, {})
96
+
97
+ const views = flatTree.reduce<Record<number, { view: ViewTreeNode; placements: PlacedElement[]; connectors: Connector[] }>>((acc, view) => {
98
+ acc[view.id] = {
99
+ view,
100
+ placements: placementsByViewId[view.id] ?? [],
101
+ connectors: connectorsByViewId[view.id] ?? [],
102
+ }
103
+ return acc
104
+ }, {})
105
+
106
+ return {
107
+ source: deepCloneExploreData(data),
108
+ tree,
109
+ views,
110
+ viewById,
111
+ placementsByViewId,
112
+ connectorsByViewId,
113
+ placementsByElementId,
114
+ childViewIdByOwnerElementId,
115
+ descendantsByViewId,
116
+ ancestorsByViewId,
117
+ }
118
+ }
119
+
120
+ export function isDescendantView(snapshot: WorkspaceGraphSnapshot, maybeDescendantId: number | null | undefined, ancestorId: number | null | undefined): boolean {
121
+ if (maybeDescendantId == null || ancestorId == null) return false
122
+ return snapshot.ancestorsByViewId[maybeDescendantId]?.includes(ancestorId) ?? false
123
+ }
124
+
125
+ export function findLowestCommonAncestorViewId(snapshot: WorkspaceGraphSnapshot, leftViewId: number | null | undefined, rightViewId: number | null | undefined): number | null {
126
+ if (leftViewId == null || rightViewId == null) return null
127
+ const leftAncestors = snapshot.ancestorsByViewId[leftViewId] ?? []
128
+ const rightAncestors = new Set(snapshot.ancestorsByViewId[rightViewId] ?? [])
129
+ let best: number | null = null
130
+ for (const candidate of leftAncestors) {
131
+ if (rightAncestors.has(candidate)) best = candidate
132
+ }
133
+ return best
134
+ }
135
+
136
+ export function relativeOwnerElementPath(snapshot: WorkspaceGraphSnapshot, ancestorViewId: number, descendantViewId: number): number[] {
137
+ const descendantAncestors = snapshot.ancestorsByViewId[descendantViewId] ?? []
138
+ const ancestorIndex = descendantAncestors.indexOf(ancestorViewId)
139
+ if (ancestorIndex === -1) return []
140
+ const relativeViewIds = descendantAncestors.slice(ancestorIndex + 1)
141
+ return relativeViewIds
142
+ .map((viewId) => snapshot.viewById[viewId]?.owner_element_id ?? null)
143
+ .filter((elementId): elementId is number => elementId != null)
144
+ }
145
+
146
+ export function viewName(snapshot: WorkspaceGraphSnapshot, viewId: number | null | undefined): string | null {
147
+ if (viewId == null) return null
148
+ return viewNameOf(snapshot.viewById, viewId)
149
+ }
150
+
151
+ export function allConnectors(snapshot: WorkspaceGraphSnapshot): Connector[] {
152
+ return Object.values(snapshot.connectorsByViewId).flat()
153
+ }
154
+
155
+ export function upsertConnectorInSnapshot(snapshot: WorkspaceGraphSnapshot | null, connector: Connector): WorkspaceGraphSnapshot | null {
156
+ if (!snapshot) return snapshot
157
+ const next = deepCloneExploreData(snapshot.source)
158
+ const viewKey = String(connector.view_id)
159
+ next.views[viewKey] ??= { placements: [], connectors: [] }
160
+ next.views[viewKey].connectors = [
161
+ connector,
162
+ ...next.views[viewKey].connectors.filter((existing) => existing.id !== connector.id),
163
+ ]
164
+ return buildWorkspaceGraphSnapshot(next)
165
+ }
166
+
167
+ export function removeConnectorFromSnapshot(snapshot: WorkspaceGraphSnapshot | null, viewId: number, connectorId: number): WorkspaceGraphSnapshot | null {
168
+ if (!snapshot) return snapshot
169
+ const next = deepCloneExploreData(snapshot.source)
170
+ const viewKey = String(viewId)
171
+ if (next.views[viewKey]) {
172
+ next.views[viewKey].connectors = next.views[viewKey].connectors.filter((connector) => connector.id !== connectorId)
173
+ }
174
+ return buildWorkspaceGraphSnapshot(next)
175
+ }
176
+
177
+ export function upsertPlacementInSnapshot(snapshot: WorkspaceGraphSnapshot | null, viewId: number, placement: PlacedElement): WorkspaceGraphSnapshot | null {
178
+ if (!snapshot) return snapshot
179
+ const next = deepCloneExploreData(snapshot.source)
180
+ const viewKey = String(viewId)
181
+ next.views[viewKey] ??= { placements: [], connectors: [] }
182
+ next.views[viewKey].placements = [
183
+ placement,
184
+ ...next.views[viewKey].placements.filter((existing) => existing.element_id !== placement.element_id),
185
+ ]
186
+ return buildWorkspaceGraphSnapshot(next)
187
+ }
188
+
189
+ export function removePlacementFromSnapshot(snapshot: WorkspaceGraphSnapshot | null, viewId: number, elementId: number): WorkspaceGraphSnapshot | null {
190
+ if (!snapshot) return snapshot
191
+ const next = deepCloneExploreData(snapshot.source)
192
+ const viewKey = String(viewId)
193
+ if (next.views[viewKey]) {
194
+ next.views[viewKey].placements = next.views[viewKey].placements.filter((placement) => placement.element_id !== elementId)
195
+ }
196
+ return buildWorkspaceGraphSnapshot(next)
197
+ }
198
+
199
+ export function overrideViewContentInSnapshot(snapshot: WorkspaceGraphSnapshot | null, viewId: number, placements: PlacedElement[], connectors: Connector[]): WorkspaceGraphSnapshot | null {
200
+ if (!snapshot) return snapshot
201
+ const next = deepCloneExploreData(snapshot.source)
202
+ next.views[String(viewId)] = {
203
+ placements: [...placements],
204
+ connectors: [...connectors],
205
+ }
206
+ return buildWorkspaceGraphSnapshot(next)
207
+ }