import { useState, useEffect, useRef } from "react"; import { useNavigate, useParams } from "react-router-dom"; import api from "../../api/client"; const sectionStyle = { backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }; 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 BuilderForm() { const { id } = useParams(); const isEdit = Boolean(id); const navigate = useNavigate(); const [name, setName] = useState(""); const [pid, setPid] = useState(""); const [steps, setSteps] = useState(""); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [buildingBinary, setBuildingBinary] = useState(false); const [buildingBuiltin, setBuildingBuiltin] = useState(false); const [error, setError] = useState(""); const [successMsg, setSuccessMsg] = useState(""); const [binaryBuilt, setBinaryBuilt] = useState(false); const [binaryUrl, setBinaryUrl] = useState(null); const [progmemCode, setProgmemCode] = useState(""); const [copied, setCopied] = useState(false); const codeRef = useRef(null); useEffect(() => { if (isEdit) loadMelody(); }, [id]); const loadMelody = async () => { setLoading(true); try { const data = await api.get(`/builder/melodies/${id}`); setName(data.name || ""); setPid(data.pid || ""); setSteps(data.steps || ""); setBinaryBuilt(Boolean(data.binary_path)); setBinaryUrl(data.binary_url || null); setProgmemCode(data.progmem_code || ""); } catch (err) { setError(err.message); } finally { setLoading(false); } }; const handleSave = async () => { if (!name.trim()) { setError("Name is required."); return; } if (!steps.trim()) { setError("Steps are required."); return; } setSaving(true); setError(""); setSuccessMsg(""); try { const body = { name: name.trim(), pid: pid.trim(), steps: steps.trim() }; if (isEdit) { await api.put(`/builder/melodies/${id}`, body); setSuccessMsg("Saved."); } else { const created = await api.post("/builder/melodies", body); navigate(`/melodies/builder/${created.id}`, { replace: true }); } } catch (err) { setError(err.message); } finally { setSaving(false); } }; const handleBuildBinary = async () => { if (!isEdit) { setError("Save the melody first before building."); return; } setBuildingBinary(true); setError(""); setSuccessMsg(""); try { const data = await api.post(`/builder/melodies/${id}/build-binary`); setBinaryBuilt(Boolean(data.binary_path)); setBinaryUrl(data.binary_url || null); setSuccessMsg("Binary built successfully."); } catch (err) { setError(err.message); } finally { setBuildingBinary(false); } }; const handleBuildBuiltin = async () => { if (!isEdit) { setError("Save the melody first before building."); return; } setBuildingBuiltin(true); setError(""); setSuccessMsg(""); try { const data = await api.post(`/builder/melodies/${id}/build-builtin`); setProgmemCode(data.progmem_code || ""); setSuccessMsg("PROGMEM code generated."); } catch (err) { setError(err.message); } finally { setBuildingBuiltin(false); } }; const handleCopy = () => { if (!progmemCode) return; navigator.clipboard.writeText(progmemCode).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); }); }; if (loading) { return
Loading...
; } return (

{isEdit ? "Edit Archetype" : "New Archetype"}

{error && (
{error}
)} {successMsg && (
{successMsg}
)}
{/* --- Info Section --- */}

Archetype Info

setName(e.target.value)} placeholder="e.g. Doksologia_3k" className={inputClass} />
setPid(e.target.value)} placeholder="e.g. builtin_doksologia_3k" className={inputClass} />

Used as the built-in firmware identifier

{countSteps(steps)} steps