import { useState } from "react"; import api from "../../api/client"; const labelStyle = { color: "var(--text-secondary)" }; const mutedStyle = { color: "var(--text-muted)" }; const inputClass = "w-full px-3 py-2 rounded-md text-sm border"; function countSteps(stepsStr) { if (!stepsStr || !stepsStr.trim()) return 0; return stepsStr.trim().split(",").length; } export default function BuildOnTheFlyModal({ open, melodyId, defaultName, defaultPid, onClose, onSuccess }) { const [name, setName] = useState(defaultName || ""); const [pid, setPid] = useState(defaultPid || ""); const [steps, setSteps] = useState(""); const [building, setBuilding] = useState(false); const [error, setError] = useState(""); const [statusMsg, setStatusMsg] = useState(""); if (!open) return null; const handleBuildAndUpload = async () => { if (!name.trim()) { setError("Name is required."); return; } if (!steps.trim()) { setError("Steps are required."); return; } setBuilding(true); setError(""); setStatusMsg(""); let builtId = null; try { // Step 1: Create the built melody record setStatusMsg("Creating melody record..."); const created = await api.post("/builder/melodies", { name: name.trim(), pid: pid.trim(), steps: steps.trim(), }); builtId = created.id; // Step 2: Build the binary setStatusMsg("Building binary..."); const built = await api.post(`/builder/melodies/${builtId}/build-binary`); // Step 3: Fetch the .bsm file and upload to Firebase Storage setStatusMsg("Uploading to cloud storage..."); const token = localStorage.getItem("access_token"); const res = await fetch(`/api${built.binary_url}`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }); if (!res.ok) throw new Error(`Failed to fetch binary: ${res.statusText}`); const blob = await res.blob(); const file = new File([blob], `${name.trim()}.bsm`, { type: "application/octet-stream" }); await api.upload(`/melodies/${melodyId}/upload/binary`, file); // Step 4: Assign to this melody setStatusMsg("Linking to melody..."); await api.post(`/builder/melodies/${builtId}/assign?firestore_melody_id=${melodyId}`); setStatusMsg("Done!"); onSuccess({ name: name.trim(), pid: pid.trim() }); } catch (err) { setError(err.message); setStatusMsg(""); } finally { setBuilding(false); } }; return (
e.target === e.currentTarget && !building && onClose()} >

Build on the Fly

Enter steps, build binary, and upload — all in one step.

{!building && ( )}
{error && (
{error}
)}
setName(e.target.value)} placeholder="e.g. Doksologia_3k" className={inputClass} disabled={building} />
setPid(e.target.value)} placeholder="e.g. builtin_doksologia_3k" className={inputClass} disabled={building} />
{countSteps(steps)} steps