diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index b07e4ce16..5a8d2ae4e 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -104,6 +104,7 @@ import { RuleSetActionsComponent } from './components/rule-set-actions/rule-set- import { RuleSetViewSwitchComponent } from './components/rule-set-view-switch/rule-set-view-switch.component'; import { MatSliderModule } from '@angular/material/slider'; import { PendingChangesGuard } from './utils/can-deactivate.guard'; +import { OverwriteFilesDialogComponent } from './dialogs/overwrite-files-dialog/overwrite-files-dialog.component'; export function HttpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); @@ -276,6 +277,7 @@ const matImports = [ ProjectOverviewScreenComponent, AddEditProjectDialogComponent, ConfirmationDialogComponent, + OverwriteFilesDialogComponent, FilePreviewScreenComponent, PdfViewerComponent, AssignOwnerDialogComponent, diff --git a/apps/red-ui/src/app/dialogs/dialog.service.ts b/apps/red-ui/src/app/dialogs/dialog.service.ts index 6c39092b4..edf13c942 100644 --- a/apps/red-ui/src/app/dialogs/dialog.service.ts +++ b/apps/red-ui/src/app/dialogs/dialog.service.ts @@ -22,6 +22,7 @@ import { ManualAnnotationService } from '../screens/file/service/manual-annotati import { ProjectWrapper } from '../state/model/project.wrapper'; import { AddEditDictionaryDialogComponent } from '../screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component'; import { AddEditRuleSetDialogComponent } from '../screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component'; +import { OverwriteFilesDialogComponent } from './overwrite-files-dialog/overwrite-files-dialog.component'; const dialogConfig = { width: '662px', @@ -331,4 +332,18 @@ export class DialogService { return ref; } + + openOverwriteFileDialog(filename: string): Promise<{ option?: 'overwrite' | 'no-overwrite'; remember?: boolean; cancel?: boolean }> { + const ref = this._dialog.open(OverwriteFilesDialogComponent, { + ...dialogConfig, + data: filename + }); + + return ref + .afterClosed() + .toPromise() + .then((res) => { + return res || { cancel: true }; + }); + } } diff --git a/apps/red-ui/src/app/dialogs/overwrite-files-dialog/overwrite-files-dialog.component.html b/apps/red-ui/src/app/dialogs/overwrite-files-dialog/overwrite-files-dialog.component.html new file mode 100644 index 000000000..962a5daa9 --- /dev/null +++ b/apps/red-ui/src/app/dialogs/overwrite-files-dialog/overwrite-files-dialog.component.html @@ -0,0 +1,17 @@ +
+
+ +
+

