From ccbb08cfb44db7cf3362102b5cf7918a21629c53 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 26 Oct 2025 11:45:40 +0100 Subject: [PATCH] fix(ui): correct imports, guard checks, logging and settings handling --- .../src/app/api/main.preload.ts | 4 + .../src/app/events/playlist.events.ts | 32 ++- .../src/app/events/setttings.events.ts | 7 +- apps/web/src/app/app.component.ts | 12 +- .../src/app/settings/settings.component.ts | 12 +- .../mpv-player-bar.component.html | 17 +- .../mpv-player-bar.component.ts | 23 +-- .../app/shared/services/mpv-player.service.ts | 3 +- global.d.ts | 8 + libs/m3u-state/src/lib/effects.ts | 4 +- .../src/lib/epg-list/epg-list.component.ts | 9 +- .../html-video-player.component.ts | 6 +- .../info-overlay/info-overlay.component.ts | 2 +- .../multi-epg-container.component.ts | 5 - .../playlist-info.component.html | 24 +-- .../playlist-info/playlist-info.component.ts | 32 ++- .../playlist-item.component.html | 192 ++++++++++-------- .../playlist-item.component.spec.ts | 10 +- .../playlist-item/playlist-item.component.ts | 42 ++-- .../recent-playlists.component.ts | 10 +- libs/ui/pipes/src/lib/moment-date.pipe.ts | 2 +- 21 files changed, 240 insertions(+), 216 deletions(-) diff --git a/apps/electron-backend/src/app/api/main.preload.ts b/apps/electron-backend/src/app/api/main.preload.ts index 547c84cff..e17de49d7 100644 --- a/apps/electron-backend/src/app/api/main.preload.ts +++ b/apps/electron-backend/src/app/api/main.preload.ts @@ -8,6 +8,10 @@ contextBridge.exposeInMainWorld('electron', { updatePlaylistFromFilePath: (filePath: string, title: string) => ipcRenderer.invoke('update-playlist-from-file-path', filePath, title), openPlaylistFromFile: () => ipcRenderer.invoke('open-playlist-from-file'), + saveFileDialog: (defaultPath: string, filters?: { name: string; extensions: string[] }[]) => + ipcRenderer.invoke('save-file-dialog', defaultPath, filters), + writeFile: (filePath: string, content: string) => + ipcRenderer.invoke('write-file', filePath, content), setUserAgent: (userAgent: string, referer?: string) => ipcRenderer.invoke('set-user-agent', userAgent, referer), openInMpv: ( diff --git a/apps/electron-backend/src/app/events/playlist.events.ts b/apps/electron-backend/src/app/events/playlist.events.ts index 91e94d920..7a271a714 100644 --- a/apps/electron-backend/src/app/events/playlist.events.ts +++ b/apps/electron-backend/src/app/events/playlist.events.ts @@ -7,7 +7,7 @@ import axios from 'axios'; import { dialog, ipcMain } from 'electron'; import { parse } from 'iptv-playlist-parser'; import { createPlaylistObject, getFilenameFromUrl } from 'm3u-utils'; -import { readFile } from 'node:fs/promises'; +import { readFile, writeFile } from 'node:fs/promises'; import { AUTO_UPDATE_PLAYLISTS } from 'shared-interfaces'; export default class PlaylistEvents { @@ -102,3 +102,33 @@ ipcMain.handle(AUTO_UPDATE_PLAYLISTS, async (event, playlistUrls) => { console.log(`Auto-updating playlist from ${url}`); } }); + +ipcMain.handle('save-file-dialog', async (event, defaultPath, filters) => { + try { + const { canceled, filePath } = await dialog.showSaveDialog({ + defaultPath, + filters: filters || [ + { name: 'All Files', extensions: ['*'] }, + ], + }); + + if (canceled || !filePath) { + return null; + } + + return filePath; + } catch (error) { + console.error('Error showing save dialog:', error); + throw error; + } +}); + +ipcMain.handle('write-file', async (event, filePath, content) => { + try { + await writeFile(filePath, content, 'utf-8'); + return { success: true }; + } catch (error) { + console.error('Error writing file:', error); + throw error; + } +}); diff --git a/apps/electron-backend/src/app/events/setttings.events.ts b/apps/electron-backend/src/app/events/setttings.events.ts index 3c512c1fd..692b266d0 100644 --- a/apps/electron-backend/src/app/events/setttings.events.ts +++ b/apps/electron-backend/src/app/events/setttings.events.ts @@ -1,6 +1,5 @@ import { ipcMain } from 'electron'; import { - MPV_PLAYER_PATH, MPV_REUSE_INSTANCE, store, } from '../services/store.service'; @@ -13,5 +12,9 @@ export default class SettingsEvents { ipcMain.handle('SETTINGS_UPDATE', (_event, arg) => { console.log('Received SETTINGS_UPDATE with data:', arg); - store.set(MPV_REUSE_INSTANCE, arg.mpvReuseInstance); + + // Only set values that are defined + if (arg.mpvReuseInstance !== undefined) { + store.set(MPV_REUSE_INSTANCE, arg.mpvReuseInstance); + } }); diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 3a1201377..7925c5f17 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -13,7 +13,6 @@ import { Language, OPEN_FILE, Settings, - SETTINGS_UPDATE, STORE_KEY, Theme, VIEW_ADD_PLAYLIST, @@ -120,13 +119,12 @@ export class AppComponent implements OnInit, OnDestroy { .getValueFromLocalStorage(STORE_KEY.Settings) .subscribe((settings: Settings) => { if (settings && Object.keys(settings).length > 0) { - // Send settings to Electron main process - if (window.electron) { - window.electron.updateSettings(settings); - } - + // No need to send settings to Electron on init + // Settings are stored in IndexedDB and loaded by the settings store + // Only specific Electron settings (MPV/VLC paths) are sent when changed in settings component + this.translate.use(settings.language ?? this.DEFAULT_LANG); - + // Fetch EPG if URLs are configured if ( window.electron && diff --git a/apps/web/src/app/settings/settings.component.ts b/apps/web/src/app/settings/settings.component.ts index 136bc4ce4..fa9fd33cf 100644 --- a/apps/web/src/app/settings/settings.component.ts +++ b/apps/web/src/app/settings/settings.component.ts @@ -252,16 +252,20 @@ export class SettingsComponent implements OnInit { onSubmit(): void { this.settingsStore.updateSettings(this.settingsForm.value).then(() => { this.applyChangedSettings(); - + if (window.electron) { window.electron.updateSettings(this.settingsForm.value); - + // Set player paths if using external players if (this.settingsForm.value.mpvPlayerPath) { - window.electron.setMpvPlayerPath(this.settingsForm.value.mpvPlayerPath); + window.electron.setMpvPlayerPath( + this.settingsForm.value.mpvPlayerPath + ); } if (this.settingsForm.value.vlcPlayerPath) { - window.electron.setVlcPlayerPath(this.settingsForm.value.vlcPlayerPath); + window.electron.setVlcPlayerPath( + this.settingsForm.value.vlcPlayerPath + ); } } }); diff --git a/apps/web/src/app/shared/components/mpv-player-bar/mpv-player-bar.component.html b/apps/web/src/app/shared/components/mpv-player-bar/mpv-player-bar.component.html index 60bb5b634..e7509d2fe 100644 --- a/apps/web/src/app/shared/components/mpv-player-bar/mpv-player-bar.component.html +++ b/apps/web/src/app/shared/components/mpv-player-bar/mpv-player-bar.component.html @@ -1,4 +1,4 @@ -@if ((activeProcesses$ | async)?.length) { + - - + @if (showActions()) { + + @if (item.url || item.filePath) { + + } + @if (item.serverUrl) { + + } - - + + + + + } diff --git a/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.spec.ts b/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.spec.ts index 95292ec66..053e3d652 100644 --- a/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.spec.ts +++ b/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.spec.ts @@ -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(); }); diff --git a/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.ts b/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.ts index 6105df7df..923b05c43 100644 --- a/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.ts +++ b/libs/ui/components/src/lib/recent-playlists/playlist-item/playlist-item.component.ts @@ -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(); readonly playlistClicked = output(); @@ -34,21 +33,24 @@ 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(); - } + await this.checkPortalStatus(); } 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'; diff --git a/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.ts b/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.ts index 77bbc715f..5439ec9c2 100644 --- a/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.ts +++ b/libs/ui/components/src/lib/recent-playlists/recent-playlists.component.ts @@ -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), diff --git a/libs/ui/pipes/src/lib/moment-date.pipe.ts b/libs/ui/pipes/src/lib/moment-date.pipe.ts index 033736f67..f40a084a7 100644 --- a/libs/ui/pipes/src/lib/moment-date.pipe.ts +++ b/libs/ui/pipes/src/lib/moment-date.pipe.ts @@ -13,7 +13,7 @@ export class MomentDatePipe implements PipeTransform { value: string, formatToParse: string, formatToReturn = 'MMMM Do, dddd' - ): any { + ): string { return moment(value, formatToParse).format(formatToReturn); } }