Refactored to make the FilterSettings genetic and type safe. Now I need to refactor the metadata component to respect the generics.

This commit is contained in:
Joseph Milazzo 2025-06-09 18:27:06 -05:00
parent 2654ea2965
commit a4c31debd0
20 changed files with 117 additions and 65 deletions

View file

@ -23,7 +23,7 @@ export enum SortField {
Random = 9 Random = 9
} }
export const allSortFields = Object.keys(SortField) export const allSeriesSortFields = Object.keys(SortField)
.filter(key => !isNaN(Number(key)) && parseInt(key, 10) >= 0) .filter(key => !isNaN(Number(key)) && parseInt(key, 10) >= 0)
.map(key => parseInt(key, 10)) as SortField[]; .map(key => parseInt(key, 10)) as SortField[];

View file

@ -48,7 +48,7 @@ const enumArray = Object.keys(FilterField)
enumArray.sort((a, b) => a.value.localeCompare(b.value)); enumArray.sort((a, b) => a.value.localeCompare(b.value));
export const allFields = enumArray export const allSeriesFilterFields = enumArray
.map(key => parseInt(key.key, 10))as FilterField[]; .map(key => parseInt(key.key, 10))as FilterField[];
export const allPeople = [ export const allPeople = [

View file

@ -2,10 +2,10 @@ import {FilterStatement} from "./filter-statement";
import {FilterCombination} from "./filter-combination"; import {FilterCombination} from "./filter-combination";
import {SortOptions} from "./sort-options"; import {SortOptions} from "./sort-options";
export interface FilterV2<T> { export interface FilterV2<TFilter, TSort extends number = number> {
name?: string; name?: string;
statements: Array<FilterStatement<T>>; statements: Array<FilterStatement<TFilter>>;
combination: FilterCombination; combination: FilterCombination;
sortOptions?: SortOptions; sortOptions?: SortOptions<TSort>;
limitTo: number; limitTo: number;
} }

View file

@ -3,3 +3,9 @@ export enum PersonFilterField {
Role = 1, Role = 1,
Name = 2 Name = 2
} }
export const allPersonFilterFields = Object.keys(PersonFilterField)
.filter(key => !isNaN(Number(key)) && parseInt(key, 10) >= 0)
.map(key => parseInt(key, 10)) as PersonFilterField[];

View file

@ -3,3 +3,7 @@ export enum PersonSortField {
SeriesCount = 2, SeriesCount = 2,
ChapterCount = 3 ChapterCount = 3
} }
export const allPersonSortFields = Object.keys(PersonSortField)
.filter(key => !isNaN(Number(key)) && parseInt(key, 10) >= 0)
.map(key => parseInt(key, 10)) as PersonSortField[];

View file

@ -1,11 +1,10 @@
import {SortField} from "../series-filter";
import {PersonSortField} from "./person-sort-field"; import {PersonSortField} from "./person-sort-field";
/** /**
* Series-based Sort options * Series-based Sort options
*/ */
export interface SortOptions { export interface SortOptions<TSort extends number = number> {
sortField: SortField; sortField: TSort;
isAscending: boolean; isAscending: boolean;
} }

View file

@ -25,5 +25,4 @@ export class FilterService {
renameSmartFilter(filter: SmartFilter) { renameSmartFilter(filter: SmartFilter) {
return this.httpClient.post(this.baseUrl + `filter/rename?filterId=${filter.id}&name=${filter.name.trim()}`, {}); return this.httpClient.post(this.baseUrl + `filter/rename?filterId=${filter.id}&name=${filter.name.trim()}`, {});
} }
} }

View file

@ -227,5 +227,4 @@ export class SeriesService {
updateDontMatch(seriesId: number, dontMatch: boolean) { updateDontMatch(seriesId: number, dontMatch: boolean) {
return this.httpClient.post<string>(this.baseUrl + `series/dont-match?seriesId=${seriesId}&dontMatch=${dontMatch}`, {}, TextResonse); return this.httpClient.post<string>(this.baseUrl + `series/dont-match?seriesId=${seriesId}&dontMatch=${dontMatch}`, {}, TextResonse);
} }
} }

View file

