Filtering First Pass (#442)

# Added
- Added: Added "In Progress" view to see everything you are currently reading
- Added: Added the ability to filter series based on format from "In Progress", "Recently Added", "Library Detail" pages.
- Added: Added total items to the above pages to showcase total series within Kavita

==============================
* Added filtering to recently added

* Cleaned up the documentation on the APIs and removed params no longer needed.

* Implemented Filtering on library detail, in progress, and recently added for format. UI is non-final.

* Moved filtering to an expander panel

* Cleaned up filtering UI a bit

* Cleaned up some code and added titles on touched pages

* Fixed recently added not re-rendering page

* Removed commented out code

* Version bump

* Added an animation to the filtering section

* Stashing changes, needing to switch lazy loading libraries out due to current version not trigging on dom mutation events

* Finally fixed all the lazy loading issues and made it so pagination works without reloading the whole page.
This commit is contained in:
Joseph Milazzo 2021-07-27 18:39:53 -05:00 committed by GitHub
parent 434bcdae4c
commit b9f20f4d19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 422 additions and 99 deletions

View file

@ -1,8 +1,31 @@
<div class="container-fluid" style="padding-top: 10px">
<h2><span *ngIf="actions.length > 0" class="">
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="header"></app-card-actionables>
</span>&nbsp;{{header}}</h2>
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'top' }"></ng-container>
<div class="row no-gutters">
<div class="col mr-auto">
<h2 style="display: inline-block">
<span *ngIf="actions.length > 0" class="">
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="header"></app-card-actionables>
</span>&nbsp;{{header}}&nbsp;<span class="badge badge-primary badge-pill" attr.aria-label="{{pagination.totalItems}} total items" *ngIf="pagination != undefined">{{pagination.totalItems}}</span>
</h2>
</div>
<button class="btn btn-secondary btn-small" (click)="collapse.toggle()" [attr.aria-expanded]="!filteringCollapsed" placement="left" ngbTooltip="{{filteringCollapsed ? 'Open' : 'Close'}} Filtering and Sorting" attr.aria-label="{{filteringCollapsed ? 'Open' : 'Close'}} Filtering and Sorting">
<i class="fa fa-filter" aria-hidden="true"></i>
<span class="sr-only">Sort / Filter</span>
</button>
</div>
<div class="row no-gutters filter-section" #collapse="ngbCollapse" [(ngbCollapse)]="filteringCollapsed">
<div class="col">
<form class="ml-2" [formGroup]="filterForm">
<div class="form-group" *ngIf="filters.length > 0">
<label for="series-filter">Filter</label>
<select class="form-control" id="series-filter" formControlName="filter" (ngModelChange)="handleFilterChange($event)" style="max-width: 200px;">
<option [value]="i" *ngFor="let opt of filters; let i = index">{{opt.title}}</option>
</select>
</div>
</form>
</div>
</div>
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'top' }"></ng-container>
<div class="row no-gutters">

View file

@ -1,9 +1,33 @@
import { Component, ContentChild, EventEmitter, Input, OnInit, Output, TemplateRef } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { Pagination } from 'src/app/_models/pagination';
import { FilterItem } from 'src/app/_models/series-filter';
import { ActionItem } from 'src/app/_services/action-factory.service';
const FILTER_PAG_REGEX = /[^0-9]/g;
export enum FilterAction {
/**
* If an option is selected on a multi select component
*/
Added = 0,
/**
* If an option is unselected on a multi select component
*/
Removed = 1,
/**
* If an option is selected on a single select component
*/
Selected = 2
}
export interface UpdateFilterEvent {
filterItem: FilterItem;
action: FilterAction;
}
const ANIMATION_SPEED = 300;
@Component({
selector: 'app-card-detail-layout',
templateUrl: './card-detail-layout.component.html',
@ -15,12 +39,29 @@ export class CardDetailLayoutComponent implements OnInit {
@Input() isLoading: boolean = false;
@Input() items: any[] = [];
@Input() pagination!: Pagination;
/**
* Any actions to exist on the header for the parent collection (library, collection)
*/
@Input() actions: ActionItem<any>[] = [];
/**
* A list of Filters which can filter the data of the page. If nothing is passed, the control will not show.
*/
@Input() filters: Array<FilterItem> = [];
@Input() trackByIdentity!: (index: number, item: any) => string;
@Output() itemClicked: EventEmitter<any> = new EventEmitter();
@Output() pageChange: EventEmitter<Pagination> = new EventEmitter();
@Output() applyFilter: EventEmitter<UpdateFilterEvent> = new EventEmitter();
@ContentChild('cardItem') itemTemplate!: TemplateRef<any>;
filterForm: FormGroup = new FormGroup({
filter: new FormControl(0, []),
});
/**
* Controls the visiblity of extended controls that sit below the main header.
*/
filteringCollapsed: boolean = true;
constructor() { }
@ -47,4 +88,11 @@ export class CardDetailLayoutComponent implements OnInit {
}
}
handleFilterChange(index: string) {
this.applyFilter.emit({
filterItem: this.filters[parseInt(index, 10)],
action: FilterAction.Selected
});
}
}

View file

@ -1,7 +1,9 @@
<div class="card">
<div class="overlay" (click)="handleClick()">
<img *ngIf="total > 0 || supressArchiveWarning" class="card-img-top" [lazyLoad]="imageUrl" [defaultImage]="imageSerivce.placeholderImage" alt="title">
<img *ngIf="total === 0 && !supressArchiveWarning" class="card-img-top" [lazyLoad]="imageSerivce.errorImage" alt="title">
<img *ngIf="total > 0 || supressArchiveWarning" class="card-img-top lazyload" [src]="imageSerivce.placeholderImage" [attr.data-src]="imageUrl"
(error)="imageSerivce.updateErroredImage($event)" aria-hidden="true" height="230px" width="158px">
<img *ngIf="total === 0 && !supressArchiveWarning" class="card-img-top lazyload" [src]="imageSerivce.errorImage" [attr.data-src]="imageUrl"
aria-hidden="true" height="230px" width="158px">
<div class="progress-banner" *ngIf="read < total && total > 0 && read !== (total -1)">
<p><ngb-progressbar type="primary" height="5px" [value]="read" [max]="total"></ngb-progressbar></p>
</div>

View file

@ -10,6 +10,8 @@ import { ActionItem } from 'src/app/_services/action-factory.service';
import { ImageService } from 'src/app/_services/image.service';
import { LibraryService } from 'src/app/_services/library.service';
import { UtilityService } from '../_services/utility.service';
// import 'lazysizes';
// import 'lazysizes/plugins/attrchange/ls.attrchange';
@Component({
selector: 'app-card-item',
@ -38,8 +40,7 @@ export class CardItemComponent implements OnInit, OnDestroy {
private readonly onDestroy = new Subject<void>();
constructor(public imageSerivce: ImageService, private libraryService: LibraryService, public utilityService: UtilityService) {
}
constructor(public imageSerivce: ImageService, private libraryService: LibraryService, public utilityService: UtilityService) {}
ngOnInit(): void {
if (this.entity.hasOwnProperty('promoted') && this.entity.hasOwnProperty('title')) {
@ -59,6 +60,7 @@ export class CardItemComponent implements OnInit, OnDestroy {
ngOnDestroy() {
this.onDestroy.next();
this.onDestroy.complete();
}
handleClick() {