From cada45fb15463b079e3f78641c0387a5fcf38177 Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Mon, 22 Jun 2026 15:15:13 +0200 Subject: [PATCH] feat: Add Inline Table Edit Mode --- static/index.html | 2 +- static/js/voice-library.js | 84 +++++++++++++++++++++++++++++++---- static/sections/s-voices.html | 1 + static/style.css | 25 +++++++++++ 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/static/index.html b/static/index.html index 4c66b1f..d25c650 100644 --- a/static/index.html +++ b/static/index.html @@ -26,7 +26,7 @@ - + diff --git a/static/js/voice-library.js b/static/js/voice-library.js index 07142a3..558322a 100644 --- a/static/js/voice-library.js +++ b/static/js/voice-library.js @@ -134,19 +134,35 @@ document.addEventListener('click', e => { const icon = btn?.querySelector('.mdi'); if (icon) icon.className = 'mdi mdi-folder' + (window._voiceGroupByTag ? '-open' : '') + '-outline'; renderVoiceList(); - } - if (e.target.closest('#voice-table-view-btn')) { + } else if (e.target.closest('#voice-table-view-btn')) { window._voiceTableView = !window._voiceTableView; + if (!window._voiceTableView) window._voiceTableEditMode = false; try { localStorage.setItem('vl-table-view', window._voiceTableView ? '1' : '0'); } catch (_) {} const btn = document.getElementById('voice-table-view-btn'); btn?.classList.toggle('active', window._voiceTableView); + + const editBtn = document.getElementById('voice-table-edit-btn'); + if (editBtn) { + editBtn.style.display = window._voiceTableView ? 'inline-flex' : 'none'; + editBtn.classList.toggle('active', !!window._voiceTableEditMode); + } + document.querySelector('.voices-workbench')?.classList.toggle('table-view', window._voiceTableView); + document.querySelector('.voices-workbench')?.classList.toggle('table-edit-mode', !!window._voiceTableEditMode); + // Auto-close any open inspector row when toggling table view if (window._voiceTableView) { document.querySelectorAll('.edit-open').forEach(r => r.classList.remove('edit-open')); const inspector = document.getElementById('voices-inspector'); if (inspector) inspector.innerHTML = '

Table View Mode
Click a row to exit table view and edit.

'; } + renderVoiceList(); + } else if (e.target.closest('#voice-table-edit-btn')) { + window._voiceTableEditMode = !window._voiceTableEditMode; + const editBtn = document.getElementById('voice-table-edit-btn'); + editBtn?.classList.toggle('active', window._voiceTableEditMode); + document.querySelector('.voices-workbench')?.classList.toggle('table-edit-mode', window._voiceTableEditMode); + renderVoiceList(); } }); // Restore the preferences on load @@ -169,6 +185,30 @@ document.addEventListener('change', e => { if (e.target.id === 'voice-sort-field') setSort(e.target.value); }); +document.addEventListener('change', async e => { + if (e.target.classList.contains('vl-inline-edit')) { + const row = e.target.closest('.vl-row'); + if (!row) return; + const voiceId = row.dataset.id; + const field = e.target.dataset.field; + let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; + + if (field === 'rating') value = parseInt(value) || 0; + + const payload = {}; + payload[field] = value; + + try { + await saveMeta(voiceId, payload); + const v = _voices.find(vv => vv.id === voiceId); + if (v) v[field] = value; + if (typeof toast === 'function') toast('Saved ' + field, 'success'); + } catch (err) { + if (typeof toast === 'function') toast('Failed to save ' + field, 'error'); + } + } +}); + const FLAG_LANGUAGE_CANDIDATES = { GB:['EN'], US:['EN'], AU:['EN'], NZ:['EN'], IE:['EN'], ZA:['EN'], NG:['EN'], KE:['EN'], GH:['EN'], JM:['EN'], TT:['EN'], CA:['EN','FR'], IN:['EN','HI'], SG:['EN','ZH'], PH:['EN','FIL'], MT:['EN','MT'], @@ -1824,7 +1864,9 @@ function makeVoiceRow(v) {
${avatarContent}
- ${escHtml(dispName)} + ${window._voiceTableEditMode + ? `` + : `${escHtml(dispName)}`}
${isClone ? 'Clone' : 'Design'} ${gender ? `${genderMap[gender]||'?'} ${genderLabel[gender]||''}` : ''} @@ -1835,18 +1877,42 @@ function makeVoiceRow(v) {
-
${flagEmoji} ${escHtml(langCode)}
-
${genderMap[gender]||'?'} ${escHtml(genderLabel[gender]||'—')}
+
+ ${window._voiceTableEditMode + ? `` + : `${flagEmoji} ${escHtml(langCode)}`} +
+
+ ${window._voiceTableEditMode + ? `` + : `${genderMap[gender]||'?'} ${escHtml(genderLabel[gender]||'—')}`} +
${isClone ? 'Clone' : 'Design'}
${benchText !== '-' ? ` ${escHtml(benchText)}` : '-'}
${dbfs} dB
${fmtDuration(v.duration)}
-
${starsHtml}
+
+ ${window._voiceTableEditMode + ? `` + : starsHtml} +
${escHtml(v.origin || '-')}
${v.seed != null ? '#' + v.seed : 'Auto'}
-
${escHtml(v.note || '')}
-
${escHtml(v.tag || '')}
-
+
+ ${window._voiceTableEditMode + ? `` + : escHtml(v.note || '')} +
+
+ ${window._voiceTableEditMode + ? `` + : escHtml(v.tag || '')} +
+
+ ${window._voiceTableEditMode + ? `` + : ``} +
diff --git a/static/sections/s-voices.html b/static/sections/s-voices.html index 44302f9..bc712b3 100644 --- a/static/sections/s-voices.html +++ b/static/sections/s-voices.html @@ -74,6 +74,7 @@ +
diff --git a/static/style.css b/static/style.css index 5a4944d..a978e85 100644 --- a/static/style.css +++ b/static/style.css @@ -1411,6 +1411,31 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami .voices-workbench.table-view .vl-tbl-tag { display: block; order: 14; font-size: 12px; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; } .voices-workbench.table-view .vl-tbl-active { display: block; order: 15; font-size: 16px; text-align: center; } +.voices-workbench.table-edit-mode .vl-compact { + background: rgba(37,99,235,0.03); + border-bottom: 1px solid var(--border); +} + +.vl-inline-edit { + background: var(--surface-light); + border: 1px solid var(--border); + color: var(--text); + font-family: inherit; + font-size: 12px; + padding: 2px 4px; + border-radius: 4px; + outline: none; + transition: border-color 0.15s; +} +.vl-inline-edit:focus { + border-color: var(--accent); +} +.vl-inline-edit.vl-name-input { + font-size: 13px; + font-weight: 600; + width: 100%; +} + /* Synth mode panel (segmented control + preview input) */ .vl-synth-panel { background: var(--surface); } .vl-synth-mode-row { display: flex; align-items: center; gap: 7px; padding: 5px 8px 5px; }