mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-04-21 12:23:20 +03:00
added theming support with 3 themes (only 2 selectable for now)
switched from css to scss default style system cleaned up unused code in app component upated youtube search results styling downloading video from home screen now shows local progress bar under that video
This commit is contained in:
@@ -16,12 +16,17 @@
|
|||||||
},
|
},
|
||||||
"Extra": {
|
"Extra": {
|
||||||
"title_top": "Youtube Downloader",
|
"title_top": "Youtube Downloader",
|
||||||
"download_only_mode": false,
|
"file_manager_enabled": true,
|
||||||
"file_manager_enabled": true
|
"allow_quality_select": true,
|
||||||
|
"download_only_mode": false
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"use_youtube_API": false,
|
"use_youtube_API": false,
|
||||||
"youtube_API_key": ""
|
"youtube_API_key": ""
|
||||||
|
},
|
||||||
|
"Themes": {
|
||||||
|
"default_theme": "default",
|
||||||
|
"allow_theme_change": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,17 @@
|
|||||||
},
|
},
|
||||||
"Extra": {
|
"Extra": {
|
||||||
"title_top": "Youtube Downloader",
|
"title_top": "Youtube Downloader",
|
||||||
"download_only_mode": false,
|
"file_manager_enabled": true,
|
||||||
"file_manager_enabled": true
|
"allow_quality_select": true,
|
||||||
|
"download_only_mode": false
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"use_youtube_API": false,
|
"use_youtube_API": false,
|
||||||
"youtube_API_key": ""
|
"youtube_API_key": ""
|
||||||
|
},
|
||||||
|
"Themes": {
|
||||||
|
"default_theme": "default",
|
||||||
|
"allow_theme_change": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/_palette.scss
Normal file
15
src/_palette.scss
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* Coolors Exported Palette - coolors.co/e8aeb7-b8e1ff-a9fff7-94fbab-82aba1 */
|
||||||
|
|
||||||
|
/* HSL */
|
||||||
|
$color1: hsla(351%, 56%, 80%, 1);
|
||||||
|
$softblue: hsla(205%, 100%, 86%, 1);
|
||||||
|
$color3: hsla(174%, 100%, 83%, 1);
|
||||||
|
$color4: hsla(133%, 93%, 78%, 1);
|
||||||
|
$color5: hsla(165%, 20%, 59%, 1);
|
||||||
|
|
||||||
|
/* RGB */
|
||||||
|
$color1: rgba(232, 174, 183, 1);
|
||||||
|
$softblue: rgba(184, 225, 255, 1);
|
||||||
|
$color3: rgba(169, 255, 247, 1);
|
||||||
|
$color4: rgba(148, 251, 171, 1);
|
||||||
|
$color5: rgba(130, 171, 161, 1);
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
<mat-toolbar color="primary" class="top">
|
<div [style.background]="postsService.theme.background_color" style="width: 100%; height: 100%;">
|
||||||
<div class="flex-row" width="100%" height="100%">
|
<mat-toolbar color="primary" class="top">
|
||||||
<div class="flex-column" style="text-align: left; margin-top: 1px;">
|
<div class="flex-row" width="100%" height="100%">
|
||||||
<button (click)="goBack()" *ngIf="router.url.split(';')[0] === '/player'" mat-icon-button><mat-icon>arrow_back</mat-icon></button>
|
<div class="flex-column" style="text-align: left; margin-top: 1px;">
|
||||||
|
<button (click)="goBack()" *ngIf="router.url.split(';')[0] === '/player'" mat-icon-button><mat-icon>arrow_back</mat-icon></button>
|
||||||
|
</div>
|
||||||
|
<div class="flex-column" style="text-align: center; margin-top: 5px;">
|
||||||
|
<div>{{topBarTitle}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-column" style="text-align: right; align-items: flex-end;">
|
||||||
|
<button *ngIf="allowThemeChange" mat-icon-button (click)="flipTheme()"><mat-icon>{{(postsService.theme.key === 'default') ? 'brightness_5' : 'brightness_2'}}</mat-icon></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-column" style="text-align: center; margin-top: 5px;">
|
</mat-toolbar>
|
||||||
<div>{{topBarTitle}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-column" style="text-align: right">
|
|
||||||
|
|
||||||
</div>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
</mat-toolbar>
|
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
|
import { Component, OnInit, ElementRef, ViewChild, HostBinding } from '@angular/core';
|
||||||
import {PostsService} from './posts.services';
|
import {PostsService} from './posts.services';
|
||||||
import {FileCardComponent} from './file-card/file-card.component';
|
import {FileCardComponent} from './file-card/file-card.component';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
@@ -16,6 +16,8 @@ import 'rxjs/add/operator/do'
|
|||||||
import 'rxjs/add/operator/switch'
|
import 'rxjs/add/operator/switch'
|
||||||
import { YoutubeSearchService, Result } from './youtube-search.service';
|
import { YoutubeSearchService, Result } from './youtube-search.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||||
|
import { THEMES_CONFIG } from '../themes';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -23,56 +25,93 @@ import { Router } from '@angular/router';
|
|||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
iOS = false;
|
|
||||||
|
|
||||||
determinateProgress = false;
|
@HostBinding('class') componentCssClass;
|
||||||
downloadingfile = false;
|
THEMES_CONFIG = THEMES_CONFIG;
|
||||||
audioOnly: boolean;
|
|
||||||
urlError = false;
|
// config items
|
||||||
path = '';
|
|
||||||
url = '';
|
|
||||||
exists = '';
|
|
||||||
topBarTitle = 'Youtube Downloader';
|
topBarTitle = 'Youtube Downloader';
|
||||||
percentDownloaded: number;
|
defaultTheme = null;
|
||||||
fileManagerEnabled = false;
|
allowThemeChange = null;
|
||||||
downloadOnlyMode = false;
|
|
||||||
baseStreamPath;
|
|
||||||
audioFolderPath;
|
|
||||||
videoFolderPath;
|
|
||||||
|
|
||||||
// youtube api
|
|
||||||
youtubeSearchEnabled = false;
|
|
||||||
youtubeAPIKey = null;
|
|
||||||
results_loading = false;
|
|
||||||
results_showing = true;
|
|
||||||
results = [];
|
|
||||||
|
|
||||||
mp3s: any[] = [];
|
|
||||||
mp4s: any[] = [];
|
|
||||||
files_cols = (window.innerWidth <= 450) ? 2 : 4;
|
|
||||||
|
|
||||||
urlForm = new FormControl('', [Validators.required]);
|
|
||||||
|
|
||||||
@ViewChild('urlinput', { read: ElementRef, static: false }) urlInput: ElementRef;
|
@ViewChild('urlinput', { read: ElementRef, static: false }) urlInput: ElementRef;
|
||||||
|
|
||||||
constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar,
|
constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar,
|
||||||
public router: Router) {
|
public router: Router, public overlayContainer: OverlayContainer) {
|
||||||
this.audioOnly = false;
|
|
||||||
|
|
||||||
|
|
||||||
// loading config
|
// loading config
|
||||||
this.postsService.loadNavItems().subscribe(result => { // loads settings
|
this.postsService.loadNavItems().subscribe(result => { // loads settings
|
||||||
this.topBarTitle = result['YoutubeDLMaterial']['Extra']['title_top'];
|
this.topBarTitle = result['YoutubeDLMaterial']['Extra']['title_top'];
|
||||||
|
const themingExists = result['YoutubeDLMaterial']['Themes'];
|
||||||
|
this.defaultTheme = themingExists ? result['YoutubeDLMaterial']['Themes']['default_theme'] : 'default';
|
||||||
|
this.allowThemeChange = themingExists ? result['YoutubeDLMaterial']['Themes']['allow_theme_change'] : true;
|
||||||
|
|
||||||
|
// sets theme to config default if it doesn't exist
|
||||||
|
if (!localStorage.getItem('theme')) {
|
||||||
|
this.setTheme(themingExists ? this.defaultTheme : 'default');
|
||||||
|
}
|
||||||
}, error => {
|
}, error => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
// theme stuff
|
||||||
|
|
||||||
|
setTheme(theme) {
|
||||||
|
// theme is registered, so set it to the stored cookie variable
|
||||||
|
let old_theme = null;
|
||||||
|
if (this.THEMES_CONFIG[theme]) {
|
||||||
|
if (localStorage.getItem('theme')) {
|
||||||
|
old_theme = localStorage.getItem('theme');
|
||||||
|
if (!this.THEMES_CONFIG[old_theme]) {
|
||||||
|
console.log('bad theme found, setting to default');
|
||||||
|
if (this.defaultTheme === null) {
|
||||||
|
// means it hasn't loaded yet
|
||||||
|
console.error('No default theme detected');
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('theme', this.defaultTheme);
|
||||||
|
old_theme = localStorage.getItem('theme'); // updates old_theme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localStorage.setItem('theme', theme);
|
||||||
|
} else {
|
||||||
|
console.error('Invalid theme: ' + theme);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.postsService.setTheme(theme);
|
||||||
|
|
||||||
|
this.onSetTheme(this.THEMES_CONFIG[theme]['css_label'], old_theme ? this.THEMES_CONFIG[old_theme]['css_label'] : old_theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSetTheme(theme, old_theme) {
|
||||||
|
if (old_theme) {
|
||||||
|
document.body.classList.remove(old_theme);
|
||||||
|
this.overlayContainer.getContainerElement().classList.remove(old_theme);
|
||||||
|
}
|
||||||
|
this.overlayContainer.getContainerElement().classList.add(theme);
|
||||||
|
this.componentCssClass = theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flipTheme() {
|
||||||
|
if (this.postsService.theme.key === 'default') {
|
||||||
|
this.setTheme('dark');
|
||||||
|
} else if (this.postsService.theme.key === 'dark') {
|
||||||
|
this.setTheme('default');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (localStorage.getItem('theme')) {
|
||||||
|
this.setTheme(localStorage.getItem('theme'));
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
goBack() {
|
goBack() {
|
||||||
this.router.navigate(['/home']);
|
this.router.navigate(['/home']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<mat-card class="example-card">
|
<mat-card class="example-card mat-elevation-z6">
|
||||||
<button (click)="deleteFile()" class="deleteButton" mat-icon-button><mat-icon>delete_forever</mat-icon></button>
|
<button (click)="deleteFile()" class="deleteButton" mat-icon-button><mat-icon>delete_forever</mat-icon></button>
|
||||||
<div style="padding:5px">
|
<div style="padding:5px">
|
||||||
<b><a href="javascript:void(0)" (click)="!isPlaylist ? mainComponent.goToFile(name, isAudio) : mainComponent.goToPlaylist(name, type)">{{title}}</a></b>
|
<b><a href="javascript:void(0)" (click)="!isPlaylist ? mainComponent.goToFile(name, isAudio) : mainComponent.goToPlaylist(name, type)">{{title}}</a></b>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<h4 mat-dialog-title>{{inputTitle}}</h4>
|
<h4 mat-dialog-title>{{inputTitle}}</h4>
|
||||||
<mat-dialog-content>
|
<mat-dialog-content>
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field>
|
<mat-form-field color="accent">
|
||||||
<input matInput (keyup.enter)="enterPressed()" [(ngModel)]="inputText" [placeholder]="inputPlaceholder">
|
<input matInput (keyup.enter)="enterPressed()" [(ngModel)]="inputText" [placeholder]="inputPlaceholder">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -82,3 +82,33 @@ mat-form-field.mat-form-field {
|
|||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.results-div {
|
||||||
|
position: relative;
|
||||||
|
top: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.first-result-card {
|
||||||
|
border-radius: 4px 4px 0px 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-result-card {
|
||||||
|
border-radius: 0px 0px 4px 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.only-result-card {
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 0px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-progress-bar {
|
||||||
|
z-index: 999;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
@@ -9,15 +9,15 @@
|
|||||||
<form class="example-form">
|
<form class="example-form">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-sm-9">
|
<div [ngClass]="allowQualitySelect ? 'col-sm-9' : null" class="col-12">
|
||||||
<mat-form-field class="example-full-width">
|
<mat-form-field color="accent" class="example-full-width">
|
||||||
<input style="padding-right: 25px;" matInput (ngModelChange)="inputChanged($event)" [(ngModel)]="url" [placeholder]="'URL' + (youtubeSearchEnabled ? ' or search' : '')" type="url" name="url" [formControl]="urlForm" required #urlinput>
|
<input style="padding-right: 25px;" matInput (ngModelChange)="inputChanged($event)" [(ngModel)]="url" [placeholder]="'URL' + (youtubeSearchEnabled ? ' or search' : '')" type="url" name="url" [formControl]="urlForm" required #urlinput>
|
||||||
<mat-error *ngIf="urlError || urlForm.invalid">Please enter a valid URL!</mat-error>
|
<mat-error *ngIf="urlError || urlForm.invalid">Please enter a valid URL!</mat-error>
|
||||||
<button class="input-clear-button" mat-icon-button (click)="clearInput()"><mat-icon>clear</mat-icon></button>
|
<button class="input-clear-button" mat-icon-button (click)="clearInput()"><mat-icon>clear</mat-icon></button>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-7 col-sm-3">
|
<div *ngIf="allowQualitySelect" class="col-7 col-sm-3">
|
||||||
<mat-form-field style="display: inline-block; width: inherit; min-width: 120px;">
|
<mat-form-field color="accent" style="display: inline-block; width: inherit; min-width: 120px;">
|
||||||
<mat-label>Quality</mat-label>
|
<mat-label>Quality</mat-label>
|
||||||
<mat-select [ngModelOptions]="{standalone: true}" [(ngModel)]="selectedQuality">
|
<mat-select [ngModelOptions]="{standalone: true}" [(ngModel)]="selectedQuality">
|
||||||
<ng-container *ngFor="let option of qualityOptions[(audioOnly) ? 'audio' : 'video']">
|
<ng-container *ngFor="let option of qualityOptions[(audioOnly) ? 'audio' : 'video']">
|
||||||
@@ -33,21 +33,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span *ngIf="results_showing">
|
<div class="results-div" *ngIf="results_showing">
|
||||||
<span *ngFor="let result of results">
|
<span *ngFor="let result of results; let i = index">
|
||||||
<mat-card style="height: 120px; border-radius: 0px">
|
<mat-card class="result-card mat-elevation-z7" [ngClass]="[(i === 0 && results.length > 1) ? 'first-result-card' : '', ((i === results.length-1) && results.length > 1) ? 'last-result-card' : '', (results.length === 1) ? 'only-result-card' : '']">
|
||||||
<div class="search-card-title">
|
<div class="search-card-title">
|
||||||
{{result.title}}
|
{{result.title}}
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 12px">
|
<div style="font-size: 12px; margin-bottom: 10px;">
|
||||||
{{result.uploaded}}
|
{{result.uploaded}}
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
|
||||||
<button mat-flat-button color="primary" style="float: left;" (click)="useURL(result.videoUrl)">Use URL</button>
|
<button mat-flat-button color="primary" style="float: left;" (click)="useURL(result.videoUrl)">Use URL</button>
|
||||||
<button mat-stroked-button color="primary" (click)="visitURL(result.videoUrl)" style="float: right">View</button>
|
<button mat-stroked-button color="primary" (click)="visitURL(result.videoUrl)" style="float: right">View</button>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<br/>
|
<br/>
|
||||||
<mat-checkbox (change)="videoModeChanged($event)" [(ngModel)]="audioOnly" style="float: left; margin-top: -12px">Only Audio</mat-checkbox>
|
<mat-checkbox (change)="videoModeChanged($event)" [(ngModel)]="audioOnly" style="float: left; margin-top: -12px">Only Audio</mat-checkbox>
|
||||||
@@ -56,7 +55,7 @@
|
|||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
<button style="margin-left: 8px; margin-bottom: 8px" (click)="downloadClicked()" [disabled]="downloadingfile" type="submit" mat-stroked-button
|
<button style="margin-left: 8px; margin-bottom: 8px" (click)="downloadClicked()" [disabled]="downloadingfile" type="submit" mat-stroked-button
|
||||||
color="primary">Download</button>
|
color="accent">Download</button>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,13 +94,18 @@
|
|||||||
<mat-grid-tile *ngFor="let file of mp3s; index as i;">
|
<mat-grid-tile *ngFor="let file of mp3s; index as i;">
|
||||||
<app-file-card (removeFile)="removeFromMp3($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
<app-file-card (removeFile)="removeFromMp3($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
||||||
[length]="file.duration" [isAudio]="true"></app-file-card>
|
[length]="file.duration" [isAudio]="true"></app-file-card>
|
||||||
|
<mat-progress-bar *ngIf="downloading_content['audio'][file.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
</mat-grid-list>
|
</mat-grid-list>
|
||||||
<mat-divider *ngIf="playlists.audio.length > 0"></mat-divider>
|
<mat-divider *ngIf="playlists.audio.length > 0"></mat-divider>
|
||||||
|
<div style="width: 100%; text-align: center; margin-top: 10px;" *ngIf="playlists.audio.length > 0">
|
||||||
|
<h6>Playlists</h6>
|
||||||
|
</div>
|
||||||
<mat-grid-list *ngIf="playlists.audio.length > 0" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
<mat-grid-list *ngIf="playlists.audio.length > 0" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
||||||
<mat-grid-tile *ngFor="let playlist of playlists.audio; let i = index;">
|
<mat-grid-tile *ngFor="let playlist of playlists.audio; let i = index;">
|
||||||
<app-file-card (removeFile)="removePlaylistMp3(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
<app-file-card (removeFile)="removePlaylistMp3(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
||||||
[length]="null" [isAudio]="true" [isPlaylist]="true" [count]="playlist.fileNames.length"></app-file-card>
|
[length]="null" [isAudio]="true" [isPlaylist]="true" [count]="playlist.fileNames.length"></app-file-card>
|
||||||
|
<mat-progress-bar *ngIf="downloading_content['audio'][playlist.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
</mat-grid-list>
|
</mat-grid-list>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,13 +125,19 @@
|
|||||||
<mat-grid-tile *ngFor="let file of mp4s; index as i;">
|
<mat-grid-tile *ngFor="let file of mp4s; index as i;">
|
||||||
<app-file-card (removeFile)="removeFromMp4($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
<app-file-card (removeFile)="removeFromMp4($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
||||||
[length]="file.duration" [isAudio]="false"></app-file-card>
|
[length]="file.duration" [isAudio]="false"></app-file-card>
|
||||||
|
<mat-progress-bar *ngIf="downloading_content['video'][file.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
</mat-grid-list>
|
</mat-grid-list>
|
||||||
<mat-divider *ngIf="playlists.video.length > 0"></mat-divider>
|
<mat-divider *ngIf="playlists.video.length > 0"></mat-divider>
|
||||||
|
|
||||||
|
<div style="width: 100%; text-align: center; margin-top: 10px;" *ngIf="playlists.video.length > 0">
|
||||||
|
<h6>Playlists</h6>
|
||||||
|
</div>
|
||||||
<mat-grid-list *ngIf="playlists.video.length > 0" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
<mat-grid-list *ngIf="playlists.video.length > 0" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
||||||
<mat-grid-tile *ngFor="let playlist of playlists.video; let i = index;">
|
<mat-grid-tile *ngFor="let playlist of playlists.video; let i = index;">
|
||||||
<app-file-card (removeFile)="removePlaylistMp4(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
<app-file-card (removeFile)="removePlaylistMp4(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
||||||
[length]="null" [isAudio]="false" [isPlaylist]="true" [count]="playlist.fileNames.length"></app-file-card>
|
[length]="null" [isAudio]="false" [isPlaylist]="true" [count]="playlist.fileNames.length"></app-file-card>
|
||||||
|
<mat-progress-bar *ngIf="downloading_content['video'][playlist.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
</mat-grid-list>
|
</mat-grid-list>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,7 +33,10 @@ export class MainComponent implements OnInit {
|
|||||||
url = '';
|
url = '';
|
||||||
exists = '';
|
exists = '';
|
||||||
percentDownloaded: number;
|
percentDownloaded: number;
|
||||||
|
|
||||||
|
// settings
|
||||||
fileManagerEnabled = false;
|
fileManagerEnabled = false;
|
||||||
|
allowQualitySelect = false;
|
||||||
downloadOnlyMode = false;
|
downloadOnlyMode = false;
|
||||||
baseStreamPath;
|
baseStreamPath;
|
||||||
audioFolderPath;
|
audioFolderPath;
|
||||||
@@ -53,6 +56,7 @@ export class MainComponent implements OnInit {
|
|||||||
files_cols = (window.innerWidth <= 450) ? 2 : 4;
|
files_cols = (window.innerWidth <= 450) ? 2 : 4;
|
||||||
playlists = {'audio': [], 'video': []};
|
playlists = {'audio': [], 'video': []};
|
||||||
playlist_thumbnails = {};
|
playlist_thumbnails = {};
|
||||||
|
downloading_content = {'audio': {}, 'video': {}};
|
||||||
|
|
||||||
urlForm = new FormControl('', [Validators.required]);
|
urlForm = new FormControl('', [Validators.required]);
|
||||||
|
|
||||||
@@ -171,6 +175,7 @@ export class MainComponent implements OnInit {
|
|||||||
this.youtubeSearchEnabled = result['YoutubeDLMaterial']['API'] && result['YoutubeDLMaterial']['API']['use_youtube_API'] &&
|
this.youtubeSearchEnabled = result['YoutubeDLMaterial']['API'] && result['YoutubeDLMaterial']['API']['use_youtube_API'] &&
|
||||||
result['YoutubeDLMaterial']['API']['youtube_API_key'];
|
result['YoutubeDLMaterial']['API']['youtube_API_key'];
|
||||||
this.youtubeAPIKey = this.youtubeSearchEnabled ? result['YoutubeDLMaterial']['API']['youtube_API_key'] : null;
|
this.youtubeAPIKey = this.youtubeSearchEnabled ? result['YoutubeDLMaterial']['API']['youtube_API_key'] : null;
|
||||||
|
this.allowQualitySelect = result['YoutubeDLMaterial']['Extra']['allow_quality_select'];
|
||||||
|
|
||||||
this.postsService.path = backendUrl;
|
this.postsService.path = backendUrl;
|
||||||
this.postsService.startPath = backendUrl;
|
this.postsService.startPath = backendUrl;
|
||||||
@@ -246,23 +251,38 @@ export class MainComponent implements OnInit {
|
|||||||
|
|
||||||
public goToFile(name, isAudio) {
|
public goToFile(name, isAudio) {
|
||||||
if (isAudio) {
|
if (isAudio) {
|
||||||
this.downloadHelperMp3(name, false, true);
|
this.downloadHelperMp3(name, false, false);
|
||||||
} else {
|
} else {
|
||||||
this.downloadHelperMp4(name, false, true);
|
this.downloadHelperMp4(name, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public goToPlaylist(playlistID, type) {
|
public goToPlaylist(playlistID, type) {
|
||||||
for (let i = 0; i < this.playlists[type].length; i++) {
|
const playlist = this.getPlaylistObjectByID(playlistID, type);
|
||||||
const playlist = this.playlists[type][i];
|
if (playlist) {
|
||||||
if (playlist.id === playlistID) {
|
if (this.downloadOnlyMode) {
|
||||||
// found the playlist, now go to it
|
this.downloading_content[type][playlistID] = true;
|
||||||
|
this.downloadPlaylist(playlist.fileNames, type, playlist.name, playlistID);
|
||||||
|
} else {
|
||||||
const fileNames = playlist.fileNames;
|
const fileNames = playlist.fileNames;
|
||||||
this.router.navigate(['/player', {fileNames: fileNames.join('|nvr|'), type: type, id: playlistID}]);
|
this.router.navigate(['/player', {fileNames: fileNames.join('|nvr|'), type: type, id: playlistID}]);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// playlist not found
|
||||||
|
console.error(`Playlist with ID ${playlistID} not found!`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPlaylistObjectByID(playlistID, type) {
|
||||||
|
for (let i = 0; i < this.playlists[type].length; i++) {
|
||||||
|
const playlist = this.playlists[type][i];
|
||||||
|
if (playlist.id === playlistID) {
|
||||||
|
return playlist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public removeFromMp3(name: string) {
|
public removeFromMp3(name: string) {
|
||||||
for (let i = 0; i < this.mp3s.length; i++) {
|
for (let i = 0; i < this.mp3s.length; i++) {
|
||||||
if (this.mp3s[i].id === name) {
|
if (this.mp3s[i].id === name) {
|
||||||
@@ -275,6 +295,7 @@ export class MainComponent implements OnInit {
|
|||||||
this.postsService.removePlaylist(playlistID, 'audio').subscribe(res => {
|
this.postsService.removePlaylist(playlistID, 'audio').subscribe(res => {
|
||||||
if (res['success']) {
|
if (res['success']) {
|
||||||
this.playlists.audio.splice(index, 1);
|
this.playlists.audio.splice(index, 1);
|
||||||
|
this.openSnackBar('Playlist successfully removed.', '');
|
||||||
}
|
}
|
||||||
this.getMp3s();
|
this.getMp3s();
|
||||||
});
|
});
|
||||||
@@ -292,6 +313,7 @@ export class MainComponent implements OnInit {
|
|||||||
this.postsService.removePlaylist(playlistID, 'video').subscribe(res => {
|
this.postsService.removePlaylist(playlistID, 'video').subscribe(res => {
|
||||||
if (res['success']) {
|
if (res['success']) {
|
||||||
this.playlists.video.splice(index, 1);
|
this.playlists.video.splice(index, 1);
|
||||||
|
this.openSnackBar('Playlist successfully removed.', '');
|
||||||
}
|
}
|
||||||
this.getMp4s();
|
this.getMp4s();
|
||||||
});
|
});
|
||||||
@@ -315,9 +337,8 @@ export class MainComponent implements OnInit {
|
|||||||
// if download only mode, just download the file. no redirect
|
// if download only mode, just download the file. no redirect
|
||||||
if (forceView === false && this.downloadOnlyMode && !this.iOS) {
|
if (forceView === false && this.downloadOnlyMode && !this.iOS) {
|
||||||
if (is_playlist) {
|
if (is_playlist) {
|
||||||
for (let i = 0; i < name.length; i++) {
|
const zipName = name[0].split(' ')[0] + name[1].split(' ')[0];
|
||||||
this.downloadAudioFile(decodeURI(name[i]));
|
this.downloadPlaylist(name, 'audio', zipName);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.downloadAudioFile(decodeURI(name));
|
this.downloadAudioFile(decodeURI(name));
|
||||||
}
|
}
|
||||||
@@ -343,9 +364,8 @@ export class MainComponent implements OnInit {
|
|||||||
// if download only mode, just download the file. no redirect
|
// if download only mode, just download the file. no redirect
|
||||||
if (forceView === false && this.downloadOnlyMode) {
|
if (forceView === false && this.downloadOnlyMode) {
|
||||||
if (is_playlist) {
|
if (is_playlist) {
|
||||||
for (let i = 0; i < name.length; i++) {
|
const zipName = name[0].split(' ')[0] + name[1].split(' ')[0];
|
||||||
this.downloadVideoFile(decodeURI(name[i]));
|
this.downloadPlaylist(name, 'video', zipName);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.downloadVideoFile(decodeURI(name));
|
this.downloadVideoFile(decodeURI(name));
|
||||||
}
|
}
|
||||||
@@ -422,7 +442,9 @@ export class MainComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadAudioFile(name) {
|
downloadAudioFile(name) {
|
||||||
|
this.downloading_content['audio'][name] = true;
|
||||||
this.postsService.downloadFileFromServer(name, 'audio').subscribe(res => {
|
this.postsService.downloadFileFromServer(name, 'audio').subscribe(res => {
|
||||||
|
this.downloading_content['audio'][name] = false;
|
||||||
const blob: Blob = res;
|
const blob: Blob = res;
|
||||||
saveAs(blob, name + '.mp3');
|
saveAs(blob, name + '.mp3');
|
||||||
|
|
||||||
@@ -437,7 +459,9 @@ export class MainComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadVideoFile(name) {
|
downloadVideoFile(name) {
|
||||||
|
this.downloading_content['video'][name] = true;
|
||||||
this.postsService.downloadFileFromServer(name, 'video').subscribe(res => {
|
this.postsService.downloadFileFromServer(name, 'video').subscribe(res => {
|
||||||
|
this.downloading_content['video'][name] = false;
|
||||||
const blob: Blob = res;
|
const blob: Blob = res;
|
||||||
saveAs(blob, name + '.mp4');
|
saveAs(blob, name + '.mp4');
|
||||||
|
|
||||||
@@ -451,6 +475,15 @@ export class MainComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadPlaylist(fileNames, type, zipName = null, playlistID = null) {
|
||||||
|
this.postsService.downloadFileFromServer(fileNames, type, zipName).subscribe(res => {
|
||||||
|
if (playlistID) { this.downloading_content[type][playlistID] = false };
|
||||||
|
const blob: Blob = res;
|
||||||
|
saveAs(blob, zipName + '.zip');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
clearInput() {
|
clearInput() {
|
||||||
this.url = '';
|
this.url = '';
|
||||||
this.results_showing = false;
|
this.results_showing = false;
|
||||||
@@ -493,7 +526,7 @@ export class MainComponent implements OnInit {
|
|||||||
const reYT = new RegExp(youtubeStrRegex);
|
const reYT = new RegExp(youtubeStrRegex);
|
||||||
const ytValid = reYT.test(str);
|
const ytValid = reYT.test(str);
|
||||||
if (valid && ytValid && Date.now() - this.last_url_check > 1000) {
|
if (valid && ytValid && Date.now() - this.last_url_check > 1000) {
|
||||||
if (str !== this.last_valid_url) {
|
if (str !== this.last_valid_url && this.allowQualitySelect) {
|
||||||
// get info
|
// get info
|
||||||
this.getURLInfo(str);
|
this.getURLInfo(str);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,6 @@ export class PlayerComponent implements OnInit {
|
|||||||
// changes the route without moving from the current view or
|
// changes the route without moving from the current view or
|
||||||
// triggering a navigation event
|
// triggering a navigation event
|
||||||
this.id = playlistID;
|
this.id = playlistID;
|
||||||
console.log(this.router.url);
|
|
||||||
this.router.navigateByUrl(this.router.url + ';id=' + playlistID);
|
this.router.navigateByUrl(this.router.url + ';id=' + playlistID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Observable } from 'rxjs/Observable';
|
|||||||
import 'rxjs/add/operator/map';
|
import 'rxjs/add/operator/map';
|
||||||
import 'rxjs/add/operator/catch';
|
import 'rxjs/add/operator/catch';
|
||||||
import 'rxjs/add/observable/throw';
|
import 'rxjs/add/observable/throw';
|
||||||
|
import { THEMES_CONFIG } from '../themes';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PostsService {
|
export class PostsService {
|
||||||
@@ -15,11 +16,17 @@ export class PostsService {
|
|||||||
startPath = 'http://localhost:17442/';
|
startPath = 'http://localhost:17442/';
|
||||||
startPathSSL = 'https://localhost:17442/'
|
startPathSSL = 'https://localhost:17442/'
|
||||||
handShakeComplete = false;
|
handShakeComplete = false;
|
||||||
|
THEMES_CONFIG = THEMES_CONFIG;
|
||||||
|
theme;
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
console.log('PostsService Initialized...');
|
console.log('PostsService Initialized...');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTheme(theme) {
|
||||||
|
this.theme = this.THEMES_CONFIG[theme];
|
||||||
|
}
|
||||||
|
|
||||||
startHandshake(url: string) {
|
startHandshake(url: string) {
|
||||||
return this.http.get(url + 'geturl');
|
return this.http.get(url + 'geturl');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,16 @@
|
|||||||
"Extra": {
|
"Extra": {
|
||||||
"title_top": "Youtube Downloader",
|
"title_top": "Youtube Downloader",
|
||||||
"file_manager_enabled": true,
|
"file_manager_enabled": true,
|
||||||
|
"allow_quality_select": true,
|
||||||
"download_only_mode": false
|
"download_only_mode": false
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"use_youtube_API": false,
|
"use_youtube_API": false,
|
||||||
"youtube_API_key": ""
|
"youtube_API_key": ""
|
||||||
|
},
|
||||||
|
"Themes": {
|
||||||
|
"default_theme": "default",
|
||||||
|
"allow_theme_change": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
|
||||||
|
|
||||||
@import '@angular/material/prebuilt-themes/indigo-pink.css';
|
|
||||||
70
src/styles.scss
Normal file
70
src/styles.scss
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
|
||||||
|
@import '@angular/material/prebuilt-themes/indigo-pink.css';
|
||||||
|
|
||||||
|
//@import './app-theme';
|
||||||
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
// @import "../node_modules/@angular/material/prebuilt-themes/purple-green.css";
|
||||||
|
@import "palette.scss";
|
||||||
|
|
||||||
|
html, body { height: 100%; }
|
||||||
|
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
|
||||||
|
|
||||||
|
@import '~@angular/material/theming';
|
||||||
|
// Plus imports for other components in your app.
|
||||||
|
|
||||||
|
/*// Typography
|
||||||
|
$custom-typography: mat-typography-config(
|
||||||
|
$font-family: Raleway,
|
||||||
|
$headline: mat-typography-level(24px, 48px, 400),
|
||||||
|
$body-1: mat-typography-level(16px, 24px, 400)
|
||||||
|
);
|
||||||
|
@include angular-material-typography($custom-typography);
|
||||||
|
*/
|
||||||
|
// Default colors
|
||||||
|
$my-app-primary: mat-palette($mat-light-blue, 700, 100, 800);
|
||||||
|
$my-app-accent: mat-palette($mat-blue, 700, 100, 800);
|
||||||
|
$my-app-warn: mat-palette($mat-red, 700, 100, 800);
|
||||||
|
|
||||||
|
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);
|
||||||
|
@include angular-material-theme($my-app-theme);
|
||||||
|
|
||||||
|
// Dark theme
|
||||||
|
$dark-primary: mat-palette($mat-indigo);
|
||||||
|
$dark-accent: mat-palette($mat-blue);
|
||||||
|
$dark-warn: mat-palette($mat-deep-orange);
|
||||||
|
|
||||||
|
$dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
|
||||||
|
|
||||||
|
.dark-theme {
|
||||||
|
@include angular-material-theme($dark-theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Light theme
|
||||||
|
$light-primary: mat-palette($mat-grey, 200, 500, 300);
|
||||||
|
$light-accent: mat-palette($mat-brown, 200);
|
||||||
|
$light-warn: mat-palette($mat-deep-orange, 200);
|
||||||
|
|
||||||
|
$light-theme: mat-light-theme($light-primary, $light-accent, $light-warn);
|
||||||
|
|
||||||
|
.light-theme {
|
||||||
|
@include angular-material-theme($light-theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-outline {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Include the common styles for Angular Material. We include this here so that you only
|
||||||
|
// have to load a single css file for Angular Material in your app.
|
||||||
|
// Be sure that you only ever include this mixin once!
|
||||||
|
@include mat-core();
|
||||||
|
|
||||||
|
// @import '../node_modules/@angular/material/theming';
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
margin: 0 auto;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
22
src/themes.ts
Normal file
22
src/themes.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const THEMES_CONFIG = {
|
||||||
|
'default': {
|
||||||
|
'key': 'default',
|
||||||
|
'background_color': 'ghostwhite',
|
||||||
|
'css_label': 'default-theme',
|
||||||
|
'social_theme': 'material-light'
|
||||||
|
},
|
||||||
|
'dark': {
|
||||||
|
'key': 'dark',
|
||||||
|
'background_color': '#757575',
|
||||||
|
'css_label': 'dark-theme',
|
||||||
|
'social_theme': 'material-dark'
|
||||||
|
},
|
||||||
|
'light': {
|
||||||
|
'key': 'light',
|
||||||
|
'background_color': 'white',
|
||||||
|
'css_label': 'light-theme',
|
||||||
|
'social_theme': 'material-light'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {THEMES_CONFIG};
|
||||||
Reference in New Issue
Block a user