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