Lots of Filtering Fixes & New Fields (#2244)

* Added an id for komf userscript to help it inject into Kavita's UI without relying on strings, given localization.

* Still working the filter fields, there is a bug with selecting an input and it setting undefined like crazy. Path is coded but not tested or validated.

* Stashing changed. Really not sure what's happening. I'm seeing 2 constructor calls for one row. I'm seeing a field change trigger 400 events. Values aren't getting set correctly on default string.

I've made a ton of changes, when resuming this work, look at the diff. All of this can be reset excluding the Path work.

* Lots of comments but the double instantiation is due to the mobile drawer. Added an ngIf which seems to work.

* Fixed dropdown options triggering a ton of looped calls. Default limitTo to 0 when user empties blank or negative.

* Removed a ton of UserId db calls from a ton of apis. Added a new API to allow UI to query a specific role to lessen load on UI.

* Optimized the code on new filtering to only load people by a given role. This should speed up heavily tagged libraries.

Commented out a bunch of code that's no longer used. Will be cleaned up later.

* Fixed support so that library filter can handle multiple selections.

* Fixed a bug when hitting enter in an input, the statement would be removed.

* Fixed multi-select not resuming from url correctly.

* Restored the series/all api for Tachiyomi to continue using until I'm motivated enough to update the extension.

* Fixed some resuming of state with dropdowns, not always setting values in correct order.

* Added FilePath Filter which lets a user search on individual files (slow, may need index)

* Added a full filepath for new filtering.
This commit is contained in:
Joe Milazzo 2023-08-29 16:03:19 -07:00 committed by GitHub
parent 69b5530a93
commit cd84913fb9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 576 additions and 238 deletions

View file

@ -37,7 +37,7 @@
"file-saver": "^2.0.5",
"lazysizes": "^5.3.2",
"ng-circle-progress": "^1.7.1",
"ng-select2-component": "^13.0.2",
"ng-select2-component": "^13.0.6",
"ngx-color-picker": "^14.0.0",
"ngx-extended-pdf-viewer": "^16.2.16",
"ngx-file-drop": "^16.0.0",
@ -10558,9 +10558,9 @@
}
},
"node_modules/ng-select2-component": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/ng-select2-component/-/ng-select2-component-13.0.2.tgz",
"integrity": "sha512-8Tms5p0V/0J0vCWOf2Vrk6tJlwbaf3D3As3iigcjRncYlfXN130agniBcZ007C3zK2KyLXJJRkEWzlCls8/TVQ==",
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/ng-select2-component/-/ng-select2-component-13.0.6.tgz",
"integrity": "sha512-CiAelglSz2aeYy0BiXRi32zc49Mq27+J1eDzTrXmf2o50MvNo3asS3NRVQcnSldo/zLcJafWCMueVfjVaV1etw==",
"dependencies": {
"ngx-infinite-scroll": ">=16.0.0",
"tslib": "^2.3.0"

View file

@ -42,7 +42,7 @@
"file-saver": "^2.0.5",
"lazysizes": "^5.3.2",
"ng-circle-progress": "^1.7.1",
"ng-select2-component": "^13.0.2",
"ng-select2-component": "^13.0.6",
"ngx-color-picker": "^14.0.0",
"ngx-extended-pdf-viewer": "^16.2.16",
"ngx-file-drop": "^16.0.0",

View file

@ -24,7 +24,9 @@ export enum FilterField
ReadProgress = 20,
Formats = 21,
ReleaseYear = 22,
ReadTime = 23
ReadTime = 23,
Path = 24,
FilePath = 25
}
export const allFields = Object.keys(FilterField)

View file

