@tldiagram/core-ui 1.95.1 → 2.0.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.
- package/dist/api/client.d.ts +184 -3
- package/dist/components/ConnectorPanel.d.ts +5 -1
- package/dist/components/CrossBranchControls.d.ts +4 -3
- package/dist/components/ElementNode.d.ts +5 -0
- package/dist/components/ElementPanel.d.ts +6 -1
- package/dist/components/LayoutSection.d.ts +2 -1
- package/dist/components/MergeDialog.d.ts +16 -0
- package/dist/components/NodeContainer.d.ts +2 -0
- package/dist/components/ProxyConnectorPanel.d.ts +4 -1
- package/dist/components/ViewExplorer/index.d.ts +1 -1
- package/dist/components/ViewFloatingMenu-vscode.d.ts +5 -0
- package/dist/components/ViewFloatingMenu.d.ts +8 -1
- package/dist/components/ViewGridNode.d.ts +3 -0
- package/dist/components/ViewPanel.d.ts +2 -1
- package/dist/components/WorkspacePanel.d.ts +2 -0
- package/dist/components/ZUI/ZUICanvas.d.ts +4 -0
- package/dist/components/ZUI/focus.d.ts +32 -0
- package/dist/components/ZUI/focus.test.d.ts +1 -0
- package/dist/components/ZUI/layout.d.ts +2 -2
- package/dist/components/ZUI/proxy.d.ts +20 -4
- package/dist/components/ZUI/renderer.d.ts +35 -1
- package/dist/components/ZUI/types.d.ts +6 -0
- package/dist/components/ZUI/useZUIInteraction.d.ts +1 -0
- package/dist/context/WorkspaceVersionContext.d.ts +49 -0
- package/dist/crossBranch/resolve.d.ts +39 -2
- package/dist/crossBranch/resolve.test.d.ts +1 -0
- package/dist/crossBranch/settings.d.ts +6 -1
- package/dist/crossBranch/types.d.ts +8 -0
- package/dist/hooks/useElementSearch.d.ts +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16529 -14030
- package/dist/pages/InfiniteZoom.d.ts +1 -0
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +6 -1
- package/dist/pages/ViewEditor/hooks/useViewContextNeighbours.d.ts +2 -0
- package/dist/pages/ViewEditor/hooks/useViewData.d.ts +4 -2
- package/dist/pages/ViewEditor/hooks/useViewEditHistory.d.ts +13 -0
- package/dist/pages/viewsJumpSearch.d.ts +22 -0
- package/dist/pages/viewsJumpSearch.test.d.ts +1 -0
- package/dist/store/useStore.d.ts +3 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/utils/elementIcon.d.ts +2 -0
- package/dist/utils/elementIcon.test.d.ts +1 -0
- package/dist/utils/sourceEditor.d.ts +7 -0
- package/dist/utils/watchDiffSummary.d.ts +34 -0
- package/package.json +2 -2
- package/src/App.tsx +12 -8
- package/src/api/client.ts +488 -26
- package/src/components/CodePreviewPanel.tsx +90 -16
- package/src/components/ConnectorPanel.tsx +34 -3
- package/src/components/ContextNeighborElement.tsx +2 -5
- package/src/components/CrossBranchControls.tsx +46 -17
- package/src/components/ElementNode.tsx +98 -47
- package/src/components/ElementPanel.tsx +62 -25
- package/src/components/InlineElementAdder.tsx +8 -3
- package/src/components/LayoutSection.tsx +4 -1
- package/src/components/MergeDialog.tsx +269 -0
- package/src/components/NodeContainer.tsx +55 -17
- package/src/components/ProxyConnectorPanel.tsx +58 -16
- package/src/components/ViewBezierConnector.tsx +116 -21
- package/src/components/ViewExplorer/index.tsx +1 -1
- package/src/components/ViewFloatingMenu-vscode.tsx +5 -0
- package/src/components/ViewFloatingMenu.tsx +110 -1
- package/src/components/ViewGridNode.tsx +59 -8
- package/src/components/ViewPanel.tsx +3 -2
- package/src/components/WorkspacePanel.tsx +938 -0
- package/src/components/ZUI/ZUICanvas.tsx +216 -122
- package/src/components/ZUI/focus.test.ts +534 -0
- package/src/components/ZUI/focus.ts +293 -0
- package/src/components/ZUI/layout.ts +7 -11
- package/src/components/ZUI/proxy.ts +470 -114
- package/src/components/ZUI/renderer.ts +510 -134
- package/src/components/ZUI/types.ts +6 -0
- package/src/components/ZUI/useZUIInteraction.ts +66 -29
- package/src/context/WorkspaceVersionContext.tsx +126 -0
- package/src/crossBranch/resolve.test.ts +342 -0
- package/src/crossBranch/resolve.ts +368 -68
- package/src/crossBranch/settings.ts +49 -3
- package/src/crossBranch/types.ts +9 -0
- package/src/hooks/useElementSearch.ts +45 -0
- package/src/index.css +11 -0
- package/src/index.ts +7 -0
- package/src/pages/AppearanceSettings.tsx +24 -1
- package/src/pages/Dependencies.tsx +231 -65
- package/src/pages/InfiniteZoom.tsx +41 -19
- package/src/pages/Settings.tsx +1 -1
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +103 -24
- package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +102 -6
- package/src/pages/ViewEditor/hooks/useViewData.ts +42 -26
- package/src/pages/ViewEditor/hooks/useViewEditHistory.ts +62 -0
- package/src/pages/ViewEditor/index.tsx +549 -59
- package/src/pages/Views.tsx +112 -41
- package/src/pages/ViewsGrid.tsx +332 -113
- package/src/pages/viewsJumpSearch.test.ts +193 -0
- package/src/pages/viewsJumpSearch.ts +111 -0
- package/src/store/useStore.ts +58 -0
- package/src/types/index.ts +10 -0
- package/src/utils/elementIcon.test.ts +28 -0
- package/src/utils/elementIcon.ts +20 -0
- package/src/utils/sourceEditor.ts +46 -0
- package/src/utils/watchDiffSummary.ts +159 -0
|
@@ -2,6 +2,24 @@ import { useEffect, useState, useRef } from 'react'
|
|
|
2
2
|
import type { SVGProps } from 'react'
|
|
3
3
|
import { Box, Button, CloseButton, HStack, Icon, Spinner, Text, Tooltip, VStack } from '@chakra-ui/react'
|
|
4
4
|
import { ExternalLinkIcon } from '@chakra-ui/icons'
|
|
5
|
+
import CodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror'
|
|
6
|
+
import { EditorView } from '@codemirror/view'
|
|
7
|
+
import { oneDark } from '@codemirror/theme-one-dark'
|
|
8
|
+
import { javascript } from '@codemirror/lang-javascript'
|
|
9
|
+
import { python } from '@codemirror/lang-python'
|
|
10
|
+
import { cpp } from '@codemirror/lang-cpp'
|
|
11
|
+
import { java } from '@codemirror/lang-java'
|
|
12
|
+
import { rust } from '@codemirror/lang-rust'
|
|
13
|
+
|
|
14
|
+
import SlidingPanel from './SlidingPanel'
|
|
15
|
+
import { api } from '../api/client'
|
|
16
|
+
import { findSymbolByName, getParser, detectLanguage, type SupportedLanguage } from '../utils/treesitter'
|
|
17
|
+
import { githubCache } from '../utils/githubCache'
|
|
18
|
+
import { getGithubRepoVisibility } from '../utils/githubApi'
|
|
19
|
+
import { parseRepoSlug } from '../utils/url'
|
|
20
|
+
import { useSourceEditor } from '../utils/sourceEditor'
|
|
21
|
+
import { toast } from '../utils/toast'
|
|
22
|
+
import type { PlacedElement } from '../types'
|
|
5
23
|
|
|
6
24
|
const GithubIcon = (props: SVGProps<SVGSVGElement>) => (
|
|
7
25
|
<svg
|
|
@@ -34,18 +52,6 @@ const customCodeTheme = EditorView.theme({
|
|
|
34
52
|
caretColor: "var(--chakra-colors-blue-400)",
|
|
35
53
|
}
|
|
36
54
|
}, { dark: true })
|
|
37
|
-
import CodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror'
|
|
38
|
-
import { EditorView } from '@codemirror/view'
|
|
39
|
-
import { oneDark } from '@codemirror/theme-one-dark'
|
|
40
|
-
import { javascript } from '@codemirror/lang-javascript'
|
|
41
|
-
import { python } from '@codemirror/lang-python'
|
|
42
|
-
import { cpp } from '@codemirror/lang-cpp'
|
|
43
|
-
import { java } from '@codemirror/lang-java'
|
|
44
|
-
import { rust } from '@codemirror/lang-rust'
|
|
45
|
-
|
|
46
|
-
import SlidingPanel from './SlidingPanel'
|
|
47
|
-
import { findSymbolByName, getParser, detectLanguage, type SupportedLanguage } from '../utils/treesitter'
|
|
48
|
-
import type { PlacedElement } from '../types'
|
|
49
55
|
|
|
50
56
|
interface Props {
|
|
51
57
|
isOpen: boolean
|
|
@@ -69,9 +75,15 @@ function parseAnchor(anchorStr: string):
|
|
|
69
75
|
return { kind: 'none' }
|
|
70
76
|
}
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
function inferLineFromDescription(description: string | null | undefined, basePath: string): number | null {
|
|
79
|
+
if (!description || !basePath) return null
|
|
80
|
+
const match = description.match(/:(\d+)(?::\d+)?$/)
|
|
81
|
+
if (!match) return null
|
|
82
|
+
const pathPart = description.slice(0, match.index)
|
|
83
|
+
if (pathPart && pathPart !== basePath) return null
|
|
84
|
+
const line = Number(match[1])
|
|
85
|
+
return Number.isFinite(line) && line > 0 ? line : null
|
|
86
|
+
}
|
|
75
87
|
|
|
76
88
|
export default function CodePreviewPanel({ isOpen, onClose, element, hasBackdrop = true }: Props) {
|
|
77
89
|
const [code, setCode] = useState('')
|
|
@@ -80,6 +92,8 @@ export default function CodePreviewPanel({ isOpen, onClose, element, hasBackdrop
|
|
|
80
92
|
const [resolvedStartLine, setResolvedStartLine] = useState<number | null>(null)
|
|
81
93
|
const [resolvedEndLine, setResolvedEndLine] = useState<number | null>(null)
|
|
82
94
|
const [isPrivateRepo, setIsPrivateRepo] = useState(false)
|
|
95
|
+
const [openingEditor, setOpeningEditor] = useState(false)
|
|
96
|
+
const { editor: sourceEditor } = useSourceEditor()
|
|
83
97
|
|
|
84
98
|
const editorRef = useRef<ReactCodeMirrorRef>(null)
|
|
85
99
|
|
|
@@ -88,6 +102,10 @@ export default function CodePreviewPanel({ isOpen, onClose, element, hasBackdrop
|
|
|
88
102
|
const basePath = hashIdx >= 0 ? filePath.slice(0, hashIdx) : filePath
|
|
89
103
|
const symbolInfoStr = hashIdx >= 0 ? filePath.slice(hashIdx + 1) : ''
|
|
90
104
|
const repoSlug = element?.repo ? parseRepoSlug(element.repo) : ''
|
|
105
|
+
const anchor = parseAnchor(symbolInfoStr)
|
|
106
|
+
const anchorStartLine = anchor.kind === 'lines' ? anchor.startLine : null
|
|
107
|
+
const fallbackStartLine = inferLineFromDescription(element?.description, basePath)
|
|
108
|
+
const editorStartLine = resolvedStartLine ?? anchorStartLine ?? fallbackStartLine
|
|
91
109
|
|
|
92
110
|
useEffect(() => {
|
|
93
111
|
if (!isOpen || !element || !repoSlug || !basePath) return
|
|
@@ -205,9 +223,31 @@ export default function CodePreviewPanel({ isOpen, onClose, element, hasBackdrop
|
|
|
205
223
|
|
|
206
224
|
const githubUrl = element?.repo && basePath
|
|
207
225
|
? `https://github.com/${repoSlug}/blob/${element.branch || 'main'}/${basePath}`
|
|
208
|
-
+ (
|
|
226
|
+
+ (editorStartLine ? `#L${editorStartLine}-L${resolvedEndLine ?? editorStartLine}` : '')
|
|
209
227
|
: null
|
|
210
228
|
|
|
229
|
+
const handleOpenInEditor = async () => {
|
|
230
|
+
if (!basePath) return
|
|
231
|
+
setOpeningEditor(true)
|
|
232
|
+
try {
|
|
233
|
+
await api.editor.open({
|
|
234
|
+
editor: sourceEditor,
|
|
235
|
+
repo: element?.repo ?? '',
|
|
236
|
+
file_path: basePath,
|
|
237
|
+
line: editorStartLine,
|
|
238
|
+
})
|
|
239
|
+
} catch (err) {
|
|
240
|
+
toast({
|
|
241
|
+
title: 'Failed to open editor',
|
|
242
|
+
description: err instanceof Error ? err.message : String(err),
|
|
243
|
+
status: 'error',
|
|
244
|
+
duration: 4000,
|
|
245
|
+
})
|
|
246
|
+
} finally {
|
|
247
|
+
setOpeningEditor(false)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
211
251
|
const getLanguageExtension = () => {
|
|
212
252
|
const extensions = [customCodeTheme]
|
|
213
253
|
const effectiveLanguage = element?.language || detectLanguage(basePath)
|
|
@@ -333,6 +373,40 @@ export default function CodePreviewPanel({ isOpen, onClose, element, hasBackdrop
|
|
|
333
373
|
</Button>
|
|
334
374
|
</Tooltip>
|
|
335
375
|
)}
|
|
376
|
+
{basePath && (
|
|
377
|
+
<Tooltip label={`Open in ${sourceEditor === 'zed' ? 'Zed' : 'VS Code'}`} placement="bottom">
|
|
378
|
+
<Button
|
|
379
|
+
aria-label={`Open in ${sourceEditor === 'zed' ? 'Zed' : 'VS Code'}`}
|
|
380
|
+
leftIcon={<ExternalLinkIcon w="12px" h="12px" />}
|
|
381
|
+
size="xs"
|
|
382
|
+
variant="outline"
|
|
383
|
+
color="whiteAlpha.700"
|
|
384
|
+
borderColor="whiteAlpha.200"
|
|
385
|
+
h="24px"
|
|
386
|
+
px={2.5}
|
|
387
|
+
fontSize="11px"
|
|
388
|
+
fontWeight="600"
|
|
389
|
+
bg="whiteAlpha.50"
|
|
390
|
+
isLoading={openingEditor}
|
|
391
|
+
onClick={handleOpenInEditor}
|
|
392
|
+
_hover={{
|
|
393
|
+
color: 'white',
|
|
394
|
+
bg: 'whiteAlpha.100',
|
|
395
|
+
borderColor: 'whiteAlpha.400',
|
|
396
|
+
textDecoration: 'none',
|
|
397
|
+
transform: 'translateY(-0.5px)',
|
|
398
|
+
boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
|
|
399
|
+
}}
|
|
400
|
+
_active={{
|
|
401
|
+
bg: 'whiteAlpha.200',
|
|
402
|
+
transform: 'translateY(0)',
|
|
403
|
+
}}
|
|
404
|
+
transition="all 0.1s"
|
|
405
|
+
>
|
|
406
|
+
Open in Editor
|
|
407
|
+
</Button>
|
|
408
|
+
</Tooltip>
|
|
409
|
+
)}
|
|
336
410
|
<CloseButton size="sm" color="whiteAlpha.500"
|
|
337
411
|
_hover={{ color: 'white', bg: 'whiteAlpha.100' }}
|
|
338
412
|
onClick={onClose} />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { memo, useEffect, useRef, useState, useCallback } from 'react'
|
|
2
2
|
import type { ConnectorPanelSlots } from '../slots'
|
|
3
3
|
import {
|
|
4
|
+
Badge,
|
|
4
5
|
Box,
|
|
5
6
|
Button,
|
|
6
7
|
Divider,
|
|
@@ -68,6 +69,10 @@ export interface ConnectorPanelProps extends ConnectorPanelSlots {
|
|
|
68
69
|
onSave: (connector: Connector) => void
|
|
69
70
|
autoSave?: boolean
|
|
70
71
|
onDelete: (edgeId: number) => void
|
|
72
|
+
visibilityOverrideDelta?: number
|
|
73
|
+
onPromoteVisibility?: (id: number) => Promise<void> | void
|
|
74
|
+
onDemoteVisibility?: (id: number) => Promise<void> | void
|
|
75
|
+
onResetVisibility?: (id: number) => Promise<void> | void
|
|
71
76
|
hasBackdrop?: boolean
|
|
72
77
|
}
|
|
73
78
|
|
|
@@ -77,7 +82,7 @@ export interface ConnectorPanelProps extends ConnectorPanelSlots {
|
|
|
77
82
|
* Location: Right side of the screen on desktop. Overlays screen on mobile.
|
|
78
83
|
* Aliases: Connector Properties, Connector Details.
|
|
79
84
|
*/
|
|
80
|
-
function ConnectorPanel({ isOpen, onClose, connector, orgId, onSave, autoSave = false, onDelete, hasBackdrop = true, connectorPanelAfterContentSlot }: ConnectorPanelProps) {
|
|
85
|
+
function ConnectorPanel({ isOpen, onClose, connector, orgId, onSave, autoSave = false, onDelete, visibilityOverrideDelta = 0, onPromoteVisibility, onDemoteVisibility, onResetVisibility, hasBackdrop = true, connectorPanelAfterContentSlot }: ConnectorPanelProps) {
|
|
81
86
|
const { canEdit, viewId } = useViewEditorContext()
|
|
82
87
|
const isReadOnly = !canEdit
|
|
83
88
|
const autoSaveEdit = autoSave && !!connector && !isReadOnly
|
|
@@ -172,9 +177,9 @@ function ConnectorPanel({ isOpen, onClose, connector, orgId, onSave, autoSave =
|
|
|
172
177
|
})
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
const handleClose = useCallback(() => {
|
|
180
|
+
const handleClose = useCallback(async () => {
|
|
176
181
|
if (autoSaveEdit) {
|
|
177
|
-
|
|
182
|
+
await saveIfDirtyRef.current?.()
|
|
178
183
|
}
|
|
179
184
|
onClose()
|
|
180
185
|
}, [autoSaveEdit, onClose])
|
|
@@ -350,6 +355,32 @@ function ConnectorPanel({ isOpen, onClose, connector, orgId, onSave, autoSave =
|
|
|
350
355
|
/>
|
|
351
356
|
</FormControl>
|
|
352
357
|
|
|
358
|
+
{connector && (onPromoteVisibility || onDemoteVisibility || onResetVisibility) && (
|
|
359
|
+
<Box borderTop="1px solid" borderColor="whiteAlpha.100" pt={2}>
|
|
360
|
+
<HStack justify="space-between" mb={2}>
|
|
361
|
+
<FormLabel fontSize="xs" fontWeight="bold" color="gray.400" mb={0}>DENSITY</FormLabel>
|
|
362
|
+
{visibilityOverrideDelta !== 0 && (
|
|
363
|
+
<Badge colorScheme={visibilityOverrideDelta > 0 ? 'teal' : 'orange'} variant="subtle">
|
|
364
|
+
{visibilityOverrideDelta > 0 ? `+${visibilityOverrideDelta}` : visibilityOverrideDelta}
|
|
365
|
+
</Badge>
|
|
366
|
+
)}
|
|
367
|
+
</HStack>
|
|
368
|
+
<HStack spacing={2}>
|
|
369
|
+
<Button variant="subtle" size="sm" color="teal.200" _hover={{ bg: 'teal.900', color: 'teal.100' }} onClick={() => onPromoteVisibility?.(connector.id)} flex={1} isDisabled={isReadOnly}>
|
|
370
|
+
Promote
|
|
371
|
+
</Button>
|
|
372
|
+
<Button variant="subtle" size="sm" color="orange.200" _hover={{ bg: 'orange.900', color: 'orange.100' }} onClick={() => onDemoteVisibility?.(connector.id)} flex={1} isDisabled={isReadOnly}>
|
|
373
|
+
Demote
|
|
374
|
+
</Button>
|
|
375
|
+
{visibilityOverrideDelta !== 0 && (
|
|
376
|
+
<Button variant="ghost" size="sm" onClick={() => onResetVisibility?.(connector.id)} isDisabled={isReadOnly}>
|
|
377
|
+
Reset
|
|
378
|
+
</Button>
|
|
379
|
+
)}
|
|
380
|
+
</HStack>
|
|
381
|
+
</Box>
|
|
382
|
+
)}
|
|
383
|
+
|
|
353
384
|
{connectorPanelAfterContentSlot}
|
|
354
385
|
|
|
355
386
|
</VStack>
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, LinkIcon } from '@chakra-ui/icons'
|
|
21
21
|
import type { PlacedElement } from '../types'
|
|
22
22
|
import { TYPE_COLORS } from '../types'
|
|
23
|
-
import {
|
|
23
|
+
import { resolveElementIconUrl } from '../utils/elementIcon'
|
|
24
24
|
import { ElementBody } from './NodeBody'
|
|
25
25
|
import { ElementContainer } from './NodeContainer'
|
|
26
26
|
|
|
@@ -61,10 +61,7 @@ function ContextNeighborNode({ data }: Props) {
|
|
|
61
61
|
const color = TYPE_COLORS[data.kind ?? ''] ?? 'gray'
|
|
62
62
|
|
|
63
63
|
const logoUrl = useMemo(() => {
|
|
64
|
-
|
|
65
|
-
const selected = data.technology_connectors?.find((link) => link.type === 'catalog' && !!(link.is_primary_icon ?? (link as any).isPrimaryIcon) && !!link.slug)
|
|
66
|
-
if (!selected?.slug) return undefined
|
|
67
|
-
return resolveIconPath(`/icons/${selected.slug}.png`)
|
|
64
|
+
return resolveElementIconUrl(data.logo_url, data.technology_connectors) ?? undefined
|
|
68
65
|
}, [data.logo_url, data.technology_connectors])
|
|
69
66
|
|
|
70
67
|
const primaryOwnerViewId = data.ownerViewIds[0] ?? data.commonAncestorViewId ?? null
|
|
@@ -16,26 +16,29 @@ import {
|
|
|
16
16
|
Text,
|
|
17
17
|
VStack,
|
|
18
18
|
} from '@chakra-ui/react'
|
|
19
|
-
import {
|
|
20
|
-
|
|
19
|
+
import {
|
|
20
|
+
CROSS_BRANCH_CONNECTOR_BUDGET_MAX,
|
|
21
|
+
CROSS_BRANCH_CONNECTOR_BUDGET_MIN,
|
|
22
|
+
} from '../crossBranch/types'
|
|
23
|
+
import type { CrossBranchConnectorPriority, CrossBranchContextSettings } from '../crossBranch/types'
|
|
21
24
|
|
|
22
25
|
interface Props {
|
|
23
26
|
settings: CrossBranchContextSettings
|
|
24
27
|
onEnabledChange: (enabled: boolean) => void
|
|
25
|
-
|
|
28
|
+
onBudgetChange: (budget: number) => void
|
|
29
|
+
onPriorityChange: (priority: CrossBranchConnectorPriority) => void
|
|
26
30
|
label?: string
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
function depthLabel(depth: number) {
|
|
30
|
-
return depth >= CROSS_BRANCH_DEPTH_ALL ? 'All' : String(depth)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
33
|
export default function CrossBranchControls({
|
|
34
34
|
settings,
|
|
35
35
|
onEnabledChange,
|
|
36
|
-
|
|
36
|
+
onBudgetChange,
|
|
37
|
+
onPriorityChange,
|
|
37
38
|
label = 'Cross-Branch',
|
|
38
39
|
}: Props) {
|
|
40
|
+
const connectorBudget = settings.connectorBudget
|
|
41
|
+
|
|
39
42
|
return (
|
|
40
43
|
<Popover placement="top-start" isLazy>
|
|
41
44
|
<PopoverTrigger>
|
|
@@ -50,7 +53,7 @@ export default function CrossBranchControls({
|
|
|
50
53
|
<HStack spacing={1.5}>
|
|
51
54
|
<Box w="7px" h="7px" rounded="full" bg={settings.enabled ? 'var(--accent)' : 'gray.500'} />
|
|
52
55
|
<Text fontSize="11px" fontWeight="normal">{label}</Text>
|
|
53
|
-
<Text fontSize="10px" color="gray.400">{settings.enabled ?
|
|
56
|
+
<Text fontSize="10px" color="gray.400">{settings.enabled ? connectorBudget : 'Off'}</Text>
|
|
54
57
|
</HStack>
|
|
55
58
|
</Button>
|
|
56
59
|
</PopoverTrigger>
|
|
@@ -71,20 +74,46 @@ export default function CrossBranchControls({
|
|
|
71
74
|
<Text fontSize="xs" fontWeight="600" color="white">Show cross-branch context</Text>
|
|
72
75
|
<Switch isChecked={settings.enabled} onChange={(event) => onEnabledChange(event.target.checked)} colorScheme="blue" />
|
|
73
76
|
</HStack>
|
|
77
|
+
<Box opacity={settings.enabled ? 1 : 0.4}>
|
|
78
|
+
<Text fontSize="10px" fontWeight="700" color="gray.400" letterSpacing="0.08em" textTransform="uppercase" mb={2}>
|
|
79
|
+
Priority
|
|
80
|
+
</Text>
|
|
81
|
+
<HStack spacing={1} bg="whiteAlpha.100" borderRadius="md" p={1}>
|
|
82
|
+
{(['external', 'internal'] as const).map((priority) => {
|
|
83
|
+
const active = settings.connectorPriority === priority
|
|
84
|
+
return (
|
|
85
|
+
<Button
|
|
86
|
+
key={priority}
|
|
87
|
+
size="xs"
|
|
88
|
+
h="24px"
|
|
89
|
+
flex={1}
|
|
90
|
+
isDisabled={!settings.enabled}
|
|
91
|
+
variant="ghost"
|
|
92
|
+
bg={active ? 'rgba(var(--accent-rgb), 0.18)' : 'transparent'}
|
|
93
|
+
color={active ? 'var(--accent)' : 'gray.300'}
|
|
94
|
+
_hover={{ bg: active ? 'rgba(var(--accent-rgb), 0.22)' : 'whiteAlpha.100' }}
|
|
95
|
+
onClick={() => onPriorityChange(priority)}
|
|
96
|
+
>
|
|
97
|
+
{priority === 'external' ? 'External' : 'Internal'}
|
|
98
|
+
</Button>
|
|
99
|
+
)
|
|
100
|
+
})}
|
|
101
|
+
</HStack>
|
|
102
|
+
</Box>
|
|
74
103
|
<Box opacity={settings.enabled ? 1 : 0.4}>
|
|
75
104
|
<HStack justify="space-between" mb={2}>
|
|
76
105
|
<Text fontSize="10px" fontWeight="700" color="gray.400" letterSpacing="0.08em" textTransform="uppercase">
|
|
77
|
-
|
|
106
|
+
Connector Budget
|
|
78
107
|
</Text>
|
|
79
|
-
<Text fontSize="xs" color="gray.300">{
|
|
108
|
+
<Text fontSize="xs" color="gray.300">{connectorBudget}</Text>
|
|
80
109
|
</HStack>
|
|
81
110
|
<Slider
|
|
82
111
|
isDisabled={!settings.enabled}
|
|
83
|
-
min={
|
|
84
|
-
max={
|
|
112
|
+
min={CROSS_BRANCH_CONNECTOR_BUDGET_MIN}
|
|
113
|
+
max={CROSS_BRANCH_CONNECTOR_BUDGET_MAX}
|
|
85
114
|
step={1}
|
|
86
|
-
value={
|
|
87
|
-
onChange={
|
|
115
|
+
value={connectorBudget}
|
|
116
|
+
onChange={onBudgetChange}
|
|
88
117
|
>
|
|
89
118
|
<SliderTrack bg="whiteAlpha.200">
|
|
90
119
|
<SliderFilledTrack bg="var(--accent)" />
|
|
@@ -92,8 +121,8 @@ export default function CrossBranchControls({
|
|
|
92
121
|
<SliderThumb boxSize={4} />
|
|
93
122
|
</Slider>
|
|
94
123
|
<HStack justify="space-between" mt={1}>
|
|
95
|
-
<Text fontSize="10px" color="gray.500">
|
|
96
|
-
<Text fontSize="10px" color="gray.500">
|
|
124
|
+
<Text fontSize="10px" color="gray.500">{CROSS_BRANCH_CONNECTOR_BUDGET_MIN}</Text>
|
|
125
|
+
<Text fontSize="10px" color="gray.500">{CROSS_BRANCH_CONNECTOR_BUDGET_MAX}</Text>
|
|
97
126
|
</HStack>
|
|
98
127
|
</Box>
|
|
99
128
|
</VStack>
|
|
@@ -7,7 +7,7 @@ import { useAccentColor } from '../context/ThemeContext'
|
|
|
7
7
|
import type { PlacedElement, ViewConnector, Tag } from '../types'
|
|
8
8
|
import { ElementContainer } from './NodeContainer'
|
|
9
9
|
import { ElementBody } from './NodeBody'
|
|
10
|
-
import {
|
|
10
|
+
import { resolveElementIconUrl } from '../utils/elementIcon'
|
|
11
11
|
import { ZoomInIcon, ZoomOutIcon, TrashIcon as TrashSvg, EditIcon as EditSvg } from './Icons'
|
|
12
12
|
import { vscodeBridge } from '../lib/vscodeBridge'
|
|
13
13
|
import type { ExtensionToWebviewMessage } from '../types/vscode-messages'
|
|
@@ -155,6 +155,8 @@ interface NodeData extends PlacedElement {
|
|
|
155
155
|
selectedHandleIds?: readonly string[]
|
|
156
156
|
reconnectCandidates?: readonly { handleId: string; edgeId: string; endpoint: 'source' | 'target'; selected: boolean }[]
|
|
157
157
|
isConnectorHighlighted?: boolean
|
|
158
|
+
versionChangeType?: 'added' | 'updated' | 'deleted' | 'initialized'
|
|
159
|
+
versionLineDelta?: { added: number; removed: number }
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
interface Props {
|
|
@@ -316,13 +318,7 @@ function ElementNode({ data, selected }: Props) {
|
|
|
316
318
|
return next
|
|
317
319
|
}, [data.reconnectCandidates])
|
|
318
320
|
|
|
319
|
-
const
|
|
320
|
-
const selected = data.technology_connectors?.find((link) => link.type === 'catalog' && !!(link.is_primary_icon ?? (link as any).isPrimaryIcon) && !!link.slug)
|
|
321
|
-
if (!selected?.slug) return undefined
|
|
322
|
-
return resolveIconPath(`/icons/${selected.slug}.png`)
|
|
323
|
-
})()
|
|
324
|
-
|
|
325
|
-
const nodeLogoUrl = data.logo_url ? resolveIconPath(data.logo_url) : derivedPrimaryIconPath
|
|
321
|
+
const nodeLogoUrl = resolveElementIconUrl(data.logo_url, data.technology_connectors) ?? undefined
|
|
326
322
|
|
|
327
323
|
const technologyLinkCount = (data.technology_connectors || []).filter((l) => !!l.label).length
|
|
328
324
|
const technologyParts = (data.technology || '')
|
|
@@ -456,6 +452,13 @@ function ElementNode({ data, selected }: Props) {
|
|
|
456
452
|
const isTarget = !!data.interactionSourceId && !isSource
|
|
457
453
|
|
|
458
454
|
const bodyCursor = isSource ? 'crosshair' : isTarget ? 'cell' : 'pointer'
|
|
455
|
+
const versionColor = data.versionChangeType === 'added'
|
|
456
|
+
? 'green.300'
|
|
457
|
+
: data.versionChangeType === 'deleted'
|
|
458
|
+
? 'red.300'
|
|
459
|
+
: data.versionChangeType
|
|
460
|
+
? 'yellow.300'
|
|
461
|
+
: undefined
|
|
459
462
|
|
|
460
463
|
return (
|
|
461
464
|
<ElementContainer
|
|
@@ -463,12 +466,14 @@ function ElementNode({ data, selected }: Props) {
|
|
|
463
466
|
isSource={isSource}
|
|
464
467
|
isTarget={isTarget}
|
|
465
468
|
isConnectorHighlighted={!!data.isConnectorHighlighted}
|
|
469
|
+
hasStack={hasChild}
|
|
470
|
+
kind={data.kind}
|
|
466
471
|
minW="180px"
|
|
467
472
|
maxW="230px"
|
|
468
473
|
cursor={bodyCursor}
|
|
469
|
-
outline={isDraggedOver ? '2px solid' : undefined}
|
|
470
|
-
outlineColor={isDraggedOver ? 'var(--accent)' :
|
|
471
|
-
outlineOffset={isDraggedOver ? '2px' : undefined}
|
|
474
|
+
outline={isDraggedOver || versionColor ? '2px solid' : undefined}
|
|
475
|
+
outlineColor={isDraggedOver ? 'var(--accent)' : versionColor}
|
|
476
|
+
outlineOffset={isDraggedOver || versionColor ? '2px' : undefined}
|
|
472
477
|
borderTopWidth={data.layerHighlightColor ? '2px' : undefined}
|
|
473
478
|
borderTopColor={data.layerHighlightColor ?? undefined}
|
|
474
479
|
onClick={handleBodyClick}
|
|
@@ -483,7 +488,7 @@ function ElementNode({ data, selected }: Props) {
|
|
|
483
488
|
style={{
|
|
484
489
|
userSelect: 'none',
|
|
485
490
|
WebkitUserSelect: 'none',
|
|
486
|
-
transition: 'outline 0.15s, outline-color 0.15s',
|
|
491
|
+
transition: 'outline 0.15s, outline-color 0.15s, opacity 0.15s',
|
|
487
492
|
} as React.CSSProperties}
|
|
488
493
|
>
|
|
489
494
|
{HANDLE_CONFIGS.flatMap(({ side, position }) =>
|
|
@@ -702,62 +707,108 @@ function ElementNode({ data, selected }: Props) {
|
|
|
702
707
|
)}
|
|
703
708
|
|
|
704
709
|
{/* Code Preview Icon/Link in Bottom Right Corner */}
|
|
705
|
-
{((data.repo || data.url)
|
|
706
|
-
<
|
|
710
|
+
{!window.__TLD_VSCODE__ && ((data.repo || data.url) || data.versionLineDelta) && (
|
|
711
|
+
<HStack
|
|
707
712
|
position="absolute"
|
|
708
713
|
bottom="8px"
|
|
709
714
|
right="8px"
|
|
710
715
|
zIndex={10}
|
|
716
|
+
spacing={1}
|
|
717
|
+
align="center"
|
|
711
718
|
>
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
? `View source: ${data.file_path?.includes('#') ? (() => { try { return JSON.parse(data.file_path.split('#')[1]).name } catch { return 'Link' } })() : 'Link'}${data.url ? ' / URL' : ''}`
|
|
716
|
-
: 'Open Link'
|
|
717
|
-
}
|
|
718
|
-
placement="top"
|
|
719
|
-
isDisabled={data.isCanvasMoving}
|
|
720
|
-
>
|
|
721
|
-
<Box
|
|
722
|
-
as="button"
|
|
723
|
-
display="flex"
|
|
724
|
-
alignItems="center"
|
|
725
|
-
justifyContent="center"
|
|
726
|
-
w="18px"
|
|
719
|
+
{data.versionLineDelta && (
|
|
720
|
+
<HStack
|
|
721
|
+
spacing={1}
|
|
727
722
|
h="18px"
|
|
723
|
+
px={1.5}
|
|
728
724
|
rounded="md"
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if (data.repo) {
|
|
735
|
-
data.onOpenCodePreview?.(data.element_id)
|
|
736
|
-
} else if (data.url) {
|
|
737
|
-
window.open(data.url, '_blank', 'noopener,noreferrer')
|
|
738
|
-
}
|
|
739
|
-
}}
|
|
740
|
-
onPointerDown={(e: React.PointerEvent) => e.stopPropagation()}
|
|
725
|
+
bg="rgba(var(--bg-main-rgb), 0.86)"
|
|
726
|
+
border="1px solid"
|
|
727
|
+
borderColor="whiteAlpha.300"
|
|
728
|
+
boxShadow="0 4px 12px rgba(0,0,0,0.28)"
|
|
729
|
+
pointerEvents="none"
|
|
741
730
|
>
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
731
|
+
{data.versionLineDelta.added > 0 && (
|
|
732
|
+
<Text fontSize="9px" fontWeight="800" lineHeight="1" color="green.300">+{data.versionLineDelta.added}</Text>
|
|
733
|
+
)}
|
|
734
|
+
{data.versionLineDelta.removed > 0 && (
|
|
735
|
+
<Text fontSize="9px" fontWeight="800" lineHeight="1" color="red.300">-{data.versionLineDelta.removed}</Text>
|
|
736
|
+
)}
|
|
737
|
+
</HStack>
|
|
738
|
+
)}
|
|
739
|
+
{(data.repo || data.url) && !window.__TLD_VSCODE__ && (
|
|
740
|
+
<Tooltip
|
|
741
|
+
label={
|
|
742
|
+
data.repo
|
|
743
|
+
? `View source: ${data.file_path?.includes('#') ? (() => { try { return JSON.parse(data.file_path.split('#')[1]).name } catch { return 'Link' } })() : 'Link'}${data.url ? ' / URL' : ''}`
|
|
744
|
+
: 'Open Link'
|
|
745
|
+
}
|
|
746
|
+
placement="top"
|
|
747
|
+
isDisabled={data.isCanvasMoving}
|
|
748
|
+
>
|
|
749
|
+
<Box
|
|
750
|
+
as="button"
|
|
751
|
+
display="flex"
|
|
752
|
+
alignItems="center"
|
|
753
|
+
justifyContent="center"
|
|
754
|
+
w="18px"
|
|
755
|
+
h="18px"
|
|
756
|
+
rounded="md"
|
|
757
|
+
color="whiteAlpha.900"
|
|
758
|
+
_hover={{ color: 'blue.300', bg: 'whiteAlpha.200', transform: 'scale(1.1)' }}
|
|
759
|
+
transition="all 0.15s"
|
|
760
|
+
onClick={(e: React.MouseEvent) => {
|
|
761
|
+
e.stopPropagation()
|
|
762
|
+
if (data.repo) {
|
|
763
|
+
data.onOpenCodePreview?.(data.element_id)
|
|
764
|
+
} else if (data.url) {
|
|
765
|
+
window.open(data.url, '_blank', 'noopener,noreferrer')
|
|
766
|
+
}
|
|
767
|
+
}}
|
|
768
|
+
onPointerDown={(e: React.PointerEvent) => e.stopPropagation()}
|
|
769
|
+
>
|
|
770
|
+
<LinkIcon w={2.5} h={2.5} />
|
|
771
|
+
</Box>
|
|
772
|
+
</Tooltip>
|
|
773
|
+
)}
|
|
774
|
+
</HStack>
|
|
746
775
|
)}
|
|
747
776
|
|
|
748
777
|
{/* VSCode specific file link with hover preview */}
|
|
749
778
|
{window.__TLD_VSCODE__ && data.file_path && (
|
|
750
|
-
<
|
|
779
|
+
<HStack
|
|
751
780
|
position="absolute"
|
|
752
781
|
bottom="8px"
|
|
753
782
|
right="8px"
|
|
754
783
|
zIndex={10}
|
|
784
|
+
spacing={1}
|
|
785
|
+
align="center"
|
|
755
786
|
>
|
|
787
|
+
{data.versionLineDelta && (
|
|
788
|
+
<HStack
|
|
789
|
+
spacing={1}
|
|
790
|
+
h="18px"
|
|
791
|
+
px={1.5}
|
|
792
|
+
rounded="md"
|
|
793
|
+
bg="rgba(var(--bg-main-rgb), 0.86)"
|
|
794
|
+
border="1px solid"
|
|
795
|
+
borderColor="whiteAlpha.300"
|
|
796
|
+
boxShadow="0 4px 12px rgba(0,0,0,0.28)"
|
|
797
|
+
pointerEvents="none"
|
|
798
|
+
>
|
|
799
|
+
{data.versionLineDelta.added > 0 && (
|
|
800
|
+
<Text fontSize="9px" fontWeight="800" lineHeight="1" color="green.300">+{data.versionLineDelta.added}</Text>
|
|
801
|
+
)}
|
|
802
|
+
{data.versionLineDelta.removed > 0 && (
|
|
803
|
+
<Text fontSize="9px" fontWeight="800" lineHeight="1" color="red.300">-{data.versionLineDelta.removed}</Text>
|
|
804
|
+
)}
|
|
805
|
+
</HStack>
|
|
806
|
+
)}
|
|
756
807
|
<VscodeCodePreview
|
|
757
808
|
filePath={data.file_path}
|
|
758
809
|
isCanvasMoving={data.isCanvasMoving}
|
|
759
810
|
/>
|
|
760
|
-
</
|
|
811
|
+
</HStack>
|
|
761
812
|
)}
|
|
762
813
|
|
|
763
814
|
{selected && !isSource && (
|