Files
2026-07-09 15:35:20 +02:00

286 lines
8.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function nowEscape(value) {
return String(value ?? '').replace(/[&<>"']/g, char => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
}[char]));
}
function nowLogoUrl(channelId) {
return `/assets/logos/${encodeURIComponent(channelId)}.svg`;
}
function nowCreateCard(item) {
const article = document.createElement('article');
article.className = 'now-card now-card-enhanced';
article.innerHTML = `
<div class="now-card-top">
<img class="now-channel-logo" data-live-logo alt="">
<div class="now-channel-info">
<div class="now-channel-name">
<span class="channel-number" data-live-channel-number></span>
<span data-live-channel-name></span>
<span data-live-favorite></span>
</div>
<div class="now-time" data-live-time-range></div>
</div>
</div>
<h3 data-live-title></h3>
<div class="now-subtitle" data-live-subtitle></div>
<div class="now-badges" data-live-badges></div>
<div class="progress-wrap">
<div class="progress-meta">
<span data-live-percent></span>
<span data-live-remaining></span>
</div>
<div class="progress-bar">
<div class="progress-fill" data-live-progress></div>
</div>
</div>
<div class="now-next" data-live-next>
<div class="now-next-label">Danach</div>
<div class="now-next-programme">
<div class="now-next-time" data-live-next-time></div>
<div class="now-next-info">
<strong data-live-next-title></strong>
<div class="now-next-subtitle" data-live-next-subtitle></div>
</div>
</div>
</div>
<p class="now-desc" data-live-description></p>
`;
nowUpdateCard(article, item);
return article;
}
function nowUpdateCard(card, item) {
const logo = card.querySelector('[data-live-logo]');
logo.src = nowLogoUrl(item.channel_id);
logo.onerror = () => {
logo.onerror = null;
logo.src = '/assets/logos/default.svg';
};
card.querySelector('[data-live-channel-number]').textContent = item.channel_number ?? '';
card.querySelector('[data-live-channel-name]').textContent = item.channel_name ?? '';
card.querySelector('[data-live-favorite]').textContent = item.favorite ? '⭐' : '';
card.querySelector('[data-live-title]').textContent = item.title ?? '';
card.querySelector('[data-live-next-subtitle]').textContent =
item.next_subtitle || '';
card.querySelector('[data-live-next-subtitle]').style.display =
item.next_subtitle ? '' : 'none';
const subtitle = card.querySelector('[data-live-subtitle]');
subtitle.textContent = item.subtitle || '';
subtitle.style.display = item.subtitle ? '' : 'none';
const badges = [];
if (item.category) {
badges.push(`${item.genre_icon || '▶'} ${item.category}`);
}
if (item.year) {
badges.push(item.year);
}
if (item.rating) {
badges.push(`FSK ${item.rating}`);
}
if (item.quality) {
badges.push(item.quality);
}
const badgesEl = card.querySelector('[data-live-badges]');
badgesEl.innerHTML = badges.map(badge => `<span class="now-badge">${nowEscape(badge)}</span>`).join('');
badgesEl.style.display = badges.length ? '' : 'none';
const next = card.querySelector('[data-live-next]');
if (item.next_title) {
next.style.display = '';
card.querySelector('[data-live-next-title]').textContent = item.next_title;
card.querySelector('[data-live-next-time]').textContent = item.next_start
? TVGuideLive.formatTime(item.next_start)
: '';
} else {
next.style.display = 'none';
}
const desc = card.querySelector('[data-live-description]');
desc.textContent = item.description || '';
desc.style.display = item.description ? '' : 'none';
nowTickCard(card, item);
}
function nowTickCard(card, item) {
const percent = TVGuideLive.calcPercent(item.start, item.stop);
const remaining = TVGuideLive.calcRemainingMinutes(item.stop);
card.querySelector('[data-live-time-range]').textContent =
`${TVGuideLive.formatTime(item.start)} ${TVGuideLive.formatTime(item.stop)}`;
card.querySelector('[data-live-percent]').textContent = `${percent}%`;
card.querySelector('[data-live-remaining]').textContent = `noch ${remaining} Minuten`;
card.querySelector('[data-live-progress]').style.width = `${percent}%`;
}
document.addEventListener('DOMContentLoaded', () => {
if (!window.LiveEngine) return;
const liveNow = new LiveEngine({
url: '/api/live-now.php',
versionUrl: '/api/version.php',
interval: 30000,
gridSelector: '[data-live-now-grid]',
itemKey: item => item.channel_id,
createItem: nowCreateCard,
updateItem: nowUpdateCard,
tickItem: nowTickCard,
emptyHtml: '<div class="empty">Aktuell wurden keine laufenden Sendungen gefunden.</div>'
});
liveNow.start();
});
function dashboardCreateCard(item) {
const button = document.createElement('button');
button.className = 'dashboard-programme-card dashboard-programme';
button.type = 'button';
button.dataset.programmeId = `${item.channel_id}-${item.start}-${item.title}`;
button.dataset.dashboardKey = item.channel_id;
button.innerHTML = `
<img src="${item.logo_url || '/assets/logos/default.svg'}" alt="">
<strong></strong>
<span></span>
<div class="dashboard-progress">
<div></div>
</div>
`;
dashboardUpdateCard(button, item);
return button;
}
function dashboardUpdateCard(card, item) {
card.querySelector('img').src = item.logo_url || '/assets/logos/default.svg';
card.querySelector('strong').textContent = item.title || '';
card.querySelector('span').textContent =
`${item.channel_name || ''} · noch ${TVGuideLive.calcRemainingMinutes(item.stop)} Min.`;
const bar = card.querySelector('.dashboard-progress div');
if (bar) {
bar.style.width = `${TVGuideLive.calcPercent(item.start, item.stop)}%`;
}
const id = `${item.channel_id}-${item.start}-${item.title}`;
card.dataset.programmeId = id;
window.TVGUIDE_PROGRAMMES = window.TVGUIDE_PROGRAMMES || {};
window.TVGUIDE_PROGRAMMES[id] = {
id,
channel_id: item.channel_id,
channel: item.channel_name,
channel_number: item.channel_number,
favorite: item.favorite,
title: item.title,
subtitle: item.subtitle || '',
description: item.description || '',
category: item.category || '',
metadata: item.metadata || {},
year: item.year || '',
rating: item.rating || '',
country: item.country || '',
start: TVGuideLive.formatTime(item.start),
stop: TVGuideLive.formatTime(item.stop)
};
}
function dashboardRender(grid, items) {
if (!grid) return;
// Beim ersten Live-Refresh vorhandene serverseitige Karten entfernen
if (!grid.dataset.dashboardInitialized) {
grid.innerHTML = '';
grid.dataset.dashboardInitialized = '1';
}
const seen = new Set();
items.slice(0, 6).forEach(item => {
const key = item.channel_id;
seen.add(key);
let card = grid.querySelector(`[data-dashboard-key="${CSS.escape(key)}"]`);
if (!card) {
card = dashboardCreateCard(item);
grid.appendChild(card);
} else {
dashboardUpdateCard(card, item);
}
});
grid.querySelectorAll('[data-dashboard-key]').forEach(card => {
if (!seen.has(card.dataset.dashboardKey)) {
card.remove();
}
});
if (items.length === 0) {
grid.innerHTML = '<div class="empty compact">Aktuell keine Sendungen.</div>';
}
}
async function dashboardRefreshNow() {
const nowGrid = document.querySelector('[data-dashboard-now]');
const favGrid = document.querySelector('[data-dashboard-favorites]');
if (!nowGrid && !favGrid) return;
try {
const data = await TVGuideLive.fetchJson('/api/live-now.php');
const items = data.items || [];
dashboardRender(nowGrid, items);
dashboardRender(
favGrid,
items.filter(item => item.favorite)
);
} catch (error) {
console.error('Dashboard konnte nicht aktualisiert werden:', error);
}
}
function dashboardTick() {
document.querySelectorAll('[data-dashboard-key]').forEach(card => {
const key = card.dataset.dashboardKey;
// vollständiger Refresh kommt alle 30 Sekunden; hier nur Balken/Restzeit über vorhandene DOM-Daten nicht möglich
});
}
document.addEventListener('DOMContentLoaded', () => {
dashboardRefreshNow();
setInterval(dashboardRefreshNow, 30000);
});