From 1a79b489abca25fbb6811e30156c4f38dc87dd5d Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 06:58:05 -0400 Subject: [PATCH 1/4] Added video info dialog File cards and subscription file cards now use video info dialog so that users can see info on each individual video Ellipsis are now added client-side to video titles in file cards --- backend/app.js | 40 ++++++++++++------- package.json | 1 + src/app/app.module.ts | 4 +- .../video-info-dialog.component.html | 28 +++++++++++++ .../video-info-dialog.component.scss | 18 +++++++++ .../video-info-dialog.component.spec.ts | 25 ++++++++++++ .../video-info-dialog.component.ts | 22 ++++++++++ src/app/file-card/file-card.component.css | 8 ++++ src/app/file-card/file-card.component.html | 12 +++--- src/app/file-card/file-card.component.ts | 18 +++++++-- src/app/main/main.component.html | 4 +- .../subscription-file-card.component.html | 1 + .../subscription-file-card.component.ts | 15 +++++-- 13 files changed, 168 insertions(+), 28 deletions(-) create mode 100644 src/app/dialogs/video-info-dialog/video-info-dialog.component.html create mode 100644 src/app/dialogs/video-info-dialog/video-info-dialog.component.scss create mode 100644 src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts create mode 100644 src/app/dialogs/video-info-dialog/video-info-dialog.component.ts diff --git a/backend/app.js b/backend/app.js index d616e57..3fdd32c 100644 --- a/backend/app.js +++ b/backend/app.js @@ -84,12 +84,16 @@ app.use(bodyParser.json()); // objects -function File(id, title, thumbnailURL, isAudio, duration) { +function File(id, title, thumbnailURL, isAudio, duration, url = null, uploader = null, size = null, path = null) { this.id = id; this.title = title; this.thumbnailURL = thumbnailURL; this.isAudio = isAudio; this.duration = duration; + this.url = url; + this.uploader = uploader; + this.size = size; + this.path = path; } // actual functions @@ -951,20 +955,21 @@ app.post('/api/getMp3s', function(req, res) { for (let i = 0; i < files.length; i++) { let file = files[i]; var file_path = file.substring(audioFolderPath.length, file.length); + + var stats = fs.statSync(file); + var id = file_path.substring(0, file_path.length-4); var jsonobj = getJSONMp3(id); if (!jsonobj) continue; var title = jsonobj.title; - - if (title.length > 14) // edits title if it's too long - { - title = title.substring(0,12) + "..."; - } + var url = jsonobj.webpage_url; + var uploader = jsonobj.uploader; + var size = stats.size; var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; var isaudio = true; - var file_obj = new File(id, title, thumbnail, isaudio, duration); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); mp3s.push(file_obj); } @@ -984,20 +989,21 @@ app.post('/api/getMp4s', function(req, res) { for (let i = 0; i < files.length; i++) { let file = files[i]; var file_path = file.substring(videoFolderPath.length, file.length); + + var stats = fs.statSync(file); + var id = file_path.substring(0, file_path.length-4); var jsonobj = getJSONMp4(id); if (!jsonobj) continue; var title = jsonobj.title; - - if (title.length > 14) // edits title if it's too long - { - title = title.substring(0,12) + "..."; - } + var url = jsonobj.webpage_url; + var uploader = jsonobj.uploader; + var size = stats.size; var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; var isaudio = false; - var file_obj = new File(id, title, thumbnail, isaudio, duration); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); mp4s.push(file_obj); } @@ -1101,6 +1107,8 @@ app.post('/api/getSubscription', async (req, res) => { for (let i = 0; i < files.length; i++) { let file = files[i]; var file_path = file.substring(appended_base_path.length, file.length); + var stats = fs.statSync(file); + var id = file_path.substring(0, file_path.length-4); var jsonobj = getJSONMp4(id, appended_base_path); if (!jsonobj) continue; @@ -1108,8 +1116,12 @@ app.post('/api/getSubscription', async (req, res) => { var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; + var url = jsonobj.webpage_url; + var uploader = jsonobj.uploader; + var size = stats.size; + var isaudio = false; - var file_obj = new File(id, title, thumbnail, isaudio, duration); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); parsed_files.push(file_obj); } diff --git a/package.json b/package.json index dcbef0f..64c1aec 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@locl/core": "0.0.1-beta.2", "core-js": "^2.4.1", "file-saver": "^2.0.2", + "filesize": "^6.1.0", "ng-lazyload-image": "^7.0.1", "ng4-configure": "^0.1.7", "ngx-content-loading": "^0.1.3", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a430fff..e88d6fb 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -49,6 +49,7 @@ import { SettingsComponent } from './settings/settings.component'; import es from '@angular/common/locales/es'; import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component'; +import { VideoInfoDialogComponent } from './dialogs/video-info-dialog/video-info-dialog.component'; registerLocaleData(es, 'es'); export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps) { @@ -70,7 +71,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible SubscriptionFileCardComponent, SubscriptionInfoDialogComponent, SettingsComponent, - AboutDialogComponent + AboutDialogComponent, + VideoInfoDialogComponent ], imports: [ BrowserModule, diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.html b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html new file mode 100644 index 0000000..234b77d --- /dev/null +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html @@ -0,0 +1,28 @@ +

