kryten-webqueue 0.7.4__tar.gz → 0.7.5__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.7.4 → kryten_webqueue-0.7.5}/CHANGELOG.md +11 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/PKG-INFO +1 -1
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/catalog/db.py +91 -17
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/routes/catalog.py +8 -6
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/routes/pages.py +20 -10
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/static/css/main.css +24 -1
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/base.html +3 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/catalog/browse.html +10 -1
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/catalog/item_detail.html +0 -1
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/pyproject.toml +1 -1
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/.github/workflows/python-publish.yml +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/.github/workflows/release.yml +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/.gitignore +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/README.md +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/config.example.json +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/deploy/kryten-webqueue.service +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/deploy/nginx-queue.conf +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/docs/IMPLEMENTATION_SPEC.md +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/docs/IMPL_API_GATE.md +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/docs/IMPL_ECONOMY.md +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/docs/IMPL_KRYTEN_PY.md +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/docs/IMPL_ROBOT.md +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/docs/PRE_PLAN_GAPS.md +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/docs/PRODUCT_PLAN.md +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/__init__.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/__main__.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/api_gate/__init__.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/api_gate/client.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/app.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/auth/__init__.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/auth/otp.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/auth/rate_limit.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/auth/session.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/catalog/__init__.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/catalog/images.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/catalog/sync.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/config.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/jobs/__init__.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/jobs/manager.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/playlists/__init__.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/playlists/fire.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/playlists/importer.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/playlists/scheduler.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/queue/__init__.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/queue/ordering.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/queue/poller.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/queue/shadow.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/routes/__init__.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/routes/admin_jobs.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/routes/admin_playlists.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/routes/admin_queue.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/routes/admin_schedules.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/routes/auth.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/routes/queue.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/routes/user.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/static/js/main.js +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/admin/index.html +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/admin/playlists.html +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/admin/schedules.html +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/auth/login.html +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/queue/index.html +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/user/dashboard.html +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/ws/__init__.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/ws/handler.py +0 -0
- {kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/ws/manager.py +0 -0
|
@@ -4,6 +4,17 @@ 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.7.5] - 2026-06-08
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Hidden categories/tags with an admin reveal toggle.** Items in the categories `Z Channel Promos`, `Z Event Movies`, `Weekday Z Promos` and the tags `grindhousebumper`, `commercialsforbumpers`, `bumpers`, `channelz`, `grindhousetrailer`, `publicaccess`, `religioustv` are now excluded from the catalog browse/search results and from the category/tag facet dropdowns. Admins (rank ≥ 3) see a notice — "Certain items are hidden from results. Show hidden items?" — that toggles them back into view via `?show_hidden=1` (ignored for non-admins).
|
|
12
|
+
- **Modern, readability-focused typography.** The UI now loads Inter (body/controls) and Sora (headings) from Google Fonts, with antialiasing and `optimizeLegibility` enabled.
|
|
13
|
+
|
|
14
|
+
### Removed
|
|
15
|
+
|
|
16
|
+
- **"Play Next" buttons** are no longer shown on the catalog browse cards or the item detail page (the underlying play-next queue mechanism is unchanged).
|
|
17
|
+
|
|
7
18
|
## [0.7.4] - 2026-06-08
|
|
8
19
|
|
|
9
20
|
### Changed
|
|
@@ -12,6 +12,47 @@ def _slugify(text: str) -> str:
|
|
|
12
12
|
return s.strip("-") or "untitled"
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
# Categories and tags whose items are hidden from the public catalog (dropdowns
|
|
16
|
+
# and search results). Admins can opt to reveal them. Matched by exact name.
|
|
17
|
+
HIDDEN_CATEGORY_NAMES = [
|
|
18
|
+
"Z Channel Promos",
|
|
19
|
+
"Z Event Movies",
|
|
20
|
+
"Weekday Z Promos",
|
|
21
|
+
]
|
|
22
|
+
HIDDEN_TAG_NAMES = [
|
|
23
|
+
"grindhousebumper",
|
|
24
|
+
"commercialsforbumpers",
|
|
25
|
+
"bumpers",
|
|
26
|
+
"channelz",
|
|
27
|
+
"grindhousetrailer",
|
|
28
|
+
"publicaccess",
|
|
29
|
+
"religioustv",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _hidden_exclusion(alias: str = "c") -> tuple[str, list]:
|
|
34
|
+
"""SQL fragment (+ params) excluding items in hidden categories/tags.
|
|
35
|
+
|
|
36
|
+
The fragment is prefixed with ``AND`` so it can be appended to an existing
|
|
37
|
+
WHERE clause that references the catalog row under ``alias``.
|
|
38
|
+
"""
|
|
39
|
+
cat_ph = ",".join("?" * len(HIDDEN_CATEGORY_NAMES))
|
|
40
|
+
tag_ph = ",".join("?" * len(HIDDEN_TAG_NAMES))
|
|
41
|
+
sql = f"""
|
|
42
|
+
AND {alias}.friendly_token NOT IN (
|
|
43
|
+
SELECT cc.friendly_token FROM catalog_categories cc
|
|
44
|
+
JOIN categories cat ON cc.category_id = cat.id
|
|
45
|
+
WHERE cat.name IN ({cat_ph})
|
|
46
|
+
)
|
|
47
|
+
AND {alias}.friendly_token NOT IN (
|
|
48
|
+
SELECT ct.friendly_token FROM catalog_tags ct
|
|
49
|
+
JOIN tags t ON ct.tag_id = t.id
|
|
50
|
+
WHERE t.name IN ({tag_ph})
|
|
51
|
+
)
|
|
52
|
+
"""
|
|
53
|
+
return sql, [*HIDDEN_CATEGORY_NAMES, *HIDDEN_TAG_NAMES]
|
|
54
|
+
|
|
55
|
+
|
|
15
56
|
MIGRATIONS = [
|
|
16
57
|
# v1: Migration tracking table
|
|
17
58
|
"""
|
|
@@ -245,7 +286,7 @@ class Database:
|
|
|
245
286
|
|
|
246
287
|
# --- Catalog ---
|
|
247
288
|
|
|
248
|
-
async def browse(self, *, category: str | None = None, tag: str | None = None, page: int = 1, per_page: int = 24) -> list[dict]:
|
|
289
|
+
async def browse(self, *, category: str | None = None, tag: str | None = None, page: int = 1, per_page: int = 24, show_hidden: bool = False) -> list[dict]:
|
|
249
290
|
query = """
|
|
250
291
|
SELECT c.friendly_token, c.title, c.duration_sec, c.cover_art_path, c.thumbnail_url, c.manifest_url
|
|
251
292
|
FROM catalog c
|
|
@@ -256,6 +297,10 @@ class Database:
|
|
|
256
297
|
)
|
|
257
298
|
"""
|
|
258
299
|
params: list = []
|
|
300
|
+
if not show_hidden:
|
|
301
|
+
excl_sql, excl_params = _hidden_exclusion("c")
|
|
302
|
+
query += excl_sql
|
|
303
|
+
params.extend(excl_params)
|
|
259
304
|
if category:
|
|
260
305
|
query += """
|
|
261
306
|
AND c.friendly_token IN (
|
|
@@ -296,7 +341,7 @@ class Database:
|
|
|
296
341
|
params.extend([per_page, (page - 1) * per_page])
|
|
297
342
|
return await self._fetch_all(query, params)
|
|
298
343
|
|
|
299
|
-
async def browse_count(self, *, category: str | None = None, tag: str | None = None) -> int:
|
|
344
|
+
async def browse_count(self, *, category: str | None = None, tag: str | None = None, show_hidden: bool = False) -> int:
|
|
300
345
|
query = """
|
|
301
346
|
SELECT COUNT(*) as cnt FROM catalog c
|
|
302
347
|
WHERE c.friendly_token NOT IN (
|
|
@@ -306,6 +351,10 @@ class Database:
|
|
|
306
351
|
)
|
|
307
352
|
"""
|
|
308
353
|
params: list = []
|
|
354
|
+
if not show_hidden:
|
|
355
|
+
excl_sql, excl_params = _hidden_exclusion("c")
|
|
356
|
+
query += excl_sql
|
|
357
|
+
params.extend(excl_params)
|
|
309
358
|
if category:
|
|
310
359
|
query += """
|
|
311
360
|
AND c.friendly_token IN (
|
|
@@ -327,7 +376,7 @@ class Database:
|
|
|
327
376
|
row = await self._fetch_one(query, params)
|
|
328
377
|
return row["cnt"] if row else 0
|
|
329
378
|
|
|
330
|
-
async def search(self, query_text: str, *, page: int = 1, per_page: int = 24) -> list[dict]:
|
|
379
|
+
async def search(self, query_text: str, *, page: int = 1, per_page: int = 24, show_hidden: bool = False) -> list[dict]:
|
|
331
380
|
sql = """
|
|
332
381
|
SELECT c.friendly_token, c.title, c.duration_sec, c.cover_art_path, c.thumbnail_url, c.manifest_url,
|
|
333
382
|
rank AS relevance
|
|
@@ -339,12 +388,20 @@ class Database:
|
|
|
339
388
|
JOIN saved_playlists sp ON spi.playlist_id = sp.id
|
|
340
389
|
WHERE sp.is_immutable = 1 AND spi.media_type = 'cm'
|
|
341
390
|
)
|
|
391
|
+
"""
|
|
392
|
+
params: list = [query_text]
|
|
393
|
+
if not show_hidden:
|
|
394
|
+
excl_sql, excl_params = _hidden_exclusion("c")
|
|
395
|
+
sql += excl_sql
|
|
396
|
+
params.extend(excl_params)
|
|
397
|
+
sql += """
|
|
342
398
|
ORDER BY rank
|
|
343
399
|
LIMIT ? OFFSET ?
|
|
344
400
|
"""
|
|
345
|
-
|
|
401
|
+
params.extend([per_page, (page - 1) * per_page])
|
|
402
|
+
return await self._fetch_all(sql, params)
|
|
346
403
|
|
|
347
|
-
async def search_count(self, query_text: str) -> int:
|
|
404
|
+
async def search_count(self, query_text: str, *, show_hidden: bool = False) -> int:
|
|
348
405
|
sql = """
|
|
349
406
|
SELECT COUNT(*) as cnt
|
|
350
407
|
FROM catalog_fts fts
|
|
@@ -356,7 +413,12 @@ class Database:
|
|
|
356
413
|
WHERE sp.is_immutable = 1 AND spi.media_type = 'cm'
|
|
357
414
|
)
|
|
358
415
|
"""
|
|
359
|
-
|
|
416
|
+
params: list = [query_text]
|
|
417
|
+
if not show_hidden:
|
|
418
|
+
excl_sql, excl_params = _hidden_exclusion("c")
|
|
419
|
+
sql += excl_sql
|
|
420
|
+
params.extend(excl_params)
|
|
421
|
+
row = await self._fetch_one(sql, params)
|
|
360
422
|
return row["cnt"] if row else 0
|
|
361
423
|
|
|
362
424
|
async def get_item(self, friendly_token: str) -> dict | None:
|
|
@@ -438,31 +500,43 @@ class Database:
|
|
|
438
500
|
row = await self._fetch_one(sql, [friendly_token])
|
|
439
501
|
return row is not None
|
|
440
502
|
|
|
441
|
-
async def get_categories(self) -> list[dict]:
|
|
503
|
+
async def get_categories(self, *, show_hidden: bool = False) -> list[dict]:
|
|
442
504
|
"""Distinct categories that have at least one catalog item, for facets."""
|
|
443
|
-
|
|
444
|
-
"""
|
|
505
|
+
sql = """
|
|
445
506
|
SELECT c.id, c.name, c.slug, COUNT(cc.friendly_token) AS cnt
|
|
446
507
|
FROM categories c
|
|
447
508
|
JOIN catalog_categories cc ON cc.category_id = c.id
|
|
509
|
+
"""
|
|
510
|
+
params: list = []
|
|
511
|
+
if not show_hidden:
|
|
512
|
+
ph = ",".join("?" * len(HIDDEN_CATEGORY_NAMES))
|
|
513
|
+
sql += f" WHERE c.name NOT IN ({ph})"
|
|
514
|
+
params.extend(HIDDEN_CATEGORY_NAMES)
|
|
515
|
+
sql += """
|
|
448
516
|
GROUP BY c.id, c.name, c.slug
|
|
449
517
|
ORDER BY c.name
|
|
450
|
-
|
|
451
|
-
)
|
|
518
|
+
"""
|
|
519
|
+
return await self._fetch_all(sql, params)
|
|
452
520
|
|
|
453
|
-
async def get_tags(self, *, limit: int = 100) -> list[dict]:
|
|
521
|
+
async def get_tags(self, *, limit: int = 100, show_hidden: bool = False) -> list[dict]:
|
|
454
522
|
"""Most-used tags that have at least one catalog item, for facets."""
|
|
455
|
-
|
|
456
|
-
"""
|
|
523
|
+
sql = """
|
|
457
524
|
SELECT t.id, t.name, COUNT(ct.friendly_token) AS cnt
|
|
458
525
|
FROM tags t
|
|
459
526
|
JOIN catalog_tags ct ON ct.tag_id = t.id
|
|
527
|
+
"""
|
|
528
|
+
params: list = []
|
|
529
|
+
if not show_hidden:
|
|
530
|
+
ph = ",".join("?" * len(HIDDEN_TAG_NAMES))
|
|
531
|
+
sql += f" WHERE t.name NOT IN ({ph})"
|
|
532
|
+
params.extend(HIDDEN_TAG_NAMES)
|
|
533
|
+
sql += """
|
|
460
534
|
GROUP BY t.id, t.name
|
|
461
535
|
ORDER BY cnt DESC, t.name ASC
|
|
462
536
|
LIMIT ?
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
)
|
|
537
|
+
"""
|
|
538
|
+
params.append(limit)
|
|
539
|
+
return await self._fetch_all(sql, params)
|
|
466
540
|
|
|
467
541
|
async def upsert_category(self, name: str) -> int:
|
|
468
542
|
"""Insert a category by name (deriving a unique slug) and return its id."""
|
|
@@ -7,23 +7,25 @@ router = APIRouter(prefix="/catalog", tags=["catalog"])
|
|
|
7
7
|
|
|
8
8
|
@router.get("/browse")
|
|
9
9
|
async def browse(request: Request, category: str | None = None, tag: str | None = None,
|
|
10
|
-
page: int = 1, user: dict = Depends(get_current_user)):
|
|
10
|
+
page: int = 1, show_hidden: int = 0, user: dict = Depends(get_current_user)):
|
|
11
11
|
"""Browse catalog with optional category/tag filter."""
|
|
12
12
|
db = request.app.state.db
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
show_hidden = bool(show_hidden) and (user.get("rank") or 0) >= 3
|
|
14
|
+
items = await db.browse(category=category, tag=tag, page=page, show_hidden=show_hidden)
|
|
15
|
+
categories = await db.get_categories(show_hidden=show_hidden)
|
|
16
|
+
tags = await db.get_tags(show_hidden=show_hidden)
|
|
16
17
|
return {"items": items, "categories": categories, "tags": tags, "page": page}
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
@router.get("/search")
|
|
20
|
-
async def search(request: Request, q: str = "", page: int = 1,
|
|
21
|
+
async def search(request: Request, q: str = "", page: int = 1, show_hidden: int = 0,
|
|
21
22
|
user: dict = Depends(get_current_user)):
|
|
22
23
|
"""Full-text search of catalog."""
|
|
23
24
|
if not q.strip():
|
|
24
25
|
raise HTTPException(400, "Query required")
|
|
25
26
|
db = request.app.state.db
|
|
26
|
-
|
|
27
|
+
show_hidden = bool(show_hidden) and (user.get("rank") or 0) >= 3
|
|
28
|
+
items = await db.search(q, page=page, show_hidden=show_hidden)
|
|
27
29
|
return {"items": items, "query": q, "page": page}
|
|
28
30
|
|
|
29
31
|
|
|
@@ -41,16 +41,19 @@ async def login_page(request: Request):
|
|
|
41
41
|
|
|
42
42
|
@router.get("/catalog/browse", response_class=HTMLResponse)
|
|
43
43
|
async def catalog_browse_page(request: Request, category: str | None = None,
|
|
44
|
-
tag: str | None = None, page: int = 1
|
|
44
|
+
tag: str | None = None, page: int = 1,
|
|
45
|
+
show_hidden: int = 0):
|
|
45
46
|
user = _get_user_or_none(request)
|
|
46
47
|
if not user:
|
|
47
48
|
return RedirectResponse("/auth/login")
|
|
48
49
|
db = request.app.state.db
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
is_admin = (user.get("rank") or 0) >= 3
|
|
51
|
+
show_hidden = bool(show_hidden) and is_admin
|
|
52
|
+
items = await db.browse(category=category, tag=tag, page=page, show_hidden=show_hidden)
|
|
53
|
+
total = await db.browse_count(category=category, tag=tag, show_hidden=show_hidden)
|
|
51
54
|
total_pages = max(1, (total + 23) // 24)
|
|
52
|
-
categories = await db.get_categories()
|
|
53
|
-
tags = await db.get_tags()
|
|
55
|
+
categories = await db.get_categories(show_hidden=show_hidden)
|
|
56
|
+
tags = await db.get_tags(show_hidden=show_hidden)
|
|
54
57
|
return templates.TemplateResponse(request, "catalog/browse.html", {
|
|
55
58
|
"user": user,
|
|
56
59
|
"items": items,
|
|
@@ -61,22 +64,27 @@ async def catalog_browse_page(request: Request, category: str | None = None,
|
|
|
61
64
|
"active_category": category,
|
|
62
65
|
"active_tag": tag,
|
|
63
66
|
"query": None,
|
|
67
|
+
"is_admin": is_admin,
|
|
68
|
+
"show_hidden": show_hidden,
|
|
64
69
|
})
|
|
65
70
|
|
|
66
71
|
|
|
67
72
|
@router.get("/catalog/search", response_class=HTMLResponse)
|
|
68
|
-
async def catalog_search_page(request: Request, q: str = "", page: int = 1
|
|
73
|
+
async def catalog_search_page(request: Request, q: str = "", page: int = 1,
|
|
74
|
+
show_hidden: int = 0):
|
|
69
75
|
user = _get_user_or_none(request)
|
|
70
76
|
if not user:
|
|
71
77
|
return RedirectResponse("/auth/login")
|
|
72
78
|
if not q.strip():
|
|
73
79
|
return RedirectResponse("/catalog/browse")
|
|
74
80
|
db = request.app.state.db
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
is_admin = (user.get("rank") or 0) >= 3
|
|
82
|
+
show_hidden = bool(show_hidden) and is_admin
|
|
83
|
+
items = await db.search(q, page=page, show_hidden=show_hidden)
|
|
84
|
+
total = await db.search_count(q, show_hidden=show_hidden)
|
|
77
85
|
total_pages = max(1, (total + 23) // 24)
|
|
78
|
-
categories = await db.get_categories()
|
|
79
|
-
tags = await db.get_tags()
|
|
86
|
+
categories = await db.get_categories(show_hidden=show_hidden)
|
|
87
|
+
tags = await db.get_tags(show_hidden=show_hidden)
|
|
80
88
|
return templates.TemplateResponse(request, "catalog/browse.html", {
|
|
81
89
|
"user": user,
|
|
82
90
|
"items": items,
|
|
@@ -87,6 +95,8 @@ async def catalog_search_page(request: Request, q: str = "", page: int = 1):
|
|
|
87
95
|
"active_category": None,
|
|
88
96
|
"active_tag": None,
|
|
89
97
|
"query": q,
|
|
98
|
+
"is_admin": is_admin,
|
|
99
|
+
"show_hidden": show_hidden,
|
|
90
100
|
})
|
|
91
101
|
|
|
92
102
|
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
--radius: 8px;
|
|
17
17
|
--shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
18
18
|
--nav-height: 4rem;
|
|
19
|
+
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
20
|
+
--font-heading: 'Sora', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
* {
|
|
@@ -30,11 +32,20 @@ html {
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
body {
|
|
33
|
-
font-family: -
|
|
35
|
+
font-family: var(--font-body);
|
|
34
36
|
background: var(--bg-primary);
|
|
35
37
|
color: var(--text-primary);
|
|
36
38
|
line-height: 1.6;
|
|
37
39
|
min-height: 100vh;
|
|
40
|
+
-webkit-font-smoothing: antialiased;
|
|
41
|
+
-moz-osx-font-smoothing: grayscale;
|
|
42
|
+
text-rendering: optimizeLegibility;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
h1, h2, h3, h4, h5, h6,
|
|
46
|
+
.nav-brand a {
|
|
47
|
+
font-family: var(--font-heading);
|
|
48
|
+
letter-spacing: -0.01em;
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
a {
|
|
@@ -127,6 +138,18 @@ a:hover {
|
|
|
127
138
|
align-items: center;
|
|
128
139
|
flex-wrap: wrap;
|
|
129
140
|
}
|
|
141
|
+
.admin-hidden-notice {
|
|
142
|
+
margin-top: 0.85rem;
|
|
143
|
+
font-size: 0.85rem;
|
|
144
|
+
color: var(--text-secondary);
|
|
145
|
+
display: flex;
|
|
146
|
+
gap: 0.5rem;
|
|
147
|
+
align-items: center;
|
|
148
|
+
flex-wrap: wrap;
|
|
149
|
+
}
|
|
150
|
+
.admin-hidden-notice a {
|
|
151
|
+
font-weight: 600;
|
|
152
|
+
}
|
|
130
153
|
.search-form {
|
|
131
154
|
display: flex;
|
|
132
155
|
gap: 0.5rem;
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>{% block title %}Queue{% endblock %} — Channel-Z</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Sora:wght@600;700&display=swap">
|
|
7
10
|
<link rel="stylesheet" href="/static/css/main.css">
|
|
8
11
|
{% block head %}{% endblock %}
|
|
9
12
|
</head>
|
{kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/catalog/browse.html
RENAMED
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
</select>
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
28
|
+
{% if is_admin %}
|
|
29
|
+
<div class="admin-hidden-notice">
|
|
30
|
+
<span>Certain items are hidden from results.</span>
|
|
31
|
+
{% if show_hidden %}
|
|
32
|
+
<a href="{{ request.url.remove_query_params('show_hidden') }}">Hide them again</a>
|
|
33
|
+
{% else %}
|
|
34
|
+
<a href="{{ request.url.include_query_params(show_hidden=1) }}">Show hidden items?</a>
|
|
35
|
+
{% endif %}
|
|
36
|
+
</div>
|
|
37
|
+
{% endif %}
|
|
28
38
|
</div>
|
|
29
39
|
|
|
30
40
|
<div class="catalog-grid">
|
|
@@ -55,7 +65,6 @@
|
|
|
55
65
|
</div>
|
|
56
66
|
<div class="card-actions">
|
|
57
67
|
<button class="btn btn-sm btn-queue" onclick="queueItem('{{ item.friendly_token }}')">Queue</button>
|
|
58
|
-
<button class="btn btn-sm btn-playnext" onclick="playNext('{{ item.friendly_token }}')">Play Next</button>
|
|
59
68
|
{% if user.rank >= 3 %}
|
|
60
69
|
<button class="btn btn-sm btn-admin" onclick="queueAsAdmin('{{ item.friendly_token }}')">Queue as Admin</button>
|
|
61
70
|
{% endif %}
|
{kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/catalog/item_detail.html
RENAMED
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
|
|
23
23
|
<div class="card-actions item-detail-actions">
|
|
24
24
|
<button class="btn btn-queue" onclick="queueItem('{{ item.friendly_token }}')">Queue</button>
|
|
25
|
-
<button class="btn btn-playnext" onclick="playNext('{{ item.friendly_token }}')">Play Next</button>
|
|
26
25
|
{% if user.rank >= 3 %}
|
|
27
26
|
<button class="btn btn-admin" onclick="queueAsAdmin('{{ item.friendly_token }}')">Queue as Admin</button>
|
|
28
27
|
{% endif %}
|
|
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.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/admin/playlists.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/admin/queue_mgmt.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/admin/schedules.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.7.4 → kryten_webqueue-0.7.5}/kryten_webqueue/templates/user/dashboard.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|