fix(ui): correct imports, guard checks, logging and settings handling

This commit is contained in:
4gray committed 2025-10-26 08:58:53 +01:00
1 parent b56519da4b
commit 9be8dce961
21 files changed
+238 -213

No files matched your search

@@ -20,10 +20,7 @@ import moment from 'moment';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { DataService, EpgService } from 'services';
import {
EpgChannel,
EpgProgram,
} from 'shared-interfaces';
import { EpgChannel, EpgProgram } from 'shared-interfaces';
import { EpgListItemComponent } from './epg-list-item/epg-list-item.component';
export interface EpgData {
@@ -164,12 +161,12 @@ export class EpgListComponent implements OnInit {
handleEpgData(programs: EpgProgram[]): void {
this.timeNow = new Date().toISOString();
this.dateToday = moment().format(DATE_FORMAT);
// Dispatch EPG availability flag
this.store.dispatch(
setEpgAvailableFlag({ value: programs.length > 0 })
);
if (programs.length > 0) {
this.setPlayingNow();
} else {
@@ -88,11 +88,7 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
Hls &&
Hls.isSupported()
) {
console.log(
'... switching channel to ',
channel.name,
url
);
console.log('... switching channel to ', channel.name, url);
this.hls = new Hls();
this.hls.attachMedia(this.videoPlayer.nativeElement);
this.hls.loadSource(url);
@@ -33,7 +33,7 @@ export class InfoOverlayComponent implements OnChanges {
stop: any | undefined;
/** Timeout for the overlay visibility */
currentTimeout: any;
private currentTimeout: any;
/**
* Calculates the necessary information for the visualization in the overview popup
@@ -159,17 +159,12 @@ export class MultiEpgContainerComponent
}
try {
console.log('Requesting EPG data:');
console.log('- Skip:', this.channelsLowerRange);
console.log('- Limit:', this.visibleChannels);
const response = await window.electron.getEpgChannelsByRange(
this.channelsLowerRange,
this.visibleChannels
);
if (response && Array.isArray(response)) {
console.log('Received channels:', response.length);
this.originalEpgData = response;
this.channels$.next(this.enrichProgramData());
@@ -82,7 +82,7 @@
<input matInput formControlName="count" />
</mat-form-field>
}
@if (isTauri) {
@if (isDesktop) {
<mat-form-field class="full-width mb-10">
<mat-label>{{
'HOME.PLAYLISTS.INFO_DIALOG.USER_AGENT' | translate
@@ -109,18 +109,16 @@
}
</mat-form-field>
}
@if (isTauri && (playlist.url || playlist.filePath)) {
<ng-container>
<mat-checkbox class="full-width" formControlName="autoRefresh">
{{ 'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE' | translate }}
</mat-checkbox>
<p>
{{
'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE_DESCRIPTION'
| translate
}}
</p>
</ng-container>
@if (isDesktop && (playlist.url || playlist.filePath)) {
<mat-checkbox class="full-width" formControlName="autoRefresh">
{{ 'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE' | translate }}
</mat-checkbox>
<p>
{{
'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE_DESCRIPTION'
| translate
}}
</p>
}
</mat-dialog-content>
<mat-dialog-actions align="end">
@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/unbound-method */
import { CommonModule, DatePipe } from '@angular/common';
import { DatePipe } from '@angular/common';
import { Component, inject } from '@angular/core';
import {
FormControl,
@@ -8,16 +7,14 @@ import {
UntypedFormGroup,
Validators,
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatButton } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatIcon } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Store } from '@ngrx/store';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { save } from '@tauri-apps/plugin-dialog';
import { writeTextFile } from '@tauri-apps/plugin-fs';
import * as PlaylistActions from 'm3u-state';
import { firstValueFrom } from 'rxjs';
import { DatabaseService, PlaylistsService } from 'services';
@@ -35,11 +32,10 @@ import { Playlist, PlaylistMeta } from 'shared-interfaces';
],
providers: [DatePipe],
imports: [
CommonModule,
MatButtonModule,
MatButton,
MatCheckboxModule,
MatDialogModule,
MatIconModule,
MatIcon,
MatInputModule,
ReactiveFormsModule,
TranslatePipe,
@@ -55,7 +51,7 @@ export class PlaylistInfoComponent {
private translate = inject(TranslateService);
public playlistData = inject<Playlist & { id: string }>(MAT_DIALOG_DATA);
isTauri = !!window.electron;
readonly isDesktop = !!window.electron;
/** Playlist object */
playlist: Playlist & { id: string };
@@ -168,20 +164,20 @@ export class PlaylistInfoComponent {
this.playlistsService.getRawPlaylistById(this.playlist._id)
);
if (this.isTauri) {
if (this.isDesktop) {
try {
const savePath = await save({
filters: [
const savePath = await window.electron.saveFileDialog(
`${this.playlist.title || 'exported'}.m3u8`,
[
{
name: 'Playlist',
extensions: ['m3u8'],
extensions: ['m3u8', 'm3u'],
},
],
defaultPath: `${this.playlist.title || 'exported'}.m3u8`,
});
]
);
if (savePath) {
await writeTextFile(savePath, playlistAsString);
await window.electron.writeFile(savePath, playlistAsString);
this.snackBar.open(
this.translate.instant(
'HOME.PLAYLISTS.INFO_DIALOG.PLAYLIST_EXPORT_SUCCESS'
@@ -1,56 +1,59 @@
<mat-list-item (click)="playlistClicked.emit(item._id)">
<mat-icon
matListItemIcon
class="upload-type-icon"
*ngIf="item?.url"
[matTooltip]="
('HOME.PLAYLISTS.ADDED_VIA_URL' | translate) + ' ' + item?.url
"
>cloud</mat-icon
>
<mat-icon
matListItemIcon
class="upload-type-icon"
*ngIf="!item?.url && !item.serverUrl && !item.macAddress"
[matTooltip]="'HOME.PLAYLISTS.ADDED_VIA_FILE' | translate"
>folder</mat-icon
>
<div class="icon-container" *ngIf="item?.serverUrl" matListItemIcon>
@if (item.url) {
<mat-icon
matListItemIcon
class="upload-type-icon"
[matTooltip]="'HOME.PLAYLISTS.XTREAM_PLAYLIST' | translate"
>public</mat-icon
[matTooltip]="
('HOME.PLAYLISTS.ADDED_VIA_URL' | translate) + ' ' + item.url
"
>cloud</mat-icon
>
<span
class="status-dot"
[class]="getStatusClass()"
[matTooltip]="'Portal status: ' + portalStatus"
></span>
</div>
<mat-icon
matListItemIcon
class="upload-type-icon"
*ngIf="item?.macAddress"
[matTooltip]="'HOME.PLAYLISTS.STALKER_PORTAL' | translate"
>dashboard</mat-icon
>
}
@if (!item.url && !item.serverUrl && !item.macAddress) {
<mat-icon
matListItemIcon
class="upload-type-icon"
[matTooltip]="'HOME.PLAYLISTS.ADDED_VIA_FILE' | translate"
>folder</mat-icon
>
}
@if (item.serverUrl) {
<div class="icon-container" matListItemIcon>
<mat-icon
class="upload-type-icon"
[matTooltip]="'HOME.PLAYLISTS.XTREAM_PLAYLIST' | translate"
>public</mat-icon
>
<span
class="status-dot"
[class]="getStatusClass()"
[matTooltip]="'Portal status: ' + portalStatus"
></span>
</div>
}
@if (item.macAddress) {
<mat-icon
matListItemIcon
class="upload-type-icon"
[matTooltip]="'HOME.PLAYLISTS.STALKER_PORTAL' | translate"
>dashboard</mat-icon
>
}
<div matListItemTitle>
{{ item.title || item.filename }}
</div>
<div matListItemLine class="meta">
<ng-container
*ngIf="!item.serverUrl && !item.portalUrl; else xtreamPlaylist"
>
@if (!item.serverUrl && !item.portalUrl) {
{{ 'HOME.PLAYLISTS.CHANNELS' | translate }}: {{ item.count }}
<ng-container *ngIf="item.importDate?.length > 3">
@if (item.importDate.length > 3) {
| {{ 'HOME.PLAYLISTS.ADDED' | translate }}:
{{ item.importDate | date }}
</ng-container>
<ng-container *ngIf="item.updateDate">
}
@if (item.updateDate) {
| {{ 'HOME.PLAYLISTS.UPDATED' | translate }}:
{{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }}
</ng-container>
<ng-container *ngIf="item.updateState === 2">
}
@if (item.updateState === 2) {
<mat-icon
[matTooltip]="
'HOME.PLAYLISTS.INFO_DIALOG.UPDATE_FAILED' | translate
@@ -58,64 +61,74 @@
[inline]="true"
>warning</mat-icon
>
</ng-container>
</ng-container>
<ng-template #xtreamPlaylist>
@if (item?.macAddress) {
}
} @else {
@if (item.macAddress) {
Stalker Portal
} @else {
Xtream Code Playlist
}
<ng-container *ngIf="item.importDate?.length > 3">
@if (item.importDate.length > 3) {
| {{ 'HOME.PLAYLISTS.ADDED' | translate }}:
{{ item.importDate | date }}
</ng-container>
<ng-container *ngIf="item.updateDate">
}
@if (item.updateDate) {
| {{ 'HOME.PLAYLISTS.UPDATED' | translate }}:
{{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }}
</ng-container>
</ng-template>
}
}
</div>
<span matListItemMeta class="action-buttons" *ngIf="showActions">
<button
*ngIf="item.url || item.filePath"
mat-icon-button
class="refresh-btn"
color="accent"
(click)="$event.stopPropagation(); refreshClicked.emit(item)"
[matTooltip]="'HOME.PLAYLISTS.REFRESH' | translate"
>
<mat-icon>sync</mat-icon>
</button>
<button
*ngIf="item.serverUrl"
mat-icon-button
class="refresh-btn"
color="accent"
(click)="$event.stopPropagation(); refreshClicked.emit(item)"
[matTooltip]="'HOME.PLAYLISTS.REFRESH_XTREAM' | translate"
>
<mat-icon>sync</mat-icon>
</button>
<button
mat-icon-button
color="accent"
class="edit-btn"
(click)="$event.stopPropagation(); editPlaylistClicked.emit(item)"
[matTooltip]="'HOME.PLAYLISTS.SHOW_DETAILS' | translate"
>
<mat-icon>edit</mat-icon>
</button>
@if (showActions()) {
<span matListItemMeta class="action-buttons">
@if (item.url || item.filePath) {
<button
mat-icon-button
class="refresh-btn"
color="accent"
(click)="
$event.stopPropagation(); refreshClicked.emit(item)
"
[matTooltip]="'HOME.PLAYLISTS.REFRESH' | translate"
>
<mat-icon>sync</mat-icon>
</button>
}
@if (item.serverUrl) {
<button
mat-icon-button
class="refresh-btn"
color="accent"
(click)="
$event.stopPropagation(); refreshClicked.emit(item)
"
[matTooltip]="'HOME.PLAYLISTS.REFRESH_XTREAM' | translate"
>
<mat-icon>sync</mat-icon>
</button>
}
<button
mat-icon-button
color="accent"
class="delete-btn"
(click)="$event.stopPropagation(); removeClicked.emit(item._id)"
[matTooltip]="'HOME.PLAYLISTS.REMOVE' | translate"
>
<mat-icon>delete</mat-icon>
</button>
</span>
<button
mat-icon-button
color="accent"
class="edit-btn"
(click)="
$event.stopPropagation(); editPlaylistClicked.emit(item)
"
[matTooltip]="'HOME.PLAYLISTS.SHOW_DETAILS' | translate"
>
<mat-icon>edit</mat-icon>
</button>
<button
mat-icon-button
color="accent"
class="delete-btn"
(click)="$event.stopPropagation(); removeClicked.emit(item._id)"
[matTooltip]="'HOME.PLAYLISTS.REMOVE' | translate"
>
<mat-icon>delete</mat-icon>
</button>
</span>
}
</mat-list-item>
@@ -4,7 +4,7 @@ import { MatListModule } from '@angular/material/list';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { MockModule } from 'ng-mocks';
import { DataService } from '../../../../../../services/src/lib/data.service';
import { DataService } from 'services';
import { PlaylistItemComponent } from './playlist-item.component';
describe('PlaylistItemComponent', () => {
@@ -27,7 +27,13 @@ describe('PlaylistItemComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(PlaylistItemComponent);
component = fixture.componentInstance;
component.item = { title: 'Playlist', id: '1' } as any;
component.item = {
title: 'Playlist',
_id: '1',
count: 10,
importDate: Date.now().toString(),
autoRefresh: false,
};
fixture.detectChanges();
});
@@ -1,9 +1,9 @@
import { DatePipe, NgIf } from '@angular/common';
import { Component, Input, OnInit, inject, output } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { DatePipe } from '@angular/common';
import { Component, Input, OnInit, inject, input, output } from '@angular/core';
import { MatIconButton } from '@angular/material/button';
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 { TranslatePipe } from '@ngx-translate/core';
import { PortalStatus, PortalStatusService } from 'services';
import { PlaylistMeta } from 'shared-interfaces';
@@ -14,17 +14,16 @@ import { PlaylistMeta } from 'shared-interfaces';
styleUrls: ['./playlist-item.component.scss'],
imports: [
DatePipe,
MatButtonModule,
MatIconModule,
MatIconButton,
MatIcon,
MatListModule,
MatTooltipModule,
NgIf,
MatTooltip,
TranslatePipe,
],
})
export class PlaylistItemComponent implements OnInit {
@Input() item!: PlaylistMeta;
@Input() showActions = true;
readonly showActions = input(true);
readonly editPlaylistClicked = output<PlaylistMeta>();
readonly playlistClicked = output<string>();
@@ -34,7 +33,6 @@ export class PlaylistItemComponent implements OnInit {
portalStatus: PortalStatus = 'unavailable';
private readonly portalStatusService = inject(PortalStatusService);
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async ngOnInit() {
if (this.item?.serverUrl) {
await this.checkPortalStatus();
@@ -43,12 +41,18 @@ export class PlaylistItemComponent implements OnInit {
private async checkPortalStatus() {
try {
this.portalStatus =
await this.portalStatusService.checkPortalStatus(
this.item.serverUrl,
this.item.username,
this.item.password
);
if (
this.item.serverUrl &&
this.item.username &&
this.item.password
) {
this.portalStatus =
await this.portalStatusService.checkPortalStatus(
this.item.serverUrl,
this.item.username,
this.item.password
);
}
} catch (error) {
console.error('Error checking portal status:', error);
this.portalStatus = 'unavailable';
@@ -10,10 +10,8 @@ import {
Output,
viewChild,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { MatIcon } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatSnackBar } from '@angular/material/snack-bar';
@@ -47,9 +45,7 @@ import { PlaylistItemComponent } from './playlist-item/playlist-item.component';
styleUrls: ['./recent-playlists.component.scss'],
imports: [
AsyncPipe,
MatButtonModule,
MatDividerModule,
MatIconModule,
MatIcon,
MatInputModule,
MatListModule,
NgxSkeletonLoaderComponent,
@@ -75,7 +71,7 @@ export class RecentPlaylistsComponent implements OnInit {
readonly ghostElements = new Array(10);
playlists$ = combineLatest([
readonly playlists$ = combineLatest([
this.store.select(selectAllPlaylistsMeta),
this.searchQuery,
this.store.select(selectActiveTypeFilters),