Want to Read List (#1392)

* Implemented a Want To Read list of series for all users, as a way to keep track of what you want to read.

When canceling a bulk action, like Add to Reading list, the selected cards wont de-select.

* Hooked up Remove from Want to Read

* When making bulk selection, allow the user to click on anywhere on the card

* Added no series messaging

* Code cleanup
This commit is contained in:
Joseph Milazzo 2022-07-28 17:18:35 -05:00 committed by GitHub
parent 495c986000
commit f130440bd0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 2209 additions and 48 deletions

View file

@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuard } from '../_guards/auth.guard';
import { WantToReadComponent } from './want-to-read/want-to-read.component';
const routes: Routes = [
{path: '**', component: WantToReadComponent, pathMatch: 'full'},
{
runGuardsAndResolvers: 'always',
canActivate: [AuthGuard],
children: [
{path: '', component: WantToReadComponent, pathMatch: 'full'},
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes), ],
exports: [RouterModule]
})
export class WantToReadRoutingModule { }

View file

@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { WantToReadComponent } from './want-to-read/want-to-read.component';
import { CardsModule } from '../cards/cards.module';
import { SidenavModule } from '../sidenav/sidenav.module';
import { WantToReadRoutingModule } from './want-to-read-routing.module';
@NgModule({
declarations: [
WantToReadComponent
],
imports: [
CommonModule,
CardsModule,
SidenavModule,
WantToReadRoutingModule
]
})
export class WantToReadModule { }

View file

@ -0,0 +1,33 @@
<div #companionBar>
<app-side-nav-companion-bar [hasFilter]="true" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
<ng-container title>
<h2>
Want To Read
</h2>
</ng-container>
</app-side-nav-companion-bar>
</div>
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" #scrollingBlock>
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
<app-card-detail-layout
[isLoading]="isLoading"
[items]="series"
[pagination]="seriesPagination"
[filterSettings]="filterSettings"
[filterOpen]="filterOpen"
[parentScroll]="scrollingBlock"
[jumpBarKeys]="jumpbarKeys"
(applyFilter)="updateFilter($event)">
<ng-template #cardItem let-item let-position="idx">
<app-series-card [data]="item" [libraryId]="item.libraryId" (reload)="loadPage()"
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)" [selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"
></app-series-card>
</ng-template>
<ng-template #noData>
No Series match your filter or exist in your list.
</ng-template>
</app-card-detail-layout>
</div>

View file

