From b5ee0d365c842efaf2d46b01b2a2e859b97dfdcd Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sun, 19 Jun 2022 01:14:59 -0400 Subject: [PATCH] Subscription file cards are now replaced with unified file cards GetAllFiles can now filter by sub_id Improved API models and added request body docs for GetAllFiles --- Public API v1.yaml | 40 ++++++++ backend/app.js | 14 ++- src/api-types/index.ts | 3 + src/api-types/models/FileTypeFilter.ts | 9 ++ src/api-types/models/GetAllFilesRequest.ts | 20 ++++ src/api-types/models/Sort.ts | 14 +++ src/app/app.module.ts | 2 - .../recent-videos.component.html | 6 +- .../recent-videos/recent-videos.component.ts | 11 ++- src/app/posts.services.ts | 10 +- .../subscription-file-card.component.html | 20 ---- .../subscription-file-card.component.scss | 76 -------------- .../subscription-file-card.component.spec.ts | 25 ----- .../subscription-file-card.component.ts | 98 ------------------- .../subscription/subscription.component.html | 33 +------ .../subscription/subscription.component.ts | 29 ++---- src/assets/i18n/messages.en.xlf | 20 ++-- 17 files changed, 126 insertions(+), 304 deletions(-) create mode 100644 src/api-types/models/FileTypeFilter.ts create mode 100644 src/api-types/models/GetAllFilesRequest.ts create mode 100644 src/api-types/models/Sort.ts delete mode 100644 src/app/subscription/subscription-file-card/subscription-file-card.component.html delete mode 100644 src/app/subscription/subscription-file-card/subscription-file-card.component.scss delete mode 100644 src/app/subscription/subscription-file-card/subscription-file-card.component.spec.ts delete mode 100644 src/app/subscription/subscription-file-card/subscription-file-card.component.ts diff --git a/Public API v1.yaml b/Public API v1.yaml index f554398..0931cf6 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -97,6 +97,11 @@ paths: summary: Get all files description: Gets all files and playlists stored in the db operationId: get-getAllFiles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GetAllFilesRequest' responses: '200': description: OK @@ -1724,6 +1729,41 @@ components: description: All video playlists items: $ref: '#/components/schemas/Playlist' + GetAllFilesRequest: + type: object + properties: + sort: + $ref: '#/components/schemas/Sort' + range: + type: array + items: + type: number + description: Two elements allowed, start index and end index + minItems: 2 + maxItems: 2 + text_search: + type: string + description: Filter files by title + file_type_filter: + $ref: '#/components/schemas/FileTypeFilter' + sub_id: + type: string + description: Include if you want to filter by subscription + Sort: + type: object + properties: + by: + type: string + description: Property to sort by + order: + type: number + description: 1 for ascending, -1 for descending + FileTypeFilter: + type: string + enum: + - audio_only + - video_only + - both GetAllFilesResponse: required: - files diff --git a/backend/app.js b/backend/app.js index a508702..4c57139 100644 --- a/backend/app.js +++ b/backend/app.js @@ -912,11 +912,11 @@ app.post('/api/getFile', optionalJwt, async function (req, res) { app.post('/api/getAllFiles', optionalJwt, async function (req, res) { // these are returned let files = null; - let playlists = null; - let sort = req.body.sort; - let range = req.body.range; - let text_search = req.body.text_search; - let file_type_filter = req.body.file_type_filter; + const sort = req.body.sort; + const range = req.body.range; + const text_search = req.body.text_search; + const file_type_filter = req.body.file_type_filter; + const sub_id = req.body.sub_id; const uuid = req.isAuthenticated() ? req.user.uid : null; const filter_obj = {user_uid: uuid}; @@ -929,6 +929,10 @@ app.post('/api/getAllFiles', optionalJwt, async function (req, res) { } } + if (sub_id) { + filter_obj['sub_id'] = sub_id; + } + if (file_type_filter === 'audio_only') filter_obj['isAudio'] = true; else if (file_type_filter === 'video_only') filter_obj['isAudio'] = false; diff --git a/src/api-types/index.ts b/src/api-types/index.ts index db5be04..973aeca 100644 --- a/src/api-types/index.ts +++ b/src/api-types/index.ts @@ -40,11 +40,13 @@ export type { DownloadTwitchChatByVODIDRequest } from './models/DownloadTwitchCh export type { DownloadTwitchChatByVODIDResponse } from './models/DownloadTwitchChatByVODIDResponse'; export type { DownloadVideosForSubscriptionRequest } from './models/DownloadVideosForSubscriptionRequest'; export { FileType } from './models/FileType'; +export { FileTypeFilter } from './models/FileTypeFilter'; export type { GenerateArgsResponse } from './models/GenerateArgsResponse'; export type { GenerateNewApiKeyResponse } from './models/GenerateNewApiKeyResponse'; export type { GetAllCategoriesResponse } from './models/GetAllCategoriesResponse'; export type { GetAllDownloadsRequest } from './models/GetAllDownloadsRequest'; export type { GetAllDownloadsResponse } from './models/GetAllDownloadsResponse'; +export type { GetAllFilesRequest } from './models/GetAllFilesRequest'; export type { GetAllFilesResponse } from './models/GetAllFilesResponse'; export type { GetAllSubscriptionsResponse } from './models/GetAllSubscriptionsResponse'; export type { GetAllTasksResponse } from './models/GetAllTasksResponse'; @@ -82,6 +84,7 @@ export type { RestoreDBBackupRequest } from './models/RestoreDBBackupRequest'; export { Schedule } from './models/Schedule'; export type { SetConfigRequest } from './models/SetConfigRequest'; export type { SharingToggle } from './models/SharingToggle'; +export type { Sort } from './models/Sort'; export type { SubscribeRequest } from './models/SubscribeRequest'; export type { SubscribeResponse } from './models/SubscribeResponse'; export type { Subscription } from './models/Subscription'; diff --git a/src/api-types/models/FileTypeFilter.ts b/src/api-types/models/FileTypeFilter.ts new file mode 100644 index 0000000..d1fac98 --- /dev/null +++ b/src/api-types/models/FileTypeFilter.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export enum FileTypeFilter { + AUDIO_ONLY = 'audio_only', + VIDEO_ONLY = 'video_only', + BOTH = 'both', +} \ No newline at end of file diff --git a/src/api-types/models/GetAllFilesRequest.ts b/src/api-types/models/GetAllFilesRequest.ts new file mode 100644 index 0000000..0acd1c3 --- /dev/null +++ b/src/api-types/models/GetAllFilesRequest.ts @@ -0,0 +1,20 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { FileTypeFilter } from './FileTypeFilter'; +import type { Sort } from './Sort'; + +export type GetAllFilesRequest = { + sort?: Sort; + range?: Array; + /** + * Filter files by title + */ + text_search?: string; + file_type_filter?: FileTypeFilter; + /** + * Include if you want to filter by subscription + */ + sub_id?: string; +}; \ No newline at end of file diff --git a/src/api-types/models/Sort.ts b/src/api-types/models/Sort.ts new file mode 100644 index 0000000..438f884 --- /dev/null +++ b/src/api-types/models/Sort.ts @@ -0,0 +1,14 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type Sort = { + /** + * Property to sort by + */ + by?: string; + /** + * 1 for ascending, -1 for descending + */ + order?: number; +}; \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a9e2ad4..238e9e8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -49,7 +49,6 @@ import { CreatePlaylistComponent } from './create-playlist/create-playlist.compo import { SubscriptionsComponent } from './subscriptions/subscriptions.component'; import { SubscribeDialogComponent } from './dialogs/subscribe-dialog/subscribe-dialog.component'; import { SubscriptionComponent } from './subscription//subscription/subscription.component'; -import { SubscriptionFileCardComponent } from './subscription/subscription-file-card/subscription-file-card.component'; import { SubscriptionInfoDialogComponent } from './dialogs/subscription-info-dialog/subscription-info-dialog.component'; import { SettingsComponent } from './settings/settings.component'; import { MatChipsModule } from '@angular/material/chips'; @@ -102,7 +101,6 @@ registerLocaleData(es, 'es'); SubscriptionsComponent, SubscribeDialogComponent, SubscriptionComponent, - SubscriptionFileCardComponent, SubscriptionInfoDialogComponent, SettingsComponent, AboutDialogComponent, diff --git a/src/app/components/recent-videos/recent-videos.component.html b/src/app/components/recent-videos/recent-videos.component.html index fa2ba2a..1f83685 100644 --- a/src/app/components/recent-videos/recent-videos.component.html +++ b/src/app/components/recent-videos/recent-videos.component.html @@ -17,7 +17,7 @@
-

