diff --git a/backend/app.js b/backend/app.js index af6089a..4ea7a3d 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1669,9 +1669,15 @@ app.post('/api/download', optionalJwt, async (req, res) => { } }); -app.post('/api/clearFinishedDownloads', optionalJwt, async (req, res) => { +app.post('/api/clearDownloads', optionalJwt, async (req, res) => { const user_uid = req.isAuthenticated() ? req.user.uid : null; - const success = db_api.removeAllRecords('download_queue', {finished: true, user_uid: user_uid}); + const clear_finished = req.body.clear_finished; + const clear_paused = req.body.clear_paused; + const clear_errors = req.body.clear_errors; + let success = true; + if (clear_finished) success &= await db_api.removeAllRecords('download_queue', {finished: true, user_uid: user_uid}); + if (clear_paused) success &= await db_api.removeAllRecords('download_queue', {paused: true, user_uid: user_uid}); + if (clear_errors) success &= await db_api.removeAllRecords('download_queue', {error: {$ne: null}, user_uid: user_uid}); res.send({success: success}); }); diff --git a/src/app/components/downloads/downloads.component.html b/src/app/components/downloads/downloads.component.html index 86e6600..9dabe7e 100644 --- a/src/app/components/downloads/downloads.component.html +++ b/src/app/components/downloads/downloads.component.html @@ -3,7 +3,7 @@ - + Date {{element.timestamp_start | date: 'short'}} @@ -19,7 +19,7 @@ - + Subscription @@ -32,13 +32,13 @@ - + Stage {{STEP_INDEX_TO_LABEL[element.step_index]}} - + Progress @@ -82,7 +82,7 @@
- +
diff --git a/src/app/components/downloads/downloads.component.ts b/src/app/components/downloads/downloads.component.ts index 6b4cedb..8dc57c7 100644 --- a/src/app/components/downloads/downloads.component.ts +++ b/src/app/components/downloads/downloads.component.ts @@ -61,7 +61,7 @@ export class DownloadsComponent implements OnInit, OnDestroy { 3: $localize`Complete` } - displayedColumns: string[] = ['date', 'title', 'stage', 'subscription', 'progress', 'actions']; + displayedColumns: string[] = ['timestamp_start', 'title', 'step_index', 'sub_name', 'percent_complete', 'actions']; dataSource = null; // new MatTableDataSource(); downloads_retrieved = false; @@ -104,7 +104,6 @@ export class DownloadsComponent implements OnInit, OnDestroy { getCurrentDownloads(): void { this.postsService.getCurrentDownloads(this.uids).subscribe(res => { - this.downloads_retrieved = true; if (res['downloads'] !== null && res['downloads'] !== undefined && JSON.stringify(this.downloads) !== JSON.stringify(res['downloads'])) { @@ -114,12 +113,12 @@ export class DownloadsComponent implements OnInit, OnDestroy { this.dataSource = new MatTableDataSource(this.downloads); this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; - this.paused_download_exists = this.downloads.find(download => download['paused'] && !download['error']); this.running_download_exists = this.downloads.find(download => !download['paused'] && !download['finished']); } else { // failed to get downloads } + this.downloads_retrieved = true; }); } @@ -134,7 +133,7 @@ export class DownloadsComponent implements OnInit, OnDestroy { }); dialogRef.afterClosed().subscribe(confirmed => { if (confirmed) { - this.postsService.clearFinishedDownloads().subscribe(res => { + this.postsService.clearDownloads(true, false, false).subscribe(res => { if (!res['success']) { this.postsService.openSnackBar('Failed to clear finished downloads!'); } @@ -143,6 +142,47 @@ export class DownloadsComponent implements OnInit, OnDestroy { }); } + clearDownloadsByType(): void { + const clearEmitter = new EventEmitter(); + const dialogRef = this.dialog.open(ConfirmDialogComponent, { + data: { + dialogType: 'selection_list', + dialogTitle: $localize`Clear downloads`, + dialogText: $localize`Select downloads to clear`, + submitText: $localize`Clear`, + doneEmitter: clearEmitter, + warnSubmitColor: true, + list: [ + { + title: $localize`Finished downloads`, + key: 'clear_finished' + }, + { + title: $localize`Paused downloads`, + key: 'clear_paused' + }, + { + title: $localize`Errored downloads`, + key: 'clear_errors' + } + ] + } + }); + clearEmitter.subscribe((done: boolean) => { + if (done) { + const selected_items = dialogRef.componentInstance.selected_items; + this.postsService.clearDownloads(selected_items.includes('clear_finished'), selected_items.includes('clear_paused'), selected_items.includes('clear_errors')).subscribe(res => { + if (!res['success']) { + this.postsService.openSnackBar($localize`Failed to clear finished downloads!`); + } else { + this.postsService.openSnackBar($localize`Cleared downloads!`); + dialogRef.close(); + } + }); + } + }); + } + pauseDownload(download_uid: string): void { this.postsService.pauseDownload(download_uid).subscribe(res => { if (!res['success']) { diff --git a/src/app/dialogs/confirm-dialog/confirm-dialog.component.html b/src/app/dialogs/confirm-dialog/confirm-dialog.component.html index 278e750..72f169c 100644 --- a/src/app/dialogs/confirm-dialog/confirm-dialog.component.html +++ b/src/app/dialogs/confirm-dialog/confirm-dialog.component.html @@ -1,18 +1,27 @@

{{dialogTitle}}

- {{dialogText}} + + + {{dialogText}} + + + + + {{item.title}} + + +
- +
\ No newline at end of file diff --git a/src/app/dialogs/confirm-dialog/confirm-dialog.component.ts b/src/app/dialogs/confirm-dialog/confirm-dialog.component.ts index 815d30e..c858122 100644 --- a/src/app/dialogs/confirm-dialog/confirm-dialog.component.ts +++ b/src/app/dialogs/confirm-dialog/confirm-dialog.component.ts @@ -8,10 +8,13 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; }) export class ConfirmDialogComponent implements OnInit { + dialogType = 'text'; dialogTitle = 'Confirm'; dialogText = 'Would you like to confirm?'; submitText = 'Yes' - cancelText = null; + cancelText = $localize`Cancel`; + list: { key: string, title: string }[] = []; + selected_items = []; submitClicked = false; closeOnSubmit = true; @@ -21,13 +24,15 @@ export class ConfirmDialogComponent implements OnInit { warnSubmitColor = false; constructor(@Inject(MAT_DIALOG_DATA) public data: any, public dialogRef: MatDialogRef) { - if (this.data.dialogTitle !== undefined) { this.dialogTitle = this.data.dialogTitle } - if (this.data.dialogText !== undefined) { this.dialogText = this.data.dialogText } - if (this.data.submitText !== undefined) { this.submitText = this.data.submitText } - if (this.data.cancelText !== undefined) { this.cancelText = this.data.cancelText } + if (this.data.dialogTitle !== undefined) { this.dialogTitle = this.data.dialogTitle } + if (this.data.dialogType !== undefined) { this.dialogType = this.data.dialogType } + if (this.data.dialogText !== undefined) { this.dialogText = this.data.dialogText } + if (this.data.list !== undefined) { this.list = this.data.list } + if (this.data.submitText !== undefined) { this.submitText = this.data.submitText } + if (this.data.cancelText !== undefined) { this.cancelText = this.data.cancelText } if (this.data.warnSubmitColor !== undefined) { this.warnSubmitColor = this.data.warnSubmitColor } if (this.data.warnSubmitColor !== undefined) { this.warnSubmitColor = this.data.warnSubmitColor } - if (this.data.closeOnSubmit !== undefined) { this.closeOnSubmit = this.data.closeOnSubmit } + if (this.data.closeOnSubmit !== undefined) { this.closeOnSubmit = this.data.closeOnSubmit } // checks if emitter exists, if so don't autoclose as it should be handled by caller if (this.data.doneEmitter) { @@ -36,7 +41,7 @@ export class ConfirmDialogComponent implements OnInit { } } - confirmClicked() { + confirmClicked(): void { if (this.onlyEmitOnDone) { this.doneEmitter.emit(true); if (this.closeOnSubmit) this.submitClicked = true; diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 3fc5c2c..d2d9046 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -95,6 +95,7 @@ import { UpdateTaskDataRequest, RestoreDBBackupRequest, Schedule, + ClearDownloadsRequest } from '../api-types'; import { isoLangs } from './settings/locales_list'; import { Title } from '@angular/platform-browser'; @@ -176,7 +177,7 @@ export class PostsService implements CanActivate { const redirect_not_required = window.location.href.includes('/player') || window.location.href.includes('/login'); // get config - this.loadNavItems().subscribe(res => { + this.getConfig().subscribe(res => { const result = !this.debugMode ? res['config_file'] : res; if (result) { this.config = result['YoutubeDLMaterial']; @@ -252,7 +253,7 @@ export class PostsService implements CanActivate { } reloadConfig() { - this.loadNavItems().subscribe(res => { + this.getConfig().subscribe(res => { const result = !this.debugMode ? res['config_file'] : res; if (result) { this.config = result['YoutubeDLMaterial']; @@ -313,7 +314,7 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'restartServer', {}, this.httpOptions); } - loadNavItems() { + getConfig() { if (isDevMode()) { return this.http.get('./assets/default.json'); } else { @@ -579,8 +580,9 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'clearDownload', body, this.httpOptions); } - clearFinishedDownloads() { - return this.http.post(this.path + 'clearFinishedDownloads', {}, this.httpOptions); + clearDownloads(clear_finished: boolean, clear_paused: boolean, clear_errors: boolean) { + const body: ClearDownloadsRequest = {clear_finished: clear_finished, clear_paused: clear_paused, clear_errors: clear_errors}; + return this.http.post(this.path + 'clearDownloads', body, this.httpOptions); } getTasks() {