@open-mercato/core 0.6.4-develop.3944.1.4100aa7fbe → 0.6.4-develop.3962.1.70f30e284c
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/.turbo/turbo-build.log +1 -1
- package/dist/global.d.js +1 -0
- package/dist/global.d.js.map +7 -0
- package/dist/modules/catalog/commands/variants.js +11 -5
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraph.js +29 -186
- package/dist/modules/workflows/components/WorkflowGraph.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraphImpl.js +196 -0
- package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +7 -0
- package/package.json +7 -7
- package/src/global.d.ts +9 -0
- package/src/modules/catalog/commands/variants.ts +14 -5
- package/src/modules/workflows/components/WorkflowGraph.tsx +39 -235
- package/src/modules/workflows/components/WorkflowGraphImpl.tsx +233 -0
|
@@ -1,32 +1,9 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Edge,
|
|
8
|
-
Controls,
|
|
9
|
-
Background,
|
|
10
|
-
BackgroundVariant,
|
|
11
|
-
MiniMap,
|
|
12
|
-
Panel,
|
|
13
|
-
useNodesState,
|
|
14
|
-
useEdgesState,
|
|
15
|
-
addEdge,
|
|
16
|
-
Connection,
|
|
17
|
-
ConnectionMode,
|
|
18
|
-
MarkerType,
|
|
19
|
-
} from '@xyflow/react'
|
|
20
|
-
import {StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode} from './nodes'
|
|
21
|
-
import { WorkflowTransitionEdge } from './WorkflowTransitionEdge'
|
|
22
|
-
import { STATUS_COLORS } from '../lib/status-colors'
|
|
23
|
-
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
24
|
-
import { Edit3 } from 'lucide-react'
|
|
25
|
-
import { useTheme } from '@open-mercato/ui/theme'
|
|
26
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
27
|
-
|
|
28
|
-
// NOTE: ReactFlow styles should be imported in the page that uses this component
|
|
29
|
-
// or in a global CSS file. Import: '@xyflow/react/dist/style.css'
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import dynamic from 'next/dynamic'
|
|
5
|
+
import type { Node, Edge, Connection } from '@xyflow/react'
|
|
6
|
+
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
30
7
|
|
|
31
8
|
export interface WorkflowGraphProps {
|
|
32
9
|
initialNodes?: Node[]
|
|
@@ -41,224 +18,51 @@ export interface WorkflowGraphProps {
|
|
|
41
18
|
height?: string
|
|
42
19
|
}
|
|
43
20
|
|
|
21
|
+
const WorkflowGraphImpl = dynamic(() => import('./WorkflowGraphImpl'), {
|
|
22
|
+
ssr: false,
|
|
23
|
+
loading: () => null,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
function WorkflowGraphPlaceholder({ height }: { height: string }) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
className="workflow-graph-container flex items-center justify-center rounded-lg border border-border bg-muted/30"
|
|
30
|
+
style={{ height }}
|
|
31
|
+
>
|
|
32
|
+
<Spinner className="h-6 w-6 text-muted-foreground" />
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
44
37
|
/**
|
|
45
|
-
* WorkflowGraph - ReactFlow wrapper
|
|
38
|
+
* WorkflowGraph — lazy-loaded ReactFlow wrapper.
|
|
46
39
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* - Mini-map for navigation
|
|
51
|
-
* - Optional editing capabilities
|
|
40
|
+
* @xyflow/react is loaded via next/dynamic({ ssr: false }) so the ~12 MB
|
|
41
|
+
* package only enters the Turbopack module graph when this component
|
|
42
|
+
* actually renders.
|
|
52
43
|
*/
|
|
53
|
-
export function WorkflowGraph({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}: WorkflowGraphProps) {
|
|
65
|
-
const t = useT()
|
|
66
|
-
// Use ReactFlow hooks for node and edge state management
|
|
67
|
-
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
|
|
68
|
-
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
|
|
69
|
-
|
|
70
|
-
// Get theme for dark mode support
|
|
71
|
-
const { resolvedTheme } = useTheme()
|
|
72
|
-
const isDark = resolvedTheme === 'dark'
|
|
73
|
-
const backgroundDotColor = isDark ? '#374151' : '#e5e7eb'
|
|
74
|
-
const [isCompactViewport, setIsCompactViewport] = useState(false)
|
|
75
|
-
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
if (typeof window === 'undefined') return
|
|
78
|
-
const mediaQuery = window.matchMedia('(max-width: 1279px)')
|
|
79
|
-
const updateViewportMode = () => setIsCompactViewport(mediaQuery.matches)
|
|
80
|
-
|
|
81
|
-
updateViewportMode()
|
|
82
|
-
mediaQuery.addEventListener('change', updateViewportMode)
|
|
83
|
-
|
|
44
|
+
export function WorkflowGraph(props: WorkflowGraphProps) {
|
|
45
|
+
const { height = '600px' } = props
|
|
46
|
+
// Track impl-chunk readiness so the loading placeholder respects the
|
|
47
|
+
// caller's `height` prop (next/dynamic's `loading` cannot access props).
|
|
48
|
+
// The browser caches the module, so the duplicate `import()` is free.
|
|
49
|
+
const [isImplReady, setIsImplReady] = React.useState(false)
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
let cancelled = false
|
|
52
|
+
void import('./WorkflowGraphImpl').then(() => {
|
|
53
|
+
if (!cancelled) setIsImplReady(true)
|
|
54
|
+
})
|
|
84
55
|
return () => {
|
|
85
|
-
|
|
56
|
+
cancelled = true
|
|
86
57
|
}
|
|
87
58
|
}, [])
|
|
88
59
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
setNodes(initialNodes)
|
|
92
|
-
}, [initialNodes, setNodes])
|
|
93
|
-
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
setEdges(initialEdges)
|
|
96
|
-
}, [initialEdges, setEdges])
|
|
97
|
-
|
|
98
|
-
// Handle connection between nodes (when user drags from one node to another)
|
|
99
|
-
const onConnect = useCallback(
|
|
100
|
-
(connection: Connection) => {
|
|
101
|
-
if (onConnectProp) {
|
|
102
|
-
// Let parent handle the connection
|
|
103
|
-
onConnectProp(connection)
|
|
104
|
-
} else {
|
|
105
|
-
// Fallback: handle internally if no parent callback
|
|
106
|
-
const newEdge = {
|
|
107
|
-
...connection,
|
|
108
|
-
type: 'workflowTransition',
|
|
109
|
-
animated: false,
|
|
110
|
-
markerEnd: {
|
|
111
|
-
type: MarkerType.ArrowClosed,
|
|
112
|
-
width: 16,
|
|
113
|
-
height: 16,
|
|
114
|
-
color: '#9ca3af',
|
|
115
|
-
},
|
|
116
|
-
}
|
|
117
|
-
setEdges((eds) => addEdge(newEdge, eds))
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
[setEdges, onConnectProp]
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
// Notify parent when nodes change
|
|
124
|
-
const handleNodesChange = useCallback(
|
|
125
|
-
(changes: any) => {
|
|
126
|
-
onNodesChange(changes)
|
|
127
|
-
if (onNodesChangeProp) {
|
|
128
|
-
onNodesChangeProp(changes)
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
[onNodesChange, onNodesChangeProp]
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
// Notify parent when edges change
|
|
135
|
-
const handleEdgesChange = useCallback(
|
|
136
|
-
(changes: any) => {
|
|
137
|
-
onEdgesChange(changes)
|
|
138
|
-
if (onEdgesChangeProp) {
|
|
139
|
-
onEdgesChangeProp(changes)
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
[onEdgesChange, onEdgesChangeProp]
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
// Register custom node types
|
|
146
|
-
const nodeTypes = useMemo(
|
|
147
|
-
() => ({
|
|
148
|
-
start: StartNode,
|
|
149
|
-
end: EndNode,
|
|
150
|
-
userTask: UserTaskNode,
|
|
151
|
-
automated: AutomatedNode,
|
|
152
|
-
subWorkflow: SubWorkflowNode,
|
|
153
|
-
waitForSignal: WaitForSignalNode,
|
|
154
|
-
waitForTimer: WaitForTimerNode,
|
|
155
|
-
}),
|
|
156
|
-
[]
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
// Register custom edge types
|
|
160
|
-
const edgeTypes = useMemo(
|
|
161
|
-
() => ({
|
|
162
|
-
workflowTransition: WorkflowTransitionEdge,
|
|
163
|
-
}),
|
|
164
|
-
[]
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
return (
|
|
168
|
-
<div className={`workflow-graph-container ${className}`} style={{ height }}>
|
|
169
|
-
<ReactFlow
|
|
170
|
-
nodes={nodes}
|
|
171
|
-
edges={edges}
|
|
172
|
-
nodeTypes={nodeTypes}
|
|
173
|
-
edgeTypes={edgeTypes}
|
|
174
|
-
onNodesChange={handleNodesChange}
|
|
175
|
-
onEdgesChange={handleEdgesChange}
|
|
176
|
-
onConnect={editable ? onConnect : undefined}
|
|
177
|
-
onNodeClick={onNodeClickProp}
|
|
178
|
-
onEdgeClick={onEdgeClickProp}
|
|
179
|
-
connectionMode={ConnectionMode.Loose}
|
|
180
|
-
fitView
|
|
181
|
-
fitViewOptions={{
|
|
182
|
-
padding: 0.2,
|
|
183
|
-
maxZoom: isCompactViewport ? 0.9 : 1,
|
|
184
|
-
}}
|
|
185
|
-
minZoom={0.1}
|
|
186
|
-
maxZoom={2}
|
|
187
|
-
defaultEdgeOptions={{
|
|
188
|
-
type: 'workflowTransition',
|
|
189
|
-
animated: false,
|
|
190
|
-
markerEnd: {
|
|
191
|
-
type: MarkerType.ArrowClosed,
|
|
192
|
-
width: 16,
|
|
193
|
-
height: 16,
|
|
194
|
-
color: '#9ca3af',
|
|
195
|
-
},
|
|
196
|
-
}}
|
|
197
|
-
nodesDraggable={editable}
|
|
198
|
-
nodesConnectable={editable}
|
|
199
|
-
elementsSelectable={editable}
|
|
200
|
-
proOptions={{ hideAttribution: true }}
|
|
201
|
-
>
|
|
202
|
-
{/* Background grid for visual reference */}
|
|
203
|
-
<Background
|
|
204
|
-
variant={BackgroundVariant.Dots}
|
|
205
|
-
gap={16}
|
|
206
|
-
size={1}
|
|
207
|
-
color={backgroundDotColor}
|
|
208
|
-
/>
|
|
209
|
-
|
|
210
|
-
{/* Zoom and pan controls */}
|
|
211
|
-
<Controls
|
|
212
|
-
showZoom={true}
|
|
213
|
-
showFitView={true}
|
|
214
|
-
showInteractive={false}
|
|
215
|
-
position={isCompactViewport ? 'bottom-right' : 'top-right'}
|
|
216
|
-
className={`!bg-card !border-border !shadow-md [&>button]:!bg-card [&>button]:!border-border [&>button]:!fill-foreground [&>button:hover]:!bg-muted ${isCompactViewport ? 'scale-90 origin-bottom-right' : ''}`}
|
|
217
|
-
/>
|
|
218
|
-
|
|
219
|
-
{/* Mini-map for navigation in large workflows */}
|
|
220
|
-
{!isCompactViewport && (
|
|
221
|
-
<MiniMap
|
|
222
|
-
nodeStrokeWidth={3}
|
|
223
|
-
nodeColor={(node) => {
|
|
224
|
-
// Color nodes by status - using status-based colors
|
|
225
|
-
const status = (node.data?.status || 'not_started') as keyof typeof STATUS_COLORS
|
|
226
|
-
return STATUS_COLORS[status]?.hex || STATUS_COLORS.not_started.hex
|
|
227
|
-
}}
|
|
228
|
-
maskColor="rgba(0, 0, 0, 0.1)"
|
|
229
|
-
position="bottom-left"
|
|
230
|
-
className="!bg-card !border !border-border !rounded-lg"
|
|
231
|
-
/>
|
|
232
|
-
)}
|
|
233
|
-
|
|
234
|
-
{/* Info panel */}
|
|
235
|
-
{!editable && !isCompactViewport && (
|
|
236
|
-
<Panel position="top-left" style={{ margin: 10 }}>
|
|
237
|
-
<div className="bg-card rounded-lg shadow-sm border border-border px-4 py-2">
|
|
238
|
-
<p className="text-sm text-muted-foreground font-medium">
|
|
239
|
-
{t('workflows.graph.visualization')}
|
|
240
|
-
</p>
|
|
241
|
-
</div>
|
|
242
|
-
</Panel>
|
|
243
|
-
)}
|
|
244
|
-
|
|
245
|
-
{editable && !isCompactViewport && (
|
|
246
|
-
<Panel position="top-left" style={{ margin: 10 }}>
|
|
247
|
-
<Alert variant="info" className="max-w-sm">
|
|
248
|
-
<Edit3 className="size-4" />
|
|
249
|
-
<AlertDescription className="font-medium">
|
|
250
|
-
{t('workflows.graph.editModeInfo')}
|
|
251
|
-
</AlertDescription>
|
|
252
|
-
</Alert>
|
|
253
|
-
</Panel>
|
|
254
|
-
)}
|
|
255
|
-
</ReactFlow>
|
|
256
|
-
</div>
|
|
257
|
-
)
|
|
60
|
+
if (!isImplReady) return <WorkflowGraphPlaceholder height={height} />
|
|
61
|
+
return <WorkflowGraphImpl {...props} />
|
|
258
62
|
}
|
|
259
63
|
|
|
260
64
|
/**
|
|
261
|
-
* WorkflowGraphReadOnly
|
|
65
|
+
* WorkflowGraphReadOnly — read-only viewer that reuses WorkflowGraph.
|
|
262
66
|
*/
|
|
263
67
|
export function WorkflowGraphReadOnly({
|
|
264
68
|
nodes,
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import '@xyflow/react/dist/style.css'
|
|
4
|
+
|
|
5
|
+
import { useCallback, useMemo, useEffect, useState } from 'react'
|
|
6
|
+
import {
|
|
7
|
+
ReactFlow,
|
|
8
|
+
Node,
|
|
9
|
+
Edge,
|
|
10
|
+
Controls,
|
|
11
|
+
Background,
|
|
12
|
+
BackgroundVariant,
|
|
13
|
+
MiniMap,
|
|
14
|
+
Panel,
|
|
15
|
+
useNodesState,
|
|
16
|
+
useEdgesState,
|
|
17
|
+
addEdge,
|
|
18
|
+
Connection,
|
|
19
|
+
ConnectionMode,
|
|
20
|
+
MarkerType,
|
|
21
|
+
} from '@xyflow/react'
|
|
22
|
+
import {StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode} from './nodes'
|
|
23
|
+
import { WorkflowTransitionEdge } from './WorkflowTransitionEdge'
|
|
24
|
+
import { STATUS_COLORS } from '../lib/status-colors'
|
|
25
|
+
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
26
|
+
import { Edit3 } from 'lucide-react'
|
|
27
|
+
import { useTheme } from '@open-mercato/ui/theme'
|
|
28
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
29
|
+
|
|
30
|
+
export interface WorkflowGraphImplProps {
|
|
31
|
+
initialNodes?: Node[]
|
|
32
|
+
initialEdges?: Edge[]
|
|
33
|
+
onNodesChange?: (changes: any[]) => void
|
|
34
|
+
onEdgesChange?: (changes: any[]) => void
|
|
35
|
+
onNodeClick?: (event: React.MouseEvent, node: Node) => void
|
|
36
|
+
onEdgeClick?: (event: React.MouseEvent, edge: Edge) => void
|
|
37
|
+
onConnect?: (connection: Connection) => void
|
|
38
|
+
editable?: boolean
|
|
39
|
+
className?: string
|
|
40
|
+
height?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default function WorkflowGraphImpl({
|
|
44
|
+
initialNodes = [],
|
|
45
|
+
initialEdges = [],
|
|
46
|
+
onNodesChange: onNodesChangeProp,
|
|
47
|
+
onEdgesChange: onEdgesChangeProp,
|
|
48
|
+
onNodeClick: onNodeClickProp,
|
|
49
|
+
onEdgeClick: onEdgeClickProp,
|
|
50
|
+
onConnect: onConnectProp,
|
|
51
|
+
editable = false,
|
|
52
|
+
className = '',
|
|
53
|
+
height = '600px',
|
|
54
|
+
}: WorkflowGraphImplProps) {
|
|
55
|
+
const t = useT()
|
|
56
|
+
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
|
|
57
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
|
|
58
|
+
|
|
59
|
+
const { resolvedTheme } = useTheme()
|
|
60
|
+
const isDark = resolvedTheme === 'dark'
|
|
61
|
+
const backgroundDotColor = isDark ? '#374151' : '#e5e7eb'
|
|
62
|
+
const [isCompactViewport, setIsCompactViewport] = useState(false)
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (typeof window === 'undefined') return
|
|
66
|
+
const mediaQuery = window.matchMedia('(max-width: 1279px)')
|
|
67
|
+
const updateViewportMode = () => setIsCompactViewport(mediaQuery.matches)
|
|
68
|
+
|
|
69
|
+
updateViewportMode()
|
|
70
|
+
mediaQuery.addEventListener('change', updateViewportMode)
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
mediaQuery.removeEventListener('change', updateViewportMode)
|
|
74
|
+
}
|
|
75
|
+
}, [])
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
setNodes(initialNodes)
|
|
79
|
+
}, [initialNodes, setNodes])
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
setEdges(initialEdges)
|
|
83
|
+
}, [initialEdges, setEdges])
|
|
84
|
+
|
|
85
|
+
const onConnect = useCallback(
|
|
86
|
+
(connection: Connection) => {
|
|
87
|
+
if (onConnectProp) {
|
|
88
|
+
onConnectProp(connection)
|
|
89
|
+
} else {
|
|
90
|
+
const newEdge = {
|
|
91
|
+
...connection,
|
|
92
|
+
type: 'workflowTransition',
|
|
93
|
+
animated: false,
|
|
94
|
+
markerEnd: {
|
|
95
|
+
type: MarkerType.ArrowClosed,
|
|
96
|
+
width: 16,
|
|
97
|
+
height: 16,
|
|
98
|
+
color: '#9ca3af',
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
setEdges((eds) => addEdge(newEdge, eds))
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
[setEdges, onConnectProp]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const handleNodesChange = useCallback(
|
|
108
|
+
(changes: any) => {
|
|
109
|
+
onNodesChange(changes)
|
|
110
|
+
if (onNodesChangeProp) {
|
|
111
|
+
onNodesChangeProp(changes)
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
[onNodesChange, onNodesChangeProp]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const handleEdgesChange = useCallback(
|
|
118
|
+
(changes: any) => {
|
|
119
|
+
onEdgesChange(changes)
|
|
120
|
+
if (onEdgesChangeProp) {
|
|
121
|
+
onEdgesChangeProp(changes)
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
[onEdgesChange, onEdgesChangeProp]
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const nodeTypes = useMemo(
|
|
128
|
+
() => ({
|
|
129
|
+
start: StartNode,
|
|
130
|
+
end: EndNode,
|
|
131
|
+
userTask: UserTaskNode,
|
|
132
|
+
automated: AutomatedNode,
|
|
133
|
+
subWorkflow: SubWorkflowNode,
|
|
134
|
+
waitForSignal: WaitForSignalNode,
|
|
135
|
+
waitForTimer: WaitForTimerNode,
|
|
136
|
+
}),
|
|
137
|
+
[]
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
const edgeTypes = useMemo(
|
|
141
|
+
() => ({
|
|
142
|
+
workflowTransition: WorkflowTransitionEdge,
|
|
143
|
+
}),
|
|
144
|
+
[]
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div className={`workflow-graph-container ${className}`} style={{ height }}>
|
|
149
|
+
<ReactFlow
|
|
150
|
+
nodes={nodes}
|
|
151
|
+
edges={edges}
|
|
152
|
+
nodeTypes={nodeTypes}
|
|
153
|
+
edgeTypes={edgeTypes}
|
|
154
|
+
onNodesChange={handleNodesChange}
|
|
155
|
+
onEdgesChange={handleEdgesChange}
|
|
156
|
+
onConnect={editable ? onConnect : undefined}
|
|
157
|
+
onNodeClick={onNodeClickProp}
|
|
158
|
+
onEdgeClick={onEdgeClickProp}
|
|
159
|
+
connectionMode={ConnectionMode.Loose}
|
|
160
|
+
fitView
|
|
161
|
+
fitViewOptions={{
|
|
162
|
+
padding: 0.2,
|
|
163
|
+
maxZoom: isCompactViewport ? 0.9 : 1,
|
|
164
|
+
}}
|
|
165
|
+
minZoom={0.1}
|
|
166
|
+
maxZoom={2}
|
|
167
|
+
defaultEdgeOptions={{
|
|
168
|
+
type: 'workflowTransition',
|
|
169
|
+
animated: false,
|
|
170
|
+
markerEnd: {
|
|
171
|
+
type: MarkerType.ArrowClosed,
|
|
172
|
+
width: 16,
|
|
173
|
+
height: 16,
|
|
174
|
+
color: '#9ca3af',
|
|
175
|
+
},
|
|
176
|
+
}}
|
|
177
|
+
nodesDraggable={editable}
|
|
178
|
+
nodesConnectable={editable}
|
|
179
|
+
elementsSelectable={editable}
|
|
180
|
+
proOptions={{ hideAttribution: true }}
|
|
181
|
+
>
|
|
182
|
+
<Background
|
|
183
|
+
variant={BackgroundVariant.Dots}
|
|
184
|
+
gap={16}
|
|
185
|
+
size={1}
|
|
186
|
+
color={backgroundDotColor}
|
|
187
|
+
/>
|
|
188
|
+
|
|
189
|
+
<Controls
|
|
190
|
+
showZoom={true}
|
|
191
|
+
showFitView={true}
|
|
192
|
+
showInteractive={false}
|
|
193
|
+
position={isCompactViewport ? 'bottom-right' : 'top-right'}
|
|
194
|
+
className={`!bg-card !border-border !shadow-md [&>button]:!bg-card [&>button]:!border-border [&>button]:!fill-foreground [&>button:hover]:!bg-muted ${isCompactViewport ? 'scale-90 origin-bottom-right' : ''}`}
|
|
195
|
+
/>
|
|
196
|
+
|
|
197
|
+
{!isCompactViewport && (
|
|
198
|
+
<MiniMap
|
|
199
|
+
nodeStrokeWidth={3}
|
|
200
|
+
nodeColor={(node) => {
|
|
201
|
+
const status = (node.data?.status || 'not_started') as keyof typeof STATUS_COLORS
|
|
202
|
+
return STATUS_COLORS[status]?.hex || STATUS_COLORS.not_started.hex
|
|
203
|
+
}}
|
|
204
|
+
maskColor="rgba(0, 0, 0, 0.1)"
|
|
205
|
+
position="bottom-left"
|
|
206
|
+
className="!bg-card !border !border-border !rounded-lg"
|
|
207
|
+
/>
|
|
208
|
+
)}
|
|
209
|
+
|
|
210
|
+
{!editable && !isCompactViewport && (
|
|
211
|
+
<Panel position="top-left" style={{ margin: 10 }}>
|
|
212
|
+
<div className="bg-card rounded-lg shadow-sm border border-border px-4 py-2">
|
|
213
|
+
<p className="text-sm text-muted-foreground font-medium">
|
|
214
|
+
{t('workflows.graph.visualization')}
|
|
215
|
+
</p>
|
|
216
|
+
</div>
|
|
217
|
+
</Panel>
|
|
218
|
+
)}
|
|
219
|
+
|
|
220
|
+
{editable && !isCompactViewport && (
|
|
221
|
+
<Panel position="top-left" style={{ margin: 10 }}>
|
|
222
|
+
<Alert variant="info" className="max-w-sm">
|
|
223
|
+
<Edit3 className="size-4" />
|
|
224
|
+
<AlertDescription className="font-medium">
|
|
225
|
+
{t('workflows.graph.editModeInfo')}
|
|
226
|
+
</AlertDescription>
|
|
227
|
+
</Alert>
|
|
228
|
+
</Panel>
|
|
229
|
+
)}
|
|
230
|
+
</ReactFlow>
|
|
231
|
+
</div>
|
|
232
|
+
)
|
|
233
|
+
}
|