mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-22 21:01:40 +00:00
Removes the Floating Button and Tuning Visibility settings sections and finishes retiring their still-live config: drops the disabledTunings menu filter and showFloatingButton gate from screen.js/ui.js and their persistence in routes.py (retired keys are stripped on write). Repositions the tuner panel opened from the v3 sidebar Plugins popover to anchor beside it via the host's stable plugin-control slot API (falling back to the popover id), clamped to the viewport so it can't open off-screen, and re-anchored on resize. Updates tuner config tests to the retired-key behavior; plugins/tuner 1.3.2 -> 1.3.3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
215 lines
11 KiB
HTML
215 lines
11 KiB
HTML
<div class="space-y-6 py-2">
|
|
<div class="flex items-center justify-between bg-dark-900/50 p-3 rounded-xl border border-gray-800/50">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-200">Auto-open on tuning change</h3>
|
|
<p class="text-[11px] text-gray-500">When a song (or arrangement) needs a different tuning, pop the tuner open automatically. It stays open until you Skip or close it.</p>
|
|
</div>
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" id="tuner-auto-open" class="sr-only peer" onchange="window._tunerToggleAutoOpen(this.checked)">
|
|
<div class="w-9 h-5 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-accent"></div>
|
|
</label>
|
|
</div>
|
|
|
|
<script>
|
|
if (window.feedBackDesktop && window.feedBackDesktop.isDesktop) {
|
|
document.currentScript.insertAdjacentHTML('beforebegin', `
|
|
<div class="flex items-center justify-between bg-dark-900/50 p-3 rounded-xl border border-gray-800/50">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-200">Force Browser Audio</h3>
|
|
<p class="text-[11px] text-gray-500">Use the browser microphone pipeline instead of the desktop audio engine.</p>
|
|
</div>
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" id="tuner-force-browser-audio" class="sr-only peer" onchange="window._tunerToggleBrowserAudio(this.checked)">
|
|
<div class="w-9 h-5 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-accent"></div>
|
|
</label>
|
|
</div>`);
|
|
}
|
|
</script>
|
|
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-400 mb-3">Custom Tunings</h3>
|
|
<div id="tuner-custom-list" class="space-y-2 mb-4">
|
|
<!-- Populated by JS -->
|
|
</div>
|
|
|
|
<div class="bg-dark-900/50 p-3 rounded-xl border border-gray-800/50">
|
|
<p class="text-[11px] font-bold text-gray-500 uppercase tracking-wider mb-3">Add Custom Tuning</p>
|
|
<div class="space-y-3">
|
|
<input type="text" id="tuner-new-name" placeholder="Tuning Name (e.g. DADGAD)"
|
|
class="w-full bg-dark-800 border-none rounded-lg px-3 py-2 text-xs text-white focus:ring-1 focus:ring-accent/30 outline-none">
|
|
|
|
<select id="tuner-new-instrument"
|
|
class="w-full bg-dark-800 border-none rounded-lg px-3 py-2 text-xs text-white focus:ring-1 focus:ring-accent/30 outline-none">
|
|
<optgroup label="Guitar">
|
|
<option value="guitar-6">Guitar 6-string</option>
|
|
<option value="guitar-7">Guitar 7-string</option>
|
|
<option value="guitar-8">Guitar 8-string</option>
|
|
</optgroup>
|
|
<optgroup label="Bass">
|
|
<option value="bass-4">Bass 4-string</option>
|
|
<option value="bass-5">Bass 5-string</option>
|
|
</optgroup>
|
|
</select>
|
|
|
|
<div class="flex flex-col gap-1">
|
|
<label class="text-[10px] text-gray-500 ml-1">Notes or Frequencies (e.g. E2, A2, D3, G3 or 82.41, 110.0...)</label>
|
|
<input type="text" id="tuner-new-freqs" placeholder="E2, A2, D3, G3, B3, E4"
|
|
class="w-full bg-dark-800 border-none rounded-lg px-3 py-2 text-xs text-white focus:ring-1 focus:ring-accent/30 outline-none font-mono">
|
|
</div>
|
|
|
|
<button onclick="window._tunerAddCustom()"
|
|
class="w-full bg-accent/20 hover:bg-accent/30 text-accent text-xs font-bold py-2 rounded-lg transition-colors border border-accent/20">
|
|
Add Tuning
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-[11px] text-gray-600 italic">Settings are saved automatically.</p>
|
|
|
|
<script>
|
|
(function() {
|
|
let config = { customTunings: {}, audioInputMode: 'auto' };
|
|
|
|
var _instrumentLabels = {
|
|
"guitar-6": "Guitar 6-string",
|
|
"guitar-7": "Guitar 7-string",
|
|
"guitar-8": "Guitar 8-string",
|
|
"bass-4": "Bass 4-string",
|
|
"bass-5": "Bass 5-string"
|
|
};
|
|
|
|
async function load() {
|
|
try {
|
|
const resp = await fetch('/api/plugins/tuner/config');
|
|
config = await resp.json();
|
|
|
|
const browserAudioToggle = document.getElementById('tuner-force-browser-audio');
|
|
if (browserAudioToggle) browserAudioToggle.checked = config.audioInputMode === 'browser';
|
|
|
|
const autoOpenToggle = document.getElementById('tuner-auto-open');
|
|
if (autoOpenToggle) autoOpenToggle.checked = config.autoOpenOnTuningChange === true;
|
|
|
|
render();
|
|
} catch (e) { console.error('Tuner settings: load failed', e); }
|
|
}
|
|
|
|
window._tunerToggleBrowserAudio = (forceBrowser) => {
|
|
config.audioInputMode = forceBrowser ? 'browser' : 'auto';
|
|
save();
|
|
};
|
|
|
|
window._tunerToggleAutoOpen = (enabled) => {
|
|
config.autoOpenOnTuningChange = enabled;
|
|
save();
|
|
};
|
|
|
|
async function save(opts) {
|
|
try {
|
|
await fetch('/api/plugins/tuner/config', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(config)
|
|
});
|
|
if (window._tunerReloadConfig) window._tunerReloadConfig();
|
|
if (opts && opts.tuningsChanged) window.feedBack?.emit('tunings:updated');
|
|
} catch (e) { console.error('Tuner settings: save failed', e); }
|
|
}
|
|
|
|
function render() {
|
|
const customList = document.getElementById('tuner-custom-list');
|
|
customList.innerHTML = '';
|
|
const customNames = Object.keys(config.customTunings);
|
|
if (customNames.length === 0) {
|
|
customList.innerHTML = '<p class="text-[10px] text-gray-600 italic ml-1">No custom tunings added.</p>';
|
|
} else {
|
|
customNames.forEach(name => {
|
|
const val = config.customTunings[name];
|
|
const strings = Array.isArray(val) ? val : (val.strings || []);
|
|
const instrument = Array.isArray(val) ? 'guitar-6' : (val.instrument || 'guitar-6');
|
|
const instrLabel = _instrumentLabels[instrument] || instrument;
|
|
|
|
const div = document.createElement('div');
|
|
div.className = 'flex items-center justify-between p-2 bg-dark-800/30 rounded-lg border border-gray-800/50';
|
|
|
|
const info = document.createElement('div');
|
|
info.className = 'flex flex-col';
|
|
const nEl = document.createElement('span');
|
|
nEl.className = 'text-xs text-white font-medium';
|
|
nEl.textContent = name + ' · ' + instrLabel;
|
|
const fEl = document.createElement('span');
|
|
fEl.className = 'text-[10px] text-gray-500 font-mono';
|
|
fEl.textContent = strings.join(', ');
|
|
info.appendChild(nEl);
|
|
info.appendChild(fEl);
|
|
|
|
const del = document.createElement('button');
|
|
del.innerHTML = '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>';
|
|
del.className = 'text-gray-600 hover:text-red-400 transition-colors p-1';
|
|
del.onclick = () => {
|
|
delete config.customTunings[name];
|
|
save({ tuningsChanged: true });
|
|
render();
|
|
};
|
|
|
|
div.appendChild(info);
|
|
div.appendChild(del);
|
|
customList.appendChild(div);
|
|
});
|
|
}
|
|
}
|
|
|
|
function noteToFreq(note) {
|
|
const notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
|
const res = note.toUpperCase().match(/^([A-G]#?)(\d)$/);
|
|
if (!res) return null;
|
|
const name = res[1];
|
|
const octave = parseInt(res[2]);
|
|
const semitones = notes.indexOf(name) + (octave + 1) * 12;
|
|
return 440 * Math.pow(2, (semitones - 69) / 12);
|
|
}
|
|
|
|
window._tunerAddCustom = () => {
|
|
const nameInput = document.getElementById('tuner-new-name');
|
|
const instrInput = document.getElementById('tuner-new-instrument');
|
|
const freqsInput = document.getElementById('tuner-new-freqs');
|
|
const name = nameInput.value.trim();
|
|
const instrument = instrInput.value;
|
|
const inputStr = freqsInput.value.trim();
|
|
|
|
let errP = document.getElementById('tuner-add-error');
|
|
if (!errP) {
|
|
errP = document.createElement('p');
|
|
errP.id = 'tuner-add-error';
|
|
errP.className = 'text-[10px] text-red-400 mt-1 ml-1 hidden';
|
|
document.getElementById('tuner-new-freqs').parentElement.parentElement.appendChild(errP);
|
|
}
|
|
const showErr = (msg) => { errP.textContent = msg; errP.classList.remove('hidden'); };
|
|
const clearErr = () => errP.classList.add('hidden');
|
|
|
|
if (!name || !inputStr) return showErr('Enter a name and notes/frequencies.');
|
|
|
|
const freqs = inputStr.split(',').map(s => {
|
|
s = s.trim();
|
|
const f = parseFloat(s);
|
|
if (!isNaN(f)) return Math.round(f * 100) / 100;
|
|
const nf = noteToFreq(s);
|
|
return nf !== null ? Math.round(nf * 100) / 100 : null;
|
|
}).filter(f => f !== null && !isNaN(f));
|
|
|
|
if (freqs.length === 0) return showErr('Invalid notes or frequencies — use E2, A2 or Hz values.');
|
|
|
|
clearErr();
|
|
config.customTunings[name] = { instrument, strings: freqs };
|
|
save({ tuningsChanged: true });
|
|
render();
|
|
nameInput.value = '';
|
|
instrInput.value = 'guitar-6';
|
|
freqsInput.value = '';
|
|
};
|
|
|
|
load();
|
|
})();
|
|
</script>
|
|
</div>
|