Library Recomendations (#1236)

* Updated cover regex for finding cover images in archives to ignore back_cover or back-cover

* Fixed an issue where Tags wouldn't save due to not pulling them from the DB.

* Refactored All series to it's own lazy loaded module

* Modularized Dashboard and library detail. Had to change main dashboard page to be libraries. Subject to change.

* Refactored login component into registration module

* Series Detail module created

* Refactored nav stuff into it's own module, not lazy loaded, but self contained.

* Refactored theme component into a dev only module so we don't incur load for temp testing modules

* Finished off modularization code. Only missing thing is to re-introduce some dashboard functionality for library view.

* Implemented a basic recommendation page for library detail
This commit is contained in:
Joseph Milazzo 2022-04-29 17:27:01 -05:00 committed by GitHub
parent 743a3ba935
commit f237aa7ab7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 1077 additions and 501 deletions

View file

@ -0,0 +1,26 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuard } from '../_guards/auth.guard';
import { LibraryAccessGuard } from '../_guards/library-access.guard';
import { LibraryDetailComponent } from './library-detail.component';
const routes: Routes = [
{
path: ':libraryId',
runGuardsAndResolvers: 'always',
canActivate: [AuthGuard, LibraryAccessGuard],
component: LibraryDetailComponent
},
{
path: '',
component: LibraryDetailComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes), ],
exports: [RouterModule]
})
export class LibraryDetailRoutingModule { }

View file

@ -10,7 +10,7 @@
<a ngbNavLink>{{tab.title | sentenceCase}}</a>
<ng-template ngbNavContent>
<ng-container *ngIf="tab.title === 'Recommended'">
<app-library [libraryId]="libraryId"></app-library>
<app-library-recommended [libraryId]="libraryId"></app-library-recommended>
</ng-container>
<ng-container *ngIf="tab.title === 'Library'">
<app-card-detail-layout

View file

