Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions src/components/WorkflowEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ReactFlow, {
ReactFlowProvider,
SelectionMode,
useReactFlow,
useViewport,
} from 'reactflow';
import 'reactflow/dist/style.css';

Expand All @@ -24,15 +25,6 @@ import JsonImporter from './JsonImporter';
import { useHistory } from '../hooks/useHistory';
import './WorkflowEditor.css';

const nodeTypes = {
operation: OperationNode,
switch: SwitchNode,
start: StartNode,
end: EndNode,
event: EventNode,
sleep: SleepNode,
};

const STORAGE_KEY = 'serverless-workflow-editor-state';

const defaultInitialNodes = [
Expand Down Expand Up @@ -82,6 +74,17 @@ function WorkflowEditor() {
const reactFlowWrapper = useRef(null);
const [reactFlowBounds, setReactFlowBounds] = useState(null);
const [isDragOver, setIsDragOver] = useState(false);
const { zoom } = useViewport();

// Node types with zoom prop
const nodeTypes = useMemo(() => ({
operation: (props) => <OperationNode {...props} zoom={zoom} />,
switch: (props) => <SwitchNode {...props} zoom={zoom} />,
start: (props) => <StartNode {...props} zoom={zoom} />,
end: (props) => <EndNode {...props} zoom={zoom} />,
event: (props) => <EventNode {...props} zoom={zoom} />,
sleep: (props) => <SleepNode {...props} zoom={zoom} />,
}), [zoom]);

// History management for undo/redo
const {
Expand Down
22 changes: 20 additions & 2 deletions src/components/nodes/EventNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ import { Handle, Position } from 'reactflow';
import { Zap } from 'lucide-react';
import './NodeStyles.css';

const EventNode = ({ data, selected }) => {
const EventNode = ({ data, selected, zoom = 1 }) => {
const [isHovered, setIsHovered] = React.useState(false);
const shouldShowDetails = zoom > 1.5 && isHovered;
return (
<div className={`custom-node event-node ${selected ? 'selected' : ''}`}>
<div
className={`custom-node event-node ${selected ? 'selected' : ''}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<Handle type="target" position={Position.Top} />

<div className="node-header">
Expand All @@ -27,6 +33,18 @@ const EventNode = ({ data, selected }) => {
<strong>Timeout:</strong> Yes
</div>
)}

{/* Show detailed event info when highly zoomed AND hovered */}
{shouldShowDetails && data.events && data.events.map((event, index) => (
<div key={index} className="detailed-action">
<div className="action-name">• Event {index + 1}</div>
{event.eventRefs && (
<div className="event-refs">
<code>{JSON.stringify(event.eventRefs)}</code>
</div>
)}
</div>
))}
</div>

<Handle type="source" position={Position.Bottom} />
Expand Down
66 changes: 66 additions & 0 deletions src/components/nodes/NodeStyles.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,69 @@
.sleep-node .node-header {
color: #0891b2;
}

/* Detailed view styles for high zoom levels */
.detailed-action {
margin-top: 8px;
padding: 6px;
background: rgba(255, 255, 255, 0.5);
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 10px;
}

.action-name {
font-weight: 600;
color: var(--text-color);
margin-bottom: 2px;
}

.action-function {
color: var(--text-secondary);
font-style: italic;
margin-bottom: 4px;
}

.action-args {
margin: 4px 0;
}

.action-args code {
font-size: 9px;
background: var(--bg-secondary);
padding: 2px 4px;
border-radius: 2px;
color: var(--text-color);
display: block;
white-space: pre-wrap;
word-break: break-word;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
}

.action-retry {
font-size: 9px;
color: var(--success-color);
font-weight: 500;
}

/* Condition detail styles for switch nodes */
.condition-detail {
margin-top: 4px;
font-size: 9px;
color: var(--text-secondary);
}

.condition-detail code {
font-size: 8px;
background: var(--bg-secondary);
padding: 1px 3px;
border-radius: 2px;
color: var(--text-color);
white-space: nowrap;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
52 changes: 50 additions & 2 deletions src/components/nodes/OperationNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,41 @@ import { Handle, Position, useReactFlow } from 'reactflow';
import { Play } from 'lucide-react';
import './NodeStyles.css';

const OperationNode = ({ data, selected }) => {
const OperationNode = ({ data, selected, zoom = 1 }) => {
const [isHovered, setIsHovered] = React.useState(false);
const shouldShowDetails = zoom > 1.5 && isHovered; // Show details only when zoomed AND hovered

// Use React Flow's useReactFlow hook to manipulate node z-index
const { setNodes } = useReactFlow();

React.useEffect(() => {
if (shouldShowDetails) {
setNodes((nds) =>
nds.map((node) => {
if (node.data === data) {
return { ...node, style: { ...node.style, zIndex: 9999 } };
}
return node;
})
);
} else {
setNodes((nds) =>
nds.map((node) => {
if (node.data === data) {
return { ...node, style: { ...node.style, zIndex: 1 } };
}
return node;
})
);
}
}, [shouldShowDetails, setNodes, data]);

return (
<div className={`custom-node operation-node ${selected ? 'selected' : ''}`}>
<div
className={`custom-node operation-node ${selected ? 'selected' : ''} ${shouldShowDetails ? 'showing-details' : ''}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<Handle type="target" position={Position.Top} />

<div className="node-header">
Expand All @@ -22,6 +54,22 @@ const OperationNode = ({ data, selected }) => {
<strong>Actions:</strong> {data.actions.length}
</div>
)}

{/* Show detailed action info when highly zoomed AND hovered */}
{shouldShowDetails && data.actions && data.actions.map((action, index) => (
<div key={index} className="detailed-action">
<div className="action-name">• {action.name}</div>
<div className="action-function">{action.functionRef?.refName || 'No function'}</div>
{action.functionRef?.arguments && Object.keys(action.functionRef.arguments).length > 0 && (
<div className="action-args">
<code>{JSON.stringify(action.functionRef.arguments)}</code>
</div>
)}
{action.retryRef && (
<div className="action-retry">🔄 Retry enabled</div>
)}
</div>
))}
</div>

<Handle type="source" position={Position.Bottom} />
Expand Down
20 changes: 18 additions & 2 deletions src/components/nodes/SwitchNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { Handle, Position } from 'reactflow';
import { GitBranch } from 'lucide-react';
import './NodeStyles.css';

const SwitchNode = ({ data, selected }) => {
const SwitchNode = ({ data, selected, zoom = 1 }) => {
const [isHovered, setIsHovered] = React.useState(false);
const shouldShowDetails = zoom > 1.5 && isHovered; // Show details only when zoomed AND hovered
const dataConditions = data.dataConditions || [];
const eventConditions = data.eventConditions || [];
const conditions = dataConditions.length > 0 ? dataConditions : eventConditions;
Expand All @@ -12,7 +14,11 @@ const SwitchNode = ({ data, selected }) => {
const totalHandles = conditions.length + (hasDefault ? 1 : 0);

return (
<div className={`custom-node switch-node switch-node-${conditionType} ${selected ? 'selected' : ''}`}>
<div
className={`custom-node switch-node switch-node-${conditionType} ${selected ? 'selected' : ''}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<Handle type="target" position={Position.Top} />

<div className="node-header">
Expand Down Expand Up @@ -41,6 +47,16 @@ const SwitchNode = ({ data, selected }) => {
{conditions.map((condition, index) => (
<div key={`condition-${index}`} className="switch-output-item">
<span className="output-label">{condition.name || condition.eventRef || condition.condition || `condition${index + 1}`}</span>
{shouldShowDetails && conditionType === 'data' && condition.condition && (
<div className="condition-detail">
<code>{condition.condition}</code>
</div>
)}
{shouldShowDetails && conditionType === 'event' && condition.eventRef && (
<div className="condition-detail">
Event: <code>{condition.eventRef}</code>
</div>
)}
<Handle
type="source"
position={Position.Right}
Expand Down