fix: volume slider par alerte, défaut 10%, suppression du pre-cache au démarrage

- Chaque alerte a son propre slider volume (0–100%, step 1, défaut 10%)
- Volume très bas possible (0%) — pas de minimum forcé
- Slider volume aussi dans le formulaire d'ajout pour setter dès la création
- Supprime le pre-cache IndexedDB au démarrage qui ralentissait le lancement
- Supprime le slider volume global (remplacé par le per-alerte)
- Migration automatique des anciennes alertes vers volume=10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
s
2026-07-02 17:40:35 +02:00
parent 857a8477d7
commit 0562b326c8

View File

@@ -748,19 +748,19 @@
<div class="panel">
<div class="panel-head-row">
<h2>Alertes de drop</h2>
<div style="display:flex;align-items:center;gap:8px;margin-left:auto;">
<span style="font-size:11px;color:var(--text-dim);">Volume</span>
<input type="range" id="alertGlobalVolume" min="0" max="100" step="5" value="25" style="width:80px;">
<span id="alertGlobalVolumeVal" style="font-size:10px;font-family:var(--font-mono);color:var(--text-dim);min-width:28px;text-align:right;">25%</span>
</div>
</div>
<div class="timer-add-row">
<input type="text" id="alertItemInput" placeholder="Nom de l'item (ex: Diamond)" style="flex:2;" list="alertItemSuggestions" autocomplete="off">
<div class="timer-add-row" style="flex-wrap:wrap;gap:6px;">
<input type="text" id="alertItemInput" placeholder="Nom de l'item (ex: Diamond)" style="flex:2;min-width:140px;" list="alertItemSuggestions" autocomplete="off">
<datalist id="alertItemSuggestions"></datalist>
<select id="alertSoundSelect" class="alert-cfg-sound"></select>
<div style="display:flex;align-items:center;gap:5px;">
<span style="font-size:10px;color:var(--text-dim);">Vol</span>
<input type="range" id="alertVolumeInput" min="0" max="100" step="1" value="10" style="width:64px;">
<span id="alertVolumeVal" style="font-size:10px;font-family:var(--font-mono);color:var(--text-dim);min-width:26px;">10%</span>
</div>
<button class="btn-ghost" id="alertAddBtn">Ajouter</button>
</div>
<p style="font-size:11.5px;color:var(--text-dim);margin:6px 0 4px;">Son joué à chaque fois que cet item drop. Volume global s'applique à toutes les alertes.</p>
<p style="font-size:11.5px;color:var(--text-dim);margin:6px 0 4px;">Chaque alerte a son propre son et volume.</p>
<div id="alertConfigList"></div>
</div>
@@ -2313,14 +2313,10 @@
return decoded;
}
function getAlertVolume(){
return parseInt(localStorage.getItem('alertGlobalVolume') || '25', 10) / 100;
}
async function playAlert(cfg){
try {
const soundId = cfg ? (cfg.soundId || 'levelup') : 'levelup';
const vol = getAlertVolume();
const vol = Math.max(0, Math.min(1, ((cfg && cfg.volume != null) ? cfg.volume : 10) / 100));
const buf = await _getDecodedBuffer(soundId);
if (!buf){ console.warn('Sound not cached:', soundId); return; }
const ctx = getAudioCtx();
@@ -2329,23 +2325,20 @@
src.buffer = buf;
src.connect(gain);
gain.connect(ctx.destination);
gain.gain.value = Math.max(0, Math.min(1, vol));
gain.gain.value = vol;
src.start();
} catch(e){ console.warn('playAlert failed', e); }
}
// migration ancien format (sans soundId)
// migration : ajoute soundId et volume si absents
let alertConfigs = JSON.parse(localStorage.getItem('alertConfigs') || '[]').map(c => ({
soundId: 'levelup', ...c
soundId: 'levelup', volume: 10, ...c
}));
function saveAlertConfigs(){
localStorage.setItem('alertConfigs', JSON.stringify(alertConfigs));
}
// Pre-cache levelup.ogg au démarrage (son par défaut)
_getDecodedBuffer('levelup').catch(() => {});
// activeTimers : Map<configId, { endsAt: number, durationSec: number, mobName: string }>
const activeTimers = new Map();
let timerClockId = null;
@@ -2513,9 +2506,12 @@
const label = s.label + (notCached ? ' (⬇)' : '');
return '<option value="'+escHtml(s.id)+'"'+(s.id === sid ? ' selected' : '')+'>'+escHtml(label)+'</option>';
}).join('');
const vol = cfg.volume != null ? cfg.volume : 10;
return '<div class="alert-cfg-row">'+
'<span class="alert-cfg-name">'+escHtml(cfg.itemName)+'</span>'+
'<select class="alert-cfg-sound" data-alert-sound="'+escHtml(cfg.id)+'">'+opts+'</select>'+
'<input type="range" min="0" max="100" step="1" value="'+vol+'" data-alert-vol="'+escHtml(cfg.id)+'" style="width:64px;" title="Volume: '+vol+'%">'+
'<span data-alert-vol-label="'+escHtml(cfg.id)+'" style="font-size:10px;font-family:var(--font-mono);color:var(--text-dim);min-width:26px;">'+vol+'%</span>'+
'<button class="btn-ghost small" data-preview-alert="'+escHtml(cfg.id)+'" title="Prévisualiser">▶</button>'+
'<button class="btn-ghost small danger" data-del-alert="'+escHtml(cfg.id)+'">✕</button>'+
'</div>';
@@ -2550,16 +2546,21 @@
sel.innerHTML = buildSoundOptions('levelup');
}
// slider volume dans le formulaire d'ajout
$('alertVolumeInput').addEventListener('input', () => {
$('alertVolumeVal').textContent = $('alertVolumeInput').value + '%';
});
$('alertAddBtn').addEventListener('click', () => {
const itemName = $('alertItemInput').value.trim();
if (!itemName) return;
const soundId = $('alertSoundSelect').value || 'levelup';
alertConfigs.push({ id: generateUUID(), itemName, nameLower: itemName.toLowerCase(), soundId });
const volume = parseInt($('alertVolumeInput').value, 10);
alertConfigs.push({ id: generateUUID(), itemName, nameLower: itemName.toLowerCase(), soundId, volume });
saveAlertConfigs();
$('alertItemInput').value = '';
renderAlertConfigList();
populateAlertItemSuggestions();
// auto-download si son pas encore en cache
if (soundId !== 'levelup') isSoundCached(soundId).then(ok => { if (!ok) downloadSound(soundId).catch(()=>{}); });
});
@@ -2592,23 +2593,26 @@
});
$('alertConfigList').addEventListener('change', e => {
const id = e.target.dataset.alertSound;
if (!id) return;
const cfg = alertConfigs.find(c => c.id === id);
const soundId = e.target.dataset.alertSound;
if (soundId){
const cfg = alertConfigs.find(c => c.id === soundId);
if (!cfg) return;
cfg.soundId = e.target.value;
saveAlertConfigs();
if (cfg.soundId !== 'levelup') isSoundCached(cfg.soundId).then(ok => { if (!ok) downloadSound(cfg.soundId).catch(()=>{}); });
return;
}
});
// ---- Volume global ----
const _volSlider = $('alertGlobalVolume');
const _volLabel = $('alertGlobalVolumeVal');
const _savedVol = localStorage.getItem('alertGlobalVolume') || '25';
if (_volSlider){ _volSlider.value = _savedVol; _volLabel.textContent = _savedVol + '%'; }
_volSlider && _volSlider.addEventListener('input', () => {
localStorage.setItem('alertGlobalVolume', _volSlider.value);
_volLabel.textContent = _volSlider.value + '%';
$('alertConfigList').addEventListener('input', e => {
const id = e.target.dataset.alertVol;
if (!id) return;
const cfg = alertConfigs.find(c => c.id === id);
if (!cfg) return;
cfg.volume = parseInt(e.target.value, 10);
saveAlertConfigs();
const label = document.querySelector('[data-alert-vol-label="'+id+'"]');
if (label) label.textContent = cfg.volume + '%';
});
// ---- Sound library ----