@ -11,13 +11,12 @@ import {Title} from '@angular/platform-browser';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {debounceTime, take} from 'rxjs/operators'; import {debounceTime, take} from 'rxjs/operators';
import {BulkSelectionService} from 'src/app/cards/bulk-selection.service'; import {BulkSelectionService} from 'src/app/cards/bulk-selection.service';
import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service'; import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
import {UtilityService} from 'src/app/shared/_services/utility.service'; import {UtilityService} from 'src/app/shared/_services/utility.service';
import {JumpKey} from 'src/app/_models/jumpbar/jump-key'; import {JumpKey} from 'src/app/_models/jumpbar/jump-key';
import {Pagination} from 'src/app/_models/pagination'; import {Pagination} from 'src/app/_models/pagination';
import {Series} from 'src/app/_models/series'; import {Series} from 'src/app/_models/series';
import {FilterEvent} from 'src/app/_models/metadata/series-filter'; import {FilterEvent, SortField} from 'src/app/_models/metadata/series-filter';
import {Action, ActionItem} from 'src/app/_services/action-factory.service'; import {Action, ActionItem} from 'src/app/_services/action-factory.service';
import {ActionService} from 'src/app/_services/action.service'; import {ActionService} from 'src/app/_services/action.service';
import {JumpbarService} from 'src/app/_services/jumpbar.service'; import {JumpbarService} from 'src/app/_services/jumpbar.service';
@ -38,6 +37,7 @@ import {BrowseTitlePipe} from "../../../_pipes/browse-title.pipe";
import {MetadataService} from "../../../_services/metadata.service"; import {MetadataService} from "../../../_services/metadata.service";
import {Observable} from "rxjs"; import {Observable} from "rxjs";
import {FilterField} from "../../../_models/metadata/v2/filter-field"; import {FilterField} from "../../../_models/metadata/v2/filter-field";
import {SeriesFilterSettings} from "../../../metadata-filter/filter-settings";
@Component({ @Component({
@ -68,8 +68,8 @@ export class AllSeriesComponent implements OnInit {
series: Series[] = []; series: Series[] = [];
loadingSeries = false; loadingSeries = false;
pagination: Pagination = new Pagination(); pagination: Pagination = new Pagination();
filter: FilterV2<FilterField> | undefined = undefined; filter: FilterV2<FilterField, SortField> | undefined = undefined;
filterSettings: FilterSettings<FilterField> = new FilterSettings(); filterSettings: SeriesFilterSettings = new SeriesFilterSettings();
filterOpen: EventEmitter<boolean> = new EventEmitter(); filterOpen: EventEmitter<boolean> = new EventEmitter();
filterActiveCheck!: FilterV2<FilterField>; filterActiveCheck!: FilterV2<FilterField>;
filterActive: boolean = false; filterActive: boolean = false;

View file

@ -3,7 +3,6 @@ import {ActivatedRoute, Router} from '@angular/router';
import {ToastrService} from 'ngx-toastr'; import {ToastrService} from 'ngx-toastr';
import {take} from 'rxjs'; import {take} from 'rxjs';
import {BulkSelectionService} from 'src/app/cards/bulk-selection.service'; import {BulkSelectionService} from 'src/app/cards/bulk-selection.service';
import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
import {ConfirmService} from 'src/app/shared/confirm.service'; import {ConfirmService} from 'src/app/shared/confirm.service';
import {DownloadService} from 'src/app/shared/_services/download.service'; import {DownloadService} from 'src/app/shared/_services/download.service';
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service'; import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
@ -28,6 +27,7 @@ import {FilterV2} from "../../../_models/metadata/v2/filter-v2";
import {Title} from "@angular/platform-browser"; import {Title} from "@angular/platform-browser";
import {WikiLink} from "../../../_models/wiki"; import {WikiLink} from "../../../_models/wiki";
import {FilterField} from "../../../_models/metadata/v2/filter-field"; import {FilterField} from "../../../_models/metadata/v2/filter-field";
import {SeriesFilterSettings} from "../../../metadata-filter/filter-settings";
@Component({ @Component({
selector: 'app-bookmarks', selector: 'app-bookmarks',
@ -65,7 +65,7 @@ export class BookmarksComponent implements OnInit {
pagination: Pagination = new Pagination(); pagination: Pagination = new Pagination();
filter: FilterV2<FilterField> | undefined = undefined; filter: FilterV2<FilterField> | undefined = undefined;
filterSettings: FilterSettings<FilterField> = new FilterSettings(); filterSettings: SeriesFilterSettings = new SeriesFilterSettings();
filterOpen: EventEmitter<boolean> = new EventEmitter(); filterOpen: EventEmitter<boolean> = new EventEmitter();
filterActive: boolean = false; filterActive: boolean = false;
filterActiveCheck!: FilterV2<FilterField>; filterActiveCheck!: FilterV2<FilterField>;

View file

@ -32,10 +32,10 @@ import {debounceTime, tap} from "rxjs/operators";
import {SortButtonComponent} from "../../_single-module/sort-button/sort-button.component"; import {SortButtonComponent} from "../../_single-module/sort-button/sort-button.component";
import {PersonSortField} from "../../_models/metadata/v2/person-sort-field"; import {PersonSortField} from "../../_models/metadata/v2/person-sort-field";
import {PersonSortOptions} from "../../_models/metadata/v2/sort-options"; import {PersonSortOptions} from "../../_models/metadata/v2/sort-options";
import {FilterSettings} from "../../metadata-filter/filter-settings";
import {PersonFilterField} from "../../_models/metadata/v2/person-filter-field"; import {PersonFilterField} from "../../_models/metadata/v2/person-filter-field";
import {FilterUtilitiesService} from "../../shared/_services/filter-utilities.service"; import {FilterUtilitiesService} from "../../shared/_services/filter-utilities.service";
import {FilterV2} from "../../_models/metadata/v2/filter-v2"; import {FilterV2} from "../../_models/metadata/v2/filter-v2";
import {PersonFilterSettings} from "../../metadata-filter/filter-settings";
@Component({ @Component({
@ -83,7 +83,7 @@ export class BrowseAuthorsComponent implements OnInit {
query: new FormControl('', []), query: new FormControl('', []),
}); });
isAscending: boolean = true; isAscending: boolean = true;
filterSettings: FilterSettings<PersonFilterField> = new FilterSettings<PersonFilterField>(); filterSettings: PersonFilterSettings = new PersonFilterSettings();
filterActive: boolean = false; filterActive: boolean = false;
filterOpen: EventEmitter<boolean> = new EventEmitter(); filterOpen: EventEmitter<boolean> = new EventEmitter();
filter: FilterV2<PersonFilterField> | undefined = undefined; filter: FilterV2<PersonFilterField> | undefined = undefined;

View file

@ -22,7 +22,6 @@ import {
} from '@angular/core'; } from '@angular/core';
import {NavigationStart, Router} from '@angular/router'; import {NavigationStart, Router} from '@angular/router';
import {VirtualScrollerComponent, VirtualScrollerModule} from '@iharbeck/ngx-virtual-scroller'; import {VirtualScrollerComponent, VirtualScrollerModule} from '@iharbeck/ngx-virtual-scroller';
import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service'; import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
import {Breakpoint, UtilityService} from 'src/app/shared/_services/utility.service'; import {Breakpoint, UtilityService} from 'src/app/shared/_services/utility.service';
import {JumpKey} from 'src/app/_models/jumpbar/jump-key'; import {JumpKey} from 'src/app/_models/jumpbar/jump-key';
@ -39,6 +38,7 @@ import {filter, map} from "rxjs/operators";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {tap} from "rxjs"; import {tap} from "rxjs";
import {FilterV2} from "../../_models/metadata/v2/filter-v2"; import {FilterV2} from "../../_models/metadata/v2/filter-v2";
import {FilterSettingsBase, SeriesFilterSettings} from "../../metadata-filter/filter-settings";
const ANIMATION_TIME_MS = 0; const ANIMATION_TIME_MS = 0;
@ -49,7 +49,7 @@ const ANIMATION_TIME_MS = 0;
* How to use: * How to use:
* - For filtering: * - For filtering:
* - pass a filterSettings which will bootstrap the filtering bar * - pass a filterSettings which will bootstrap the filtering bar
* - pass a jumpbar method binding to calc the count for the entity * - pass a jumpbar method binding to calc the count for the entity (not implemented yet)
* - For card layout * - For card layout
* - Pass an identity function for trackby * - Pass an identity function for trackby
* - Pass a pagination object for the total count * - Pass a pagination object for the total count
@ -84,7 +84,7 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
*/ */
@Input() parentScroll!: Element | Window; @Input() parentScroll!: Element | Window;
// Filter Code // We need to pass filterOpen from the grandfather to the metadata filter due to the filter button being in a separate component
@Input() filterOpen!: EventEmitter<boolean>; @Input() filterOpen!: EventEmitter<boolean>;
/** /**
* Should filtering be shown on the page * Should filtering be shown on the page
@ -98,7 +98,7 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
* A trackBy to help with rendering. This is required as without it there are issues when scrolling * A trackBy to help with rendering. This is required as without it there are issues when scrolling
*/ */
@Input({required: true}) trackByIdentity!: TrackByFunction<any>; @Input({required: true}) trackByIdentity!: TrackByFunction<any>;
@Input() filterSettings!: FilterSettings<number>; @Input() filterSettings!: FilterSettingsBase;
@Input() refresh!: EventEmitter<void>; @Input() refresh!: EventEmitter<void>;
/** /**
* Pass the filter object optionally. If not passed, will create a SeriesFilter by default * Pass the filter object optionally. If not passed, will create a SeriesFilter by default
@ -150,7 +150,7 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
// } // }
if (this.filterSettings === undefined) { if (this.filterSettings === undefined) {
this.filterSettings = new FilterSettings(); this.filterSettings = new SeriesFilterSettings();
this.cdRef.markForCheck(); this.cdRef.markForCheck();
} }

View file

@ -19,7 +19,6 @@ import {ToastrService} from 'ngx-toastr';
import {debounceTime, take} from 'rxjs/operators'; import {debounceTime, take} from 'rxjs/operators';
import {BulkSelectionService} from 'src/app/cards/bulk-selection.service'; import {BulkSelectionService} from 'src/app/cards/bulk-selection.service';
import {EditCollectionTagsComponent} from 'src/app/cards/_modals/edit-collection-tags/edit-collection-tags.component'; import {EditCollectionTagsComponent} from 'src/app/cards/_modals/edit-collection-tags/edit-collection-tags.component';
import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service'; import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
import {Breakpoint, UtilityService} from 'src/app/shared/_services/utility.service'; import {Breakpoint, UtilityService} from 'src/app/shared/_services/utility.service';
import {UserCollection} from 'src/app/_models/collection-tag'; import {UserCollection} from 'src/app/_models/collection-tag';
@ -63,6 +62,7 @@ import {DefaultModalOptions} from "../../../_models/default-modal-options";
import {ScrobbleProviderNamePipe} from "../../../_pipes/scrobble-provider-name.pipe"; import {ScrobbleProviderNamePipe} from "../../../_pipes/scrobble-provider-name.pipe";
import {PromotedIconComponent} from "../../../shared/_components/promoted-icon/promoted-icon.component"; import {PromotedIconComponent} from "../../../shared/_components/promoted-icon/promoted-icon.component";
import {FilterStatement} from "../../../_models/metadata/v2/filter-statement"; import {FilterStatement} from "../../../_models/metadata/v2/filter-statement";
import {SeriesFilterSettings} from "../../../metadata-filter/filter-settings";
@Component({ @Component({
selector: 'app-collection-detail', selector: 'app-collection-detail',
@ -111,7 +111,7 @@ export class CollectionDetailComponent implements OnInit, AfterContentChecked {
pagination: Pagination = new Pagination(); pagination: Pagination = new Pagination();
collectionTagActions: ActionItem<UserCollection>[] = []; collectionTagActions: ActionItem<UserCollection>[] = [];
filter: FilterV2<FilterField> | undefined = undefined; filter: FilterV2<FilterField> | undefined = undefined;
filterSettings: FilterSettings<FilterField> = new FilterSettings(); filterSettings: SeriesFilterSettings = new SeriesFilterSettings();
summary: string = ''; summary: string = '';
user!: User; user!: User;

View file

@ -25,7 +25,6 @@ import {EVENTS, MessageHubService} from '../_services/message-hub.service';
import {SeriesService} from '../_services/series.service'; import {SeriesService} from '../_services/series.service';
import {NavService} from '../_services/nav.service'; import {NavService} from '../_services/nav.service';
import {FilterUtilitiesService} from '../shared/_services/filter-utilities.service'; import {FilterUtilitiesService} from '../shared/_services/filter-utilities.service';
import {FilterSettings} from '../metadata-filter/filter-settings';
import {JumpKey} from '../_models/jumpbar/jump-key'; import {JumpKey} from '../_models/jumpbar/jump-key';
import {SeriesRemovedEvent} from '../_models/events/series-removed-event'; import {SeriesRemovedEvent} from '../_models/events/series-removed-event';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@ -43,6 +42,7 @@ import {FilterField} from "../_models/metadata/v2/filter-field";
import {CardActionablesComponent} from "../_single-module/card-actionables/card-actionables.component"; import {CardActionablesComponent} from "../_single-module/card-actionables/card-actionables.component";
import {LoadingComponent} from "../shared/loading/loading.component"; import {LoadingComponent} from "../shared/loading/loading.component";
import {debounceTime, ReplaySubject, tap} from "rxjs"; import {debounceTime, ReplaySubject, tap} from "rxjs";
import {SeriesFilterSettings} from "../metadata-filter/filter-settings";
@Component({ @Component({
selector: 'app-library-detail', selector: 'app-library-detail',
@ -76,7 +76,7 @@ export class LibraryDetailComponent implements OnInit {
pagination: Pagination = {currentPage: 0, totalPages: 0, totalItems: 0, itemsPerPage: 0}; pagination: Pagination = {currentPage: 0, totalPages: 0, totalItems: 0, itemsPerPage: 0};
actions: ActionItem<Library>[] = []; actions: ActionItem<Library>[] = [];
filter: FilterV2<FilterField> | undefined = undefined; filter: FilterV2<FilterField> | undefined = undefined;
filterSettings: FilterSettings<FilterField> = new FilterSettings(); filterSettings: SeriesFilterSettings = new SeriesFilterSettings();
filterOpen: EventEmitter<boolean> = new EventEmitter(); filterOpen: EventEmitter<boolean> = new EventEmitter();
filterActive: boolean = false; filterActive: boolean = false;
filterActiveCheck!: FilterV2<FilterField>; filterActiveCheck!: FilterV2<FilterField>;

View file

@ -18,7 +18,7 @@ import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from "@angular
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap"; import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {FilterCombination} from "../../../_models/metadata/v2/filter-combination"; import {FilterCombination} from "../../../_models/metadata/v2/filter-combination";
import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service"; import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service";
import {allFields} from "../../../_models/metadata/v2/filter-field"; import {allSeriesFilterFields} from "../../../_models/metadata/v2/filter-field";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {distinctUntilChanged, tap} from "rxjs/operators"; import {distinctUntilChanged, tap} from "rxjs/operators";
import {translate, TranslocoDirective} from "@jsverse/transloco"; import {translate, TranslocoDirective} from "@jsverse/transloco";
@ -43,7 +43,7 @@ export class MetadataBuilderComponent implements OnInit {
* The number of statements that can be. 0 means unlimited. -1 means none. * The number of statements that can be. 0 means unlimited. -1 means none.
*/ */
@Input() statementLimit = 0; @Input() statementLimit = 0;
@Input() availableFilterFields = allFields; @Input() availableFilterFields = allSeriesFilterFields;
@Output() update: EventEmitter<FilterV2<number>> = new EventEmitter<FilterV2<number>>(); @Output() update: EventEmitter<FilterV2<number>> = new EventEmitter<FilterV2<number>>();
@Output() apply: EventEmitter<void> = new EventEmitter<void>(); @Output() apply: EventEmitter<void> = new EventEmitter<void>();

View file

@ -14,7 +14,7 @@ import {FilterStatement} from '../../../_models/metadata/v2/filter-statement';
import {BehaviorSubject, distinctUntilChanged, filter, Observable, of, startWith, switchMap, tap} from 'rxjs'; import {BehaviorSubject, distinctUntilChanged, filter, Observable, of, startWith, switchMap, tap} from 'rxjs';
import {MetadataService} from 'src/app/_services/metadata.service'; import {MetadataService} from 'src/app/_services/metadata.service';
import {FilterComparison} from 'src/app/_models/metadata/v2/filter-comparison'; import {FilterComparison} from 'src/app/_models/metadata/v2/filter-comparison';
import {allFields, FilterField} from 'src/app/_models/metadata/v2/filter-field'; import {allSeriesFilterFields, FilterField} from 'src/app/_models/metadata/v2/filter-field';
import {AsyncPipe} from "@angular/common"; import {AsyncPipe} from "@angular/common";
import {FilterFieldPipe} from "../../../_pipes/filter-field.pipe"; import {FilterFieldPipe} from "../../../_pipes/filter-field.pipe";
import {FilterComparisonPipe} from "../../../_pipes/filter-comparison.pipe"; import {FilterComparisonPipe} from "../../../_pipes/filter-comparison.pipe";
@ -148,7 +148,7 @@ export class MetadataFilterRowComponent implements OnInit {
* Slightly misleading as this is the initial state and will be updated on the filterStatement event emitter * Slightly misleading as this is the initial state and will be updated on the filterStatement event emitter
*/ */
@Input() preset!: FilterStatement<number>; @Input() preset!: FilterStatement<number>;
@Input() availableFields: Array<FilterField> = allFields; @Input() availableFields: Array<FilterField> = allSeriesFilterFields;
@Output() filterStatement = new EventEmitter<FilterStatement<number>>(); @Output() filterStatement = new EventEmitter<FilterStatement<number>>();

View file

@ -1,11 +1,31 @@
import {FilterV2} from "../_models/metadata/v2/filter-v2"; import {FilterV2} from "../_models/metadata/v2/filter-v2";
import {SortField} from "../_models/metadata/series-filter";
import {PersonSortField} from "../_models/metadata/v2/person-sort-field";
import {PersonFilterField} from "../_models/metadata/v2/person-filter-field";
import {FilterField} from "../_models/metadata/v2/filter-field";
export class FilterSettings<T> { export class FilterSettingsBase<TFilter extends number = number, TSort extends number = number> {
presetsV2: FilterV2<T> | undefined; presetsV2: FilterV2<TFilter, TSort> | undefined;
sortDisabled = false; sortDisabled = false;
/** /**
* The number of statements that can be on the filter. Set to 1 to disable adding more. * The number of statements that can be on the filter. Set to 1 to disable adding more.
*/ */
statementLimit: number = 0; statementLimit: number = 0;
saveDisabled: boolean = false; saveDisabled: boolean = false;
} }
/**
* Filter Settings for Series entity
*/
export class SeriesFilterSettings extends FilterSettingsBase<FilterField, SortField> {
type = 'sortField';
}
/**
* Filter Settings for People entity
*/
export class PersonFilterSettings extends FilterSettingsBase<PersonFilterField, PersonSortField> {
type = 'personSortField';
}

View file

@ -2,10 +2,12 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
computed,
ContentChild, ContentChild,
DestroyRef, DestroyRef,
EventEmitter, EventEmitter,
inject, inject,
input,
Input, Input,
OnInit, OnInit,
Output Output
@ -14,9 +16,8 @@ import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from '@angular
import {NgbCollapse} from '@ng-bootstrap/ng-bootstrap'; import {NgbCollapse} from '@ng-bootstrap/ng-bootstrap';
import {Breakpoint, UtilityService} from '../shared/_services/utility.service'; import {Breakpoint, UtilityService} from '../shared/_services/utility.service';
import {Library} from '../_models/library/library'; import {Library} from '../_models/library/library';
import {allSortFields, FilterEvent, FilterItem, SortField} from '../_models/metadata/series-filter'; import {allSeriesSortFields, FilterEvent, FilterItem} from '../_models/metadata/series-filter';
import {ToggleService} from '../_services/toggle.service'; import {ToggleService} from '../_services/toggle.service';
import {FilterSettings} from './filter-settings';
import {FilterV2} from '../_models/metadata/v2/filter-v2'; import {FilterV2} from '../_models/metadata/v2/filter-v2';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {DrawerComponent} from '../shared/drawer/drawer.component'; import {DrawerComponent} from '../shared/drawer/drawer.component';
@ -24,37 +25,42 @@ import {AsyncPipe, NgClass, NgTemplateOutlet} from '@angular/common';
import {translate, TranslocoModule, TranslocoService} from "@jsverse/transloco"; import {translate, TranslocoModule, TranslocoService} from "@jsverse/transloco";
import {SortFieldPipe} from "../_pipes/sort-field.pipe"; import {SortFieldPipe} from "../_pipes/sort-field.pipe";
import {MetadataBuilderComponent} from "./_components/metadata-builder/metadata-builder.component"; import {MetadataBuilderComponent} from "./_components/metadata-builder/metadata-builder.component";
import {allFields, FilterField} from "../_models/metadata/v2/filter-field"; import {allSeriesFilterFields, FilterField} from "../_models/metadata/v2/filter-field";
import {FilterService} from "../_services/filter.service"; import {FilterService} from "../_services/filter.service";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
import {animate, style, transition, trigger} from "@angular/animations"; import {animate, style, transition, trigger} from "@angular/animations";
import {SortButtonComponent} from "../_single-module/sort-button/sort-button.component"; import {SortButtonComponent} from "../_single-module/sort-button/sort-button.component";
import {FilterUtilitiesService} from "../shared/_services/filter-utilities.service";
import {FilterSettingsBase} from "./filter-settings";
import {allPersonSortFields} from "../_models/metadata/v2/person-sort-field";
import {allPersonFilterFields} from "../_models/metadata/v2/person-filter-field";
@Component({ @Component({
selector: 'app-metadata-filter', selector: 'app-metadata-filter',
templateUrl: './metadata-filter.component.html', templateUrl: './metadata-filter.component.html',
styleUrls: ['./metadata-filter.component.scss'], styleUrls: ['./metadata-filter.component.scss'],
animations: [ animations: [
trigger('inOutAnimation', [ trigger('inOutAnimation', [
transition(':enter', [ transition(':enter', [
style({ height: 0, opacity: 0 }), style({ height: 0, opacity: 0 }),
animate('.5s ease-out', style({ height: 300, opacity: 1 })) animate('.5s ease-out', style({ height: 300, opacity: 1 }))
]), ]),
transition(':leave', [ transition(':leave', [
style({ height: 300, opacity: 1 }), style({ height: 300, opacity: 1 }),
animate('.5s ease-in', style({ height: 0, opacity: 0 })) animate('.5s ease-in', style({ height: 0, opacity: 0 }))
]) ])
]), ]),
], ],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgTemplateOutlet, DrawerComponent, imports: [NgTemplateOutlet, DrawerComponent,
ReactiveFormsModule, FormsModule, AsyncPipe, TranslocoModule, ReactiveFormsModule, FormsModule, AsyncPipe, TranslocoModule,
MetadataBuilderComponent, NgClass, SortButtonComponent] MetadataBuilderComponent, NgClass, SortButtonComponent]
}) })
export class MetadataFilterComponent implements OnInit { export class MetadataFilterComponent<TFilter extends number = number, TSort extends number = number> implements OnInit {
protected readonly allFilterFields = allFields; protected readonly allFilterFields = allSeriesFilterFields;
private readonly destroyRef = inject(DestroyRef); private readonly destroyRef = inject(DestroyRef);
private readonly filterUtilityService = inject(FilterUtilitiesService);
public readonly utilityService = inject(UtilityService); public readonly utilityService = inject(UtilityService);
private readonly cdRef = inject(ChangeDetectorRef); private readonly cdRef = inject(ChangeDetectorRef);
private readonly toastr = inject(ToastrService); private readonly toastr = inject(ToastrService);
@ -63,7 +69,7 @@ export class MetadataFilterComponent implements OnInit {
protected readonly translocoService = inject(TranslocoService); protected readonly translocoService = inject(TranslocoService);
private readonly sortFieldPipe = new SortFieldPipe(this.translocoService); private readonly sortFieldPipe = new SortFieldPipe(this.translocoService);
protected readonly allSortFields = allSortFields.map(f => { protected readonly allSortFields = allSeriesSortFields.map(f => {
return {title: this.sortFieldPipe.transform(f), value: f}; return {title: this.sortFieldPipe.transform(f), value: f};
}).sort((a, b) => a.title.localeCompare(b.title)); }).sort((a, b) => a.title.localeCompare(b.title));
@ -76,7 +82,23 @@ export class MetadataFilterComponent implements OnInit {
* Should filtering be shown on the page * Should filtering be shown on the page
*/ */
@Input() filteringDisabled: boolean = false; @Input() filteringDisabled: boolean = false;
@Input({required: true}) filterSettings!: FilterSettings<FilterField>; //@Input({required: true}) filterSettings!: FilterSettings<T>;
@Input({required: true}) filterSettings!: FilterSettingsBase<TFilter, TSort>;
/**
* Entity type derives the Sort and Filter fields
*/
entityType = input<'series' | 'person'>('series');
sortFieldOptions = computed(() => {
if (this.entityType() === 'series') return allSeriesSortFields;
return allPersonSortFields;
});
filterFieldOptions = computed(() => {
if (this.entityType() === 'series') return allSeriesFilterFields;
return allPersonFilterFields;
});
@Output() applyFilter: EventEmitter<FilterEvent> = new EventEmitter(); @Output() applyFilter: EventEmitter<FilterEvent> = new EventEmitter();
@ContentChild('[ngbCollapse]') collapse!: NgbCollapse; @ContentChild('[ngbCollapse]') collapse!: NgbCollapse;
@ -100,7 +122,7 @@ export class MetadataFilterComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
if (this.filterSettings === undefined) { if (this.filterSettings === undefined) {
this.filterSettings = new FilterSettings(); this.filterSettings = new FilterSettingsBase<TFilter, TSort>();
this.cdRef.markForCheck(); this.cdRef.markForCheck();
} }
@ -159,8 +181,10 @@ export class MetadataFilterComponent implements OnInit {
this.filterV2 = this.deepClone(this.filterSettings.presetsV2); this.filterV2 = this.deepClone(this.filterSettings.presetsV2);
const defaultSortField = this.sortFieldOptions()[0];
this.sortGroup = new FormGroup({ this.sortGroup = new FormGroup({
sortField: new FormControl({value: this.filterV2?.sortOptions?.sortField || SortField.SortName, disabled: this.filterSettings.sortDisabled}, []), sortField: new FormControl({value: this.filterV2?.sortOptions?.sortField || defaultSortField, disabled: this.filterSettings.sortDisabled}, []),
limitTo: new FormControl(this.filterV2?.limitTo || 0, []), limitTo: new FormControl(this.filterV2?.limitTo || 0, []),
name: new FormControl(this.filterV2?.name || '', []) name: new FormControl(this.filterV2?.name || '', [])
}); });
@ -192,9 +216,11 @@ export class MetadataFilterComponent implements OnInit {
this.isAscendingSort = isAscending; this.isAscendingSort = isAscending;
if (this.filterV2?.sortOptions === null) { if (this.filterV2?.sortOptions === null) {
const defaultSortField = this.sortFieldOptions()[0];
this.filterV2.sortOptions = { this.filterV2.sortOptions = {
isAscending: this.isAscendingSort, isAscending: this.isAscendingSort,
sortField: SortField.SortName sortField: defaultSortField
} }
} }
@ -236,5 +262,6 @@ export class MetadataFilterComponent implements OnInit {
this.toggleService.set(!this.filteringCollapsed); this.toggleService.set(!this.filteringCollapsed);
} }
protected readonly Breakpoint = Breakpoint; protected readonly Breakpoint = Breakpoint;
} }

View file

@ -12,7 +12,6 @@ import {TextResonse} from "../../_types/text-response";
import {environment} from "../../../environments/environment"; import {environment} from "../../../environments/environment";
import {map, tap} from "rxjs/operators"; import {map, tap} from "rxjs/operators";
import {Observable, of, switchMap} from "rxjs"; import {Observable, of, switchMap} from "rxjs";
import {Location} from "@angular/common";
import {PersonFilterField} from "../../_models/metadata/v2/person-filter-field"; import {PersonFilterField} from "../../_models/metadata/v2/person-filter-field";
@ -21,12 +20,11 @@ import {PersonFilterField} from "../../_models/metadata/v2/person-filter-field";
}) })
export class FilterUtilitiesService { export class FilterUtilitiesService {
private readonly location = inject(Location);
private readonly router = inject(Router); private readonly router = inject(Router);
private readonly metadataService = inject(MetadataService); private readonly metadataService = inject(MetadataService);
private readonly http = inject(HttpClient); private readonly http = inject(HttpClient);
private apiUrl = environment.apiUrl; private readonly apiUrl = environment.apiUrl;
encodeFilter(filter: FilterV2<number> | undefined) { encodeFilter(filter: FilterV2<number> | undefined) {
return this.http.post<string>(this.apiUrl + 'filter/encode', filter, TextResonse); return this.http.post<string>(this.apiUrl + 'filter/encode', filter, TextResonse);

View file

@ -16,7 +16,6 @@ import {Title} from '@angular/platform-browser';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {debounceTime, take} from 'rxjs'; import {debounceTime, take} from 'rxjs';
import {BulkSelectionService} from 'src/app/cards/bulk-selection.service'; import {BulkSelectionService} from 'src/app/cards/bulk-selection.service';
import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service'; import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
import {UtilityService} from 'src/app/shared/_services/utility.service'; import {UtilityService} from 'src/app/shared/_services/utility.service';
import {SeriesRemovedEvent} from 'src/app/_models/events/series-removed-event'; import {SeriesRemovedEvent} from 'src/app/_models/events/series-removed-event';
@ -41,6 +40,7 @@ import {
import {translate, TranslocoDirective} from "@jsverse/transloco"; import {translate, TranslocoDirective} from "@jsverse/transloco";
import {FilterV2} from "../../../_models/metadata/v2/filter-v2"; import {FilterV2} from "../../../_models/metadata/v2/filter-v2";
import {FilterField} from "../../../_models/metadata/v2/filter-field"; import {FilterField} from "../../../_models/metadata/v2/filter-field";
import {SeriesFilterSettings} from "../../../metadata-filter/filter-settings";
@Component({ @Component({
@ -60,7 +60,7 @@ export class WantToReadComponent implements OnInit, AfterContentChecked {
series: Array<Series> = []; series: Array<Series> = [];
pagination: Pagination = new Pagination(); pagination: Pagination = new Pagination();
filter: FilterV2<FilterField> | undefined = undefined; filter: FilterV2<FilterField> | undefined = undefined;
filterSettings: FilterSettings<FilterField> = new FilterSettings(); filterSettings: SeriesFilterSettings = new SeriesFilterSettings();
refresh: EventEmitter<void> = new EventEmitter(); refresh: EventEmitter<void> = new EventEmitter();
filterActiveCheck!: FilterV2<FilterField>; filterActiveCheck!: FilterV2<FilterField>;