Kavita/UI/Web/src/app/dashboard/_components/dashboard.component.ts
Joe Milazzo ed4f9e0144
Preparation for Release (#2135)
* Don't allow Comic libraries to do any scrobbling as there aren't any Comic scrobbling providers yet.

* Fixed a bug where if you have multiple libraries pointing the same folder (for whatever reason), the Scan Folder api could be rejected.

* Handle if publication from an epub is empty to avoid a bad parse error

* Cleaned up some hardcoded default strings.

* Fixed up some defaulting code for the cache size.

* Changed how moving something back to on deck works after it's been removed. Now any progress will trigger it, as epubs don't have chapters.

* Ignore .caltrash, which is a Calibre managed folder, when scanning.

* Added the ability to see Volume Last Read Date (or individual chapter) in details drawer. Hover over the clock for the full timestamp.
2023-07-17 06:48:15 -07:00

197 lines
7.8 KiB
TypeScript

import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
inject,
Input,
OnInit
} from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Router, RouterLink } from '@angular/router';
import { Observable, of, ReplaySubject } from 'rxjs';
import { debounceTime, map, take, tap, shareReplay } from 'rxjs/operators';
import { FilterQueryParam } from 'src/app/shared/_services/filter-utilities.service';
import { SeriesAddedEvent } from 'src/app/_models/events/series-added-event';
import { SeriesRemovedEvent } from 'src/app/_models/events/series-removed-event';
import { Library } from 'src/app/_models/library';
import { RecentlyAddedItem } from 'src/app/_models/recently-added-item';
import { Series } from 'src/app/_models/series';
import { SortField } from 'src/app/_models/metadata/series-filter';
import { SeriesGroup } from 'src/app/_models/series-group';
import { AccountService } from 'src/app/_services/account.service';
import { ImageService } from 'src/app/_services/image.service';
import { LibraryService } from 'src/app/_services/library.service';
import { MessageHubService, EVENTS } from 'src/app/_services/message-hub.service';
import { SeriesService } from 'src/app/_services/series.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import { CardItemComponent } from '../../cards/card-item/card-item.component';
import { SeriesCardComponent } from '../../cards/series-card/series-card.component';
import { CarouselReelComponent } from '../../carousel/_components/carousel-reel/carousel-reel.component';
import { NgIf, AsyncPipe } from '@angular/common';
import { SideNavCompanionBarComponent } from '../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [SideNavCompanionBarComponent, NgIf, RouterLink, CarouselReelComponent, SeriesCardComponent, CardItemComponent, AsyncPipe]
})
export class DashboardComponent implements OnInit {
/**
* By default, 0, but if non-zero, will limit all API calls to library id
*/
@Input() libraryId: number = 0;
libraries$: Observable<Library[]> = of([]);
isLoading = true;
isAdmin$: Observable<boolean> = of(false);
recentlyUpdatedSeries: SeriesGroup[] = [];
inProgress: Series[] = [];
recentlyAddedSeries: Series[] = [];
/**
* We use this Replay subject to slow the amount of times we reload the UI
*/
private loadRecentlyAdded$: ReplaySubject<void> = new ReplaySubject<void>();
private readonly destroyRef = inject(DestroyRef);
constructor(public accountService: AccountService, private libraryService: LibraryService,
private seriesService: SeriesService, private router: Router,
private titleService: Title, public imageService: ImageService,
private messageHub: MessageHubService, private readonly cdRef: ChangeDetectorRef) {
this.messageHub.messages$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(res => {
if (res.event === EVENTS.SeriesAdded) {
const seriesAddedEvent = res.payload as SeriesAddedEvent;
this.seriesService.getSeries(seriesAddedEvent.seriesId).subscribe(series => {
this.recentlyAddedSeries = [series, ...this.recentlyAddedSeries];
this.cdRef.markForCheck();
});
} else if (res.event === EVENTS.SeriesRemoved) {
const seriesRemovedEvent = res.payload as SeriesRemovedEvent;
this.inProgress = this.inProgress.filter(item => item.id != seriesRemovedEvent.seriesId);
this.recentlyAddedSeries = this.recentlyAddedSeries.filter(item => item.id != seriesRemovedEvent.seriesId);
this.recentlyUpdatedSeries = this.recentlyUpdatedSeries.filter(item => item.seriesId != seriesRemovedEvent.seriesId);
this.cdRef.markForCheck();
} else if (res.event === EVENTS.ScanSeries) {
// We don't have events for when series are updated, but we do get events when a scan update occurs. Refresh recentlyAdded at that time.
this.loadRecentlyAdded$.next();
}
});
this.isAdmin$ = this.accountService.currentUser$.pipe(
takeUntilDestroyed(this.destroyRef),
map(user => (user && this.accountService.hasAdminRole(user)) || false),
shareReplay()
);
this.loadRecentlyAdded$.pipe(debounceTime(1000), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.loadRecentlyUpdated();
this.loadRecentlyAddedSeries();
this.cdRef.markForCheck();
});
}
ngOnInit(): void {
this.titleService.setTitle('Kavita - Dashboard');
this.isLoading = true;
this.cdRef.markForCheck();
this.libraries$ = this.libraryService.getLibraries().pipe(take(1), takeUntilDestroyed(this.destroyRef), tap((libs) => {
this.isLoading = false;
this.cdRef.markForCheck();
}));
this.reloadSeries();
}
reloadSeries() {
this.loadOnDeck();
this.loadRecentlyUpdated();
this.loadRecentlyAddedSeries();
}
reloadInProgress(series: Series | number) {
this.loadOnDeck();
}
loadOnDeck() {
let api = this.seriesService.getOnDeck(0, 1, 30);
if (this.libraryId > 0) {
api = this.seriesService.getOnDeck(this.libraryId, 1, 30);
}
api.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((updatedSeries) => {
this.inProgress = updatedSeries.result;
this.cdRef.markForCheck();
});
}
loadRecentlyAddedSeries() {
let api = this.seriesService.getRecentlyAdded(0, 1, 30);
if (this.libraryId > 0) {
api = this.seriesService.getRecentlyAdded(this.libraryId, 1, 30);
}
api.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((updatedSeries) => {
this.recentlyAddedSeries = updatedSeries.result;
this.cdRef.markForCheck();
});
}
loadRecentlyUpdated() {
let api = this.seriesService.getRecentlyUpdatedSeries();
if (this.libraryId > 0) {
api = this.seriesService.getRecentlyUpdatedSeries();
}
api.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(updatedSeries => {
this.recentlyUpdatedSeries = updatedSeries.filter(group => {
if (this.libraryId === 0) return true;
return group.libraryId === this.libraryId;
});
this.cdRef.markForCheck();
});
}
handleRecentlyAddedChapterClick(item: RecentlyAddedItem) {
this.router.navigate(['library', item.libraryId, 'series', item.seriesId]);
}
handleSectionClick(sectionTitle: string) {
if (sectionTitle.toLowerCase() === 'recently updated series') {
const params: any = {};
params[FilterQueryParam.SortBy] = SortField.LastChapterAdded + ',false'; // sort by last chapter added, desc
params[FilterQueryParam.Page] = 1;
params['title'] = 'Recently Updated';
this.router.navigate(['all-series'], {queryParams: params});
} else if (sectionTitle.toLowerCase() === 'on deck') {
const params: any = {};
params[FilterQueryParam.ReadStatus] = 'true,false,false';
params[FilterQueryParam.SortBy] = SortField.LastChapterAdded + ',false'; // sort by last chapter added, desc
params[FilterQueryParam.Page] = 1;
params['title'] = 'On Deck';
this.router.navigate(['all-series'], {queryParams: params});
}else if (sectionTitle.toLowerCase() === 'newly added series') {
const params: any = {};
params[FilterQueryParam.SortBy] = SortField.Created + ',false'; // sort by created, desc
params[FilterQueryParam.Page] = 1;
params['title'] = 'Newly Added';
this.router.navigate(['all-series'], {queryParams: params});
}
}
removeFromArray(arr: Array<any>, element: any) {
const index = arr.indexOf(element);
if (index >= 0) {
arr.splice(index);
}
}
}