kryten-webqueue 0.6.2__tar.gz → 0.6.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/CHANGELOG.md +25 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/PKG-INFO +1 -1
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/catalog/db.py +18 -2
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/queue/shadow.py +29 -8
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/static/css/main.css +40 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/auth/login.html +2 -1
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/queue/index.html +17 -2
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/pyproject.toml +1 -1
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/.github/workflows/python-publish.yml +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/.github/workflows/release.yml +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/.gitignore +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/README.md +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/config.example.json +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/deploy/kryten-webqueue.service +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/deploy/nginx-queue.conf +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/docs/IMPLEMENTATION_SPEC.md +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/docs/IMPL_API_GATE.md +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/docs/IMPL_ECONOMY.md +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/docs/IMPL_KRYTEN_PY.md +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/docs/IMPL_ROBOT.md +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/docs/PRE_PLAN_GAPS.md +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/docs/PRODUCT_PLAN.md +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/__init__.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/__main__.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/api_gate/__init__.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/api_gate/client.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/app.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/auth/__init__.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/auth/otp.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/auth/rate_limit.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/auth/session.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/catalog/__init__.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/catalog/images.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/catalog/sync.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/config.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/jobs/__init__.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/jobs/manager.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/playlists/__init__.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/playlists/fire.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/playlists/importer.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/playlists/scheduler.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/queue/__init__.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/queue/ordering.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/queue/poller.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/routes/__init__.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/routes/admin_jobs.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/routes/admin_playlists.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/routes/admin_queue.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/routes/admin_schedules.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/routes/auth.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/routes/catalog.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/routes/pages.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/routes/queue.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/routes/user.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/static/js/main.js +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/admin/index.html +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/admin/playlists.html +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/admin/schedules.html +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/base.html +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/catalog/browse.html +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/user/dashboard.html +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/ws/__init__.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/ws/handler.py +0 -0
- {kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/ws/manager.py +0 -0
|
@@ -4,7 +4,32 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
## [0.6.4] - 2026-06-08
|
|
7
8
|
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **Quality-weighted catalog browse ordering** — The catalog landing no longer leads with alphabetical junk (art-less, number-prefixed "02 - Episode" entries). Browse results are now ranked by signals derived entirely from existing data — no curation or featured-item maintenance required: (1) items with box art (cover or thumbnail) first, (2) then by real popularity (times queued, from `queue_history`), (3) titles beginning with a letter before number/symbol-prefixed titles, (4) then alphabetical for a stable tail. Applies to both the unfiltered landing and category-filtered browse; search continues to use full-text relevance.
|
|
12
|
+
|
|
13
|
+
[0.6.4]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.6.4
|
|
14
|
+
|
|
15
|
+
## [0.6.3] - 2026-06-08
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **Queue items missing title/duration for externally-queued media** — CyTube playlist items nest their metadata under a `media` key (`{uid, temp, queueby, media: {id, title, seconds, type}}`), but the shadow reconciler read flat fields, so any item not added by the running webqueue instance showed "Unknown" with a `0:00` duration. The reconciler now reads the nested `media` object (with a flat-key fallback) and backfills title/duration/media-id onto known items. Externally-queued items now also show their CyTube `queueby` as the requester.
|
|
20
|
+
- **Now Playing runtime showing `NaN:NaN`** — The now-playing total used the preformatted `duration` string instead of the numeric `seconds`, producing `NaN:NaN` for the total and remaining time. It now uses `seconds` (with a numeric fallback).
|
|
21
|
+
- **Estimated start times** — Now derived from the numeric now-playing `seconds`/`currentTime`, so the queue ETAs are correct now that item durations are read properly.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **Sticky, highlighted Now Playing card** — The Now Playing card is pinned below the navbar while scrolling a long queue, is highlighted with a subtle accent ring/glow, and the page auto-scrolls it into view on load.
|
|
26
|
+
- **Case-sensitivity notice on login** — The OTP login view now warns that the CyTube username is case-sensitive and must match exactly (e.g. `TacoBelmont` ≠ `tacobelmont`); the username field no longer auto-capitalizes or autocorrects.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **Sticky navbar hardening** — Added `scroll-padding-top` (via a `--nav-height` variable) so scrolled-to content clears the sticky header.
|
|
31
|
+
|
|
32
|
+
[0.6.3]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.6.3
|
|
8
33
|
## [0.6.2] - 2026-06-07
|
|
9
34
|
|
|
10
35
|
### Fixed
|
|
@@ -238,7 +238,8 @@ class Database:
|
|
|
238
238
|
|
|
239
239
|
async def browse(self, *, category: str | None = None, page: int = 1, per_page: int = 24) -> list[dict]:
|
|
240
240
|
query = """
|
|
241
|
-
SELECT c.friendly_token, c.title, c.duration_sec, c.cover_art_path, c.thumbnail_url, c.manifest_url
|
|
241
|
+
SELECT c.friendly_token, c.title, c.duration_sec, c.cover_art_path, c.thumbnail_url, c.manifest_url,
|
|
242
|
+
(SELECT COUNT(*) FROM queue_history qh WHERE qh.friendly_token = c.friendly_token) AS play_count
|
|
242
243
|
FROM catalog c
|
|
243
244
|
WHERE c.friendly_token NOT IN (
|
|
244
245
|
SELECT spi.media_id FROM saved_playlist_items spi
|
|
@@ -256,7 +257,22 @@ class Database:
|
|
|
256
257
|
)
|
|
257
258
|
"""
|
|
258
259
|
params.append(category)
|
|
259
|
-
|
|
260
|
+
# Quality-weighted ordering so the landing page leads with presentable
|
|
261
|
+
# items instead of alphabetical junk. No curation required — every signal
|
|
262
|
+
# is derived from existing data:
|
|
263
|
+
# 1. Items with box art (cover or thumbnail) first.
|
|
264
|
+
# 2. Then by real popularity (times queued).
|
|
265
|
+
# 3. Titles beginning with a letter before number/symbol-prefixed
|
|
266
|
+
# "02 - Episode" style entries.
|
|
267
|
+
# 4. Finally alphabetical for a stable, predictable tail.
|
|
268
|
+
query += """
|
|
269
|
+
ORDER BY
|
|
270
|
+
(c.cover_art_path IS NOT NULL OR c.thumbnail_url IS NOT NULL) DESC,
|
|
271
|
+
play_count DESC,
|
|
272
|
+
(CASE WHEN c.title GLOB '[A-Za-z]*' THEN 0 ELSE 1 END) ASC,
|
|
273
|
+
c.title ASC
|
|
274
|
+
LIMIT ? OFFSET ?
|
|
275
|
+
"""
|
|
260
276
|
params.extend([per_page, (page - 1) * per_page])
|
|
261
277
|
return await self._fetch_all(query, params)
|
|
262
278
|
|
|
@@ -63,20 +63,40 @@ class QueueShadow:
|
|
|
63
63
|
|
|
64
64
|
for pos, polled in enumerate(playlist_items):
|
|
65
65
|
uid = polled["uid"]
|
|
66
|
+
# CyTube playlist items nest the media metadata under a "media"
|
|
67
|
+
# key ({uid, temp, queueby, media: {id, title, seconds, type}}).
|
|
68
|
+
# Fall back to flat keys for forward/backward compatibility.
|
|
69
|
+
media = polled.get("media") if isinstance(polled.get("media"), dict) else polled
|
|
70
|
+
title = media.get("title") or polled.get("title") or ""
|
|
71
|
+
media_type = media.get("type") or polled.get("type") or "unknown"
|
|
72
|
+
media_id = media.get("id") or polled.get("id") or ""
|
|
73
|
+
duration_sec = _to_seconds(media.get("seconds", media.get("duration")))
|
|
74
|
+
queueby = polled.get("queueby") or None
|
|
75
|
+
|
|
66
76
|
if uid in local_map:
|
|
67
|
-
# Preserve local metadata, update position
|
|
68
|
-
|
|
77
|
+
# Preserve local metadata, update position; backfill any
|
|
78
|
+
# fields we never captured locally (e.g. title/duration for
|
|
79
|
+
# items first added by an external client or a prior run).
|
|
80
|
+
merged = {**local_map[uid], "position": pos}
|
|
81
|
+
if not merged.get("title"):
|
|
82
|
+
merged["title"] = title
|
|
83
|
+
if not merged.get("duration_sec"):
|
|
84
|
+
merged["duration_sec"] = duration_sec
|
|
85
|
+
if not merged.get("media_id"):
|
|
86
|
+
merged["media_id"] = media_id
|
|
87
|
+
if not merged.get("media_type") or merged.get("media_type") == "unknown":
|
|
88
|
+
merged["media_type"] = media_type
|
|
69
89
|
else:
|
|
70
90
|
# New item from external source
|
|
71
91
|
merged = {
|
|
72
92
|
"uid": uid,
|
|
73
93
|
"position": pos,
|
|
74
|
-
"title":
|
|
75
|
-
"media_type":
|
|
76
|
-
"media_id":
|
|
77
|
-
"duration_sec":
|
|
94
|
+
"title": title,
|
|
95
|
+
"media_type": media_type,
|
|
96
|
+
"media_id": media_id,
|
|
97
|
+
"duration_sec": duration_sec,
|
|
78
98
|
"is_pay": False,
|
|
79
|
-
"paid_by":
|
|
99
|
+
"paid_by": queueby,
|
|
80
100
|
"tier": None,
|
|
81
101
|
"z_cost": None,
|
|
82
102
|
"schedule_id": None,
|
|
@@ -97,7 +117,8 @@ class QueueShadow:
|
|
|
97
117
|
# Start from now-playing elapsed or now
|
|
98
118
|
start_cursor = datetime.now(UTC)
|
|
99
119
|
if self._now_playing:
|
|
100
|
-
|
|
120
|
+
np_total = _to_seconds(self._now_playing.get("seconds", self._now_playing.get("duration")))
|
|
121
|
+
remaining = np_total - _to_seconds(self._now_playing.get("currentTime"))
|
|
101
122
|
start_cursor += timedelta(seconds=max(0, remaining))
|
|
102
123
|
|
|
103
124
|
for item in self._items:
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
--border: #33334a;
|
|
16
16
|
--radius: 8px;
|
|
17
17
|
--shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
18
|
+
--nav-height: 4rem;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
* {
|
|
@@ -23,6 +24,11 @@
|
|
|
23
24
|
box-sizing: border-box;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
html {
|
|
28
|
+
/* Keep anchored/scrolled-to content clear of the sticky navbar. */
|
|
29
|
+
scroll-padding-top: calc(var(--nav-height) + 1rem);
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
body {
|
|
27
33
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
28
34
|
background: var(--bg-primary);
|
|
@@ -228,6 +234,18 @@ a:hover {
|
|
|
228
234
|
}
|
|
229
235
|
}
|
|
230
236
|
|
|
237
|
+
/* Keep the now-playing card pinned while scrolling a long queue. */
|
|
238
|
+
.now-playing-section {
|
|
239
|
+
position: sticky;
|
|
240
|
+
top: calc(var(--nav-height) + 1rem);
|
|
241
|
+
align-self: start;
|
|
242
|
+
}
|
|
243
|
+
@media (max-width: 900px) {
|
|
244
|
+
.now-playing-section {
|
|
245
|
+
position: static;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
231
249
|
.now-playing-card {
|
|
232
250
|
background: var(--bg-card);
|
|
233
251
|
border-radius: var(--radius);
|
|
@@ -235,6 +253,9 @@ a:hover {
|
|
|
235
253
|
display: flex;
|
|
236
254
|
gap: 1rem;
|
|
237
255
|
align-items: flex-start;
|
|
256
|
+
/* Highlight the currently-playing item: subtle accent ring + glow. */
|
|
257
|
+
border: 1px solid var(--accent);
|
|
258
|
+
box-shadow: 0 0 0 1px rgba(108, 92, 231, 0.25), 0 6px 18px rgba(108, 92, 231, 0.18);
|
|
238
259
|
}
|
|
239
260
|
.np-info {
|
|
240
261
|
flex: 1;
|
|
@@ -432,6 +453,25 @@ a:hover {
|
|
|
432
453
|
.otp-sent-msg {
|
|
433
454
|
color: var(--success);
|
|
434
455
|
}
|
|
456
|
+
.auth-hint {
|
|
457
|
+
margin-top: 0.75rem;
|
|
458
|
+
padding: 0.6rem 0.85rem;
|
|
459
|
+
font-size: 0.85rem;
|
|
460
|
+
color: var(--text-secondary);
|
|
461
|
+
background: rgba(253, 203, 110, 0.1);
|
|
462
|
+
border: 1px solid var(--warning);
|
|
463
|
+
border-radius: var(--radius);
|
|
464
|
+
text-align: left;
|
|
465
|
+
}
|
|
466
|
+
.auth-hint strong {
|
|
467
|
+
color: var(--warning);
|
|
468
|
+
}
|
|
469
|
+
.auth-hint code {
|
|
470
|
+
background: var(--bg-secondary);
|
|
471
|
+
padding: 0.05rem 0.3rem;
|
|
472
|
+
border-radius: 4px;
|
|
473
|
+
font-size: 0.85em;
|
|
474
|
+
}
|
|
435
475
|
|
|
436
476
|
/* User Dashboard */
|
|
437
477
|
.user-header {
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
<div class="auth-container">
|
|
7
7
|
<h1>Login</h1>
|
|
8
8
|
<p>Enter your CyTube username to receive a one-time code via PM.</p>
|
|
9
|
+
<p class="auth-hint"><strong>Username is case-sensitive</strong> — it must match your CyTube username exactly. For example, <code>TacoBelmont</code> is not the same as <code>tacobelmont</code>.</p>
|
|
9
10
|
|
|
10
11
|
<div id="otp-request" class="auth-form">
|
|
11
|
-
<input type="text" id="username" placeholder="CyTube username" autocomplete="username" maxlength="50">
|
|
12
|
+
<input type="text" id="username" placeholder="CyTube username (case-sensitive)" autocomplete="username" autocapitalize="none" autocorrect="off" spellcheck="false" maxlength="50">
|
|
12
13
|
<button id="request-otp-btn" class="btn btn-primary">Send Code</button>
|
|
13
14
|
</div>
|
|
14
15
|
|
|
@@ -58,6 +58,7 @@ function connectWebSocket() {
|
|
|
58
58
|
const msg = JSON.parse(event.data);
|
|
59
59
|
if (msg.type === 'queue_state' || msg.type === 'queue_update') {
|
|
60
60
|
renderQueue(msg.data);
|
|
61
|
+
maybeInitialScroll(msg.data);
|
|
61
62
|
} else if (msg.type === 'pong') {
|
|
62
63
|
// keepalive ack
|
|
63
64
|
} else if (msg.type === 'schedule_fired') {
|
|
@@ -80,8 +81,10 @@ function renderQueue(state) {
|
|
|
80
81
|
|
|
81
82
|
if (state.now_playing) {
|
|
82
83
|
const np = state.now_playing;
|
|
83
|
-
const elapsed = np.currentTime || 0;
|
|
84
|
-
|
|
84
|
+
const elapsed = Number(np.currentTime) || 0;
|
|
85
|
+
// CyTube's now-playing payload uses numeric `seconds` for the total
|
|
86
|
+
// runtime (`duration` is a preformatted "HH:MM:SS" string).
|
|
87
|
+
const total = Number(np.seconds ?? np.duration_sec ?? 0) || 0;
|
|
85
88
|
const remaining = Math.max(0, total - elapsed);
|
|
86
89
|
npEl.innerHTML = `
|
|
87
90
|
<div class="np-cover">${coverHtml(np)}</div>
|
|
@@ -131,6 +134,17 @@ function renderQueue(state) {
|
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
136
|
|
|
137
|
+
// On first render with live data, bring the now-playing card into view.
|
|
138
|
+
let didInitialScroll = false;
|
|
139
|
+
function maybeInitialScroll(state) {
|
|
140
|
+
if (didInitialScroll || !state || !state.now_playing) return;
|
|
141
|
+
didInitialScroll = true;
|
|
142
|
+
const sec = document.querySelector('.now-playing-section');
|
|
143
|
+
if (sec) {
|
|
144
|
+
setTimeout(() => sec.scrollIntoView({behavior: 'smooth', block: 'start'}), 150);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
134
148
|
function formatTime(sec) {
|
|
135
149
|
sec = Math.floor(sec);
|
|
136
150
|
const h = Math.floor(sec / 3600);
|
|
@@ -169,6 +183,7 @@ async function initialLoad() {
|
|
|
169
183
|
if (resp.ok) {
|
|
170
184
|
const state = await resp.json();
|
|
171
185
|
renderQueue(state);
|
|
186
|
+
maybeInitialScroll(state);
|
|
172
187
|
}
|
|
173
188
|
} catch (e) { /* WS will take over */ }
|
|
174
189
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/admin/playlists.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/admin/queue_mgmt.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/admin/schedules.html
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/catalog/browse.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/catalog/item_detail.html
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.6.2 → kryten_webqueue-0.6.4}/kryten_webqueue/templates/user/dashboard.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|