diff --git a/frontend/js/i18n/de.js b/frontend/js/i18n/de.js index 692d7cd..f9a0166 100644 --- a/frontend/js/i18n/de.js +++ b/frontend/js/i18n/de.js @@ -126,6 +126,8 @@ export default { 'dashboard.prompt_group_name': 'Gruppenname:', 'dashboard.error_pairing_code': 'Geben Sie einen gültigen 6-stelligen Kopplungscode ein', 'dashboard.confirm_add_to_group': '{name} ist bereits in: {groups}\n\nAuch zu „{target}“ hinzufügen?', + 'dashboard.confirm_move_to_group': '{name} ist derzeit in: {groups}\n\nNach "{target}" verschieben?', + 'dashboard.toast.move_partial': 'Entfernen aus {group} fehlgeschlagen — Bildschirm ist noch in beiden', 'dashboard.confirm_assign_playlist': 'Playlist „{playlist}“ allen Geräten in „{group}“ zuweisen?', 'dashboard.confirm_destructive_command': '{cmd} alle {n} Geräte in „{group}“?\n\nDies kann nicht rückgängig gemacht werden.', 'dashboard.confirm_delete_group': 'Diese Gruppe löschen? Geräte sind nicht betroffen.', diff --git a/frontend/js/i18n/en.js b/frontend/js/i18n/en.js index 4aaead6..b43a047 100644 --- a/frontend/js/i18n/en.js +++ b/frontend/js/i18n/en.js @@ -223,6 +223,8 @@ export default { 'dashboard.prompt_group_name': 'Group name:', 'dashboard.error_pairing_code': 'Enter a valid 6-digit pairing code', 'dashboard.confirm_add_to_group': '{name} is already in: {groups}\n\nAdd it to "{target}" too?', + 'dashboard.confirm_move_to_group': '{name} is currently in: {groups}\n\nMove it to "{target}"?', + 'dashboard.toast.move_partial': 'Could not remove it from {group} — it is still in both', 'dashboard.confirm_assign_playlist': 'Assign playlist "{playlist}" to all devices in "{group}"?', 'dashboard.confirm_destructive_command': '{cmd} all {n} devices in "{group}"?\n\nThis cannot be undone.', 'dashboard.confirm_delete_group': 'Delete this group? Devices will not be affected.', diff --git a/frontend/js/i18n/es.js b/frontend/js/i18n/es.js index b85d89e..7f75d64 100644 --- a/frontend/js/i18n/es.js +++ b/frontend/js/i18n/es.js @@ -126,6 +126,8 @@ export default { 'dashboard.prompt_group_name': 'Nombre del grupo:', 'dashboard.error_pairing_code': 'Ingresa un código de vinculación válido de 6 dígitos', 'dashboard.confirm_add_to_group': '{name} ya está en: {groups}\n\n¿Agregarlo también a "{target}"?', + 'dashboard.confirm_move_to_group': '{name} está actualmente en: {groups}\n\n¿Moverla a "{target}"?', + 'dashboard.toast.move_partial': 'No se pudo quitar de {group}: sigue en ambos', 'dashboard.confirm_assign_playlist': '¿Asignar la lista "{playlist}" a todos los dispositivos de "{group}"?', 'dashboard.confirm_destructive_command': '¿{cmd} todos los {n} dispositivos de "{group}"?\n\nEsto no se puede deshacer.', 'dashboard.confirm_delete_group': '¿Eliminar este grupo? Los dispositivos no se verán afectados.', diff --git a/frontend/js/i18n/fr.js b/frontend/js/i18n/fr.js index 57ac05a..29b237f 100644 --- a/frontend/js/i18n/fr.js +++ b/frontend/js/i18n/fr.js @@ -126,6 +126,8 @@ export default { 'dashboard.prompt_group_name': 'Nom du groupe :', 'dashboard.error_pairing_code': 'Saisissez un code d\'appairage valide à 6 chiffres', 'dashboard.confirm_add_to_group': '{name} est déjà dans : {groups}\n\nL\'ajouter aussi à « {target} » ?', + 'dashboard.confirm_move_to_group': '{name} est actuellement dans : {groups}\n\nLe déplacer vers "{target}" ?', + 'dashboard.toast.move_partial': 'Impossible de le retirer de {group} — il est encore dans les deux', 'dashboard.confirm_assign_playlist': 'Attribuer la liste « {playlist} » à tous les appareils de « {group} » ?', 'dashboard.confirm_destructive_command': '{cmd} les {n} appareils de « {group} » ?\n\nCette action est irréversible.', 'dashboard.confirm_delete_group': 'Supprimer ce groupe ? Les appareils ne seront pas affectés.', diff --git a/frontend/js/i18n/it.js b/frontend/js/i18n/it.js index ce6a98f..99d3bab 100644 --- a/frontend/js/i18n/it.js +++ b/frontend/js/i18n/it.js @@ -127,6 +127,8 @@ export default { 'dashboard.prompt_group_name': 'Nome gruppo:', 'dashboard.error_pairing_code': 'Inserisci un codice di associazione valido di 6 cifre', 'dashboard.confirm_add_to_group': '{name} è già presente in: {groups}\n\nAggiungerlo anche a "{target}"?', + 'dashboard.confirm_move_to_group': '{name} è attualmente in: {groups}\n\nSpostarlo in "{target}"?', + 'dashboard.toast.move_partial': 'Impossibile rimuoverlo da {group}: è ancora in entrambi', 'dashboard.confirm_assign_playlist': 'Assegnare la playlist "{playlist}" a tutti i dispositivi in "{group}"?', 'dashboard.confirm_destructive_command': 'Eseguire {cmd} su tutti i {n} dispositivi in "{group}"?\n\nL\'azione è irreversibile.', 'dashboard.confirm_delete_group': 'Eliminare questo gruppo? I dispositivi non verranno rimossi dal sistema.', diff --git a/frontend/js/i18n/pt.js b/frontend/js/i18n/pt.js index 3d78ab5..50335fa 100644 --- a/frontend/js/i18n/pt.js +++ b/frontend/js/i18n/pt.js @@ -126,6 +126,8 @@ export default { 'dashboard.prompt_group_name': 'Nome do grupo:', 'dashboard.error_pairing_code': 'Digite um código de pareamento válido de 6 dígitos', 'dashboard.confirm_add_to_group': '{name} já está em: {groups}\n\nAdicionar também a "{target}"?', + 'dashboard.confirm_move_to_group': '{name} está atualmente em: {groups}\n\nMover para "{target}"?', + 'dashboard.toast.move_partial': 'Não foi possível remover de {group} — continua em ambos', 'dashboard.confirm_assign_playlist': 'Atribuir a playlist "{playlist}" a todos os dispositivos de "{group}"?', 'dashboard.confirm_destructive_command': '{cmd} todos os {n} dispositivos de "{group}"?\n\nIsso não pode ser desfeito.', 'dashboard.confirm_delete_group': 'Excluir este grupo? Os dispositivos não serão afetados.', diff --git a/frontend/js/views/dashboard.js b/frontend/js/views/dashboard.js index 252e629..60232dd 100644 --- a/frontend/js/views/dashboard.js +++ b/frontend/js/views/dashboard.js @@ -767,13 +767,28 @@ function attachGroupHandlers(groupsWithDevices, allDevices) { showToast(t('dashboard.toast.already_in_group', { name: deviceName, group: targetGroup.name }), 'info'); return; } - // If the device is in another group, mirror the Manage modal's confirm. - const others = (groupsByDeviceId.get(deviceId) || []).map(g => g.name); + // Dragging a screen onto a group MOVES it. This used to borrow the Manage modal's + // "add it too?" confirm and then only add — so the screen ended up in both groups while the + // toast claimed it had moved, the page still showed the old group, and a second attempt said + // "already in group 2". Reported by a customer doing exactly that with two screens. + // The Manage modal keeps add/remove checkboxes: multi-group membership is deliberate THERE. + // It is not deliberate here, and it is not harmless — deviceSyncGroup() picks arbitrarily + // when a device is in several sync-enabled groups, so a half-move leaves sync ambiguous. + const others = groupsByDeviceId.get(deviceId) || []; if (others.length > 0) { - if (!confirm(t('dashboard.confirm_add_to_group', { name: deviceName, groups: others.join(', '), target: targetGroup.name }))) return; + if (!confirm(t('dashboard.confirm_move_to_group', { + name: deviceName, groups: others.map(g => g.name).join(', '), target: targetGroup.name, + }))) return; } try { + // Add first, then drop the old memberships: if the add fails the screen keeps the group it + // had rather than being left ungrouped by a half-finished move. await api.addDeviceToGroup(groupId, deviceId); + for (const g of others) { + if (g.id === groupId) continue; + try { await api.removeDeviceFromGroup(g.id, deviceId); } + catch (e) { showToast(t('dashboard.toast.move_partial', { group: g.name }), 'warning'); } + } showToast(t('dashboard.toast.moved_device', { name: deviceName, group: targetGroup.name }), 'success'); loadDashboard(); } catch (err) { showToast(err.message, 'error'); }