fix(downloads): isolate season header build warnings

This commit is contained in:
4gray committed 2026-08-02 15:58:55 +02:00
1 parent ce18f41219
commit beba983255
7 files changed
+233 -180

No files matched your search

@@ -1,78 +1,14 @@
<section class="seasons-section">
<div class="section-header">
<div class="section-header__left">
<h3 class="section-title">
{{ 'PORTALS.SEASONS_AND_EPISODES' | translate }}
</h3>
</div>
@if (
downloadPresenter.presentationVisible() ||
(hasSeasons() && !showSeasonEmptyState())
) {
<div class="section-header__actions">
@if (downloadPresenter.presentationVisible()) {
<button
mat-stroked-button
class="season-download-button"
data-test-id="download-season"
[disabled]="downloadPresenter.seasonDisabled()"
[attr.aria-label]="
downloadPresenter.batchRunning()
? ('DOWNLOADS.ADDING_TO_QUEUE' | translate)
: ('DOWNLOADS.DOWNLOAD_SEASON_ARIA'
| translate
: {
count: downloadPresenter.eligibleEpisodeCount(),
})
"
[attr.aria-busy]="
downloadPresenter.batchRunning() ? 'true' : null
"
(click)="downloadPresenter.enqueueSeason()"
>
@if (downloadPresenter.batchRunning()) {
<mat-spinner
diameter="18"
aria-hidden="true"
></mat-spinner>
{{ 'DOWNLOADS.ADDING_TO_QUEUE' | translate }}
} @else {
<mat-icon>download</mat-icon>
{{
'DOWNLOADS.DOWNLOAD_SEASON'
| translate
: {
count: downloadPresenter.eligibleEpisodeCount(),
}
}}
}
</button>
}
@if (hasSeasons() && !showSeasonEmptyState()) {
<div class="view-toggle">
<mat-button-toggle-group
[value]="viewMode()"
(change)="setViewMode($event.value)"
hideSingleSelectionIndicator
>
<mat-button-toggle
value="grid"
[matTooltip]="'PORTALS.GRID_VIEW' | translate"
>
<mat-icon>grid_view</mat-icon>
</mat-button-toggle>
<mat-button-toggle
value="list"
[matTooltip]="'PORTALS.LIST_VIEW' | translate"
>
<mat-icon>view_list</mat-icon>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
}
</div>
}
</div>
<app-season-header
[downloadVisible]="downloadPresenter.presentationVisible()"
[batchRunning]="downloadPresenter.batchRunning()"
[seasonDisabled]="downloadPresenter.seasonDisabled()"
[eligibleEpisodeCount]="downloadPresenter.eligibleEpisodeCount()"
[viewToggleVisible]="hasSeasons() && !showSeasonEmptyState()"
[viewMode]="viewMode()"
(downloadSeason)="downloadPresenter.enqueueSeason()"
(viewModeChange)="setViewMode($event)"
/>
@if (hasSeasons()) {
<app-season-tabs
@@ -3,97 +3,6 @@
margin-top: 24px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
min-height: 32px;
gap: 16px;
&__left {
display: flex;
align-items: center;
gap: 16px;
flex: 1;
}
&__actions {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 8px;
}
.section-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
color: var(--text-primary);
letter-spacing: -0.01em;
}
.view-toggle {
mat-button-toggle-group {
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
overflow: hidden;
background: rgba(255, 255, 255, 0.03);
::ng-deep {
.mat-button-toggle {
background: transparent;
.mat-button-toggle-button {
padding: 0 8px;
height: 32px;
}
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
color: var(--text-secondary);
}
&.mat-button-toggle-checked {
background: rgba(255, 255, 255, 0.1);
mat-icon {
color: var(--accent-color);
}
}
&:hover:not(.mat-button-toggle-checked) {
background: rgba(255, 255, 255, 0.05);
}
}
.mat-button-toggle-appearance-standard {
.mat-button-toggle-label-content {
line-height: 32px;
padding: 0;
}
}
.mat-button-toggle + .mat-button-toggle {
border-left: 1px solid rgba(255, 255, 255, 0.1);
}
}
}
}
}
.season-download-button {
min-height: 36px;
mat-spinner {
display: inline-block;
margin-inline-end: 8px;
}
}
.loading-container {
display: flex;
justify-content: center;
@@ -800,15 +709,3 @@ app-season-tabs {
}
}
}
@media (max-width: 560px) {
.section-header {
align-items: flex-start;
flex-wrap: wrap;
&__actions {
width: 100%;
justify-content: space-between;
}
}
}
@@ -596,6 +596,9 @@ describe('SeasonContainerComponent', () => {
expect(button.getAttribute('aria-label')).toBe(
'Download season, 1 episodes available'
);
expect(
button.querySelector('.season-download-button__label')?.textContent
).toContain('Download season (1)');
expect(button.nextElementSibling?.classList).toContain('view-toggle');
button.click();
@@ -12,7 +12,6 @@ import {
untracked,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatDialog } from '@angular/material/dialog';
import { MatIcon } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@@ -41,9 +40,12 @@ import {
parseDuration,
} from './episode-progress.util';
import { SeasonDownloadPresenter } from './season-download-presenter';
import {
type EpisodeViewMode,
SeasonHeaderComponent,
} from './season-header.component';
import { SeasonTabsComponent } from './season-tabs.component';
type EpisodeViewMode = 'grid' | 'list';
const EPISODE_VIEW_MODE_KEY = 'iptvnator_episode_view_mode';
export interface SeasonContainerPlaybackToggleRequest {
@@ -59,11 +61,11 @@ export interface SeasonContainerPlaybackToggleRequest {
providers: [SeasonDownloadPresenter],
imports: [
MatButtonModule,
MatButtonToggleModule,
MatIcon,
MatProgressSpinnerModule,
MatTooltipModule,
ProgressCapsuleComponent,
SeasonHeaderComponent,
SeasonTabsComponent,
TranslateModule,
],
@@ -0,0 +1,69 @@
<div class="section-header">
<div class="section-header__left">
<h3 class="section-title">
{{ 'PORTALS.SEASONS_AND_EPISODES' | translate }}
</h3>
</div>
@if (downloadVisible() || viewToggleVisible()) {
<div class="section-header__actions">
@if (downloadVisible()) {
<button
mat-stroked-button
class="season-download-button"
data-test-id="download-season"
[disabled]="seasonDisabled()"
[attr.aria-label]="
batchRunning()
? ('DOWNLOADS.ADDING_TO_QUEUE' | translate)
: ('DOWNLOADS.DOWNLOAD_SEASON_ARIA'
| translate: { count: eligibleEpisodeCount() })
"
[attr.aria-busy]="batchRunning() ? 'true' : null"
(click)="downloadSeason.emit()"
>
@if (batchRunning()) {
<mat-spinner
diameter="18"
aria-hidden="true"
></mat-spinner>
} @else {
<mat-icon>download</mat-icon>
}
<span class="season-download-button__label">
@if (batchRunning()) {
{{ 'DOWNLOADS.ADDING_TO_QUEUE' | translate }}
} @else {
{{
'DOWNLOADS.DOWNLOAD_SEASON'
| translate
: { count: eligibleEpisodeCount() }
}}
}
</span>
</button>
}
@if (viewToggleVisible()) {
<div class="view-toggle">
<mat-button-toggle-group
[value]="viewMode()"
(change)="viewModeChange.emit($event.value)"
hideSingleSelectionIndicator
>
<mat-button-toggle
value="grid"
[matTooltip]="'PORTALS.GRID_VIEW' | translate"
>
<mat-icon>grid_view</mat-icon>
</mat-button-toggle>
<mat-button-toggle
value="list"
[matTooltip]="'PORTALS.LIST_VIEW' | translate"
>
<mat-icon>view_list</mat-icon>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
}
</div>
}
</div>
@@ -0,0 +1,106 @@
:host {
display: block;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
min-height: 32px;
gap: 16px;
&__left {
display: flex;
align-items: center;
gap: 16px;
flex: 1;
}
&__actions {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 8px;
}
.section-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
color: var(--text-primary);
letter-spacing: -0.01em;
}
.view-toggle {
mat-button-toggle-group {
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
overflow: hidden;
background: rgba(255, 255, 255, 0.03);
::ng-deep {
.mat-button-toggle {
background: transparent;
.mat-button-toggle-button {
padding: 0 8px;
height: 32px;
}
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
color: var(--text-secondary);
}
&.mat-button-toggle-checked {
background: rgba(255, 255, 255, 0.1);
mat-icon {
color: var(--accent-color);
}
}
&:hover:not(.mat-button-toggle-checked) {
background: rgba(255, 255, 255, 0.05);
}
}
.mat-button-toggle-appearance-standard {
.mat-button-toggle-label-content {
line-height: 32px;
padding: 0;
}
}
.mat-button-toggle + .mat-button-toggle {
border-left: 1px solid rgba(255, 255, 255, 0.1);
}
}
}
}
}
.season-download-button {
min-height: 36px;
mat-spinner {
display: inline-block;
margin-inline-end: 8px;
}
}
@media (max-width: 560px) {
.section-header {
align-items: flex-start;
flex-wrap: wrap;
&__actions {
width: 100%;
justify-content: space-between;
}
}
}
@@ -0,0 +1,40 @@
import {
ChangeDetectionStrategy,
Component,
input,
output,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatIcon } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
export type EpisodeViewMode = 'grid' | 'list';
@Component({
selector: 'app-season-header',
templateUrl: './season-header.component.html',
styleUrls: ['./season-header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
MatButtonModule,
MatButtonToggleModule,
MatIcon,
MatProgressSpinnerModule,
MatTooltipModule,
TranslateModule,
],
})
export class SeasonHeaderComponent {
readonly downloadVisible = input.required<boolean>();
readonly batchRunning = input.required<boolean>();
readonly seasonDisabled = input.required<boolean>();
readonly eligibleEpisodeCount = input.required<number>();
readonly viewToggleVisible = input.required<boolean>();
readonly viewMode = input.required<EpisodeViewMode>();
readonly downloadSeason = output<void>();
readonly viewModeChange = output<EpisodeViewMode>();
}