Skip to content

Commit

Permalink
Merge pull request #10 from jakemoresca/action-pane
Browse files Browse the repository at this point in the history
Action pane
  • Loading branch information
jakemoresca committed Aug 18, 2024
2 parents 4cc86fc + 1578bbe commit 98d8d8d
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 41 deletions.
147 changes: 120 additions & 27 deletions actionFlow.editor/src/components/controls/property-field.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,125 @@
import { ChangeEvent } from "react";
import { NodePropertyDefinition, NodePropertyType } from "../left-pane/NodeProperties";
import { Label, TextInput } from "flowbite-react";
import {
NodePropertyDefinition,
NodePropertyType,
} from "../left-pane/NodeProperties";
import { Label, Select, Textarea, TextInput } from "flowbite-react";
import TableProperties from "./table-properties";
import { getNodeColumnDefinition, toTableProperties } from "@/modules/nodes/node-column-definition-provider";
import {
getNodeColumnDefinition,
toTableProperties,
} from "@/modules/nodes/node-column-definition-provider";

export type PropertyFieldData = {
nodeType: string
properties: Record<string, any>;
propertyDefinition: NodePropertyDefinition
handlePropertyChange?: (event: ChangeEvent<HTMLInputElement>) => void
};

export default function PropertyField({ nodeType, properties, propertyDefinition, handlePropertyChange }: PropertyFieldData) {

switch(propertyDefinition.propertyType) {
case NodePropertyType.Label:
return <Label htmlFor="label" value={properties[propertyDefinition.propertyName]} />

case NodePropertyType.TextField:
return <TextInput name={propertyDefinition.propertyName} type="text" placeholder={propertyDefinition.propertyLabel} value={properties[propertyDefinition.propertyName]} onChange={handlePropertyChange} />

case NodePropertyType.NumberField:
return <TextInput name={propertyDefinition.propertyName} type="number" placeholder={propertyDefinition.propertyLabel} value={properties[propertyDefinition.propertyName]} onChange={handlePropertyChange} />

case NodePropertyType.Properties:
const nodeColumnDefinitions = getNodeColumnDefinition(nodeType, propertyDefinition.propertyName);
const tableProperties = toTableProperties(properties, "variables");
return <TableProperties columnDefinitions={nodeColumnDefinitions} properties={tableProperties} handlePropertyChange={handlePropertyChange} />
}
nodeType: string;
properties: Record<string, any>;
propertyDefinition: NodePropertyDefinition;
handlePropertyChange?: (event: ChangeEvent) => void;
};

export default function PropertyField({
nodeType,
properties,
propertyDefinition,
handlePropertyChange,
}: PropertyFieldData) {

const value = getValue(properties, propertyDefinition);

switch (propertyDefinition.propertyType) {
case NodePropertyType.Label:
return (
<Label
htmlFor="label"
value={value}
/>
);

case NodePropertyType.TextField:
return (
<TextInput
name={propertyDefinition.propertyName}
type="text"
placeholder={propertyDefinition.propertyLabel}
value={value}
onChange={handlePropertyChange}
/>
);

case NodePropertyType.NumberField:
return (
<TextInput
name={propertyDefinition.propertyName}
type="number"
placeholder={propertyDefinition.propertyLabel}
value={value}
onChange={handlePropertyChange}
/>
);

case NodePropertyType.Properties:
const nodeColumnDefinitions = getNodeColumnDefinition(
nodeType,
propertyDefinition.propertyName
);
const tableProperties = toTableProperties(
properties,
propertyDefinition.propertyName
);
return (
<TableProperties
columnDefinitions={nodeColumnDefinitions}
properties={tableProperties}
handlePropertyChange={handlePropertyChange}
/>
);

case NodePropertyType.List:
return (
<Select
name={propertyDefinition.propertyName}
onChange={handlePropertyChange}
value={value}
>
{propertyDefinition.propertySources?.map((x, index) => {
return (
<option
key={`${propertyDefinition.propertyName}_source_${index}`}
>
{x}
</option>
);
})}
</Select>
);

case NodePropertyType.TextArea:
return (
<Textarea
name={propertyDefinition.propertyName}
placeholder={propertyDefinition.propertyLabel}
rows={4}
value={value}
/>
);
}

}