+ + + {{ 'overwrite-files-dialog.options.remember' | translate }} + +
+ +
+
+
+
+
+
diff --git a/apps/red-ui/src/app/dialogs/overwrite-files-dialog/overwrite-files-dialog.component.scss b/apps/red-ui/src/app/dialogs/overwrite-files-dialog/overwrite-files-dialog.component.scss new file mode 100644 index 000000000..d2bca127f --- /dev/null +++ b/apps/red-ui/src/app/dialogs/overwrite-files-dialog/overwrite-files-dialog.component.scss @@ -0,0 +1,7 @@ +mat-checkbox { + margin-top: 16px; +} + +.dialog-actions > div:not(:last-child) { + margin-right: 32px; +} diff --git a/apps/red-ui/src/app/dialogs/overwrite-files-dialog/overwrite-files-dialog.component.ts b/apps/red-ui/src/app/dialogs/overwrite-files-dialog/overwrite-files-dialog.component.ts new file mode 100644 index 000000000..0f0d93a51 --- /dev/null +++ b/apps/red-ui/src/app/dialogs/overwrite-files-dialog/overwrite-files-dialog.component.ts @@ -0,0 +1,28 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'redaction-overwrite-files-dialog', + templateUrl: './overwrite-files-dialog.component.html', + styleUrls: ['./overwrite-files-dialog.component.scss'] +}) +export class OverwriteFilesDialogComponent implements OnInit { + public remember = false; + + constructor( + private readonly _translateService: TranslateService, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public filename: string + ) {} + + ngOnInit(): void {} + + cancel() { + this.dialogRef.close(); + } + + selectOption(option: 'overwrite' | 'no-overwrite') { + this.dialogRef.close({ option, remember: this.remember }); + } +} diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts index 1df08a03c..bf8d37810 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts @@ -212,13 +212,15 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy { event.preventDefault(); } - uploadFiles(files: File[] | FileList) { - this._uploadFiles(convertFiles(files, this.appStateService.activeProjectId)); + async uploadFiles(files: File[] | FileList) { + await this._uploadFiles(convertFiles(files, this.appStateService.activeProjectId)); } - private _uploadFiles(files: FileUploadModel[]) { - this._fileUploadService.uploadFiles(files); - this._uploadStatusOverlayService.openStatusOverlay(); + private async _uploadFiles(files: FileUploadModel[]) { + const fileCount = await this._fileUploadService.uploadFiles(files); + if (fileCount) { + this._uploadStatusOverlayService.openStatusOverlay(); + } this._changeDetectorRef.detectChanges(); } diff --git a/apps/red-ui/src/app/upload/file-drop/file-drop.component.ts b/apps/red-ui/src/app/upload/file-drop/file-drop.component.ts index 193f8558a..663dbe54b 100644 --- a/apps/red-ui/src/app/upload/file-drop/file-drop.component.ts +++ b/apps/red-ui/src/app/upload/file-drop/file-drop.component.ts @@ -44,9 +44,11 @@ export class FileDropComponent implements OnInit { event.preventDefault(); } - uploadFiles(files: FileUploadModel[]) { - this._fileUploadService.uploadFiles(files); - this._uploadStatusOverlayService.openStatusOverlay(); + async uploadFiles(files: FileUploadModel[]) { + const fileCount = await this._fileUploadService.uploadFiles(files); + if (fileCount) { + this._uploadStatusOverlayService.openStatusOverlay(); + } this._dialogRef.detach(); this._changeDetectorRef.detectChanges(); } diff --git a/apps/red-ui/src/app/upload/file-upload.service.ts b/apps/red-ui/src/app/upload/file-upload.service.ts index d02960f80..dbd377a24 100644 --- a/apps/red-ui/src/app/upload/file-upload.service.ts +++ b/apps/red-ui/src/app/upload/file-upload.service.ts @@ -6,6 +6,7 @@ import { FileManagementControllerService } from '@redaction/red-ui-http'; import { interval, Subscription } from 'rxjs'; import { AppConfigKey, AppConfigService } from '../app-config/app-config.service'; import { TranslateService } from '@ngx-translate/core'; +import { DialogService } from '../dialogs/dialog.service'; @Injectable({ providedIn: 'root' @@ -23,7 +24,8 @@ export class FileUploadService { private readonly _applicationRef: ApplicationRef, private readonly _translateService: TranslateService, private readonly _appConfigService: AppConfigService, - private readonly _fileManagementControllerService: FileManagementControllerService + private readonly _fileManagementControllerService: FileManagementControllerService, + private readonly _dialogService: DialogService ) { interval(2500).subscribe((val) => { this._handleUploads(); @@ -39,20 +41,41 @@ export class FileUploadService { } } - uploadFiles(files: FileUploadModel[]) { + async uploadFiles(files: FileUploadModel[]): Promise { const maxSizeMB = this._appConfigService.getConfig(AppConfigKey.MAX_FILE_SIZE_MB, 50); const maxSizeBytes = maxSizeMB * 1024 * 1024; - files.forEach((file) => { + const projectFiles = this._appStateService.activeProject.files; + let option: 'overwrite' | 'no-overwrite' | undefined = undefined; + for (let idx = 0; idx < files.length; ++idx) { + const file = files[idx]; + let currentOption = option; + if (!!projectFiles.find((pf) => pf.filename === file.file.name)) { + if (!option) { + const res = await this._dialogService.openOverwriteFileDialog(file.file.name); + if (res.cancel) { + return; + } + currentOption = res.option; + option = res.remember ? currentOption : undefined; + } + + if (currentOption === 'no-overwrite') { + files.splice(idx, 1); + --idx; + continue; + } + } if (file.size > maxSizeBytes) { file.completed = true; file.error = { message: this._translateService.instant('upload-status.error.file-size', { size: maxSizeMB }) }; file.sizeError = true; } - }); + } this.files.push(...files); files.forEach((newFile) => { this.scheduleUpload(newFile); }); + return files.length; } stopAllUploads() { diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 79b16d29f..d546729e5 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -695,5 +695,15 @@ "rule-editor": "Rule Editor", "watermark": "Watermark", "pending-changes-guard": "WARNING: You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.", - "reset-filters": "Reset Filters" + "reset-filters": "Reset Filters", + "overwrite-files-dialog": { + "title": "File already exists!", + "question": "{{filename}} already exists. What do you want to do?", + "options": { + "overwrite": "Overwrite", + "no-overwrite": "Keep old file", + "cancel": "Cancel all uploads", + "remember": "Remember option" + } + } }