1
0
Fork 0

refactor: switch all node types to left/right handles for L-to-R layout

- Updated handle positions:
  - Target: Position.Left (incoming edges from left)
  - Source: Position.Right (outgoing edges to right)
- Updated MiniMap nodeColor to color-code all 9 node types
- Maintains consistency across narrative, primitive, and control nodes
This commit is contained in:
Jason Fraley 2026-07-28 17:49:48 -04:00
parent 4a573f7cfc
commit cfba38ca24
10 changed files with 42 additions and 25 deletions

View file

@ -8,7 +8,7 @@ export function BooleanNode({ data }: NodeProps) {
return ( return (
<div className="bg-emerald-950/80 border border-emerald-500/50 rounded-lg shadow-md min-w-28"> <div className="bg-emerald-950/80 border border-emerald-500/50 rounded-lg shadow-md min-w-28">
<Handle type="target" position={Position.Top} className="!bg-emerald-400" /> <Handle type="target" position={Position.Left} className="!bg-emerald-400" />
<div className="flex items-center gap-2 px-3 py-2 border-b border-emerald-500/30"> <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" /> <CheckCircleIcon className="size-4 text-emerald-400 shrink-0" />
<span className="text-sm font-medium text-emerald-200 flex-1 truncate"> <span className="text-sm font-medium text-emerald-200 flex-1 truncate">
@ -18,7 +18,7 @@ export function BooleanNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-emerald-300/80 line-clamp-2"> <div className="px-3 py-2 text-xs text-emerald-300/80 line-clamp-2">
{d.description || "Boolean flag"} {d.description || "Boolean flag"}
</div> </div>
<Handle type="source" position={Position.Bottom} className="!bg-emerald-400" /> <Handle type="source" position={Position.Right} className="!bg-emerald-400" />
</div> </div>
); );
} }

View file

