@tldiagram/core-ui 2.0.4 → 2.0.6
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/index.js +5878 -5796
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +1 -0
- package/dist/utils/string.d.ts +4 -0
- package/dist/utils/string.test.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/ElementPanel.tsx +86 -54
- package/src/components/ProxyConnectorPanel.tsx +3 -2
- package/src/components/SlidingPanel.tsx +0 -2
- package/src/pages/AppearanceSettings.tsx +47 -31
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +38 -0
- package/src/pages/ViewEditor/hooks/useViewData.ts +12 -2
- package/src/pages/ViewEditor/index.tsx +18 -13
- package/src/pages/ViewsGrid.tsx +2 -2
- package/src/utils/string.test.ts +19 -0
- package/src/utils/string.ts +7 -0
|
@@ -173,6 +173,7 @@ export declare function useCanvasInteractions({ viewId, canEdit, drawingMode: _d
|
|
|
173
173
|
clientX: number;
|
|
174
174
|
clientY: number;
|
|
175
175
|
}) => void;
|
|
176
|
+
stableOnReconnectPick: (targetElementId: number) => Promise<boolean>;
|
|
176
177
|
showAddingElementAt: (clientX: number, clientY: number, expandResults?: boolean, mode?: "add" | "connect", forceConnect?: boolean) => void;
|
|
177
178
|
onNodesChange: (changes: NodeChange[]) => void;
|
|
178
179
|
onEdgesChange: (changes: EdgeChange[]) => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
Box,
|
|
7
7
|
Button,
|
|
8
8
|
CloseButton,
|
|
9
|
-
Divider,
|
|
10
9
|
FormControl,
|
|
11
10
|
FormLabel,
|
|
12
11
|
HStack,
|
|
@@ -1034,49 +1033,87 @@ function ElementPanel({ isOpen, onClose, element, onSave, autoSave = false, onDe
|
|
|
1034
1033
|
|
|
1035
1034
|
{elementPanelAfterContentSlot}
|
|
1036
1035
|
|
|
1037
|
-
{element && (onPromoteVisibility || onDemoteVisibility || onResetVisibility) && (
|
|
1038
|
-
<Box borderTop="1px solid" borderColor="whiteAlpha.100" pt={
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
{visibilityOverrideDelta
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1036
|
+
{(element && (onPromoteVisibility || onDemoteVisibility || onResetVisibility)) || (isEdit && canEdit && onMerge) ? (
|
|
1037
|
+
<Box borderTop="1px solid" borderColor="whiteAlpha.100" pt={4} pb={1}>
|
|
1038
|
+
{element && (onPromoteVisibility || onDemoteVisibility || onResetVisibility) && (
|
|
1039
|
+
<>
|
|
1040
|
+
<HStack justify="space-between" mb={2}>
|
|
1041
|
+
<FormLabel fontSize="xs" fontWeight="semibold" letterSpacing="wider" color="gray.500" mb={0} textTransform="uppercase">Density</FormLabel>
|
|
1042
|
+
{visibilityOverrideDelta !== 0 && (
|
|
1043
|
+
<Badge colorScheme={visibilityOverrideDelta > 0 ? 'teal' : 'orange'} variant="subtle" fontSize="xs">
|
|
1044
|
+
{visibilityOverrideDelta > 0 ? `+${visibilityOverrideDelta}` : visibilityOverrideDelta}
|
|
1045
|
+
</Badge>
|
|
1046
|
+
)}
|
|
1047
|
+
</HStack>
|
|
1048
|
+
<HStack spacing={2} mb={isEdit && canEdit && onMerge ? 2 : 0}>
|
|
1049
|
+
<Button
|
|
1050
|
+
variant="outline"
|
|
1051
|
+
size="sm"
|
|
1052
|
+
borderColor="teal.700"
|
|
1053
|
+
color="teal.300"
|
|
1054
|
+
_hover={{ bg: 'teal.900', borderColor: 'teal.500', color: 'teal.100' }}
|
|
1055
|
+
onClick={() => onPromoteVisibility?.(element.id)}
|
|
1056
|
+
flex={1}
|
|
1057
|
+
isDisabled={isReadOnly}
|
|
1058
|
+
>
|
|
1059
|
+
Promote
|
|
1060
|
+
</Button>
|
|
1061
|
+
<Button
|
|
1062
|
+
variant="outline"
|
|
1063
|
+
size="sm"
|
|
1064
|
+
borderColor="orange.700"
|
|
1065
|
+
color="orange.300"
|
|
1066
|
+
_hover={{ bg: 'orange.900', borderColor: 'orange.500', color: 'orange.100' }}
|
|
1067
|
+
onClick={() => onDemoteVisibility?.(element.id)}
|
|
1068
|
+
flex={1}
|
|
1069
|
+
isDisabled={isReadOnly}
|
|
1070
|
+
>
|
|
1071
|
+
Demote
|
|
1072
|
+
</Button>
|
|
1073
|
+
{visibilityOverrideDelta !== 0 && (
|
|
1074
|
+
<Button variant="ghost" size="sm" color="gray.400" _hover={{ bg: 'whiteAlpha.100', color: 'white' }} onClick={() => onResetVisibility?.(element.id)} isDisabled={isReadOnly}>
|
|
1075
|
+
Reset
|
|
1076
|
+
</Button>
|
|
1077
|
+
)}
|
|
1078
|
+
</HStack>
|
|
1079
|
+
</>
|
|
1080
|
+
)}
|
|
1081
|
+
{isEdit && canEdit && onMerge && (
|
|
1082
|
+
<Button
|
|
1083
|
+
variant="outline"
|
|
1084
|
+
size="sm"
|
|
1085
|
+
borderColor="teal.700"
|
|
1086
|
+
color="teal.300"
|
|
1087
|
+
_hover={{ bg: 'teal.900', borderColor: 'teal.500', color: 'teal.100' }}
|
|
1088
|
+
onClick={() => onMerge(element.id)}
|
|
1089
|
+
w="full"
|
|
1090
|
+
>
|
|
1091
|
+
Merge
|
|
1053
1092
|
</Button>
|
|
1054
|
-
|
|
1055
|
-
<Button variant="ghost" size="sm" onClick={() => onResetVisibility?.(element.id)} isDisabled={isReadOnly}>
|
|
1056
|
-
Reset
|
|
1057
|
-
</Button>
|
|
1058
|
-
)}
|
|
1059
|
-
</HStack>
|
|
1060
|
-
</Box>
|
|
1061
|
-
)}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
{isEdit && canEdit && onMerge && (
|
|
1066
|
-
<Box borderTop="1px solid" borderColor="whiteAlpha.100" pt={2}>
|
|
1067
|
-
<Button variant="subtle" size="sm" color="teal.200" _hover={{ bg: 'teal.900', color: 'teal.100' }}
|
|
1068
|
-
onClick={() => onMerge(element.id)} w="full">
|
|
1069
|
-
Merge
|
|
1070
|
-
</Button>
|
|
1093
|
+
)}
|
|
1071
1094
|
</Box>
|
|
1072
|
-
)}
|
|
1095
|
+
) : null}
|
|
1073
1096
|
|
|
1074
1097
|
{isEdit && canEdit && (
|
|
1075
|
-
<HStack borderTop="1px solid" borderColor="whiteAlpha.100" pt={
|
|
1076
|
-
<Button
|
|
1098
|
+
<HStack borderTop="1px solid" borderColor="whiteAlpha.100" pt={4} pb={1} spacing={2}>
|
|
1099
|
+
<Button
|
|
1100
|
+
variant="ghost"
|
|
1101
|
+
size="sm"
|
|
1102
|
+
color="gray.400"
|
|
1103
|
+
_hover={{ bg: 'whiteAlpha.100', color: 'white' }}
|
|
1104
|
+
onClick={handleDelete}
|
|
1105
|
+
flex={1}
|
|
1106
|
+
>
|
|
1077
1107
|
Remove
|
|
1078
1108
|
</Button>
|
|
1079
|
-
<Button
|
|
1109
|
+
<Button
|
|
1110
|
+
variant="ghost"
|
|
1111
|
+
size="sm"
|
|
1112
|
+
color="red.400"
|
|
1113
|
+
_hover={{ bg: 'red.900', color: 'red.200' }}
|
|
1114
|
+
onClick={confirmPermanentDelete.onOpen}
|
|
1115
|
+
flex={1}
|
|
1116
|
+
>
|
|
1080
1117
|
Delete Element
|
|
1081
1118
|
</Button>
|
|
1082
1119
|
</HStack>
|
|
@@ -1084,24 +1121,19 @@ function ElementPanel({ isOpen, onClose, element, onSave, autoSave = false, onDe
|
|
|
1084
1121
|
</VStack>
|
|
1085
1122
|
</ScrollIndicatorWrapper>
|
|
1086
1123
|
|
|
1087
|
-
<Divider borderColor="whiteAlpha.100" />
|
|
1088
|
-
|
|
1089
1124
|
{/* Footer */}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1125
|
+
{!autoSaveEdit && (
|
|
1126
|
+
<HStack px={4} py={3} justify="flex-end" flexShrink={0}>
|
|
1127
|
+
<Button variant="ghost" size="sm" onClick={handleClose}>
|
|
1128
|
+
Cancel
|
|
1129
|
+
</Button>
|
|
1130
|
+
{canEdit && (
|
|
1131
|
+
<Button size="sm" px={5} colorScheme="blue" onClick={handleSave} isLoading={loading}>
|
|
1132
|
+
Save
|
|
1096
1133
|
</Button>
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
</Button>
|
|
1101
|
-
)}
|
|
1102
|
-
</HStack>
|
|
1103
|
-
)}
|
|
1104
|
-
</HStack>
|
|
1134
|
+
)}
|
|
1135
|
+
</HStack>
|
|
1136
|
+
)}
|
|
1105
1137
|
</SlidingPanel>
|
|
1106
1138
|
|
|
1107
1139
|
<ConfirmDialog
|
|
@@ -6,6 +6,7 @@ import PanelHeader from './PanelHeader'
|
|
|
6
6
|
import { ChevronRightIcon, NavigationIcon, TrashIcon, EditIcon } from './Icons'
|
|
7
7
|
import { useViewEditorContext } from '../pages/ViewEditor/context'
|
|
8
8
|
import type { Connector } from '../types'
|
|
9
|
+
import { truncate } from '../utils/string'
|
|
9
10
|
|
|
10
11
|
interface Props {
|
|
11
12
|
isOpen: boolean
|
|
@@ -83,11 +84,11 @@ export default function ProxyConnectorPanel({
|
|
|
83
84
|
<VStack align="start" spacing={1} flex={1}>
|
|
84
85
|
<HStack spacing={2}>
|
|
85
86
|
<Text color="white" fontSize="sm" fontWeight="semibold" isTruncated>
|
|
86
|
-
{leaf.source.actualElementName}
|
|
87
|
+
{truncate(leaf.source.actualElementName)}
|
|
87
88
|
</Text>
|
|
88
89
|
<Icon as={ChevronRightIcon} color="whiteAlpha.400" />
|
|
89
90
|
<Text color="white" fontSize="sm" fontWeight="semibold" isTruncated>
|
|
90
|
-
{leaf.target.actualElementName}
|
|
91
|
+
{truncate(leaf.target.actualElementName)}
|
|
91
92
|
</Text>
|
|
92
93
|
</HStack>
|
|
93
94
|
|
|
@@ -1,46 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
Button,
|
|
4
|
+
FormLabel,
|
|
5
|
+
HStack,
|
|
6
|
+
Menu,
|
|
7
|
+
MenuButton,
|
|
8
|
+
MenuItem,
|
|
9
|
+
MenuList,
|
|
10
|
+
Text,
|
|
11
|
+
Tooltip,
|
|
12
|
+
VStack,
|
|
13
|
+
Wrap,
|
|
14
|
+
WrapItem,
|
|
15
|
+
} from '@chakra-ui/react'
|
|
2
16
|
import { ACCENT_OPTIONS, BACKGROUND_OPTIONS, ELEMENT_OPTIONS } from '../constants/colors'
|
|
3
17
|
import { useTheme } from '../context/ThemeContext'
|
|
4
18
|
import { useSourceEditor } from '../utils/sourceEditor'
|
|
5
|
-
import
|
|
19
|
+
import { ChevronDownIcon } from '../components/Icons'
|
|
6
20
|
|
|
7
21
|
export default function AppearanceSettings({ compact = false }: { compact?: boolean }) {
|
|
8
22
|
const { accent, setAccent, background, setBackground, elementColor, setElementColor } = useTheme()
|
|
9
23
|
const { editor, setEditor } = useSourceEditor()
|
|
10
|
-
const swatchSize = compact ? '
|
|
24
|
+
const swatchSize = compact ? '21px' : '32px'
|
|
11
25
|
const sectionGap = compact ? 5 : 8
|
|
12
26
|
|
|
13
27
|
return (
|
|
14
28
|
<VStack align="start" spacing={sectionGap} maxW={compact ? '320px' : '480px'} w="full">
|
|
15
|
-
<Box w="full">
|
|
16
|
-
<HStack justify="space-between" align="end" w="full" mb={compact ? 0 : 1}>
|
|
17
|
-
<Box>
|
|
18
|
-
<Text fontFamily="heading" fontSize={compact ? 'md' : 'lg'} fontWeight="bold" color="gray.100" mb={1}>
|
|
19
|
-
Theme
|
|
20
|
-
</Text>
|
|
21
|
-
</Box>
|
|
22
|
-
</HStack>
|
|
23
|
-
</Box>
|
|
24
29
|
|
|
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
30
|
|
|
45
31
|
<Box w="full">
|
|
46
32
|
<FormLabel mb={3} fontSize={compact ? 'xs' : 'sm'} textTransform="uppercase" letterSpacing="0.12em" color="gray.400">
|
|
@@ -153,6 +139,36 @@ export default function AppearanceSettings({ compact = false }: { compact?: bool
|
|
|
153
139
|
})}
|
|
154
140
|
</Wrap>
|
|
155
141
|
</Box>
|
|
142
|
+
|
|
143
|
+
<Box w="full">
|
|
144
|
+
<FormLabel mb={3} fontSize={compact ? 'xs' : 'sm'} textTransform="uppercase" letterSpacing="0.12em" color="gray.400">
|
|
145
|
+
Editor
|
|
146
|
+
</FormLabel>
|
|
147
|
+
<Menu>
|
|
148
|
+
<MenuButton
|
|
149
|
+
as={Button}
|
|
150
|
+
size="sm"
|
|
151
|
+
variant="clay"
|
|
152
|
+
rightIcon={<ChevronDownIcon size={12} strokeWidth={4} />}
|
|
153
|
+
minW="140px"
|
|
154
|
+
textAlign="left"
|
|
155
|
+
bg="whiteAlpha.100"
|
|
156
|
+
color="gray.100"
|
|
157
|
+
_hover={{ bg: 'whiteAlpha.200' }}
|
|
158
|
+
_active={{ bg: 'whiteAlpha.300' }}
|
|
159
|
+
>
|
|
160
|
+
{editor === 'zed' ? 'Zed' : 'VS Code'}
|
|
161
|
+
</MenuButton>
|
|
162
|
+
<MenuList>
|
|
163
|
+
<MenuItem onClick={() => setEditor('zed')} fontWeight={editor === 'zed' ? 'bold' : 'normal'}>
|
|
164
|
+
Zed
|
|
165
|
+
</MenuItem>
|
|
166
|
+
<MenuItem onClick={() => setEditor('vscode')} fontWeight={editor === 'vscode' ? 'bold' : 'normal'}>
|
|
167
|
+
VS Code
|
|
168
|
+
</MenuItem>
|
|
169
|
+
</MenuList>
|
|
170
|
+
</Menu>
|
|
171
|
+
</Box>
|
|
156
172
|
</VStack>
|
|
157
173
|
)
|
|
158
174
|
}
|
|
@@ -1008,6 +1008,43 @@ export function useCanvasInteractions({
|
|
|
1008
1008
|
document.addEventListener('pointercancel', up)
|
|
1009
1009
|
}, [canEdit, clearHandleReconnectListeners, performReconnect, rfNodesRef, _rfEdgesRef, syncHandleReconnectDrag])
|
|
1010
1010
|
|
|
1011
|
+
const stableOnReconnectPick = useCallback(async (targetElementId: number) => {
|
|
1012
|
+
const picking = reconnectPickingRef.current
|
|
1013
|
+
if (!canEdit || !picking) return false
|
|
1014
|
+
|
|
1015
|
+
const oldConnector = _rfEdgesRef.current.find((candidate) => candidate.id === String(picking.edgeId))
|
|
1016
|
+
const pickedNode = rfNodesRef.current.find((node) => node.id === String(targetElementId))
|
|
1017
|
+
if (!oldConnector || !pickedNode) return false
|
|
1018
|
+
|
|
1019
|
+
const fixedNodeId = picking.endpoint === 'source' ? oldConnector.target : oldConnector.source
|
|
1020
|
+
if (fixedNodeId === pickedNode.id) return false
|
|
1021
|
+
const fixedNode = rfNodesRef.current.find((node) => node.id === fixedNodeId)
|
|
1022
|
+
if (!fixedNode) return false
|
|
1023
|
+
|
|
1024
|
+
const closest = picking.endpoint === 'source'
|
|
1025
|
+
? findClosestHandles(pickedNode, fixedNode)
|
|
1026
|
+
: findClosestHandles(fixedNode, pickedNode)
|
|
1027
|
+
const newConnection: Connection = picking.endpoint === 'source'
|
|
1028
|
+
? {
|
|
1029
|
+
source: pickedNode.id,
|
|
1030
|
+
sourceHandle: ensureVisualHandleId(closest.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? closest.sourceHandle,
|
|
1031
|
+
target: fixedNode.id,
|
|
1032
|
+
targetHandle: ensureVisualHandleId(closest.targetHandle, DEFAULT_TARGET_HANDLE_SIDE) ?? closest.targetHandle,
|
|
1033
|
+
}
|
|
1034
|
+
: {
|
|
1035
|
+
source: fixedNode.id,
|
|
1036
|
+
sourceHandle: ensureVisualHandleId(closest.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? closest.sourceHandle,
|
|
1037
|
+
target: pickedNode.id,
|
|
1038
|
+
targetHandle: ensureVisualHandleId(closest.targetHandle, DEFAULT_TARGET_HANDLE_SIDE) ?? closest.targetHandle,
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
reconnectPickingRef.current = null
|
|
1042
|
+
setReconnectPicking(null)
|
|
1043
|
+
setConnectorLongPressMenu(null)
|
|
1044
|
+
await performReconnect(oldConnector, newConnection)
|
|
1045
|
+
return true
|
|
1046
|
+
}, [canEdit, _rfEdgesRef, performReconnect, rfNodesRef])
|
|
1047
|
+
|
|
1011
1048
|
// ── Click-connect ghost cursor tracking ────────────────────────────────────
|
|
1012
1049
|
useEffect(() => {
|
|
1013
1050
|
if (!clickConnectMode) {
|
|
@@ -1528,6 +1565,7 @@ export function useCanvasInteractions({
|
|
|
1528
1565
|
stableOnConnectTo,
|
|
1529
1566
|
stableOnInteractionStart,
|
|
1530
1567
|
stableOnStartHandleReconnect,
|
|
1568
|
+
stableOnReconnectPick,
|
|
1531
1569
|
showAddingElementAt,
|
|
1532
1570
|
// RF event handlers
|
|
1533
1571
|
onNodesChange,
|
|
@@ -227,7 +227,13 @@ export function useViewData({
|
|
|
227
227
|
queryFn: () => api.workspace.views.treeAround(viewId, { ancestorLevels: 2, descendantLevels: 2 }),
|
|
228
228
|
staleTime: 0,
|
|
229
229
|
}).catch(() => null)
|
|
230
|
-
if (tree)
|
|
230
|
+
if (tree) {
|
|
231
|
+
const links = buildViewContentLinks(tree, viewId, viewElementsRef.current)
|
|
232
|
+
useStore.getState().setTreeData(tree)
|
|
233
|
+
useStore.getState().setLinksMap(links.linksMap)
|
|
234
|
+
useStore.getState().setParentLinksMap(links.parentLinksMap)
|
|
235
|
+
useStore.getState().setIncomingLinks(links.incomingLinks)
|
|
236
|
+
}
|
|
231
237
|
}, [queryClient, viewId])
|
|
232
238
|
|
|
233
239
|
// ── Fetch view content ──────────────────────────────────────────────────
|
|
@@ -281,8 +287,12 @@ export function useViewData({
|
|
|
281
287
|
if (fresh) {
|
|
282
288
|
setViewElements(fresh.placements)
|
|
283
289
|
setConnectors(fresh.connectors)
|
|
290
|
+
const links = buildViewContentLinks(treeDataRef.current, viewId, fresh.placements)
|
|
291
|
+
setLinksMap(links.linksMap)
|
|
292
|
+
setParentLinksMap(links.parentLinksMap)
|
|
293
|
+
useStore.getState().setIncomingLinks(links.incomingLinks)
|
|
284
294
|
}
|
|
285
|
-
}, [queryClient, setConnectors, setViewElements, viewId])
|
|
295
|
+
}, [queryClient, setConnectors, setLinksMap, setParentLinksMap, setViewElements, viewId])
|
|
286
296
|
|
|
287
297
|
// ── Element mutation helpers ───────────────────────────────────────────────
|
|
288
298
|
const handleElementDeleted = useCallback((deletedId: number) => {
|
|
@@ -485,6 +485,7 @@ function ViewEditorInner({
|
|
|
485
485
|
const stableOnConnectToRef = useRef<(targetElementId: number) => Promise<void>>(async () => { })
|
|
486
486
|
const stableOnInteractionStartRef = useRef<(elementId: number, options?: { sourceHandle?: string; clientX?: number; clientY?: number }) => void>(() => { })
|
|
487
487
|
const stableOnStartHandleReconnectRef = useRef<(args: { edgeId: string; endpoint: 'source' | 'target'; handleId: string; clientX: number; clientY: number }) => void>(() => { })
|
|
488
|
+
const stableOnReconnectPickRef = useRef<(targetElementId: number) => Promise<boolean>>(async () => false)
|
|
488
489
|
|
|
489
490
|
// ── Drawing engine ────────────────────────────────────────────────────────
|
|
490
491
|
const drawing = useDrawingEngine(viewId)
|
|
@@ -518,18 +519,21 @@ function ViewEditorInner({
|
|
|
518
519
|
stableOnZoomOut: useCallback(async (id: number) => { await stableOnZoomOutRef.current(id) }, []),
|
|
519
520
|
stableOnNavigateToView: useCallback((id: number) => { stableOnNavigateToViewRef.current(id) }, []),
|
|
520
521
|
stableOnSelect: useCallback((obj: PlacedElement) => {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
522
|
+
void stableOnReconnectPickRef.current(obj.element_id).then((handled) => {
|
|
523
|
+
if (handled) return
|
|
524
|
+
setSelectedEdge(null)
|
|
525
|
+
setSelectedProxyConnectorDetails(null)
|
|
526
|
+
closeProxyConnectorPanelRef.current()
|
|
527
|
+
closeConnectorPanelRef.current()
|
|
528
|
+
setSelectedElement({
|
|
529
|
+
id: obj.element_id, name: obj.name, description: obj.description, kind: obj.kind,
|
|
530
|
+
technology: obj.technology, url: obj.url, logo_url: obj.logo_url,
|
|
531
|
+
technology_connectors: obj.technology_connectors, tags: obj.tags, repo: obj.repo,
|
|
532
|
+
branch: obj.branch, file_path: obj.file_path, language: obj.language,
|
|
533
|
+
created_at: '', updated_at: '', has_view: false, view_label: null,
|
|
534
|
+
})
|
|
535
|
+
openElementPanelRef.current()
|
|
531
536
|
})
|
|
532
|
-
openElementPanelRef.current()
|
|
533
537
|
}, []),
|
|
534
538
|
stableOnOpenCodePreview: useCallback((elementId: number) => {
|
|
535
539
|
const obj = previewViewElementsRef.current.find((o) => o.element_id === elementId)
|
|
@@ -1052,7 +1056,8 @@ function ViewEditorInner({
|
|
|
1052
1056
|
stableOnConnectToRef.current = canvas.stableOnConnectTo
|
|
1053
1057
|
stableOnInteractionStartRef.current = canvas.stableOnInteractionStart
|
|
1054
1058
|
stableOnStartHandleReconnectRef.current = canvas.stableOnStartHandleReconnect
|
|
1055
|
-
|
|
1059
|
+
stableOnReconnectPickRef.current = canvas.stableOnReconnectPick
|
|
1060
|
+
}, [canvas.stableOnZoomIn, canvas.stableOnZoomOut, canvas.stableOnNavigateToView, canvas.stableOnRemoveElement, canvas.stableOnConnectTo, canvas.stableOnInteractionStart, canvas.stableOnStartHandleReconnect, canvas.stableOnReconnectPick])
|
|
1056
1061
|
const viewName = view?.name ?? null
|
|
1057
1062
|
|
|
1058
1063
|
const [expandedAncestorGroups, setExpandedAncestorGroups] = useState<Set<string>>(new Set())
|
|
@@ -1780,7 +1785,7 @@ function ViewEditorInner({
|
|
|
1780
1785
|
menu={connectorLongPressMenu}
|
|
1781
1786
|
onEdit={(edgeId) => { const connector = connectors.find((e) => e.id === edgeId); if (connector) { setSelectedEdge(connector); connectorPanel.onOpen() }; setConnectorLongPressMenu(null) }}
|
|
1782
1787
|
onMoveSource={(edgeId) => { const picking = { edgeId, endpoint: 'source' as const }; reconnectPickingRef.current = picking; setReconnectPicking(picking); setConnectorLongPressMenu(null) }}
|
|
1783
|
-
onMoveTarget={(edgeId) => { const picking = { edgeId, endpoint: 'target' as const }; reconnectPickingRef.current = picking; setConnectorLongPressMenu(null) }}
|
|
1788
|
+
onMoveTarget={(edgeId) => { const picking = { edgeId, endpoint: 'target' as const }; reconnectPickingRef.current = picking; setReconnectPicking(picking); setConnectorLongPressMenu(null) }}
|
|
1784
1789
|
onDelete={async (edgeId) => {
|
|
1785
1790
|
setConnectorLongPressMenu(null)
|
|
1786
1791
|
if (!viewId) return
|
package/src/pages/ViewsGrid.tsx
CHANGED
|
@@ -832,7 +832,7 @@ function ViewGridInner({ onShare, treeData, loading, focusedId, onFocusChange, s
|
|
|
832
832
|
onStartRename: () => startEdit(n.id, n.name),
|
|
833
833
|
onDetails: () => handleDetailsOpen(n.id),
|
|
834
834
|
onDelete: () => { setDeleteTargetId(n.id); onDeleteOpen() },
|
|
835
|
-
onShare: onShare ? () => onShare(n.id) : () => {},
|
|
835
|
+
onShare: onShare ? () => onShare(n.id) : () => { },
|
|
836
836
|
onEditNameChange: setEditName,
|
|
837
837
|
onEditCommit: commitEdit,
|
|
838
838
|
onEditCancel: cancelEdit,
|
|
@@ -1088,7 +1088,7 @@ function ViewGridInner({ onShare, treeData, loading, focusedId, onFocusChange, s
|
|
|
1088
1088
|
<Text color="gray.600" fontSize="sm" mb={1}>No views yet.</Text>
|
|
1089
1089
|
{canEdit && (
|
|
1090
1090
|
<>
|
|
1091
|
-
<Text color="gray.700" fontSize="xs" mb={4}>Click "New
|
|
1091
|
+
<Text color="gray.700" fontSize="xs" mb={4}>Click "+ New" to get started.</Text>
|
|
1092
1092
|
|
|
1093
1093
|
</>
|
|
1094
1094
|
)}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { truncate } from './string'
|
|
3
|
+
|
|
4
|
+
describe('truncate', () => {
|
|
5
|
+
it('does not truncate strings shorter than or equal to the limit', () => {
|
|
6
|
+
expect(truncate('hello', 10)).toBe('hello')
|
|
7
|
+
expect(truncate('1234567890', 10)).toBe('1234567890')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('truncates strings longer than the limit and adds ellipsis', () => {
|
|
11
|
+
expect(truncate('hello world', 5)).toBe('hello...')
|
|
12
|
+
expect(truncate('12345678901', 10)).toBe('1234567890...')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('uses default limit of 15', () => {
|
|
16
|
+
expect(truncate('123456789012345')).toBe('123456789012345')
|
|
17
|
+
expect(truncate('1234567890123456')).toBe('123456789012345...')
|
|
18
|
+
})
|
|
19
|
+
})
|