From 920afa68502e4d6237846f439170b8eac08e86ea Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 10 Oct 2025 11:49:35 +0200 Subject: [PATCH] refactor(nx): created shared and m3u-utils library --- .../src/app/api/main.preload.ts | 6 +- .../src/app/events/playlist.events.ts | 47 +++++++++ apps/electron-backend/src/main.ts | 39 ++++---- apps/web/src/app/app.module.ts | 2 +- apps/web/src/app/home/home.component.ts | 28 +++--- .../playlist-info/playlist-info.component.ts | 2 +- .../recent-playlists.component.ts | 4 +- .../stalker-portal-import.component.ts | 2 +- .../home/url-upload/url-upload.component.html | 8 +- .../home/url-upload/url-upload.component.ts | 3 +- .../xtream-code-import.component.ts | 10 +- .../art-player/art-player.component.ts | 5 +- .../channel-list-container.component.ts | 17 ++-- .../html-video-player.component.ts | 10 +- .../info-overlay/info-overlay.component.ts | 4 +- .../multi-epg-container.component.ts | 2 +- .../video-player/sidebar/sidebar.component.ts | 2 +- .../video-player/toolbar/toolbar.component.ts | 12 +-- .../video-player/video-player.component.ts | 3 +- .../web-player-view.component.ts | 2 +- .../web/src/app/services/playlists.service.ts | 18 ++-- apps/web/src/app/services/pwa.service.ts | 4 +- apps/web/src/app/services/tauri.service.ts | 34 ++++--- .../src/app/settings/settings.component.ts | 2 +- apps/web/src/app/shared/channel.model.ts | 2 +- .../add-playlist-dialog.component.ts | 4 +- apps/web/src/app/shared/playlist-meta.type.ts | 2 +- apps/web/src/app/state/actions.ts | 3 +- apps/web/src/app/state/effects.ts | 2 +- apps/web/src/app/state/selectors.ts | 2 +- apps/web/src/app/state/state.ts | 2 +- apps/web/src/global.d.ts | 2 + apps/web/src/shared/playlist.utils.ts | 95 ------------------ apps/web/src/typings.d.ts | 3 +- libs/shared/interfaces/eslint.config.mjs | 21 ++++ libs/shared/interfaces/jest.config.ts | 13 +++ libs/shared/interfaces/package.json | 11 +++ libs/shared/interfaces/project.json | 26 +++++ libs/shared/interfaces/src/index.ts | 4 + .../interfaces/src/lib}/channel.interface.ts | 0 .../shared/interfaces/src/lib}/constants.ts | 0 .../src/lib/parsed-playlist.interface.ts | 29 ++++++ .../interfaces/src/lib}/playlist.interface.ts | 0 libs/shared/interfaces/tsconfig.json | 23 +++++ libs/shared/interfaces/tsconfig.lib.json | 10 ++ libs/shared/interfaces/tsconfig.spec.json | 15 +++ libs/shared/m3u-utils/eslint.config.mjs | 21 ++++ libs/shared/m3u-utils/jest.config.ts | 13 +++ libs/shared/m3u-utils/package.json | 11 +++ libs/shared/m3u-utils/project.json | 26 +++++ libs/shared/m3u-utils/src/index.ts | 1 + .../m3u-utils/src/lib/playlist.utils.ts | 98 +++++++++++++++++++ libs/shared/m3u-utils/tsconfig.json | 23 +++++ libs/shared/m3u-utils/tsconfig.lib.json | 10 ++ libs/shared/m3u-utils/tsconfig.spec.json | 15 +++ nx.json | 10 +- package-lock.json | 1 + package.json | 23 ++--- tsconfig.base.json | 4 +- 59 files changed, 566 insertions(+), 225 deletions(-) create mode 100644 apps/electron-backend/src/app/events/playlist.events.ts delete mode 100644 apps/web/src/shared/playlist.utils.ts create mode 100644 libs/shared/interfaces/eslint.config.mjs create mode 100644 libs/shared/interfaces/jest.config.ts create mode 100644 libs/shared/interfaces/package.json create mode 100644 libs/shared/interfaces/project.json create mode 100644 libs/shared/interfaces/src/index.ts rename {apps/web/src/shared => libs/shared/interfaces/src/lib}/channel.interface.ts (100%) rename {apps/web/src/shared => libs/shared/interfaces/src/lib}/constants.ts (100%) create mode 100644 libs/shared/interfaces/src/lib/parsed-playlist.interface.ts rename {apps/web/src/shared => libs/shared/interfaces/src/lib}/playlist.interface.ts (100%) create mode 100644 libs/shared/interfaces/tsconfig.json create mode 100644 libs/shared/interfaces/tsconfig.lib.json create mode 100644 libs/shared/interfaces/tsconfig.spec.json create mode 100644 libs/shared/m3u-utils/eslint.config.mjs create mode 100644 libs/shared/m3u-utils/jest.config.ts create mode 100644 libs/shared/m3u-utils/package.json create mode 100644 libs/shared/m3u-utils/project.json create mode 100644 libs/shared/m3u-utils/src/index.ts create mode 100644 libs/shared/m3u-utils/src/lib/playlist.utils.ts create mode 100644 libs/shared/m3u-utils/tsconfig.json create mode 100644 libs/shared/m3u-utils/tsconfig.lib.json create mode 100644 libs/shared/m3u-utils/tsconfig.spec.json diff --git a/apps/electron-backend/src/app/api/main.preload.ts b/apps/electron-backend/src/app/api/main.preload.ts index 37f2214d6..ab62c2f51 100644 --- a/apps/electron-backend/src/app/api/main.preload.ts +++ b/apps/electron-backend/src/app/api/main.preload.ts @@ -1,6 +1,8 @@ import { contextBridge, ipcRenderer } from 'electron'; contextBridge.exposeInMainWorld('electron', { - getAppVersion: () => ipcRenderer.invoke('get-app-version'), - platform: process.platform, + getAppVersion: () => ipcRenderer.invoke('get-app-version'), + platform: process.platform, + fetchPlaylistByUrl: (url: string) => + ipcRenderer.invoke('fetch-playlist-by-url', url), }); diff --git a/apps/electron-backend/src/app/events/playlist.events.ts b/apps/electron-backend/src/app/events/playlist.events.ts new file mode 100644 index 000000000..d0dfba480 --- /dev/null +++ b/apps/electron-backend/src/app/events/playlist.events.ts @@ -0,0 +1,47 @@ +/** + * This module is responsible on handling all the inter process communications + * between the frontend to the electron backend. + */ + +import axios from 'axios'; +import { ipcMain } from 'electron'; +import { parse } from 'iptv-playlist-parser'; +import { createPlaylistObject, getFilenameFromUrl } from 'm3u-utils'; + +export default class PlaylistEvents { + static bootstrapPlaylistEvents(): Electron.IpcMain { + return ipcMain; + } +} + +const https = require('https'); + +// Retrieve app version +ipcMain.handle('fetch-playlist-by-url', async (event, url) => { + try { + const agent = new https.Agent({ + rejectUnauthorized: false, + }); + const result = await axios.get(url, { httpsAgent: agent }); + const parsedPlaylist = parse(result.data); + + const extractedName = + url && url.length > 1 ? getFilenameFromUrl(url) : ''; + const playlistName = + !extractedName || extractedName === 'Untitled playlist' + ? 'Imported from URL' + : extractedName; + + const playlistObject = createPlaylistObject( + playlistName, + parsedPlaylist, + url, + 'URL' + ); + + return playlistObject; + } catch (error) { + console.error('Error fetching playlist:', error); + throw error; + } +}); diff --git a/apps/electron-backend/src/main.ts b/apps/electron-backend/src/main.ts index 6c871db48..40a41a7ce 100644 --- a/apps/electron-backend/src/main.ts +++ b/apps/electron-backend/src/main.ts @@ -1,29 +1,30 @@ -import SquirrelEvents from './app/events/squirrel.events'; -import ElectronEvents from './app/events/electron.events'; -import UpdateEvents from './app/events/update.events'; import { app, BrowserWindow } from 'electron'; import App from './app/app'; +import ElectronEvents from './app/events/electron.events'; +import PlaylistEvents from './app/events/playlist.events'; +import SquirrelEvents from './app/events/squirrel.events'; export default class Main { - static initialize() { - if (SquirrelEvents.handleEvents()) { - // squirrel event handled (except first run event) and app will exit in 1000ms, so don't do anything else - app.quit(); + static initialize() { + if (SquirrelEvents.handleEvents()) { + // squirrel event handled (except first run event) and app will exit in 1000ms, so don't do anything else + app.quit(); + } } - } - static bootstrapApp() { - App.main(app, BrowserWindow); - } - - static bootstrapAppEvents() { - ElectronEvents.bootstrapElectronEvents(); - - // initialize auto updater service - if (!App.isDevelopmentMode()) { - // UpdateEvents.initAutoUpdateService(); + static bootstrapApp() { + App.main(app, BrowserWindow); + } + + static bootstrapAppEvents() { + ElectronEvents.bootstrapElectronEvents(); + PlaylistEvents.bootstrapPlaylistEvents(); + + // initialize auto updater service + if (!App.isDevelopmentMode()) { + // UpdateEvents.initAutoUpdateService(); + } } - } } // handle setup events as quickly as possible diff --git a/apps/web/src/app/app.module.ts b/apps/web/src/app/app.module.ts index 1303161c3..73ee8da7b 100644 --- a/apps/web/src/app/app.module.ts +++ b/apps/web/src/app/app.module.ts @@ -38,7 +38,7 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { * Conditionally imports the necessary service based on the current environment */ export function DataFactory() { - if (isTauri()) { + if (window.electron) { return new TauriService(); } return new PwaService(); diff --git a/apps/web/src/app/home/home.component.ts b/apps/web/src/app/home/home.component.ts index fa1596db0..0438db85a 100644 --- a/apps/web/src/app/home/home.component.ts +++ b/apps/web/src/app/home/home.component.ts @@ -1,11 +1,10 @@ - -import { Component, NgZone } from '@angular/core'; +import { Component, inject, OnDestroy } from '@angular/core'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Store } from '@ngrx/store'; import { TranslatePipe } from '@ngx-translate/core'; +import { Playlist } from 'shared-interfaces'; import { ERROR, PLAYLIST_PARSE_RESPONSE } from '../../shared/ipc-commands'; -import { Playlist } from '../../shared/playlist.interface'; import { DataService } from '../services/data.service'; import { HeaderComponent } from '../shared/components/header/header.component'; import { addPlaylist } from '../state/actions'; @@ -16,13 +15,17 @@ import { RecentPlaylistsComponent } from './recent-playlists/recent-playlists.co templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], imports: [ - HeaderComponent, - MatProgressBarModule, - RecentPlaylistsComponent, - TranslatePipe -], + HeaderComponent, + MatProgressBarModule, + RecentPlaylistsComponent, + TranslatePipe, + ], }) -export class HomeComponent { +export class HomeComponent implements OnDestroy { + private dataService = inject(DataService); + private snackBar = inject(MatSnackBar); + private readonly store = inject(Store); + /** Loading spinner state */ isLoading = false; @@ -48,12 +51,7 @@ export class HomeComponent { listeners = []; - constructor( - private dataService: DataService, - private ngZone: NgZone, - private snackBar: MatSnackBar, - private readonly store: Store - ) { + constructor() { this.setRendererListeners(); } diff --git a/apps/web/src/app/home/recent-playlists/playlist-info/playlist-info.component.ts b/apps/web/src/app/home/recent-playlists/playlist-info/playlist-info.component.ts index be220221e..327ac4e31 100644 --- a/apps/web/src/app/home/recent-playlists/playlist-info/playlist-info.component.ts +++ b/apps/web/src/app/home/recent-playlists/playlist-info/playlist-info.component.ts @@ -20,7 +20,7 @@ import { isTauri } from '@tauri-apps/api/core'; import { save } from '@tauri-apps/plugin-dialog'; import { writeTextFile } from '@tauri-apps/plugin-fs'; import { firstValueFrom } from 'rxjs'; -import { Playlist } from '../../../../shared/playlist.interface'; +import { Playlist } from 'shared-interfaces'; import { DatabaseService } from '../../../services/database.service'; import { PlaylistsService } from '../../../services/playlists.service'; import { PlaylistMeta } from '../../../shared/playlist-meta.type'; diff --git a/apps/web/src/app/home/recent-playlists/recent-playlists.component.ts b/apps/web/src/app/home/recent-playlists/recent-playlists.component.ts index a1456de7b..5da99fc20 100644 --- a/apps/web/src/app/home/recent-playlists/recent-playlists.component.ts +++ b/apps/web/src/app/home/recent-playlists/recent-playlists.component.ts @@ -4,7 +4,6 @@ import { Component, ElementRef, EventEmitter, - HostListener, Input, OnDestroy, OnInit, @@ -23,8 +22,7 @@ import { Store } from '@ngrx/store'; import { TranslatePipe, TranslateService } from '@ngx-translate/core'; import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader'; import { BehaviorSubject, combineLatest, map } from 'rxjs'; -import { GLOBAL_FAVORITES_PLAYLIST_ID } from '../../../shared/constants'; -import { Playlist } from '../../../shared/playlist.interface'; +import { GLOBAL_FAVORITES_PLAYLIST_ID, Playlist } from 'shared-interfaces'; import { DataService } from '../../services/data.service'; import { DatabaseService } from '../../services/database.service'; import { SortService } from '../../services/sort.service'; diff --git a/apps/web/src/app/home/stalker-portal-import/stalker-portal-import.component.ts b/apps/web/src/app/home/stalker-portal-import/stalker-portal-import.component.ts index f2c7ad3fc..40b91299a 100644 --- a/apps/web/src/app/home/stalker-portal-import/stalker-portal-import.component.ts +++ b/apps/web/src/app/home/stalker-portal-import/stalker-portal-import.component.ts @@ -11,8 +11,8 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { Store } from '@ngrx/store'; import { TranslatePipe } from '@ngx-translate/core'; +import { Playlist } from 'shared-interfaces'; import { v4 as uuid } from 'uuid'; -import { Playlist } from '../../../shared/playlist.interface'; import { DataService } from '../../services/data.service'; import { addPlaylist } from '../../state/actions'; diff --git a/apps/web/src/app/home/url-upload/url-upload.component.html b/apps/web/src/app/home/url-upload/url-upload.component.html index edc39bc04..0db7cbf21 100644 --- a/apps/web/src/app/home/url-upload/url-upload.component.html +++ b/apps/web/src/app/home/url-upload/url-upload.component.html @@ -7,10 +7,10 @@ formControlName="playlistUrl" /> - @if (!isTauri) { -

- {{ 'HOME.URL_UPLOAD.CORS_NOTE' | translate }} -

+ @if (!isDesktop) { +

+ {{ 'HOME.URL_UPLOAD.CORS_NOTE' | translate }} +

}