feat(channels): R14 zoom-fit table — fluid columns + column fogyókúra

- DataTable: opt-in `fixedLayout` (colgroup + table-layout:fixed) so the
  table fits its container at any zoom/text-size; only the channel manager
  opts in — playlists/discovery/Plex keep auto-sizing.
- Remove the N/S/L and Sync columns from the manager table. Fully-synced
  status becomes a ✓ charm on the Stored count; the sync filter moves to an
  `applySyncFilter` pre-filter still driven by the top chips.
- Channel column is now fluid: ChannelLink truncates with the full name on
  hover (title). Data columns get fixed widths so only Channel/About flex.
- N/S/L (normal/short/live) breakdown moves to the channel page: add the
  three counts to the channelDetail endpoint (one grouped query, same
  filters as the list) + the ChannelDetail type + a meta-line entry.
- Drop the now-dead sync/types i18n keys (en+hu).
This commit is contained in:
2026-08-15 05:28:59 +02:00
parent ea7bf593cb
commit 7ee0e9cd02
11 changed files with 114 additions and 129 deletions
+24 -1
View File
@@ -229,6 +229,9 @@ def _channel_detail_dict(
explored: bool,
blocked: bool,
stored: int,
count_normal: int,
count_short: int,
count_live: int,
deep_requested: bool,
priority: int,
tag_ids: list[int],
@@ -253,6 +256,11 @@ def _channel_detail_dict(
"explored": explored,
"blocked": blocked,
"stored_videos": stored,
# Per-type breakdown (Normal / Shorts / Live) — R14 moved it off the manager table onto the
# channel page. Same aggregation the manager list uses (count_normal = stored − shorts − live).
"count_normal": count_normal,
"count_short": count_short,
"count_live": count_live,
# Deep-backfill state, so the channel page can offer "get full history" when the catalog
# holds only the recent-backfill/RSS subset (video_count is YouTube's raw total).
"deep_requested": deep_requested,
@@ -304,7 +312,19 @@ def channel_detail(
)
).first() is not None
blocked = _is_blocked(db, user, channel_id)
stored = db.scalar(select(func.count(Video.id)).where(Video.channel_id == channel_id)) or 0
# Stored total + the Normal/Shorts/Live split in one grouped pass (same filters as the manager
# list), so the channel page can show the per-type breakdown R14 moved off the table.
n_all, n_short, n_live = db.execute(
select(
func.count(Video.id),
func.count(Video.id).filter(Video.is_short.is_(True)),
func.count(Video.id).filter(Video.live_status.in_(LIVE_OR_UPCOMING)),
).where(Video.channel_id == channel_id)
).one()
stored = int(n_all or 0)
count_short = int(n_short or 0)
count_live = int(n_live or 0)
count_normal = max(0, stored - count_short - count_live)
tag_ids = list(
db.scalars(
select(ChannelTag.tag_id).where(
@@ -318,6 +338,9 @@ def channel_detail(
explored=explored,
blocked=blocked,
stored=int(stored),
count_normal=count_normal,
count_short=count_short,
count_live=count_live,
deep_requested=bool(sub.deep_requested) if sub else False,
priority=int(sub.priority) if sub else 0,
tag_ids=tag_ids,