99 lines
3.6 KiB
TypeScript
99 lines
3.6 KiB
TypeScript
import { SetFlagNode, FlagEffect } from '../../../types/dialogue';
|
||
import { useDialogueStore } from '../../../store/dialogueStore';
|
||
import { useValidation } from '../../../hooks/useValidation';
|
||
import styles from '../RightPanel.module.css';
|
||
|
||
interface Props {
|
||
node: SetFlagNode;
|
||
dialogueId: string;
|
||
}
|
||
|
||
export function SetFlagInspector({ 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 updateEffect(idx: number, patch: Partial<FlagEffect>) {
|
||
const effects = node.effects.map((e, i) => i === idx ? { ...e, ...patch } : e);
|
||
updateNode(dialogueId, node.id, { effects });
|
||
}
|
||
|
||
function addEffect() {
|
||
const effects: FlagEffect[] = [...node.effects, { flag: '', value: 1 }];
|
||
updateNode(dialogueId, node.id, { effects });
|
||
}
|
||
|
||
function removeEffect(idx: number) {
|
||
const effects = node.effects.filter((_, i) => i !== idx);
|
||
updateNode(dialogueId, node.id, { effects });
|
||
}
|
||
|
||
return (
|
||
<div className={styles.inspector}>
|
||
<div className={styles.inspectorHeader}>
|
||
<span className={styles.nodeTypeBadge} style={{ background: '#179299' }}>SetFlag</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>
|
||
|
||
<div style={{ marginTop: 8 }}>
|
||
<div className={styles.sectionHeader}>
|
||
<span className={styles.label}>Flag Effects</span>
|
||
<button className={styles.addBtn} onClick={addEffect}>+ Add</button>
|
||
</div>
|
||
{node.effects.map((e, idx) => (
|
||
<div key={idx} className={styles.clauseRow}>
|
||
<input
|
||
className={styles.inputSmall}
|
||
value={e.flag}
|
||
placeholder="flag_name"
|
||
onChange={ev => updateEffect(idx, { flag: ev.target.value })}
|
||
/>
|
||
<span className={styles.eqLabel}>=</span>
|
||
<input
|
||
className={styles.inputSmall}
|
||
value={String(e.value)}
|
||
placeholder="value"
|
||
onChange={ev => updateEffect(idx, { value: isNaN(Number(ev.target.value)) ? ev.target.value : Number(ev.target.value) })}
|
||
/>
|
||
<button className={styles.removeBtn} onClick={() => removeEffect(idx)}>×</button>
|
||
</div>
|
||
))}
|
||
{node.effects.length === 0 && <div className={styles.emptyHint}>No effects defined.</div>}
|
||
</div>
|
||
|
||
<div style={{ marginTop: 8 }}>
|
||
<label className={styles.label}>Next node</label>
|
||
<input
|
||
className={styles.input}
|
||
value={node.next}
|
||
placeholder="node_id"
|
||
onChange={e => updateNode(dialogueId, node.id, { next: e.target.value })}
|
||
list={`nodelist-sf-${node.id}`}
|
||
/>
|
||
<datalist id={`nodelist-sf-${node.id}`}>
|
||
{nodeIds.map(id => <option key={id} value={id} />)}
|
||
</datalist>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|