@likec4/diagram 1.21.1 → 1.22.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/dist/LikeC4Diagram.js +9 -13
- package/dist/base/index.d.ts +0 -1
- package/dist/base/primitives/element/styles.css.js +1 -1
- package/dist/{base → components}/Link.css.js +3 -3
- package/dist/components/SearchControl.css.d.ts +2 -0
- package/dist/components/SearchControl.css.js +7 -0
- package/dist/components/SearchControl.d.ts +5 -0
- package/dist/components/SearchControl.js +17 -0
- package/dist/context/DiagramFeatures.d.ts +1 -1
- package/dist/context/IconRenderer.d.ts +10 -0
- package/dist/context/IconRenderer.js +34 -0
- package/dist/hooks/useCurrentViewId.js +8 -0
- package/dist/hooks/useDiagram.d.ts +108 -72
- package/dist/hooks/useDiagram.js +3 -0
- package/dist/likec4diagram/DiagramActor.js +13 -2
- package/dist/likec4diagram/DiagramUI.js +0 -2
- package/dist/likec4diagram/XYFlow.d.ts +6 -1
- package/dist/likec4diagram/XYFlow.js +37 -31
- package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +2 -1
- package/dist/likec4diagram/state/actorContext.d.ts +620 -468
- package/dist/likec4diagram/state/assign.js +1 -1
- package/dist/likec4diagram/state/machine.d.ts +120 -74
- package/dist/likec4diagram/state/machine.js +32 -12
- package/dist/likec4diagram/state/syncManualLayoutActor.js +4 -8
- package/dist/likec4diagram/ui/controls/Controls.js +65 -48
- package/dist/likec4diagram/ui/controls/NavigationButtons.js +44 -69
- package/dist/likec4diagram/ui/controls/ToggleReadonly.d.ts +1 -0
- package/dist/likec4diagram/ui/controls/ToggleReadonly.js +29 -0
- package/dist/likec4diagram/ui/controls/_shared.js +1 -1
- package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +1 -1
- package/dist/likec4diagram/ui/search/ElementsColumn.css.d.ts +15 -0
- package/dist/likec4diagram/ui/search/ElementsColumn.css.js +30 -0
- package/dist/likec4diagram/ui/search/ElementsColumn.d.ts +1 -0
- package/dist/likec4diagram/ui/search/ElementsColumn.js +243 -0
- package/dist/likec4diagram/ui/search/LikeC4Search.css.d.ts +8 -0
- package/dist/likec4diagram/ui/search/LikeC4Search.css.js +20 -0
- package/dist/likec4diagram/ui/search/LikeC4Search.d.ts +1 -0
- package/dist/likec4diagram/ui/search/LikeC4Search.js +265 -0
- package/dist/likec4diagram/ui/search/SearchInput.d.ts +1 -0
- package/dist/likec4diagram/ui/search/SearchInput.js +196 -0
- package/dist/likec4diagram/ui/search/ViewsColum.d.ts +9 -0
- package/dist/likec4diagram/ui/search/ViewsColum.js +130 -0
- package/dist/likec4diagram/ui/search/ViewsColumn.css.d.ts +5 -0
- package/dist/likec4diagram/ui/search/ViewsColumn.css.js +12 -0
- package/dist/likec4diagram/ui/search/_shared.css.d.ts +16 -0
- package/dist/likec4diagram/ui/search/_shared.css.js +5 -0
- package/dist/likec4diagram/ui/search/state.d.ts +19 -0
- package/dist/likec4diagram/ui/search/state.js +72 -0
- package/dist/likec4diagram/ui/search/utils.d.ts +3 -0
- package/dist/likec4diagram/ui/search/utils.js +23 -0
- package/dist/likec4diagram/useLayoutConstraints.js +4 -5
- package/dist/likec4model/useLikeC4ElementsTree.d.ts +1 -0
- package/dist/likec4model/useLikeC4ElementsTree.js +1 -0
- package/dist/overlays/Overlays.js +9 -9
- package/dist/overlays/element-details/ElementDetailsCard.js +20 -13
- package/dist/overlays/overlay/Overlay.d.ts +6 -2
- package/dist/overlays/overlay/Overlay.js +18 -29
- package/dist/overlays/relationship-details/SelectEdge.js +1 -3
- package/dist/overlays/relationships-browser/RelationshipsBrowser.js +6 -5
- package/dist/overlays/relationships-browser/SelectElement.js +10 -6
- package/dist/overlays/relationships-browser/actor.d.ts +2 -2
- package/dist/overlays/relationships-browser/actor.js +8 -8
- package/dist/overlays/relationships-browser/custom/nodeTypes.js +2 -2
- package/dist/style.css +1 -1
- package/package.json +13 -13
- package/src/LikeC4Diagram.tsx +0 -1
- package/src/base/index.ts +0 -2
- package/src/base/primitives/element/styles.css.ts +2 -2
- package/src/{base → components}/Link.css.ts +3 -3
- package/src/components/SearchControl.css.ts +69 -0
- package/src/components/SearchControl.tsx +33 -0
- package/src/context/DiagramFeatures.tsx +1 -1
- package/src/context/IconRenderer.tsx +54 -1
- package/src/globals.css.ts +0 -2
- package/src/hooks/useDiagram.ts +5 -0
- package/src/likec4diagram/DiagramActor.tsx +15 -2
- package/src/likec4diagram/DiagramUI.tsx +0 -4
- package/src/likec4diagram/XYFlow.tsx +39 -31
- package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +2 -1
- package/src/likec4diagram/state/assign.ts +1 -1
- package/src/likec4diagram/state/machine.ts +40 -14
- package/src/likec4diagram/state/syncManualLayoutActor.ts +6 -9
- package/src/likec4diagram/ui/controls/Controls.tsx +37 -25
- package/src/likec4diagram/ui/controls/NavigationButtons.tsx +36 -58
- package/src/likec4diagram/ui/controls/ToggleReadonly.tsx +31 -0
- package/src/likec4diagram/ui/controls/styles.css.ts +3 -1
- package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +1 -1
- package/src/likec4diagram/ui/search/ElementsColumn.css.ts +119 -0
- package/src/likec4diagram/ui/search/ElementsColumn.tsx +291 -0
- package/src/likec4diagram/ui/search/LikeC4Search.css.ts +117 -0
- package/src/likec4diagram/ui/search/LikeC4Search.tsx +312 -0
- package/src/likec4diagram/ui/search/SearchInput.tsx +235 -0
- package/src/likec4diagram/ui/search/ViewsColum.tsx +158 -0
- package/src/likec4diagram/ui/search/ViewsColumn.css.ts +37 -0
- package/src/likec4diagram/ui/search/_shared.css.ts +104 -0
- package/src/likec4diagram/ui/search/state.ts +76 -0
- package/src/likec4diagram/ui/search/utils.ts +20 -0
- package/src/likec4diagram/useLayoutConstraints.ts +4 -5
- package/src/likec4model/useLikeC4ElementsTree.ts +4 -4
- package/src/overlays/Overlays.tsx +31 -25
- package/src/overlays/element-details/ElementDetailsCard.css.ts +4 -4
- package/src/overlays/element-details/ElementDetailsCard.tsx +245 -240
- package/src/overlays/overlay/Overlay.css.ts +10 -9
- package/src/overlays/overlay/Overlay.tsx +38 -26
- package/src/overlays/relationship-details/SelectEdge.css.ts +1 -0
- package/src/overlays/relationship-details/SelectEdge.tsx +2 -4
- package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +10 -5
- package/src/overlays/relationships-browser/SelectElement.tsx +88 -82
- package/src/overlays/relationships-browser/actor.ts +10 -10
- package/src/overlays/relationships-browser/custom/nodeTypes.tsx +2 -2
- package/dist/LikeC4Search.css.d.ts +0 -1
- package/dist/LikeC4Search.css.js +0 -5
- package/dist/LikeC4Search.d.ts +0 -2
- package/dist/LikeC4Search.js +0 -88
- package/src/LikeC4Search.css.ts +0 -12
- package/src/LikeC4Search.tsx +0 -91
- /package/dist/{base → components}/Link.css.d.ts +0 -0
- /package/dist/{base → components}/Link.d.ts +0 -0
- /package/dist/{base → components}/Link.js +0 -0
- /package/src/{base → components}/Link.tsx +0 -0
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
import type { ElementShape } from '@likec4/core'
|
|
2
|
+
import {
|
|
3
|
+
type Icon,
|
|
4
|
+
type IconProps,
|
|
5
|
+
IconBrowser,
|
|
6
|
+
IconCylinder,
|
|
7
|
+
IconDeviceMobile,
|
|
8
|
+
IconRectangularPrism,
|
|
9
|
+
IconReorder,
|
|
10
|
+
IconUser,
|
|
11
|
+
} from '@tabler/icons-react'
|
|
1
12
|
import clsx from 'clsx'
|
|
2
|
-
import { type ReactNode, createContext, useContext } from 'react'
|
|
13
|
+
import { type ForwardRefExoticComponent, type ReactNode, type RefAttributes, createContext, useContext } from 'react'
|
|
3
14
|
import type { ElementIconRenderer } from '../LikeC4Diagram.props'
|
|
4
15
|
|
|
5
16
|
const IconRendererContext = createContext<ElementIconRenderer | null>(null)
|
|
@@ -43,3 +54,45 @@ export function IconRenderer({
|
|
|
43
54
|
</div>
|
|
44
55
|
)
|
|
45
56
|
}
|
|
57
|
+
|
|
58
|
+
const ShapeIcons = {
|
|
59
|
+
browser: IconBrowser,
|
|
60
|
+
cylinder: IconCylinder,
|
|
61
|
+
mobile: IconDeviceMobile,
|
|
62
|
+
person: IconUser,
|
|
63
|
+
queue: IconReorder,
|
|
64
|
+
rectangle: IconRectangularPrism,
|
|
65
|
+
storage: IconCylinder,
|
|
66
|
+
} satisfies Record<ElementShape, ForwardRefExoticComponent<IconProps & RefAttributes<Icon>>>
|
|
67
|
+
|
|
68
|
+
export function IconOrShapeRenderer({
|
|
69
|
+
element,
|
|
70
|
+
className,
|
|
71
|
+
}: {
|
|
72
|
+
element: {
|
|
73
|
+
id: string
|
|
74
|
+
title: string
|
|
75
|
+
shape: ElementShape
|
|
76
|
+
icon?: string | null | undefined
|
|
77
|
+
}
|
|
78
|
+
className: string
|
|
79
|
+
}) {
|
|
80
|
+
const icon = IconRenderer({
|
|
81
|
+
element,
|
|
82
|
+
className,
|
|
83
|
+
})
|
|
84
|
+
if (icon) {
|
|
85
|
+
return icon
|
|
86
|
+
}
|
|
87
|
+
const ShapeIcon = ShapeIcons[element.shape]
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
className={clsx(
|
|
91
|
+
className,
|
|
92
|
+
'likec4-shape-icon',
|
|
93
|
+
)}
|
|
94
|
+
>
|
|
95
|
+
<ShapeIcon />
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
package/src/globals.css.ts
CHANGED
|
@@ -7,8 +7,6 @@ import { mantine, vars, whereDark } from './theme-vars'
|
|
|
7
7
|
export const rootClassName = 'likec4-diagram-root'
|
|
8
8
|
|
|
9
9
|
globalStyle(`.${rootClassName}`, {
|
|
10
|
-
containerName: 'likec4-diagram-root',
|
|
11
|
-
containerType: 'size',
|
|
12
10
|
overflow: 'hidden',
|
|
13
11
|
position: 'relative',
|
|
14
12
|
width: '100%',
|
package/src/hooks/useDiagram.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EdgeId, Fqn, NodeId, ViewId } from '@likec4/core'
|
|
2
2
|
import { useMemo, useTransition } from 'react'
|
|
3
3
|
import type { PartialDeep } from 'type-fest'
|
|
4
|
+
import type { FeatureName } from '../context/DiagramFeatures'
|
|
4
5
|
import { useDiagramActor } from '../hooks/useDiagramActor'
|
|
5
6
|
import type { OpenSourceParams } from '../LikeC4Diagram.props'
|
|
6
7
|
import type { AlignmentMode } from '../likec4diagram/state/aligners'
|
|
@@ -125,5 +126,9 @@ export function useDiagram() {
|
|
|
125
126
|
stopWalkthrough: () => {
|
|
126
127
|
actor.send({ type: 'walkthrough.end' })
|
|
127
128
|
},
|
|
129
|
+
|
|
130
|
+
toggleFeature: (feature: FeatureName, forceValue?: boolean) => {
|
|
131
|
+
actor.send({ type: 'toggle.feature', feature, ...(forceValue !== undefined && { forceValue }) })
|
|
132
|
+
},
|
|
128
133
|
}), [actor])
|
|
129
134
|
}
|
|
@@ -2,11 +2,12 @@ import type { ViewId } from '@likec4/core'
|
|
|
2
2
|
import { useCallbackRef } from '@mantine/hooks'
|
|
3
3
|
import { useStoreApi } from '@xyflow/react'
|
|
4
4
|
import { type PropsWithChildren, useEffect } from 'react'
|
|
5
|
-
import { useDiagramEventHandlers, useEnabledFeatures } from '../context'
|
|
5
|
+
import { DiagramFeatures, useDiagramEventHandlers, useEnabledFeatures } from '../context'
|
|
6
6
|
import { useDiagramActor } from '../hooks/useDiagramActor'
|
|
7
7
|
import { useUpdateEffect } from '../hooks/useUpdateEffect'
|
|
8
8
|
import { LikeC4ViewMachineContextProvider } from './state/actorContext'
|
|
9
9
|
// import { useInspector } from './state/inspector'
|
|
10
|
+
import { useDiagramContext } from '../hooks/useDiagramContext'
|
|
10
11
|
import { type Input, diagramMachine } from './state/machine'
|
|
11
12
|
import { syncManualLayoutActor } from './state/syncManualLayoutActor'
|
|
12
13
|
import type { Types } from './types'
|
|
@@ -54,7 +55,9 @@ export function DiagramActor({ input, children }: PropsWithChildren<{ input: Act
|
|
|
54
55
|
}}
|
|
55
56
|
>
|
|
56
57
|
<SyncStore input={input} />
|
|
57
|
-
|
|
58
|
+
<DiagramActorToggledFeatures>
|
|
59
|
+
{children}
|
|
60
|
+
</DiagramActorToggledFeatures>
|
|
58
61
|
</LikeC4ViewMachineContextProvider>
|
|
59
62
|
)
|
|
60
63
|
)
|
|
@@ -77,3 +80,13 @@ const SyncStore = ({ input: { view, xyedges, xynodes, ...inputs } }: { input: Ac
|
|
|
77
80
|
|
|
78
81
|
return null
|
|
79
82
|
}
|
|
83
|
+
|
|
84
|
+
function DiagramActorToggledFeatures({ children }: PropsWithChildren) {
|
|
85
|
+
const toggledFeatures = useDiagramContext(s => s.toggledFeatures)
|
|
86
|
+
return (
|
|
87
|
+
<DiagramFeatures
|
|
88
|
+
overrides={toggledFeatures}>
|
|
89
|
+
{children}
|
|
90
|
+
</DiagramFeatures>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { IfEnabled } from '../context'
|
|
2
|
-
import { LikeC4Search } from '../LikeC4Search'
|
|
3
2
|
import { Overlays } from '../overlays/Overlays'
|
|
4
3
|
import { Controls, DiagramTitlePanel, DynamicViewWalkthrough, NotationPanel } from './ui'
|
|
5
4
|
|
|
@@ -8,9 +7,6 @@ export function DiagramUI() {
|
|
|
8
7
|
<>
|
|
9
8
|
<Controls />
|
|
10
9
|
<Overlays />
|
|
11
|
-
<IfEnabled feature="Search">
|
|
12
|
-
<LikeC4Search />
|
|
13
|
-
</IfEnabled>
|
|
14
10
|
<IfEnabled feature="ViewTitle">
|
|
15
11
|
<DiagramTitlePanel />
|
|
16
12
|
</IfEnabled>
|
|
@@ -2,6 +2,7 @@ import { type EdgeId, type NodeId, nonNullable } from '@likec4/core'
|
|
|
2
2
|
import { useCallbackRef } from '@mantine/hooks'
|
|
3
3
|
import clsx from 'clsx'
|
|
4
4
|
import { shallowEqual } from 'fast-equals'
|
|
5
|
+
import { memo } from 'react'
|
|
5
6
|
import type { EnforceOptional } from 'type-fest/source/enforce-optional'
|
|
6
7
|
import { BaseXYFlow } from '../base/BaseXYFlow'
|
|
7
8
|
import { useDiagramEventHandlers } from '../context'
|
|
@@ -9,6 +10,7 @@ import { useDiagram } from '../hooks/useDiagram'
|
|
|
9
10
|
import { useDiagramContext } from '../hooks/useDiagramContext'
|
|
10
11
|
import type { LikeC4DiagramProperties } from '../LikeC4Diagram.props'
|
|
11
12
|
import { edgeTypes, nodeTypes } from './custom'
|
|
13
|
+
import { DiagramUI } from './DiagramUI'
|
|
12
14
|
import type { DiagramContext } from './state/machine'
|
|
13
15
|
import type { Types } from './types'
|
|
14
16
|
import { useLayoutConstraints } from './useLayoutConstraints'
|
|
@@ -22,6 +24,13 @@ const selectXYProps = (ctx: DiagramContext) => ({
|
|
|
22
24
|
fitViewPadding: ctx.fitViewPadding,
|
|
23
25
|
enableFitView: ctx.features.enableFitView,
|
|
24
26
|
enableReadOnly: ctx.features.enableReadOnly,
|
|
27
|
+
...(!ctx.features.enableFitView && {
|
|
28
|
+
viewport: {
|
|
29
|
+
x: -Math.min(ctx.view.bounds.x, 0),
|
|
30
|
+
y: -Math.min(ctx.view.bounds.y, 0),
|
|
31
|
+
zoom: 1,
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
25
34
|
})
|
|
26
35
|
const equalsXYProps = (a: ReturnType<typeof selectXYProps>, b: ReturnType<typeof selectXYProps>): boolean =>
|
|
27
36
|
a.initialized === b.initialized &&
|
|
@@ -31,7 +40,8 @@ const equalsXYProps = (a: ReturnType<typeof selectXYProps>, b: ReturnType<typeof
|
|
|
31
40
|
a.enableFitView === b.enableFitView &&
|
|
32
41
|
a.enableReadOnly === b.enableReadOnly &&
|
|
33
42
|
shallowEqual(a.nodes, b.nodes) &&
|
|
34
|
-
shallowEqual(a.edges, b.edges)
|
|
43
|
+
shallowEqual(a.edges, b.edges) &&
|
|
44
|
+
shallowEqual(a.viewport, b.viewport)
|
|
35
45
|
|
|
36
46
|
type Picked = EnforceOptional<
|
|
37
47
|
Pick<
|
|
@@ -43,7 +53,7 @@ type Picked = EnforceOptional<
|
|
|
43
53
|
>
|
|
44
54
|
export type LikeC4DiagramXYFlowProps = Required<Picked>
|
|
45
55
|
|
|
46
|
-
export const LikeC4DiagramXYFlow = ({ background,
|
|
56
|
+
export const LikeC4DiagramXYFlow = memo<LikeC4DiagramXYFlowProps>(({ background, nodesDraggable, nodesSelectable }) => {
|
|
47
57
|
const diagram = useDiagram()
|
|
48
58
|
const {
|
|
49
59
|
initialized,
|
|
@@ -80,10 +90,10 @@ export const LikeC4DiagramXYFlow = ({ background, ...rest }: LikeC4DiagramXYFlow
|
|
|
80
90
|
nodeTypes={nodeTypes}
|
|
81
91
|
edgeTypes={edgeTypes}
|
|
82
92
|
onNodesChange={useCallbackRef((changes) => {
|
|
83
|
-
diagram.send({ type: 'xyflow.
|
|
93
|
+
diagram.send({ type: 'xyflow.applyNodeChanges', changes })
|
|
84
94
|
})}
|
|
85
95
|
onEdgesChange={useCallbackRef((changes) => {
|
|
86
|
-
diagram.send({ type: 'xyflow.
|
|
96
|
+
diagram.send({ type: 'xyflow.applyEdgeChanges', changes })
|
|
87
97
|
})}
|
|
88
98
|
background={initialized ? background : 'transparent'}
|
|
89
99
|
// Fitview is handled in onInit
|
|
@@ -105,6 +115,7 @@ export const LikeC4DiagramXYFlow = ({ background, ...rest }: LikeC4DiagramXYFlow
|
|
|
105
115
|
})}
|
|
106
116
|
onDoubleClick={useCallbackRef(e => {
|
|
107
117
|
e.stopPropagation()
|
|
118
|
+
e.preventDefault()
|
|
108
119
|
diagram.send({ type: 'xyflow.paneDblClick' })
|
|
109
120
|
onCanvasDblClick?.(e as any)
|
|
110
121
|
})}
|
|
@@ -115,34 +126,31 @@ export const LikeC4DiagramXYFlow = ({ background, ...rest }: LikeC4DiagramXYFlow
|
|
|
115
126
|
onInit={useCallbackRef((instance) => {
|
|
116
127
|
diagram.send({ type: 'xyflow.init', instance })
|
|
117
128
|
})}
|
|
118
|
-
{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}}
|
|
136
|
-
{...onCanvasContextMenu && {
|
|
137
|
-
onPaneContextMenu: useCallbackRef((event) => {
|
|
138
|
-
onCanvasContextMenu(event as any)
|
|
139
|
-
}),
|
|
140
|
-
}}
|
|
129
|
+
onNodeContextMenu={useCallbackRef((event, node) => {
|
|
130
|
+
const diagramNode = nonNullable(
|
|
131
|
+
diagram.findDiagramNode(node.id as NodeId),
|
|
132
|
+
`diagramNode ${node.id} not found`,
|
|
133
|
+
)
|
|
134
|
+
onNodeContextMenu?.(diagramNode, event)
|
|
135
|
+
})}
|
|
136
|
+
onEdgeContextMenu={useCallbackRef((event, edge) => {
|
|
137
|
+
const diagramEdge = nonNullable(
|
|
138
|
+
diagram.findDiagramEdge(edge.id as EdgeId),
|
|
139
|
+
`diagramEdge ${edge.id} not found`,
|
|
140
|
+
)
|
|
141
|
+
onEdgeContextMenu?.(diagramEdge, event)
|
|
142
|
+
})}
|
|
143
|
+
onPaneContextMenu={useCallbackRef((event) => {
|
|
144
|
+
onCanvasContextMenu?.(event as any)
|
|
145
|
+
})}
|
|
141
146
|
{...enableFitView && {
|
|
142
147
|
onViewportResize,
|
|
143
148
|
}}
|
|
144
|
-
{
|
|
145
|
-
{
|
|
146
|
-
{...
|
|
149
|
+
nodesDraggable={nodesDraggable}
|
|
150
|
+
nodesSelectable={nodesSelectable}
|
|
151
|
+
{...(notReadOnly && nodesDraggable && layoutConstraints)}
|
|
152
|
+
{...props}>
|
|
153
|
+
<DiagramUI />
|
|
154
|
+
</BaseXYFlow>
|
|
147
155
|
)
|
|
148
|
-
}
|
|
156
|
+
}, shallowEqual)
|
|
@@ -27,7 +27,7 @@ import { IconArrowRight, IconFileSymlink, IconInfoCircle, IconZoomScan } from '@
|
|
|
27
27
|
import clsx from 'clsx'
|
|
28
28
|
import { type MouseEventHandler, type PropsWithChildren, forwardRef, Fragment, useCallback } from 'react'
|
|
29
29
|
import { filter, isTruthy, map, partition, pipe } from 'remeda'
|
|
30
|
-
import { Link } from '../../../
|
|
30
|
+
import { Link } from '../../../components/Link'
|
|
31
31
|
import { IfEnabled, useDiagramEventHandlers, useEnabledFeature } from '../../../context'
|
|
32
32
|
import { useMantinePortalProps } from '../../../hooks'
|
|
33
33
|
import { useDiagram } from '../../../hooks/useDiagram'
|
|
@@ -127,6 +127,7 @@ export function RelationshipsDropdownMenu({
|
|
|
127
127
|
closeOnClickOutside
|
|
128
128
|
clickOutsideEvents={['pointerdown', 'mousedown', 'click']}
|
|
129
129
|
closeOnEscape
|
|
130
|
+
keepMounted={false}
|
|
130
131
|
closeOnItemClick={false}
|
|
131
132
|
disabled={disabled}
|
|
132
133
|
position="bottom-start"
|
|
@@ -321,7 +321,7 @@ export function resetEdgeControlPoints({ context }: ActionArg): Partial<DiagramC
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
// Edge is a loop
|
|
324
|
-
if (source
|
|
324
|
+
if (source === target) {
|
|
325
325
|
const loopSize = 80
|
|
326
326
|
const centerOfTopBoundary = new VectorImpl(0, source.height || 0)
|
|
327
327
|
.mul(-0.5)
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
type EdgeId,
|
|
5
5
|
type Fqn,
|
|
6
6
|
type NodeId,
|
|
7
|
-
type RelationId,
|
|
8
7
|
type StepEdgeId,
|
|
9
8
|
type ViewChange,
|
|
10
9
|
type ViewId,
|
|
@@ -42,7 +41,7 @@ import {
|
|
|
42
41
|
} from 'xstate'
|
|
43
42
|
import { MinZoom } from '../../base/const'
|
|
44
43
|
import type { EnabledFeatures } from '../../context'
|
|
45
|
-
import { AllDisabled } from '../../context/DiagramFeatures'
|
|
44
|
+
import { type FeatureName, AllDisabled } from '../../context/DiagramFeatures'
|
|
46
45
|
import type { OpenSourceParams } from '../../LikeC4Diagram.props'
|
|
47
46
|
import { relationshipDetailsActor } from '../../overlays/relationship-details/actor'
|
|
48
47
|
import { relationshipsBrowserActor } from '../../overlays/relationships-browser/actor'
|
|
@@ -87,8 +86,12 @@ export interface Input {
|
|
|
87
86
|
fitViewPadding: number
|
|
88
87
|
}
|
|
89
88
|
|
|
89
|
+
type ToggledFeatures = Partial<EnabledFeatures>
|
|
90
|
+
|
|
90
91
|
export interface DiagramContext extends Input {
|
|
91
92
|
features: EnabledFeatures
|
|
93
|
+
// This is used to override features from props
|
|
94
|
+
toggledFeatures: ToggledFeatures
|
|
92
95
|
initialized: boolean
|
|
93
96
|
viewport: Viewport
|
|
94
97
|
viewportChangedManually: boolean
|
|
@@ -136,8 +139,8 @@ export namespace DiagramContext {
|
|
|
136
139
|
export type Events =
|
|
137
140
|
| HotKeyEvent
|
|
138
141
|
| { type: 'xyflow.init'; instance: ReactFlowInstance<Types.Node, Types.Edge> }
|
|
139
|
-
| { type: 'xyflow.
|
|
140
|
-
| { type: 'xyflow.
|
|
142
|
+
| { type: 'xyflow.applyNodeChanges'; changes: NodeChange<Types.Node>[] }
|
|
143
|
+
| { type: 'xyflow.applyEdgeChanges'; changes: EdgeChange<Types.Edge>[] }
|
|
141
144
|
| { type: 'xyflow.viewportMoved'; viewport: Viewport; manually: boolean }
|
|
142
145
|
| { type: 'xyflow.nodeClick'; node: Types.Node }
|
|
143
146
|
| { type: 'xyflow.edgeClick'; edge: Types.Edge }
|
|
@@ -166,6 +169,7 @@ export type Events =
|
|
|
166
169
|
| { type: 'walkthrough.start'; stepId?: StepEdgeId }
|
|
167
170
|
| { type: 'walkthrough.step'; direction: 'next' | 'previous' }
|
|
168
171
|
| { type: 'walkthrough.end' }
|
|
172
|
+
| { type: 'toggle.feature'; feature: FeatureName; forceValue?: boolean }
|
|
169
173
|
|
|
170
174
|
export type ActionArg = { context: DiagramContext; event: Events }
|
|
171
175
|
|
|
@@ -307,12 +311,24 @@ export const diagramMachine = setup({
|
|
|
307
311
|
})
|
|
308
312
|
})
|
|
309
313
|
},
|
|
314
|
+
|
|
315
|
+
'updateFeatures': assign(({ event }) => {
|
|
316
|
+
assertEvent(event, 'update.features')
|
|
317
|
+
return {
|
|
318
|
+
features: {
|
|
319
|
+
...event.features,
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
}),
|
|
310
323
|
},
|
|
311
324
|
}).createMachine({
|
|
312
325
|
initial: 'initializing',
|
|
313
326
|
context: ({ input, self, spawn }) => ({
|
|
314
327
|
...input,
|
|
315
328
|
features: { ...AllDisabled },
|
|
329
|
+
toggledFeatures: {
|
|
330
|
+
enableReadOnly: true,
|
|
331
|
+
},
|
|
316
332
|
initialized: false,
|
|
317
333
|
viewportChangedManually: false,
|
|
318
334
|
lastOnNavigate: null,
|
|
@@ -634,7 +650,7 @@ export const diagramMachine = setup({
|
|
|
634
650
|
pending: {
|
|
635
651
|
entry: enqueueActions(({ enqueue }) => {
|
|
636
652
|
enqueue.sendTo(c => c.context.syncLayoutActorRef, { type: 'stop' })
|
|
637
|
-
enqueue.stopChild('
|
|
653
|
+
enqueue.stopChild('syncLayout')
|
|
638
654
|
enqueue({
|
|
639
655
|
type: 'trigger:NavigateTo',
|
|
640
656
|
params: ({ context }) => ({
|
|
@@ -657,15 +673,16 @@ export const diagramMachine = setup({
|
|
|
657
673
|
},
|
|
658
674
|
},
|
|
659
675
|
})
|
|
676
|
+
enqueue.raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 80 })
|
|
660
677
|
} else {
|
|
661
678
|
enqueue({
|
|
662
679
|
type: 'xyflow:setViewportCenter',
|
|
663
680
|
params: getBBoxCenter(event.view.bounds),
|
|
664
681
|
})
|
|
682
|
+
enqueue.raise({ type: 'fitDiagram', duration: 200 }, { id: 'fitDiagram', delay: 25 })
|
|
665
683
|
}
|
|
666
684
|
enqueue.assign(updateNavigationHistory)
|
|
667
685
|
enqueue.assign(mergeXYNodesEdges)
|
|
668
|
-
enqueue.raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 75 })
|
|
669
686
|
}),
|
|
670
687
|
target: 'done',
|
|
671
688
|
},
|
|
@@ -765,12 +782,12 @@ export const diagramMachine = setup({
|
|
|
765
782
|
},
|
|
766
783
|
},
|
|
767
784
|
on: {
|
|
768
|
-
'xyflow.
|
|
785
|
+
'xyflow.applyNodeChanges': {
|
|
769
786
|
actions: assign({
|
|
770
787
|
xynodes: ({ context, event }) => applyNodeChanges(event.changes, context.xynodes),
|
|
771
788
|
}),
|
|
772
789
|
},
|
|
773
|
-
'xyflow.
|
|
790
|
+
'xyflow.applyEdgeChanges': {
|
|
774
791
|
actions: assign({
|
|
775
792
|
xyedges: ({ context, event }) => applyEdgeChanges(event.changes, context.xyedges),
|
|
776
793
|
}),
|
|
@@ -838,17 +855,19 @@ export const diagramMachine = setup({
|
|
|
838
855
|
},
|
|
839
856
|
},
|
|
840
857
|
})
|
|
858
|
+
enqueue.raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 80 })
|
|
841
859
|
} else {
|
|
842
860
|
enqueue({
|
|
843
861
|
type: 'xyflow:setViewportCenter',
|
|
844
862
|
params: getBBoxCenter(event.view.bounds),
|
|
845
863
|
})
|
|
864
|
+
enqueue.raise({ type: 'fitDiagram', duration: 200 }, { id: 'fitDiagram', delay: 25 })
|
|
846
865
|
}
|
|
847
866
|
} else {
|
|
848
867
|
enqueue.sendTo(c => c.context.syncLayoutActorRef, { type: 'synced' })
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
868
|
+
if (!context.viewportChangedManually) {
|
|
869
|
+
enqueue.raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 50 })
|
|
870
|
+
}
|
|
852
871
|
}
|
|
853
872
|
enqueue.assign(mergeXYNodesEdges)
|
|
854
873
|
enqueue.assign({
|
|
@@ -861,9 +880,7 @@ export const diagramMachine = setup({
|
|
|
861
880
|
actions: assign(({ event }) => ({ ...event.inputs })),
|
|
862
881
|
},
|
|
863
882
|
'update.features': {
|
|
864
|
-
actions:
|
|
865
|
-
features: ({ event }) => event.features,
|
|
866
|
-
}),
|
|
883
|
+
actions: 'updateFeatures',
|
|
867
884
|
},
|
|
868
885
|
'update.nodeData': {
|
|
869
886
|
actions: assign(updateNodeData),
|
|
@@ -919,6 +936,15 @@ export const diagramMachine = setup({
|
|
|
919
936
|
guard: 'is dynamic view',
|
|
920
937
|
target: '.walkthrough',
|
|
921
938
|
},
|
|
939
|
+
'toggle.feature': {
|
|
940
|
+
actions: assign({
|
|
941
|
+
toggledFeatures: ({ context, event }) => ({
|
|
942
|
+
...context.toggledFeatures,
|
|
943
|
+
[`enable${event.feature}`]: event.forceValue ??
|
|
944
|
+
!(context.toggledFeatures[`enable${event.feature}`] ?? context.features[`enable${event.feature}`]),
|
|
945
|
+
}),
|
|
946
|
+
}),
|
|
947
|
+
},
|
|
922
948
|
},
|
|
923
949
|
exit: [
|
|
924
950
|
stopChild('layout'),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ViewChange, ViewId } from '@likec4/core'
|
|
2
|
-
import { boxToRect, getBoundsOfRects, getNodeDimensions } from '@xyflow/system'
|
|
2
|
+
import { type Rect, boxToRect, getBoundsOfRects, getNodeDimensions } from '@xyflow/system'
|
|
3
3
|
import { hasAtLeast, reduce } from 'remeda'
|
|
4
4
|
import {
|
|
5
5
|
type ActorRef,
|
|
@@ -132,12 +132,7 @@ function createViewChange(parentContext: Machine.DiagramContext): ViewChange.Sav
|
|
|
132
132
|
|
|
133
133
|
const { nodeLookup } = xystore.getState()
|
|
134
134
|
const movedNodes = new Set<string>()
|
|
135
|
-
let bounds
|
|
136
|
-
x: 0,
|
|
137
|
-
y: 0,
|
|
138
|
-
width: 1,
|
|
139
|
-
height: 1,
|
|
140
|
-
}
|
|
135
|
+
let bounds: Rect | undefined
|
|
141
136
|
|
|
142
137
|
const nodes = reduce([...nodeLookup.values()], (acc, node) => {
|
|
143
138
|
const dimensions = getNodeDimensions(node)
|
|
@@ -154,7 +149,7 @@ function createViewChange(parentContext: Machine.DiagramContext): ViewChange.Sav
|
|
|
154
149
|
width: Math.ceil(dimensions.width),
|
|
155
150
|
height: Math.ceil(dimensions.height),
|
|
156
151
|
}
|
|
157
|
-
bounds = getBoundsOfRects(bounds, rect)
|
|
152
|
+
bounds = bounds ? getBoundsOfRects(bounds, rect) : rect
|
|
158
153
|
return acc
|
|
159
154
|
}, {} as ViewChange.SaveManualLayout['layout']['nodes'])
|
|
160
155
|
|
|
@@ -202,10 +197,12 @@ function createViewChange(parentContext: Machine.DiagramContext): ViewChange.Sav
|
|
|
202
197
|
x2: Math.ceil(Math.max(...allX)),
|
|
203
198
|
y2: Math.ceil(Math.max(...allY)),
|
|
204
199
|
})
|
|
205
|
-
bounds = getBoundsOfRects(bounds, rect)
|
|
200
|
+
bounds = bounds ? getBoundsOfRects(bounds, rect) : rect
|
|
206
201
|
return acc
|
|
207
202
|
}, {} as ViewChange.SaveManualLayout['layout']['edges'])
|
|
208
203
|
|
|
204
|
+
bounds ??= view.bounds
|
|
205
|
+
|
|
209
206
|
return {
|
|
210
207
|
op: 'save-manual-layout',
|
|
211
208
|
layout: {
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import { ActionIconGroup, Badge, Loader, Stack } from '@mantine/core'
|
|
2
|
-
import {
|
|
3
|
-
import { IconFileSymlink, IconFocusCentered, IconSearch } from '@tabler/icons-react'
|
|
1
|
+
import { ActionIconGroup, Badge, Group, Loader, Stack } from '@mantine/core'
|
|
2
|
+
import { IconFileSymlink, IconFocusCentered, IconMenu2 } from '@tabler/icons-react'
|
|
4
3
|
import { clsx } from 'clsx'
|
|
4
|
+
import { LayoutGroup } from 'framer-motion'
|
|
5
5
|
import { memo } from 'react'
|
|
6
|
-
import {
|
|
6
|
+
import { useDiagramEventHandlers, useEnabledFeatures } from '../../../context'
|
|
7
7
|
import { useMantinePortalProps } from '../../../hooks'
|
|
8
8
|
import { useDiagram } from '../../../hooks/useDiagram'
|
|
9
9
|
import { useDiagramSyncLayoutState } from '../../../hooks/useDiagramActor'
|
|
10
10
|
import { useDiagramContext } from '../../../hooks/useDiagramContext'
|
|
11
11
|
import { stopPropagation } from '../../../utils'
|
|
12
|
+
import { LikeC4Search } from '../search/LikeC4Search'
|
|
12
13
|
import { ActionIcon, Tooltip } from './_shared'
|
|
13
14
|
import { ChangeAutoLayoutButton } from './ChangeAutoLayoutButton'
|
|
14
15
|
import { LayoutDriftNotification } from './LayoutDriftNotification'
|
|
15
16
|
import { ManualLayoutToolsButton } from './ManualLayoutToolsButton'
|
|
16
17
|
import { NavigationButtons } from './NavigationButtons'
|
|
17
18
|
import * as css from './styles.css'
|
|
19
|
+
import { ToggleReadonly } from './ToggleReadonly'
|
|
18
20
|
|
|
19
21
|
export const Controls = memo(() => {
|
|
20
22
|
const { viewId, hasLayoutDrift, viewportChanged } = useDiagramContext(c => ({
|
|
@@ -25,41 +27,47 @@ export const Controls = memo(() => {
|
|
|
25
27
|
const diagram = useDiagram()
|
|
26
28
|
const {
|
|
27
29
|
onOpenSource,
|
|
30
|
+
onBurgerMenuClick,
|
|
28
31
|
} = useDiagramEventHandlers()
|
|
29
32
|
const {
|
|
30
33
|
enableControls,
|
|
31
34
|
enableSearch,
|
|
32
35
|
enableNavigationButtons,
|
|
33
36
|
enableReadOnly,
|
|
34
|
-
enableEdgeEditing,
|
|
35
37
|
enableVscode,
|
|
36
38
|
} = useEnabledFeatures()
|
|
37
39
|
const notReadOnly = !enableReadOnly
|
|
38
40
|
const portalProps = useMantinePortalProps()
|
|
41
|
+
|
|
39
42
|
return (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
<Group
|
|
44
|
+
align="flex-start"
|
|
45
|
+
className={clsx(
|
|
46
|
+
'react-flow__panel',
|
|
47
|
+
css.panel,
|
|
48
|
+
'likec4-top-left-panel',
|
|
49
|
+
)}
|
|
50
|
+
gap="xs"
|
|
51
|
+
onClick={stopPropagation}
|
|
52
|
+
>
|
|
53
|
+
<Stack align="flex-start" gap="xs">
|
|
54
|
+
<LayoutGroup>
|
|
55
|
+
<Group
|
|
56
|
+
className={clsx(css.navigationButtons, 'likec4-navigation-webview')}
|
|
57
|
+
gap={'xs'}>
|
|
58
|
+
{onBurgerMenuClick && (
|
|
54
59
|
<ActionIcon
|
|
55
60
|
onClick={e => {
|
|
56
61
|
e.stopPropagation()
|
|
57
|
-
|
|
62
|
+
onBurgerMenuClick()
|
|
58
63
|
}}>
|
|
59
|
-
<
|
|
64
|
+
<IconMenu2 />
|
|
60
65
|
</ActionIcon>
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
)}
|
|
67
|
+
{enableNavigationButtons && <NavigationButtons />}
|
|
68
|
+
</Group>
|
|
69
|
+
</LayoutGroup>
|
|
70
|
+
<ActionIconGroup className={css.actionIconGroup} orientation="vertical">
|
|
63
71
|
{enableVscode && (
|
|
64
72
|
<Tooltip label="Open source" {...portalProps}>
|
|
65
73
|
<ActionIcon
|
|
@@ -71,6 +79,7 @@ export const Controls = memo(() => {
|
|
|
71
79
|
</ActionIcon>
|
|
72
80
|
</Tooltip>
|
|
73
81
|
)}
|
|
82
|
+
{enableControls && <ToggleReadonly />}
|
|
74
83
|
{enableControls && hasLayoutDrift && <LayoutDriftNotification {...portalProps} />}
|
|
75
84
|
{enableControls && notReadOnly && (
|
|
76
85
|
<>
|
|
@@ -90,9 +99,12 @@ export const Controls = memo(() => {
|
|
|
90
99
|
</Tooltip>
|
|
91
100
|
)}
|
|
92
101
|
</ActionIconGroup>
|
|
93
|
-
{notReadOnly && <SyncLayoutBadge />}
|
|
94
102
|
</Stack>
|
|
95
|
-
|
|
103
|
+
<Group>
|
|
104
|
+
{enableSearch && <LikeC4Search />}
|
|
105
|
+
{notReadOnly && <SyncLayoutBadge />}
|
|
106
|
+
</Group>
|
|
107
|
+
</Group>
|
|
96
108
|
)
|
|
97
109
|
})
|
|
98
110
|
|