@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
|
@@ -8,6 +8,8 @@ export interface ElementContainerProps extends BoxProps {
|
|
|
8
8
|
isSource?: boolean
|
|
9
9
|
isTarget?: boolean
|
|
10
10
|
isConnectorHighlighted?: boolean
|
|
11
|
+
hasStack?: boolean
|
|
12
|
+
kind?: string | null
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export const ElementContainer = memo(forwardRef<ElementContainerProps, 'div'>(({
|
|
@@ -15,19 +17,24 @@ export const ElementContainer = memo(forwardRef<ElementContainerProps, 'div'>(({
|
|
|
15
17
|
isSource,
|
|
16
18
|
isTarget,
|
|
17
19
|
isConnectorHighlighted,
|
|
20
|
+
hasStack,
|
|
21
|
+
kind: _kind,
|
|
18
22
|
children,
|
|
19
23
|
...props
|
|
20
24
|
}, ref) => {
|
|
21
25
|
const { accent } = useAccentColor()
|
|
22
26
|
|
|
27
|
+
const brandedBorder = hexToRgba('#a0aec0', 0.5)
|
|
28
|
+
|
|
23
29
|
const borderColor = isSource
|
|
24
30
|
? accent
|
|
25
31
|
: isTarget
|
|
26
32
|
? 'teal.300'
|
|
27
33
|
: isSelected || isConnectorHighlighted
|
|
28
34
|
? accent
|
|
29
|
-
:
|
|
35
|
+
: brandedBorder
|
|
30
36
|
|
|
37
|
+
// Shadows matching ZUICanvas / high-fidelity look
|
|
31
38
|
const selectionShadow = `0 0 0 3px ${hexToRgba(accent, 0.35)}, 0 10px 36px rgba(0,0,0,0.55), 0 3px 10px rgba(0,0,0,0.4)`
|
|
32
39
|
const sourceShadow = `0 0 0 3px ${hexToRgba(accent, 0.55)}, 0 0 24px ${hexToRgba(accent, 0.25)}`
|
|
33
40
|
const edgeHighlightShadow = `0 0 0 2px ${hexToRgba(accent, 0.2)}, 0 8px 32px rgba(0,0,0,0.55), 0 2px 8px rgba(0,0,0,0.35)`
|
|
@@ -36,23 +43,54 @@ export const ElementContainer = memo(forwardRef<ElementContainerProps, 'div'>(({
|
|
|
36
43
|
|
|
37
44
|
const boxShadow = isSource ? sourceShadow : isSelected ? selectionShadow : isConnectorHighlighted ? edgeHighlightShadow : restingShadow
|
|
38
45
|
|
|
46
|
+
const finalBorderColor = borderColor
|
|
47
|
+
|
|
39
48
|
return (
|
|
40
|
-
<Box
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
<Box position="relative" zIndex={1}>
|
|
50
|
+
{hasStack && (
|
|
51
|
+
<>
|
|
52
|
+
{/* Stack effect matching ZUI renderer.ts (offset 4px and 8px) */}
|
|
53
|
+
<Box
|
|
54
|
+
position="absolute"
|
|
55
|
+
inset={0}
|
|
56
|
+
transform="translate(8px, 8px)"
|
|
57
|
+
bg="var(--bg-element)"
|
|
58
|
+
borderColor={finalBorderColor}
|
|
59
|
+
borderWidth="1px"
|
|
60
|
+
rounded="lg"
|
|
61
|
+
opacity={0.4}
|
|
62
|
+
zIndex={-2}
|
|
63
|
+
/>
|
|
64
|
+
<Box
|
|
65
|
+
position="absolute"
|
|
66
|
+
inset={0}
|
|
67
|
+
transform="translate(4px, 4px)"
|
|
68
|
+
bg="var(--bg-element)"
|
|
69
|
+
borderColor={finalBorderColor}
|
|
70
|
+
borderWidth="1px"
|
|
71
|
+
rounded="lg"
|
|
72
|
+
opacity={0.7}
|
|
73
|
+
zIndex={-1}
|
|
74
|
+
/>
|
|
75
|
+
</>
|
|
76
|
+
)}
|
|
77
|
+
<Box
|
|
78
|
+
ref={ref}
|
|
79
|
+
bg="var(--bg-element)"
|
|
80
|
+
borderColor={finalBorderColor}
|
|
81
|
+
borderWidth="1px"
|
|
82
|
+
rounded="lg"
|
|
83
|
+
boxShadow={boxShadow}
|
|
84
|
+
transition="all var(--chakra-transitions-duration-fast) var(--chakra-transitions-easing-pop)"
|
|
85
|
+
position="relative"
|
|
86
|
+
_hover={{
|
|
87
|
+
borderColor: isSource ? accent : isTarget ? 'teal.200' : accent,
|
|
88
|
+
boxShadow: hoverShadow,
|
|
89
|
+
}}
|
|
90
|
+
{...props}
|
|
91
|
+
>
|
|
92
|
+
{children}
|
|
93
|
+
</Box>
|
|
56
94
|
</Box>
|
|
57
95
|
)
|
|
58
96
|
}))
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import { Box, Button, HStack, Icon, Text, VStack, Divider, Flex } from '@chakra-ui/react'
|
|
1
|
+
import { Box, Button, HStack, Icon, Text, VStack, Divider, Flex, IconButton } from '@chakra-ui/react'
|
|
2
2
|
import { useNavigate } from 'react-router-dom'
|
|
3
3
|
import type { ProxyConnectorDetails } from '../crossBranch/types'
|
|
4
4
|
import SlidingPanel from './SlidingPanel'
|
|
5
5
|
import PanelHeader from './PanelHeader'
|
|
6
|
-
import { ChevronRightIcon, NavigationIcon } from './Icons'
|
|
6
|
+
import { ChevronRightIcon, NavigationIcon, TrashIcon, EditIcon } from './Icons'
|
|
7
|
+
import { useViewEditorContext } from '../pages/ViewEditor/context'
|
|
8
|
+
import type { Connector } from '../types'
|
|
7
9
|
|
|
8
10
|
interface Props {
|
|
9
11
|
isOpen: boolean
|
|
10
12
|
onClose: () => void
|
|
11
13
|
details: ProxyConnectorDetails | null
|
|
12
14
|
hasBackdrop?: boolean
|
|
15
|
+
onEdit?: (connector: Connector) => void
|
|
16
|
+
onDelete?: (connectorId: number, ownerViewId: number) => void
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export default function ProxyConnectorPanel({
|
|
@@ -17,8 +21,11 @@ export default function ProxyConnectorPanel({
|
|
|
17
21
|
onClose,
|
|
18
22
|
details,
|
|
19
23
|
hasBackdrop = true,
|
|
24
|
+
onEdit,
|
|
25
|
+
onDelete,
|
|
20
26
|
}: Props) {
|
|
21
27
|
const navigate = useNavigate()
|
|
28
|
+
const { canEdit } = useViewEditorContext()
|
|
22
29
|
|
|
23
30
|
return (
|
|
24
31
|
<SlidingPanel
|
|
@@ -27,6 +34,7 @@ export default function ProxyConnectorPanel({
|
|
|
27
34
|
panelKey="proxy-connector-panel"
|
|
28
35
|
width={{ base: 'calc(100vw - 24px)', md: '300px' }}
|
|
29
36
|
hasBackdrop={hasBackdrop}
|
|
37
|
+
zIndex={950}
|
|
30
38
|
>
|
|
31
39
|
<PanelHeader title="Relationships" onClose={onClose} />
|
|
32
40
|
|
|
@@ -71,21 +79,54 @@ export default function ProxyConnectorPanel({
|
|
|
71
79
|
>
|
|
72
80
|
<VStack align="stretch" spacing={3}>
|
|
73
81
|
<Box>
|
|
74
|
-
<HStack
|
|
75
|
-
<
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
<HStack justify="space-between" align="start">
|
|
83
|
+
<VStack align="start" spacing={1} flex={1}>
|
|
84
|
+
<HStack spacing={2}>
|
|
85
|
+
<Text color="white" fontSize="sm" fontWeight="semibold" isTruncated>
|
|
86
|
+
{leaf.source.actualElementName}
|
|
87
|
+
</Text>
|
|
88
|
+
<Icon as={ChevronRightIcon} color="whiteAlpha.400" />
|
|
89
|
+
<Text color="white" fontSize="sm" fontWeight="semibold" isTruncated>
|
|
90
|
+
{leaf.target.actualElementName}
|
|
91
|
+
</Text>
|
|
92
|
+
</HStack>
|
|
93
|
+
|
|
94
|
+
{(leaf.connector.label || leaf.connector.relationship) && (
|
|
95
|
+
<Text color="gray.400" fontSize="xs" fontStyle={!leaf.connector.label ? 'italic' : 'normal'}>
|
|
96
|
+
{leaf.connector.label || leaf.connector.relationship}
|
|
97
|
+
</Text>
|
|
98
|
+
)}
|
|
99
|
+
</VStack>
|
|
83
100
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
{canEdit && (
|
|
102
|
+
<HStack spacing={1} mt={-1}>
|
|
103
|
+
<IconButton
|
|
104
|
+
aria-label="Edit connector"
|
|
105
|
+
icon={<EditIcon size={14} />}
|
|
106
|
+
size="xs"
|
|
107
|
+
variant="ghost"
|
|
108
|
+
color="blue.300"
|
|
109
|
+
_hover={{ bg: 'blue.900', color: 'blue.100' }}
|
|
110
|
+
onClick={(e) => {
|
|
111
|
+
e.stopPropagation()
|
|
112
|
+
onEdit?.(leaf.connector)
|
|
113
|
+
}}
|
|
114
|
+
/>
|
|
115
|
+
<IconButton
|
|
116
|
+
aria-label="Delete connector"
|
|
117
|
+
icon={<TrashIcon size={14} />}
|
|
118
|
+
size="xs"
|
|
119
|
+
variant="ghost"
|
|
120
|
+
color="red.400"
|
|
121
|
+
_hover={{ bg: 'red.900', color: 'red.100' }}
|
|
122
|
+
onClick={(e) => {
|
|
123
|
+
e.stopPropagation()
|
|
124
|
+
onDelete?.(leaf.connector.id, leaf.ownerViewId)
|
|
125
|
+
}}
|
|
126
|
+
/>
|
|
127
|
+
</HStack>
|
|
128
|
+
)}
|
|
129
|
+
</HStack>
|
|
89
130
|
</Box>
|
|
90
131
|
|
|
91
132
|
{leaf.connector.description && (
|
|
@@ -128,3 +169,4 @@ export default function ProxyConnectorPanel({
|
|
|
128
169
|
</SlidingPanel>
|
|
129
170
|
)
|
|
130
171
|
}
|
|
172
|
+
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { memo } from 'react'
|
|
1
|
+
import { memo, useCallback } from 'react'
|
|
2
2
|
import { BaseEdge, EdgeLabelRenderer, Position, useStore, type EdgeProps } from 'reactflow'
|
|
3
3
|
import { measureEdgeLabel, useEdgeLabelLayout } from './ViewEditorEdgeLabelLayout'
|
|
4
|
+
import type { ProxyConnectorDetails } from '../crossBranch/types'
|
|
4
5
|
|
|
5
6
|
const CURVATURE = 0.5
|
|
6
7
|
|
|
@@ -35,6 +36,7 @@ function ViewBezierConnector({
|
|
|
35
36
|
}: EdgeProps) {
|
|
36
37
|
const sourceNode = useStore((s) => s.nodeInternals.get(source))
|
|
37
38
|
const targetNode = useStore((s) => s.nodeInternals.get(target))
|
|
39
|
+
const edge = useStore((s) => s.edges.find((candidate) => candidate.id === id))
|
|
38
40
|
|
|
39
41
|
const finalSourceX = sourceX
|
|
40
42
|
const finalSourceY = sourceY
|
|
@@ -74,6 +76,37 @@ function ViewBezierConnector({
|
|
|
74
76
|
const text = (!selected && fullText.length > 30) ? `${fullText.slice(0, 30)}...` : fullText
|
|
75
77
|
const textWidth = text ? measureEdgeLabel(text, `${fontWeight} ${fontSize}px Inter, system-ui, sans-serif`) : 0
|
|
76
78
|
const padding = Array.isArray(labelBgPadding) ? labelBgPadding : [2, 4]
|
|
79
|
+
const proxyBadgeCount = typeof (edge?.data as { proxyBadgeCount?: number } | undefined)?.proxyBadgeCount === 'number'
|
|
80
|
+
? (edge?.data as { proxyBadgeCount: number }).proxyBadgeCount
|
|
81
|
+
: 0
|
|
82
|
+
const proxyBadgeDetails = ((edge?.data as { proxyBadgeDetails?: ProxyConnectorDetails | null } | undefined)?.proxyBadgeDetails) ?? null
|
|
83
|
+
const proxyBadgeText = proxyBadgeCount > 0 ? `+${proxyBadgeCount}` : ''
|
|
84
|
+
const versionChangeType = (edge?.data as { versionChangeType?: string } | undefined)?.versionChangeType
|
|
85
|
+
const versionBadgeText = versionChangeType === 'added'
|
|
86
|
+
? '+ connector'
|
|
87
|
+
: versionChangeType === 'deleted'
|
|
88
|
+
? '- connector'
|
|
89
|
+
: versionChangeType
|
|
90
|
+
? '~ connector'
|
|
91
|
+
: ''
|
|
92
|
+
const badgeFontSize = 11
|
|
93
|
+
const badgeHorizontalPadding = 7
|
|
94
|
+
const badgeSize = 24
|
|
95
|
+
const labelWidth = textWidth + padding[1] * 2
|
|
96
|
+
const versionBadgeWidth = versionBadgeText
|
|
97
|
+
? measureEdgeLabel(versionBadgeText, `700 ${badgeFontSize}px Inter, system-ui, sans-serif`) + badgeHorizontalPadding * 2
|
|
98
|
+
: 0
|
|
99
|
+
const badgeWidth = proxyBadgeText
|
|
100
|
+
? Math.max(badgeSize, measureEdgeLabel(proxyBadgeText, `600 ${badgeFontSize}px Inter, system-ui, sans-serif`) + badgeHorizontalPadding * 2)
|
|
101
|
+
: 0
|
|
102
|
+
const labelHeight = text ? fontSize + padding[0] * 2 : 0
|
|
103
|
+
const badgeGap = (text && (proxyBadgeText || versionBadgeText)) || (proxyBadgeText && versionBadgeText) ? 8 : 0
|
|
104
|
+
const stackWidth = Math.max(labelWidth, badgeWidth, versionBadgeWidth)
|
|
105
|
+
const stackHeight = labelHeight +
|
|
106
|
+
(text && (proxyBadgeText || versionBadgeText) ? badgeGap : 0) +
|
|
107
|
+
(versionBadgeText ? badgeSize : 0) +
|
|
108
|
+
(versionBadgeText && proxyBadgeText ? badgeGap : 0) +
|
|
109
|
+
(proxyBadgeText ? badgeSize : 0)
|
|
77
110
|
|
|
78
111
|
// Cubic bezier midpoint at t=0.5
|
|
79
112
|
const labelX = 0.125 * finalSourceX + 0.375 * cp1x + 0.375 * cp2x + 0.125 * finalTargetX
|
|
@@ -82,16 +115,23 @@ function ViewBezierConnector({
|
|
|
82
115
|
const labelLayout = useEdgeLabelLayout({
|
|
83
116
|
id,
|
|
84
117
|
preferredX: labelX,
|
|
85
|
-
preferredY: labelY,
|
|
86
|
-
width:
|
|
87
|
-
height: fontSize + padding[0] * 2,
|
|
118
|
+
preferredY: labelY + (stackHeight > 0 ? (stackHeight - labelHeight) / 2 : 0),
|
|
119
|
+
width: stackWidth,
|
|
120
|
+
height: stackHeight || (fontSize + padding[0] * 2),
|
|
88
121
|
dx: finalTargetX - finalSourceX,
|
|
89
122
|
dy: finalTargetY - finalSourceY,
|
|
90
123
|
})
|
|
91
124
|
|
|
92
|
-
const
|
|
93
|
-
const labelPath = text ? ` M ${labelLayout.x - labelWidth / 2},${
|
|
125
|
+
const labelCenterY = labelLayout.y - ((proxyBadgeText || versionBadgeText) ? (stackHeight - labelHeight) / 2 : 0)
|
|
126
|
+
const labelPath = text ? ` M ${labelLayout.x - labelWidth / 2},${labelCenterY} L ${labelLayout.x + labelWidth / 2},${labelCenterY}` : ''
|
|
94
127
|
const combinedInteractionPath = `${interactionPath}${labelPath}`
|
|
128
|
+
const handleBadgeClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
|
|
129
|
+
event.preventDefault()
|
|
130
|
+
event.stopPropagation()
|
|
131
|
+
if (!proxyBadgeDetails) return
|
|
132
|
+
const onOpenProxyBadge = (edge?.data as { onOpenProxyBadge?: (details: ProxyConnectorDetails) => void } | undefined)?.onOpenProxyBadge
|
|
133
|
+
onOpenProxyBadge?.(proxyBadgeDetails)
|
|
134
|
+
}, [edge?.data, proxyBadgeDetails])
|
|
95
135
|
|
|
96
136
|
return (
|
|
97
137
|
<>
|
|
@@ -108,7 +148,7 @@ function ViewBezierConnector({
|
|
|
108
148
|
interactionWidth={20}
|
|
109
149
|
style={{ stroke: 'transparent' }}
|
|
110
150
|
/>
|
|
111
|
-
{text && (
|
|
151
|
+
{(text || proxyBadgeText || versionBadgeText) && (
|
|
112
152
|
<EdgeLabelRenderer>
|
|
113
153
|
<div
|
|
114
154
|
style={{
|
|
@@ -117,22 +157,77 @@ function ViewBezierConnector({
|
|
|
117
157
|
pointerEvents: 'none',
|
|
118
158
|
opacity: Number(labelStyle?.opacity ?? 1),
|
|
119
159
|
zIndex: 2,
|
|
160
|
+
display: 'flex',
|
|
161
|
+
flexDirection: 'column',
|
|
162
|
+
alignItems: 'center',
|
|
163
|
+
gap: badgeGap,
|
|
120
164
|
}}
|
|
121
165
|
>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
{text && (
|
|
167
|
+
<div
|
|
168
|
+
style={{
|
|
169
|
+
padding: `${padding[0]}px ${padding[1]}px`,
|
|
170
|
+
borderRadius: Array.isArray(labelBgBorderRadius) ? labelBgBorderRadius[0] : Number(labelBgBorderRadius ?? 4),
|
|
171
|
+
background: String(labelBgStyle?.fill ?? 'var(--chakra-colors-gray-900)'),
|
|
172
|
+
color: String(labelStyle?.fill ?? 'var(--accent)'),
|
|
173
|
+
fontSize,
|
|
174
|
+
fontWeight,
|
|
175
|
+
lineHeight: 1,
|
|
176
|
+
whiteSpace: 'nowrap',
|
|
177
|
+
}}
|
|
178
|
+
>
|
|
179
|
+
{text}
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
{proxyBadgeText && (
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
onClick={handleBadgeClick}
|
|
186
|
+
style={{
|
|
187
|
+
minWidth: badgeWidth,
|
|
188
|
+
height: badgeSize,
|
|
189
|
+
padding: `0 ${badgeHorizontalPadding}px`,
|
|
190
|
+
borderRadius: 999,
|
|
191
|
+
background: 'var(--bg-element)',
|
|
192
|
+
border: '1px dashed rgba(var(--accent-rgb), 0.8)',
|
|
193
|
+
color: 'white',
|
|
194
|
+
display: 'flex',
|
|
195
|
+
alignItems: 'center',
|
|
196
|
+
justifyContent: 'center',
|
|
197
|
+
fontSize: badgeFontSize,
|
|
198
|
+
fontWeight: 600,
|
|
199
|
+
lineHeight: 1,
|
|
200
|
+
boxShadow: selected ? '0 0 0 1px rgba(255,255,255,0.2)' : 'none',
|
|
201
|
+
cursor: proxyBadgeDetails ? 'pointer' : 'default',
|
|
202
|
+
pointerEvents: 'auto',
|
|
203
|
+
appearance: 'none',
|
|
204
|
+
}}
|
|
205
|
+
>
|
|
206
|
+
{proxyBadgeText}
|
|
207
|
+
</button>
|
|
208
|
+
)}
|
|
209
|
+
{versionBadgeText && (
|
|
210
|
+
<div
|
|
211
|
+
style={{
|
|
212
|
+
minWidth: versionBadgeWidth,
|
|
213
|
+
height: badgeSize,
|
|
214
|
+
padding: `0 ${badgeHorizontalPadding}px`,
|
|
215
|
+
borderRadius: 999,
|
|
216
|
+
background: 'rgba(17, 24, 39, 0.9)',
|
|
217
|
+
border: `1px solid ${versionChangeType === 'added' ? '#68d391' : versionChangeType === 'deleted' ? '#fc8181' : '#f6e05e'}`,
|
|
218
|
+
color: versionChangeType === 'added' ? '#68d391' : versionChangeType === 'deleted' ? '#fc8181' : '#f6e05e',
|
|
219
|
+
display: 'flex',
|
|
220
|
+
alignItems: 'center',
|
|
221
|
+
justifyContent: 'center',
|
|
222
|
+
fontSize: badgeFontSize,
|
|
223
|
+
fontWeight: 700,
|
|
224
|
+
lineHeight: 1,
|
|
225
|
+
boxShadow: '0 6px 18px rgba(0,0,0,0.28)',
|
|
226
|
+
}}
|
|
227
|
+
>
|
|
228
|
+
{versionBadgeText}
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
136
231
|
</div>
|
|
137
232
|
</EdgeLabelRenderer>
|
|
138
233
|
)}
|
|
@@ -43,7 +43,7 @@ interface Props {
|
|
|
43
43
|
tagColors: Record<string, Tag>
|
|
44
44
|
selectedElement?: LibraryElement | null
|
|
45
45
|
onUpdateTags?: (elementId: number, tags: string[]) => Promise<void>
|
|
46
|
-
onCreateTag: (tag: string, color?: string) => Promise<void>
|
|
46
|
+
onCreateTag: (tag: string, color?: string, description?: string) => Promise<void>
|
|
47
47
|
layers: ViewLayer[]
|
|
48
48
|
onHoverLayer: (tags: string[] | null, color?: string | null) => void
|
|
49
49
|
onCreateLayer: (name: string, tags: string[], color: string) => Promise<void>
|
|
@@ -20,6 +20,11 @@ interface ViewFloatingMenuProps {
|
|
|
20
20
|
onImport: () => void
|
|
21
21
|
onExport: () => void
|
|
22
22
|
onShare: () => void
|
|
23
|
+
canUndo?: boolean
|
|
24
|
+
canRedo?: boolean
|
|
25
|
+
undoRedoDisabled?: boolean
|
|
26
|
+
onUndo?: () => void
|
|
27
|
+
onRedo?: () => void
|
|
23
28
|
isFreePlan: boolean
|
|
24
29
|
canUpgrade?: boolean
|
|
25
30
|
activeTags?: string[]
|
|
@@ -2,7 +2,7 @@ import React, { memo } from 'react'
|
|
|
2
2
|
import type { ViewFloatingMenuSlots } from '../slots'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
HStack, Tooltip, Button, Box, Text, Popover, PopoverTrigger, Portal, PopoverContent, PopoverBody, IconButton, useDisclosure
|
|
5
|
+
HStack, Tooltip, Button, Box, Text, Popover, PopoverTrigger, Portal, PopoverContent, PopoverBody, IconButton, Slider, SliderTrack, SliderFilledTrack, SliderThumb, useDisclosure
|
|
6
6
|
} from '@chakra-ui/react'
|
|
7
7
|
import { DownloadIcon } from '@chakra-ui/icons'
|
|
8
8
|
import {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
TagsIcon,
|
|
18
18
|
} from './Icons'
|
|
19
19
|
import { KbdHint } from './PanelUI'
|
|
20
|
+
import { RedoSvg, UndoSvg } from './ViewDrawMenu'
|
|
20
21
|
import { useViewEditorContext } from '../pages/ViewEditor/context'
|
|
21
22
|
import type { Tag, ViewLayer } from '../types'
|
|
22
23
|
|
|
@@ -35,6 +36,13 @@ export interface ViewFloatingMenuProps extends ViewFloatingMenuSlots {
|
|
|
35
36
|
onShare?: () => void
|
|
36
37
|
focusMode: boolean
|
|
37
38
|
onFocusModeChange: (enabled: boolean) => void
|
|
39
|
+
densityLevel?: number
|
|
40
|
+
onDensityLevelChange?: (level: number) => void
|
|
41
|
+
canUndo?: boolean
|
|
42
|
+
canRedo?: boolean
|
|
43
|
+
undoRedoDisabled?: boolean
|
|
44
|
+
onUndo?: () => void
|
|
45
|
+
onRedo?: () => void
|
|
38
46
|
|
|
39
47
|
// Tag-related props
|
|
40
48
|
allTags: string[]
|
|
@@ -73,6 +81,13 @@ function ViewFloatingMenu({
|
|
|
73
81
|
onExport,
|
|
74
82
|
focusMode,
|
|
75
83
|
onFocusModeChange,
|
|
84
|
+
densityLevel = 0,
|
|
85
|
+
onDensityLevelChange,
|
|
86
|
+
canUndo = false,
|
|
87
|
+
canRedo = false,
|
|
88
|
+
undoRedoDisabled = false,
|
|
89
|
+
onUndo,
|
|
90
|
+
onRedo,
|
|
76
91
|
allTags,
|
|
77
92
|
layers,
|
|
78
93
|
tagColors,
|
|
@@ -90,6 +105,11 @@ function ViewFloatingMenu({
|
|
|
90
105
|
}: ViewFloatingMenuProps) {
|
|
91
106
|
const { canEdit } = useViewEditorContext()
|
|
92
107
|
const { isOpen: isTagsOpen, onClose: onTagsClose, onToggle: onTagsToggle } = useDisclosure()
|
|
108
|
+
const [draftDensityLevel, setDraftDensityLevel] = React.useState(densityLevel)
|
|
109
|
+
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
setDraftDensityLevel(densityLevel)
|
|
112
|
+
}, [densityLevel])
|
|
93
113
|
|
|
94
114
|
return (
|
|
95
115
|
<HStack
|
|
@@ -131,6 +151,46 @@ function ViewFloatingMenu({
|
|
|
131
151
|
</Button>
|
|
132
152
|
</Tooltip>
|
|
133
153
|
|
|
154
|
+
{(canUndo || canRedo) && (
|
|
155
|
+
<>
|
|
156
|
+
<Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
|
|
157
|
+
{canUndo && (
|
|
158
|
+
<Tooltip label="Undo" placement="top" openDelay={200}>
|
|
159
|
+
<IconButton
|
|
160
|
+
aria-label="Undo"
|
|
161
|
+
icon={<UndoSvg />}
|
|
162
|
+
variant="ghost"
|
|
163
|
+
h="28px"
|
|
164
|
+
minW="28px"
|
|
165
|
+
px={0}
|
|
166
|
+
color="gray.300"
|
|
167
|
+
isDisabled={undoRedoDisabled}
|
|
168
|
+
_disabled={{ opacity: 0.35, cursor: 'not-allowed' }}
|
|
169
|
+
_hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
|
|
170
|
+
onClick={onUndo}
|
|
171
|
+
/>
|
|
172
|
+
</Tooltip>
|
|
173
|
+
)}
|
|
174
|
+
{canRedo && (
|
|
175
|
+
<Tooltip label="Redo" placement="top" openDelay={200}>
|
|
176
|
+
<IconButton
|
|
177
|
+
aria-label="Redo"
|
|
178
|
+
icon={<RedoSvg />}
|
|
179
|
+
variant="ghost"
|
|
180
|
+
h="28px"
|
|
181
|
+
minW="28px"
|
|
182
|
+
px={0}
|
|
183
|
+
color="gray.300"
|
|
184
|
+
isDisabled={undoRedoDisabled}
|
|
185
|
+
_disabled={{ opacity: 0.35, cursor: 'not-allowed' }}
|
|
186
|
+
_hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
|
|
187
|
+
onClick={onRedo}
|
|
188
|
+
/>
|
|
189
|
+
</Tooltip>
|
|
190
|
+
)}
|
|
191
|
+
</>
|
|
192
|
+
)}
|
|
193
|
+
|
|
134
194
|
{!hideFocusView && (
|
|
135
195
|
<>
|
|
136
196
|
<Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
|
|
@@ -265,6 +325,55 @@ function ViewFloatingMenu({
|
|
|
265
325
|
</>
|
|
266
326
|
)}
|
|
267
327
|
|
|
328
|
+
{onDensityLevelChange && (
|
|
329
|
+
<>
|
|
330
|
+
<Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
|
|
331
|
+
<Tooltip label={`Density ${draftDensityLevel}`} placement="top" openDelay={200}>
|
|
332
|
+
<Box
|
|
333
|
+
w="92px"
|
|
334
|
+
h="28px"
|
|
335
|
+
px={2.5}
|
|
336
|
+
display="flex"
|
|
337
|
+
alignItems="center"
|
|
338
|
+
bg="whiteAlpha.50"
|
|
339
|
+
rounded="md"
|
|
340
|
+
>
|
|
341
|
+
<Slider
|
|
342
|
+
aria-label="Density"
|
|
343
|
+
min={-2}
|
|
344
|
+
max={2}
|
|
345
|
+
step={1}
|
|
346
|
+
value={draftDensityLevel}
|
|
347
|
+
onChange={setDraftDensityLevel}
|
|
348
|
+
onChangeEnd={(value) => {
|
|
349
|
+
setDraftDensityLevel(value)
|
|
350
|
+
onDensityLevelChange(value)
|
|
351
|
+
}}
|
|
352
|
+
focusThumbOnChange={false}
|
|
353
|
+
>
|
|
354
|
+
<SliderTrack h="3px" bg="whiteAlpha.200">
|
|
355
|
+
<SliderFilledTrack bg="var(--accent)" />
|
|
356
|
+
</SliderTrack>
|
|
357
|
+
{[-2, -1, 0, 1, 2].map((value) => (
|
|
358
|
+
<Box
|
|
359
|
+
key={value}
|
|
360
|
+
position="absolute"
|
|
361
|
+
left={`${((value + 2) / 4) * 100}%`}
|
|
362
|
+
top="50%"
|
|
363
|
+
transform="translate(-50%, -50%)"
|
|
364
|
+
w="1px"
|
|
365
|
+
h="9px"
|
|
366
|
+
bg={draftDensityLevel >= value ? 'var(--accent)' : 'whiteAlpha.400'}
|
|
367
|
+
pointerEvents="none"
|
|
368
|
+
/>
|
|
369
|
+
))}
|
|
370
|
+
<SliderThumb boxSize="12px" bg="white" border="2px solid" borderColor="var(--accent)" />
|
|
371
|
+
</Slider>
|
|
372
|
+
</Box>
|
|
373
|
+
</Tooltip>
|
|
374
|
+
</>
|
|
375
|
+
)}
|
|
376
|
+
|
|
268
377
|
{/* Draw mode toggle */}
|
|
269
378
|
<Tooltip
|
|
270
379
|
label={drawingMode ? 'Exit drawing mode' : 'Draw on diagram'}
|