From ae01ae92841d818dcf3265f345759acfa01f9475 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 16 Jan 2022 15:09:49 +0100 Subject: [PATCH] Add delete handler for delete key Signed-off-by: trivernis --- mediarepo-ui/package.json | 1 + .../shared/app-base/app-base.module.ts | 18 ++ .../file-action-base.component.ts | 176 ++++++++++++++++++ .../file-context-menu.component.html | 16 +- .../file-context-menu.component.ts | 172 +---------------- .../file-gallery/file-gallery.component.html | 4 +- .../file-gallery/file-gallery.component.ts | 32 ++-- .../file-grid/file-grid.component.html | 4 +- .../file-grid/file-grid.component.ts | 31 +-- .../file-multiview.component.html | 10 +- .../file-multiview.component.ts | 34 ++-- .../error-broker/error-broker.service.ts | 9 + mediarepo-ui/yarn.lock | 5 + 13 files changed, 296 insertions(+), 216 deletions(-) create mode 100644 mediarepo-ui/src/app/components/shared/app-base/app-base.module.ts create mode 100644 mediarepo-ui/src/app/components/shared/app-base/file-action-base/file-action-base.component.ts diff --git a/mediarepo-ui/package.json b/mediarepo-ui/package.json index 2369bff..46f9e9d 100644 --- a/mediarepo-ui/package.json +++ b/mediarepo-ui/package.json @@ -32,6 +32,7 @@ "primeng": "^13.0.4", "rxjs": "~7.5.2", "tslib": "^2.3.1", + "w3c-keys": "^1.0.3", "zone.js": "~0.11.4" }, "devDependencies": { diff --git a/mediarepo-ui/src/app/components/shared/app-base/app-base.module.ts b/mediarepo-ui/src/app/components/shared/app-base/app-base.module.ts new file mode 100644 index 0000000..34bf9e6 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-base/app-base.module.ts @@ -0,0 +1,18 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {FileActionBaseComponent} from "./file-action-base/file-action-base.component"; + + +@NgModule({ + declarations: [ + FileActionBaseComponent, + ], + exports: [ + FileActionBaseComponent, + ], + imports: [ + CommonModule + ] +}) +export class AppBaseModule { +} diff --git a/mediarepo-ui/src/app/components/shared/app-base/file-action-base/file-action-base.component.ts b/mediarepo-ui/src/app/components/shared/app-base/file-action-base/file-action-base.component.ts new file mode 100644 index 0000000..520b886 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-base/file-action-base/file-action-base.component.ts @@ -0,0 +1,176 @@ +import {Component} from "@angular/core"; +import {FileService} from "../../../../services/file/file.service"; +import {clipboard} from "@tauri-apps/api"; +import {FileHelper} from "../../../../services/file/file.helper"; +import {FileStatus} from "../../../../../api/api-types/files"; +import {File} from "../../../../../api/models/File"; +import {SafeResourceUrl} from "@angular/platform-browser"; +import {BehaviorSubject} from "rxjs"; +import {BusyDialogComponent} from "../../app-common/busy-dialog/busy-dialog.component"; +import {ConfirmDialogComponent, ConfirmDialogData} from "../../app-common/confirm-dialog/confirm-dialog.component"; +import {MatDialog, MatDialogConfig, MatDialogRef} from "@angular/material/dialog"; +import {ErrorBrokerService} from "../../../../services/error-broker/error-broker.service"; + +type ProgressDialogContext = { + dialog: MatDialogRef, + progress: BehaviorSubject, + message: BehaviorSubject, +}; + +@Component({ + selector: "app-file-action-base", + template: "

Do not use

", +}) +export class FileActionBaseComponent { + constructor(private dialog: MatDialog, private errorBroker: ErrorBrokerService, private fileService: FileService) { + } + + public async copyFileContentDescriptor(file: File): Promise { + await clipboard.writeText(file.cd); + } + + public async exportFile(file: File): Promise { + const path = await FileHelper.getFileDownloadLocation(file); + + if (path) { + await this.errorBroker.try(() => this.fileService.saveFile(file, path)); + } + } + + public async updateStatus(files: File[], status: FileStatus) { + if (files.length === 1) { + let changeConfirmed; + + if (status === "Deleted") { + changeConfirmed = await this.openConfirmDialog( + "Confirm deletion", + "Do you really want to move this file to trash?", + "Delete", + "warn", + this.getImageThumbnail(files[0]) + ); + } else { + changeConfirmed = true; + } + + if (changeConfirmed) { + await this.errorBroker.try(async () => { + const newFile = await this.fileService.updateFileStatus(files[0].id, status); + files[0].status = newFile.status; + }); + } + } else { + const statusChangeConfirmed = await this.openConfirmDialog( + "Confirm mass status change", + `Do you really want to change the status of ${files.length} files to '${status}'?`, + "Change status", + status === "Deleted" ? "warn" : "primary" + ); + if (statusChangeConfirmed) { + await this.iterateWithProgress( + `Updating file status to '${status}'`, + files, + (file) => this.errorBroker.try(async () => { + const newFile = await this.fileService.updateFileStatus(file.id, status); + file.status = newFile.status; + }) + ); + } + } + } + + public async deletePermanently(files: File[]) { + if (files.length === 1) { + const deletionConfirmed = await this.openConfirmDialog( + "Confirm deletion", + "Do you really want to permanently delete this file?", + "Delete permanently", + "warn", + this.getImageThumbnail(files[0]), + ); + if (deletionConfirmed) { + await this.errorBroker.try(() => this.fileService.deleteFile(files[0].id)); + } + } else { + const deletionConfirmed = await this.openConfirmDialog( + "Confirm mass deletion", + `Do you really want to permanently delete ${files.length} files?`, + "Delete permanently", + "warn" + ); + if (deletionConfirmed) { + await this.iterateWithProgress( + "Deleting files", + files, + (file) => this.errorBroker.try(() => this.fileService.deleteFile(file.id)) + ); + } + } + } + + protected getImageThumbnail(file: File): SafeResourceUrl | undefined { + const mimeParts = FileHelper.parseMime(file.mimeType); + + if (mimeParts && ["image", "video"].includes(mimeParts[0])) { + return this.fileService.buildThumbnailUrl(file, 250, 250); + } else { + return; + } + } + + protected async iterateWithProgress(title: string, items: T[], action: (arg: T) => Promise): Promise { + const totalCount = items.length; + const dialogCtx = this.openProgressDialog(title, `0/${totalCount}`); + let count = 0; + + for (const item of items) { + await action(item); + dialogCtx.message.next(`${++count}/${totalCount}`); + dialogCtx.progress.next(count / totalCount); + } + dialogCtx.dialog.close(true); + } + + protected openProgressDialog(title: string, message: string): ProgressDialogContext { + const dialogMessage = new BehaviorSubject(message); + const dialogProgress = new BehaviorSubject(0); + + const dialog = this.dialog.open(BusyDialogComponent, { + data: { + message: dialogMessage, + progress: dialogProgress, + title, + allowCancel: false, + }, + disableClose: true, + minWidth: "30%", + minHeight: "30%", + }); + + return { + dialog, + message: dialogMessage, + progress: dialogProgress, + }; + } + + protected openConfirmDialog( + title: string, + question: string, + confirmAction: string, + confirmColor?: "primary" | "warn", + image?: SafeResourceUrl | string + ): Promise { + const dialog = this.dialog.open(ConfirmDialogComponent, { + data: { + title, + message: question, + confirmAction, + denyAction: "Cancel", + confirmColor, + image + } + } as MatDialogConfig & { data: ConfirmDialogData }); + return dialog.afterClosed().toPromise(); + } +} diff --git a/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.html b/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.html index 0001e15..5917d35 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.html +++ b/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.html @@ -1,23 +1,25 @@ - - - - - + - - + + diff --git a/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts b/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts index 59334f0..e385669 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts @@ -1,16 +1,12 @@ import {Component, EventEmitter, OnChanges, Output, SimpleChanges, ViewChild} from "@angular/core"; import {File} from "../../../../../api/models/File"; import {ContextMenuComponent} from "../../app-common/context-menu/context-menu.component"; -import {clipboard} from "@tauri-apps/api"; import {FileService} from "../../../../services/file/file.service"; import {ErrorBrokerService} from "../../../../services/error-broker/error-broker.service"; -import {FileHelper} from "../../../../services/file/file.helper"; -import {FileStatus} from "../../../../../api/api-types/files"; -import {MatDialog, MatDialogConfig, MatDialogRef} from "@angular/material/dialog"; +import {MatDialog, MatDialogRef} from "@angular/material/dialog"; import {BusyDialogComponent} from "../../app-common/busy-dialog/busy-dialog.component"; import {BehaviorSubject} from "rxjs"; -import {ConfirmDialogComponent, ConfirmDialogData} from "../../app-common/confirm-dialog/confirm-dialog.component"; -import {SafeResourceUrl} from "@angular/platform-browser"; +import {FileActionBaseComponent} from "../../app-base/file-action-base/file-action-base.component"; type ProgressDialogContext = { dialog: MatDialogRef, @@ -23,7 +19,7 @@ type ProgressDialogContext = { templateUrl: "./file-context-menu.component.html", styleUrls: ["./file-context-menu.component.scss"] }) -export class FileContextMenuComponent implements OnChanges { +export class FileContextMenuComponent extends FileActionBaseComponent implements OnChanges { public files: File[] = []; @@ -36,7 +32,8 @@ export class FileContextMenuComponent implements OnChanges { @ViewChild("contextMenu") contextMenu!: ContextMenuComponent; @Output() fileUpdate = new EventEmitter(); - constructor(private fileService: FileService, private errorBroker: ErrorBrokerService, private dialog: MatDialog) { + constructor(fileService: FileService, errorBroker: ErrorBrokerService, dialog: MatDialog) { + super(dialog, errorBroker, fileService); } public ngOnChanges(changes: SimpleChanges): void { @@ -51,99 +48,6 @@ export class FileContextMenuComponent implements OnChanges { this.contextMenu.onContextMenu(event); } - public async copyFileHash(): Promise { - await clipboard.writeText(this.files[0].cd); - } - - public async exportFile(): Promise { - const path = await FileHelper.getFileDownloadLocation(this.files[0]); - - if (path) { - try { - await this.fileService.saveFile(this.files[0], path); - } catch (err) { - this.errorBroker.showError(err); - } - } - } - - public async updateStatus(status: FileStatus) { - if (this.files.length === 1) { - let changeConfirmed; - - if (status === "Deleted") { - changeConfirmed = await this.openConfirmDialog( - "Confirm deletion", - "Do you really want to move this file to trash?", - "Delete", - "warn", - this.getImageThumbnail(this.files[0]) - ); - } else { - changeConfirmed = true; - } - - if (changeConfirmed) { - const newFile = await this.fileService.updateFileStatus(this.files[0].id, status); - this.files[0].status = newFile.status; - this.fileUpdate.emit(); - this.applyStatus(); - } - } else { - const statusChangeConfirmed = await this.openConfirmDialog( - "Confirm mass status change", - `Do you really want to change the status of ${this.files.length} files to '${status}'?`, - "Change status", - status === "Deleted" ? "warn" : "primary" - ); - if (statusChangeConfirmed) { - await this.iterateWithProgress( - `Updating file status to '${status}'`, - this.files, - async (file) => { - const newFile = await this.fileService.updateFileStatus(file.id, status); - file.status = newFile.status; - } - ); - this.fileUpdate.emit(); - this.applyStatus(); - } - } - } - - public async deletePermanently() { - if (this.files.length === 1) { - const deletionConfirmed = await this.openConfirmDialog( - "Confirm deletion", - "Do you really want to permanently delete this file?", - "Delete permanently", - "warn", - this.getImageThumbnail(this.files[0]), - ); - if (deletionConfirmed) { - await this.fileService.deleteFile(this.files[0].id); - this.fileUpdate.emit(); - this.applyStatus(); - } - } else { - const deletionConfirmed = await this.openConfirmDialog( - "Confirm mass deletion", - `Do you really want to permanently delete ${this.files.length} files?`, - "Delete permanently", - "warn" - ); - if (deletionConfirmed) { - await this.iterateWithProgress( - "Deleting files", - this.files, - async (file) => this.fileService.deleteFile(file.id) - ); - this.fileUpdate.emit(); - this.applyStatus(); - } - } - } - private applyStatus() { this.actionDeletePermantently = true; this.actionDelete = this.actionArchive = this.actionImported = this.actionRestore = false; @@ -156,70 +60,4 @@ export class FileContextMenuComponent implements OnChanges { this.actionRestore ||= file.status === "Deleted"; } } - - private getImageThumbnail(file: File): SafeResourceUrl | undefined { - const mimeParts = FileHelper.parseMime(file.mimeType); - - if (mimeParts && ["image", "video"].includes(mimeParts[0])) { - return this.fileService.buildThumbnailUrl(file, 250, 250); - } else { - return; - } - } - - private async iterateWithProgress(title: string, items: T[], action: (arg: T) => Promise): Promise { - const totalCount = items.length; - const dialogCtx = this.openProgressDialog(title, `0/${totalCount}`); - let count = 0; - - for (const item of items) { - await action(item); - dialogCtx.message.next(`${++count}/${totalCount}`); - dialogCtx.progress.next(count / totalCount); - } - dialogCtx.dialog.close(true); - } - - private openProgressDialog(title: string, message: string): ProgressDialogContext { - const dialogMessage = new BehaviorSubject(message); - const dialogProgress = new BehaviorSubject(0); - - const dialog = this.dialog.open(BusyDialogComponent, { - data: { - message: dialogMessage, - progress: dialogProgress, - title, - allowCancel: false, - }, - disableClose: true, - minWidth: "30%", - minHeight: "30%", - }); - - return { - dialog, - message: dialogMessage, - progress: dialogProgress, - }; - } - - private openConfirmDialog( - title: string, - question: string, - confirmAction: string, - confirmColor?: "primary" | "warn", - image?: SafeResourceUrl | string - ): Promise { - const dialog = this.dialog.open(ConfirmDialogComponent, { - data: { - title, - message: question, - confirmAction, - denyAction: "Cancel", - confirmColor, - image - } - } as MatDialogConfig & { data: ConfirmDialogData }); - return dialog.afterClosed().toPromise(); - } } diff --git a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html index 1ede790..c778839 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html +++ b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html @@ -1,8 +1,8 @@ -