Browse Source

Merge d5c2009703 into c2cb508f22

pull/8242/merge
Mister_Ideas 1 day ago
committed by GitHub
parent
commit
cbd89396dc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 1
      libs/ui/src/lib/accounts-table/accounts-table.component.html
  3. 2
      libs/ui/src/lib/accounts-table/accounts-table.component.ts
  4. 11
      libs/ui/src/lib/holdings-table/holdings-table.component.html
  5. 13
      libs/ui/src/lib/holdings-table/holdings-table.component.ts
  6. 1
      libs/ui/src/lib/persist-sort/index.ts
  7. 70
      libs/ui/src/lib/persist-sort/persist-sort.directive.spec.ts
  8. 57
      libs/ui/src/lib/persist-sort/persist-sort.directive.ts

3
CHANGELOG.md

@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Improved the holdings table of the home overview by adding the sorting by name and performance
- Changed the default sorting of the holdings table to the performance in descending order
- Persisted the sorting of the holdings and accounts tables on the device
- Improved the market price in the benchmark detail dialog by adding the currency and reducing the precision on mobile
- Improved the style of the close button in the dialog footer component
- Improved the style of the close button in the dialog header component

1
libs/ui/src/lib/accounts-table/accounts-table.component.html

@ -20,6 +20,7 @@
matSortActive="name"
matSortDirection="asc"
[dataSource]="dataSource"
[gfPersistSort]="'accounts'"
>
<ng-container matColumnDef="status">
<th

2
libs/ui/src/lib/accounts-table/accounts-table.component.ts

@ -8,6 +8,7 @@ import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { AccountWithValue } from '@ghostfolio/common/types';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';
import { NotificationService } from '@ghostfolio/ui/notifications';
import { GfPersistSortDirective } from '@ghostfolio/ui/persist-sort';
import { GfValueComponent } from '@ghostfolio/ui/value';
import {
@ -42,6 +43,7 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
GfEntityLogoComponent,
GfPersistSortDirective,
GfValueComponent,
IonIcon,
MatButtonModule,

11
libs/ui/src/lib/holdings-table/holdings-table.component.html

@ -5,8 +5,9 @@
matSort
[class.simple]="mode() === 'simple'"
[dataSource]="dataSource"
[matSortActive]="sortActive()"
[matSortDirection]="sortDirection()"
[gfPersistSort]="'holdings-' + mode()"
[matSortActive]="sortActive"
[matSortDirection]="sortDirection"
>
<ng-container matColumnDef="icon" [sticky]="mode() === 'default'">
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
@ -210,11 +211,7 @@
</td>
</ng-container>
<tr
*matHeaderRowDef="displayedColumns()"
mat-header-row
[class.d-none]="mode() === 'simple'"
></tr>
<tr *matHeaderRowDef="displayedColumns()" mat-header-row></tr>
<tr
*matRowDef="let row; columns: displayedColumns()"
mat-row

13
libs/ui/src/lib/holdings-table/holdings-table.component.ts

@ -27,6 +27,7 @@ import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component';
import { GfPersistSortDirective } from '../persist-sort/persist-sort.directive';
import { GfValueComponent } from '../value/value.component';
import { HoldingsTableItem } from './interfaces/interfaces';
@ -34,6 +35,7 @@ import { HoldingsTableItem } from './interfaces/interfaces';
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
GfEntityLogoComponent,
GfPersistSortDirective,
GfValueComponent,
MatButtonModule,
MatDialogModule,
@ -101,15 +103,8 @@ export class GfHoldingsTableComponent {
return !this.holdings();
});
protected readonly sortActive = computed(() => {
return this.mode() === 'default'
? 'allocationInPercentage'
: 'assetProfile.name';
});
protected readonly sortDirection = computed<SortDirection>(() => {
return this.mode() === 'default' ? 'desc' : 'asc';
});
protected readonly sortActive = 'netPerformancePercentWithCurrencyEffect';
protected readonly sortDirection: SortDirection = 'desc';
public constructor() {
this.dataSource.sortingDataAccessor = (holding, path) => {

1
libs/ui/src/lib/persist-sort/index.ts

@ -0,0 +1 @@
export * from './persist-sort.directive';

70
libs/ui/src/lib/persist-sort/persist-sort.directive.spec.ts

@ -0,0 +1,70 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { GfPersistSortDirective } from './persist-sort.directive';
@Component({
imports: [GfPersistSortDirective, MatSortModule],
template: `<table
gfPersistSort="test"
matSort
matSortActive="name"
matSortDirection="asc"
></table>`
})
class TestHostComponent {}
describe('GfPersistSortDirective', () => {
let fixture: ComponentFixture<TestHostComponent>;
const create = () => {
fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
return fixture.debugElement.children[0].injector.get(MatSort);
};
beforeEach(() => {
localStorage.clear();
TestBed.configureTestingModule({ imports: [TestHostComponent] });
});
it('keeps the default sorting without a stored value', () => {
const sort = create();
expect(sort.active).toBe('name');
expect(sort.direction).toBe('asc');
});
it('stores the sorting on change', () => {
const sort = create();
sort.sort({ id: 'value', start: 'desc', disableClear: false });
expect(JSON.parse(localStorage.getItem('gf-sort-test') ?? '{}')).toEqual({
active: 'value',
direction: 'desc'
});
});
it('restores a stored sorting', () => {
localStorage.setItem(
'gf-sort-test',
JSON.stringify({ active: 'value', direction: 'desc' })
);
const sort = create();
expect(sort.active).toBe('value');
expect(sort.direction).toBe('desc');
});
it('ignores an invalid stored sorting', () => {
localStorage.setItem('gf-sort-test', '{"active":"value","direction":"x"}');
const sort = create();
expect(sort.active).toBe('name');
});
});

57
libs/ui/src/lib/persist-sort/persist-sort.directive.ts

@ -0,0 +1,57 @@
import { DestroyRef, Directive, inject, input, OnInit } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatSort, SortDirection } from '@angular/material/sort';
const STORAGE_KEY_PREFIX = 'gf-sort-';
/**
* Remembers the sorting of a `matSort` table on the device and restores it
* on the next visit.
*/
@Directive({
selector: '[gfPersistSort]'
})
export class GfPersistSortDirective implements OnInit {
public readonly gfPersistSort = input.required<string>();
private readonly destroyRef = inject(DestroyRef);
private readonly matSort = inject(MatSort, { self: true });
public ngOnInit() {
const storageKey = `${STORAGE_KEY_PREFIX}${this.gfPersistSort()}`;
const storedSort = this.readStoredSort(storageKey);
if (storedSort) {
this.matSort.active = storedSort.active;
this.matSort.direction = storedSort.direction;
}
this.matSort.sortChange
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ active, direction }) => {
try {
globalThis.localStorage?.setItem(
storageKey,
JSON.stringify({ active, direction })
);
} catch {}
});
}
private readStoredSort(aStorageKey: string) {
try {
const item = globalThis.localStorage?.getItem(aStorageKey);
const { active, direction } = JSON.parse(item ?? 'null') ?? {};
if (
typeof active === 'string' &&
active.length > 0 &&
(direction === 'asc' || direction === 'desc')
) {
return { active, direction: direction as SortDirection };
}
} catch {}
return undefined;
}
}
Loading…
Cancel
Save