shinkan-jinkendo/frontend/src/components/ExerciseAttachmentMediaStrip.jsx

153 lines
6.0 KiB
React
Raw Normal View History

/**
* Nur Medien, die noch nicht im Fließtext eingebettet sind — ohne Doppel-Darstellung.
*/
import React, { useMemo, useState } from 'react'
import ExerciseMediaEmbed from './ExerciseMediaEmbed'
import ExerciseMediaThumbTile from './ExerciseMediaThumbTile'
import ReportContentModal from './ReportContentModal'
import { resolveExerciseMediaFileUrl } from '../utils/exerciseMediaUrl'
import {
collectInlineExerciseMediaIdsFromExercise,
} from '../utils/exerciseInlineMediaRefs'
function isTrashHidden(m) {
return String(m?.asset_lifecycle_state || 'active').toLowerCase() === 'trash_hidden'
}
export default function ExerciseAttachmentMediaStrip({ exerciseId, exercise }) {
const [preview, setPreview] = useState(null)
const [reportTarget, setReportTarget] = useState(null)
const inlineIds = useMemo(() => collectInlineExerciseMediaIdsFromExercise(exercise), [exercise])
const orphans = useMemo(() => {
const list = (exercise?.media || []).filter((m) => m && !isTrashHidden(m))
return list.filter((m) => !inlineIds.has(Number(m.id)))
}, [exercise, inlineIds])
if (!orphans.length || exerciseId == null) return null
return (
<section className="card exercise-detail-section exercise-attachment-media-strip">
<h2>Angehängte Medien</h2>
<p style={{ marginTop: '6px', color: 'var(--text2)', fontSize: '0.88rem' }}>
Hier erscheinen nur Verknüpfungen, die noch nicht im Fließtext eingebettet sind (reine Material-Anhänge).
</p>
<div className="exercise-orphan-media-grid">
{orphans.map((m) => {
const lc = String(m.asset_lifecycle_state || 'active').toLowerCase()
const caption = (m.title || '').trim() || (m.original_filename || '').trim() || `Medium #${m.id}`
return (
<article key={m.id} className="exercise-orphan-media-card">
<div className="exercise-orphan-media-card__head">
<ExerciseMediaThumbTile
exerciseId={exerciseId}
media={m}
onOpenPreview={setPreview}
size={88}
/>
<div className="exercise-orphan-media-card__meta">
<strong className="exercise-orphan-media-card__title">{caption}</strong>
<span className="exercise-orphan-media-card__sub">
#{m.id}
{m.embed_platform ? ` · ${m.embed_platform}` : ''}
{m.media_type ? ` · ${m.media_type}` : ''}
</span>
{lc === 'trash_soft' && (
<span className="exercise-orphan-media-card__warn">Papierkorb (Stufe 1)</span>
)}
</div>
</div>
<ExerciseMediaEmbed exerciseId={exerciseId} media={m} layoutSize="medium" />
{(m.asset_lifecycle_state || 'active') === 'active' && !m.asset_legal_hold_active && (
<button
type="button"
onClick={() => setReportTarget(m)}
style={{
background: 'none',
border: 'none',
padding: '4px 0 0',
cursor: 'pointer',
fontSize: '0.72rem',
color: 'var(--text3)',
textDecoration: 'underline',
textAlign: 'left',
}}
>
Inhalt melden
</button>
)}
</article>
)
})}
</div>
{reportTarget && (
<ReportContentModal
targetType="media_asset"
targetId={reportTarget.media_asset_id || reportTarget.id}
targetLabel={reportTarget.title || reportTarget.original_filename || `Medium #${reportTarget.id}`}
onClose={() => setReportTarget(null)}
/>
)}
{preview && (
<div
role="dialog"
aria-modal="true"
aria-label="Medienvorschau"
style={{
position: 'fixed',
inset: 0,
background: 'rgba(0,0,0,0.55)',
zIndex: 1001,
overflow: 'auto',
padding: '16px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
onClick={() => setPreview(null)}
onKeyDown={(e) => e.key === 'Escape' && setPreview(null)}
>
<div
className="card"
style={{ maxWidth: 720, width: '100%', maxHeight: '90vh', overflow: 'auto' }}
onClick={(e) => e.stopPropagation()}
>
<h3 style={{ marginTop: 0, fontSize: '1.05rem' }}>Vorschau</h3>
{preview.embed_url ? (
<p style={{ fontSize: '14px', wordBreak: 'break-all' }}>
<a href={preview.embed_url} target="_blank" rel="noreferrer">
{preview.embed_url}
</a>
</p>
) : preview.mime_type?.startsWith('video/') || preview.media_type === 'video' ? (
<video
src={resolveExerciseMediaFileUrl(exerciseId, preview)}
controls
style={{ width: '100%', borderRadius: '8px', maxHeight: '70vh' }}
/>
) : preview.mime_type?.startsWith('image/') || preview.media_type === 'image' ? (
<img
alt=""
src={resolveExerciseMediaFileUrl(exerciseId, preview)}
style={{ maxWidth: '100%', borderRadius: '8px', maxHeight: '70vh', objectFit: 'contain' }}
/>
) : (
<p style={{ fontSize: '14px' }}>
<a href={resolveExerciseMediaFileUrl(exerciseId, preview)} target="_blank" rel="noreferrer">
Datei öffnen
</a>
</p>
)}
<div style={{ marginTop: '16px' }}>
<button type="button" className="btn btn-secondary" onClick={() => setPreview(null)}>
Schließen
</button>
</div>
</div>
</div>
)}
</section>
)
}