1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-21 18:55:36 +02:00

fix: capture popup non-persistance

- Save and restore dialog open/closed state in sessionStorage (App.jsx)
- Persist form data: locator, error, busy, jobStatus, jobUid (CaptureDialog.jsx)
- Auto-resume polling if capture job was in progress before page refresh
- Only clear form on fresh user click, not when restoring from refresh
- Clean up sessionStorage when capture completes successfully

Fixes: Capture pop-up disappears on page refresh with unsaved data
This commit is contained in:
TheGeneralist 2026-07-03 13:31:46 +02:00 committed by GitHub
parent 2502de45b6
commit d6b52ba06c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 158 additions and 51 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Archivr</title>
<script type="module" crossorigin src="/assets/index-MmGgQJvV.js"></script>
<script type="module" crossorigin src="/assets/index-Bysig1_i.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CprXPmri.css">
</head>
<body>

View file

@ -76,7 +76,15 @@ export default function App() {
const [searchBusy, setSearchBusy] = useState(false)
const [runs, setRuns] = useState([])
const [tagNodes, setTagNodes] = useState([])
const [captureDialogOpen, setCaptureDialogOpen] = useState(false)
const [captureDialogOpen, setCaptureDialogOpen] = useState(() => {
const saved = sessionStorage.getItem('captureDialogOpen')
return saved === 'true'
})
// Persist captureDialogOpen to sessionStorage
useEffect(() => {
sessionStorage.setItem('captureDialogOpen', captureDialogOpen)
}, [captureDialogOpen])
const loadEntries = useCallback(async (aid, q, tag) => {
if (!aid) return

View file

@ -3,12 +3,89 @@ 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 isFirstRenderRef = useRef(true)
const hasResumedPollingRef = useRef(false)
const [locator, setLocator] = useState(() => {
const saved = sessionStorage.getItem('captureDialogLocator')
return saved || ''
})
const [error, setError] = useState(() => {
const saved = sessionStorage.getItem('captureDialogError')
return saved || null
})
const [busy, setBusy] = useState(() => {
const saved = sessionStorage.getItem('captureDialogBusy')
return saved === 'true'
})
const [jobStatus, setJobStatus] = useState(() => {
const saved = sessionStorage.getItem('captureDialogJobStatus')
return saved || null
})
const [jobUid, setJobUid] = useState(() => {
const saved = sessionStorage.getItem('captureDialogJobUid')
return saved || null
})
const pollRef = useRef(null)
// Persist state to sessionStorage
useEffect(() => {
sessionStorage.setItem('captureDialogLocator', locator)
}, [locator])
useEffect(() => {
sessionStorage.setItem('captureDialogError', error || '')
}, [error])
useEffect(() => {
sessionStorage.setItem('captureDialogBusy', busy)
}, [busy])
useEffect(() => {
sessionStorage.setItem('captureDialogJobStatus', jobStatus || '')
}, [jobStatus])
useEffect(() => {
sessionStorage.setItem('captureDialogJobUid', jobUid || '')
}, [jobUid])
// On mount, resume polling if a job was in progress before page refresh
useEffect(() => {
if (hasResumedPollingRef.current) return
if (!jobUid || jobStatus !== 'running' || !archiveId) return
hasResumedPollingRef.current = true
// Resume polling for the saved job
pollRef.current = setInterval(async () => {
try {
const updated = await pollCaptureJob(archiveId, jobUid)
if (updated.status === 'completed') {
clearInterval(pollRef.current)
pollRef.current = null
setBusy(false)
setJobStatus('completed')
clearCaptureState()
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)
}, [jobUid, jobStatus, archiveId, onCaptured])
// Handle dialog close event
useEffect(() => {
const dialog = dialogRef.current
if (!dialog) return
@ -20,21 +97,41 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
return () => dialog.removeEventListener('close', handleClose)
}, [onClose])
// Handle open/close from parent
useEffect(() => {
const dialog = dialogRef.current
if (!dialog) return
if (open) {
// Only clear state if this is a fresh open from user click (not a restore on first render)
if (!isFirstRenderRef.current) {
setLocator('')
setError(null)
setJobStatus(null)
setBusy(false)
setJobUid(null)
clearInterval(pollRef.current)
}
isFirstRenderRef.current = false
if (!dialog.open) dialog.showModal()
} else {
if (dialog.open) dialog.close()
}
}, [open])
function clearCaptureState() {
sessionStorage.removeItem('captureDialogLocator')
sessionStorage.removeItem('captureDialogError')
sessionStorage.removeItem('captureDialogBusy')
sessionStorage.removeItem('captureDialogJobStatus')
sessionStorage.removeItem('captureDialogJobUid')
setLocator('')
setError(null)
setBusy(false)
setJobStatus(null)
setJobUid(null)
}
async function handleSubmit() {
if (!locator.trim()) { setError('Enter a locator.'); return }
setBusy(true)
@ -42,6 +139,7 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
setJobStatus(null)
try {
const job = await submitCapture(archiveId, locator.trim())
setJobUid(job.job_uid)
setJobStatus('running')
pollRef.current = setInterval(async () => {
try {
@ -51,6 +149,7 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
pollRef.current = null
setBusy(false)
setJobStatus('completed')
clearCaptureState()
dialogRef.current?.close()
onCaptured()
} else if (updated.status === 'failed') {