@likec4/diagram 1.39.5 → 1.40.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/bundle/{chunk-CW_KClZe.js → chunk-CDr-4Ymp.js} +936 -903
- package/bundle/custom.js +61 -60
- package/bundle/index.js +2667 -539
- package/dist/LikeC4Diagram.d.ts +1 -1
- package/dist/LikeC4Diagram.js +7 -4
- package/dist/LikeC4Diagram.props.d.ts +6 -1
- package/dist/base/const.d.ts +9 -0
- package/dist/base/const.js +10 -1
- package/dist/base/index.d.ts +1 -0
- package/dist/base/primitives/edge/EdgeLabel.js +2 -1
- package/dist/base/primitives/edge/EdgeLabelContainer.js +10 -4
- package/dist/base/primitives/edge/EdgePath.js +17 -8
- package/dist/base/primitives/edge/edge.css.d.ts +0 -1
- package/dist/base/primitives/edge/edge.css.js +6 -14
- package/dist/base/primitives/element/ElementActionButtons.js +1 -3
- package/dist/base/primitives/element/ElementNodeContainer.d.ts +0 -6
- package/dist/base/primitives/element/ElementNodeContainer.js +3 -26
- package/dist/base/primitives/element/ElementTitle.js +2 -2
- package/dist/base/types.d.ts +3 -3
- package/dist/bundle/LikeC4View.js +4 -12
- package/dist/bundle/LikeC4View.props.d.ts +12 -3
- package/dist/bundle/custom.js +1 -1
- package/dist/custom/customNodes.d.ts +6 -0
- package/dist/custom/customNodes.js +2 -1
- package/dist/custom/index.js +1 -1
- package/dist/hooks/useDiagram.d.ts +5 -1
- package/dist/hooks/useDiagram.js +3 -0
- package/dist/hooks/useXYFlow.d.ts +4 -439
- package/dist/likec4diagram/DiagramUI.js +1 -1
- package/dist/likec4diagram/DiagramXYFlow.js +8 -4
- package/dist/likec4diagram/convert-to-xyflow.d.ts +14 -0
- package/dist/likec4diagram/convert-to-xyflow.js +29 -0
- package/dist/likec4diagram/custom/edges/RelationshipEdge.d.ts +3 -3
- package/dist/likec4diagram/custom/edges/RelationshipEdge.js +0 -1
- package/dist/likec4diagram/custom/edges/SequenceStepEdge.d.ts +3 -0
- package/dist/likec4diagram/custom/edges/SequenceStepEdge.js +71 -0
- package/dist/likec4diagram/custom/index.js +7 -2
- package/dist/likec4diagram/custom/nodes/ElementActions.d.ts +2 -1
- package/dist/likec4diagram/custom/nodes/SequenceActorNode.d.ts +4 -0
- package/dist/likec4diagram/custom/nodes/SequenceActorNode.js +157 -0
- package/dist/likec4diagram/custom/nodes/index.d.ts +1 -0
- package/dist/likec4diagram/custom/nodes/nodes.d.ts +1 -2
- package/dist/likec4diagram/relationship-popover/RelationshipPopover.js +31 -27
- package/dist/likec4diagram/types.d.ts +41 -7
- package/dist/likec4diagram/xyflow-diagram/diagram-view.d.ts +11 -0
- package/dist/likec4diagram/{useViewToNodesEdges.js → xyflow-diagram/diagram-view.js} +4 -13
- package/dist/likec4diagram/xyflow-sequence/_types.d.ts +41 -0
- package/dist/likec4diagram/xyflow-sequence/const.d.ts +17 -0
- package/dist/likec4diagram/xyflow-sequence/const.js +16 -0
- package/dist/likec4diagram/xyflow-sequence/layouter.d.ts +46 -0
- package/dist/likec4diagram/xyflow-sequence/layouter.js +408 -0
- package/dist/likec4diagram/xyflow-sequence/sequence-view.d.ts +7 -0
- package/dist/likec4diagram/xyflow-sequence/sequence-view.js +273 -0
- package/dist/likec4diagram/xyflow-sequence/utils.d.ts +14 -0
- package/dist/likec4diagram/xyflow-sequence/utils.js +92 -0
- package/dist/navigationpanel/NavigationPanelControls.js +2 -2
- package/dist/navigationpanel/controls/ViewDetailsCard.js +5 -2
- package/dist/navigationpanel/dropdown/ProjectsMenu.js +53 -44
- package/dist/navigationpanel/walkthrough/ActiveWalkthroughControls.d.ts +1 -2
- package/dist/navigationpanel/walkthrough/ActiveWalkthroughControls.js +10 -41
- package/dist/navigationpanel/walkthrough/DynamicViewControls.d.ts +292 -0
- package/dist/navigationpanel/walkthrough/DynamicViewControls.js +119 -0
- package/dist/navigationpanel/walkthrough/index.d.ts +2 -1
- package/dist/overlays/relationship-details/RelationshipDetails.js +4 -1
- package/dist/state/DiagramActorProvider.d.ts +3 -2
- package/dist/state/DiagramActorProvider.js +19 -11
- package/dist/state/assign.js +11 -4
- package/dist/state/diagram-machine.d.ts +224 -194
- package/dist/state/diagram-machine.js +170 -63
- package/dist/state/utils.d.ts +7 -1
- package/dist/state/utils.js +67 -7
- package/dist/styles-min.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css2.js +1 -1
- package/package.json +8 -7
- package/panda.buildinfo.json +30 -9
- package/src/LikeC4Diagram.props.ts +14 -1
- package/src/LikeC4Diagram.tsx +7 -4
- package/src/base/const.ts +12 -0
- package/src/base/index.ts +2 -0
- package/src/base/primitives/edge/EdgeLabel.tsx +23 -19
- package/src/base/primitives/edge/EdgeLabelContainer.tsx +12 -7
- package/src/base/primitives/edge/EdgePath.tsx +25 -14
- package/src/base/primitives/edge/edge.css.ts +5 -14
- package/src/base/primitives/element/ElementActionButtons.tsx +1 -3
- package/src/base/primitives/element/ElementNodeContainer.tsx +3 -30
- package/src/base/primitives/element/ElementTitle.tsx +2 -3
- package/src/base/types.ts +7 -5
- package/src/bundle/LikeC4View.props.ts +14 -3
- package/src/bundle/LikeC4View.tsx +4 -14
- package/src/bundle/ReactLikeC4.tsx +1 -2
- package/src/custom/customNodes.ts +7 -0
- package/src/hooks/useDiagram.ts +10 -0
- package/src/likec4diagram/DiagramUI.tsx +1 -1
- package/src/likec4diagram/DiagramXYFlow.tsx +10 -5
- package/src/likec4diagram/convert-to-xyflow.ts +45 -0
- package/src/likec4diagram/custom/edges/RelationshipEdge.tsx +0 -1
- package/src/likec4diagram/custom/edges/SequenceStepEdge.tsx +65 -0
- package/src/likec4diagram/custom/index.ts +7 -1
- package/src/likec4diagram/custom/nodes/ElementActions.tsx +4 -2
- package/src/likec4diagram/custom/nodes/SequenceActorNode.tsx +175 -0
- package/src/likec4diagram/custom/nodes/index.ts +1 -0
- package/src/likec4diagram/custom/nodes/nodes.tsx +1 -1
- package/src/likec4diagram/relationship-popover/RelationshipPopover.tsx +36 -30
- package/src/likec4diagram/types.ts +96 -13
- package/src/likec4diagram/{useViewToNodesEdges.ts → xyflow-diagram/diagram-view.ts} +7 -28
- package/src/likec4diagram/xyflow-sequence/_types.ts +45 -0
- package/src/likec4diagram/xyflow-sequence/const.ts +37 -0
- package/src/likec4diagram/xyflow-sequence/layouter.ts +567 -0
- package/src/likec4diagram/xyflow-sequence/sequence-view.ts +337 -0
- package/src/likec4diagram/xyflow-sequence/utils.ts +112 -0
- package/src/navigationpanel/NavigationPanelControls.tsx +2 -2
- package/src/navigationpanel/controls/ViewDetailsCard.tsx +5 -2
- package/src/navigationpanel/dropdown/ProjectsMenu.tsx +5 -1
- package/src/navigationpanel/walkthrough/ActiveWalkthroughControls.tsx +5 -40
- package/src/navigationpanel/walkthrough/DynamicViewControls.tsx +125 -0
- package/src/navigationpanel/walkthrough/index.tsx +2 -1
- package/src/overlays/relationship-details/RelationshipDetails.tsx +5 -2
- package/src/state/DiagramActorProvider.tsx +21 -10
- package/src/state/assign.ts +18 -5
- package/src/state/diagram-machine.ts +207 -75
- package/src/state/utils.ts +81 -7
- package/dist/likec4diagram/useViewToNodesEdges.d.ts +0 -12
|
@@ -5,9 +5,9 @@ import { shallowEqual, useCallbackRef } from '@mantine/hooks'
|
|
|
5
5
|
import { IconX } from '@tabler/icons-react'
|
|
6
6
|
import { memo, useState } from 'react'
|
|
7
7
|
import { isBoolean } from 'remeda'
|
|
8
|
+
import { FitViewPaddings } from '../base/const'
|
|
8
9
|
import { FramerMotionConfig } from '../context/FramerMotionConfig'
|
|
9
10
|
import { LikeC4Diagram } from '../LikeC4Diagram'
|
|
10
|
-
import type { ViewPadding } from '../LikeC4Diagram.props'
|
|
11
11
|
import { useLikeC4Model } from '../likec4model/useLikeC4Model'
|
|
12
12
|
import { Overlay } from '../overlays/overlay/Overlay'
|
|
13
13
|
import type { LikeC4ViewProps } from './LikeC4View.props'
|
|
@@ -37,13 +37,6 @@ export function LikeC4View<A extends t.aux.Any = t.aux.UnknownLayouted>({
|
|
|
37
37
|
return <LikeC4ViewInner view={view.$view} {...props} />
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const DEFAULT_BROWSER_PADDING: ViewPadding = {
|
|
41
|
-
top: '50px',
|
|
42
|
-
bottom: '16px',
|
|
43
|
-
left: '16px',
|
|
44
|
-
right: '16px',
|
|
45
|
-
}
|
|
46
|
-
|
|
47
40
|
type LikeC4ViewInnerProps<A extends t.aux.Any> = Omit<LikeC4ViewProps<A>, 'viewId'> & {
|
|
48
41
|
view: t.DiagramView<A>
|
|
49
42
|
}
|
|
@@ -56,8 +49,6 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<t.aux.UnknownLayouted>>(({
|
|
|
56
49
|
colorScheme: explicitColorScheme,
|
|
57
50
|
injectFontCss = true,
|
|
58
51
|
controls = false,
|
|
59
|
-
fitView = true,
|
|
60
|
-
fitViewPadding = '16px',
|
|
61
52
|
background = 'transparent',
|
|
62
53
|
browser = true,
|
|
63
54
|
showDiagramTitle = false,
|
|
@@ -129,8 +120,8 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<t.aux.UnknownLayouted>>(({
|
|
|
129
120
|
pannable={pannable}
|
|
130
121
|
zoomable={zoomable}
|
|
131
122
|
background={background}
|
|
132
|
-
fitView={
|
|
133
|
-
fitViewPadding={
|
|
123
|
+
fitView={true}
|
|
124
|
+
fitViewPadding={FitViewPaddings.default}
|
|
134
125
|
showDiagramTitle={showDiagramTitle}
|
|
135
126
|
showNotations={showNotations && hasNotations}
|
|
136
127
|
enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
|
|
@@ -152,7 +143,6 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<t.aux.UnknownLayouted>>(({
|
|
|
152
143
|
// so we don't want enable search and hotkeys
|
|
153
144
|
enableSearch={false}
|
|
154
145
|
{...isBrowserEnabled && {
|
|
155
|
-
onNavigateTo: onNavigateTo,
|
|
156
146
|
onCanvasClick: onNavigateToThisView,
|
|
157
147
|
onNodeClick: onNavigateToThisView,
|
|
158
148
|
}}
|
|
@@ -176,8 +166,8 @@ const LikeC4ViewInner = memo<LikeC4ViewInnerProps<t.aux.UnknownLayouted>>(({
|
|
|
176
166
|
controls="next"
|
|
177
167
|
readonly
|
|
178
168
|
fitView
|
|
179
|
-
fitViewPadding={DEFAULT_BROWSER_PADDING}
|
|
180
169
|
{...props}
|
|
170
|
+
fitViewPadding={FitViewPaddings.withControls}
|
|
181
171
|
{...browserProps}
|
|
182
172
|
showNotations={(browserProps.showNotations ?? true) &&
|
|
183
173
|
(browserView.notation?.nodes.length ?? 0) > 0}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type * as t from '@likec4/core/types'
|
|
2
|
-
import type { DiagramView } from '@likec4/core/types'
|
|
3
2
|
import { cx } from '@likec4/styles/css'
|
|
4
3
|
import { type CSSProperties } from 'react'
|
|
5
4
|
import { isFunction, isString } from 'remeda'
|
|
@@ -65,7 +64,7 @@ export function ReactLikeC4<A extends t.aux.Any = t.aux.UnknownLayouted>({
|
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
type ReactLikeC4InnerProps<A extends t.aux.Any> = Omit<ReactLikeC4Props<A>, 'viewId'> & {
|
|
68
|
-
view: DiagramView<A>
|
|
67
|
+
view: t.DiagramView<A>
|
|
69
68
|
}
|
|
70
69
|
function ReactLikeC4Inner<A extends t.aux.Any>({
|
|
71
70
|
className,
|
|
@@ -266,10 +266,17 @@ export type CustomViewGroupNodeProps = {
|
|
|
266
266
|
nodeModel: NodeModel.IsGroup<Any>
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
export const sequenceActorNode: CustomNodeComponent<CustomSequenceActorNodeProps> = customDiagramNode as any
|
|
270
|
+
export type CustomSequenceActorNodeProps = {
|
|
271
|
+
nodeProps: NodeProps<Types.SequenceActorNodeData, 'seq-actor'>
|
|
272
|
+
nodeModel: NodeModel.WithElement<Any>
|
|
273
|
+
}
|
|
274
|
+
|
|
269
275
|
export interface CustomNodes {
|
|
270
276
|
element?: FunctionComponent<NodeProps<Types.ElementNodeData, 'element'>>
|
|
271
277
|
deployment?: FunctionComponent<NodeProps<Types.DeploymentElementNodeData, 'deployment'>>
|
|
272
278
|
compoundElement?: FunctionComponent<NodeProps<Types.CompoundElementNodeData, 'compound-element'>>
|
|
273
279
|
compoundDeployment?: FunctionComponent<NodeProps<Types.CompoundDeploymentNodeData, 'compound-deployment'>>
|
|
274
280
|
viewGroup?: FunctionComponent<NodeProps<Types.ViewGroupNodeData, 'view-group'>>
|
|
281
|
+
sequenceActor?: FunctionComponent<NodeProps<Types.SequenceActorNodeData, 'seq-actor'>>
|
|
275
282
|
}
|
package/src/hooks/useDiagram.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type DiagramEdge,
|
|
4
4
|
type DiagramNode,
|
|
5
5
|
type DiagramView,
|
|
6
|
+
type DynamicViewDisplayVariant,
|
|
6
7
|
type NodeId,
|
|
7
8
|
type NodeNotation as ElementNotation,
|
|
8
9
|
type scalar,
|
|
@@ -115,6 +116,11 @@ export interface DiagramApi {
|
|
|
115
116
|
highlightNotation(notation: ElementNotation, kind?: string): void
|
|
116
117
|
unhighlightNotation(): void
|
|
117
118
|
openSearch(searchValue?: string): void
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Switch dynamic view display variant
|
|
122
|
+
*/
|
|
123
|
+
switchDynamicViewVariant(variant: DynamicViewDisplayVariant): void
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
/**
|
|
@@ -246,6 +252,10 @@ export function useDiagram(): DiagramApi {
|
|
|
246
252
|
openSearch: (searchValue?: string) => {
|
|
247
253
|
actor.send({ type: 'open.search', ...(searchValue && { search: searchValue }) })
|
|
248
254
|
},
|
|
255
|
+
|
|
256
|
+
switchDynamicViewVariant: (variant: DynamicViewDisplayVariant) => {
|
|
257
|
+
actor.send({ type: 'switch.dynamicViewVariant', variant })
|
|
258
|
+
},
|
|
249
259
|
}), [actor])
|
|
250
260
|
}
|
|
251
261
|
|
|
@@ -32,7 +32,7 @@ export const DiagramUI = memo(() => {
|
|
|
32
32
|
: <Controls />}
|
|
33
33
|
{overlaysActorRef && <Overlays overlaysActorRef={overlaysActorRef} />}
|
|
34
34
|
{enableNotations && <NotationPanel />}
|
|
35
|
-
{enableControls
|
|
35
|
+
{enableControls === true && (
|
|
36
36
|
<>
|
|
37
37
|
{enableViewTitle && <DiagramTitlePanel />}
|
|
38
38
|
{enableDynamicViewWalkthrough && <DynamicViewWalkthrough />}
|
|
@@ -3,7 +3,7 @@ import { cx } from '@likec4/styles/css'
|
|
|
3
3
|
import { useCallbackRef, useDebouncedCallback, useTimeout } from '@mantine/hooks'
|
|
4
4
|
import { useCustomCompareMemo } from '@react-hookz/web'
|
|
5
5
|
import type { OnMove, OnMoveEnd } from '@xyflow/system'
|
|
6
|
-
import { shallowEqual } from 'fast-equals'
|
|
6
|
+
import { deepEqual, shallowEqual } from 'fast-equals'
|
|
7
7
|
import { type PropsWithChildren } from 'react'
|
|
8
8
|
import type { Simplify } from 'type-fest'
|
|
9
9
|
import { BaseXYFlow } from '../base/BaseXYFlow'
|
|
@@ -13,7 +13,7 @@ import { useUpdateEffect } from '../hooks'
|
|
|
13
13
|
import { useDiagram, useDiagramContext } from '../hooks/useDiagram'
|
|
14
14
|
import type { LikeC4DiagramProperties } from '../LikeC4Diagram.props'
|
|
15
15
|
import type { DiagramContext } from '../state/types'
|
|
16
|
-
import { BuiltinNodes as defaultNodeTypes, edgeTypes } from './custom'
|
|
16
|
+
import { BuiltinNodes as defaultNodeTypes, edgeTypes, SequenceParallelArea } from './custom'
|
|
17
17
|
import { DiagramUI } from './DiagramUI'
|
|
18
18
|
import type { Types } from './types'
|
|
19
19
|
import { useLayoutConstraints } from './useLayoutConstraints'
|
|
@@ -26,7 +26,9 @@ const selectXYProps = (ctx: DiagramContext) => ({
|
|
|
26
26
|
zoomable: ctx.zoomable,
|
|
27
27
|
fitViewPadding: ctx.fitViewPadding,
|
|
28
28
|
enableFitView: ctx.features.enableFitView,
|
|
29
|
-
enableReadOnly: ctx.features.enableReadOnly || ctx.toggledFeatures.enableReadOnly
|
|
29
|
+
enableReadOnly: ctx.features.enableReadOnly || ctx.toggledFeatures.enableReadOnly
|
|
30
|
+
// if dynamic view display mode is sequence, enable readonly
|
|
31
|
+
|| (ctx.dynamicViewVariant === 'sequence' && ctx.view._type === 'dynamic'),
|
|
30
32
|
...(!ctx.features.enableFitView && {
|
|
31
33
|
viewport: {
|
|
32
34
|
x: -Math.min(ctx.view.bounds.x, 0),
|
|
@@ -41,7 +43,7 @@ const equalsXYProps = (a: ReturnType<typeof selectXYProps>, b: ReturnType<typeof
|
|
|
41
43
|
a.zoomable === b.zoomable &&
|
|
42
44
|
a.enableFitView === b.enableFitView &&
|
|
43
45
|
a.enableReadOnly === b.enableReadOnly &&
|
|
44
|
-
|
|
46
|
+
deepEqual(a.fitViewPadding, b.fitViewPadding) &&
|
|
45
47
|
shallowEqual(a.nodes, b.nodes) &&
|
|
46
48
|
shallowEqual(a.edges, b.edges) &&
|
|
47
49
|
shallowEqual(a.viewport ?? null, b.viewport ?? null)
|
|
@@ -123,7 +125,9 @@ export function LikeC4DiagramXYFlow({
|
|
|
123
125
|
'compound-element': renderNodes?.compoundElement ?? defaultNodeTypes.compoundElement,
|
|
124
126
|
'compound-deployment': renderNodes?.compoundDeployment ?? defaultNodeTypes.compoundDeployment,
|
|
125
127
|
'view-group': renderNodes?.viewGroup ?? defaultNodeTypes.viewGroup,
|
|
126
|
-
|
|
128
|
+
'seq-actor': renderNodes?.sequenceActor ?? defaultNodeTypes.sequenceActor,
|
|
129
|
+
'seq-parallel': SequenceParallelArea,
|
|
130
|
+
} satisfies Record<Types.Node['type'], any>
|
|
127
131
|
},
|
|
128
132
|
[renderNodes],
|
|
129
133
|
shallowEqual,
|
|
@@ -224,6 +228,7 @@ export function LikeC4DiagramXYFlow({
|
|
|
224
228
|
}}
|
|
225
229
|
nodesDraggable={notReadOnly && nodesDraggable}
|
|
226
230
|
nodesSelectable={nodesSelectable}
|
|
231
|
+
edgesFocusable={false}
|
|
227
232
|
{...(notReadOnly && nodesDraggable && layoutConstraints)}
|
|
228
233
|
{...props}
|
|
229
234
|
{...reactFlowProps}>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DynamicViewDisplayVariant, LayoutedView, WhereOperator } from '@likec4/core/types'
|
|
2
|
+
import type { Types } from './types'
|
|
3
|
+
import { diagramToXY } from './xyflow-diagram/diagram-view'
|
|
4
|
+
import { sequenceViewToXY } from './xyflow-sequence/sequence-view'
|
|
5
|
+
|
|
6
|
+
type ConvertToXYFlowInput = {
|
|
7
|
+
view: LayoutedView
|
|
8
|
+
where: WhereOperator | null
|
|
9
|
+
nodesSelectable: boolean
|
|
10
|
+
dynamicViewVariant: DynamicViewDisplayVariant
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function convertToXYFlow({ dynamicViewVariant, ...params }: ConvertToXYFlowInput): {
|
|
14
|
+
view: LayoutedView
|
|
15
|
+
xynodes: Types.Node[]
|
|
16
|
+
xyedges: Types.Edge[]
|
|
17
|
+
} {
|
|
18
|
+
const view = params.view
|
|
19
|
+
const isDynamic = view._type === 'dynamic'
|
|
20
|
+
|
|
21
|
+
const { bounds, xynodes, xyedges } = isDynamic && dynamicViewVariant === 'sequence'
|
|
22
|
+
? sequenceViewToXY(view)
|
|
23
|
+
: diagramToXY(params)
|
|
24
|
+
|
|
25
|
+
if (isDynamic && view.variant !== dynamicViewVariant) {
|
|
26
|
+
return {
|
|
27
|
+
view: {
|
|
28
|
+
...view,
|
|
29
|
+
bounds,
|
|
30
|
+
variant: dynamicViewVariant,
|
|
31
|
+
},
|
|
32
|
+
xynodes,
|
|
33
|
+
xyedges,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
view: bounds === view.bounds ? view : {
|
|
39
|
+
...view,
|
|
40
|
+
bounds,
|
|
41
|
+
},
|
|
42
|
+
xynodes,
|
|
43
|
+
xyedges,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getSmoothStepPath } from '@xyflow/system'
|
|
2
|
+
import type { EdgeProps } from '../../../base'
|
|
3
|
+
import { EdgeActionButton, EdgeContainer, EdgeLabel, EdgeLabelContainer, EdgePath } from '../../../base/primitives'
|
|
4
|
+
import { useDiagram } from '../../../hooks/useDiagram'
|
|
5
|
+
import type { Types } from '../../types'
|
|
6
|
+
|
|
7
|
+
const LABEL_OFFSET = 16
|
|
8
|
+
export function SequenceStepEdge(props: EdgeProps<Types.SequenceStepEdgeData>) {
|
|
9
|
+
const diagram = useDiagram()
|
|
10
|
+
const { navigateTo } = props.data
|
|
11
|
+
const isSelfLoop = props.source === props.target
|
|
12
|
+
const isBack = props.sourceX > props.targetX
|
|
13
|
+
const [path] = getSmoothStepPath({
|
|
14
|
+
sourceX: props.sourceX,
|
|
15
|
+
sourceY: props.sourceY,
|
|
16
|
+
sourcePosition: props.sourcePosition,
|
|
17
|
+
targetX: props.targetX,
|
|
18
|
+
targetY: props.targetY,
|
|
19
|
+
targetPosition: props.targetPosition,
|
|
20
|
+
...(isSelfLoop && {
|
|
21
|
+
offset: 30,
|
|
22
|
+
borderRadius: 16,
|
|
23
|
+
}),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
let labelX = props.sourceX
|
|
27
|
+
switch (true) {
|
|
28
|
+
case isSelfLoop:
|
|
29
|
+
labelX = props.sourceX + 24 + LABEL_OFFSET
|
|
30
|
+
break
|
|
31
|
+
case isBack:
|
|
32
|
+
labelX = props.sourceX - LABEL_OFFSET
|
|
33
|
+
break
|
|
34
|
+
default:
|
|
35
|
+
labelX = props.sourceX + LABEL_OFFSET
|
|
36
|
+
break
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<EdgeContainer {...props}>
|
|
41
|
+
<EdgePath
|
|
42
|
+
edgeProps={props}
|
|
43
|
+
svgPath={path}
|
|
44
|
+
/>
|
|
45
|
+
<EdgeLabelContainer
|
|
46
|
+
edgeProps={props}
|
|
47
|
+
labelPosition={{
|
|
48
|
+
x: labelX,
|
|
49
|
+
y: props.sourceY + (!isSelfLoop ? LABEL_OFFSET : 0),
|
|
50
|
+
translate: isBack ? 'translate(-100%, 0)' : undefined,
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<EdgeLabel edgeProps={props}>
|
|
54
|
+
{navigateTo && (
|
|
55
|
+
<EdgeActionButton
|
|
56
|
+
onClick={e => {
|
|
57
|
+
e.stopPropagation()
|
|
58
|
+
diagram.navigateTo(navigateTo)
|
|
59
|
+
}} />
|
|
60
|
+
)}
|
|
61
|
+
</EdgeLabel>
|
|
62
|
+
</EdgeLabelContainer>
|
|
63
|
+
</EdgeContainer>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { CustomNodes } from '../../custom/customNodes'
|
|
2
2
|
import type { Types } from '../types'
|
|
3
3
|
import { RelationshipEdge } from './edges/RelationshipEdge'
|
|
4
|
+
import { SequenceStepEdge } from './edges/SequenceStepEdge'
|
|
4
5
|
import {
|
|
5
6
|
CompoundDeploymentNode,
|
|
6
7
|
CompoundElementNode,
|
|
7
8
|
DeploymentNode,
|
|
8
9
|
ElementNode,
|
|
10
|
+
SequenceActorNode,
|
|
9
11
|
ViewGroupNode,
|
|
10
|
-
} from './nodes
|
|
12
|
+
} from './nodes'
|
|
11
13
|
|
|
12
14
|
export const BuiltinNodes = {
|
|
13
15
|
element: ElementNode,
|
|
@@ -15,8 +17,12 @@ export const BuiltinNodes = {
|
|
|
15
17
|
compoundElement: CompoundElementNode,
|
|
16
18
|
compoundDeployment: CompoundDeploymentNode,
|
|
17
19
|
viewGroup: ViewGroupNode,
|
|
20
|
+
sequenceActor: SequenceActorNode,
|
|
18
21
|
} satisfies Required<CustomNodes>
|
|
19
22
|
|
|
23
|
+
export { SequenceParallelArea } from './nodes/SequenceActorNode'
|
|
24
|
+
|
|
20
25
|
export const edgeTypes = {
|
|
21
26
|
relationship: RelationshipEdge,
|
|
27
|
+
'seq-step': SequenceStepEdge,
|
|
22
28
|
} satisfies { [key in Types.Edge['type']]: any }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NodeId } from '@likec4/core'
|
|
1
|
+
import type { NodeId } from '@likec4/core/types'
|
|
2
2
|
import { IconTransform, IconZoomScan } from '@tabler/icons-react'
|
|
3
3
|
import { useMemo } from 'react'
|
|
4
4
|
import { hasAtLeast } from 'remeda'
|
|
@@ -8,6 +8,8 @@ import { useEnabledFeatures } from '../../../context/DiagramFeatures'
|
|
|
8
8
|
import { useDiagram } from '../../../hooks/useDiagram'
|
|
9
9
|
import type { Types } from '../../types'
|
|
10
10
|
|
|
11
|
+
type RequiredData = Pick<Types.ElementNodeData, 'modelFqn' | 'navigateTo'>
|
|
12
|
+
|
|
11
13
|
type WithExtraButtons = {
|
|
12
14
|
/**
|
|
13
15
|
* Add extra action buttons
|
|
@@ -31,7 +33,7 @@ type WithExtraButtons = {
|
|
|
31
33
|
extraButtons?: ElementActionButtons.Item[]
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
export type ElementActionsProps = NodeProps<
|
|
36
|
+
export type ElementActionsProps = NodeProps<RequiredData> & WithExtraButtons
|
|
35
37
|
|
|
36
38
|
export const ElementActions = ({
|
|
37
39
|
extraButtons,
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { defaultTheme } from '@likec4/core'
|
|
2
|
+
import { css } from '@likec4/styles/css'
|
|
3
|
+
import { Box } from '@likec4/styles/jsx'
|
|
4
|
+
import type { NodeProps } from '@xyflow/react'
|
|
5
|
+
import { Handle } from '@xyflow/react'
|
|
6
|
+
import { Position } from '@xyflow/system'
|
|
7
|
+
import { ElementNodeContainer, ElementShape, ElementTitle } from '../../../custom'
|
|
8
|
+
import { type Types } from '../../types'
|
|
9
|
+
import { ElementActions } from './ElementActions'
|
|
10
|
+
import { ElementDetailsButtonWithHandler } from './nodes'
|
|
11
|
+
|
|
12
|
+
const positionMap: Record<Types.SequenceActorNodePort['position'], Position> = {
|
|
13
|
+
left: Position.Left,
|
|
14
|
+
right: Position.Right,
|
|
15
|
+
top: Position.Top,
|
|
16
|
+
bottom: Position.Bottom,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ActorStepPort = ({
|
|
20
|
+
data,
|
|
21
|
+
port: p,
|
|
22
|
+
}: {
|
|
23
|
+
data: Types.SequenceActorNodeData
|
|
24
|
+
port: Types.SequenceActorNodePort
|
|
25
|
+
}) => {
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<Box
|
|
29
|
+
data-likec4-color={data.color}
|
|
30
|
+
className={css({
|
|
31
|
+
position: 'absolute',
|
|
32
|
+
backgroundColor: 'var(--likec4-palette-fill)',
|
|
33
|
+
rounded: 'xs',
|
|
34
|
+
width: {
|
|
35
|
+
base: '5px',
|
|
36
|
+
_whenHovered: '7px',
|
|
37
|
+
_whenSelected: '7px',
|
|
38
|
+
},
|
|
39
|
+
// pointerEvents: 'none',
|
|
40
|
+
transition: 'fast',
|
|
41
|
+
translateX: '-1/2',
|
|
42
|
+
translateY: '-1/2',
|
|
43
|
+
translate: 'auto',
|
|
44
|
+
// transform: 'translate',
|
|
45
|
+
})}
|
|
46
|
+
style={{
|
|
47
|
+
top: p.cy,
|
|
48
|
+
left: p.cx,
|
|
49
|
+
height: p.height,
|
|
50
|
+
// transform: p.type === 'source' ? 'translate(-50%, -16px)' : 'translate(-50%, -50%)',
|
|
51
|
+
// transform: 'translate(-50%, -50%)',
|
|
52
|
+
// zIndex: p.type === 'source' ? 1 : 0,
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
<Handle
|
|
56
|
+
id={p.id}
|
|
57
|
+
type={p.type}
|
|
58
|
+
position={positionMap[p.position]}
|
|
59
|
+
style={{
|
|
60
|
+
top: p.cy - 3,
|
|
61
|
+
left: p.cx - 3,
|
|
62
|
+
width: 6,
|
|
63
|
+
height: 6,
|
|
64
|
+
right: 'unset',
|
|
65
|
+
bottom: 'unset',
|
|
66
|
+
visibility: 'hidden',
|
|
67
|
+
transform: p.position === 'left' ? 'translate(-150%, 0)' : 'translate(100%, 0)',
|
|
68
|
+
}} />
|
|
69
|
+
</>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function SequenceActorNode(props: NodeProps<Types.SequenceActorNode>) {
|
|
74
|
+
const data = props.data
|
|
75
|
+
const {
|
|
76
|
+
positionAbsoluteY,
|
|
77
|
+
data: {
|
|
78
|
+
viewHeight,
|
|
79
|
+
hovered: isHovered = false,
|
|
80
|
+
ports,
|
|
81
|
+
},
|
|
82
|
+
} = props
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
<Box
|
|
87
|
+
className={css({
|
|
88
|
+
position: 'absolute',
|
|
89
|
+
rounded: 'xs',
|
|
90
|
+
top: '1',
|
|
91
|
+
pointerEvents: 'none',
|
|
92
|
+
transition: 'fast',
|
|
93
|
+
translateX: '-1/2',
|
|
94
|
+
translate: 'auto',
|
|
95
|
+
// transform: 'translate',
|
|
96
|
+
})}
|
|
97
|
+
style={{
|
|
98
|
+
// position: 'absolute',
|
|
99
|
+
backgroundColor: defaultTheme.elements.gray.fill,
|
|
100
|
+
opacity: isHovered ? 0.5 : 0.4,
|
|
101
|
+
left: '50%',
|
|
102
|
+
width: isHovered ? 3 : 2,
|
|
103
|
+
height: viewHeight - positionAbsoluteY,
|
|
104
|
+
zIndex: -1,
|
|
105
|
+
pointerEvents: 'none',
|
|
106
|
+
// top: p.y,
|
|
107
|
+
// left: p.width / 2,
|
|
108
|
+
// height: p.type === 'source' ? 48 : 32,
|
|
109
|
+
// transform: p.type === 'source' ? 'translate(-50%, -16px)' : 'translate(-50%, -50%)',
|
|
110
|
+
// transform: 'translate(-50%, -50%)',
|
|
111
|
+
// zIndex: p.type === 'source' ? 1 : 0,
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
{
|
|
115
|
+
/* <div
|
|
116
|
+
// css={{
|
|
117
|
+
// backgroundColor: 'var(--likec4-palette-fill)',
|
|
118
|
+
// }}
|
|
119
|
+
style={{
|
|
120
|
+
position: 'absolute',
|
|
121
|
+
backgroundColor: defaultTheme.elements.gray.fill,
|
|
122
|
+
opacity: 0.5,
|
|
123
|
+
top: 8,
|
|
124
|
+
left: 'calc(50% - 1px)',
|
|
125
|
+
width: 2,
|
|
126
|
+
height: viewHeight - positionAbsoluteY,
|
|
127
|
+
zIndex: -1,
|
|
128
|
+
pointerEvents: 'none',
|
|
129
|
+
}}>
|
|
130
|
+
</div> */
|
|
131
|
+
}
|
|
132
|
+
<ElementNodeContainer nodeProps={props}>
|
|
133
|
+
<ElementShape {...props} />
|
|
134
|
+
<ElementTitle {...props} />
|
|
135
|
+
{data.modelFqn && (
|
|
136
|
+
<>
|
|
137
|
+
<ElementActions {...props} data={data as Types.ElementNodeData} />
|
|
138
|
+
<ElementDetailsButtonWithHandler {...props} data={data as Types.ElementNodeData} />
|
|
139
|
+
</>
|
|
140
|
+
)}
|
|
141
|
+
</ElementNodeContainer>
|
|
142
|
+
{ports.map(p => <ActorStepPort key={p.id} port={p} data={props.data} />)}
|
|
143
|
+
</>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function SequenceParallelArea(props: NodeProps<Types.SequenceActorNode>) {
|
|
148
|
+
return (
|
|
149
|
+
<Box
|
|
150
|
+
data-likec4-color={props.data.color}
|
|
151
|
+
css={{
|
|
152
|
+
width: '100%',
|
|
153
|
+
height: '100%',
|
|
154
|
+
border: 'default',
|
|
155
|
+
rounded: 'sm',
|
|
156
|
+
borderWidth: 1,
|
|
157
|
+
'--_color': {
|
|
158
|
+
base: 'var(--likec4-palette-stroke)',
|
|
159
|
+
_dark: '[color-mix(in srgb, var(--likec4-palette-hiContrast) 40%, var(--likec4-palette-fill))]',
|
|
160
|
+
},
|
|
161
|
+
borderColor: '[var(--_color)/30]',
|
|
162
|
+
backgroundColor: 'var(--likec4-palette-fill)/15',
|
|
163
|
+
pointerEvents: 'none',
|
|
164
|
+
paddingLeft: '2',
|
|
165
|
+
paddingTop: '0.5',
|
|
166
|
+
fontSize: 'xs',
|
|
167
|
+
fontWeight: 'bold',
|
|
168
|
+
letterSpacing: '.75px',
|
|
169
|
+
color: '[var(--_color)/75]',
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
PARALLEL
|
|
173
|
+
</Box>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
@@ -20,7 +20,7 @@ import { DeploymentElementActions, ElementActions } from './ElementActions'
|
|
|
20
20
|
import { CompoundDeploymentToolbar, CompoundElementToolbar } from './toolbar/CompoundToolbar'
|
|
21
21
|
import { DeploymentElementToolbar, ElementToolbar } from './toolbar/ElementToolbar'
|
|
22
22
|
|
|
23
|
-
type CustomNodes = Required<CustomNodesType>
|
|
23
|
+
export type CustomNodes = Required<CustomNodesType>
|
|
24
24
|
|
|
25
25
|
export function ElementDetailsButtonWithHandler(
|
|
26
26
|
props: NodeProps<Types.ElementNodeData | Types.DeploymentElementNodeData>,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
useRef,
|
|
30
30
|
useState,
|
|
31
31
|
} from 'react'
|
|
32
|
-
import { clamp, filter, isTruthy, map, partition, pipe } from 'remeda'
|
|
32
|
+
import { clamp, filter, isEmpty, isTruthy, map, partition, pipe } from 'remeda'
|
|
33
33
|
import { Link } from '../../components/Link'
|
|
34
34
|
import { useDiagramEventHandlers } from '../../context/DiagramEventHandlers'
|
|
35
35
|
import { useEnabledFeatures } from '../../context/DiagramFeatures'
|
|
@@ -61,6 +61,7 @@ function selectDiagramContext(c: DiagramContext) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export const RelationshipPopover = memo(() => {
|
|
64
|
+
const likec4model = useLikeC4Model()
|
|
64
65
|
const actorRef = useActorRef(RelationshipPopoverActorLogic)
|
|
65
66
|
const diagram = useDiagram()
|
|
66
67
|
const { viewId, selected } = useDiagramContext(selectDiagramContext)
|
|
@@ -132,13 +133,42 @@ export const RelationshipPopover = memo(() => {
|
|
|
132
133
|
[openedEdgeId],
|
|
133
134
|
)
|
|
134
135
|
|
|
135
|
-
if (!diagramEdge || !sourceNode || !targetNode) {
|
|
136
|
+
if (!diagramEdge || !sourceNode || !targetNode || isEmpty(diagramEdge.relations)) {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const [direct, nested] = pipe(
|
|
141
|
+
diagramEdge.relations,
|
|
142
|
+
map(id => {
|
|
143
|
+
try {
|
|
144
|
+
return likec4model.relationship(id)
|
|
145
|
+
} catch (e) {
|
|
146
|
+
// View was cached, but likec4model based on new data
|
|
147
|
+
console.error(
|
|
148
|
+
`View is cached and likec4model missing relationship ${id} from ${sourceNode.id} -> ${targetNode.id}`,
|
|
149
|
+
e,
|
|
150
|
+
)
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
}),
|
|
154
|
+
filter(isTruthy),
|
|
155
|
+
partition(r => r.source.id === sourceNode.id && r.target.id === targetNode.id),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if (direct.length === 0 && nested.length === 0) {
|
|
159
|
+
console.warn('No relationships found diagram edge', {
|
|
160
|
+
diagramEdge,
|
|
161
|
+
sourceNode,
|
|
162
|
+
targetNode,
|
|
163
|
+
})
|
|
136
164
|
return null
|
|
137
165
|
}
|
|
138
166
|
|
|
139
167
|
return (
|
|
140
168
|
<RelationshipPopoverInternal
|
|
141
169
|
viewId={viewId}
|
|
170
|
+
direct={direct}
|
|
171
|
+
nested={nested}
|
|
142
172
|
diagramEdge={diagramEdge}
|
|
143
173
|
sourceNode={sourceNode}
|
|
144
174
|
targetNode={targetNode}
|
|
@@ -153,6 +183,8 @@ const getEdgeLabelElement = (edgeId: string, container: HTMLDivElement | undefin
|
|
|
153
183
|
|
|
154
184
|
type RelationshipPopoverInternalProps = {
|
|
155
185
|
viewId: ViewId
|
|
186
|
+
direct: LikeC4Model.AnyRelation[]
|
|
187
|
+
nested: LikeC4Model.AnyRelation[]
|
|
156
188
|
diagramEdge: DiagramEdge
|
|
157
189
|
sourceNode: DiagramNode
|
|
158
190
|
targetNode: DiagramNode
|
|
@@ -163,6 +195,8 @@ const POPOVER_PADDING = 8
|
|
|
163
195
|
const RelationshipPopoverInternal = ({
|
|
164
196
|
viewId,
|
|
165
197
|
diagramEdge,
|
|
198
|
+
direct,
|
|
199
|
+
nested,
|
|
166
200
|
sourceNode,
|
|
167
201
|
targetNode,
|
|
168
202
|
onMouseEnter,
|
|
@@ -240,36 +274,8 @@ const RelationshipPopoverInternal = ({
|
|
|
240
274
|
}
|
|
241
275
|
}, [referenceEl])
|
|
242
276
|
|
|
243
|
-
const likec4model = useLikeC4Model()
|
|
244
277
|
const diagram = useDiagram()
|
|
245
278
|
|
|
246
|
-
const [direct, nested] = pipe(
|
|
247
|
-
diagramEdge.relations,
|
|
248
|
-
map(id => {
|
|
249
|
-
try {
|
|
250
|
-
return likec4model.relationship(id)
|
|
251
|
-
} catch (e) {
|
|
252
|
-
// View was cached, but likec4model based on new data
|
|
253
|
-
console.error(
|
|
254
|
-
`View is cached and likec4model missing relationship ${id} from ${sourceNode.id} -> ${targetNode.id}`,
|
|
255
|
-
e,
|
|
256
|
-
)
|
|
257
|
-
return null
|
|
258
|
-
}
|
|
259
|
-
}),
|
|
260
|
-
filter(isTruthy),
|
|
261
|
-
partition(r => r.source.id === sourceNode.id && r.target.id === targetNode.id),
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
if (direct.length === 0 && nested.length === 0) {
|
|
265
|
-
console.error('No relationships found diagram edge', {
|
|
266
|
-
diagramEdge,
|
|
267
|
-
sourceNode,
|
|
268
|
-
targetNode,
|
|
269
|
-
})
|
|
270
|
-
return null
|
|
271
|
-
}
|
|
272
|
-
|
|
273
279
|
const navigateTo = enableNavigateTo
|
|
274
280
|
? (viewId: ViewId) => {
|
|
275
281
|
diagram.navigateTo(viewId)
|