// The confirmation dialog for copying a scenario's current content. // // The dialog shows exactly what the update changes, and collects any // `${Placeholder}` answers the adventure has no stored value for. An adventure // started before those were saved has none, and so does one whose author added // a placeholder since. The update overwrites plot text and scenario-derived // cards, so nothing happens until you press Update. import { useState } from 'react' import { createPortal } from 'react-dom' import { FIELD_LABELS, clip } from './format' function RefreshModal({ plan, onConfirm, onCancel }) { const [values, setValues] = useState( Object.fromEntries((plan.placeholders_needed || []).map((n) => [n, ''])), ) const [busy, setBusy] = useState(false) const { added = [], updated = [], removed = [] } = plan.cards || {} const world = plan.world_state || {} const fields = Object.entries(plan.fields || {}) const submit = (e) => { e.preventDefault() setBusy(true) onConfirm(values).finally(() => setBusy(false)) } // Portalled to
: this modal is opened from inside .side-panel, whose // panel-in animation (fill mode `both`) makes it the containing block for // position:fixed children — an overlay rendered in place would be trapped in // the 420px panel and clipped by its overflow. Same trap for any future modal // opened from a drawer or panel. return createPortal(