Working on dialog editor, on dialogs, on phone
This commit is contained in:
1 parent
0e27521bea
commit
8f7797789d
63 files changed
+9502
-1459
No files matched your search
@@ -0,0 +1,120 @@
|
||||
import { ConditionNode, ConditionClause, ConditionOp } from '../../../types/dialogue';
|
||||
import { useDialogueStore } from '../../../store/dialogueStore';
|
||||
import { useValidation } from '../../../hooks/useValidation';
|
||||
import styles from '../RightPanel.module.css';
|
||||
|
||||
const OPS: ConditionOp[] = ['Equals', 'NotEquals', 'GreaterThan', 'LessThan'];
|
||||
|
||||
interface Props {
|
||||
node: ConditionNode;
|
||||
dialogueId: string;
|
||||
}
|
||||
|
||||
export function ConditionInspector({ 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 updateClause(idx: number, patch: Partial<ConditionClause>) {
|
||||
const conditions = node.conditions.map((c, i) => i === idx ? { ...c, ...patch } : c);
|
||||
updateNode(dialogueId, node.id, { conditions });
|
||||
}
|
||||
|
||||
function addClause() {
|
||||
const conditions: ConditionClause[] = [...node.conditions, { flag: '', op: 'Equals', value: 1 }];
|
||||
updateNode(dialogueId, node.id, { conditions });
|
||||
}
|
||||
|
||||
function removeClause(idx: number) {
|
||||
const conditions = node.conditions.filter((_, i) => i !== idx);
|
||||
updateNode(dialogueId, node.id, { conditions });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.inspector}>
|
||||
<div className={styles.inspectorHeader}>
|
||||
<span className={styles.nodeTypeBadge} style={{ background: '#8839ef' }}>Condition</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}>Conditions (ALL must pass)</span>
|
||||
<button className={styles.addBtn} onClick={addClause}>+ Add</button>
|
||||
</div>
|
||||
{node.conditions.map((c, idx) => (
|
||||
<div key={idx} className={styles.clauseRow}>
|
||||
<input
|
||||
className={styles.inputSmall}
|
||||
value={c.flag}
|
||||
placeholder="flag"
|
||||
onChange={e => updateClause(idx, { flag: e.target.value })}
|
||||
/>
|
||||
<select
|
||||
className={styles.selectSmall}
|
||||
value={c.op}
|
||||
onChange={e => updateClause(idx, { op: e.target.value as ConditionOp })}
|
||||
>
|
||||
{OPS.map(op => <option key={op} value={op}>{op}</option>)}
|
||||
</select>
|
||||
<input
|
||||
className={styles.inputSmall}
|
||||
value={String(c.value)}
|
||||
placeholder="value"
|
||||
onChange={e => updateClause(idx, { value: isNaN(Number(e.target.value)) ? e.target.value : Number(e.target.value) })}
|
||||
/>
|
||||
<button className={styles.removeBtn} onClick={() => removeClause(idx)}>×</button>
|
||||
</div>
|
||||
))}
|
||||
{node.conditions.length === 0 && <div className={styles.emptyHint}>No conditions. Always true.</div>}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<label className={styles.label}>True → next node</label>
|
||||
<input
|
||||
className={styles.input}
|
||||
value={node.trueNext}
|
||||
placeholder="node_id"
|
||||
onChange={e => updateNode(dialogueId, node.id, { trueNext: e.target.value })}
|
||||
list={`nodelist-true-${node.id}`}
|
||||
/>
|
||||
<datalist id={`nodelist-true-${node.id}`}>
|
||||
{nodeIds.map(id => <option key={id} value={id} />)}
|
||||
</datalist>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<label className={styles.label}>False → next node</label>
|
||||
<input
|
||||
className={styles.input}
|
||||
value={node.falseNext}
|
||||
placeholder="node_id"
|
||||
onChange={e => updateNode(dialogueId, node.id, { falseNext: e.target.value })}
|
||||
list={`nodelist-false-${node.id}`}
|
||||
/>
|
||||
<datalist id={`nodelist-false-${node.id}`}>
|
||||
{nodeIds.map(id => <option key={id} value={id} />)}
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user