feat: improve media library layout and loading behavior
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 24s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 28s

- Added a hidden anchor element to the media library for smooth scrolling to the top of the grid after uploads.
- Introduced a sequence reference to manage concurrent media item fetch requests, ensuring accurate loading states.
- Updated upload summary messaging to include a note about the list being refreshed after uploads.
- Enhanced loading state management to prevent unnecessary updates when fetch requests are out of sync.
This commit is contained in:
Lars 2026-05-07 22:02:18 +02:00
parent cc4e621f95
commit ceef6f09e2
2 changed files with 23 additions and 2 deletions

View File

@ -5563,6 +5563,14 @@ a.analysis-split__nav-item {
color: var(--text2); color: var(--text2);
flex: 1 1 200px; flex: 1 1 200px;
} }
.media-library__grid-top-anchor {
height: 1px;
margin: 0;
padding: 0;
overflow: hidden;
visibility: hidden;
pointer-events: none;
}
.media-library__upload-icon { .media-library__upload-icon {
vertical-align: middle; vertical-align: middle;
margin-right: 6px; margin-right: 6px;

View File

@ -262,6 +262,8 @@ export default function MediaLibraryPage() {
const [uploadClubId, setUploadClubId] = useState('') const [uploadClubId, setUploadClubId] = useState('')
const [uploadBusy, setUploadBusy] = useState(false) const [uploadBusy, setUploadBusy] = useState(false)
const [uploadSummary, setUploadSummary] = useState('') const [uploadSummary, setUploadSummary] = useState('')
const mediaListFetchSeqRef = useRef(0)
const gridTopAnchorRef = useRef(null)
const loadClubs = useCallback(async () => { const loadClubs = useCallback(async () => {
try { try {
@ -277,6 +279,7 @@ export default function MediaLibraryPage() {
}, [loadClubs]) }, [loadClubs])
const loadItems = useCallback(async () => { const loadItems = useCallback(async () => {
const seq = ++mediaListFetchSeqRef.current
setLoading(true) setLoading(true)
setError('') setError('')
try { try {
@ -294,6 +297,7 @@ export default function MediaLibraryPage() {
? { uploaded_by: Number(filterUploaderId) } ? { uploaded_by: Number(filterUploaderId) }
: {}), : {}),
}) })
if (seq !== mediaListFetchSeqRef.current) return
setItems(res.items || []) setItems(res.items || [])
setViewer(res.viewer || null) setViewer(res.viewer || null)
if (res.filter_meta?.uploaders?.length) { if (res.filter_meta?.uploaders?.length) {
@ -301,9 +305,10 @@ export default function MediaLibraryPage() {
} }
setSelected(new Set()) setSelected(new Set())
} catch (e) { } catch (e) {
if (seq !== mediaListFetchSeqRef.current) return
setError(e.message || String(e)) setError(e.message || String(e))
} finally { } finally {
setLoading(false) if (seq === mediaListFetchSeqRef.current) setLoading(false)
} }
}, [lifecycle, q, mediaKind, filterClubId, filterUploaderId, isSuperadmin, viewer?.show_uploader_meta]) }, [lifecycle, q, mediaKind, filterClubId, filterUploaderId, isSuperadmin, viewer?.show_uploader_meta])
@ -485,9 +490,15 @@ export default function MediaLibraryPage() {
...(uploadVis === 'club' ? { club_id: Number(uploadClubId) } : {}), ...(uploadVis === 'club' ? { club_id: Number(uploadClubId) } : {}),
}) })
setUploadSummary( setUploadSummary(
`Archiv-Upload: neu ${res.created_count}, bereits vorhanden ${res.duplicate_count}, fehlgeschlagen ${res.failed_count}.`, `Archiv-Upload: neu ${res.created_count}, bereits vorhanden ${res.duplicate_count}, fehlgeschlagen ${res.failed_count}. Liste aktualisiert.`,
) )
if (res.created_count > 0 || res.duplicate_count > 0) {
setFilterUploaderId('')
}
await loadItems() await loadItems()
window.requestAnimationFrame(() => {
gridTopAnchorRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
})
} catch (err) { } catch (err) {
window.alert(err.message || String(err)) window.alert(err.message || String(err))
} finally { } finally {
@ -682,6 +693,8 @@ export default function MediaLibraryPage() {
</p> </p>
) : null} ) : null}
<div ref={gridTopAnchorRef} className="media-library__grid-top-anchor" aria-hidden="true" />
{loading && !items.length ? <div className="spinner media-library__spinner" /> : null} {loading && !items.length ? <div className="spinner media-library__spinner" /> : null}
{!loading && !items.length && !error ? ( {!loading && !items.length && !error ? (