@likec4/diagram 1.12.2 → 1.13.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/package.json +8 -8
- package/src/LikeC4CustomColors.tsx +2 -2
- package/src/LikeC4Diagram.props.ts +15 -7
- package/src/LikeC4Diagram.tsx +23 -14
- package/src/StaticLikeC4Diagram.tsx +2 -0
- package/src/components/Overlay.css.ts +78 -0
- package/src/components/Overlay.tsx +102 -0
- package/src/components/OverlayContext.tsx +11 -0
- package/src/components/index.ts +2 -0
- package/src/likec4model/index.ts +1 -0
- package/src/likec4model/useLikeC4ElementsTree.ts +44 -0
- package/src/overlays/edge-details/EdgeDetailsOverlay.css.ts +13 -0
- package/src/overlays/edge-details/EdgeDetailsOverlay.tsx +37 -0
- package/src/overlays/index.tsx +33 -0
- package/src/overlays/relationships-of/RelationshipsOverlay.tsx +33 -0
- package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +359 -0
- package/src/overlays/relationships-of/SelectElement.css.ts +18 -0
- package/src/overlays/relationships-of/SelectElement.tsx +133 -0
- package/src/overlays/relationships-of/_types.ts +56 -0
- package/src/overlays/relationships-of/styles.css.ts +34 -0
- package/src/overlays/relationships-of/use-layouted-relationships.ts +607 -0
- package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +76 -0
- package/src/overlays/relationships-of/xyflow/ElementNode.tsx +130 -0
- package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +22 -0
- package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +59 -0
- package/src/overlays/relationships-of/xyflow/styles.css.ts +170 -0
- package/src/state/diagram-to-xyflow.ts +1 -1
- package/src/state/diagramStore.ts +119 -27
- package/src/ui/top-left/ChangeAutoLayoutButton.tsx +1 -1
- package/src/ui/top-left/ManualLayoutToolsButton.tsx +110 -0
- package/src/ui/top-left/TopLeftPanel.tsx +15 -31
- package/src/ui/top-left/_shared.ts +2 -2
- package/src/xyflow/EdgeMarkers.tsx +19 -0
- package/src/xyflow/XYFlowEvents.tsx +21 -24
- package/src/xyflow/XYFlowInner.tsx +6 -10
- package/src/xyflow/edges/EdgeLabel.tsx +6 -5
- package/src/xyflow/edges/RelationshipEdge.tsx +4 -3
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +16 -0
- package/src/xyflow/edges/edges.css.ts +5 -11
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +17 -40
- package/src/xyflow/nodes/compound/CompoundNode.tsx +219 -28
- package/src/xyflow/nodes/element/ElementLink.tsx +11 -8
- package/src/xyflow/nodes/element/ElementNode.tsx +211 -140
- package/src/xyflow/nodes/element/element.css.ts +22 -33
- package/src/xyflow/nodes/shared/Toolbar.tsx +81 -27
- package/src/xyflow/nodes/use-animate-variants.tsx +64 -0
- package/src/xyflow/useLayoutConstraints.ts +95 -125
- package/src/xyflow/utils.ts +4 -0
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +0 -32
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ActionIconGroup, Group, Popover, PopoverDropdown, type PopoverProps, PopoverTarget } from '@mantine/core'
|
|
2
|
+
import {
|
|
3
|
+
IconLayoutAlignBottom,
|
|
4
|
+
IconLayoutAlignCenter,
|
|
5
|
+
IconLayoutAlignLeft,
|
|
6
|
+
IconLayoutAlignMiddle,
|
|
7
|
+
IconLayoutAlignRight,
|
|
8
|
+
IconLayoutAlignTop,
|
|
9
|
+
IconLayoutCollage,
|
|
10
|
+
IconRouteOff
|
|
11
|
+
} from '@tabler/icons-react'
|
|
12
|
+
import { useDiagramStoreApi } from '../../hooks/useDiagramState'
|
|
13
|
+
import { ActionIcon, Tooltip } from './_shared'
|
|
14
|
+
|
|
15
|
+
const Action = ({
|
|
16
|
+
label,
|
|
17
|
+
icon,
|
|
18
|
+
onClick
|
|
19
|
+
}: {
|
|
20
|
+
label: string
|
|
21
|
+
icon: React.ReactNode
|
|
22
|
+
onClick: React.MouseEventHandler
|
|
23
|
+
}) => (
|
|
24
|
+
<Tooltip label={label} withinPortal={false} position="top">
|
|
25
|
+
<ActionIcon onClick={onClick}>
|
|
26
|
+
{icon}
|
|
27
|
+
</ActionIcon>
|
|
28
|
+
</Tooltip>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export const ManualLayoutToolsButton = (props: PopoverProps) => {
|
|
32
|
+
const store = useDiagramStoreApi()
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Popover
|
|
36
|
+
position="right-start"
|
|
37
|
+
clickOutsideEvents={[
|
|
38
|
+
'pointerdown'
|
|
39
|
+
]}
|
|
40
|
+
radius="xs"
|
|
41
|
+
shadow="lg"
|
|
42
|
+
offset={{
|
|
43
|
+
mainAxis: 10
|
|
44
|
+
}}
|
|
45
|
+
{...props}>
|
|
46
|
+
<PopoverTarget>
|
|
47
|
+
<Tooltip label="Manual layouting tools" withinPortal={false} position="top-end">
|
|
48
|
+
<ActionIcon>
|
|
49
|
+
<IconLayoutCollage />
|
|
50
|
+
</ActionIcon>
|
|
51
|
+
</Tooltip>
|
|
52
|
+
</PopoverTarget>
|
|
53
|
+
<PopoverDropdown p={0}>
|
|
54
|
+
<Group gap={'xs'}>
|
|
55
|
+
<ActionIconGroup pos={'relative'}>
|
|
56
|
+
<Action
|
|
57
|
+
label="Align left"
|
|
58
|
+
icon={<IconLayoutAlignLeft />}
|
|
59
|
+
onClick={e => {
|
|
60
|
+
e.stopPropagation()
|
|
61
|
+
store.getState().align('Left')
|
|
62
|
+
}} />
|
|
63
|
+
<Action
|
|
64
|
+
label="Align center"
|
|
65
|
+
icon={<IconLayoutAlignCenter />}
|
|
66
|
+
onClick={e => {
|
|
67
|
+
e.stopPropagation()
|
|
68
|
+
store.getState().align('Center')
|
|
69
|
+
}} />
|
|
70
|
+
<Action
|
|
71
|
+
label="Align right"
|
|
72
|
+
icon={<IconLayoutAlignRight />}
|
|
73
|
+
onClick={e => {
|
|
74
|
+
e.stopPropagation()
|
|
75
|
+
store.getState().align('Right')
|
|
76
|
+
}} />
|
|
77
|
+
<Action
|
|
78
|
+
label="Align top"
|
|
79
|
+
icon={<IconLayoutAlignTop />}
|
|
80
|
+
onClick={e => {
|
|
81
|
+
e.stopPropagation()
|
|
82
|
+
store.getState().align('Top')
|
|
83
|
+
}} />
|
|
84
|
+
<Action
|
|
85
|
+
label="Align middle"
|
|
86
|
+
icon={<IconLayoutAlignMiddle />}
|
|
87
|
+
onClick={e => {
|
|
88
|
+
e.stopPropagation()
|
|
89
|
+
store.getState().align('Middle')
|
|
90
|
+
}} />
|
|
91
|
+
<Action
|
|
92
|
+
label="Align bottom"
|
|
93
|
+
icon={<IconLayoutAlignBottom />}
|
|
94
|
+
onClick={e => {
|
|
95
|
+
e.stopPropagation()
|
|
96
|
+
store.getState().align('Bottom')
|
|
97
|
+
}} />
|
|
98
|
+
</ActionIconGroup>
|
|
99
|
+
<Action
|
|
100
|
+
label="Reset all control points"
|
|
101
|
+
icon={<IconRouteOff />}
|
|
102
|
+
onClick={e => {
|
|
103
|
+
e.stopPropagation()
|
|
104
|
+
store.getState().resetEdgeControlPoints()
|
|
105
|
+
}} />
|
|
106
|
+
</Group>
|
|
107
|
+
</PopoverDropdown>
|
|
108
|
+
</Popover>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
@@ -10,8 +10,7 @@ import {
|
|
|
10
10
|
type PopoverProps,
|
|
11
11
|
Stack,
|
|
12
12
|
Text,
|
|
13
|
-
TooltipGroup
|
|
14
|
-
type TooltipProps
|
|
13
|
+
TooltipGroup
|
|
15
14
|
} from '@mantine/core'
|
|
16
15
|
import {
|
|
17
16
|
IconAlertTriangle,
|
|
@@ -19,8 +18,7 @@ import {
|
|
|
19
18
|
IconChevronRight,
|
|
20
19
|
IconFileSymlink,
|
|
21
20
|
IconFocusCentered,
|
|
22
|
-
IconMenu2
|
|
23
|
-
IconRouteOff
|
|
21
|
+
IconMenu2
|
|
24
22
|
} from '@tabler/icons-react'
|
|
25
23
|
import clsx from 'clsx'
|
|
26
24
|
import { AnimatePresence, LayoutGroup, m } from 'framer-motion'
|
|
@@ -30,12 +28,14 @@ import { useMantinePortalProps } from '../../hooks/useMantinePortalProps'
|
|
|
30
28
|
import { mantine } from '../../theme-vars'
|
|
31
29
|
import { ActionIcon, Tooltip } from './_shared'
|
|
32
30
|
import { ChangeAutoLayoutButton } from './ChangeAutoLayoutButton'
|
|
31
|
+
import { ManualLayoutToolsButton } from './ManualLayoutToolsButton'
|
|
33
32
|
import * as css from './styles.css'
|
|
34
33
|
|
|
35
34
|
const historySelector = (s: DiagramState) => ({
|
|
36
35
|
showBurgerMenu: !!s.onBurgerMenuClick,
|
|
37
|
-
hasStepBack: s.navigationHistoryIndex > 0,
|
|
38
|
-
hasStepForward: s.
|
|
36
|
+
hasStepBack: s.showNavigationButtons && !!s.onNavigateTo && s.navigationHistoryIndex > 0,
|
|
37
|
+
hasStepForward: s.showNavigationButtons && !!s.onNavigateTo
|
|
38
|
+
&& s.navigationHistoryIndex < s.navigationHistory.length - 1
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
const BackwardForwardButtons = () => {
|
|
@@ -135,22 +135,6 @@ const LayoutDriftNotification = (props: PopoverProps) => (
|
|
|
135
135
|
</HoverCard>
|
|
136
136
|
)
|
|
137
137
|
|
|
138
|
-
const ResetControlPointsButton = (props: Omit<TooltipProps, 'label' | 'children'>) => {
|
|
139
|
-
const store = useDiagramStoreApi()
|
|
140
|
-
|
|
141
|
-
return (
|
|
142
|
-
<Tooltip label="Reset all control points" {...props}>
|
|
143
|
-
<ActionIcon
|
|
144
|
-
onClick={e => {
|
|
145
|
-
e.stopPropagation()
|
|
146
|
-
store.getState().resetEdgeControlPoints()
|
|
147
|
-
}}>
|
|
148
|
-
<IconRouteOff />
|
|
149
|
-
</ActionIcon>
|
|
150
|
-
</Tooltip>
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
138
|
export const TopLeftPanel = () => {
|
|
155
139
|
const store = useDiagramStoreApi()
|
|
156
140
|
const {
|
|
@@ -160,25 +144,25 @@ export const TopLeftPanel = () => {
|
|
|
160
144
|
showChangeAutoLayout,
|
|
161
145
|
showGoToSource,
|
|
162
146
|
viewportChanged,
|
|
163
|
-
|
|
147
|
+
showManualLayoutTools
|
|
164
148
|
} = useDiagramState(s => {
|
|
165
149
|
const isNotWalkthrough = isNullish(s.activeWalkthrough)
|
|
166
150
|
const isNotFocused = isNullish(s.focusedNodeId)
|
|
167
151
|
const isNotActive = isNotWalkthrough && isNotFocused
|
|
168
152
|
return ({
|
|
169
|
-
showNavigationButtons: !!s.onBurgerMenuClick || s.showNavigationButtons && !!s.onNavigateTo,
|
|
170
|
-
showFitDiagram: s.fitViewEnabled && s.zoomable && isNotWalkthrough,
|
|
171
|
-
showLayoutDriftWarning: !s.readonly && s.view.hasLayoutDrift === true && isNotActive,
|
|
172
|
-
showChangeAutoLayout: s.isEditable() && isNotActive,
|
|
173
|
-
showGoToSource: !!s.onOpenSourceView && isNotWalkthrough,
|
|
153
|
+
showNavigationButtons: !!s.onBurgerMenuClick || (s.showNavigationButtons && !!s.onNavigateTo),
|
|
154
|
+
showFitDiagram: s.controls && s.fitViewEnabled && s.zoomable && isNotWalkthrough,
|
|
155
|
+
showLayoutDriftWarning: s.controls && !s.readonly && s.view.hasLayoutDrift === true && isNotActive,
|
|
156
|
+
showChangeAutoLayout: s.controls && s.isEditable() && isNotActive,
|
|
157
|
+
showGoToSource: s.controls && !!s.onOpenSourceView && isNotWalkthrough,
|
|
174
158
|
viewportChanged: s.viewportChanged,
|
|
175
|
-
|
|
159
|
+
showManualLayoutTools: s.controls && s.readonly !== true && s.experimentalEdgeEditing === true
|
|
176
160
|
})
|
|
177
161
|
})
|
|
178
162
|
const portalProps = useMantinePortalProps()
|
|
179
163
|
|
|
180
164
|
return (
|
|
181
|
-
<TooltipGroup openDelay={
|
|
165
|
+
<TooltipGroup openDelay={600} closeDelay={150}>
|
|
182
166
|
<Stack
|
|
183
167
|
className={clsx(
|
|
184
168
|
'react-flow__panel',
|
|
@@ -203,7 +187,7 @@ export const TopLeftPanel = () => {
|
|
|
203
187
|
)}
|
|
204
188
|
{showChangeAutoLayout && <ChangeAutoLayoutButton {...portalProps} />}
|
|
205
189
|
{showLayoutDriftWarning && <LayoutDriftNotification {...portalProps} />}
|
|
206
|
-
{
|
|
190
|
+
{showManualLayoutTools && <ManualLayoutToolsButton {...portalProps} />}
|
|
207
191
|
{showFitDiagram && (
|
|
208
192
|
<Tooltip label={viewportChanged ? 'Center camera' : 'Camera is centered'} {...portalProps}>
|
|
209
193
|
<ActionIcon
|
|
@@ -39,6 +39,24 @@ const Arrow = (props: SVGProps<SVGMarkerElement>) => (
|
|
|
39
39
|
</marker>
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
+
const Crow = (props: SVGProps<SVGMarkerElement>) => (
|
|
43
|
+
<marker
|
|
44
|
+
viewBox="-1 -1 12 12"
|
|
45
|
+
refX={8}
|
|
46
|
+
refY={4}
|
|
47
|
+
markerWidth="8"
|
|
48
|
+
markerHeight="8"
|
|
49
|
+
preserveAspectRatio="xMaxYMid meet"
|
|
50
|
+
orient="auto-start-reverse"
|
|
51
|
+
{...props}>
|
|
52
|
+
<path
|
|
53
|
+
d="M 8 0 L 0 4 L 8 8 M 8 4 L 0 4"
|
|
54
|
+
fill="none"
|
|
55
|
+
strokeWidth={1}
|
|
56
|
+
/>
|
|
57
|
+
</marker>
|
|
58
|
+
)
|
|
59
|
+
|
|
42
60
|
const OArrow = (props: SVGProps<SVGMarkerElement>) => (
|
|
43
61
|
<marker
|
|
44
62
|
viewBox="-1 -1 12 10"
|
|
@@ -139,6 +157,7 @@ const ODot = (props: SVGProps<SVGMarkerElement>) => (
|
|
|
139
157
|
|
|
140
158
|
export const EdgeMarkers = {
|
|
141
159
|
Arrow,
|
|
160
|
+
Crow,
|
|
142
161
|
OArrow,
|
|
143
162
|
Open,
|
|
144
163
|
Diamond,
|
|
@@ -50,12 +50,9 @@ export function useXYFlowEvents() {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const lastClickWasRecent = (ms = 2000) => {
|
|
53
|
-
const prevTimestamp = lastClickTimestamp.current
|
|
54
|
-
lastClickTimestamp.current = Date.now()
|
|
55
|
-
|
|
56
|
-
return false
|
|
57
|
-
}
|
|
58
|
-
return prevTimestamp + ms > Date.now()
|
|
53
|
+
const prevTimestamp = lastClickTimestamp.current ?? 0
|
|
54
|
+
const now = lastClickTimestamp.current = Date.now()
|
|
55
|
+
return prevTimestamp + ms > now
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
return ({
|
|
@@ -68,16 +65,15 @@ export function useXYFlowEvents() {
|
|
|
68
65
|
resetFocusAndLastClicked
|
|
69
66
|
} = diagramApi.getState()
|
|
70
67
|
resetFocusAndLastClicked()
|
|
71
|
-
if (!onCanvasDblClick) {
|
|
72
|
-
event.stopPropagation()
|
|
73
|
-
}
|
|
74
68
|
if (zoomable && viewportChanged) {
|
|
75
69
|
fitDiagram()
|
|
76
70
|
}
|
|
71
|
+
if (!onCanvasDblClick) {
|
|
72
|
+
event.stopPropagation()
|
|
73
|
+
}
|
|
77
74
|
onCanvasDblClick?.(event)
|
|
78
75
|
},
|
|
79
76
|
onPaneClick: (event) => {
|
|
80
|
-
console.log('onPaneClick')
|
|
81
77
|
if (dbclickLock()) {
|
|
82
78
|
return
|
|
83
79
|
}
|
|
@@ -132,7 +128,7 @@ export function useXYFlowEvents() {
|
|
|
132
128
|
// if we focused on a node, and clicked on another node - focus on the clicked node
|
|
133
129
|
const shallChangeFocus = !!focusedNodeId && focusedNodeId !== xynode.id
|
|
134
130
|
// if user clicked on the same node twice in a short time, focus on it
|
|
135
|
-
const clickedRecently = lastClickedNodeId === xynode.id
|
|
131
|
+
const clickedRecently = lastClickWasRecent() && lastClickedNodeId === xynode.id
|
|
136
132
|
|
|
137
133
|
if (clickedRecently && !!onOpenSourceElement) {
|
|
138
134
|
onOpenSourceElement(xynode.data.element.id)
|
|
@@ -141,27 +137,27 @@ export function useXYFlowEvents() {
|
|
|
141
137
|
}
|
|
142
138
|
}
|
|
143
139
|
|
|
144
|
-
if (enableFocusMode
|
|
140
|
+
if (enableFocusMode) {
|
|
145
141
|
switch (true) {
|
|
146
|
-
case
|
|
142
|
+
case clickedRecently && focusedNodeId === xynode.id: {
|
|
147
143
|
focusOnNode(false)
|
|
148
144
|
fitDiagram()
|
|
149
145
|
break
|
|
150
146
|
}
|
|
151
|
-
case
|
|
152
|
-
case enableFocusMode && clickedRecently && focusedNodeId !== xynode.id: {
|
|
147
|
+
case (shallChangeFocus || clickedRecently): {
|
|
153
148
|
focusOnNode(xynode.id)
|
|
154
149
|
break
|
|
155
150
|
}
|
|
156
|
-
case nodesSelectable: {
|
|
157
|
-
xystore.getState().addSelectedNodes([xynode.id])
|
|
158
|
-
break
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (!onNodeClick) {
|
|
162
|
-
event.stopPropagation()
|
|
163
151
|
}
|
|
152
|
+
} else if (nodesSelectable) {
|
|
153
|
+
xystore.getState().addSelectedNodes([xynode.id])
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!onNodeClick && (enableFocusMode || nodesSelectable)) {
|
|
157
|
+
event.stopPropagation()
|
|
158
|
+
return
|
|
164
159
|
}
|
|
160
|
+
|
|
165
161
|
onNodeClick?.(
|
|
166
162
|
xynode.data.element,
|
|
167
163
|
event
|
|
@@ -176,16 +172,17 @@ export function useXYFlowEvents() {
|
|
|
176
172
|
setLastClickedNode
|
|
177
173
|
} = diagramApi.getState()
|
|
178
174
|
setLastClickedNode(xynode.id)
|
|
179
|
-
|
|
175
|
+
lastClickWasRecent()
|
|
180
176
|
if (isNonNullish(focusedNodeId) || enableFocusMode) {
|
|
181
177
|
// if we are already focused on the node, cancel
|
|
182
178
|
if (focusedNodeId === xynode.id) {
|
|
179
|
+
focusOnNode(false)
|
|
183
180
|
fitDiagram()
|
|
184
181
|
} else {
|
|
185
182
|
focusOnNode(xynode.id)
|
|
186
183
|
}
|
|
187
|
-
event.stopPropagation()
|
|
188
184
|
}
|
|
185
|
+
event.stopPropagation()
|
|
189
186
|
},
|
|
190
187
|
onEdgeClick: (event, xyedge) => {
|
|
191
188
|
const {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Controls } from '@xyflow/react'
|
|
2
1
|
import { shallowEqual } from 'fast-equals'
|
|
3
|
-
import { memo
|
|
2
|
+
import { memo } from 'react'
|
|
4
3
|
import { isNonNullish } from 'remeda'
|
|
5
4
|
import { useDiagramState } from '../hooks/useDiagramState'
|
|
6
5
|
import type { LikeC4DiagramProperties } from '../LikeC4Diagram.props'
|
|
@@ -10,26 +9,25 @@ import NotationPanel from '../ui/notation'
|
|
|
10
9
|
import { TopLeftPanel } from '../ui/top-left/TopLeftPanel'
|
|
11
10
|
import { XYFlowBackground } from './XYFlowBackground'
|
|
12
11
|
|
|
13
|
-
type XYFlowInnerProps =
|
|
12
|
+
type XYFlowInnerProps = {
|
|
14
13
|
background: NonNullable<LikeC4DiagramProperties['background']>
|
|
15
|
-
controls: boolean
|
|
16
14
|
showDiagramTitle: boolean
|
|
17
|
-
}
|
|
15
|
+
}
|
|
18
16
|
|
|
19
17
|
export const XYFlowInner = /* @__PURE__ */ memo(function XYFlowInnerR({
|
|
20
|
-
children,
|
|
21
18
|
background,
|
|
22
|
-
controls,
|
|
23
19
|
showDiagramTitle
|
|
24
20
|
}: XYFlowInnerProps) {
|
|
25
21
|
const {
|
|
26
22
|
showNotations,
|
|
27
23
|
isDynamicView,
|
|
24
|
+
showTopLeft,
|
|
28
25
|
isDynamicViewActive,
|
|
29
26
|
enableDynamicViewWalkthrough
|
|
30
27
|
} = useDiagramState(s => ({
|
|
31
28
|
showNotations: s.showNotations,
|
|
32
29
|
isDynamicView: s.isDynamicView,
|
|
30
|
+
showTopLeft: s.controls || (s.showNavigationButtons && !!s.onNavigateTo) || !!s.onBurgerMenuClick,
|
|
33
31
|
enableDynamicViewWalkthrough: s.enableDynamicViewWalkthrough,
|
|
34
32
|
isDynamicViewActive: isNonNullish(s.activeWalkthrough)
|
|
35
33
|
}))
|
|
@@ -40,12 +38,10 @@ export const XYFlowInner = /* @__PURE__ */ memo(function XYFlowInnerR({
|
|
|
40
38
|
return (
|
|
41
39
|
<>
|
|
42
40
|
{isBgWithPattern && <XYFlowBackground background={background} />}
|
|
43
|
-
{
|
|
44
|
-
<TopLeftPanel />
|
|
41
|
+
{showTopLeft && <TopLeftPanel />}
|
|
45
42
|
{isDiagramTitleVisible && <DiagramTitlePanel />}
|
|
46
43
|
{isDynamicView && enableDynamicViewWalkthrough && <DynamicViewWalkthrough />}
|
|
47
44
|
{showNotations && <NotationPanel />}
|
|
48
|
-
{children}
|
|
49
45
|
</>
|
|
50
46
|
)
|
|
51
47
|
}, shallowEqual)
|
|
@@ -22,6 +22,7 @@ import { isTruthy } from 'remeda'
|
|
|
22
22
|
import { useDiagramState, useDiagramStoreApi, useMantinePortalProps } from '../../hooks'
|
|
23
23
|
import { useLikeC4Model } from '../../likec4model/useLikeC4Model'
|
|
24
24
|
import type { RelationshipData } from '../types'
|
|
25
|
+
import { stopPropagation } from '../utils'
|
|
25
26
|
import * as edgesCss from './edges.css'
|
|
26
27
|
import { RelationshipsDropdownMenu } from './RelationshipsDropdownMenu'
|
|
27
28
|
|
|
@@ -125,9 +126,9 @@ const NotePopover = ({ notes, children }: PropsWithChildren<{ notes: string }>)
|
|
|
125
126
|
<PopoverDropdown
|
|
126
127
|
component={Stack}
|
|
127
128
|
p={'xs'}
|
|
128
|
-
onPointerDownCapture={
|
|
129
|
-
onClick={
|
|
130
|
-
onDoubleClick={
|
|
129
|
+
onPointerDownCapture={stopPropagation}
|
|
130
|
+
onClick={stopPropagation}
|
|
131
|
+
onDoubleClick={stopPropagation}
|
|
131
132
|
>
|
|
132
133
|
<ScrollAreaAutosize maw={450} mah={350} type="scroll" mx={'auto'} mt={2}>
|
|
133
134
|
<Text component="div" className={edgesCss.edgeNoteText} p={4}>{notes}</Text>
|
|
@@ -171,13 +172,13 @@ function NavigateToBtn({ viewId }: NavigateToBtnProps) {
|
|
|
171
172
|
className={clsx('nodrag nopan', edgesCss.cssNavigateBtn)}
|
|
172
173
|
size={'sm'}
|
|
173
174
|
radius="sm"
|
|
174
|
-
onPointerDownCapture={
|
|
175
|
+
onPointerDownCapture={stopPropagation}
|
|
175
176
|
onClick={event => {
|
|
176
177
|
event.stopPropagation()
|
|
177
178
|
diagramApi.getState().onNavigateTo?.(viewId, event)
|
|
178
179
|
}}
|
|
179
180
|
role="button"
|
|
180
|
-
onDoubleClick={
|
|
181
|
+
onDoubleClick={stopPropagation}
|
|
181
182
|
>
|
|
182
183
|
<IconZoomScan className={edgesCss.cssNavigateBtnIcon} />
|
|
183
184
|
</ActionIcon>
|
|
@@ -63,8 +63,9 @@ const toMarker = (arrowType?: RelationshipArrowType): EdgeMarkerType | undefined
|
|
|
63
63
|
}
|
|
64
64
|
switch (arrowType) {
|
|
65
65
|
case 'normal':
|
|
66
|
-
case 'crow':
|
|
67
66
|
return 'Arrow' as const
|
|
67
|
+
case 'crow':
|
|
68
|
+
return 'Crow' as const
|
|
68
69
|
case 'onormal':
|
|
69
70
|
return 'OArrow' as const
|
|
70
71
|
case 'diamond':
|
|
@@ -332,7 +333,7 @@ export const RelationshipEdge = memo<EdgeProps<XYFlowEdge>>(function Relationshi
|
|
|
332
333
|
const onRmbControlPointerDown = (index: number, e: ReactPointerEvent<SVGCircleElement>, domNode: HTMLDivElement) => {
|
|
333
334
|
const { xyflow } = diagramStore.getState()
|
|
334
335
|
|
|
335
|
-
if(controlPoints.length <= 1) {
|
|
336
|
+
if (controlPoints.length <= 1) {
|
|
336
337
|
return
|
|
337
338
|
}
|
|
338
339
|
|
|
@@ -349,7 +350,7 @@ export const RelationshipEdge = memo<EdgeProps<XYFlowEdge>>(function Relationshi
|
|
|
349
350
|
domNode.removeEventListener('pointerup', onPointerUp, {
|
|
350
351
|
capture: true
|
|
351
352
|
})
|
|
352
|
-
e.stopPropagation()
|
|
353
|
+
e.stopPropagation()
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
domNode.addEventListener('pointerup', onPointerUp, {
|
|
@@ -122,6 +122,22 @@ export function RelationshipsDropdownMenu({
|
|
|
122
122
|
{nested.map(renderRelationship)}
|
|
123
123
|
</>
|
|
124
124
|
)}
|
|
125
|
+
{
|
|
126
|
+
/* <Box pos={'absolute'} top={5} right={6}>
|
|
127
|
+
<ActionIcon
|
|
128
|
+
size={24}
|
|
129
|
+
variant="light"
|
|
130
|
+
onClick={e => {
|
|
131
|
+
e.stopPropagation()
|
|
132
|
+
diagramApi.getState().openOverlay({
|
|
133
|
+
edgeDetails: edge.id
|
|
134
|
+
})
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<IconArrowsMaximize style={{ width: '70%' }} />
|
|
138
|
+
</ActionIcon>
|
|
139
|
+
</Box> */
|
|
140
|
+
}
|
|
125
141
|
</MenuDropdown>
|
|
126
142
|
</Menu>
|
|
127
143
|
)
|
|
@@ -45,23 +45,17 @@ globalStyle(`:where(${isSelected}) ${container}[data-edge-hovered='true']`, {
|
|
|
45
45
|
[xyvars.edge.strokeWidth]: '4'
|
|
46
46
|
}
|
|
47
47
|
})
|
|
48
|
-
// globalStyle(`${container}[data-edge-hovered='true']`, {
|
|
49
|
-
// vars: {
|
|
50
|
-
// [xyvars.edge.stroke]: `color-mix(in srgb, ${vars.relation.lineColor}, white 35%)`
|
|
51
|
-
// },
|
|
52
|
-
// })
|
|
53
48
|
|
|
54
|
-
globalStyle(`.react-flow__edges > svg`, {
|
|
49
|
+
globalStyle(`.likec4-diagram .react-flow__edges > svg`, {
|
|
55
50
|
mixBlendMode: 'plus-lighter'
|
|
56
51
|
})
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// })
|
|
52
|
+
globalStyle(`:where([data-mantine-color-scheme="light"]) .likec4-diagram .react-flow__edges > svg`, {
|
|
53
|
+
mixBlendMode: 'screen'
|
|
54
|
+
})
|
|
61
55
|
|
|
62
56
|
export const dimmed = style({})
|
|
63
57
|
|
|
64
|
-
globalStyle(`.react-flow__edges > svg:has(${dimmed})`, {
|
|
58
|
+
globalStyle(`.likec4-diagram .react-flow__edges > svg:has(${dimmed})`, {
|
|
65
59
|
opacity: 0.6,
|
|
66
60
|
transition: 'opacity 600ms ease-in-out, filter 600ms ease-in-out',
|
|
67
61
|
transitionDelay: '200ms',
|
|
@@ -8,7 +8,17 @@ export const container = style({
|
|
|
8
8
|
height: '100%',
|
|
9
9
|
position: 'relative',
|
|
10
10
|
padding: 0,
|
|
11
|
-
margin: 0
|
|
11
|
+
margin: 0,
|
|
12
|
+
':before': {
|
|
13
|
+
content: ' ',
|
|
14
|
+
position: 'absolute',
|
|
15
|
+
top: 0,
|
|
16
|
+
left: -16,
|
|
17
|
+
width: 16,
|
|
18
|
+
height: 'min(100%, 80px)',
|
|
19
|
+
pointerEvents: 'all',
|
|
20
|
+
background: 'transparent'
|
|
21
|
+
}
|
|
12
22
|
})
|
|
13
23
|
|
|
14
24
|
export const nodeHandlerInCenter = style({
|
|
@@ -59,7 +69,7 @@ export const compoundBody = style({
|
|
|
59
69
|
}
|
|
60
70
|
},
|
|
61
71
|
':before': {
|
|
62
|
-
content: '
|
|
72
|
+
content: ' ',
|
|
63
73
|
position: 'absolute',
|
|
64
74
|
top: 0,
|
|
65
75
|
left: 0,
|
|
@@ -185,10 +195,6 @@ globalStyle(`${indicator} rect`, {
|
|
|
185
195
|
fill: 'none'
|
|
186
196
|
})
|
|
187
197
|
|
|
188
|
-
// globalStyle(`:where(.react-flow__node:focus-within:not(.selected)) ${indicator} rect`, {
|
|
189
|
-
// stroke: `color-mix(in srgb, ${vars.element.stroke} 30%, ${vars.element.loContrast})`,
|
|
190
|
-
// strokeWidth: 8
|
|
191
|
-
// })
|
|
192
198
|
const navigateBtnColor = createVar('navigateBtnColor')
|
|
193
199
|
|
|
194
200
|
globalStyle(`${container}`, {
|
|
@@ -209,26 +215,17 @@ export const navigateBtn = style({
|
|
|
209
215
|
top: 6,
|
|
210
216
|
cursor: 'pointer',
|
|
211
217
|
color: `var(--_compound-title-color,${navigateBtnColor})`,
|
|
212
|
-
|
|
213
|
-
opacity: 0.75,
|
|
214
|
-
transition: 'all 150ms ease-out',
|
|
215
|
-
transitionDelay: '0ms',
|
|
218
|
+
opacity: 'var(--ai-opacity)',
|
|
216
219
|
backgroundColor: 'var(--ai-bg)',
|
|
217
220
|
vars: {
|
|
218
|
-
'--ai-
|
|
221
|
+
'--ai-opacity': '0.75',
|
|
222
|
+
'--ai-bg-idle': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`,
|
|
223
|
+
'--ai-bg': `var(--ai-bg-idle)`,
|
|
219
224
|
'--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill} 65%, ${vars.element.stroke})`,
|
|
220
225
|
'--ai-hover': `color-mix(in srgb , ${vars.element.fill} 50%, ${vars.element.stroke})`
|
|
221
226
|
},
|
|
222
227
|
':hover': {
|
|
223
|
-
boxShadow: mantine.shadows.
|
|
224
|
-
transform: 'scale(1.42)',
|
|
225
|
-
opacity: 1,
|
|
226
|
-
transitionDelay: '0'
|
|
227
|
-
},
|
|
228
|
-
':active': {
|
|
229
|
-
transform: 'scale(1.1)',
|
|
230
|
-
opacity: 1,
|
|
231
|
-
transitionDelay: '0'
|
|
228
|
+
boxShadow: mantine.shadows.md
|
|
232
229
|
},
|
|
233
230
|
selectors: {
|
|
234
231
|
[`:where([data-mantine-color-scheme='light'] .likec4-compound-transparent) &`]: {
|
|
@@ -237,26 +234,6 @@ export const navigateBtn = style({
|
|
|
237
234
|
'--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 60%)`,
|
|
238
235
|
'--ai-hover': `color-mix(in srgb , ${vars.element.fill}, transparent 20%)`
|
|
239
236
|
}
|
|
240
|
-
},
|
|
241
|
-
[`:where(.react-flow__node:not(.dragging) ${container}:hover) &:not(:hover)`]: {
|
|
242
|
-
boxShadow: mantine.shadows.lg,
|
|
243
|
-
transform: 'scale(1.223)',
|
|
244
|
-
opacity: 1,
|
|
245
|
-
transitionDelay: '250ms',
|
|
246
|
-
vars: {
|
|
247
|
-
'--ai-bg': 'var(--ai-bg-hover)'
|
|
248
|
-
}
|
|
249
237
|
}
|
|
250
238
|
}
|
|
251
239
|
})
|
|
252
|
-
globalStyle(`${navigateBtn} svg.icon`, {
|
|
253
|
-
width: '75%',
|
|
254
|
-
height: '75%',
|
|
255
|
-
strokeWidth: 1.5
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
// globalStyle(`:where(.react-flow__node:not(.dragging)) ${cssIndicator}`, {
|
|
259
|
-
// visibility: 'visible',
|
|
260
|
-
// ":hover": {
|
|
261
|
-
// }
|
|
262
|
-
// })
|