Feat: Fav channel in localstorage
This commit is contained in:
+29
-16
@@ -10,24 +10,35 @@ function makePlaceholder(name) {
|
||||
return el;
|
||||
}
|
||||
|
||||
function makeChannelCell(ch) {
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'channel-cell';
|
||||
if (ch.icon) {
|
||||
const img = document.createElement('img');
|
||||
img.className = 'channel-logo'; img.src = ch.icon;
|
||||
img.onerror = () => img.replaceWith(makePlaceholder(ch.name));
|
||||
cell.appendChild(img);
|
||||
} else { cell.appendChild(makePlaceholder(ch.name)); }
|
||||
const name = document.createElement('div');
|
||||
name.className = 'channel-name'; name.textContent = ch.name; name.title = ch.name;
|
||||
cell.appendChild(name);
|
||||
cell.appendChild(makeFavBtn(ch.id));
|
||||
cell.addEventListener('click', () => openPip(ch.name, getNowPlaying(ch.id)));
|
||||
return cell;
|
||||
}
|
||||
|
||||
function renderChannels() {
|
||||
const list = document.getElementById('channelsList');
|
||||
list.innerHTML = '';
|
||||
channels.forEach(ch => {
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'channel-cell';
|
||||
if (ch.icon) {
|
||||
const img = document.createElement('img');
|
||||
img.className = 'channel-logo'; img.src = ch.icon;
|
||||
img.onerror = () => img.replaceWith(makePlaceholder(ch.name));
|
||||
cell.appendChild(img);
|
||||
} else { cell.appendChild(makePlaceholder(ch.name)); }
|
||||
const name = document.createElement('div');
|
||||
name.className = 'channel-name'; name.textContent = ch.name; name.title = ch.name;
|
||||
cell.appendChild(name);
|
||||
cell.addEventListener('click', () => openPip(ch.name, getNowPlaying(ch.id)));
|
||||
list.appendChild(cell);
|
||||
});
|
||||
const { favs, rest } = sortedChannels(channels);
|
||||
|
||||
if (favs.length) {
|
||||
favs.forEach(ch => list.appendChild(makeChannelCell(ch)));
|
||||
const sep = document.createElement('div');
|
||||
sep.className = 'fav-separator';
|
||||
list.appendChild(sep);
|
||||
}
|
||||
rest.forEach(ch => list.appendChild(makeChannelCell(ch)));
|
||||
}
|
||||
|
||||
function renderRuler() {
|
||||
@@ -70,7 +81,9 @@ function renderPrograms() {
|
||||
|
||||
const now = new Date();
|
||||
|
||||
channels.forEach((ch, i) => {
|
||||
const { favs: _f, rest: _r } = sortedChannels(channels);
|
||||
const orderedChannels = [..._f, ..._r];
|
||||
orderedChannels.forEach((ch, i) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'program-row';
|
||||
row.style.cssText = `top:${i*ROW_H}px;position:absolute;left:0;right:0;`;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// ── FAVORITES ─────────────────────────────────────────────────────────────────
|
||||
let favorites = new Set(JSON.parse(localStorage.getItem('gridtv-favorites') || '[]'));
|
||||
|
||||
function saveFavorites() {
|
||||
localStorage.setItem('gridtv-favorites', JSON.stringify([...favorites]));
|
||||
}
|
||||
|
||||
function toggleFavorite(chId, btn) {
|
||||
if (favorites.has(chId)) {
|
||||
favorites.delete(chId);
|
||||
btn.classList.remove('is-fav');
|
||||
btn.title = L.fav_add;
|
||||
} else {
|
||||
favorites.add(chId);
|
||||
btn.classList.add('is-fav');
|
||||
btn.title = L.fav_remove;
|
||||
}
|
||||
saveFavorites();
|
||||
renderAll();
|
||||
}
|
||||
|
||||
function makeFavBtn(chId) {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'fav-btn' + (favorites.has(chId) ? ' is-fav' : '');
|
||||
btn.title = favorites.has(chId) ? L.fav_remove : L.fav_add;
|
||||
btn.textContent = '★';
|
||||
btn.addEventListener('click', e => { e.stopPropagation(); toggleFavorite(chId, btn); });
|
||||
return btn;
|
||||
}
|
||||
|
||||
function sortedChannels(list) {
|
||||
const favs = list.filter(ch => favorites.has(ch.id));
|
||||
const rest = list.filter(ch => !favorites.has(ch.id));
|
||||
return { favs, rest };
|
||||
}
|
||||
+3
-1
@@ -6,7 +6,9 @@ function renderMobile() {
|
||||
const winStart = new Date(now.getTime() - 30*60000);
|
||||
const winEnd = new Date(now.getTime() + 4*3600000);
|
||||
|
||||
channels.forEach(ch => {
|
||||
const { favs: _f, rest: _r } = sortedChannels(channels);
|
||||
const orderedChannels = [..._f, ..._r];
|
||||
orderedChannels.forEach(ch => {
|
||||
const progs = (programs[ch.id]||[]).filter(p => p.stop>winStart && p.start<winEnd);
|
||||
if (!progs.length) return;
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<?php
|
||||
$js_dir = __DIR__ . '/../js/';
|
||||
include $js_dir . 'config.js';
|
||||
$js_modules = ['utils', 'epg', 'mobile', 'tooltip', 'sources', 'm3u', 'player', 'themes', 'search', 'program', 'live'];
|
||||
$js_modules = ['utils', 'favorites', 'epg', 'mobile', 'tooltip', 'sources', 'm3u', 'player', 'themes', 'search', 'program', 'live'];
|
||||
foreach ($js_modules as $mod) {
|
||||
echo file_get_contents($js_dir . $mod . '.js');
|
||||
}
|
||||
|
||||
@@ -851,6 +851,24 @@
|
||||
background: var(--accent); opacity: .4; width: 2px;
|
||||
}
|
||||
|
||||
|
||||
/* ── FAVORITES ───────────────────────────────────────────────────────────── */
|
||||
.fav-btn {
|
||||
background: none; border: none; color: var(--text-muted);
|
||||
cursor: pointer; font-size: 14px; line-height: 1;
|
||||
margin-left: auto; padding: 2px 4px; flex-shrink: 0;
|
||||
transition: color .15s, transform .15s;
|
||||
opacity: 0;
|
||||
}
|
||||
.channel-cell:hover .fav-btn { opacity: 1; }
|
||||
.fav-btn.is-fav { color: var(--accent); opacity: 1; }
|
||||
.fav-btn:hover { transform: scale(1.2); }
|
||||
|
||||
.fav-separator {
|
||||
height: 2px; background: var(--accent);
|
||||
opacity: .3; margin: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
<link id="themeStylesheet" rel="stylesheet" href="themes/default.css">
|
||||
</head>
|
||||
|
||||
Reference in New Issue
Block a user