mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-21 18:55:36 +02:00
feat(capture): async polling in CaptureDialog + pollCaptureJob api helper
This commit is contained in:
parent
f76f5438f2
commit
9ac28459ed
2 changed files with 52 additions and 9 deletions
|
|
@ -64,9 +64,14 @@ export async function submitCapture(archiveId, locator) {
|
|||
body: JSON.stringify({ locator }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const msg = await res.text();
|
||||
throw new Error(msg || `HTTP ${res.status}`);
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(body.error || `HTTP ${res.status}`);
|
||||
}
|
||||
return res.json(); // { job_uid, status: "pending" }
|
||||
}
|
||||
|
||||
export async function pollCaptureJob(archiveId, jobUid) {
|
||||
return getJson(`/api/archives/${archiveId}/capture_jobs/${jobUid}`);
|
||||
}
|
||||
|
||||
// ── Auth helpers ─────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
import { useRef, useEffect, useState } from 'react'
|
||||
import { submitCapture } from '../api'
|
||||
import { submitCapture, pollCaptureJob } from '../api'
|
||||
|
||||
export default function CaptureDialog({ open, archiveId, onClose, onCaptured }) {
|
||||
const dialogRef = useRef(null)
|
||||
const [locator, setLocator] = useState('')
|
||||
const [error, setError] = useState(null)
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [jobStatus, setJobStatus] = useState(null) // null | 'running' | 'completed' | 'failed'
|
||||
const pollRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const dialog = dialogRef.current
|
||||
if (!dialog) return
|
||||
const handleClose = () => onClose()
|
||||
const handleClose = () => {
|
||||
clearInterval(pollRef.current)
|
||||
onClose()
|
||||
}
|
||||
dialog.addEventListener('close', handleClose)
|
||||
return () => dialog.removeEventListener('close', handleClose)
|
||||
}, [onClose])
|
||||
|
|
@ -21,6 +26,9 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
|
|||
if (open) {
|
||||
setLocator('')
|
||||
setError(null)
|
||||
setJobStatus(null)
|
||||
setBusy(false)
|
||||
clearInterval(pollRef.current)
|
||||
if (!dialog.open) dialog.showModal()
|
||||
} else {
|
||||
if (dialog.open) dialog.close()
|
||||
|
|
@ -31,17 +39,47 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
|
|||
if (!locator.trim()) { setError('Enter a locator.'); return }
|
||||
setBusy(true)
|
||||
setError(null)
|
||||
setJobStatus(null)
|
||||
try {
|
||||
await submitCapture(archiveId, locator.trim())
|
||||
dialogRef.current?.close()
|
||||
onCaptured()
|
||||
const job = await submitCapture(archiveId, locator.trim())
|
||||
setJobStatus('running')
|
||||
pollRef.current = setInterval(async () => {
|
||||
try {
|
||||
const updated = await pollCaptureJob(archiveId, job.job_uid)
|
||||
if (updated.status === 'completed') {
|
||||
clearInterval(pollRef.current)
|
||||
pollRef.current = null
|
||||
setBusy(false)
|
||||
setJobStatus('completed')
|
||||
dialogRef.current?.close()
|
||||
onCaptured()
|
||||
} else if (updated.status === 'failed') {
|
||||
clearInterval(pollRef.current)
|
||||
pollRef.current = null
|
||||
setBusy(false)
|
||||
setJobStatus('failed')
|
||||
setError(updated.error_text || 'Capture failed.')
|
||||
}
|
||||
// pending / running: keep polling
|
||||
} catch (pollErr) {
|
||||
clearInterval(pollRef.current)
|
||||
pollRef.current = null
|
||||
setBusy(false)
|
||||
setError(pollErr.message)
|
||||
}
|
||||
}, 500)
|
||||
} catch (e) {
|
||||
setError(e.message)
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
function buttonLabel() {
|
||||
if (!busy) return 'Capture'
|
||||
if (jobStatus === 'running') return 'Running\u2026'
|
||||
return 'Capturing\u2026'
|
||||
}
|
||||
|
||||
return (
|
||||
<dialog ref={dialogRef} className="capture-dialog">
|
||||
<div className="capture-dialog-inner">
|
||||
|
|
@ -56,7 +94,7 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
|
|||
<div className="capture-actions">
|
||||
<button type="button" className="capture-cancel" onClick={() => dialogRef.current?.close()}>Cancel</button>
|
||||
<button type="button" className="capture-submit" onClick={handleSubmit} disabled={busy}>
|
||||
{busy ? 'Capturing\u2026' : 'Capture'}
|
||||
{buttonLabel()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue