Angular 16 (#2007)
* Removed adv, which isn't needed. * Updated zone * Updated to angular 16 * Updated to angular 16 (partially) * Updated to angular 16 * Package update for Angular 16 (and other dependencies) is complete. * Replaced all takeUntil(this.onDestroy) with new takeUntilDestroyed() * Updated all inputs that have ! to be required and deleted all unit tests. * Corrected how takeUntilDestroyed() is supposed to be implemented.
This commit is contained in:
parent
9bc8361381
commit
9c06cccd35
87 changed files with 3964 additions and 20426 deletions
|
|
@ -1,20 +1,20 @@
|
|||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { LegendPosition } from '@swimlane/ngx-charts';
|
||||
import { Subject, map, takeUntil, Observable } from 'rxjs';
|
||||
import { DayOfWeek, StatisticsService } from 'src/app/_services/statistics.service';
|
||||
import { PieDataItem } from '../../_models/pie-data-item';
|
||||
import { StatCount } from '../../_models/stat-count';
|
||||
import { DayOfWeekPipe } from '../../_pipes/day-of-week.pipe';
|
||||
import {ChangeDetectionStrategy, Component, DestroyRef, inject} from '@angular/core';
|
||||
import {FormControl} from '@angular/forms';
|
||||
import {LegendPosition} from '@swimlane/ngx-charts';
|
||||
import {map, Observable} from 'rxjs';
|
||||
import {DayOfWeek, StatisticsService} from 'src/app/_services/statistics.service';
|
||||
import {PieDataItem} from '../../_models/pie-data-item';
|
||||
import {StatCount} from '../../_models/stat-count';
|
||||
import {DayOfWeekPipe} from '../../_pipes/day-of-week.pipe';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
|
||||
@Component({
|
||||
selector: 'app-day-breakdown',
|
||||
templateUrl: './day-breakdown.component.html',
|
||||
styleUrls: ['./day-breakdown.component.scss']
|
||||
styleUrls: ['./day-breakdown.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DayBreakdownComponent implements OnDestroy {
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
export class DayBreakdownComponent {
|
||||
|
||||
view: [number, number] = [0,0];
|
||||
gradient: boolean = true;
|
||||
|
|
@ -28,6 +28,7 @@ export class DayBreakdownComponent implements OnDestroy {
|
|||
|
||||
formControl: FormControl = new FormControl(true, []);
|
||||
dayBreakdown$!: Observable<Array<PieDataItem>>;
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(private statService: StatisticsService) {
|
||||
const dayOfWeekPipe = new DayOfWeekPipe();
|
||||
|
|
@ -37,13 +38,8 @@ export class DayBreakdownComponent implements OnDestroy {
|
|||
return {name: dayOfWeekPipe.transform(d.value), value: d.count};
|
||||
})
|
||||
}),
|
||||
takeUntil(this.onDestroy)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,33 @@
|
|||
import { ChangeDetectionStrategy, Component, OnDestroy, QueryList, ViewChildren } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
inject,
|
||||
OnDestroy,
|
||||
QueryList,
|
||||
ViewChildren
|
||||
} from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { LegendPosition } from '@swimlane/ngx-charts';
|
||||
import { Observable, Subject, BehaviorSubject, combineLatest, map, takeUntil, shareReplay } from 'rxjs';
|
||||
import { MangaFormatPipe } from 'src/app/pipe/manga-format.pipe';
|
||||
import { StatisticsService } from 'src/app/_services/statistics.service';
|
||||
import { SortableHeader, SortEvent, compare } from 'src/app/_single-module/table/_directives/sortable-header.directive';
|
||||
import { FileExtension, FileExtensionBreakdown } from '../../_models/file-breakdown';
|
||||
import { PieDataItem } from '../../_models/pie-data-item';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
|
||||
export interface StackedBarChartDataItem {
|
||||
name: string,
|
||||
series: Array<PieDataItem>;
|
||||
}
|
||||
|
||||
const mangaFormatPipe = new MangaFormatPipe();
|
||||
|
||||
@Component({
|
||||
selector: 'app-file-breakdown-stats',
|
||||
templateUrl: './file-breakdown-stats.component.html',
|
||||
styleUrls: ['./file-breakdown-stats.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FileBreakdownStatsComponent implements OnDestroy {
|
||||
export class FileBreakdownStatsComponent {
|
||||
|
||||
@ViewChildren(SortableHeader<PieDataItem>) headers!: QueryList<SortableHeader<PieDataItem>>;
|
||||
|
||||
|
|
@ -29,11 +35,12 @@ export class FileBreakdownStatsComponent implements OnDestroy {
|
|||
files$!: Observable<Array<FileExtension>>;
|
||||
vizData$!: Observable<Array<StackedBarChartDataItem>>;
|
||||
vizData2$!: Observable<Array<PieDataItem>>;
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
|
||||
currentSort = new BehaviorSubject<SortEvent<FileExtension>>({column: 'extension', direction: 'asc'});
|
||||
currentSort$: Observable<SortEvent<FileExtension>> = this.currentSort.asObservable();
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
view: [number, number] = [700, 400];
|
||||
gradient: boolean = true;
|
||||
showLegend: boolean = true;
|
||||
|
|
@ -48,7 +55,7 @@ export class FileBreakdownStatsComponent implements OnDestroy {
|
|||
|
||||
|
||||
constructor(private statService: StatisticsService) {
|
||||
this.rawData$ = this.statService.getFileBreakdown().pipe(takeUntil(this.onDestroy), shareReplay());
|
||||
this.rawData$ = this.statService.getFileBreakdown().pipe(takeUntilDestroyed(this.destroyRef), shareReplay());
|
||||
|
||||
this.files$ = combineLatest([this.currentSort$, this.rawData$]).pipe(
|
||||
map(([sortConfig, data]) => {
|
||||
|
|
@ -61,20 +68,15 @@ export class FileBreakdownStatsComponent implements OnDestroy {
|
|||
return sortConfig.direction === 'asc' ? res : -res;
|
||||
}) : fileBreakdown;
|
||||
}),
|
||||
takeUntil(this.onDestroy)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
|
||||
|
||||
this.vizData2$ = this.files$.pipe(takeUntil(this.onDestroy), map(data => data.map(d => {
|
||||
this.vizData2$ = this.files$.pipe(takeUntilDestroyed(this.destroyRef), map(data => data.map(d => {
|
||||
return {name: d.extension || 'Not Categorized', value: d.totalFiles, extra: d.totalSize};
|
||||
})));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
onSort(evt: SortEvent<FileExtension>) {
|
||||
this.currentSort.next(evt);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
inject,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
QueryList,
|
||||
ViewChildren
|
||||
} from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { LegendPosition } from '@swimlane/ngx-charts';
|
||||
import { Observable, Subject, BehaviorSubject, combineLatest, map, takeUntil } from 'rxjs';
|
||||
import { StatisticsService } from 'src/app/_services/statistics.service';
|
||||
import { compare, SortableHeader, SortEvent } from 'src/app/_single-module/table/_directives/sortable-header.directive';
|
||||
import { PieDataItem } from '../../_models/pie-data-item';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
|
||||
@Component({
|
||||
selector: 'app-manga-format-stats',
|
||||
|
|
@ -12,13 +22,13 @@ import { PieDataItem } from '../../_models/pie-data-item';
|
|||
styleUrls: ['./manga-format-stats.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MangaFormatStatsComponent implements OnInit, OnDestroy {
|
||||
export class MangaFormatStatsComponent {
|
||||
|
||||
@ViewChildren(SortableHeader<PieDataItem>) headers!: QueryList<SortableHeader<PieDataItem>>;
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
formats$!: Observable<Array<PieDataItem>>;
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
|
||||
currentSort = new BehaviorSubject<SortEvent<PieDataItem>>({column: 'value', direction: 'asc'});
|
||||
currentSort$: Observable<SortEvent<PieDataItem>> = this.currentSort.asObservable();
|
||||
|
||||
|
|
@ -44,20 +54,10 @@ export class MangaFormatStatsComponent implements OnInit, OnDestroy {
|
|||
return sortConfig.direction === 'asc' ? res : -res;
|
||||
}) : data;
|
||||
}),
|
||||
takeUntil(this.onDestroy)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
onSort(evt: SortEvent<PieDataItem>) {
|
||||
this.currentSort.next(evt);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
import { ChangeDetectionStrategy, Component, OnDestroy, QueryList, ViewChildren } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
inject,
|
||||
OnDestroy,
|
||||
QueryList,
|
||||
ViewChildren
|
||||
} from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { LegendPosition } from '@swimlane/ngx-charts';
|
||||
import { Observable, Subject, map, takeUntil, combineLatest, BehaviorSubject } from 'rxjs';
|
||||
import { StatisticsService } from 'src/app/_services/statistics.service';
|
||||
import { compare, SortableHeader, SortEvent } from 'src/app/_single-module/table/_directives/sortable-header.directive';
|
||||
import { PieDataItem } from '../../_models/pie-data-item';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
|
||||
@Component({
|
||||
selector: 'app-publication-status-stats',
|
||||
|
|
@ -12,13 +21,12 @@ import { PieDataItem } from '../../_models/pie-data-item';
|
|||
styleUrls: ['./publication-status-stats.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PublicationStatusStatsComponent implements OnDestroy {
|
||||
export class PublicationStatusStatsComponent {
|
||||
|
||||
@ViewChildren(SortableHeader<PieDataItem>) headers!: QueryList<SortableHeader<PieDataItem>>;
|
||||
|
||||
publicationStatues$!: Observable<Array<PieDataItem>>;
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
|
||||
currentSort = new BehaviorSubject<SortEvent<PieDataItem>>({column: 'value', direction: 'asc'});
|
||||
currentSort$: Observable<SortEvent<PieDataItem>> = this.currentSort.asObservable();
|
||||
|
||||
|
|
@ -32,6 +40,8 @@ export class PublicationStatusStatsComponent implements OnDestroy {
|
|||
domain: ['#5AA454', '#A10A28', '#C7B42C', '#AAAAAA']
|
||||
};
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
formControl: FormControl = new FormControl(true, []);
|
||||
|
||||
|
||||
|
|
@ -44,15 +54,10 @@ export class PublicationStatusStatsComponent implements OnDestroy {
|
|||
return sortConfig.direction === 'asc' ? res : -res;
|
||||
}) : data;
|
||||
}),
|
||||
takeUntil(this.onDestroy)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
onSort(evt: SortEvent<PieDataItem>) {
|
||||
this.currentSort.next(evt);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, DestroyRef, inject, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { filter, map, Observable, of, shareReplay, Subject, switchMap, takeUntil } from 'rxjs';
|
||||
import { MangaFormatPipe } from 'src/app/pipe/manga-format.pipe';
|
||||
|
|
@ -7,6 +7,7 @@ import { MemberService } from 'src/app/_services/member.service';
|
|||
import { StatisticsService } from 'src/app/_services/statistics.service';
|
||||
import { PieDataItem } from '../../_models/pie-data-item';
|
||||
import { TimePeriods } from '../top-readers/top-readers.component';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
|
||||
const options: Intl.DateTimeFormatOptions = { month: "short", day: "numeric" };
|
||||
const mangaFormatPipe = new MangaFormatPipe();
|
||||
|
|
@ -17,7 +18,7 @@ const mangaFormatPipe = new MangaFormatPipe();
|
|||
styleUrls: ['./reading-activity.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ReadingActivityComponent implements OnInit, OnDestroy {
|
||||
export class ReadingActivityComponent implements OnInit {
|
||||
/**
|
||||
* Only show for one user
|
||||
*/
|
||||
|
|
@ -33,10 +34,10 @@ export class ReadingActivityComponent implements OnInit, OnDestroy {
|
|||
users$: Observable<Member[]> | undefined;
|
||||
data$: Observable<Array<PieDataItem>>;
|
||||
timePeriods = TimePeriods;
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(private statService: StatisticsService, private memberService: MemberService) {
|
||||
this.data$ = this.formGroup.valueChanges.pipe(
|
||||
this.data$ = this.formGroup.valueChanges.pipe(
|
||||
switchMap(_ => this.statService.getReadCountByDay(this.formGroup.get('users')!.value, this.formGroup.get('days')!.value)),
|
||||
map(data => {
|
||||
const gList = data.reduce((formats, entry) => {
|
||||
|
|
@ -56,26 +57,20 @@ export class ReadingActivityComponent implements OnInit, OnDestroy {
|
|||
return {name: format, value: 0, series: gList[format].series}
|
||||
});
|
||||
}),
|
||||
takeUntil(this.onDestroy),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
shareReplay(),
|
||||
);
|
||||
|
||||
|
||||
this.data$.subscribe();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.users$ = (this.isAdmin ? this.memberService.getMembers() : of([])).pipe(filter(_ => this.isAdmin), takeUntil(this.onDestroy), shareReplay());
|
||||
this.users$ = (this.isAdmin ? this.memberService.getMembers() : of([])).pipe(filter(_ => this.isAdmin), takeUntilDestroyed(this.destroyRef), shareReplay());
|
||||
this.formGroup.get('users')?.setValue(this.userId, {emitValue: true});
|
||||
|
||||
if (!this.isAdmin) {
|
||||
this.formGroup.get('users')?.disable();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ChangeDetectionStrategy, Component, HostListener, OnDestroy } from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, DestroyRef, HostListener, inject, OnDestroy} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { BehaviorSubject, map, Observable, shareReplay, Subject, takeUntil } from 'rxjs';
|
||||
import {BehaviorSubject, map, Observable, ReplaySubject, shareReplay, Subject, takeUntil} from 'rxjs';
|
||||
import { FilterQueryParam } from 'src/app/shared/_services/filter-utilities.service';
|
||||
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { Series } from 'src/app/_models/series';
|
||||
|
|
@ -11,6 +11,7 @@ import { StatisticsService } from 'src/app/_services/statistics.service';
|
|||
import { PieDataItem } from '../../_models/pie-data-item';
|
||||
import { ServerStatistics } from '../../_models/server-statistics';
|
||||
import { GenericListModalComponent } from '../_modals/generic-list-modal/generic-list-modal.component';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
|
||||
@Component({
|
||||
selector: 'app-server-stats',
|
||||
|
|
@ -18,7 +19,7 @@ import { GenericListModalComponent } from '../_modals/generic-list-modal/generic
|
|||
styleUrls: ['./server-stats.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ServerStatsComponent implements OnDestroy {
|
||||
export class ServerStatsComponent {
|
||||
|
||||
releaseYears$!: Observable<Array<PieDataItem>>;
|
||||
mostActiveUsers$!: Observable<Array<PieDataItem>>;
|
||||
|
|
@ -27,41 +28,42 @@ export class ServerStatsComponent implements OnDestroy {
|
|||
recentlyRead$!: Observable<Array<PieDataItem>>;
|
||||
stats$!: Observable<ServerStatistics>;
|
||||
seriesImage: (data: PieDataItem) => string;
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
openSeries = (data: PieDataItem) => {
|
||||
const series = data.extra as Series;
|
||||
this.router.navigate(['library', series.libraryId, 'series', series.id]);
|
||||
}
|
||||
|
||||
breakpointSubject = new BehaviorSubject<Breakpoint>(1);
|
||||
breakpointSubject = new ReplaySubject<Breakpoint>(1);
|
||||
breakpoint$: Observable<Breakpoint> = this.breakpointSubject.asObservable();
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
@HostListener('window:orientationchange', ['$event'])
|
||||
onResize() {
|
||||
onResize() {
|
||||
this.breakpointSubject.next(this.utilityService.getActiveBreakpoint());
|
||||
}
|
||||
|
||||
|
||||
get Breakpoint() { return Breakpoint; }
|
||||
|
||||
constructor(private statService: StatisticsService, private router: Router, private imageService: ImageService,
|
||||
constructor(private statService: StatisticsService, private router: Router, private imageService: ImageService,
|
||||
private metadataService: MetadataService, private modalService: NgbModal, private utilityService: UtilityService) {
|
||||
this.seriesImage = (data: PieDataItem) => {
|
||||
if (data.extra) return this.imageService.getSeriesCoverImage(data.extra.id);
|
||||
return '';
|
||||
return '';
|
||||
}
|
||||
|
||||
this.breakpointSubject.next(this.utilityService.getActiveBreakpoint());
|
||||
|
||||
this.stats$ = this.statService.getServerStatistics().pipe(takeUntil(this.onDestroy), shareReplay());
|
||||
this.releaseYears$ = this.statService.getTopYears().pipe(takeUntil(this.onDestroy));
|
||||
this.stats$ = this.statService.getServerStatistics().pipe(takeUntilDestroyed(this.destroyRef), shareReplay());
|
||||
this.releaseYears$ = this.statService.getTopYears().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this.mostActiveUsers$ = this.stats$.pipe(
|
||||
map(d => d.mostActiveUsers),
|
||||
map(userCounts => userCounts.map(count => {
|
||||
return {name: count.value.username, value: count.count};
|
||||
})),
|
||||
takeUntil(this.onDestroy)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
|
||||
this.mostActiveLibrary$ = this.stats$.pipe(
|
||||
|
|
@ -69,7 +71,7 @@ export class ServerStatsComponent implements OnDestroy {
|
|||
map(counts => counts.map(count => {
|
||||
return {name: count.value.name, value: count.count};
|
||||
})),
|
||||
takeUntil(this.onDestroy)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
|
||||
this.mostActiveSeries$ = this.stats$.pipe(
|
||||
|
|
@ -77,7 +79,7 @@ export class ServerStatsComponent implements OnDestroy {
|
|||
map(counts => counts.map(count => {
|
||||
return {name: count.value.name, value: count.count, extra: count.value};
|
||||
})),
|
||||
takeUntil(this.onDestroy)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
|
||||
this.recentlyRead$ = this.stats$.pipe(
|
||||
|
|
@ -85,15 +87,10 @@ export class ServerStatsComponent implements OnDestroy {
|
|||
map(counts => counts.map(count => {
|
||||
return {name: count.name, value: -1, extra: count};
|
||||
})),
|
||||
takeUntil(this.onDestroy)
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
openGenreList() {
|
||||
this.metadataService.getAllGenres().subscribe(genres => {
|
||||
const ref = this.modalService.open(GenericListModalComponent, { scrollable: true });
|
||||
|
|
@ -130,6 +127,6 @@ export class ServerStatsComponent implements OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export class StatListComponent {
|
|||
* Optional data to put in tooltip
|
||||
*/
|
||||
@Input() description: string = '';
|
||||
@Input() data$!: Observable<PieDataItem[]>;
|
||||
@Input({required: true}) data$!: Observable<PieDataItem[]>;
|
||||
@Input() image: ((data: PieDataItem) => string) | undefined = undefined;
|
||||
/**
|
||||
* Optional callback handler when an item is clicked
|
||||
|
|
@ -31,7 +31,7 @@ export class StatListComponent {
|
|||
@Input() handleClick: ((data: PieDataItem) => void) | undefined = undefined;
|
||||
|
||||
doClick(item: PieDataItem) {
|
||||
if (!this.handleClick) return;
|
||||
if (!this.handleClick) return;
|
||||
this.handleClick(item);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
DestroyRef,
|
||||
inject,
|
||||
OnDestroy,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { FormGroup, FormControl } from '@angular/forms';
|
||||
import { Observable, Subject, takeUntil, switchMap, shareReplay } from 'rxjs';
|
||||
import { StatisticsService } from 'src/app/_services/statistics.service';
|
||||
import { TopUserRead } from '../../_models/top-reads';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
|
||||
export const TimePeriods: Array<{title: string, value: number}> = [{title: 'This Week', value: new Date().getDay() || 1}, {title: 'Last 7 Days', value: 7}, {title: 'Last 30 Days', value: 30}, {title: 'Last 90 Days', value: 90}, {title: 'Last Year', value: 365}, {title: 'All Time', value: 0}];
|
||||
|
||||
|
|
@ -12,22 +21,22 @@ export const TimePeriods: Array<{title: string, value: number}> = [{title: 'This
|
|||
styleUrls: ['./top-readers.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TopReadersComponent implements OnInit, OnDestroy {
|
||||
export class TopReadersComponent implements OnInit {
|
||||
|
||||
formGroup: FormGroup;
|
||||
timePeriods = TimePeriods;
|
||||
|
||||
users$: Observable<TopUserRead[]>;
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
constructor(private statsService: StatisticsService, private readonly cdRef: ChangeDetectorRef) {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(private statsService: StatisticsService, private readonly cdRef: ChangeDetectorRef) {
|
||||
this.formGroup = new FormGroup({
|
||||
'days': new FormControl(this.timePeriods[0].value, []),
|
||||
});
|
||||
|
||||
this.users$ = this.formGroup.valueChanges.pipe(
|
||||
switchMap(_ => this.statsService.getTopUsers(this.formGroup.get('days')?.value as number)),
|
||||
takeUntil(this.onDestroy),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
|
@ -38,9 +47,4 @@ export class TopReadersComponent implements OnInit, OnDestroy {
|
|||
this.formGroup.get('days')?.setValue(this.timePeriods[0].value, {emitEvent: true});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@
|
|||
</div>
|
||||
|
||||
<div class="row g-0 pt-4 pb-2 " style="height: 242px">
|
||||
<app-stat-list [data$]="precentageRead$" label="% Read" title="Library Read Progress"></app-stat-list>
|
||||
<app-stat-list [data$]="percentageRead$" label="% Read" title="Library Read Progress"></app-stat-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
DestroyRef,
|
||||
inject,
|
||||
OnDestroy,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { map, Observable, shareReplay, Subject, takeUntil } from 'rxjs';
|
||||
import { FilterUtilitiesService } from 'src/app/shared/_services/filter-utilities.service';
|
||||
import { UserReadStatistics } from 'src/app/statistics/_models/user-read-statistics';
|
||||
|
|
@ -9,6 +17,7 @@ import { AccountService } from 'src/app/_services/account.service';
|
|||
import { PieDataItem } from '../../_models/pie-data-item';
|
||||
import { LibraryService } from 'src/app/_services/library.service';
|
||||
import { PercentPipe } from '@angular/common';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-stats',
|
||||
|
|
@ -16,20 +25,19 @@ import { PercentPipe } from '@angular/common';
|
|||
styleUrls: ['./user-stats.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class UserStatsComponent implements OnInit, OnDestroy {
|
||||
export class UserStatsComponent implements OnInit {
|
||||
|
||||
userId: number | undefined = undefined;
|
||||
userStats$!: Observable<UserReadStatistics>;
|
||||
readSeries$!: Observable<ReadHistoryEvent[]>;
|
||||
isAdmin$: Observable<boolean>;
|
||||
precentageRead$!: Observable<PieDataItem[]>;
|
||||
percentageRead$!: Observable<PieDataItem[]>;
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
constructor(private readonly cdRef: ChangeDetectorRef, private statService: StatisticsService,
|
||||
constructor(private readonly cdRef: ChangeDetectorRef, private statService: StatisticsService,
|
||||
private filterService: FilterUtilitiesService, private accountService: AccountService, private memberService: MemberService,
|
||||
private libraryService: LibraryService) {
|
||||
this.isAdmin$ = this.accountService.currentUser$.pipe(takeUntil(this.onDestroy), map(u => {
|
||||
private libraryService: LibraryService) {
|
||||
this.isAdmin$ = this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), map(u => {
|
||||
if (!u) return false;
|
||||
return this.accountService.hasAdminRole(u);
|
||||
}));
|
||||
|
|
@ -42,26 +50,20 @@ export class UserStatsComponent implements OnInit, OnDestroy {
|
|||
this.memberService.getMember().subscribe(me => {
|
||||
this.userId = me.id;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.userStats$ = this.statService.getUserStatistics(this.userId).pipe(takeUntil(this.onDestroy), shareReplay());
|
||||
|
||||
this.userStats$ = this.statService.getUserStatistics(this.userId).pipe(takeUntilDestroyed(this.destroyRef), shareReplay());
|
||||
this.readSeries$ = this.statService.getReadingHistory(this.userId).pipe(
|
||||
takeUntil(this.onDestroy),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
);
|
||||
|
||||
|
||||
const pipe = new PercentPipe('en-US');
|
||||
this.libraryService.getLibraryNames().subscribe(names => {
|
||||
this.precentageRead$ = this.userStats$.pipe(takeUntil(this.onDestroy), map(d => d.percentReadPerLibrary.map(l => {
|
||||
this.percentageRead$ = this.userStats$.pipe(takeUntilDestroyed(this.destroyRef), map(d => d.percentReadPerLibrary.map(l => {
|
||||
return {name: names[l.count], value: parseFloat((pipe.transform(l.value, '1.1-1') || '0').replace('%', ''))};
|
||||
}).sort((a: PieDataItem, b: PieDataItem) => b.value - a.value)));
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue