Fixed minor issues with the Archetypes
This commit is contained in:
@@ -196,7 +196,8 @@ async def build_binary(melody_id: str) -> BuiltMelodyInDB:
|
|||||||
_ensure_storage_dir()
|
_ensure_storage_dir()
|
||||||
values = steps_string_to_values(row["steps"])
|
values = steps_string_to_values(row["steps"])
|
||||||
|
|
||||||
bsm_path = STORAGE_DIR / f"{melody_id}.bsm"
|
file_name = f"{row['pid']}.bsm" if row.get("pid") else f"{melody_id}.bsm"
|
||||||
|
bsm_path = STORAGE_DIR / file_name
|
||||||
with open(bsm_path, "wb") as f:
|
with open(bsm_path, "wb") as f:
|
||||||
for value in values:
|
for value in values:
|
||||||
value = value & 0xFFFF
|
value = value & 0xFFFF
|
||||||
|
|||||||
@@ -459,12 +459,12 @@ export default function MelodyDetail() {
|
|||||||
const binaryFilename = `${binaryPid}.bsm`;
|
const binaryFilename = `${binaryPid}.bsm`;
|
||||||
|
|
||||||
// Derive a display name: for firebase URLs extract the filename portion
|
// Derive a display name: for firebase URLs extract the filename portion
|
||||||
let displayName = binaryFilename;
|
let downloadName = binaryFilename;
|
||||||
if (!files.binary_url && melody.url) {
|
if (!files.binary_url && melody.url) {
|
||||||
try {
|
try {
|
||||||
const urlPath = decodeURIComponent(new URL(melody.url).pathname);
|
const urlPath = decodeURIComponent(new URL(melody.url).pathname);
|
||||||
const parts = urlPath.split("/");
|
const parts = urlPath.split("/");
|
||||||
displayName = parts[parts.length - 1] || binaryFilename;
|
downloadName = parts[parts.length - 1] || binaryFilename;
|
||||||
} catch { /* keep binaryFilename */ }
|
} catch { /* keep binaryFilename */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ export default function MelodyDetail() {
|
|||||||
const objectUrl = URL.createObjectURL(blob);
|
const objectUrl = URL.createObjectURL(blob);
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = objectUrl;
|
a.href = objectUrl;
|
||||||
a.download = displayName;
|
a.download = downloadName;
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(objectUrl);
|
URL.revokeObjectURL(objectUrl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -493,21 +493,40 @@ export default function MelodyDetail() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<span className="inline-flex flex-col gap-0.5">
|
||||||
<span className="inline-flex items-center gap-2">
|
<span className="inline-flex items-center gap-2">
|
||||||
|
{builtMelody?.name ? (
|
||||||
|
<strong style={{ color: "var(--text-heading)" }}>{builtMelody.name}</strong>
|
||||||
|
) : (
|
||||||
<a
|
<a
|
||||||
href={binaryUrl}
|
href={binaryUrl}
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
className="underline"
|
className="underline"
|
||||||
style={{ color: "var(--accent)" }}
|
style={{ color: "var(--accent)" }}
|
||||||
>
|
>
|
||||||
{displayName}
|
{downloadName}
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
{!files.binary_url && melody.url && (
|
{!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)" }}>
|
<span className="text-xs px-1.5 py-0.5 rounded" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-muted)" }}>
|
||||||
via URL
|
via URL
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
{builtMelody?.name && (
|
||||||
|
<span className="text-xs" style={{ color: "var(--text-muted)" }}>
|
||||||
|
file:{" "}
|
||||||
|
<a
|
||||||
|
href={binaryUrl}
|
||||||
|
onClick={handleDownload}
|
||||||
|
className="underline font-mono"
|
||||||
|
style={{ color: "var(--accent)" }}
|
||||||
|
>
|
||||||
|
{downloadName}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export default function MelodyForm() {
|
|||||||
const [showPlayback, setShowPlayback] = useState(false);
|
const [showPlayback, setShowPlayback] = useState(false);
|
||||||
const [builtMelody, setBuiltMelody] = useState(null);
|
const [builtMelody, setBuiltMelody] = useState(null);
|
||||||
const [assignedBinaryName, setAssignedBinaryName] = useState(null);
|
const [assignedBinaryName, setAssignedBinaryName] = useState(null);
|
||||||
|
const [assignedBinaryPid, setAssignedBinaryPid] = useState(null);
|
||||||
|
|
||||||
// Metadata / Admin Notes
|
// Metadata / Admin Notes
|
||||||
const [adminNotes, setAdminNotes] = useState([]);
|
const [adminNotes, setAdminNotes] = useState([]);
|
||||||
@@ -145,6 +146,7 @@ export default function MelodyForm() {
|
|||||||
const bm = await api.get(`/builder/melodies/for-melody/${id}`);
|
const bm = await api.get(`/builder/melodies/for-melody/${id}`);
|
||||||
setBuiltMelody(bm || null);
|
setBuiltMelody(bm || null);
|
||||||
setAssignedBinaryName(bm?.name || null);
|
setAssignedBinaryName(bm?.name || null);
|
||||||
|
setAssignedBinaryPid(bm?.pid || null);
|
||||||
} catch {
|
} catch {
|
||||||
setBuiltMelody(null);
|
setBuiltMelody(null);
|
||||||
}
|
}
|
||||||
@@ -391,7 +393,12 @@ export default function MelodyForm() {
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate("/melodies")}
|
onClick={async () => {
|
||||||
|
if (savedMelodyId) {
|
||||||
|
try { await api.delete(`/melodies/${savedMelodyId}`); } catch { /* best-effort */ }
|
||||||
|
}
|
||||||
|
navigate("/melodies");
|
||||||
|
}}
|
||||||
className="px-4 py-2 text-sm rounded-md transition-colors"
|
className="px-4 py-2 text-sm rounded-md transition-colors"
|
||||||
style={{ backgroundColor: "var(--btn-neutral)", color: "var(--text-white)" }}
|
style={{ backgroundColor: "var(--btn-neutral)", color: "var(--text-white)" }}
|
||||||
>
|
>
|
||||||
@@ -648,12 +655,21 @@ export default function MelodyForm() {
|
|||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Binary File (.bsm / .bin)</label>
|
<label className="block text-sm font-medium mb-1" style={labelStyle}>Binary File (.bsm / .bin)</label>
|
||||||
{existingFiles.binary_url && (
|
{existingFiles.binary_url && (
|
||||||
<p className="text-xs mb-1" style={{ color: "var(--success)" }}>
|
<div className="text-xs mb-1" style={{ color: "var(--success)" }}>
|
||||||
{assignedBinaryName
|
{assignedBinaryName ? (
|
||||||
? <><strong>{assignedBinaryName}.bsm</strong> is set. Selecting a new file will replace it.</>
|
<>
|
||||||
: "Current file uploaded. Selecting a new file will replace it."}
|
<p><strong>{assignedBinaryName}</strong> has been assigned. Selecting a new file will replace it.</p>
|
||||||
|
{assignedBinaryPid && (
|
||||||
|
<p className="mt-0.5" style={{ color: "var(--text-muted)" }}>
|
||||||
|
filename: <span className="font-mono">{assignedBinaryPid}.bsm</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p>Current file uploaded. Selecting a new file will replace it.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<input type="file" accept=".bin,.bsm" onChange={(e) => setBinaryFile(e.target.files[0] || null)} className="w-full text-sm" style={mutedStyle} />
|
<input type="file" accept=".bin,.bsm" onChange={(e) => setBinaryFile(e.target.files[0] || null)} className="w-full text-sm" style={mutedStyle} />
|
||||||
<div className="flex gap-2 mt-2">
|
<div className="flex gap-2 mt-2">
|
||||||
<button
|
<button
|
||||||
@@ -806,6 +822,7 @@ export default function MelodyForm() {
|
|||||||
onSuccess={(archetype) => {
|
onSuccess={(archetype) => {
|
||||||
setShowSelectBuilt(false);
|
setShowSelectBuilt(false);
|
||||||
setAssignedBinaryName(archetype.name);
|
setAssignedBinaryName(archetype.name);
|
||||||
|
setAssignedBinaryPid(archetype.pid || null);
|
||||||
if (!pid.trim() && archetype.pid) setPid(archetype.pid);
|
if (!pid.trim() && archetype.pid) setPid(archetype.pid);
|
||||||
if (archetype.steps != null) updateInfo("steps", archetype.steps);
|
if (archetype.steps != null) updateInfo("steps", archetype.steps);
|
||||||
if (archetype.totalNotes != null) updateInfo("totalNotes", archetype.totalNotes);
|
if (archetype.totalNotes != null) updateInfo("totalNotes", archetype.totalNotes);
|
||||||
@@ -838,6 +855,7 @@ export default function MelodyForm() {
|
|||||||
onSuccess={(archetype) => {
|
onSuccess={(archetype) => {
|
||||||
setShowBuildOnTheFly(false);
|
setShowBuildOnTheFly(false);
|
||||||
setAssignedBinaryName(archetype.name);
|
setAssignedBinaryName(archetype.name);
|
||||||
|
setAssignedBinaryPid(archetype.pid || null);
|
||||||
if (!pid.trim() && archetype.pid) setPid(archetype.pid);
|
if (!pid.trim() && archetype.pid) setPid(archetype.pid);
|
||||||
if (archetype.steps != null) updateInfo("steps", archetype.steps);
|
if (archetype.steps != null) updateInfo("steps", archetype.steps);
|
||||||
if (archetype.totalNotes != null) updateInfo("totalNotes", archetype.totalNotes);
|
if (archetype.totalNotes != null) updateInfo("totalNotes", archetype.totalNotes);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export default function SelectArchetypeModal({ open, melodyId, currentMelody, cu
|
|||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`Failed to download binary: ${res.statusText}`);
|
if (!res.ok) throw new Error(`Failed to download binary: ${res.statusText}`);
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
const file = new File([blob], `${archetype.name}.bsm`, { type: "application/octet-stream" });
|
const file = new File([blob], `${archetype.pid || archetype.name}.bsm`, { type: "application/octet-stream" });
|
||||||
|
|
||||||
await api.upload(`/melodies/${melodyId}/upload/binary`, file);
|
await api.upload(`/melodies/${melodyId}/upload/binary`, file);
|
||||||
await api.post(`/builder/melodies/${archetype.id}/assign?firestore_melody_id=${melodyId}`);
|
await api.post(`/builder/melodies/${archetype.id}/assign?firestore_melody_id=${melodyId}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user