@ -2,9 +2,8 @@ import { Component, EventEmitter, HostListener, OnDestroy, OnInit } from '@angul
import { Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { Subject } from 'rxjs';
import { debounceTime, take, takeUntil, takeWhile } from 'rxjs/operators';
import { debounceTime, take, takeUntil } from 'rxjs/operators';
import { BulkSelectionService } from '../cards/bulk-selection.service';
import { FilterSettings } from '../metadata-filter/filter-settings';
import { KEY_CODES, UtilityService } from '../shared/_services/utility.service';
import { SeriesAddedEvent } from '../_models/events/series-added-event';
import { Library } from '../_models/library';
@ -18,6 +17,7 @@ import { EVENTS, MessageHubService } from '../_services/message-hub.service';
import { SeriesService } from '../_services/series.service';
import { NavService } from '../_services/nav.service';
import { FilterUtilitiesService } from '../shared/_services/filter-utilities.service';
import { FilterSettings } from '../metadata-filter/filter-settings';
@Component({
selector: 'app-library-detail',
@ -87,8 +87,9 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
private libraryService: LibraryService, private titleService: Title, private actionFactoryService: ActionFactoryService,
private actionService: ActionService, public bulkSelectionService: BulkSelectionService, private hubService: MessageHubService,
private utilityService: UtilityService, public navService: NavService, private filterUtilityService: FilterUtilitiesService) {
const routeId = this.route.snapshot.paramMap.get('id');
const routeId = this.route.snapshot.paramMap.get('libraryId');
if (routeId === null) {
console.log('Redirecting due to not seeing libraryId in route');
this.router.navigateByUrl('/libraries');
return;
}

View file

@ -0,0 +1,28 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LibraryDetailComponent } from './library-detail.component';
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
import { PipeModule } from '../pipe/pipe.module';
import { LibraryDetailRoutingModule } from './library-detail-routing.module';
import { SharedSideNavCardsModule } from '../shared-side-nav-cards/shared-side-nav-cards.module';
import { LibraryRecommendedComponent } from './library-recommended/library-recommended.component';
import { CarouselModule } from '../carousel/carousel.module';
@NgModule({
declarations: [LibraryDetailComponent, LibraryRecommendedComponent],
imports: [
CommonModule,
NgbNavModule,
CarouselModule, // because this is heavy, we might want recommended in a new url
PipeModule,
SharedSideNavCardsModule,
LibraryDetailRoutingModule
]
})
export class LibraryDetailModule { }

View file

@ -0,0 +1,42 @@
<ng-container *ngIf="onDeck$ | async as onDeck">
<app-carousel-reel [items]="onDeck" title="On Deck">
<ng-template #carouselItem let-item let-position="idx">
<app-series-card [data]="item" [libraryId]="item.libraryId" [suppressLibraryLink]="libraryId !== 0" (reload)="reloadInProgress($event)" (dataChanged)="reloadInProgress($event)"></app-series-card>
</ng-template>
</app-carousel-reel>
</ng-container>
<ng-container *ngIf="quickReads$ | async as quickReads">
<app-carousel-reel [items]="quickReads" title="Quick Reads">
<ng-template #carouselItem let-item let-position="idx">
<app-series-card [data]="item" [libraryId]="item.libraryId" [suppressLibraryLink]="libraryId !== 0" (reload)="reloadInProgress($event)" (dataChanged)="reloadInProgress($event)"></app-series-card>
</ng-template>
</app-carousel-reel>
</ng-container>
<ng-container *ngIf="highlyRated$ | async as highlyRated">
<app-carousel-reel [items]="highlyRated" title="Highly Rated">
<ng-template #carouselItem let-item let-position="idx">
<app-series-card [data]="item" [libraryId]="item.libraryId" [suppressLibraryLink]="libraryId !== 0" (reload)="reloadInProgress($event)" (dataChanged)="reloadInProgress($event)"></app-series-card>
</ng-template>
</app-carousel-reel>
</ng-container>
<ng-container *ngIf="rediscover$ | async as rediscover">
<app-carousel-reel [items]="rediscover" title="Rediscover">
<ng-template #carouselItem let-item let-position="idx">
<app-series-card [data]="item" [libraryId]="item.libraryId" [suppressLibraryLink]="libraryId !== 0" (reload)="reloadInProgress($event)" (dataChanged)="reloadInProgress($event)"></app-series-card>
</ng-template>
</app-carousel-reel>
</ng-container>
<ng-container *ngIf="genre$ | async as genre">
<ng-container *ngIf="moreIn$ | async as moreIn">
<app-carousel-reel [items]="moreIn" title="More In {{genre.title}}">
<ng-template #carouselItem let-item let-position="idx">
<app-series-card [data]="item" [libraryId]="item.libraryId" [suppressLibraryLink]="libraryId !== 0" (reload)="reloadInProgress($event)" (dataChanged)="reloadInProgress($event)"></app-series-card>
</ng-template>
</app-carousel-reel>
</ng-container>
</ng-container>

View file

@ -0,0 +1,65 @@
import { Component, Input, OnInit } from '@angular/core';
import { map, Observable, shareReplay } from 'rxjs';
import { Genre } from 'src/app/_models/genre';
import { Series } from 'src/app/_models/series';
import { MetadataService } from 'src/app/_services/metadata.service';
import { RecommendationService } from 'src/app/_services/recommendation.service';
import { SeriesService } from 'src/app/_services/series.service';
@Component({
selector: 'app-library-recommended',
templateUrl: './library-recommended.component.html',
styleUrls: ['./library-recommended.component.scss']
})
export class LibraryRecommendedComponent implements OnInit {
@Input() libraryId: number = 0;
quickReads$!: Observable<Series[]>;
highlyRated$!: Observable<Series[]>;
onDeck$!: Observable<Series[]>;
rediscover$!: Observable<Series[]>;
moreIn$!: Observable<Series[]>;
genre: string = '';
genre$!: Observable<Genre>;
constructor(private recommendationService: RecommendationService, private seriesService: SeriesService, private metadataService: MetadataService) { }
ngOnInit(): void {
this.quickReads$ = this.recommendationService.getQuickReads(this.libraryId)
.pipe(map(p => p.result), shareReplay());
this.highlyRated$ = this.recommendationService.getHighlyRated(this.libraryId)
.pipe(map(p => p.result), shareReplay());
this.rediscover$ = this.recommendationService.getRediscover(this.libraryId)
.pipe(map(p => p.result), shareReplay());
this.onDeck$ = this.seriesService.getOnDeck(this.libraryId)
.pipe(map(p => p.result), shareReplay());
this.genre$ = this.metadataService.getAllGenres([this.libraryId]).pipe(map(genres => genres[Math.floor(Math.random() * genres.length)]), shareReplay());
this.genre$.subscribe(genre => {
this.moreIn$ = this.recommendationService.getMoreIn(this.libraryId, genre.id).pipe(map(p => p.result), shareReplay());
});
}
reloadInProgress(series: Series | boolean) {
if (series === true || series === false) {
if (!series) {return;}
}
// If the update to Series doesn't affect the requirement to be in this stream, then ignore update request
const seriesObj = (series as Series);
if (seriesObj.pagesRead !== seriesObj.pages && seriesObj.pagesRead !== 0) {
return;
}
//this.loadOnDeck();
}
}