Surface the screenshot-request verdict as a toast

The server already acks dashboard:request-screenshot with
{ delivered, reason } (offline / unsupported via the capability
registry), but no dashboard sender passed a callback, so clicking
Screenshot on an offline device or an unsupporting player type showed
"Screenshot requested" and then silently did nothing.

requestScreenshot() now takes an optional callback using the same
.timeout(5000) pattern as sendCommand(); the device-detail Screenshot
button passes one and toasts the verdict (requested / unsupported /
offline / no response). The dashboard grid and the 5s Now Playing poll
keep firing-and-forgetting - no behavior change there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131RYmVh8ePEhparD3mXBhU
This commit is contained in:
Rob K 2026-08-07 08:52:19 +01:00
parent 16d8295373
commit 0f2ec474f4
8 changed files with 42 additions and 5 deletions

View file

@ -347,6 +347,9 @@ export default {
'device.assign.select_first': 'Erst etwas auswählen',
'device.assign.kiosk_widget_name': 'Kiosk: {name}',
'device.toast.screenshot_requested': 'Screenshot angefordert',
'device.toast.screenshot_unsupported': 'Der Player dieses Displays unterstützt keine Screenshots',
'device.toast.screenshot_offline': 'Display ist offline — Screenshot nicht angefordert',
'device.toast.screenshot_failed': 'Screenshot-Anfrage fehlgeschlagen — keine Antwort vom Server',
'device.toast.renamed': 'Bildschirm umbenannt',
'device.toast.removing': 'Wird entfernt...',
'device.toast.removed': 'Bildschirm entfernt',

View file

@ -648,6 +648,9 @@ export default {
'device.assign.kiosk_widget_name': 'Kiosk: {name}',
// Toasts
'device.toast.screenshot_requested': 'Screenshot requested',
'device.toast.screenshot_unsupported': 'This display\'s player can\'t take screenshots',
'device.toast.screenshot_offline': 'Display is offline — screenshot not requested',
'device.toast.screenshot_failed': 'Screenshot request failed — no response from server',
'device.toast.renamed': 'Display renamed',
'device.toast.removing': 'Removing...',
'device.toast.removed': 'Display removed',

View file

@ -377,6 +377,9 @@ export default {
'device.assign.select_first': 'Primero selecciona algo',
'device.assign.kiosk_widget_name': 'Kiosco: {name}',
'device.toast.screenshot_requested': 'Captura solicitada',
'device.toast.screenshot_unsupported': 'El reproductor de esta pantalla no admite capturas de pantalla',
'device.toast.screenshot_offline': 'La pantalla está desconectada — captura no solicitada',
'device.toast.screenshot_failed': 'La solicitud de captura falló — sin respuesta del servidor',
'device.toast.renamed': 'Pantalla renombrada',
'device.toast.removing': 'Eliminando...',
'device.toast.removed': 'Pantalla eliminada',

View file

@ -347,6 +347,9 @@ export default {
'device.assign.select_first': 'Sélectionnez d\'abord un élément',
'device.assign.kiosk_widget_name': 'Kiosque : {name}',
'device.toast.screenshot_requested': 'Capture demandée',
'device.toast.screenshot_unsupported': 'Le lecteur de cet écran ne prend pas en charge les captures d\'écran',
'device.toast.screenshot_offline': 'Écran hors ligne — capture non demandée',
'device.toast.screenshot_failed': 'Échec de la demande de capture — pas de réponse du serveur',
'device.toast.renamed': 'Écran renommé',
'device.toast.removing': 'Suppression...',
'device.toast.removed': 'Écran retiré',

View file

@ -371,6 +371,9 @@ export default {
'device.assign.kiosk_widget_name': 'Chiosco: {name}',
// Toasts
'device.toast.screenshot_requested': 'Richiesta screenshot inviata',
'device.toast.screenshot_unsupported': 'Il player di questo schermo non supporta gli screenshot',
'device.toast.screenshot_offline': 'Lo schermo è offline — screenshot non richiesto',
'device.toast.screenshot_failed': 'Richiesta screenshot non riuscita — nessuna risposta dal server',
'device.toast.renamed': 'Schermo rinominato',
'device.toast.removing': 'Rimozione in corso...',
'device.toast.removed': 'Schermo rimosso',

View file

@ -347,6 +347,9 @@ export default {
'device.assign.select_first': 'Selecione algo primeiro',
'device.assign.kiosk_widget_name': 'Quiosque: {name}',
'device.toast.screenshot_requested': 'Captura solicitada',
'device.toast.screenshot_unsupported': 'O player desta tela não suporta capturas de tela',
'device.toast.screenshot_offline': 'A tela está offline — captura não solicitada',
'device.toast.screenshot_failed': 'Falha na solicitação de captura — sem resposta do servidor',
'device.toast.renamed': 'Tela renomeada',
'device.toast.removing': 'Removendo...',
'device.toast.removed': 'Tela removida',

View file

@ -112,9 +112,22 @@ function emit(event, data) {
if (cbs) cbs.forEach(cb => cb(data));
}
export function requestScreenshot(deviceId) {
// Optional callback receives the server-side ack: { delivered, reason, capability }.
// reason is 'offline' (no live connection) or 'unsupported' (this player type can't
// take screenshots). Callers without a callback keep firing-and-forgetting — the
// dashboard grid and the device-detail 5s poll stay silent; only the explicit
// Screenshot button asks for the verdict.
export function requestScreenshot(deviceId, callback) {
console.log('requestScreenshot:', deviceId, 'socket connected:', dashboardSocket?.connected);
if (dashboardSocket) dashboardSocket.emit('dashboard:request-screenshot', { device_id: deviceId });
if (!dashboardSocket) return;
if (typeof callback === 'function') {
dashboardSocket.timeout(5000).emit('dashboard:request-screenshot', { device_id: deviceId }, (err, ack) => {
if (err) callback({ delivered: false, reason: 'no_ack' });
else callback(ack || { delivered: false, reason: 'no_ack' });
});
} else {
dashboardSocket.emit('dashboard:request-screenshot', { device_id: deviceId });
}
}
export function startRemote(deviceId) {

View file

@ -1084,10 +1084,16 @@ function setupActions(device) {
document.getElementById('devicePreviewBtn')?.addEventListener('click', () => showDevicePreview(device));
// Screenshot button
// Screenshot button — pass a callback so the server's verdict surfaces as a toast
// instead of the request silently going nowhere (offline device, or a player type
// that can't capture at all, e.g. BrightSign).
document.getElementById('screenshotBtn')?.addEventListener('click', () => {
requestScreenshot(device.id);
showToast(t('device.toast.screenshot_requested'), 'info');
requestScreenshot(device.id, (ack) => {
if (ack?.delivered) showToast(t('device.toast.screenshot_requested'), 'info');
else if (ack?.reason === 'unsupported') showToast(t('device.toast.screenshot_unsupported'), 'warning');
else if (ack?.reason === 'offline') showToast(t('device.toast.screenshot_offline'), 'warning');
else showToast(t('device.toast.screenshot_failed'), 'error');
});
});
// Rename