Browse Source

Various improvements

pull/2323/head
Thomas 2 years ago
parent
commit
6900624e85
  1. 0
      apps/client/src/app/directives/file-drop/file-drop.directive.ts
  2. 9
      apps/client/src/app/directives/file-drop/file-drop.module.ts
  3. 120
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  4. 12
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html
  5. 5
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.module.ts
  6. 34
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.scss
  7. 5
      apps/client/src/styles.scss

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

9
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 {}

120
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) { public onImportStepChange(event: StepperSelectionEvent) {
if (event.selectedIndex === ImportStep.UPLOAD_FILE) { if (event.selectedIndex === ImportStep.UPLOAD_FILE) {
this.importStep = ImportStep.UPLOAD_FILE; this.importStep = ImportStep.UPLOAD_FILE;
@ -175,7 +189,38 @@ export class ImportActivitiesDialog implements OnDestroy {
aStepper.reset(); aStepper.reset();
} }
private async handleFile(file: File): Promise<void> { public onSelectFile(stepper: MatStepper) {
const input = document.createElement('input');
input.accept = 'application/JSON, .csv';
input.type = 'file';
input.onchange = (event) => {
// Getting the file reference
const file = (event.target as HTMLInputElement).files[0];
this.handleFile({ file, stepper });
};
input.click();
}
public updateSelection(activities: Activity[]) {
this.selectedActivities = activities.filter(({ error }) => {
return !error;
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private async handleFile({
file,
stepper
}: {
file: File;
stepper: MatStepper;
}): Promise<void> {
this.snackBar.open('⏳ ' + $localize`Validating data...`); this.snackBar.open('⏳ ' + $localize`Validating data...`);
// Setting up the reader // Setting up the reader
@ -186,12 +231,13 @@ export class ImportActivitiesDialog implements OnDestroy {
const fileContent = readerEvent.target.result as string; const fileContent = readerEvent.target.result as string;
try { try {
if (file.type === 'application/json' || file.name.endsWith('.json')) { if (file.name.endsWith('.json')) {
const content = JSON.parse(fileContent); const content = JSON.parse(fileContent);
this.accounts = content.accounts; this.accounts = content.accounts;
if (!Array.isArray(content.activities)) { if (!isArray(content.activities)) {
if (Array.isArray(content.orders)) { if (isArray(content.orders)) {
this.handleImportError({ this.handleImportError({
activities: [], activities: [],
error: { error: {
@ -206,65 +252,57 @@ export class ImportActivitiesDialog implements OnDestroy {
} }
} }
const { activities } = await this.importActivitiesService.importJson({ try {
const { activities } =
await this.importActivitiesService.importJson({
accounts: content.accounts, accounts: content.accounts,
activities: content.activities, activities: content.activities,
isDryRun: true isDryRun: true
}); });
this.activities = activities; this.activities = activities;
} else if (file.type === 'text/csv' || file.name.endsWith('.csv')) { } 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({ const data = await this.importActivitiesService.importCsv({
fileContent, fileContent,
isDryRun: true, isDryRun: true,
userAccounts: this.data.user.accounts userAccounts: this.data.user.accounts
}); });
this.activities = data.activities; this.activities = data.activities;
} else {
throw new Error();
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
this.handleImportError({ error, activities: [] }); this.handleImportError({
} finally { activities: error?.activities ?? [],
this.importStep = ImportStep.SELECT_ACTIVITIES; error: {
this.snackBar.dismiss(); error: { message: error?.error?.message ?? [error?.message] }
this.changeDetectorRef.markForCheck();
} }
}; });
} }
public onFilesDropped(files: FileList): void {
if (files.length === 0) {
return; return;
} }
const droppedFile = files[0]; throw new Error();
this.handleFile(droppedFile); } catch (error) {
} console.error(error);
this.handleImportError({
public onSelectFile(aStepper: MatStepper) { activities: [],
const input = document.createElement('input'); error: { error: { message: ['Unexpected format'] } }
input.accept = 'application/JSON, .csv'; });
input.type = 'file'; } finally {
this.importStep = ImportStep.SELECT_ACTIVITIES;
input.onchange = (event) => { this.snackBar.dismiss();
// Getting the file reference
const file = (event.target as HTMLInputElement).files[0];
this.handleFile(file);
};
input.click(); stepper.next();
}
public updateSelection(activities: Activity[]) { this.changeDetectorRef.markForCheck();
this.selectedActivities = activities.filter(({ error }) => {
return !error;
});
} }
};
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
} }
private handleImportError({ private handleImportError({

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

@ -70,13 +70,13 @@
<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="drop-area cursor-pointer text-center p-4" class="drop-area p-4 text-center text-muted"
gfFileDrop gfFileDrop
(click)="onSelectFile(stepper)" (click)="onSelectFile(stepper)"
(filesDropped)="onFilesDropped($event)" (filesDropped)="onFilesDropped({stepper, files: $event})"
> >
<div <div
class="button-content d-flex align-items-center justify-content-center flex-column" class="align-items-center d-flex flex-column justify-content-center"
> >
<ion-icon <ion-icon
class="cloud-icon" class="cloud-icon"
@ -85,7 +85,8 @@
<span i18n>Choose or drop a file here</span> <span i18n>Choose or drop a file here</span>
</div> </div>
</button> </button>
<p class="mb-0 mt-4 text-center"> <p class="mb-0 mt-3 text-center">
<small>
<span class="mr-1" i18n <span class="mr-1" i18n
>The following file formats are supported:</span >The following file formats are supported:</span
> >
@ -100,6 +101,7 @@
target="_blank" target="_blank"
>JSON</a >JSON</a
> >
</small>
</p> </p>
</div> </div>
</ng-template> </ng-template>
@ -116,7 +118,7 @@
> >
</ng-template> </ng-template>
<div class="pt-3"> <div class="pt-3">
<ng-container *ngIf="errorMessages.length === 0; else errorMessage"> <ng-container *ngIf="errorMessages?.length === 0; else errorMessage">
<gf-activities-table <gf-activities-table
*ngIf="importStep === 1" *ngIf="importStep === 1"
[activities]="activities" [activities]="activities"

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

@ -10,20 +10,21 @@ import { MatSelectModule } from '@angular/material/select';
import { MatStepperModule } from '@angular/material/stepper'; import { MatStepperModule } from '@angular/material/stepper';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module'; import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module'; import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { GfFileDropModule } from '@ghostfolio/client/directives/file-drop/file-drop.module';
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: [FileDropDirective, ImportActivitiesDialog], declarations: [ImportActivitiesDialog],
imports: [ imports: [
CommonModule, CommonModule,
FormsModule, FormsModule,
GfActivitiesTableModule, GfActivitiesTableModule,
GfDialogFooterModule, GfDialogFooterModule,
GfDialogHeaderModule, GfDialogHeaderModule,
GfFileDropModule,
GfSymbolModule, GfSymbolModule,
MatButtonModule, MatButtonModule,
MatDialogModule, MatDialogModule,

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

@ -34,26 +34,30 @@
} }
.drop-area { .drop-area {
border: 3px dashed rgba(var(--palette-primary-500), 1); background-color: rgba(var(--palette-foreground-base), 0.02);
border-radius: 8px; border: 1px dashed
background-color: inherit; rgba(
transition: border-color 0.1s ease; var(--palette-foreground-divider),
color: rgba(var(--palette-primary-500), 1); var(--palette-foreground-divider-alpha)
);
border-radius: 0.25rem;
.button-content { &:hover {
font-weight: bolder; border-color: rgba(var(--palette-primary-500), 1) !important;
color: rgba(var(--palette-primary-500), 1);
} }
.cloud-icon { .cloud-icon {
font-size: 35px; /* Adjust the icon size as needed */ font-size: 2.5rem;
font-weight: bolder; }
margin-bottom: 8px; /* Adjust the spacing between icon and text */
color: inherit;
} }
&:hover {
border-color: #28a745;
color: #28a745;
} }
:host-context(.is-dark-theme) {
.drop-area {
border-color: rgba(
var(--palette-foreground-divider-dark),
var(--palette-foreground-divider-alpha-dark)
);
} }
} }

5
apps/client/src/styles.scss

@ -430,6 +430,11 @@ ngx-skeleton-loader {
} }
} }
.mat-stepper-vertical,
.mat-stepper-horizontal {
background: transparent !important;
}
.mdc-button { .mdc-button {
&.mat-accent, &.mat-accent,
&.mat-primary { &.mat-primary {

Loading…
Cancel
Save