mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
i18n batch 7: index.html modal + player overlay
- Add-Display modal in index.html: marked translatable elements with data-i18n / data-i18n-placeholder / data-i18n-html attributes - app.js: translateStaticDom() walks data-i18n* on init and on every language-changed event so static HTML stays in sync - server/player/index.html: standalone player gets its own inline PLAYER_I18N table (en/es/fr/de/pt) with a tiny _t() helper. Reads rd_lang from localStorage (set by dashboard) so the player picks up the same language. Translates info overlay, setup screen, and status messages. - 1018 keys total in dashboard locales, parity 100%. This completes the wiring; Android resources are next. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
6d6f901ef4
commit
aebaacf2c1
|
|
@ -156,7 +156,7 @@
|
|||
<div class="modal-overlay" id="addDeviceModal" style="display:none">
|
||||
<div class="modal" style="max-width:560px">
|
||||
<div class="modal-header">
|
||||
<h3>Add Display</h3>
|
||||
<h3 data-i18n="add_display.title">Add Display</h3>
|
||||
<button class="btn-icon" data-close-modal="addDeviceModal">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||||
|
|
@ -164,37 +164,37 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="modal-description" style="margin-bottom:16px">Enter the 6-digit pairing code shown on the display.</p>
|
||||
<p class="modal-description" style="margin-bottom:16px" data-i18n="add_display.intro">Enter the 6-digit pairing code shown on the display.</p>
|
||||
<div class="form-group">
|
||||
<label>Pairing Code</label>
|
||||
<label data-i18n="add_display.pairing_code">Pairing Code</label>
|
||||
<input type="text" id="pairingCodeInput" maxlength="6" pattern="[0-9]{6}" placeholder="000000" class="pairing-input">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Display Name (optional)</label>
|
||||
<input type="text" id="deviceNameInput" placeholder="e.g., Lobby TV" class="input">
|
||||
<label data-i18n="add_display.display_name">Display Name (optional)</label>
|
||||
<input type="text" id="deviceNameInput" data-i18n-placeholder="add_display.name_placeholder" placeholder="e.g., Lobby TV" class="input">
|
||||
</div>
|
||||
<div style="border-top:1px solid var(--border,#1e293b);margin-top:20px;padding-top:16px">
|
||||
<p style="font-size:12px;color:var(--text-muted,#64748b);margin-bottom:10px;font-weight:500">Need a player app? Install one to get a pairing code:</p>
|
||||
<p style="font-size:12px;color:var(--text-muted,#64748b);margin-bottom:10px;font-weight:500" data-i18n="add_display.need_player">Need a player app? Install one to get a pairing code:</p>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
|
||||
<a href="/download/apk" class="btn btn-secondary btn-sm" style="text-decoration:none;justify-content:center;font-size:12px">
|
||||
🤖 Android APK
|
||||
🤖 <span data-i18n="add_display.android_apk">Android APK</span>
|
||||
</a>
|
||||
<a href="/player" target="_blank" class="btn btn-secondary btn-sm" style="text-decoration:none;justify-content:center;font-size:12px">
|
||||
🌐 Web Player
|
||||
🌐 <span data-i18n="add_display.web_player">Web Player</span>
|
||||
</a>
|
||||
<a href="/scripts/raspberry-pi-setup.sh" class="btn btn-secondary btn-sm" style="text-decoration:none;justify-content:center;font-size:12px">
|
||||
🥏 Raspberry Pi
|
||||
🥏 <span data-i18n="add_display.raspberry_pi">Raspberry Pi</span>
|
||||
</a>
|
||||
<a href="/scripts/windows-setup.bat" class="btn btn-secondary btn-sm" style="text-decoration:none;justify-content:center;font-size:12px">
|
||||
💻 Windows
|
||||
💻 <span data-i18n="add_display.windows">Windows</span>
|
||||
</a>
|
||||
</div>
|
||||
<p style="font-size:11px;color:var(--text-muted,#64748b);margin-top:8px">Smart TVs (LG/Samsung): open the built-in browser and navigate to <code style="background:var(--bg-input,#0f172a);padding:1px 4px;border-radius:3px">/player</code></p>
|
||||
<p style="font-size:11px;color:var(--text-muted,#64748b);margin-top:8px" data-i18n-html="add_display.smart_tv_note">Smart TVs (LG/Samsung): open the built-in browser and navigate to <code style="background:var(--bg-input,#0f172a);padding:1px 4px;border-radius:3px">/player</code></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" data-close-modal="addDeviceModal">Cancel</button>
|
||||
<button class="btn btn-primary" id="pairDeviceBtn">Pair Display</button>
|
||||
<button class="btn btn-secondary" data-close-modal="addDeviceModal" data-i18n="common.cancel">Cancel</button>
|
||||
<button class="btn btn-primary" id="pairDeviceBtn" data-i18n="add_display.pair_btn">Pair Display</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,23 @@ function renderNavLabels() {
|
|||
});
|
||||
}
|
||||
|
||||
// Translate any element marked with data-i18n / data-i18n-placeholder /
|
||||
// data-i18n-html. Runs on init and on every language change. Used for static
|
||||
// HTML in index.html (e.g. the Add-Display modal) where t() can't be inlined
|
||||
// at template time.
|
||||
function translateStaticDom(root = document) {
|
||||
root.querySelectorAll('[data-i18n]').forEach((el) => {
|
||||
const key = el.getAttribute('data-i18n');
|
||||
el.textContent = t(key);
|
||||
});
|
||||
root.querySelectorAll('[data-i18n-html]').forEach((el) => {
|
||||
el.innerHTML = t(el.getAttribute('data-i18n-html'));
|
||||
});
|
||||
root.querySelectorAll('[data-i18n-placeholder]').forEach((el) => {
|
||||
el.placeholder = t(el.getAttribute('data-i18n-placeholder'));
|
||||
});
|
||||
}
|
||||
|
||||
function isAuthenticated() {
|
||||
return !!localStorage.getItem('token');
|
||||
}
|
||||
|
|
@ -249,7 +266,11 @@ function updateSidebarUser() {
|
|||
|
||||
// Initialize
|
||||
renderNavLabels();
|
||||
window.addEventListener('language-changed', renderNavLabels);
|
||||
translateStaticDom();
|
||||
window.addEventListener('language-changed', () => {
|
||||
renderNavLabels();
|
||||
translateStaticDom();
|
||||
});
|
||||
|
||||
if (isAuthenticated()) {
|
||||
connectSocket();
|
||||
|
|
|
|||
|
|
@ -1048,4 +1048,18 @@ export default {
|
|||
'help.shortcuts': 'Tastaturkürzel',
|
||||
'help.shortcut_esc': 'Web-Player zurücksetzen (auf Player-Seite)',
|
||||
'help.shortcut_f': 'Vollbild umschalten (Web-Player)',
|
||||
|
||||
// Add Display modal
|
||||
'add_display.title': 'Bildschirm hinzufügen',
|
||||
'add_display.intro': 'Geben Sie den 6-stelligen Kopplungscode ein, der auf dem Bildschirm angezeigt wird.',
|
||||
'add_display.pairing_code': 'Kopplungscode',
|
||||
'add_display.display_name': 'Anzeigename (optional)',
|
||||
'add_display.name_placeholder': 'z. B. Lobby-TV',
|
||||
'add_display.need_player': 'Player-App benötigt? Installieren Sie eine, um einen Kopplungscode zu erhalten:',
|
||||
'add_display.android_apk': 'Android-APK',
|
||||
'add_display.web_player': 'Web-Player',
|
||||
'add_display.raspberry_pi': 'Raspberry Pi',
|
||||
'add_display.windows': 'Windows',
|
||||
'add_display.smart_tv_note': 'Smart TVs (LG/Samsung): öffnen Sie den integrierten Browser und navigieren Sie zu <code style="background:var(--bg-input,#0f172a);padding:1px 4px;border-radius:3px">/player</code>',
|
||||
'add_display.pair_btn': 'Bildschirm koppeln',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1084,4 +1084,18 @@ export default {
|
|||
'help.shortcuts': 'Keyboard Shortcuts',
|
||||
'help.shortcut_esc': 'Reset web player (on player page)',
|
||||
'help.shortcut_f': 'Toggle fullscreen (web player)',
|
||||
|
||||
// Add Display modal (in index.html)
|
||||
'add_display.title': 'Add Display',
|
||||
'add_display.intro': 'Enter the 6-digit pairing code shown on the display.',
|
||||
'add_display.pairing_code': 'Pairing Code',
|
||||
'add_display.display_name': 'Display Name (optional)',
|
||||
'add_display.name_placeholder': 'e.g., Lobby TV',
|
||||
'add_display.need_player': 'Need a player app? Install one to get a pairing code:',
|
||||
'add_display.android_apk': 'Android APK',
|
||||
'add_display.web_player': 'Web Player',
|
||||
'add_display.raspberry_pi': 'Raspberry Pi',
|
||||
'add_display.windows': 'Windows',
|
||||
'add_display.smart_tv_note': 'Smart TVs (LG/Samsung): open the built-in browser and navigate to <code style="background:var(--bg-input,#0f172a);padding:1px 4px;border-radius:3px">/player</code>',
|
||||
'add_display.pair_btn': 'Pair Display',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1047,4 +1047,18 @@ export default {
|
|||
'help.shortcuts': 'Atajos de teclado',
|
||||
'help.shortcut_esc': 'Reiniciar reproductor web (en la página del reproductor)',
|
||||
'help.shortcut_f': 'Alternar pantalla completa (reproductor web)',
|
||||
|
||||
// Add Display modal
|
||||
'add_display.title': 'Agregar pantalla',
|
||||
'add_display.intro': 'Ingresa el código de vinculación de 6 dígitos mostrado en la pantalla.',
|
||||
'add_display.pairing_code': 'Código de vinculación',
|
||||
'add_display.display_name': 'Nombre (opcional)',
|
||||
'add_display.name_placeholder': 'p. ej., TV Vestíbulo',
|
||||
'add_display.need_player': '¿Necesitas la app del reproductor? Instala una para obtener un código:',
|
||||
'add_display.android_apk': 'APK Android',
|
||||
'add_display.web_player': 'Reproductor web',
|
||||
'add_display.raspberry_pi': 'Raspberry Pi',
|
||||
'add_display.windows': 'Windows',
|
||||
'add_display.smart_tv_note': 'Smart TVs (LG/Samsung): abre el navegador integrado y ve a <code style="background:var(--bg-input,#0f172a);padding:1px 4px;border-radius:3px">/player</code>',
|
||||
'add_display.pair_btn': 'Vincular pantalla',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1048,4 +1048,18 @@ export default {
|
|||
'help.shortcuts': 'Raccourcis clavier',
|
||||
'help.shortcut_esc': 'Réinitialiser le lecteur web (sur la page du lecteur)',
|
||||
'help.shortcut_f': 'Basculer le plein écran (lecteur web)',
|
||||
|
||||
// Add Display modal
|
||||
'add_display.title': 'Ajouter un écran',
|
||||
'add_display.intro': 'Saisissez le code d\'appairage à 6 chiffres affiché sur l\'écran.',
|
||||
'add_display.pairing_code': 'Code d\'appairage',
|
||||
'add_display.display_name': 'Nom (facultatif)',
|
||||
'add_display.name_placeholder': 'ex. TV du hall',
|
||||
'add_display.need_player': 'Besoin d\'une app de lecture ? Installez-en une pour obtenir un code :',
|
||||
'add_display.android_apk': 'APK Android',
|
||||
'add_display.web_player': 'Lecteur web',
|
||||
'add_display.raspberry_pi': 'Raspberry Pi',
|
||||
'add_display.windows': 'Windows',
|
||||
'add_display.smart_tv_note': 'Smart TVs (LG/Samsung) : ouvrez le navigateur intégré et allez à <code style="background:var(--bg-input,#0f172a);padding:1px 4px;border-radius:3px">/player</code>',
|
||||
'add_display.pair_btn': 'Apparier l\'écran',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1048,4 +1048,18 @@ export default {
|
|||
'help.shortcuts': 'Atalhos de teclado',
|
||||
'help.shortcut_esc': 'Reiniciar player web (na página do player)',
|
||||
'help.shortcut_f': 'Alternar tela cheia (player web)',
|
||||
|
||||
// Add Display modal
|
||||
'add_display.title': 'Adicionar tela',
|
||||
'add_display.intro': 'Digite o código de pareamento de 6 dígitos exibido na tela.',
|
||||
'add_display.pairing_code': 'Código de pareamento',
|
||||
'add_display.display_name': 'Nome (opcional)',
|
||||
'add_display.name_placeholder': 'ex. TV do lobby',
|
||||
'add_display.need_player': 'Precisa de um app player? Instale um para obter um código:',
|
||||
'add_display.android_apk': 'APK Android',
|
||||
'add_display.web_player': 'Player web',
|
||||
'add_display.raspberry_pi': 'Raspberry Pi',
|
||||
'add_display.windows': 'Windows',
|
||||
'add_display.smart_tv_note': 'Smart TVs (LG/Samsung): abra o navegador integrado e vá para <code style="background:var(--bg-input,#0f172a);padding:1px 4px;border-radius:3px">/player</code>',
|
||||
'add_display.pair_btn': 'Parear tela',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -85,11 +85,85 @@
|
|||
<div id="statusOverlay" style="display:none">
|
||||
<div class="spinner"></div>
|
||||
<h2>ScreenTinker</h2>
|
||||
<p id="statusText">Connecting...</p>
|
||||
<p id="statusText" data-i18n="connecting">Connecting...</p>
|
||||
</div>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
// ==================== i18n ====================
|
||||
// Lightweight inline i18n for the player. The player is a standalone page
|
||||
// on display devices — it doesn't import the dashboard's i18n module.
|
||||
// Keep keys in sync with the player overlay strings; falls back to en.
|
||||
const PLAYER_I18N = {
|
||||
en: {
|
||||
web_player: 'Web Player',
|
||||
server_url: 'Server URL',
|
||||
server_url_placeholder: 'https://sign.yourdomain.com',
|
||||
connect: 'Connect',
|
||||
pairing_code: 'Pairing Code',
|
||||
pairing_hint: 'Enter this code in the dashboard to pair this display',
|
||||
connecting: 'Connecting...',
|
||||
connecting_muted: 'Connecting (audio muted)...',
|
||||
info_title: 'ScreenTinker Web Player',
|
||||
info_close_hint: 'Press Back again or click to close',
|
||||
info_device_id: 'Device ID',
|
||||
info_device_name: 'Device Name',
|
||||
info_server: 'Server',
|
||||
info_status: 'Status',
|
||||
info_now_playing: 'Now Playing',
|
||||
info_resolution: 'Resolution',
|
||||
info_uptime: 'Uptime',
|
||||
info_platform: 'Platform',
|
||||
info_cache: 'Cache',
|
||||
info_connected: 'Connected',
|
||||
info_disconnected: 'Disconnected',
|
||||
info_active: 'Active',
|
||||
info_inactive: 'Inactive',
|
||||
info_nothing: 'Nothing',
|
||||
info_na: 'N/A',
|
||||
info_sw: 'Service Worker',
|
||||
},
|
||||
es: {
|
||||
web_player: 'Reproductor web', server_url: 'URL del servidor', server_url_placeholder: 'https://signage.tudominio.com', connect: 'Conectar', pairing_code: 'Código de vinculación', pairing_hint: 'Ingresa este código en el panel para vincular esta pantalla', connecting: 'Conectando...', connecting_muted: 'Conectando (audio silenciado)...', info_title: 'Reproductor web ScreenTinker', info_close_hint: 'Presiona Atrás de nuevo o haz clic para cerrar', info_device_id: 'ID del dispositivo', info_device_name: 'Nombre del dispositivo', info_server: 'Servidor', info_status: 'Estado', info_now_playing: 'Reproduciendo', info_resolution: 'Resolución', info_uptime: 'Tiempo activo', info_platform: 'Plataforma', info_cache: 'Caché', info_connected: 'Conectado', info_disconnected: 'Desconectado', info_active: 'Activo', info_inactive: 'Inactivo', info_nothing: 'Nada', info_na: 'N/D', info_sw: 'Service Worker',
|
||||
},
|
||||
fr: {
|
||||
web_player: 'Lecteur web', server_url: 'URL du serveur', server_url_placeholder: 'https://signage.votredomaine.com', connect: 'Connecter', pairing_code: 'Code d’appairage', pairing_hint: 'Saisissez ce code dans le tableau de bord pour apparier cet écran', connecting: 'Connexion...', connecting_muted: 'Connexion (audio coupé)...', info_title: 'Lecteur web ScreenTinker', info_close_hint: 'Appuyez à nouveau sur Retour ou cliquez pour fermer', info_device_id: 'ID de l’appareil', info_device_name: 'Nom de l’appareil', info_server: 'Serveur', info_status: 'État', info_now_playing: 'En lecture', info_resolution: 'Résolution', info_uptime: 'Disponibilité', info_platform: 'Plateforme', info_cache: 'Cache', info_connected: 'Connecté', info_disconnected: 'Déconnecté', info_active: 'Actif', info_inactive: 'Inactif', info_nothing: 'Rien', info_na: 'N/D', info_sw: 'Service Worker',
|
||||
},
|
||||
de: {
|
||||
web_player: 'Web-Player', server_url: 'Server-URL', server_url_placeholder: 'https://signage.ihredomain.com', connect: 'Verbinden', pairing_code: 'Kopplungscode', pairing_hint: 'Geben Sie diesen Code im Dashboard ein, um diesen Bildschirm zu koppeln', connecting: 'Verbindung wird hergestellt...', connecting_muted: 'Verbindung (Audio stummgeschaltet)...', info_title: 'ScreenTinker Web-Player', info_close_hint: 'Erneut Zurück drücken oder klicken zum Schließen', info_device_id: 'Geräte-ID', info_device_name: 'Gerätename', info_server: 'Server', info_status: 'Status', info_now_playing: 'Aktuelle Wiedergabe', info_resolution: 'Auflösung', info_uptime: 'Betriebszeit', info_platform: 'Plattform', info_cache: 'Cache', info_connected: 'Verbunden', info_disconnected: 'Getrennt', info_active: 'Aktiv', info_inactive: 'Inaktiv', info_nothing: 'Nichts', info_na: 'N/V', info_sw: 'Service Worker',
|
||||
},
|
||||
pt: {
|
||||
web_player: 'Player web', server_url: 'URL do servidor', server_url_placeholder: 'https://sign.seudominio.com', connect: 'Conectar', pairing_code: 'Código de pareamento', pairing_hint: 'Digite este código no painel para parear esta tela', connecting: 'Conectando...', connecting_muted: 'Conectando (áudio mudo)...', info_title: 'Player web ScreenTinker', info_close_hint: 'Pressione Voltar novamente ou clique para fechar', info_device_id: 'ID do dispositivo', info_device_name: 'Nome do dispositivo', info_server: 'Servidor', info_status: 'Status', info_now_playing: 'Reproduzindo', info_resolution: 'Resolução', info_uptime: 'Tempo ativo', info_platform: 'Plataforma', info_cache: 'Cache', info_connected: 'Conectado', info_disconnected: 'Desconectado', info_active: 'Ativo', info_inactive: 'Inativo', info_nothing: 'Nada', info_na: 'N/D', info_sw: 'Service Worker',
|
||||
},
|
||||
};
|
||||
const PLAYER_LANG = (() => {
|
||||
const stored = localStorage.getItem('rd_lang');
|
||||
const detected = (stored || navigator.language || 'en').split('-')[0];
|
||||
return PLAYER_I18N[detected] ? detected : 'en';
|
||||
})();
|
||||
const _t = (k) => (PLAYER_I18N[PLAYER_LANG] && PLAYER_I18N[PLAYER_LANG][k]) || PLAYER_I18N.en[k] || k;
|
||||
|
||||
// Apply translations to the static setup screen markup
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const setSubtitle = document.querySelector('#setupScreen .subtitle');
|
||||
if (setSubtitle) setSubtitle.textContent = _t('web_player');
|
||||
const lblServer = document.querySelector('#urlForm label');
|
||||
if (lblServer) lblServer.textContent = _t('server_url');
|
||||
const inputServer = document.getElementById('serverUrl');
|
||||
if (inputServer) inputServer.placeholder = _t('server_url_placeholder');
|
||||
const connectBtn = document.getElementById('connectBtn');
|
||||
if (connectBtn) connectBtn.textContent = _t('connect');
|
||||
const pairingP = document.querySelector('#pairingSection p:first-child');
|
||||
if (pairingP) pairingP.textContent = _t('pairing_code');
|
||||
const pairingHint = document.querySelector('#pairingSection .pairing-hint');
|
||||
if (pairingHint) pairingHint.textContent = _t('pairing_hint');
|
||||
// Translate any element with data-i18n attribute
|
||||
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||
const k = el.getAttribute('data-i18n');
|
||||
if (k) el.textContent = _t(k);
|
||||
});
|
||||
});
|
||||
|
||||
// ==================== Config ====================
|
||||
const STORAGE_KEY = 'rd_web_player';
|
||||
const HEARTBEAT_INTERVAL = 15000;
|
||||
|
|
@ -228,7 +302,7 @@
|
|||
tapOverlay.onclick = () => {
|
||||
unlockAudio();
|
||||
tapOverlay.remove();
|
||||
if (!isPlaying) showStatus('Connecting...');
|
||||
if (!isPlaying) showStatus(_t('connecting'));
|
||||
connect(config.serverUrl);
|
||||
};
|
||||
document.body.appendChild(tapOverlay);
|
||||
|
|
@ -237,7 +311,7 @@
|
|||
setTimeout(() => {
|
||||
if (tapOverlay.parentNode) {
|
||||
tapOverlay.remove();
|
||||
if (!isPlaying) showStatus('Connecting (audio muted)...');
|
||||
if (!isPlaying) showStatus(_t('connecting_muted'));
|
||||
connect(config.serverUrl);
|
||||
}
|
||||
}, 5000);
|
||||
|
|
@ -973,9 +1047,9 @@
|
|||
infoDiv.id = 'infoOverlay';
|
||||
infoDiv.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:800;display:none;flex-direction:column;align-items:center;justify-content:center;color:#f1f5f9;font-family:-apple-system,sans-serif';
|
||||
infoDiv.innerHTML = `
|
||||
<h2 style="color:#3b82f6;margin-bottom:16px">ScreenTinker Web Player</h2>
|
||||
<h2 style="color:#3b82f6;margin-bottom:16px">${_t('info_title')}</h2>
|
||||
<div style="font-size:14px;line-height:2;text-align:center;color:#94a3b8" id="infoContent"></div>
|
||||
<p style="margin-top:24px;font-size:12px;color:#64748b">Press Back again or click to close</p>
|
||||
<p style="margin-top:24px;font-size:12px;color:#64748b">${_t('info_close_hint')}</p>
|
||||
`;
|
||||
infoDiv.onclick = () => { infoDiv.style.display = 'none'; };
|
||||
document.body.appendChild(infoDiv);
|
||||
|
|
@ -990,15 +1064,15 @@
|
|||
if (!el) return;
|
||||
const item = playlist[currentIndex];
|
||||
el.innerHTML = `
|
||||
Device ID: ${escHtml(config.deviceId?.slice(0, 8) || 'N/A')}...<br>
|
||||
Device Name: ${escHtml(config.deviceName || 'N/A')}<br>
|
||||
Server: ${escHtml(config.serverUrl || 'N/A')}<br>
|
||||
Status: ${socket?.connected ? '<span style="color:#22c55e">Connected</span>' : '<span style="color:#ef4444">Disconnected</span>'}<br>
|
||||
Now Playing: ${escHtml(item?.filename || 'Nothing')} (${currentIndex + 1}/${playlist.length})<br>
|
||||
Resolution: ${screen.width}x${screen.height}<br>
|
||||
Uptime: ${Math.floor(performance.now() / 60000)}m<br>
|
||||
Platform: ${escHtml(navigator.platform)}<br>
|
||||
Cache: Service Worker ${navigator.serviceWorker?.controller ? '<span style="color:#22c55e">Active</span>' : 'Inactive'}
|
||||
${_t('info_device_id')}: ${escHtml(config.deviceId?.slice(0, 8) || _t('info_na'))}...<br>
|
||||
${_t('info_device_name')}: ${escHtml(config.deviceName || _t('info_na'))}<br>
|
||||
${_t('info_server')}: ${escHtml(config.serverUrl || _t('info_na'))}<br>
|
||||
${_t('info_status')}: ${socket?.connected ? `<span style="color:#22c55e">${_t('info_connected')}</span>` : `<span style="color:#ef4444">${_t('info_disconnected')}</span>`}<br>
|
||||
${_t('info_now_playing')}: ${escHtml(item?.filename || _t('info_nothing'))} (${currentIndex + 1}/${playlist.length})<br>
|
||||
${_t('info_resolution')}: ${screen.width}x${screen.height}<br>
|
||||
${_t('info_uptime')}: ${Math.floor(performance.now() / 60000)}m<br>
|
||||
${_t('info_platform')}: ${escHtml(navigator.platform)}<br>
|
||||
${_t('info_cache')}: ${_t('info_sw')} ${navigator.serviceWorker?.controller ? `<span style="color:#22c55e">${_t('info_active')}</span>` : _t('info_inactive')}
|
||||
`;
|
||||
}, 2000);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue