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:
Isaac Grynsztein
2020-02-20 10:45:37 -05:00
parent 8545016f1d
commit c58f8a4058
16 changed files with 319 additions and 80 deletions

View File

@@ -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
} }
} }
} }

View File

@@ -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
View 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);

View File

@@ -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>

View File

@@ -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']);
} }

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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);
} }

View File

@@ -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);
} }

View File

@@ -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');
} }

View File

@@ -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
} }
} }
} }

View File

@@ -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
View 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
View 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};