Added SpeedCalc and MelodyBuilder. Evaluation Pending

This commit is contained in:
2026-02-22 13:17:54 +02:00
parent 8a8c665dfd
commit 8703c4fe26
27 changed files with 4075 additions and 3 deletions

View File

@@ -0,0 +1,121 @@
import { useState, useEffect } from "react";
import api from "../../api/client";
export default function SelectBuiltMelodyModal({ open, melodyId, onClose, onSuccess }) {
const [melodies, setMelodies] = useState([]);
const [loading, setLoading] = useState(false);
const [assigning, setAssigning] = useState(null); // id of the one being assigned
const [error, setError] = useState("");
useEffect(() => {
if (open) loadMelodies();
}, [open]);
const loadMelodies = async () => {
setLoading(true);
setError("");
try {
const data = await api.get("/builder/melodies");
// Only show those with a built binary
setMelodies((data.melodies || []).filter((m) => m.binary_path));
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleSelect = async (builtMelody) => {
setAssigning(builtMelody.id);
setError("");
try {
// 1. Fetch the .bsm file from the builder endpoint
const token = localStorage.getItem("access_token");
const res = await fetch(`/api${builtMelody.binary_url}`, {
headers: token ? { Authorization: `Bearer ${token}` } : {},
});
if (!res.ok) throw new Error(`Failed to download binary: ${res.statusText}`);
const blob = await res.blob();
const file = new File([blob], `${builtMelody.name}.bsm`, { type: "application/octet-stream" });
// 2. Upload to Firebase Storage via the existing melody upload endpoint
await api.upload(`/melodies/${melodyId}/upload/binary`, file);
// 3. Mark this built melody as assigned to this Firestore melody
await api.post(`/builder/melodies/${builtMelody.id}/assign?firestore_melody_id=${melodyId}`);
onSuccess();
} catch (err) {
setError(err.message);
} finally {
setAssigning(null);
}
};
if (!open) return null;
return (
<div
className="fixed inset-0 flex items-center justify-center z-50"
style={{ backgroundColor: "rgba(0,0,0,0.6)" }}
onClick={(e) => e.target === e.currentTarget && onClose()}
>
<div
className="w-full max-w-2xl rounded-lg border shadow-xl"
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
>
<div className="flex items-center justify-between px-6 py-4 border-b" style={{ borderColor: "var(--border-primary)" }}>
<h2 className="text-lg font-semibold" style={{ color: "var(--text-heading)" }}>Select Built Melody</h2>
<button onClick={onClose} className="text-xl leading-none" style={{ color: "var(--text-muted)" }}>&times;</button>
</div>
<div className="p-6">
{error && (
<div className="text-sm rounded-md p-3 mb-4 border" style={{ backgroundColor: "var(--danger-bg)", borderColor: "var(--danger)", color: "var(--danger-text)" }}>
{error}
</div>
)}
{loading ? (
<div className="text-center py-8" style={{ color: "var(--text-muted)" }}>Loading...</div>
) : melodies.length === 0 ? (
<div className="text-center py-8 text-sm" style={{ color: "var(--text-muted)" }}>
No built binaries found. Go to <strong>Melody Builder</strong> to create one first.
</div>
) : (
<div className="space-y-2 max-h-96 overflow-y-auto">
{melodies.map((m) => (
<div
key={m.id}
className="flex items-center justify-between rounded-lg px-4 py-3 border transition-colors"
style={{ borderColor: "var(--border-primary)", backgroundColor: "var(--bg-primary)" }}
>
<div>
<p className="text-sm font-medium" style={{ color: "var(--text-heading)" }}>{m.name}</p>
<p className="text-xs mt-0.5 font-mono" style={{ color: "var(--text-muted)" }}>
PID: {m.pid || "—"} &nbsp;·&nbsp; {m.steps?.split(",").length || 0} steps
</p>
</div>
<button
onClick={() => handleSelect(m)}
disabled={Boolean(assigning)}
className="px-3 py-1.5 text-xs rounded-md disabled:opacity-50 transition-colors font-medium"
style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
>
{assigning === m.id ? "Uploading..." : "Select & Upload"}
</button>
</div>
))}
</div>
)}
</div>
<div className="flex justify-end px-6 py-4 border-t" style={{ borderColor: "var(--border-primary)" }}>
<button onClick={onClose} className="px-4 py-2 text-sm rounded-md transition-colors" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-primary)" }}>
Cancel
</button>
</div>
</div>
</div>
);
}