CODEX - Added Modal Playback for Archetypes

This commit is contained in:
2026-02-23 09:57:12 +02:00
parent fb3bbac245
commit 12e793aa7e
4 changed files with 554 additions and 57 deletions

View File

@@ -47,6 +47,13 @@ function mapPercentageToStepDelay(percent, minSpeed, maxSpeed) {
return Math.round(a * Math.pow(b / a, t));
}
function normalizeFileUrl(url) {
if (!url || typeof url !== "string") return null;
if (url.startsWith("http") || url.startsWith("/api")) return url;
if (url.startsWith("/")) return `/api${url}`;
return `/api/${url}`;
}
function hueForDepth(index, count) {
const safeCount = Math.max(1, count);
const t = Math.max(0, Math.min(1, index / safeCount));
@@ -121,6 +128,7 @@ export default function MelodyDetail() {
const [codeCopied, setCodeCopied] = useState(false);
const [showSpeedCalc, setShowSpeedCalc] = useState(false);
const [showPlayback, setShowPlayback] = useState(false);
const [offlineSaving, setOfflineSaving] = useState(false);
useEffect(() => {
api.get("/settings/melody").then((ms) => {
@@ -192,6 +200,32 @@ export default function MelodyDetail() {
}
};
const handleToggleAvailableOffline = async (nextValue) => {
if (!canEdit || !melody) return;
setOfflineSaving(true);
setError("");
try {
const body = {
information: { ...(melody.information || {}), available_offline: nextValue },
default_settings: melody.default_settings || {},
type: melody.type || "all",
uid: melody.uid || "",
pid: melody.pid || "",
metadata: melody.metadata || {},
};
if (melody.url) body.url = melody.url;
await api.put(`/melodies/${id}`, body);
setMelody((prev) => ({
...prev,
information: { ...(prev?.information || {}), available_offline: nextValue },
}));
} catch (err) {
setError(err.message);
} finally {
setOfflineSaving(false);
}
};
if (loading) {
return <div className="text-center py-8" style={{ color: "var(--text-muted)" }}>Loading...</div>;
}
@@ -490,10 +524,24 @@ export default function MelodyDetail() {
>
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>Files</h2>
<dl className="space-y-4">
<Field label="Available as Built-In">
<label className="inline-flex items-center gap-2">
<input
type="checkbox"
checked={Boolean(info.available_offline)}
disabled={!canEdit || offlineSaving}
onChange={(e) => handleToggleAvailableOffline(e.target.checked)}
className="h-4 w-4 rounded"
/>
<span style={{ color: "var(--text-secondary)" }}>
{info.available_offline ? "Enabled" : "Disabled"}
</span>
</label>
</Field>
<Field label="Binary File">
{(() => {
// Prefer the uploaded file URL, fall back to melody.url (legacy/firebase storage URL)
const binaryUrl = files.binary_url || melody.url || null;
const binaryUrl = normalizeFileUrl(files.binary_url || melody.url || null);
if (!binaryUrl) return <span style={{ color: "var(--text-muted)" }}>Not uploaded</span>;
const binaryPid = builtMelody?.pid || melody.pid || "binary";
@@ -539,15 +587,26 @@ export default function MelodyDetail() {
{builtMelody?.name ? (
<strong style={{ color: "var(--text-heading)" }}>{builtMelody.name}</strong>
) : (
<a
href={binaryUrl}
onClick={handleDownload}
className="underline"
style={{ color: "var(--accent)" }}
>
<span className="text-sm" style={{ color: "var(--text-secondary)" }}>
{downloadName}
</a>
</span>
)}
<button
type="button"
onClick={handleDownload}
className="px-2 py-0.5 text-xs rounded-full"
style={{ color: "var(--text-link)", backgroundColor: "rgba(88,156,250,0.14)" }}
>
Download
</button>
<button
type="button"
onClick={() => setShowPlayback(true)}
className="px-2 py-0.5 text-xs rounded-full"
style={{ color: "var(--text-secondary)", backgroundColor: "var(--bg-card-hover)" }}
>
View
</button>
{!files.binary_url && melody.url && (
<span className="text-xs px-1.5 py-0.5 rounded" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-muted)" }}>
via URL