diff --git a/frontend/src/app.css b/frontend/src/app.css index 794423a..a50bf27 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -5628,6 +5628,22 @@ a.analysis-split__nav-item { cursor: pointer; color: var(--text1); } +.media-library__card-thumb-hit { + display: block; + width: 100%; + margin: 0; + padding: 0; + border: none; + background: transparent; + cursor: zoom-in; + text-align: left; + font: inherit; + color: inherit; + -webkit-tap-highlight-color: transparent; +} +.media-library__card-thumb-hit:active { + opacity: 0.96; +} .media-library__card-thumb-wrap { aspect-ratio: 1; background: var(--surface2); @@ -5717,6 +5733,21 @@ a.analysis-split__nav-item { overflow: hidden; background: var(--surface2); } +.media-library__table-thumb-hit { + display: block; + margin: 0; + padding: 0; + border: none; + background: transparent; + cursor: zoom-in; + font: inherit; + border-radius: 8px; + -webkit-tap-highlight-color: transparent; +} +.media-library__table-thumb-hit:focus-visible { + outline: 2px solid var(--accent, #3b82f6); + outline-offset: 2px; +} .media-library__table-thumb .media-library__thumb-img, .media-library__table-thumb .media-library__thumb-video { width: 56px; @@ -5865,3 +5896,74 @@ a.analysis-split__nav-item { flex: 1 1 160px; min-width: 0; } + +/* Vorschau-Modal (Vollbild nah) */ +.media-library__overlay--preview { + background: rgba(0, 0, 0, 0.72); + z-index: 210; +} +.media-library__modal--preview { + max-width: min(92vw, 960px); + width: 100%; + max-height: min(94vh, 900px); + display: flex; + flex-direction: column; + overflow: hidden; +} +.media-library__modal--preview .media-library__modal-head { + flex-shrink: 0; +} +.media-library__preview-title { + flex: 1; + min-width: 0; + font-size: 1rem; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: 8px; +} +.media-library__modal--preview .media-library__modal-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.media-library__preview-head-actions { + display: flex; + align-items: center; + gap: 6px; + flex-shrink: 0; +} +.media-library__preview-body { + flex: 1; + min-height: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 12px 16px 20px; + background: var(--surface2); +} +.media-library__preview-img { + max-width: 100%; + max-height: min(78vh, 720px); + width: auto; + height: auto; + object-fit: contain; + border-radius: 8px; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12); +} +.media-library__preview-video { + width: 100%; + max-height: min(78vh, 720px); + border-radius: 8px; + background: #000; +} +.media-library__preview-fallback { + text-align: center; + padding: 24px 16px; + max-width: 360px; +} +.media-library__preview-fallback .btn { + margin-top: 12px; +} diff --git a/frontend/src/pages/MediaLibraryPage.jsx b/frontend/src/pages/MediaLibraryPage.jsx index b3f9067..1f5519d 100644 --- a/frontend/src/pages/MediaLibraryPage.jsx +++ b/frontend/src/pages/MediaLibraryPage.jsx @@ -80,6 +80,14 @@ function MediaThumb({ mediaId, mimeType }) { return
} +function previewDisplayKind(mimeType) { + const m = (mimeType || '').toLowerCase() + if (m.startsWith('image/')) return 'image' + if (m.startsWith('video/')) return 'video' + if (m.includes('pdf')) return 'pdf' + return 'other' +} + export default function MediaLibraryPage() { const { user } = useAuth() const isPlatformAdmin = user?.role === 'admin' || user?.role === 'superadmin' @@ -102,6 +110,7 @@ export default function MediaLibraryPage() { const [bulkClubId, setBulkClubId] = useState('') const [bulkApplyVis, setBulkApplyVis] = useState(false) const [busy, setBusy] = useState(false) + const [preview, setPreview] = useState(null) const loadClubs = useCallback(async () => { try { @@ -143,6 +152,15 @@ export default function MediaLibraryPage() { return () => clearTimeout(t) }, [lifecycle, q, loadItems]) + useEffect(() => { + if (!preview) return + const onKey = (e) => { + if (e.key === 'Escape') setPreview(null) + } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [preview]) + const toggleSel = (id) => { setSelected((prev) => { const n = new Set(prev) @@ -158,6 +176,7 @@ export default function MediaLibraryPage() { } const openEdit = (it) => { + setPreview(null) const p = it.permissions || {} setModal(it) setModalDraft({ @@ -299,8 +318,8 @@ export default function MediaLibraryPage() {

- Veröffentlichte Medien (Verein/Plattform) und eigene Uploads. Bearbeiten und Papierkorb über das - Menü pro Medium — Bulk unten in der Leiste. + Veröffentlichte Medien (Verein/Plattform) und eigene Uploads. Vorschau: Bild oder Video groß + anklicken. Bearbeiten und Papierkorb über das Menü pro Medium — Bulk unten in der Leiste.

@@ -397,9 +416,16 @@ export default function MediaLibraryPage() { > -
- -
+
{it.original_filename || `Medium #${it.id}`} @@ -439,9 +465,16 @@ export default function MediaLibraryPage() { toggleSel(it.id)} /> -
- -
+ {it.original_filename || `#${it.id}`} {it.visibility} @@ -471,6 +504,99 @@ export default function MediaLibraryPage() { ) : null}
+ {preview ? ( +
setPreview(null)} + > +
e.stopPropagation()} + > +
+

+ {preview.original_filename || `Medium #${preview.id}`} +

+
+ + +
+
+
+ {(() => { + const url = resolveMediaAssetFileUrl(preview.id) + const kind = previewDisplayKind(preview.mime_type) + if (!url) { + return

Keine Datei-URL.

+ } + if (kind === 'image') { + return ( + + ) + } + if (kind === 'video') { + return ( + + ) + } + if (kind === 'pdf') { + return ( +
+

PDF — zur Ansicht im neuen Tab öffnen.

+ + PDF öffnen + +
+ ) + } + return ( +
+

+ Vorschau für diesen Typ nicht verfügbar ({preview.mime_type || 'unbekannt'}). +

+ + Datei öffnen + +
+ ) + })()} +
+
+
+ ) : null} + {bulkOpen ? (