@tldiagram/core-ui 1.94.3 → 1.94.5
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/PanelUI.d.ts +3 -2
- package/dist/components/ViewFloatingMenu.d.ts +3 -1
- package/dist/demo/viewEditor.d.ts +3 -1
- package/dist/index.js +5815 -5708
- package/package.json +1 -1
- package/src/components/PanelUI.tsx +3 -2
- package/src/components/ViewExplorer/TagManager/index.tsx +94 -43
- package/src/components/ViewExplorer/ViewNavigator.tsx +122 -36
- package/src/components/ViewExplorer/index.tsx +9 -33
- package/src/components/ViewFloatingMenu.tsx +46 -34
- package/src/demo/viewEditor.ts +7 -36
- package/src/pages/InfiniteZoom.tsx +2 -2
- package/src/pages/ViewEditor/index.tsx +2 -0
- package/src/utils/toast.ts +8 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { Box } from '@chakra-ui/react'
|
|
2
|
+
import { Box, BoxProps } from '@chakra-ui/react'
|
|
3
3
|
|
|
4
|
-
export function KbdHint({ children }: { children: string }) {
|
|
4
|
+
export function KbdHint({ children, ...props }: { children: string } & BoxProps) {
|
|
5
5
|
return (
|
|
6
6
|
<Box
|
|
7
7
|
as="span"
|
|
@@ -17,6 +17,7 @@ export function KbdHint({ children }: { children: string }) {
|
|
|
17
17
|
color="whiteAlpha.900"
|
|
18
18
|
flexShrink={0}
|
|
19
19
|
ml={2}
|
|
20
|
+
{...props}
|
|
20
21
|
>
|
|
21
22
|
{children}
|
|
22
23
|
</Box>
|
|
@@ -60,6 +60,7 @@ export const TagManager: React.FC<Props> = ({
|
|
|
60
60
|
layerCounts,
|
|
61
61
|
}) => {
|
|
62
62
|
const [isCollapsed, setIsCollapsed] = useState(false)
|
|
63
|
+
const [isUnusedCollapsed, setIsUnusedCollapsed] = useState(true)
|
|
63
64
|
const [expandedLayerIds, setExpandedLayerIds] = useState<Set<number>>(new Set())
|
|
64
65
|
const [namingPopover, setNamingPopover] = useState<{
|
|
65
66
|
isOpen: boolean;
|
|
@@ -122,7 +123,7 @@ export const TagManager: React.FC<Props> = ({
|
|
|
122
123
|
const handleConfirmNaming = async (name: string) => {
|
|
123
124
|
const { tags } = namingPopover
|
|
124
125
|
if (!tags || tags.length === 0) return
|
|
125
|
-
|
|
126
|
+
|
|
126
127
|
const color = pickUnusedColor(Object.values(tagColors).map(t => t.color))
|
|
127
128
|
await onCreateLayer(name, tags, color)
|
|
128
129
|
setNamingPopover(prev => ({ ...prev, isOpen: false, targetTag: null, targetLayerId: null }))
|
|
@@ -300,28 +301,53 @@ export const TagManager: React.FC<Props> = ({
|
|
|
300
301
|
<Divider borderColor="whiteAlpha.100" />
|
|
301
302
|
)}
|
|
302
303
|
{unusedTags.length > 0 && (
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
304
|
+
<VStack align="stretch" spacing={2}>
|
|
305
|
+
<HStack
|
|
306
|
+
spacing={1.5}
|
|
307
|
+
cursor="pointer"
|
|
308
|
+
onClick={() => setIsUnusedCollapsed(!isUnusedCollapsed)}
|
|
309
|
+
role="button"
|
|
310
|
+
opacity={0.6}
|
|
311
|
+
_hover={{ opacity: 1 }}
|
|
312
|
+
transition="opacity 0.2s"
|
|
313
|
+
>
|
|
314
|
+
<Box
|
|
315
|
+
transform={isUnusedCollapsed ? 'rotate(-90deg)' : 'none'}
|
|
316
|
+
transition="transform 0.2s"
|
|
317
|
+
display="flex"
|
|
318
|
+
alignItems="center"
|
|
319
|
+
>
|
|
320
|
+
<ChevronDownIcon size={10} />
|
|
321
|
+
</Box>
|
|
322
|
+
<Text fontSize="10px" fontWeight="bold" color="gray.500" textTransform="uppercase" letterSpacing="0.05em">
|
|
323
|
+
Unused on view ({unusedTags.length})
|
|
324
|
+
</Text>
|
|
325
|
+
</HStack>
|
|
326
|
+
{!isUnusedCollapsed && (
|
|
327
|
+
<Wrap spacing={1} opacity={0.6}>
|
|
328
|
+
{unusedTags.map((tag) => (
|
|
329
|
+
<WrapItem key={tag}>
|
|
330
|
+
<TagItem
|
|
331
|
+
tag={tag}
|
|
332
|
+
color={tagColors[tag]?.color || '#A0AEC0'}
|
|
333
|
+
description={tagColors[tag]?.description || null}
|
|
334
|
+
isAssigned={(selectedElement.tags || []).includes(tag)}
|
|
335
|
+
tagCount={tagCounts[tag]}
|
|
336
|
+
onToggle={() => onToggleTagOnElement(tag)}
|
|
337
|
+
onHover={(active) => onHoverLayer(active ? [tag] : null, tagColors[tag]?.color)}
|
|
338
|
+
onDropTag={(dragged: string) => handleCreateGroup(tag, dragged, tag)}
|
|
339
|
+
onDropLayer={(draggedId: number) => handleCreateGroupFromLayer(tag, draggedId)}
|
|
340
|
+
namingPopover={namingPopover.targetTag === tag ? namingPopover : undefined}
|
|
341
|
+
onConfirmNaming={handleConfirmNaming}
|
|
342
|
+
onCloseNaming={() => setNamingPopover(prev => ({ ...prev, isOpen: false, targetTag: null }))}
|
|
343
|
+
onSetColor={(color) => onCreateTag(tag, color)}
|
|
344
|
+
onSetDescription={(desc) => onCreateTag(tag, tagColors[tag]?.color, desc)}
|
|
345
|
+
/>
|
|
346
|
+
</WrapItem>
|
|
347
|
+
))}
|
|
348
|
+
</Wrap>
|
|
349
|
+
)}
|
|
350
|
+
</VStack>
|
|
325
351
|
)}
|
|
326
352
|
</VStack>
|
|
327
353
|
</Box>
|
|
@@ -385,26 +411,51 @@ export const TagManager: React.FC<Props> = ({
|
|
|
385
411
|
<Divider borderColor="whiteAlpha.100" />
|
|
386
412
|
)}
|
|
387
413
|
{unusedTags.length > 0 && (
|
|
388
|
-
<
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
/>
|
|
405
|
-
</
|
|
406
|
-
|
|
407
|
-
|
|
414
|
+
<VStack align="stretch" spacing={2}>
|
|
415
|
+
<HStack
|
|
416
|
+
spacing={1.5}
|
|
417
|
+
cursor="pointer"
|
|
418
|
+
onClick={() => setIsUnusedCollapsed(!isUnusedCollapsed)}
|
|
419
|
+
role="button"
|
|
420
|
+
opacity={0.6}
|
|
421
|
+
_hover={{ opacity: 1 }}
|
|
422
|
+
transition="opacity 0.2s"
|
|
423
|
+
>
|
|
424
|
+
<Box
|
|
425
|
+
transform={isUnusedCollapsed ? 'rotate(-90deg)' : 'none'}
|
|
426
|
+
transition="transform 0.2s"
|
|
427
|
+
display="flex"
|
|
428
|
+
alignItems="center"
|
|
429
|
+
>
|
|
430
|
+
<ChevronDownIcon size={10} />
|
|
431
|
+
</Box>
|
|
432
|
+
<Text fontSize="10px" fontWeight="bold" color="gray.500" textTransform="uppercase" letterSpacing="0.05em">
|
|
433
|
+
Other tags ({unusedTags.length})
|
|
434
|
+
</Text>
|
|
435
|
+
</HStack>
|
|
436
|
+
{!isUnusedCollapsed && (
|
|
437
|
+
<Wrap spacing={2} opacity={0.6}>
|
|
438
|
+
{unusedTags.map((tag) => (
|
|
439
|
+
<WrapItem key={tag}>
|
|
440
|
+
<TagItem
|
|
441
|
+
tag={tag}
|
|
442
|
+
color={tagColors[tag]?.color || '#A0AEC0'}
|
|
443
|
+
description={tagColors[tag]?.description || null}
|
|
444
|
+
tagCount={tagCounts[tag]}
|
|
445
|
+
onHover={(active) => onHoverLayer(active ? [tag] : null, tagColors[tag]?.color)}
|
|
446
|
+
onDropTag={(dragged: string) => handleCreateGroup(tag, dragged, tag)}
|
|
447
|
+
onDropLayer={(draggedId: number) => handleCreateGroupFromLayer(tag, draggedId)}
|
|
448
|
+
namingPopover={namingPopover.targetTag === tag ? namingPopover : undefined}
|
|
449
|
+
onConfirmNaming={handleConfirmNaming}
|
|
450
|
+
onCloseNaming={() => setNamingPopover(prev => ({ ...prev, isOpen: false, targetTag: null }))}
|
|
451
|
+
onSetColor={(color) => onCreateTag(tag, color)}
|
|
452
|
+
onSetDescription={(desc) => onCreateTag(tag, tagColors[tag]?.color, desc)}
|
|
453
|
+
/>
|
|
454
|
+
</WrapItem>
|
|
455
|
+
))}
|
|
456
|
+
</Wrap>
|
|
457
|
+
)}
|
|
458
|
+
</VStack>
|
|
408
459
|
)}
|
|
409
460
|
</VStack>
|
|
410
461
|
</Box>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Box, Tooltip, VStack, Text,
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Box, Tooltip, VStack, Text, HStack } from '@chakra-ui/react'
|
|
3
3
|
import { ZoomInIcon, ZoomOutIcon, ChevronDownIcon } from '../Icons'
|
|
4
4
|
import { KbdHint } from '../PanelUI'
|
|
5
5
|
import { NavItem } from './types'
|
|
@@ -20,6 +20,8 @@ export const ViewNavigator: React.FC<Props> = ({
|
|
|
20
20
|
onFilterToggle,
|
|
21
21
|
onHoverZoom,
|
|
22
22
|
}) => {
|
|
23
|
+
const [hoveredType, setHoveredType] = useState<'out' | 'in' | null>(null)
|
|
24
|
+
|
|
23
25
|
const renderNavButton = (type: 'out' | 'in', items: NavItem[]) => {
|
|
24
26
|
const isOut = type === 'out'
|
|
25
27
|
const label = isOut ? 'Zoom Out' : 'Zoom In'
|
|
@@ -28,6 +30,7 @@ export const ViewNavigator: React.FC<Props> = ({
|
|
|
28
30
|
const disabled = items.length === 0
|
|
29
31
|
const isActive = activeFilter === type
|
|
30
32
|
const accentColor = isOut ? PARENT_VIEW_COLOR : CHILD_VIEW_COLOR
|
|
33
|
+
const isHovered = hoveredType === type
|
|
31
34
|
|
|
32
35
|
const subtitle = disabled
|
|
33
36
|
? isOut
|
|
@@ -46,7 +49,7 @@ export const ViewNavigator: React.FC<Props> = ({
|
|
|
46
49
|
: 'No child views'
|
|
47
50
|
: `Navigate to ${isOut ? 'Parent' : 'Child'} View [${shortcut}]`
|
|
48
51
|
}
|
|
49
|
-
placement="
|
|
52
|
+
placement="top"
|
|
50
53
|
openDelay={400}
|
|
51
54
|
>
|
|
52
55
|
<Box
|
|
@@ -56,41 +59,125 @@ export const ViewNavigator: React.FC<Props> = ({
|
|
|
56
59
|
disabled={disabled}
|
|
57
60
|
onClick={() => onFilterToggle(type, items)}
|
|
58
61
|
onMouseEnter={() => {
|
|
62
|
+
setHoveredType(type)
|
|
59
63
|
if (disabled || items.length !== 1) return
|
|
60
64
|
onHoverZoom?.(items[0].elementId ?? null, type)
|
|
61
65
|
}}
|
|
62
66
|
onMouseLeave={() => {
|
|
67
|
+
setHoveredType(null)
|
|
63
68
|
if (disabled || items.length !== 1) return
|
|
64
69
|
onHoverZoom?.(null, null)
|
|
65
70
|
}}
|
|
66
71
|
opacity={disabled ? 0.4 : 1}
|
|
72
|
+
flex={hoveredType === null ? 1 : (isHovered ? 4 : 1)}
|
|
73
|
+
transition="all 0.4s cubic-bezier(0.4, 0, 0.2, 1)"
|
|
74
|
+
minW={0}
|
|
75
|
+
overflow="hidden"
|
|
76
|
+
position="relative"
|
|
77
|
+
px={isHovered || hoveredType === null ? 3 : 1}
|
|
78
|
+
sx={{
|
|
79
|
+
'.panel-action-icon-container': {
|
|
80
|
+
transition: 'all 0.3s ease',
|
|
81
|
+
width: '28px',
|
|
82
|
+
height: '28px',
|
|
83
|
+
marginRight: isOut ? '0.5rem' : 0,
|
|
84
|
+
marginLeft: isOut ? 0 : '0.5rem',
|
|
85
|
+
position: 'relative',
|
|
86
|
+
}
|
|
87
|
+
}}
|
|
67
88
|
>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
{isOut ? (
|
|
90
|
+
<>
|
|
91
|
+
<Box className="panel-action-icon-container" color={disabled ? 'gray.600' : accentColor}>
|
|
92
|
+
<IconCmp />
|
|
93
|
+
{items.length > 1 && (
|
|
94
|
+
<Box
|
|
95
|
+
position="absolute"
|
|
96
|
+
bottom="-5px"
|
|
97
|
+
right="-5px"
|
|
98
|
+
color="white"
|
|
99
|
+
fontSize="12px"
|
|
100
|
+
fontWeight="bold"
|
|
101
|
+
minW="14px"
|
|
102
|
+
h="14px"
|
|
103
|
+
display="flex"
|
|
104
|
+
alignItems="center"
|
|
105
|
+
justifyContent="center"
|
|
106
|
+
zIndex={2}
|
|
107
|
+
>
|
|
108
|
+
{items.length}
|
|
109
|
+
</Box>
|
|
110
|
+
)}
|
|
111
|
+
</Box>
|
|
112
|
+
<VStack
|
|
113
|
+
align="start"
|
|
114
|
+
spacing={0}
|
|
115
|
+
flex={1}
|
|
116
|
+
minW={0}
|
|
117
|
+
opacity={hoveredType === 'in' ? 0 : 1}
|
|
118
|
+
transition="opacity 0.2s, transform 0.3s"
|
|
119
|
+
transform={hoveredType === 'in' ? 'translateX(-10px)' : 'none'}
|
|
120
|
+
>
|
|
121
|
+
<Text fontSize="xs" color={disabled ? 'gray.500' : 'white'} fontWeight="bold" isTruncated w="full" textAlign="left">
|
|
122
|
+
{label}
|
|
123
|
+
</Text>
|
|
124
|
+
{isHovered && (
|
|
125
|
+
<Text fontSize="10px" color={disabled ? 'gray.600' : isActive ? accentColor : 'gray.500'} isTruncated w="full" transition="color 0.15s" textAlign="left">
|
|
126
|
+
{subtitle}
|
|
127
|
+
</Text>
|
|
128
|
+
)}
|
|
129
|
+
</VStack>
|
|
130
|
+
<Box opacity={hoveredType === 'in' ? 0 : 1} transition="opacity 0.2s">
|
|
131
|
+
<KbdHint ml={1}>{shortcut}</KbdHint>
|
|
132
|
+
</Box>
|
|
133
|
+
</>
|
|
134
|
+
) : (
|
|
135
|
+
<>
|
|
136
|
+
<Box opacity={hoveredType === 'out' ? 0 : 1} transition="opacity 0.2s">
|
|
137
|
+
<KbdHint ml={0} mr={1}>{shortcut}</KbdHint>
|
|
138
|
+
</Box>
|
|
139
|
+
<VStack
|
|
140
|
+
align="end"
|
|
141
|
+
spacing={0}
|
|
142
|
+
flex={1}
|
|
143
|
+
minW={0}
|
|
144
|
+
opacity={hoveredType === 'out' ? 0 : 1}
|
|
145
|
+
transition="opacity 0.2s, transform 0.3s"
|
|
146
|
+
transform={hoveredType === 'out' ? 'translateX(10px)' : 'none'}
|
|
147
|
+
>
|
|
148
|
+
<Text fontSize="xs" color={disabled ? 'gray.500' : 'white'} fontWeight="bold" isTruncated w="full" textAlign="right">
|
|
149
|
+
{label}
|
|
150
|
+
</Text>
|
|
151
|
+
{isHovered && (
|
|
152
|
+
<Text fontSize="10px" color={disabled ? 'gray.600' : isActive ? accentColor : 'gray.500'} isTruncated w="full" transition="color 0.15s" textAlign="right">
|
|
153
|
+
{subtitle}
|
|
154
|
+
</Text>
|
|
155
|
+
)}
|
|
156
|
+
</VStack>
|
|
157
|
+
<Box className="panel-action-icon-container" color={disabled ? 'gray.600' : accentColor}>
|
|
158
|
+
<IconCmp />
|
|
159
|
+
{items.length > 1 && (
|
|
160
|
+
<Box
|
|
161
|
+
position="absolute"
|
|
162
|
+
bottom="-5px"
|
|
163
|
+
right="-5px"
|
|
164
|
+
color="white"
|
|
165
|
+
fontSize="12px"
|
|
166
|
+
fontWeight="bold"
|
|
167
|
+
minW="14px"
|
|
168
|
+
h="14px"
|
|
169
|
+
display="flex"
|
|
170
|
+
alignItems="center"
|
|
171
|
+
justifyContent="center"
|
|
172
|
+
zIndex={2}
|
|
173
|
+
>
|
|
174
|
+
{items.length}
|
|
175
|
+
</Box>
|
|
176
|
+
)}
|
|
177
|
+
</Box>
|
|
178
|
+
</>
|
|
179
|
+
)}
|
|
180
|
+
{items.length > 1 && isHovered && (
|
|
94
181
|
<Box
|
|
95
182
|
color="whiteAlpha.400"
|
|
96
183
|
_groupHover={{ color: 'white' }}
|
|
@@ -102,20 +189,19 @@ export const ViewNavigator: React.FC<Props> = ({
|
|
|
102
189
|
<ChevronDownIcon size={12} strokeWidth={3.5} />
|
|
103
190
|
</Box>
|
|
104
191
|
)}
|
|
105
|
-
{isActive && items.length <= 1 && (
|
|
192
|
+
{isActive && items.length <= 1 && isHovered && (
|
|
106
193
|
<Box w="5px" h="5px" rounded="full" bg={accentColor} flexShrink={0} mx={1} />
|
|
107
194
|
)}
|
|
108
|
-
<KbdHint>{shortcut}</KbdHint>
|
|
109
195
|
</Box>
|
|
110
196
|
</Tooltip>
|
|
111
197
|
)
|
|
112
198
|
}
|
|
113
199
|
|
|
114
200
|
return (
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
</
|
|
201
|
+
<HStack spacing={0} align="stretch" flexShrink={0} height="60px" borderColor="whiteAlpha.100">
|
|
202
|
+
{renderNavButton('out', parents)}
|
|
203
|
+
<Box w="1px" bg="whiteAlpha.100" />
|
|
204
|
+
{renderNavButton('in', children)}
|
|
205
|
+
</HStack>
|
|
120
206
|
)
|
|
121
207
|
}
|
|
@@ -142,23 +142,17 @@ function ViewExplorer({
|
|
|
142
142
|
const children = useMemo(() => {
|
|
143
143
|
if (viewId == null) return []
|
|
144
144
|
const diagMap = new Map<number, NavItem>()
|
|
145
|
-
|
|
146
|
-
if (n.parent_view_id === viewId)
|
|
147
|
-
diagMap.set(n.id, { id: n.id, name: n.name, subtitle: 'Child View' })
|
|
148
|
-
})
|
|
145
|
+
|
|
149
146
|
const objMap = new Map(viewElements.map((o) => [o.element_id, o.name]))
|
|
150
147
|
Object.entries(linksMap).forEach(([elementIdStr, links]) => {
|
|
151
148
|
const elementId = Number(elementIdStr)
|
|
152
|
-
if (!Number.isFinite(elementId)) return
|
|
153
|
-
|
|
149
|
+
if (!Number.isFinite(elementId) || !onCanvasIds.has(elementId)) return
|
|
150
|
+
|
|
154
151
|
const objName = objMap.get(elementId) || 'Element'
|
|
155
152
|
links.forEach((link) => {
|
|
156
153
|
const existing = diagMap.get(link.to_view_id)
|
|
157
154
|
if (existing) {
|
|
158
|
-
if (!existing.
|
|
159
|
-
if (existing.subtitle === 'Child View') {
|
|
160
|
-
existing.subtitle = `Child View (Via ${objName})`
|
|
161
|
-
} else if (!existing.subtitle?.includes(objName)) {
|
|
155
|
+
if (!existing.subtitle?.includes(objName)) {
|
|
162
156
|
existing.subtitle = `${existing.subtitle}, ${objName}`
|
|
163
157
|
}
|
|
164
158
|
} else {
|
|
@@ -166,13 +160,13 @@ function ViewExplorer({
|
|
|
166
160
|
id: link.to_view_id,
|
|
167
161
|
name: link.to_view_name,
|
|
168
162
|
subtitle: `Via ${objName}`,
|
|
169
|
-
elementId:
|
|
163
|
+
elementId: elementId,
|
|
170
164
|
})
|
|
171
165
|
}
|
|
172
166
|
})
|
|
173
167
|
})
|
|
174
168
|
return Array.from(diagMap.values())
|
|
175
|
-
}, [viewId,
|
|
169
|
+
}, [viewId, linksMap, viewElements, onCanvasIds])
|
|
176
170
|
|
|
177
171
|
const viewHoverMap = useMemo(() => {
|
|
178
172
|
const map = new Map<number, { elementId: number | undefined; type: 'in' | 'out' }>()
|
|
@@ -184,32 +178,14 @@ function ViewExplorer({
|
|
|
184
178
|
const filteredByMode = useMemo(() => {
|
|
185
179
|
if (activeFilter === 'out') {
|
|
186
180
|
const parentIds = new Set(parents.map((p) => p.id))
|
|
187
|
-
|
|
188
|
-
const findAncestors = (id: number) => {
|
|
189
|
-
const node = treeNodes.find(n => n.id === id)
|
|
190
|
-
if (node?.parent_view_id) {
|
|
191
|
-
ancestors.add(node.parent_view_id)
|
|
192
|
-
findAncestors(node.parent_view_id)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
if (viewId) findAncestors(viewId)
|
|
196
|
-
return flat.filter((n) => parentIds.has(n.id) || ancestors.has(n.id) || n.id === viewId)
|
|
181
|
+
return flat.filter((n) => parentIds.has(n.id) || n.id === viewId)
|
|
197
182
|
}
|
|
198
183
|
if (activeFilter === 'in') {
|
|
199
184
|
const childIds = new Set(children.map((c) => c.id))
|
|
200
|
-
|
|
201
|
-
const traverse = (nodes: TreeNode[]) => {
|
|
202
|
-
nodes.forEach(n => {
|
|
203
|
-
subtree.add(n.id)
|
|
204
|
-
traverse(n.children)
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
const current = flat.find(n => n.id === viewId)
|
|
208
|
-
if (current) traverse(current.children)
|
|
209
|
-
return flat.filter((n) => childIds.has(n.id) || subtree.has(n.id) || n.id === viewId)
|
|
185
|
+
return flat.filter((n) => childIds.has(n.id) || n.id === viewId)
|
|
210
186
|
}
|
|
211
187
|
return flat
|
|
212
|
-
}, [flat, parents, children, activeFilter, viewId
|
|
188
|
+
}, [flat, parents, children, activeFilter, viewId])
|
|
213
189
|
|
|
214
190
|
const filtered = useMemo(() => {
|
|
215
191
|
if (!query.trim()) return filteredByMode
|
|
@@ -49,6 +49,8 @@ export interface ViewFloatingMenuProps extends ViewFloatingMenuSlots {
|
|
|
49
49
|
setHighlightColor: (color: string | null) => void
|
|
50
50
|
|
|
51
51
|
toolbarSlot?: React.ReactNode
|
|
52
|
+
hideFocusView?: boolean
|
|
53
|
+
hideExpandExtras?: boolean
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/**
|
|
@@ -83,6 +85,8 @@ function ViewFloatingMenu({
|
|
|
83
85
|
setHighlightColor,
|
|
84
86
|
shareSlot,
|
|
85
87
|
toolbarSlot,
|
|
88
|
+
hideFocusView = false,
|
|
89
|
+
hideExpandExtras = false,
|
|
86
90
|
}: ViewFloatingMenuProps) {
|
|
87
91
|
const { canEdit } = useViewEditorContext()
|
|
88
92
|
const { isOpen: isTagsOpen, onClose: onTagsClose, onToggle: onTagsToggle } = useDisclosure()
|
|
@@ -127,24 +131,28 @@ function ViewFloatingMenu({
|
|
|
127
131
|
</Button>
|
|
128
132
|
</Tooltip>
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
134
|
+
{!hideFocusView && (
|
|
135
|
+
<>
|
|
136
|
+
<Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
|
|
137
|
+
<Tooltip label={focusMode ? 'Show context' : 'Focus on this view'} placement="top" openDelay={200}>
|
|
138
|
+
<Button
|
|
139
|
+
variant="ghost"
|
|
140
|
+
h="28px"
|
|
141
|
+
px={2.5}
|
|
142
|
+
color={focusMode ? 'var(--accent)' : 'gray.300'}
|
|
143
|
+
bg={focusMode ? 'rgba(var(--accent-rgb), 0.12)' : 'transparent'}
|
|
144
|
+
_hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
|
|
145
|
+
onClick={() => onFocusModeChange(!focusMode)}
|
|
146
|
+
>
|
|
147
|
+
<HStack spacing={1.5}>
|
|
148
|
+
<FocusSvg />
|
|
149
|
+
<Text fontSize="11px" fontWeight="normal">Focus View</Text>
|
|
150
|
+
<Box w="6px" h="6px" rounded="full" bg={focusMode ? 'var(--accent)' : 'gray.500'} />
|
|
151
|
+
</HStack>
|
|
152
|
+
</Button>
|
|
153
|
+
</Tooltip>
|
|
154
|
+
</>
|
|
155
|
+
)}
|
|
148
156
|
<Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
|
|
149
157
|
|
|
150
158
|
{(allTags.length > 0 || layers.length > 0) && (
|
|
@@ -356,22 +364,26 @@ function ViewFloatingMenu({
|
|
|
356
364
|
</>
|
|
357
365
|
)}
|
|
358
366
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
367
|
+
{!hideExpandExtras && (
|
|
368
|
+
<>
|
|
369
|
+
<Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
|
|
370
|
+
<Button
|
|
371
|
+
variant="ghost"
|
|
372
|
+
h="28px"
|
|
373
|
+
minW="36px"
|
|
374
|
+
px={2}
|
|
375
|
+
display="inline-flex"
|
|
376
|
+
alignItems="center"
|
|
377
|
+
justifyContent="center"
|
|
378
|
+
color="gray.300"
|
|
379
|
+
_hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
|
|
380
|
+
onClick={() => setExtrasOpen((prev) => !prev)}
|
|
381
|
+
aria-label={extrasOpen ? 'Collapse extras' : 'Expand extras'}
|
|
382
|
+
>
|
|
383
|
+
{extrasOpen ? <CollapseExtrasSvg /> : <ExpandExtrasSvg />}
|
|
384
|
+
</Button>
|
|
385
|
+
</>
|
|
386
|
+
)}
|
|
375
387
|
</HStack>
|
|
376
388
|
)
|
|
377
389
|
}
|