122 lines
5.0 KiB
JavaScript
122 lines
5.0 KiB
JavaScript
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({ name: builtMelody.name, pid: builtMelody.pid });
|
|
} 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)" }}>×</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 || "—"} · {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>
|
|
);
|
|
}
|