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:
parent
2502de45b6
commit
d6b52ba06c
5 changed files with 158 additions and 51 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
setLocator('')
|
||||
setError(null)
|
||||
setJobStatus(null)
|
||||
setBusy(false)
|
||||
clearInterval(pollRef.current)
|
||||
// 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') {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue