/* Tab: Música de fondo — biblioteca de carpetas + canciones para el montaje de vídeos */ const MUSIC_MAX_BYTES = 30 * 1024 * 1024; function formatDuration(sec) { sec = Math.round(sec || 0); const m = Math.floor(sec / 60); const s = sec % 60; return `${m}:${String(s).padStart(2, "0")}`; } function NewMusicFolderModal({ onClose, onCreated }) { const [name, setName] = React.useState(""); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(""); async function handleCreate(e) { e.preventDefault(); if (!name.trim()) { setError("El nombre es obligatorio"); return; } setLoading(true); setError(""); try { const r = await fetch("/api/music/folders", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: name.trim() }), }); const d = await r.json(); if (r.ok && d.success) { onCreated(d.folder); onClose(); } else setError(d.detail || "Error al crear la carpeta"); } catch { setError("Error de conexión"); } finally { setLoading(false); } } return (
e.stopPropagation()}>

Nueva carpeta

setName(e.target.value)} placeholder="Lo-fi relajante" autoFocus/>
{error &&
{error}
}
); } function MusicLibraryTab() { const [folders, setFolders] = React.useState([]); const [selectedFolderId, setSelectedFolderId] = React.useState(null); const [tracks, setTracks] = React.useState([]); const [loadingTracks, setLoadingTracks] = React.useState(false); const [uploading, setUploading] = React.useState(false); const [uploadError, setUploadError] = React.useState(""); const [showNewFolder, setShowNewFolder] = React.useState(false); const [playingId, setPlayingId] = React.useState(null); const audioRef = React.useRef(null); const fileInputRef = React.useRef(null); function fetchFolders() { fetch("/api/music/folders", { credentials: "same-origin" }) .then((r) => r.json()) .then((d) => { const list = d.folders || []; setFolders(list); setSelectedFolderId((prev) => prev && list.some((f) => f.id === prev) ? prev : (list[0]?.id || null)); }) .catch(() => {}); } React.useEffect(() => { fetchFolders(); }, []); React.useEffect(() => { if (!selectedFolderId) { setTracks([]); return; } setLoadingTracks(true); fetch(`/api/music/tracks?folder_id=${selectedFolderId}`, { credentials: "same-origin" }) .then((r) => r.json()) .then((d) => setTracks(d.tracks || [])) .catch(() => setTracks([])) .finally(() => setLoadingTracks(false)); }, [selectedFolderId]); async function deleteFolder(folder) { if (!confirm(`¿Borrar la carpeta "${folder.name}" y todas sus canciones?`)) return; const r = await fetch(`/api/music/folders/${folder.id}`, { method: "DELETE", credentials: "same-origin" }); if (r.ok) { const remaining = folders.filter((f) => f.id !== folder.id); setFolders(remaining); if (selectedFolderId === folder.id) setSelectedFolderId(remaining[0]?.id || null); } } async function deleteTrack(track) { if (!confirm(`¿Borrar "${track.original_name}"?`)) return; const r = await fetch(`/api/music/tracks/${track.id}`, { method: "DELETE", credentials: "same-origin" }); if (r.ok) setTracks((prev) => prev.filter((t) => t.id !== track.id)); } function playPreview(url, id) { if (audioRef.current) audioRef.current.pause(); if (playingId === id) { setPlayingId(null); return; } const a = new Audio(url); audioRef.current = a; a.play().catch(() => {}); setPlayingId(id); a.onended = () => setPlayingId(null); } async function handleUpload(e) { const file = e.target.files && e.target.files[0]; e.target.value = ""; if (!file || !selectedFolderId) return; setUploadError(""); if (file.size > MUSIC_MAX_BYTES) { setUploadError("La canción no debe superar 30MB"); return; } setUploading(true); try { const fd = new FormData(); fd.append("file", file); fd.append("folder_id", selectedFolderId); const r = await fetch("/api/music/tracks", { method: "POST", credentials: "same-origin", body: fd }); const d = await r.json(); if (r.ok && d.success) setTracks((prev) => [d.track, ...prev]); else setUploadError(d.detail || "Error al subir la canción"); } catch { setUploadError("Error de conexión"); } finally { setUploading(false); } } const selectedFolder = folders.find((f) => f.id === selectedFolderId) || null; return (
{showNewFolder && ( setShowNewFolder(false)} onCreated={(f) => { setFolders((prev) => [f, ...prev]); setSelectedFolderId(f.id); }}/> )} {/* Panel izquierdo: carpetas */}

Carpetas

{folders.length === 0 && (
Aún no tienes carpetas. Crea una para empezar a subir música.
)}
{folders.map((f) => (
setSelectedFolderId(f.id)} className="row" style={{ justifyContent: "space-between", cursor: "pointer", padding: "8px 10px", borderRadius: 8, background: selectedFolderId === f.id ? "var(--accent-softer)" : "transparent", color: selectedFolderId === f.id ? "var(--text)" : "var(--text-2)", }}> {f.name}
))}
{/* Panel derecho: canciones de la carpeta activa */}

{selectedFolder ? selectedFolder.name : "Selecciona una carpeta"}

{selectedFolder && ( <> )}
{uploadError && (
{uploadError}
)} {!selectedFolder && (
Crea o elige una carpeta a la izquierda.
)} {selectedFolder && loadingTracks && (
Cargando…
)} {selectedFolder && !loadingTracks && tracks.length === 0 && (
Esta carpeta no tiene canciones todavía. Máximo 30MB por canción.
)} {selectedFolder && !loadingTracks && tracks.length > 0 && (
{tracks.map((t) => (
{t.original_name}
{formatDuration(t.duration)}
))}
)}
); } Object.assign(window, { MusicLibraryTab });