{{file.title}}

+ + +
+
Name: 
+
{{file.title}}
+
+
+
URL: 
+ +
+
+
Uploader: 
+
{{file.uploader ? file.uploader : 'N/A'}}
+
+
+
File size: 
+
{{filesize(file.size)}}
+
+
+
Path: 
+
{{file.path}}
+
+
+ + + + \ No newline at end of file diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.scss b/src/app/dialogs/video-info-dialog/video-info-dialog.component.scss new file mode 100644 index 0000000..6e7c4f9 --- /dev/null +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.scss @@ -0,0 +1,18 @@ +.info-item { + margin-bottom: 12px; + width: 100%; +} + +.info-item-value { + font-size: 13px; + display: inline-block; + width: 70%; +} + +.spacer {flex: 1 1 auto;} + +.info-item-label { + display: inline-block; + width: 30%; + vertical-align: top; +} diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts b/src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts new file mode 100644 index 0000000..126ea43 --- /dev/null +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VideoInfoDialogComponent } from './video-info-dialog.component'; + +describe('VideoInfoDialogComponent', () => { + let component: VideoInfoDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ VideoInfoDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(VideoInfoDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.ts b/src/app/dialogs/video-info-dialog/video-info-dialog.component.ts new file mode 100644 index 0000000..4bfc8e3 --- /dev/null +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import filesize from 'filesize'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; + +@Component({ + selector: 'app-video-info-dialog', + templateUrl: './video-info-dialog.component.html', + styleUrls: ['./video-info-dialog.component.scss'] +}) +export class VideoInfoDialogComponent implements OnInit { + file: any; + filesize; + constructor(@Inject(MAT_DIALOG_DATA) public data: any) { } + + ngOnInit(): void { + this.filesize = filesize; + if (this.data) { + this.file = this.data.file; + } + } + +} diff --git a/src/app/file-card/file-card.component.css b/src/app/file-card/file-card.component.css index 342885f..a581bca 100644 --- a/src/app/file-card/file-card.component.css +++ b/src/app/file-card/file-card.component.css @@ -51,6 +51,14 @@ -webkit-line-clamp: 2; } +.file-link { + width: 80%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} + @media (max-width: 576px){ .example-card { diff --git a/src/app/file-card/file-card.component.html b/src/app/file-card/file-card.component.html index 1c796c5..fd4317d 100644 --- a/src/app/file-card/file-card.component.html +++ b/src/app/file-card/file-card.component.html @@ -1,8 +1,9 @@
- {{title}} -
+
+ {{title}} +
ID: {{name}}
Count: {{count}}
@@ -15,10 +16,11 @@
- - + + + - +
diff --git a/src/app/file-card/file-card.component.ts b/src/app/file-card/file-card.component.ts index 770976e..1c896c2 100644 --- a/src/app/file-card/file-card.component.ts +++ b/src/app/file-card/file-card.component.ts @@ -5,6 +5,8 @@ import {EventEmitter} from '@angular/core'; import { MainComponent } from 'app/main/main.component'; import { Subject, Observable } from 'rxjs'; import 'rxjs/add/observable/merge'; +import { MatDialog } from '@angular/material/dialog'; +import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component'; @Component({ selector: 'app-file-card', @@ -12,7 +14,7 @@ import 'rxjs/add/observable/merge'; styleUrls: ['./file-card.component.css'] }) export class FileCardComponent implements OnInit { - + @Input() file: any; @Input() title: string; @Input() length: string; @Input() name: string; @@ -29,8 +31,10 @@ export class FileCardComponent implements OnInit { scrollSubject; scrollAndLoad; - constructor(private postsService: PostsService, public snackBar: MatSnackBar, public mainComponent: MainComponent) { - this.scrollSubject = new Subject(); + constructor(private postsService: PostsService, public snackBar: MatSnackBar, public mainComponent: MainComponent, + private dialog: MatDialog) { + + this.scrollSubject = new Subject(); this.scrollAndLoad = Observable.merge( Observable.fromEvent(window, 'scroll'), this.scrollSubject @@ -57,6 +61,14 @@ export class FileCardComponent implements OnInit { } + openSubscriptionInfoDialog() { + const dialogRef = this.dialog.open(VideoInfoDialogComponent, { + data: { + file: this.file, + } + }); + } + onImgError(event) { this.image_errored = true; } diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index dc11d79..982a917 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -203,7 +203,7 @@
- @@ -244,7 +244,7 @@
- diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.html b/src/app/subscription/subscription-file-card/subscription-file-card.component.html index a2972a9..81897db 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.html +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.html @@ -4,6 +4,7 @@
+ diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts index c2c23e2..6c9b3b7 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts @@ -3,6 +3,8 @@ import { Observable, Subject } from 'rxjs'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Router } from '@angular/router'; import { PostsService } from 'app/posts.services'; +import { MatDialog } from '@angular/material/dialog'; +import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component'; @Component({ selector: 'app-subscription-file-card', @@ -25,7 +27,7 @@ export class SubscriptionFileCardComponent implements OnInit { @Output() goToFileEmit = new EventEmitter(); @Output() reloadSubscription = new EventEmitter(); - constructor(private snackBar: MatSnackBar, private postsService: PostsService) { + constructor(private snackBar: MatSnackBar, private postsService: PostsService, private dialog: MatDialog) { this.scrollSubject = new Subject(); this.scrollAndLoad = Observable.merge( Observable.fromEvent(window, 'scroll'), @@ -55,6 +57,14 @@ export class SubscriptionFileCardComponent implements OnInit { this.goToFileEmit.emit(this.file.id); } + openSubscriptionInfoDialog() { + const dialogRef = this.dialog.open(VideoInfoDialogComponent, { + data: { + file: this.file, + } + }); + } + deleteAndRedownload() { this.postsService.deleteSubscriptionFile(this.sub, this.file.id, false).subscribe(res => { this.reloadSubscription.emit(true); @@ -77,8 +87,7 @@ export class SubscriptionFileCardComponent implements OnInit { } -function fancyTimeFormat(time) -{ +function fancyTimeFormat(time) { // Hours, minutes and seconds const hrs = ~~(time / 3600); const mins = ~~((time % 3600) / 60); From 9dc607e7ee553448de4d345a5a68ba50275f441c Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 18:07:35 -0400 Subject: [PATCH 2/4] fixed bug where no subscriptions resulted in a client error --- src/app/subscriptions/subscriptions.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/subscriptions/subscriptions.component.ts b/src/app/subscriptions/subscriptions.component.ts index 9614e87..0f89c61 100644 --- a/src/app/subscriptions/subscriptions.component.ts +++ b/src/app/subscriptions/subscriptions.component.ts @@ -33,6 +33,9 @@ export class SubscriptionsComponent implements OnInit { this.postsService.getAllSubscriptions().subscribe(res => { this.subscriptions_loading = false; this.subscriptions = res['subscriptions']; + if (!this.subscriptions) { + return; + } for (let i = 0; i < this.subscriptions.length; i++) { const sub = this.subscriptions[i]; From b2730926c82c6294fed0172cf91bd9e225cfbd1c Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 21:38:49 -0400 Subject: [PATCH 3/4] Updated translation details to improve clarity Added upload date property to files in UI Subscription videos can now be filtered by some of their properties (size, upload date, name, duration) Subscription videos are now centered --- backend/app.js | 21 ++++-- .../video-info-dialog.component.html | 14 ++-- src/app/file-card/file-card.component.html | 2 +- src/app/file-card/file-card.component.ts | 5 +- .../subscription-file-card.component.ts | 3 +- .../subscription/subscription.component.html | 64 +++++++++++-------- .../subscription/subscription.component.scss | 8 +++ .../subscription/subscription.component.ts | 50 +++++++++++++++ 8 files changed, 127 insertions(+), 40 deletions(-) diff --git a/backend/app.js b/backend/app.js index 3fdd32c..9fbecb9 100644 --- a/backend/app.js +++ b/backend/app.js @@ -84,7 +84,7 @@ app.use(bodyParser.json()); // objects -function File(id, title, thumbnailURL, isAudio, duration, url = null, uploader = null, size = null, path = null) { +function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date) { this.id = id; this.title = title; this.thumbnailURL = thumbnailURL; @@ -94,6 +94,7 @@ function File(id, title, thumbnailURL, isAudio, duration, url = null, uploader = this.uploader = uploader; this.size = size; this.path = path; + this.upload_date = upload_date; } // actual functions @@ -964,12 +965,15 @@ app.post('/api/getMp3s', function(req, res) { var title = jsonobj.title; var url = jsonobj.webpage_url; var uploader = jsonobj.uploader; + var upload_date = jsonobj.upload_date; + upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`; + var size = stats.size; var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; var isaudio = true; - var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); mp3s.push(file_obj); } @@ -998,12 +1002,15 @@ app.post('/api/getMp4s', function(req, res) { var title = jsonobj.title; var url = jsonobj.webpage_url; var uploader = jsonobj.uploader; - var size = stats.size; - + var upload_date = jsonobj.upload_date; + upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`; var thumbnail = jsonobj.thumbnail; var duration = jsonobj.duration; + + var size = stats.size; + var isaudio = false; - var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); mp4s.push(file_obj); } @@ -1118,10 +1125,12 @@ app.post('/api/getSubscription', async (req, res) => { var duration = jsonobj.duration; var url = jsonobj.webpage_url; var uploader = jsonobj.uploader; + var upload_date = jsonobj.upload_date; + upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`; var size = stats.size; var isaudio = false; - var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file); + var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date); parsed_files.push(file_obj); } diff --git a/src/app/dialogs/video-info-dialog/video-info-dialog.component.html b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html index 234b77d..0c0c105 100644 --- a/src/app/dialogs/video-info-dialog/video-info-dialog.component.html +++ b/src/app/dialogs/video-info-dialog/video-info-dialog.component.html @@ -2,25 +2,29 @@
-
Name: 
+
Name: 
{{file.title}}
-
URL: 
+
URL: 
-
Uploader: 
+
Uploader: 
{{file.uploader ? file.uploader : 'N/A'}}
-
File size: 
+
File size: 
{{filesize(file.size)}}
-
Path: 
+
Path: 
{{file.path}}
+
+
Upload Date: 
+
{{file.upload_date}}
+
diff --git a/src/app/file-card/file-card.component.html b/src/app/file-card/file-card.component.html index fd4317d..ef79cf6 100644 --- a/src/app/file-card/file-card.component.html +++ b/src/app/file-card/file-card.component.html @@ -19,7 +19,7 @@ - + diff --git a/src/app/file-card/file-card.component.ts b/src/app/file-card/file-card.component.ts index 1c896c2..16f33ed 100644 --- a/src/app/file-card/file-card.component.ts +++ b/src/app/file-card/file-card.component.ts @@ -61,11 +61,12 @@ export class FileCardComponent implements OnInit { } - openSubscriptionInfoDialog() { + openVideoInfoDialog() { const dialogRef = this.dialog.open(VideoInfoDialogComponent, { data: { file: this.file, - } + }, + minWidth: '50vw' }); } diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts index 6c9b3b7..093eb94 100644 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts +++ b/src/app/subscription/subscription-file-card/subscription-file-card.component.ts @@ -61,7 +61,8 @@ export class SubscriptionFileCardComponent implements OnInit { const dialogRef = this.dialog.open(VideoInfoDialogComponent, { data: { file: this.file, - } + }, + minWidth: '50vw' }); } diff --git a/src/app/subscription/subscription/subscription.component.html b/src/app/subscription/subscription/subscription.component.html index d94a0a4..86ece5a 100644 --- a/src/app/subscription/subscription/subscription.component.html +++ b/src/app/subscription/subscription/subscription.component.html @@ -1,30 +1,44 @@ -
- -
-

- {{subscription.name}} -

-
- -
+
+ +
+

+ {{subscription.name}} +

+
+ +
-
-
-
-
-

Videos

-
-
- - - search - +
+
+
+
+ + + {{filterOption['value']['label']}} + + +
+
+ +
+
+
+
+
+

Videos

+
+
+ + + search + +
-
-
-
-
- +
+
+
+ +
diff --git a/src/app/subscription/subscription/subscription.component.scss b/src/app/subscription/subscription/subscription.component.scss index 10557a0..a9c26bb 100644 --- a/src/app/subscription/subscription/subscription.component.scss +++ b/src/app/subscription/subscription/subscription.component.scss @@ -8,6 +8,13 @@ left: 15px; } +.filter-select-parent { + position: absolute; + top: 0px; + left: 20px; + display: block; +} + .search-bar { transition: all .5s ease; position: relative; @@ -29,6 +36,7 @@ .flex-grid { width: 100%; display: block; + position: relative; } .col { width: 33%; diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index 0bcf008..f9ff060 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -17,6 +17,30 @@ export class SubscriptionComponent implements OnInit { search_mode = false; search_text = ''; searchIsFocused = false; + descendingMode = true; + filterProperties = { + 'upload_date': { + 'key': 'upload_date', + 'label': 'Upload Date', + 'property': 'upload_date' + }, + 'name': { + 'key': 'name', + 'label': 'Name', + 'property': 'title' + }, + 'file_size': { + 'key': 'file_size', + 'label': 'File Size', + 'property': 'size' + }, + 'duration': { + 'key': 'duration', + 'label': 'Duration', + 'property': 'duration' + } + }; + filterProperty = this.filterProperties['upload_date']; constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router) { } @@ -27,6 +51,12 @@ export class SubscriptionComponent implements OnInit { this.getSubscription(); this.getConfig(); } + + // set filter property to cached + const cached_filter_property = localStorage.getItem('filter_property'); + if (cached_filter_property && this.filterProperties[cached_filter_property]) { + this.filterProperty = this.filterProperties[cached_filter_property]; + } } goBack() { @@ -42,6 +72,7 @@ export class SubscriptionComponent implements OnInit { } else { this.filtered_files = this.files; } + this.filterByProperty(this.filterProperty['property']); }); } @@ -72,4 +103,23 @@ export class SubscriptionComponent implements OnInit { this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue)); } + filterByProperty(prop) { + if (this.descendingMode) { + this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? -1 : 1)); + } else { + this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? 1 : -1)); + } + } + + filterOptionChanged(value) { + // this.filterProperty = value; + this.filterByProperty(value['property']); + localStorage.setItem('filter_property', value['key']); + } + + toggleModeChange() { + this.descendingMode = !this.descendingMode; + this.filterByProperty(this.filterProperty['property']); + } + } From bdb6a082745c999690338fca26b6b64289e69390 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Tue, 17 Mar 2020 22:24:52 -0400 Subject: [PATCH 4/4] Added ability to download subscription videos as zip --- backend/app.js | 37 +++++++++++++------ src/app/posts.services.ts | 7 ++-- .../subscription/subscription.component.html | 1 + .../subscription/subscription.component.scss | 20 ++++++++++ .../subscription/subscription.component.ts | 18 +++++++++ 5 files changed, 69 insertions(+), 14 deletions(-) diff --git a/backend/app.js b/backend/app.js index 9fbecb9..a02a6cd 100644 --- a/backend/app.js +++ b/backend/app.js @@ -358,10 +358,16 @@ function getVideoFormatID(name) } } -async function createPlaylistZipFile(fileNames, type, outputName) { +async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvided = null) { return new Promise(async resolve => { - let zipFolderPath = path.join(__dirname, (type === 'audio') ? audioFolderPath : videoFolderPath); - // let name = fileNames[0].split(' ')[0] + fileNames[1].split(' ')[0]; + let zipFolderPath = null; + + if (!fullPathProvided) { + zipFolderPath = path.join(__dirname, (type === 'audio') ? audioFolderPath : videoFolderPath); + } else { + zipFolderPath = path.join(__dirname, config_api.getConfigItem('ytdl_subscriptions_base_path')); + } + let ext = (type === 'audio') ? '.mp3' : '.mp4'; let output = fs.createWriteStream(path.join(zipFolderPath, outputName + '.zip')); @@ -381,7 +387,8 @@ async function createPlaylistZipFile(fileNames, type, outputName) { for (let i = 0; i < fileNames.length; i++) { let fileName = fileNames[i]; - archive.file(zipFolderPath + fileName + ext, {name: fileName + ext}) + let file_path = !fullPathProvided ? zipFolderPath + fileName + ext : fileName; + archive.file(file_path, {name: fileName + ext}) } await archive.finalize(); @@ -1141,9 +1148,6 @@ app.post('/api/getSubscription', async (req, res) => { } else { res.sendStatus(500); } - - - }); app.post('/api/downloadVideosForSubscription', async (req, res) => { @@ -1278,11 +1282,12 @@ app.post('/api/deleteMp4', async (req, res) => { app.post('/api/downloadFile', async (req, res) => { let fileNames = req.body.fileNames; - let is_playlist = req.body.is_playlist; + let zip_mode = req.body.zip_mode; let type = req.body.type; let outputName = req.body.outputName; + let fullPathProvided = req.body.fullPathProvided; let file = null; - if (!is_playlist) { + if (!zip_mode) { fileNames = decodeURIComponent(fileNames); if (type === 'audio') { file = __dirname + '/' + audioFolderPath + fileNames + '.mp3'; @@ -1293,10 +1298,20 @@ app.post('/api/downloadFile', async (req, res) => { for (let i = 0; i < fileNames.length; i++) { fileNames[i] = decodeURIComponent(fileNames[i]); } - file = await createPlaylistZipFile(fileNames, type, outputName); + file = await createPlaylistZipFile(fileNames, type, outputName, fullPathProvided); } - res.sendFile(file); + res.sendFile(file, function (err) { + if (err) { + next(err); + } else if (fullPathProvided) { + try { + fs.unlinkSync(file); + } catch(e) { + console.log("ERROR: Failed to remove file", file); + } + } + }); }); app.post('/api/deleteFile', async (req, res) => { diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 6ea4133..97b63de 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -114,11 +114,12 @@ export class PostsService { return this.http.post(this.path + 'getMp4s', {}); } - downloadFileFromServer(fileName, type, outputName = null) { + downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null) { return this.http.post(this.path + 'downloadFile', {fileNames: fileName, type: type, - is_playlist: Array.isArray(fileName), - outputName: outputName}, + zip_mode: Array.isArray(fileName), + outputName: outputName, + fullPathProvided: fullPathProvided}, {responseType: 'blob'}); } diff --git a/src/app/subscription/subscription/subscription.component.html b/src/app/subscription/subscription/subscription.component.html index 86ece5a..44ccb41 100644 --- a/src/app/subscription/subscription/subscription.component.html +++ b/src/app/subscription/subscription/subscription.component.html @@ -42,4 +42,5 @@
+
\ No newline at end of file diff --git a/src/app/subscription/subscription/subscription.component.scss b/src/app/subscription/subscription/subscription.component.scss index a9c26bb..7734644 100644 --- a/src/app/subscription/subscription/subscription.component.scss +++ b/src/app/subscription/subscription/subscription.component.scss @@ -38,7 +38,27 @@ display: block; position: relative; } + .col { width: 33%; display: inline-block; +} + +.spinner { + width: 50px; + height: 50px; + bottom: 3px; + left: 3px; + position: absolute; +} + +.save-button { + right: 25px; + position: absolute; + bottom: 25px; +} + +.save-icon { + bottom: 1px; + position: relative; } \ No newline at end of file diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index f9ff060..496f2d1 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -41,6 +41,7 @@ export class SubscriptionComponent implements OnInit { } }; filterProperty = this.filterProperties['upload_date']; + downloading = false; constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router) { } @@ -122,4 +123,21 @@ export class SubscriptionComponent implements OnInit { this.filterByProperty(this.filterProperty['property']); } + downloadContent() { + const fileNames = []; + for (let i = 0; i < this.files.length; i++) { + fileNames.push(this.files[i].path); + } + + this.downloading = true; + this.postsService.downloadFileFromServer(fileNames, 'video', this.subscription.name, true).subscribe(res => { + this.downloading = false; + const blob: Blob = res; + saveAs(blob, this.subscription.name + '.zip'); + }, err => { + console.log(err); + this.downloading = false; + }); + } + }