From 0edd5e4edcd42a36d5255702de0c0429b36ba072 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 16 Jan 2022 13:27:40 +0100 Subject: [PATCH] Add confirm dialog to changes of file status Signed-off-by: trivernis --- .../confirm-dialog.component.html | 1 + .../confirm-dialog.component.scss | 4 + .../confirm-dialog.component.ts | 27 +++- .../file-context-menu.component.ts | 129 +++++++++++++++--- mediarepo-ui/src/app/utils/filter-utils.ts | 6 +- 5 files changed, 136 insertions(+), 31 deletions(-) diff --git a/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.html b/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.html index 7f34ff5..89d3086 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.html +++ b/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.html @@ -2,6 +2,7 @@ {{title}}
+ {{message}}
diff --git a/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.scss b/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.scss index ff14107..4ede38e 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.scss +++ b/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.scss @@ -6,3 +6,7 @@ margin-left: 1em; } } + +app-content-aware-image { + margin-bottom: 1em; +} diff --git a/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.ts b/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.ts index 8fbe65f..6ef2095 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.ts +++ b/mediarepo-ui/src/app/components/shared/app-common/confirm-dialog/confirm-dialog.component.ts @@ -1,6 +1,17 @@ import {Component, Inject} from "@angular/core"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {ThemePalette} from "@angular/material/core"; +import {SafeResourceUrl} from "@angular/platform-browser"; + +export type ConfirmDialogData = { + title: string, + message: string, + image?: string | SafeResourceUrl, + confirmAction: string, + denyAction?: string, + confirmColor?: ThemePalette, + denyColor?: ThemePalette +}; @Component({ selector: "app-confirm-dialog", @@ -9,17 +20,18 @@ import {ThemePalette} from "@angular/material/core"; }) export class ConfirmDialogComponent { - title = ""; - message = ""; - confirmAction = ""; - confirmColor: ThemePalette = "primary"; - denyAction = "Cancel"; - denyColor: ThemePalette = "accent"; + public title = ""; + public message = ""; + public confirmAction = ""; + public image?: string | SafeResourceUrl; + public confirmColor: ThemePalette = "primary"; + public denyAction = "Cancel"; + public denyColor: ThemePalette = "accent"; constructor( public dialogRef: MatDialogRef, @Inject( - MAT_DIALOG_DATA) data: { title: string, message: string, confirmAction: string, denyAction?: string, confirmColor?: ThemePalette, denyColor?: ThemePalette} + MAT_DIALOG_DATA) data: ConfirmDialogData ) { this.title = data.title; this.message = data.message; @@ -27,6 +39,7 @@ export class ConfirmDialogComponent { this.denyAction = data.denyAction ?? this.denyAction; this.confirmColor = data.confirmColor ?? this.confirmColor; this.denyColor = data.denyColor ?? this.denyColor; + this.image = data.image; } public closeDialog(result: boolean) { 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 9db4dcf..59334f0 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,4 +1,4 @@ -import {Component, EventEmitter, Output, ViewChild} from "@angular/core"; +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"; @@ -6,9 +6,11 @@ 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, MatDialogRef} from "@angular/material/dialog"; +import {MatDialog, MatDialogConfig, 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"; type ProgressDialogContext = { dialog: MatDialogRef, @@ -21,7 +23,7 @@ type ProgressDialogContext = { templateUrl: "./file-context-menu.component.html", styleUrls: ["./file-context-menu.component.scss"] }) -export class FileContextMenuComponent { +export class FileContextMenuComponent implements OnChanges { public files: File[] = []; @@ -37,6 +39,12 @@ export class FileContextMenuComponent { constructor(private fileService: FileService, private errorBroker: ErrorBrokerService, private dialog: MatDialog) { } + public ngOnChanges(changes: SimpleChanges): void { + if (changes["files"]) { + this.applyStatus(); + } + } + public onContextMenu(event: MouseEvent, files: File[]) { this.files = files; this.applyStatus(); @@ -61,45 +69,104 @@ export class FileContextMenuComponent { public async updateStatus(status: FileStatus) { if (this.files.length === 1) { - const newFile = await this.fileService.updateFileStatus(this.files[0].id, status); - this.files[0].status = newFile.status; + 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 { - 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; - } + 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(); + } } - this.fileUpdate.emit(); } public async deletePermanently() { if (this.files.length === 1) { - await this.fileService.deleteFile(this.files[0].id); + 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 { - await this.iterateWithProgress( - "Deleting files", - this.files, - async (file) => this.fileService.deleteFile(file.id) + 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(); + } } - this.fileUpdate.emit(); } private applyStatus() { this.actionDeletePermantently = true; + this.actionDelete = this.actionArchive = this.actionImported = this.actionRestore = false; + for (const file of this.files) { this.actionDeletePermantently &&= file.status === "Deleted"; this.actionDelete ||= file.status !== "Deleted"; - this.actionArchive ||= file.status !== "Archived"; - this.actionImported ||= file.status !== "Imported"; + this.actionArchive ||= file.status !== "Archived" && file.status !== "Deleted"; + this.actionImported ||= file.status !== "Imported" && file.status !== "Deleted"; 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}`); @@ -135,4 +202,24 @@ export class FileContextMenuComponent { 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/utils/filter-utils.ts b/mediarepo-ui/src/app/utils/filter-utils.ts index 8a5e3ee..f805082 100644 --- a/mediarepo-ui/src/app/utils/filter-utils.ts +++ b/mediarepo-ui/src/app/utils/filter-utils.ts @@ -29,7 +29,7 @@ function tagQueryToString(tagQuery: TagQuery): string { export function propertyQueryToString(propertyQuery: PropertyQuery): string { if ("Status" in propertyQuery) { - return buildExpression("Status", "is", propertyQuery.Status); + return buildExpression("Status", "=", propertyQuery.Status); } else if ("FileSize" in propertyQuery) { return buildExpression( "FileSize", @@ -61,9 +61,9 @@ export function propertyQueryToString(propertyQuery: PropertyQuery): string { getValue(propertyQuery.TagCount).toString() ); } else if ("Cd" in propertyQuery) { - return buildExpression("ContentDescriptor", "is", propertyQuery.Cd); + return buildExpression("ContentDescriptor", "=", propertyQuery.Cd); } else if ("Id" in propertyQuery) { - return buildExpression("FileId", "is", propertyQuery.Id.toString()); + return buildExpression("FileId", "=", propertyQuery.Id.toString()); } else { return "Invalid Expression"; }