From ceef6f09e27a794c651511969cc60bb10bb3ad48 Mon Sep 17 00:00:00 2001
From: Lars
Date: Thu, 7 May 2026 22:02:18 +0200
Subject: [PATCH] feat: improve media library layout and loading behavior
- 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.
---
frontend/src/app.css | 8 ++++++++
frontend/src/pages/MediaLibraryPage.jsx | 17 +++++++++++++++--
2 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/frontend/src/app.css b/frontend/src/app.css
index 1df0845..bcd4fad 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -5563,6 +5563,14 @@ a.analysis-split__nav-item {
color: var(--text2);
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 {
vertical-align: middle;
margin-right: 6px;
diff --git a/frontend/src/pages/MediaLibraryPage.jsx b/frontend/src/pages/MediaLibraryPage.jsx
index 290e5af..3ff9883 100644
--- a/frontend/src/pages/MediaLibraryPage.jsx
+++ b/frontend/src/pages/MediaLibraryPage.jsx
@@ -262,6 +262,8 @@ export default function MediaLibraryPage() {
const [uploadClubId, setUploadClubId] = useState('')
const [uploadBusy, setUploadBusy] = useState(false)
const [uploadSummary, setUploadSummary] = useState('')
+ const mediaListFetchSeqRef = useRef(0)
+ const gridTopAnchorRef = useRef(null)
const loadClubs = useCallback(async () => {
try {
@@ -277,6 +279,7 @@ export default function MediaLibraryPage() {
}, [loadClubs])
const loadItems = useCallback(async () => {
+ const seq = ++mediaListFetchSeqRef.current
setLoading(true)
setError('')
try {
@@ -294,6 +297,7 @@ export default function MediaLibraryPage() {
? { uploaded_by: Number(filterUploaderId) }
: {}),
})
+ if (seq !== mediaListFetchSeqRef.current) return
setItems(res.items || [])
setViewer(res.viewer || null)
if (res.filter_meta?.uploaders?.length) {
@@ -301,9 +305,10 @@ export default function MediaLibraryPage() {
}
setSelected(new Set())
} catch (e) {
+ if (seq !== mediaListFetchSeqRef.current) return
setError(e.message || String(e))
} finally {
- setLoading(false)
+ if (seq === mediaListFetchSeqRef.current) setLoading(false)
}
}, [lifecycle, q, mediaKind, filterClubId, filterUploaderId, isSuperadmin, viewer?.show_uploader_meta])
@@ -485,9 +490,15 @@ export default function MediaLibraryPage() {
...(uploadVis === 'club' ? { club_id: Number(uploadClubId) } : {}),
})
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()
+ window.requestAnimationFrame(() => {
+ gridTopAnchorRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
+ })
} catch (err) {
window.alert(err.message || String(err))
} finally {
@@ -682,6 +693,8 @@ export default function MediaLibraryPage() {
) : null}
+
+
{loading && !items.length ? : null}
{!loading && !items.length && !error ? (