Fixes and Changes again
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user