{(Object.keys(DEVICE_INFO) as DeviceType[]).map((type) => {
const info = DEVICE_INFO[type];
@@ -268,9 +250,7 @@ function DeviceEditor({
min={0}
max={100}
value={device.position}
- onChange={(e) =>
- updateDevice(device.id, { position: Number(e.target.value) })
- }
+ onChange={(e) => updateDevice(device.id, { position: Number(e.target.value) })}
className="flex-1 h-1 accent-zinc-400"
/>
@@ -284,9 +264,7 @@ function DeviceEditor({
min={0}
max={100}
value={device.positionY}
- onChange={(e) =>
- updateDevice(device.id, { positionY: Number(e.target.value) })
- }
+ onChange={(e) => updateDevice(device.id, { positionY: Number(e.target.value) })}
className="flex-1 h-1 accent-zinc-400"
/>
@@ -330,18 +308,9 @@ export default function RibbonDesigner() {
}
}, [design, setValue]);
- const setStripes = useCallback(
- (stripes: Stripe[]) => setDesign((d) => ({ ...d, stripes })),
- [],
- );
- const setDevices = useCallback(
- (devices: Device[]) => setDesign((d) => ({ ...d, devices })),
- [],
- );
- const toggleMirror = useCallback(
- () => setDesign((d) => ({ ...d, mirrored: !d.mirrored })),
- [],
- );
+ const setStripes = useCallback((stripes: Stripe[]) => setDesign((d) => ({ ...d, stripes })), []);
+ const setDevices = useCallback((devices: Device[]) => setDesign((d) => ({ ...d, devices })), []);
+ const toggleMirror = useCallback(() => setDesign((d) => ({ ...d, mirrored: !d.mirrored })), []);
return (
diff --git a/src/components/admin/narrative-flow/BooleanNode.tsx b/src/components/admin/narrative-flow/BooleanNode.tsx
index aaebb6b..ed72273 100644
--- a/src/components/admin/narrative-flow/BooleanNode.tsx
+++ b/src/components/admin/narrative-flow/BooleanNode.tsx
@@ -8,7 +8,10 @@ export function BooleanNode({ data }: NodeProps) {
const d = data as { value?: boolean; description?: string };
return (
-
+
diff --git a/src/components/admin/narrative-flow/BranchNode.tsx b/src/components/admin/narrative-flow/BranchNode.tsx
index 608e59b..af74bc4 100644
--- a/src/components/admin/narrative-flow/BranchNode.tsx
+++ b/src/components/admin/narrative-flow/BranchNode.tsx
@@ -6,7 +6,10 @@ import { NodeShell } from "./NodeShell";
export function BranchNode(_props: NodeProps) {
return (
-
+
Branch
diff --git a/src/components/admin/narrative-flow/ChoiceNode.tsx b/src/components/admin/narrative-flow/ChoiceNode.tsx
index c9d796c..23c6407 100644
--- a/src/components/admin/narrative-flow/ChoiceNode.tsx
+++ b/src/components/admin/narrative-flow/ChoiceNode.tsx
@@ -9,7 +9,10 @@ export function ChoiceNode({ data }: NodeProps) {
const options = d.options ?? [];
return (
-
+
diff --git a/src/components/admin/narrative-flow/CompareNode.tsx b/src/components/admin/narrative-flow/CompareNode.tsx
index 44d6a17..cbd90ca 100644
--- a/src/components/admin/narrative-flow/CompareNode.tsx
+++ b/src/components/admin/narrative-flow/CompareNode.tsx
@@ -9,7 +9,10 @@ export function CompareNode({ data }: NodeProps) {
const op = d.operator ?? ">";
return (
-
+
Compare
diff --git a/src/components/admin/narrative-flow/DateNode.tsx b/src/components/admin/narrative-flow/DateNode.tsx
index e47ee83..ed9bcf4 100644
--- a/src/components/admin/narrative-flow/DateNode.tsx
+++ b/src/components/admin/narrative-flow/DateNode.tsx
@@ -8,7 +8,10 @@ export function DateNode({ data }: NodeProps) {
const d = data as { value?: string; description?: string };
return (
-
+
diff --git a/src/components/admin/narrative-flow/FlagNode.tsx b/src/components/admin/narrative-flow/FlagNode.tsx
index ee9b5f5..6fa51e5 100644
--- a/src/components/admin/narrative-flow/FlagNode.tsx
+++ b/src/components/admin/narrative-flow/FlagNode.tsx
@@ -8,12 +8,13 @@ export function FlagNode({ data }: NodeProps) {
const d = data as { flagName?: string };
return (
-
+
-
- Flag
-
+ Flag
{d.flagName || "Unnamed flag"}
diff --git a/src/components/admin/narrative-flow/FormatNode.tsx b/src/components/admin/narrative-flow/FormatNode.tsx
index c953f16..b81a011 100644
--- a/src/components/admin/narrative-flow/FormatNode.tsx
+++ b/src/components/admin/narrative-flow/FormatNode.tsx
@@ -25,7 +25,11 @@ export function FormatNode({ data }: NodeProps) {
];
return (
-
+
Format
diff --git a/src/components/admin/narrative-flow/GameDataNode.tsx b/src/components/admin/narrative-flow/GameDataNode.tsx
index b6ce5d2..d88605f 100644
--- a/src/components/admin/narrative-flow/GameDataNode.tsx
+++ b/src/components/admin/narrative-flow/GameDataNode.tsx
@@ -20,16 +20,15 @@ export function GameDataNode({ data }: NodeProps) {
const fieldLabel = GAME_DATA_FIELDS.find((f) => f.value === d.field)?.label ?? "Select field";
return (
-
+
-
- Game Data
-
-
-
- {fieldLabel}
+ Game Data
+ {fieldLabel}
);
}
diff --git a/src/components/admin/narrative-flow/LogicNode.tsx b/src/components/admin/narrative-flow/LogicNode.tsx
index f8f2d79..acbc097 100644
--- a/src/components/admin/narrative-flow/LogicNode.tsx
+++ b/src/components/admin/narrative-flow/LogicNode.tsx
@@ -9,7 +9,10 @@ export function LogicNode({ data }: NodeProps) {
const op = d.operator ?? "AND";
return (
-
+
Logic
diff --git a/src/components/admin/narrative-flow/MathNode.tsx b/src/components/admin/narrative-flow/MathNode.tsx
index 658a204..f1cee1f 100644
--- a/src/components/admin/narrative-flow/MathNode.tsx
+++ b/src/components/admin/narrative-flow/MathNode.tsx
@@ -9,7 +9,10 @@ export function MathNode({ data }: NodeProps) {
const op = d.operator ?? "+";
return (
-
+
Math
diff --git a/src/components/admin/narrative-flow/NarrativeBeatNode.tsx b/src/components/admin/narrative-flow/NarrativeBeatNode.tsx
index 4102a8a..e87b6a6 100644
--- a/src/components/admin/narrative-flow/NarrativeBeatNode.tsx
+++ b/src/components/admin/narrative-flow/NarrativeBeatNode.tsx
@@ -8,7 +8,10 @@ export function NarrativeBeatNode({ data }: NodeProps) {
const d = data as { title?: string; text?: string };
return (
-
+
diff --git a/src/components/admin/narrative-flow/NarrativeFlowEditor.tsx b/src/components/admin/narrative-flow/NarrativeFlowEditor.tsx
index e328ed8..bf703d1 100644
--- a/src/components/admin/narrative-flow/NarrativeFlowEditor.tsx
+++ b/src/components/admin/narrative-flow/NarrativeFlowEditor.tsx
@@ -2,16 +2,16 @@
import { useCallback, useEffect, useRef, useState } from "react";
import {
- ReactFlow,
- Background,
- Controls,
- MiniMap,
- useNodesState,
- useEdgesState,
addEdge,
- type Node,
- type Edge,
+ Background,
type Connection,
+ Controls,
+ type Edge,
+ MiniMap,
+ type Node,
+ ReactFlow,
+ useEdgesState,
+ useNodesState,
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";
@@ -38,7 +38,7 @@ import { NotNode } from "./NotNode";
import { BranchNode } from "./BranchNode";
import { SequenceNode } from "./SequenceNode";
import { NodeEditDialog } from "./NodeEditDialog";
-import { NODE_SOCKETS, type SocketType } from "./types";
+import { NODE_SOCKETS } from "./types";
const nodeTypes = {
narrativeBeat: NarrativeBeatNode,
@@ -106,7 +106,12 @@ export function NarrativeFlowEditor() {
const isValidConnection = useCallback(
(connection: Edge | Connection) => {
- if (!connection.source || !connection.target || !connection.sourceHandle || !connection.targetHandle) {
+ if (
+ !connection.source ||
+ !connection.target ||
+ !connection.sourceHandle ||
+ !connection.targetHandle
+ ) {
return false;
}
const sourceNode = nodes.find((n) => n.id === connection.source);
@@ -121,8 +126,16 @@ export function NarrativeFlowEditor() {
const targetSocket = targetSockets.inputs.find((s) => s.id === connection.targetHandle);
// Dynamic handles (arg-*, option-*) are treated as "any"
- const sourceType = sourceSocket?.type ?? (connection.sourceHandle.startsWith("arg-") || connection.sourceHandle.startsWith("option-") ? "any" : null);
- const targetType = targetSocket?.type ?? (connection.targetHandle.startsWith("arg-") || connection.targetHandle.startsWith("option-") ? "any" : null);
+ const sourceType =
+ sourceSocket?.type ??
+ (connection.sourceHandle.startsWith("arg-") || connection.sourceHandle.startsWith("option-")
+ ? "any"
+ : null);
+ const targetType =
+ targetSocket?.type ??
+ (connection.targetHandle.startsWith("arg-") || connection.targetHandle.startsWith("option-")
+ ? "any"
+ : null);
if (sourceType === null || targetType === null) return false;
if (sourceType === "any" || targetType === "any") return true;
@@ -135,21 +148,15 @@ export function NarrativeFlowEditor() {
setEditingNodeId(node.id);
}, []);
- const onNodeContextMenu = useCallback(
- (e: React.MouseEvent, node: Node) => {
- e.preventDefault();
- setContextMenu({ x: e.clientX, y: e.clientY, nodeId: node.id });
- },
- [],
- );
+ const onNodeContextMenu = useCallback((e: React.MouseEvent, node: Node) => {
+ e.preventDefault();
+ setContextMenu({ x: e.clientX, y: e.clientY, nodeId: node.id });
+ }, []);
- const onEdgeContextMenu = useCallback(
- (e: React.MouseEvent, edge: Edge) => {
- e.preventDefault();
- setContextMenu({ x: e.clientX, y: e.clientY, edgeId: edge.id });
- },
- [],
- );
+ const onEdgeContextMenu = useCallback((e: React.MouseEvent, edge: Edge) => {
+ e.preventDefault();
+ setContextMenu({ x: e.clientX, y: e.clientY, edgeId: edge.id });
+ }, []);
const onPaneContextMenu = useCallback((e: MouseEvent | React.MouseEvent) => {
e.preventDefault();
@@ -178,7 +185,7 @@ export function NarrativeFlowEditor() {
[setNodes],
);
- const selectedNode = editingNodeId ? nodes.find((n) => n.id === editingNodeId) ?? null : null;
+ const selectedNode = editingNodeId ? (nodes.find((n) => n.id === editingNodeId) ?? null) : null;
const addNode = useCallback(
(
@@ -240,32 +247,129 @@ export function NarrativeFlowEditor() {
const toolbarButtons = (
- Story
-
-
-
-
+
+ Story
+
+
+
+
+
- Data
-
-
-
-
-
-
-
+
+ Data
+
+
+
+
+
+
+
+
- Logic
-
-
-
-
-
-
-
+
+ Logic
+
+
+
+
+
+
+
+
- {nodes.length} node{nodes.length !== 1 ? "s" : ""}, {edges.length} edge{edges.length !== 1 ? "s" : ""}
+ {nodes.length} node{nodes.length !== 1 ? "s" : ""}, {edges.length} edge
+ {edges.length !== 1 ? "s" : ""}
);
diff --git a/src/components/admin/narrative-flow/NodeEditDialog.tsx b/src/components/admin/narrative-flow/NodeEditDialog.tsx
index de8fadb..f2d1ed3 100644
--- a/src/components/admin/narrative-flow/NodeEditDialog.tsx
+++ b/src/components/admin/narrative-flow/NodeEditDialog.tsx
@@ -1,12 +1,12 @@
"use client";
-import { useEffect, useState, type FC } from "react";
+import { type FC, useEffect, useState } from "react";
import {
Dialog,
DialogContent,
+ DialogFooter,
DialogHeader,
DialogTitle,
- DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -22,12 +22,7 @@ interface NodeEditDialogProps {
onSave: (nodeId: string, data: Record) => void;
}
-export const NodeEditDialog: FC = ({
- node,
- open,
- onOpenChange,
- onSave,
-}) => {
+export const NodeEditDialog: FC = ({ node, open, onOpenChange, onSave }) => {
const [formData, setFormData] = useState>({});
useEffect(() => {
@@ -326,10 +321,7 @@ export const NodeEditDialog: FC = ({
variant="outline"
size="sm"
onClick={() => {
- const updated = [
- ...options,
- { id: crypto.randomUUID(), label: "" },
- ];
+ const updated = [...options, { id: crypto.randomUUID(), label: "" }];
updateField("options", updated);
}}
className="dark:border-zinc-700 dark:text-zinc-300"
@@ -584,11 +576,7 @@ export const NodeEditDialog: FC = ({
case "not":
case "branch":
case "sequence": {
- return (
-
- No configurable properties.
-
- );
+ return No configurable properties.
;
}
default:
@@ -620,7 +608,10 @@ export const NodeEditDialog: FC = ({
return (