refactor: some general code improvements

This commit is contained in:
4gray committed 2022-04-16 20:11:31 +02:00
1 parent 41f3f34faa
commit cb07cc2386
22 files changed
+405 -319

No files matched your search

+8
View File
@@ -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,
});
});
+1 -1
View File
@@ -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;
}
+2
View File
@@ -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';
+4 -1
View File
@@ -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);
}
});
+3 -2
View File
@@ -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))
)
);
}
+1 -7
View File
@@ -17,13 +17,7 @@
<mat-icon class="tab-icon">list</mat-icon>
{{ 'HOME.TABS.RECENTLY_ADDED' | translate }}
</ng-template>
<app-recent-playlists
class="recent-playlists"
[playlists]="playlists"
(playlistClicked)="getPlaylist($event)"
(refreshClicked)="refreshPlaylist($event)"
(removeClicked)="removeClicked($event)"
>
<app-recent-playlists class="recent-playlists">
</app-recent-playlists>
</mat-tab>
<mat-tab>
+17 -65
View File
@@ -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<HomeComponent>;
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;
+3 -75
View File
@@ -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<PlaylistMeta[]> }): 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
@@ -55,15 +55,13 @@
</mat-hint>
</mat-form-field>
<ng-container *ngIf="playlist.url || playlist.filePath">
<mat-checkbox
class="full-width"
formControlName="autoRefresh"
>
<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
'HOME.PLAYLISTS.INFO_DIALOG.AUTO_UPDATE_DESCRIPTION'
| translate
}}
</p>
</ng-container>
@@ -0,0 +1,66 @@
<mat-list-item cdkDrag (click)="playlistClicked.emit(item._id)">
<mat-icon mat-list-icon cdkDragHandle class="drag-icon"
>drag_indicator</mat-icon
>
<mat-icon
mat-list-icon
*ngIf="item?.url"
[matTooltip]="
('HOME.PLAYLISTS.ADDED_VIA_URL' | translate) + ' ' + item?.url
"
>cloud</mat-icon
>
<mat-icon
mat-list-icon
*ngIf="!item?.url"
[matTooltip]="'HOME.PLAYLISTS.ADDED_VIA_FILE' | translate"
>folder</mat-icon
>
<div mat-line>{{ item.title || item.filename }}</div>
<div mat-line class="meta">
{{ 'HOME.PLAYLISTS.CHANNELS' | translate }}: {{ item.count }}
<ng-container *ngIf="item.importDate?.length > 3">
| {{ 'HOME.PLAYLISTS.ADDED' | translate }}:
{{ item.importDate | date }}
</ng-container>
<ng-container *ngIf="item.updateDate">
| {{ 'HOME.PLAYLISTS.UPDATED' | translate }}:
{{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }}
</ng-container>
<ng-container *ngIf="item.updateState === 2">
<mat-icon
[matTooltip]="
'HOME.PLAYLISTS.INFO_DIALOG.UPDATE_FAILED' | translate
"
[inline]="true"
>warning</mat-icon
>
</ng-container>
</div>
<button
*ngIf="item.url || item.filePath"
mat-icon-button
color="accent"
(click)="$event.stopPropagation(); refreshClicked.emit(item)"
[matTooltip]="'HOME.PLAYLISTS.REFRESH' | translate"
>
<mat-icon>sync</mat-icon>
</button>
<button
mat-icon-button
color="accent"
(click)="$event.stopPropagation(); editPlaylistClicked.emit(item)"
[matTooltip]="'HOME.PLAYLISTS.SHOW_DETAILS' | translate"
>
<mat-icon>edit</mat-icon>
</button>
<button
mat-icon-button
color="accent"
(click)="$event.stopPropagation(); removeClicked.emit(item._id)"
[matTooltip]="'HOME.PLAYLISTS.REMOVE' | translate"
>
<mat-icon>delete</mat-icon>
</button>
<mat-divider></mat-divider>
</mat-list-item>
@@ -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;
}
@@ -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<PlaylistItemComponent>;
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();
});
});
@@ -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<string> = new EventEmitter();
/** Emits on playlist refresh click */
@Output() refreshClicked: EventEmitter<PlaylistMeta> = new EventEmitter();
/** Emits on playlist remove click */
@Output() removeClicked: EventEmitter<string> = new EventEmitter();
/** Emits on playlist edit click */
@Output() editPlaylistClicked: EventEmitter<PlaylistMeta> =
new EventEmitter();
}
@@ -6,7 +6,10 @@
{{ 'HOME.PLAYLISTS.ADD_FIRST' | translate }}
</div>
</mat-list-item>
<mat-list-item *ngIf="playlists.length > 0" (click)="playlistClicked.emit('GLOBAL_FAVORITES')">
<mat-list-item
*ngIf="playlists.length > 0"
(click)="getPlaylist('GLOBAL_FAVORITES')"
>
<mat-icon class="favorites-icon" mat-list-icon>star</mat-icon>
<div mat-line>Global favorites</div>
<div mat-line class="meta">
@@ -15,74 +18,12 @@
<mat-divider></mat-divider>
</mat-list-item>
<mat-list-item
cdkDrag
<app-playlist-item
*ngFor="let item of playlists; last as last"
(click)="playlistClicked.emit(item._id)"
>
<mat-icon mat-list-icon cdkDragHandle class="drag-icon"
>drag_indicator</mat-icon
>
<mat-icon
mat-list-icon
*ngIf="item?.url"
[matTooltip]="
('HOME.PLAYLISTS.ADDED_VIA_URL' | translate) + ' ' + item?.url
"
>cloud</mat-icon
>
<mat-icon
mat-list-icon
*ngIf="!item?.url"
[matTooltip]="'HOME.PLAYLISTS.ADDED_VIA_FILE' | translate"
>folder</mat-icon
>
<div mat-line>{{ item.title || item.filename }}</div>
<div mat-line class="meta">
{{ 'HOME.PLAYLISTS.CHANNELS' | translate }}: {{ item.count }}
<ng-container *ngIf="item.importDate?.length > 3">
| {{ 'HOME.PLAYLISTS.ADDED' | translate }}:
{{ item.importDate | date }}
</ng-container>
<ng-container *ngIf="item.updateDate">
| {{ 'HOME.PLAYLISTS.UPDATED' | translate }}:
{{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }}
</ng-container>
<ng-container *ngIf="item.updateState === 2">
<mat-icon
[matTooltip]="
'HOME.PLAYLISTS.INFO_DIALOG.UPDATE_FAILED' | translate
"
[inline]="true"
>warning</mat-icon
>
</ng-container>
</div>
<button
*ngIf="item.url || item.filePath"
mat-icon-button
color="accent"
(click)="$event.stopPropagation(); refreshClicked.emit(item)"
[matTooltip]="'HOME.PLAYLISTS.REFRESH' | translate"
>
<mat-icon>sync</mat-icon>
</button>
<button
mat-icon-button
color="accent"
(click)="$event.stopPropagation(); openInfoDialog(item)"
[matTooltip]="'HOME.PLAYLISTS.SHOW_DETAILS' | translate"
>
<mat-icon>edit</mat-icon>
</button>
<button
mat-icon-button
color="accent"
(click)="$event.stopPropagation(); removeClicked.emit(item._id)"
[matTooltip]="'HOME.PLAYLISTS.REMOVE' | translate"
>
<mat-icon>delete</mat-icon>
</button>
<mat-divider *ngIf="!last"></mat-divider>
</mat-list-item>
[item]="item"
(editPlaylistClicked)="openInfoDialog($event)"
(playlistClicked)="getPlaylist($event)"
(refreshClicked)="refreshPlaylist($event)"
(removeClicked)="removeClicked($event)"
></app-playlist-item>
</mat-nav-list>
@@ -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;
}
@@ -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<RecentPlaylistsComponent>;
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,
}
);
});
});
@@ -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<string> = new EventEmitter();
/** Emits on playlist refresh click */
@Output() refreshClicked: EventEmitter<PlaylistMeta> = new EventEmitter();
/** Emits on playlist remove click */
@Output() removeClicked: EventEmitter<string> = new EventEmitter();
/** IPC Renderer commands list with callbacks */
commandsList = [
new IpcCommand(
PLAYLIST_GET_ALL_RESPONSE,
(response: { payload: Partial<PlaylistMeta[]> }) => {
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 }),
});
}
}
@@ -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';
@@ -15,10 +15,7 @@
>
</app-channel-list-container>
<ng-template #playlistsTemplate>
<app-recent-playlists
[playlists]="playlists"
(playlistClicked)="getPlaylist($event)"
>
<app-recent-playlists (playlistClicked)="getPlaylist($event)">
</app-recent-playlists>
</ng-template>
</ng-container>
@@ -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 => {
+3 -1
View File
@@ -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 {}
+18 -31
View File
@@ -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"]
}