/** * Modal: Medium aus Archiv verknüpfen oder neue Datei hochladen, dann Inline-Platzhalter §11 einfügen. */ import React, { useEffect, useState, useCallback } from 'react' import api from '../utils/api' import { INLINE_MEDIA_SIZES, DEFAULT_INLINE_MEDIA_SIZE, sanitizeInlineMediaSize, } from '../constants/inlineExerciseMedia' /** MIME/Dateiname → Übungs-media_type */ function inferExerciseMediaType(file) { if (!file) return 'image' const mime = (file.type || '').toLowerCase() if (mime.startsWith('image/')) return 'image' if (mime.startsWith('video/')) return 'video' if (mime === 'application/pdf' || mime.includes('pdf')) return 'document' const name = (file.name || '').toLowerCase() if (/\.(mp4|webm|mov|mkv|avi|m4v|mpeg|mpg)$/.test(name)) return 'video' if (/\.(jpg|jpeg|png|gif|webp|bmp|svg)$/.test(name)) return 'image' if (/\.pdf$/.test(name)) return 'document' return 'image' } /** * @param {{ * open: boolean, * onClose: () => void, * exerciseId: number, * onMediaListChanged: () => Promise, * onInserted: (exerciseMediaId: number, displaySize: string) => void, * }} props */ export default function ExerciseInlineFileMediaModal({ open, onClose, exerciseId, onMediaListChanged, onInserted, }) { const [tab, setTab] = useState('library') const [q, setQ] = useState('') const [loading, setLoading] = useState(false) const [items, setItems] = useState([]) const [err, setErr] = useState(null) const [selectedAssetId, setSelectedAssetId] = useState(null) const [busy, setBusy] = useState(false) const [uploadFile, setUploadFile] = useState(null) const [uploadTitle, setUploadTitle] = useState('') const [displaySize, setDisplaySize] = useState(DEFAULT_INLINE_MEDIA_SIZE) const loadAssets = useCallback(async () => { setLoading(true) setErr(null) try { const res = await api.listMediaAssets({ q: q.trim() || undefined, limit: 48, lifecycle: 'active', }) setItems(Array.isArray(res.items) ? res.items : []) } catch (e) { setErr(e.message || String(e)) setItems([]) } finally { setLoading(false) } }, [q]) useEffect(() => { if (!open) return undefined setTab('library') setSelectedAssetId(null) setUploadFile(null) setUploadTitle('') setDisplaySize(DEFAULT_INLINE_MEDIA_SIZE) setErr(null) const t = setTimeout(loadAssets, 280) return () => clearTimeout(t) }, [open]) useEffect(() => { if (!open || tab !== 'library') return undefined const t = setTimeout(loadAssets, 300) return () => clearTimeout(t) }, [q, open, tab, loadAssets]) const handleLinkSelected = async () => { if (!selectedAssetId) { alert('Bitte ein Archiv-Medium auswählen.') return } const size = sanitizeInlineMediaSize(displaySize) setBusy(true) setErr(null) try { const row = await api.attachExerciseMediaFromAsset(exerciseId, { media_asset_id: selectedAssetId, title: '', description: '', context: 'ablauf', is_primary: false, }) const mid = row?.id if (mid == null) { throw new Error('Antwort ohne exercise_media-ID') } await onMediaListChanged() onInserted(Number(mid), size) onClose() } catch (e) { const msg = e.message || String(e) setErr(msg) alert(msg) } finally { setBusy(false) } } const handleUploadAndInsert = async () => { if (!uploadFile) { alert('Bitte eine Datei wählen.') return } const size = sanitizeInlineMediaSize(displaySize) const inferred = inferExerciseMediaType(uploadFile) const fd = new FormData() fd.append('file', uploadFile) fd.append('media_type', inferred) fd.append('title', uploadTitle.trim()) fd.append('description', '') fd.append('context', 'ablauf') fd.append('is_primary', 'false') setBusy(true) setErr(null) try { const row = await api.uploadExerciseMedia(exerciseId, fd) const mid = row?.id if (mid == null) { throw new Error('Antwort ohne exercise_media-ID') } await onMediaListChanged() onInserted(Number(mid), size) setUploadFile(null) setUploadTitle('') onClose() } catch (e) { if (e.code === 'MEDIA_ASSET_IN_TRASH' && e.payload?.media_asset_id != null) { alert( 'Dieselbe Datei existiert bereits im Papierkorb — bitte in der Medienbibliothek reaktivieren oder eine andere Datei wählen.', ) } else { alert(e.message || String(e)) } setErr(e.message || String(e)) } finally { setBusy(false) } } if (!open) return null return (
e.target === e.currentTarget && !busy && onClose()}>
e.stopPropagation()} >

Medium im Textfeld

{tab === 'library' && ( <> setQ(e.target.value)} placeholder="Name, Tag, © …" disabled={busy} /> {loading ?

Laden…

: null} {err && tab === 'library' && !loading ? (

{err}

) : null}
{items.map((it) => { const id = it.id const selected = selectedAssetId === id const label = it.original_filename || it.copyright_notice || `Archiv #${id}` return ( ) })}
{!loading && items.length === 0 ? (

Keine Treffer — Suche anpassen oder „Neu hochladen“.

) : null} )} {tab === 'upload' && ( <> { const f = e.target.files?.[0] || null setUploadFile(f) e.target.value = '' }} /> {uploadFile ? (

{uploadFile.name}

) : null} setUploadTitle(e.target.value)} disabled={busy} /> )}
{tab === 'library' ? ( ) : ( )}
) }