feat: typed socket foundation for LiteGraph-style blueprint editor
Phase 1 — Typed Socket Foundation: - SocketType enum: flow, number, string, boolean, any - Socket definitions for all 9 existing node types (inputs + outputs) - TypedHandle component: color-coded circles (flow=white square, number=purple, string=teal, boolean=green, any=indigo) - NodeHandles helper: auto-renders handles from socket config with proportional spacing - isValidConnection: rejects type mismatches, any-type sockets accept all connections - All existing nodes refactored to use NodeHandles instead of manual Handle components
This commit is contained in:
parent
cfba38ca24
commit
9a7462e5f7
13 changed files with 311 additions and 39 deletions
|
|
@ -1,14 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
||||
import { type NodeProps } from "@xyflow/react";
|
||||
import { CheckCircleIcon } from "lucide-react";
|
||||
import { NodeHandles } from "./NodeHandles";
|
||||
|
||||
export function BooleanNode({ data }: NodeProps) {
|
||||
const d = data as { value?: boolean; description?: string };
|
||||
|
||||
return (
|
||||
<div className="bg-emerald-950/80 border border-emerald-500/50 rounded-lg shadow-md min-w-28">
|
||||
<Handle type="target" position={Position.Left} className="!bg-emerald-400" />
|
||||
<NodeHandles nodeType="boolean" />
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-emerald-500/30">
|
||||
<CheckCircleIcon className="size-4 text-emerald-400 shrink-0" />
|
||||
<span className="text-sm font-medium text-emerald-200 flex-1 truncate">
|
||||
|
|
@ -18,7 +19,6 @@ export function BooleanNode({ data }: NodeProps) {
|
|||
<div className="px-3 py-2 text-xs text-emerald-300/80 line-clamp-2">
|
||||
{d.description || "Boolean flag"}
|
||||
</div>
|
||||
<Handle type="source" position={Position.Right} className="!bg-emerald-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
"use client";
|
||||
|
||||
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
||||
import { type NodeProps } from "@xyflow/react";
|
||||
import { PencilIcon, WaypointsIcon } from "lucide-react";
|
||||
import { NodeHandles } from "./NodeHandles";
|
||||
|
||||
export function ChoiceNode({ data }: NodeProps) {
|
||||
const d = data as { question?: string; options?: Array<{ id: string; label: string }> };
|
||||
|
||||
const options = d.options ?? [];
|
||||
|
||||
return (
|
||||
<div className="bg-amber-950/80 border border-amber-500/50 rounded-lg shadow-md min-w-56">
|
||||
<Handle type="target" position={Position.Left} className="!bg-amber-400" />
|
||||
<NodeHandles nodeType="choice" />
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-amber-500/30">
|
||||
<WaypointsIcon className="size-4 text-amber-400 shrink-0" />
|
||||
<span className="text-sm font-medium text-amber-200 flex-1 truncate">
|
||||
|
|
@ -25,16 +25,10 @@ export function ChoiceNode({ data }: NodeProps) {
|
|||
options.map((opt) => (
|
||||
<div
|
||||
key={opt.id}
|
||||
className="relative flex items-center gap-2 text-xs text-amber-300/80 bg-amber-950/40 rounded px-2 py-1"
|
||||
className="flex items-center gap-2 text-xs text-amber-300/80 bg-amber-950/40 rounded px-2 py-1"
|
||||
>
|
||||
<span className="w-1 h-1 rounded-full bg-amber-400 shrink-0" />
|
||||
<span className="flex-1 truncate">{opt.label}</span>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={opt.id}
|
||||
className="!bg-amber-400 !size-2 !min-w-0 !min-h-0 !border-0"
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
||||
import { type NodeProps } from "@xyflow/react";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { NodeHandles } from "./NodeHandles";
|
||||
|
||||
export function DateNode({ data }: NodeProps) {
|
||||
const d = data as { value?: string; description?: string };
|
||||
|
||||
return (
|
||||
<div className="bg-cyan-950/80 border border-cyan-500/50 rounded-lg shadow-md min-w-32">
|
||||
<Handle type="target" position={Position.Left} className="!bg-cyan-400" />
|
||||
<NodeHandles nodeType="date" />
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-cyan-500/30">
|
||||
<CalendarIcon className="size-4 text-cyan-400 shrink-0" />
|
||||
<span className="text-sm font-medium text-cyan-200 flex-1 truncate">
|
||||
|
|
@ -18,7 +19,6 @@ export function DateNode({ data }: NodeProps) {
|
|||
<div className="px-3 py-2 text-xs text-cyan-300/80 line-clamp-2">
|
||||
{d.description || "Date value"}
|
||||
</div>
|
||||
<Handle type="source" position={Position.Right} className="!bg-cyan-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
||||
import { type NodeProps } from "@xyflow/react";
|
||||
import { BookOpenTextIcon, PencilIcon } from "lucide-react";
|
||||
import { NodeHandles } from "./NodeHandles";
|
||||
|
||||
export function NarrativeBeatNode({ data }: NodeProps) {
|
||||
const d = data as { title?: string; text?: string };
|
||||
|
||||
return (
|
||||
<div className="bg-blue-950/80 border border-blue-500/50 rounded-lg shadow-md min-w-56">
|
||||
<Handle type="target" position={Position.Left} className="!bg-blue-400" />
|
||||
<NodeHandles nodeType="narrativeBeat" />
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-blue-500/30">
|
||||
<BookOpenTextIcon className="size-4 text-blue-400 shrink-0" />
|
||||
<span className="text-sm font-medium text-blue-200 flex-1 truncate">
|
||||
|
|
@ -19,7 +20,6 @@ export function NarrativeBeatNode({ data }: NodeProps) {
|
|||
<div className="px-3 py-2 text-xs text-blue-300/80 line-clamp-3">
|
||||
{d.text || "No description."}
|
||||
</div>
|
||||
<Handle type="source" position={Position.Right} className="!bg-blue-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { RandomNode } from "./RandomNode";
|
|||
import { BooleanNode } from "./BooleanNode";
|
||||
import { DateNode } from "./DateNode";
|
||||
import { NodeEditDialog } from "./NodeEditDialog";
|
||||
import { NODE_SOCKETS, type SocketType } from "./types";
|
||||
|
||||
const nodeTypes = {
|
||||
narrativeBeat: NarrativeBeatNode,
|
||||
|
|
@ -85,6 +86,29 @@ export function NarrativeFlowEditor() {
|
|||
[setEdges],
|
||||
);
|
||||
|
||||
const isValidConnection = useCallback(
|
||||
(connection: Edge | Connection) => {
|
||||
if (!connection.source || !connection.target || !connection.sourceHandle || !connection.targetHandle) {
|
||||
return false;
|
||||
}
|
||||
const sourceNode = nodes.find((n) => n.id === connection.source);
|
||||
const targetNode = nodes.find((n) => n.id === connection.target);
|
||||
if (!sourceNode || !targetNode) return false;
|
||||
|
||||
const sourceSockets = NODE_SOCKETS[sourceNode.type as string];
|
||||
const targetSockets = NODE_SOCKETS[targetNode.type as string];
|
||||
if (!sourceSockets || !targetSockets) return false;
|
||||
|
||||
const sourceSocket = sourceSockets.outputs.find((s) => s.id === connection.sourceHandle);
|
||||
const targetSocket = targetSockets.inputs.find((s) => s.id === connection.targetHandle);
|
||||
if (!sourceSocket || !targetSocket) return false;
|
||||
|
||||
if (sourceSocket.type === "any" || targetSocket.type === "any") return true;
|
||||
return sourceSocket.type === targetSocket.type;
|
||||
},
|
||||
[nodes],
|
||||
);
|
||||
|
||||
const onNodeClick = useCallback((_e: React.MouseEvent, node: Node) => {
|
||||
setEditingNodeId(node.id);
|
||||
}, []);
|
||||
|
|
@ -253,6 +277,7 @@ export function NarrativeFlowEditor() {
|
|||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
isValidConnection={isValidConnection}
|
||||
onNodeClick={onNodeClick}
|
||||
onNodeContextMenu={onNodeContextMenu}
|
||||
onEdgeContextMenu={onEdgeContextMenu}
|
||||
|
|
|
|||
52
src/components/admin/narrative-flow/NodeHandles.tsx
Normal file
52
src/components/admin/narrative-flow/NodeHandles.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"use client";
|
||||
|
||||
import { TypedHandle } from "./TypedHandle";
|
||||
import { NODE_SOCKETS } from "./types";
|
||||
|
||||
interface NodeHandlesProps {
|
||||
nodeType: string;
|
||||
dynamicOutputs?: Array<{ id: string; label: string }>;
|
||||
}
|
||||
|
||||
export function NodeHandles({ nodeType, dynamicOutputs }: NodeHandlesProps) {
|
||||
const config = NODE_SOCKETS[nodeType];
|
||||
if (!config) return null;
|
||||
|
||||
const inputs = config.inputs;
|
||||
const outputs = dynamicOutputs
|
||||
? dynamicOutputs.map((o) => ({ ...o, type: "flow" as const }))
|
||||
: config.outputs;
|
||||
|
||||
const maxSlots = Math.max(inputs.length, outputs.length, 1);
|
||||
|
||||
return (
|
||||
<>
|
||||
{inputs.map((socket, i) => {
|
||||
const topPercent = maxSlots === 1 ? 50 : (i / (maxSlots - 1)) * 80 + 10;
|
||||
return (
|
||||
<TypedHandle
|
||||
key={socket.id}
|
||||
id={socket.id}
|
||||
socketType={socket.type}
|
||||
label={socket.label}
|
||||
isInput={true}
|
||||
style={{ top: `${topPercent}%` }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{outputs.map((socket, i) => {
|
||||
const topPercent = maxSlots === 1 ? 50 : (i / (maxSlots - 1)) * 80 + 10;
|
||||
return (
|
||||
<TypedHandle
|
||||
key={socket.id}
|
||||
id={socket.id}
|
||||
socketType={socket.type}
|
||||
label={socket.label}
|
||||
isInput={false}
|
||||
style={{ top: `${topPercent}%` }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
||||
import { type NodeProps } from "@xyflow/react";
|
||||
import { HashIcon } from "lucide-react";
|
||||
import { NodeHandles } from "./NodeHandles";
|
||||
|
||||
export function NumberNode({ data }: NodeProps) {
|
||||
const d = data as { value?: number; description?: string };
|
||||
|
||||
return (
|
||||
<div className="bg-purple-950/80 border border-purple-500/50 rounded-lg shadow-md min-w-32">
|
||||
<Handle type="target" position={Position.Left} className="!bg-purple-400" />
|
||||
<NodeHandles nodeType="number" />
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-purple-500/30">
|
||||
<HashIcon className="size-4 text-purple-400 shrink-0" />
|
||||
<span className="text-sm font-medium text-purple-200 flex-1 truncate">
|
||||
|
|
@ -18,7 +19,6 @@ export function NumberNode({ data }: NodeProps) {
|
|||
<div className="px-3 py-2 text-xs text-purple-300/80 line-clamp-2">
|
||||
{d.description || "Numeric value"}
|
||||
</div>
|
||||
<Handle type="source" position={Position.Right} className="!bg-purple-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
||||
import { type NodeProps } from "@xyflow/react";
|
||||
import { FlagIcon, PencilIcon } from "lucide-react";
|
||||
import { NodeHandles } from "./NodeHandles";
|
||||
|
||||
export function OutcomeNode({ data }: NodeProps) {
|
||||
const d = data as {
|
||||
|
|
@ -9,12 +10,11 @@ export function OutcomeNode({ data }: NodeProps) {
|
|||
text?: string;
|
||||
effects?: Array<{ type: string; value: string }>;
|
||||
};
|
||||
|
||||
const effects = d.effects ?? [];
|
||||
|
||||
return (
|
||||
<div className="bg-emerald-950/80 border border-emerald-500/50 rounded-lg shadow-md min-w-56">
|
||||
<Handle type="target" position={Position.Left} className="!bg-emerald-400" />
|
||||
<NodeHandles nodeType="outcome" />
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-emerald-500/30">
|
||||
<FlagIcon className="size-4 text-emerald-400 shrink-0" />
|
||||
<span className="text-sm font-medium text-emerald-200 flex-1 truncate">
|
||||
|
|
@ -33,10 +33,7 @@ export function OutcomeNode({ data }: NodeProps) {
|
|||
<span className="text-xs text-emerald-400/40 italic block">None</span>
|
||||
) : (
|
||||
effects.map((eff, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-1 text-xs text-emerald-300/70"
|
||||
>
|
||||
<div key={i} className="flex items-center gap-1 text-xs text-emerald-300/70">
|
||||
<span className="text-emerald-400/80 font-mono">{eff.type}</span>
|
||||
<span className="text-emerald-500/40">:</span>
|
||||
<span className="truncate">{eff.value}</span>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
||||
import { type NodeProps } from "@xyflow/react";
|
||||
import { ShuffleIcon } from "lucide-react";
|
||||
import { NodeHandles } from "./NodeHandles";
|
||||
|
||||
export function RandomNode({ data }: NodeProps) {
|
||||
const d = data as { min?: number; max?: number; description?: string };
|
||||
|
||||
return (
|
||||
<div className="bg-pink-950/80 border border-pink-500/50 rounded-lg shadow-md min-w-32">
|
||||
<Handle type="target" position={Position.Left} className="!bg-pink-400" />
|
||||
<NodeHandles nodeType="random" />
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-pink-500/30">
|
||||
<ShuffleIcon className="size-4 text-pink-400 shrink-0" />
|
||||
<span className="text-sm font-medium text-pink-200 flex-1 truncate">
|
||||
|
|
@ -18,7 +19,6 @@ export function RandomNode({ data }: NodeProps) {
|
|||
<div className="px-3 py-2 text-xs text-pink-300/80 line-clamp-2">
|
||||
{d.description || "Random number generator"}
|
||||
</div>
|
||||
<Handle type="source" position={Position.Right} className="!bg-pink-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
||||
import { type NodeProps } from "@xyflow/react";
|
||||
import { TypeIcon } from "lucide-react";
|
||||
import { NodeHandles } from "./NodeHandles";
|
||||
|
||||
export function StringNode({ data }: NodeProps) {
|
||||
const d = data as { value?: string; description?: string };
|
||||
|
||||
return (
|
||||
<div className="bg-teal-950/80 border border-teal-500/50 rounded-lg shadow-md min-w-32">
|
||||
<Handle type="target" position={Position.Left} className="!bg-teal-400" />
|
||||
<NodeHandles nodeType="string" />
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-teal-500/30">
|
||||
<TypeIcon className="size-4 text-teal-400 shrink-0" />
|
||||
<span className="text-sm font-medium text-teal-200 flex-1 truncate">
|
||||
|
|
@ -18,7 +19,6 @@ export function StringNode({ data }: NodeProps) {
|
|||
<div className="px-3 py-2 text-xs text-teal-300/80 line-clamp-2">
|
||||
{d.description || "String value"}
|
||||
</div>
|
||||
<Handle type="source" position={Position.Right} className="!bg-teal-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
59
src/components/admin/narrative-flow/TypedHandle.tsx
Normal file
59
src/components/admin/narrative-flow/TypedHandle.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"use client";
|
||||
|
||||
import { Handle, Position, type HandleProps } from "@xyflow/react";
|
||||
import { SOCKET_COLORS, type SocketType } from "./types";
|
||||
|
||||
interface TypedHandleProps {
|
||||
id: string;
|
||||
socketType: SocketType;
|
||||
label?: string;
|
||||
isInput: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export function TypedHandle({
|
||||
id,
|
||||
socketType,
|
||||
label,
|
||||
isInput,
|
||||
style,
|
||||
}: TypedHandleProps) {
|
||||
const color = SOCKET_COLORS[socketType];
|
||||
const isFlow = socketType === "flow";
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute flex items-center gap-1.5"
|
||||
style={{
|
||||
[isInput ? "left" : "right"]: -6,
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<Handle
|
||||
type={isInput ? "target" : "source"}
|
||||
position={isInput ? Position.Left : Position.Right}
|
||||
id={id}
|
||||
style={{
|
||||
background: color,
|
||||
width: isFlow ? 12 : 10,
|
||||
height: isFlow ? 12 : 10,
|
||||
borderRadius: isFlow ? 2 : "50%",
|
||||
border: `2px solid ${color}40`,
|
||||
}}
|
||||
/>
|
||||
{label && (
|
||||
<span
|
||||
className="text-[9px] font-mono leading-none whitespace-nowrap pointer-events-none"
|
||||
style={{
|
||||
color: `${color}cc`,
|
||||
[isInput ? "paddingLeft" : "paddingRight"]: 2,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
||||
import { type NodeProps } from "@xyflow/react";
|
||||
import { TimerIcon } from "lucide-react";
|
||||
import { NodeHandles } from "./NodeHandles";
|
||||
|
||||
export function WaitNode({ data }: NodeProps) {
|
||||
const d = data as { ticks?: number; input?: unknown };
|
||||
|
||||
return (
|
||||
<div className="bg-yellow-950/80 border border-yellow-500/50 rounded-lg shadow-md min-w-48">
|
||||
<Handle type="target" position={Position.Left} className="!bg-yellow-400" />
|
||||
<NodeHandles nodeType="wait" />
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-yellow-500/30">
|
||||
<TimerIcon className="size-4 text-yellow-400 shrink-0" />
|
||||
<span className="text-sm font-medium text-yellow-200 flex-1 truncate">
|
||||
|
|
@ -16,9 +17,10 @@ export function WaitNode({ data }: NodeProps) {
|
|||
</span>
|
||||
</div>
|
||||
<div className="px-3 py-2 text-xs text-yellow-300/80 line-clamp-2">
|
||||
{d.input !== undefined ? `Input: ${String(d.input).substring(0, 50)}${String(d.input).length > 50 ? "..." : ""}` : "No input"}
|
||||
{d.input !== undefined
|
||||
? `Input: ${String(d.input).substring(0, 50)}${String(d.input).length > 50 ? "..." : ""}`
|
||||
: "No input"}
|
||||
</div>
|
||||
<Handle type="source" position={Position.Right} className="!bg-yellow-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
143
src/components/admin/narrative-flow/types.ts
Normal file
143
src/components/admin/narrative-flow/types.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
export type SocketType = "flow" | "number" | "string" | "boolean" | "any";
|
||||
|
||||
export interface SocketDef {
|
||||
id: string;
|
||||
type: SocketType;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const SOCKET_COLORS: Record<SocketType, string> = {
|
||||
flow: "#e4e4e7",
|
||||
number: "#a855f7",
|
||||
string: "#14b8a6",
|
||||
boolean: "#22c55e",
|
||||
any: "#6366f1",
|
||||
};
|
||||
|
||||
export interface NodeSocketConfig {
|
||||
inputs: SocketDef[];
|
||||
outputs: SocketDef[];
|
||||
}
|
||||
|
||||
export const NODE_SOCKETS: Record<string, NodeSocketConfig> = {
|
||||
narrativeBeat: {
|
||||
inputs: [
|
||||
{ id: "flow-in", type: "flow", label: "" },
|
||||
{ id: "text-var", type: "any", label: "text" },
|
||||
],
|
||||
outputs: [{ id: "flow-out", type: "flow", label: "" }],
|
||||
},
|
||||
choice: {
|
||||
inputs: [
|
||||
{ id: "flow-in", type: "flow", label: "" },
|
||||
{ id: "question-var", type: "string", label: "question" },
|
||||
],
|
||||
outputs: [{ id: "flow-out", type: "flow", label: "" }],
|
||||
},
|
||||
outcome: {
|
||||
inputs: [
|
||||
{ id: "flow-in", type: "flow", label: "" },
|
||||
{ id: "trigger", type: "boolean", label: "trigger" },
|
||||
],
|
||||
outputs: [],
|
||||
},
|
||||
wait: {
|
||||
inputs: [
|
||||
{ id: "flow-in", type: "flow", label: "" },
|
||||
{ id: "ticks", type: "number", label: "ticks" },
|
||||
],
|
||||
outputs: [{ id: "flow-out", type: "flow", label: "" }],
|
||||
},
|
||||
number: {
|
||||
inputs: [],
|
||||
outputs: [{ id: "value", type: "number", label: "out" }],
|
||||
},
|
||||
string: {
|
||||
inputs: [],
|
||||
outputs: [{ id: "value", type: "string", label: "out" }],
|
||||
},
|
||||
random: {
|
||||
inputs: [
|
||||
{ id: "min", type: "number", label: "min" },
|
||||
{ id: "max", type: "number", label: "max" },
|
||||
],
|
||||
outputs: [{ id: "value", type: "number", label: "out" }],
|
||||
},
|
||||
boolean: {
|
||||
inputs: [],
|
||||
outputs: [{ id: "value", type: "boolean", label: "out" }],
|
||||
},
|
||||
date: {
|
||||
inputs: [],
|
||||
outputs: [{ id: "value", type: "string", label: "out" }],
|
||||
},
|
||||
gameData: {
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ id: "number-out", type: "number", label: "num" },
|
||||
{ id: "string-out", type: "string", label: "str" },
|
||||
{ id: "boolean-out", type: "boolean", label: "bool" },
|
||||
],
|
||||
},
|
||||
flag: {
|
||||
inputs: [
|
||||
{ id: "flow-in", type: "flow", label: "" },
|
||||
{ id: "set-value", type: "boolean", label: "set" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "flow-out", type: "flow", label: "" },
|
||||
{ id: "value", type: "boolean", label: "out" },
|
||||
],
|
||||
},
|
||||
compare: {
|
||||
inputs: [
|
||||
{ id: "a", type: "number", label: "A" },
|
||||
{ id: "b", type: "number", label: "B" },
|
||||
],
|
||||
outputs: [{ id: "result", type: "boolean", label: "out" }],
|
||||
},
|
||||
math: {
|
||||
inputs: [
|
||||
{ id: "a", type: "number", label: "A" },
|
||||
{ id: "b", type: "number", label: "B" },
|
||||
],
|
||||
outputs: [{ id: "result", type: "number", label: "out" }],
|
||||
},
|
||||
format: {
|
||||
inputs: [
|
||||
{ id: "template", type: "string", label: "tmpl" },
|
||||
{ id: "arg-0", type: "any", label: "{0}" },
|
||||
{ id: "arg-1", type: "any", label: "{1}" },
|
||||
],
|
||||
outputs: [{ id: "result", type: "string", label: "out" }],
|
||||
},
|
||||
logic: {
|
||||
inputs: [
|
||||
{ id: "a", type: "boolean", label: "A" },
|
||||
{ id: "b", type: "boolean", label: "B" },
|
||||
],
|
||||
outputs: [{ id: "result", type: "boolean", label: "out" }],
|
||||
},
|
||||
not: {
|
||||
inputs: [{ id: "value", type: "boolean", label: "in" }],
|
||||
outputs: [{ id: "result", type: "boolean", label: "out" }],
|
||||
},
|
||||
branch: {
|
||||
inputs: [
|
||||
{ id: "flow-in", type: "flow", label: "" },
|
||||
{ id: "condition", type: "boolean", label: "cond" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "flow-true", type: "flow", label: "true" },
|
||||
{ id: "flow-false", type: "flow", label: "false" },
|
||||
],
|
||||
},
|
||||
sequence: {
|
||||
inputs: [{ id: "flow-in", type: "flow", label: "" }],
|
||||
outputs: [
|
||||
{ id: "flow-1", type: "flow", label: "1" },
|
||||
{ id: "flow-2", type: "flow", label: "2" },
|
||||
{ id: "flow-3", type: "flow", label: "3" },
|
||||
],
|
||||
},
|
||||
};
|
||||
Loading…
Reference in a new issue