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
40
crates/archivr-server/static/assets/index-Bysig1_i.js
Normal file
40
crates/archivr-server/static/assets/index-Bysig1_i.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Archivr</title>
|
<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">
|
<link rel="stylesheet" crossorigin href="/assets/index-CprXPmri.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,15 @@ export default function App() {
|
||||||
const [searchBusy, setSearchBusy] = useState(false)
|
const [searchBusy, setSearchBusy] = useState(false)
|
||||||
const [runs, setRuns] = useState([])
|
const [runs, setRuns] = useState([])
|
||||||
const [tagNodes, setTagNodes] = 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) => {
|
const loadEntries = useCallback(async (aid, q, tag) => {
|
||||||
if (!aid) return
|
if (!aid) return
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,89 @@ import { submitCapture, pollCaptureJob } from '../api'
|
||||||
|
|
||||||
export default function CaptureDialog({ open, archiveId, onClose, onCaptured }) {
|
export default function CaptureDialog({ open, archiveId, onClose, onCaptured }) {
|
||||||
const dialogRef = useRef(null)
|
const dialogRef = useRef(null)
|
||||||
const [locator, setLocator] = useState('')
|
const isFirstRenderRef = useRef(true)
|
||||||
const [error, setError] = useState(null)
|
const hasResumedPollingRef = useRef(false)
|
||||||
const [busy, setBusy] = useState(false)
|
|
||||||
const [jobStatus, setJobStatus] = useState(null) // null | 'running' | 'completed' | 'failed'
|
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)
|
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(() => {
|
useEffect(() => {
|
||||||
const dialog = dialogRef.current
|
const dialog = dialogRef.current
|
||||||
if (!dialog) return
|
if (!dialog) return
|
||||||
|
|
@ -20,21 +97,41 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
|
||||||
return () => dialog.removeEventListener('close', handleClose)
|
return () => dialog.removeEventListener('close', handleClose)
|
||||||
}, [onClose])
|
}, [onClose])
|
||||||
|
|
||||||
|
// Handle open/close from parent
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dialog = dialogRef.current
|
const dialog = dialogRef.current
|
||||||
if (!dialog) return
|
if (!dialog) return
|
||||||
|
|
||||||
if (open) {
|
if (open) {
|
||||||
setLocator('')
|
// Only clear state if this is a fresh open from user click (not a restore on first render)
|
||||||
setError(null)
|
if (!isFirstRenderRef.current) {
|
||||||
setJobStatus(null)
|
setLocator('')
|
||||||
setBusy(false)
|
setError(null)
|
||||||
clearInterval(pollRef.current)
|
setJobStatus(null)
|
||||||
|
setBusy(false)
|
||||||
|
setJobUid(null)
|
||||||
|
clearInterval(pollRef.current)
|
||||||
|
}
|
||||||
|
isFirstRenderRef.current = false
|
||||||
if (!dialog.open) dialog.showModal()
|
if (!dialog.open) dialog.showModal()
|
||||||
} else {
|
} else {
|
||||||
if (dialog.open) dialog.close()
|
if (dialog.open) dialog.close()
|
||||||
}
|
}
|
||||||
}, [open])
|
}, [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() {
|
async function handleSubmit() {
|
||||||
if (!locator.trim()) { setError('Enter a locator.'); return }
|
if (!locator.trim()) { setError('Enter a locator.'); return }
|
||||||
setBusy(true)
|
setBusy(true)
|
||||||
|
|
@ -42,6 +139,7 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
|
||||||
setJobStatus(null)
|
setJobStatus(null)
|
||||||
try {
|
try {
|
||||||
const job = await submitCapture(archiveId, locator.trim())
|
const job = await submitCapture(archiveId, locator.trim())
|
||||||
|
setJobUid(job.job_uid)
|
||||||
setJobStatus('running')
|
setJobStatus('running')
|
||||||
pollRef.current = setInterval(async () => {
|
pollRef.current = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -51,6 +149,7 @@ export default function CaptureDialog({ open, archiveId, onClose, onCaptured })
|
||||||
pollRef.current = null
|
pollRef.current = null
|
||||||
setBusy(false)
|
setBusy(false)
|
||||||
setJobStatus('completed')
|
setJobStatus('completed')
|
||||||
|
clearCaptureState()
|
||||||
dialogRef.current?.close()
|
dialogRef.current?.close()
|
||||||
onCaptured()
|
onCaptured()
|
||||||
} else if (updated.status === 'failed') {
|
} else if (updated.status === 'failed') {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue