// ============================================================ // Display Brightness & Power Control // ============================================================ let displayBrightnessTimers = {}; const DISPLAY_THROTTLE_MS = 50; async function loadDisplayMonitors() { const token = localStorage.getItem('media_server_token'); if (!token) return; const container = document.getElementById('displayMonitors'); if (!container) return; try { const response = await fetch('/api/display/monitors?refresh=true', { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) { container.innerHTML = `

Failed to load monitors

`; return; } const monitors = await response.json(); if (monitors.length === 0) { container.innerHTML = `

No monitors detected

`; return; } container.innerHTML = ''; monitors.forEach(monitor => { const card = document.createElement('div'); card.className = 'display-monitor-card'; card.id = `monitor-card-${monitor.id}`; const brightnessValue = monitor.brightness !== null ? monitor.brightness : 0; const brightnessDisabled = monitor.brightness === null ? 'disabled' : ''; let powerBtn = ''; if (monitor.power_supported) { powerBtn = ` `; } const details = [monitor.resolution, monitor.manufacturer].filter(Boolean).join(' \u00B7 '); const detailsHtml = details ? `${details}` : ''; const primaryBadge = monitor.is_primary ? `${t('display.primary')}` : ''; card.innerHTML = `
${monitor.name}${primaryBadge} ${detailsHtml}
${powerBtn}
${brightnessValue}%
`; container.appendChild(card); }); } catch (e) { console.error('Failed to load display monitors:', e); } } function onDisplayBrightnessInput(monitorId, value) { const label = document.getElementById(`brightness-val-${monitorId}`); if (label) label.textContent = `${value}%`; if (displayBrightnessTimers[monitorId]) clearTimeout(displayBrightnessTimers[monitorId]); displayBrightnessTimers[monitorId] = setTimeout(() => { sendDisplayBrightness(monitorId, parseInt(value)); displayBrightnessTimers[monitorId] = null; }, DISPLAY_THROTTLE_MS); } function onDisplayBrightnessChange(monitorId, value) { if (displayBrightnessTimers[monitorId]) { clearTimeout(displayBrightnessTimers[monitorId]); displayBrightnessTimers[monitorId] = null; } sendDisplayBrightness(monitorId, parseInt(value)); } async function sendDisplayBrightness(monitorId, brightness) { const token = localStorage.getItem('media_server_token'); try { await fetch(`/api/display/brightness/${monitorId}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ brightness }) }); } catch (e) { console.error('Failed to set brightness:', e); } } async function toggleDisplayPower(monitorId, monitorName) { const btn = document.getElementById(`power-btn-${monitorId}`); const isOn = btn && btn.classList.contains('on'); const newState = !isOn; const token = localStorage.getItem('media_server_token'); try { const response = await fetch(`/api/display/power/${monitorId}`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ on: newState }) }); const data = await response.json(); if (data.success) { if (btn) { btn.classList.toggle('on', newState); btn.classList.toggle('off', !newState); btn.title = newState ? t('display.power_off') : t('display.power_on'); } showToast(newState ? 'Monitor turned on' : 'Monitor turned off', 'success'); } else { showToast('Failed to change monitor power', 'error'); } } catch (e) { console.error('Failed to set display power:', e); showToast('Failed to change monitor power', 'error'); } } // ============================================================ // Header Quick Links // ============================================================ async function loadHeaderLinks() { const token = localStorage.getItem('media_server_token'); if (!token) return; const container = document.getElementById('headerLinks'); if (!container) return; try { const response = await fetch('/api/links/list', { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) return; const links = await response.json(); container.innerHTML = ''; for (const link of links) { const a = document.createElement('a'); a.href = link.url; a.target = '_blank'; a.rel = 'noopener noreferrer'; a.className = 'header-link'; a.title = link.label || link.url; const iconSvg = await fetchMdiIcon(link.icon || 'mdi:link'); a.innerHTML = iconSvg; container.appendChild(a); } } catch (e) { console.warn('Failed to load header links:', e); } } // ============================================================ // Links Management // ============================================================ let _loadLinksPromise = null; let linkFormDirty = false; async function loadLinksTable() { if (_loadLinksPromise) return _loadLinksPromise; _loadLinksPromise = _loadLinksTableImpl(); _loadLinksPromise.finally(() => { _loadLinksPromise = null; }); return _loadLinksPromise; } async function _loadLinksTableImpl() { const token = localStorage.getItem('media_server_token'); const tbody = document.getElementById('linksTableBody'); try { const response = await fetch('/api/links/list', { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) { throw new Error('Failed to fetch links'); } const linksList = await response.json(); if (linksList.length === 0) { tbody.innerHTML = '