function getValue(properties: Record<string, any>, propertyDefinition: NodePropertyDefinition) {
const propertyDefinitionValueSource = propertyDefinition.propertyName;

if(propertyDefinitionValueSource.includes(".")) {
const valuePropertyPaths = propertyDefinitionValueSource.split(".");
let propertySource = properties;
let value;

for (let index = 0; index < valuePropertyPaths.length; index++) {
value = propertySource[valuePropertyPaths[index]];
propertySource = value;
}

return value;
}

return properties[propertyDefinitionValueSource];
}
14 changes: 9 additions & 5 deletions actionFlow.editor/src/components/left-pane/ActionDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Drawer, Button, Label } from "flowbite-react"
import { Node } from "@xyflow/react";
import { getNodeColumnDefinition, toTableProperties } from "@/modules/nodes/node-column-definition-provider";
import { BaseNodeData } from "../nodes/BaseNode";
import TableProperties from "../controls/table-properties";
import NodeProperties from "./NodeProperties";
import { NodeTypeKeys } from "../nodes";

export type ActionDrawerData = {
onAddAction?: () => void;
Expand All @@ -14,12 +12,18 @@ export type ActionDrawerData = {
export default function ActionDrawer({ onAddAction: addAction, onDeleteAction, selectedNodes }: ActionDrawerData) {

const validNodeTypesToDelete = [
"variable"
NodeTypeKeys.variable.type,
NodeTypeKeys.sendHttpCall.type,
NodeTypeKeys.controlFlow.type,
NodeTypeKeys.forLoop.type
]

const validNodeTypesToAddTo = [
"input",
"variable"
NodeTypeKeys.variable.type,
NodeTypeKeys.sendHttpCall.type,
NodeTypeKeys.controlFlow.type,
NodeTypeKeys.forLoop.type
]

const canDelete = selectedNodes && selectedNodes.length > 0 && selectedNodes?.every(x => {
Expand Down
14 changes: 14 additions & 0 deletions actionFlow.editor/src/components/left-pane/AddActionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ export const AddActionModalDataActions = [
type: NodeTypeKeys.variable.type,
icon: actionIcon,
},
{
name: NodeTypeKeys.sendHttpCall.name,
type: NodeTypeKeys.sendHttpCall.type,
icon: actionIcon,
},
{
name: NodeTypeKeys.controlFlow.name,
type: NodeTypeKeys.controlFlow.type,
icon: actionIcon,
},{
name: NodeTypeKeys.forLoop.name,
type: NodeTypeKeys.forLoop.type,
icon: actionIcon,
},
];

export default function AddActionModal({
Expand Down
3 changes: 3 additions & 0 deletions actionFlow.editor/src/components/left-pane/NodeProperties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ export type NodePropertyDefinition = {
propertyType: NodePropertyType;
propertyLabel: string;
index: number;
propertySources?: string[]
};

export enum NodePropertyType {
Label,
TextField,
NumberField,
Properties,
List,
TextArea,
}

export default function NodeProperties({ node }: NodePropertiesData) {
Expand Down
29 changes: 29 additions & 0 deletions actionFlow.editor/src/components/nodes/ControlFlowNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Node, NodeProps } from "@xyflow/react";
import { Handle, Position } from "@xyflow/react";
import { NodeBase } from "@xyflow/system";
import ConditionSection from "./ConditionSection";
import { BaseNodeData } from "./BaseNode";

export type ControlFlowNodeData = BaseNodeData & {
conditions?: ControlFlowConditionsData;
};

export type ControlFlowConditionsData = {
expressions?: string;
}

export type ControlFlowNode = NodeBase & Node<ControlFlowNodeData>;

export default function ControlFlowNode({ id, data }: NodeProps<ControlFlowNode>) {
return (
<div className="block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
<Handle type="target" position={Position.Top} id={`node_${id}_top`} />
<h5 className="text-xs font-bold dark:text-white">{data.label}</h5>

<ConditionSection condition={data.conditions?.expressions} />

<Handle type="source" position={Position.Bottom} id={`node_${id}_bottom`} />
<Handle type="source" position={Position.Right} id={`node_${id}_right`} />
</div>
);
}
37 changes: 37 additions & 0 deletions actionFlow.editor/src/components/nodes/ForLoopNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Node, NodeProps } from "@xyflow/react";
import { Handle, Position } from "@xyflow/react";
import { NodeBase } from "@xyflow/system";
import ConditionSection from "./ConditionSection";
import { BaseNodeData } from "./BaseNode";

export type ForLoopNodeData = BaseNodeData & {
initializerVariable?: string;
initialValue?: string;
loopCondition?: string;
iterator?: string;
};

export type ForLoopNode = NodeBase & Node<ForLoopNodeData>;

export default function ForLoopNode({ id, data }: NodeProps<ForLoopNode>) {

const loopString = `For (${data.initializerVariable} = ${data.initialValue}; ${data.loopCondition}; ${data.iterator})`;

return (
<div className="block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
<Handle type="target" position={Position.Top} id={`node_${id}_top`} />
<h5 className="text-xs font-bold dark:text-white">{data.label}</h5>

<ConditionSection condition={data.condition} />

<ul role="list" className="text-gray-500 dark:text-gray-400">
<li className="flex space-x-2 rtl:space-x-reverse items-center">
<span className="leading-tight text-xs">{loopString}</span>
</li>
</ul>

<Handle type="source" position={Position.Bottom} id={`node_${id}_bottom`} />
<Handle type="source" position={Position.Right} id={`node_${id}_right`} />
</div>
);
}
32 changes: 32 additions & 0 deletions actionFlow.editor/src/components/nodes/SendHttpCallNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Node, NodeProps } from "@xyflow/react";
import { Handle, Position } from "@xyflow/react";
import { NodeBase } from "@xyflow/system";
import ConditionSection from "./ConditionSection";
import { BaseNodeData } from "./BaseNode";

export type SendHttpCallNodeData = BaseNodeData & {
headers?: Record<string, string>;
url?: string;
method?: string;
body?: string;
resultVariable?: string;
};
export type SendHttpCallNode = NodeBase & Node<SendHttpCallNodeData>;

export default function SendHttpCallNode({ id, data }: NodeProps<SendHttpCallNode>) {
return (
<div className="block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
<Handle type="target" position={Position.Top} id={`node_${id}_top`} />
<h5 className="text-xs font-bold dark:text-white">{data.label}</h5>

<ConditionSection condition={data.condition} />
<ul role="list" className="text-gray-500 dark:text-gray-400">
<li className="flex space-x-2 rtl:space-x-reverse items-center">
<span className="leading-tight text-xs">{data.url}</span>
</li>
</ul>

<Handle type="source" position={Position.Bottom} id={`node_${id}_bottom`} />
</div>
);
}
8 changes: 4 additions & 4 deletions actionFlow.editor/src/components/nodes/VariableNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import type { Node, NodeProps } from "@xyflow/react";
import { Handle, Position } from "@xyflow/react";
import { NodeBase } from '@xyflow/system';
import ConditionSection from "./ConditionSection";
import { BaseNode, BaseNodeData } from "./BaseNode";
import { BaseNodeData } from "./BaseNode";

export type VariableNodeData = BaseNodeData & {
variables?: Record<string, string>
}
export type VariableNode = NodeBase & Node<VariableNodeData>;

export default function VariableNode({
data
id, data
}: NodeProps<VariableNode>) {

const renderProperties = (data: VariableNodeData) => {
Expand Down Expand Up @@ -38,13 +38,13 @@ export default function VariableNode({
return (
// We add this class to use the same styles as React Flow's default nodes.
<div className="block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
<Handle type="target" position={Position.Top} />
<Handle type="target" position={Position.Top} id={`node_${id}_top`} />
<h5 className="text-xs font-bold dark:text-white">{data.label}</h5>

<ConditionSection condition={data.condition} />
{renderProperties(data)}

<Handle type="source" position={Position.Bottom} />
<Handle type="source" position={Position.Bottom} id={`node_${id}_bottom`} />
</div>
);
}
Loading

0 comments on commit 98d8d8d

Please sign in to comment.