Implementing infinite scroll for library items

This commit is contained in:
Zoe Roux 2020-08-01 02:52:04 +02:00
parent 7e77e804b6
commit e8c505cb56
9 changed files with 1446 additions and 1341 deletions

2668
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,37 +9,38 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^9.1.9",
"@angular/animations": "^9.1.12",
"@angular/cdk": "^9.2.4",
"@angular/common": "^9.1.9",
"@angular/compiler": "^9.1.9",
"@angular/core": "^9.1.9",
"@angular/forms": "^9.1.9",
"@angular/common": "^9.1.12",
"@angular/compiler": "^9.1.12",
"@angular/core": "^9.1.12",
"@angular/forms": "^9.1.12",
"@angular/material": "^9.2.4",
"@angular/platform-browser": "^9.1.9",
"@angular/platform-browser-dynamic": "^9.1.9",
"@angular/router": "^9.1.9",
"@angular/platform-browser": "^9.1.12",
"@angular/platform-browser-dynamic": "^9.1.12",
"@angular/router": "^9.1.12",
"angular-auth-oidc-client": "10.0.14",
"bootstrap": "^4.5.0",
"detect-browser": "^5.1.0",
"detect-browser": "^5.1.1",
"hammerjs": "^2.0.8",
"hls.js": "^0.13.2",
"jquery": "^3.5.1",
"ngx-infinite-scroll": "^9.0.0",
"popper.js": "^1.16.1",
"zone.js": "^0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.901.7",
"@angular/cli": "^9.1.7",
"@angular/compiler-cli": "^9.1.9",
"@angular/language-service": "^9.1.9",
"@angular-devkit/build-angular": "^0.901.12",
"@angular/cli": "^9.1.12",
"@angular/compiler-cli": "^9.1.12",
"@angular/language-service": "^9.1.12",
"@types/bootstrap": "^4.5.0",
"@types/hls.js": "^0.12.6",
"@types/jasmine": "^3.5.10",
"@types/jasmine": "^3.5.11",
"@types/jasminewd2": "^2.0.8",
"@types/jquery": "^3.3.38",
"@types/node": "^13.13.10",
"@types/video.js": "^7.3.9",
"@types/jquery": "^3.5.1",
"@types/node": "^13.13.15",
"@types/video.js": "^7.3.10",
"codelyzer": "^5.2.2",
"protractor": "^7.0.0",
"ts-node": "~8.6.2",

View File

