fix: remplacer prompt/confirm par modals HTML (non supportés dans Electron)

Electron bloque prompt() et confirm() dans le renderer process.
Ajout d'un showModal() générique (champs configurables, mode confirmation)
avec overlay, focus auto, Escape/Enter, et fermeture en cliquant l'overlay.
Tous les appels prompt/confirm dans editGroup, deleteGroup et addGroup
sont migrés vers showModal.
This commit is contained in:
2026-07-01 13:28:08 +02:00
parent 955bfdfe07
commit b3fbe31a2d
3 changed files with 180 additions and 11 deletions
+114 -11
View File
@@ -337,13 +337,21 @@ async function fetchGroups() {
}
async function editGroup(id, currentName, currentBitrate) {
const newName = prompt('Nom du groupe:', currentName);
if (newName === null || newName.trim() === '') return;
const result = await showModal({
title: 'Modifier le groupe',
fields: [
{ name: 'name', label: 'Nom', default: currentName },
{ name: 'bitrate', label: 'Bitrate (kbps)', type: 'number', default: currentBitrate, min: 32, max: 320, step: 1 }
],
confirmLabel: 'Modifier'
});
const newBitrateStr = prompt('Bitrate (kbps, 32-320):', String(currentBitrate));
if (newBitrateStr === null) return;
if (!result) return;
const newBitrate = parseInt(newBitrateStr);
const newName = result.name.trim();
const newBitrate = parseInt(result.bitrate);
if (!newName) { showNotification('Nom requis', 'error'); return; }
if (isNaN(newBitrate) || newBitrate < 32 || newBitrate > 320) {
showNotification('Bitrate invalide (32-320 kbps)', 'error');
return;
@@ -353,7 +361,7 @@ async function editGroup(id, currentName, currentBitrate) {
const response = await fetch(`${API_BASE}/admin/groups/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: newName.trim(), audioBitrate: newBitrate })
body: JSON.stringify({ name: newName, audioBitrate: newBitrate })
});
if (response.ok) {
@@ -370,7 +378,14 @@ async function editGroup(id, currentName, currentBitrate) {
}
async function deleteGroup(id, name) {
if (!confirm(`Supprimer le groupe "${name}" ?`)) return;
const confirmed = await showModal({
title: 'Supprimer le groupe',
message: `Supprimer le groupe "${name}" ? Cette action est irréversible.`,
confirmLabel: 'Supprimer',
confirmClass: 'btn-danger'
});
if (!confirmed) return;
try {
const response = await fetch(`${API_BASE}/admin/groups/${id}`, {
@@ -623,21 +638,33 @@ document.addEventListener('DOMContentLoaded', () => {
const btnAddGroup = document.getElementById('btn-add-group');
if (btnAddGroup) {
btnAddGroup.addEventListener('click', async () => {
const name = prompt('Nom du groupe:');
if (!name) return;
const result = await showModal({
title: 'Nouveau groupe',
fields: [
{ name: 'name', label: 'Nom du groupe' },
{ name: 'bitrate', label: 'Bitrate (kbps)', type: 'number', default: 96, min: 32, max: 320, step: 1 }
],
confirmLabel: 'Créer'
});
if (!result || !result.name.trim()) return;
const name = result.name.trim();
const audioBitrate = parseInt(result.bitrate) || 96;
try {
const response = await fetch(`${API_BASE}/admin/groups`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, audioBitrate: 96 })
body: JSON.stringify({ name, audioBitrate })
});
if (response.ok) {
showNotification('Groupe créé', 'success');
await fetchGroups();
} else {
showNotification('Erreur création groupe', 'error');
const err = await response.json().catch(() => ({}));
showNotification('Erreur: ' + (err.error || 'Création échouée'), 'error');
}
} catch (error) {
console.error('Erreur add group:', error);
@@ -725,6 +752,82 @@ document.addEventListener('DOMContentLoaded', () => {
// ========== Helpers ==========
/**
* Modal générique (remplace prompt/confirm, non supportés dans Electron).
* - fields[] → formulaire ; message → confirmation simple
* Retourne : objet {champ: valeur} | true (confirm) | null (annulé)
*/
function showModal({ title, fields = [], confirmLabel = 'Confirmer', confirmClass = 'btn-primary', message = null }) {
return new Promise((resolve) => {
const overlay = document.getElementById('modal-overlay');
const titleEl = document.getElementById('modal-title');
const bodyEl = document.getElementById('modal-body');
const cancelBtn = document.getElementById('modal-cancel');
const confirmBtn = document.getElementById('modal-confirm');
titleEl.textContent = title;
confirmBtn.textContent = confirmLabel;
confirmBtn.className = `btn ${confirmClass}`;
if (message) {
bodyEl.innerHTML = `<p class="modal-message">${escapeHtml(message)}</p>`;
} else {
bodyEl.innerHTML = fields.map(field => `
<div class="form-group">
<label>${escapeHtml(field.label)}</label>
<input
type="${field.type || 'text'}"
id="modal-field-${field.name}"
class="form-control"
value="${escapeHtml(String(field.default ?? ''))}"
${field.min !== undefined ? `min="${field.min}"` : ''}
${field.max !== undefined ? `max="${field.max}"` : ''}
${field.step !== undefined ? `step="${field.step}"` : ''}>
</div>
`).join('');
}
overlay.classList.remove('hidden');
const firstInput = bodyEl.querySelector('input');
if (firstInput) { firstInput.focus(); firstInput.select(); }
function cleanup() {
overlay.classList.add('hidden');
cancelBtn.removeEventListener('click', onCancel);
confirmBtn.removeEventListener('click', onConfirm);
overlay.removeEventListener('click', onOverlayClick);
document.removeEventListener('keydown', onKeydown);
}
function onCancel() { cleanup(); resolve(null); }
function onConfirm() {
if (message) {
cleanup(); resolve(true);
} else {
const result = {};
fields.forEach(f => {
const input = document.getElementById(`modal-field-${f.name}`);
result[f.name] = input ? input.value : '';
});
cleanup(); resolve(result);
}
}
function onOverlayClick(e) { if (e.target === overlay) onCancel(); }
function onKeydown(e) {
if (e.key === 'Escape') onCancel();
if (e.key === 'Enter' && document.activeElement?.tagName !== 'BUTTON') onConfirm();
}
cancelBtn.addEventListener('click', onCancel);
confirmBtn.addEventListener('click', onConfirm);
overlay.addEventListener('click', onOverlayClick);
document.addEventListener('keydown', onKeydown);
});
}
function slugify(text) {
return text.toString().normalize('NFD').replace(/[̀-ͯ]/g, '')
.toLowerCase().trim().replace(/\s+/g, '-').replace(/[^\w-]+/g, '').replace(/--+/g, '-');