import React from "react"; import { Statement, getCharColor } from "@/lib/vn-parser"; import { Icons } from "./VNIcons"; import { POSITION_MAP, Sprite, SpritePosition } from "@/lib/sprite-bank"; interface VNPreviewProps { currentStatement: Statement | null; currentSayIndex: number; totalPreviewSteps: number; onPrev: () => void; onNext: () => void; statements: Statement[]; spriteBank: Record; onChoiceSelect?: (jumpLabel: string) => void; } export const VNPreview: React.FC = ({ currentStatement, currentSayIndex, totalPreviewSteps, onPrev, onNext, statements, spriteBank, onChoiceSelect, }) => { // Track state of all characters on scene and the current label const activeSprites: Record< string, { emotion: string; pos: SpritePosition } > = {}; let currentLabel: string | null = null; statements.forEach((s) => { if (s.lineNumber <= (currentStatement?.lineNumber || 999999)) { if (s.type === "label") { currentLabel = s.label || null; } if (s.type === "sprite" && s.character) { activeSprites[s.character] = { emotion: s.emotion || "default", pos: (s.value as SpritePosition) || "pos5", }; } } }); const getSpriteUrlLocal = (char: string, emo: string) => { const bank = spriteBank[char]; if (!bank) return null; return bank.find((s) => s.id === emo)?.url || bank[0]?.url || null; }; const renderDialogueBox = () => { if (!currentStatement) return null; if (currentStatement.type === "choice") { return (
{currentStatement.choices?.map((choice, i) => ( ))}
); } if (currentStatement.type !== "say") return null; return (
{currentStatement.character && (
{currentStatement.character}
)}
{[ ...(currentStatement.segments || []), ...(currentStatement.continuations || []), ].map((seg, i) => ( {seg.modifiers.wait && } {seg.modifiers.click && } {seg.modifiers.clickLock && } {seg.text} ))}
); }; return (
PREVIEW | {currentStatement?.character || "NARRATOR"} {currentLabel && ( SCENE: {currentLabel} )}
{Object.entries(activeSprites).map(([charName, data]) => { const url = getSpriteUrlLocal(charName, data.emotion); const isSpeaking = charName === currentStatement?.character; const isSmallPos = data.pos === "pos7" || data.pos === "pos8"; const baseScale = isSmallPos ? 0.8 : 1.0; const finalScale = isSpeaking ? baseScale * 1.05 : baseScale; const translateY = isSpeaking ? "0rem" : "0.5rem"; return (
{url ? ( {charName} ) : (
)} {!isSpeaking && (
{charName}
)}
); })}
{renderDialogueBox()} {!currentStatement && (
No statement selected
)}
STEP {currentSayIndex + 1} OF {totalPreviewSteps}

Controls

TYPE to edit script.
ARROWS to preview dialogue steps.
OUTLINE to jump to specific lines.
SAVE to download raw script file.
); };