@tldiagram/core-ui 2.0.6 → 2.0.7
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/components/ZUI/ZUICanvas.d.ts +2 -0
- package/dist/components/ZUI/renderer.d.ts +1 -1
- package/dist/components/ZUI/types.d.ts +1 -0
- package/dist/index.js +8739 -8369
- package/dist/utils/exploreDiffLens.d.ts +47 -0
- package/dist/utils/exploreDiffLens.test.d.ts +1 -0
- package/dist/utils/watchDiffSummary.d.ts +4 -0
- package/dist/utils/watchDiffSummary.test.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/WorkspacePanel.tsx +34 -8
- package/src/components/ZUI/ZUICanvas.tsx +103 -2
- package/src/components/ZUI/renderer.ts +40 -4
- package/src/components/ZUI/types.ts +1 -0
- package/src/components/ZUI/useZUIInteraction.ts +3 -0
- package/src/context/WorkspaceVersionContext.tsx +4 -4
- package/src/pages/InfiniteZoom.tsx +210 -0
- package/src/utils/exploreDiffLens.test.ts +185 -0
- package/src/utils/exploreDiffLens.ts +262 -0
- package/src/utils/watchDiffSummary.test.ts +95 -0
- package/src/utils/watchDiffSummary.ts +35 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { WatchDiff } from '../api/client';
|
|
2
|
+
import type { ExploreData } from '../types';
|
|
3
|
+
import { type WatchChangeType } from './watchDiffSummary';
|
|
4
|
+
export interface ExploreDiffLineDelta {
|
|
5
|
+
added: number;
|
|
6
|
+
removed: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ExploreDiffDetail {
|
|
9
|
+
key: string;
|
|
10
|
+
resourceType: string;
|
|
11
|
+
resourceId?: number;
|
|
12
|
+
changeType: WatchChangeType;
|
|
13
|
+
summary?: string;
|
|
14
|
+
ownerType: string;
|
|
15
|
+
ownerKey: string;
|
|
16
|
+
language?: string;
|
|
17
|
+
addedLines: number;
|
|
18
|
+
removedLines: number;
|
|
19
|
+
sourcePath?: string;
|
|
20
|
+
line?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ExploreDiffTarget extends ExploreDiffDetail {
|
|
23
|
+
label: string;
|
|
24
|
+
viewId?: number;
|
|
25
|
+
viewName?: string;
|
|
26
|
+
unplaced: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface ExploreDiffLens {
|
|
29
|
+
versionId: number;
|
|
30
|
+
elementChanges: Map<number, WatchChangeType>;
|
|
31
|
+
connectorChanges: Map<number, WatchChangeType>;
|
|
32
|
+
elementLineDeltas: Map<number, ExploreDiffLineDelta>;
|
|
33
|
+
diffDetailsByResource: Map<string, ExploreDiffDetail>;
|
|
34
|
+
orderedTargets: ExploreDiffTarget[];
|
|
35
|
+
unplacedTargets: ExploreDiffTarget[];
|
|
36
|
+
changedElementIds: Set<number>;
|
|
37
|
+
changedConnectorIds: Set<number>;
|
|
38
|
+
ancestorElementIds: Set<number>;
|
|
39
|
+
siblingElementIds: Set<number>;
|
|
40
|
+
contextElementIds: Set<number>;
|
|
41
|
+
contextConnectorIds: Set<number>;
|
|
42
|
+
totalAddedLines: number;
|
|
43
|
+
totalRemovedLines: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function diffResourceKey(resourceType: string | null | undefined, resourceId: number | null | undefined): string;
|
|
46
|
+
export declare function sourcePathFromDiff(diff: Pick<WatchDiff, 'owner_type' | 'owner_key'>): string | undefined;
|
|
47
|
+
export declare function buildExploreDiffLens(data: ExploreData, diffs: WatchDiff[] | null | undefined, versionId: number): ExploreDiffLens;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -26,9 +26,13 @@ export interface WatchDiffSummary {
|
|
|
26
26
|
elements: WatchResourceStat;
|
|
27
27
|
connectors: WatchResourceStat;
|
|
28
28
|
}
|
|
29
|
+
export declare function changedResourceCount(stat: WatchResourceStat): number;
|
|
30
|
+
export declare function totalResourceCount(stat: WatchResourceStat): number;
|
|
29
31
|
export declare function normalizeWatchChangeType(value: string): WatchChangeType;
|
|
32
|
+
export declare function isWatchDiffChange(value: string | null | undefined): boolean;
|
|
30
33
|
export declare function emptyWatchResourceStat(): WatchResourceStat;
|
|
31
34
|
export declare function summarizeWatchDiffs(diffs: WatchDiff[] | null | undefined): WatchDiffSummary;
|
|
32
35
|
export declare function formatStatLine(label: string, stat: WatchResourceStat): string;
|
|
36
|
+
export declare function formatDiagramResourceSummary(summary: WatchDiffSummary): string;
|
|
33
37
|
export declare function formatTldStatLine(summary: WatchDiffSummary): string;
|
|
34
38
|
export declare function buildWatchDiffLocations(data: ExploreData, diffs: WatchDiff[] | null | undefined): WatchDiffLocation[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -35,7 +35,10 @@ import {
|
|
|
35
35
|
import { buildWorkspaceVersionPreview, useWorkspaceVersionPreview } from '../context/WorkspaceVersionContext'
|
|
36
36
|
import {
|
|
37
37
|
buildWatchDiffLocations,
|
|
38
|
+
formatDiagramResourceSummary,
|
|
39
|
+
isWatchDiffChange,
|
|
38
40
|
summarizeWatchDiffs,
|
|
41
|
+
totalResourceCount,
|
|
39
42
|
type WatchDiffLocation,
|
|
40
43
|
type WatchDiffSummary,
|
|
41
44
|
} from '../utils/watchDiffSummary'
|
|
@@ -127,9 +130,7 @@ function ResourceCountDisplay({ summary }: { summary: WatchDiffSummary }) {
|
|
|
127
130
|
{ label: 'Elements', stat: summary.elements },
|
|
128
131
|
{ label: 'Connectors', stat: summary.connectors },
|
|
129
132
|
]
|
|
130
|
-
const total = rows.reduce((sum, row) => (
|
|
131
|
-
sum + row.stat.added + row.stat.updated + row.stat.deleted + row.stat.initialized
|
|
132
|
-
), 0)
|
|
133
|
+
const total = rows.reduce((sum, row) => sum + totalResourceCount(row.stat), 0)
|
|
133
134
|
const changes = [
|
|
134
135
|
{ key: 'added', label: 'added', color: 'green.300' },
|
|
135
136
|
{ key: 'updated', label: 'updated', color: 'yellow.300' },
|
|
@@ -165,7 +166,7 @@ function ResourceCountDisplay({ summary }: { summary: WatchDiffSummary }) {
|
|
|
165
166
|
</Text>
|
|
166
167
|
) : null
|
|
167
168
|
})}
|
|
168
|
-
{row.stat
|
|
169
|
+
{totalResourceCount(row.stat) === 0 && (
|
|
169
170
|
<Text fontSize="11px" color="gray.600" fontFamily="mono">none</Text>
|
|
170
171
|
)}
|
|
171
172
|
</HStack>
|
|
@@ -408,10 +409,17 @@ export default function WorkspacePanel() {
|
|
|
408
409
|
}, [activeDiffLocationIndex, navigableDiffLocations, navigateToDiffLocation])
|
|
409
410
|
|
|
410
411
|
const activeVersion = preview?.version ?? selectedVersion
|
|
412
|
+
|
|
413
|
+
const navigateToDiffMap = useCallback(() => {
|
|
414
|
+
const targetVersion = activeVersion ?? selectedVersion
|
|
415
|
+
if (!targetVersion) return
|
|
416
|
+
navigate(`/views?view=explore&diffVersion=${targetVersion.id}`)
|
|
417
|
+
}, [activeVersion, navigate, selectedVersion])
|
|
418
|
+
|
|
411
419
|
const diffSummary = useMemo(() => summarizeWatchDiffs(diffs), [diffs])
|
|
412
|
-
const totalFileChanges = diffSummary.files
|
|
413
|
-
const
|
|
414
|
-
|
|
420
|
+
const totalFileChanges = totalResourceCount(diffSummary.files)
|
|
421
|
+
const diagramResourceSummary = formatDiagramResourceSummary(diffSummary)
|
|
422
|
+
const hasDiffMapTargets = useMemo(() => diffs.some((diff) => isWatchDiffChange(diff.change_type)), [diffs])
|
|
415
423
|
const activeDiffLocation = activeDiffLocationIndex >= 0 ? navigableDiffLocations[activeDiffLocationIndex] : null
|
|
416
424
|
const headerAddedLines = activeDiffLocation?.addedLines ?? diffSummary.elements.addedLines + diffSummary.connectors.addedLines
|
|
417
425
|
const headerRemovedLines = activeDiffLocation?.removedLines ?? diffSummary.elements.removedLines + diffSummary.connectors.removedLines
|
|
@@ -716,10 +724,28 @@ export default function WorkspacePanel() {
|
|
|
716
724
|
<Text fontSize="12px" color="gray.400" fontWeight="500" noOfLines={1} flex={1}>
|
|
717
725
|
{activeDiffLocation
|
|
718
726
|
? `${activeDiffLocationIndex + 1} of ${navigableDiffLocations.length}: ${activeDiffLocation.label}`
|
|
719
|
-
:
|
|
727
|
+
: diagramResourceSummary}
|
|
720
728
|
</Text>
|
|
721
729
|
</HStack>
|
|
722
730
|
<HStack spacing={1} flexShrink={0}>
|
|
731
|
+
<Tooltip label="Open diff map" placement="top">
|
|
732
|
+
<Button
|
|
733
|
+
size="sm"
|
|
734
|
+
h="32px"
|
|
735
|
+
px={3}
|
|
736
|
+
variant="solid"
|
|
737
|
+
bg="whiteAlpha.200"
|
|
738
|
+
_hover={{ bg: 'whiteAlpha.300' }}
|
|
739
|
+
_active={{ bg: 'whiteAlpha.400' }}
|
|
740
|
+
leftIcon={<ViewIcon boxSize={3.5} />}
|
|
741
|
+
fontSize="12px"
|
|
742
|
+
fontWeight="600"
|
|
743
|
+
isDisabled={!activeVersion || !hasDiffMapTargets}
|
|
744
|
+
onClick={navigateToDiffMap}
|
|
745
|
+
>
|
|
746
|
+
Diff map
|
|
747
|
+
</Button>
|
|
748
|
+
</Tooltip>
|
|
723
749
|
<Tooltip label="Previous element" placement="top">
|
|
724
750
|
<IconButton
|
|
725
751
|
aria-label="Previous"
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import { Link as RouterLink } from 'react-router-dom'
|
|
27
27
|
import { ExternalLinkIcon } from '@chakra-ui/icons'
|
|
28
28
|
import type { ExploreData } from '../../types'
|
|
29
|
+
import { api } from '../../api/client'
|
|
29
30
|
import { computeLayout } from './layout'
|
|
30
31
|
import { renderFrame, getExpandThresholds, getCameraRebase, rawCameraView, screenToWorldX, screenToWorldY, worldToScreenX, worldToScreenY, setOnImageLoadCallback, setHighlightedTags as setRendererHighlightedTags, setHiddenTags as setRendererHiddenTags, setHighlightColor as setRendererHighlightColor, setVersionDiff as setRendererVersionDiff } from './renderer'
|
|
31
32
|
import { useZUIInteraction } from './useZUIInteraction'
|
|
@@ -35,6 +36,9 @@ import { buildWorkspaceGraphSnapshot } from '../../crossBranch/graph'
|
|
|
35
36
|
import type { CrossBranchContextSettings } from '../../crossBranch/types'
|
|
36
37
|
import { DEFAULT_MIN_CONNECTOR_ANCHOR_ALPHA } from '../../crossBranch/settings'
|
|
37
38
|
import type { WorkspaceVersionFollowTarget, WorkspaceVersionPreview } from '../../context/WorkspaceVersionContext'
|
|
39
|
+
import { diffResourceKey, type ExploreDiffDetail, type ExploreDiffLens } from '../../utils/exploreDiffLens'
|
|
40
|
+
import { getSourceEditor } from '../../utils/sourceEditor'
|
|
41
|
+
import { toast } from '../../utils/toast'
|
|
38
42
|
import {
|
|
39
43
|
buildProxyConnectorSpatialIndex,
|
|
40
44
|
buildVisibleProxyConnectors,
|
|
@@ -71,6 +75,7 @@ interface Props {
|
|
|
71
75
|
hiddenTags?: string[]
|
|
72
76
|
versionPreview?: WorkspaceVersionPreview | null
|
|
73
77
|
versionFollowTarget?: WorkspaceVersionFollowTarget | null
|
|
78
|
+
diffLens?: ExploreDiffLens | null
|
|
74
79
|
crossBranchSettings: CrossBranchContextSettings
|
|
75
80
|
hoverLocked?: boolean
|
|
76
81
|
}
|
|
@@ -244,6 +249,60 @@ function fitWorldRect(
|
|
|
244
249
|
}
|
|
245
250
|
}
|
|
246
251
|
|
|
252
|
+
function diffColorScheme(change: string | undefined): 'green' | 'red' | 'yellow' | 'blue' {
|
|
253
|
+
if (change === 'added') return 'green'
|
|
254
|
+
if (change === 'deleted') return 'red'
|
|
255
|
+
if (change === 'initialized') return 'blue'
|
|
256
|
+
return 'yellow'
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function DiffDetailBlock({
|
|
260
|
+
detail,
|
|
261
|
+
onOpenSource,
|
|
262
|
+
}: {
|
|
263
|
+
detail: ExploreDiffDetail | null
|
|
264
|
+
onOpenSource: (detail: ExploreDiffDetail) => void
|
|
265
|
+
}) {
|
|
266
|
+
if (!detail) return null
|
|
267
|
+
const hasLines = detail.addedLines > 0 || detail.removedLines > 0
|
|
268
|
+
return (
|
|
269
|
+
<VStack align="stretch" spacing={2} mb={3}>
|
|
270
|
+
<HStack spacing={2} minW={0}>
|
|
271
|
+
<Badge colorScheme={diffColorScheme(detail.changeType)} variant="subtle" fontSize="2xs">
|
|
272
|
+
{detail.changeType}
|
|
273
|
+
</Badge>
|
|
274
|
+
{hasLines && (
|
|
275
|
+
<HStack spacing={1.5} fontSize="xs" fontFamily="mono">
|
|
276
|
+
{detail.addedLines > 0 && <Text color="green.300">+{detail.addedLines}</Text>}
|
|
277
|
+
{detail.removedLines > 0 && <Text color="red.300">-{detail.removedLines}</Text>}
|
|
278
|
+
</HStack>
|
|
279
|
+
)}
|
|
280
|
+
</HStack>
|
|
281
|
+
{detail.summary && (
|
|
282
|
+
<Text fontSize="xs" color="gray.200" noOfLines={3}>{detail.summary}</Text>
|
|
283
|
+
)}
|
|
284
|
+
{detail.sourcePath && (
|
|
285
|
+
<Text fontSize="11px" color="gray.500" fontFamily="mono" noOfLines={2}>{detail.sourcePath}</Text>
|
|
286
|
+
)}
|
|
287
|
+
{detail.sourcePath && (
|
|
288
|
+
<Button
|
|
289
|
+
size="xs"
|
|
290
|
+
variant="outline"
|
|
291
|
+
colorScheme="blue"
|
|
292
|
+
alignSelf="flex-start"
|
|
293
|
+
onClick={(event) => {
|
|
294
|
+
event.stopPropagation()
|
|
295
|
+
onOpenSource(detail)
|
|
296
|
+
}}
|
|
297
|
+
>
|
|
298
|
+
Open Source
|
|
299
|
+
</Button>
|
|
300
|
+
)}
|
|
301
|
+
<Divider borderColor="whiteAlpha.200" />
|
|
302
|
+
</VStack>
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
247
306
|
function findFirstExpandableNode(groups: DiagramGroupLayout[]): PathItem | null {
|
|
248
307
|
for (const group of groups) {
|
|
249
308
|
const found = findFirstExpandableNodeInTree(group.nodes, 0, 0, 1, 0, 0)
|
|
@@ -292,7 +351,7 @@ function findFirstExpandableNodeInTree(
|
|
|
292
351
|
return null
|
|
293
352
|
}
|
|
294
353
|
|
|
295
|
-
export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({ data, onReady, onZoom, onPan, initialCameraFrame, highlightedTags, highlightColor, hiddenTags, versionPreview, versionFollowTarget, crossBranchSettings, hoverLocked = false }, ref) {
|
|
354
|
+
export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({ data, onReady, onZoom, onPan, initialCameraFrame, highlightedTags, highlightColor, hiddenTags, versionPreview, versionFollowTarget, diffLens, crossBranchSettings, hoverLocked = false }, ref) {
|
|
296
355
|
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
297
356
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
298
357
|
const cameraTransitionRef = useRef<number | null>(null)
|
|
@@ -466,6 +525,34 @@ export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({
|
|
|
466
525
|
)
|
|
467
526
|
}, [hoveredScreenRect, containerSize])
|
|
468
527
|
|
|
528
|
+
const hoveredDiffDetail = useMemo(() => {
|
|
529
|
+
if (!hoveredItem || !diffLens) return null
|
|
530
|
+
if (hoveredItem.type === 'node') {
|
|
531
|
+
return diffLens.diffDetailsByResource.get(diffResourceKey('element', hoveredItem.data.elementId)) ?? null
|
|
532
|
+
}
|
|
533
|
+
if (hoveredItem.type === 'edge' && !hoveredItem.data.isProxy) {
|
|
534
|
+
return hoveredItem.data.id
|
|
535
|
+
? diffLens.diffDetailsByResource.get(diffResourceKey('connector', hoveredItem.data.id)) ?? null
|
|
536
|
+
: null
|
|
537
|
+
}
|
|
538
|
+
return null
|
|
539
|
+
}, [diffLens, hoveredItem])
|
|
540
|
+
|
|
541
|
+
const handleOpenSource = useCallback((detail: ExploreDiffDetail) => {
|
|
542
|
+
if (!detail.sourcePath) return
|
|
543
|
+
api.editor.open({
|
|
544
|
+
editor: getSourceEditor(),
|
|
545
|
+
file_path: detail.sourcePath,
|
|
546
|
+
line: detail.line ?? null,
|
|
547
|
+
}).catch((error: unknown) => {
|
|
548
|
+
toast({
|
|
549
|
+
title: 'Could not open source',
|
|
550
|
+
description: error instanceof Error ? error.message : 'The source editor command failed.',
|
|
551
|
+
status: 'error',
|
|
552
|
+
})
|
|
553
|
+
})
|
|
554
|
+
}, [])
|
|
555
|
+
|
|
469
556
|
// Debounce breadcrumb computation so getPathAt doesn't run on every scroll tick
|
|
470
557
|
const [breadcrumbView, setBreadcrumbView] = useState(viewState)
|
|
471
558
|
const breadcrumbTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
@@ -820,6 +907,18 @@ export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({
|
|
|
820
907
|
}, [hiddenTags])
|
|
821
908
|
|
|
822
909
|
useEffect(() => {
|
|
910
|
+
if (diffLens) {
|
|
911
|
+
setRendererVersionDiff(
|
|
912
|
+
diffLens.elementChanges,
|
|
913
|
+
diffLens.connectorChanges,
|
|
914
|
+
diffLens.elementLineDeltas,
|
|
915
|
+
diffLens.contextElementIds,
|
|
916
|
+
diffLens.contextConnectorIds,
|
|
917
|
+
true,
|
|
918
|
+
)
|
|
919
|
+
needsRedrawRef.current = true
|
|
920
|
+
return
|
|
921
|
+
}
|
|
823
922
|
const pulsedElementChanges = new Map<number, string>()
|
|
824
923
|
const pulsedElementLineDeltas = new Map<number, { added: number; removed: number }>()
|
|
825
924
|
if (versionFollowTarget?.resourceType === 'element' && versionFollowTarget.resourceId) {
|
|
@@ -832,7 +931,7 @@ export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({
|
|
|
832
931
|
versionPreview?.elementLineDeltas ?? pulsedElementLineDeltas,
|
|
833
932
|
)
|
|
834
933
|
needsRedrawRef.current = true
|
|
835
|
-
}, [versionPreview, versionFollowTarget])
|
|
934
|
+
}, [diffLens, versionPreview, versionFollowTarget])
|
|
836
935
|
|
|
837
936
|
useEffect(() => {
|
|
838
937
|
if (!initialized || !versionFollowTarget?.viewId) return
|
|
@@ -979,6 +1078,7 @@ export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({
|
|
|
979
1078
|
</PopoverHeader>
|
|
980
1079
|
<PopoverBody px={4} py={3}>
|
|
981
1080
|
<VStack align="start" spacing={3}>
|
|
1081
|
+
<DiffDetailBlock detail={hoveredDiffDetail} onOpenSource={handleOpenSource} />
|
|
982
1082
|
{hoveredItem.data.technology && (
|
|
983
1083
|
<Box>
|
|
984
1084
|
<Text color="gray.400" fontSize="xs" fontWeight="600" mb={0.5} letterSpacing="wider">TECHNOLOGY</Text>
|
|
@@ -1033,6 +1133,7 @@ export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({
|
|
|
1033
1133
|
</PopoverHeader>
|
|
1034
1134
|
<PopoverBody px={4} py={3}>
|
|
1035
1135
|
<VStack align="start" spacing={3}>
|
|
1136
|
+
<DiffDetailBlock detail={hoveredDiffDetail} onOpenSource={handleOpenSource} />
|
|
1036
1137
|
<VStack align="start" spacing={1}>
|
|
1037
1138
|
<Text color="gray.400" fontSize="2xs" fontWeight="600" letterSpacing="wider">BETWEEN</Text>
|
|
1038
1139
|
<Text fontSize="xs" color="gray.200">
|
|
@@ -139,14 +139,23 @@ export function setHiddenTags(tags: Set<string>): void {
|
|
|
139
139
|
let currentVersionElementChanges: Map<number, string> = new Map()
|
|
140
140
|
let currentVersionConnectorChanges: Map<number, string> = new Map()
|
|
141
141
|
let currentVersionElementLineDeltas: Map<number, { added: number; removed: number }> = new Map()
|
|
142
|
+
let currentDiffContextElementIds: Set<number> = new Set()
|
|
143
|
+
let currentDiffContextConnectorIds: Set<number> = new Set()
|
|
144
|
+
let currentDiffLensActive = false
|
|
142
145
|
export function setVersionDiff(
|
|
143
146
|
elementChanges: Map<number, string>,
|
|
144
147
|
connectorChanges: Map<number, string>,
|
|
145
148
|
elementLineDeltas: Map<number, { added: number; removed: number }> = new Map(),
|
|
149
|
+
contextElementIds: Set<number> = new Set(),
|
|
150
|
+
contextConnectorIds: Set<number> = new Set(),
|
|
151
|
+
diffLensActive = false,
|
|
146
152
|
): void {
|
|
147
153
|
currentVersionElementChanges = elementChanges
|
|
148
154
|
currentVersionConnectorChanges = connectorChanges
|
|
149
155
|
currentVersionElementLineDeltas = elementLineDeltas
|
|
156
|
+
currentDiffContextElementIds = contextElementIds
|
|
157
|
+
currentDiffContextConnectorIds = contextConnectorIds
|
|
158
|
+
currentDiffLensActive = diffLensActive
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
/**
|
|
@@ -1078,10 +1087,20 @@ function drawNode(
|
|
|
1078
1087
|
const change = currentVersionElementChanges.get(node.elementId)
|
|
1079
1088
|
if (!change) {
|
|
1080
1089
|
ctx.save()
|
|
1081
|
-
|
|
1090
|
+
const isContext = currentDiffLensActive && currentDiffContextElementIds.has(node.elementId)
|
|
1091
|
+
ctx.globalAlpha = parentAlpha * (isContext ? 0.45 : 0.9)
|
|
1082
1092
|
ctx.fillStyle = canvasBg
|
|
1083
1093
|
traceShape()
|
|
1084
1094
|
ctx.fill()
|
|
1095
|
+
if (isContext && drawScreenW > 40) {
|
|
1096
|
+
ctx.globalAlpha = parentAlpha * 0.55
|
|
1097
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.18)'
|
|
1098
|
+
ctx.lineWidth = 1.5 / drawZoom
|
|
1099
|
+
ctx.setLineDash([4 / drawZoom, 4 / drawZoom])
|
|
1100
|
+
traceShape()
|
|
1101
|
+
ctx.stroke()
|
|
1102
|
+
ctx.setLineDash([])
|
|
1103
|
+
}
|
|
1085
1104
|
ctx.restore()
|
|
1086
1105
|
} else {
|
|
1087
1106
|
const color = change === 'added' ? '#68d391' : change === 'deleted' ? '#fc8181' : '#f6e05e'
|
|
@@ -1286,9 +1305,26 @@ function drawEdges(
|
|
|
1286
1305
|
ctx.save()
|
|
1287
1306
|
const edgeChange = currentVersionConnectorChanges.get(edge.id)
|
|
1288
1307
|
const versionPreviewActive = currentVersionElementChanges.size > 0 || currentVersionConnectorChanges.size > 0
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1308
|
+
const edgeContext = currentDiffLensActive && (
|
|
1309
|
+
currentDiffContextConnectorIds.has(edge.id) ||
|
|
1310
|
+
currentDiffContextElementIds.has(node.elementId) ||
|
|
1311
|
+
currentDiffContextElementIds.has(target.elementId) ||
|
|
1312
|
+
currentVersionElementChanges.has(node.elementId) ||
|
|
1313
|
+
currentVersionElementChanges.has(target.elementId)
|
|
1314
|
+
)
|
|
1315
|
+
ctx.globalAlpha = versionPreviewActive && !edgeChange
|
|
1316
|
+
? edgeContext
|
|
1317
|
+
? Math.max(alpha * 0.28, 0.12)
|
|
1318
|
+
: Math.max(alpha * 0.08, 0.04)
|
|
1319
|
+
: connectorAlpha(alpha)
|
|
1320
|
+
ctx.strokeStyle = edgeChange === 'added'
|
|
1321
|
+
? '#68d391'
|
|
1322
|
+
: edgeChange === 'deleted'
|
|
1323
|
+
? '#fc8181'
|
|
1324
|
+
: edgeChange
|
|
1325
|
+
? '#f6e05e'
|
|
1326
|
+
: accent
|
|
1327
|
+
ctx.lineWidth = (edgeChange ? CONNECTOR_LINE_PX * 1.35 : CONNECTOR_LINE_PX) / zoom
|
|
1292
1328
|
|
|
1293
1329
|
let midX = (sH.x + tH.x) / 2
|
|
1294
1330
|
let midY = (sH.y + tH.y) / 2
|
|
@@ -205,6 +205,7 @@ type IndexedEdge =
|
|
|
205
205
|
sourceLabel: string
|
|
206
206
|
targetLabel: string
|
|
207
207
|
label: string
|
|
208
|
+
id: number
|
|
208
209
|
diagramId: number
|
|
209
210
|
sourceObjId: number
|
|
210
211
|
targetObjId: number
|
|
@@ -410,6 +411,7 @@ function buildEdgeSpatialIndex(groups: DiagramGroupLayout[]): EdgeSpatialIndex {
|
|
|
410
411
|
sourceLabel: source.label,
|
|
411
412
|
targetLabel: target.label,
|
|
412
413
|
label: edge.label || 'Connection',
|
|
414
|
+
id: edge.id,
|
|
413
415
|
diagramId: group.diagramId,
|
|
414
416
|
sourceObjId: source.elementId,
|
|
415
417
|
targetObjId: target.elementId,
|
|
@@ -495,6 +497,7 @@ function findHoveredEdge(
|
|
|
495
497
|
sourceId: bestEdge.sourceLabel,
|
|
496
498
|
targetId: bestEdge.targetLabel,
|
|
497
499
|
label: bestEdge.label,
|
|
500
|
+
id: bestEdge.id,
|
|
498
501
|
diagramId: bestEdge.diagramId,
|
|
499
502
|
sourceObjId: bestEdge.sourceObjId,
|
|
500
503
|
targetObjId: bestEdge.targetObjId
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import type { WatchDiff, WatchRepository, WatchVersion, WorkspaceVersion } from '../api/client'
|
|
3
|
-
import { normalizeWatchChangeType } from '../utils/watchDiffSummary'
|
|
3
|
+
import { isWatchDiffChange, normalizeWatchChangeType } from '../utils/watchDiffSummary'
|
|
4
4
|
|
|
5
5
|
export type VersionChangeType = 'added' | 'updated' | 'deleted' | 'initialized'
|
|
6
6
|
|
|
@@ -62,16 +62,16 @@ export function buildWorkspaceVersionPreview(args: {
|
|
|
62
62
|
const change = normalizeWatchChangeType(diff.change_type)
|
|
63
63
|
summary[change] += 1
|
|
64
64
|
if (diff.resource_type === 'element' && diff.resource_id) {
|
|
65
|
-
elementChanges.set(diff.resource_id, change)
|
|
65
|
+
if (isWatchDiffChange(diff.change_type)) elementChanges.set(diff.resource_id, change)
|
|
66
66
|
const added = Math.max(0, diff.added_lines ?? 0)
|
|
67
67
|
const removed = Math.max(0, diff.removed_lines ?? 0)
|
|
68
|
-
if (added > 0 || removed > 0) {
|
|
68
|
+
if (isWatchDiffChange(diff.change_type) && (added > 0 || removed > 0)) {
|
|
69
69
|
elementLineDeltas.set(diff.resource_id, { added, removed })
|
|
70
70
|
}
|
|
71
71
|
summary.elements += 1
|
|
72
72
|
}
|
|
73
73
|
if (diff.resource_type === 'connector' && diff.resource_id) {
|
|
74
|
-
connectorChanges.set(diff.resource_id, change)
|
|
74
|
+
if (isWatchDiffChange(diff.change_type)) connectorChanges.set(diff.resource_id, change)
|
|
75
75
|
summary.connectors += 1
|
|
76
76
|
}
|
|
77
77
|
})
|