Field notebook: capture NPC lines, tear pages onto the board
A dialogue line gains a "✎ Note this" capture that saves it to a per-playthrough notebook (migration 035 + notebook endpoints). The inventory notebook lists captured pages in a handwriting font (Google "Reenie Beanie") and "✂ Tear to board" drops a page onto the current board as a note exhibit (reusing addNote), then removes the page. Board notes render handwritten too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+23
-6
@@ -85,13 +85,13 @@ function ToolRack({ index }: { index: number }) {
|
||||
return <div ref={stageRef} className="inv-stage" />
|
||||
}
|
||||
|
||||
export function Inventory({ onClose, session }: { onClose?: () => void; session?: PhoneSession }) {
|
||||
export function Inventory({ onClose, session, onTearToBoard }: { onClose?: () => void; session?: PhoneSession; onTearToBoard?: (text: string) => void }) {
|
||||
const [index, setIndex] = useState(0)
|
||||
const [active, setActive] = useState<string | null>(null)
|
||||
|
||||
if (active) return <div className="inv-tool">
|
||||
<button className="inv-back" onClick={() => setActive(null)}>‹ TOOLS</button>
|
||||
{active === 'phone' ? <PhonePreview session={session} /> : <NotebookTool />}
|
||||
{active === 'phone' ? <PhonePreview session={session} /> : <NotebookTool playthroughId={session?.playthroughId} onTear={onTearToBoard} />}
|
||||
</div>
|
||||
|
||||
return <div className="inv-backdrop">
|
||||
@@ -108,13 +108,30 @@ export function Inventory({ onClose, session }: { onClose?: () => void; session?
|
||||
</div>
|
||||
}
|
||||
|
||||
// Placeholder notebook tool — a lined page you can scribble on (not yet persisted).
|
||||
function NotebookTool() {
|
||||
const [text, setText] = useState('')
|
||||
// Field notebook: the lines captured from NPCs during play. A page can be torn off
|
||||
// onto the board as a note (when a board tear handler is available).
|
||||
function NotebookTool({ playthroughId, onTear }: { playthroughId?: string; onTear?: (text: string) => void }) {
|
||||
const [pages, setPages] = useState<{ id: string; text: string }[]>([])
|
||||
useEffect(() => {
|
||||
if (!playthroughId) return
|
||||
fetch(`/api/playthroughs/${playthroughId}/notebook`).then(r => r.ok ? r.json() : []).then(setPages).catch(() => {})
|
||||
}, [playthroughId])
|
||||
const remove = async (id: string) => {
|
||||
if (playthroughId) await fetch(`/api/playthroughs/${playthroughId}/notebook/${id}`, { method: 'DELETE' })
|
||||
setPages(list => list.filter(page => page.id !== id))
|
||||
}
|
||||
const tear = (page: { id: string; text: string }) => { onTear?.(page.text); void remove(page.id) }
|
||||
return <div className="notebook">
|
||||
<div className="notebook-page">
|
||||
<div className="notebook-head">FIELD NOTEBOOK</div>
|
||||
<textarea value={text} onChange={e => setText(e.target.value)} placeholder="Jot a lead…" spellCheck={false} />
|
||||
{!pages.length && <p className="notebook-empty">Nothing noted yet. During a conversation, use “✎ Note this” to jot down what someone says.</p>}
|
||||
{pages.map(page => <div key={page.id} className="notebook-note">
|
||||
<p>{page.text}</p>
|
||||
<div className="notebook-note-actions">
|
||||
{onTear && <button onClick={() => tear(page)} title="Tear off onto the board">✂ Tear to board</button>}
|
||||
<button className="ghost" onClick={() => void remove(page.id)} title="Discard">×</button>
|
||||
</div>
|
||||
</div>)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user