Filtering Bugfixes (#1220)

* Cleaned up random strings and unified them in one place.

* Implemented the ability to disable typeaheads

* Refactored disable state to disable controls on filter

* Fixed an overflow regression on title

* Updated ComicInfo DTO which had some bad properties on it

* Cleaned up some code around disabled typeaheads/filters

* Fixed typeaheads causing resets to state and mucking up filter presets

* Fixed state not refreshing between page loads

* Fixed a bad parsing for My Charms Are Wasted on Kuroiwa Medaka - Ch. 37.5 - Volume Extras

* Cleanup within the metadata filter to reuse logic and minimize extra loops.

* Fixed a timing issue with typeahead and first load for people

* Fixed a bug in Publication Status for a given library, which would fail due to not performing some of the query in memory. Removed a custom index on Series table that wasn't used and potentially caused constraint issues.

* Added a wiki link for stats collections

* Security bump

* Fixed the regex
This commit is contained in:
Joseph Milazzo 2022-04-16 18:29:11 -05:00 committed by GitHub
parent e3aa9abf55
commit e630e0b2c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1856 additions and 291 deletions

View file

@ -1,10 +1,42 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
import { LibraryType } from 'src/app/_models/library';
import { Pagination } from 'src/app/_models/pagination';
import { SeriesFilter, SortField } from 'src/app/_models/series-filter';
import { SeriesService } from 'src/app/_services/series.service';
/**
* Used to pass state between the filter and the url
*/
export enum FilterQueryParam {
Format = 'format',
Genres = 'genres',
AgeRating = 'ageRating',
PublicationStatus = 'publicationStatus',
Tags = 'tags',
Languages = 'languages',
CollectionTags = 'collectionTags',
Libraries = 'libraries',
Writers = 'writers',
Artists = 'artists',
Character = 'character',
Colorist = 'colorist',
CoverArtists = 'coverArtists',
Editor = 'editor',
Inker = 'inker',
Letterer = 'letterer',
Penciller = 'penciller',
Publisher = 'publisher',
Translator = 'translators',
ReadStatus = 'readStatus',
SortBy = 'sortBy',
Rating = 'rating',
Name = 'name',
/**
* This is a pagination control
*/
Page = 'page'
}
@Injectable({
providedIn: 'root'
})
@ -38,10 +70,11 @@ export class FilterUtilitiesService {
/**
* Will fetch current page from route if present
* @param ActivatedRouteSnapshot to fetch page from. Must be from component else may get stale data
* @returns A default pagination object
*/
pagination(): Pagination {
return {currentPage: parseInt(this.route.snapshot.queryParamMap.get('page') || '1', 10), itemsPerPage: 30, totalItems: 0, totalPages: 1};
pagination(snapshot: ActivatedRouteSnapshot): Pagination {
return {currentPage: parseInt(snapshot.queryParamMap.get('page') || '1', 10), itemsPerPage: 30, totalItems: 0, totalPages: 1};
}
@ -54,46 +87,44 @@ export class FilterUtilitiesService {
urlFromFilter(currentUrl: string, filter: SeriesFilter | undefined) {
if (filter === undefined) return currentUrl;
let params = '';
params += this.joinFilter(filter.formats, 'format');
params += this.joinFilter(filter.genres, 'genres');
params += this.joinFilter(filter.ageRating, 'ageRating');
params += this.joinFilter(filter.publicationStatus, 'publicationStatus');
params += this.joinFilter(filter.tags, 'tags');
params += this.joinFilter(filter.languages, 'languages');
params += this.joinFilter(filter.collectionTags, 'collectionTags');
params += this.joinFilter(filter.libraries, 'libraries');
params += this.joinFilter(filter.formats, FilterQueryParam.Format);
params += this.joinFilter(filter.genres, FilterQueryParam.Genres);
params += this.joinFilter(filter.ageRating, FilterQueryParam.AgeRating);
params += this.joinFilter(filter.publicationStatus, FilterQueryParam.PublicationStatus);
params += this.joinFilter(filter.tags, FilterQueryParam.Tags);
params += this.joinFilter(filter.languages, FilterQueryParam.Languages);
params += this.joinFilter(filter.collectionTags, FilterQueryParam.CollectionTags);
params += this.joinFilter(filter.libraries, FilterQueryParam.Libraries);
params += this.joinFilter(filter.writers, 'writers');
params += this.joinFilter(filter.artists, 'artists');
params += this.joinFilter(filter.character, 'character');
params += this.joinFilter(filter.colorist, 'colorist');
params += this.joinFilter(filter.coverArtist, 'coverArtists');
params += this.joinFilter(filter.editor, 'editor');
params += this.joinFilter(filter.inker, 'inker');
params += this.joinFilter(filter.letterer, 'letterer');
params += this.joinFilter(filter.penciller, 'penciller');
params += this.joinFilter(filter.publisher, 'publisher');
params += this.joinFilter(filter.translators, 'translators');
params += this.joinFilter(filter.writers, FilterQueryParam.Writers);
params += this.joinFilter(filter.artists, FilterQueryParam.Artists);
params += this.joinFilter(filter.character, FilterQueryParam.Character);
params += this.joinFilter(filter.colorist, FilterQueryParam.Colorist);
params += this.joinFilter(filter.coverArtist, FilterQueryParam.CoverArtists);
params += this.joinFilter(filter.editor, FilterQueryParam.Editor);
params += this.joinFilter(filter.inker, FilterQueryParam.Inker);
params += this.joinFilter(filter.letterer, FilterQueryParam.Letterer);
params += this.joinFilter(filter.penciller, FilterQueryParam.Penciller);
params += this.joinFilter(filter.publisher, FilterQueryParam.Publisher);
params += this.joinFilter(filter.translators, FilterQueryParam.Translator);
// readStatus (we need to do an additonal check as there is a default case)
if (filter.readStatus && filter.readStatus.inProgress !== true && filter.readStatus.notRead !== true && filter.readStatus.read !== true) {
params += '&readStatus=' + `${filter.readStatus.inProgress},${filter.readStatus.notRead},${filter.readStatus.read}`;
params += `&${FilterQueryParam.ReadStatus}=${filter.readStatus.inProgress},${filter.readStatus.notRead},${filter.readStatus.read}`;
}
// sortBy (additional check to not save to url if default case)
if (filter.sortOptions && !(filter.sortOptions.sortField === SortField.SortName && filter.sortOptions.isAscending === true)) {
params += '&sortBy=' + filter.sortOptions.sortField + ',' + filter.sortOptions.isAscending;
params += `&${FilterQueryParam.SortBy}=${filter.sortOptions.sortField},${filter.sortOptions.isAscending}`;
}
if (filter.rating > 0) {
params += '&rating=' + filter.rating;
params += `&${FilterQueryParam.Rating}=${filter.rating}`;
}
if (filter.seriesNameQuery !== '') {
params += '&name=' + encodeURIComponent(filter.seriesNameQuery);
params += `&${FilterQueryParam.Name}=${encodeURIComponent(filter.seriesNameQuery)}`;
}
return currentUrl + params;
@ -102,143 +133,143 @@ export class FilterUtilitiesService {
private joinFilter(filterProp: Array<any>, key: string) {
let params = '';
if (filterProp.length > 0) {
params += `&${key}=` + filterProp.join(',');
params += `&${key}=${filterProp.join(',')}`;
}
return params;
}
/**
* Returns a new instance of a filterSettings that is populated with filter presets from URL
* @param ActivatedRouteSnapshot to fetch page from. Must be from component else may get stale data
* @returns The Preset filter and if something was set within
*/
filterPresetsFromUrl(): [SeriesFilter, boolean] {
const snapshot = this.route.snapshot;
filterPresetsFromUrl(snapshot: ActivatedRouteSnapshot): [SeriesFilter, boolean] {
const filter = this.seriesService.createSeriesFilter();
let anyChanged = false;
const format = snapshot.queryParamMap.get('format');
const format = snapshot.queryParamMap.get(FilterQueryParam.Format);
if (format !== undefined && format !== null) {
filter.formats = [...filter.formats, ...format.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const genres = snapshot.queryParamMap.get('genres');
const genres = snapshot.queryParamMap.get(FilterQueryParam.Genres);
if (genres !== undefined && genres !== null) {
filter.genres = [...filter.genres, ...genres.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const ageRating = snapshot.queryParamMap.get('ageRating');
const ageRating = snapshot.queryParamMap.get(FilterQueryParam.AgeRating);
if (ageRating !== undefined && ageRating !== null) {
filter.ageRating = [...filter.ageRating, ...ageRating.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const publicationStatus = snapshot.queryParamMap.get('publicationStatus');
const publicationStatus = snapshot.queryParamMap.get(FilterQueryParam.PublicationStatus);
if (publicationStatus !== undefined && publicationStatus !== null) {
filter.publicationStatus = [...filter.publicationStatus, ...publicationStatus.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const tags = snapshot.queryParamMap.get('tags');
const tags = snapshot.queryParamMap.get(FilterQueryParam.Tags);
if (tags !== undefined && tags !== null) {
filter.tags = [...filter.tags, ...tags.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const languages = snapshot.queryParamMap.get('languages');
const languages = snapshot.queryParamMap.get(FilterQueryParam.Languages);
if (languages !== undefined && languages !== null) {
filter.languages = [...filter.languages, ...languages.split(',')];
anyChanged = true;
}
const writers = snapshot.queryParamMap.get('writers');
const writers = snapshot.queryParamMap.get(FilterQueryParam.Writers);
if (writers !== undefined && writers !== null) {
filter.writers = [...filter.writers, ...writers.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const artists = snapshot.queryParamMap.get('artists');
const artists = snapshot.queryParamMap.get(FilterQueryParam.Artists);
if (artists !== undefined && artists !== null) {
filter.artists = [...filter.artists, ...artists.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const character = snapshot.queryParamMap.get('character');
const character = snapshot.queryParamMap.get(FilterQueryParam.Character);
if (character !== undefined && character !== null) {
filter.character = [...filter.character, ...character.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const colorist = snapshot.queryParamMap.get('colorist');
const colorist = snapshot.queryParamMap.get(FilterQueryParam.Colorist);
if (colorist !== undefined && colorist !== null) {
filter.colorist = [...filter.colorist, ...colorist.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const coverArtists = snapshot.queryParamMap.get('coverArtists');
const coverArtists = snapshot.queryParamMap.get(FilterQueryParam.CoverArtists);
if (coverArtists !== undefined && coverArtists !== null) {
filter.coverArtist = [...filter.coverArtist, ...coverArtists.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const editor = snapshot.queryParamMap.get('editor');
const editor = snapshot.queryParamMap.get(FilterQueryParam.Editor);
if (editor !== undefined && editor !== null) {
filter.editor = [...filter.editor, ...editor.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const inker = snapshot.queryParamMap.get('inker');
const inker = snapshot.queryParamMap.get(FilterQueryParam.Inker);
if (inker !== undefined && inker !== null) {
filter.inker = [...filter.inker, ...inker.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const letterer = snapshot.queryParamMap.get('letterer');
const letterer = snapshot.queryParamMap.get(FilterQueryParam.Letterer);
if (letterer !== undefined && letterer !== null) {
filter.letterer = [...filter.letterer, ...letterer.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const penciller = snapshot.queryParamMap.get('penciller');
const penciller = snapshot.queryParamMap.get(FilterQueryParam.Penciller);
if (penciller !== undefined && penciller !== null) {
filter.penciller = [...filter.penciller, ...penciller.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const publisher = snapshot.queryParamMap.get('publisher');
const publisher = snapshot.queryParamMap.get(FilterQueryParam.Publisher);
if (publisher !== undefined && publisher !== null) {
filter.publisher = [...filter.publisher, ...publisher.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const translators = snapshot.queryParamMap.get('translators');
const translators = snapshot.queryParamMap.get(FilterQueryParam.Translator);
if (translators !== undefined && translators !== null) {
filter.translators = [...filter.translators, ...translators.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const libraries = snapshot.queryParamMap.get('libraries');
const libraries = snapshot.queryParamMap.get(FilterQueryParam.Libraries);
if (libraries !== undefined && libraries !== null) {
filter.libraries = [...filter.libraries, ...libraries.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
const collectionTags = snapshot.queryParamMap.get('collectionTags');
const collectionTags = snapshot.queryParamMap.get(FilterQueryParam.CollectionTags);
if (collectionTags !== undefined && collectionTags !== null) {
filter.collectionTags = [...filter.collectionTags, ...collectionTags.split(',').map(item => parseInt(item, 10))];
anyChanged = true;
}
// Rating, seriesName,
const rating = snapshot.queryParamMap.get('rating');
const rating = snapshot.queryParamMap.get(FilterQueryParam.Rating);
if (rating !== undefined && rating !== null && parseInt(rating, 10) > 0) {
filter.rating = parseInt(rating, 10);
anyChanged = true;
}
/// Read status is encoded as true,true,true
const readStatus = snapshot.queryParamMap.get('readStatus');
const readStatus = snapshot.queryParamMap.get(FilterQueryParam.ReadStatus);
if (readStatus !== undefined && readStatus !== null) {
const values = readStatus.split(',').map(i => i === 'true');
if (values.length === 3) {
@ -249,7 +280,7 @@ export class FilterUtilitiesService {
}
}
const sortBy = snapshot.queryParamMap.get('sortBy');
const sortBy = snapshot.queryParamMap.get(FilterQueryParam.SortBy);
if (sortBy !== undefined && sortBy !== null) {
const values = sortBy.split(',');
if (values.length === 1) {
@ -264,7 +295,7 @@ export class FilterUtilitiesService {
}
}
const searchNameQuery = snapshot.queryParamMap.get('name');
const searchNameQuery = snapshot.queryParamMap.get(FilterQueryParam.Name);
if (searchNameQuery !== undefined && searchNameQuery !== null && searchNameQuery !== '') {
filter.seriesNameQuery = decodeURIComponent(searchNameQuery);
anyChanged = true;