@ -0,0 +1,146 @@
import { DOCUMENT } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Router, ActivatedRoute } from '@angular/router';
import { Subject, take, pipe, debounceTime, takeUntil } from 'rxjs';
import { BulkSelectionService } from 'src/app/cards/bulk-selection.service';
import { FilterSettings } from 'src/app/metadata-filter/filter-settings';
import { FilterUtilitiesService } from 'src/app/shared/_services/filter-utilities.service';
import { UtilityService, KEY_CODES } from 'src/app/shared/_services/utility.service';
import { JumpKey } from 'src/app/_models/jumpbar/jump-key';
import { Pagination } from 'src/app/_models/pagination';
import { Series } from 'src/app/_models/series';
import { SeriesFilter, FilterEvent } from 'src/app/_models/series-filter';
import { Action } from 'src/app/_services/action-factory.service';
import { ActionService } from 'src/app/_services/action.service';
import { ImageService } from 'src/app/_services/image.service';
import { MessageHubService, EVENTS } from 'src/app/_services/message-hub.service';
import { ScrollService } from 'src/app/_services/scroll.service';
import { SeriesService } from 'src/app/_services/series.service';
@Component({
selector: 'app-want-to-read',
templateUrl: './want-to-read.component.html',
styleUrls: ['./want-to-read.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class WantToReadComponent implements OnInit, OnDestroy {
@ViewChild('scrollingBlock') scrollingBlock: ElementRef<HTMLDivElement> | undefined;
@ViewChild('companionBar') companionBar: ElementRef<HTMLDivElement> | undefined;
isLoading: boolean = true;
series: Array<Series> = [];
seriesPagination!: Pagination;
filter: SeriesFilter | undefined = undefined;
filterSettings: FilterSettings = new FilterSettings();
filterActiveCheck!: SeriesFilter;
filterActive: boolean = false;
jumpbarKeys: Array<JumpKey> = [];
filterOpen: EventEmitter<boolean> = new EventEmitter();
private onDestory: Subject<void> = new Subject<void>();
bulkActionCallback = (action: Action, data: any) => {
const selectedSeriesIndexies = this.bulkSelectionService.getSelectedCardsForSource('series');
const selectedSeries = this.series.filter((series, index: number) => selectedSeriesIndexies.includes(index + ''));
switch (action) {
case Action.RemoveFromWantToReadList:
this.actionService.removeMultipleSeriesFromWantToReadList(selectedSeries.map(s => s.id), () => {
this.bulkSelectionService.deselectAll();
this.cdRef.markForCheck();
});
break;
}
}
collectionTag: any;
tagImage: any;
get ScrollingBlockHeight() {
if (this.scrollingBlock === undefined) return 'calc(var(--vh)*100)';
const navbar = this.document.querySelector('.navbar') as HTMLElement;
if (navbar === null) return 'calc(var(--vh)*100)';
const companionHeight = this.companionBar!.nativeElement.offsetHeight;
const navbarHeight = navbar.offsetHeight;
const totalHeight = companionHeight + navbarHeight + 21; //21px to account for padding
return 'calc(var(--vh)*100 - ' + totalHeight + 'px)';
}
constructor(public imageService: ImageService, private router: Router, private route: ActivatedRoute,
private seriesService: SeriesService, private titleService: Title,
public bulkSelectionService: BulkSelectionService, private actionService: ActionService, private messageHub: MessageHubService,
private filterUtilityService: FilterUtilitiesService, private utilityService: UtilityService, @Inject(DOCUMENT) private document: Document,
private readonly cdRef: ChangeDetectorRef, private scrollService: ScrollService) {
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
this.titleService.setTitle('Want To Read');
this.seriesPagination = this.filterUtilityService.pagination(this.route.snapshot);
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
this.filterActiveCheck = this.seriesService.createSeriesFilter();
this.cdRef.markForCheck();
}
ngOnInit(): void {
this.messageHub.messages$.pipe(takeUntil(this.onDestory), debounceTime(2000)).subscribe(event => {
if (event.event === EVENTS.SeriesRemoved) {
this.loadPage();
}
});
}
ngAfterContentChecked(): void {
this.scrollService.setScrollContainer(this.scrollingBlock);
}
ngOnDestroy() {
this.onDestory.next();
this.onDestory.complete();
}
@HostListener('document:keydown.shift', ['$event'])
handleKeypress(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = true;
}
}
@HostListener('document:keyup.shift', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (event.key === KEY_CODES.SHIFT) {
this.bulkSelectionService.isShiftDown = false;
}
}
loadPage() {
this.filterActive = !this.utilityService.deepEqual(this.filter, this.filterActiveCheck);
this.isLoading = true;
this.cdRef.markForCheck();
this.seriesService.getWantToRead(undefined, undefined, this.filter).pipe(take(1)).subscribe(paginatedList => {
this.series = paginatedList.result;
this.seriesPagination = paginatedList.pagination;
this.jumpbarKeys = this.utilityService.getJumpKeys(this.series, (series: Series) => series.name);
this.isLoading = false;
window.scrollTo(0, 0);
this.cdRef.markForCheck();
});
}
updateFilter(data: FilterEvent) {
this.filter = data.filter;
if (!data.isFirst) this.filterUtilityService.updateUrlFromFilter(this.seriesPagination, this.filter);
this.loadPage();
}
}