From 369386f97643313187c850a3f2d4a0481e279909 Mon Sep 17 00:00:00 2001 From: Frane Caleta Date: Thu, 14 Sep 2023 21:12:48 +0200 Subject: [PATCH] Add drop file functionality on import (#2323) * Add drop file functionality on import * Update changelog --------- Co-authored-by: Thomas <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 4 + .../file-drop/file-drop.directive.ts | 28 +++ .../directives/file-drop/file-drop.module.ts | 9 + .../import-activities-dialog.component.ts | 191 ++++++++++-------- .../import-activities-dialog.html | 51 +++-- .../import-activities-dialog.module.ts | 2 + .../import-activities-dialog.scss | 28 +++ apps/client/src/styles.scss | 5 + 8 files changed, 213 insertions(+), 105 deletions(-) create mode 100644 apps/client/src/app/directives/file-drop/file-drop.directive.ts create mode 100644 apps/client/src/app/directives/file-drop/file-drop.module.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d2424f99..b96fb62f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added support to drop a file in the import activities dialog + ### Changed - Harmonized the logger output: () diff --git a/apps/client/src/app/directives/file-drop/file-drop.directive.ts b/apps/client/src/app/directives/file-drop/file-drop.directive.ts new file mode 100644 index 00000000..65f3ed23 --- /dev/null +++ b/apps/client/src/app/directives/file-drop/file-drop.directive.ts @@ -0,0 +1,28 @@ +import { Directive, HostListener, Output, EventEmitter } from '@angular/core'; + +@Directive({ + selector: '[gfFileDrop]' +}) +export class FileDropDirective { + @Output() filesDropped = new EventEmitter(); + + @HostListener('dragenter', ['$event']) onDragEnter(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + } + + @HostListener('dragover', ['$event']) onDragOver(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + } + + @HostListener('drop', ['$event']) onDrop(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + + // Prevent the browser's default behavior for handling the file drop + event.dataTransfer.dropEffect = 'copy'; + + this.filesDropped.emit(event.dataTransfer.files); + } +} diff --git a/apps/client/src/app/directives/file-drop/file-drop.module.ts b/apps/client/src/app/directives/file-drop/file-drop.module.ts new file mode 100644 index 00000000..a0148516 --- /dev/null +++ b/apps/client/src/app/directives/file-drop/file-drop.module.ts @@ -0,0 +1,9 @@ +import { NgModule } from '@angular/core'; + +import { FileDropDirective } from './file-drop.directive'; + +@NgModule({ + declarations: [FileDropDirective], + exports: [FileDropDirective] +}) +export class GfFileDropModule {} diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index 46a30731..d11cafdb 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -137,6 +137,20 @@ export class ImportActivitiesDialog implements OnDestroy { } } + public onFilesDropped({ + files, + stepper + }: { + files: FileList; + stepper: MatStepper; + }): void { + if (files.length === 0) { + return; + } + + this.handleFile({ stepper, file: files[0] }); + } + public onImportStepChange(event: StepperSelectionEvent) { if (event.selectedIndex === ImportStep.UPLOAD_FILE) { this.importStep = ImportStep.UPLOAD_FILE; @@ -175,97 +189,15 @@ export class ImportActivitiesDialog implements OnDestroy { aStepper.reset(); } - public onSelectFile(aStepper: MatStepper) { + public onSelectFile(stepper: MatStepper) { const input = document.createElement('input'); input.accept = 'application/JSON, .csv'; input.type = 'file'; input.onchange = (event) => { - this.snackBar.open('⏳ ' + $localize`Validating data...`); - // Getting the file reference const file = (event.target as HTMLInputElement).files[0]; - - // Setting up the reader - const reader = new FileReader(); - reader.readAsText(file, 'UTF-8'); - - reader.onload = async (readerEvent) => { - const fileContent = readerEvent.target.result as string; - - try { - if (file.name.endsWith('.json')) { - const content = JSON.parse(fileContent); - - this.accounts = content.accounts; - - if (!isArray(content.activities)) { - if (isArray(content.orders)) { - this.handleImportError({ - activities: [], - error: { - error: { - message: [`orders needs to be renamed to activities`] - } - } - }); - return; - } else { - throw new Error(); - } - } - - try { - const { activities } = - await this.importActivitiesService.importJson({ - accounts: content.accounts, - activities: content.activities, - isDryRun: true - }); - this.activities = activities; - } catch (error) { - console.error(error); - this.handleImportError({ error, activities: content.activities }); - } - - return; - } else if (file.name.endsWith('.csv')) { - try { - const data = await this.importActivitiesService.importCsv({ - fileContent, - isDryRun: true, - userAccounts: this.data.user.accounts - }); - this.activities = data.activities; - } catch (error) { - console.error(error); - this.handleImportError({ - activities: error?.activities ?? [], - error: { - error: { message: error?.error?.message ?? [error?.message] } - } - }); - } - - return; - } - - throw new Error(); - } catch (error) { - console.error(error); - this.handleImportError({ - activities: [], - error: { error: { message: ['Unexpected format'] } } - }); - } finally { - this.importStep = ImportStep.SELECT_ACTIVITIES; - this.snackBar.dismiss(); - - aStepper.next(); - - this.changeDetectorRef.markForCheck(); - } - }; + this.handleFile({ file, stepper }); }; input.click(); @@ -282,6 +214,97 @@ export class ImportActivitiesDialog implements OnDestroy { this.unsubscribeSubject.complete(); } + private async handleFile({ + file, + stepper + }: { + file: File; + stepper: MatStepper; + }): Promise { + this.snackBar.open('⏳ ' + $localize`Validating data...`); + + // Setting up the reader + const reader = new FileReader(); + reader.readAsText(file, 'UTF-8'); + + reader.onload = async (readerEvent) => { + const fileContent = readerEvent.target.result as string; + + try { + if (file.name.endsWith('.json')) { + const content = JSON.parse(fileContent); + + this.accounts = content.accounts; + + if (!isArray(content.activities)) { + if (isArray(content.orders)) { + this.handleImportError({ + activities: [], + error: { + error: { + message: [`orders needs to be renamed to activities`] + } + } + }); + return; + } else { + throw new Error(); + } + } + + try { + const { activities } = + await this.importActivitiesService.importJson({ + accounts: content.accounts, + activities: content.activities, + isDryRun: true + }); + this.activities = activities; + } catch (error) { + console.error(error); + this.handleImportError({ error, activities: content.activities }); + } + + return; + } else if (file.name.endsWith('.csv')) { + try { + const data = await this.importActivitiesService.importCsv({ + fileContent, + isDryRun: true, + userAccounts: this.data.user.accounts + }); + this.activities = data.activities; + } catch (error) { + console.error(error); + this.handleImportError({ + activities: error?.activities ?? [], + error: { + error: { message: error?.error?.message ?? [error?.message] } + } + }); + } + + return; + } + + throw new Error(); + } catch (error) { + console.error(error); + this.handleImportError({ + activities: [], + error: { error: { message: ['Unexpected format'] } } + }); + } finally { + this.importStep = ImportStep.SELECT_ACTIVITIES; + this.snackBar.dismiss(); + + stepper.next(); + + this.changeDetectorRef.markForCheck(); + } + }; + } + private handleImportError({ activities, error diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html index 02071c13..06cadad8 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -70,29 +70,38 @@
-

- The following file formats are supported: - CSV - or - JSON +

+ + The following file formats are supported: + CSV + or + JSON +

@@ -109,7 +118,7 @@ >
- +