Open the request in a window, and name the endpoint
The preview was a 10rem box in the corner of a docked panel: enough to see that there is JSON, not enough to read it. It opens as a modal with the endpoint line and a copy button (with an execCommand fallback, since navigator.clipboard is undefined on this app's plain-http origin). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
065d1c2571
commit
4bce3bbd17
@ -1219,18 +1219,69 @@ function ApplyModeChooser({ op, applyMode, setApplyMode, count }) {
|
|||||||
return <Segmented value={applyMode} onChange={setApplyMode} options={options} />
|
return <Segmented value={applyMode} onChange={setApplyMode} options={options} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function RequestPreview({ payload }) {
|
// The request as it will be sent. A 40rem-tall box in the corner of a docked
|
||||||
|
// panel could show about six lines of it, which is enough to see that there is
|
||||||
|
// JSON and not enough to read it, so it opens as a window of its own.
|
||||||
|
//
|
||||||
|
// It is the payload itself -- `submitOp` posts exactly what `buildPayload`
|
||||||
|
// returns, with no last-minute additions -- so what is shown here is what goes
|
||||||
|
// over the wire, minus the two things the browser never sees: the session that
|
||||||
|
// names `pf_user`, and the territory predicate the server ANDs on.
|
||||||
|
function RequestPreview({ payload, op, versionId }) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
const onKey = (e) => { if (e.key === 'Escape') setOpen(false) }
|
||||||
|
window.addEventListener('keydown', onKey)
|
||||||
|
return () => window.removeEventListener('keydown', onKey)
|
||||||
|
}, [open])
|
||||||
|
|
||||||
if (!payload) return null
|
if (!payload) return null
|
||||||
|
const text = JSON.stringify(payload, null, 2)
|
||||||
|
const line = `POST /api/versions/${versionId}/${op}`
|
||||||
|
|
||||||
|
// navigator.clipboard is undefined on a plain-http origin, which this app is
|
||||||
|
// served over, so the fallback is not decoration.
|
||||||
|
const copy = async () => {
|
||||||
|
try {
|
||||||
|
if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(text)
|
||||||
|
else {
|
||||||
|
const ta = document.createElement('textarea')
|
||||||
|
ta.value = text
|
||||||
|
ta.style.position = 'fixed'; ta.style.opacity = '0'
|
||||||
|
document.body.appendChild(ta); ta.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(ta)
|
||||||
|
}
|
||||||
|
setCopied(true); setTimeout(() => setCopied(false), 1500)
|
||||||
|
} catch { /* leave the text on screen to select by hand */ }
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TextButton onClick={() => setOpen(o => !o)}>
|
<TextButton onClick={() => setOpen(true)}>▸ request</TextButton>
|
||||||
{open ? '▾' : '▸'} request
|
|
||||||
</TextButton>
|
|
||||||
{open && (
|
{open && (
|
||||||
<pre className="mt-1 font-mono text-gray-600 bg-gray-50 border border-gray-100 rounded p-2 overflow-auto max-h-40 leading-relaxed">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40"
|
||||||
{JSON.stringify(payload, null, 2)}
|
onClick={() => setOpen(false)}>
|
||||||
|
<div className="bg-white rounded-lg shadow-xl w-full max-w-3xl mx-4 flex flex-col max-h-[85vh]"
|
||||||
|
onClick={e => e.stopPropagation()}>
|
||||||
|
<div className="flex items-center gap-3 px-5 py-3 border-b border-gray-200 shrink-0">
|
||||||
|
<span className="font-medium text-gray-700 text-sm">Request</span>
|
||||||
|
<span className="font-mono text-gray-600 text-xs truncate" title={line}>{line}</span>
|
||||||
|
<div className="ml-auto flex items-center gap-3">
|
||||||
|
<TextButton onClick={copy}>{copied ? 'copied' : 'copy'}</TextButton>
|
||||||
|
<button onClick={() => setOpen(false)}
|
||||||
|
className="text-gray-600 hover:text-gray-800 text-lg leading-none">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<pre className="overflow-auto p-5 font-mono text-gray-700 text-xs leading-relaxed whitespace-pre">
|
||||||
|
{text}
|
||||||
</pre>
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -1408,7 +1459,7 @@ export default function OperationPanel({
|
|||||||
{opBusy && <span className="text-gray-500 leading-snug">writing rows…</span>}
|
{opBusy && <span className="text-gray-500 leading-snug">writing rows…</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RequestPreview payload={buildPayload(activeOp)} />
|
<RequestPreview payload={buildPayload(activeOp)} op={activeOp} versionId={versionId} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user