From e48ebeae87d992362535143d5c575f3c0ba1784a Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 14 Apr 2026 19:55:15 +0200 Subject: [PATCH] Add ProjectDialoguesPanel component for managing project dialogues --- src/app/ProjectDialoguesPanel.tsx | 246 ++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 src/app/ProjectDialoguesPanel.tsx diff --git a/src/app/ProjectDialoguesPanel.tsx b/src/app/ProjectDialoguesPanel.tsx new file mode 100644 index 00000000..86b2a068 --- /dev/null +++ b/src/app/ProjectDialoguesPanel.tsx @@ -0,0 +1,246 @@ +import { useEffect, useState, type KeyboardEvent as ReactKeyboardEvent } from "react"; + +import { + getProjectDialogues, + type ProjectDialogue, + type ProjectDialogueLibrary +} from "../dialogues/project-dialogues"; + +interface ProjectDialoguesPanelProps { + dialogues: ProjectDialogueLibrary; + selectedDialogueId: string | null; + onSelectDialogue(dialogueId: string | null): void; + onAddDialogue(): void; + onDeleteDialogue(dialogueId: string): void; + onSetDialogueTitle(dialogueId: string, title: string): void; + onAddDialogueLine(dialogueId: string): void; + onDeleteDialogueLine(dialogueId: string, lineId: string): void; + onSetDialogueLineSpeaker( + dialogueId: string, + lineId: string, + speakerName: string | null + ): void; + onSetDialogueLineText(dialogueId: string, lineId: string, text: string): void; +} + +function commitOnEnter( + event: ReactKeyboardEvent, + commit: () => void +) { + if (event.key !== "Enter") { + return; + } + + event.currentTarget.blur(); + commit(); +} + +export function ProjectDialoguesPanel({ + dialogues, + selectedDialogueId, + onSelectDialogue, + onAddDialogue, + onDeleteDialogue, + onSetDialogueTitle, + onAddDialogueLine, + onDeleteDialogueLine, + onSetDialogueLineSpeaker, + onSetDialogueLineText +}: ProjectDialoguesPanelProps) { + const dialogueList = getProjectDialogues(dialogues); + const selectedDialogue = + selectedDialogueId === null ? null : dialogues.dialogues[selectedDialogueId] ?? null; + const [titleDraft, setTitleDraft] = useState(selectedDialogue?.title ?? ""); + const [lineDrafts, setLineDrafts] = useState< + Record + >({}); + + useEffect(() => { + setTitleDraft(selectedDialogue?.title ?? ""); + setLineDrafts( + selectedDialogue === null + ? {} + : Object.fromEntries( + selectedDialogue.lines.map((line) => [ + line.id, + { + speakerName: line.speakerName ?? "", + text: line.text + } + ]) + ) + ); + }, [selectedDialogueId, selectedDialogue]); + + const commitTitle = () => { + if (selectedDialogue === null) { + return; + } + + onSetDialogueTitle(selectedDialogue.id, titleDraft); + }; + + const getLineDraft = (dialogue: ProjectDialogue, lineId: string) => + lineDrafts[lineId] ?? + (() => { + const line = dialogue.lines.find((candidate) => candidate.id === lineId); + return { + speakerName: line?.speakerName ?? "", + text: line?.text ?? "" + }; + })(); + + return ( +
+
Dialogues
+ {dialogueList.length === 0 ? ( +
+ No project dialogues authored yet. +
+ ) : ( +
+ {dialogueList.map((dialogue) => ( +
+
+ + +
+
+ ))} +
+ )} + +
+ +
+ + {selectedDialogue === null ? ( +
+ Select a dialogue to edit its title and lines. +
+ ) : ( +
+ + +
Lines
+
+ {selectedDialogue.lines.map((line, index) => { + const draft = getLineDraft(selectedDialogue, line.id); + + return ( +
+
+
{`Line ${index + 1}`}
+ +
+ +