mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Filtering Bugfixes (#1220)
* Cleaned up random strings and unified them in one place. * Implemented the ability to disable typeaheads * Refactored disable state to disable controls on filter * Fixed an overflow regression on title * Updated ComicInfo DTO which had some bad properties on it * Cleaned up some code around disabled typeaheads/filters * Fixed typeaheads causing resets to state and mucking up filter presets * Fixed state not refreshing between page loads * Fixed a bad parsing for My Charms Are Wasted on Kuroiwa Medaka - Ch. 37.5 - Volume Extras * Cleanup within the metadata filter to reuse logic and minimize extra loops. * Fixed a timing issue with typeahead and first load for people * Fixed a bug in Publication Status for a given library, which would fail due to not performing some of the query in memory. Removed a custom index on Series table that wasn't used and potentially caused constraint issues. * Added a wiki link for stats collections * Security bump * Fixed the regex
This commit is contained in:
parent
e3aa9abf55
commit
e630e0b2c9
@ -169,6 +169,7 @@ namespace API.Tests.Parser
|
|||||||
[InlineData("Love Hina - Volume 01 [Scans].pdf", "Love Hina")]
|
[InlineData("Love Hina - Volume 01 [Scans].pdf", "Love Hina")]
|
||||||
[InlineData("It's Witching Time! 001 (Digital) (Anonymous1234)", "It's Witching Time!")]
|
[InlineData("It's Witching Time! 001 (Digital) (Anonymous1234)", "It's Witching Time!")]
|
||||||
[InlineData("Zettai Karen Children v02 c003 - The Invisible Guardian (2) [JS Scans]", "Zettai Karen Children")]
|
[InlineData("Zettai Karen Children v02 c003 - The Invisible Guardian (2) [JS Scans]", "Zettai Karen Children")]
|
||||||
|
[InlineData("My Charms Are Wasted on Kuroiwa Medaka - Ch. 37.5 - Volume Extras", "My Charms Are Wasted on Kuroiwa Medaka")]
|
||||||
public void ParseSeriesTest(string filename, string expected)
|
public void ParseSeriesTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
||||||
|
@ -99,12 +99,12 @@ public class MetadataController : BaseApiController
|
|||||||
/// <param name="libraryIds">String separated libraryIds or null for all publication status</param>
|
/// <param name="libraryIds">String separated libraryIds or null for all publication status</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("publication-status")]
|
[HttpGet("publication-status")]
|
||||||
public async Task<ActionResult<IList<AgeRatingDto>>> GetAllPublicationStatus(string? libraryIds)
|
public ActionResult<IList<AgeRatingDto>> GetAllPublicationStatus(string? libraryIds)
|
||||||
{
|
{
|
||||||
var ids = libraryIds?.Split(",").Select(int.Parse).ToList();
|
var ids = libraryIds?.Split(",").Select(int.Parse).ToList();
|
||||||
if (ids != null && ids.Count > 0)
|
if (ids is {Count: > 0})
|
||||||
{
|
{
|
||||||
return Ok(await _unitOfWork.SeriesRepository.GetAllPublicationStatusesDtosForLibrariesAsync(ids));
|
return Ok(_unitOfWork.SeriesRepository.GetAllPublicationStatusesDtosForLibrariesAsync(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(Enum.GetValues<PublicationStatus>().Select(t => new PublicationStatusDto()
|
return Ok(Enum.GetValues<PublicationStatus>().Select(t => new PublicationStatusDto()
|
||||||
|
@ -47,11 +47,11 @@ namespace API.Data.Metadata
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float UserRating { get; set; }
|
public float UserRating { get; set; }
|
||||||
|
|
||||||
public string AlternateSeries { get; set; } = string.Empty;
|
|
||||||
public string StoryArc { get; set; } = string.Empty;
|
public string StoryArc { get; set; } = string.Empty;
|
||||||
public string SeriesGroup { get; set; } = string.Empty;
|
public string SeriesGroup { get; set; } = string.Empty;
|
||||||
public string AlternativeSeries { get; set; } = string.Empty;
|
public string AlternateNumber { get; set; } = string.Empty;
|
||||||
public string AlternativeNumber { get; set; } = string.Empty;
|
public int AlternateCount { get; set; } = 0;
|
||||||
|
public string AlternateSeries { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is Epub only: calibre:title_sort
|
/// This is Epub only: calibre:title_sort
|
||||||
|
1466
API/Data/Migrations/20220416211340_RemoveCustomIndex.Designer.cs
generated
Normal file
1466
API/Data/Migrations/20220416211340_RemoveCustomIndex.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
API/Data/Migrations/20220416211340_RemoveCustomIndex.cs
Normal file
25
API/Data/Migrations/20220416211340_RemoveCustomIndex.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class RemoveCustomIndex : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Series_Name_NormalizedName_LocalizedName_LibraryId_Format",
|
||||||
|
table: "Series");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Series_Name_NormalizedName_LocalizedName_LibraryId_Format",
|
||||||
|
table: "Series",
|
||||||
|
columns: new[] { "Name", "NormalizedName", "LocalizedName", "LibraryId", "Format" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -744,9 +744,6 @@ namespace API.Data.Migrations
|
|||||||
|
|
||||||
b.HasIndex("LibraryId");
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
b.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId", "Format")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("Series");
|
b.ToTable("Series");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ public interface ISeriesRepository
|
|||||||
Task<IList<SeriesMetadata>> GetSeriesMetadataForIdsAsync(IEnumerable<int> seriesIds);
|
Task<IList<SeriesMetadata>> GetSeriesMetadataForIdsAsync(IEnumerable<int> seriesIds);
|
||||||
Task<IList<AgeRatingDto>> GetAllAgeRatingsDtosForLibrariesAsync(List<int> libraryIds);
|
Task<IList<AgeRatingDto>> GetAllAgeRatingsDtosForLibrariesAsync(List<int> libraryIds);
|
||||||
Task<IList<LanguageDto>> GetAllLanguagesForLibrariesAsync(List<int> libraryIds);
|
Task<IList<LanguageDto>> GetAllLanguagesForLibrariesAsync(List<int> libraryIds);
|
||||||
Task<IList<PublicationStatusDto>> GetAllPublicationStatusesDtosForLibrariesAsync(List<int> libraryIds);
|
IEnumerable<PublicationStatusDto> GetAllPublicationStatusesDtosForLibrariesAsync(List<int> libraryIds);
|
||||||
Task<IEnumerable<GroupedSeriesDto>> GetRecentlyUpdatedSeries(int userId, int pageSize = 30);
|
Task<IEnumerable<GroupedSeriesDto>> GetRecentlyUpdatedSeries(int userId, int pageSize = 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -884,19 +884,19 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<PublicationStatusDto>> GetAllPublicationStatusesDtosForLibrariesAsync(List<int> libraryIds)
|
public IEnumerable<PublicationStatusDto> GetAllPublicationStatusesDtosForLibrariesAsync(List<int> libraryIds)
|
||||||
{
|
{
|
||||||
return await _context.Series
|
return _context.Series
|
||||||
.Where(s => libraryIds.Contains(s.LibraryId))
|
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||||
.Select(s => s.Metadata.PublicationStatus)
|
.Select(s => s.Metadata.PublicationStatus)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
|
.AsEnumerable()
|
||||||
.Select(s => new PublicationStatusDto()
|
.Select(s => new PublicationStatusDto()
|
||||||
{
|
{
|
||||||
Value = s,
|
Value = s,
|
||||||
Title = s.ToDescription()
|
Title = s.ToDescription()
|
||||||
})
|
})
|
||||||
.OrderBy(s => s.Title)
|
.OrderBy(s => s.Title);
|
||||||
.ToListAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,78 +3,75 @@ using System.Collections.Generic;
|
|||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Entities.Interfaces;
|
using API.Entities.Interfaces;
|
||||||
using API.Entities.Metadata;
|
using API.Entities.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
namespace API.Entities
|
namespace API.Entities;
|
||||||
|
|
||||||
|
public class Series : IEntityDate
|
||||||
{
|
{
|
||||||
[Index(nameof(Name), nameof(NormalizedName), nameof(LocalizedName), nameof(LibraryId), nameof(Format), IsUnique = true)]
|
public int Id { get; set; }
|
||||||
public class Series : IEntityDate
|
/// <summary>
|
||||||
{
|
/// The UI visible Name of the Series. This may or may not be the same as the OriginalName
|
||||||
public int Id { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
public string Name { get; set; }
|
||||||
/// The UI visible Name of the Series. This may or may not be the same as the OriginalName
|
/// <summary>
|
||||||
/// </summary>
|
/// Used internally for name matching. <see cref="Parser.Parser.Normalize"/>
|
||||||
public string Name { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
public string NormalizedName { get; set; }
|
||||||
/// Used internally for name matching. <see cref="Parser.Parser.Normalize"/>
|
/// <summary>
|
||||||
/// </summary>
|
/// The name used to sort the Series. By default, will be the same as Name.
|
||||||
public string NormalizedName { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
public string SortName { get; set; }
|
||||||
/// The name used to sort the Series. By default, will be the same as Name.
|
/// <summary>
|
||||||
/// </summary>
|
/// Name in original language (Japanese for Manga). By default, will be same as Name.
|
||||||
public string SortName { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
public string LocalizedName { get; set; }
|
||||||
/// Name in original language (Japanese for Manga). By default, will be same as Name.
|
/// <summary>
|
||||||
/// </summary>
|
/// Original Name on disk. Not exposed to UI.
|
||||||
public string LocalizedName { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
public string OriginalName { get; set; }
|
||||||
/// Original Name on disk. Not exposed to UI.
|
/// <summary>
|
||||||
/// </summary>
|
/// Time of creation
|
||||||
public string OriginalName { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
public DateTime Created { get; set; }
|
||||||
/// Time of creation
|
/// <summary>
|
||||||
/// </summary>
|
/// Whenever a modification occurs. Ie) New volumes, removed volumes, title update, etc
|
||||||
public DateTime Created { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
public DateTime LastModified { get; set; }
|
||||||
/// Whenever a modification occurs. Ie) New volumes, removed volumes, title update, etc
|
/// <summary>
|
||||||
/// </summary>
|
/// Absolute path to the (managed) image file
|
||||||
public DateTime LastModified { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
/// <remarks>The file is managed internally to Kavita's APPDIR</remarks>
|
||||||
/// Absolute path to the (managed) image file
|
public string CoverImage { get; set; }
|
||||||
/// </summary>
|
/// <summary>
|
||||||
/// <remarks>The file is managed internally to Kavita's APPDIR</remarks>
|
/// Denotes if the CoverImage has been overridden by the user. If so, it will not be updated during normal scan operations.
|
||||||
public string CoverImage { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
public bool CoverImageLocked { get; set; }
|
||||||
/// Denotes if the CoverImage has been overridden by the user. If so, it will not be updated during normal scan operations.
|
/// <summary>
|
||||||
/// </summary>
|
/// Sum of all Volume page counts
|
||||||
public bool CoverImageLocked { get; set; }
|
/// </summary>
|
||||||
/// <summary>
|
public int Pages { get; set; }
|
||||||
/// Sum of all Volume page counts
|
|
||||||
/// </summary>
|
|
||||||
public int Pages { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of all the files attached to this series
|
/// The type of all the files attached to this series
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MangaFormat Format { get; set; } = MangaFormat.Unknown;
|
public MangaFormat Format { get; set; } = MangaFormat.Unknown;
|
||||||
|
|
||||||
public bool NameLocked { get; set; }
|
public bool NameLocked { get; set; }
|
||||||
public bool SortNameLocked { get; set; }
|
public bool SortNameLocked { get; set; }
|
||||||
public bool LocalizedNameLocked { get; set; }
|
public bool LocalizedNameLocked { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When a Chapter was last added onto the Series
|
/// When a Chapter was last added onto the Series
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime LastChapterAdded { get; set; }
|
public DateTime LastChapterAdded { get; set; }
|
||||||
|
|
||||||
public SeriesMetadata Metadata { get; set; }
|
public SeriesMetadata Metadata { get; set; }
|
||||||
public ICollection<AppUserRating> Ratings { get; set; } = new List<AppUserRating>();
|
public ICollection<AppUserRating> Ratings { get; set; } = new List<AppUserRating>();
|
||||||
public ICollection<AppUserProgress> Progress { get; set; } = new List<AppUserProgress>();
|
public ICollection<AppUserProgress> Progress { get; set; } = new List<AppUserProgress>();
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
public List<Volume> Volumes { get; set; }
|
public List<Volume> Volumes { get; set; }
|
||||||
public Library Library { get; set; }
|
public Library Library { get; set; }
|
||||||
public int LibraryId { get; set; }
|
public int LibraryId { get; set; }
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -132,9 +132,9 @@ namespace API.Parser
|
|||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)(?:, Chapter )(?<Chapter>\d+)",
|
@"(?<Series>.*)(?:, Chapter )(?<Chapter>\d+)",
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// Please Go Home, Akutsu-San! - Chapter 038.5 - Volume Announcement.cbz
|
// Please Go Home, Akutsu-San! - Chapter 038.5 - Volume Announcement.cbz, My Charms Are Wasted on Kuroiwa Medaka - Ch. 37.5 - Volume Extras
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)(\s|_|-)(?!Vol)(\s|_|-)(?:Chapter)(\s|_|-)(?<Chapter>\d+)",
|
@"(?<Series>.+?)(\s|_|-)(?!Vol)(\s|_|-)((?:Chapter)|(?:Ch\.))(\s|_|-)(?<Chapter>\d+)",
|
||||||
MatchOptions, RegexTimeout),
|
MatchOptions, RegexTimeout),
|
||||||
// [dmntsf.net] One Piece - Digital Colored Comics Vol. 20 Ch. 177 - 30 Million vs 81 Million.cbz
|
// [dmntsf.net] One Piece - Digital Colored Comics Vol. 20 Ch. 177 - 30 Million vs 81 Million.cbz
|
||||||
new Regex(
|
new Regex(
|
||||||
|
6
UI/Web/package-lock.json
generated
6
UI/Web/package-lock.json
generated
@ -3730,9 +3730,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"async": {
|
"async": {
|
||||||
"version": "2.6.3",
|
"version": "2.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"lodash": "^4.17.14"
|
"lodash": "^4.17.14"
|
||||||
|
@ -195,6 +195,7 @@ export class SeriesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createSeriesFilter(filter?: SeriesFilter) {
|
createSeriesFilter(filter?: SeriesFilter) {
|
||||||
|
if (filter !== undefined) return filter;
|
||||||
const data: SeriesFilter = {
|
const data: SeriesFilter = {
|
||||||
formats: [],
|
formats: [],
|
||||||
libraries: [],
|
libraries: [],
|
||||||
@ -225,8 +226,6 @@ export class SeriesService {
|
|||||||
seriesNameQuery: '',
|
seriesNameQuery: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (filter === undefined) return data;
|
return data;
|
||||||
|
|
||||||
return filter;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="stat-collection" class="form-label" aria-describedby="collection-info">Allow Anonymous Usage Collection</label>
|
<label for="stat-collection" class="form-label" aria-describedby="collection-info">Allow Anonymous Usage Collection</label>
|
||||||
<p class="accent" id="collection-info">Send anonymous usage and error information to Kavita's servers. This includes information on your browser, error reporting as well as OS and runtime version. We will use this information to prioritize features, bug fixes, and preformance tuning. Requires restart to take effect.</p>
|
<p class="accent" id="collection-info">Send anonymous usage and error information to Kavita's servers. This includes information on your browser, error reporting as well as OS and runtime version. We will use this information to prioritize features, bug fixes, and preformance tuning. Requires restart to take effect. See <a href="https://wiki.kavitareader.com/en/faq" target="_blank" referrerpolicy="no-refer">wiki</a> for what is collected.</p>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input id="stat-collection" type="checkbox" aria-label="Stat Collection" class="form-check-input" formControlName="allowStatCollection">
|
<input id="stat-collection" type="checkbox" aria-label="Stat Collection" class="form-check-input" formControlName="allowStatCollection">
|
||||||
<label for="stat-collection" class="form-check-label">Send Data</label>
|
<label for="stat-collection" class="form-check-label">Send Data</label>
|
||||||
|
@ -31,6 +31,7 @@ export class AllSeriesComponent implements OnInit, OnDestroy {
|
|||||||
onDestroy: Subject<void> = new Subject<void>();
|
onDestroy: Subject<void> = new Subject<void>();
|
||||||
filterSettings: FilterSettings = new FilterSettings();
|
filterSettings: FilterSettings = new FilterSettings();
|
||||||
filterOpen: EventEmitter<boolean> = new EventEmitter();
|
filterOpen: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
filterActiveCheck!: SeriesFilter;
|
||||||
filterActive: boolean = false;
|
filterActive: boolean = false;
|
||||||
|
|
||||||
bulkActionCallback = (action: Action, data: any) => {
|
bulkActionCallback = (action: Action, data: any) => {
|
||||||
@ -79,8 +80,9 @@ export class AllSeriesComponent implements OnInit, OnDestroy {
|
|||||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||||
this.titleService.setTitle('Kavita - All Series');
|
this.titleService.setTitle('Kavita - All Series');
|
||||||
|
|
||||||
this.pagination = this.filterUtilityService.pagination();
|
this.pagination = this.filterUtilityService.pagination(this.route.snapshot);
|
||||||
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl();
|
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
|
||||||
|
this.filterActiveCheck = this.seriesService.createSeriesFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -117,12 +119,7 @@ export class AllSeriesComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadPage() {
|
loadPage() {
|
||||||
// The filter is out of sync with the presets from typeaheads on first load but syncs afterwards
|
this.filterActive = !this.utilityService.deepEqual(this.filter, this.filterActiveCheck);
|
||||||
if (this.filter == undefined) {
|
|
||||||
this.filter = this.seriesService.createSeriesFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.filterActive = !this.utilityService.deepEqual(this.filter, this.filterSettings.presets);
|
|
||||||
this.seriesService.getAllSeries(this.pagination?.currentPage, this.pagination?.itemsPerPage, this.filter).pipe(take(1)).subscribe(series => {
|
this.seriesService.getAllSeries(this.pagination?.currentPage, this.pagination?.itemsPerPage, this.filter).pipe(take(1)).subscribe(series => {
|
||||||
this.series = series.result;
|
this.series = series.result;
|
||||||
this.pagination = series.pagination;
|
this.pagination = series.pagination;
|
||||||
|
@ -15,7 +15,6 @@ import { SeriesAddedToCollectionEvent } from 'src/app/_models/events/series-adde
|
|||||||
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, SeriesFilter } from 'src/app/_models/series-filter';
|
import { FilterEvent, SeriesFilter } from 'src/app/_models/series-filter';
|
||||||
import { AccountService } from 'src/app/_services/account.service';
|
|
||||||
import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service';
|
import { Action, ActionFactoryService, 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 { CollectionTagService } from 'src/app/_services/collection-tag.service';
|
import { CollectionTagService } from 'src/app/_services/collection-tag.service';
|
||||||
@ -41,6 +40,7 @@ export class CollectionDetailComponent implements OnInit, OnDestroy {
|
|||||||
summary: string = '';
|
summary: string = '';
|
||||||
|
|
||||||
actionInProgress: boolean = false;
|
actionInProgress: boolean = false;
|
||||||
|
filterActiveCheck!: SeriesFilter;
|
||||||
filterActive: boolean = false;
|
filterActive: boolean = false;
|
||||||
|
|
||||||
filterOpen: EventEmitter<boolean> = new EventEmitter();
|
filterOpen: EventEmitter<boolean> = new EventEmitter();
|
||||||
@ -98,9 +98,11 @@ export class CollectionDetailComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
const tagId = parseInt(routeId, 10);
|
const tagId = parseInt(routeId, 10);
|
||||||
|
|
||||||
this.seriesPagination = this.filterUtilityService.pagination();
|
this.seriesPagination = this.filterUtilityService.pagination(this.route.snapshot);
|
||||||
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl();
|
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
|
||||||
this.filterSettings.presets.collectionTags = [tagId];
|
this.filterSettings.presets.collectionTags = [tagId];
|
||||||
|
this.filterActiveCheck = this.seriesService.createSeriesFilter();
|
||||||
|
this.filterActiveCheck.collectionTags = [tagId];
|
||||||
|
|
||||||
this.updateTag(tagId);
|
this.updateTag(tagId);
|
||||||
}
|
}
|
||||||
@ -161,7 +163,7 @@ export class CollectionDetailComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadPage() {
|
loadPage() {
|
||||||
this.filterActive = !this.utilityService.deepEqual(this.filter, this.filterSettings.presets);
|
this.filterActive = !this.utilityService.deepEqual(this.filter, this.filterActiveCheck);
|
||||||
this.seriesService.getAllSeries(this.seriesPagination?.currentPage, this.seriesPagination?.itemsPerPage, this.filter).pipe(take(1)).subscribe(series => {
|
this.seriesService.getAllSeries(this.seriesPagination?.currentPage, this.seriesPagination?.itemsPerPage, this.filter).pipe(take(1)).subscribe(series => {
|
||||||
this.series = series.result;
|
this.series = series.result;
|
||||||
this.seriesPagination = series.pagination;
|
this.seriesPagination = series.pagination;
|
||||||
|
@ -37,6 +37,7 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
|||||||
filterSettings: FilterSettings = new FilterSettings();
|
filterSettings: FilterSettings = new FilterSettings();
|
||||||
filterOpen: EventEmitter<boolean> = new EventEmitter();
|
filterOpen: EventEmitter<boolean> = new EventEmitter();
|
||||||
filterActive: boolean = false;
|
filterActive: boolean = false;
|
||||||
|
filterActiveCheck!: SeriesFilter;
|
||||||
|
|
||||||
tabs: Array<{title: string, fragment: string}> = [
|
tabs: Array<{title: string, fragment: string}> = [
|
||||||
{title: 'Library', fragment: ''},
|
{title: 'Library', fragment: ''},
|
||||||
@ -101,9 +102,14 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
this.actions = this.actionFactoryService.getLibraryActions(this.handleAction.bind(this));
|
this.actions = this.actionFactoryService.getLibraryActions(this.handleAction.bind(this));
|
||||||
|
|
||||||
this.pagination = this.filterUtilityService.pagination();
|
this.pagination = this.filterUtilityService.pagination(this.route.snapshot);
|
||||||
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl();
|
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
|
||||||
this.filterSettings.presets.libraries = [this.libraryId];
|
if (this.filterSettings.presets) this.filterSettings.presets.libraries = [this.libraryId];
|
||||||
|
// Setup filterActiveCheck to check filter against
|
||||||
|
this.filterActiveCheck = this.seriesService.createSeriesFilter();
|
||||||
|
this.filterActiveCheck.libraries = [this.libraryId];
|
||||||
|
|
||||||
|
this.filterSettings.libraryDisabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -153,6 +159,7 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
updateFilter(data: FilterEvent) {
|
updateFilter(data: FilterEvent) {
|
||||||
this.filter = data.filter;
|
this.filter = data.filter;
|
||||||
|
|
||||||
if (!data.isFirst) this.filterUtilityService.updateUrlFromFilter(this.pagination, this.filter);
|
if (!data.isFirst) this.filterUtilityService.updateUrlFromFilter(this.pagination, this.filter);
|
||||||
this.loadPage();
|
this.loadPage();
|
||||||
}
|
}
|
||||||
@ -165,7 +172,7 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.loadingSeries = true;
|
this.loadingSeries = true;
|
||||||
this.filterActive = !this.utilityService.deepEqual(this.filter, this.filterSettings.presets);
|
this.filterActive = !this.utilityService.deepEqual(this.filter, this.filterActiveCheck);
|
||||||
this.seriesService.getSeriesForLibrary(0, this.pagination?.currentPage, this.pagination?.itemsPerPage, this.filter).pipe(take(1)).subscribe(series => {
|
this.seriesService.getSeriesForLibrary(0, this.pagination?.currentPage, this.pagination?.itemsPerPage, this.filter).pipe(take(1)).subscribe(series => {
|
||||||
this.series = series.result;
|
this.series = series.result;
|
||||||
this.pagination = series.pagination;
|
this.pagination = series.pagination;
|
||||||
|
@ -3,6 +3,7 @@ import { Title } from '@angular/platform-browser';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ReplaySubject, Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
import { debounceTime, filter, take, takeUntil } from 'rxjs/operators';
|
import { debounceTime, filter, take, takeUntil } from 'rxjs/operators';
|
||||||
|
import { FilterQueryParam } from '../shared/_services/filter-utilities.service';
|
||||||
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
||||||
import { SeriesRemovedEvent } from '../_models/events/series-removed-event';
|
import { SeriesRemovedEvent } from '../_models/events/series-removed-event';
|
||||||
import { Library } from '../_models/library';
|
import { Library } from '../_models/library';
|
||||||
@ -153,19 +154,19 @@ export class LibraryComponent implements OnInit, OnDestroy {
|
|||||||
handleSectionClick(sectionTitle: string) {
|
handleSectionClick(sectionTitle: string) {
|
||||||
if (sectionTitle.toLowerCase() === 'recently updated series') {
|
if (sectionTitle.toLowerCase() === 'recently updated series') {
|
||||||
const params: any = {};
|
const params: any = {};
|
||||||
params['sortBy'] = SortField.LastChapterAdded + ',false'; // sort by last chapter added, desc
|
params[FilterQueryParam.SortBy] = SortField.LastChapterAdded + ',false'; // sort by last chapter added, desc
|
||||||
params['page'] = 1;
|
params[FilterQueryParam.Page] = 1;
|
||||||
this.router.navigate(['all-series'], {queryParams: params});
|
this.router.navigate(['all-series'], {queryParams: params});
|
||||||
} else if (sectionTitle.toLowerCase() === 'on deck') {
|
} else if (sectionTitle.toLowerCase() === 'on deck') {
|
||||||
const params: any = {};
|
const params: any = {};
|
||||||
params['readStatus'] = 'true,false,false';
|
params[FilterQueryParam.ReadStatus] = 'true,false,false';
|
||||||
params['sortBy'] = SortField.LastChapterAdded + ',false'; // sort by last chapter added, desc
|
params[FilterQueryParam.SortBy] = SortField.LastChapterAdded + ',false'; // sort by last chapter added, desc
|
||||||
params['page'] = 1;
|
params[FilterQueryParam.Page] = 1;
|
||||||
this.router.navigate(['all-series'], {queryParams: params});
|
this.router.navigate(['all-series'], {queryParams: params});
|
||||||
}else if (sectionTitle.toLowerCase() === 'newly added series') {
|
}else if (sectionTitle.toLowerCase() === 'newly added series') {
|
||||||
const params: any = {};
|
const params: any = {};
|
||||||
params['sortBy'] = SortField.Created + ',false'; // sort by created, desc
|
params[FilterQueryParam.SortBy] = SortField.Created + ',false'; // sort by created, desc
|
||||||
params['page'] = 1;
|
params[FilterQueryParam.Page] = 1;
|
||||||
this.router.navigate(['all-series'], {queryParams: params});
|
this.router.navigate(['all-series'], {queryParams: params});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,11 @@ export class FilterSettings {
|
|||||||
tagsDisabled = false;
|
tagsDisabled = false;
|
||||||
languageDisabled = false;
|
languageDisabled = false;
|
||||||
publicationStatusDisabled = false;
|
publicationStatusDisabled = false;
|
||||||
|
searchNameDisabled = false;
|
||||||
presets: SeriesFilter | undefined;
|
presets: SeriesFilter | undefined;
|
||||||
/**
|
/**
|
||||||
* Should the filter section be open by default
|
* Should the filter section be open by default
|
||||||
|
* @deprecated This is deprecated UX pattern. New style is to show highlight on filter button.
|
||||||
*/
|
*/
|
||||||
openByDefault = false;
|
openByDefault = false;
|
||||||
}
|
}
|
@ -19,13 +19,13 @@
|
|||||||
|
|
||||||
<ng-template #filterSection>
|
<ng-template #filterSection>
|
||||||
<ng-template #globalFilterTooltip>This is library agnostic</ng-template>
|
<ng-template #globalFilterTooltip>This is library agnostic</ng-template>
|
||||||
<div class="filter-section mx-auto pb-3">
|
<div class="filter-section mx-auto pb-3" *ngIf="fullyLoaded">
|
||||||
<div class="row justify-content-center g-0">
|
<div class="row justify-content-center g-0">
|
||||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.formatDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="format" class="form-label">Format</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
<label for="format" class="form-label">Format</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
||||||
<span class="visually-hidden" id="filter-global-format-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
<span class="visually-hidden" id="filter-global-format-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
||||||
<app-typeahead (selectedData)="updateFormatFilters($event)" [settings]="formatSettings" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updateFormatFilters($event)" [settings]="formatSettings" [reset]="resetTypeaheads" [disabled]="filterSettings.formatDisabled">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.title}}
|
{{item.title}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -36,10 +36,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3"*ngIf="!filterSettings.libraryDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="libraries" class="form-label">Libraries</label>
|
<label for="libraries" class="form-label">Libraries</label>
|
||||||
<app-typeahead (selectedData)="updateLibraryFilters($event)" [settings]="librarySettings" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updateLibraryFilters($event)" [settings]="librarySettings" [reset]="resetTypeaheads" [disabled]="filterSettings.libraryDisabled">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -50,11 +50,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.collectionDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="collections" class="form-label">Collections</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
<label for="collections" class="form-label">Collections</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
||||||
<span class="visually-hidden" id="filter-global-collections-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
<span class="visually-hidden" id="filter-global-collections-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
||||||
<app-typeahead (selectedData)="updateCollectionFilters($event)" [settings]="collectionSettings" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updateCollectionFilters($event)" [settings]="collectionSettings" [reset]="resetTypeaheads" [disabled]="filterSettings.collectionDisabled">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.title}}
|
{{item.title}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -65,10 +65,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.genresDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="genres" class="form-label">Genres</label>
|
<label for="genres" class="form-label">Genres</label>
|
||||||
<app-typeahead (selectedData)="updateGenreFilters($event)" [settings]="genreSettings" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updateGenreFilters($event)" [settings]="genreSettings" [reset]="resetTypeaheads" [disabled]="filterSettings.genresDisabled">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.title}}
|
{{item.title}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -79,10 +79,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.tagsDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="tags" class="form-label">Tags</label>
|
<label for="tags" class="form-label">Tags</label>
|
||||||
<app-typeahead (selectedData)="updateTagFilters($event)" [settings]="tagsSettings" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updateTagFilters($event)" [settings]="tagsSettings" [reset]="resetTypeaheads" [disabled]="filterSettings.tagsDisabled">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.title}}
|
{{item.title}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -95,10 +95,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-center g-0">
|
<div class="row justify-content-center g-0">
|
||||||
<!-- The People row -->
|
<!-- The People row -->
|
||||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.CoverArtist)">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="cover-artist" class="form-label">Cover Artists</label>
|
<label for="cover-artist" class="form-label">Cover Artists</label>
|
||||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.CoverArtist)" [settings]="getPersonsSettings(PersonRole.CoverArtist)" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.CoverArtist)" [settings]="getPersonsSettings(PersonRole.CoverArtist)"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.CoverArtist)">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -109,10 +110,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Writer)">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="writers" class="form-label">Writers</label>
|
<label for="writers" class="form-label">Writers</label>
|
||||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Writer)" [settings]="getPersonsSettings(PersonRole.Writer)" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Writer)" [settings]="getPersonsSettings(PersonRole.Writer)"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Writer)">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -123,10 +125,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Publisher)">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="publisher" class="form-label">Publisher</label>
|
<label for="publisher" class="form-label">Publisher</label>
|
||||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Publisher)" [settings]="getPersonsSettings(PersonRole.Publisher)" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Publisher)" [settings]="getPersonsSettings(PersonRole.Publisher)"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Publisher)">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -137,10 +140,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Penciller)">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="penciller" class="form-label">Penciller</label>
|
<label for="penciller" class="form-label">Penciller</label>
|
||||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Penciller)" [settings]="getPersonsSettings(PersonRole.Penciller)" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Penciller)" [settings]="getPersonsSettings(PersonRole.Penciller)"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Penciller)">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -151,10 +155,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Letterer)">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="letterer" class="form-label">Letterer</label>
|
<label for="letterer" class="form-label">Letterer</label>
|
||||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Letterer)" [settings]="getPersonsSettings(PersonRole.Letterer)" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Letterer)" [settings]="getPersonsSettings(PersonRole.Letterer)"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Letterer)">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -165,10 +170,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Inker)">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="inker" class="form-label">Inker</label>
|
<label for="inker" class="form-label">Inker</label>
|
||||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Inker)" [settings]="getPersonsSettings(PersonRole.Inker)" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Inker)" [settings]="getPersonsSettings(PersonRole.Inker)"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Inker)">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -179,10 +185,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Editor)">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="editor" class="form-label">Editor</label>
|
<label for="editor" class="form-label">Editor</label>
|
||||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Editor)" [settings]="getPersonsSettings(PersonRole.Editor)" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Editor)" [settings]="getPersonsSettings(PersonRole.Editor)"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Editor)">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -193,10 +200,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Colorist)">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="colorist" class="form-label">Colorist</label>
|
<label for="colorist" class="form-label">Colorist</label>
|
||||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Colorist)" [settings]="getPersonsSettings(PersonRole.Colorist)" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Colorist)" [settings]="getPersonsSettings(PersonRole.Colorist)"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Colorist)">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -207,10 +215,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Character)">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="character" class="form-label">Character</label>
|
<label for="character" class="form-label">Character</label>
|
||||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Character)" [settings]="getPersonsSettings(PersonRole.Character)" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Character)" [settings]="getPersonsSettings(PersonRole.Character)"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Character)">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -221,10 +230,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Translator)">
|
<div class="col-md-2 me-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="translators" class="form-label">Translators</label>
|
<label for="translators" class="form-label">Translators</label>
|
||||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Translator)" [settings]="getPersonsSettings(PersonRole.Translator)" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Translator)" [settings]="getPersonsSettings(PersonRole.Translator)"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Translator)">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -236,7 +246,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-center g-0">
|
<div class="row justify-content-center g-0">
|
||||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.readProgressDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<label class="form-label">Read Progress</label>
|
<label class="form-label">Read Progress</label>
|
||||||
<form [formGroup]="readProgressGroup">
|
<form [formGroup]="readProgressGroup">
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
@ -254,7 +264,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.ratingDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<label for="ratings" class="form-label">Rating</label>
|
<label for="ratings" class="form-label">Rating</label>
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<ngb-rating class="rating-star" [(rate)]="filter.rating" (rateChange)="updateRating($event)" [resettable]="true">
|
<ngb-rating class="rating-star" [(rate)]="filter.rating" (rateChange)="updateRating($event)" [resettable]="true">
|
||||||
@ -265,9 +275,9 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.ageRatingDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<label for="age-rating" class="form-label">Age Rating</label>
|
<label for="age-rating" class="form-label">Age Rating</label>
|
||||||
<app-typeahead (selectedData)="updateAgeRating($event)" [settings]="ageRatingSettings" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updateAgeRating($event)" [settings]="ageRatingSettings" [reset]="resetTypeaheads" [disabled]="filterSettings.ageRatingDisabled">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.title}}
|
{{item.title}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -277,9 +287,10 @@
|
|||||||
</app-typeahead>
|
</app-typeahead>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.languageDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<label for="languages" class="form-label">Language</label>
|
<label for="languages" class="form-label">Language</label>
|
||||||
<app-typeahead (selectedData)="updateLanguageRating($event)" [settings]="languageSettings" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updateLanguages($event)" [settings]="languageSettings"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="filterSettings.languageDisabled">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.title}}
|
{{item.title}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -289,9 +300,10 @@
|
|||||||
</app-typeahead>
|
</app-typeahead>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.publicationStatusDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<label for="publication-status" class="form-label">Publication Status</label>
|
<label for="publication-status" class="form-label">Publication Status</label>
|
||||||
<app-typeahead (selectedData)="updatePublicationStatus($event)" [settings]="publicationStatusSettings" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updatePublicationStatus($event)" [settings]="publicationStatusSettings"
|
||||||
|
[reset]="resetTypeaheads" [disabled]="filterSettings.publicationStatusDisabled">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.title}}
|
{{item.title}}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -313,11 +325,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.sortDisabled">
|
<div class="col-md-2 me-3">
|
||||||
<form [formGroup]="sortGroup">
|
<form [formGroup]="sortGroup">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="sort-options" class="form-label">Sort By</label>
|
<label for="sort-options" class="form-label">Sort By</label>
|
||||||
<button class="btn btn-sm btn-secondary-outline" (click)="updateSortOrder()" style="height: 25px; padding-bottom: 0px;">
|
<button class="btn btn-sm btn-secondary-outline" (click)="updateSortOrder()" style="height: 25px; padding-bottom: 0px;" [disabled]="filterSettings.sortDisabled">
|
||||||
<i class="fa fa-arrow-up" title="Ascending" *ngIf="isAscendingSort; else descSort"></i>
|
<i class="fa fa-arrow-up" title="Ascending" *ngIf="isAscendingSort; else descSort"></i>
|
||||||
<ng-template #descSort>
|
<ng-template #descSort>
|
||||||
<i class="fa fa-arrow-down" title="Descending"></i>
|
<i class="fa fa-arrow-down" title="Descending"></i>
|
||||||
@ -332,7 +344,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 me-3" *ngIf="filterSettings.sortDisabled"></div>
|
|
||||||
<div class="col-md-2 me-3"></div>
|
<div class="col-md-2 me-3"></div>
|
||||||
<div class="col-md-2 me-3 mt-4">
|
<div class="col-md-2 me-3 mt-4">
|
||||||
<button class="btn btn-secondary col-12" (click)="clear()">Clear</button>
|
<button class="btn btn-secondary col-12" (click)="clear()">Clear</button>
|
||||||
|
@ -53,7 +53,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
tagsSettings: TypeaheadSettings<Tag> = new TypeaheadSettings();
|
tagsSettings: TypeaheadSettings<Tag> = new TypeaheadSettings();
|
||||||
languageSettings: TypeaheadSettings<Language> = new TypeaheadSettings();
|
languageSettings: TypeaheadSettings<Language> = new TypeaheadSettings();
|
||||||
peopleSettings: {[PersonRole: string]: TypeaheadSettings<Person>} = {};
|
peopleSettings: {[PersonRole: string]: TypeaheadSettings<Person>} = {};
|
||||||
resetTypeaheads: Subject<boolean> = new ReplaySubject(1);
|
resetTypeaheads: ReplaySubject<boolean> = new ReplaySubject(1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controls the visiblity of extended controls that sit below the main header.
|
* Controls the visiblity of extended controls that sit below the main header.
|
||||||
@ -71,6 +71,8 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
updateApplied: number = 0;
|
updateApplied: number = 0;
|
||||||
|
|
||||||
|
fullyLoaded: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
private onDestory: Subject<void> = new Subject();
|
private onDestory: Subject<void> = new Subject();
|
||||||
|
|
||||||
@ -84,20 +86,32 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
constructor(private libraryService: LibraryService, private metadataService: MetadataService, private seriesService: SeriesService,
|
constructor(private libraryService: LibraryService, private metadataService: MetadataService, private seriesService: SeriesService,
|
||||||
private utilityService: UtilityService, private collectionTagService: CollectionTagService) {
|
private utilityService: UtilityService, private collectionTagService: CollectionTagService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.filterSettings === undefined) {
|
||||||
|
this.filterSettings = new FilterSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filterOpen) {
|
||||||
|
this.filterOpen.pipe(takeUntil(this.onDestory)).subscribe(openState => {
|
||||||
|
this.filteringCollapsed = !openState;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.filter = this.seriesService.createSeriesFilter();
|
this.filter = this.seriesService.createSeriesFilter();
|
||||||
this.readProgressGroup = new FormGroup({
|
this.readProgressGroup = new FormGroup({
|
||||||
read: new FormControl(this.filter.readStatus.read, []),
|
read: new FormControl({value: this.filter.readStatus.read, disabled: this.filterSettings.readProgressDisabled}, []),
|
||||||
notRead: new FormControl(this.filter.readStatus.notRead, []),
|
notRead: new FormControl({value: this.filter.readStatus.notRead, disabled: this.filterSettings.readProgressDisabled}, []),
|
||||||
inProgress: new FormControl(this.filter.readStatus.inProgress, []),
|
inProgress: new FormControl({value: this.filter.readStatus.inProgress, disabled: this.filterSettings.readProgressDisabled}, []),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sortGroup = new FormGroup({
|
this.sortGroup = new FormGroup({
|
||||||
sortField: new FormControl(this.filter.sortOptions?.sortField || SortField.SortName, []),
|
sortField: new FormControl({value: this.filter.sortOptions?.sortField || SortField.SortName, disabled: this.filterSettings.sortDisabled}, []),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.seriesNameGroup = new FormGroup({
|
this.seriesNameGroup = new FormGroup({
|
||||||
seriesNameQuery: new FormControl(this.filter.seriesNameQuery || '', [])
|
seriesNameQuery: new FormControl({value: this.filter.seriesNameQuery || '', disabled: this.filterSettings.searchNameDisabled}, [])
|
||||||
});
|
});
|
||||||
|
|
||||||
this.readProgressGroup.valueChanges.pipe(takeUntil(this.onDestory)).subscribe(changes => {
|
this.readProgressGroup.valueChanges.pipe(takeUntil(this.onDestory)).subscribe(changes => {
|
||||||
@ -138,19 +152,21 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
.subscribe(changes => {
|
.subscribe(changes => {
|
||||||
this.filter.seriesNameQuery = changes;
|
this.filter.seriesNameQuery = changes;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.loadFromPresetsAndSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnDestroy() {
|
||||||
if (this.filterSettings === undefined) {
|
this.onDestory.next();
|
||||||
this.filterSettings = new FilterSettings();
|
this.onDestory.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.filterOpen) {
|
getPersonsSettings(role: PersonRole) {
|
||||||
this.filterOpen.pipe(takeUntil(this.onDestory)).subscribe(openState => {
|
return this.peopleSettings[role];
|
||||||
this.filteringCollapsed = !openState;
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
loadFromPresetsAndSetup() {
|
||||||
|
this.fullyLoaded = false;
|
||||||
if (this.filterSettings.presets) {
|
if (this.filterSettings.presets) {
|
||||||
this.readProgressGroup.get('read')?.patchValue(this.filterSettings.presets.readStatus.read);
|
this.readProgressGroup.get('read')?.patchValue(this.filterSettings.presets.readStatus.read);
|
||||||
this.readProgressGroup.get('notRead')?.patchValue(this.filterSettings.presets.readStatus.notRead);
|
this.readProgressGroup.get('notRead')?.patchValue(this.filterSettings.presets.readStatus.notRead);
|
||||||
@ -174,21 +190,6 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.setupTypeaheads();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.onDestory.next();
|
|
||||||
this.onDestory.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
getPersonsSettings(role: PersonRole) {
|
|
||||||
return this.peopleSettings[role];
|
|
||||||
}
|
|
||||||
|
|
||||||
setupTypeaheads() {
|
|
||||||
|
|
||||||
this.setupFormatTypeahead();
|
this.setupFormatTypeahead();
|
||||||
|
|
||||||
forkJoin([
|
forkJoin([
|
||||||
@ -201,7 +202,8 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
this.setupGenreTypeahead(),
|
this.setupGenreTypeahead(),
|
||||||
this.setupPersonTypeahead(),
|
this.setupPersonTypeahead(),
|
||||||
]).subscribe(results => {
|
]).subscribe(results => {
|
||||||
this.resetTypeaheads.next(true);
|
this.fullyLoaded = true;
|
||||||
|
this.resetTypeaheads.next(false); // Pass false to ensure we reset to the preset and not to an empty typeahead
|
||||||
if (this.filterSettings.openByDefault) {
|
if (this.filterSettings.openByDefault) {
|
||||||
this.filteringCollapsed = false;
|
this.filteringCollapsed = false;
|
||||||
}
|
}
|
||||||
@ -226,8 +228,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
if (this.filterSettings.presets?.formats && this.filterSettings.presets?.formats.length > 0) {
|
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.formatSettings.savedData = mangaFormatFilters.filter(item => this.filterSettings.presets?.formats.includes(item.value));
|
||||||
this.filter.formats = this.formatSettings.savedData.map(item => item.value);
|
this.updateFormatFilters(this.formatSettings.savedData);
|
||||||
this.resetTypeaheads.next(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +252,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
if (this.filterSettings.presets?.libraries && this.filterSettings.presets?.libraries.length > 0) {
|
if (this.filterSettings.presets?.libraries && this.filterSettings.presets?.libraries.length > 0) {
|
||||||
return this.librarySettings.fetchFn('').pipe(map(libraries => {
|
return this.librarySettings.fetchFn('').pipe(map(libraries => {
|
||||||
this.librarySettings.savedData = libraries.filter(item => this.filterSettings.presets?.libraries.includes(item.id));
|
this.librarySettings.savedData = libraries.filter(item => this.filterSettings.presets?.libraries.includes(item.id));
|
||||||
this.filter.libraries = this.librarySettings.savedData.map(item => item.id);
|
this.updateLibraryFilters(this.librarySettings.savedData);
|
||||||
return of(true);
|
return of(true);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -278,7 +279,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
if (this.filterSettings.presets?.genres && this.filterSettings.presets?.genres.length > 0) {
|
if (this.filterSettings.presets?.genres && this.filterSettings.presets?.genres.length > 0) {
|
||||||
return this.genreSettings.fetchFn('').pipe(map(genres => {
|
return this.genreSettings.fetchFn('').pipe(map(genres => {
|
||||||
this.genreSettings.savedData = genres.filter(item => this.filterSettings.presets?.genres.includes(item.id));
|
this.genreSettings.savedData = genres.filter(item => this.filterSettings.presets?.genres.includes(item.id));
|
||||||
this.filter.genres = this.genreSettings.savedData.map(item => item.id);
|
this.updateGenreFilters(this.genreSettings.savedData);
|
||||||
return of(true);
|
return of(true);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -305,7 +306,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
if (this.filterSettings.presets?.ageRating && this.filterSettings.presets?.ageRating.length > 0) {
|
if (this.filterSettings.presets?.ageRating && this.filterSettings.presets?.ageRating.length > 0) {
|
||||||
return this.ageRatingSettings.fetchFn('').pipe(map(rating => {
|
return this.ageRatingSettings.fetchFn('').pipe(map(rating => {
|
||||||
this.ageRatingSettings.savedData = rating.filter(item => this.filterSettings.presets?.ageRating.includes(item.value));
|
this.ageRatingSettings.savedData = rating.filter(item => this.filterSettings.presets?.ageRating.includes(item.value));
|
||||||
this.filter.ageRating = this.ageRatingSettings.savedData.map(item => item.value);
|
this.updateAgeRating(this.ageRatingSettings.savedData);
|
||||||
return of(true);
|
return of(true);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -332,7 +333,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
if (this.filterSettings.presets?.publicationStatus && this.filterSettings.presets?.publicationStatus.length > 0) {
|
if (this.filterSettings.presets?.publicationStatus && this.filterSettings.presets?.publicationStatus.length > 0) {
|
||||||
return this.publicationStatusSettings.fetchFn('').pipe(map(statuses => {
|
return this.publicationStatusSettings.fetchFn('').pipe(map(statuses => {
|
||||||
this.publicationStatusSettings.savedData = statuses.filter(item => this.filterSettings.presets?.publicationStatus.includes(item.value));
|
this.publicationStatusSettings.savedData = statuses.filter(item => this.filterSettings.presets?.publicationStatus.includes(item.value));
|
||||||
this.filter.publicationStatus = this.publicationStatusSettings.savedData.map(item => item.value);
|
this.updatePublicationStatus(this.publicationStatusSettings.savedData);
|
||||||
return of(true);
|
return of(true);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -358,7 +359,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
if (this.filterSettings.presets?.tags && this.filterSettings.presets?.tags.length > 0) {
|
if (this.filterSettings.presets?.tags && this.filterSettings.presets?.tags.length > 0) {
|
||||||
return this.tagsSettings.fetchFn('').pipe(map(tags => {
|
return this.tagsSettings.fetchFn('').pipe(map(tags => {
|
||||||
this.tagsSettings.savedData = tags.filter(item => this.filterSettings.presets?.tags.includes(item.id));
|
this.tagsSettings.savedData = tags.filter(item => this.filterSettings.presets?.tags.includes(item.id));
|
||||||
this.filter.tags = this.tagsSettings.savedData.map(item => item.id);
|
this.updateTagFilters(this.tagsSettings.savedData);
|
||||||
return of(true);
|
return of(true);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -384,7 +385,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
if (this.filterSettings.presets?.languages && this.filterSettings.presets?.languages.length > 0) {
|
if (this.filterSettings.presets?.languages && this.filterSettings.presets?.languages.length > 0) {
|
||||||
return this.languageSettings.fetchFn('').pipe(map(languages => {
|
return this.languageSettings.fetchFn('').pipe(map(languages => {
|
||||||
this.languageSettings.savedData = languages.filter(item => this.filterSettings.presets?.languages.includes(item.isoCode));
|
this.languageSettings.savedData = languages.filter(item => this.filterSettings.presets?.languages.includes(item.isoCode));
|
||||||
this.filter.languages = this.languageSettings.savedData.map(item => item.isoCode);
|
this.updateLanguages(this.languageSettings.savedData);
|
||||||
return of(true);
|
return of(true);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -410,7 +411,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
if (this.filterSettings.presets?.collectionTags && this.filterSettings.presets?.collectionTags.length > 0) {
|
if (this.filterSettings.presets?.collectionTags && this.filterSettings.presets?.collectionTags.length > 0) {
|
||||||
return this.collectionSettings.fetchFn('').pipe(map(tags => {
|
return this.collectionSettings.fetchFn('').pipe(map(tags => {
|
||||||
this.collectionSettings.savedData = tags.filter(item => this.filterSettings.presets?.collectionTags.includes(item.id));
|
this.collectionSettings.savedData = tags.filter(item => this.filterSettings.presets?.collectionTags.includes(item.id));
|
||||||
this.filter.collectionTags = this.collectionSettings.savedData.map(item => item.id);
|
this.updateCollectionFilters(this.collectionSettings.savedData);
|
||||||
return of(true);
|
return of(true);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -423,16 +424,15 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
const fetch = personSettings.fetchFn as ((filter: string) => Observable<Person[]>);
|
const fetch = personSettings.fetchFn as ((filter: string) => Observable<Person[]>);
|
||||||
return fetch('').pipe(map(people => {
|
return fetch('').pipe(map(people => {
|
||||||
personSettings.savedData = people.filter(item => presetField.includes(item.id));
|
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.peopleSettings[role] = personSettings;
|
||||||
this.updatePersonFilters(personSettings.savedData as Person[], role);
|
this.updatePersonFilters(personSettings.savedData, role);
|
||||||
return true;
|
return true;
|
||||||
}));
|
}));
|
||||||
} else {
|
|
||||||
this.peopleSettings[role] = personSettings;
|
|
||||||
return of(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.peopleSettings[role] = personSettings;
|
||||||
|
return of(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPersonTypeahead() {
|
setupPersonTypeahead() {
|
||||||
@ -449,8 +449,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
this.updateFromPreset('penciller', this.filter.penciller, this.filterSettings.presets?.penciller, PersonRole.Penciller),
|
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('publisher', this.filter.publisher, this.filterSettings.presets?.publisher, PersonRole.Publisher),
|
||||||
this.updateFromPreset('translators', this.filter.translators, this.filterSettings.presets?.translators, PersonRole.Translator)
|
this.updateFromPreset('translators', this.filter.translators, this.filterSettings.presets?.translators, PersonRole.Translator)
|
||||||
]).pipe(map(results => {
|
]).pipe(map(_ => {
|
||||||
this.resetTypeaheads.next(true);
|
|
||||||
return of(true);
|
return of(true);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -537,6 +536,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateRating(rating: any) {
|
updateRating(rating: any) {
|
||||||
|
if (this.filterSettings.ratingDisabled) return;
|
||||||
this.filter.rating = rating;
|
this.filter.rating = rating;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +548,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
this.filter.publicationStatus = dtos.map(item => item.value) || [];
|
this.filter.publicationStatus = dtos.map(item => item.value) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLanguageRating(languages: Language[]) {
|
updateLanguages(languages: Language[]) {
|
||||||
this.filter.languages = languages.map(item => item.isoCode) || [];
|
this.filter.languages = languages.map(item => item.isoCode) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,6 +563,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateSortOrder() {
|
updateSortOrder() {
|
||||||
|
if (this.filterSettings.sortDisabled) return;
|
||||||
this.isAscendingSort = !this.isAscendingSort;
|
this.isAscendingSort = !this.isAscendingSort;
|
||||||
if (this.filter.sortOptions === null) {
|
if (this.filter.sortOptions === null) {
|
||||||
this.filter.sortOptions = {
|
this.filter.sortOptions = {
|
||||||
@ -582,7 +583,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
|
|||||||
this.sortGroup.get('sortField')?.setValue(SortField.SortName);
|
this.sortGroup.get('sortField')?.setValue(SortField.SortName);
|
||||||
this.isAscendingSort = true;
|
this.isAscendingSort = true;
|
||||||
// Apply any presets which will trigger the apply
|
// Apply any presets which will trigger the apply
|
||||||
this.setupTypeaheads();
|
this.loadFromPresetsAndSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
apply() {
|
apply() {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
|
import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { NgbModal, NgbNavChangeEvent, NgbRatingConfig } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { forkJoin, Subject } from 'rxjs';
|
import { forkJoin, Subject } from 'rxjs';
|
||||||
import { finalize, map, take, takeUntil, takeWhile } from 'rxjs/operators';
|
import { finalize, take, takeUntil, takeWhile } from 'rxjs/operators';
|
||||||
import { BulkSelectionService } from '../cards/bulk-selection.service';
|
import { BulkSelectionService } from '../cards/bulk-selection.service';
|
||||||
import { CardDetailsModalComponent } from '../cards/_modals/card-details-modal/card-details-modal.component';
|
import { CardDetailsModalComponent } from '../cards/_modals/card-details-modal/card-details-modal.component';
|
||||||
import { EditSeriesModalComponent } from '../cards/_modals/edit-series-modal/edit-series-modal.component';
|
import { EditSeriesModalComponent } from '../cards/_modals/edit-series-modal/edit-series-modal.component';
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
<!-- This first row will have random information about the series-->
|
<!-- This first row will have random information about the series-->
|
||||||
<div class="row g-0 mb-2">
|
<div class="row g-0 mb-2">
|
||||||
<app-tag-badge title="Age Rating" *ngIf="seriesMetadata.ageRating" a11y-click="13,32" class="clickable col-auto" (click)="goTo('ageRating', seriesMetadata.ageRating)" [selectionMode]="TagBadgeCursor.Clickable">{{metadataService.getAgeRating(this.seriesMetadata.ageRating) | async}}</app-tag-badge>
|
<app-tag-badge title="Age Rating" *ngIf="seriesMetadata.ageRating" a11y-click="13,32" class="clickable col-auto" (click)="goTo(FilterQueryParam.AgeRating, seriesMetadata.ageRating)" [selectionMode]="TagBadgeCursor.Clickable">{{metadataService.getAgeRating(this.seriesMetadata.ageRating) | async}}</app-tag-badge>
|
||||||
<ng-container *ngIf="series">
|
<ng-container *ngIf="series">
|
||||||
<app-tag-badge *ngIf="seriesMetadata.releaseYear > 0" title="Release date" class="col-auto">{{seriesMetadata.releaseYear}}</app-tag-badge>
|
<app-tag-badge *ngIf="seriesMetadata.releaseYear > 0" title="Release date" class="col-auto">{{seriesMetadata.releaseYear}}</app-tag-badge>
|
||||||
<app-tag-badge *ngIf="seriesMetadata.language !== null && seriesMetadata.language !== ''" title="Language" a11y-click="13,32" class="col-auto" (click)="goTo('languages', seriesMetadata.language)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.language}}</app-tag-badge>
|
<app-tag-badge *ngIf="seriesMetadata.language !== null && seriesMetadata.language !== ''" title="Language" a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Languages, seriesMetadata.language)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.language}}</app-tag-badge>
|
||||||
<app-tag-badge title="Publication Status" a11y-click="13,32" class="col-auto" (click)="goTo('publicationStatus', seriesMetadata.publicationStatus)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.publicationStatus | publicationStatus}}</app-tag-badge>
|
<app-tag-badge title="Publication Status" a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.PublicationStatus, seriesMetadata.publicationStatus)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.publicationStatus | publicationStatus}}</app-tag-badge>
|
||||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo('format', series.format)" [selectionMode]="TagBadgeCursor.Clickable">
|
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Format, series.format)" [selectionMode]="TagBadgeCursor.Clickable">
|
||||||
<app-series-format [format]="series.format">{{utilityService.mangaFormat(series.format)}}</app-series-format>
|
<app-series-format [format]="series.format">{{utilityService.mangaFormat(series.format)}}</app-series-format>
|
||||||
</app-tag-badge>
|
</app-tag-badge>
|
||||||
<app-tag-badge title="Last Read" class="col-auto" *ngIf="series.latestReadDate && series.latestReadDate !== '' && (series.latestReadDate | date: 'shortDate') !== '1/1/01'" [selectionMode]="TagBadgeCursor.Selectable">
|
<app-tag-badge title="Last Read" class="col-auto" *ngIf="series.latestReadDate && series.latestReadDate !== '' && (series.latestReadDate | date: 'shortDate') !== '1/1/01'" [selectionMode]="TagBadgeCursor.Selectable">
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.genres">
|
<app-badge-expander [items]="seriesMetadata.genres">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo('genres', item.id)" [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Genres, item.id)" [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -70,7 +70,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.writers">
|
<app-badge-expander [items]="seriesMetadata.writers">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('writers', item.id)" [person]="item"></app-person-badge>
|
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Writers, item.id)" [person]="item"></app-person-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -89,7 +89,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.coverArtists">
|
<app-badge-expander [items]="seriesMetadata.coverArtists">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('coverArtists', item.id)" [person]="item"></app-person-badge>
|
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.CoverArtists, item.id)" [person]="item"></app-person-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -102,7 +102,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.characters">
|
<app-badge-expander [items]="seriesMetadata.characters">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('character', item.id)" [person]="item"></app-person-badge>
|
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Character, item.id)" [person]="item"></app-person-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -115,7 +115,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.colorists">
|
<app-badge-expander [items]="seriesMetadata.colorists">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('colorist', item.id)" [person]="item"></app-person-badge>
|
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Colorist, item.id)" [person]="item"></app-person-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -128,7 +128,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.editors">
|
<app-badge-expander [items]="seriesMetadata.editors">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('editor', item.id)" [person]="item"></app-person-badge>
|
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Editor, item.id)" [person]="item"></app-person-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -141,7 +141,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.inkers">
|
<app-badge-expander [items]="seriesMetadata.inkers">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('inker', item.id)" [person]="item"></app-person-badge>
|
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Inker, item.id)" [person]="item"></app-person-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -154,7 +154,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.letterers">
|
<app-badge-expander [items]="seriesMetadata.letterers">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('letterer', item.id)" [person]="item"></app-person-badge>
|
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Letterer, item.id)" [person]="item"></app-person-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -166,7 +166,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.tags">
|
<app-badge-expander [items]="seriesMetadata.tags">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo('tags', item.id)" [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Tags, item.id)" [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -178,7 +178,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.translators">
|
<app-badge-expander [items]="seriesMetadata.translators">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('translators', item.id)" [person]="item"></app-person-badge>
|
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Translator, item.id)" [person]="item"></app-person-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -191,7 +191,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.pencillers">
|
<app-badge-expander [items]="seriesMetadata.pencillers">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('penciller', item.id)" [person]="item"></app-person-badge>
|
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Penciller, item.id)" [person]="item"></app-person-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
@ -204,7 +204,7 @@
|
|||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-badge-expander [items]="seriesMetadata.publishers">
|
<app-badge-expander [items]="seriesMetadata.publishers">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('publisher', item.id)" [person]="item"></app-person-badge>
|
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo(FilterQueryParam.Publisher, item.id)" [person]="item"></app-person-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-badge-expander>
|
</app-badge-expander>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { TagBadgeCursor } from '../shared/tag-badge/tag-badge.component';
|
import { TagBadgeCursor } from '../shared/tag-badge/tag-badge.component';
|
||||||
|
import { FilterQueryParam } from '../shared/_services/filter-utilities.service';
|
||||||
import { UtilityService } from '../shared/_services/utility.service';
|
import { UtilityService } from '../shared/_services/utility.service';
|
||||||
import { MangaFormat } from '../_models/manga-format';
|
import { MangaFormat } from '../_models/manga-format';
|
||||||
import { ReadingList } from '../_models/reading-list';
|
import { ReadingList } from '../_models/reading-list';
|
||||||
@ -38,6 +39,10 @@ export class SeriesMetadataDetailComponent implements OnInit, OnChanges {
|
|||||||
return TagBadgeCursor;
|
return TagBadgeCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get FilterQueryParam() {
|
||||||
|
return FilterQueryParam;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(public utilityService: UtilityService, public metadataService: MetadataService, private router: Router) { }
|
constructor(public utilityService: UtilityService, public metadataService: MetadataService, private router: Router) { }
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
@ -64,10 +69,10 @@ export class SeriesMetadataDetailComponent implements OnInit, OnChanges {
|
|||||||
this.isCollapsed = !this.isCollapsed;
|
this.isCollapsed = !this.isCollapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
goTo(queryParamName: string, filter: any) {
|
goTo(queryParamName: FilterQueryParam, filter: any) {
|
||||||
let params: any = {};
|
let params: any = {};
|
||||||
params[queryParamName] = filter;
|
params[queryParamName] = filter;
|
||||||
params['page'] = 1;
|
params[FilterQueryParam.Page] = 1;
|
||||||
this.router.navigate(['library', this.series.libraryId], {queryParams: params});
|
this.router.navigate(['library', this.series.libraryId], {queryParams: params});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,42 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
|
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
|
||||||
import { LibraryType } from 'src/app/_models/library';
|
|
||||||
import { Pagination } from 'src/app/_models/pagination';
|
import { Pagination } from 'src/app/_models/pagination';
|
||||||
import { SeriesFilter, SortField } from 'src/app/_models/series-filter';
|
import { SeriesFilter, SortField } from 'src/app/_models/series-filter';
|
||||||
import { SeriesService } from 'src/app/_services/series.service';
|
import { SeriesService } from 'src/app/_services/series.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to pass state between the filter and the url
|
||||||
|
*/
|
||||||
|
export enum FilterQueryParam {
|
||||||
|
Format = 'format',
|
||||||
|
Genres = 'genres',
|
||||||
|
AgeRating = 'ageRating',
|
||||||
|
PublicationStatus = 'publicationStatus',
|
||||||
|
Tags = 'tags',
|
||||||
|
Languages = 'languages',
|
||||||
|
CollectionTags = 'collectionTags',
|
||||||
|
Libraries = 'libraries',
|
||||||
|
Writers = 'writers',
|
||||||
|
Artists = 'artists',
|
||||||
|
Character = 'character',
|
||||||
|
Colorist = 'colorist',
|
||||||
|
CoverArtists = 'coverArtists',
|
||||||
|
Editor = 'editor',
|
||||||
|
Inker = 'inker',
|
||||||
|
Letterer = 'letterer',
|
||||||
|
Penciller = 'penciller',
|
||||||
|
Publisher = 'publisher',
|
||||||
|
Translator = 'translators',
|
||||||
|
ReadStatus = 'readStatus',
|
||||||
|
SortBy = 'sortBy',
|
||||||
|
Rating = 'rating',
|
||||||
|
Name = 'name',
|
||||||
|
/**
|
||||||
|
* This is a pagination control
|
||||||
|
*/
|
||||||
|
Page = 'page'
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@ -38,10 +70,11 @@ export class FilterUtilitiesService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Will fetch current page from route if present
|
* Will fetch current page from route if present
|
||||||
|
* @param ActivatedRouteSnapshot to fetch page from. Must be from component else may get stale data
|
||||||
* @returns A default pagination object
|
* @returns A default pagination object
|
||||||
*/
|
*/
|
||||||
pagination(): Pagination {
|
pagination(snapshot: ActivatedRouteSnapshot): Pagination {
|
||||||
return {currentPage: parseInt(this.route.snapshot.queryParamMap.get('page') || '1', 10), itemsPerPage: 30, totalItems: 0, totalPages: 1};
|
return {currentPage: parseInt(snapshot.queryParamMap.get('page') || '1', 10), itemsPerPage: 30, totalItems: 0, totalPages: 1};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -54,46 +87,44 @@ export class FilterUtilitiesService {
|
|||||||
urlFromFilter(currentUrl: string, filter: SeriesFilter | undefined) {
|
urlFromFilter(currentUrl: string, filter: SeriesFilter | undefined) {
|
||||||
if (filter === undefined) return currentUrl;
|
if (filter === undefined) return currentUrl;
|
||||||
let params = '';
|
let params = '';
|
||||||
|
|
||||||
|
|
||||||
|
params += this.joinFilter(filter.formats, FilterQueryParam.Format);
|
||||||
params += this.joinFilter(filter.formats, 'format');
|
params += this.joinFilter(filter.genres, FilterQueryParam.Genres);
|
||||||
params += this.joinFilter(filter.genres, 'genres');
|
params += this.joinFilter(filter.ageRating, FilterQueryParam.AgeRating);
|
||||||
params += this.joinFilter(filter.ageRating, 'ageRating');
|
params += this.joinFilter(filter.publicationStatus, FilterQueryParam.PublicationStatus);
|
||||||
params += this.joinFilter(filter.publicationStatus, 'publicationStatus');
|
params += this.joinFilter(filter.tags, FilterQueryParam.Tags);
|
||||||
params += this.joinFilter(filter.tags, 'tags');
|
params += this.joinFilter(filter.languages, FilterQueryParam.Languages);
|
||||||
params += this.joinFilter(filter.languages, 'languages');
|
params += this.joinFilter(filter.collectionTags, FilterQueryParam.CollectionTags);
|
||||||
params += this.joinFilter(filter.collectionTags, 'collectionTags');
|
params += this.joinFilter(filter.libraries, FilterQueryParam.Libraries);
|
||||||
params += this.joinFilter(filter.libraries, 'libraries');
|
|
||||||
|
|
||||||
params += this.joinFilter(filter.writers, 'writers');
|
params += this.joinFilter(filter.writers, FilterQueryParam.Writers);
|
||||||
params += this.joinFilter(filter.artists, 'artists');
|
params += this.joinFilter(filter.artists, FilterQueryParam.Artists);
|
||||||
params += this.joinFilter(filter.character, 'character');
|
params += this.joinFilter(filter.character, FilterQueryParam.Character);
|
||||||
params += this.joinFilter(filter.colorist, 'colorist');
|
params += this.joinFilter(filter.colorist, FilterQueryParam.Colorist);
|
||||||
params += this.joinFilter(filter.coverArtist, 'coverArtists');
|
params += this.joinFilter(filter.coverArtist, FilterQueryParam.CoverArtists);
|
||||||
params += this.joinFilter(filter.editor, 'editor');
|
params += this.joinFilter(filter.editor, FilterQueryParam.Editor);
|
||||||
params += this.joinFilter(filter.inker, 'inker');
|
params += this.joinFilter(filter.inker, FilterQueryParam.Inker);
|
||||||
params += this.joinFilter(filter.letterer, 'letterer');
|
params += this.joinFilter(filter.letterer, FilterQueryParam.Letterer);
|
||||||
params += this.joinFilter(filter.penciller, 'penciller');
|
params += this.joinFilter(filter.penciller, FilterQueryParam.Penciller);
|
||||||
params += this.joinFilter(filter.publisher, 'publisher');
|
params += this.joinFilter(filter.publisher, FilterQueryParam.Publisher);
|
||||||
params += this.joinFilter(filter.translators, 'translators');
|
params += this.joinFilter(filter.translators, FilterQueryParam.Translator);
|
||||||
|
|
||||||
// readStatus (we need to do an additonal check as there is a default case)
|
// readStatus (we need to do an additonal check as there is a default case)
|
||||||
if (filter.readStatus && filter.readStatus.inProgress !== true && filter.readStatus.notRead !== true && filter.readStatus.read !== true) {
|
if (filter.readStatus && filter.readStatus.inProgress !== true && filter.readStatus.notRead !== true && filter.readStatus.read !== true) {
|
||||||
params += '&readStatus=' + `${filter.readStatus.inProgress},${filter.readStatus.notRead},${filter.readStatus.read}`;
|
params += `&${FilterQueryParam.ReadStatus}=${filter.readStatus.inProgress},${filter.readStatus.notRead},${filter.readStatus.read}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sortBy (additional check to not save to url if default case)
|
// sortBy (additional check to not save to url if default case)
|
||||||
if (filter.sortOptions && !(filter.sortOptions.sortField === SortField.SortName && filter.sortOptions.isAscending === true)) {
|
if (filter.sortOptions && !(filter.sortOptions.sortField === SortField.SortName && filter.sortOptions.isAscending === true)) {
|
||||||
params += '&sortBy=' + filter.sortOptions.sortField + ',' + filter.sortOptions.isAscending;
|
params += `&${FilterQueryParam.SortBy}=${filter.sortOptions.sortField},${filter.sortOptions.isAscending}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.rating > 0) {
|
if (filter.rating > 0) {
|
||||||
params += '&rating=' + filter.rating;
|
params += `&${FilterQueryParam.Rating}=${filter.rating}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.seriesNameQuery !== '') {
|
if (filter.seriesNameQuery !== '') {
|
||||||
params += '&name=' + encodeURIComponent(filter.seriesNameQuery);
|
params += `&${FilterQueryParam.Name}=${encodeURIComponent(filter.seriesNameQuery)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentUrl + params;
|
return currentUrl + params;
|
||||||
@ -102,143 +133,143 @@ export class FilterUtilitiesService {
|
|||||||
private joinFilter(filterProp: Array<any>, key: string) {
|
private joinFilter(filterProp: Array<any>, key: string) {
|
||||||
let params = '';
|
let params = '';
|
||||||
if (filterProp.length > 0) {
|
if (filterProp.length > 0) {
|
||||||
params += `&${key}=` + filterProp.join(',');
|
params += `&${key}=${filterProp.join(',')}`;
|
||||||
}
|
}
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new instance of a filterSettings that is populated with filter presets from URL
|
* Returns a new instance of a filterSettings that is populated with filter presets from URL
|
||||||
|
* @param ActivatedRouteSnapshot to fetch page from. Must be from component else may get stale data
|
||||||
* @returns The Preset filter and if something was set within
|
* @returns The Preset filter and if something was set within
|
||||||
*/
|
*/
|
||||||
filterPresetsFromUrl(): [SeriesFilter, boolean] {
|
filterPresetsFromUrl(snapshot: ActivatedRouteSnapshot): [SeriesFilter, boolean] {
|
||||||
const snapshot = this.route.snapshot;
|
|
||||||
const filter = this.seriesService.createSeriesFilter();
|
const filter = this.seriesService.createSeriesFilter();
|
||||||
let anyChanged = false;
|
let anyChanged = false;
|
||||||
|
|
||||||
const format = snapshot.queryParamMap.get('format');
|
const format = snapshot.queryParamMap.get(FilterQueryParam.Format);
|
||||||
if (format !== undefined && format !== null) {
|
if (format !== undefined && format !== null) {
|
||||||
filter.formats = [...filter.formats, ...format.split(',').map(item => parseInt(item, 10))];
|
filter.formats = [...filter.formats, ...format.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const genres = snapshot.queryParamMap.get('genres');
|
const genres = snapshot.queryParamMap.get(FilterQueryParam.Genres);
|
||||||
if (genres !== undefined && genres !== null) {
|
if (genres !== undefined && genres !== null) {
|
||||||
filter.genres = [...filter.genres, ...genres.split(',').map(item => parseInt(item, 10))];
|
filter.genres = [...filter.genres, ...genres.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ageRating = snapshot.queryParamMap.get('ageRating');
|
const ageRating = snapshot.queryParamMap.get(FilterQueryParam.AgeRating);
|
||||||
if (ageRating !== undefined && ageRating !== null) {
|
if (ageRating !== undefined && ageRating !== null) {
|
||||||
filter.ageRating = [...filter.ageRating, ...ageRating.split(',').map(item => parseInt(item, 10))];
|
filter.ageRating = [...filter.ageRating, ...ageRating.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicationStatus = snapshot.queryParamMap.get('publicationStatus');
|
const publicationStatus = snapshot.queryParamMap.get(FilterQueryParam.PublicationStatus);
|
||||||
if (publicationStatus !== undefined && publicationStatus !== null) {
|
if (publicationStatus !== undefined && publicationStatus !== null) {
|
||||||
filter.publicationStatus = [...filter.publicationStatus, ...publicationStatus.split(',').map(item => parseInt(item, 10))];
|
filter.publicationStatus = [...filter.publicationStatus, ...publicationStatus.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = snapshot.queryParamMap.get('tags');
|
const tags = snapshot.queryParamMap.get(FilterQueryParam.Tags);
|
||||||
if (tags !== undefined && tags !== null) {
|
if (tags !== undefined && tags !== null) {
|
||||||
filter.tags = [...filter.tags, ...tags.split(',').map(item => parseInt(item, 10))];
|
filter.tags = [...filter.tags, ...tags.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const languages = snapshot.queryParamMap.get('languages');
|
const languages = snapshot.queryParamMap.get(FilterQueryParam.Languages);
|
||||||
if (languages !== undefined && languages !== null) {
|
if (languages !== undefined && languages !== null) {
|
||||||
filter.languages = [...filter.languages, ...languages.split(',')];
|
filter.languages = [...filter.languages, ...languages.split(',')];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const writers = snapshot.queryParamMap.get('writers');
|
const writers = snapshot.queryParamMap.get(FilterQueryParam.Writers);
|
||||||
if (writers !== undefined && writers !== null) {
|
if (writers !== undefined && writers !== null) {
|
||||||
filter.writers = [...filter.writers, ...writers.split(',').map(item => parseInt(item, 10))];
|
filter.writers = [...filter.writers, ...writers.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const artists = snapshot.queryParamMap.get('artists');
|
const artists = snapshot.queryParamMap.get(FilterQueryParam.Artists);
|
||||||
if (artists !== undefined && artists !== null) {
|
if (artists !== undefined && artists !== null) {
|
||||||
filter.artists = [...filter.artists, ...artists.split(',').map(item => parseInt(item, 10))];
|
filter.artists = [...filter.artists, ...artists.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const character = snapshot.queryParamMap.get('character');
|
const character = snapshot.queryParamMap.get(FilterQueryParam.Character);
|
||||||
if (character !== undefined && character !== null) {
|
if (character !== undefined && character !== null) {
|
||||||
filter.character = [...filter.character, ...character.split(',').map(item => parseInt(item, 10))];
|
filter.character = [...filter.character, ...character.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorist = snapshot.queryParamMap.get('colorist');
|
const colorist = snapshot.queryParamMap.get(FilterQueryParam.Colorist);
|
||||||
if (colorist !== undefined && colorist !== null) {
|
if (colorist !== undefined && colorist !== null) {
|
||||||
filter.colorist = [...filter.colorist, ...colorist.split(',').map(item => parseInt(item, 10))];
|
filter.colorist = [...filter.colorist, ...colorist.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const coverArtists = snapshot.queryParamMap.get('coverArtists');
|
const coverArtists = snapshot.queryParamMap.get(FilterQueryParam.CoverArtists);
|
||||||
if (coverArtists !== undefined && coverArtists !== null) {
|
if (coverArtists !== undefined && coverArtists !== null) {
|
||||||
filter.coverArtist = [...filter.coverArtist, ...coverArtists.split(',').map(item => parseInt(item, 10))];
|
filter.coverArtist = [...filter.coverArtist, ...coverArtists.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = snapshot.queryParamMap.get('editor');
|
const editor = snapshot.queryParamMap.get(FilterQueryParam.Editor);
|
||||||
if (editor !== undefined && editor !== null) {
|
if (editor !== undefined && editor !== null) {
|
||||||
filter.editor = [...filter.editor, ...editor.split(',').map(item => parseInt(item, 10))];
|
filter.editor = [...filter.editor, ...editor.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inker = snapshot.queryParamMap.get('inker');
|
const inker = snapshot.queryParamMap.get(FilterQueryParam.Inker);
|
||||||
if (inker !== undefined && inker !== null) {
|
if (inker !== undefined && inker !== null) {
|
||||||
filter.inker = [...filter.inker, ...inker.split(',').map(item => parseInt(item, 10))];
|
filter.inker = [...filter.inker, ...inker.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const letterer = snapshot.queryParamMap.get('letterer');
|
const letterer = snapshot.queryParamMap.get(FilterQueryParam.Letterer);
|
||||||
if (letterer !== undefined && letterer !== null) {
|
if (letterer !== undefined && letterer !== null) {
|
||||||
filter.letterer = [...filter.letterer, ...letterer.split(',').map(item => parseInt(item, 10))];
|
filter.letterer = [...filter.letterer, ...letterer.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const penciller = snapshot.queryParamMap.get('penciller');
|
const penciller = snapshot.queryParamMap.get(FilterQueryParam.Penciller);
|
||||||
if (penciller !== undefined && penciller !== null) {
|
if (penciller !== undefined && penciller !== null) {
|
||||||
filter.penciller = [...filter.penciller, ...penciller.split(',').map(item => parseInt(item, 10))];
|
filter.penciller = [...filter.penciller, ...penciller.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const publisher = snapshot.queryParamMap.get('publisher');
|
const publisher = snapshot.queryParamMap.get(FilterQueryParam.Publisher);
|
||||||
if (publisher !== undefined && publisher !== null) {
|
if (publisher !== undefined && publisher !== null) {
|
||||||
filter.publisher = [...filter.publisher, ...publisher.split(',').map(item => parseInt(item, 10))];
|
filter.publisher = [...filter.publisher, ...publisher.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const translators = snapshot.queryParamMap.get('translators');
|
const translators = snapshot.queryParamMap.get(FilterQueryParam.Translator);
|
||||||
if (translators !== undefined && translators !== null) {
|
if (translators !== undefined && translators !== null) {
|
||||||
filter.translators = [...filter.translators, ...translators.split(',').map(item => parseInt(item, 10))];
|
filter.translators = [...filter.translators, ...translators.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const libraries = snapshot.queryParamMap.get('libraries');
|
const libraries = snapshot.queryParamMap.get(FilterQueryParam.Libraries);
|
||||||
if (libraries !== undefined && libraries !== null) {
|
if (libraries !== undefined && libraries !== null) {
|
||||||
filter.libraries = [...filter.libraries, ...libraries.split(',').map(item => parseInt(item, 10))];
|
filter.libraries = [...filter.libraries, ...libraries.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const collectionTags = snapshot.queryParamMap.get('collectionTags');
|
const collectionTags = snapshot.queryParamMap.get(FilterQueryParam.CollectionTags);
|
||||||
if (collectionTags !== undefined && collectionTags !== null) {
|
if (collectionTags !== undefined && collectionTags !== null) {
|
||||||
filter.collectionTags = [...filter.collectionTags, ...collectionTags.split(',').map(item => parseInt(item, 10))];
|
filter.collectionTags = [...filter.collectionTags, ...collectionTags.split(',').map(item => parseInt(item, 10))];
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rating, seriesName,
|
// Rating, seriesName,
|
||||||
const rating = snapshot.queryParamMap.get('rating');
|
const rating = snapshot.queryParamMap.get(FilterQueryParam.Rating);
|
||||||
if (rating !== undefined && rating !== null && parseInt(rating, 10) > 0) {
|
if (rating !== undefined && rating !== null && parseInt(rating, 10) > 0) {
|
||||||
filter.rating = parseInt(rating, 10);
|
filter.rating = parseInt(rating, 10);
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read status is encoded as true,true,true
|
/// Read status is encoded as true,true,true
|
||||||
const readStatus = snapshot.queryParamMap.get('readStatus');
|
const readStatus = snapshot.queryParamMap.get(FilterQueryParam.ReadStatus);
|
||||||
if (readStatus !== undefined && readStatus !== null) {
|
if (readStatus !== undefined && readStatus !== null) {
|
||||||
const values = readStatus.split(',').map(i => i === 'true');
|
const values = readStatus.split(',').map(i => i === 'true');
|
||||||
if (values.length === 3) {
|
if (values.length === 3) {
|
||||||
@ -249,7 +280,7 @@ export class FilterUtilitiesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortBy = snapshot.queryParamMap.get('sortBy');
|
const sortBy = snapshot.queryParamMap.get(FilterQueryParam.SortBy);
|
||||||
if (sortBy !== undefined && sortBy !== null) {
|
if (sortBy !== undefined && sortBy !== null) {
|
||||||
const values = sortBy.split(',');
|
const values = sortBy.split(',');
|
||||||
if (values.length === 1) {
|
if (values.length === 1) {
|
||||||
@ -264,7 +295,7 @@ export class FilterUtilitiesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchNameQuery = snapshot.queryParamMap.get('name');
|
const searchNameQuery = snapshot.queryParamMap.get(FilterQueryParam.Name);
|
||||||
if (searchNameQuery !== undefined && searchNameQuery !== null && searchNameQuery !== '') {
|
if (searchNameQuery !== undefined && searchNameQuery !== null && searchNameQuery !== '') {
|
||||||
filter.seriesNameQuery = decodeURIComponent(searchNameQuery);
|
filter.seriesNameQuery = decodeURIComponent(searchNameQuery);
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<ng-content select="[title]"></ng-content>
|
<ng-content select="[title]"></ng-content>
|
||||||
<ng-content select="[subtitle]"></ng-content>
|
<ng-content select="[subtitle]"></ng-content>
|
||||||
</div>
|
</div>
|
||||||
<div class="col mr-auto hide-if-empty d-none d-sm-flex">
|
<div class="col mr-auto d-none d-sm-flex hide-if-empty">
|
||||||
<ng-content select="[main]"></ng-content>
|
<ng-content select="[main]"></ng-content>
|
||||||
</div>
|
</div>
|
||||||
<div class="col" *ngIf="hasFilter">
|
<div class="col" *ngIf="hasFilter">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.hide-if-empty:empty {
|
.hide-if-empty:empty {
|
||||||
display: none;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .companion-bar {
|
::ng-deep .companion-bar {
|
||||||
|
@ -5,17 +5,17 @@
|
|||||||
<span class="visually-hidden">Field is locked</span>
|
<span class="visually-hidden">Field is locked</span>
|
||||||
</span>
|
</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="typeahead-input" (click)="onInputFocus($event)">
|
<div class="typeahead-input" [ngClass]="{'disabled': disabled}" (click)="onInputFocus($event)">
|
||||||
<app-tag-badge *ngFor="let option of optionSelection.selected(); let i = index">
|
<app-tag-badge *ngFor="let option of optionSelection.selected(); let i = index">
|
||||||
<ng-container [ngTemplateOutlet]="badgeTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: i }"></ng-container>
|
<ng-container [ngTemplateOutlet]="badgeTemplate" [ngTemplateOutletContext]="{ $implicit: option, idx: i }"></ng-container>
|
||||||
<i class="fa fa-times" (click)="toggleSelection(option)" tabindex="0" aria-label="close"></i>
|
<i class="fa fa-times" *ngIf="!disabled" (click)="toggleSelection(option)" tabindex="0" aria-label="close"></i>
|
||||||
</app-tag-badge>
|
</app-tag-badge>
|
||||||
|
|
||||||
<input #input [id]="settings.id" type="text" autocomplete="off" formControlName="typeahead">
|
<input #input [id]="settings.id" type="text" autocomplete="off" formControlName="typeahead" *ngIf="!disabled">
|
||||||
<div class="spinner-border spinner-border-sm {{settings.multiple ? 'close-offset' : ''}}" role="status" *ngIf="isLoadingOptions">
|
<div class="spinner-border spinner-border-sm {{settings.multiple ? 'close-offset' : ''}}" role="status" *ngIf="isLoadingOptions">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="settings.multiple && (selectedData | async) as selected">
|
<ng-container *ngIf="!disabled && settings.multiple && (selectedData | async) as selected">
|
||||||
<button class="btn btn-close float-end mt-2" *ngIf="selected.length > 0" style="font-size: 0.8rem;" (click)="clearSelections()"></button>
|
<button class="btn btn-close float-end mt-2" *ngIf="selected.length > 0" style="font-size: 0.8rem;" (click)="clearSelections()"></button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,6 +43,10 @@ input {
|
|||||||
border: 1px solid var(--input-border-color);
|
border: 1px solid var(--input-border-color);
|
||||||
color: var(--body-text-color);
|
color: var(--body-text-color);
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
outline: 0 !important;
|
outline: 0 !important;
|
||||||
border-radius: .28571429rem;
|
border-radius: .28571429rem;
|
||||||
|
@ -2,7 +2,7 @@ import { DOCUMENT } from '@angular/common';
|
|||||||
import { Component, ContentChild, ElementRef, EventEmitter, HostListener, Inject, Input, OnDestroy, OnInit, Output, Renderer2, RendererStyleFlags2, TemplateRef, ViewChild } from '@angular/core';
|
import { Component, ContentChild, ElementRef, EventEmitter, HostListener, Inject, Input, OnDestroy, OnInit, Output, Renderer2, RendererStyleFlags2, TemplateRef, ViewChild } from '@angular/core';
|
||||||
import { FormControl, FormGroup } from '@angular/forms';
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
import { Observable, of, ReplaySubject, Subject } from 'rxjs';
|
import { Observable, of, ReplaySubject, Subject } from 'rxjs';
|
||||||
import { debounceTime, filter, map, shareReplay, switchMap, take, takeUntil, tap } from 'rxjs/operators';
|
import { debounceTime, distinctUntilChanged, filter, map, shareReplay, switchMap, take, takeUntil, tap } from 'rxjs/operators';
|
||||||
import { KEY_CODES } from '../shared/_services/utility.service';
|
import { KEY_CODES } from '../shared/_services/utility.service';
|
||||||
import { SelectionCompareFn, TypeaheadSettings } from './typeahead-settings';
|
import { SelectionCompareFn, TypeaheadSettings } from './typeahead-settings';
|
||||||
|
|
||||||
@ -141,17 +141,22 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
@Input() settings!: TypeaheadSettings<any>;
|
@Input() settings!: TypeaheadSettings<any>;
|
||||||
/**
|
/**
|
||||||
* When true, component will re-init and set back to false.
|
* When true, will reset field to no selections. When false, will reset to saved data
|
||||||
*/
|
*/
|
||||||
@Input() reset: Subject<boolean> = new ReplaySubject(1);
|
@Input() reset: ReplaySubject<boolean> = new ReplaySubject(1);
|
||||||
/**
|
/**
|
||||||
* When a field is locked, we render custom css to indicate to the user. Does not affect functionality.
|
* When a field is locked, we render custom css to indicate to the user. Does not affect functionality.
|
||||||
*/
|
*/
|
||||||
@Input() locked: boolean = false;
|
@Input() locked: boolean = false;
|
||||||
|
/**
|
||||||
|
* If disabled, a user will not be able to interact with the typeahead
|
||||||
|
*/
|
||||||
|
@Input() disabled: boolean = false;
|
||||||
@Output() selectedData = new EventEmitter<any[] | any>();
|
@Output() selectedData = new EventEmitter<any[] | any>();
|
||||||
@Output() newItemAdded = new EventEmitter<any[] | any>();
|
@Output() newItemAdded = new EventEmitter<any[] | any>();
|
||||||
@Output() onUnlock = new EventEmitter<void>();
|
@Output() onUnlock = new EventEmitter<void>();
|
||||||
@Output() lockedChange = new EventEmitter<boolean>();
|
@Output() lockedChange = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
|
||||||
@ViewChild('input') inputElem!: ElementRef<HTMLInputElement>;
|
@ViewChild('input') inputElem!: ElementRef<HTMLInputElement>;
|
||||||
@ContentChild('optionItem') optionTemplate!: TemplateRef<any>;
|
@ContentChild('optionItem') optionTemplate!: TemplateRef<any>;
|
||||||
@ -178,8 +183,8 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
this.reset.pipe(takeUntil(this.onDestroy)).subscribe((reset: boolean) => {
|
this.reset.pipe(takeUntil(this.onDestroy)).subscribe((resetToEmpty: boolean) => {
|
||||||
this.clearSelections();
|
this.clearSelections(resetToEmpty);
|
||||||
this.init();
|
this.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -274,6 +279,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
@HostListener('window:keydown', ['$event'])
|
@HostListener('window:keydown', ['$event'])
|
||||||
handleKeyPress(event: KeyboardEvent) {
|
handleKeyPress(event: KeyboardEvent) {
|
||||||
if (!this.hasFocus) { return; }
|
if (!this.hasFocus) { return; }
|
||||||
|
if (this.disabled) return;
|
||||||
|
|
||||||
switch(event.key) {
|
switch(event.key) {
|
||||||
case KEY_CODES.DOWN_ARROW:
|
case KEY_CODES.DOWN_ARROW:
|
||||||
@ -347,15 +353,26 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
this.resetField();
|
this.resetField();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelections() {
|
clearSelections(untoggleAll: boolean = false) {
|
||||||
if (this.optionSelection) {
|
if (this.optionSelection) {
|
||||||
this.optionSelection.selected().forEach(item => this.optionSelection.toggle(item, false));
|
if (!untoggleAll && this.settings.savedData) {
|
||||||
|
const isArray = this.settings.savedData.hasOwnProperty('length');
|
||||||
|
if (isArray) {
|
||||||
|
this.optionSelection = new SelectionModel<any>(true, this.settings.savedData);
|
||||||
|
} else {
|
||||||
|
this.optionSelection = new SelectionModel<any>(true, [this.settings.savedData]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.optionSelection.selected().forEach(item => this.optionSelection.toggle(item, false));
|
||||||
|
}
|
||||||
|
|
||||||
this.selectedData.emit(this.optionSelection.selected());
|
this.selectedData.emit(this.optionSelection.selected());
|
||||||
this.resetField();
|
this.resetField();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOptionClick(opt: any) {
|
handleOptionClick(opt: any) {
|
||||||
|
if (this.disabled) return;
|
||||||
if (!this.settings.multiple && this.optionSelection.selected().length > 0) {
|
if (!this.settings.multiple && this.optionSelection.selected().length > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -402,6 +419,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
if (this.disabled) return;
|
||||||
|
|
||||||
if (!this.settings.multiple && this.optionSelection.selected().length > 0) {
|
if (!this.settings.multiple && this.optionSelection.selected().length > 0) {
|
||||||
return;
|
return;
|
||||||
@ -452,6 +470,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unlock(event: any) {
|
unlock(event: any) {
|
||||||
|
if (this.disabled) return;
|
||||||
this.locked = !this.locked;
|
this.locked = !this.locked;
|
||||||
this.onUnlock.emit();
|
this.onUnlock.emit();
|
||||||
this.lockedChange.emit(this.locked);
|
this.lockedChange.emit(this.locked);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user