mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 20:16:15 -08:00
feat(portal): show each season's own poster beside the season tabs (#1628)
Series detail pages now render the selected season's poster as a season
cover next to the season tabs and description, and the fullscreen episode
panel shows the same poster as a season strip above its tabs.
Resolution is TMDB-first, like the show artwork merge: the lazy season
enrichment stores `/tv/{id}/season/{n}` `poster_path` as a w342 URL in
`tmdb_season_posters` (Xtream) or `StalkerSeriesTmdbSeasonsService.posters()`
(Stalker), under the same write-only-if-changed convergence guard as the
season overview. Xtream falls back to the provider's `seasons[].cover_big`/
`cover` when it is an http(s) URL other than the show poster, because panels
repeat the show poster on every season. Stalker is TMDB-only.
The cover column is not rendered for one-season items, seasons without a
poster, or a failed image, so every fallback is today's markup. It is sized
by a new `--season-cover-width` token (96/120/144px per Settings.coverSize).
The hero poster never follows the season.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
43c1ceac16
commit
7790e68147
36 files changed
+1119
-102
No files matched your search
@@ -719,6 +719,76 @@ test('@xtream season watched toggle — marks a season, survives reload, and cle
|
||||
await expect(page.locator('.season-tabs__done')).toHaveCount(0);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Season cover beside the season tabs (provider tier)
|
||||
//
|
||||
// The default scenario's get_series_info carries a distinct seasons[].cover
|
||||
// per season, which differs from the show cover, so with TMDB enrichment off
|
||||
// the serial details page shows that cover next to the tabs and swaps it on
|
||||
// a tab click. Image requests are answered locally so a slow or blocked CDN
|
||||
// cannot fold the column (the app hides a cover whose image failed).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('@xtream season cover — shows the provider season cover and follows the selected tab', async ({
|
||||
page,
|
||||
request,
|
||||
}) => {
|
||||
const TRANSPARENT_PNG = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
|
||||
'base64'
|
||||
);
|
||||
await page.route('**/picsum.photos/**', (route) =>
|
||||
route.fulfill({ contentType: 'image/png', body: TRANSPARENT_PNG })
|
||||
);
|
||||
|
||||
const categories = (await (
|
||||
await request.get(
|
||||
`${MOCK_SERVER}/player_api.php?username=${DEFAULT_USERNAME}&password=${DEFAULT_PASSWORD}&action=get_series_categories`
|
||||
)
|
||||
).json()) as Array<{ category_id: string; category_name: string }>;
|
||||
const category = categories[0];
|
||||
const seriesItems = (await (
|
||||
await request.get(
|
||||
`${MOCK_SERVER}/player_api.php?username=${DEFAULT_USERNAME}&password=${DEFAULT_PASSWORD}&action=get_series&category_id=${category.category_id}`
|
||||
)
|
||||
).json()) as Array<{ name: string; series_id: number }>;
|
||||
const targetSeries = seriesItems[0];
|
||||
|
||||
await addXtreamPortal(page);
|
||||
await page.goto(page.url().replace(/\/vod.*$/, '/series'));
|
||||
|
||||
const categoryItem = page
|
||||
.locator('.context-panel .category-item')
|
||||
.filter({ hasText: category.category_name })
|
||||
.first();
|
||||
await expect(categoryItem).toBeVisible({ timeout: 10_000 });
|
||||
await categoryItem.click();
|
||||
|
||||
const seriesCard = page
|
||||
.locator('app-grid-list mat-card')
|
||||
.filter({ hasText: targetSeries.name })
|
||||
.first();
|
||||
await expect(seriesCard).toBeVisible({ timeout: 10_000 });
|
||||
await seriesCard.click();
|
||||
|
||||
// Season 1 is auto-selected; its provider cover sits beside the tabs.
|
||||
// The mock seeds season art as `season-<id>-<n>` (cover) and
|
||||
// `season-big-<id>-<n>` (cover_big); the app prefers cover_big.
|
||||
const cover = page.locator('[data-testid="season-cover"]');
|
||||
await expect(cover).toBeVisible({ timeout: 15_000 });
|
||||
await expect(cover).toHaveAttribute(
|
||||
'src',
|
||||
new RegExp(`season(-big)?-${targetSeries.series_id}-1/`)
|
||||
);
|
||||
|
||||
// The cover follows the selected tab.
|
||||
await page.locator('.season-tabs__pill').nth(1).click();
|
||||
await expect(cover).toHaveAttribute(
|
||||
'src',
|
||||
new RegExp(`season(-big)?-${targetSeries.series_id}-2/`)
|
||||
);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Series-level watched toggle: the season header's ⋮ menu marks EVERY season
|
||||
// in one action (default scenario: 3 seasons × 8 episodes = 24), flips to
|
||||
|
||||
@@ -1,32 +1,39 @@
|
||||
// Cover size tokens — consumed by all cover/poster grids and rails.
|
||||
// Toggled via [data-cover-size] attribute on <html>, set from SettingsStore.
|
||||
// --season-cover-width sizes the season cover beside the season tabs on the
|
||||
// series detail page; medium matches the About block's 120px poster.
|
||||
:root {
|
||||
--cover-grid-min-width: 148px;
|
||||
--cover-rail-width: 172px;
|
||||
--cover-gap: 16px;
|
||||
--season-cover-width: 120px;
|
||||
}
|
||||
|
||||
:root[data-cover-size='small'] {
|
||||
--cover-grid-min-width: 120px;
|
||||
--cover-rail-width: 140px;
|
||||
--cover-gap: 12px;
|
||||
--season-cover-width: 96px;
|
||||
}
|
||||
|
||||
:root[data-cover-size='medium'] {
|
||||
--cover-grid-min-width: 148px;
|
||||
--cover-rail-width: 172px;
|
||||
--cover-gap: 16px;
|
||||
--season-cover-width: 120px;
|
||||
}
|
||||
|
||||
:root[data-cover-size='large'] {
|
||||
--cover-grid-min-width: 200px;
|
||||
--cover-rail-width: 220px;
|
||||
--cover-gap: 20px;
|
||||
--season-cover-width: 144px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
:root[data-cover-size='large'] {
|
||||
--cover-grid-min-width: 160px;
|
||||
--cover-rail-width: 170px;
|
||||
--season-cover-width: 120px;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user