From be0b3a5a5a6f95aec635aafa984c48075e0833ca Mon Sep 17 00:00:00 2001 From: bonamin Date: Mon, 23 Feb 2026 11:03:03 +0200 Subject: [PATCH] CODEX - Probably fixed the download buttons --- frontend/src/melodies/MelodyDetail.jsx | 19 ++-- frontend/src/melodies/MelodyForm.jsx | 44 +++++----- frontend/src/melodies/MelodyList.jsx | 112 +++++++++++++++++------- frontend/src/melodies/PlaybackModal.jsx | 16 +++- 4 files changed, 130 insertions(+), 61 deletions(-) diff --git a/frontend/src/melodies/MelodyDetail.jsx b/frontend/src/melodies/MelodyDetail.jsx index 62b9c55..5f1c6bc 100644 --- a/frontend/src/melodies/MelodyDetail.jsx +++ b/frontend/src/melodies/MelodyDetail.jsx @@ -532,8 +532,13 @@ export default function MelodyDetail() { {(() => { - // Prefer the uploaded file URL, fall back to melody.url (legacy/firebase storage URL) - const binaryUrl = normalizeFileUrl(files.binary_url || melody.url || null); + // Common source of truth: assigned archetype binary first, then melody URL, then uploaded file URL. + const binaryUrl = normalizeFileUrl( + (builtMelody?.binary_url ? `/api${builtMelody.binary_url}` : null) || + melody.url || + files.binary_url || + null + ); if (!binaryUrl) return Not uploaded; const binaryPid = builtMelody?.pid || melody.pid || "binary"; @@ -541,7 +546,7 @@ export default function MelodyDetail() { // Derive a display name: for firebase URLs extract the filename portion let downloadName = binaryFilename; - if (!files.binary_url && melody.url) { + if (!builtMelody?.binary_url && !files.binary_url && melody.url) { try { const urlPath = decodeURIComponent(new URL(melody.url).pathname); const parts = urlPath.split("/"); @@ -577,7 +582,7 @@ export default function MelodyDetail() { a.click(); URL.revokeObjectURL(objectUrl); } catch (err) { - setError(err.message); + window.open(binaryUrl, "_blank", "noopener,noreferrer"); } }; @@ -607,6 +612,9 @@ export default function MelodyDetail() { > View + + {info.totalNotes ?? 0} active notes + {!files.binary_url && melody.url && ( via URL @@ -626,9 +634,6 @@ export default function MelodyDetail() { )} - - {info.totalNotes ?? 0} active notes - ); })()} diff --git a/frontend/src/melodies/MelodyForm.jsx b/frontend/src/melodies/MelodyForm.jsx index 35cee9c..131a390 100644 --- a/frontend/src/melodies/MelodyForm.jsx +++ b/frontend/src/melodies/MelodyForm.jsx @@ -244,7 +244,7 @@ export default function MelodyForm() { a.click(); URL.revokeObjectURL(objectUrl); } catch (err) { - setError(err.message); + window.open(fileUrl, "_blank", "noopener,noreferrer"); } }; @@ -751,20 +751,25 @@ export default function MelodyForm() {
{(() => { - const binaryUrl = normalizeFileUrl(existingFiles.binary_url || url || null); + const binaryUrl = normalizeFileUrl( + (builtMelody?.binary_url ? `/api${builtMelody.binary_url}` : null) || + url || + existingFiles.binary_url || + null + ); const fallback = assignedBinaryPid ? `${assignedBinaryPid}.bsm` : (pid ? `${pid}.bsm` : "Click to Download"); const binaryName = resolveFilename(binaryUrl, fallback); return (
{binaryUrl ? ( - + {binaryName} ) : ( No binary uploaded )} {binaryUrl && ( -
+
+ {information.totalNotes ?? 0} active notes
)} -
{information.totalNotes ?? 0} active notes
); })()} @@ -794,25 +799,19 @@ export default function MelodyForm() { onChange={(e) => setBinaryFile(e.target.files[0] || null)} className="hidden" /> - - {binaryFile && ( -

- selected: {binaryFile.name} -

- )} -
+
+
+ {binaryFile && ( +

+ selected: {binaryFile.name} +

+ )}
diff --git a/frontend/src/melodies/MelodyList.jsx b/frontend/src/melodies/MelodyList.jsx index 454f8cd..031ef7c 100644 --- a/frontend/src/melodies/MelodyList.jsx +++ b/frontend/src/melodies/MelodyList.jsx @@ -140,31 +140,6 @@ function formatRelativeTime(isoValue) { return `${diffYears} year${diffYears === 1 ? "" : "s"} ago`; } -function getBinaryUrl(row) { - const candidate = row?.url; - if (!candidate || typeof candidate !== "string") return null; - if (candidate.startsWith("http") || candidate.startsWith("/api")) return candidate; - if (candidate.startsWith("/")) return `/api${candidate}`; - return `/api/${candidate}`; -} - -function getBinaryFilename(row) { - const url = getBinaryUrl(row); - if (!url) return null; - - try { - const parsed = new URL(url, window.location.origin); - const path = decodeURIComponent(parsed.pathname || ""); - const parts = path.split("/"); - const name = parts[parts.length - 1]; - if (name && name.toLowerCase().endsWith(".bsm")) return name; - } catch { - // fallback below - } - - return row?.pid ? `${row.pid}.bsm` : "melody.bsm"; -} - function toSafeCppSymbol(input, fallback = "melody") { const base = String(input || "").trim().toLowerCase(); const cleaned = base.replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, ""); @@ -288,6 +263,7 @@ export default function MelodyList() { const [showOfflineModal, setShowOfflineModal] = useState(false); const [builtInSavingId, setBuiltInSavingId] = useState(null); const [viewRow, setViewRow] = useState(null); + const [builtMap, setBuiltMap] = useState({}); const columnPickerRef = useRef(null); const creatorPickerRef = useRef(null); const navigate = useNavigate(); @@ -355,6 +331,62 @@ export default function MelodyList() { fetchMelodies(); }, [search, typeFilter, toneFilter, statusFilter]); + useEffect(() => { + let canceled = false; + const loadBuiltAssignments = async () => { + if (!melodies.length) { + setBuiltMap({}); + return; + } + const pairs = await Promise.all( + melodies.map(async (m) => { + try { + const bm = await api.get(`/builder/melodies/for-melody/${m.id}`); + return [m.id, bm || null]; + } catch { + return [m.id, null]; + } + }) + ); + if (canceled) return; + const next = {}; + for (const [id, bm] of pairs) next[id] = bm; + setBuiltMap(next); + }; + loadBuiltAssignments(); + return () => { canceled = true; }; + }, [melodies]); + + const resolveEffectiveBinary = (row) => { + const built = builtMap[row?.id] || null; + const candidate = built?.binary_url + ? `/api${built.binary_url}` + : row?.url || null; + const url = candidate + ? (candidate.startsWith("http") || candidate.startsWith("/api") + ? candidate + : candidate.startsWith("/") + ? `/api${candidate}` + : `/api/${candidate}`) + : null; + const source = built?.binary_url ? "Archetype" : (url ? "Melody URL" : null); + const filename = (() => { + if (!url) return null; + try { + const parsed = new URL(url, window.location.origin); + const path = decodeURIComponent(parsed.pathname || ""); + const parts = path.split("/"); + const name = parts[parts.length - 1]; + if (name) return name; + } catch { + // fallback below + } + if (built?.pid) return `${built.pid}.bsm`; + return row?.pid ? `${row.pid}.bsm` : "melody.bsm"; + })(); + return { url, filename, source, built }; + }; + const handleDelete = async () => { if (!deleteTarget) return; try { @@ -396,7 +428,8 @@ export default function MelodyList() { const downloadBinary = async (e, row) => { e.stopPropagation(); - const binaryUrl = getBinaryUrl(row); + const resolved = resolveEffectiveBinary(row); + const binaryUrl = resolved.url; if (!binaryUrl) return; try { @@ -422,11 +455,16 @@ export default function MelodyList() { const objectUrl = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = objectUrl; - a.download = getBinaryFilename(row) || "melody.bsm"; + a.download = resolved.filename || "melody.bsm"; a.click(); URL.revokeObjectURL(objectUrl); } catch (err) { - setError(err.message); + const fallbackUrl = resolveEffectiveBinary(row).url; + if (fallbackUrl) { + window.open(fallbackUrl, "_blank", "noopener,noreferrer"); + } else { + setError(err.message); + } } }; @@ -807,8 +845,9 @@ export default function MelodyList() { ); } case "binaryFile": { - const binaryUrl = getBinaryUrl(row); - const filename = getBinaryFilename(row); + const resolved = resolveEffectiveBinary(row); + const binaryUrl = resolved.url; + const filename = resolved.filename; const totalNotes = info.totalNotes ?? 0; if (!binaryUrl) { return ( @@ -820,7 +859,14 @@ export default function MelodyList() { } return ( - {filename || "binary.bsm"} + + {filename || "binary.bsm"} + {resolved.source && ( + + {resolved.source} + + )} +