Remove ProjectDialoguesPanel component and related files

This commit is contained in:
2026-04-15 09:53:14 +02:00
parent 40541f60f7
commit da4e02a6f2
3 changed files with 0 additions and 366 deletions

View File

@@ -1,253 +0,0 @@
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<HTMLInputElement>,
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<string, { speakerName: string; text: string }>
>({});
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 (
<div className="form-section">
<div className="label">Dialogues</div>
{dialogueList.length === 0 ? (
<div className="outliner-empty">
No project dialogues authored yet.
</div>
) : (
<div className="outliner-list">
{dialogueList.map((dialogue) => (
<div
key={dialogue.id}
className={`outliner-item outliner-item--compact ${
selectedDialogue?.id === dialogue.id
? "outliner-item--selected"
: ""
}`.trim()}
>
<div className="outliner-item__row">
<button
className="outliner-item__select"
type="button"
onClick={() => onSelectDialogue(dialogue.id)}
>
<span className="outliner-item__title">{dialogue.title}</span>
<span className="outliner-item__meta">
{dialogue.lines.length} line
{dialogue.lines.length === 1 ? "" : "s"}
</span>
</button>
<button
className="outliner-item__delete"
type="button"
aria-label={`Delete ${dialogue.title}`}
onClick={() => onDeleteDialogue(dialogue.id)}
>
x
</button>
</div>
</div>
))}
</div>
)}
<div className="inline-actions">
<button className="toolbar__button" type="button" onClick={onAddDialogue}>
Add Dialogue
</button>
</div>
{selectedDialogue === null ? (
<div className="outliner-empty">
Select a dialogue to edit its title and lines.
</div>
) : (
<div className="form-section">
<label className="form-field">
<span className="label">Title</span>
<input
className="text-input"
type="text"
value={titleDraft}
onChange={(event) => {
const nextValue = event.currentTarget.value;
setTitleDraft(nextValue);
}}
onBlur={commitTitle}
onKeyDown={(event) => commitOnEnter(event, commitTitle)}
/>
</label>
<div className="label">Lines</div>
<div className="outliner-list">
{selectedDialogue.lines.map((line, index) => {
const draft = getLineDraft(selectedDialogue, line.id);
return (
<div key={line.id} className="outliner-item">
<div className="outliner-item__row">
<div className="outliner-item__meta">{`Line ${index + 1}`}</div>
<button
className="outliner-item__delete"
type="button"
aria-label={`Delete line ${index + 1}`}
onClick={() => onDeleteDialogueLine(selectedDialogue.id, line.id)}
>
x
</button>
</div>
<label className="form-field">
<span className="label">Speaker</span>
<input
className="text-input"
type="text"
placeholder="Optional"
value={draft.speakerName}
onChange={(event) => {
const nextSpeakerName = event.currentTarget.value;
setLineDrafts((current) => ({
...current,
[line.id]: {
...draft,
speakerName: nextSpeakerName
}
}));
}}
onBlur={() =>
onSetDialogueLineSpeaker(
selectedDialogue.id,
line.id,
draft.speakerName
)
}
/>
</label>
<label className="form-field">
<span className="label">Text</span>
<textarea
className="text-input"
rows={3}
value={draft.text}
onChange={(event) => {
const nextText = event.currentTarget.value;
setLineDrafts((current) => ({
...current,
[line.id]: {
...draft,
text: nextText
}
}));
}}
onBlur={() =>
onSetDialogueLineText(
selectedDialogue.id,
line.id,
draft.text
)
}
/>
</label>
</div>
);
})}
</div>
<div className="inline-actions">
<button
className="toolbar__button"
type="button"
onClick={() => onAddDialogueLine(selectedDialogue.id)}
>
Add Line
</button>
</div>
</div>
)}
</div>
);
}

View File

@@ -1,49 +0,0 @@
import { createOpaqueId } from "../core/ids";
import {
cloneProjectDialogueLibrary,
type ProjectDialogueLibrary
} from "../dialogues/project-dialogues";
import type { EditorCommand } from "./command";
interface SetProjectDialoguesCommandOptions {
label: string;
dialogues: ProjectDialogueLibrary;
}
export function createSetProjectDialoguesCommand(
options: SetProjectDialoguesCommandOptions
): EditorCommand {
const nextDialogues = cloneProjectDialogueLibrary(options.dialogues);
let previousDialogues: ProjectDialogueLibrary | null = null;
return {
id: createOpaqueId("command"),
label: options.label,
execute(context) {
const currentProjectDocument = context.getProjectDocument();
if (previousDialogues === null) {
previousDialogues = cloneProjectDialogueLibrary(
currentProjectDocument.dialogues
);
}
context.setProjectDocument({
...currentProjectDocument,
dialogues: cloneProjectDialogueLibrary(nextDialogues)
});
},
undo(context) {
if (previousDialogues === null) {
return;
}
const currentProjectDocument = context.getProjectDocument();
context.setProjectDocument({
...currentProjectDocument,
dialogues: cloneProjectDialogueLibrary(previousDialogues)
});
}
};
}

View File

@@ -1,64 +0,0 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { ProjectDialoguesPanel } from "../../src/app/ProjectDialoguesPanel";
import { createProjectDialogue } from "../../src/dialogues/project-dialogues";
describe("ProjectDialoguesPanel", () => {
it("lets the user type into title, speaker, and text fields without crashing", () => {
const dialogue = createProjectDialogue({
id: "dialogue-market",
title: "Market Greeting",
lines: [
{
id: "dialogue-line-1",
speakerName: "Merchant",
text: "Fresh fruit."
}
]
});
render(
<ProjectDialoguesPanel
dialogues={{
dialogues: {
[dialogue.id]: dialogue
}
}}
selectedDialogueId={dialogue.id}
onSelectDialogue={() => {}}
onAddDialogue={() => {}}
onDeleteDialogue={() => {}}
onSetDialogueTitle={vi.fn()}
onAddDialogueLine={() => {}}
onDeleteDialogueLine={() => {}}
onSetDialogueLineSpeaker={vi.fn()}
onSetDialogueLineText={vi.fn()}
/>
);
const titleInput = screen.getByDisplayValue("Market Greeting");
const speakerInput = screen.getByDisplayValue("Merchant");
const textInput = screen.getByDisplayValue("Fresh fruit.");
fireEvent.change(titleInput, {
target: {
value: "Morning Market"
}
});
fireEvent.change(speakerInput, {
target: {
value: "Vendor"
}
});
fireEvent.change(textInput, {
target: {
value: "Fresh fruit and bread."
}
});
expect(titleInput).toHaveValue("Morning Market");
expect(speakerInput).toHaveValue("Vendor");
expect(textInput).toHaveValue("Fresh fruit and bread.");
});
});