From cb07cc238659b1c364aeae47132b09643fc9f34b Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 16 Apr 2022 20:11:31 +0200 Subject: [PATCH] refactor: some general code improvements --- epg-worker.ts | 8 ++ shared/ipc-command.class.ts | 2 +- shared/ipc-commands.ts | 2 + shared/playlist.utils.ts | 5 +- src/app/app.component.ts | 5 +- src/app/home/home.component.html | 8 +- src/app/home/home.component.spec.ts | 82 +++-------- src/app/home/home.component.ts | 78 +---------- .../playlist-info.component.html | 8 +- .../playlist-item.component.html | 66 +++++++++ .../playlist-item.component.scss | 32 +++++ .../playlist-item.component.spec.ts | 38 ++++++ .../playlist-item/playlist-item.component.ts | 25 ++++ .../recent-playlists.component.html | 81 ++--------- .../recent-playlists.component.scss | 27 ---- .../recent-playlists.component.spec.ts | 63 ++++++++- .../recent-playlists.component.ts | 128 +++++++++++++++--- .../channel-list-container.component.spec.ts | 1 - .../video-player/video-player.component.html | 5 +- .../video-player/video-player.component.ts | 7 - src/app/shared/shared.module.ts | 4 +- tsconfig.json | 49 +++---- 22 files changed, 405 insertions(+), 319 deletions(-) create mode 100644 src/app/home/recent-playlists/playlist-item/playlist-item.component.html create mode 100644 src/app/home/recent-playlists/playlist-item/playlist-item.component.scss create mode 100644 src/app/home/recent-playlists/playlist-item/playlist-item.component.spec.ts create mode 100644 src/app/home/recent-playlists/playlist-item/playlist-item.component.ts diff --git a/epg-worker.ts b/epg-worker.ts index 9bf237f4b..527f976ec 100644 --- a/epg-worker.ts +++ b/epg-worker.ts @@ -7,6 +7,8 @@ import { EPG_ERROR, EPG_FETCH, EPG_FETCH_DONE, + EPG_GET_CHANNELS, + EPG_GET_CHANNELS_DONE, EPG_GET_PROGRAM, EPG_GET_PROGRAM_DONE, } from './shared/ipc-commands'; @@ -105,3 +107,9 @@ ipcRenderer.on(EPG_GET_PROGRAM, (event, args) => { }); } }); + +ipcRenderer.on(EPG_GET_CHANNELS, (event, args) => { + ipcRenderer.send(EPG_GET_CHANNELS_DONE, { + payload: EPG_DATA, + }); +}); diff --git a/shared/ipc-command.class.ts b/shared/ipc-command.class.ts index eec9433dc..ca000c361 100644 --- a/shared/ipc-command.class.ts +++ b/shared/ipc-command.class.ts @@ -1,5 +1,5 @@ export class IpcCommand { - constructor(public id: string, public callback: () => void) { + constructor(public id: string, public callback: (payload) => void) { this.id = id; this.callback = callback; } diff --git a/shared/ipc-commands.ts b/shared/ipc-commands.ts index 820a7bc34..ad3cc6b73 100644 --- a/shared/ipc-commands.ts +++ b/shared/ipc-commands.ts @@ -4,6 +4,8 @@ export const EPG_FETCH_DONE = 'EPG:FETCH_DONE'; export const EPG_ERROR = 'EPG:ERROR'; export const EPG_GET_PROGRAM = 'EPG:GET_PROGRAM'; export const EPG_GET_PROGRAM_DONE = 'EPG:GET_PROGRAM_DONE'; +export const EPG_GET_CHANNELS = 'EPG:GET_CHANNELS'; +export const EPG_GET_CHANNELS_DONE = 'EPG:GET_CHANNELS_DONE'; // Playlist related commands export const PLAYLIST_GET_ALL = 'PLAYLIST:GET_ALL'; diff --git a/shared/playlist.utils.ts b/shared/playlist.utils.ts index 3e0718b79..d473a87f6 100644 --- a/shared/playlist.utils.ts +++ b/shared/playlist.utils.ts @@ -12,7 +12,10 @@ export function aggregateFavoriteChannels(playlists: Playlist[]): Channel[] { playlists.forEach((playlist) => { if (playlist.favorites?.length > 0) { playlist.playlist.items.forEach((channel) => { - if (playlist.favorites.includes(channel.id)) { + if ( + playlist.favorites.includes(channel.id) || + playlist.favorites.includes(channel.url) + ) { favorites.push(channel); } }); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 3d89e0b2f..d20e334ec 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -17,6 +17,7 @@ import { import { DataService } from './services/data.service'; import { SettingsService } from './services/settings.service'; import { WhatsNewService } from './services/whats-new.service'; +import { Language } from './settings/language.enum'; import { Settings } from './settings/settings.interface'; import { Theme } from './settings/theme.enum'; import { STORE_KEY } from './shared/enums/store-keys.enum'; @@ -55,7 +56,7 @@ export class AppComponent { ]; /** Default language as fallback */ - DEFAULT_LANG = 'en'; + DEFAULT_LANG = Language.ENGLISH; /** * Creates an instance of AppComponent @@ -109,7 +110,7 @@ export class AppComponent { if (this.electronService.isElectron) { this.commandsList.forEach((command) => this.electronService.listenOn(command.id, () => - this.ngZone.run(() => command.callback()) + this.ngZone.run((data) => command.callback(data)) ) ); } diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 83037ce8b..2d942120e 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -17,13 +17,7 @@ list {{ 'HOME.TABS.RECENTLY_ADDED' | translate }} - + diff --git a/src/app/home/home.component.spec.ts b/src/app/home/home.component.spec.ts index 0ebf198e2..dda518243 100644 --- a/src/app/home/home.component.spec.ts +++ b/src/app/home/home.component.spec.ts @@ -1,30 +1,26 @@ -import { UploadFile } from 'ngx-uploader'; -import { - PLAYLIST_GET_BY_ID, - PLAYLIST_PARSE, - PLAYLIST_PARSE_BY_URL, - PLAYLIST_REMOVE_BY_ID, - PLAYLIST_UPDATE, -} from './../../../shared/ipc-commands'; -import { TranslatePipe, TranslateService } from '@ngx-translate/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MockComponent, MockModule, MockPipe, MockProvider } from 'ng-mocks'; -import { HomeComponent, PlaylistMeta } from './home.component'; -import { HeaderComponent } from '../shared/components/header/header.component'; -import { RecentPlaylistsComponent } from '../home/recent-playlists/recent-playlists.component'; -import { FileUploadComponent } from '../home/file-upload/file-upload.component'; -import { UrlUploadComponent } from '../home/url-upload/url-upload.component'; -import { MatTabsModule } from '@angular/material/tabs'; import { MatIconModule } from '@angular/material/icon'; import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { RouterTestingModule } from '@angular/router/testing'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatTabsModule } from '@angular/material/tabs'; import { Router } from '@angular/router'; -import { DialogService } from '../services/dialog.service'; -import { ElectronServiceStub } from '../services/electron.service.stub'; -import { DataService } from '../services/data.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockComponent, MockModule, MockPipe, MockProvider } from 'ng-mocks'; import { NgxIndexedDBModule, NgxIndexedDBService } from 'ngx-indexed-db'; +import { UploadFile } from 'ngx-uploader'; import { of } from 'rxjs'; +import { FileUploadComponent } from '../home/file-upload/file-upload.component'; +import { RecentPlaylistsComponent } from '../home/recent-playlists/recent-playlists.component'; +import { UrlUploadComponent } from '../home/url-upload/url-upload.component'; +import { DataService } from '../services/data.service'; +import { ElectronServiceStub } from '../services/electron.service.stub'; +import { HeaderComponent } from '../shared/components/header/header.component'; +import { + PLAYLIST_PARSE, + PLAYLIST_PARSE_BY_URL, +} from './../../../shared/ipc-commands'; +import { HomeComponent } from './home.component'; class MatSnackBarStub { open(): void {} @@ -38,7 +34,6 @@ class NgxIndexedDBServiceStub { describe('HomeComponent', () => { let component: HomeComponent; - let dialogService: DialogService; let fixture: ComponentFixture; let electronService: DataService; let router: Router; @@ -64,7 +59,6 @@ describe('HomeComponent', () => { providers: [ { provide: MatSnackBar, useClass: MatSnackBarStub }, { provide: DataService, useClass: ElectronServiceStub }, - MockProvider(DialogService), MockProvider(TranslateService), { provide: NgxIndexedDBService, @@ -78,7 +72,7 @@ describe('HomeComponent', () => { fixture = TestBed.createComponent(HomeComponent); component = fixture.componentInstance; electronService = TestBed.inject(DataService); - dialogService = TestBed.inject(DialogService); + router = TestBed.inject(Router); TestBed.inject(NgxIndexedDBService); fixture.detectChanges(); @@ -88,48 +82,6 @@ describe('HomeComponent', () => { expect(component).toBeTruthy(); }); - it('should open the confirmation dialog on remove icon click', () => { - const playlistId = '12345'; - jest.spyOn(dialogService, 'openConfirmDialog'); - component.removeClicked(playlistId); - expect(dialogService.openConfirmDialog).toHaveBeenCalledTimes(1); - }); - - it('should send an event to the main process to remove a playlist', () => { - const playlistId = '12345'; - jest.spyOn(electronService, 'sendIpcEvent'); - component.removePlaylist(playlistId); - expect(electronService.sendIpcEvent).toHaveBeenCalledWith( - PLAYLIST_REMOVE_BY_ID, - { id: playlistId } - ); - }); - - it('should send an event to the main process to refresh a playlist', () => { - const playlistMeta: PlaylistMeta = { - _id: 'iptv1', - filePath: '/home/user/lists/iptv.m3u', - } as PlaylistMeta; - jest.spyOn(electronService, 'sendIpcEvent'); - component.refreshPlaylist(playlistMeta); - expect(electronService.sendIpcEvent).toHaveBeenCalledWith( - PLAYLIST_UPDATE, - { id: playlistMeta._id, filePath: playlistMeta.filePath } - ); - }); - - it('should send an event to the main process to get a playlist', () => { - const playlistId = '6789'; - jest.spyOn(electronService, 'sendIpcEvent'); - component.getPlaylist(playlistId); - expect(electronService.sendIpcEvent).toHaveBeenCalledWith( - PLAYLIST_GET_BY_ID, - { - id: playlistId, - } - ); - }); - it('should send an event to the main process to get a playlist by URL', () => { const playlistTitle = 'playlist.m3u'; const playlistUrl = 'http://test.com/' + playlistTitle; diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 8812eb4bf..6ee51a2bd 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -1,25 +1,17 @@ import { Component, NgZone } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Router } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; import { UploadFile } from 'ngx-uploader'; import { ERROR, - PLAYLIST_GET_ALL, - PLAYLIST_GET_ALL_RESPONSE, - PLAYLIST_GET_BY_ID, PLAYLIST_PARSE, PLAYLIST_PARSE_BY_URL, PLAYLIST_PARSE_RESPONSE, - PLAYLIST_REMOVE_BY_ID, - PLAYLIST_REMOVE_BY_ID_RESPONSE, - PLAYLIST_UPDATE, PLAYLIST_UPDATE_RESPONSE, } from '../../../shared/ipc-commands'; import { Playlist } from '../../../shared/playlist.interface'; import { DataService } from '../services/data.service'; import { ChannelStore } from '../state'; -import { DialogService } from './../services/dialog.service'; /** Type to describe meta data of a playlist */ export type PlaylistMeta = Pick< @@ -45,8 +37,10 @@ export type PlaylistMeta = Pick< export class HomeComponent { /** Added playlists */ playlists: PlaylistMeta[] = []; + /** Loading spinner state */ isLoading = false; + /** IPC Renderer commands list with callbacks */ commandsList = [ { @@ -56,21 +50,6 @@ export class HomeComponent { this.navigateToPlayer(); }, }, - { - id: PLAYLIST_GET_ALL_RESPONSE, - execute: (response: { payload: Partial }): void => { - this.playlists = response.payload; - }, - }, - { - id: PLAYLIST_REMOVE_BY_ID_RESPONSE, - execute: (): void => { - this.snackBar.open('Done! Playlist was removed.', null, { - duration: 2000, - }); - this.electronService.sendIpcEvent(PLAYLIST_GET_ALL); - }, - }, { id: ERROR, execute: (response: { message: string; status: number }): void => { @@ -90,24 +69,18 @@ export class HomeComponent { /** * Creates an instanceof HomeComponent * @param channelStore channels store - * @param dialogService dialog service * @param electronService electron service * @param ngZone angular ngZone module * @param router angular router * @param snackBar snackbar for notification messages - * @param translate translate service */ constructor( private electronService: DataService, - private dialogService: DialogService, private ngZone: NgZone, private channelStore: ChannelStore, private router: Router, - private snackBar: MatSnackBar, - private translate: TranslateService + private snackBar: MatSnackBar ) { - // get all playlists - this.electronService.sendIpcEvent(PLAYLIST_GET_ALL); // set all renderer listeners this.setRendererListeners(); } @@ -177,51 +150,6 @@ export class HomeComponent { }); } - /** - * Triggers on remove click - * @param playlistId playlist id to remove - */ - removeClicked(playlistId: string): void { - this.dialogService.openConfirmDialog({ - title: this.translate.instant('HOME.PLAYLISTS.REMOVE_DIALOG.TITLE'), - message: this.translate.instant( - 'HOME.PLAYLISTS.REMOVE_DIALOG.MESSAGE' - ), - onConfirm: (): void => this.removePlaylist(playlistId), - }); - } - - /** - * Removes the provided playlist from the database - * @param playlistId playlist id to remove - */ - removePlaylist(playlistId: string): void { - this.electronService.sendIpcEvent(PLAYLIST_REMOVE_BY_ID, { - id: playlistId, - }); - } - - /** - * Sends an IPC event with the playlist details to the main process to trigger the refresh operation - * @param item playlist to update - */ - refreshPlaylist(item: PlaylistMeta): void { - this.electronService.sendIpcEvent(PLAYLIST_UPDATE, { - id: item._id, - ...(item.url ? { url: item.url } : { filePath: item.filePath }), - }); - } - - /** - * Requests playlist by id - * @param playlistId playlist id - */ - getPlaylist(playlistId: string): void { - this.electronService.sendIpcEvent(PLAYLIST_GET_BY_ID, { - id: playlistId, - }); - } - /** * Returns last segment (part after last slash "/") of the given URL * @param value URL as string diff --git a/src/app/home/recent-playlists/playlist-info/playlist-info.component.html b/src/app/home/recent-playlists/playlist-info/playlist-info.component.html index 11de3c786..cfe746ab2 100644 --- a/src/app/home/recent-playlists/playlist-info/playlist-info.component.html +++ b/src/app/home/recent-playlists/playlist-info/playlist-info.component.html @@ -55,15 +55,13 @@ - + {{ 'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE' | translate }}

{{ - 'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE_DESCRIPTION' | translate + 'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE_DESCRIPTION' + | translate }}

diff --git a/src/app/home/recent-playlists/playlist-item/playlist-item.component.html b/src/app/home/recent-playlists/playlist-item/playlist-item.component.html new file mode 100644 index 000000000..f542dc72f --- /dev/null +++ b/src/app/home/recent-playlists/playlist-item/playlist-item.component.html @@ -0,0 +1,66 @@ + + drag_indicator + cloud + folder +
{{ item.title || item.filename }}
+
+ {{ 'HOME.PLAYLISTS.CHANNELS' | translate }}: {{ item.count }} + + | {{ 'HOME.PLAYLISTS.ADDED' | translate }}: + {{ item.importDate | date }} + + + | {{ 'HOME.PLAYLISTS.UPDATED' | translate }}: + {{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }} + + + warning + +
+ + + + +
diff --git a/src/app/home/recent-playlists/playlist-item/playlist-item.component.scss b/src/app/home/recent-playlists/playlist-item/playlist-item.component.scss new file mode 100644 index 000000000..10620d0ad --- /dev/null +++ b/src/app/home/recent-playlists/playlist-item/playlist-item.component.scss @@ -0,0 +1,32 @@ +mat-list-item { + cursor: pointer; +} + +.mat-list-base { + padding-top: 0 !important; +} + +.meta { + font-size: 12px !important; + color: #666; + margin-top: 2px !important; +} + +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: 0 5px 5px -3px rgb(0 0 0 / 20%), 0 8px 10px 1px rgb(0 0 0 / 14%), 0 3px 14px 2px rgb(0 0 0 / 12%); +} + +.cdk-drag-placeholder { + opacity: 0; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.drag-icon { + cursor: move; + margin-left: -20px; +} diff --git a/src/app/home/recent-playlists/playlist-item/playlist-item.component.spec.ts b/src/app/home/recent-playlists/playlist-item/playlist-item.component.spec.ts new file mode 100644 index 000000000..69caecdb1 --- /dev/null +++ b/src/app/home/recent-playlists/playlist-item/playlist-item.component.spec.ts @@ -0,0 +1,38 @@ +/* tslint:disable:no-unused-variable */ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from '@angular/material/list'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockModule, MockPipe, MockProviders } from 'ng-mocks'; +import { PlaylistItemComponent } from './playlist-item.component'; + +describe('PlaylistItemComponent', () => { + let component: PlaylistItemComponent; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [PlaylistItemComponent, MockPipe(TranslatePipe)], + providers: [MockProviders(TranslateService)], + imports: [ + MockModule(MatIconModule), + MockModule(MatListModule), + MockModule(MatTooltipModule), + ], + }).compileComponents(); + }) + ); + + beforeEach(() => { + fixture = TestBed.createComponent(PlaylistItemComponent); + component = fixture.componentInstance; + component.item = { title: 'Playlist', id: '1' } as any; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/home/recent-playlists/playlist-item/playlist-item.component.ts b/src/app/home/recent-playlists/playlist-item/playlist-item.component.ts new file mode 100644 index 000000000..ef312734c --- /dev/null +++ b/src/app/home/recent-playlists/playlist-item/playlist-item.component.ts @@ -0,0 +1,25 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { PlaylistMeta } from '../../home.component'; + +@Component({ + selector: 'app-playlist-item', + templateUrl: './playlist-item.component.html', + styleUrls: ['./playlist-item.component.scss'], +}) +export class PlaylistItemComponent { + /** Playlist item */ + @Input() item: PlaylistMeta; + + /** Emits on playlist selection */ + @Output() playlistClicked: EventEmitter = new EventEmitter(); + + /** Emits on playlist refresh click */ + @Output() refreshClicked: EventEmitter = new EventEmitter(); + + /** Emits on playlist remove click */ + @Output() removeClicked: EventEmitter = new EventEmitter(); + + /** Emits on playlist edit click */ + @Output() editPlaylistClicked: EventEmitter = + new EventEmitter(); +} diff --git a/src/app/home/recent-playlists/recent-playlists.component.html b/src/app/home/recent-playlists/recent-playlists.component.html index 40cae1c8a..e49d43545 100644 --- a/src/app/home/recent-playlists/recent-playlists.component.html +++ b/src/app/home/recent-playlists/recent-playlists.component.html @@ -6,7 +6,10 @@ {{ 'HOME.PLAYLISTS.ADD_FIRST' | translate }} - + star
Global favorites
@@ -15,74 +18,12 @@ - - drag_indicator - cloud - folder -
{{ item.title || item.filename }}
-
- {{ 'HOME.PLAYLISTS.CHANNELS' | translate }}: {{ item.count }} - - | {{ 'HOME.PLAYLISTS.ADDED' | translate }}: - {{ item.importDate | date }} - - - | {{ 'HOME.PLAYLISTS.UPDATED' | translate }}: - {{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }} - - - warning - -
- - - - -
+ [item]="item" + (editPlaylistClicked)="openInfoDialog($event)" + (playlistClicked)="getPlaylist($event)" + (refreshClicked)="refreshPlaylist($event)" + (removeClicked)="removeClicked($event)" + > diff --git a/src/app/home/recent-playlists/recent-playlists.component.scss b/src/app/home/recent-playlists/recent-playlists.component.scss index 6e110ab9c..9b6590509 100644 --- a/src/app/home/recent-playlists/recent-playlists.component.scss +++ b/src/app/home/recent-playlists/recent-playlists.component.scss @@ -1,11 +1,3 @@ -mat-list-item { - cursor: pointer; -} - -.mat-list-base { - padding-top: 0 !important; -} - .meta { font-size: 12px !important; color: #666; @@ -33,25 +25,6 @@ mat-list-item { } } -.cdk-drag-preview { - box-sizing: border-box; - border-radius: 4px; - box-shadow: 0 5px 5px -3px rgb(0 0 0 / 20%), 0 8px 10px 1px rgb(0 0 0 / 14%), 0 3px 14px 2px rgb(0 0 0 / 12%); -} - -.cdk-drag-placeholder { - opacity: 0; -} - -.cdk-drag-animating { - transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); -} - -.drag-icon { - cursor: move; - margin-left: -20px; -} - .favorites-icon { margin-left: 16px; } diff --git a/src/app/home/recent-playlists/recent-playlists.component.spec.ts b/src/app/home/recent-playlists/recent-playlists.component.spec.ts index 0fc9ab2db..3a331a16f 100644 --- a/src/app/home/recent-playlists/recent-playlists.component.spec.ts +++ b/src/app/home/recent-playlists/recent-playlists.component.spec.ts @@ -1,20 +1,27 @@ -import { TranslatePipe, TranslateService } from '@ngx-translate/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { MockModule, MockPipe, MockProvider } from 'ng-mocks'; -import { DataService } from '../../services/data.service'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; -import { RecentPlaylistsComponent } from './recent-playlists.component'; -import { PlaylistMeta } from '../home.component'; +import { MatIconModule } from '@angular/material/icon'; import { MatListModule } from '@angular/material/list'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatIconModule } from '@angular/material/icon'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { MockModule, MockPipe, MockProvider } from 'ng-mocks'; +import { + PLAYLIST_GET_BY_ID, + PLAYLIST_REMOVE_BY_ID, + PLAYLIST_UPDATE, +} from '../../../../shared/ipc-commands'; +import { DataService } from '../../services/data.service'; +import { DialogService } from '../../services/dialog.service'; import { ElectronServiceStub } from '../../services/electron.service.stub'; +import { PlaylistMeta } from '../home.component'; +import { RecentPlaylistsComponent } from './recent-playlists.component'; describe('RecentPlaylistsComponent', () => { let component: RecentPlaylistsComponent; let fixture: ComponentFixture; let electronService: DataService; let dialog: MatDialog; + let dialogService: DialogService; beforeEach( waitForAsync(() => { @@ -32,6 +39,7 @@ describe('RecentPlaylistsComponent', () => { providers: [ { provide: DataService, useClass: ElectronServiceStub }, MockProvider(TranslateService), + MockProvider(DialogService), ], }).compileComponents(); }) @@ -43,6 +51,7 @@ describe('RecentPlaylistsComponent', () => { component.playlists = []; dialog = TestBed.inject(MatDialog); electronService = TestBed.inject(DataService); + dialogService = TestBed.inject(DialogService); fixture.detectChanges(); }); @@ -71,4 +80,46 @@ describe('RecentPlaylistsComponent', () => { component.drop(event); expect(electronService.sendIpcEvent).toHaveBeenCalledTimes(1); }); + + it('should open the confirmation dialog on remove icon click', () => { + const playlistId = '12345'; + jest.spyOn(dialogService, 'openConfirmDialog'); + component.removeClicked(playlistId); + expect(dialogService.openConfirmDialog).toHaveBeenCalledTimes(1); + }); + + it('should send an event to the main process to remove a playlist', () => { + const playlistId = '12345'; + jest.spyOn(electronService, 'sendIpcEvent'); + component.removePlaylist(playlistId); + expect(electronService.sendIpcEvent).toHaveBeenCalledWith( + PLAYLIST_REMOVE_BY_ID, + { id: playlistId } + ); + }); + + it('should send an event to the main process to refresh a playlist', () => { + const playlistMeta: PlaylistMeta = { + _id: 'iptv1', + filePath: '/home/user/lists/iptv.m3u', + } as PlaylistMeta; + jest.spyOn(electronService, 'sendIpcEvent'); + component.refreshPlaylist(playlistMeta); + expect(electronService.sendIpcEvent).toHaveBeenCalledWith( + PLAYLIST_UPDATE, + { id: playlistMeta._id, filePath: playlistMeta.filePath } + ); + }); + + it('should send an event to the main process to get a playlist', () => { + const playlistId = '6789'; + jest.spyOn(electronService, 'sendIpcEvent'); + component.getPlaylist(playlistId); + expect(electronService.sendIpcEvent).toHaveBeenCalledWith( + PLAYLIST_GET_BY_ID, + { + id: playlistId, + } + ); + }); }); diff --git a/src/app/home/recent-playlists/recent-playlists.component.ts b/src/app/home/recent-playlists/recent-playlists.component.ts index f86a056f2..453bdd75e 100644 --- a/src/app/home/recent-playlists/recent-playlists.component.ts +++ b/src/app/home/recent-playlists/recent-playlists.component.ts @@ -1,10 +1,22 @@ -import { PLAYLIST_UPDATE_POSITIONS } from './../../../../shared/ipc-commands'; -import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { PlaylistInfoComponent } from './playlist-info/playlist-info.component'; -import { PlaylistMeta } from './../home.component'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { Component, NgZone } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { TranslateService } from '@ngx-translate/core'; +import { IpcCommand } from '../../../../shared/ipc-command.class'; import { DataService } from '../../services/data.service'; +import { + PLAYLIST_GET_ALL, + PLAYLIST_GET_ALL_RESPONSE, + PLAYLIST_GET_BY_ID, + PLAYLIST_REMOVE_BY_ID, + PLAYLIST_REMOVE_BY_ID_RESPONSE, + PLAYLIST_UPDATE, + PLAYLIST_UPDATE_POSITIONS, +} from './../../../../shared/ipc-commands'; +import { DialogService } from './../../services/dialog.service'; +import { PlaylistMeta } from './../home.component'; +import { PlaylistInfoComponent } from './playlist-info/playlist-info.component'; @Component({ selector: 'app-recent-playlists', @@ -13,27 +25,66 @@ import { DataService } from '../../services/data.service'; }) export class RecentPlaylistsComponent { /** All available playlists */ - @Input() playlists: PlaylistMeta[]; + playlists: PlaylistMeta[] = []; - /** Emits on playlist selection */ - @Output() playlistClicked: EventEmitter = new EventEmitter(); - - /** Emits on playlist refresh click */ - @Output() refreshClicked: EventEmitter = new EventEmitter(); - - /** Emits on playlist remove click */ - @Output() removeClicked: EventEmitter = new EventEmitter(); + /** IPC Renderer commands list with callbacks */ + commandsList = [ + new IpcCommand( + PLAYLIST_GET_ALL_RESPONSE, + (response: { payload: Partial }) => { + this.playlists = response.payload; + } + ), + new IpcCommand(PLAYLIST_REMOVE_BY_ID_RESPONSE, (): void => { + this.snackBar.open('Done! Playlist was removed.', null, { + duration: 2000, + }); + this.electronService.sendIpcEvent(PLAYLIST_GET_ALL); + }), + ]; /** * Creates an instance of the component * @param dialog angular material dialog reference + * @param dialogService dialog service * @param electronService electron service + * @param snackBar angular material snackbar reference + * @param translate translate service */ constructor( - public dialog: MatDialog, - private electronService: DataService + private dialog: MatDialog, + private dialogService: DialogService, + private electronService: DataService, + private ngZone: NgZone, + private snackBar: MatSnackBar, + private translate: TranslateService ) {} + ngOnInit(): void { + // get all playlists + this.electronService.sendIpcEvent(PLAYLIST_GET_ALL); + this.setRendererListeners(); + } + + /** + * Set electrons main process listeners + */ + setRendererListeners(): void { + this.commandsList.forEach((command) => { + if (this.electronService.isElectron) { + this.electronService.listenOn(command.id, (event, response) => + this.ngZone.run(() => command.callback(response)) + ); + } else { + this.electronService.listenOn(command.id, (response) => { + if (response.data.type === command.id) { + command.callback(response.data); + } + }); + } + }); + } + /** * Opens the details dialog with the information about the provided playlist * @param data selected playlist @@ -59,4 +110,49 @@ export class RecentPlaylistsComponent { this.playlists ); } + + /** + * Requests playlist by id + * @param playlistId playlist id + */ + getPlaylist(playlistId: string): void { + this.electronService.sendIpcEvent(PLAYLIST_GET_BY_ID, { + id: playlistId, + }); + } + + /** + * Triggers on remove click + * @param playlistId playlist id to remove + */ + removeClicked(playlistId: string): void { + this.dialogService.openConfirmDialog({ + title: this.translate.instant('HOME.PLAYLISTS.REMOVE_DIALOG.TITLE'), + message: this.translate.instant( + 'HOME.PLAYLISTS.REMOVE_DIALOG.MESSAGE' + ), + onConfirm: (): void => this.removePlaylist(playlistId), + }); + } + + /** + * Removes the provided playlist from the database + * @param playlistId playlist id to remove + */ + removePlaylist(playlistId: string): void { + this.electronService.sendIpcEvent(PLAYLIST_REMOVE_BY_ID, { + id: playlistId, + }); + } + + /** + * Sends an IPC event with the playlist details to the main process to trigger the refresh operation + * @param item playlist to update + */ + refreshPlaylist(item: PlaylistMeta): void { + this.electronService.sendIpcEvent(PLAYLIST_UPDATE, { + id: item._id, + ...(item.url ? { url: item.url } : { filePath: item.filePath }), + }); + } } diff --git a/src/app/player/components/channel-list-container/channel-list-container.component.spec.ts b/src/app/player/components/channel-list-container/channel-list-container.component.spec.ts index c228306c9..f6603816d 100644 --- a/src/app/player/components/channel-list-container/channel-list-container.component.spec.ts +++ b/src/app/player/components/channel-list-container/channel-list-container.component.spec.ts @@ -1,5 +1,4 @@ import { ScrollingModule } from '@angular/cdk/scrolling'; -/* eslint-disable @typescript-eslint/unbound-method */ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; import { MatExpansionModule } from '@angular/material/expansion'; diff --git a/src/app/player/components/video-player/video-player.component.html b/src/app/player/components/video-player/video-player.component.html index 43eddeacc..e621fe94d 100644 --- a/src/app/player/components/video-player/video-player.component.html +++ b/src/app/player/components/video-player/video-player.component.html @@ -15,10 +15,7 @@ > - + diff --git a/src/app/player/components/video-player/video-player.component.ts b/src/app/player/components/video-player/video-player.component.ts index 88739d6ec..903a565ff 100644 --- a/src/app/player/components/video-player/video-player.component.ts +++ b/src/app/player/components/video-player/video-player.component.ts @@ -6,7 +6,6 @@ import { filter, Observable } from 'rxjs'; import { Channel } from '../../../../../shared/channel.interface'; import { PLAYLIST_GET_ALL, - PLAYLIST_GET_ALL_RESPONSE, PLAYLIST_GET_BY_ID, PLAYLIST_PARSE_RESPONSE, } from '../../../../../shared/ipc-commands'; @@ -66,12 +65,6 @@ export class VideoPlayerComponent implements OnInit { /** IPC Renderer commands list with callbacks */ commandsList = [ - { - id: PLAYLIST_GET_ALL_RESPONSE, - execute: (response: { payload: Playlist[] }): void => { - this.playlists = response.payload; - }, - }, { id: PLAYLIST_PARSE_RESPONSE, execute: (response: { payload: Playlist }): void => { diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 634ec054f..f812cf511 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -6,13 +6,13 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; import { FilterPipeModule } from 'ngx-filter-pipe'; import { NgxWhatsNewModule } from 'ngx-whats-new'; +import { PlaylistItemComponent } from '../home/recent-playlists/playlist-item/playlist-item.component'; import { RecentPlaylistsComponent } from '../home/recent-playlists/recent-playlists.component'; import { MaterialModule } from '../material.module'; import { HeaderComponent } from './components/'; import { AboutDialogComponent } from './components/about-dialog/about-dialog.component'; import { ConfirmDialogComponent } from './components/confirm-dialog/confirm-dialog.component'; import { MomentDatePipe } from './pipes/moment-date.pipe'; - @NgModule({ declarations: [ ConfirmDialogComponent, @@ -20,6 +20,7 @@ import { MomentDatePipe } from './pipes/moment-date.pipe'; MomentDatePipe, AboutDialogComponent, RecentPlaylistsComponent, + PlaylistItemComponent, ], imports: [ CommonModule, @@ -44,6 +45,7 @@ import { MomentDatePipe } from './pipes/moment-date.pipe'; ReactiveFormsModule, TranslateModule, RecentPlaylistsComponent, + PlaylistItemComponent, ], }) export class SharedModule {} diff --git a/tsconfig.json b/tsconfig.json index 67deca65d..bfd1ec653 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,33 +1,20 @@ { - "compileOnSave": false, - "compilerOptions": { - "module": "es2020", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "es5", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2017", - "es2016", - "es2015", - "dom" - ], - "resolveJsonModule": true - }, - "files": [ - "src/main.ts", - "src/polyfills.ts" - ], - "include": [ - "src/**/*.d.ts" - ], - "exclude": [ - "node_modules" - ] + "compileOnSave": false, + "compilerOptions": { + "module": "es2020", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": ["node_modules/@types"], + "lib": ["es2017", "es2016", "es2015", "dom"], + "resolveJsonModule": true, + "strict": false + }, + "files": ["src/main.ts", "src/polyfills.ts"], + "include": ["src/**/*.d.ts"], + "exclude": ["node_modules"] }