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) {
if (event.selectedIndex === ImportStep.UPLOAD_FILE) {
this.importStep = ImportStep.UPLOAD_FILE;
@ -175,7 +189,38 @@ export class ImportActivitiesDialog implements OnDestroy {
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...`);
// Setting up the reader
@ -186,12 +231,13 @@ export class ImportActivitiesDialog implements OnDestroy {
const fileContent = readerEvent.target.result as string;
try {
if (file.type === 'application/json' || file.name.endsWith('.json')) {
if (file.name.endsWith('.json')) {
const content = JSON.parse(fileContent);
this.accounts = content.accounts;
if (!Array.isArray(content.activities)) {
if (Array.isArray(content.orders)) {
if (!isArray(content.activities)) {
if (isArray(content.orders)) {
this.handleImportError({
activities: [],
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,
activities: content.activities,
isDryRun: true
});
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({
fileContent,
isDryRun: true,
userAccounts: this.data.user.accounts
});
this.activities = data.activities;
} else {
throw new Error();
}
} catch (error) {
console.error(error);
this.handleImportError({ error, activities: [] });
} finally {
this.importStep = ImportStep.SELECT_ACTIVITIES;
this.snackBar.dismiss();
this.changeDetectorRef.markForCheck();
this.handleImportError({
activities: error?.activities ?? [],
error: {
error: { message: error?.error?.message ?? [error?.message] }
}
};
});
}
public onFilesDropped(files: FileList): void {
if (files.length === 0) {
return;
}
const droppedFile = files[0];
this.handleFile(droppedFile);
}
public onSelectFile(aStepper: 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);
};
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();
input.click();
}
stepper.next();
public updateSelection(activities: Activity[]) {
this.selectedActivities = activities.filter(({ error }) => {
return !error;
});
this.changeDetectorRef.markForCheck();
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
};
}
private handleImportError({

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

@ -70,13 +70,13 @@
<ng-template #selectFile>
<div class="d-flex flex-column justify-content-center">
<button
class="drop-area cursor-pointer text-center p-4"
class="drop-area p-4 text-center text-muted"
gfFileDrop
(click)="onSelectFile(stepper)"
(filesDropped)="onFilesDropped($event)"
(filesDropped)="onFilesDropped({stepper, files: $event})"
>
<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
class="cloud-icon"
@ -85,7 +85,8 @@
<span i18n>Choose or drop a file here</span>
</div>
</button>
<p class="mb-0 mt-4 text-center">
<p class="mb-0 mt-3 text-center">
<small>
<span class="mr-1" i18n
>The following file formats are supported:</span
>
@ -100,6 +101,7 @@
target="_blank"
>JSON</a
>
</small>
</p>
</div>
</ng-template>
@ -116,7 +118,7 @@
>
</ng-template>
<div class="pt-3">
<ng-container *ngIf="errorMessages.length === 0; else errorMessage">
<ng-container *ngIf="errorMessages?.length === 0; else errorMessage">
<gf-activities-table
*ngIf="importStep === 1"
[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 { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.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 { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
import { FileDropDirective } from './file-drop.directive';
import { ImportActivitiesDialog } from './import-activities-dialog.component';
@NgModule({
declarations: [FileDropDirective, ImportActivitiesDialog],
declarations: [ImportActivitiesDialog],
imports: [
CommonModule,
FormsModule,
GfActivitiesTableModule,
GfDialogFooterModule,
GfDialogHeaderModule,
GfFileDropModule,
GfSymbolModule,
MatButtonModule,
MatDialogModule,

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

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

Loading…
Cancel
Save