My videos

+

My files

@@ -35,7 +35,7 @@
- No videos found. + No files found.
@@ -46,7 +46,7 @@ -
+
File type diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index 76e297f..11f8713 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -1,7 +1,7 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { PostsService } from 'app/posts.services'; import { Router } from '@angular/router'; -import { FileType } from '../../../api-types'; +import { FileType, FileTypeFilter } from '../../../api-types'; import { MatPaginator } from '@angular/material/paginator'; import { Subject } from 'rxjs'; import { distinctUntilChanged } from 'rxjs/operators'; @@ -13,6 +13,9 @@ import { distinctUntilChanged } from 'rxjs/operators'; }) export class RecentVideosComponent implements OnInit { + @Input() usePaginator = true; + @Input() sub_id = null; + cached_file_count = 0; loading_files = null; @@ -104,7 +107,7 @@ export class RecentVideosComponent implements OnInit { // set file type filter to cached value const cached_file_type_filter = localStorage.getItem('file_type_filter'); - if (cached_file_type_filter) { + if (this.usePaginator && cached_file_type_filter) { this.fileTypeFilter = cached_file_type_filter; } @@ -163,7 +166,7 @@ export class RecentVideosComponent implements OnInit { const current_file_index = (this.paginator?.pageIndex ? this.paginator.pageIndex : 0)*this.pageSize; const sort = {by: this.filterProperty['property'], order: this.descendingMode ? -1 : 1}; const range = [current_file_index, current_file_index + this.pageSize]; - this.postsService.getAllFiles(sort, range, this.search_mode ? this.search_text : null, this.fileTypeFilter).subscribe(res => { + this.postsService.getAllFiles(sort, range, this.search_mode ? this.search_text : null, this.fileTypeFilter as FileTypeFilter, this.sub_id).subscribe(res => { this.file_count = res['file_count']; this.paged_data = res['files']; for (let i = 0; i < this.paged_data.length; i++) { diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 186418a..5ec95d9 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -97,7 +97,10 @@ import { Schedule, ClearDownloadsRequest, Category, - UpdateFileRequest + UpdateFileRequest, + Sort, + FileTypeFilter, + GetAllFilesRequest } from '../api-types'; import { isoLangs } from './settings/locales_list'; import { Title } from '@angular/platform-browser'; @@ -355,8 +358,9 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'getFile', body, this.httpOptions); } - getAllFiles(sort, range, text_search, file_type_filter) { - return this.http.post(this.path + 'getAllFiles', {sort: sort, range: range, text_search: text_search, file_type_filter: file_type_filter}, this.httpOptions); + getAllFiles(sort: Sort, range: number[], text_search: string, file_type_filter: FileTypeFilter, sub_id: string) { + const body: GetAllFilesRequest = {sort: sort, range: range, text_search: text_search, file_type_filter: file_type_filter, sub_id: sub_id}; + return this.http.post(this.path + 'getAllFiles', body, this.httpOptions); } updateFile(uid: string, change_obj: Object) { 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 deleted file mode 100644 index 81897db..0000000 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.html +++ /dev/null @@ -1,20 +0,0 @@ -
-
- Length: {{formattedDuration}} -
- - - - - - - -
-
- Thumbnail -
- - {{file.title}} -
-
-
diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.scss b/src/app/subscription/subscription-file-card/subscription-file-card.component.scss deleted file mode 100644 index 65cd869..0000000 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.scss +++ /dev/null @@ -1,76 +0,0 @@ -.example-card { - width: 200px; - height: 200px; - padding: 0px; - cursor: pointer; - } - - .menuButton { - right: 0px; - top: -1px; - position: absolute; - z-index: 999; - - } - - /* Coerce the icon container away from display:inline */ - .mat-icon-button .mat-button-wrapper { - display: flex; - justify-content: center; - } - - .image { - width: 200px; - height: 112.5px; - object-fit: cover; - } - - .example-full-width-height { - width: 100%; - height: 100% - } - - .centered { - margin: 0 auto; - top: 50%; - left: 50%; - } - - .img-div { - max-height: 80px; - padding: 0px; - margin: 32px 0px 0px -5px; - width: calc(100% + 5px + 5px); - } - - .max-two-lines { - display: -webkit-box; - display: -moz-box; - max-height: 2.4em; - line-height: 1.2em; - overflow: hidden; - text-overflow: ellipsis; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - bottom: 5px; - position: absolute; - } - - .duration-time { - position: absolute; - left: 5px; - top: 5px; - z-index: 99999; - } - - @media (max-width: 576px){ - - .example-card { - width: 175px !important; - } - - .image { - width: 175px; - } - - } diff --git a/src/app/subscription/subscription-file-card/subscription-file-card.component.spec.ts b/src/app/subscription/subscription-file-card/subscription-file-card.component.spec.ts deleted file mode 100644 index 42facb5..0000000 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { SubscriptionFileCardComponent } from './subscription-file-card.component'; - -describe('SubscriptionFileCardComponent', () => { - let component: SubscriptionFileCardComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ SubscriptionFileCardComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SubscriptionFileCardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); 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 deleted file mode 100644 index 2257fd8..0000000 --- a/src/app/subscription/subscription-file-card/subscription-file-card.component.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; -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', - templateUrl: './subscription-file-card.component.html', - styleUrls: ['./subscription-file-card.component.scss'] -}) -export class SubscriptionFileCardComponent implements OnInit { - image_errored = false; - image_loaded = false; - - formattedDuration = null; - - @Input() file; - @Input() sub; - @Input() use_youtubedl_archive = false; - - @Output() goToFileEmit = new EventEmitter(); - @Output() reloadSubscription = new EventEmitter(); - - constructor(private snackBar: MatSnackBar, private postsService: PostsService, private dialog: MatDialog) {} - - ngOnInit() { - if (this.file.duration) { - this.formattedDuration = fancyTimeFormat(this.file.duration); - } - } - - onImgError(event) { - this.image_errored = true; - } - - imageLoaded(loaded) { - this.image_loaded = true; - } - - goToFile() { - const emit_obj = { - uid: this.file.uid, - url: this.file.requested_formats ? this.file.requested_formats[0].url : this.file.url - } - this.goToFileEmit.emit(emit_obj); - } - - openSubscriptionInfoDialog() { - const dialogRef = this.dialog.open(VideoInfoDialogComponent, { - data: { - file: this.file, - }, - minWidth: '50vw' - }); - } - - deleteAndRedownload() { - this.postsService.deleteSubscriptionFile(this.sub, this.file.id, false, this.file.uid).subscribe(res => { - this.reloadSubscription.emit(true); - this.openSnackBar(`Successfully deleted file: '${this.file.id}'`, 'Dismiss.'); - }); - } - - deleteForever() { - this.postsService.deleteSubscriptionFile(this.sub, this.file.id, true, this.file.uid).subscribe(res => { - this.reloadSubscription.emit(true); - this.openSnackBar(`Successfully deleted file: '${this.file.id}'`, 'Dismiss.'); - }); - } - - public openSnackBar(message: string, action: string) { - this.snackBar.open(message, action, { - duration: 2000, - }); - } - -} - -function fancyTimeFormat(time) { - // Hours, minutes and seconds - const hrs = ~~(time / 3600); - const mins = ~~((time % 3600) / 60); - const secs = ~~time % 60; - - // Output like "1:01" or "4:03:59" or "123:03:59" - let ret = ''; - - if (hrs > 0) { - ret += '' + hrs + ':' + (mins < 10 ? '0' : ''); - } - - ret += '' + mins + ':' + (secs < 10 ? '0' : ''); - ret += '' + secs; - return ret; -} diff --git a/src/app/subscription/subscription/subscription.component.html b/src/app/subscription/subscription/subscription.component.html index 892108a..586879c 100644 --- a/src/app/subscription/subscription/subscription.component.html +++ b/src/app/subscription/subscription/subscription.component.html @@ -10,38 +10,7 @@
-
-
-
- - - {{filterOption['value']['label']}} - - -
-
- -
-
-
-
-
-

Videos

-
-
- - - search - -
-
-
-
-
- -
-
-
+
diff --git a/src/app/subscription/subscription/subscription.component.ts b/src/app/subscription/subscription/subscription.component.ts index 967b5bd..09baef2 100644 --- a/src/app/subscription/subscription/subscription.component.ts +++ b/src/app/subscription/subscription/subscription.component.ts @@ -100,22 +100,12 @@ export class SubscriptionComponent implements OnInit, OnDestroy { }); } - getConfig() { + getConfig(): void { this.use_youtubedl_archive = this.postsService.config['Downloader']['use_youtubedl_archive']; } - goToFile(emit_obj) { - const uid = emit_obj['uid']; - const url = emit_obj['url']; - localStorage.setItem('player_navigator', this.router.url); - if (this.subscription.streamingOnly) { - this.router.navigate(['/player', {uid: uid, url: url}]); - } else { - this.router.navigate(['/player', {uid: uid}]); - } - } - onSearchInputChanged(newvalue) { + onSearchInputChanged(newvalue: string): void { if (newvalue.length > 0) { this.search_mode = true; this.filterFiles(newvalue); @@ -129,7 +119,7 @@ export class SubscriptionComponent implements OnInit, OnDestroy { this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue)); } - filterByProperty(prop) { + filterByProperty(prop: string): void { if (this.descendingMode) { this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? -1 : 1)); } else { @@ -142,17 +132,12 @@ export class SubscriptionComponent implements OnInit, OnDestroy { localStorage.setItem('filter_property', value['key']); } - toggleModeChange() { + toggleModeChange(): void { this.descendingMode = !this.descendingMode; this.filterByProperty(this.filterProperty['property']); } - downloadContent() { - const fileNames = []; - for (let i = 0; i < this.files.length; i++) { - fileNames.push(this.files[i].path); - } - + downloadContent(): void { this.downloading = true; this.postsService.downloadSubFromServer(this.subscription.id).subscribe(res => { this.downloading = false; @@ -164,7 +149,7 @@ export class SubscriptionComponent implements OnInit, OnDestroy { }); } - editSubscription() { + editSubscription(): void { this.dialog.open(EditSubscriptionDialogComponent, { data: { sub: this.postsService.getSubscriptionByID(this.subscription.id) @@ -172,7 +157,7 @@ export class SubscriptionComponent implements OnInit, OnDestroy { }); } - watchSubscription() { + watchSubscription(): void { this.router.navigate(['/player', {sub_id: this.subscription.id}]) } diff --git a/src/assets/i18n/messages.en.xlf b/src/assets/i18n/messages.en.xlf index 1d963b3..8eff8b7 100644 --- a/src/assets/i18n/messages.en.xlf +++ b/src/assets/i18n/messages.en.xlf @@ -592,10 +592,6 @@ src/app/components/recent-videos/recent-videos.component.html 24 - - src/app/subscription/subscription/subscription.component.html - 33 - search field description @@ -682,21 +678,21 @@ Edit role - - My videos + + My files src/app/components/recent-videos/recent-videos.component.html 20 - My videos title + My files title - - No videos found. + + No files found. src/app/components/recent-videos/recent-videos.component.html 38 - No videos found + No files found File type @@ -1135,10 +1131,6 @@ src/app/create-playlist/create-playlist.component.html 20 - - src/app/subscription/subscription/subscription.component.html - 29 - Videos title