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
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { ExtendedGraph, Position, Size } from "./graph";

// Defaults
export const DEFAULT_NODE_SIZE = {
height: 50,
width: 90,
height: 60,
width: 180,
};

export function applyAutoLayout(graph: ExtendedGraph): ExtendedGraph {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
}

.dec-root.dark .custom-node-container {
@apply dec:bg-[rgb(85,83,83)]
@apply dec:bg-[#2d3748]
dec:border-[#e5e4e2];
}

Expand Down Expand Up @@ -161,4 +161,75 @@
dec:border-gray-600
dec:text-gray-200;
}

/* task leaf nodes */
.dec-root .dec-task-node-container {
@apply dec:rounded-lg
dec:bg-white
dec:shadow-sm
dec:transition-[border,box-shadow]
dec:h-full
dec:w-full;
border-top: 4px solid var(--task-node-color);
}

.dec-root.dark .dec-task-node-container {
@apply dec:bg-[#2d3748];
}

.dec-root .dec-task-node-container:hover {
@apply dec:shadow-[0_0_10px_rgba(0,65,208,0.3)];
}

.dec-root.dark .dec-task-node-container:hover {
@apply dec:shadow-[0_0_10px_rgba(255,255,255,0.3)];
}

.dec-root .dec-task-node-container.selected {
@apply dec:shadow-[0_0_10px_rgba(59,130,246,0.8)];
}

.dec-root.dark .dec-task-node-container.selected {
@apply dec:bg-[#3d4a5c]
dec:shadow-[0_0_10px_rgba(255,255,255,0.3)];
}

.dec-root .dec-task-node-content {
@apply dec:flex
dec:items-center
dec:gap-3
dec:px-4
dec:py-3;
}

.dec-root .dec-task-node-icon {
color: var(--task-node-color);
}

.dec-root .dec-task-node-label {
@apply dec:flex
dec:flex-col
dec:gap-0.5;
}

.dec-root .dec-task-node-name {
@apply dec:text-sm
dec:text-black
dec:leading-tight;
}

.dec-root.dark .dec-task-node-name {
@apply dec:text-white;
}

.dec-root .dec-task-node-type {
@apply dec:text-[9px]
dec:uppercase
dec:text-gray-500
dec:leading-tight;
}

.dec-root.dark .dec-task-node-type {
@apply dec:text-gray-400;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const initialNodes: RF.Node[] = [
position: { x: 100, y: 0 },
height: DEFAULT_NODE_SIZE.height,
width: DEFAULT_NODE_SIZE.width,
data: { label: "Node 1" },
data: { label: "CallNode" },
},
{
id: "n2",
Expand All @@ -55,15 +55,15 @@ const initialNodes: RF.Node[] = [
position: { x: 100, y: 200 },
height: DEFAULT_NODE_SIZE.height,
width: DEFAULT_NODE_SIZE.width,
data: { label: "Node 3" },
data: { label: "SwitchNode" },
},
{
id: "n4",
type: GraphNodeType.Emit,
position: { x: 0, y: 300 },
position: { x: -100, y: 300 },
height: DEFAULT_NODE_SIZE.height,
width: DEFAULT_NODE_SIZE.width,
data: { label: "Node 4" },
data: { label: "EmitNode" },
},
{
id: "n5",
Expand All @@ -76,7 +76,7 @@ const initialNodes: RF.Node[] = [
{
id: "n6",
type: GraphNodeType.Fork,
position: { x: 200, y: 300 },
position: { x: 300, y: 300 },
height: DEFAULT_NODE_SIZE.height,
width: DEFAULT_NODE_SIZE.width,
data: { label: "Node 6" },
Expand All @@ -87,31 +87,31 @@ const initialNodes: RF.Node[] = [
position: { x: 100, y: 400 },
height: DEFAULT_NODE_SIZE.height,
width: DEFAULT_NODE_SIZE.width,
data: { label: "Node 7" },
data: { label: "ListenNode" },
},
{
id: "n8",
type: GraphNodeType.Raise,
position: { x: 100, y: 500 },
height: DEFAULT_NODE_SIZE.height,
width: DEFAULT_NODE_SIZE.width,
data: { label: "Node 8" },
data: { label: "RaiseNode" },
},
{
id: "n9",
type: GraphNodeType.Run,
position: { x: 100, y: 600 },
height: DEFAULT_NODE_SIZE.height,
width: DEFAULT_NODE_SIZE.width,
data: { label: "Node 9" },
data: { label: "RunNode" },
},
{
id: "n10",
type: GraphNodeType.Set,
position: { x: 100, y: 700 },
height: DEFAULT_NODE_SIZE.height,
width: DEFAULT_NODE_SIZE.width,
data: { label: "Node 10" },
data: { label: "SetNode" },
},
{
id: "n11",
Expand All @@ -127,7 +127,7 @@ const initialNodes: RF.Node[] = [
position: { x: 100, y: 900 },
height: DEFAULT_NODE_SIZE.height,
width: DEFAULT_NODE_SIZE.width,
data: { label: "Node 12" },
data: { label: "WaitNode" },
},
];

Expand All @@ -139,10 +139,12 @@ const initialEdges: RF.Edge[] = [
type: GraphEdgeType.Default,
data: {
wayPoints: [
{ x: 145, y: 60 },
{ x: 170, y: 60 },
{ x: 170, y: 85 },
{ x: 145, y: 85 },
{ x: 190, y: 60 },
{ x: 190, y: 70 },
{ x: 140, y: 70 },
{ x: 140, y: 85 },
{ x: 190, y: 85 },
{ x: 190, y: 95 },
],
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
* limitations under the License.
*/

import type React from "react";
import { GraphNodeType } from "@serverlessworkflow/sdk";
import * as RF from "@xyflow/react";
import { type LeafNodeType, taskNodeConfigMap } from "./taskNodeConfig";

// Node types must match sdk GraphNodeType enum
export const NodeTypes: RF.NodeTypes = {
Expand All @@ -38,6 +40,35 @@ export type BaseNodeData = {
label: string;
};

interface NodeContentProps {
id: string;
data: BaseNodeData;
selected: boolean;
type: string;
}

function TaskNodeContent({ id, data, selected, type }: NodeContentProps) {
const config = taskNodeConfigMap[type as LeafNodeType];
const Icon = config.icon;
return (
<div
className={`dec-task-node-container ${selected ? "selected" : ""}`}
style={{ "--task-node-color": config.color } as React.CSSProperties}
data-testid={`${type}-node-${id}`}
>
<RF.Handle type="target" position={RF.Position.Top} />
<div className="dec-task-node-content">
<Icon size={20} className="dec-task-node-icon" />
<div className="dec-task-node-label">
<span className="dec-task-node-name">{data.label}</span>
<span className="dec-task-node-type">{config.typeLabel}</span>
</div>
</div>
<RF.Handle type="source" position={RF.Position.Bottom} />
</div>
);
}

// TODO: These props are just a placeholder
interface PlaceholderProps {
id: string;
Expand Down Expand Up @@ -65,8 +96,7 @@ function PlaceholderContent({ id, data, selected, type }: PlaceholderProps) {
/* call node */
export type CallNodeType = RF.Node<BaseNodeData, typeof GraphNodeType.Call>;
export function CallNode({ id, data, selected, type }: RF.NodeProps<CallNodeType>) {
// TODO: This component is just a placeholder
return <PlaceholderContent id={id} data={data} selected={selected} type={type} />;
return <TaskNodeContent id={id} data={data} selected={selected} type={type} />;
}

/* do node */
Expand All @@ -79,8 +109,7 @@ export function DoNode({ id, data, selected, type }: RF.NodeProps<DoNodeType>) {
/* emit node */
export type EmitNodeType = RF.Node<BaseNodeData, typeof GraphNodeType.Emit>;
export function EmitNode({ id, data, selected, type }: RF.NodeProps<EmitNodeType>) {
// TODO: This component is just a placeholder
return <PlaceholderContent id={id} data={data} selected={selected} type={type} />;
return <TaskNodeContent id={id} data={data} selected={selected} type={type} />;
}

/* for node */
Expand All @@ -100,36 +129,31 @@ export function ForkNode({ id, data, selected, type }: RF.NodeProps<ForkNodeType
/* listen node */
export type ListenNodeType = RF.Node<BaseNodeData, typeof GraphNodeType.Listen>;
export function ListenNode({ id, data, selected, type }: RF.NodeProps<ListenNodeType>) {
// TODO: This component is just a placeholder
return <PlaceholderContent id={id} data={data} selected={selected} type={type} />;
return <TaskNodeContent id={id} data={data} selected={selected} type={type} />;
}

/* raise node */
export type RaiseNodeType = RF.Node<BaseNodeData, typeof GraphNodeType.Raise>;
export function RaiseNode({ id, data, selected, type }: RF.NodeProps<RaiseNodeType>) {
// TODO: This component is just a placeholder
return <PlaceholderContent id={id} data={data} selected={selected} type={type} />;
return <TaskNodeContent id={id} data={data} selected={selected} type={type} />;
}

/* run node */
export type RunNodeType = RF.Node<BaseNodeData, typeof GraphNodeType.Run>;
export function RunNode({ id, data, selected, type }: RF.NodeProps<RunNodeType>) {
// TODO: This component is just a placeholder
return <PlaceholderContent id={id} data={data} selected={selected} type={type} />;
return <TaskNodeContent id={id} data={data} selected={selected} type={type} />;
}

/* set node */
export type SetNodeType = RF.Node<BaseNodeData, typeof GraphNodeType.Set>;
export function SetNode({ id, data, selected, type }: RF.NodeProps<SetNodeType>) {
// TODO: This component is just a placeholder
return <PlaceholderContent id={id} data={data} selected={selected} type={type} />;
return <TaskNodeContent id={id} data={data} selected={selected} type={type} />;
}

/* switch node */
export type SwitchNodeType = RF.Node<BaseNodeData, typeof GraphNodeType.Switch>;
export function SwitchNode({ id, data, selected, type }: RF.NodeProps<SwitchNodeType>) {
// TODO: This component is just a placeholder
return <PlaceholderContent id={id} data={data} selected={selected} type={type} />;
return <TaskNodeContent id={id} data={data} selected={selected} type={type} />;
}

/* try node */
Expand All @@ -142,6 +166,5 @@ export function TryNode({ id, data, selected, type }: RF.NodeProps<TryNodeType>)
/* wait node */
export type WaitNodeType = RF.Node<BaseNodeData, typeof GraphNodeType.Wait>;
export function WaitNode({ id, data, selected, type }: RF.NodeProps<WaitNodeType>) {
// TODO: This component is just a placeholder
return <PlaceholderContent id={id} data={data} selected={selected} type={type} />;
return <TaskNodeContent id={id} data={data} selected={selected} type={type} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2021-Present The Serverless Workflow Specification Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { GraphNodeType } from "@serverlessworkflow/sdk";
import {
AlertTriangle,
ArrowRightLeft,
AudioLines,
Clock,
Megaphone,
PenLine,
Phone,
Terminal,
} from "lucide-react";
import type { ComponentType } from "react";

export type LeafNodeType =
| typeof GraphNodeType.Call
| typeof GraphNodeType.Emit
| typeof GraphNodeType.Listen
| typeof GraphNodeType.Raise
| typeof GraphNodeType.Run
| typeof GraphNodeType.Set
| typeof GraphNodeType.Switch
| typeof GraphNodeType.Wait;

export interface TaskNodeConfig {
color: string;
icon: ComponentType<{ size?: number; className?: string }>;
typeLabel: string;
}

export const taskNodeConfigMap: Record<LeafNodeType, TaskNodeConfig> = {
[GraphNodeType.Call]: {
color: "#2563EB",
icon: Phone,
typeLabel: "CALL",
},
[GraphNodeType.Emit]: {
color: "#8B5CF6",
icon: Megaphone,
typeLabel: "EMIT",
},
[GraphNodeType.Listen]: {
color: "#CA8A04",
icon: AudioLines,
typeLabel: "LISTEN",
},
[GraphNodeType.Raise]: {
color: "#DC2626",
icon: AlertTriangle,
typeLabel: "RAISE",
},
[GraphNodeType.Run]: {
color: "#10B981",
icon: Terminal,
typeLabel: "RUN",
},
[GraphNodeType.Set]: {
color: "#EA580C",
icon: PenLine,
typeLabel: "SET",
},
[GraphNodeType.Switch]: {
color: "#BE185D",
icon: ArrowRightLeft,
typeLabel: "SWITCH",
},
[GraphNodeType.Wait]: {
color: "#84CC16",
icon: Clock,
typeLabel: "WAIT",
},
};
Loading
Loading