mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
feat(ui): compact episode stills in the list view
When a season has genuinely distinct per-episode art (TMDB stills or provider images), list rows show a 96x54 thumbnail with the episode number riding on it instead of the number square. When every episode repeats the same image (providers often send the series poster), the plain number square stays — a column of identical posters is worse than none. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d4f6a8b1f7
commit
169925dbbe
4 files changed
+144
-3
No files matched your search
@@ -296,9 +296,36 @@
|
||||
isEpisodePlayingInline(episode)
|
||||
"
|
||||
>
|
||||
<div class="episode-list-item__number">
|
||||
{{ episode.episode_num }}
|
||||
</div>
|
||||
@if (listThumbnailsEnabled()) {
|
||||
<div class="episode-list-item__thumb">
|
||||
@if (episodeInfo?.movie_image) {
|
||||
<img
|
||||
[src]="
|
||||
episodeInfo?.movie_image ?? ''
|
||||
"
|
||||
(error)="
|
||||
$event.target.src =
|
||||
'./assets/images/default-episode.png'
|
||||
"
|
||||
alt="Episode thumbnail"
|
||||
loading="lazy"
|
||||
/>
|
||||
} @else {
|
||||
<mat-icon
|
||||
class="episode-list-item__thumb-placeholder"
|
||||
>play_circle_outline</mat-icon
|
||||
>
|
||||
}
|
||||
<span
|
||||
class="episode-list-item__thumb-number"
|
||||
>{{ episode.episode_num }}</span
|
||||
>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="episode-list-item__number">
|
||||
{{ episode.episode_num }}
|
||||
</div>
|
||||
}
|
||||
<div class="episode-list-item__content">
|
||||
<div class="episode-list-item__header">
|
||||
<h4 class="episode-list-item__title">
|
||||
|
||||
@@ -582,6 +582,48 @@ app-season-tabs {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// Compact 16:9 episode still (only when the season has distinct
|
||||
// per-episode art); the episode number rides on the image.
|
||||
&__thumb {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 96px;
|
||||
height: 54px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&__thumb-placeholder {
|
||||
color: var(--text-muted);
|
||||
font-size: 22px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
&__thumb-number {
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
right: 3px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
padding: 0 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
@@ -330,6 +330,61 @@ describe('SeasonContainerComponent', () => {
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('shows list thumbnails when episode images are distinct', () => {
|
||||
setRequiredInputs({
|
||||
'1': [
|
||||
createEpisode({
|
||||
id: '101',
|
||||
info: { plot: 'a', movie_image: 'still-1.jpg' } as never,
|
||||
}),
|
||||
createEpisode({
|
||||
id: '102',
|
||||
episode_num: 2,
|
||||
info: { plot: 'b', movie_image: 'still-2.jpg' } as never,
|
||||
}),
|
||||
],
|
||||
});
|
||||
component.setViewMode('list');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.listThumbnailsEnabled()).toBe(true);
|
||||
expect(
|
||||
fixture.nativeElement.querySelectorAll('.episode-list-item__thumb')
|
||||
.length
|
||||
).toBe(2);
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('.episode-list-item__number')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps the number square when every episode repeats the same image', () => {
|
||||
setRequiredInputs({
|
||||
'1': [
|
||||
createEpisode({
|
||||
id: '101',
|
||||
info: { plot: 'a', movie_image: 'poster.jpg' } as never,
|
||||
}),
|
||||
createEpisode({
|
||||
id: '102',
|
||||
episode_num: 2,
|
||||
info: { plot: 'b', movie_image: 'poster.jpg' } as never,
|
||||
}),
|
||||
],
|
||||
});
|
||||
component.setViewMode('list');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.listThumbnailsEnabled()).toBe(false);
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('.episode-list-item__thumb')
|
||||
).toBeNull();
|
||||
expect(
|
||||
fixture.nativeElement.querySelectorAll(
|
||||
'.episode-list-item__number'
|
||||
).length
|
||||
).toBe(2);
|
||||
});
|
||||
|
||||
it('renders the season description for the selected season', () => {
|
||||
fixture.componentRef.setInput('seasonDescriptions', {
|
||||
'1': 'Season one overview',
|
||||
|
||||
@@ -155,6 +155,23 @@ export class SeasonContainerComponent implements OnInit {
|
||||
);
|
||||
private lastAutoSelectKey: string | null = null;
|
||||
|
||||
/**
|
||||
* Show thumbnails in the list view only when episodes have genuinely
|
||||
* distinct stills (TMDB or per-episode provider art). When every episode
|
||||
* carries the same image (providers often repeat the series poster) a
|
||||
* column of identical pictures is worse than the plain number square.
|
||||
*/
|
||||
readonly listThumbnailsEnabled = computed(() => {
|
||||
const episodes = this.selectedSeasonEpisodes();
|
||||
const images = episodes
|
||||
.map((episode) => this.getEpisodeInfo(episode)?.movie_image)
|
||||
.filter((image): image is string => !!image);
|
||||
if (images.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return episodes.length === 1 || new Set(images).size > 1;
|
||||
});
|
||||
|
||||
readonly selectedSeasonDescription = computed(() => {
|
||||
const selected = this.selectedSeason();
|
||||
if (!selected) {
|
||||
|
||||
Reference in new issue
Block a user