import { useEffect, useMemo, useState } from 'react'; import { Box, Button, Stack, Typography, Paper, TextField, MenuItem } from '@mui/material'; type MediaItem = { key: string; size: number; lastModified: string | null; }; export default function MediaLibrary({ onInsert, onSetFeature, showSetFeature, selectionMode, selectedKeys, onToggleSelect, }: { onInsert?: (url: string) => void; onSetFeature?: (url: string) => void; showSetFeature?: boolean; selectionMode?: boolean; selectedKeys?: string[]; onToggleSelect?: (key: string) => void; }) { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [query, setQuery] = useState(''); const [sortBy, setSortBy] = useState<'date_desc' | 'date_asc' | 'name_asc' | 'size_desc'>('date_desc'); const [uploading, setUploading] = useState(false); const [uploadingCount, setUploadingCount] = useState(0); const load = async () => { try { setLoading(true); setError(''); const res = await fetch('/api/media/list?prefix=images/'); if (!res.ok) throw new Error(await res.text()); const data = await res.json(); setItems(Array.isArray(data.items) ? data.items : []); } catch (e: any) { setError(e?.message || 'Failed to load media'); } finally { setLoading(false); } }; useEffect(() => { load(); }, []); // Paste-to-upload clipboard images useEffect(() => { const handler = async (e: ClipboardEvent) => { try { const items = e.clipboardData?.items; if (!items || items.length === 0) return; const files: File[] = []; for (let i = 0; i < items.length; i++) { const it = items[i]; if (it.kind === 'file') { const f = it.getAsFile(); if (f && f.type.startsWith('image/')) files.push(f); } } if (files.length === 0) return; setUploading(true); setUploadingCount(files.length); for (const file of files) { try { const fd = new FormData(); fd.append('image', file, file.name || 'pasted-image.png'); const res = await fetch('/api/media/image', { method: 'POST', body: fd }); if (!res.ok) throw new Error(await res.text()); } catch (err: any) { setError(err?.message || 'Clipboard upload failed'); } } setUploading(false); setUploadingCount(0); await load(); } catch {} }; window.addEventListener('paste', handler); return () => window.removeEventListener('paste', handler); }, []); const del = async (key: string) => { try { setError(''); const res = await fetch('/api/media/obj', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key }), }); if (!res.ok) throw new Error(await res.text()); await load(); } catch (e: any) { setError(e?.message || 'Delete failed'); } }; const filtered = useMemo(() => { const q = query.trim().toLowerCase(); let arr = items.filter(it => it.key.toLowerCase().includes(q)); arr.sort((a, b) => { if (sortBy === 'date_desc') return (new Date(b.lastModified || 0).getTime()) - (new Date(a.lastModified || 0).getTime()); if (sortBy === 'date_asc') return (new Date(a.lastModified || 0).getTime()) - (new Date(b.lastModified || 0).getTime()); if (sortBy === 'size_desc') return (b.size - a.size); // name_asc const an = a.key.split('/').slice(-1)[0].toLowerCase(); const bn = b.key.split('/').slice(-1)[0].toLowerCase(); return an.localeCompare(bn); }); return arr; }, [items, query, sortBy]); const fmtSize = (n: number) => { if (n >= 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + ' MB'; if (n >= 1024) return (n / 1024).toFixed(1) + ' KB'; return n + ' B'; }; const fmtDate = (s: string | null) => s ? new Date(s).toLocaleString() : ''; const copyUrl = async (url: string) => { try { await navigator.clipboard.writeText(url); } catch {} }; return ( setQuery(e.target.value)} /> setSortBy(e.target.value as any)}> Newest Oldest Name Largest Tip: paste an image (Cmd/Ctrl+V) to upload{uploading ? ` — uploading ${uploadingCount}…` : ''} {error && {error}} {filtered.map((it) => { const url = `/api/media/obj?key=${encodeURIComponent(it.key)}`; const selected = !!selectedKeys?.includes(it.key); const name = it.key.split('/').slice(-1)[0]; return ( {name} {name} {fmtSize(it.size)} · {fmtDate(it.lastModified)} {selectionMode && onToggleSelect ? ( ) : ( onInsert && ( ) )} {showSetFeature && onSetFeature && ( )} ); })} {filtered.length === 0 && !loading && ( No images found. )} ); }