// Save Points: naming a place in the story, and going back to one. // // "Save Point" is the word throughout, and the words branch, head, node and // fork appear nowhere a player can read (`BROWSER-UX-SPEC.md` §23). The panel // is deliberately small — a form, a list, and two confirmations. It is not a // branch explorer, and the tree it sits over stays out of sight. // // Both confirmations exist to say what does *not* happen, because that is the // part a player cannot see and would otherwise assume the worst about: restore // keeps the later story, and deleting a Save Point deletes no story at all. import { useEffect, useState } from 'react' import { api } from '../../../api' function SavePointPanel({ advId, refreshKey, onRestored, onError }) { const [points, setPoints] = useState(null) const [failed, setFailed] = useState(null) const [name, setName] = useState('') const [note, setNote] = useState('') const [saving, setSaving] = useState(false) const [busyId, setBusyId] = useState(null) const [renaming, setRenaming] = useState(null) // { id, text } const [confirming, setConfirming] = useState(null) // { id, kind } const [tick, setTick] = useState(0) useEffect(() => { let cancelled = false setFailed(null) api.listCheckpoints(advId) .then((list) => { if (!cancelled) setPoints(list) }) .catch((err) => { if (!cancelled) setFailed(err.message) }) return () => { cancelled = true } }, [advId, refreshKey, tick]) // Answers whether it worked, so a caller can keep its editor open on a // refusal — a rename the server turned down must not take the typed name // with it. async function run(id, work) { setBusyId(id) try { await work() setTick((t) => t + 1) return true } catch (err) { onError(err.message) return false } finally { setBusyId(null) } } async function create(e) { e.preventDefault() if (!name.trim() || saving) return setSaving(true) try { await api.createCheckpoint(advId, name.trim(), note.trim()) setName('') setNote('') setTick((t) => t + 1) } catch (err) { onError(err.message) } finally { setSaving(false) } } const restore = (p) => run(p.id, async () => { onRestored(await api.restoreCheckpoint(advId, p.id)) setConfirming(null) }) const rename = async (p) => { if (await run(p.id, () => api.renameCheckpoint(advId, p.id, renaming.text))) { setRenaming(null) } } const remove = async (p) => { if (await run(p.id, () => api.deleteCheckpoint(advId, p.id))) setConfirming(null) } return (
No Save Points yet. Name a moment you might want to come back to, then keep playing — going back to it later leaves everything you wrote after it in place.
)}