@ -8,7 +8,7 @@ import {AgeRating} from '../_models/metadata/age-rating';
import {AgeRatingDto} from '../_models/metadata/age-rating-dto';
import {Language} from '../_models/metadata/language';
import {PublicationStatusDto} from '../_models/metadata/publication-status-dto';
import {Person} from '../_models/metadata/person';
import {Person, PersonRole} from '../_models/metadata/person';
import {Tag} from '../_models/tag';
import {TextResonse} from '../_types/text-response';
import {FilterComparison} from '../_models/metadata/v2/filter-comparison';
@ -33,44 +33,44 @@ export class MetadataService {
constructor(private httpClient: HttpClient, private router: Router) { }
applyFilter(page: Array<any>, filter: FilterField, comparison: FilterComparison, value: string) {
const dto: SeriesFilterV2 = {
statements: [this.createDefaultFilterStatement(filter, comparison, value + '')],
combination: FilterCombination.Or,
limitTo: 0
};
//
// console.log('navigating to: ', this.filterUtilityService.urlFromFilterV2(page.join('/'), dto));
// this.router.navigateByUrl(this.filterUtilityService.urlFromFilterV2(page.join('/'), dto));
// applyFilter(page: Array<any>, filter: FilterField, comparison: FilterComparison, value: string) {
// const dto: SeriesFilterV2 = {
// statements: [this.createDefaultFilterStatement(filter, comparison, value + '')],
// combination: FilterCombination.Or,
// limitTo: 0
// };
// //
// // console.log('navigating to: ', this.filterUtilityService.urlFromFilterV2(page.join('/'), dto));
// // this.router.navigateByUrl(this.filterUtilityService.urlFromFilterV2(page.join('/'), dto));
//
// // Creates a temp name for the filter
// this.httpClient.post<string>(this.baseUrl + 'filter/create-temp', dto, TextResonse).pipe(map(name => {
// dto.name = name;
// }), switchMap((_) => {
// let params: any = {};
// params['filterName'] = dto.name;
// return this.router.navigate(page, {queryParams: params});
// })).subscribe();
//
// }
// Creates a temp name for the filter
this.httpClient.post<string>(this.baseUrl + 'filter/create-temp', dto, TextResonse).pipe(map(name => {
dto.name = name;
}), switchMap((_) => {
let params: any = {};
params['filterName'] = dto.name;
return this.router.navigate(page, {queryParams: params});
})).subscribe();
// getFilter(filterName: string) {
// return this.httpClient.get<SeriesFilterV2>(this.baseUrl + 'filter?name=' + filterName);
// }
}
getFilter(filterName: string) {
return this.httpClient.get<SeriesFilterV2>(this.baseUrl + 'filter?name=' + filterName);
}
getAgeRating(ageRating: AgeRating) {
if (this.ageRatingTypes != undefined && this.ageRatingTypes.hasOwnProperty(ageRating)) {
return of(this.ageRatingTypes[ageRating]);
}
return this.httpClient.get<string>(this.baseUrl + 'series/age-rating?ageRating=' + ageRating, TextResonse).pipe(map(ratingString => {
if (this.ageRatingTypes === undefined) {
this.ageRatingTypes = {};
}
this.ageRatingTypes[ageRating] = ratingString;
return this.ageRatingTypes[ageRating];
}));
}
// getAgeRating(ageRating: AgeRating) {
// if (this.ageRatingTypes != undefined && this.ageRatingTypes.hasOwnProperty(ageRating)) {
// return of(this.ageRatingTypes[ageRating]);
// }
// return this.httpClient.get<string>(this.baseUrl + 'series/age-rating?ageRating=' + ageRating, TextResonse).pipe(map(ratingString => {
// if (this.ageRatingTypes === undefined) {
// this.ageRatingTypes = {};
// }
//
// this.ageRatingTypes[ageRating] = ratingString;
// return this.ageRatingTypes[ageRating];
// }));
// }
getAllAgeRatings(libraries?: Array<number>) {
let method = 'metadata/age-ratings'
@ -132,10 +132,14 @@ export class MetadataService {
return this.httpClient.get<Array<Person>>(this.baseUrl + method);
}
getChapterSummary(chapterId: number) {
return this.httpClient.get<string>(this.baseUrl + 'metadata/chapter-summary?chapterId=' + chapterId, TextResonse);
getAllPeopleByRole(role: PersonRole) {
return this.httpClient.get<Array<Person>>(this.baseUrl + 'metadata/people-by-role?role=' + role);
}
// getChapterSummary(chapterId: number) {
// return this.httpClient.get<string>(this.baseUrl + 'metadata/chapter-summary?chapterId=' + chapterId, TextResonse);
// }
createDefaultFilterDto(): SeriesFilterV2 {
return {
statements: [] as FilterStatement[],
@ -159,6 +163,6 @@ export class MetadataService {
updateFilter(arr: Array<FilterStatement>, index: number, filterStmt: FilterStatement) {
arr[index].comparison = filterStmt.comparison;
arr[index].field = filterStmt.field;
arr[index].value = filterStmt.value + '';
arr[index].value = filterStmt.value ? filterStmt.value + '' : '';
}
}

View file

@ -39,7 +39,7 @@ export class SeriesService {
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
const data = filter || {};
return this.httpClient.post<PaginatedResult<Series[]>>(this.baseUrl + 'series/all', data, {observe: 'response', params}).pipe(
return this.httpClient.post<PaginatedResult<Series[]>>(this.baseUrl + 'series/all-v2', data, {observe: 'response', params}).pipe(
map((response: any) => {
return this.utilityService.createPaginatedResult(response, this.paginatedResults);
})

View file

@ -115,12 +115,14 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
}
ngOnInit(): void {
console.log('[card-detail-layout] ngOnInit')
if (this.trackByIdentity === undefined) {
this.trackByIdentity = (_: number, item: any) => `${this.header}_${this.updateApplied}_${item?.libraryId}`;
}
if (this.filterSettings === undefined) {
this.filterSettings = new FilterSettings();
console.log('[card-detail-layout] creating blank FilterSettings');
this.cdRef.markForCheck();
}
@ -178,6 +180,7 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
this.applyFilter.emit(event);
this.updateApplied++;
this.filter = event.filterV2;
console.log('[card-detail-layout] apply filter')
this.cdRef.markForCheck();
}

View file

@ -135,6 +135,11 @@ export class LibraryDetailComponent implements OnInit {
}
}
get Debug() {
console.log('rendered section ');
return 0;
}
constructor(private route: ActivatedRoute, private router: Router, private seriesService: SeriesService,
private libraryService: LibraryService, private titleService: Title, private actionFactoryService: ActionFactoryService,
private actionService: ActionService, public bulkSelectionService: BulkSelectionService, private hubService: MessageHubService,

View file

@ -1,9 +1,8 @@
<ng-container *transloco="let t; read: 'metadata-builder'">
<ng-container *ngIf="filter">
<ng-container *ngIf="utilityService.getActiveBreakpoint() === Breakpoint.Desktop; else mobileView">
<div class="container-fluid">
<form [formGroup]="formGroup">
<form [formGroup]="formGroup">
<ng-container *ngIf="utilityService.getActiveBreakpoint() === Breakpoint.Desktop; else mobileView">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-md-2">
<select class="form-select" formControlName="comparison">
@ -12,33 +11,30 @@
</div>
<div class="col-md-2">
<button class="btn btn-icon" (click)="addFilter()" [ngbTooltip]="t('add-rule')" [disabled]="statementLimit === -1 || (statementLimit > 0 && filter.statements.length >= statementLimit)">
<button type="button" class="btn btn-icon" (click)="addFilter()" [ngbTooltip]="t('add-rule')" [disabled]="statementLimit === -1 || (statementLimit > 0 && filter.statements.length >= statementLimit)">
<i class="fa fa-solid fa-plus" aria-hidden="true"></i>
<span class="visually-hidden" aria-hidden="true">{{t('add-rule')}}</span>
</button>
</div>
</div>
</form>
<div class="row mb-2" *ngFor="let filterStmt of filter.statements; let i = index">
<div class="col-md-10">
<app-metadata-row-filter [preset]="filterStmt" [availableFields]="availableFilterFields" (filterStatement)="updateFilter(i, $event)">
<div class="col-md-1 ms-2">
<button class="btn btn-icon" #removeBtn [ngbTooltip]="t('remove-rule', {num: i})" (click)="removeFilter(i)" *ngIf="i < (filter.statements.length - 1) && filter.statements.length > 1">
<i class="fa-solid fa-minus" aria-hidden="true"></i>
<span class="visually-hidden">{{t('remove-rule', {num: i})}}</span>
</button>
</div>
</app-metadata-row-filter>
<div class="row mb-2" *ngFor="let filterStmt of filter.statements; let i = index">
<div class="col-md-10">
<app-metadata-row-filter [index]="i + 100" [preset]="filterStmt" [availableFields]="availableFilterFields" (filterStatement)="updateFilter(i, $event)">
<div class="col-md-1 ms-2">
<button type="button" class="btn btn-icon" #removeBtn [ngbTooltip]="t('remove-rule', {num: i})" (click)="removeFilter(i)" *ngIf="i < (filter.statements.length - 1) && filter.statements.length > 1">
<i class="fa-solid fa-minus" aria-hidden="true"></i>
<span class="visually-hidden">{{t('remove-rule', {num: i})}}</span>
</button>
</div>
</app-metadata-row-filter>
</div>
</div>
</div>
</div>
</ng-container>
</ng-container>
<ng-template #mobileView>
<!-- TODO: Robbie please help me style this drawer only view -->
<div class="container-fluid">
<form [formGroup]="formGroup">
<ng-template #mobileView>
<div class="container-fluid">
<div class="row mb-3">
<div class="col-md-2 col-10">
<select class="form-select" formControlName="comparison">
@ -53,22 +49,21 @@
</button>
</div>
</div>
</form>
<div class="row mb-3" *ngFor="let filterStmt of filter.statements; let i = index">
<div class="col-md-12">
<app-metadata-row-filter [preset]="filterStmt" [availableFields]="availableFilterFields" (filterStatement)="updateFilter(i, $event)">
<div class="col-md-1 ms-2 col-1">
<button class="btn btn-icon" #removeBtn [ngbTooltip]="t('remove-rule')" (click)="removeFilter(i)" *ngIf="i < (filter.statements.length - 1) && filter.statements.length > 1">
<i class="fa-solid fa-minus" aria-hidden="true"></i>
<span class="visually-hidden">{{t('remove-rule')}}</span>
</button>
</div>
</app-metadata-row-filter>
<div class="row mb-3" *ngFor="let filterStmt of filter.statements; let i = index">
<div class="col-md-12">
<app-metadata-row-filter [index]="i" [preset]="filterStmt" [availableFields]="availableFilterFields" (filterStatement)="updateFilter(i, $event)">
<div class="col-md-1 ms-2 col-1">
<button type="button" class="btn btn-icon" #removeBtn [ngbTooltip]="t('remove-rule')" (click)="removeFilter(i)" *ngIf="i < (filter.statements.length - 1) && filter.statements.length > 1">
<i class="fa-solid fa-minus" aria-hidden="true"></i>
<span class="visually-hidden">{{t('remove-rule')}}</span>
</button>
</div>
</app-metadata-row-filter>
</div>
</div>
</div>
</div>
</ng-template>
</ng-template>
</form>
</ng-container>
</ng-container>

View file

@ -19,10 +19,9 @@ import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from "@angular
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {FilterCombination} from "../../../_models/metadata/v2/filter-combination";
import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service";
import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison";
import {allFields, FilterField} from "../../../_models/metadata/v2/filter-field";
import {allFields} from "../../../_models/metadata/v2/filter-field";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {tap} from "rxjs/operators";
import {distinctUntilChanged, tap} from "rxjs/operators";
import {translate, TranslocoDirective} from "@ngneat/transloco";
@Component({
@ -52,12 +51,14 @@ export class MetadataBuilderComponent implements OnInit {
@Input() statementLimit = 0;
@Input() availableFilterFields = allFields;
@Output() update: EventEmitter<SeriesFilterV2> = new EventEmitter<SeriesFilterV2>();
@Output() apply: EventEmitter<void> = new EventEmitter<void>();
private readonly cdRef = inject(ChangeDetectorRef);
private readonly metadataService = inject(MetadataService);
protected readonly utilityService = inject(UtilityService);
protected readonly filterUtilityService = inject(FilterUtilitiesService);
private readonly destroyRef = inject(DestroyRef);
protected readonly Breakpoint = Breakpoint;
formGroup: FormGroup = new FormGroup({});
@ -66,39 +67,30 @@ export class MetadataBuilderComponent implements OnInit {
{value: FilterCombination.And, title: translate('metadata-builder.and')},
];
get Breakpoint() { return Breakpoint; }
ngOnInit() {
if (this.filter === undefined) {
// I've left this in to see if it ever happens or not
console.error('No filter, creating one in metadata-builder')
// If there is no default preset, let's open with series name
this.filter = this.filterUtilityService.createSeriesV2Filter();
this.filter.statements.push({
value: '',
comparison: FilterComparison.Equal,
field: FilterField.SeriesName
});
}
console.log('[builder] ngOnInit');
this.formGroup.addControl('comparison', new FormControl<FilterCombination>(this.filter?.combination || FilterCombination.Or, []));
this.formGroup.valueChanges.pipe(takeUntilDestroyed(this.destroyRef), tap(values => {
this.filter.combination = parseInt(this.formGroup.get('comparison')?.value, 10);
this.formGroup.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef), tap(values => {
this.filter.combination = parseInt(this.formGroup.get('comparison')?.value, 10) as FilterCombination;
console.log('[builder] emitting filter from comparison change');
this.update.emit(this.filter);
})).subscribe()
})).subscribe();
}
addFilter() {
console.log('[builder] Adding Filter')
this.filter.statements = [this.metadataService.createDefaultFilterStatement(), ...this.filter.statements];
this.cdRef.markForCheck();
}
removeFilter(index: number) {
console.log('[builder] Removing filter')
this.filter.statements = this.filter.statements.slice(0, index).concat(this.filter.statements.slice(index + 1))
this.cdRef.markForCheck();
}
updateFilter(index: number, filterStmt: FilterStatement) {
console.log('[builder] updating filter: ', this.filter.statements);
this.metadataService.updateFilter(this.filter.statements, index, filterStmt);
this.update.emit(this.filter);
}

View file

@ -2,11 +2,9 @@
<form [formGroup]="formGroup">
<div class="row g-0">
<div class="col-md-3 me-2 col-10 mb-2">
<ng-container *ngIf="formGroup.get('input') as control">
<select class="form-select me-2" formControlName="input">
<option *ngFor="let field of availableFields" [value]="field">{{field | filterField}}</option>
</select>
</ng-container>
<select class="form-select me-2" formControlName="input">
<option *ngFor="let field of availableFields" [value]="field">{{field | filterField}}</option>
</select>
</div>
<div class="col-md-2 me-2 col-10 mb-2">
@ -22,7 +20,7 @@
<input type="text" class="form-control me-2" autocomplete="true" formControlName="filterValue">
</ng-container>
<ng-container *ngSwitchCase="PredicateType.Number">
<input type="number" inputmode="numeric" class="form-control me-2" formControlName="filterValue">
<input type="number" inputmode="numeric" class="form-control me-2" formControlName="filterValue" min="0">
</ng-container>
<ng-container *ngSwitchCase="PredicateType.Dropdown">
<ng-container *ngIf="dropdownOptions$ | async as opts">

View file

@ -3,15 +3,23 @@ import {
ChangeDetectorRef,
Component,
DestroyRef,
EventEmitter,
inject,
EventEmitter, inject,
Input,
OnInit,
Output
Output,
} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {FilterStatement} from '../../../_models/metadata/v2/filter-statement';
import {BehaviorSubject, distinctUntilChanged, map, Observable, of, startWith, switchMap, tap} from 'rxjs';
import {
BehaviorSubject,
distinctUntilChanged, filter,
map,
Observable,
of,
startWith,
switchMap,
tap
} from 'rxjs';
import {MetadataService} from 'src/app/_services/metadata.service';
import {mangaFormatFilters} from 'src/app/_models/metadata/series-filter';
import {PersonRole} from 'src/app/_models/metadata/person';
@ -32,7 +40,7 @@ enum PredicateType {
Dropdown = 3,
}
const StringFields = [FilterField.SeriesName, FilterField.Summary];
const StringFields = [FilterField.SeriesName, FilterField.Summary, FilterField.Path, FilterField.FilePath];
const NumberFields = [FilterField.ReadTime, FilterField.ReleaseYear, FilterField.ReadProgress, FilterField.UserRating];
const DropdownFields = [FilterField.PublicationStatus, FilterField.Languages, FilterField.AgeRating,
FilterField.Translators, FilterField.Characters, FilterField.Publisher,
@ -81,6 +89,7 @@ const DropdownComparisons = [FilterComparison.Equal,
})
export class MetadataFilterRowComponent implements OnInit {
@Input() index: number = 0; // This is only for debugging
/**
* Slightly misleading as this is the initial state and will be updated on the filterStatement event emitter
*/
@ -100,9 +109,7 @@ export class MetadataFilterRowComponent implements OnInit {
dropdownOptions$ = of<Select2Option[]>([]);
loaded: boolean = false;
get PredicateType() { return PredicateType };
protected readonly PredicateType = PredicateType;
get MultipleDropdownAllowed() {
const comp = parseInt(this.formGroup.get('comparison')?.value, 10) as FilterComparison;
@ -113,38 +120,37 @@ export class MetadataFilterRowComponent implements OnInit {
private readonly collectionTagService: CollectionTagService) {}
ngOnInit() {
console.log('[ngOnInit] creating stmt (' + this.index + '): ', this.preset)
this.formGroup.addControl('input', new FormControl<FilterField>(FilterField.SeriesName, []));
this.formGroup.get('input')?.valueChanges.subscribe((val: string) => this.handleFieldChange(val));
this.formGroup.get('input')?.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe((val: string) => this.handleFieldChange(val));
this.populateFromPreset();
this.formGroup.get('filterValue')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef), tap(v => console.log('filterValue: ', v))).subscribe();
// Dropdown dynamic option selection
this.dropdownOptions$ = this.formGroup.get('input')!.valueChanges.pipe(
startWith(this.preset.value),
switchMap((_) => this.getDropdownObservable()),
tap((opts) => {
if (!this.formGroup.get('filterValue')?.value) {
this. populateFromPreset();
return;
}
if (this.MultipleDropdownAllowed) {
this.formGroup.get('filterValue')?.setValue((opts[0].value + '').split(','));
} else {
this.formGroup.get('filterValue')?.setValue(opts[0].value);
}
distinctUntilChanged(),
filter(() => {
const inputVal = parseInt(this.formGroup.get('input')?.value, 10) as FilterField;
return DropdownFields.includes(inputVal);
}),
switchMap((_) => this.getDropdownObservable()),
takeUntilDestroyed(this.destroyRef)
);
this.formGroup.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe(_ => {
this.filterStatement.emit({
this.formGroup!.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe(_ => {
const stmt = {
comparison: parseInt(this.formGroup.get('comparison')?.value, 10) as FilterComparison,
field: parseInt(this.formGroup.get('input')?.value, 10) as FilterField,
value: this.formGroup.get('filterValue')?.value!
});
};
if (!stmt.value && stmt.field !== FilterField.SeriesName) return;
console.log('updating parent with new statement: ', stmt.value)
this.filterStatement.emit(stmt);
});
this.loaded = true;
@ -152,30 +158,37 @@ export class MetadataFilterRowComponent implements OnInit {
}
populateFromPreset() {
const val = this.preset.value === "undefined" || !this.preset.value ? '' : this.preset.value;
console.log('populating preset: ', val);
this.formGroup.get('comparison')?.patchValue(this.preset.comparison);
this.formGroup.get('input')?.patchValue(this.preset.field);
if (StringFields.includes(this.preset.field)) {
this.formGroup.get('filterValue')?.patchValue(this.preset.value);
this.formGroup.get('filterValue')?.patchValue(val);
} else if (DropdownFields.includes(this.preset.field)) {
if (this.MultipleDropdownAllowed) {
this.formGroup.get('filterValue')?.setValue(this.preset.value.split(','));
if (this.MultipleDropdownAllowed || val.includes(',')) {
console.log('setting multiple values: ', val.split(',').map(d => parseInt(d, 10)));
this.formGroup.get('filterValue')?.patchValue(val.split(',').map(d => parseInt(d, 10)));
} else {
if (this.preset.field === FilterField.Languages) {
this.formGroup.get('filterValue')?.setValue(this.preset.value);
this.formGroup.get('filterValue')?.patchValue(val);
} else {
this.formGroup.get('filterValue')?.setValue(parseInt(this.preset.value, 10));
this.formGroup.get('filterValue')?.patchValue(parseInt(val, 10));
}
}
} else {
this.formGroup.get('filterValue')?.patchValue(parseInt(this.preset.value, 10));
this.formGroup.get('filterValue')?.patchValue(parseInt(val, 10));
}
this.formGroup.get('comparison')?.patchValue(this.preset.comparison);
this.formGroup.get('input')?.setValue(this.preset.field);
this.cdRef.markForCheck();
}
getDropdownObservable(): Observable<Select2Option[]> {
const filterField = parseInt(this.formGroup.get('input')?.value, 10) as FilterField;
console.log('Getting dropdown observable: ', filterField);
switch (filterField) {
case FilterField.PublicationStatus:
return this.metadataService.getAllPublicationStatus().pipe(map(pubs => pubs.map(pub => {
@ -224,41 +237,46 @@ export class MetadataFilterRowComponent implements OnInit {
}
getPersonOptions(role: PersonRole) {
return this.metadataService.getAllPeople().pipe(map(people => people.filter(p2 => p2.role === role).map(person => {
return this.metadataService.getAllPeopleByRole(role).pipe(map(people => people.map(person => {
return {value: person.id, label: person.name}
})))
})));
}
handleFieldChange(val: string) {
const inputVal = parseInt(val, 10) as FilterField;
console.log('HandleFieldChange: ', val);
if (StringFields.includes(inputVal)) {
this.validComparisons$.next(StringComparisons);
this.predicateType$.next(PredicateType.Text);
if (this.loaded) this.formGroup.get('filterValue')?.setValue('');
if (this.loaded) {
this.formGroup.get('filterValue')?.patchValue('',{emitEvent: false});
console.log('setting filterValue to empty string', this.formGroup.get('filterValue')?.value)
} // BUG: undefined is getting set and the input value isn't updating and emitting to the backend
return;
}
if (NumberFields.includes(inputVal)) {
let comps = [...NumberComparisons];
const comps = [...NumberComparisons];
if (inputVal === FilterField.ReleaseYear) {
comps.push(...DateComparisons);
}
this.validComparisons$.next(comps);
this.predicateType$.next(PredicateType.Number);
if (this.loaded) this.formGroup.get('filterValue')?.setValue('');
if (this.loaded) this.formGroup.get('filterValue')?.patchValue(0);
return;
}
if (DropdownFields.includes(inputVal)) {
let comps = [...DropdownComparisons];
const comps = [...DropdownComparisons];
if (inputVal === FilterField.AgeRating) {
comps.push(...NumberComparisons);
}
this.validComparisons$.next(comps);
this.predicateType$.next(PredicateType.Dropdown);
return;
}
}

View file

@ -58,6 +58,10 @@ export class FilterFieldPipe implements PipeTransform {
return translate('filter-field-pipe.user-rating');
case FilterField.Writers:
return translate('filter-field-pipe.writers');
case FilterField.Path:
return translate('filter-field-pipe.path');
case FilterField.FilePath:
return translate('filter-field-pipe.file-path');
default:
throw new Error(`Invalid FilterField value: ${value}`);
}

View file

@ -1,17 +1,18 @@
<ng-container *transloco="let t; read: 'metadata-filter'">
<ng-container *ngIf="toggleService.toggleState$ | async as isOpen">
<div class="phone-hidden">
<div class="phone-hidden" *ngIf="utilityService.getActiveBreakpoint() > Breakpoint.Tablet">
<div #collapse="ngbCollapse" [ngbCollapse]="!isOpen" (ngbCollapseChange)="setToggle($event)">
<ng-container [ngTemplateOutlet]="filterSection"></ng-container>
</div>
</div>
<div class="not-phone-hidden">
<div class="not-phone-hidden" *ngIf="utilityService.getActiveBreakpoint() < Breakpoint.Desktop">
<app-drawer #commentDrawer="drawer" [isOpen]="isOpen" [options]="{topOffset: 56}" (drawerClosed)="toggleService.set(false)">
<h5 header>
{{t('filter-title')}}
</h5>
<div body class="drawer-body">
<!-- TODO: BUG: Filter section is instantiated twice if this isn't ngIf'd -->
<ng-container [ngTemplateOutlet]="filterSection"></ng-container>
</div>
</app-drawer>
@ -19,9 +20,13 @@
</ng-container>
<ng-template #filterSection>
<div class="filter-section mx-auto pb-3" *ngIf="fullyLoaded">
<div class="filter-section mx-auto pb-3" *ngIf="fullyLoaded && filterV2">
<div class="row justify-content-center g-0">
<app-metadata-builder [filter]="filterV2!" [availableFilterFields]="allFilterFields" (update)="handleFilters($event)" [statementLimit]="filterSettings.statementLimit"></app-metadata-builder>
<app-metadata-builder [filter]="filterV2"
[availableFilterFields]="allFilterFields"
[statementLimit]="filterSettings.statementLimit"
(update)="handleFilters($event)">
</app-metadata-builder>
</div>
<form [formGroup]="sortGroup" class="container-fluid">
<div class="row mb-3">

View file

@ -76,6 +76,7 @@ export class MetadataFilterComponent implements OnInit {
allFilterFields = allFields;
handleFilters(filter: SeriesFilterV2) {
console.log('[metadata-filter] updating filter');
this.filterV2 = filter;
}
@ -86,6 +87,7 @@ export class MetadataFilterComponent implements OnInit {
constructor(public toggleService: ToggleService) {}
ngOnInit(): void {
console.log('[metadata-filter] ngOnInit')
if (this.filterSettings === undefined) {
this.filterSettings = new FilterSettings();
this.cdRef.markForCheck();
@ -137,6 +139,7 @@ export class MetadataFilterComponent implements OnInit {
loadFromPresetsAndSetup() {
this.fullyLoaded = false;
console.log('[metadata-filter] loading from preset and setting up');
this.filterV2 = this.deepClone(this.filterSettings.presetsV2);
this.sortGroup = new FormGroup({
@ -145,6 +148,7 @@ export class MetadataFilterComponent implements OnInit {
});
this.sortGroup.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
console.log('[metadata-filter] sortGroup value change');
if (this.filterV2?.sortOptions === null) {
this.filterV2.sortOptions = {
isAscending: this.isAscendingSort,
@ -152,12 +156,11 @@ export class MetadataFilterComponent implements OnInit {
};
}
this.filterV2!.sortOptions!.sortField = parseInt(this.sortGroup.get('sortField')?.value, 10);
this.filterV2!.limitTo = parseInt(this.sortGroup.get('limitTo')?.value, 10);
this.filterV2!.limitTo = Math.max(parseInt(this.sortGroup.get('limitTo')?.value || '0', 10), 0);
this.cdRef.markForCheck();
});
this.fullyLoaded = true;
this.cdRef.markForCheck();
this.apply();
}
@ -173,6 +176,7 @@ export class MetadataFilterComponent implements OnInit {
}
this.filterV2!.sortOptions!.isAscending = this.isAscendingSort;
console.log('[metadata-filter] updated filter sort order')
}
clear() {
@ -181,7 +185,6 @@ export class MetadataFilterComponent implements OnInit {
}
apply() {
this.applyFilter.emit({isFirst: this.updateApplied === 0, filterV2: this.filterV2!});
if (this.utilityService.getActiveBreakpoint() === Breakpoint.Mobile && this.updateApplied !== 0) {

View file

@ -1731,7 +1731,9 @@
"tags": "Tags",
"translators": "Translators",
"user-rating": "User Rating",
"writers": "Writers"
"writers": "Writers",
"path": "Path",
"file-path": "File Path"
},
"filter-comparison-pipe": {