mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
refactor: code improvements, bugfixing
This commit is contained in:
1 parent
d3e3c349a1
commit
973f6f64ba
13 files changed
+161
-58
No files matched your search
@@ -16,6 +16,9 @@ import { MaterialModule } from './material.module';
|
||||
import { RecentPlaylistsComponent } from './components/recent-playlists/recent-playlists.component';
|
||||
import { FilterPipeModule } from 'ngx-filter-pipe';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NgxIndexedDBModule } from 'ngx-indexed-db';
|
||||
import { dbConfig } from './db.config';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -33,6 +36,7 @@ import { FormsModule } from '@angular/forms';
|
||||
FilterPipeModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
NgxIndexedDBModule.forRoot(dbConfig),
|
||||
MaterialModule,
|
||||
NgxUploaderModule,
|
||||
ServiceWorkerModule.register('ngsw-worker.js', {
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mat-list {
|
||||
margin-top: 10px;
|
||||
#top-panel {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
#channels-list {
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 65px);
|
||||
}
|
||||
|
||||
.active {
|
||||
background: #ddd;
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
<mat-list>
|
||||
<mat-list id="top-panel">
|
||||
<mat-list-item>
|
||||
<mat-form-field class="full-width">
|
||||
<input matInput placeholder="Search channel" [(ngModel)]="searchTerm.inf.title">
|
||||
<input matInput placeholder="Search channel {{channelList?.length}}" [(ngModel)]="searchTerm.inf.title">
|
||||
</mat-form-field>
|
||||
</mat-list-item>
|
||||
<mat-list-item *ngFor=" let channel of channelList | filterBy: searchTerm">
|
||||
<button mat-button (click)="changeChannel.emit({ url: channel.url, title: channel.inf.title})">
|
||||
{{channel?.inf?.title || 'Unnamed Channel'}}
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button mat-icon-button [routerLink]="'/'" matTooltip="Upload or select other playlist"><mat-icon>create_new_folder</mat-icon></button>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
<mat-nav-list id="channels-list">
|
||||
<mat-list-item *ngFor=" let channel of channelList | filterBy: searchTerm; index as i" [class.active]="selected?.id === channel.id" (click)="selectChannel(channel)">
|
||||
{{i+1 + '. ' + channel?.inf?.title || 'Unnamed Channel'}}
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Channel } from 'src/app/state';
|
||||
|
||||
@Component({
|
||||
selector: 'app-channel-list-container',
|
||||
@@ -9,7 +10,12 @@ export class ChannelListContainerComponent {
|
||||
/**
|
||||
* Channels array
|
||||
*/
|
||||
@Input() channelList;
|
||||
@Input() channelList: Channel[];
|
||||
|
||||
/**
|
||||
* Selected channel
|
||||
*/
|
||||
selected: Channel;
|
||||
|
||||
/**
|
||||
* Emits on channel change
|
||||
@@ -27,4 +33,13 @@ export class ChannelListContainerComponent {
|
||||
title: '',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets clicked channel as selected and emits them to the parent component
|
||||
* @param channel selected channel
|
||||
*/
|
||||
selectChannel(channel: Channel): void {
|
||||
this.selected = channel;
|
||||
this.changeChannel.emit({ url: channel.url, title: channel.inf.title});
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
:host {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.drop-container {
|
||||
height: 200px;
|
||||
border: 2px dashed #ccc;
|
||||
@@ -8,7 +14,7 @@
|
||||
color: #666;
|
||||
}
|
||||
|
||||
input {
|
||||
#file input {
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
padding: 5px;
|
||||
@@ -18,7 +24,7 @@ input {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.inputfile {
|
||||
.input-file {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
@@ -27,7 +33,7 @@ input {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.inputfile + label {
|
||||
.input-file + label {
|
||||
font-size: 1.25em;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
@@ -35,12 +41,12 @@ input {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.inputfile:focus + label,
|
||||
.inputfile + label:hover {
|
||||
.input-file:focus + label,
|
||||
.input-file + label:hover {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.inputfile + label {
|
||||
.input-file + label {
|
||||
cursor: pointer;
|
||||
line-height: 35px;
|
||||
padding: 3px 10px;
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
<mat-card *ngIf="(playlists).length > 0">
|
||||
<mat-card-content>
|
||||
<app-recent-playlists [playlists]="playlists" (playlistClicked)="setPlaylist($event)" (removeClicked)="removePlaylist($event)">
|
||||
</app-recent-playlists>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<br />
|
||||
<mat-card>
|
||||
<mat-card-subtitle>Add playlist</mat-card-subtitle>
|
||||
<div class="drop-container" ngFileDrop [options]="options" (uploadOutput)="onUploadOutput($event)"
|
||||
@@ -9,20 +16,11 @@
|
||||
Drag & drop files here
|
||||
</p>
|
||||
<p>or</p>
|
||||
<input type="file" ngFileSelect [options]="options" (uploadOutput)="onUploadOutput($event)" class="inputfile"
|
||||
<input type="file" ngFileSelect [options]="options" (uploadOutput)="onUploadOutput($event)" class="input-file"
|
||||
name="file" id="file" [uploadInput]="uploadInput" multiple>
|
||||
<label for="file">
|
||||
Choose a file
|
||||
</label>
|
||||
</div>
|
||||
</mat-card>
|
||||
<br>
|
||||
<ng-container *ngIf="(playlists | keyvalue)?.length > 0">
|
||||
<mat-card>
|
||||
<mat-card-subtitle>Recent playlists</mat-card-subtitle>
|
||||
<mat-card-content>
|
||||
<app-recent-playlists [playlists]="playlists" (playlistClicked)="setPlaylist($event)">
|
||||
</app-recent-playlists>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</ng-container>
|
||||
<mat-progress-bar *ngIf="isLoading" mode="indeterminate"></mat-progress-bar>
|
||||
@@ -9,6 +9,21 @@ import {
|
||||
import { ChannelStore, createChannel } from 'src/app/state';
|
||||
import { M3uService } from 'src/app/services/m3u-service.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgxIndexedDBService } from 'ngx-indexed-db';
|
||||
import { PLAYLISTS_STORE } from 'src/app/db.config';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
|
||||
/**
|
||||
* Describes playlist interface
|
||||
*/
|
||||
export interface Playlist {
|
||||
id?: number;
|
||||
title: string;
|
||||
filename: string;
|
||||
playlist: any;
|
||||
importDate: number;
|
||||
lastUsage: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-playlist-uploader',
|
||||
@@ -22,18 +37,22 @@ export class PlaylistUploaderComponent {
|
||||
humanizeBytes: Function;
|
||||
dragOver: boolean;
|
||||
options: UploaderOptions;
|
||||
playlists: any;
|
||||
playlists: Playlist[] = [];
|
||||
isLoading = false;
|
||||
|
||||
/**
|
||||
* Creates an instanceof PlaylistUploaderComponent
|
||||
* @param channelStore channels store
|
||||
* @param dbService indexeddb service
|
||||
* @param m3uService m3u service
|
||||
* @param router angulars router
|
||||
*/
|
||||
constructor(
|
||||
private channelStore: ChannelStore,
|
||||
private dbService: NgxIndexedDBService,
|
||||
private m3uService: M3uService,
|
||||
private router: Router
|
||||
private router: Router,
|
||||
private snackBar: MatSnackBar
|
||||
) {
|
||||
this.getPlaylists();
|
||||
|
||||
@@ -52,6 +71,7 @@ export class PlaylistUploaderComponent {
|
||||
*/
|
||||
onUploadOutput(output: UploadOutput): void {
|
||||
if (output.type === 'allAddedToQueue') {
|
||||
this.isLoading = true;
|
||||
if (this.files.length > 0) {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = fileLoadedEvent => {
|
||||
@@ -64,7 +84,7 @@ export class PlaylistUploaderComponent {
|
||||
);
|
||||
this.savePlaylist(
|
||||
this.files[0].name,
|
||||
JSON.stringify(playlist)
|
||||
playlist
|
||||
);
|
||||
|
||||
this.setPlaylist(playlist);
|
||||
@@ -108,6 +128,7 @@ export class PlaylistUploaderComponent {
|
||||
* Navigates to the video player route
|
||||
*/
|
||||
navigateToPlayer(): void {
|
||||
this.isLoading = false;
|
||||
this.router.navigateByUrl('/iptv', { skipLocationChange: true });
|
||||
}
|
||||
|
||||
@@ -117,21 +138,21 @@ export class PlaylistUploaderComponent {
|
||||
* @param playlist playlist to save
|
||||
*/
|
||||
savePlaylist(name: string, playlist: any): void {
|
||||
localStorage.setItem(name, playlist);
|
||||
this.dbService.add<Playlist>('playlists', { filename: name, title: name, playlist, importDate: new Date().getMilliseconds(), lastUsage: new Date().getMilliseconds() }).then(() => {
|
||||
console.log('playlist saved!')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all saved playlists from the localStorage
|
||||
* Reads all saved playlists from the browser store
|
||||
*/
|
||||
getPlaylists(): void {
|
||||
this.playlists = { ...localStorage };
|
||||
|
||||
this.playlists = Object.keys(this.playlists)
|
||||
.filter(key => key.includes('.m3u'))
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = JSON.parse(this.playlists[key]);
|
||||
return obj;
|
||||
}, {});
|
||||
this.dbService.getAll('playlists').then(
|
||||
(playlists: Playlist[]) => this.playlists = playlists,
|
||||
error => {
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,9 +160,19 @@ export class PlaylistUploaderComponent {
|
||||
* @param playlist m3u playlist
|
||||
*/
|
||||
setPlaylist(playlist: any): void {
|
||||
playlist.segments.forEach(element => {
|
||||
this.channelStore.add(createChannel(element));
|
||||
this.navigateToPlayer();
|
||||
});
|
||||
this.channelStore.reset();
|
||||
const channels = playlist.segments.map(element => createChannel(element));
|
||||
this.channelStore.upsertMany(channels);
|
||||
this.navigateToPlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the provided playlist from the indexedDb
|
||||
* @param playlist playlist to remove
|
||||
*/
|
||||
removePlaylist(playlist: Playlist) {
|
||||
this.dbService.delete(PLAYLISTS_STORE, playlist.id);
|
||||
this.getPlaylists();
|
||||
this.snackBar.open('Done! Playlist was removed.', null,{ duration: 2000 });
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,15 @@
|
||||
<button *ngFor="let playlist of playlists | keyvalue" mat-stroked-button (click)="playlistClicked.emit(playlist.value)">
|
||||
{{playlist.key}}
|
||||
</button>
|
||||
<mat-nav-list>
|
||||
<div mat-subheader>Recently added playlists</div>
|
||||
<a mat-list-item *ngFor="let item of playlists; last as last" (click)="playlistClicked.emit(item.playlist);">
|
||||
<mat-icon mat-list-icon>folder</mat-icon>
|
||||
<div mat-line>{{ item.title || item.filename }}</div>
|
||||
<div mat-line>Channels: {{ item.playlist?.segments.length }}</div>
|
||||
<!--<button mat-icon-button color="primary" (click)="$event.stopPropagation(); renameClicked.emit(item)" matTooltip="Rename playlist">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>-->
|
||||
<button mat-icon-button color="primary" (click)="$event.stopPropagation(); removeClicked.emit(item)" matTooltip="Remove playlist">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
<mat-divider *ngIf="!last"></mat-divider>
|
||||
</a>
|
||||
</mat-nav-list>
|
||||
@@ -1,3 +1,3 @@
|
||||
button {
|
||||
margin: 5px;
|
||||
}
|
||||
mat-list-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Playlist } from '../playlist-uploader/playlist-uploader.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-recent-playlists',
|
||||
@@ -6,13 +7,15 @@ import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
styleUrls: ['./recent-playlists.component.scss'],
|
||||
})
|
||||
export class RecentPlaylistsComponent {
|
||||
/**
|
||||
* Playlist object
|
||||
*/
|
||||
@Input() playlists: any;
|
||||
/** All available playlists */
|
||||
@Input() playlists: Playlist[];
|
||||
|
||||
/**
|
||||
* Emits on playlist click
|
||||
*/
|
||||
/** Emits on playlist selection */
|
||||
@Output() playlistClicked: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
/** Emits on playlist remove click */
|
||||
@Output() removeClicked: EventEmitter<Playlist> = new EventEmitter();
|
||||
|
||||
/** Emits on playlist rename click */
|
||||
@Output() renameClicked: EventEmitter<Playlist> = new EventEmitter();
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
<mat-drawer
|
||||
#drawer
|
||||
mode="side"
|
||||
opened
|
||||
(keydown.escape)="close()"
|
||||
disableClose>
|
||||
<ng-container *ngIf="channels$ | async as channels">
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { DBConfig } from 'ngx-indexed-db';
|
||||
|
||||
export const PLAYLISTS_STORE = 'playlists';
|
||||
|
||||
export const dbConfig: DBConfig = {
|
||||
name: 'MyIptvDb',
|
||||
version: 1,
|
||||
objectStoresMeta: [{
|
||||
store: PLAYLISTS_STORE,
|
||||
storeConfig: { keyPath: 'id', autoIncrement: true },
|
||||
storeSchema: [
|
||||
{ name: 'title', keypath: 'title', options: { unique: false } },
|
||||
{ name: 'filename', keypath: 'filename', options: { unique: false } },
|
||||
{ name: 'playlist', keypath: 'playlist', options: { unique: false } },
|
||||
{ name: 'importDate', keypath: 'importDate', options: { unique: false } },
|
||||
{ name: 'lastUsage', keypath: 'lastUsage', options: { unique: false } },
|
||||
]
|
||||
}]
|
||||
};
|
||||
@@ -4,8 +4,10 @@ import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
|
||||
@NgModule({
|
||||
exports: [
|
||||
@@ -14,7 +16,9 @@ import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
MatProgressBarModule,
|
||||
MatSidenavModule,
|
||||
MatSnackBarModule,
|
||||
MatToolbarModule,
|
||||
],
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user