mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
refactor: code style improvements
This commit is contained in:
1 parent
1b9dc0b37e
commit
837849ee57
9 files changed
+95
-158
No files matched your search
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user