From c58f8a405804271ae291d60637b9d0e36b82bd10 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Thu, 20 Feb 2020 10:45:37 -0500 Subject: [PATCH] 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 --- backend/config/default.json | 9 +- backend/config/encrypted.json | 9 +- src/_palette.scss | 15 +++ src/app/app.component.html | 28 ++--- src/app/app.component.ts | 105 ++++++++++++------ src/app/file-card/file-card.component.html | 2 +- .../input-dialog/input-dialog.component.html | 2 +- src/app/main/main.component.css | 30 +++++ src/app/main/main.component.html | 32 ++++-- src/app/main/main.component.ts | 59 +++++++--- src/app/player/player.component.ts | 1 - src/app/posts.services.ts | 7 ++ src/assets/default.json | 5 + src/styles.css | 3 - src/styles.scss | 70 ++++++++++++ src/themes.ts | 22 ++++ 16 files changed, 319 insertions(+), 80 deletions(-) create mode 100644 src/_palette.scss delete mode 100644 src/styles.css create mode 100644 src/styles.scss create mode 100644 src/themes.ts diff --git a/backend/config/default.json b/backend/config/default.json index 53f204c..681dcdb 100644 --- a/backend/config/default.json +++ b/backend/config/default.json @@ -16,12 +16,17 @@ }, "Extra": { "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": { "use_youtube_API": false, "youtube_API_key": "" + }, + "Themes": { + "default_theme": "default", + "allow_theme_change": true } } } diff --git a/backend/config/encrypted.json b/backend/config/encrypted.json index 905eb81..bd6a925 100644 --- a/backend/config/encrypted.json +++ b/backend/config/encrypted.json @@ -16,12 +16,17 @@ }, "Extra": { "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": { "use_youtube_API": false, "youtube_API_key": "" + }, + "Themes": { + "default_theme": "default", + "allow_theme_change": true } } } diff --git a/src/_palette.scss b/src/_palette.scss new file mode 100644 index 0000000..90bc8bd --- /dev/null +++ b/src/_palette.scss @@ -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); \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index 331f8e0..47a8357 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,15 +1,17 @@ - -
-
- +
+ +
+
+ +
+
+
{{topBarTitle}}
+
+
+ +
-
-
{{topBarTitle}}
-
-
+ -
-
- - - \ No newline at end of file + +
\ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b256e73..a0e6c8d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -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 {FileCardComponent} from './file-card/file-card.component'; import { Observable } from 'rxjs/Observable'; @@ -16,6 +16,8 @@ import 'rxjs/add/operator/do' import 'rxjs/add/operator/switch' import { YoutubeSearchService, Result } from './youtube-search.service'; import { Router } from '@angular/router'; +import { OverlayContainer } from '@angular/cdk/overlay'; +import { THEMES_CONFIG } from '../themes'; @Component({ selector: 'app-root', @@ -23,56 +25,93 @@ import { Router } from '@angular/router'; styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { - iOS = false; - determinateProgress = false; - downloadingfile = false; - audioOnly: boolean; - urlError = false; - path = ''; - url = ''; - exists = ''; + @HostBinding('class') componentCssClass; + THEMES_CONFIG = THEMES_CONFIG; + + // config items topBarTitle = 'Youtube Downloader'; - percentDownloaded: number; - fileManagerEnabled = false; - 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]); + defaultTheme = null; + allowThemeChange = null; @ViewChild('urlinput', { read: ElementRef, static: false }) urlInput: ElementRef; constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar, - public router: Router) { - this.audioOnly = false; - + public router: Router, public overlayContainer: OverlayContainer) { // loading config this.postsService.loadNavItems().subscribe(result => { // loads settings 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 => { 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() { this.router.navigate(['/home']); } diff --git a/src/app/file-card/file-card.component.html b/src/app/file-card/file-card.component.html index dcf35a0..d4cbc5a 100644 --- a/src/app/file-card/file-card.component.html +++ b/src/app/file-card/file-card.component.html @@ -1,4 +1,4 @@ - +
{{title}} diff --git a/src/app/input-dialog/input-dialog.component.html b/src/app/input-dialog/input-dialog.component.html index 9bca104..a636cf6 100644 --- a/src/app/input-dialog/input-dialog.component.html +++ b/src/app/input-dialog/input-dialog.component.html @@ -1,7 +1,7 @@

{{inputTitle}}

- +
diff --git a/src/app/main/main.component.css b/src/app/main/main.component.css index 98e1899..e50149a 100644 --- a/src/app/main/main.component.css +++ b/src/app/main/main.component.css @@ -81,4 +81,34 @@ mat-form-field.mat-form-field { .margined { margin-left: 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; } \ No newline at end of file diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index d9da512..c281784 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -9,15 +9,15 @@
-
- +
+ Please enter a valid URL!
-
- +
+ Quality @@ -33,21 +33,20 @@
- - - +
+ +
{{result.title}}
-
+
{{result.uploaded}}
-
- +

Only Audio @@ -56,7 +55,7 @@ + color="accent">Download
@@ -95,13 +94,18 @@ + +
+
Playlists
+
+
@@ -121,13 +125,19 @@ + + +
+
Playlists
+
+
diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index d6262b2..a72ebb1 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -33,7 +33,10 @@ export class MainComponent implements OnInit { url = ''; exists = ''; percentDownloaded: number; + + // settings fileManagerEnabled = false; + allowQualitySelect = false; downloadOnlyMode = false; baseStreamPath; audioFolderPath; @@ -53,6 +56,7 @@ export class MainComponent implements OnInit { files_cols = (window.innerWidth <= 450) ? 2 : 4; playlists = {'audio': [], 'video': []}; playlist_thumbnails = {}; + downloading_content = {'audio': {}, 'video': {}}; 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'] && result['YoutubeDLMaterial']['API']['youtube_API_key']; 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.startPath = backendUrl; @@ -246,23 +251,38 @@ export class MainComponent implements OnInit { public goToFile(name, isAudio) { if (isAudio) { - this.downloadHelperMp3(name, false, true); + this.downloadHelperMp3(name, false, false); } else { - this.downloadHelperMp4(name, false, true); + this.downloadHelperMp4(name, false, false); } } public goToPlaylist(playlistID, type) { - for (let i = 0; i < this.playlists[type].length; i++) { - const playlist = this.playlists[type][i]; - if (playlist.id === playlistID) { - // found the playlist, now go to it + const playlist = this.getPlaylistObjectByID(playlistID, type); + if (playlist) { + if (this.downloadOnlyMode) { + this.downloading_content[type][playlistID] = true; + this.downloadPlaylist(playlist.fileNames, type, playlist.name, playlistID); + } else { const fileNames = playlist.fileNames; 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) { for (let i = 0; i < this.mp3s.length; i++) { if (this.mp3s[i].id === name) { @@ -275,6 +295,7 @@ export class MainComponent implements OnInit { this.postsService.removePlaylist(playlistID, 'audio').subscribe(res => { if (res['success']) { this.playlists.audio.splice(index, 1); + this.openSnackBar('Playlist successfully removed.', ''); } this.getMp3s(); }); @@ -292,6 +313,7 @@ export class MainComponent implements OnInit { this.postsService.removePlaylist(playlistID, 'video').subscribe(res => { if (res['success']) { this.playlists.video.splice(index, 1); + this.openSnackBar('Playlist successfully removed.', ''); } this.getMp4s(); }); @@ -315,9 +337,8 @@ export class MainComponent implements OnInit { // if download only mode, just download the file. no redirect if (forceView === false && this.downloadOnlyMode && !this.iOS) { if (is_playlist) { - for (let i = 0; i < name.length; i++) { - this.downloadAudioFile(decodeURI(name[i])); - } + const zipName = name[0].split(' ')[0] + name[1].split(' ')[0]; + this.downloadPlaylist(name, 'audio', zipName); } else { this.downloadAudioFile(decodeURI(name)); } @@ -343,9 +364,8 @@ export class MainComponent implements OnInit { // if download only mode, just download the file. no redirect if (forceView === false && this.downloadOnlyMode) { if (is_playlist) { - for (let i = 0; i < name.length; i++) { - this.downloadVideoFile(decodeURI(name[i])); - } + const zipName = name[0].split(' ')[0] + name[1].split(' ')[0]; + this.downloadPlaylist(name, 'video', zipName); } else { this.downloadVideoFile(decodeURI(name)); } @@ -422,7 +442,9 @@ export class MainComponent implements OnInit { } downloadAudioFile(name) { + this.downloading_content['audio'][name] = true; this.postsService.downloadFileFromServer(name, 'audio').subscribe(res => { + this.downloading_content['audio'][name] = false; const blob: Blob = res; saveAs(blob, name + '.mp3'); @@ -437,7 +459,9 @@ export class MainComponent implements OnInit { } downloadVideoFile(name) { + this.downloading_content['video'][name] = true; this.postsService.downloadFileFromServer(name, 'video').subscribe(res => { + this.downloading_content['video'][name] = false; const blob: Blob = res; 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() { this.url = ''; this.results_showing = false; @@ -493,7 +526,7 @@ export class MainComponent implements OnInit { const reYT = new RegExp(youtubeStrRegex); const ytValid = reYT.test(str); 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 this.getURLInfo(str); } diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index eebd514..7ea5407 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -205,7 +205,6 @@ export class PlayerComponent implements OnInit { // changes the route without moving from the current view or // triggering a navigation event this.id = playlistID; - console.log(this.router.url); this.router.navigateByUrl(this.router.url + ';id=' + playlistID); } diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index a8bea50..b6a9feb 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -6,6 +6,7 @@ import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/throw'; +import { THEMES_CONFIG } from '../themes'; @Injectable() export class PostsService { @@ -15,11 +16,17 @@ export class PostsService { startPath = 'http://localhost:17442/'; startPathSSL = 'https://localhost:17442/' handShakeComplete = false; + THEMES_CONFIG = THEMES_CONFIG; + theme; constructor(private http: HttpClient) { console.log('PostsService Initialized...'); } + setTheme(theme) { + this.theme = this.THEMES_CONFIG[theme]; + } + startHandshake(url: string) { return this.http.get(url + 'geturl'); } diff --git a/src/assets/default.json b/src/assets/default.json index a96b56d..f37bd0f 100644 --- a/src/assets/default.json +++ b/src/assets/default.json @@ -17,11 +17,16 @@ "Extra": { "title_top": "Youtube Downloader", "file_manager_enabled": true, + "allow_quality_select": true, "download_only_mode": false }, "API": { "use_youtube_API": false, "youtube_API_key": "" + }, + "Themes": { + "default_theme": "default", + "allow_theme_change": true } } } diff --git a/src/styles.css b/src/styles.css deleted file mode 100644 index f35bcde..0000000 --- a/src/styles.css +++ /dev/null @@ -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'; diff --git a/src/styles.scss b/src/styles.scss new file mode 100644 index 0000000..8456541 --- /dev/null +++ b/src/styles.scss @@ -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%; +} \ No newline at end of file diff --git a/src/themes.ts b/src/themes.ts new file mode 100644 index 0000000..8fea9ee --- /dev/null +++ b/src/themes.ts @@ -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};