feat: Dashboard komplett neu - weisse Schrift, grosse Font, Nav-Links, Film-Info
- Hintergrund: #1a1a2e (dunkles Blau statt fast-schwarz) - Text: #ffffff (weiss statt grau) - Muted: #b8b8d0 (hell genug zum Lesen) - Minimum Font: 14px, Body: 17px, Titel: 19px - Nav-Links: Downloads + Status im Header - Film-Info: Jahr/Land/Genre per Wikidata (lazy load) - Kein Monospace mehr, system-ui Sans-Serif - ACHTUNG: Extra-Routes Import-Block NICHT LOESCHEN
This commit is contained in:
parent
8cda46ab54
commit
b52c53fab0
1 changed files with 196 additions and 147 deletions
|
|
@ -45,32 +45,29 @@ HTML = r"""<!DOCTYPE html>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Save.TV Archiv</title>
|
<title>Save.TV Archiv</title>
|
||||||
<style>
|
<style>
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;700;800&family=JetBrains+Mono:wght@400;600&display=swap');
|
|
||||||
:root {
|
:root {
|
||||||
--bg: #0e0e14;
|
--bg: #1a1a2e;
|
||||||
--surface: #16161e;
|
--surface: #222240;
|
||||||
--border: #2a2a3a;
|
--border: #33335a;
|
||||||
--accent: #e8421a;
|
--accent: #e84040;
|
||||||
--accent2: #ff7043;
|
--text: #ffffff;
|
||||||
--text: #f0f0f5;
|
--muted: #b8b8d0;
|
||||||
--muted: #9999b0;
|
--urgent: #ff4444;
|
||||||
--urgent: #ff3d3d;
|
--kino: #44cc88;
|
||||||
--kino: #4caf92;
|
--tv: #6699cc;
|
||||||
--tv: #5a7fa8;
|
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
body {
|
body {
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-family: system-ui, -apple-system, sans-serif;
|
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||||||
font-size: 16px;
|
font-size: 17px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
min-height: 100vh;
|
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 2px solid var(--border);
|
||||||
padding: 18px 28px;
|
padding: 20px 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
@ -78,97 +75,109 @@ HTML = r"""<!DOCTYPE html>
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 28px;
|
||||||
|
}
|
||||||
.logo {
|
.logo {
|
||||||
font-family: 'Syne', sans-serif;
|
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-size: 24px;
|
font-size: 26px;
|
||||||
letter-spacing: -0.5px;
|
|
||||||
}
|
}
|
||||||
.logo span { color: var(--accent); }
|
.logo span { color: var(--accent); }
|
||||||
.stats { color: var(--muted); font-size: 14px; }
|
nav { display: flex; gap: 12px; }
|
||||||
main { max-width: 1100px; margin: 0 auto; padding: 28px 20px; }
|
nav a {
|
||||||
|
color: var(--muted);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all .2s;
|
||||||
|
}
|
||||||
|
nav a:hover { color: #fff; border-color: var(--accent); background: rgba(232,64,64,.1); }
|
||||||
|
.stats { color: var(--muted); font-size: 15px; }
|
||||||
|
main { max-width: 1100px; margin: 0 auto; padding: 32px 24px; }
|
||||||
.section-header {
|
.section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
margin: 32px 0 14px;
|
margin: 36px 0 16px;
|
||||||
font-family: 'Syne', sans-serif;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 17px;
|
font-size: 20px;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 1.5px;
|
|
||||||
}
|
}
|
||||||
.badge {
|
.badge {
|
||||||
padding: 3px 10px;
|
padding: 4px 12px;
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
font-family: system-ui, sans-serif;
|
font-weight: 700;
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
.badge-urgent { background: var(--urgent); color: #fff; }
|
.badge-urgent { background: var(--urgent); color: #fff; }
|
||||||
.badge-kino { background: var(--kino); color: #000; }
|
.badge-kino { background: var(--kino); color: #000; }
|
||||||
.badge-tv { background: var(--tv); color: #fff; }
|
.badge-tv { background: var(--tv); color: #fff; }
|
||||||
.film-grid {
|
.film-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.film-card {
|
.film-card {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
padding: 18px 20px;
|
padding: 20px 22px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color .15s, background .15s;
|
transition: border-color .15s, background .15s;
|
||||||
position: relative;
|
position: relative;
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
.film-card:hover { border-color: var(--accent); background: #16161f; }
|
.film-card:hover { border-color: var(--accent); background: #2a2a4a; }
|
||||||
.film-card.selected {
|
.film-card.selected {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
background: #1a0d0a;
|
background: #2e1a1a;
|
||||||
box-shadow: inset 0 0 0 1px var(--accent);
|
box-shadow: inset 0 0 0 1px var(--accent);
|
||||||
}
|
}
|
||||||
.film-card.downloaded { border-color: var(--kino); opacity: .6; }
|
.film-card.downloaded { border-color: var(--kino); opacity: .55; }
|
||||||
.film-card.downloading { border-color: #ffa726; }
|
.film-card.downloading { border-color: #ffa726; }
|
||||||
.film-title {
|
.film-title {
|
||||||
font-family: 'Syne', sans-serif;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 17px;
|
font-size: 19px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
.film-meta {
|
.film-meta {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.film-info {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .4s;
|
||||||
|
}
|
||||||
|
.film-info.loaded { opacity: 1; }
|
||||||
.days-badge {
|
.days-badge {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 3px 8px;
|
padding: 3px 10px;
|
||||||
border-radius: 2px;
|
border-radius: 4px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
.days-urgent { background: var(--urgent); color: #fff; }
|
.days-urgent { background: var(--urgent); color: #fff; }
|
||||||
.days-ok { background: var(--border); color: var(--muted); }
|
.days-ok { background: var(--border); color: var(--muted); }
|
||||||
.status-icon {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
.checkbox-indicator {
|
.checkbox-indicator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 14px;
|
||||||
right: 12px;
|
right: 14px;
|
||||||
width: 22px;
|
width: 24px;
|
||||||
height: 22px;
|
height: 24px;
|
||||||
border: 2px solid var(--border);
|
border: 2px solid var(--border);
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -180,28 +189,32 @@ HTML = r"""<!DOCTYPE html>
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
.status-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border-top: 1px solid var(--border);
|
border-top: 2px solid var(--border);
|
||||||
padding: 14px 28px;
|
padding: 16px 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
.selected-count {
|
.selected-count {
|
||||||
font-family: 'Syne', sans-serif;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 17px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
.selected-count span { color: var(--accent); }
|
.selected-count span { color: var(--accent); }
|
||||||
.btn {
|
.btn {
|
||||||
padding: 10px 22px;
|
padding: 12px 24px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
font-family: 'Syne', sans-serif;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -220,13 +233,14 @@ HTML = r"""<!DOCTYPE html>
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
.spinner {
|
.spinner {
|
||||||
width: 32px;
|
width: 36px;
|
||||||
height: 32px;
|
height: 36px;
|
||||||
border: 2px solid var(--border);
|
border: 3px solid var(--border);
|
||||||
border-top-color: var(--accent);
|
border-top-color: var(--accent);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
|
|
@ -240,9 +254,9 @@ HTML = r"""<!DOCTYPE html>
|
||||||
background: #1a2a1a;
|
background: #1a2a1a;
|
||||||
border: 1px solid var(--kino);
|
border: 1px solid var(--kino);
|
||||||
color: var(--kino);
|
color: var(--kino);
|
||||||
padding: 12px 20px;
|
padding: 14px 24px;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
font-size: 15px;
|
font-size: 16px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity .3s;
|
transition: opacity .3s;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
|
|
@ -251,46 +265,47 @@ HTML = r"""<!DOCTYPE html>
|
||||||
.toast.show { opacity: 1; }
|
.toast.show { opacity: 1; }
|
||||||
.filter-bar {
|
.filter-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
margin: 20px 0 10px;
|
margin: 20px 0 12px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.filter-btn {
|
.filter-btn {
|
||||||
padding: 5px 14px;
|
padding: 8px 18px;
|
||||||
border-radius: 3px;
|
border-radius: 6px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: system-ui, sans-serif;
|
font-size: 15px;
|
||||||
font-size: 14px;
|
|
||||||
transition: all .15s;
|
transition: all .15s;
|
||||||
}
|
}
|
||||||
.filter-btn.active { border-color: var(--accent); color: var(--text); background: #1a0d0a; }
|
.filter-btn.active { border-color: var(--accent); color: #fff; background: rgba(232,64,64,.15); }
|
||||||
.search-input {
|
.search-input {
|
||||||
padding: 6px 14px;
|
padding: 8px 16px;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
border-radius: 3px;
|
border-radius: 6px;
|
||||||
font-family: system-ui, sans-serif;
|
font-size: 16px;
|
||||||
font-size: 15px;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
width: 220px;
|
width: 240px;
|
||||||
transition: border-color .15s;
|
transition: border-color .15s;
|
||||||
}
|
}
|
||||||
.search-input:focus { border-color: var(--accent); }
|
.search-input:focus { border-color: var(--accent); }
|
||||||
.dl-status {
|
.dl-status { font-size: 15px; color: #ffa726; margin-top: 6px; }
|
||||||
font-size: 14px;
|
|
||||||
color: #ffa726;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
.dl-done { color: var(--kino); }
|
.dl-done { color: var(--kino); }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="logo">Save<span>.</span>TV <span style="color:var(--muted);font-weight:400;font-size:14px">Archiv</span></div>
|
<div class="header-left">
|
||||||
|
<div class="logo">Save<span>.</span>TV</div>
|
||||||
|
<nav>
|
||||||
|
<a href="/downloads">▼ Downloads</a>
|
||||||
|
<a href="/status">⚙ Status</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
<div class="stats" id="header-stats">Lade...</div>
|
<div class="stats" id="header-stats">Lade...</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
|
|
@ -301,29 +316,29 @@ HTML = r"""<!DOCTYPE html>
|
||||||
<div id="content" style="display:none">
|
<div id="content" style="display:none">
|
||||||
<div class="filter-bar">
|
<div class="filter-bar">
|
||||||
<button class="filter-btn active" onclick="setFilter('all')">Alle</button>
|
<button class="filter-btn active" onclick="setFilter('all')">Alle</button>
|
||||||
<button class="filter-btn" onclick="setFilter('urgent')">🔴 Dringend</button>
|
<button class="filter-btn" onclick="setFilter('urgent')">🔴 Dringend</button>
|
||||||
<button class="filter-btn" onclick="setFilter('kino')">🎬 Kino</button>
|
<button class="filter-btn" onclick="setFilter('kino')">🎬 Kino</button>
|
||||||
<button class="filter-btn" onclick="setFilter('tv')">📺 TV-Film</button>
|
<button class="filter-btn" onclick="setFilter('tv')">📺 TV-Film</button>
|
||||||
<input class="search-input" id="search" placeholder="Suchen..." oninput="renderFilms()">
|
<input class="search-input" id="search" placeholder="Suchen..." oninput="renderFilms()">
|
||||||
</div>
|
</div>
|
||||||
<div id="urgent-section" style="display:none">
|
<div id="urgent-section" style="display:none">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span>🔴 Dringend</span>
|
<span>🔴 Dringend</span>
|
||||||
<span class="badge badge-urgent" id="urgent-count">0</span>
|
<span class="badge badge-urgent" id="urgent-count">0</span>
|
||||||
<span style="color:var(--muted);font-size:14px;margin-left:4px">laufen bald ab</span>
|
<span style="color:var(--muted);font-size:15px;margin-left:6px">laufen bald ab</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="film-grid" id="urgent-grid"></div>
|
<div class="film-grid" id="urgent-grid"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="kino-section" style="display:none">
|
<div id="kino-section" style="display:none">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span>🎬 Kino-Highlights</span>
|
<span>🎬 Kino-Highlights</span>
|
||||||
<span class="badge badge-kino" id="kino-count">0</span>
|
<span class="badge badge-kino" id="kino-count">0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="film-grid" id="kino-grid"></div>
|
<div class="film-grid" id="kino-grid"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tv-section" style="display:none">
|
<div id="tv-section" style="display:none">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span>📺 Deutsche TV-Filme</span>
|
<span>📺 Deutsche TV-Filme</span>
|
||||||
<span class="badge badge-tv" id="tv-count">0</span>
|
<span class="badge badge-tv" id="tv-count">0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="film-grid" id="tv-grid"></div>
|
<div class="film-grid" id="tv-grid"></div>
|
||||||
|
|
@ -331,10 +346,10 @@ HTML = r"""<!DOCTYPE html>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div class="selected-count"><span id="sel-count">0</span> ausgewählt</div>
|
<div class="selected-count"><span id="sel-count">0</span> ausgewählt</div>
|
||||||
<button class="btn btn-select-urgent" onclick="selectUrgent()">🔴 Alle Dringenden</button>
|
<button class="btn btn-select-urgent" onclick="selectUrgent()">🔴 Alle Dringenden</button>
|
||||||
<button class="btn btn-ghost" onclick="clearSelection()">Abwählen</button>
|
<button class="btn btn-ghost" onclick="clearSelection()">Abwählen</button>
|
||||||
<button class="btn btn-primary" id="dl-btn" onclick="startDownloads()" disabled>⬇ Download starten</button>
|
<button class="btn btn-primary" id="dl-btn" onclick="startDownloads()" disabled>⬇ Download starten</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toast" id="toast"></div>
|
<div class="toast" id="toast"></div>
|
||||||
|
|
||||||
|
|
@ -343,6 +358,38 @@ let allFilms = [];
|
||||||
let selected = new Set();
|
let selected = new Set();
|
||||||
let downloads = {};
|
let downloads = {};
|
||||||
let currentFilter = 'all';
|
let currentFilter = 'all';
|
||||||
|
let filmInfoCache = {};
|
||||||
|
let infoQueue = [];
|
||||||
|
let infoLoading = false;
|
||||||
|
|
||||||
|
function queueInfoLoad(films) {
|
||||||
|
films.forEach(f => {
|
||||||
|
if (!filmInfoCache[f.tid] && !infoQueue.some(q => q.tid === f.tid)) {
|
||||||
|
infoQueue.push({tid: f.tid, title: f.title});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!infoLoading) processInfoQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processInfoQueue() {
|
||||||
|
if (!infoQueue.length) { infoLoading = false; return; }
|
||||||
|
infoLoading = true;
|
||||||
|
const item = infoQueue.shift();
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/filminfo?title=' + encodeURIComponent(item.title));
|
||||||
|
const d = await r.json();
|
||||||
|
let parts = [];
|
||||||
|
if (d.year) parts.push(d.year);
|
||||||
|
if (d.countries && d.countries.length) parts.push(d.countries.join('/'));
|
||||||
|
if (d.genres && d.genres.length) parts.push(d.genres.join(', '));
|
||||||
|
if (parts.length) {
|
||||||
|
filmInfoCache[item.tid] = parts.join(' \u00b7 ');
|
||||||
|
const el = document.getElementById('info-' + item.tid);
|
||||||
|
if (el) { el.textContent = filmInfoCache[item.tid]; el.classList.add('loaded'); }
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
setTimeout(processInfoQueue, 200);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadFilms() {
|
async function loadFilms() {
|
||||||
const resp = await fetch('/api/films');
|
const resp = await fetch('/api/films');
|
||||||
|
|
@ -352,80 +399,85 @@ async function loadFilms() {
|
||||||
document.getElementById('loading').style.display = 'none';
|
document.getElementById('loading').style.display = 'none';
|
||||||
document.getElementById('content').style.display = 'block';
|
document.getElementById('content').style.display = 'block';
|
||||||
document.getElementById('header-stats').textContent =
|
document.getElementById('header-stats').textContent =
|
||||||
`${data.total} Aufnahmen · ${data.kino} Kino · ${data.urgent} dringend`;
|
data.total + ' Aufnahmen \u00b7 ' + data.kino + ' Kino \u00b7 ' + data.urgent + ' dringend';
|
||||||
renderFilms();
|
renderFilms();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFilter(f) {
|
function setFilter(f) {
|
||||||
currentFilter = f;
|
currentFilter = f;
|
||||||
document.querySelectorAll('.filter-btn').forEach((b, i) => {
|
document.querySelectorAll('.filter-btn').forEach(function(b, i) {
|
||||||
b.classList.toggle('active', ['all','urgent','kino','tv'][i] === f);
|
b.classList.toggle('active', ['all','urgent','kino','tv'][i] === f);
|
||||||
});
|
});
|
||||||
renderFilms();
|
renderFilms();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFilms() {
|
function renderFilms() {
|
||||||
const q = document.getElementById('search').value.toLowerCase();
|
var q = document.getElementById('search').value.toLowerCase();
|
||||||
const films = allFilms.filter(f => {
|
var films = allFilms.filter(function(f) {
|
||||||
if (q && !f.title.toLowerCase().includes(q)) return false;
|
if (q && f.title.toLowerCase().indexOf(q) === -1) return false;
|
||||||
if (currentFilter === 'urgent') return f.days_left <= 7;
|
if (currentFilter === 'urgent') return f.days_left <= 7;
|
||||||
if (currentFilter === 'kino') return f.cinema && f.days_left > 7;
|
if (currentFilter === 'kino') return f.cinema && f.days_left > 7;
|
||||||
if (currentFilter === 'tv') return !f.cinema;
|
if (currentFilter === 'tv') return !f.cinema;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const urgent = films.filter(f => f.days_left <= 7);
|
var urgent = films.filter(function(f) { return f.days_left <= 7; });
|
||||||
const kino = films.filter(f => f.cinema && f.days_left > 7);
|
var kino = films.filter(function(f) { return f.cinema && f.days_left > 7; });
|
||||||
const tv = films.filter(f => !f.cinema && f.days_left > 7);
|
var tv = films.filter(function(f) { return !f.cinema && f.days_left > 7; });
|
||||||
|
|
||||||
document.getElementById('urgent-count').textContent = urgent.length;
|
document.getElementById('urgent-count').textContent = urgent.length;
|
||||||
document.getElementById('kino-count').textContent = kino.length;
|
document.getElementById('kino-count').textContent = kino.length;
|
||||||
document.getElementById('tv-count').textContent = tv.length;
|
document.getElementById('tv-count').textContent = tv.length;
|
||||||
|
|
||||||
function renderGrid(arr, gridId, sectionId) {
|
function renderGrid(arr, gridId, sectionId) {
|
||||||
const el = document.getElementById(sectionId);
|
var el = document.getElementById(sectionId);
|
||||||
const grid = document.getElementById(gridId);
|
var grid = document.getElementById(gridId);
|
||||||
if (!arr.length) { el.style.display = 'none'; return; }
|
if (!arr.length) { el.style.display = 'none'; return; }
|
||||||
el.style.display = 'block';
|
el.style.display = 'block';
|
||||||
grid.innerHTML = arr.map(f => filmCard(f)).join('');
|
grid.innerHTML = arr.map(function(f) { return filmCard(f); }).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
renderGrid(urgent, 'urgent-grid', 'urgent-section');
|
renderGrid(urgent, 'urgent-grid', 'urgent-section');
|
||||||
renderGrid(kino, 'kino-grid', 'kino-section');
|
renderGrid(kino, 'kino-grid', 'kino-section');
|
||||||
renderGrid(tv, 'tv-grid', 'tv-section');
|
renderGrid(tv, 'tv-grid', 'tv-section');
|
||||||
|
queueInfoLoad(allFilms);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filmCard(f) {
|
function filmCard(f) {
|
||||||
const sel = selected.has(f.tid);
|
var sel = selected.has(f.tid);
|
||||||
const dlState = downloads[f.tid];
|
var dlState = downloads[f.tid];
|
||||||
const daysClass = f.days_left <= 3 ? 'days-urgent' : 'days-ok';
|
var daysClass = f.days_left <= 3 ? 'days-urgent' : 'days-ok';
|
||||||
const daysLabel = f.days_left === 0 ? 'heute' : f.days_left === 1 ? '1 Tag' : `${f.days_left} Tage`;
|
var daysLabel = f.days_left === 0 ? 'heute' : f.days_left === 1 ? '1 Tag' : f.days_left + ' Tage';
|
||||||
|
|
||||||
let cardClass = 'film-card';
|
var cardClass = 'film-card';
|
||||||
if (sel) cardClass += ' selected';
|
if (sel) cardClass += ' selected';
|
||||||
if (dlState === 'done') cardClass += ' downloaded';
|
if (dlState === 'done') cardClass += ' downloaded';
|
||||||
if (dlState === 'running') cardClass += ' downloading';
|
if (dlState === 'running') cardClass += ' downloading';
|
||||||
|
|
||||||
let statusEl = `<div class="checkbox-indicator">${sel ? '✓' : ''}</div>`;
|
var statusEl = '<div class="checkbox-indicator">' + (sel ? '\u2713' : '') + '</div>';
|
||||||
let extraLine = '';
|
var extraLine = '';
|
||||||
if (dlState === 'running') {
|
if (dlState === 'running') {
|
||||||
statusEl = `<div class="status-icon">⬇</div>`;
|
statusEl = '<div class="status-icon">\u2b07</div>';
|
||||||
extraLine = `<div class="dl-status">Download läuft...</div>`;
|
extraLine = '<div class="dl-status">Download l\u00e4uft...</div>';
|
||||||
} else if (dlState === 'done') {
|
} else if (dlState === 'done') {
|
||||||
statusEl = `<div class="status-icon">✅</div>`;
|
statusEl = '<div class="status-icon">\u2705</div>';
|
||||||
extraLine = `<div class="dl-status dl-done">Gespeichert</div>`;
|
extraLine = '<div class="dl-status dl-done">Gespeichert</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<div class="${cardClass}" onclick="toggleFilm(${f.tid})" data-tid="${f.tid}">
|
var infoText = filmInfoCache[f.tid] || '';
|
||||||
${statusEl}
|
var infoClass = infoText ? 'film-info loaded' : 'film-info';
|
||||||
<div class="film-title">${f.title}</div>
|
|
||||||
<div class="film-meta">
|
return '<div class="' + cardClass + '" onclick="toggleFilm(' + f.tid + ')" data-tid="' + f.tid + '">'
|
||||||
<span>${f.station}</span>
|
+ statusEl
|
||||||
<span class="badge ${f.cinema ? 'badge-kino' : 'badge-tv'}">${f.cinema ? 'Kino' : 'TV'}</span>
|
+ '<div class="film-title">' + f.title + '</div>'
|
||||||
<span class="days-badge ${daysClass}">${daysLabel}</span>
|
+ '<div class="film-meta">'
|
||||||
</div>
|
+ '<span>' + f.station + '</span>'
|
||||||
${extraLine}
|
+ '<span class="badge ' + (f.cinema ? 'badge-kino' : 'badge-tv') + '">' + (f.cinema ? 'Kino' : 'TV') + '</span>'
|
||||||
</div>`;
|
+ '<span class="days-badge ' + daysClass + '">' + daysLabel + '</span>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '<div class="' + infoClass + '" id="info-' + f.tid + '">' + infoText + '</div>'
|
||||||
|
+ extraLine
|
||||||
|
+ '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFilm(tid) {
|
function toggleFilm(tid) {
|
||||||
|
|
@ -442,7 +494,7 @@ function updateToolbar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectUrgent() {
|
function selectUrgent() {
|
||||||
allFilms.filter(f => f.days_left <= 7 && downloads[f.tid] !== 'done').forEach(f => selected.add(f.tid));
|
allFilms.filter(function(f) { return f.days_left <= 7 && downloads[f.tid] !== 'done'; }).forEach(function(f) { selected.add(f.tid); });
|
||||||
updateToolbar();
|
updateToolbar();
|
||||||
renderFilms();
|
renderFilms();
|
||||||
}
|
}
|
||||||
|
|
@ -454,38 +506,34 @@ function clearSelection() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startDownloads() {
|
async function startDownloads() {
|
||||||
const tids = Array.from(selected);
|
var tids = Array.from(selected);
|
||||||
if (!tids.length) return;
|
if (!tids.length) return;
|
||||||
document.getElementById('dl-btn').disabled = true;
|
document.getElementById('dl-btn').disabled = true;
|
||||||
|
showToast('Starte ' + tids.length + ' Download(s)...');
|
||||||
showToast(`Starte ${tids.length} Download(s)...`);
|
tids.forEach(function(tid) { downloads[tid] = 'running'; });
|
||||||
|
|
||||||
tids.forEach(tid => { downloads[tid] = 'running'; });
|
|
||||||
selected.clear();
|
selected.clear();
|
||||||
updateToolbar();
|
updateToolbar();
|
||||||
renderFilms();
|
renderFilms();
|
||||||
|
|
||||||
const resp = await fetch('/api/download', {
|
var resp = await fetch('/api/download', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({tids})
|
body: JSON.stringify({tids: tids})
|
||||||
});
|
});
|
||||||
const result = await resp.json();
|
var result = await resp.json();
|
||||||
|
result.results.forEach(function(r) {
|
||||||
result.results.forEach(r => {
|
|
||||||
downloads[r.tid] = r.ok ? 'done' : 'error';
|
downloads[r.tid] = r.ok ? 'done' : 'error';
|
||||||
});
|
});
|
||||||
renderFilms();
|
renderFilms();
|
||||||
|
var ok = result.results.filter(function(r) { return r.ok; }).length;
|
||||||
const ok = result.results.filter(r => r.ok).length;
|
showToast('\u2705 ' + ok + '/' + tids.length + ' Downloads gestartet');
|
||||||
showToast(`✅ ${ok}/${tids.length} Downloads gestartet`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showToast(msg) {
|
function showToast(msg) {
|
||||||
const t = document.getElementById('toast');
|
var t = document.getElementById('toast');
|
||||||
t.textContent = msg;
|
t.textContent = msg;
|
||||||
t.classList.add('show');
|
t.classList.add('show');
|
||||||
setTimeout(() => t.classList.remove('show'), 3500);
|
setTimeout(function() { t.classList.remove('show'); }, 3500);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFilms();
|
loadFilms();
|
||||||
|
|
@ -494,6 +542,7 @@ loadFilms();
|
||||||
</html>"""
|
</html>"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return render_template_string(HTML)
|
return render_template_string(HTML)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue