update: Major Overhaul to all subsystems
This commit is contained in:
@@ -400,10 +400,7 @@ export default function MelodyComposer() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<section
|
||||
className="rounded-lg border p-4"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
>
|
||||
<section className="ui-section-card">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<button type="button" onClick={addStep} className="px-3 py-1.5 text-sm rounded-md" style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}>+ Step</button>
|
||||
<button type="button" onClick={removeStep} className="px-3 py-1.5 text-sm rounded-md" style={{ backgroundColor: "var(--btn-neutral)", color: "var(--text-white)" }}>- Step</button>
|
||||
@@ -485,10 +482,7 @@ export default function MelodyComposer() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="rounded-lg border"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
>
|
||||
<section className="ui-section-card">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-max border-separate border-spacing-0">
|
||||
<thead>
|
||||
@@ -633,10 +627,7 @@ export default function MelodyComposer() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="rounded-lg border p-4"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
>
|
||||
<section className="ui-section-card">
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-xs mb-1" style={{ color: "var(--text-muted)" }}>Generated CSV notation</p>
|
||||
|
||||
@@ -58,9 +58,7 @@ function normalizeFileUrl(url) {
|
||||
function Field({ label, children }) {
|
||||
return (
|
||||
<div>
|
||||
<dt className="text-xs font-medium uppercase tracking-wide" style={{ color: "var(--text-muted)" }}>
|
||||
{label}
|
||||
</dt>
|
||||
<dt className="ui-field-label">{label}</dt>
|
||||
<dd className="mt-1 text-sm" style={{ color: "var(--text-primary)" }}>{children || "-"}</dd>
|
||||
</div>
|
||||
);
|
||||
@@ -70,9 +68,7 @@ function UrlField({ label, value }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
return (
|
||||
<div>
|
||||
<dt className="text-xs font-medium uppercase tracking-wide mb-1" style={{ color: "var(--text-muted)" }}>
|
||||
{label}
|
||||
</dt>
|
||||
<dt className="ui-field-label mb-1">{label}</dt>
|
||||
<dd className="flex items-center gap-2">
|
||||
<span
|
||||
className="text-sm font-mono flex-1 min-w-0"
|
||||
@@ -354,12 +350,11 @@ export default function MelodyDetail() {
|
||||
<div className="space-y-6">
|
||||
{/* Melody Information */}
|
||||
<section
|
||||
className="rounded-lg p-6 border"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
className="ui-section-card"
|
||||
>
|
||||
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>
|
||||
Melody Information
|
||||
</h2>
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Melody Information</h2>
|
||||
</div>
|
||||
<dl className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<Field label="Color">
|
||||
{info.color ? (
|
||||
@@ -422,12 +417,11 @@ export default function MelodyDetail() {
|
||||
|
||||
{/* Identifiers */}
|
||||
<section
|
||||
className="rounded-lg p-6 border"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
className="ui-section-card"
|
||||
>
|
||||
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>
|
||||
Identifiers
|
||||
</h2>
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Identifiers</h2>
|
||||
</div>
|
||||
<dl className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<Field label="Document ID">{melody.id}</Field>
|
||||
<Field label="PID (Playback ID)">{melody.pid}</Field>
|
||||
@@ -444,12 +438,11 @@ export default function MelodyDetail() {
|
||||
<div className="space-y-6">
|
||||
{/* Default Settings */}
|
||||
<section
|
||||
className="rounded-lg p-6 border"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
className="ui-section-card"
|
||||
>
|
||||
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>
|
||||
Default Settings
|
||||
</h2>
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Default Settings</h2>
|
||||
</div>
|
||||
<dl className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<Field label="Speed">
|
||||
{settings.speed != null ? (
|
||||
@@ -474,7 +467,7 @@ export default function MelodyDetail() {
|
||||
</Field>
|
||||
</div>
|
||||
<div className="col-span-2 md:col-span-3">
|
||||
<dt className="text-xs font-medium uppercase tracking-wide mb-2" style={{ color: "var(--text-muted)" }}>Note Assignments</dt>
|
||||
<dt className="ui-field-label mb-2">Note Assignments</dt>
|
||||
<dd>
|
||||
{settings.noteAssignments?.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
@@ -512,10 +505,11 @@ export default function MelodyDetail() {
|
||||
|
||||
{/* Files */}
|
||||
<section
|
||||
className="rounded-lg p-6 border"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
className="ui-section-card"
|
||||
>
|
||||
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>Files</h2>
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Files</h2>
|
||||
</div>
|
||||
<dl className="space-y-4">
|
||||
<Field label="Available as Built-In">
|
||||
<label className="inline-flex items-center gap-2">
|
||||
@@ -681,12 +675,11 @@ export default function MelodyDetail() {
|
||||
{/* Firmware Code section — only shown if a built melody with PROGMEM code is assigned */}
|
||||
{builtMelody?.progmem_code && (
|
||||
<section
|
||||
className="rounded-lg p-6 border mt-6"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
className="ui-section-card mt-6"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="ui-section-card__title-row">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold" style={{ color: "var(--text-heading)" }}>Firmware Code</h2>
|
||||
<h2 className="ui-section-card__title">Firmware Code</h2>
|
||||
<p className="text-xs mt-0.5" style={{ color: "var(--text-muted)" }}>
|
||||
PROGMEM code for built-in firmware playback · PID: <span className="font-mono">{builtMelody.pid}</span>
|
||||
</p>
|
||||
@@ -723,10 +716,11 @@ export default function MelodyDetail() {
|
||||
{/* Metadata section */}
|
||||
{melody.metadata && (
|
||||
<section
|
||||
className="rounded-lg p-6 border mt-6"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
className="ui-section-card mt-6"
|
||||
>
|
||||
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>History</h2>
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">History</h2>
|
||||
</div>
|
||||
<dl className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{melody.metadata.dateCreated && (
|
||||
<Field label="Date Created">
|
||||
@@ -750,10 +744,11 @@ export default function MelodyDetail() {
|
||||
|
||||
{/* Admin Notes section */}
|
||||
<section
|
||||
className="rounded-lg p-6 border mt-6"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
className="ui-section-card mt-6"
|
||||
>
|
||||
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>Admin Notes</h2>
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Admin Notes</h2>
|
||||
</div>
|
||||
{(melody.metadata?.adminNotes?.length || 0) > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{melody.metadata.adminNotes.map((note, i) => (
|
||||
|
||||
@@ -46,12 +46,6 @@ const defaultSettings = {
|
||||
noteAssignments: [],
|
||||
};
|
||||
|
||||
// Dark-themed styles
|
||||
const sectionStyle = {
|
||||
backgroundColor: "var(--bg-card)",
|
||||
borderColor: "var(--border-primary)",
|
||||
};
|
||||
const headingStyle = { color: "var(--text-heading)" };
|
||||
const labelStyle = { color: "var(--text-secondary)" };
|
||||
const mutedStyle = { color: "var(--text-muted)" };
|
||||
|
||||
@@ -408,7 +402,7 @@ export default function MelodyForm() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h1 className="text-2xl font-bold" style={headingStyle}>
|
||||
<h1 className="text-2xl font-bold" style={{ color: "var(--text-heading)" }}>
|
||||
{isEdit ? "Edit Melody" : "Add Melody"}
|
||||
</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -533,13 +527,15 @@ export default function MelodyForm() {
|
||||
{/* ===== Left Column ===== */}
|
||||
<div className="space-y-6">
|
||||
{/* --- Melody Info Section --- */}
|
||||
<section className="rounded-lg p-6 border" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-4" style={headingStyle}>Melody Information</h2>
|
||||
<section className="ui-section-card">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Melody Information</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Name (localized) */}
|
||||
<div className="md:col-span-2">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<label className="block text-sm font-medium" style={labelStyle}>Name *</label>
|
||||
<label className="ui-form-label">Name *</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTranslationModal({ open: true, field: "Name", fieldKey: "name", multiline: false })}
|
||||
@@ -561,7 +557,7 @@ export default function MelodyForm() {
|
||||
{/* Description (localized) */}
|
||||
<div className="md:col-span-2">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<label className="block text-sm font-medium" style={labelStyle}>Description</label>
|
||||
<label className="ui-form-label">Description</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTranslationModal({ open: true, field: "Description", fieldKey: "description", multiline: true })}
|
||||
@@ -576,31 +572,31 @@ export default function MelodyForm() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Melody Tone</label>
|
||||
<label className="ui-form-label">Melody Tone</label>
|
||||
<select value={information.melodyTone} onChange={(e) => updateInfo("melodyTone", e.target.value)} className={inputClass}>
|
||||
{MELODY_TONES.map((t) => (<option key={t} value={t}>{t.charAt(0).toUpperCase() + t.slice(1)}</option>))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Type</label>
|
||||
<label className="ui-form-label">Type</label>
|
||||
<select value={type} onChange={(e) => setType(e.target.value)} className={inputClass}>
|
||||
{MELODY_TYPES.map((t) => (<option key={t} value={t}>{t.charAt(0).toUpperCase() + t.slice(1)}</option>))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Total Archetype Notes</label>
|
||||
<label className="ui-form-label">Total Archetype Notes</label>
|
||||
<input type="number" min="1" max="16" value={information.totalNotes} onChange={(e) => { const val = parseInt(e.target.value, 10); updateInfo("totalNotes", Math.max(1, Math.min(16, val || 1))); }} className={inputClass} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Steps</label>
|
||||
<label className="ui-form-label">Steps</label>
|
||||
<input type="number" min="0" value={information.steps} onChange={(e) => updateInfo("steps", parseInt(e.target.value, 10) || 0)} className={inputClass} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Min Speed</label>
|
||||
<label className="ui-form-label">Min Speed</label>
|
||||
<input type="number" min="0" value={information.minSpeed} onChange={(e) => updateInfo("minSpeed", parseInt(e.target.value, 10) || 0)} className={inputClass} />
|
||||
{information.minSpeed > 0 && (
|
||||
<p className="text-xs mt-1" style={mutedStyle}>{minBpm} bpm · {information.minSpeed} ms</p>
|
||||
@@ -608,7 +604,7 @@ export default function MelodyForm() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Max Speed</label>
|
||||
<label className="ui-form-label">Max Speed</label>
|
||||
<input type="number" min="0" value={information.maxSpeed} onChange={(e) => updateInfo("maxSpeed", parseInt(e.target.value, 10) || 0)} className={inputClass} />
|
||||
{information.maxSpeed > 0 && (
|
||||
<p className="text-xs mt-1" style={mutedStyle}>{maxBpm} bpm · {information.maxSpeed} ms</p>
|
||||
@@ -617,7 +613,7 @@ export default function MelodyForm() {
|
||||
|
||||
{/* Color */}
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Color</label>
|
||||
<label className="ui-form-label">Color</label>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="w-8 h-8 rounded flex-shrink-0 border" style={{ backgroundColor: information.color ? normalizeColor(information.color) : "transparent", borderColor: "var(--border-primary)" }} />
|
||||
<input type="text" value={information.color} onChange={(e) => updateInfo("color", e.target.value)} placeholder="e.g. #FF5733 or 0xFF5733" className="flex-1 px-3 py-2 rounded-md text-sm border" />
|
||||
@@ -645,7 +641,7 @@ export default function MelodyForm() {
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Custom Tags</label>
|
||||
<label className="ui-form-label">Custom Tags</label>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<input type="text" value={tagInput} onChange={(e) => setTagInput(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addTag(); } }} placeholder="Add a tag and press Enter" className="flex-1 px-3 py-2 rounded-md text-sm border" />
|
||||
<button type="button" onClick={addTag} className="px-3 py-2 text-sm rounded-md transition-colors" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-primary)" }}>Add</button>
|
||||
@@ -665,16 +661,18 @@ export default function MelodyForm() {
|
||||
</section>
|
||||
|
||||
{/* --- Identifiers Section --- */}
|
||||
<section className="rounded-lg p-6 border" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-4" style={headingStyle}>Identifiers</h2>
|
||||
<section className="ui-section-card">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Identifiers</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>PID (Playback ID)</label>
|
||||
<label className="ui-form-label">PID (Playback ID)</label>
|
||||
<input type="text" value={pid} onChange={(e) => setPid(e.target.value)} placeholder="eg. builtin_festive_vesper" className={inputClass} />
|
||||
</div>
|
||||
{url && (
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>URL (auto-set from binary upload)</label>
|
||||
<label className="ui-form-label">URL (auto-set from binary upload)</label>
|
||||
<input type="text" value={url} readOnly className={inputClass} style={{ opacity: 0.7 }} />
|
||||
</div>
|
||||
)}
|
||||
@@ -685,11 +683,13 @@ export default function MelodyForm() {
|
||||
{/* ===== Right Column ===== */}
|
||||
<div className="space-y-6">
|
||||
{/* --- Default Settings Section --- */}
|
||||
<section className="rounded-lg p-6 border" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-4" style={headingStyle}>Default Settings</h2>
|
||||
<section className="ui-section-card">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Default Settings</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Speed</label>
|
||||
<label className="ui-form-label">Speed</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input type="range" min="1" max="100" value={settings.speed} onChange={(e) => updateSettings("speed", parseInt(e.target.value, 10))} className="flex-1 h-2 rounded-lg appearance-none cursor-pointer" />
|
||||
<span className="text-sm font-medium w-12 text-right" style={labelStyle}>{settings.speed}%</span>
|
||||
@@ -700,7 +700,7 @@ export default function MelodyForm() {
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Duration</label>
|
||||
<label className="ui-form-label">Duration</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input type="range" min="0" max={Math.max(0, durationValues.length - 1)} value={currentDurationIdx} onChange={(e) => updateSettings("duration", durationValues[parseInt(e.target.value, 10)] ?? 0)} className="flex-1 h-2 rounded-lg appearance-none cursor-pointer" />
|
||||
<span className="text-sm font-medium w-24 text-right" style={labelStyle}>{formatDuration(settings.duration)}</span>
|
||||
@@ -709,12 +709,12 @@ export default function MelodyForm() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Total Run Duration</label>
|
||||
<label className="ui-form-label">Total Run Duration</label>
|
||||
<input type="number" min="0" value={settings.totalRunDuration} onChange={(e) => updateSettings("totalRunDuration", parseInt(e.target.value, 10) || 0)} className={inputClass} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Pause Duration</label>
|
||||
<label className="ui-form-label">Pause Duration</label>
|
||||
<input type="number" min="0" value={settings.pauseDuration} onChange={(e) => updateSettings("pauseDuration", parseInt(e.target.value, 10) || 0)} className={inputClass} />
|
||||
</div>
|
||||
|
||||
@@ -724,13 +724,13 @@ export default function MelodyForm() {
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Echo Ring (comma-separated integers)</label>
|
||||
<label className="ui-form-label">Echo Ring (comma-separated integers)</label>
|
||||
<input type="text" value={settings.echoRing.join(", ")} onChange={(e) => updateSettings("echoRing", parseIntList(e.target.value))} placeholder="e.g. 0, 1, 0, 1" className={inputClass} />
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-sm font-medium" style={labelStyle}>Note Assignments</label>
|
||||
<label className="ui-form-label">Note Assignments</label>
|
||||
<span className="text-xs px-2 py-0.5 rounded-full" style={{ backgroundColor: "var(--bg-card-hover)", color: "var(--text-muted)" }}>
|
||||
{computeTotalActiveBells(settings.noteAssignments)} active bell{computeTotalActiveBells(settings.noteAssignments) !== 1 ? "s" : ""}
|
||||
</span>
|
||||
@@ -752,8 +752,10 @@ export default function MelodyForm() {
|
||||
</section>
|
||||
|
||||
{/* --- File Upload Section --- */}
|
||||
<section className="rounded-lg p-6 border" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-4" style={headingStyle}>Files</h2>
|
||||
<section className="ui-section-card">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Files</h2>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
@@ -768,7 +770,7 @@ export default function MelodyForm() {
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Binary File (.bsm)</label>
|
||||
<label className="ui-form-label">Binary File (.bsm)</label>
|
||||
{(() => {
|
||||
const { effectiveUrl: binaryUrl, effectiveName: binaryName } = getEffectiveBinary();
|
||||
const missingArchetype = Boolean(pid) && !builtMelody?.id;
|
||||
@@ -867,7 +869,7 @@ export default function MelodyForm() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Audio Preview (.mp3)</label>
|
||||
<label className="ui-form-label">Audio Preview (.mp3)</label>
|
||||
{normalizeFileUrl(existingFiles.preview_url) ? (
|
||||
<div className="mb-2 space-y-1">
|
||||
{(() => {
|
||||
@@ -916,8 +918,10 @@ export default function MelodyForm() {
|
||||
</div>
|
||||
|
||||
{/* --- Admin Notes Section --- */}
|
||||
<section className="rounded-lg p-6 border mt-6" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-4" style={headingStyle}>Admin Notes</h2>
|
||||
<section className="ui-section-card mt-6">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Admin Notes</h2>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{adminNotes.map((note, i) => (
|
||||
<div key={i} className="flex items-start gap-3 rounded-lg p-3 border" style={{ borderColor: "var(--border-primary)", backgroundColor: "var(--bg-primary)" }}>
|
||||
|
||||
@@ -14,12 +14,7 @@ const DEFAULT_NOTE_ASSIGNMENT_COLORS = [
|
||||
"#F87171", "#EF4444", "#DC2626", "#B91C1C",
|
||||
];
|
||||
|
||||
const sectionStyle = {
|
||||
backgroundColor: "var(--bg-card)",
|
||||
borderColor: "var(--border-primary)",
|
||||
};
|
||||
const headingStyle = { color: "var(--text-heading)" };
|
||||
const labelStyle = { color: "var(--text-secondary)" };
|
||||
const mutedStyle = { color: "var(--text-muted)" };
|
||||
|
||||
export default function MelodySettings() {
|
||||
@@ -200,8 +195,10 @@ export default function MelodySettings() {
|
||||
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
|
||||
{/* --- Languages Section --- */}
|
||||
<section className="rounded-lg p-6 border" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-4" style={headingStyle}>Available Languages</h2>
|
||||
<section className="ui-section-card">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__header-title">Available Languages</h2>
|
||||
</div>
|
||||
<div className="space-y-2 mb-4">
|
||||
{settings.available_languages.map((code) => (
|
||||
<div
|
||||
@@ -237,8 +234,10 @@ export default function MelodySettings() {
|
||||
</section>
|
||||
|
||||
{/* --- Quick Colors Section --- */}
|
||||
<section className="rounded-lg p-6 border" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-4" style={headingStyle}>Quick Selection Colors</h2>
|
||||
<section className="ui-section-card">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__header-title">Quick Selection Colors</h2>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3 mb-4">
|
||||
{settings.quick_colors.map((color) => (
|
||||
<div key={color} className="relative group">
|
||||
@@ -279,8 +278,10 @@ export default function MelodySettings() {
|
||||
</section>
|
||||
|
||||
{/* --- Duration Presets Section --- */}
|
||||
<section className="rounded-lg p-6 xl:col-span-2 border" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-4" style={headingStyle}>Duration Presets (seconds)</h2>
|
||||
<section className="ui-section-card xl:col-span-2">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__header-title">Duration Presets (seconds)</h2>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{settings.duration_values.map((val) => (
|
||||
<div
|
||||
@@ -317,8 +318,10 @@ export default function MelodySettings() {
|
||||
</section>
|
||||
|
||||
{/* --- Note Assignment Colors --- */}
|
||||
<section className="rounded-lg p-6 xl:col-span-2 border" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-2" style={headingStyle}>Note Assignment Color Coding</h2>
|
||||
<section className="ui-section-card xl:col-span-2">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__header-title">Note Assignment Color Coding</h2>
|
||||
</div>
|
||||
<p className="text-xs mb-4" style={mutedStyle}>
|
||||
Colors used in Composer, Playback, and View table dots. Click a bell to customize.
|
||||
</p>
|
||||
|
||||
@@ -3,8 +3,6 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import api from "../../api/client";
|
||||
import ConfirmDialog from "../../components/ConfirmDialog";
|
||||
|
||||
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";
|
||||
|
||||
@@ -262,21 +260,23 @@ export default function ArchetypeForm() {
|
||||
)}
|
||||
|
||||
<div className="space-y-6">
|
||||
<section className="rounded-lg p-6 border" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>Archetype Info</h2>
|
||||
<section className="ui-section-card">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Archetype Info</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Name *</label>
|
||||
<label className="ui-form-label">Name *</label>
|
||||
<input type="text" value={name} onChange={(e) => handleNameChange(e.target.value)} placeholder="e.g. Doksologia_3k" className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>PID (Playback ID) *</label>
|
||||
<label className="ui-form-label">PID (Playback ID) *</label>
|
||||
<input type="text" value={pid} onChange={(e) => handlePidChange(e.target.value)} placeholder="e.g. builtin_doksologia_3k" className={inputClass} />
|
||||
<p className="text-xs mt-1" style={mutedStyle}>Used as the built-in firmware identifier. Must be unique.</p>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<label className="block text-sm font-medium" style={labelStyle}>Steps *</label>
|
||||
<label className="ui-form-label">Steps *</label>
|
||||
<span className="text-xs" style={mutedStyle}>{countSteps(steps)} steps</span>
|
||||
</div>
|
||||
<textarea
|
||||
@@ -295,8 +295,10 @@ export default function ArchetypeForm() {
|
||||
</section>
|
||||
|
||||
{isEdit && (
|
||||
<section className="rounded-lg p-6 border" style={sectionStyle}>
|
||||
<h2 className="text-lg font-semibold mb-1" style={{ color: "var(--text-heading)" }}>Build</h2>
|
||||
<section className="ui-section-card">
|
||||
<div className="ui-section-card__title-row">
|
||||
<h2 className="ui-section-card__title">Build</h2>
|
||||
</div>
|
||||
<p className="text-sm mb-4" style={mutedStyle}>
|
||||
Save any changes above before building. Rebuilding will overwrite previous output.
|
||||
{hasUnsavedChanges && (
|
||||
@@ -402,7 +404,7 @@ export default function ArchetypeForm() {
|
||||
)}
|
||||
|
||||
{!isEdit && (
|
||||
<div className="rounded-lg p-4 border text-sm" style={{ borderColor: "var(--border-primary)", ...sectionStyle, color: "var(--text-muted)" }}>
|
||||
<div className="ui-section-card text-sm" style={{ color: "var(--text-muted)" }}>
|
||||
Build actions (Binary + PROGMEM Code) will be available after saving.
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user