@likec4/diagram 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LikeC4Diagram.css.d.ts +0 -1
- package/dist/LikeC4Diagram.props.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1398 -1114
- package/dist/state/DiagramContext.d.ts +86 -4159
- package/dist/state/diagramStore.d.ts +88 -4162
- package/dist/state/hooks.d.ts +4 -0
- package/dist/state/index.d.ts +1 -2
- package/dist/style.css +182 -156
- package/dist/ui/options/EdgeOptions.d.ts +19 -0
- package/dist/ui/options/NodeOptions.d.ts +2 -2
- package/dist/xyflow/EdgeMarkers.d.ts +11 -0
- package/dist/xyflow/XYFlowEvents.d.ts +8 -5
- package/dist/xyflow/const.d.ts +3 -3
- package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
- package/dist/xyflow/edges/RelationshipEdge.d.ts +3 -2
- package/dist/xyflow/edges/edges.css.d.ts +1 -0
- package/dist/xyflow/edges/utils.d.ts +1 -0
- package/dist/xyflow/hooks/index.d.ts +3 -2
- package/dist/xyflow/hooks/useXYFlow.d.ts +13 -12
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +1 -0
- package/dist/xyflow/nodes/element/ElementIcon.d.ts +1 -4
- package/dist/xyflow/nodes/element/element.css.d.ts +2 -1
- package/dist/xyflow/types.d.ts +2 -32
- package/package.json +19 -19
- package/src/LikeC4Diagram.css.ts +0 -8
- package/src/LikeC4Diagram.props.ts +2 -2
- package/src/LikeC4Diagram.tsx +14 -20
- package/src/globals.css.ts +10 -3
- package/src/index.ts +1 -1
- package/src/mantine.css.ts +2 -2
- package/src/state/DiagramContext.tsx +44 -13
- package/src/state/diagramStore.ts +64 -52
- package/src/state/hooks.ts +5 -0
- package/src/state/index.ts +1 -2
- package/src/ui/BackwardForwardPanel.css.ts +9 -9
- package/src/ui/BackwardForwardPanel.tsx +1 -1
- package/src/ui/DiagramTitlePanel.css.ts +7 -2
- package/src/ui/DiagramTitlePanel.tsx +5 -3
- package/src/ui/DynamicViewWalkthrough.tsx +6 -4
- package/src/ui/OptionsPanel.tsx +37 -27
- package/src/ui/options/EdgeOptions.tsx +157 -0
- package/src/ui/options/NodeOptions.tsx +37 -29
- package/src/xyflow/EdgeMarkers.tsx +148 -0
- package/src/xyflow/FitviewOnDiagramChange.tsx +8 -4
- package/src/xyflow/SelectEdgesOnNodeFocus.tsx +59 -55
- package/src/xyflow/SyncWithDiagram.tsx +13 -20
- package/src/xyflow/XYFlow.tsx +2 -4
- package/src/xyflow/XYFlowEvents.tsx +48 -33
- package/src/xyflow/XYFlowInner.tsx +1 -1
- package/src/xyflow/const.ts +3 -3
- package/src/xyflow/diagram-to-xyflow.ts +17 -41
- package/src/xyflow/edges/RelationshipEdge.tsx +207 -118
- package/src/xyflow/edges/edges.css.ts +36 -22
- package/src/xyflow/edges/utils.ts +10 -0
- package/src/xyflow/hooks/index.ts +4 -2
- package/src/xyflow/hooks/useLayoutConstraints.ts +3 -4
- package/src/xyflow/hooks/useXYFlow.ts +28 -1
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +34 -24
- package/src/xyflow/nodes/compound/CompoundNode.tsx +52 -53
- package/src/xyflow/nodes/element/ElementIcon.tsx +11 -22
- package/src/xyflow/nodes/element/ElementLink.tsx +11 -16
- package/src/xyflow/nodes/element/ElementNode.tsx +64 -108
- package/src/xyflow/nodes/element/element.css.ts +30 -10
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -1
- package/src/xyflow/types.ts +2 -4
- package/src/xyflow/utils.ts +3 -6
- package/dist/KeepAspectRatioContainer.d.ts +0 -9
- package/dist/icons/Clipboard.d.ts +0 -3
- package/dist/icons/Link.d.ts +0 -3
- package/dist/icons/ZoomIn.d.ts +0 -3
- package/dist/icons/index.d.ts +0 -3
- package/dist/mantine/theme.d.ts +0 -3
- package/dist/state/WhenInitialized.d.ts +0 -3
- package/dist/ui/Camera.d.ts +0 -3
- package/src/KeepAspectRatioContainer.tsx +0 -36
- package/src/icons/Clipboard.tsx +0 -24
- package/src/icons/Link.tsx +0 -23
- package/src/icons/ZoomIn.tsx +0 -25
- package/src/icons/index.ts +0 -3
- package/src/mantine/theme.ts +0 -20
- package/src/state/WhenInitialized.tsx +0 -24
- package/src/ui/Camera.tsx +0 -174
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { rem } from '@mantine/core'
|
|
2
2
|
import { createVar, fallbackVar, globalStyle, keyframes, style } from '@vanilla-extract/css'
|
|
3
|
+
import { calc } from '@vanilla-extract/css-utils'
|
|
3
4
|
import { mantine } from '../../../mantine.css'
|
|
4
5
|
import { vars } from '../../../theme.css'
|
|
5
6
|
|
|
@@ -27,6 +28,12 @@ export const container = style({
|
|
|
27
28
|
}
|
|
28
29
|
})
|
|
29
30
|
|
|
31
|
+
export const handleCenter = style({
|
|
32
|
+
top: '50%',
|
|
33
|
+
left: '50%',
|
|
34
|
+
visibility: 'hidden'
|
|
35
|
+
})
|
|
36
|
+
|
|
30
37
|
export const containerAnimated = style({
|
|
31
38
|
willChange: 'transform'
|
|
32
39
|
})
|
|
@@ -56,8 +63,11 @@ const outlineColor = fallbackVar(
|
|
|
56
63
|
vars.element.stroke
|
|
57
64
|
)
|
|
58
65
|
|
|
66
|
+
const indicatorStroke = createVar('indicator-stroke')
|
|
67
|
+
|
|
59
68
|
export const indicator = style({
|
|
60
|
-
stroke:
|
|
69
|
+
stroke: indicatorStroke,
|
|
70
|
+
fill: 'none',
|
|
61
71
|
transformOrigin: 'center center',
|
|
62
72
|
strokeWidth: 6,
|
|
63
73
|
animationDuration: '800ms',
|
|
@@ -65,6 +75,9 @@ export const indicator = style({
|
|
|
65
75
|
animationDirection: 'alternate',
|
|
66
76
|
opacity: 0.6,
|
|
67
77
|
visibility: 'hidden',
|
|
78
|
+
vars: {
|
|
79
|
+
[indicatorStroke]: vars.element.loContrast
|
|
80
|
+
},
|
|
68
81
|
selectors: {
|
|
69
82
|
':where(.react-flow__node.selected) &': {
|
|
70
83
|
visibility: 'visible',
|
|
@@ -78,6 +91,11 @@ export const indicator = style({
|
|
|
78
91
|
':where([data-likec4-shape="queue"], [data-likec4-shape="cylinder"], [data-likec4-shape="storage"]) &': {
|
|
79
92
|
strokeWidth: 10
|
|
80
93
|
},
|
|
94
|
+
[`:where([data-mantine-color-scheme='light']) &`]: {
|
|
95
|
+
vars: {
|
|
96
|
+
[indicatorStroke]: `color-mix(in srgb, ${vars.element.fill} 60%, ${vars.element.loContrast})`
|
|
97
|
+
}
|
|
98
|
+
},
|
|
81
99
|
[`${dimmed} &`]: {
|
|
82
100
|
visibility: 'hidden',
|
|
83
101
|
display: 'none'
|
|
@@ -104,7 +122,7 @@ export const title = style({
|
|
|
104
122
|
fontStyle: 'normal',
|
|
105
123
|
textAlign: 'center',
|
|
106
124
|
fontWeight: 500,
|
|
107
|
-
fontSize: rem(19),
|
|
125
|
+
fontSize: rem(19.5),
|
|
108
126
|
lineHeight: 1.2,
|
|
109
127
|
textWrap: 'balance',
|
|
110
128
|
color: vars.element.hiContrast
|
|
@@ -175,34 +193,34 @@ export const element = style({
|
|
|
175
193
|
}
|
|
176
194
|
})
|
|
177
195
|
|
|
178
|
-
export const
|
|
196
|
+
export const iconOffsetY = createVar('offset-y')
|
|
197
|
+
|
|
198
|
+
const iconMaxH = createVar('max-h')
|
|
179
199
|
|
|
180
200
|
export const elementIcon = style({
|
|
181
201
|
flex: `0 1 ${iconMaxH}`,
|
|
182
202
|
maxHeight: iconMaxH,
|
|
183
|
-
marginTop: -8,
|
|
184
203
|
position: 'relative',
|
|
185
204
|
userSelect: 'none',
|
|
186
205
|
pointerEvents: 'none',
|
|
187
206
|
overflow: 'visible',
|
|
188
207
|
mixBlendMode: 'hard-light',
|
|
189
208
|
'vars': {
|
|
190
|
-
[iconMaxH]: '
|
|
209
|
+
[iconMaxH]: calc(iconOffsetY).subtract('12px').toString()
|
|
191
210
|
}
|
|
192
211
|
})
|
|
193
212
|
|
|
194
213
|
globalStyle(`${elementIcon} img`, {
|
|
195
214
|
position: 'absolute',
|
|
196
215
|
left: 0,
|
|
197
|
-
bottom:
|
|
216
|
+
bottom: 4,
|
|
198
217
|
width: '100%',
|
|
199
218
|
height: 'auto',
|
|
200
219
|
objectFit: 'contain',
|
|
201
|
-
maxHeight:
|
|
220
|
+
maxHeight: iconOffsetY
|
|
202
221
|
})
|
|
203
222
|
|
|
204
223
|
const filterShadow = createVar('filter-shadow')
|
|
205
|
-
const filterShadowHovered = createVar('filter-shadow-hovered')
|
|
206
224
|
|
|
207
225
|
export const cssShapeSvg = style({
|
|
208
226
|
top: '0px',
|
|
@@ -212,7 +230,7 @@ export const cssShapeSvg = style({
|
|
|
212
230
|
fill: vars.element.fill,
|
|
213
231
|
stroke: vars.element.stroke,
|
|
214
232
|
overflow: 'visible',
|
|
215
|
-
filter:
|
|
233
|
+
filter: filterShadow,
|
|
216
234
|
transition: 'filter 300ms ease-out',
|
|
217
235
|
transitionDelay: '0ms',
|
|
218
236
|
zIndex: -1,
|
|
@@ -225,7 +243,9 @@ export const cssShapeSvg = style({
|
|
|
225
243
|
},
|
|
226
244
|
selectors: {
|
|
227
245
|
[`:where(.react-flow__node.selected, .react-flow__node:focus-visible, ${container}:focus-visible) &`]: {
|
|
228
|
-
|
|
246
|
+
vars: {
|
|
247
|
+
[filterShadow]: `none`
|
|
248
|
+
}
|
|
229
249
|
}
|
|
230
250
|
}
|
|
231
251
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ActionIcon } from '@mantine/core'
|
|
2
2
|
import { IconZoomScan } from '@tabler/icons-react'
|
|
3
3
|
import clsx from 'clsx'
|
|
4
|
-
import { useDiagramStoreApi } from '../../../state'
|
|
4
|
+
import { useDiagramStoreApi } from '../../../state/hooks'
|
|
5
5
|
|
|
6
6
|
export type NavigateToBtnProps = {
|
|
7
7
|
xynodeId: string
|
package/src/xyflow/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BBox, DiagramEdge, DiagramNode, Fqn,
|
|
1
|
+
import type { BBox, DiagramEdge, DiagramNode, Fqn, XYPosition } from '@likec4/core'
|
|
2
2
|
import type {
|
|
3
3
|
Edge,
|
|
4
4
|
EdgeMouseHandler,
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
import { isNode } from '@xyflow/react'
|
|
14
14
|
import type { MouseEvent as ReactMouseEvent } from 'react'
|
|
15
15
|
import { isTruthy } from 'remeda'
|
|
16
|
-
import type { SetReadonly, SetRequired
|
|
16
|
+
import type { SetReadonly, SetRequired } from 'type-fest'
|
|
17
17
|
|
|
18
18
|
export type ElementNodeData = {
|
|
19
19
|
fqn: Fqn
|
|
@@ -61,8 +61,6 @@ export type RelationshipData = {
|
|
|
61
61
|
// viewId: ViewID
|
|
62
62
|
// if set - edge was changed by user
|
|
63
63
|
controlPoints: XYPosition[] | null
|
|
64
|
-
headPoint: Point | null
|
|
65
|
-
tailPoint: Point | null
|
|
66
64
|
type: 'bezier' | 'poly'
|
|
67
65
|
// if dynamic view
|
|
68
66
|
stepNum: number | null
|
package/src/xyflow/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hasAtLeast, invariant } from '@likec4/core'
|
|
2
|
-
import type { DiagramEdge,
|
|
2
|
+
import type { DiagramEdge, Point } from '@likec4/core/types'
|
|
3
3
|
import type { XYPosition } from '@xyflow/react'
|
|
4
4
|
import { Bezier } from 'bezier-js'
|
|
5
5
|
import { isArray } from 'remeda'
|
|
@@ -33,17 +33,14 @@ export function bezierControlPoints(diagramEdge: DiagramEdge) {
|
|
|
33
33
|
inflections.forEach(t => {
|
|
34
34
|
const { x, y } = bezier.get(t)
|
|
35
35
|
handles.push({
|
|
36
|
-
x:
|
|
37
|
-
y:
|
|
36
|
+
x: Math.round(x),
|
|
37
|
+
y: Math.round(y)
|
|
38
38
|
})
|
|
39
39
|
})
|
|
40
40
|
bezierPoints = rest
|
|
41
41
|
start = end
|
|
42
42
|
}
|
|
43
43
|
invariant(bezierPoints.length === 0, 'all points should be consumed')
|
|
44
|
-
if (diagramEdge.dir === 'back' && handles.length > 0) {
|
|
45
|
-
handles.reverse()
|
|
46
|
-
}
|
|
47
44
|
|
|
48
45
|
return handles
|
|
49
46
|
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { HTMLAttributes } from 'react';
|
|
2
|
-
|
|
3
|
-
type KeepAspectRatioContainerProps = HTMLAttributes<HTMLDivElement> & {
|
|
4
|
-
enabled: boolean;
|
|
5
|
-
width: number;
|
|
6
|
-
height: number;
|
|
7
|
-
};
|
|
8
|
-
export declare function KeepAspectRatioContainer({ children, width, height, enabled, className, style, ...rest }: KeepAspectRatioContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
-
export {};
|
package/dist/icons/Link.d.ts
DELETED
package/dist/icons/ZoomIn.d.ts
DELETED
package/dist/icons/index.d.ts
DELETED
package/dist/mantine/theme.d.ts
DELETED
package/dist/ui/Camera.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { Box } from '@mantine/core'
|
|
2
|
-
import clsx from 'clsx'
|
|
3
|
-
import { type HTMLAttributes, useId } from 'react'
|
|
4
|
-
import * as css from './LikeC4Diagram.css'
|
|
5
|
-
|
|
6
|
-
type KeepAspectRatioContainerProps = HTMLAttributes<HTMLDivElement> & {
|
|
7
|
-
// Whether to keep the aspect ratio
|
|
8
|
-
enabled: boolean
|
|
9
|
-
width: number
|
|
10
|
-
height: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function KeepAspectRatioContainer({
|
|
14
|
-
children,
|
|
15
|
-
width,
|
|
16
|
-
height,
|
|
17
|
-
enabled,
|
|
18
|
-
className,
|
|
19
|
-
style,
|
|
20
|
-
...rest
|
|
21
|
-
}: KeepAspectRatioContainerProps) {
|
|
22
|
-
return (
|
|
23
|
-
<Box
|
|
24
|
-
{...rest}
|
|
25
|
-
className={clsx(css.keepAspectRatioContainer, className)}
|
|
26
|
-
style={enabled
|
|
27
|
-
? {
|
|
28
|
-
...style,
|
|
29
|
-
aspectRatio: `${Math.ceil(width)}/${Math.ceil(height)}`,
|
|
30
|
-
maxHeight: `var(--likec4-view-max-height, ${Math.ceil(height)}px)`
|
|
31
|
-
}
|
|
32
|
-
: style}>
|
|
33
|
-
{children}
|
|
34
|
-
</Box>
|
|
35
|
-
)
|
|
36
|
-
}
|
package/src/icons/Clipboard.tsx
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
|
-
import { forwardRef, type SVGProps } from 'react'
|
|
3
|
-
|
|
4
|
-
export const ClipboardCopy = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(({ className, ...props }, ref) => (
|
|
5
|
-
<svg
|
|
6
|
-
ref={ref}
|
|
7
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
-
className={clsx('icon icon-tabler icon-tabler-zoom-in', className)}
|
|
9
|
-
width="24"
|
|
10
|
-
height="24"
|
|
11
|
-
viewBox="0 0 24 24"
|
|
12
|
-
strokeWidth="1.5"
|
|
13
|
-
stroke="currentColor"
|
|
14
|
-
fill="none"
|
|
15
|
-
strokeLinecap="round"
|
|
16
|
-
strokeLinejoin="round"
|
|
17
|
-
{...props}
|
|
18
|
-
>
|
|
19
|
-
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
20
|
-
<path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h3m9 -9v-5a2 2 0 0 0 -2 -2h-2" />
|
|
21
|
-
<path d="M13 17v-1a1 1 0 0 1 1 -1h1m3 0h1a1 1 0 0 1 1 1v1m0 3v1a1 1 0 0 1 -1 1h-1m-3 0h-1a1 1 0 0 1 -1 -1v-1" />
|
|
22
|
-
<path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" />
|
|
23
|
-
</svg>
|
|
24
|
-
))
|
package/src/icons/Link.tsx
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
|
-
import { forwardRef, type SVGProps } from 'react'
|
|
3
|
-
|
|
4
|
-
export const Link = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(({ className, ...props }, ref) => (
|
|
5
|
-
<svg
|
|
6
|
-
ref={ref}
|
|
7
|
-
className={clsx('icon icon-tabler', className)}
|
|
8
|
-
stroke="currentColor"
|
|
9
|
-
fill="currentColor"
|
|
10
|
-
strokeWidth="0"
|
|
11
|
-
viewBox="0 0 20 20"
|
|
12
|
-
height="20"
|
|
13
|
-
width="20"
|
|
14
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
-
{...props}
|
|
16
|
-
>
|
|
17
|
-
<path
|
|
18
|
-
fillRule="evenodd"
|
|
19
|
-
d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z"
|
|
20
|
-
clipRule="evenodd">
|
|
21
|
-
</path>
|
|
22
|
-
</svg>
|
|
23
|
-
))
|
package/src/icons/ZoomIn.tsx
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
|
-
import { forwardRef, type SVGProps } from 'react'
|
|
3
|
-
|
|
4
|
-
export const ZoomIn = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(({ className, ...props }, ref) => (
|
|
5
|
-
<svg
|
|
6
|
-
ref={ref}
|
|
7
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
-
className={clsx('icon icon-tabler icon-tabler-zoom-in', className)}
|
|
9
|
-
width="24"
|
|
10
|
-
height="24"
|
|
11
|
-
viewBox="0 0 24 24"
|
|
12
|
-
strokeWidth="1.5"
|
|
13
|
-
stroke="currentColor"
|
|
14
|
-
fill="none"
|
|
15
|
-
strokeLinecap="round"
|
|
16
|
-
strokeLinejoin="round"
|
|
17
|
-
{...props}
|
|
18
|
-
>
|
|
19
|
-
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
20
|
-
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" />
|
|
21
|
-
<path d="M7 10l6 0" />
|
|
22
|
-
<path d="M10 7l0 6" />
|
|
23
|
-
<path d="M21 21l-6 -6" />
|
|
24
|
-
</svg>
|
|
25
|
-
))
|
package/src/icons/index.ts
DELETED
package/src/mantine/theme.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { createTheme, type MantineTheme } from '@mantine/core'
|
|
2
|
-
|
|
3
|
-
export const theme = createTheme({
|
|
4
|
-
primaryColor: 'indigo',
|
|
5
|
-
cursorType: 'pointer',
|
|
6
|
-
defaultRadius: 'sm',
|
|
7
|
-
respectReducedMotion: true,
|
|
8
|
-
headings: {
|
|
9
|
-
fontWeight: '500',
|
|
10
|
-
sizes: {
|
|
11
|
-
h1: {
|
|
12
|
-
// fontSize: '2rem',
|
|
13
|
-
fontWeight: '600'
|
|
14
|
-
},
|
|
15
|
-
h2: {
|
|
16
|
-
// fontSize: '1.85rem',
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}) as MantineTheme
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { type PropsWithChildren, useEffect, useState } from 'react'
|
|
2
|
-
import { useDiagramStoreApi } from './useDiagramStore'
|
|
3
|
-
|
|
4
|
-
export function WhenInitialized({ children }: PropsWithChildren) {
|
|
5
|
-
const diagramApi = useDiagramStoreApi()
|
|
6
|
-
|
|
7
|
-
const [isInitialized, setIsInitialized] = useState(diagramApi.getState().initialized)
|
|
8
|
-
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
if (isInitialized) {
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
return diagramApi.subscribe(
|
|
14
|
-
s => s.initialized,
|
|
15
|
-
setIsInitialized
|
|
16
|
-
)
|
|
17
|
-
}, [isInitialized])
|
|
18
|
-
|
|
19
|
-
if (!isInitialized) {
|
|
20
|
-
return null
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return <>{children}</>
|
|
24
|
-
}
|
package/src/ui/Camera.tsx
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { shallowEqual } from '@mantine/hooks'
|
|
2
|
-
import { useDebouncedEffect, useIsMounted } from '@react-hookz/web'
|
|
3
|
-
import {
|
|
4
|
-
type ReactFlowState,
|
|
5
|
-
useOnSelectionChange,
|
|
6
|
-
useOnViewportChange,
|
|
7
|
-
useStore,
|
|
8
|
-
useUpdateNodeInternals,
|
|
9
|
-
type Viewport
|
|
10
|
-
} from '@xyflow/react'
|
|
11
|
-
import { memo, useEffect, useRef, useState } from 'react'
|
|
12
|
-
import { uniq } from 'remeda'
|
|
13
|
-
import { useXYFlow } from '../xyflow/hooks'
|
|
14
|
-
import type { XYFlowNode } from '../xyflow/types'
|
|
15
|
-
import { distance } from '../xyflow/utils'
|
|
16
|
-
|
|
17
|
-
const selector = (state: ReactFlowState) => {
|
|
18
|
-
return {
|
|
19
|
-
isUserSelectionActive: state.userSelectionActive,
|
|
20
|
-
isDragging: state.paneDragging || state.nodes.some(n => n.dragging ?? false)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const CameraMemo = /* @__PURE__ */ memo(function Camera() {
|
|
25
|
-
const isMounted = useIsMounted()
|
|
26
|
-
// const isUserSelectionActive = useStore(selectUserSelectionActive)
|
|
27
|
-
const {
|
|
28
|
-
isUserSelectionActive,
|
|
29
|
-
isDragging
|
|
30
|
-
} = useStore(selector, shallowEqual)
|
|
31
|
-
const reactflow = useXYFlow()
|
|
32
|
-
const updateNd = useUpdateNodeInternals()
|
|
33
|
-
|
|
34
|
-
const padding = 0 // editor.fitViewPadding
|
|
35
|
-
const viewId = '' // editor.viewId
|
|
36
|
-
const fitOnSelect: boolean = false // editor.fitOnSelect
|
|
37
|
-
|
|
38
|
-
const previousViewport = useRef<Viewport | null>(null)
|
|
39
|
-
const isZoomPendingRef = useRef(false)
|
|
40
|
-
|
|
41
|
-
const viewportChangeStart = useRef<Viewport | null>(null)
|
|
42
|
-
const prevViewId = useRef(viewId)
|
|
43
|
-
const isReady = reactflow.viewportInitialized
|
|
44
|
-
const selectedNodesRef = useRef([] as string[])
|
|
45
|
-
|
|
46
|
-
const [selectedNodes, setSelectedNodes] = useState([] as XYFlowNode[])
|
|
47
|
-
|
|
48
|
-
// WORKAROUND
|
|
49
|
-
|
|
50
|
-
useOnViewportChange({
|
|
51
|
-
onStart: (viewport) => {
|
|
52
|
-
viewportChangeStart.current = { ...viewport }
|
|
53
|
-
},
|
|
54
|
-
onEnd: (end) => {
|
|
55
|
-
if (!previousViewport.current) {
|
|
56
|
-
viewportChangeStart.current = null
|
|
57
|
-
}
|
|
58
|
-
if (!viewportChangeStart.current) {
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
const start = {
|
|
62
|
-
...viewportChangeStart.current
|
|
63
|
-
}
|
|
64
|
-
const d = distance(start, end)
|
|
65
|
-
if (d > 15) {
|
|
66
|
-
previousViewport.current = null
|
|
67
|
-
}
|
|
68
|
-
viewportChangeStart.current = null
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
const fixUseOnViewportChange = () => {
|
|
73
|
-
// WORKAROUND:
|
|
74
|
-
// react-flow triggers useOnViewportChange
|
|
75
|
-
// We reset the viewportChangeStart, before useOnViewportChange.onEnd called
|
|
76
|
-
setTimeout(() => {
|
|
77
|
-
viewportChangeStart.current = null
|
|
78
|
-
}, 50)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
useOnSelectionChange({
|
|
82
|
-
onChange: ({ nodes, edges }) => {
|
|
83
|
-
if (!isMounted() || fitOnSelect === false) {
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
if (nodes.length === 0 && edges.length === 0) {
|
|
87
|
-
setSelectedNodes([])
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
const selected = new Set([
|
|
91
|
-
...nodes.map(({ id }) => reactflow.getNode(id)),
|
|
92
|
-
...edges.flatMap((edge) => [
|
|
93
|
-
reactflow.getNode(edge.source),
|
|
94
|
-
reactflow.getNode(edge.target)
|
|
95
|
-
])
|
|
96
|
-
].filter(Boolean))
|
|
97
|
-
setSelectedNodes([...selected])
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
const selectedNodesHash = selectedNodes.map((node) => node.id).sort().join('\n')
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
isZoomPendingRef.current = selectedNodesHash !== ''
|
|
104
|
-
}, [selectedNodesHash])
|
|
105
|
-
|
|
106
|
-
useDebouncedEffect(
|
|
107
|
-
() => {
|
|
108
|
-
if (isUserSelectionActive || isDragging || !isReady) {
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
if (selectedNodes.length === 0) {
|
|
112
|
-
if (previousViewport.current) {
|
|
113
|
-
console.log(`Camera: revert viewport`)
|
|
114
|
-
reactflow.setViewport(previousViewport.current, {
|
|
115
|
-
duration: 300
|
|
116
|
-
})
|
|
117
|
-
previousViewport.current = null
|
|
118
|
-
}
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
if (!isZoomPendingRef.current) {
|
|
122
|
-
return
|
|
123
|
-
}
|
|
124
|
-
isZoomPendingRef.current = false
|
|
125
|
-
previousViewport.current ??= { ...reactflow.getViewport() }
|
|
126
|
-
const zoom = previousViewport.current.zoom
|
|
127
|
-
reactflow.fitView({
|
|
128
|
-
duration: 350,
|
|
129
|
-
maxZoom: Math.max(1, zoom),
|
|
130
|
-
padding,
|
|
131
|
-
nodes: selectedNodes
|
|
132
|
-
})
|
|
133
|
-
fixUseOnViewportChange()
|
|
134
|
-
},
|
|
135
|
-
[selectedNodesHash, isReady, isDragging, isUserSelectionActive],
|
|
136
|
-
previousViewport.current ? 100 : 1500
|
|
137
|
-
// previousViewport.current ? 200 : 2500
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
const ids = selectedNodes.map((node) => node.id)
|
|
142
|
-
for (const id of uniq([...selectedNodesRef.current, ...ids])) {
|
|
143
|
-
try {
|
|
144
|
-
updateNd(id)
|
|
145
|
-
} catch (e) {
|
|
146
|
-
console.warn(`Camera: failed useUpdateNodeInternals`, e)
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
selectedNodesRef.current = ids
|
|
150
|
-
}, [selectedNodesHash, updateNd])
|
|
151
|
-
|
|
152
|
-
useDebouncedEffect(
|
|
153
|
-
() => {
|
|
154
|
-
if (!isReady || prevViewId.current === viewId) {
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
const zoom = previousViewport.current?.zoom ?? reactflow.getZoom()
|
|
158
|
-
reactflow.fitView({
|
|
159
|
-
duration: 400,
|
|
160
|
-
maxZoom: Math.max(1, zoom),
|
|
161
|
-
padding
|
|
162
|
-
})
|
|
163
|
-
previousViewport.current = null
|
|
164
|
-
prevViewId.current = viewId
|
|
165
|
-
},
|
|
166
|
-
[isReady, viewId],
|
|
167
|
-
50,
|
|
168
|
-
500
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
return null
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
export default CameraMemo
|