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,104 @@
|
||||
import { useDialogueStore } from '../../store/dialogueStore';
|
||||
import { makeNodeId } from '../../utils/idGen';
|
||||
import { DialogueNode, NodeType } from '../../types/dialogue';
|
||||
import { MAIN_CHARACTER } from '../../constants/characters';
|
||||
import styles from './GraphToolbar.module.css';
|
||||
|
||||
const NODE_DEFAULTS: Record<string, () => Omit<DialogueNode, 'id'>> = {
|
||||
Line: () => ({
|
||||
type: 'Line',
|
||||
speaker: MAIN_CHARACTER,
|
||||
portrait: 'resources/dialogue/portrait_hero_neutral.png',
|
||||
text: '',
|
||||
next: '',
|
||||
}),
|
||||
Choice: () => ({
|
||||
type: 'Choice',
|
||||
speaker: MAIN_CHARACTER,
|
||||
portrait: 'resources/dialogue/portrait_hero_neutral.png',
|
||||
text: '',
|
||||
choices: [],
|
||||
}),
|
||||
Condition: () => ({
|
||||
type: 'Condition',
|
||||
conditions: [],
|
||||
trueNext: '',
|
||||
falseNext: '',
|
||||
}),
|
||||
SetFlag: () => ({
|
||||
type: 'SetFlag',
|
||||
effects: [],
|
||||
next: '',
|
||||
}),
|
||||
CutsceneStart: () => ({
|
||||
type: 'CutsceneStart',
|
||||
cutsceneId: '',
|
||||
next: '',
|
||||
}),
|
||||
End: () => ({ type: 'End' }),
|
||||
};
|
||||
|
||||
export function GraphToolbar() {
|
||||
const { file, selectedDialogueId, selectedNodeId, addNode, applyAutoLayout, startPlay, setDialogueMobileMode, persistentFlags, resetFlags } = useDialogueStore();
|
||||
|
||||
const dialogue = file?.dialogues.find(d => d.id === selectedDialogueId);
|
||||
if (!dialogue) return null;
|
||||
|
||||
function handleAddNode(type: NodeType) {
|
||||
if (!selectedDialogueId || !dialogue) return;
|
||||
const existingIds = new Set(dialogue.nodes.map(n => n.id));
|
||||
const id = makeNodeId(type, existingIds);
|
||||
const node = { id, ...NODE_DEFAULTS[type]() } as DialogueNode;
|
||||
addNode(selectedDialogueId, node, selectedNodeId ?? undefined);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.toolbar}>
|
||||
<div className={styles.group}>
|
||||
<span className={styles.label}>Add:</span>
|
||||
{(['Line', 'Choice', 'Condition', 'SetFlag', 'CutsceneStart', 'End'] as NodeType[]).map(type => (
|
||||
<button key={type} className={styles.btn} onClick={() => handleAddNode(type)}>
|
||||
{type}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.separator} />
|
||||
<div className={styles.group}>
|
||||
<button className={styles.btn} onClick={() => applyAutoLayout(selectedDialogueId!)}>
|
||||
⬡ Auto Layout
|
||||
</button>
|
||||
<button className={[styles.btn, styles.btnPlay].join(' ')} onClick={startPlay}>
|
||||
▶ Play
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.separator} />
|
||||
<div className={styles.group}>
|
||||
<label className={styles.toggle}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={!!dialogue.mobileMode}
|
||||
onChange={e => setDialogueMobileMode(selectedDialogueId!, e.target.checked)}
|
||||
/>
|
||||
<span>📱 Mobile</span>
|
||||
</label>
|
||||
</div>
|
||||
{dialogue.mobileMode && (
|
||||
<div className={styles.mobileBanner}>
|
||||
Mobile mode — portraits will be overridden on export
|
||||
</div>
|
||||
)}
|
||||
{Object.keys(persistentFlags).length > 0 && (
|
||||
<>
|
||||
<div className={styles.separator} />
|
||||
<div className={styles.group}>
|
||||
<span className={styles.label}>🚩 Flags:</span>
|
||||
{Object.entries(persistentFlags).map(([k, v]) => (
|
||||
<span key={k} className={styles.flagPill}>{k}={v}</span>
|
||||
))}
|
||||
<button className={styles.btnReset} onClick={resetFlags}>Reset</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user