From 837849ee574d0f790ca2f09f0802c40db1a418bc Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 8 Nov 2025 10:24:41 +0100 Subject: [PATCH] refactor: code style improvements --- apps/web/project.json | 22 +++++ libs/m3u-state/tsconfig.lib.json | 1 + .../epg-list-item.component.html | 2 +- .../src/lib/epg-list/epg-list.component.ts | 92 +++++++++---------- .../html-video-player.component.spec.ts | 7 +- .../info-overlay.component.spec.ts | 27 ------ .../multi-epg-container.component.ts | 26 +++--- .../recent-playlists.component.html | 4 +- .../recent-playlists.component.ts | 72 +++------------ 9 files changed, 95 insertions(+), 158 deletions(-) delete mode 100644 libs/ui/components/src/lib/info-overlay/info-overlay.component.spec.ts diff --git a/apps/web/project.json b/apps/web/project.json index 4bb746386..a971bd13f 100644 --- a/apps/web/project.json +++ b/apps/web/project.json @@ -45,6 +45,25 @@ ], "outputHashing": "all" }, + "pwa": { + "baseHref": "/", + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kb", + "maximumError": "8kb" + } + ], + "outputHashing": "all", + "optimization": true, + "extractLicenses": true, + "sourceMap": false + }, "development": { "optimization": false, "extractLicenses": false, @@ -60,6 +79,9 @@ "production": { "buildTarget": "web:build:production" }, + "pwa": { + "buildTarget": "web:build:pwa" + }, "development": { "buildTarget": "web:build:development" } diff --git a/libs/m3u-state/tsconfig.lib.json b/libs/m3u-state/tsconfig.lib.json index 13468aff4..643cfe5bc 100644 --- a/libs/m3u-state/tsconfig.lib.json +++ b/libs/m3u-state/tsconfig.lib.json @@ -7,6 +7,7 @@ "inlineSources": true, "types": [] }, + "files": ["../../global.d.ts"], "exclude": [ "src/**/*.spec.ts", "src/test-setup.ts", diff --git a/libs/ui/components/src/lib/epg-list/epg-list-item/epg-list-item.component.html b/libs/ui/components/src/lib/epg-list/epg-list-item/epg-list-item.component.html index d06100f57..3fc5902dc 100644 --- a/libs/ui/components/src/lib/epg-list/epg-list-item/epg-list-item.component.html +++ b/libs/ui/components/src/lib/epg-list/epg-list-item/epg-list-item.component.html @@ -17,7 +17,7 @@ > } -@if (item.desc?.length > 0) { +@if (item?.desc?.length > 0) { ; + readonly timeshiftUntil$ = this.store.select(selectActive).pipe( + map((active) => { + // Create EpgChannel with proper structure + const displayNames = active?.name + ? [{ lang: '', value: active.name }] + : []; + const icons = active?.tvg?.logo ? [{ src: active.tvg.logo }] : []; + + this.channel = { + id: active?.tvg?.id || '', + displayName: displayNames, + url: active?.url ? [active.url] : [], + icon: icons, + }; + return ( + active?.tvg?.rec || active?.timeshift || active?.catchup?.days + ); + }), + map((value) => moment().subtract(value, 'days').toISOString()) + ); private readonly selectedDate$ = new BehaviorSubject( moment().format(DATE_FORMAT) ); + /** Filtered EPG programs based on selected date */ + filteredItems$ = combineLatest([this.items$, this.selectedDate$]).pipe( + map(([items, selectedDate]) => + items + .filter( + (item) => + moment(item.start).format('YYYY-MM-DD') === selectedDate + ) + .sort((a, b) => moment(a.start).diff(moment(b.start))) + ) + ); + /** * Helper function to get channel display name */ @@ -108,47 +137,10 @@ export class EpgListComponent implements OnInit { return channel.icon[0]?.src || ''; } - /** Filtered EPG programs based on selected date */ - filteredItems$ = combineLatest([this.items$, this.selectedDate$]).pipe( - map(([items, selectedDate]) => - items - .filter( - (item) => - moment(item.start).format('YYYY-MM-DD') === selectedDate - ) - .sort((a, b) => moment(a.start).diff(moment(b.start))) - ) - ); - /** * Subscribe for values from the store on component init */ ngOnInit(): void { - this.timeshiftUntil$ = this.store.select(selectActive).pipe( - map((active) => { - // Create EpgChannel with proper structure - const displayNames = active?.name - ? [{ lang: '', value: active.name }] - : []; - const icons = active?.tvg?.logo - ? [{ src: active.tvg.logo }] - : []; - - this.channel = { - id: active?.tvg?.id || '', - displayName: displayNames, - url: active?.url ? [active.url] : [], - icon: icons, - }; - return ( - active?.tvg?.rec || - active?.timeshift || - active?.catchup?.days - ); - }), - map((value) => moment().subtract(value, 'days').toISOString()) - ); - this.items$.subscribe((programs) => this.handleEpgData(programs)); this.dateToday = moment().format(DATE_FORMAT); this.selectedDate$.next(this.dateToday); diff --git a/libs/ui/components/src/lib/html-video-player/html-video-player.component.spec.ts b/libs/ui/components/src/lib/html-video-player/html-video-player.component.spec.ts index 6fc84e427..e7e0fc103 100644 --- a/libs/ui/components/src/lib/html-video-player/html-video-player.component.spec.ts +++ b/libs/ui/components/src/lib/html-video-player/html-video-player.component.spec.ts @@ -1,12 +1,13 @@ import { SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; -import { DataService } from '../../../../../services/src/lib/data.service'; +import { DataService } from 'services'; import { HtmlVideoPlayerComponent } from './html-video-player.component'; describe('HtmlVideoPlayerComponent', () => { let component: HtmlVideoPlayerComponent; let fixture: ComponentFixture; + // eslint-disable-next-line @typescript-eslint/no-unused-vars let dataService: DataService; const TEST_CHANNEL = { @@ -45,7 +46,9 @@ describe('HtmlVideoPlayerComponent', () => { it('should call play channel function after input changes', () => { jest.spyOn(component, 'playChannel'); - jest.spyOn(global.console, 'error').mockImplementation(() => {}); + jest.spyOn(global.console, 'error').mockImplementation(() => { + /* empty */ + }); component.ngOnChanges({ channel: new SimpleChange(null, TEST_CHANNEL, true), }); diff --git a/libs/ui/components/src/lib/info-overlay/info-overlay.component.spec.ts b/libs/ui/components/src/lib/info-overlay/info-overlay.component.spec.ts deleted file mode 100644 index fb58722a7..000000000 --- a/libs/ui/components/src/lib/info-overlay/info-overlay.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { MomentDatePipe } from '@iptvnator/pipes'; -import { MockPipe } from 'ng-mocks'; -/* tslint:disable:no-unused-variable */ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { InfoOverlayComponent } from './info-overlay.component'; - -describe('InfoOverlayComponent', () => { - let component: InfoOverlayComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [InfoOverlayComponent, MockPipe(MomentDatePipe)], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(InfoOverlayComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/libs/ui/components/src/lib/multi-epg/multi-epg-container.component.ts b/libs/ui/components/src/lib/multi-epg/multi-epg-container.component.ts index 4c6f1158a..e4808ecf2 100644 --- a/libs/ui/components/src/lib/multi-epg/multi-epg-container.component.ts +++ b/libs/ui/components/src/lib/multi-epg/multi-epg-container.component.ts @@ -6,11 +6,11 @@ import { ChangeDetectorRef, Component, ElementRef, - Inject, + inject, Input, OnDestroy, OnInit, - ViewChild, + viewChild, } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; @@ -56,18 +56,16 @@ interface EnrichedChannel extends EpgChannel { export class MultiEpgContainerComponent implements OnInit, AfterViewInit, OnDestroy { - @ViewChild('epgContainer') epgContainer!: ElementRef; + readonly epgContainer = viewChild.required('epgContainer'); @Input() set playlistChannels(value: Observable) { if (value) { - value.subscribe((channels) => { - this._playlistChannels = channels; + value.subscribe(() => { this.initializeVisibleChannels(); this.requestPrograms(); }); } } - private _playlistChannels: Channel[] = []; readonly timeHeader = Array.from({ length: 24 }, (_, i) => i); readonly hourWidth$ = new BehaviorSubject(150); @@ -82,16 +80,14 @@ export class MultiEpgContainerComponent originalEpgData: any[] = []; private dateCache = new Map(); - private interval: any; + private interval!: number; isLastPage = false; totalChannels = 0; - constructor( - private dialog: MatDialog, - private cdr: ChangeDetectorRef, - @Inject(COMPONENT_OVERLAY_REF) private overlayRef: OverlayRef - ) {} + private readonly dialog = inject(MatDialog); + private cdr = inject(ChangeDetectorRef); + private overlayRef = inject(COMPONENT_OVERLAY_REF); ngOnInit() { this.calculateCurrentTimeBar(); @@ -123,9 +119,9 @@ export class MultiEpgContainerComponent } private initializeVisibleChannels(): void { - if (this.epgContainer) { - const containerHeight = - this.epgContainer.nativeElement.offsetHeight; + const epgContainer = this.epgContainer(); + if (epgContainer) { + const containerHeight = epgContainer.nativeElement.offsetHeight; const calculatedVisibleChannels = Math.floor( (containerHeight - this.barHeight) / this.barHeight ); diff --git a/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.html b/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.html index bdf2eff94..a8d5690f4 100644 --- a/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.html +++ b/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.html @@ -25,7 +25,7 @@ } @else { - + star @@ -42,7 +42,7 @@ @for (item of playlists; track item._id; let last = $last) { (); + + readonly allPlaylistsLoaded = this.store.selectSignal( + selectPlaylistsLoadingFlag + ); + readonly searchQueryInput = viewChild>('searchQuery'); @@ -112,58 +114,6 @@ export class RecentPlaylistsComponent implements OnInit { }) ); - allPlaylistsLoaded = this.store.selectSignal(selectPlaylistsLoadingFlag); - - @Input() sidebarMode = false; - @Output() playlistClicked = new EventEmitter(); - - /** IPC Renderer commands list with callbacks */ - commandsList = [ - { - id: PLAYLIST_UPDATE_RESPONSE, - execute: (response: { - payload: { message: string; playlist: Playlist }; - }) => { - this.snackBar.open(response.payload.message, undefined, { - duration: 2000, - }); - this.store.dispatch( - PlaylistActions.updatePlaylist({ - playlistId: response.payload.playlist._id, - playlist: response.payload.playlist, - }) - ); - }, - }, - { - id: AUTO_UPDATE_PLAYLISTS_RESPONSE, - execute: (response: { - payload: { message: string; playlists: Playlist[] }; - }) => { - this.store.dispatch( - PlaylistActions.updateManyPlaylists({ - playlists: response.payload.playlists, - }) - ); - }, - }, - ]; - - ngOnInit(): void { - this.setRendererListeners(); - } - - setRendererListeners(): void { - this.commandsList.forEach((command) => { - const cb = (response: any) => { - if (response.data.type === command.id) { - command.execute(response.data); - } - }; - this.dataService.listenOn(command.id, cb); - }); - } - /** * Opens the details dialog with the information about the provided playlist * @param data selected playlist @@ -190,7 +140,7 @@ export class RecentPlaylistsComponent implements OnInit { ); } - getGlobalFavorites() { + navigateToGlobalFavorites() { this.router.navigate(['playlists', GLOBAL_FAVORITES_PLAYLIST_ID]); this.playlistClicked.emit(GLOBAL_FAVORITES_PLAYLIST_ID); }