@tldiagram/core-ui 1.95.0 → 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/MiniZoomOnboarding.d.ts +2 -1
- 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 +5 -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 +14597 -12083
- 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/MiniZoomOnboarding.tsx +29 -22
- 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 +226 -127
- 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 +76 -27
- 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
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { Box, FormLabel, HStack, Text, Tooltip, VStack, Wrap, WrapItem } from '@chakra-ui/react'
|
|
1
|
+
import { Box, FormLabel, HStack, Select, Text, Tooltip, VStack, Wrap, WrapItem } from '@chakra-ui/react'
|
|
2
2
|
import { ACCENT_OPTIONS, BACKGROUND_OPTIONS, ELEMENT_OPTIONS } from '../constants/colors'
|
|
3
3
|
import { useTheme } from '../context/ThemeContext'
|
|
4
|
+
import { useSourceEditor } from '../utils/sourceEditor'
|
|
5
|
+
import type { SourceEditor } from '../api/client'
|
|
4
6
|
|
|
5
7
|
export default function AppearanceSettings({ compact = false }: { compact?: boolean }) {
|
|
6
8
|
const { accent, setAccent, background, setBackground, elementColor, setElementColor } = useTheme()
|
|
9
|
+
const { editor, setEditor } = useSourceEditor()
|
|
7
10
|
const swatchSize = compact ? '28px' : '32px'
|
|
8
11
|
const sectionGap = compact ? 5 : 8
|
|
9
12
|
|
|
@@ -19,6 +22,26 @@ export default function AppearanceSettings({ compact = false }: { compact?: bool
|
|
|
19
22
|
</HStack>
|
|
20
23
|
</Box>
|
|
21
24
|
|
|
25
|
+
<Box w="full">
|
|
26
|
+
<FormLabel mb={3} fontSize={compact ? 'xs' : 'sm'} textTransform="uppercase" letterSpacing="0.12em" color="gray.400">
|
|
27
|
+
Source Editor
|
|
28
|
+
</FormLabel>
|
|
29
|
+
<Select
|
|
30
|
+
size="sm"
|
|
31
|
+
value={editor}
|
|
32
|
+
onChange={(event) => setEditor(event.target.value as SourceEditor)}
|
|
33
|
+
bg="whiteAlpha.50"
|
|
34
|
+
borderColor="whiteAlpha.200"
|
|
35
|
+
color="gray.100"
|
|
36
|
+
maxW="220px"
|
|
37
|
+
_hover={{ borderColor: 'whiteAlpha.400' }}
|
|
38
|
+
_focus={{ borderColor: 'blue.400', boxShadow: '0 0 0 1px var(--chakra-colors-blue-400)' }}
|
|
39
|
+
>
|
|
40
|
+
<option value="zed">Zed</option>
|
|
41
|
+
<option value="vscode">VS Code</option>
|
|
42
|
+
</Select>
|
|
43
|
+
</Box>
|
|
44
|
+
|
|
22
45
|
<Box w="full">
|
|
23
46
|
<FormLabel mb={3} fontSize={compact ? 'xs' : 'sm'} textTransform="uppercase" letterSpacing="0.12em" color="gray.400">
|
|
24
47
|
Accent
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
import { useSearchParams } from 'react-router-dom'
|
|
2
3
|
import { motion } from 'framer-motion'
|
|
3
4
|
import {
|
|
4
5
|
Box,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
MenuItem,
|
|
14
15
|
MenuList,
|
|
15
16
|
Spinner,
|
|
17
|
+
Badge,
|
|
16
18
|
Tag,
|
|
17
19
|
Text,
|
|
18
20
|
VStack,
|
|
@@ -27,6 +29,7 @@ import { ElementBody } from '../components/NodeBody'
|
|
|
27
29
|
import DependenciesOnboarding from '../components/DependenciesOnboarding'
|
|
28
30
|
import { useTheme } from '../context/ThemeContext'
|
|
29
31
|
import { hexToRgba } from '../constants/colors'
|
|
32
|
+
import { useWorkspaceVersionPreview, type VersionChangeType } from '../context/WorkspaceVersionContext'
|
|
30
33
|
|
|
31
34
|
// ── Data types ─────────────────────────────────────────────────────────────
|
|
32
35
|
interface ElementWithNeighbours extends DependencyElement {
|
|
@@ -39,6 +42,8 @@ interface NeighbourNode {
|
|
|
39
42
|
position: 'left' | 'right' | 'top' | 'bottom'
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
const PAGE_SIZE = 50
|
|
46
|
+
|
|
42
47
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
43
48
|
function computeNeighbourCounts(elements: DependencyElement[], connectors: DependencyConnector[]): ElementWithNeighbours[] {
|
|
44
49
|
const counts = new Map<string, Set<string>>()
|
|
@@ -175,11 +180,13 @@ function NeighbourCard({
|
|
|
175
180
|
onClick,
|
|
176
181
|
setRef,
|
|
177
182
|
compactLevel = 0,
|
|
183
|
+
versionChangeType,
|
|
178
184
|
}: {
|
|
179
185
|
node: NeighbourNode
|
|
180
186
|
onClick: () => void
|
|
181
187
|
setRef?: (el: HTMLDivElement | null) => void
|
|
182
188
|
compactLevel?: number
|
|
189
|
+
versionChangeType?: VersionChangeType
|
|
183
190
|
}) {
|
|
184
191
|
const cardPadding = compactLevel >= 3 ? 1 : compactLevel >= 2 ? 1.5 : compactLevel >= 1 ? 2 : 3
|
|
185
192
|
const showTech = compactLevel < 2
|
|
@@ -195,6 +202,13 @@ function NeighbourCard({
|
|
|
195
202
|
compactLevel >= 2 ? (nameLen > 20 ? '2xs' : 'xs') :
|
|
196
203
|
compactLevel >= 1 ? (nameLen > 22 ? 'xs' : 'sm') :
|
|
197
204
|
(nameLen > 24 ? 'xs' : 'sm')
|
|
205
|
+
const versionColor = versionChangeType === 'added'
|
|
206
|
+
? 'green.300'
|
|
207
|
+
: versionChangeType === 'deleted'
|
|
208
|
+
? 'red.300'
|
|
209
|
+
: versionChangeType
|
|
210
|
+
? 'yellow.300'
|
|
211
|
+
: undefined
|
|
198
212
|
|
|
199
213
|
return (
|
|
200
214
|
<motion.div
|
|
@@ -212,6 +226,9 @@ function NeighbourCard({
|
|
|
212
226
|
p={0}
|
|
213
227
|
cursor="pointer"
|
|
214
228
|
borderColor="whiteAlpha.200"
|
|
229
|
+
outline={versionColor ? '2px solid' : undefined}
|
|
230
|
+
outlineColor={versionColor}
|
|
231
|
+
outlineOffset={versionColor ? '2px' : undefined}
|
|
215
232
|
_hover={{ borderColor: 'var(--accent)', boxShadow: '0 0 0 1px rgba(var(--accent-rgb), 0.25)' }}
|
|
216
233
|
>
|
|
217
234
|
<ElementBody
|
|
@@ -254,14 +271,25 @@ const TYPE_HEX: Record<string, string> = {
|
|
|
254
271
|
export default function Dependencies() {
|
|
255
272
|
const setHeader = useSetHeader()
|
|
256
273
|
const { accent, elementColor } = useTheme()
|
|
274
|
+
const [searchParams, setSearchParams] = useSearchParams()
|
|
275
|
+
const { preview: versionPreview, followTarget: versionFollowTarget } = useWorkspaceVersionPreview()
|
|
276
|
+
const versionPulseChangeForElement = useCallback((elementId: number): VersionChangeType | undefined => {
|
|
277
|
+
if (versionFollowTarget?.resourceType !== 'element' || versionFollowTarget.resourceId !== elementId) return undefined
|
|
278
|
+
return versionFollowTarget.changeType ?? versionPreview?.elementChanges.get(elementId)
|
|
279
|
+
}, [versionFollowTarget, versionPreview])
|
|
257
280
|
|
|
258
281
|
const [elements, setElements] = useState<DependencyElement[]>([])
|
|
259
282
|
const [allEdges, setAllEdges] = useState<DependencyConnector[]>([])
|
|
260
283
|
const [loading, setLoading] = useState(true)
|
|
284
|
+
const [pageLoading, setPageLoading] = useState(false)
|
|
261
285
|
const [search, setSearch] = useState('')
|
|
262
286
|
const [typeFilter, setTypeFilter] = useState('')
|
|
263
287
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
264
288
|
const [topRatio, setTopRatio] = useState(0.45)
|
|
289
|
+
const [page, setPage] = useState(0)
|
|
290
|
+
const [hasNextPage, setHasNextPage] = useState(false)
|
|
291
|
+
const [totalCount, setTotalCount] = useState(0)
|
|
292
|
+
const [neighbourElements, setNeighbourElements] = useState<Record<string, DependencyElement>>({})
|
|
265
293
|
|
|
266
294
|
// Graph layout measurement
|
|
267
295
|
const graphRef = useRef<HTMLDivElement>(null)
|
|
@@ -287,57 +315,124 @@ export default function Dependencies() {
|
|
|
287
315
|
|
|
288
316
|
useEffect(() => { applyPan(0, 0) }, [selectedId, applyPan])
|
|
289
317
|
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
const requestedId = searchParams.get('element')
|
|
320
|
+
if (requestedId) setSelectedId(requestedId)
|
|
321
|
+
}, [searchParams])
|
|
322
|
+
|
|
323
|
+
const selectElement = useCallback((id: string | null) => {
|
|
324
|
+
setSelectedId(id)
|
|
325
|
+
const next = new URLSearchParams(searchParams)
|
|
326
|
+
if (id) next.set('element', id)
|
|
327
|
+
else next.delete('element')
|
|
328
|
+
setSearchParams(next, { replace: true })
|
|
329
|
+
}, [searchParams, setSearchParams])
|
|
330
|
+
|
|
290
331
|
// Header
|
|
291
332
|
useEffect(() => {
|
|
292
333
|
setHeader({
|
|
293
334
|
hideMobileBar: true,
|
|
294
|
-
node:
|
|
295
|
-
<HStack
|
|
296
|
-
bg="whiteAlpha.50"
|
|
297
|
-
border="1px solid"
|
|
298
|
-
borderColor="whiteAlpha.100"
|
|
299
|
-
px={3}
|
|
300
|
-
py={1}
|
|
301
|
-
borderRadius="md"
|
|
302
|
-
spacing={3}
|
|
303
|
-
>
|
|
304
|
-
<Text fontSize="xs" color="whiteAlpha.800" fontWeight="medium" display={{ base: 'none', compact: 'inline' }}>
|
|
305
|
-
{elements.length} <Text as="span" color="whiteAlpha.400" fontWeight="normal">elements</Text>
|
|
306
|
-
</Text>
|
|
307
|
-
<Box w="1px" h="10px" bg="whiteAlpha.200" display={{ base: 'none', compact: 'block' }} />
|
|
308
|
-
<Text fontSize="xs" color="whiteAlpha.800" fontWeight="medium" display={{ base: 'none', compact: 'inline' }}>
|
|
309
|
-
{allEdges.length} <Text as="span" color="whiteAlpha.400" fontWeight="normal">connectors</Text>
|
|
310
|
-
</Text>
|
|
311
|
-
<Text fontSize="xs" color="whiteAlpha.800" fontWeight="medium" display={{ base: 'none', sm: 'inline', compact: 'none' }}>
|
|
312
|
-
{elements.length}<Text as="span" color="whiteAlpha.400">E</Text>
|
|
313
|
-
<Text as="span" color="whiteAlpha.200" mx={1}>/</Text>
|
|
314
|
-
{allEdges.length}<Text as="span" color="whiteAlpha.400">C</Text>
|
|
315
|
-
</Text>
|
|
316
|
-
</HStack>
|
|
317
|
-
),
|
|
335
|
+
node: null,
|
|
318
336
|
})
|
|
319
337
|
return () => setHeader(null)
|
|
320
|
-
}, [
|
|
338
|
+
}, [setHeader])
|
|
339
|
+
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
setPage(0)
|
|
342
|
+
}, [search])
|
|
321
343
|
|
|
322
344
|
// Data fetch
|
|
323
345
|
useEffect(() => {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
346
|
+
let cancelled = false
|
|
347
|
+
const timer = window.setTimeout(() => {
|
|
348
|
+
setPageLoading(true)
|
|
349
|
+
api.dependencies
|
|
350
|
+
.list({ limit: PAGE_SIZE, offset: page * PAGE_SIZE, search })
|
|
351
|
+
.then((resp) => {
|
|
352
|
+
if (cancelled) return
|
|
353
|
+
const objs = resp.elements || []
|
|
354
|
+
const edgs = resp.connectors || []
|
|
355
|
+
const total = resp.totalCount
|
|
356
|
+
setElements(objs)
|
|
357
|
+
setAllEdges(edgs)
|
|
358
|
+
setTotalCount(total ?? page * PAGE_SIZE + objs.length)
|
|
359
|
+
setHasNextPage(total === undefined ? objs.length === PAGE_SIZE : page * PAGE_SIZE + objs.length < total)
|
|
360
|
+
|
|
361
|
+
setSelectedId((current) => {
|
|
362
|
+
if (objs.length === 0) return null
|
|
363
|
+
if (current && objs.some((obj) => obj.id === current)) return current
|
|
364
|
+
const withCounts = computeNeighbourCounts(objs, edgs)
|
|
365
|
+
const sorted = [...withCounts].sort((a, b) => b.neighbourCount - a.neighbourCount)
|
|
366
|
+
return sorted[0]?.id ?? null
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
.catch(() => { /* intentionally empty */ })
|
|
370
|
+
.finally(() => {
|
|
371
|
+
if (!cancelled) {
|
|
372
|
+
setLoading(false)
|
|
373
|
+
setPageLoading(false)
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
}, 180)
|
|
377
|
+
return () => {
|
|
378
|
+
cancelled = true
|
|
379
|
+
window.clearTimeout(timer)
|
|
380
|
+
}
|
|
381
|
+
}, [page, search])
|
|
382
|
+
|
|
383
|
+
const elementUniverse = useMemo(() => {
|
|
384
|
+
const byID = new Map<string, DependencyElement>()
|
|
385
|
+
elements.forEach((element) => byID.set(element.id, element))
|
|
386
|
+
Object.values(neighbourElements).forEach((element) => byID.set(element.id, element))
|
|
387
|
+
return Array.from(byID.values())
|
|
388
|
+
}, [elements, neighbourElements])
|
|
389
|
+
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
if (selectedId === null) return
|
|
392
|
+
const known = new Set(elementUniverse.map((element) => element.id))
|
|
393
|
+
const missing = new Set<string>()
|
|
394
|
+
allEdges.forEach((connector) => {
|
|
395
|
+
if (connector.source_element_id === selectedId && !known.has(connector.target_element_id)) {
|
|
396
|
+
missing.add(connector.target_element_id)
|
|
397
|
+
}
|
|
398
|
+
if (connector.target_element_id === selectedId && !known.has(connector.source_element_id)) {
|
|
399
|
+
missing.add(connector.source_element_id)
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
if (missing.size === 0) return
|
|
403
|
+
let cancelled = false
|
|
404
|
+
Promise.all(
|
|
405
|
+
Array.from(missing).slice(0, 120).map((id) =>
|
|
406
|
+
api.elements.get(Number(id)).then((element) => ({
|
|
407
|
+
id: String(element.id),
|
|
408
|
+
name: element.name,
|
|
409
|
+
type: element.kind,
|
|
410
|
+
description: element.description,
|
|
411
|
+
technology: element.technology,
|
|
412
|
+
url: element.url,
|
|
413
|
+
logo_url: element.logo_url,
|
|
414
|
+
technology_connectors: element.technology_connectors,
|
|
415
|
+
tags: element.tags,
|
|
416
|
+
repo: element.repo,
|
|
417
|
+
branch: element.branch,
|
|
418
|
+
language: element.language,
|
|
419
|
+
file_path: element.file_path,
|
|
420
|
+
created_at: element.created_at,
|
|
421
|
+
updated_at: element.updated_at,
|
|
422
|
+
} satisfies DependencyElement)).catch(() => null),
|
|
423
|
+
),
|
|
424
|
+
).then((items) => {
|
|
425
|
+
if (cancelled) return
|
|
426
|
+
setNeighbourElements((prev) => {
|
|
427
|
+
const next = { ...prev }
|
|
428
|
+
items.forEach((item) => {
|
|
429
|
+
if (item) next[item.id] = item
|
|
430
|
+
})
|
|
431
|
+
return next
|
|
337
432
|
})
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}, [])
|
|
433
|
+
})
|
|
434
|
+
return () => { cancelled = true }
|
|
435
|
+
}, [allEdges, elementUniverse, selectedId])
|
|
341
436
|
|
|
342
437
|
// Derived data
|
|
343
438
|
const elementsWithCounts = useMemo(
|
|
@@ -364,12 +459,12 @@ export default function Dependencies() {
|
|
|
364
459
|
|
|
365
460
|
const selectedElement = useMemo(() => {
|
|
366
461
|
if (selectedId === null) return null
|
|
367
|
-
return
|
|
368
|
-
}, [
|
|
462
|
+
return elementUniverse.find((o) => o.id === selectedId) || null
|
|
463
|
+
}, [elementUniverse, selectedId])
|
|
369
464
|
const neighbourGraph = useMemo(() => {
|
|
370
465
|
if (selectedId === null) return []
|
|
371
|
-
return getNeighbourGraph(selectedId,
|
|
372
|
-
}, [selectedId,
|
|
466
|
+
return getNeighbourGraph(selectedId, elementUniverse, allEdges)
|
|
467
|
+
}, [selectedId, elementUniverse, allEdges])
|
|
373
468
|
|
|
374
469
|
// Divider drag
|
|
375
470
|
const startDrag = useCallback(() => {
|
|
@@ -497,7 +592,7 @@ export default function Dependencies() {
|
|
|
497
592
|
|
|
498
593
|
if (loading) {
|
|
499
594
|
return (
|
|
500
|
-
<Flex h="
|
|
595
|
+
<Flex h="100%" align="center" justify="center">
|
|
501
596
|
<Spinner size="xl" color="blue.500" thickness="3px" />
|
|
502
597
|
</Flex>
|
|
503
598
|
)
|
|
@@ -528,9 +623,11 @@ export default function Dependencies() {
|
|
|
528
623
|
const colSpacing = maxCompactLevel >= 3 ? 2 : maxCompactLevel >= 2 ? 3 : maxCompactLevel >= 1 ? 5 : 8
|
|
529
624
|
const nodeSpacing = maxCompactLevel >= 2 ? 1 : maxCompactLevel >= 1 ? 2 : 3
|
|
530
625
|
const selectedCardShadow = `0 0 0 3px ${hexToRgba(accent, 0.38)}, 0 18px 48px ${hexToRgba(accent, 0.12)}, 0 10px 36px rgba(0,0,0,0.55), 0 3px 10px rgba(0,0,0,0.4)`
|
|
626
|
+
const rangeStart = elements.length > 0 ? page * PAGE_SIZE + 1 : 0
|
|
627
|
+
const rangeEnd = page * PAGE_SIZE + elements.length
|
|
531
628
|
|
|
532
629
|
return (
|
|
533
|
-
<Box h="
|
|
630
|
+
<Box h="100%" display="flex" flexDir="column" bg="var(--bg-canvas)">
|
|
534
631
|
<Box ref={containerRef} flex={1} display="flex" flexDir="column" overflow="hidden">
|
|
535
632
|
|
|
536
633
|
{/* ── Top: Listing ──────────────────────────────────────────────────── */}
|
|
@@ -594,9 +691,45 @@ export default function Dependencies() {
|
|
|
594
691
|
</MenuList>
|
|
595
692
|
</Menu>
|
|
596
693
|
<Box flex={1} />
|
|
597
|
-
|
|
598
|
-
|
|
694
|
+
|
|
695
|
+
<HStack spacing={4} mr={4} display={{ base: 'none', md: 'flex' }}>
|
|
696
|
+
<HStack spacing={1.5}>
|
|
697
|
+
<Text fontSize="xs" color="whiteAlpha.900" fontWeight="bold">{totalCount}</Text>
|
|
698
|
+
<Text fontSize="xs" color="whiteAlpha.400">elements</Text>
|
|
699
|
+
</HStack>
|
|
700
|
+
<HStack spacing={1.5}>
|
|
701
|
+
<Text fontSize="xs" color="whiteAlpha.900" fontWeight="bold">{allEdges.length}</Text>
|
|
702
|
+
<Text fontSize="xs" color="whiteAlpha.400">connectors</Text>
|
|
703
|
+
</HStack>
|
|
704
|
+
</HStack>
|
|
705
|
+
|
|
706
|
+
<Box w="1px" h="12px" bg="whiteAlpha.200" mr={2} display={{ base: 'none', md: 'block' }} />
|
|
707
|
+
|
|
708
|
+
<Text fontSize="xs" color="gray.600" fontWeight="medium">
|
|
709
|
+
{rangeStart}-{rangeEnd} <Text as="span" color="gray.700" display={{ base: 'none', sm: 'inline' }}>of {totalCount}</Text>
|
|
599
710
|
</Text>
|
|
711
|
+
{pageLoading && <Spinner size="xs" color="gray.500" />}
|
|
712
|
+
<HStack spacing={1} data-pan-block="true">
|
|
713
|
+
<Button
|
|
714
|
+
variant="elevated"
|
|
715
|
+
size="xs"
|
|
716
|
+
isDisabled={page === 0 || pageLoading}
|
|
717
|
+
onClick={() => setPage((current) => Math.max(0, current - 1))}
|
|
718
|
+
>
|
|
719
|
+
Previous
|
|
720
|
+
</Button>
|
|
721
|
+
<Text fontSize="xs" color="gray.500" minW="48px" textAlign="center">
|
|
722
|
+
Page {page + 1}
|
|
723
|
+
</Text>
|
|
724
|
+
<Button
|
|
725
|
+
variant="elevated"
|
|
726
|
+
size="xs"
|
|
727
|
+
isDisabled={!hasNextPage || pageLoading}
|
|
728
|
+
onClick={() => setPage((current) => current + 1)}
|
|
729
|
+
>
|
|
730
|
+
Next
|
|
731
|
+
</Button>
|
|
732
|
+
</HStack>
|
|
600
733
|
</Flex>
|
|
601
734
|
|
|
602
735
|
{/* Column headers */}
|
|
@@ -640,6 +773,15 @@ export default function Dependencies() {
|
|
|
640
773
|
const color = TYPE_COLORS[typeKey] ?? 'gray'
|
|
641
774
|
const accentHex = TYPE_HEX[typeKey] ?? '#718096'
|
|
642
775
|
const isSelected = selectedId === obj.id
|
|
776
|
+
const versionChangeType = versionPulseChangeForElement(Number(obj.id))
|
|
777
|
+
const versionLineDelta = versionPreview?.elementLineDeltas.get(Number(obj.id))
|
|
778
|
+
const versionColor = versionChangeType === 'added'
|
|
779
|
+
? 'green.300'
|
|
780
|
+
: versionChangeType === 'deleted'
|
|
781
|
+
? 'red.300'
|
|
782
|
+
: versionChangeType
|
|
783
|
+
? 'yellow.300'
|
|
784
|
+
: undefined
|
|
643
785
|
|
|
644
786
|
return (
|
|
645
787
|
<Flex
|
|
@@ -653,9 +795,12 @@ export default function Dependencies() {
|
|
|
653
795
|
bg={isSelected ? 'rgba(66,153,225,0.07)' : 'transparent'}
|
|
654
796
|
_hover={{ bg: isSelected ? 'rgba(66,153,225,0.1)' : 'whiteAlpha.50' }}
|
|
655
797
|
transition="background 0.1s"
|
|
656
|
-
onClick={() =>
|
|
798
|
+
onClick={() => selectElement(isSelected ? null : obj.id)}
|
|
657
799
|
position="relative"
|
|
658
800
|
role="row"
|
|
801
|
+
outline={versionColor ? '1px solid' : undefined}
|
|
802
|
+
outlineColor={versionColor}
|
|
803
|
+
outlineOffset="-1px"
|
|
659
804
|
>
|
|
660
805
|
{/* Left type-color accent */}
|
|
661
806
|
<Box
|
|
@@ -669,14 +814,28 @@ export default function Dependencies() {
|
|
|
669
814
|
|
|
670
815
|
{/* Name */}
|
|
671
816
|
<Box flex={1} minW={0} mr={4}>
|
|
672
|
-
<
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
817
|
+
<HStack spacing={2} minW={0}>
|
|
818
|
+
<Text
|
|
819
|
+
fontSize="sm"
|
|
820
|
+
fontWeight={isSelected ? 'semibold' : 'medium'}
|
|
821
|
+
color={isSelected ? 'white' : 'gray.100'}
|
|
822
|
+
noOfLines={1}
|
|
823
|
+
minW={0}
|
|
824
|
+
>
|
|
825
|
+
{obj.name}
|
|
826
|
+
</Text>
|
|
827
|
+
{versionChangeType && (
|
|
828
|
+
<Badge colorScheme={versionChangeType === 'added' ? 'green' : versionChangeType === 'deleted' ? 'red' : 'yellow'} fontSize="8px" flexShrink={0}>
|
|
829
|
+
{versionChangeType === 'added' ? '+' : versionChangeType === 'deleted' ? '-' : '~'}
|
|
830
|
+
</Badge>
|
|
831
|
+
)}
|
|
832
|
+
{versionLineDelta && (
|
|
833
|
+
<HStack spacing={1} flexShrink={0}>
|
|
834
|
+
{versionLineDelta.added > 0 && <Text fontSize="10px" color="green.300" fontWeight="800">+{versionLineDelta.added}</Text>}
|
|
835
|
+
{versionLineDelta.removed > 0 && <Text fontSize="10px" color="red.300" fontWeight="800">-{versionLineDelta.removed}</Text>}
|
|
836
|
+
</HStack>
|
|
837
|
+
)}
|
|
838
|
+
</HStack>
|
|
680
839
|
</Box>
|
|
681
840
|
|
|
682
841
|
{/* Type badge */}
|
|
@@ -800,7 +959,8 @@ export default function Dependencies() {
|
|
|
800
959
|
key={n.element.id}
|
|
801
960
|
node={n}
|
|
802
961
|
compactLevel={maxCompactLevel}
|
|
803
|
-
|
|
962
|
+
versionChangeType={versionPulseChangeForElement(Number(n.element.id))}
|
|
963
|
+
onClick={() => selectElement(n.element.id)}
|
|
804
964
|
/>
|
|
805
965
|
))}
|
|
806
966
|
</HStack>
|
|
@@ -823,7 +983,8 @@ export default function Dependencies() {
|
|
|
823
983
|
key={n.element.id}
|
|
824
984
|
node={n}
|
|
825
985
|
compactLevel={leftCompactLevel}
|
|
826
|
-
|
|
986
|
+
versionChangeType={versionPulseChangeForElement(Number(n.element.id))}
|
|
987
|
+
onClick={() => selectElement(n.element.id)}
|
|
827
988
|
/>
|
|
828
989
|
))}
|
|
829
990
|
</VStack>
|
|
@@ -844,6 +1005,9 @@ export default function Dependencies() {
|
|
|
844
1005
|
bg={elementColor}
|
|
845
1006
|
borderColor={accent}
|
|
846
1007
|
borderWidth="2px"
|
|
1008
|
+
outline={selectedId && versionPulseChangeForElement(Number(selectedId)) ? '3px solid' : undefined}
|
|
1009
|
+
outlineColor={selectedId && versionPulseChangeForElement(Number(selectedId)) === 'added' ? 'green.300' : selectedId && versionPulseChangeForElement(Number(selectedId)) === 'deleted' ? 'red.300' : selectedId && versionPulseChangeForElement(Number(selectedId)) ? 'yellow.300' : undefined}
|
|
1010
|
+
outlineOffset="3px"
|
|
847
1011
|
boxShadow={selectedCardShadow}
|
|
848
1012
|
>
|
|
849
1013
|
<ElementBody
|
|
@@ -873,10 +1037,11 @@ export default function Dependencies() {
|
|
|
873
1037
|
{column.map((n) => (
|
|
874
1038
|
<NeighbourCard
|
|
875
1039
|
key={n.element.id}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1040
|
+
node={n}
|
|
1041
|
+
compactLevel={rightCompactLevel}
|
|
1042
|
+
versionChangeType={versionPulseChangeForElement(Number(n.element.id))}
|
|
1043
|
+
onClick={() => selectElement(n.element.id)}
|
|
1044
|
+
/>
|
|
880
1045
|
))}
|
|
881
1046
|
</VStack>
|
|
882
1047
|
))}
|
|
@@ -897,7 +1062,8 @@ export default function Dependencies() {
|
|
|
897
1062
|
key={n.element.id}
|
|
898
1063
|
node={n}
|
|
899
1064
|
compactLevel={maxCompactLevel}
|
|
900
|
-
|
|
1065
|
+
versionChangeType={versionPulseChangeForElement(Number(n.element.id))}
|
|
1066
|
+
onClick={() => selectElement(n.element.id)}
|
|
901
1067
|
/>
|
|
902
1068
|
))}
|
|
903
1069
|
</HStack>
|