From bffcdc19f7700d999e1bfc98359f06de66c5bc52 Mon Sep 17 00:00:00 2001 From: bonamin Date: Sun, 22 Feb 2026 21:37:58 +0200 Subject: [PATCH] CODEX - Added extra columns and settings again --- frontend/src/melodies/MelodyComposer.jsx | 25 ++++---- frontend/src/melodies/MelodyDetail.jsx | 46 ++++++++++++++- frontend/src/melodies/MelodyForm.jsx | 29 ++++++++++ frontend/src/melodies/MelodyList.jsx | 72 +++++++++++++++++++++--- 4 files changed, 148 insertions(+), 24 deletions(-) diff --git a/frontend/src/melodies/MelodyComposer.jsx b/frontend/src/melodies/MelodyComposer.jsx index 00c9ec9..6965400 100644 --- a/frontend/src/melodies/MelodyComposer.jsx +++ b/frontend/src/melodies/MelodyComposer.jsx @@ -345,8 +345,18 @@ export default function MelodyComposer() { Clear -
- {steps.length} steps, {noteCount} notes +
+
+ {steps.length} steps, {noteCount} notes +
+
@@ -429,17 +439,6 @@ export default function MelodyComposer() { - | -
- -
{currentStep >= 0 && ( diff --git a/frontend/src/melodies/MelodyDetail.jsx b/frontend/src/melodies/MelodyDetail.jsx index b268cbb..1630046 100644 --- a/frontend/src/melodies/MelodyDetail.jsx +++ b/frontend/src/melodies/MelodyDetail.jsx @@ -31,6 +31,22 @@ import { formatDuration, } from "./melodyUtils"; +function formatBpm(ms) { + const value = Number(ms); + if (!value || value <= 0) return null; + return Math.round(60000 / value); +} + +function mapPercentageToStepDelay(percent, minSpeed, maxSpeed) { + if (minSpeed == null || maxSpeed == null) return null; + const p = Math.max(0, Math.min(100, Number(percent || 0))); + const t = p / 100; + const a = Number(minSpeed); + const b = Number(maxSpeed); + if (a <= 0 || b <= 0) return Math.round(a + (b - a) * t); + return Math.round(a * Math.pow(b / a, t)); +} + function Field({ label, children }) { return (
@@ -193,6 +209,10 @@ export default function MelodyDetail() { const info = melody.information || {}; const settings = melody.default_settings || {}; + const speedMs = mapPercentageToStepDelay(settings.speed, info.minSpeed, info.maxSpeed); + const speedBpm = formatBpm(speedMs); + const minBpm = formatBpm(info.minSpeed); + const maxBpm = formatBpm(info.maxSpeed); const languages = melodySettings?.available_languages || ["en"]; const displayName = getLocalizedValue(info.name, displayLang, "Untitled Melody"); @@ -313,8 +333,22 @@ export default function MelodyDetail() { {info.steps} {info.totalNotes} {info.totalActiveBells ?? "-"} - {info.minSpeed} - {info.maxSpeed} + + {info.minSpeed ? ( +
+
{minBpm} BPM
+
{info.minSpeed} ms
+
+ ) : "-"} +
+ + {info.maxSpeed ? ( +
+
{maxBpm} BPM
+
{info.maxSpeed} ms
+
+ ) : "-"} +
{info.color ? ( @@ -391,7 +425,13 @@ export default function MelodyDetail() { Default Settings
- {settings.speed}% + + {settings.speed != null ? ( + + {settings.speed}%{speedBpm ? ` · ${speedBpm} BPM` : ""}{speedMs ? ` · ${speedMs} ms` : ""} + + ) : "-"} + {formatDuration(settings.duration)} {settings.totalRunDuration} {settings.pauseDuration} diff --git a/frontend/src/melodies/MelodyForm.jsx b/frontend/src/melodies/MelodyForm.jsx index f29a0c0..1817cf2 100644 --- a/frontend/src/melodies/MelodyForm.jsx +++ b/frontend/src/melodies/MelodyForm.jsx @@ -53,6 +53,22 @@ const headingStyle = { color: "var(--text-heading)" }; const labelStyle = { color: "var(--text-secondary)" }; const mutedStyle = { color: "var(--text-muted)" }; +function formatBpm(ms) { + const value = Number(ms); + if (!value || value <= 0) return null; + return Math.round(60000 / value); +} + +function mapPercentageToStepDelay(percent, minSpeed, maxSpeed) { + if (minSpeed == null || maxSpeed == null) return null; + const p = Math.max(0, Math.min(100, Number(percent || 0))); + const t = p / 100; + const a = Number(minSpeed); + const b = Number(maxSpeed); + if (a <= 0 || b <= 0) return Math.round(a + (b - a) * t); + return Math.round(a * Math.pow(b / a, t)); +} + export default function MelodyForm() { const { id } = useParams(); const isEdit = Boolean(id); @@ -308,6 +324,10 @@ export default function MelodyForm() { const durationIndex = durationValues.indexOf(settings.duration); const currentDurationIdx = durationIndex >= 0 ? durationIndex : 0; + const speedMs = mapPercentageToStepDelay(settings.speed, information.minSpeed, information.maxSpeed); + const speedBpm = formatBpm(speedMs); + const minBpm = formatBpm(information.minSpeed); + const maxBpm = formatBpm(information.maxSpeed); const inputClass = "w-full px-3 py-2 rounded-md text-sm border"; @@ -508,11 +528,17 @@ export default function MelodyForm() {
updateInfo("minSpeed", parseInt(e.target.value, 10) || 0)} className={inputClass} /> + {information.minSpeed > 0 && ( +

{minBpm} BPM · {information.minSpeed} ms

+ )}
updateInfo("maxSpeed", parseInt(e.target.value, 10) || 0)} className={inputClass} /> + {information.maxSpeed > 0 && ( +

{maxBpm} BPM · {information.maxSpeed} ms

+ )}
{/* Color */} @@ -594,6 +620,9 @@ export default function MelodyForm() { updateSettings("speed", parseInt(e.target.value, 10))} className="flex-1 h-2 rounded-lg appearance-none cursor-pointer" /> {settings.speed}%
+
+ {speedBpm && speedMs ? `${speedBpm} BPM · ${speedMs} ms` : "Set MIN/MAX speed to compute BPM"} +
diff --git a/frontend/src/melodies/MelodyList.jsx b/frontend/src/melodies/MelodyList.jsx index 35a416b..353c68a 100644 --- a/frontend/src/melodies/MelodyList.jsx +++ b/frontend/src/melodies/MelodyList.jsx @@ -64,10 +64,38 @@ function getDefaultVisibleColumns() { function speedBarColor(speedPercent) { const v = Math.max(0, Math.min(100, Number(speedPercent || 0))); - const hue = (v / 100) * 120; + if (v <= 50) { + const t = v / 50; + const hue = 120 + (210 - 120) * t; // green -> blue + return `hsl(${hue}, 85%, 46%)`; + } + const t = (v - 50) / 50; + const hue = 210 + (0 - 210) * t; // blue -> red return `hsl(${hue}, 85%, 46%)`; } +function durationBarColor(percent) { + const v = Math.max(0, Math.min(100, Number(percent || 0))); + const hue = 220 + (0 - 220) * (v / 100); // blue -> red + return `hsl(${hue}, 85%, 46%)`; +} + +function formatBpm(ms) { + const value = Number(ms); + if (!value || value <= 0) return null; + return Math.round(60000 / value); +} + +function mapPercentageToStepDelay(percent, minSpeed, maxSpeed) { + if (minSpeed == null || maxSpeed == null) return null; + const p = Math.max(0, Math.min(100, Number(percent || 0))); + const t = p / 100; + const a = Number(minSpeed); + const b = Number(maxSpeed); + if (a <= 0 || b <= 0) return Math.round(a + (b - a) * t); + return Math.round(a * Math.pow(b / a, t)); +} + function parseDateValue(isoValue) { if (!isoValue) return 0; const time = new Date(isoValue).getTime(); @@ -373,7 +401,19 @@ export default function MelodyList() { ); } case "type": - return {row.type}; + { + const typeStyles = { + orthodox: { color: "#7dd3fc", backgroundColor: "rgba(14,165,233,0.15)" }, + catholic: { color: "#fda4af", backgroundColor: "rgba(244,63,94,0.15)" }, + all: { color: "#86efac", backgroundColor: "rgba(34,197,94,0.14)" }, + }; + const style = typeStyles[row.type] || { color: "var(--text-secondary)", backgroundColor: "var(--bg-card-hover)" }; + return ( + + {row.type || "-"} + + ); + } case "tone": return {info.melodyTone || "-"}; case "totalNotes": @@ -381,9 +421,21 @@ export default function MelodyList() { case "totalActiveBells": return info.totalActiveBells ?? "-"; case "minSpeed": - return info.minSpeed ?? "-"; + if (!info.minSpeed) return "-"; + return ( +
+
{formatBpm(info.minSpeed)} BPM
+
{info.minSpeed} ms
+
+ ); case "maxSpeed": - return info.maxSpeed ?? "-"; + if (!info.maxSpeed) return "-"; + return ( +
+
{formatBpm(info.maxSpeed)} BPM
+
{info.maxSpeed} ms
+
+ ); case "tags": return info.customTags?.length > 0 ? (
@@ -402,10 +454,13 @@ export default function MelodyList() { ); case "speed": if (ds.speed == null) return "-"; + { + const speedMs = mapPercentageToStepDelay(ds.speed, info.minSpeed, info.maxSpeed); + const bpm = formatBpm(speedMs); return (
- {ds.speed}% + {ds.speed}%{bpm ? ` · ${bpm} BPM` : ""}{speedMs ? ` · ${speedMs} ms` : ""}
); + } case "duration": if (ds.duration == null) return "-"; if (Number(ds.duration) === 0) { @@ -453,7 +509,7 @@ export default function MelodyList() { className="h-full rounded-full transition-all" style={{ width: `${Math.max(0, Math.min(100, percent))}%`, - backgroundColor: speedBarColor(percent), + backgroundColor: durationBarColor(percent), }} />
@@ -766,7 +822,7 @@ export default function MelodyList() { @@ -813,7 +869,7 @@ export default function MelodyList() {