mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Metadata Tags (#947)
* Implemented the ability to click a metadata tag (in series detail) and load a pre-filtered view. Apply still needs to be implemented (preset load is out of sync with external filter) * Refactored people to properly use typeahead so duplicates don't happen and use an observable chain so we can update the screen correctly * Many refactoring to ensure that the timings for filtering always works
This commit is contained in:
parent
06be7de6b2
commit
80e9738f67
@ -22,7 +22,7 @@ namespace API.DTOs
|
||||
/// </summary>
|
||||
public ICollection<TagDto> Tags { get; set; }
|
||||
public ICollection<PersonDto> Writers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Artists { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> CoverArtists { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Publishers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Characters { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Pencillers { get; set; } = new List<PersonDto>();
|
||||
|
@ -452,6 +452,7 @@ public class SeriesRepository : ISeriesRepository
|
||||
allPeopleIds.AddRange(filter.Publisher);
|
||||
allPeopleIds.AddRange(filter.CoverArtist);
|
||||
allPeopleIds.AddRange(filter.Translators);
|
||||
//allPeopleIds.AddRange(filter.Artist);
|
||||
|
||||
hasPeopleFilter = allPeopleIds.Count > 0;
|
||||
hasGenresFilter = filter.Genres.Count > 0;
|
||||
|
@ -63,7 +63,7 @@ namespace API.Helpers
|
||||
.ForMember(dest => dest.Writers,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Writer)))
|
||||
.ForMember(dest => dest.Artists,
|
||||
.ForMember(dest => dest.CoverArtists,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.CoverArtist)))
|
||||
.ForMember(dest => dest.Characters,
|
||||
|
@ -12,7 +12,7 @@ export interface SeriesMetadata {
|
||||
tags: Array<Tag>;
|
||||
collectionTags: Array<CollectionTag>;
|
||||
writers: Array<Person>;
|
||||
artists: Array<Person>;
|
||||
coverArtists: Array<Person>;
|
||||
publishers: Array<Person>;
|
||||
characters: Array<Person>;
|
||||
pencillers: Array<Person>;
|
||||
|
@ -61,10 +61,10 @@
|
||||
<label for="libraries">Libraries</label>
|
||||
<app-typeahead (selectedData)="updateLibraryFilters($event)" [settings]="librarySettings" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -117,13 +117,13 @@
|
||||
<!-- The People row -->
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.CoverArtist)">
|
||||
<div class="form-group">
|
||||
<label for="cover-artist">(Cover) Artists</label>
|
||||
<label for="cover-artist">Cover Artists</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.CoverArtist)" [settings]="getPersonsSettings(PersonRole.CoverArtist)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -134,10 +134,10 @@
|
||||
<label for="writers">Writers</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Writer)" [settings]="getPersonsSettings(PersonRole.Writer)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -148,10 +148,10 @@
|
||||
<label for="publisher">Publisher</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Publisher)" [settings]="getPersonsSettings(PersonRole.Publisher)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -162,10 +162,10 @@
|
||||
<label for="penciller">Penciller</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Penciller)" [settings]="getPersonsSettings(PersonRole.Penciller)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -176,10 +176,10 @@
|
||||
<label for="letterer">Letterer</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Letterer)" [settings]="getPersonsSettings(PersonRole.Letterer)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -190,10 +190,10 @@
|
||||
<label for="inker">Inker</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Inker)" [settings]="getPersonsSettings(PersonRole.Inker)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -204,10 +204,10 @@
|
||||
<label for="editor">Editor</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Editor)" [settings]="getPersonsSettings(PersonRole.Editor)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -218,10 +218,10 @@
|
||||
<label for="colorist">Colorist</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Colorist)" [settings]="getPersonsSettings(PersonRole.Colorist)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -232,10 +232,10 @@
|
||||
<label for="character">Character</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Character)" [settings]="getPersonsSettings(PersonRole.Character)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -246,10 +246,10 @@
|
||||
<label for="translators">Translators</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Translator)" [settings]="getPersonsSettings(PersonRole.Translator)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
<ng-template #optionItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
{{item.name}}
|
||||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
@ -328,9 +328,9 @@
|
||||
<div class="form-group">
|
||||
<label for="sort-options">Sort By</label>
|
||||
<button class="btn btn-sm btn-secondary-outline" (click)="updateSortOrder()" style="height: 25px; padding-bottom: 0px;">
|
||||
<i class="fa fa-arrow-down" title="Ascending" *ngIf="isAscendingSort; else descSort"></i>
|
||||
<i class="fa fa-arrow-up" title="Ascending" *ngIf="isAscendingSort; else descSort"></i>
|
||||
<ng-template #descSort>
|
||||
<i class="fa fa-arrow-up" title="Descending"></i>
|
||||
<i class="fa fa-arrow-down" title="Descending"></i>
|
||||
</ng-template>
|
||||
</button>
|
||||
<select id="sort-options" class="form-control" formControlName="sortField" style="height: 38px;">
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, ContentChild, EventEmitter, Input, OnDestroy, OnInit, Output, TemplateRef } from '@angular/core';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { Observable, of, ReplaySubject, Subject } from 'rxjs';
|
||||
import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { TypeaheadSettings } from 'src/app/typeahead/typeahead-settings';
|
||||
@ -34,13 +34,16 @@ export class FilterSettings {
|
||||
peopleDisabled = false;
|
||||
readProgressDisabled = false;
|
||||
ratingDisabled = false;
|
||||
presetLibraryId = 0;
|
||||
presetCollectionId = 0;
|
||||
sortDisabled = false;
|
||||
ageRatingDisabled = false;
|
||||
tagsDisabled = false;
|
||||
languageDisabled = false;
|
||||
publicationStatusDisabled = false;
|
||||
presets: SeriesFilter | undefined;
|
||||
/**
|
||||
* Should the filter section be open by default
|
||||
*/
|
||||
openByDefault = false;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -65,17 +68,16 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
@Output() applyFilter: EventEmitter<SeriesFilter> = new EventEmitter();
|
||||
|
||||
@ContentChild('cardItem') itemTemplate!: TemplateRef<any>;
|
||||
|
||||
|
||||
formatSettings: TypeaheadSettings<FilterItem<MangaFormat>> = new TypeaheadSettings();
|
||||
librarySettings: TypeaheadSettings<FilterItem<Library>> = new TypeaheadSettings();
|
||||
genreSettings: TypeaheadSettings<FilterItem<Genre>> = new TypeaheadSettings();
|
||||
collectionSettings: TypeaheadSettings<FilterItem<CollectionTag>> = new TypeaheadSettings();
|
||||
ageRatingSettings: TypeaheadSettings<FilterItem<AgeRatingDto>> = new TypeaheadSettings();
|
||||
publicationStatusSettings: TypeaheadSettings<FilterItem<PublicationStatusDto>> = new TypeaheadSettings();
|
||||
tagsSettings: TypeaheadSettings<FilterItem<Tag>> = new TypeaheadSettings();
|
||||
languageSettings: TypeaheadSettings<FilterItem<Language>> = new TypeaheadSettings();
|
||||
peopleSettings: {[PersonRole: string]: TypeaheadSettings<FilterItem<Person>>} = {};
|
||||
librarySettings: TypeaheadSettings<Library> = new TypeaheadSettings();
|
||||
genreSettings: TypeaheadSettings<Genre> = new TypeaheadSettings();
|
||||
collectionSettings: TypeaheadSettings<CollectionTag> = new TypeaheadSettings();
|
||||
ageRatingSettings: TypeaheadSettings<AgeRatingDto> = new TypeaheadSettings();
|
||||
publicationStatusSettings: TypeaheadSettings<PublicationStatusDto> = new TypeaheadSettings();
|
||||
tagsSettings: TypeaheadSettings<Tag> = new TypeaheadSettings();
|
||||
languageSettings: TypeaheadSettings<Language> = new TypeaheadSettings();
|
||||
peopleSettings: {[PersonRole: string]: TypeaheadSettings<Person>} = {};
|
||||
resetTypeaheads: Subject<boolean> = new ReplaySubject(1);
|
||||
|
||||
/**
|
||||
@ -85,8 +87,6 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
|
||||
filter!: SeriesFilter;
|
||||
libraries: Array<FilterItem<Library>> = [];
|
||||
genres: Array<FilterItem<Genre>> = [];
|
||||
persons: Array<FilterItem<Person>> = [];
|
||||
|
||||
|
||||
readProgressGroup!: FormGroup;
|
||||
@ -152,24 +152,12 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.trackByIdentity = (index: number, item: any) => `${this.header}_${this.pagination?.currentPage}_${this.updateApplied}`;
|
||||
this.setupFormatTypeahead();
|
||||
|
||||
if (this.filterSettings === undefined) {
|
||||
this.filterSettings = new FilterSettings();
|
||||
}
|
||||
|
||||
this.libraryService.getLibrariesForMember().subscribe(libs => {
|
||||
this.libraries = libs.map(lib => {
|
||||
return {
|
||||
title: lib.name,
|
||||
value: lib,
|
||||
selected: true,
|
||||
}
|
||||
});
|
||||
this.setupTypeaheads();
|
||||
});
|
||||
|
||||
|
||||
this.setupTypeaheads();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -178,14 +166,25 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
setupTypeaheads() {
|
||||
this.setupLibraryTypeahead();
|
||||
this.setupCollectionTagTypeahead();
|
||||
this.setupPersonTypeahead();
|
||||
this.setupAgeRatingSettings();
|
||||
this.setupPublicationStatusSettings();
|
||||
this.setupTagSettings();
|
||||
this.setupLanguageSettings();
|
||||
this.setupGenreTypeahead();
|
||||
|
||||
this.setupFormatTypeahead();
|
||||
|
||||
forkJoin([
|
||||
this.setupLibraryTypeahead(),
|
||||
this.setupCollectionTagTypeahead(),
|
||||
this.setupAgeRatingSettings(),
|
||||
this.setupPublicationStatusSettings(),
|
||||
this.setupTagSettings(),
|
||||
this.setupLanguageSettings(),
|
||||
this.setupGenreTypeahead(),
|
||||
this.setupPersonTypeahead(),
|
||||
]).subscribe(results => {
|
||||
this.resetTypeaheads.next(true);
|
||||
if (this.filterSettings.openByDefault) {
|
||||
this.filteringCollapsed = false;
|
||||
}
|
||||
this.apply();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -200,6 +199,12 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
const f = filter.toLowerCase();
|
||||
return options.filter(m => m.title.toLowerCase() === f);
|
||||
}
|
||||
|
||||
if (this.filterSettings.presets?.formats && this.filterSettings.presets?.formats.length > 0) {
|
||||
this.formatSettings.savedData = mangaFormatFilters.filter(item => this.filterSettings.presets?.formats.includes(item.value));
|
||||
this.filter.formats = this.formatSettings.savedData.map(item => item.value);
|
||||
this.resetTypeaheads.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
setupLibraryTypeahead() {
|
||||
@ -209,18 +214,21 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
this.librarySettings.unique = true;
|
||||
this.librarySettings.addIfNonExisting = false;
|
||||
this.librarySettings.fetchFn = (filter: string) => {
|
||||
return of (this.libraries)
|
||||
return this.libraryService.getLibrariesForMember();
|
||||
};
|
||||
this.librarySettings.compareFn = (options: FilterItem<Library>[], filter: string) => {
|
||||
this.librarySettings.compareFn = (options: Library[], filter: string) => {
|
||||
const f = filter.toLowerCase();
|
||||
return options.filter(m => m.title.toLowerCase() === f);
|
||||
return options.filter(m => m.name.toLowerCase() === f);
|
||||
}
|
||||
|
||||
if (this.filterSettings.presetLibraryId > 0) {
|
||||
this.librarySettings.savedData = this.libraries.filter(item => item.value.id === this.filterSettings.presetLibraryId);
|
||||
this.filter.libraries = this.librarySettings.savedData.map(item => item.value.id);
|
||||
this.resetTypeaheads.next(true); // For some reason library just doesn't update properly with savedData
|
||||
if (this.filterSettings.presets?.libraries && this.filterSettings.presets?.libraries.length > 0) {
|
||||
return this.librarySettings.fetchFn('').pipe(map(libraries => {
|
||||
this.librarySettings.savedData = libraries.filter(item => this.filterSettings.presets?.libraries.includes(item.id));
|
||||
this.filter.libraries = this.librarySettings.savedData.map(item => item.id);
|
||||
return of(true);
|
||||
}));
|
||||
}
|
||||
return of(true);
|
||||
}
|
||||
|
||||
setupGenreTypeahead() {
|
||||
@ -230,20 +238,21 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
this.genreSettings.unique = true;
|
||||
this.genreSettings.addIfNonExisting = false;
|
||||
this.genreSettings.fetchFn = (filter: string) => {
|
||||
return this.metadataService.getAllGenres(this.filter.libraries).pipe(map(genres => {
|
||||
return genres.map(genre => {
|
||||
return {
|
||||
title: genre.title,
|
||||
value: genre,
|
||||
selected: false,
|
||||
}
|
||||
})
|
||||
}));
|
||||
return this.metadataService.getAllGenres(this.filter.libraries);
|
||||
};
|
||||
this.genreSettings.compareFn = (options: FilterItem<Genre>[], filter: string) => {
|
||||
this.genreSettings.compareFn = (options: Genre[], filter: string) => {
|
||||
const f = filter.toLowerCase();
|
||||
return options.filter(m => m.title.toLowerCase() === f);
|
||||
}
|
||||
|
||||
if (this.filterSettings.presets?.genres && this.filterSettings.presets?.genres.length > 0) {
|
||||
return this.genreSettings.fetchFn('').pipe(map(genres => {
|
||||
this.genreSettings.savedData = genres.filter(item => this.filterSettings.presets?.genres.includes(item.id));
|
||||
this.filter.genres = this.genreSettings.savedData.map(item => item.id);
|
||||
return of(true);
|
||||
}));
|
||||
}
|
||||
return of(true);
|
||||
}
|
||||
|
||||
setupAgeRatingSettings() {
|
||||
@ -253,20 +262,21 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
this.ageRatingSettings.unique = true;
|
||||
this.ageRatingSettings.addIfNonExisting = false;
|
||||
this.ageRatingSettings.fetchFn = (filter: string) => {
|
||||
return this.metadataService.getAllAgeRatings(this.filter.libraries).pipe(map(ratings => {
|
||||
return ratings.map(rating => {
|
||||
return {
|
||||
title: rating.title,
|
||||
value: rating,
|
||||
selected: false,
|
||||
}
|
||||
})
|
||||
}));
|
||||
return this.metadataService.getAllAgeRatings(this.filter.libraries);
|
||||
};
|
||||
this.ageRatingSettings.compareFn = (options: FilterItem<AgeRatingDto>[], filter: string) => {
|
||||
this.ageRatingSettings.compareFn = (options: AgeRatingDto[], filter: string) => {
|
||||
const f = filter.toLowerCase();
|
||||
return options.filter(m => m.title.toLowerCase() === f && this.utilityService.filter(m.title, filter));
|
||||
}
|
||||
|
||||
if (this.filterSettings.presets?.ageRating && this.filterSettings.presets?.ageRating.length > 0) {
|
||||
return this.ageRatingSettings.fetchFn('').pipe(map(rating => {
|
||||
this.ageRatingSettings.savedData = rating.filter(item => this.filterSettings.presets?.ageRating.includes(item.value));
|
||||
this.filter.ageRating = this.ageRatingSettings.savedData.map(item => item.value);
|
||||
return of(true);
|
||||
}));
|
||||
}
|
||||
return of(true);
|
||||
}
|
||||
|
||||
setupPublicationStatusSettings() {
|
||||
@ -276,20 +286,21 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
this.publicationStatusSettings.unique = true;
|
||||
this.publicationStatusSettings.addIfNonExisting = false;
|
||||
this.publicationStatusSettings.fetchFn = (filter: string) => {
|
||||
return this.metadataService.getAllPublicationStatus(this.filter.libraries).pipe(map(statuses => {
|
||||
return statuses.map(status => {
|
||||
return {
|
||||
title: status.title,
|
||||
value: status,
|
||||
selected: false,
|
||||
}
|
||||
})
|
||||
}));
|
||||
return this.metadataService.getAllPublicationStatus(this.filter.libraries);
|
||||
};
|
||||
this.publicationStatusSettings.compareFn = (options: FilterItem<PublicationStatusDto>[], filter: string) => {
|
||||
this.publicationStatusSettings.compareFn = (options: PublicationStatusDto[], filter: string) => {
|
||||
const f = filter.toLowerCase();
|
||||
return options.filter(m => m.title.toLowerCase() === f && this.utilityService.filter(m.title, filter));
|
||||
}
|
||||
|
||||
if (this.filterSettings.presets?.publicationStatus && this.filterSettings.presets?.publicationStatus.length > 0) {
|
||||
return this.publicationStatusSettings.fetchFn('').pipe(map(statuses => {
|
||||
this.publicationStatusSettings.savedData = statuses.filter(item => this.filterSettings.presets?.publicationStatus.includes(item.value));
|
||||
this.filter.publicationStatus = this.publicationStatusSettings.savedData.map(item => item.value);
|
||||
return of(true);
|
||||
}));
|
||||
}
|
||||
return of(true);
|
||||
}
|
||||
|
||||
setupTagSettings() {
|
||||
@ -299,20 +310,21 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
this.tagsSettings.unique = true;
|
||||
this.tagsSettings.addIfNonExisting = false;
|
||||
this.tagsSettings.fetchFn = (filter: string) => {
|
||||
return this.metadataService.getAllTags(this.filter.libraries).pipe(map(tags => {
|
||||
return tags.map(tag => {
|
||||
return {
|
||||
title: tag.title,
|
||||
value: tag,
|
||||
selected: false,
|
||||
}
|
||||
})
|
||||
}));
|
||||
return this.metadataService.getAllTags(this.filter.libraries);
|
||||
};
|
||||
this.tagsSettings.compareFn = (options: FilterItem<Tag>[], filter: string) => {
|
||||
this.tagsSettings.compareFn = (options: Tag[], filter: string) => {
|
||||
const f = filter.toLowerCase();
|
||||
return options.filter(m => m.title.toLowerCase() === f && this.utilityService.filter(m.title, filter));
|
||||
}
|
||||
|
||||
if (this.filterSettings.presets?.tags && this.filterSettings.presets?.tags.length > 0) {
|
||||
return this.tagsSettings.fetchFn('').pipe(map(tags => {
|
||||
this.tagsSettings.savedData = tags.filter(item => this.filterSettings.presets?.tags.includes(item.id));
|
||||
this.filter.tags = this.tagsSettings.savedData.map(item => item.id);
|
||||
return of(true);
|
||||
}));
|
||||
}
|
||||
return of(true);
|
||||
}
|
||||
|
||||
setupLanguageSettings() {
|
||||
@ -322,20 +334,21 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
this.languageSettings.unique = true;
|
||||
this.languageSettings.addIfNonExisting = false;
|
||||
this.languageSettings.fetchFn = (filter: string) => {
|
||||
return this.metadataService.getAllLanguages(this.filter.libraries).pipe(map(tags => {
|
||||
return tags.map(tag => {
|
||||
return {
|
||||
title: tag.title,
|
||||
value: tag,
|
||||
selected: false,
|
||||
}
|
||||
})
|
||||
}));
|
||||
return this.metadataService.getAllLanguages(this.filter.libraries);
|
||||
};
|
||||
this.languageSettings.compareFn = (options: FilterItem<Language>[], filter: string) => {
|
||||
this.languageSettings.compareFn = (options: Language[], filter: string) => {
|
||||
const f = filter.toLowerCase();
|
||||
return options.filter(m => m.title.toLowerCase() === f && this.utilityService.filter(m.title, filter));
|
||||
}
|
||||
|
||||
if (this.filterSettings.presets?.languages && this.filterSettings.presets?.languages.length > 0) {
|
||||
return this.languageSettings.fetchFn('').pipe(map(languages => {
|
||||
this.languageSettings.savedData = languages.filter(item => this.filterSettings.presets?.languages.includes(item.isoCode));
|
||||
this.filter.languages = this.languageSettings.savedData.map(item => item.isoCode);
|
||||
return of(true);
|
||||
}));
|
||||
}
|
||||
return of(true);
|
||||
}
|
||||
|
||||
setupCollectionTagTypeahead() {
|
||||
@ -345,128 +358,81 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
this.collectionSettings.unique = true;
|
||||
this.collectionSettings.addIfNonExisting = false;
|
||||
this.collectionSettings.fetchFn = (filter: string) => {
|
||||
return this.collectionTagService.allTags().pipe(map(tags => {
|
||||
return tags.map(lib => {
|
||||
return {
|
||||
title: lib.title,
|
||||
value: lib,
|
||||
selected: false,
|
||||
}
|
||||
});
|
||||
}));
|
||||
return this.collectionTagService.allTags();
|
||||
};
|
||||
this.collectionSettings.compareFn = (options: FilterItem<CollectionTag>[], filter: string) => {
|
||||
this.collectionSettings.compareFn = (options: CollectionTag[], filter: string) => {
|
||||
const f = filter.toLowerCase();
|
||||
return options.filter(m => m.title.toLowerCase() === f);
|
||||
}
|
||||
|
||||
if (this.filterSettings.presetCollectionId > 0) {
|
||||
this.collectionSettings.fetchFn('').subscribe(tags => {
|
||||
this.collectionSettings.savedData = tags.filter(item => item.value.id === this.filterSettings.presetCollectionId);
|
||||
this.filter.collectionTags = this.collectionSettings.savedData.map(item => item.value.id);
|
||||
this.resetTypeaheads.next(true);
|
||||
});
|
||||
if (this.filterSettings.presets?.collectionTags && this.filterSettings.presets?.collectionTags.length > 0) {
|
||||
return this.collectionSettings.fetchFn('').pipe(map(tags => {
|
||||
this.collectionSettings.savedData = tags.filter(item => this.filterSettings.presets?.collectionTags.includes(item.id));
|
||||
this.filter.collectionTags = this.collectionSettings.savedData.map(item => item.id);
|
||||
return of(true);
|
||||
}));
|
||||
}
|
||||
return of(true);
|
||||
}
|
||||
|
||||
applyPresets() {
|
||||
|
||||
// if (this.filterSettings.presetCollectionId > 0) {
|
||||
// this.collectionSettings.fetchFn('').subscribe(tags => {
|
||||
// this.collectionSettings.savedData = tags.filter(item => item.value.id === this.filterSettings.presetCollectionId);
|
||||
// this.filter.collectionTags = this.collectionSettings.savedData.map(item => item.value.id);
|
||||
// this.resetTypeaheads.next(true);
|
||||
// });
|
||||
// }
|
||||
updateFromPreset(id: string, peopleFilterField: Array<any>, presetField: Array<any> | undefined, role: PersonRole) {
|
||||
const personSettings = this.createBlankPersonSettings(id, role)
|
||||
if (presetField && presetField.length > 0) {
|
||||
const fetch = personSettings.fetchFn as ((filter: string) => Observable<Person[]>);
|
||||
return fetch('').pipe(map(people => {
|
||||
personSettings.savedData = people.filter(item => presetField.includes(item.id));
|
||||
peopleFilterField = personSettings.savedData.map(item => item.id);
|
||||
this.resetTypeaheads.next(true);
|
||||
this.peopleSettings[role] = personSettings;
|
||||
this.updatePersonFilters(personSettings.savedData as Person[], role);
|
||||
return true;
|
||||
}));
|
||||
} else {
|
||||
this.peopleSettings[role] = personSettings;
|
||||
return of(true);
|
||||
}
|
||||
}
|
||||
|
||||
setupPersonTypeahead() {
|
||||
this.peopleSettings = {};
|
||||
|
||||
var personSettings = this.createBlankPersonSettings('writers');
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(PersonRole.Writer, filter);
|
||||
};
|
||||
this.peopleSettings[PersonRole.Writer] = personSettings;
|
||||
|
||||
personSettings = this.createBlankPersonSettings('character');
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(PersonRole.Character, filter);
|
||||
};
|
||||
this.peopleSettings[PersonRole.Character] = personSettings;
|
||||
|
||||
personSettings = this.createBlankPersonSettings('colorist');
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(PersonRole.Colorist, filter);
|
||||
};
|
||||
this.peopleSettings[PersonRole.Colorist] = personSettings;
|
||||
|
||||
personSettings = this.createBlankPersonSettings('cover-artist');
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(PersonRole.CoverArtist, filter);
|
||||
};
|
||||
this.peopleSettings[PersonRole.CoverArtist] = personSettings;
|
||||
|
||||
personSettings = this.createBlankPersonSettings('editor');
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(PersonRole.Editor, filter);
|
||||
};
|
||||
this.peopleSettings[PersonRole.Editor] = personSettings;
|
||||
|
||||
personSettings = this.createBlankPersonSettings('inker');
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(PersonRole.Inker, filter);
|
||||
};
|
||||
this.peopleSettings[PersonRole.Inker] = personSettings;
|
||||
|
||||
personSettings = this.createBlankPersonSettings('letterer');
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(PersonRole.Letterer, filter);
|
||||
};
|
||||
this.peopleSettings[PersonRole.Letterer] = personSettings;
|
||||
|
||||
personSettings = this.createBlankPersonSettings('penciller');
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(PersonRole.Penciller, filter);
|
||||
};
|
||||
this.peopleSettings[PersonRole.Penciller] = personSettings;
|
||||
|
||||
personSettings = this.createBlankPersonSettings('publisher');
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(PersonRole.Publisher, filter);
|
||||
};
|
||||
this.peopleSettings[PersonRole.Publisher] = personSettings;
|
||||
|
||||
personSettings = this.createBlankPersonSettings('translators');
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(PersonRole.Translator, filter);
|
||||
};
|
||||
this.peopleSettings[PersonRole.Translator] = personSettings;
|
||||
}
|
||||
|
||||
fetchPeople(role: PersonRole, filter: string): Observable<FilterItem<Person>[]> {
|
||||
return this.metadataService.getAllPeople(this.filter.libraries).pipe(map(people => {
|
||||
return people.filter(p => p.role == role && this.utilityService.filter(p.name, filter)).map((p: Person) => {
|
||||
return {
|
||||
title: p.name,
|
||||
value: p,
|
||||
selected: false,
|
||||
}
|
||||
});
|
||||
return forkJoin([
|
||||
this.updateFromPreset('writers', this.filter.writers, this.filterSettings.presets?.writers, PersonRole.Writer),
|
||||
this.updateFromPreset('character', this.filter.character, this.filterSettings.presets?.character, PersonRole.Character),
|
||||
this.updateFromPreset('colorist', this.filter.colorist, this.filterSettings.presets?.colorist, PersonRole.Colorist),
|
||||
this.updateFromPreset('cover-artist', this.filter.coverArtist, this.filterSettings.presets?.coverArtist, PersonRole.CoverArtist),
|
||||
this.updateFromPreset('editor', this.filter.editor, this.filterSettings.presets?.editor, PersonRole.Editor),
|
||||
this.updateFromPreset('inker', this.filter.inker, this.filterSettings.presets?.inker, PersonRole.Inker),
|
||||
this.updateFromPreset('letterer', this.filter.letterer, this.filterSettings.presets?.letterer, PersonRole.Letterer),
|
||||
this.updateFromPreset('penciller', this.filter.penciller, this.filterSettings.presets?.penciller, PersonRole.Penciller),
|
||||
this.updateFromPreset('publisher', this.filter.publisher, this.filterSettings.presets?.publisher, PersonRole.Publisher),
|
||||
this.updateFromPreset('translators', this.filter.translators, this.filterSettings.presets?.translators, PersonRole.Translator)
|
||||
]).pipe(map(results => {
|
||||
this.resetTypeaheads.next(true);
|
||||
return of(true);
|
||||
}));
|
||||
}
|
||||
|
||||
createBlankPersonSettings(id: string) {
|
||||
var personSettings = new TypeaheadSettings<FilterItem<Person>>();
|
||||
fetchPeople(role: PersonRole, filter: string) {
|
||||
return this.metadataService.getAllPeople(this.filter.libraries).pipe(map(people => {
|
||||
return people.filter(p => p.role == role && this.utilityService.filter(p.name, filter));
|
||||
}));
|
||||
}
|
||||
|
||||
createBlankPersonSettings(id: string, role: PersonRole) {
|
||||
var personSettings = new TypeaheadSettings<Person>();
|
||||
personSettings.minCharacters = 0;
|
||||
personSettings.multiple = true;
|
||||
personSettings.unique = true;
|
||||
personSettings.addIfNonExisting = false;
|
||||
personSettings.id = id;
|
||||
personSettings.compareFn = (options: FilterItem<Person>[], filter: string) => {
|
||||
personSettings.compareFn = (options: Person[], filter: string) => {
|
||||
const f = filter.toLowerCase();
|
||||
return options.filter(m => m.title.toLowerCase() === f);
|
||||
return options.filter(m => m.name.toLowerCase() === f);
|
||||
}
|
||||
personSettings.fetchFn = (filter: string) => {
|
||||
return this.fetchPeople(role, filter);
|
||||
};
|
||||
return personSettings;
|
||||
}
|
||||
|
||||
@ -491,75 +457,75 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
|
||||
updateFormatFilters(formats: FilterItem<MangaFormat>[]) {
|
||||
this.filter.formats = formats.map(item => item.value) || [];
|
||||
updateFormatFilters(formats: MangaFormat[]) {
|
||||
this.filter.formats = formats.map(item => item) || [];
|
||||
}
|
||||
|
||||
updateLibraryFilters(libraries: FilterItem<Library>[]) {
|
||||
this.filter.libraries = libraries.map(item => item.value.id) || [];
|
||||
updateLibraryFilters(libraries: Library[]) {
|
||||
this.filter.libraries = libraries.map(item => item.id) || [];
|
||||
}
|
||||
|
||||
updateGenreFilters(genres: FilterItem<Genre>[]) {
|
||||
this.filter.genres = genres.map(item => item.value.id) || [];
|
||||
updateGenreFilters(genres: Genre[]) {
|
||||
this.filter.genres = genres.map(item => item.id) || [];
|
||||
}
|
||||
|
||||
updateTagFilters(tags: FilterItem<Tag>[]) {
|
||||
this.filter.tags = tags.map(item => item.value.id) || [];
|
||||
updateTagFilters(tags: Tag[]) {
|
||||
this.filter.tags = tags.map(item => item.id) || [];
|
||||
}
|
||||
|
||||
updatePersonFilters(persons: FilterItem<Person>[], role: PersonRole) {
|
||||
updatePersonFilters(persons: Person[], role: PersonRole) {
|
||||
switch (role) {
|
||||
case PersonRole.CoverArtist:
|
||||
this.filter.coverArtist = persons.map(p => p.value.id);
|
||||
this.filter.coverArtist = persons.map(p => p.id);
|
||||
break;
|
||||
case PersonRole.Character:
|
||||
this.filter.character = persons.map(p => p.value.id);
|
||||
this.filter.character = persons.map(p => p.id);
|
||||
break;
|
||||
case PersonRole.Colorist:
|
||||
this.filter.colorist = persons.map(p => p.value.id);
|
||||
this.filter.colorist = persons.map(p => p.id);
|
||||
break;
|
||||
case PersonRole.Editor:
|
||||
this.filter.editor = persons.map(p => p.value.id);
|
||||
this.filter.editor = persons.map(p => p.id);
|
||||
break;
|
||||
case PersonRole.Inker:
|
||||
this.filter.inker = persons.map(p => p.value.id);
|
||||
this.filter.inker = persons.map(p => p.id);
|
||||
break;
|
||||
case PersonRole.Letterer:
|
||||
this.filter.letterer = persons.map(p => p.value.id);
|
||||
this.filter.letterer = persons.map(p => p.id);
|
||||
break;
|
||||
case PersonRole.Penciller:
|
||||
this.filter.penciller = persons.map(p => p.value.id);
|
||||
this.filter.penciller = persons.map(p => p.id);
|
||||
break;
|
||||
case PersonRole.Publisher:
|
||||
this.filter.publisher = persons.map(p => p.value.id);
|
||||
this.filter.publisher = persons.map(p => p.id);
|
||||
break;
|
||||
case PersonRole.Writer:
|
||||
this.filter.writers = persons.map(p => p.value.id);
|
||||
this.filter.writers = persons.map(p => p.id);
|
||||
break;
|
||||
case PersonRole.Translator:
|
||||
this.filter.translators = persons.map(p => p.value.id);
|
||||
this.filter.translators = persons.map(p => p.id);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
updateCollectionFilters(tags: FilterItem<CollectionTag>[]) {
|
||||
this.filter.collectionTags = tags.map(item => item.value.id) || [];
|
||||
updateCollectionFilters(tags: CollectionTag[]) {
|
||||
this.filter.collectionTags = tags.map(item => item.id) || [];
|
||||
}
|
||||
|
||||
updateRating(rating: any) {
|
||||
this.filter.rating = rating;
|
||||
}
|
||||
|
||||
updateAgeRating(ratingDtos: FilterItem<AgeRatingDto>[]) {
|
||||
this.filter.ageRating = ratingDtos.map(item => item.value.value) || [];
|
||||
updateAgeRating(ratingDtos: AgeRatingDto[]) {
|
||||
this.filter.ageRating = ratingDtos.map(item => item.value) || [];
|
||||
}
|
||||
|
||||
updatePublicationStatus(dtos: FilterItem<PublicationStatusDto>[]) {
|
||||
this.filter.publicationStatus = dtos.map(item => item.value.value) || [];
|
||||
updatePublicationStatus(dtos: PublicationStatusDto[]) {
|
||||
this.filter.publicationStatus = dtos.map(item => item.value) || [];
|
||||
}
|
||||
|
||||
updateLanguageRating(languages: FilterItem<Language>[]) {
|
||||
this.filter.languages = languages.map(item => item.value.isoCode) || [];
|
||||
updateLanguageRating(languages: Language[]) {
|
||||
this.filter.languages = languages.map(item => item.isoCode) || [];
|
||||
}
|
||||
|
||||
updateReadStatus(status: string) {
|
||||
@ -595,12 +561,8 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
||||
this.readProgressGroup.get('inProgress')?.setValue(true);
|
||||
this.sortGroup.get('sortField')?.setValue(SortField.SortName);
|
||||
this.isAscendingSort = true;
|
||||
// Apply any presets
|
||||
// Apply any presets which will trigger the apply
|
||||
this.setupTypeaheads();
|
||||
this.resetTypeaheads.next(true);
|
||||
|
||||
this.applyFilter.emit(this.filter);
|
||||
this.updateApplied++;
|
||||
}
|
||||
|
||||
apply() {
|
||||
|
@ -8,7 +8,7 @@ import { debounceTime, take, takeUntil, takeWhile } from 'rxjs/operators';
|
||||
import { BulkSelectionService } from 'src/app/cards/bulk-selection.service';
|
||||
import { FilterSettings } from 'src/app/cards/card-detail-layout/card-detail-layout.component';
|
||||
import { EditCollectionTagsComponent } from 'src/app/cards/_modals/edit-collection-tags/edit-collection-tags.component';
|
||||
import { KEY_CODES } from 'src/app/shared/_services/utility.service';
|
||||
import { KEY_CODES, UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { CollectionTag } from 'src/app/_models/collection-tag';
|
||||
import { SeriesAddedToCollectionEvent } from 'src/app/_models/events/series-added-to-collection-event';
|
||||
import { Pagination } from 'src/app/_models/pagination';
|
||||
@ -82,7 +82,8 @@ export class CollectionDetailComponent implements OnInit, OnDestroy {
|
||||
constructor(public imageService: ImageService, private collectionService: CollectionTagService, private router: Router, private route: ActivatedRoute,
|
||||
private seriesService: SeriesService, private toastr: ToastrService, private actionFactoryService: ActionFactoryService,
|
||||
private modalService: NgbModal, private titleService: Title, private accountService: AccountService,
|
||||
public bulkSelectionService: BulkSelectionService, private actionService: ActionService, private messageHub: MessageHubService) {
|
||||
public bulkSelectionService: BulkSelectionService, private actionService: ActionService, private messageHub: MessageHubService,
|
||||
private utilityService: UtilityService) {
|
||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
@ -98,7 +99,8 @@ export class CollectionDetailComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
const tagId = parseInt(routeId, 10);
|
||||
|
||||
this.filterSettings.presetCollectionId = tagId;
|
||||
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.utilityService.filterPresetsFromUrl(this.route.snapshot, this.seriesService.createSeriesFilter());
|
||||
this.filterSettings.presets.collectionTags = [tagId];
|
||||
|
||||
this.updateTag(tagId);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { Subject } from 'rxjs';
|
||||
import { debounceTime, take, takeUntil, takeWhile } from 'rxjs/operators';
|
||||
import { BulkSelectionService } from '../cards/bulk-selection.service';
|
||||
import { FilterSettings } from '../cards/card-detail-layout/card-detail-layout.component';
|
||||
import { KEY_CODES } from '../shared/_services/utility.service';
|
||||
import { KEY_CODES, UtilityService } from '../shared/_services/utility.service';
|
||||
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
||||
import { Library } from '../_models/library';
|
||||
import { Pagination } from '../_models/pagination';
|
||||
@ -73,12 +73,14 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
||||
|
||||
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) {
|
||||
private actionService: ActionService, public bulkSelectionService: BulkSelectionService, private hubService: MessageHubService,
|
||||
private utilityService: UtilityService) {
|
||||
const routeId = this.route.snapshot.paramMap.get('id');
|
||||
if (routeId === null) {
|
||||
this.router.navigateByUrl('/libraries');
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
this.libraryId = parseInt(routeId, 10);
|
||||
this.libraryService.getLibraryNames().pipe(take(1)).subscribe(names => {
|
||||
@ -87,7 +89,9 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
this.actions = this.actionFactoryService.getLibraryActions(this.handleAction.bind(this));
|
||||
this.pagination = {currentPage: 0, itemsPerPage: 30, totalItems: 0, totalPages: 1};
|
||||
this.filterSettings.presetLibraryId = this.libraryId;
|
||||
|
||||
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.utilityService.filterPresetsFromUrl(this.route.snapshot, this.seriesService.createSeriesFilter());
|
||||
this.filterSettings.presets.libraries = [this.libraryId];
|
||||
|
||||
this.loadPage();
|
||||
}
|
||||
@ -136,6 +140,7 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
||||
|
||||
updateFilter(data: SeriesFilter) {
|
||||
this.filter = data;
|
||||
console.log('filter: ', this.filter);
|
||||
if (this.pagination !== undefined && this.pagination !== null) {
|
||||
this.pagination.currentPage = 1;
|
||||
this.onPageChange(this.pagination);
|
||||
@ -151,6 +156,7 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.loadingSeries = true;
|
||||
|
||||
// The filter is out of sync with the presets from typeaheads on first load but syncs afterwards
|
||||
if (this.filter == undefined) {
|
||||
this.filter = this.seriesService.createSeriesFilter();
|
||||
this.filter.libraries.push(this.libraryId);
|
||||
|
@ -4,14 +4,14 @@
|
||||
|
||||
<!-- This first row will have random information about the series-->
|
||||
<div class="row no-gutters mb-2">
|
||||
<app-tag-badge title="Age Rating" *ngIf="seriesMetadata.ageRating">{{metadataService.getAgeRating(this.seriesMetadata.ageRating) | async}}</app-tag-badge>
|
||||
<app-tag-badge title="Age Rating" *ngIf="seriesMetadata.ageRating" a11y-click="13,32" class="clickable" (click)="goTo('ageRating', seriesMetadata.ageRating)" [selectionMode]="TagBadgeCursor.Clickable">{{metadataService.getAgeRating(this.seriesMetadata.ageRating) | async}}</app-tag-badge>
|
||||
<ng-container *ngIf="series">
|
||||
<!-- Maybe we can put the library this resides in to make it easier to get back -->
|
||||
<!-- tooltip here explaining how this is year of first issue -->
|
||||
<app-tag-badge *ngIf="seriesMetadata.releaseYear > 0" title="Release date">{{seriesMetadata.releaseYear}}</app-tag-badge>
|
||||
<app-tag-badge *ngIf="seriesMetadata.language !== null && seriesMetadata.language !== ''" title="Language">{{seriesMetadata.language}}</app-tag-badge>
|
||||
<app-tag-badge title="Publication Status">{{seriesMetadata.publicationStatus | publicationStatus}}</app-tag-badge>
|
||||
<app-tag-badge [selectionMode]="TagBadgeCursor.NotAllowed">
|
||||
<app-tag-badge *ngIf="seriesMetadata.language !== null && seriesMetadata.language !== ''" title="Language" a11y-click="13,32" class="clickable" (click)="goTo('languages', seriesMetadata.language)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.language}}</app-tag-badge>
|
||||
<app-tag-badge title="Publication Status" a11y-click="13,32" class="clickable" (click)="goTo('publicationStatus', seriesMetadata.publicationStatus)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.publicationStatus | publicationStatus}}</app-tag-badge>
|
||||
<app-tag-badge a11y-click="13,32" class="clickable" (click)="goTo('format', series.format)" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
<app-series-format [format]="series.format">{{utilityService.mangaFormat(series.format)}}</app-series-format>
|
||||
</app-tag-badge>
|
||||
</ng-container>
|
||||
@ -24,7 +24,7 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.genres">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-tag-badge [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
||||
<app-tag-badge a11y-click="13,32" class="clickable" (click)="goTo('genres', item.id)" [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -36,21 +36,21 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.collectionTags">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-tag-badge a11y-click="13,32" class="clickable" routerLink="/collections/{{item.id}}" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
<app-tag-badge a11y-click="13,32" class="clickable" routerLink="/collections/{{item.id}}" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
{{item.title}}
|
||||
</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.writers && seriesMetadata.writers.length > 0">
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.writers && seriesMetadata.writers.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Authors</h5>
|
||||
<h5>Writers/Authors</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.writers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('writers', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -62,14 +62,14 @@
|
||||
</div>
|
||||
|
||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="isCollapsed" id="extended-series-metadata">
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.artists && seriesMetadata.artists.length > 0">
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.coverArtists && seriesMetadata.coverArtists.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Artists</h5>
|
||||
<h5>Cover Artists</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.artists">
|
||||
<app-badge-expander [items]="seriesMetadata.coverArtists">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('coverArtists', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -82,7 +82,7 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.characters">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('character', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -95,7 +95,7 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.colorists">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('colorist', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -108,7 +108,7 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.editors">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('editor', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -121,7 +121,7 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.inkers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('inker', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -134,7 +134,7 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.letterers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('letterer', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -146,7 +146,7 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.tags">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-tag-badge [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
||||
<app-tag-badge a11y-click="13,32" class="clickable" (click)="goTo('tags', item.id)" [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -158,7 +158,7 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.translators">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('translators', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -171,7 +171,7 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.pencillers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('penciller', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
@ -184,7 +184,7 @@
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.publishers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('publisher', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { TagBadgeCursor } from '../shared/tag-badge/tag-badge.component';
|
||||
import { UtilityService } from '../shared/_services/utility.service';
|
||||
import { MangaFormat } from '../_models/manga-format';
|
||||
@ -32,12 +33,12 @@ export class SeriesMetadataDetailComponent implements OnInit, OnChanges {
|
||||
return TagBadgeCursor;
|
||||
}
|
||||
|
||||
constructor(public utilityService: UtilityService, public metadataService: MetadataService) { }
|
||||
constructor(public utilityService: UtilityService, public metadataService: MetadataService, private router: Router) { }
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.hasExtendedProperites = this.seriesMetadata.colorists.length > 0 ||
|
||||
this.seriesMetadata.editors.length > 0 ||
|
||||
this.seriesMetadata.artists.length > 0 ||
|
||||
this.seriesMetadata.coverArtists.length > 0 ||
|
||||
this.seriesMetadata.inkers.length > 0 ||
|
||||
this.seriesMetadata.letterers.length > 0 ||
|
||||
this.seriesMetadata.pencillers.length > 0 ||
|
||||
@ -58,5 +59,11 @@ export class SeriesMetadataDetailComponent implements OnInit, OnChanges {
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
}
|
||||
|
||||
goTo(queryParamName: string, filter: any) {
|
||||
let params: any = {};
|
||||
params[queryParamName] = filter;
|
||||
params['page'] = 1;
|
||||
this.router.navigate(['library', this.series.libraryId], {queryParams: params});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { FilterSettings } from 'src/app/cards/card-detail-layout/card-detail-layout.component';
|
||||
import { Chapter } from 'src/app/_models/chapter';
|
||||
import { LibraryType } from 'src/app/_models/library';
|
||||
import { MangaFormat } from 'src/app/_models/manga-format';
|
||||
import { AgeRating } from 'src/app/_models/metadata/age-rating';
|
||||
import { Series } from 'src/app/_models/series';
|
||||
import { SeriesFilter } from 'src/app/_models/series-filter';
|
||||
import { Volume } from 'src/app/_models/volume';
|
||||
|
||||
export enum KEY_CODES {
|
||||
@ -94,6 +97,116 @@ export class UtilityService {
|
||||
return input.toUpperCase().replace(reg, '').includes(filter.toUpperCase().replace(reg, ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of a filterSettings that is populated with filter presets from URL
|
||||
* @param snapshot
|
||||
* @param blankFilter Filter to start with
|
||||
* @returns The Preset filter and if something was set within
|
||||
*/
|
||||
filterPresetsFromUrl(snapshot: ActivatedRouteSnapshot, blankFilter: SeriesFilter): [SeriesFilter, boolean] {
|
||||
const filter = Object.assign({}, blankFilter);
|
||||
let anyChanged = false;
|
||||
|
||||
const format = snapshot.queryParamMap.get('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');
|
||||
if (genres !== undefined && genres !== null) {
|
||||
filter.genres = [...filter.genres, ...genres.split(',').map(item => parseInt(item, 10))];
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
const ageRating = snapshot.queryParamMap.get('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');
|
||||
if (publicationStatus !== undefined && publicationStatus !== null) {
|
||||
filter.publicationStatus = [...filter.publicationStatus, ...publicationStatus.split(',').map(item => parseInt(item, 10))];
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
const tags = snapshot.queryParamMap.get('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');
|
||||
if (languages !== undefined && languages !== null) {
|
||||
filter.languages = [...filter.languages, ...languages.split(',')];
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
const writers = snapshot.queryParamMap.get('writers');
|
||||
if (writers !== undefined && writers !== null) {
|
||||
filter.writers = [...filter.writers, ...writers.split(',').map(item => parseInt(item, 10))];
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
const character = snapshot.queryParamMap.get('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');
|
||||
if (colorist !== undefined && colorist !== null) {
|
||||
filter.colorist = [...filter.colorist, ...colorist.split(',').map(item => parseInt(item, 10))];
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
const coverArtists = snapshot.queryParamMap.get('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');
|
||||
if (editor !== undefined && editor !== null) {
|
||||
filter.editor = [...filter.editor, ...editor.split(',').map(item => parseInt(item, 10))];
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
const inker = snapshot.queryParamMap.get('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');
|
||||
if (letterer !== undefined && letterer !== null) {
|
||||
filter.letterer = [...filter.letterer, ...letterer.split(',').map(item => parseInt(item, 10))];
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
const penciller = snapshot.queryParamMap.get('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');
|
||||
if (publisher !== undefined && publisher !== null) {
|
||||
filter.publisher = [...filter.publisher, ...publisher.split(',').map(item => parseInt(item, 10))];
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
const translators = snapshot.queryParamMap.get('translators');
|
||||
if (translators !== undefined && translators !== null) {
|
||||
filter.translators = [...filter.translators, ...translators.split(',').map(item => parseInt(item, 10))];
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
|
||||
return [filter, anyChanged];
|
||||
}
|
||||
|
||||
mangaFormat(format: MangaFormat): string {
|
||||
switch (format) {
|
||||
case MangaFormat.EPUB:
|
||||
|
@ -1,15 +1,4 @@
|
||||
<!-- <div class="badge">
|
||||
<div class="col-4 img">
|
||||
<i class="fa fa-user-circle" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<span style="font-size: 12px;">{{person.name}}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
<div class="tagbadge cursor">
|
||||
<div class="tagbadge cursor clickable" style="cursor: pointer;">
|
||||
<div class="media">
|
||||
<!-- <img src="..." class="align-self-center mr-3" alt="..."> -->
|
||||
<i class="fa fa-user-circle align-self-center mr-2" aria-hidden="true"></i>
|
||||
|
@ -9,6 +9,7 @@ import { Person } from '../../_models/person';
|
||||
export class PersonBadgeComponent implements OnInit {
|
||||
|
||||
@Input() person!: Person;
|
||||
|
||||
|
||||
constructor() { }
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user