mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 21:26:15 -08:00
feat(portals): mark a whole series as watched in one click (#1451)
* feat(portals): mark a whole series as watched in one click Adds a series-level watched toggle to the season header's new overflow menu on both Xtream and Stalker series detail pages (issue #1442 v2, building on the season-level toggle from #1447). - Shared: buildSeriesWatchToggleRequest flattens every loaded season with the season builder's mark/unmark semantics; the direction is always the one the label advertised, never re-inferred at persist time. Watch-toggle state math for both scopes moves into the new component-provided SeasonWatchPresenter (the container component sat at the max-lines cap). - Xtream: the series request reuses SerialDetailsSeasonWatchService through a scope-parameterized handle(), the same stillCurrent ownership guard, and the XtreamStore.loadAllPositions badge refresh. - Stalker: the season handler's core is extracted into runWatchToggleBatch (feedback keys per scope). Lazy Ministra VOD hydrates unloaded seasons sequentially first (zero writes on a failed fetch, silent abort on navigation), re-runs the position reconcile synchronously so newly hydrated episodes' legacy rows are cleaned, then rebuilds the request keeping the clicked direction; an all-watched outcome reports an honest count-0 snackbar. - Container: new hasUnloadedSeasons input blocks the fully-watched verdict and the count label while lazy seasons are unloaded, and the empty mark request contract lets the host hydrate-then-rebuild. Six new XTREAM i18n keys, synced to all 18 locales via the i18n-fill workflow. No new IPC: the existing playback-position batch channels are season-agnostic. Refs #1442 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): treat an empty Stalker season answer as loaded, not pending A lazy season the portal ANSWERS for with zero episodes was still counted as unloaded (episodes.length === 0 heuristic): the series label stayed countless forever and every series toggle re-fetched the empty season, while a glitch-empty answer could silently skip a season and still report success as if nothing remained. VodSeriesSeasonVm gains an episodesLoaded flag set by every successful episode fetch — including an empty one — and the series toggle's pending predicate, hydration re-check, and hasUnloadedVodSeasons now key on it. A loaded-and-empty season unblocks the count label and the fully-watched verdict instead of re-fetching; a fresh detail mount still re-fetches, so a one-off glitch self-corrects next session. Addresses the Greptile P1 on PR #1451. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): harden lazy season hydration against malformed and racing loads Two review findings on the series watched toggle: - fetchVodSeriesEpisodes now trusts an empty answer only when the envelope actually carried a well-formed array; a malformed envelope or an answer whose rows contain no recognizable episode rejects, so the load fails instead of the season being recorded loaded-and-empty and silently skipped by the series batch. - loadEpisodesForSeason is single-flight per season: a tab click, the spillover prefetch, the quick-start recursion, and the series-toggle hydration join one in-flight request instead of duplicating portal traffic — previously a second request's failure could abort a series toggle whose original request succeeded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7fc9380bff
commit
0a2fe263db
48 files changed
+2340
-261
No files matched your search
@@ -280,8 +280,45 @@ same captured playlist/series identity. After any Xtream toggle
|
||||
(single or batch) the host refreshes `XtreamStore.loadAllPositions` —
|
||||
the catalog reads series-progress badges from the store, which otherwise
|
||||
loads positions once per playlist — unless the playlist changed
|
||||
meanwhile. The
|
||||
host reports busy-state back through the `seasonWatchBatchRunning` input.
|
||||
meanwhile. The host reports busy-state back through the
|
||||
`seasonWatchBatchRunning` input.
|
||||
|
||||
A series-level counterpart lives in a `⋮` menu at the end of the same
|
||||
header row (`SeasonWatchPresenter` in `libs/ui/components` owns the state
|
||||
math for both scopes; the container component sits at the max-lines cap).
|
||||
`buildSeriesWatchToggleRequest` flattens every LOADED season with the same
|
||||
mark/unmark semantics, and the direction is always the one the label
|
||||
advertised (`markWatched: !seriesFullyWatched()`), never re-inferred from
|
||||
data at persist time. Hosts route the request through the same machinery
|
||||
as the season toggle — Xtream via the scope-parameterized
|
||||
`SerialDetailsSeasonWatchService.handle(..., scope)`, Stalker via the
|
||||
extracted `runWatchToggleBatch` core — sharing the busy flag, the
|
||||
ownership guards, and the catalog-badge refresh. Stalker lazy-VOD is the
|
||||
special case: unopened seasons have empty episode lists, so the container
|
||||
reports them through the `hasUnloadedSeasons` input (blocks the
|
||||
"fully watched" verdict and switches the label to its countless variant),
|
||||
and may emit an EMPTY mark request when every loaded episode is watched.
|
||||
The Stalker host then hydrates the missing seasons sequentially through
|
||||
`loadEpisodesForSeason` (aborting silently on navigation, and aborting
|
||||
with zero writes plus the series failure snackbar if any season fails to
|
||||
load). A season the portal ANSWERS for with zero episodes becomes
|
||||
loaded-and-empty (`VodSeriesSeasonVm.episodesLoaded`), not pending —
|
||||
`episodes.length === 0` alone would keep it counted as unloaded forever,
|
||||
locking the label countless and re-fetching it on every series toggle.
|
||||
Only a well-formed empty array earns that trust: `fetchVodSeriesEpisodes`
|
||||
rejects a malformed envelope or an answer whose rows contain no
|
||||
recognizable episode, so those fail the load instead of masquerading as
|
||||
an empty season. `loadEpisodesForSeason` is single-flight per season — a
|
||||
tab click, the spillover prefetch, the quick-start recursion, and the
|
||||
series-toggle hydration join one in-flight request instead of
|
||||
duplicating it (a second request's failure could abort a toggle whose
|
||||
original request succeeded).
|
||||
The host synchronously re-runs the position reconcile
|
||||
(`applyReconciledSeriesPositions` — the effect-fed maps only update on
|
||||
the next change-detection tick, and enqueuing against stale maps would
|
||||
miss the hydrated episodes' legacy rows), rebuilds the request from the
|
||||
now-complete seasons keeping the captured direction, and reports an
|
||||
honest count-0 success if hydration reveals nothing left to mark.
|
||||
|
||||
## Components
|
||||
|
||||
|
||||
Reference in new issue
Block a user