@likec4/diagram 1.9.0 → 1.12.1
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 +20 -43
- package/src/LikeC4CustomColors.tsx +5 -2
- package/src/LikeC4Diagram.css.ts +1 -1
- package/src/LikeC4Diagram.props.ts +40 -94
- package/src/LikeC4Diagram.tsx +14 -4
- package/src/LikeC4ModelProvider.tsx +21 -0
- package/src/StaticLikeC4Diagram.tsx +1 -0
- package/src/globals.css.ts +2 -3
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useMantinePortalProps.ts +8 -0
- package/src/hooks/useXYFlow.ts +1 -2
- package/src/index.ts +9 -0
- package/src/likec4model/LikeC4ModelContext.ts +4 -0
- package/src/likec4model/index.ts +1 -0
- package/src/likec4model/useLikeC4Model.ts +58 -0
- package/src/state/diagram-to-xyflow.ts +1 -2
- package/src/state/diagramStore.ts +185 -66
- package/src/{theme.css.ts → theme-vars.ts} +11 -4
- package/src/ui/DiagramTitlePanel.css.ts +2 -3
- package/src/ui/DiagramTitlePanel.tsx +25 -3
- package/src/ui/DynamicViewWalkthrough.css.ts +28 -2
- package/src/ui/DynamicViewWalkthrough.tsx +97 -52
- package/src/ui/OptionsPanel.css.ts +1 -2
- package/src/ui/OptionsPanel.tsx +5 -2
- package/src/ui/notation/NotationPanel.css.ts +2 -3
- package/src/ui/notation/NotationPanel.tsx +10 -11
- package/src/ui/options/NodeOptions.tsx +4 -1
- package/src/ui/top-left/ChangeAutoLayoutButton.tsx +122 -71
- package/src/ui/top-left/TopLeftPanel.tsx +128 -117
- package/src/ui/top-left/_shared.ts +7 -6
- package/src/ui/top-left/styles.css.ts +48 -8
- package/src/utils/vector.ts +57 -0
- package/src/xyflow/FitviewOnDiagramChange.tsx +5 -4
- package/src/xyflow/SelectEdgesOnNodeFocus.tsx +7 -5
- package/src/xyflow/XYFlow.tsx +13 -17
- package/src/xyflow/XYFlowEvents.tsx +58 -58
- package/src/xyflow/XYFlowInner.tsx +4 -8
- package/src/xyflow/edges/EdgeLabel.tsx +185 -0
- package/src/xyflow/edges/RelationshipEdge.tsx +170 -137
- package/src/xyflow/edges/RelationshipsDropdownMenu.css.ts +60 -0
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +214 -0
- package/src/xyflow/edges/edges.css.ts +75 -7
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +1 -2
- package/src/xyflow/nodes/compound/CompoundNode.tsx +25 -8
- package/src/xyflow/nodes/element/ElementLink.css.ts +1 -1
- package/src/xyflow/nodes/element/ElementLink.tsx +4 -8
- package/src/xyflow/nodes/element/ElementNode.tsx +47 -8
- package/src/xyflow/nodes/element/element.css.ts +1 -2
- package/src/xyflow/nodes/shared/Toolbar.tsx +426 -0
- package/src/xyflow/types.ts +1 -24
- package/src/xyflow/useLayoutConstraints.ts +10 -6
- package/dist/LikeC4CustomColors.d.ts +0 -3
- package/dist/LikeC4CustomColors.props.d.ts +0 -5
- package/dist/LikeC4Diagram.css.d.ts +0 -4
- package/dist/LikeC4Diagram.d.ts +0 -7
- package/dist/LikeC4Diagram.props.d.ts +0 -154
- package/dist/StaticLikeC4Diagram.d.ts +0 -4
- package/dist/globals.css.d.ts +0 -1
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/useDiagramState.d.ts +0 -6
- package/dist/hooks/useSetState.d.ts +0 -3
- package/dist/hooks/useUpdateEffect.d.ts +0 -5
- package/dist/hooks/useXYFlow.d.ts +0 -267
- package/dist/index.d.ts +0 -9
- package/dist/index.mjs +0 -3684
- package/dist/mantine/EnsureMantine.d.ts +0 -8
- package/dist/mantine.css.d.ts +0 -3
- package/dist/state/DiagramContext.d.ts +0 -1192
- package/dist/state/diagram-to-xyflow.d.ts +0 -12
- package/dist/state/diagramStore.d.ts +0 -1268
- package/dist/style.css +0 -2139
- package/dist/theme.css.d.ts +0 -63
- package/dist/ui/DiagramTitlePanel.css.d.ts +0 -4
- package/dist/ui/DiagramTitlePanel.d.ts +0 -1
- package/dist/ui/DynamicViewWalkthrough.css.d.ts +0 -3
- package/dist/ui/DynamicViewWalkthrough.d.ts +0 -1
- package/dist/ui/OptionsPanel.css.d.ts +0 -1
- package/dist/ui/OptionsPanel.d.ts +0 -1
- package/dist/ui/notation/NotationPanel.css.d.ts +0 -6
- package/dist/ui/notation/NotationPanel.d.ts +0 -1
- package/dist/ui/notation/index.d.ts +0 -1
- package/dist/ui/options/EdgeOptions.d.ts +0 -19
- package/dist/ui/options/NodeOptions.d.ts +0 -19
- package/dist/ui/top-left/ChangeAutoLayoutButton.d.ts +0 -3
- package/dist/ui/top-left/TopLeftPanel.d.ts +0 -4
- package/dist/ui/top-left/_shared.d.ts +0 -5
- package/dist/ui/top-left/styles.css.d.ts +0 -3
- package/dist/xyflow/EdgeMarkers.d.ts +0 -12
- package/dist/xyflow/FitviewOnDiagramChange.d.ts +0 -4
- package/dist/xyflow/SelectEdgesOnNodeFocus.d.ts +0 -1
- package/dist/xyflow/XYFlow.d.ts +0 -12
- package/dist/xyflow/XYFlowBackground.d.ts +0 -11
- package/dist/xyflow/XYFlowEvents.d.ts +0 -161
- package/dist/xyflow/XYFlowInner.d.ts +0 -10
- package/dist/xyflow/const.d.ts +0 -7
- package/dist/xyflow/edges/RelationshipEdge.d.ts +0 -32
- package/dist/xyflow/edges/edges.css.d.ts +0 -12
- package/dist/xyflow/edges/utils.d.ts +0 -17
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +0 -11
- package/dist/xyflow/nodes/compound/CompoundNode.d.ts +0 -6
- package/dist/xyflow/nodes/compound/index.d.ts +0 -1
- package/dist/xyflow/nodes/element/ElementLink.css.d.ts +0 -3
- package/dist/xyflow/nodes/element/ElementLink.d.ts +0 -7
- package/dist/xyflow/nodes/element/ElementNode.d.ts +0 -6
- package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +0 -18
- package/dist/xyflow/nodes/element/element.css.d.ts +0 -19
- package/dist/xyflow/nodes/element/index.d.ts +0 -1
- package/dist/xyflow/nodes/shared/NavigateToBtn.d.ts +0 -5
- package/dist/xyflow/types.d.ts +0 -50
- package/dist/xyflow/useLayoutConstraints.d.ts +0 -9
- package/dist/xyflow/utils.d.ts +0 -18
- package/src/mantine.css.ts +0 -7
- package/src/reset.d.ts +0 -2
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BorderStyle,
|
|
3
|
+
defaultTheme,
|
|
4
|
+
type Element,
|
|
5
|
+
ElementShapes,
|
|
6
|
+
type Fqn,
|
|
7
|
+
type NonEmptyArray,
|
|
8
|
+
type ThemeColor,
|
|
9
|
+
type ViewChange
|
|
10
|
+
} from '@likec4/core'
|
|
11
|
+
import {
|
|
12
|
+
ActionIcon,
|
|
13
|
+
Box,
|
|
14
|
+
Button,
|
|
15
|
+
CheckIcon,
|
|
16
|
+
ColorSwatch,
|
|
17
|
+
Divider,
|
|
18
|
+
Flex,
|
|
19
|
+
Group,
|
|
20
|
+
keys,
|
|
21
|
+
Menu,
|
|
22
|
+
MenuDropdown,
|
|
23
|
+
MenuItem,
|
|
24
|
+
MenuTarget,
|
|
25
|
+
Paper,
|
|
26
|
+
Popover,
|
|
27
|
+
PopoverDropdown,
|
|
28
|
+
type PopoverProps,
|
|
29
|
+
PopoverTarget,
|
|
30
|
+
rem,
|
|
31
|
+
SegmentedControl,
|
|
32
|
+
Slider,
|
|
33
|
+
Space,
|
|
34
|
+
Stack,
|
|
35
|
+
Text,
|
|
36
|
+
Tooltip,
|
|
37
|
+
TooltipGroup
|
|
38
|
+
} from '@mantine/core'
|
|
39
|
+
import { IconCheck, IconFileSymlink, IconSelector } from '@tabler/icons-react'
|
|
40
|
+
import { NodeToolbar, type NodeToolbarProps } from '@xyflow/react'
|
|
41
|
+
import clsx from 'clsx'
|
|
42
|
+
import { useEffect, useState } from 'react'
|
|
43
|
+
import { useDiagramState, useDiagramStoreApi, useMantinePortalProps, useUpdateEffect } from '../../../hooks'
|
|
44
|
+
|
|
45
|
+
const {
|
|
46
|
+
primary,
|
|
47
|
+
secondary,
|
|
48
|
+
muted,
|
|
49
|
+
...otherColors
|
|
50
|
+
} = defaultTheme.elements
|
|
51
|
+
|
|
52
|
+
const themedColors = [
|
|
53
|
+
{ key: 'primary', value: primary.fill },
|
|
54
|
+
{ key: 'secondary', value: secondary.fill },
|
|
55
|
+
{ key: 'muted', value: muted.fill }
|
|
56
|
+
] satisfies Array<{ key: ThemeColor; value: string }>
|
|
57
|
+
|
|
58
|
+
const colors = keys(otherColors).map(key => ({
|
|
59
|
+
key,
|
|
60
|
+
value: defaultTheme.elements[key].fill
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
type ThemeColorKey = typeof themedColors[0]['key']
|
|
64
|
+
type ColorKey = typeof colors[0]['key']
|
|
65
|
+
|
|
66
|
+
const stopEventPropagation = (e: React.MouseEvent) => e.stopPropagation()
|
|
67
|
+
|
|
68
|
+
type ToolbarProps = NodeToolbarProps & {
|
|
69
|
+
element: Element
|
|
70
|
+
onColorPreview: (color: ThemeColor | null) => void
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type OnStyleChange = (style: ViewChange.ChangeElementStyle['style']) => void
|
|
74
|
+
|
|
75
|
+
export function CompoundToolbar({
|
|
76
|
+
element,
|
|
77
|
+
onColorPreview,
|
|
78
|
+
...props
|
|
79
|
+
}: ToolbarProps) {
|
|
80
|
+
const targets = [element.id] as NonEmptyArray<Fqn>
|
|
81
|
+
const diagramApi = useDiagramStoreApi()
|
|
82
|
+
const { hasGoToSource } = useDiagramState(s => ({
|
|
83
|
+
hasGoToSource: !!s.onOpenSourceElement
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
const onChange: OnStyleChange = (style) => {
|
|
87
|
+
diagramApi.getState().triggerChangeElementStyle({
|
|
88
|
+
op: 'change-element-style',
|
|
89
|
+
style,
|
|
90
|
+
targets
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Toolbar element={element} {...props}>
|
|
96
|
+
<ColorButton
|
|
97
|
+
element={element}
|
|
98
|
+
isOpacityEditable
|
|
99
|
+
onColorPreview={onColorPreview}
|
|
100
|
+
onChange={onChange}
|
|
101
|
+
position="left-start"
|
|
102
|
+
/>
|
|
103
|
+
<BorderStyleOption
|
|
104
|
+
elementBorderStyle={element.style?.border}
|
|
105
|
+
onChange={onChange}
|
|
106
|
+
/>
|
|
107
|
+
{hasGoToSource && <GoToSourceButton elementId={element.id} />}
|
|
108
|
+
</Toolbar>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function Toolbar({ element, children, ...props }: Omit<ToolbarProps, 'onColorPreview'>) {
|
|
113
|
+
return (
|
|
114
|
+
<NodeToolbar {...props}>
|
|
115
|
+
<Paper
|
|
116
|
+
className={clsx('nodrag', 'nopan')}
|
|
117
|
+
px={5}
|
|
118
|
+
pb={8}
|
|
119
|
+
pt={4}
|
|
120
|
+
radius={'sm'}
|
|
121
|
+
shadow="xl"
|
|
122
|
+
onDoubleClickCapture={stopEventPropagation}
|
|
123
|
+
onPointerDown={stopEventPropagation}
|
|
124
|
+
onClick={stopEventPropagation}
|
|
125
|
+
onDoubleClick={stopEventPropagation}
|
|
126
|
+
withBorder>
|
|
127
|
+
<Stack gap={4}>
|
|
128
|
+
<Box pl={2}>
|
|
129
|
+
<Text c="dimmed" fz={10} fw={500}>{element.id}</Text>
|
|
130
|
+
</Box>
|
|
131
|
+
<Group gap={3}>
|
|
132
|
+
{children}
|
|
133
|
+
</Group>
|
|
134
|
+
</Stack>
|
|
135
|
+
</Paper>
|
|
136
|
+
</NodeToolbar>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function ElementToolbar({
|
|
141
|
+
element,
|
|
142
|
+
onColorPreview,
|
|
143
|
+
...props
|
|
144
|
+
}: ToolbarProps) {
|
|
145
|
+
const targets = [element.id] as NonEmptyArray<Fqn>
|
|
146
|
+
const diagramApi = useDiagramStoreApi()
|
|
147
|
+
const { hasGoToSource } = useDiagramState(s => ({
|
|
148
|
+
hasGoToSource: !!s.onOpenSourceElement
|
|
149
|
+
}))
|
|
150
|
+
const portalProps = useMantinePortalProps()
|
|
151
|
+
|
|
152
|
+
const onChange: OnStyleChange = (style) => {
|
|
153
|
+
diagramApi.getState().triggerChangeElementStyle({
|
|
154
|
+
op: 'change-element-style',
|
|
155
|
+
style,
|
|
156
|
+
targets
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<Toolbar element={element} {...props}>
|
|
162
|
+
<Menu
|
|
163
|
+
openDelay={300}
|
|
164
|
+
closeDelay={450}
|
|
165
|
+
floatingStrategy={'fixed'}
|
|
166
|
+
closeOnClickOutside
|
|
167
|
+
clickOutsideEvents={['pointerdown', 'mousedown', 'click']}
|
|
168
|
+
closeOnEscape
|
|
169
|
+
closeOnItemClick={false}
|
|
170
|
+
position="bottom-start"
|
|
171
|
+
offset={2}
|
|
172
|
+
styles={{
|
|
173
|
+
item: {
|
|
174
|
+
padding: 'calc(var(--mantine-spacing-xs) / 1.5) var(--mantine-spacing-xs)'
|
|
175
|
+
}
|
|
176
|
+
}}
|
|
177
|
+
{...portalProps}
|
|
178
|
+
>
|
|
179
|
+
<MenuTarget>
|
|
180
|
+
<Button
|
|
181
|
+
variant="light"
|
|
182
|
+
color="gray"
|
|
183
|
+
size="compact-xs"
|
|
184
|
+
rightSection={<IconSelector size={12} />}
|
|
185
|
+
>
|
|
186
|
+
{element.shape}
|
|
187
|
+
</Button>
|
|
188
|
+
</MenuTarget>
|
|
189
|
+
<MenuDropdown
|
|
190
|
+
// className={css.menuDropdown}
|
|
191
|
+
// onPointerDownCapture={stopEventPropagation}
|
|
192
|
+
// onPointerDown={stopEventPropagation}
|
|
193
|
+
onDoubleClick={stopEventPropagation}
|
|
194
|
+
onClick={stopEventPropagation}
|
|
195
|
+
>
|
|
196
|
+
{ElementShapes.map(shape => (
|
|
197
|
+
<MenuItem
|
|
198
|
+
fz={12}
|
|
199
|
+
fw={500}
|
|
200
|
+
key={shape}
|
|
201
|
+
value={shape}
|
|
202
|
+
rightSection={element.shape === shape ? <IconCheck size={12} /> : undefined}
|
|
203
|
+
onClick={e => {
|
|
204
|
+
e.stopPropagation()
|
|
205
|
+
onChange({ shape })
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
{shape}
|
|
209
|
+
</MenuItem>
|
|
210
|
+
))}
|
|
211
|
+
</MenuDropdown>
|
|
212
|
+
</Menu>
|
|
213
|
+
<ColorButton
|
|
214
|
+
element={element}
|
|
215
|
+
onColorPreview={onColorPreview}
|
|
216
|
+
onChange={onChange}
|
|
217
|
+
position="right-end"
|
|
218
|
+
/>
|
|
219
|
+
{hasGoToSource && <GoToSourceButton elementId={element.id} />}
|
|
220
|
+
</Toolbar>
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
type ColorButtonProps = Omit<PopoverProps, 'onChange'> & {
|
|
225
|
+
element: Element
|
|
226
|
+
isOpacityEditable?: boolean
|
|
227
|
+
onColorPreview: (color: ThemeColor | null) => void
|
|
228
|
+
onChange: OnStyleChange
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function GoToSourceButton({ elementId }: { elementId: Fqn }) {
|
|
232
|
+
const diagramApi = useDiagramStoreApi()
|
|
233
|
+
return (
|
|
234
|
+
<ActionIcon
|
|
235
|
+
size={'sm'}
|
|
236
|
+
variant="subtle"
|
|
237
|
+
color="gray"
|
|
238
|
+
onClick={e => {
|
|
239
|
+
e.stopPropagation()
|
|
240
|
+
diagramApi.getState().onOpenSourceElement?.(elementId)
|
|
241
|
+
}}>
|
|
242
|
+
<IconFileSymlink size={'70%'} stroke={1.8} />
|
|
243
|
+
</ActionIcon>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function ColorButton({
|
|
248
|
+
element,
|
|
249
|
+
onColorPreview,
|
|
250
|
+
isOpacityEditable = false,
|
|
251
|
+
onChange,
|
|
252
|
+
...props
|
|
253
|
+
}: ColorButtonProps) {
|
|
254
|
+
const elementColor: ThemeColor = (element.color as ThemeColor) ?? 'primary'
|
|
255
|
+
return (
|
|
256
|
+
<Popover
|
|
257
|
+
clickOutsideEvents={['pointerdown', 'mousedown', 'click']}
|
|
258
|
+
position="right-end"
|
|
259
|
+
offset={2}
|
|
260
|
+
{...props}
|
|
261
|
+
>
|
|
262
|
+
<PopoverTarget>
|
|
263
|
+
<Button variant="subtle" color="gray" size="compact-xs" px={3}>
|
|
264
|
+
<ColorSwatch
|
|
265
|
+
color={defaultTheme.elements[elementColor]?.fill}
|
|
266
|
+
size={14}
|
|
267
|
+
withShadow
|
|
268
|
+
style={{ color: '#fff', cursor: 'pointer' }}
|
|
269
|
+
/>
|
|
270
|
+
</Button>
|
|
271
|
+
</PopoverTarget>
|
|
272
|
+
<PopoverDropdown p={'xs'}>
|
|
273
|
+
<ColorSwatches
|
|
274
|
+
elementColor={elementColor}
|
|
275
|
+
onColorPreview={onColorPreview}
|
|
276
|
+
onChange={(color) => onChange({ color })}
|
|
277
|
+
/>
|
|
278
|
+
{isOpacityEditable && (
|
|
279
|
+
<>
|
|
280
|
+
<Space h={'xs'} />
|
|
281
|
+
<Divider label="opacity" labelPosition="left" />
|
|
282
|
+
<Space h={'xs'} />
|
|
283
|
+
<OpacityOption
|
|
284
|
+
elementOpacity={element.style?.opacity}
|
|
285
|
+
onOpacityChange={(opacity) => {
|
|
286
|
+
onChange({ opacity })
|
|
287
|
+
}} />
|
|
288
|
+
</>
|
|
289
|
+
)}
|
|
290
|
+
</PopoverDropdown>
|
|
291
|
+
</Popover>
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function ColorSwatches({
|
|
296
|
+
elementColor,
|
|
297
|
+
onColorPreview,
|
|
298
|
+
onChange
|
|
299
|
+
}: {
|
|
300
|
+
elementColor: ThemeColor
|
|
301
|
+
onColorPreview: (color: ThemeColor | null) => void
|
|
302
|
+
onChange: (color: ThemeColor) => void
|
|
303
|
+
}) {
|
|
304
|
+
const changeColor = (color: ColorKey | ThemeColorKey) => (e: React.MouseEvent) => {
|
|
305
|
+
e.stopPropagation()
|
|
306
|
+
onColorPreview(null)
|
|
307
|
+
if (elementColor === color) {
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
onChange(color)
|
|
311
|
+
}
|
|
312
|
+
return (
|
|
313
|
+
<Stack gap={2} onMouseLeave={() => onColorPreview(null)}>
|
|
314
|
+
<TooltipGroup openDelay={1000} closeDelay={300}>
|
|
315
|
+
<Flex maw={120} gap="6" justify="flex-start" align="flex-start" direction="row" wrap="wrap">
|
|
316
|
+
{themedColors.map(({ key, value }) => (
|
|
317
|
+
<Tooltip
|
|
318
|
+
key={key}
|
|
319
|
+
label={key}
|
|
320
|
+
fz={'xs'}
|
|
321
|
+
color="dark"
|
|
322
|
+
offset={2}
|
|
323
|
+
transitionProps={{ duration: 140, transition: 'slide-up' }}>
|
|
324
|
+
<ColorSwatch
|
|
325
|
+
color={value}
|
|
326
|
+
size={18}
|
|
327
|
+
withShadow
|
|
328
|
+
onMouseEnter={() => onColorPreview(key)}
|
|
329
|
+
onClick={changeColor(key)}
|
|
330
|
+
style={{ color: '#fff', cursor: 'pointer' }}
|
|
331
|
+
>
|
|
332
|
+
{elementColor === key && <CheckIcon style={{ width: rem(10), height: rem(10) }} />}
|
|
333
|
+
</ColorSwatch>
|
|
334
|
+
</Tooltip>
|
|
335
|
+
))}
|
|
336
|
+
</Flex>
|
|
337
|
+
|
|
338
|
+
<Flex
|
|
339
|
+
mt="sm"
|
|
340
|
+
maw={110}
|
|
341
|
+
gap="6"
|
|
342
|
+
justify="flex-start"
|
|
343
|
+
align="flex-start"
|
|
344
|
+
direction="row"
|
|
345
|
+
wrap="wrap">
|
|
346
|
+
{colors.map(({ key, value }) => (
|
|
347
|
+
<Tooltip
|
|
348
|
+
key={key}
|
|
349
|
+
label={key}
|
|
350
|
+
fz={'xs'}
|
|
351
|
+
color="dark"
|
|
352
|
+
offset={2}
|
|
353
|
+
transitionProps={{ duration: 140, transition: 'slide-up' }}>
|
|
354
|
+
<ColorSwatch
|
|
355
|
+
color={value}
|
|
356
|
+
size={18}
|
|
357
|
+
onMouseEnter={() => onColorPreview(key)}
|
|
358
|
+
onClick={changeColor(key)}
|
|
359
|
+
style={{ color: '#fff', cursor: 'pointer' }}
|
|
360
|
+
>
|
|
361
|
+
{elementColor === key && <CheckIcon style={{ width: rem(10), height: rem(10) }} />}
|
|
362
|
+
</ColorSwatch>
|
|
363
|
+
</Tooltip>
|
|
364
|
+
))}
|
|
365
|
+
</Flex>
|
|
366
|
+
</TooltipGroup>
|
|
367
|
+
</Stack>
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function BorderStyleOption({
|
|
372
|
+
elementBorderStyle = 'dashed',
|
|
373
|
+
onChange
|
|
374
|
+
}: {
|
|
375
|
+
elementBorderStyle: BorderStyle | undefined
|
|
376
|
+
onChange: OnStyleChange
|
|
377
|
+
}) {
|
|
378
|
+
const [value, setValue] = useState(elementBorderStyle)
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
setValue(elementBorderStyle)
|
|
381
|
+
}, [elementBorderStyle])
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<Box>
|
|
385
|
+
<SegmentedControl
|
|
386
|
+
size="xs"
|
|
387
|
+
fullWidth
|
|
388
|
+
withItemsBorders={false}
|
|
389
|
+
value={value}
|
|
390
|
+
onChange={v => {
|
|
391
|
+
const border = v as BorderStyle
|
|
392
|
+
setValue(border)
|
|
393
|
+
onChange({ border })
|
|
394
|
+
}}
|
|
395
|
+
data={[
|
|
396
|
+
{ label: 'Solid', value: 'solid' },
|
|
397
|
+
{ label: 'Dashed', value: 'dashed' },
|
|
398
|
+
{ label: 'Dotted', value: 'dotted' },
|
|
399
|
+
{ label: 'None', value: 'none' }
|
|
400
|
+
]}
|
|
401
|
+
/>
|
|
402
|
+
</Box>
|
|
403
|
+
)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function OpacityOption({
|
|
407
|
+
elementOpacity = 100,
|
|
408
|
+
onOpacityChange
|
|
409
|
+
}: {
|
|
410
|
+
elementOpacity: number | undefined
|
|
411
|
+
onOpacityChange: (opacity: number) => void
|
|
412
|
+
}) {
|
|
413
|
+
const [value, setValue] = useState(elementOpacity)
|
|
414
|
+
useUpdateEffect(() => {
|
|
415
|
+
setValue(elementOpacity)
|
|
416
|
+
}, [elementOpacity])
|
|
417
|
+
|
|
418
|
+
return (
|
|
419
|
+
<Slider
|
|
420
|
+
size={'sm'}
|
|
421
|
+
color={'dark'}
|
|
422
|
+
value={value}
|
|
423
|
+
onChange={setValue}
|
|
424
|
+
onChangeEnd={onOpacityChange} />
|
|
425
|
+
)
|
|
426
|
+
}
|
package/src/xyflow/types.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import type { BBox, DiagramEdge, DiagramNode, Fqn, XYPoint } from '@likec4/core'
|
|
2
|
-
import type {
|
|
3
|
-
Edge,
|
|
4
|
-
EdgeMouseHandler,
|
|
5
|
-
InternalNode,
|
|
6
|
-
Node,
|
|
7
|
-
NodeMouseHandler,
|
|
8
|
-
OnMoveEnd,
|
|
9
|
-
OnMoveStart,
|
|
10
|
-
ReactFlowInstance,
|
|
11
|
-
ReactFlowState
|
|
12
|
-
} from '@xyflow/react'
|
|
2
|
+
import type { Edge, InternalNode, Node, ReactFlowInstance, ReactFlowState } from '@xyflow/react'
|
|
13
3
|
import { isNode } from '@xyflow/react'
|
|
14
|
-
import type { MouseEvent as ReactMouseEvent } from 'react'
|
|
15
4
|
import { isTruthy } from 'remeda'
|
|
16
5
|
import type { Simplify } from 'type-fest'
|
|
17
6
|
|
|
@@ -44,7 +33,6 @@ export type RelationshipData = {
|
|
|
44
33
|
edge: DiagramEdge
|
|
45
34
|
// if set - edge was changed by user
|
|
46
35
|
controlPoints: XYPoint[] | null
|
|
47
|
-
stepNum: number | null
|
|
48
36
|
label: null | {
|
|
49
37
|
bbox: BBox
|
|
50
38
|
text: string
|
|
@@ -69,14 +57,3 @@ export namespace XYFlowEdge {
|
|
|
69
57
|
|
|
70
58
|
export type XYFlowInstance = ReactFlowInstance<XYFlowNode, XYFlowEdge>
|
|
71
59
|
export type XYFlowState = ReactFlowState<XYFlowNode, XYFlowEdge>
|
|
72
|
-
|
|
73
|
-
export type XYFlowEventHandlers = {
|
|
74
|
-
onPanelClick: (event: ReactMouseEvent) => void
|
|
75
|
-
onNodeContextMenu: NodeMouseHandler<XYFlowNode>
|
|
76
|
-
onEdgeContextMenu: EdgeMouseHandler<XYFlowEdge>
|
|
77
|
-
onPaneContextMenu: (event: ReactMouseEvent | MouseEvent) => void
|
|
78
|
-
onNodeClick: NodeMouseHandler<XYFlowNode>
|
|
79
|
-
onEdgeClick: EdgeMouseHandler<XYFlowEdge>
|
|
80
|
-
onMoveStart: OnMoveStart
|
|
81
|
-
onMoveEnd: OnMoveEnd
|
|
82
|
-
}
|
|
@@ -181,12 +181,16 @@ function createLayoutConstraints(
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
const draggingNodes = new Set(nodeLookup.values()
|
|
185
|
+
.filter(x => x.dragging === true || x.id === draggingNodeId || x.selected)
|
|
186
|
+
.map(x => x.id))
|
|
187
|
+
|
|
184
188
|
while (traverse.length > 0) {
|
|
185
189
|
const { xynode, parent } = traverse.shift()!
|
|
186
|
-
const isDragging =
|
|
190
|
+
const isDragging = draggingNodes.has(xynode.id)
|
|
187
191
|
|
|
188
192
|
// Traverse children if the node is a compound, not dragging, and is an ancestor of the dragging node
|
|
189
|
-
const shouldTraverse = !isDragging && xynode.type === 'compound' && isAncestor(xynode.id,
|
|
193
|
+
const shouldTraverse = !isDragging && xynode.type === 'compound' && draggingNodes.values().some(x => isAncestor(xynode.id, x))
|
|
190
194
|
|
|
191
195
|
const rect = shouldTraverse ? new Compound(solver, xynode, parent) : new Leaf(solver, xynode, parent, isDragging)
|
|
192
196
|
rects.set(xynode.id, rect)
|
|
@@ -203,8 +207,7 @@ function createLayoutConstraints(
|
|
|
203
207
|
|
|
204
208
|
solver.updateVariables()
|
|
205
209
|
solver.maxIterations = 1000
|
|
206
|
-
|
|
207
|
-
const rectsToUpdate = [...rects.values()].filter(r => r.id !== draggingNodeId)
|
|
210
|
+
const rectsToUpdate = [...rects.values()].filter(r => !draggingNodes.has(r.id))
|
|
208
211
|
|
|
209
212
|
function updateXYFlowNodes() {
|
|
210
213
|
solver.updateVariables()
|
|
@@ -243,8 +246,8 @@ function createLayoutConstraints(
|
|
|
243
246
|
animationFrameId = null
|
|
244
247
|
const pos = nodeLookup.get(xynode.id)!.internals.positionAbsolute
|
|
245
248
|
const rect = nonNullable(rects.get(xynode.id))
|
|
246
|
-
solver.suggestValue(rect.minX, Math.
|
|
247
|
-
solver.suggestValue(rect.minY, Math.
|
|
249
|
+
solver.suggestValue(rect.minX, Math.floor(pos.x))
|
|
250
|
+
solver.suggestValue(rect.minY, Math.floor(pos.y))
|
|
248
251
|
updateXYFlowNodes()
|
|
249
252
|
})
|
|
250
253
|
}
|
|
@@ -273,6 +276,7 @@ export function useLayoutConstraints(): LayoutConstraints {
|
|
|
273
276
|
solverRef.current?.onNodeDrag(xynode)
|
|
274
277
|
},
|
|
275
278
|
onNodeDragStop: (_event, _xynode) => {
|
|
279
|
+
solverRef.current?.updateXYFlowNodes()
|
|
276
280
|
diagramApi.getState().scheduleSaveManualLayout()
|
|
277
281
|
solverRef.current = undefined
|
|
278
282
|
}
|
package/dist/LikeC4Diagram.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { LikeC4DiagramEventHandlers, LikeC4DiagramProperties } from './LikeC4Diagram.props';
|
|
2
|
-
|
|
3
|
-
export type LikeC4DiagramProps = LikeC4DiagramProperties & LikeC4DiagramEventHandlers;
|
|
4
|
-
export declare function LikeC4Diagram({ view, className, fitView, fitViewPadding, readonly, pannable, zoomable, nodesSelectable, nodesDraggable, background, controls, showElementLinks, showDiagramTitle, showNotations, enableDynamicViewWalkthrough, enableFocusMode, initialWidth, initialHeight, keepAspectRatio, experimentalEdgeEditing, onCanvasClick, onCanvasContextMenu, onCanvasDblClick, onEdgeClick, onChange, onEdgeContextMenu, onNavigateTo, onNodeClick, onNodeContextMenu, onOpenSourceElement, onOpenSourceView, onOpenSourceRelation, onBurgerMenuClick, renderIcon, where, showNavigationButtons }: LikeC4DiagramProps): import("react/jsx-runtime").JSX.Element;
|
|
5
|
-
export declare namespace LikeC4Diagram {
|
|
6
|
-
var displayName: string;
|
|
7
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { DiagramEdge, DiagramNode, DiagramView, Fqn, RelationID, ViewChange, ViewID, WhereOperator } from '@likec4/core';
|
|
2
|
-
import { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
|
3
|
-
import { XYFlowEdge, XYFlowNode } from './xyflow/types';
|
|
4
|
-
import { XYBackground } from './xyflow/XYFlowBackground';
|
|
5
|
-
|
|
6
|
-
export type { WhereOperator };
|
|
7
|
-
export type DiagramNodeWithNavigate = Omit<DiagramNode, 'navigateTo'> & {
|
|
8
|
-
navigateTo: ViewID;
|
|
9
|
-
};
|
|
10
|
-
type ElementIconNodeProps = {
|
|
11
|
-
id: string;
|
|
12
|
-
title: string;
|
|
13
|
-
icon?: string | undefined;
|
|
14
|
-
};
|
|
15
|
-
export type ElementIconRenderer = (props: {
|
|
16
|
-
node: ElementIconNodeProps;
|
|
17
|
-
}) => ReactNode;
|
|
18
|
-
export type OnNavigateTo = {
|
|
19
|
-
(to: ViewID, event?: ReactMouseEvent, element?: DiagramNodeWithNavigate, xynode?: XYFlowNode): void;
|
|
20
|
-
};
|
|
21
|
-
export type OnNodeClick = (event: {
|
|
22
|
-
element: DiagramNode;
|
|
23
|
-
xynode: XYFlowNode;
|
|
24
|
-
event: ReactMouseEvent;
|
|
25
|
-
}) => void;
|
|
26
|
-
export type OnEdgeClick = (event: {
|
|
27
|
-
edge: DiagramEdge;
|
|
28
|
-
xyedge: XYFlowEdge;
|
|
29
|
-
event: ReactMouseEvent;
|
|
30
|
-
}) => void;
|
|
31
|
-
/**
|
|
32
|
-
* On pane/canvas click (not on any node or edge)
|
|
33
|
-
*/
|
|
34
|
-
export type OnCanvasClick = (event: ReactMouseEvent) => void;
|
|
35
|
-
export type ChangeEvent = {
|
|
36
|
-
change: ViewChange;
|
|
37
|
-
};
|
|
38
|
-
export type OnChange = {
|
|
39
|
-
(event: ChangeEvent): void;
|
|
40
|
-
};
|
|
41
|
-
export type LikeC4ColorScheme = 'light' | 'dark';
|
|
42
|
-
export interface LikeC4DiagramProperties {
|
|
43
|
-
view: DiagramView;
|
|
44
|
-
className?: string | undefined;
|
|
45
|
-
/**
|
|
46
|
-
* Controls color scheme used for styling the flow
|
|
47
|
-
* By default inherits from system or surrounding MantineProvider
|
|
48
|
-
*
|
|
49
|
-
* @example 'light' | 'dark'
|
|
50
|
-
*/
|
|
51
|
-
/**
|
|
52
|
-
* Show/hide controls menu
|
|
53
|
-
* @default false
|
|
54
|
-
*/
|
|
55
|
-
controls?: boolean | undefined;
|
|
56
|
-
/**
|
|
57
|
-
* Enable/disable panning
|
|
58
|
-
* @default true
|
|
59
|
-
*/
|
|
60
|
-
pannable?: boolean | undefined;
|
|
61
|
-
/**
|
|
62
|
-
* Enable/disable zooming
|
|
63
|
-
* @default true
|
|
64
|
-
*/
|
|
65
|
-
zoomable?: boolean | undefined;
|
|
66
|
-
/**
|
|
67
|
-
* Disable any editing (dragging still can be enabled with `nodesDraggable`)
|
|
68
|
-
* @default true
|
|
69
|
-
*/
|
|
70
|
-
readonly?: boolean | undefined;
|
|
71
|
-
/**
|
|
72
|
-
* If set, initial viewport will show all nodes & edges
|
|
73
|
-
* @default true
|
|
74
|
-
*/
|
|
75
|
-
fitView?: boolean | undefined;
|
|
76
|
-
/**
|
|
77
|
-
* Seems like this is percentage of the view size
|
|
78
|
-
* @default 0
|
|
79
|
-
*/
|
|
80
|
-
fitViewPadding?: number | undefined;
|
|
81
|
-
nodesSelectable?: boolean | undefined;
|
|
82
|
-
/**
|
|
83
|
-
* @default false
|
|
84
|
-
*/
|
|
85
|
-
nodesDraggable?: boolean | undefined;
|
|
86
|
-
initialWidth?: number | undefined;
|
|
87
|
-
initialHeight?: number | undefined;
|
|
88
|
-
/**
|
|
89
|
-
* Keep aspect ratio of the diagram
|
|
90
|
-
* @default false
|
|
91
|
-
*/
|
|
92
|
-
keepAspectRatio?: boolean | undefined;
|
|
93
|
-
/**
|
|
94
|
-
* Background pattern
|
|
95
|
-
* @default 'dots'
|
|
96
|
-
*/
|
|
97
|
-
background?: 'transparent' | 'solid' | XYBackground | undefined;
|
|
98
|
-
/**
|
|
99
|
-
* Display hovercards with element links
|
|
100
|
-
* @default true
|
|
101
|
-
*/
|
|
102
|
-
showElementLinks?: boolean | undefined;
|
|
103
|
-
/**
|
|
104
|
-
* Display panel with diagram title / description
|
|
105
|
-
* @default true
|
|
106
|
-
*/
|
|
107
|
-
showDiagramTitle?: boolean | undefined;
|
|
108
|
-
/**
|
|
109
|
-
* Show back/forward navigation buttons
|
|
110
|
-
* @default false
|
|
111
|
-
*/
|
|
112
|
-
showNavigationButtons?: undefined | boolean;
|
|
113
|
-
/**
|
|
114
|
-
* Display notations of the view
|
|
115
|
-
* @default true
|
|
116
|
-
*/
|
|
117
|
-
showNotations?: boolean | undefined;
|
|
118
|
-
/**
|
|
119
|
-
* If double click on a node should enable focus mode, i.e. highlight incoming/outgoing edges
|
|
120
|
-
* @default true
|
|
121
|
-
*/
|
|
122
|
-
enableFocusMode?: boolean | undefined;
|
|
123
|
-
/**
|
|
124
|
-
* If Walkthrough for dynamic views should be enabled
|
|
125
|
-
* @default false
|
|
126
|
-
*/
|
|
127
|
-
enableDynamicViewWalkthrough?: boolean | undefined;
|
|
128
|
-
/**
|
|
129
|
-
* Experimental feature to enable edge editing
|
|
130
|
-
* @default false
|
|
131
|
-
*/
|
|
132
|
-
experimentalEdgeEditing?: boolean | undefined;
|
|
133
|
-
/**
|
|
134
|
-
* Render custom icon for a node
|
|
135
|
-
* By default, if icon is http:// or https://, it will be rendered as an image
|
|
136
|
-
*/
|
|
137
|
-
renderIcon?: ElementIconRenderer | undefined;
|
|
138
|
-
where?: WhereOperator<string, string> | undefined;
|
|
139
|
-
}
|
|
140
|
-
export interface LikeC4DiagramEventHandlers {
|
|
141
|
-
onChange?: OnChange | null | undefined;
|
|
142
|
-
onNavigateTo?: OnNavigateTo | null | undefined;
|
|
143
|
-
onNodeClick?: OnNodeClick | null | undefined;
|
|
144
|
-
onNodeContextMenu?: OnNodeClick | null | undefined;
|
|
145
|
-
onCanvasContextMenu?: OnCanvasClick | null | undefined;
|
|
146
|
-
onEdgeClick?: OnEdgeClick | null | undefined;
|
|
147
|
-
onEdgeContextMenu?: OnEdgeClick | null | undefined;
|
|
148
|
-
onCanvasClick?: OnCanvasClick | null | undefined;
|
|
149
|
-
onCanvasDblClick?: OnCanvasClick | null | undefined;
|
|
150
|
-
onBurgerMenuClick?: null | undefined | (() => void);
|
|
151
|
-
onOpenSourceView?: null | undefined | (() => void);
|
|
152
|
-
onOpenSourceElement?: null | undefined | ((fqn: Fqn) => void);
|
|
153
|
-
onOpenSourceRelation?: null | undefined | ((id: RelationID) => void);
|
|
154
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { LikeC4DiagramProperties } from './LikeC4Diagram.props';
|
|
2
|
-
|
|
3
|
-
export type StaticLikeC4DiagramProps = Pick<LikeC4DiagramProperties, 'view' | 'keepAspectRatio' | 'className' | 'fitView' | 'fitViewPadding' | 'background' | 'initialWidth' | 'initialHeight' | 'renderIcon' | 'where'>;
|
|
4
|
-
export declare function StaticLikeC4Diagram({ view, fitView, fitViewPadding, background, ...rest }: StaticLikeC4DiagramProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/globals.css.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const rootClassName = "likec4-diagram-root";
|