@ -10,13 +10,13 @@ export function ChoiceNode({ data }: NodeProps) {
return ( return (
<div className="bg-amber-950/80 border border-amber-500/50 rounded-lg shadow-md min-w-56"> <div className="bg-amber-950/80 border border-amber-500/50 rounded-lg shadow-md min-w-56">
<Handle type="target" position={Position.Top} className="!bg-amber-400" /> <Handle type="target" position={Position.Left} className="!bg-amber-400" />
<div className="flex items-center gap-2 px-3 py-2 border-b border-amber-500/30"> <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" /> <WaypointsIcon className="size-4 text-amber-400 shrink-0" />
<span className="text-sm font-medium text-amber-200 flex-1 truncate"> <span className="text-sm font-medium text-amber-200 flex-1 truncate">
{d.question || "Untitled Choice"} {d.question || "Untitled Choice"}
</span> </span>
<PencilIcon className="size-3 text-amber-500/40 shrink-0" /> <PencilIcon className="size-3 text-amber-500/60 shrink-0" />
</div> </div>
<div className="px-3 py-2 space-y-1"> <div className="px-3 py-2 space-y-1">
{options.length === 0 ? ( {options.length === 0 ? (

View file

@ -8,7 +8,7 @@ export function DateNode({ data }: NodeProps) {
return ( return (
<div className="bg-cyan-950/80 border border-cyan-500/50 rounded-lg shadow-md min-w-32"> <div className="bg-cyan-950/80 border border-cyan-500/50 rounded-lg shadow-md min-w-32">
<Handle type="target" position={Position.Top} className="!bg-cyan-400" /> <Handle type="target" position={Position.Left} className="!bg-cyan-400" />
<div className="flex items-center gap-2 px-3 py-2 border-b border-cyan-500/30"> <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" /> <CalendarIcon className="size-4 text-cyan-400 shrink-0" />
<span className="text-sm font-medium text-cyan-200 flex-1 truncate"> <span className="text-sm font-medium text-cyan-200 flex-1 truncate">
@ -18,7 +18,7 @@ export function DateNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-cyan-300/80 line-clamp-2"> <div className="px-3 py-2 text-xs text-cyan-300/80 line-clamp-2">
{d.description || "Date value"} {d.description || "Date value"}
</div> </div>
<Handle type="source" position={Position.Bottom} className="!bg-cyan-400" /> <Handle type="source" position={Position.Right} className="!bg-cyan-400" />
</div> </div>
); );
} }

View file

@ -8,7 +8,7 @@ export function NarrativeBeatNode({ data }: NodeProps) {
return ( return (
<div className="bg-blue-950/80 border border-blue-500/50 rounded-lg shadow-md min-w-56"> <div className="bg-blue-950/80 border border-blue-500/50 rounded-lg shadow-md min-w-56">
<Handle type="target" position={Position.Top} className="!bg-blue-400" /> <Handle type="target" position={Position.Left} className="!bg-blue-400" />
<div className="flex items-center gap-2 px-3 py-2 border-b border-blue-500/30"> <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" /> <BookOpenTextIcon className="size-4 text-blue-400 shrink-0" />
<span className="text-sm font-medium text-blue-200 flex-1 truncate"> <span className="text-sm font-medium text-blue-200 flex-1 truncate">
@ -19,7 +19,7 @@ export function NarrativeBeatNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-blue-300/80 line-clamp-3"> <div className="px-3 py-2 text-xs text-blue-300/80 line-clamp-3">
{d.text || "No description."} {d.text || "No description."}
</div> </div>
<Handle type="source" position={Position.Bottom} className="!bg-blue-400" /> <Handle type="source" position={Position.Right} className="!bg-blue-400" />
</div> </div>
); );
} }

View file

@ -267,13 +267,30 @@ export function NarrativeFlowEditor() {
<MiniMap <MiniMap
className="!border-zinc-700" className="!border-zinc-700"
style={{ background: "#18181b" }} style={{ background: "#18181b" }}
nodeColor={(n) => nodeColor={(n) => {
n.type === "choice" switch (n.type) {
? "#d97706" case "choice":
: n.type === "outcome" return "#d97706"; // amber
? "#10b981" case "outcome":
: "#3b82f6" return "#10b981"; // emerald
} case "narrativeBeat":
return "#3b82f6"; // blue
case "wait":
return "#facc15"; // yellow
case "number":
return "#a855f7"; // purple
case "string":
return "#14b8a6"; // teal
case "random":
return "#ec4899"; // pink
case "boolean":
return "#34d399"; // light green
case "date":
return "#06b6d4"; // cyan
default:
return "#6366f1"; // indigo
}
}}
/> />
</ReactFlow> </ReactFlow>

View file

@ -8,7 +8,7 @@ export function NumberNode({ data }: NodeProps) {
return ( return (
<div className="bg-purple-950/80 border border-purple-500/50 rounded-lg shadow-md min-w-32"> <div className="bg-purple-950/80 border border-purple-500/50 rounded-lg shadow-md min-w-32">
<Handle type="target" position={Position.Top} className="!bg-purple-400" /> <Handle type="target" position={Position.Left} className="!bg-purple-400" />
<div className="flex items-center gap-2 px-3 py-2 border-b border-purple-500/30"> <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" /> <HashIcon className="size-4 text-purple-400 shrink-0" />
<span className="text-sm font-medium text-purple-200 flex-1 truncate"> <span className="text-sm font-medium text-purple-200 flex-1 truncate">
@ -18,7 +18,7 @@ export function NumberNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-purple-300/80 line-clamp-2"> <div className="px-3 py-2 text-xs text-purple-300/80 line-clamp-2">
{d.description || "Numeric value"} {d.description || "Numeric value"}
</div> </div>
<Handle type="source" position={Position.Bottom} className="!bg-purple-400" /> <Handle type="source" position={Position.Right} className="!bg-purple-400" />
</div> </div>
); );
} }

View file

@ -14,13 +14,13 @@ export function OutcomeNode({ data }: NodeProps) {
return ( return (
<div className="bg-emerald-950/80 border border-emerald-500/50 rounded-lg shadow-md min-w-56"> <div className="bg-emerald-950/80 border border-emerald-500/50 rounded-lg shadow-md min-w-56">
<Handle type="target" position={Position.Top} className="!bg-emerald-400" /> <Handle type="target" position={Position.Left} className="!bg-emerald-400" />
<div className="flex items-center gap-2 px-3 py-2 border-b border-emerald-500/30"> <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" /> <FlagIcon className="size-4 text-emerald-400 shrink-0" />
<span className="text-sm font-medium text-emerald-200 flex-1 truncate"> <span className="text-sm font-medium text-emerald-200 flex-1 truncate">
{d.title || "Untitled Outcome"} {d.title || "Untitled Outcome"}
</span> </span>
<PencilIcon className="size-3 text-emerald-500/40 shrink-0" /> <PencilIcon className="size-3 text-emerald-500/60 shrink-0" />
</div> </div>
<div className="px-3 py-2 border-b border-emerald-500/20 text-xs text-emerald-300/80 line-clamp-2"> <div className="px-3 py-2 border-b border-emerald-500/20 text-xs text-emerald-300/80 line-clamp-2">
{d.text || "No description."} {d.text || "No description."}

View file

@ -8,7 +8,7 @@ export function RandomNode({ data }: NodeProps) {
return ( return (
<div className="bg-pink-950/80 border border-pink-500/50 rounded-lg shadow-md min-w-32"> <div className="bg-pink-950/80 border border-pink-500/50 rounded-lg shadow-md min-w-32">
<Handle type="target" position={Position.Top} className="!bg-pink-400" /> <Handle type="target" position={Position.Left} className="!bg-pink-400" />
<div className="flex items-center gap-2 px-3 py-2 border-b border-pink-500/30"> <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" /> <ShuffleIcon className="size-4 text-pink-400 shrink-0" />
<span className="text-sm font-medium text-pink-200 flex-1 truncate"> <span className="text-sm font-medium text-pink-200 flex-1 truncate">
@ -18,7 +18,7 @@ export function RandomNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-pink-300/80 line-clamp-2"> <div className="px-3 py-2 text-xs text-pink-300/80 line-clamp-2">
{d.description || "Random number generator"} {d.description || "Random number generator"}
</div> </div>
<Handle type="source" position={Position.Bottom} className="!bg-pink-400" /> <Handle type="source" position={Position.Right} className="!bg-pink-400" />
</div> </div>
); );
} }

View file

@ -8,7 +8,7 @@ export function StringNode({ data }: NodeProps) {
return ( return (
<div className="bg-teal-950/80 border border-teal-500/50 rounded-lg shadow-md min-w-32"> <div className="bg-teal-950/80 border border-teal-500/50 rounded-lg shadow-md min-w-32">
<Handle type="target" position={Position.Top} className="!bg-teal-400" /> <Handle type="target" position={Position.Left} className="!bg-teal-400" />
<div className="flex items-center gap-2 px-3 py-2 border-b border-teal-500/30"> <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" /> <TypeIcon className="size-4 text-teal-400 shrink-0" />
<span className="text-sm font-medium text-teal-200 flex-1 truncate"> <span className="text-sm font-medium text-teal-200 flex-1 truncate">
@ -18,7 +18,7 @@ export function StringNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-teal-300/80 line-clamp-2"> <div className="px-3 py-2 text-xs text-teal-300/80 line-clamp-2">
{d.description || "String value"} {d.description || "String value"}
</div> </div>
<Handle type="source" position={Position.Bottom} className="!bg-teal-400" /> <Handle type="source" position={Position.Right} className="!bg-teal-400" />
</div> </div>
); );
} }

View file

@ -8,7 +8,7 @@ export function WaitNode({ data }: NodeProps) {
return ( return (
<div className="bg-yellow-950/80 border border-yellow-500/50 rounded-lg shadow-md min-w-48"> <div className="bg-yellow-950/80 border border-yellow-500/50 rounded-lg shadow-md min-w-48">
<Handle type="target" position={Position.Top} className="!bg-yellow-400" /> <Handle type="target" position={Position.Left} className="!bg-yellow-400" />
<div className="flex items-center gap-2 px-3 py-2 border-b border-yellow-500/30"> <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" /> <TimerIcon className="size-4 text-yellow-400 shrink-0" />
<span className="text-sm font-medium text-yellow-200 flex-1 truncate"> <span className="text-sm font-medium text-yellow-200 flex-1 truncate">
@ -18,7 +18,7 @@ export function WaitNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-yellow-300/80 line-clamp-2"> <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> </div>
<Handle type="source" position={Position.Bottom} className="!bg-yellow-400" /> <Handle type="source" position={Position.Right} className="!bg-yellow-400" />
</div> </div>
); );
} }