@ -14,7 +14,7 @@ import {StreamResolverService} from "./services/resolvers/stream-resolver.servic
import {ShowDetailsComponent} from './pages/show-details/show-details.component';
import {AuthGuard} from "./auth/misc/authenticated-guard.service";
import {LibraryItem} from "../models/library-item";
import {CrudApi, LibraryItemService, LibraryService} from "./services/api.service";
import {LibraryItemService, LibraryService} from "./services/api.service";
const routes: Routes = [
{path: "browse", component: LibraryItemGridComponent, pathMatch: "full",

View File

@ -33,13 +33,14 @@ import {MatDialogModule} from '@angular/material/dialog';
import {FallbackDirective} from "./misc/fallback.directive";
import {AuthModule} from "./auth/auth.module";
import {AuthRoutingModule} from "./auth/auth-routing.module";
import { TrailerDialogComponent } from './pages/trailer-dialog/trailer-dialog.component';
import {TrailerDialogComponent} from './pages/trailer-dialog/trailer-dialog.component';
import {CollectionsListComponent} from "./collection-list/collections-list.component";
import { MetadataEditComponent } from './pages/metadata-edit/metadata-edit.component';
import {MetadataEditComponent} from './pages/metadata-edit/metadata-edit.component';
import {MatChipsModule} from "@angular/material/chips";
import {MatAutocompleteModule} from "@angular/material/autocomplete";
import {MatExpansionModule} from "@angular/material/expansion";
import { ShowGridComponent } from './components/show-grid/show-grid.component';
import {ShowGridComponent} from './components/show-grid/show-grid.component';
import {InfiniteScrollModule} from "ngx-infinite-scroll";
@NgModule({
@ -87,7 +88,8 @@ import { ShowGridComponent } from './components/show-grid/show-grid.component';
AuthModule,
MatChipsModule,
MatAutocompleteModule,
MatExpansionModule
MatExpansionModule,
InfiniteScrollModule
],
bootstrap: [AppComponent]
})

View File

@ -24,7 +24,7 @@
</div>
</mat-menu>
<div class="container-fluid justify-content-center">
<div class="container-fluid justify-content-center" infinite-scroll (scrolled)="this.page?.loadNext(this.client)">
<a class="show" *ngFor="let item of this.page.items" [href]="getLink(item)" [routerLink]="getLink(item)">
<div matRipple [style.background-image]="getThumb(item.slug)" > </div>
<p class="title">{{item.title}}</p>

View File

@ -4,6 +4,7 @@ import {DomSanitizer} from '@angular/platform-browser';
import {ItemType, LibraryItem} from "../../../models/library-item";
import {Page} from "../../../models/page";
import {LibraryItemService} from "../../services/api.service";
import {HttpClient} from "@angular/common/http";
@Component({
selector: 'app-browse',
@ -18,7 +19,10 @@ export class LibraryItemGridComponent
sortKeys: string[] = ["title", "start year", "end year", "status", "type"]
sortUp: boolean = true;
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer, private items: LibraryItemService)
constructor(private route: ActivatedRoute,
private sanitizer: DomSanitizer,
private items: LibraryItemService,
public client: HttpClient)
{
this.route.data.subscribe((data) =>
{

View File

@ -1,11 +1,11 @@
import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {Observable} from "rxjs"
import {map} from "rxjs/operators"
import {Page} from "../../models/page";
import {IResource} from "../../models/resources/resource";
import {Library} from "../../models/library";
import {LibraryItem} from "../../models/library-item";
import {map} from "rxjs/operators";
class CrudApi<T extends IResource>
{
@ -23,21 +23,10 @@ class CrudApi<T extends IResource>
params += "sortBy=" + args.sort;
if (params == "?")
params = "";
return this.client.get<Page<T>>(`/api/${this.route}${params}`);
return this.client.get<Page<T>>(`/api/${this.route}${params}`)
.pipe(map(x => Object.assign(new Page<T>(), x)));
}
loadNext(page: Page<T>): Observable<Page<T>>
{
if (page.next == null)
return;
return this.client.get<Page<T>>(page.next).pipe(map(x =>
{
x.items = page.items.concat(x.items);
x.count += page.count;
return x;
}));
}
create(item: T): Observable<T>
{
return this.client.post<T>(`/api/${this.route}`, item);

View File

@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { EMPTY, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import {catchError, map} from 'rxjs/operators';
import {Page} from "../../../models/page";
import {IResource} from "../../../models/resources/resource";
@ -15,22 +15,25 @@ export class PageResolver
static forResource<T extends IResource>(resource: string)
{
@Injectable()
class Resolver<T> implements Resolve<Page<T>>
class Resolver implements Resolve<Page<T>>
{
constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
resolve(route: ActivatedRouteSnapshot): Page<T> | Observable<Page<T>> | Promise<Page<T>>
{
return this.http.get<Page<T>>(`api/${resource}`).pipe(catchError((error: HttpErrorResponse) =>
{
console.log(error.status + " - " + error.message);
this.snackBar.open(`An unknown error occurred: ${error.message}.`, null, {
horizontalPosition: "left",
panelClass: ['snackError'],
duration: 2500 }
);
return EMPTY;
}));
return this.http.get<Page<T>>(`api/${resource}`)
.pipe(
map(x => Object.assign(new Page<T>(), x)),
catchError((error: HttpErrorResponse) =>
{
console.log(error.status + " - " + error.message);
this.snackBar.open(`An unknown error occurred: ${error.message}.`, null, {
horizontalPosition: "left",
panelClass: ['snackError'],
duration: 2500
});
return EMPTY;
}));
}
}
PageResolver.resolvers.push(Resolver);

View File

@ -1,8 +1,26 @@
export interface Page<T>
import {HttpClient} from "@angular/common/http";
export class Page<T>
{
this: string
next: string
first: string
count: number
items: T[]
constructor() {}
loadNext(client: HttpClient)
{
if (this.next == null)
return;
client.get<Page<T>>(this.next).subscribe(x =>
{
this.items.push(...x.items);
this.count += x.count;
this.next = x.next;
this.this = x.this;
});
}
}