mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
feat(epg): add vertical list view for the live EPG panel
Add an EPG list view — a vertical, single-day programme list — as an alternative rendering of the live EPG panel, selectable via a new Settings → EPG → "Guide view" toggle (epgViewMode: 'timeline' | 'list', default 'timeline' so existing users see no change). - New EpgListViewComponent (app-epg-list-view) mirrors EpgTimelineComponent's input/output contract 1:1, so all four live hosts (M3U player, unified live tab, Xtream, Stalker) swap the panel with a plain @if and identical bindings. - Reuses the shared view-agnostic EPG modules (classifyTimelineWhen, hasProgramsForDateKey, epg-archive.util catch-up gating, epg-summary.util collapsed-summary maths, epg-date helpers, EpgProgrammeDialogService, app-epg-timeline-empty-state) — no duplicated logic. - Rows show time range, title, optional description, live progress on the on-air row, catch-up "Watch" on past rows when archive playback is available, and a details dialog; keyboard activation guards nested buttons (target === currentTarget). - Auto-focuses the on-air row on channel select, restores it across collapse/expand remounts, and shows a sticky in-flow "On now" strip (never overlaying rows) when the current programme is scrolled away; all scroll maths is rect-based relative to the scroller. - List mode raises only the inline panel height via an epg--list modifier (--epg-inline-height clamp); timeline and collapsed heights are unchanged. - Setting flows end-to-end (Settings interface → DEFAULT_SETTINGS → SettingsStore/StorageMap → segmented control in the EPG section); Electron-only UI, PWA stays on the timeline default. i18n keys added to all 18 locales. - Tests: new component/row/utils/scroll-controller specs, settings persistence spec, swap tests in all four host specs, and Electron E2E for the settings round-trip and the rendered list view. Docs updated (m3u-playlist-module.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
401012eac4
commit
c4890748e0
57 files changed
+2981
-90
No files matched your search
@@ -266,16 +266,33 @@ per-tab EPG logic:
|
||||
favorites). Callers read their `progressTick()` signal first so the computed
|
||||
re-runs on the ~30s tick.
|
||||
|
||||
### EPG Timeline Panel
|
||||
### EPG Panel (Timeline & List views)
|
||||
|
||||
The programme guide under the player is a horizontal **timeline ribbon**
|
||||
(`app-epg-timeline`, `libs/ui/epg/src/lib/epg-timeline/`) shared by all four live
|
||||
surfaces: the M3U video player, the unified live tab, and the Xtream and Stalker
|
||||
live-stream layouts. It replaces the former vertical `app-epg-list` /
|
||||
`app-epg-view`.
|
||||
The programme guide under the player renders in one of **two interchangeable
|
||||
views**, chosen by the **`epgViewMode`** setting (`'timeline'` default, or
|
||||
`'list'`; Settings → EPG → *Guide view*):
|
||||
|
||||
The component stays presentation-focused; the reusable, view-agnostic pieces
|
||||
(so a future list view can share them) are split out and re-exported from
|
||||
- **Timeline** — a horizontal **ribbon** (`app-epg-timeline`,
|
||||
`libs/ui/epg/src/lib/epg-timeline/`).
|
||||
- **List** — a vertical, single-day **programme list** (`app-epg-list-view`,
|
||||
`libs/ui/epg/src/lib/epg-list-view/`) with a prev/today/next stepper.
|
||||
|
||||
Both are shared by all four live surfaces: the M3U video player, the unified live
|
||||
tab, and the Xtream and Stalker live-stream layouts (replacing the former
|
||||
vertical `app-epg-list` / `app-epg-view`). `EpgListViewComponent` mirrors
|
||||
`EpgTimelineComponent`'s input/output contract **1:1**, so each host swaps them
|
||||
with a plain `@if (epgViewMode() === 'list') { <app-epg-list-view … /> } @else {
|
||||
<app-epg-timeline … /> }` — identical bindings in both branches. Hosts read
|
||||
`epgViewMode` from `SettingsStore` (a signal), so flipping the setting swaps the
|
||||
panel live. The setting flows end-to-end (`Settings.epgViewMode` →
|
||||
`DEFAULT_SETTINGS` → `SettingsStore`/`StorageMap` → the segmented control in
|
||||
`settings-epg-section`) and needs no backend change. The control is
|
||||
**Electron-only in practice** — the EPG settings section (and the form control)
|
||||
is gated behind `supportsEpg`, which is false in PWA; there the stored value
|
||||
simply stays at the `'timeline'` default.
|
||||
|
||||
Both components stay presentation-focused; the reusable, view-agnostic pieces
|
||||
(shared by the timeline and the list) are split out and re-exported from
|
||||
`@iptvnator/ui/epg`:
|
||||
|
||||
- `epg-timeline.utils.ts` (axis/blocks/date helpers) + `epg-timeline-render.util.ts`
|
||||
@@ -290,6 +307,19 @@ The component stays presentation-focused; the reusable, view-agnostic pieces
|
||||
scrolling + channel-select auto-focus); timeline-specific, kept out of the
|
||||
component so it stays under the line ceiling.
|
||||
|
||||
The **list view** (`epg-list-view/`) composes those same shared modules — it does
|
||||
**not** duplicate classification or gating logic. It reuses `classifyTimelineWhen`
|
||||
/ `hasProgramsForDateKey` / `nearestDateKeyWithPrograms`, `epg-archive.util`,
|
||||
`epg-summary.util`, the `epg-date` helpers, `EpgProgrammeDialogService`, and the
|
||||
shared `app-epg-timeline-empty-state` — and drops all ribbon geometry, zoom, and
|
||||
horizontal scroll. It filters the loaded window to the selected day (overlap-based,
|
||||
matching `hasProgramsForDateKey`), sorts, and deduplicates via a pure
|
||||
`buildEpgListRows` (`epg-list-view.utils.ts`); renders each row through the dumb
|
||||
`app-epg-list-view-row`; and delegates its own vertical auto-focus + sticky
|
||||
"now" strip to `EpgListScrollController` (`epg-list-scroll.controller.ts`). Render
|
||||
states, the collapsed inline summary, the date stepper, catch-up/timeshift
|
||||
activation, and the details dialog behave identically to the timeline.
|
||||
|
||||
- **One channel, preloaded window.** The panel always shows a single channel.
|
||||
Each provider returns a multi-day window in roughly one call (M3U
|
||||
`GET_CHANNEL_PROGRAMS`; Stalker `get_epg_info`; Xtream `get_simple_data_table`),
|
||||
@@ -370,7 +400,11 @@ The component stays presentation-focused; the reusable, view-agnostic pieces
|
||||
tag / "Watch") stays pinned at the bottom. With an inline player the guide is
|
||||
a compact panel (`.epg.epg--inline` → `flex: 0 0 clamp(180px, 36vh, 264px)`
|
||||
in `_portal-layout.scss`) so the player stays dominant; with an external
|
||||
player the guide keeps `flex: 1` and fills the whole content area.
|
||||
player the guide keeps `flex: 1` and fills the whole content area. In **list
|
||||
mode** the hosts also set `.epg--list`, which raises only the inline clamp
|
||||
(`--epg-inline-height: clamp(280px, 46vh, 430px)`) — vertical rows need more
|
||||
height than the ribbon; the timeline height is unchanged, and the collapsed
|
||||
56px clamp still wins because the modifier sets just the CSS variable.
|
||||
- **Wide-tier description preview.** When a block is the `wide` tier (rendered
|
||||
width ≥ `132px`, i.e. long programmes and/or zoomed in) **and** the programme
|
||||
has a `desc`, a dimmed (`--text-secondary`) preview of the description renders
|
||||
|
||||
Reference in new issue
Block a user