refactor: code style improvements

This commit is contained in:
4gray committed 2025-11-08 10:24:41 +01:00
1 parent 1b9dc0b37e
commit 837849ee57
9 files changed
+95 -158

No files matched your search

+22
View File
@@ -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"
}
+1
View File
@@ -7,6 +7,7 @@
"inlineSources": true,
"types": []
},
"files": ["../../global.d.ts"],
"exclude": [
"src/**/*.spec.ts",
"src/test-setup.ts",
@@ -17,7 +17,7 @@
>
}
@if (item.desc?.length > 0) {
@if (item?.desc?.length > 0) {
<span
(click)="$event.stopPropagation(); showDescription(item)"
matListItemMeta
@@ -1,11 +1,11 @@
import { AsyncPipe } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { MatIconButton } from '@angular/material/button';
import { MatDivider } from '@angular/material/divider';
import { MatIcon } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatTooltip } from '@angular/material/tooltip';
import { MomentDatePipe } from '@iptvnator/pipes';
import { Store } from '@ngrx/store';
import { TranslatePipe } from '@ngx-translate/core';
@@ -17,9 +17,8 @@ import {
setEpgAvailableFlag,
} from 'm3u-state';
import moment from 'moment';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { DataService, EpgService } from 'services';
import { BehaviorSubject, combineLatest, map } from 'rxjs';
import { EpgService } from 'services';
import { EpgChannel, EpgProgram } from 'shared-interfaces';
import { EpgListItemComponent } from './epg-list-item/epg-list-item.component';
@@ -35,11 +34,11 @@ const DATE_FORMAT = 'YYYY-MM-DD';
AsyncPipe,
EpgListItemComponent,
FormsModule,
MatButtonModule,
MatDividerModule,
MatIconModule,
MatDivider,
MatIcon,
MatIconButton,
MatListModule,
MatTooltipModule,
MatTooltip,
MomentDatePipe,
TranslatePipe,
],
@@ -49,7 +48,6 @@ const DATE_FORMAT = 'YYYY-MM-DD';
})
export class EpgListComponent implements OnInit {
private readonly store = inject(Store);
private dataService = inject(DataService);
private readonly epgService = inject(EpgService);
/** Channel info in EPG format */
@@ -76,12 +74,43 @@ export class EpgListComponent implements OnInit {
timeNow!: string;
/** Timeshift availability date, based on tvg-rec value from the channel */
timeshiftUntil$!: Observable<string>;
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<string>(
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);
@@ -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<HtmlVideoPlayerComponent>;
// 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),
});
@@ -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<InfoOverlayComponent>;
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();
});
});
@@ -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<ElementRef>('epgContainer');
@Input() set playlistChannels(value: Observable<Channel[]>) {
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<number>(150);
@@ -82,16 +80,14 @@ export class MultiEpgContainerComponent
originalEpgData: any[] = [];
private dateCache = new Map<string, Date>();
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<OverlayRef>(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
);
@@ -25,7 +25,7 @@
</div>
</mat-list-item>
} @else {
<mat-list-item (click)="getGlobalFavorites()">
<mat-list-item (click)="navigateToGlobalFavorites()">
<mat-icon class="favorites-icon" matListItemIcon
>star</mat-icon
>
@@ -42,7 +42,7 @@
@for (item of playlists; track item._id; let last = $last) {
<app-playlist-item
[item]="item"
[showActions]="!sidebarMode"
[showActions]="!sidebarMode()"
(editPlaylistClicked)="openInfoDialog($event)"
(playlistClicked)="getPlaylist(item)"
(refreshClicked)="refreshPlaylist($event)"
@@ -3,11 +3,9 @@ import { AsyncPipe } from '@angular/common';
import {
Component,
ElementRef,
EventEmitter,
inject,
Input,
OnInit,
Output,
input,
output,
viewChild,
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
@@ -28,11 +26,8 @@ import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader';
import { BehaviorSubject, combineLatest, map } from 'rxjs';
import { DatabaseService, DataService, SortService } from 'services';
import {
AUTO_UPDATE_PLAYLISTS_RESPONSE,
GLOBAL_FAVORITES_PLAYLIST_ID,
Playlist,
PLAYLIST_UPDATE,
PLAYLIST_UPDATE_RESPONSE,
PlaylistMeta,
} from 'shared-interfaces';
import { DialogService } from '../confirm-dialog/dialog.service';
@@ -53,7 +48,7 @@ import { PlaylistItemComponent } from './playlist-item/playlist-item.component';
TranslatePipe,
],
})
export class RecentPlaylistsComponent implements OnInit {
export class RecentPlaylistsComponent {
private readonly databaseService = inject(DatabaseService);
private readonly dialog = inject(MatDialog);
private readonly dialogService = inject(DialogService);
@@ -64,6 +59,13 @@ export class RecentPlaylistsComponent implements OnInit {
private readonly store = inject(Store);
private readonly translate = inject(TranslateService);
readonly sidebarMode = input(false);
readonly playlistClicked = output<string>();
readonly allPlaylistsLoaded = this.store.selectSignal(
selectPlaylistsLoadingFlag
);
readonly searchQueryInput =
viewChild<ElementRef<HTMLInputElement>>('searchQuery');
@@ -112,58 +114,6 @@ export class RecentPlaylistsComponent implements OnInit {
})
);
allPlaylistsLoaded = this.store.selectSignal(selectPlaylistsLoadingFlag);
@Input() sidebarMode = false;
@Output() playlistClicked = new EventEmitter<string>();
/** 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);
}