improvemtns again, to the archetype builder, and playback
This commit is contained in:
@@ -10,7 +10,20 @@ function countSteps(stepsStr) {
|
||||
return stepsStr.trim().split(",").length;
|
||||
}
|
||||
|
||||
export default function BuildOnTheFlyModal({ open, melodyId, defaultName, defaultPid, onClose, onSuccess }) {
|
||||
function computeStepsAndNotes(stepsStr) {
|
||||
if (!stepsStr || !stepsStr.trim()) return { steps: 0, totalNotes: 0 };
|
||||
const tokens = stepsStr.trim().split(",");
|
||||
const bellSet = new Set();
|
||||
for (const token of tokens) {
|
||||
for (const part of token.split("+")) {
|
||||
const n = parseInt(part.trim(), 10);
|
||||
if (!isNaN(n) && n >= 1 && n <= 16) bellSet.add(n);
|
||||
}
|
||||
}
|
||||
return { steps: tokens.length, totalNotes: bellSet.size || 1 };
|
||||
}
|
||||
|
||||
export default function BuildOnTheFlyModal({ open, melodyId, currentMelody, defaultName, defaultPid, onClose, onSuccess }) {
|
||||
const [name, setName] = useState(defaultName || "");
|
||||
const [pid, setPid] = useState(defaultPid || "");
|
||||
const [steps, setSteps] = useState("");
|
||||
@@ -57,8 +70,29 @@ export default function BuildOnTheFlyModal({ open, melodyId, defaultName, defaul
|
||||
setStatusMsg("Linking to melody...");
|
||||
await api.post(`/builder/melodies/${builtId}/assign?firestore_melody_id=${melodyId}`);
|
||||
|
||||
// Step 5: Update the melody's information with archetype_csv, steps, and totalNotes
|
||||
setStatusMsg("Saving archetype data...");
|
||||
const csv = steps.trim();
|
||||
const { steps: stepCount, totalNotes } = computeStepsAndNotes(csv);
|
||||
if (currentMelody && csv) {
|
||||
const existingInfo = currentMelody.information || {};
|
||||
await api.put(`/melodies/${melodyId}`, {
|
||||
information: {
|
||||
...existingInfo,
|
||||
archetype_csv: csv,
|
||||
steps: stepCount,
|
||||
totalNotes,
|
||||
},
|
||||
default_settings: currentMelody.default_settings,
|
||||
type: currentMelody.type,
|
||||
url: currentMelody.url,
|
||||
uid: currentMelody.uid,
|
||||
pid: currentMelody.pid,
|
||||
});
|
||||
}
|
||||
|
||||
setStatusMsg("Done!");
|
||||
onSuccess({ name: name.trim(), pid: pid.trim() });
|
||||
onSuccess({ name: name.trim(), pid: pid.trim(), steps: stepCount, totalNotes, archetype_csv: csv });
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
setStatusMsg("");
|
||||
|
||||
@@ -7,6 +7,24 @@ 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 copyText(text, onSuccess) {
|
||||
const fallback = () => {
|
||||
const ta = document.createElement("textarea");
|
||||
ta.value = text;
|
||||
ta.style.cssText = "position:fixed;top:0;left:0;opacity:0";
|
||||
document.body.appendChild(ta);
|
||||
ta.focus();
|
||||
ta.select();
|
||||
try { document.execCommand("copy"); onSuccess?.(); } catch (_) {}
|
||||
document.body.removeChild(ta);
|
||||
};
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(onSuccess).catch(fallback);
|
||||
} else {
|
||||
fallback();
|
||||
}
|
||||
}
|
||||
|
||||
function countSteps(stepsStr) {
|
||||
if (!stepsStr || !stepsStr.trim()) return 0;
|
||||
return stepsStr.trim().split(",").length;
|
||||
@@ -128,10 +146,7 @@ export default function BuilderForm() {
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!progmemCode) return;
|
||||
navigator.clipboard.writeText(progmemCode).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
});
|
||||
copyText(progmemCode, () => { setCopied(true); setTimeout(() => setCopied(false), 2000); });
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
|
||||
@@ -3,6 +3,25 @@ import { useNavigate } from "react-router-dom";
|
||||
import api from "../../api/client";
|
||||
import ConfirmDialog from "../../components/ConfirmDialog";
|
||||
|
||||
function fallbackCopy(text, onSuccess) {
|
||||
const ta = document.createElement("textarea");
|
||||
ta.value = text;
|
||||
ta.style.cssText = "position:fixed;top:0;left:0;opacity:0";
|
||||
document.body.appendChild(ta);
|
||||
ta.focus();
|
||||
ta.select();
|
||||
try { document.execCommand("copy"); onSuccess?.(); } catch (_) {}
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
|
||||
function copyText(text, onSuccess) {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(onSuccess).catch(() => fallbackCopy(text, onSuccess));
|
||||
} else {
|
||||
fallbackCopy(text, onSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
function CodeSnippetModal({ melody, onClose }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
if (!melody) return null;
|
||||
@@ -23,12 +42,7 @@ function CodeSnippetModal({ melody, onClose }) {
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(melody.progmem_code).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
});
|
||||
}}
|
||||
onClick={() => copyText(melody.progmem_code, () => { setCopied(true); setTimeout(() => setCopied(false), 2000); })}
|
||||
className="px-3 py-1.5 text-xs rounded transition-colors"
|
||||
style={{
|
||||
backgroundColor: copied ? "var(--success-bg)" : "var(--bg-card-hover)",
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import api from "../../api/client";
|
||||
|
||||
export default function SelectBuiltMelodyModal({ open, melodyId, onClose, onSuccess }) {
|
||||
function computeStepsAndNotes(stepsStr) {
|
||||
if (!stepsStr || !stepsStr.trim()) return { steps: 0, totalNotes: 0 };
|
||||
const tokens = stepsStr.trim().split(",");
|
||||
const bellSet = new Set();
|
||||
for (const token of tokens) {
|
||||
for (const part of token.split("+")) {
|
||||
const n = parseInt(part.trim(), 10);
|
||||
if (!isNaN(n) && n >= 1 && n <= 16) bellSet.add(n);
|
||||
}
|
||||
}
|
||||
return { steps: tokens.length, totalNotes: bellSet.size || 1 };
|
||||
}
|
||||
|
||||
export default function SelectBuiltMelodyModal({ open, melodyId, currentMelody, onClose, onSuccess }) {
|
||||
const [melodies, setMelodies] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [assigning, setAssigning] = useState(null); // id of the one being assigned
|
||||
@@ -44,7 +57,27 @@ export default function SelectBuiltMelodyModal({ open, melodyId, onClose, onSucc
|
||||
// 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 });
|
||||
// 4. Update the melody's information with archetype_csv, steps, and totalNotes
|
||||
const csv = builtMelody.steps || "";
|
||||
const { steps, totalNotes } = computeStepsAndNotes(csv);
|
||||
if (currentMelody && csv) {
|
||||
const existingInfo = currentMelody.information || {};
|
||||
await api.put(`/melodies/${melodyId}`, {
|
||||
information: {
|
||||
...existingInfo,
|
||||
archetype_csv: csv,
|
||||
steps,
|
||||
totalNotes,
|
||||
},
|
||||
default_settings: currentMelody.default_settings,
|
||||
type: currentMelody.type,
|
||||
url: currentMelody.url,
|
||||
uid: currentMelody.uid,
|
||||
pid: currentMelody.pid,
|
||||
});
|
||||
}
|
||||
|
||||
onSuccess({ name: builtMelody.name, pid: builtMelody.pid, steps, totalNotes, archetype_csv: csv });
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user