mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat: switch playlists from the sidebar
This commit is contained in:
1 parent
220b4204f8
commit
0bc71d4791
7 files changed
+186
-55
No files matched your search
@@ -328,6 +328,7 @@ export class Api {
|
||||
count: 1,
|
||||
title: 1,
|
||||
_id: 1,
|
||||
id: 1,
|
||||
url: 1,
|
||||
importDate: 1,
|
||||
userAgent: 1,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Component, NgZone } from '@angular/core';
|
||||
import { UploadFile } from 'ngx-uploader';
|
||||
import { ChannelStore, createChannel } from '../state';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Playlist } from '../../../shared/playlist.interface';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { UploadFile } from 'ngx-uploader';
|
||||
import {
|
||||
ERROR,
|
||||
PLAYLIST_GET_ALL,
|
||||
@@ -18,8 +16,10 @@ import {
|
||||
PLAYLIST_UPDATE,
|
||||
PLAYLIST_UPDATE_RESPONSE,
|
||||
} from '../../../shared/ipc-commands';
|
||||
import { DialogService } from './../services/dialog.service';
|
||||
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<
|
||||
@@ -51,8 +51,10 @@ export class HomeComponent {
|
||||
commandsList = [
|
||||
{
|
||||
id: PLAYLIST_PARSE_RESPONSE,
|
||||
execute: (response: { payload: Playlist }): void =>
|
||||
this.setPlaylist(response.payload),
|
||||
execute: (response: { payload: Playlist }): void => {
|
||||
this.channelStore.setPlaylist(response.payload);
|
||||
this.navigateToPlayer();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: PLAYLIST_GET_ALL_RESPONSE,
|
||||
@@ -97,9 +99,9 @@ export class HomeComponent {
|
||||
*/
|
||||
constructor(
|
||||
private electronService: DataService,
|
||||
private channelStore: ChannelStore,
|
||||
private dialogService: DialogService,
|
||||
private ngZone: NgZone,
|
||||
private channelStore: ChannelStore,
|
||||
private router: Router,
|
||||
private snackBar: MatSnackBar,
|
||||
private translate: TranslateService
|
||||
@@ -175,26 +177,6 @@ export class HomeComponent {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given playlist as active for the current session
|
||||
* @param playlist playlist object
|
||||
*/
|
||||
setPlaylist(playlist: Playlist): void {
|
||||
this.channelStore.remove();
|
||||
const favorites = playlist.favorites || [];
|
||||
const channels = playlist.playlist.items.map((element) =>
|
||||
createChannel(element)
|
||||
);
|
||||
this.channelStore.upsertMany(channels);
|
||||
this.channelStore.update((store) => ({
|
||||
...store,
|
||||
active: undefined,
|
||||
favorites,
|
||||
playlistId: playlist.id,
|
||||
}));
|
||||
this.navigateToPlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers on remove click
|
||||
* @param playlistId playlist id to remove
|
||||
|
||||
+31
-13
@@ -1,3 +1,29 @@
|
||||
<div id="playlists-navigation">
|
||||
<div class="current-playlist">
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="showPlaylists()"
|
||||
matTooltip="Back to playlists overview"
|
||||
>
|
||||
<mat-icon>arrow_back_ios</mat-icon>
|
||||
</button>
|
||||
<div class="playlist-info">
|
||||
<div class="name">{{ playlistMeta?.filename }}</div>
|
||||
<div class="channels-count">{{ playlistMeta?.count }} channels</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
routerLink="/"
|
||||
[matTooltip]="
|
||||
'CHANNELS.UPLOAD_OR_SELECT_OTHER_PLAYLIST' | translate
|
||||
"
|
||||
>
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-tab-group color="primary" backgroundColor="primary">
|
||||
<mat-tab [label]="'CHANNELS.ALL_CHANNELS' | translate">
|
||||
<mat-list>
|
||||
@@ -6,20 +32,9 @@
|
||||
class="full-width search-bar"
|
||||
type="search"
|
||||
#search
|
||||
placeholder="{{ 'CHANNELS.SEARCH_CHANNEL' | translate }} ({{
|
||||
_channelList?.length
|
||||
}})"
|
||||
[placeholder]="'CHANNELS.SEARCH_CHANNEL' | translate"
|
||||
[(ngModel)]="searchTerm.name"
|
||||
/>
|
||||
<button
|
||||
mat-icon-button
|
||||
routerLink="/"
|
||||
[matTooltip]="
|
||||
'CHANNELS.UPLOAD_OR_SELECT_OTHER_PLAYLIST' | translate
|
||||
"
|
||||
>
|
||||
<mat-icon>create_new_folder</mat-icon>
|
||||
</button>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
<mat-divider></mat-divider>
|
||||
@@ -99,7 +114,10 @@
|
||||
</mat-nav-list>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab *ngIf="playlistId !== 'GLOBAL_FAVORITES'" [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"
|
||||
|
||||
+29
-1
@@ -11,7 +11,7 @@
|
||||
}
|
||||
|
||||
.scroll-viewport {
|
||||
min-height: calc(100vh - 142px);
|
||||
min-height: calc(100vh - 165px);
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
@@ -78,3 +78,31 @@
|
||||
background-size: 10px 10px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#playlists-navigation {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
|
||||
.playlist-info {
|
||||
flex: 1;
|
||||
max-width: 300px;
|
||||
|
||||
.name {
|
||||
font-size: 1.1em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.channels-count {
|
||||
font-size: 0.8em;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.current-playlist {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import * as _ from 'lodash';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Channel } from '../../../../../shared/channel.interface';
|
||||
import { Playlist } from '../../../../../shared/playlist.interface';
|
||||
import { ChannelQuery, ChannelStore } from '../../../state';
|
||||
|
||||
@Component({
|
||||
@@ -32,6 +35,15 @@ export class ChannelListContainerComponent {
|
||||
this.groupedChannels = _.groupBy(value, 'group.title');
|
||||
}
|
||||
|
||||
/** Meta information about current playlist */
|
||||
playlistMeta: Pick<Playlist, 'count' | 'filename'> = {
|
||||
count: this.channelQuery.getValue().playlistCount,
|
||||
filename: this.channelQuery.getValue().playlistFilename,
|
||||
};
|
||||
|
||||
/** Toggles the sidebar view from channels to playlists */
|
||||
@Output() changeView = new EventEmitter<any>();
|
||||
|
||||
/** Object with channels sorted by groups */
|
||||
groupedChannels: { [key: string]: Channel[] };
|
||||
|
||||
@@ -106,4 +118,11 @@ export class ChannelListContainerComponent {
|
||||
trackByFn(index: number, channel: Channel): string {
|
||||
return channel.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits event to the parent to toggle to the playlists overview
|
||||
*/
|
||||
showPlaylists() {
|
||||
this.changeView.emit('PLAYLISTS');
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,19 @@
|
||||
disableClose
|
||||
>
|
||||
<ng-container *ngIf="channels$ | async as channels">
|
||||
<app-channel-list-container [channelList]="channels">
|
||||
<app-channel-list-container
|
||||
*ngIf="sidebarView === 'CHANNELS'; else playlistsTemplate"
|
||||
[channelList]="channels"
|
||||
(changeView)="setSidebarView($event)"
|
||||
>
|
||||
</app-channel-list-container>
|
||||
<ng-template #playlistsTemplate>
|
||||
<app-recent-playlists
|
||||
[playlists]="playlists"
|
||||
(playlistClicked)="getPlaylist($event)"
|
||||
>
|
||||
</app-recent-playlists>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</mat-drawer>
|
||||
|
||||
@@ -32,13 +43,13 @@
|
||||
>
|
||||
<mat-icon>
|
||||
{{
|
||||
(favorites$ | async).includes(activeChannel.id)
|
||||
(favorites$ | async).includes(activeChannel?.id)
|
||||
? 'star'
|
||||
: 'star_outline'
|
||||
}}</mat-icon
|
||||
>
|
||||
</button>
|
||||
{{ activeChannel.name }}
|
||||
{{ activeChannel?.name }}
|
||||
<div class="spacer"></div>
|
||||
<button
|
||||
*ngIf="epgAvailable$ | async"
|
||||
@@ -56,7 +67,7 @@
|
||||
[options]="{
|
||||
sources: [
|
||||
{
|
||||
src: activeChannel.url + activeChannel.epgParams,
|
||||
src: activeChannel?.url + activeChannel?.epgParams,
|
||||
type: 'application/x-mpegURL'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { Component, NgZone, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatSidenav } from '@angular/material/sidenav';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
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';
|
||||
import { Playlist } from '../../../../../shared/playlist.interface';
|
||||
import { DataService } from '../../../services/data.service';
|
||||
import { Settings, VideoPlayer } from '../../../settings/settings.interface';
|
||||
import { STORE_KEY } from '../../../shared/enums/store-keys.enum';
|
||||
import { ChannelQuery, ChannelStore } from '../../../state';
|
||||
import { EpgProgram } from './../../models/epg-program.model';
|
||||
|
||||
/** Possible sidebar view options */
|
||||
type SidebarView = 'CHANNELS' | 'PLAYLISTS';
|
||||
|
||||
@Component({
|
||||
selector: 'app-video-player',
|
||||
templateUrl: './video-player.component.html',
|
||||
@@ -19,14 +29,11 @@ export class VideoPlayerComponent implements OnInit {
|
||||
/** Active selected channel */
|
||||
activeChannel$: Observable<Channel> = this.channelQuery
|
||||
.select((state) => state.active)
|
||||
.pipe(tap((channel) => (this.channelTitle = channel?.name)));
|
||||
.pipe(filter((channel) => Boolean(channel)));
|
||||
|
||||
/** Channels list */
|
||||
channels$: Observable<Channel[]> = this.channelQuery.selectAll();
|
||||
|
||||
/** Name of the selected channel */
|
||||
channelTitle: string;
|
||||
|
||||
/** EPG availability flag */
|
||||
epgAvailable$: Observable<boolean> = this.channelQuery.select(
|
||||
(store) => store.epgAvailable
|
||||
@@ -48,12 +55,35 @@ export class VideoPlayerComponent implements OnInit {
|
||||
showCaptions: false,
|
||||
};
|
||||
|
||||
/** Playlists array */
|
||||
playlists = [];
|
||||
|
||||
/** Sidebar object */
|
||||
@ViewChild('sidenav') sideNav: MatSidenav;
|
||||
|
||||
/** ID of the current playlist */
|
||||
playlistId = this.channelQuery.getValue().playlistId;
|
||||
|
||||
/** 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 => {
|
||||
this.channelStore.setPlaylist(response.payload);
|
||||
this.setSidebarView('CHANNELS');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/** Current sidebar view */
|
||||
sidebarView: SidebarView = 'CHANNELS';
|
||||
|
||||
/**
|
||||
* Creates an instance of VideoPlayerComponent
|
||||
* @param channelQuery akita's channel query
|
||||
@@ -64,15 +94,39 @@ export class VideoPlayerComponent implements OnInit {
|
||||
constructor(
|
||||
private channelQuery: ChannelQuery,
|
||||
private channelStore: ChannelStore,
|
||||
private dataService: DataService,
|
||||
private ngZone: NgZone,
|
||||
private snackBar: MatSnackBar,
|
||||
private storage: StorageMap
|
||||
) {}
|
||||
) {
|
||||
this.dataService.sendIpcEvent(PLAYLIST_GET_ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets video player and subscribes to channel list from the store
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.applySettings();
|
||||
this.setRendererListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set electrons main process listeners
|
||||
*/
|
||||
setRendererListeners(): void {
|
||||
this.commandsList.forEach((command) => {
|
||||
if (this.dataService.isElectron) {
|
||||
this.dataService.listenOn(command.id, (event, response) =>
|
||||
this.ngZone.run(() => command.execute(response))
|
||||
);
|
||||
} else {
|
||||
this.dataService.listenOn(command.id, (response) => {
|
||||
if (response.data.type === command.id) {
|
||||
command.execute(response.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,4 +158,22 @@ export class VideoPlayerComponent implements OnInit {
|
||||
this.snackBar.open('Favorites were updated!', null, { duration: 2000 });
|
||||
this.channelStore.updateFavorite(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the sidebar view to the specified value
|
||||
* @param view view to change
|
||||
*/
|
||||
setSidebarView(view: SidebarView) {
|
||||
this.sidebarView = view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests playlist by id
|
||||
* @param playlistId playlist id
|
||||
*/
|
||||
getPlaylist(playlistId: string): void {
|
||||
this.dataService.sendIpcEvent(PLAYLIST_GET_BY_ID, {
|
||||
id: playlistId,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user