Fixes and Changes again

This commit is contained in:
2026-02-22 17:28:27 +02:00
parent 8abb65ac8d
commit ae4b31328f
11 changed files with 1617 additions and 96 deletions

View File

@@ -273,7 +273,8 @@ export default function MelodyDetail() {
<span className="capitalize">{info.melodyTone}</span>
</Field>
<Field label="Steps">{info.steps}</Field>
<Field label="Total Active Notes (bells)">{info.totalNotes}</Field>
<Field label="Total Archetype Notes">{info.totalNotes}</Field>
<Field label="Total Active Bells">{info.totalActiveBells ?? "-"}</Field>
<Field label="Min Speed">{info.minSpeed}</Field>
<Field label="Max Speed">{info.maxSpeed}</Field>
<Field label="Color">
@@ -367,11 +368,36 @@ export default function MelodyDetail() {
</Field>
</div>
<div className="col-span-2 md:col-span-3">
<Field label="Note Assignments">
{settings.noteAssignments?.length > 0
? settings.noteAssignments.join(", ")
: "-"}
</Field>
<dt className="text-xs font-medium uppercase tracking-wide mb-2" style={{ color: "var(--text-muted)" }}>Note Assignments</dt>
<dd>
{settings.noteAssignments?.length > 0 ? (
<div className="flex flex-wrap gap-1.5">
{settings.noteAssignments.map((assignedBell, noteIdx) => (
<div
key={noteIdx}
className="flex flex-col items-center rounded-md border"
style={{
minWidth: "36px",
padding: "4px 6px",
backgroundColor: "var(--bg-card-hover)",
borderColor: "var(--border-primary)",
}}
>
<span className="text-xs font-bold leading-tight" style={{ color: "var(--text-secondary)" }}>
{noteIdx + 1}
</span>
<div className="w-full my-0.5" style={{ height: "1px", backgroundColor: "var(--border-primary)" }} />
<span className="text-xs leading-tight" style={{ color: "var(--text-muted)" }}>
{assignedBell > 0 ? assignedBell : "—"}
</span>
</div>
))}
</div>
) : (
<span style={{ color: "var(--text-muted)" }}>-</span>
)}
<p className="text-xs mt-1" style={{ color: "var(--text-muted)" }}>Top = Note #, Bottom = Assigned Bell</p>
</dd>
</div>
</dl>
</section>
@@ -384,42 +410,62 @@ export default function MelodyDetail() {
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>Files</h2>
<dl className="space-y-4">
<Field label="Binary File">
{files.binary_url ? (() => {
{(() => {
// Prefer the uploaded file URL, fall back to melody.url (legacy/firebase storage URL)
const binaryUrl = files.binary_url || melody.url || null;
if (!binaryUrl) return <span style={{ color: "var(--text-muted)" }}>Not uploaded</span>;
const binaryPid = builtMelody?.pid || melody.pid || "binary";
const binaryFilename = `${binaryPid}.bsm`;
// Derive a display name: for firebase URLs extract the filename portion
let displayName = binaryFilename;
if (!files.binary_url && melody.url) {
try {
const urlPath = decodeURIComponent(new URL(melody.url).pathname);
const parts = urlPath.split("/");
displayName = parts[parts.length - 1] || binaryFilename;
} catch { /* keep binaryFilename */ }
}
const handleDownload = async (e) => {
e.preventDefault();
try {
const token = localStorage.getItem("access_token");
const res = await fetch(files.binary_url, {
const res = await fetch(binaryUrl, {
headers: token ? { Authorization: `Bearer ${token}` } : {},
});
if (!res.ok) throw new Error(`Download failed: ${res.statusText}`);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const objectUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = binaryFilename;
a.href = objectUrl;
a.download = displayName;
a.click();
URL.revokeObjectURL(url);
URL.revokeObjectURL(objectUrl);
} catch (err) {
// surface error in page error state if possible
console.error(err);
}
};
return (
<a
href={files.binary_url}
onClick={handleDownload}
className="underline"
style={{ color: "var(--accent)" }}
>
{binaryFilename}
</a>
<span className="inline-flex items-center gap-2">
<a
href={binaryUrl}
onClick={handleDownload}
className="underline"
style={{ color: "var(--accent)" }}
>
{displayName}
</a>
{!files.binary_url && melody.url && (
<span className="text-xs px-1.5 py-0.5 rounded" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-muted)" }}>
via URL
</span>
)}
</span>
);
})() : (
<span style={{ color: "var(--text-muted)" }}>Not uploaded</span>
)}
})()}
</Field>
<Field label="Audio Preview">
{files.preview_url ? (
@@ -486,6 +532,57 @@ export default function MelodyDetail() {
</section>
)}
{/* Metadata section */}
{melody.metadata && (
<section
className="rounded-lg p-6 border mt-6"
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
>
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>History</h2>
<dl className="grid grid-cols-2 md:grid-cols-4 gap-4">
{melody.metadata.dateCreated && (
<Field label="Date Created">
{new Date(melody.metadata.dateCreated).toLocaleString()}
</Field>
)}
{melody.metadata.createdBy && (
<Field label="Created By">{melody.metadata.createdBy}</Field>
)}
{melody.metadata.dateEdited && (
<Field label="Last Edited">
{new Date(melody.metadata.dateEdited).toLocaleString()}
</Field>
)}
{melody.metadata.lastEditedBy && (
<Field label="Last Edited By">{melody.metadata.lastEditedBy}</Field>
)}
</dl>
</section>
)}
{/* Admin Notes section */}
<section
className="rounded-lg p-6 border mt-6"
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
>
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>Admin Notes</h2>
{(melody.metadata?.adminNotes?.length || 0) > 0 ? (
<div className="space-y-2">
{melody.metadata.adminNotes.map((note, i) => (
<div
key={i}
className="rounded-lg p-3 border text-sm"
style={{ borderColor: "var(--border-primary)", backgroundColor: "var(--bg-primary)", color: "var(--text-primary)" }}
>
{note}
</div>
))}
</div>
) : (
<p className="text-sm" style={{ color: "var(--text-muted)" }}>No admin notes yet. Edit this melody to add notes.</p>
)}
</section>
<PlaybackModal
open={showPlayback}
melody={melody}