mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(epg): add vertical list view for the live EPG panel (#1115)
* 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>
* fix(epg): address list-view review findings from Codex and Greptile
- Reset the list view to today when a new channel's programme set
arrives while the user is parked on another day (timeline parity):
the scroll controller now keys by the full programme-set identity
(programsFocusKey) and commits today before focusing, instead of
silently stranding the new channel on the stale day. (Codex P2)
- Centralise the 'timeline' fallback as a resolvedEpgViewMode computed
on SettingsStore; the four live hosts consume the derived signal
instead of duplicating the `?? 'timeline'` expression. (Greptile P2)
- Extract the component's reactive plumbing into
registerEpgListViewEffects(), bringing the component back under the
300-line guideline (290). (Greptile P2)
- Controller spec rewritten around programme-set fixtures with new
coverage: return-to-today on channel switch, day navigation left
alone, no-takeover when today has no data, empty-set no-op.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(epg): drop malformed programmes from the list-view day filter
Reject programmes whose stop is not after their start in
buildEpgListRows — same as the timeline's buildTimelineBlocks. Bad
provider data would otherwise render impossible time ranges and could
even be offered as catch-up playable. (Codex P2 on e6fd0d08)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
f015ec1ca9
commit
0b526b119a
58 files changed
+3090
-91
No files matched your search
@@ -1,7 +1,8 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { computed, inject } from '@angular/core';
|
||||
import {
|
||||
patchState,
|
||||
signalStore,
|
||||
withComputed,
|
||||
withHooks,
|
||||
withMethods,
|
||||
withState,
|
||||
@@ -11,6 +12,7 @@ import { firstValueFrom } from 'rxjs';
|
||||
import {
|
||||
DEFAULT_DASHBOARD_RAILS_SETTINGS,
|
||||
ElectronBridgeTrustOptions,
|
||||
EpgViewMode,
|
||||
Language,
|
||||
Settings,
|
||||
StartupBehavior,
|
||||
@@ -43,6 +45,7 @@ const DEFAULT_SETTINGS: Settings = {
|
||||
downloadFolder: '',
|
||||
recordingFolder: '',
|
||||
coverSize: 'medium',
|
||||
epgViewMode: 'timeline',
|
||||
dashboardRails: DEFAULT_DASHBOARD_RAILS_SETTINGS,
|
||||
preferUploadedEpgOverXtream: false,
|
||||
trustedPrivateNetworkEpgUrls: [],
|
||||
@@ -91,6 +94,16 @@ function scheduleEmbeddedMpvPrepare(): void {
|
||||
export const SettingsStore = signalStore(
|
||||
{ providedIn: 'root' },
|
||||
withState<Settings>(DEFAULT_SETTINGS),
|
||||
withComputed((store) => ({
|
||||
/**
|
||||
* Live EPG panel layout with the `'timeline'` default applied — the
|
||||
* single source of truth for the four live hosts, so the fallback is
|
||||
* not duplicated per call-site.
|
||||
*/
|
||||
resolvedEpgViewMode: computed<EpgViewMode>(
|
||||
() => store.epgViewMode?.() ?? 'timeline'
|
||||
),
|
||||
})),
|
||||
withMethods((store, storage = inject(StorageMap)) => ({
|
||||
async loadSettings() {
|
||||
try {
|
||||
@@ -173,6 +186,8 @@ export const SettingsStore = signalStore(
|
||||
store.recordingFolder?.() ??
|
||||
DEFAULT_SETTINGS.recordingFolder,
|
||||
coverSize: store.coverSize?.() ?? DEFAULT_SETTINGS.coverSize,
|
||||
epgViewMode:
|
||||
store.epgViewMode?.() ?? DEFAULT_SETTINGS.epgViewMode,
|
||||
dashboardRails: normalizeDashboardRailsSettings(
|
||||
store.dashboardRails?.()
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user