feat(pwa): auto-generate playlist with global favorites

This commit is contained in:
4gray committed 2022-01-24 00:31:42 +01:00
1 parent 7e7e9391f6
commit 98ff7f4fa4
5 files changed
+168 -35

No files matched your search

@@ -1,11 +1,20 @@
<mat-nav-list cdkDropList (cdkDropListDropped)="drop($event)">
<mat-list-item *ngIf="playlists.length === 0">
<mat-icon mat-list-icon>cloud</mat-icon>
<mat-icon class="favorites-icon" mat-list-icon>search</mat-icon>
<div mat-line>{{ 'HOME.PLAYLISTS.NO_PLAYLISTS' | translate }}</div>
<div mat-line class="meta">
{{ 'HOME.PLAYLISTS.ADD_FIRST' | translate }}
</div>
</mat-list-item>
<mat-list-item *ngIf="playlists.length > 0" (click)="playlistClicked.emit('GLOBAL_FAVORITES')">
<mat-icon class="favorites-icon" mat-list-icon>star</mat-icon>
<div mat-line>Global favorites</div>
<div mat-line class="meta">
Auto-generated playlist with aggregated favorites from all playlists
</div>
<mat-divider></mat-divider>
</mat-list-item>
<mat-list-item
cdkDrag
*ngFor="let item of playlists; last as last"
@@ -57,3 +57,7 @@ mat-nav-list {
cursor: move;
margin-left: -20px;
}
.favorites-icon {
margin-left: 16px;
}
@@ -99,7 +99,7 @@
</mat-nav-list>
</mat-tab>
<mat-tab [label]="'CHANNELS.FAVORITES' | translate">
<mat-tab *ngIf="playlistId !== 'GLOBAL_FAVORITES'" [label]="'CHANNELS.FAVORITES' | translate">
<mat-nav-list
*ngIf="favorites$ | async as favorites"
id="favorites-list"
@@ -5,11 +5,10 @@ import {
Input,
ViewChild,
} from '@angular/core';
import { Channel, ChannelStore, ChannelQuery } from '../../../state';
import * as _ from 'lodash';
import { MatSnackBar } from '@angular/material/snack-bar';
import * as _ from 'lodash';
import { Observable } from 'rxjs';
import { Channel, ChannelQuery, ChannelStore } from '../../../state';
@Component({
selector: 'app-channel-list-container',
@@ -61,6 +60,9 @@ export class ChannelListContainerComponent {
}
}
/** ID of the current playlist */
playlistId: string;
/**
* Creates an instance of ChannelListContainerComponent
* @param channelQuery akita's channel query
@@ -71,7 +73,9 @@ export class ChannelListContainerComponent {
private channelQuery: ChannelQuery,
private channelStore: ChannelStore,
private snackBar: MatSnackBar
) {}
) {
this.playlistId = this.channelQuery.getValue().playlistId;
}
/**
* Sets clicked channel as selected and emits them to the parent component
+145 -29
View File
@@ -1,10 +1,12 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { guid } from '@datorama/akita';
import { Channel } from 'diagnostics_channel';
import { parse } from 'iptv-playlist-parser';
import { NgxIndexedDBService } from 'ngx-indexed-db';
import { switchMap } from 'rxjs';
import { catchError, combineLatest, map, switchMap, throwError } from 'rxjs';
import {
ERROR,
PLAYLIST_GET_ALL,
PLAYLIST_GET_ALL_RESPONSE,
PLAYLIST_GET_BY_ID,
@@ -16,6 +18,7 @@ import {
PLAYLIST_SAVE_DETAILS,
PLAYLIST_UPDATE,
PLAYLIST_UPDATE_FAVORITES,
PLAYLIST_UPDATE_POSITIONS,
} from '../../../shared/ipc-commands';
import {
Playlist,
@@ -26,12 +29,19 @@ import { ParsedPlaylist } from '../../typings';
import { DbStores } from '../indexed-db.config';
import { DataService } from './data.service';
/**
* Id of the channel with favorite channels aggregated from all added playlists
*/
export const GLOBAL_FAVORITES_PLAYLIST_ID = 'GLOBAL_FAVORITES';
@Injectable({
providedIn: 'root',
})
export class PwaService extends DataService {
/** Proxy URL to avoid CORS issues */
corsProxyUrl = 'https://api.codetabs.com/v1/proxy?quest=';
corsProxyUrl = AppConfig.production
? 'https://iptvnator-playlist-parser-api.vercel.app/parse?url='
: 'http://localhost:3000/parse?url=';
/**
* Creates an instance of PwaService.
@@ -53,6 +63,81 @@ export class PwaService extends DataService {
return AppConfig.version;
}
/**
* Aggregates favorite channels as objects from all available playlists
* @param playlists all available playlists
* @returns favorite channels
*/
aggregateFavoriteChannels(playlists: Playlist[]): Channel[] {
const favorites = [];
playlists.forEach((playlist) => {
if (playlist.favorites?.length > 0) {
playlist.playlist.items.forEach((channel) => {
if (playlist.favorites.includes(channel.id)) {
favorites.push(channel);
}
});
}
});
return favorites;
}
/**
* Creates a simplified playlist object which is used for global favorites
* @param channels channels list
* @returns simplified playlist object
*/
createFavoritesPlaylist(channels: Channel[]): Partial<Playlist> {
return {
id: GLOBAL_FAVORITES_PLAYLIST_ID,
_id: GLOBAL_FAVORITES_PLAYLIST_ID,
count: channels.length,
playlist: {
items: channels,
},
};
}
/**
* Returns the count of favorite channels from all playlists
*/
getGlobalFavoritesCount() {
return this.dbService.getAll(DbStores.Playlists).pipe(
map((playlists: Playlist[]) => {
let count = 0;
playlists.forEach((playlist) => {
if (playlist.favorites.length > 0) {
count = count + playlist.favorites.length;
}
});
return count;
})
);
}
/**
* Sends a message with playlist that contains favorite channels from all available playlists
*/
sendPlaylistWithGlobalFavorites() {
this.dbService
.getAll(DbStores.Playlists)
.pipe(
map((playlists: Playlist[]) => {
const favoriteChannels =
this.aggregateFavoriteChannels(playlists);
const favPlaylist =
this.createFavoritesPlaylist(favoriteChannels);
return favPlaylist;
})
)
.subscribe((playlist) => {
window.postMessage({
type: PLAYLIST_PARSE_RESPONSE,
payload: playlist,
});
});
}
/**
* Handles incoming IPC commands
* @param type ipc command type
@@ -80,14 +165,18 @@ export class PwaService extends DataService {
payload: playlistObject,
});
} else if (type === PLAYLIST_GET_BY_ID) {
this.dbService
.getByIndex(DbStores.Playlists, '_id', payload.id)
.subscribe((playlist) => {
window.postMessage({
type: PLAYLIST_PARSE_RESPONSE,
payload: playlist,
if (payload.id === GLOBAL_FAVORITES_PLAYLIST_ID) {
this.sendPlaylistWithGlobalFavorites();
} else {
this.dbService
.getByIndex(DbStores.Playlists, '_id', payload.id)
.subscribe((playlist) => {
window.postMessage({
type: PLAYLIST_PARSE_RESPONSE,
payload: playlist,
});
});
});
}
} else if (type === PLAYLIST_REMOVE_BY_ID) {
this.dbService
.delete(DbStores.Playlists, payload.id)
@@ -98,12 +187,16 @@ export class PwaService extends DataService {
});
});
} else if (type === PLAYLIST_GET_ALL) {
this.dbService.getAll(DbStores.Playlists).subscribe((playlists) => {
window.postMessage({
type: PLAYLIST_GET_ALL_RESPONSE,
payload: playlists,
this.dbService
.getAll(DbStores.Playlists)
.subscribe((playlists: Playlist[]) => {
window.postMessage({
type: PLAYLIST_GET_ALL_RESPONSE,
payload: playlists.sort(
(a, b) => a.position - b.position
),
});
});
});
} else if (type === PLAYLIST_SAVE_DETAILS) {
this.dbService
.getByID(DbStores.Playlists, payload._id)
@@ -142,6 +235,14 @@ export class PwaService extends DataService {
.get(`${this.corsProxyUrl}${payload.url}`, {
responseType: 'text',
})
.pipe(
catchError((error) => {
window.postMessage({
type: ERROR,
});
return throwError(() => error);
})
)
.subscribe((response: any) => {
const refreshedPlaylist =
this.convertFileStringToPlaylist(response);
@@ -171,6 +272,23 @@ export class PwaService extends DataService {
});
});
});
} else if (type === PLAYLIST_UPDATE_POSITIONS) {
const requests = payload.map((playlist, index) => {
return this.dbService
.getByID(DbStores.Playlists, playlist._id)
.pipe(
switchMap((playlist: Playlist) => {
return this.dbService.update(DbStores.Playlists, {
...playlist,
position: index,
});
})
);
});
combineLatest(requests).subscribe(() => {
console.log('playlist positions were updated...');
});
}
}
@@ -180,29 +298,27 @@ export class PwaService extends DataService {
*/
fetchFromUrl(payload: Partial<Playlist>): void {
this.http
.get(`${this.corsProxyUrl}${payload.url}`, {
responseType: 'text',
})
.get(`${this.corsProxyUrl}${payload.url}`)
.pipe(
catchError((error) => {
window.postMessage({
type: ERROR,
message: 'something went wrong',
status: error.status,
});
return throwError(() => error);
})
)
.subscribe((response: any) => {
const parsedPlaylist =
this.convertFileStringToPlaylist(response);
const playlistObject = this.createPlaylistObject(
payload.title,
parsedPlaylist,
payload.url,
'URL'
);
// save to db
this.dbService
.add(DbStores.Playlists, playlistObject)
.add(DbStores.Playlists, response)
.subscribe(() => {
console.log('playlist was added...');
});
window.postMessage({
type: PLAYLIST_PARSE_RESPONSE,
payload: playlistObject,
payload: response,
});
});
}