Browse Source

Feature - add drag and drop functionality on import

pull/2323/head
Frane Caleta 2 years ago
committed by Thomas
parent
commit
338839197d
  1. 29
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/file-drop.directive.ts
  2. 111
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  3. 17
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html
  4. 3
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.module.ts
  5. 30
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss

29
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/file-drop.directive.ts

@ -0,0 +1,29 @@
import { Directive, HostListener, Output, EventEmitter } from '@angular/core';
@Directive({
selector: '[appFileDrop]'
})
export class FileDropDirective {
@Output() filesDropped = new EventEmitter<FileList>();
@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';
const files = event.dataTransfer.files;
this.filesDropped.emit(files);
}
}

111
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts

@ -175,6 +175,117 @@ export class ImportActivitiesDialog implements OnDestroy {
aStepper.reset(); aStepper.reset();
} }
public onFilesDropped(files: FileList): void {
if (files.length === 0) {
return;
}
const droppedFile = files[0];
// Check the file type and handle it accordingly (JSON/CSV)
if (
droppedFile.type === 'application/json' ||
droppedFile.name.endsWith('.json')
) {
// Handle JSON file
const reader = new FileReader();
reader.readAsText(droppedFile, 'UTF-8');
reader.onload = async (readerEvent) => {
const fileContent = readerEvent.target.result as string;
try {
const content = JSON.parse(fileContent);
this.accounts = content.accounts;
if (!Array.isArray(content.activities)) {
if (Array.isArray(content.orders)) {
// Handle orders (activities) data
const { activities } =
await this.importActivitiesService.importJson({
accounts: content.accounts,
activities: content.orders,
isDryRun: true
});
this.activities = activities;
} else {
throw new Error('Unexpected JSON format');
}
} else {
// Handle activities data
const { activities } =
await this.importActivitiesService.importJson({
accounts: content.accounts,
activities: content.activities,
isDryRun: true
});
this.activities = activities;
}
} catch (error) {
console.error(error);
this.handleImportError({
activities: [],
error: {
error: { message: ['Error handling JSON file'] }
}
});
} finally {
this.importStep = ImportStep.SELECT_ACTIVITIES;
this.snackBar.dismiss();
// Proceed with further steps or updates as needed
this.changeDetectorRef.markForCheck();
}
};
} else if (
droppedFile.type === 'text/csv' ||
droppedFile.name.endsWith('.csv')
) {
// Handle CSV file
const reader = new FileReader();
reader.readAsText(droppedFile, 'UTF-8');
reader.onload = async (readerEvent) => {
const fileContent = readerEvent.target.result as string;
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] }
}
});
} finally {
this.importStep = ImportStep.SELECT_ACTIVITIES;
this.snackBar.dismiss();
// Proceed with further steps or updates as needed
this.changeDetectorRef.markForCheck();
}
};
} else {
// Handle unsupported file type
console.error('Unsupported file format');
this.handleImportError({
activities: [],
error: {
error: { message: ['Unsupported file format'] }
}
});
this.importStep = ImportStep.SELECT_ACTIVITIES;
this.snackBar.dismiss();
// Proceed with further steps or updates as needed
this.changeDetectorRef.markForCheck();
}
}
public onSelectFile(aStepper: MatStepper) { public onSelectFile(aStepper: MatStepper) {
const input = document.createElement('input'); const input = document.createElement('input');
input.accept = 'application/JSON, .csv'; input.accept = 'application/JSON, .csv';

17
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html

@ -70,13 +70,18 @@
<ng-template #selectFile> <ng-template #selectFile>
<div class="d-flex flex-column justify-content-center"> <div class="d-flex flex-column justify-content-center">
<button <button
class="py-4" appFileDrop
color="primary" class="custom-button"
mat-stroked-button
(click)="onSelectFile(stepper)" (click)="onSelectFile(stepper)"
> (filesDropped)="onFilesDropped($event)"
<ion-icon class="mr-2" name="cloud-upload-outline"></ion-icon> >
<span i18n>Choose File</span> <div class="button-content">
<ion-icon
class="cloud-icon"
name="cloud-upload-outline"
></ion-icon>
<span i18n>Choose or drop a file here</span>
</div>
</button> </button>
<p class="mb-0 mt-4 text-center"> <p class="mb-0 mt-4 text-center">
<span class="mr-1" i18n <span class="mr-1" i18n

3
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.module.ts

@ -13,10 +13,11 @@ import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-heade
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module'; import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
import { FileDropDirective } from './file-drop.directive';
import { ImportActivitiesDialog } from './import-activities-dialog.component'; import { ImportActivitiesDialog } from './import-activities-dialog.component';
@NgModule({ @NgModule({
declarations: [ImportActivitiesDialog], declarations: [ImportActivitiesDialog, FileDropDirective],
imports: [ imports: [
CommonModule, CommonModule,
FormsModule, FormsModule,

30
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss

@ -32,4 +32,34 @@
right: 1.5rem; right: 1.5rem;
top: calc(50% - 10px); top: calc(50% - 10px);
} }
.custom-button {
border: 3px dashed rgba(var(--palette-primary-500), 1);
border-radius: 8px;
padding: 30px;
text-align: center;
background-color: inherit;
cursor: pointer;
transition: border-color 0.1s ease;
color: rgba(var(--palette-primary-500), 1);
.button-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-weight: 700;
}
.cloud-icon {
font-size: 30px; /* Adjust the icon size as needed */
margin-bottom: 8px; /* Adjust the spacing between icon and text */
color: inherit;
}
&:hover {
border-color: #28a745;
color: #28a745;
}
}
} }

Loading…
Cancel
Save