' + t('links.empty') + '

'; return; } tbody.innerHTML = linksList.map(link => ` ${escapeHtml(link.name)} ${escapeHtml(link.url)} ${escapeHtml(link.label || '')}
`).join(''); resolveMdiIcons(tbody); } catch (error) { console.error('Error loading links:', error); tbody.innerHTML = 'Failed to load links'; } } function showAddLinkDialog() { const dialog = document.getElementById('linkDialog'); const form = document.getElementById('linkForm'); const title = document.getElementById('linkDialogTitle'); form.reset(); document.getElementById('linkOriginalName').value = ''; document.getElementById('linkIsEdit').value = 'false'; document.getElementById('linkName').disabled = false; document.getElementById('linkIconPreview').innerHTML = ''; title.textContent = t('links.dialog.add'); linkFormDirty = false; document.body.classList.add('dialog-open'); dialog.showModal(); } async function showEditLinkDialog(linkName) { const token = localStorage.getItem('media_server_token'); const dialog = document.getElementById('linkDialog'); const title = document.getElementById('linkDialogTitle'); try { const response = await fetch('/api/links/list', { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) { throw new Error('Failed to fetch link details'); } const linksList = await response.json(); const link = linksList.find(l => l.name === linkName); if (!link) { showToast(t('links.msg.not_found'), 'error'); return; } document.getElementById('linkOriginalName').value = linkName; document.getElementById('linkIsEdit').value = 'true'; document.getElementById('linkName').value = linkName; document.getElementById('linkName').disabled = true; document.getElementById('linkUrl').value = link.url; document.getElementById('linkIcon').value = link.icon || ''; document.getElementById('linkLabel').value = link.label || ''; document.getElementById('linkDescription').value = link.description || ''; // Update icon preview const preview = document.getElementById('linkIconPreview'); if (link.icon) { fetchMdiIcon(link.icon).then(svg => { preview.innerHTML = svg; }); } else { preview.innerHTML = ''; } title.textContent = t('links.dialog.edit'); linkFormDirty = false; document.body.classList.add('dialog-open'); dialog.showModal(); } catch (error) { console.error('Error loading link for edit:', error); showToast(t('links.msg.load_failed'), 'error'); } } async function closeLinkDialog() { if (linkFormDirty) { if (!await showConfirm(t('links.confirm.unsaved'))) { return; } } const dialog = document.getElementById('linkDialog'); linkFormDirty = false; dialog.close(); document.body.classList.remove('dialog-open'); } async function saveLink(event) { event.preventDefault(); const submitBtn = event.target.querySelector('button[type="submit"]'); if (submitBtn) submitBtn.disabled = true; const token = localStorage.getItem('media_server_token'); const isEdit = document.getElementById('linkIsEdit').value === 'true'; const linkName = isEdit ? document.getElementById('linkOriginalName').value : document.getElementById('linkName').value; const data = { url: document.getElementById('linkUrl').value, icon: document.getElementById('linkIcon').value || 'mdi:link', label: document.getElementById('linkLabel').value || '', description: document.getElementById('linkDescription').value || '' }; const endpoint = isEdit ? `/api/links/update/${linkName}` : `/api/links/create/${linkName}`; const method = isEdit ? 'PUT' : 'POST'; try { const response = await fetch(endpoint, { method, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (response.ok && result.success) { showToast(t(isEdit ? 'links.msg.updated' : 'links.msg.created'), 'success'); linkFormDirty = false; closeLinkDialog(); } else { showToast(result.detail || t(isEdit ? 'links.msg.update_failed' : 'links.msg.create_failed'), 'error'); } } catch (error) { console.error('Error saving link:', error); showToast(t(isEdit ? 'links.msg.update_failed' : 'links.msg.create_failed'), 'error'); } finally { if (submitBtn) submitBtn.disabled = false; } } async function deleteLinkConfirm(linkName) { if (!await showConfirm(t('links.confirm.delete').replace('{name}', linkName))) { return; } const token = localStorage.getItem('media_server_token'); try { const response = await fetch(`/api/links/delete/${linkName}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); const result = await response.json(); if (response.ok && result.success) { showToast(t('links.msg.deleted'), 'success'); } else { showToast(result.detail || t('links.msg.delete_failed'), 'error'); } } catch (error) { console.error('Error deleting link:', error); showToast(t('links.msg.delete_failed'), 'error'); } }