Working on dialog editor, on dialogs, on phone

This commit is contained in:
Vladislav Khorev committed 2026-06-05 21:17:51 +03:00
1 parent 0e27521bea
commit 8f7797789d
63 files changed
+9502 -1459

No files matched your search

@@ -0,0 +1,110 @@
import { ChoiceNode, ChoiceOption } from '../../../types/dialogue';
import { useDialogueStore } from '../../../store/dialogueStore';
import { SpeakerField } from '../shared/SpeakerField';
import { useValidation } from '../../../hooks/useValidation';
import styles from '../RightPanel.module.css';
interface Props {
node: ChoiceNode;
dialogueId: string;
}
export function ChoiceInspector({ node, dialogueId }: Props) {
const { updateNode, deleteNode, file } = useDialogueStore();
const { issuesByNodeId } = useValidation();
const issues = issuesByNodeId[node.id] ?? [];
const dialogue = file?.dialogues.find(d => d.id === dialogueId);
const nodeIds = dialogue?.nodes.map(n => n.id) ?? [];
function updateChoice(idx: number, patch: Partial<ChoiceOption>) {
const choices = node.choices.map((c, i) => i === idx ? { ...c, ...patch } : c);
updateNode(dialogueId, node.id, { choices });
}
function addChoice() {
const newId = `choice_${Date.now()}`;
const choices: ChoiceOption[] = [...node.choices, { id: newId, kind: 'Optional', text: '', next: '' }];
updateNode(dialogueId, node.id, { choices });
}
function removeChoice(idx: number) {
const choices = node.choices.filter((_, i) => i !== idx);
updateNode(dialogueId, node.id, { choices });
}
return (
<div className={styles.inspector}>
<div className={styles.inspectorHeader}>
<span className={styles.nodeTypeBadge} style={{ background: '#df8e1d' }}>Choice</span>
<span className={styles.nodeIdLabel}>{node.id}</span>
<button className={styles.deleteNodeBtn} onClick={() => deleteNode(dialogueId, node.id)} title="Delete node">🗑</button>
</div>
{issues.length > 0 && (
<div className={styles.issueList}>
{issues.map((issue, i) => (
<div key={i} className={issue.severity === 'error' ? styles.issueError : styles.issueWarning}>
{issue.severity === 'error' ? '⚠' : '!'} {issue.message}
</div>
))}
</div>
)}
<div>
<label className={styles.label}>Node ID</label>
<input className={styles.input} value={node.id} readOnly />
</div>
<SpeakerField
speaker={node.speaker}
portrait={node.portrait}
onSpeakerChange={(s, p) => updateNode(dialogueId, node.id, { speaker: s, portrait: p })}
/>
<div style={{ marginTop: 12 }}>
<div className={styles.sectionHeader}>
<span className={styles.label}>Choices</span>
<button className={styles.addBtn} onClick={addChoice}>+ Add</button>
</div>
{node.choices.map((choice, idx) => (
<div key={choice.id} className={styles.choiceEditorRow}>
<div className={styles.choiceEditorHeader}>
<select
className={styles.selectSmall}
value={choice.kind}
onChange={e => updateChoice(idx, { kind: e.target.value as 'Main' | 'Optional' })}
>
<option value="Main">Main</option>
<option value="Optional">Optional</option>
</select>
<span className={styles.choiceIdLabel}>#{idx + 1}</span>
<button className={styles.removeBtn} onClick={() => removeChoice(idx)}>×</button>
</div>
<input
className={styles.input}
value={choice.text}
placeholder="Choice text..."
onChange={e => updateChoice(idx, { text: e.target.value })}
/>
<input
className={styles.input}
value={choice.next}
placeholder="Next node ID..."
onChange={e => updateChoice(idx, { next: e.target.value })}
list={`nodelist-choice-${choice.id}`}
/>
<datalist id={`nodelist-choice-${choice.id}`}>
{nodeIds.map(id => <option key={id} value={id} />)}
</datalist>
</div>
))}
{node.choices.length === 0 && (
<div className={styles.emptyHint}>No choices yet. Click "+ Add".</div>
)}
</div>
</div>
);
}