feat(ui): add "All lossless / All lossy" group entries to ranked targets

Convenience: pick a group + constraints (e.g. All lossless, ≥24-bit/≥96kHz) and
it expands into one concrete per-format target each (FLAC/ALAC/WAV, or the five
lossy formats) at that slot — so you don't add them one by one. Purely UI; the
backend still ranks concrete per-format targets. Re-adding a group skips formats
that already have an identical target, and the expanded entries can be
reordered/pruned individually afterwards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
dev 2026-06-24 23:24:28 +02:00
parent 124e8bb21c
commit 551d12dba3
2 changed files with 34 additions and 9 deletions

View file

@ -5058,11 +5058,13 @@
<div class="ranked-target-add">
<select id="rt-add-format" onchange="onRtAddFormatChange()">
<optgroup label="Lossless">
<option value="group:lossless">All lossless (FLAC + ALAC + WAV)</option>
<option value="flac">FLAC</option>
<option value="alac">ALAC</option>
<option value="wav">WAV / AIFF</option>
</optgroup>
<optgroup label="Lossy">
<option value="group:lossy">All lossy (MP3 + AAC + OGG + Opus + WMA)</option>
<option value="mp3">MP3</option>
<option value="aac">AAC</option>
<option value="ogg">OGG (Vorbis)</option>

View file

@ -2069,9 +2069,18 @@ function deleteRankedTarget(i) {
// Lossless formats take bit-depth + sample-rate constraints; lossy take a
// minimum bitrate. Single source of truth for the add-target field toggle.
const RT_LOSSLESS_FORMATS = ['flac', 'alac', 'wav'];
const RT_LOSSY_FORMATS = ['mp3', 'aac', 'ogg', 'opus', 'wma'];
// "group:" selections are a UI convenience: picking one + constraints expands
// into individual per-format targets at that slot (the backend still works
// purely on concrete per-format targets). The user reorders/prunes after.
const RT_GROUPS = { 'group:lossless': RT_LOSSLESS_FORMATS, 'group:lossy': RT_LOSSY_FORMATS };
function rtSelectionIsLossless(val) {
return val === 'group:lossless' || RT_LOSSLESS_FORMATS.includes(val);
}
function onRtAddFormatChange() {
const lossless = RT_LOSSLESS_FORMATS.includes(document.getElementById('rt-add-format')?.value);
const lossless = rtSelectionIsLossless(document.getElementById('rt-add-format')?.value);
const llFields = document.querySelector('.rt-lossless-fields');
const lyFields = document.querySelector('.rt-lossy-fields');
if (llFields) llFields.style.display = lossless ? '' : 'none';
@ -2079,19 +2088,33 @@ function onRtAddFormatChange() {
}
function addRankedTarget() {
const fmt = document.getElementById('rt-add-format')?.value || 'flac';
const t = { format: fmt };
if (RT_LOSSLESS_FORMATS.includes(fmt)) {
const val = document.getElementById('rt-add-format')?.value || 'flac';
// Collect the constraints once; they apply to every format we add.
const constraints = {};
if (rtSelectionIsLossless(val)) {
const bd = document.getElementById('rt-add-bitdepth')?.value;
const sr = document.getElementById('rt-add-samplerate')?.value;
if (bd) t.bit_depth = parseInt(bd, 10);
if (sr) t.min_sample_rate = parseInt(sr, 10);
if (bd) constraints.bit_depth = parseInt(bd, 10);
if (sr) constraints.min_sample_rate = parseInt(sr, 10);
} else {
const br = document.getElementById('rt-add-bitrate')?.value;
if (br) t.min_bitrate = parseInt(br, 10);
if (br) constraints.min_bitrate = parseInt(br, 10);
}
t.label = rtLabel(t);
currentRankedTargets.push(t);
// A group expands into one concrete target per format; a single format is
// just a one-element list. Skip a format that already has an identical
// target so re-adding a group doesn't pile up duplicates.
const formats = RT_GROUPS[val] || [val];
const sig = (t) => `${t.format}|${t.bit_depth || ''}|${t.min_sample_rate || ''}|${t.min_bitrate || ''}`;
const existing = new Set(currentRankedTargets.map(sig));
formats.forEach(fmt => {
const t = { format: fmt, ...constraints };
if (existing.has(sig(t))) return;
t.label = rtLabel(t);
currentRankedTargets.push(t);
existing.add(sig(t));
});
renderRankedTargets();
debouncedSaveQualityProfile();
}