Misc Enhancements (#1525)

* Moved the data connection for the Database out of appsettings.json and hardcoded it. This will allow for more customization and cleaner update process.

* Removed unneeded code

* Updated pdf viewer to 15.0.0 (pdf 2.6), which now supports east-asian fonts

* Fixed up some regex parsing for volumes that have a float number.

* Fixed a bug where the tooltip for Publication Status wouldn't show

* Fixed some weird parsing rules where v1.1 would parse as volume 1 chapter 1

* Fixed a bug where bookmarking button was hidden for admins without bookmark role (due to migration)

* Unified the star rating component in series detail to match metadata filter.

* Fixed a bug in the bulk selection code when using shift selection, where the inverse of what was selected would be toggled.

* Fixed some old code where if on all series page, only English as a language would return. We now return all languages of all libraries.

* Updated api/metadata/languages documentation

* Refactored some bookmark api names: get-bookmarks -> chapter-bookmarks, get-all-bookmarks -> all-bookmarks, get-series-bookmarks -> series-bookmarks, etc.

* Refactored all cases of createSeriesFilter to filterUtiltityService.

Added ability to search for a series on Bookmarks page.

Fixed a bug where people filters wouldn't respect the disable flag froms ettings.

* Cleaned up a bit of the circular downloader code.

* Implemented Russian Parsing

* Fixed an issue where some users that had a missing theme entry wouldn't be able to update their user preferences.

* Refactored normalization to exclude !, thus allowing series with ! to be different from each other.

* Fixed a migration exit case

* Fixed broken unit test
This commit is contained in:
Joseph Milazzo 2022-09-13 18:59:26 -05:00 committed by GitHub
parent b7d88f08d8
commit 00f0ad5a3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 508 additions and 419 deletions

View file

@ -12650,9 +12650,9 @@
}
},
"ngx-extended-pdf-viewer": {
"version": "14.5.3",
"resolved": "https://registry.npmjs.org/ngx-extended-pdf-viewer/-/ngx-extended-pdf-viewer-14.5.3.tgz",
"integrity": "sha512-9pqnbonKcu/6SIwPe3yCfHzsO1fgO7qIwETHD7UuS2kAG5GM7VkEwrqMoF7qsZ0Lq/rkqFBcGsS4GYW5JK+oEQ==",
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/ngx-extended-pdf-viewer/-/ngx-extended-pdf-viewer-15.0.2.tgz",
"integrity": "sha512-3cuJ87hqod8b/DiIjLNCYxLZYkfi+bm0PsjMFw4GnGfjKB7QJv0p/+KvrCdD68k18Aim5Sd5BMZhF2pHelp1mw==",
"requires": {
"lodash.deburr": "^4.1.0",
"tslib": "^2.3.0"

View file

@ -39,7 +39,7 @@
"lazysizes": "^5.3.2",
"ng-circle-progress": "^1.6.0",
"ngx-color-picker": "^12.0.0",
"ngx-extended-pdf-viewer": "^14.5.2",
"ngx-extended-pdf-viewer": "^15.0.0",
"ngx-file-drop": "^14.0.1",
"ngx-infinite-scroll": "^13.0.2",
"ngx-toastr": "^14.2.1",

View file

@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
import { Chapter } from '../_models/chapter';
import { CollectionTag } from '../_models/collection-tag';
import { Library } from '../_models/library';
import { MangaFormat } from '../_models/manga-format';
import { ReadingList } from '../_models/reading-list';
import { Series } from '../_models/series';
import { Volume } from '../_models/volume';
@ -271,13 +270,13 @@ export class ActionFactoryService {
action: Action.MarkAsRead,
title: 'Mark as Read',
callback: this.dummyCallback,
requiresAdmin: false
requiresAdmin: false
},
{
action: Action.MarkAsUnread,
title: 'Mark as Unread',
callback: this.dummyCallback,
requiresAdmin: false
requiresAdmin: false
},
{
action: Action.AddToReadingList,

View file

@ -1,4 +1,4 @@
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Location } from '@angular/common';
import { Router } from '@angular/router';
@ -10,6 +10,9 @@ import { MangaFormat } from '../_models/manga-format';
import { BookmarkInfo } from '../_models/manga-reader/bookmark-info';
import { PageBookmark } from '../_models/page-bookmark';
import { ProgressBookmark } from '../_models/progress-bookmark';
import { SeriesFilter } from '../_models/series-filter';
import { UtilityService } from '../shared/_services/utility.service';
import { FilterUtilitiesService } from '../shared/_services/filter-utilities.service';
export const CHAPTER_ID_DOESNT_EXIST = -1;
export const CHAPTER_ID_NOT_FETCHED = -2;
@ -24,7 +27,9 @@ export class ReaderService {
// Override background color for reader and restore it onDestroy
private originalBodyColor!: string;
constructor(private httpClient: HttpClient, private router: Router, private location: Location) { }
constructor(private httpClient: HttpClient, private router: Router,
private location: Location, private utilityService: UtilityService,
private filterUtilitySerivce: FilterUtilitiesService) { }
getNavigationArray(libraryId: number, seriesId: number, chapterId: number, format: MangaFormat) {
if (format === undefined) format = MangaFormat.ARCHIVE;
@ -50,20 +55,24 @@ export class ReaderService {
return this.httpClient.post(this.baseUrl + 'reader/unbookmark', {seriesId, volumeId, chapterId, page});
}
getAllBookmarks() {
return this.httpClient.get<PageBookmark[]>(this.baseUrl + 'reader/get-all-bookmarks');
getAllBookmarks(filter: SeriesFilter | undefined) {
let params = new HttpParams();
params = this.utilityService.addPaginationIfExists(params, undefined, undefined);
const data = this.filterUtilitySerivce.createSeriesFilter(filter);
return this.httpClient.post<PageBookmark[]>(this.baseUrl + 'reader/all-bookmarks', data);
}
getBookmarks(chapterId: number) {
return this.httpClient.get<PageBookmark[]>(this.baseUrl + 'reader/get-bookmarks?chapterId=' + chapterId);
return this.httpClient.get<PageBookmark[]>(this.baseUrl + 'reader/chapter-bookmarks?chapterId=' + chapterId);
}
getBookmarksForVolume(volumeId: number) {
return this.httpClient.get<PageBookmark[]>(this.baseUrl + 'reader/get-volume-bookmarks?volumeId=' + volumeId);
return this.httpClient.get<PageBookmark[]>(this.baseUrl + 'reader/volume-bookmarks?volumeId=' + volumeId);
}
getBookmarksForSeries(seriesId: number) {
return this.httpClient.get<PageBookmark[]>(this.baseUrl + 'reader/get-series-bookmarks?seriesId=' + seriesId);
return this.httpClient.get<PageBookmark[]>(this.baseUrl + 'reader/series-bookmarks?seriesId=' + seriesId);
}
clearBookmarks(seriesId: number) {

View file

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { FilterUtilitiesService } from '../shared/_services/filter-utilities.service';
import { UtilityService } from '../shared/_services/utility.service';
import { Chapter } from '../_models/chapter';
import { ChapterMetadata } from '../_models/chapter-metadata';
@ -26,12 +27,13 @@ export class SeriesService {
paginatedResults: PaginatedResult<Series[]> = new PaginatedResult<Series[]>();
paginatedSeriesForTagsResults: PaginatedResult<Series[]> = new PaginatedResult<Series[]>();
constructor(private httpClient: HttpClient, private imageService: ImageService, private utilityService: UtilityService) { }
constructor(private httpClient: HttpClient, private imageService: ImageService,
private utilityService: UtilityService, private filterUtilitySerivce: FilterUtilitiesService) { }
getAllSeries(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) {
let params = new HttpParams();
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
const data = this.createSeriesFilter(filter);
const data = this.filterUtilitySerivce.createSeriesFilter(filter);
return this.httpClient.post<PaginatedResult<Series[]>>(this.baseUrl + 'series/all', data, {observe: 'response', params}).pipe(
map((response: any) => {
@ -43,7 +45,7 @@ export class SeriesService {
getSeriesForLibrary(libraryId: number, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) {
let params = new HttpParams();
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
const data = this.createSeriesFilter(filter);
const data = this.filterUtilitySerivce.createSeriesFilter(filter);
return this.httpClient.post<PaginatedResult<Series[]>>(this.baseUrl + 'series?libraryId=' + libraryId, data, {observe: 'response', params}).pipe(
map((response: any) => {
@ -109,7 +111,7 @@ export class SeriesService {
}
getRecentlyAdded(libraryId: number = 0, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) {
const data = this.createSeriesFilter(filter);
const data = this.filterUtilitySerivce.createSeriesFilter(filter);
let params = new HttpParams();
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
@ -125,7 +127,7 @@ export class SeriesService {
}
getWantToRead(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter): Observable<PaginatedResult<Series[]>> {
const data = this.createSeriesFilter(filter);
const data = this.filterUtilitySerivce.createSeriesFilter(filter);
let params = new HttpParams();
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
@ -137,7 +139,7 @@ export class SeriesService {
}
getOnDeck(libraryId: number = 0, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) {
const data = this.createSeriesFilter(filter);
const data = this.filterUtilitySerivce.createSeriesFilter(filter);
let params = new HttpParams();
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
@ -204,41 +206,4 @@ export class SeriesService {
getSeriesDetail(seriesId: number) {
return this.httpClient.get<SeriesDetail>(this.baseUrl + 'series/series-detail?seriesId=' + seriesId);
}
createSeriesFilter(filter?: SeriesFilter) {
if (filter !== undefined) return filter;
const data: SeriesFilter = {
formats: [],
libraries: [],
genres: [],
writers: [],
artists: [],
penciller: [],
inker: [],
colorist: [],
letterer: [],
coverArtist: [],
editor: [],
publisher: [],
character: [],
translators: [],
collectionTags: [],
rating: 0,
readStatus: {
read: true,
inProgress: true,
notRead: true
},
sortOptions: null,
ageRating: [],
tags: [],
languages: [],
publicationStatus: [],
seriesNameQuery: '',
};
return data;
}
}

View file

@ -13,7 +13,6 @@ import { Series } from '../_models/series';
import { FilterEvent, SeriesFilter } from '../_models/series-filter';
import { Action } from '../_services/action-factory.service';
import { ActionService } from '../_services/action.service';
import { LibraryService } from '../_services/library.service';
import { EVENTS, Message, MessageHubService } from '../_services/message-hub.service';
import { SeriesService } from '../_services/series.service';
@ -86,14 +85,14 @@ export class AllSeriesComponent implements OnInit, OnDestroy {
private titleService: Title, private actionService: ActionService,
public bulkSelectionService: BulkSelectionService, private hubService: MessageHubService,
private utilityService: UtilityService, private route: ActivatedRoute,
private filterUtilityService: FilterUtilitiesService, private libraryService: LibraryService) {
private filterUtilityService: FilterUtilitiesService) {
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
this.titleService.setTitle('Kavita - All Series');
this.pagination = this.filterUtilityService.pagination(this.route.snapshot);
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
this.filterActiveCheck = this.seriesService.createSeriesFilter();
this.filterActiveCheck = this.filterUtilityService.createSeriesFilter();
}
ngOnInit(): void {

View file

@ -1,4 +1,4 @@
<app-side-nav-companion-bar [hasFilter]="false">
<app-side-nav-companion-bar [hasFilter]="true" [filterOpenByDefault]="filterSettings.openByDefault" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
<h2 title>
Bookmarks
</h2>
@ -8,9 +8,10 @@
<app-card-detail-layout
[isLoading]="loadingBookmarks"
[items]="series"
[filterSettings]="filterSettings"
[trackByIdentity]="trackByIdentity"
[refresh]="refresh"
(applyFilter)="updateFilter($event)"
>
<ng-template #cardItem let-item let-position="idx">
<app-card-item [entity]="item" (reload)="loadBookmarks()" [title]="item.name" [imageUrl]="imageService.getSeriesCoverImage(item.id)"

View file

@ -1,13 +1,17 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostListener, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { take, Subject } from 'rxjs';
import { BulkSelectionService } from 'src/app/cards/bulk-selection.service';
import { FilterSettings } from 'src/app/metadata-filter/filter-settings';
import { ConfirmService } from 'src/app/shared/confirm.service';
import { DownloadService } from 'src/app/shared/_services/download.service';
import { FilterUtilitiesService } from 'src/app/shared/_services/filter-utilities.service';
import { KEY_CODES } from 'src/app/shared/_services/utility.service';
import { PageBookmark } from 'src/app/_models/page-bookmark';
import { Pagination } from 'src/app/_models/pagination';
import { Series } from 'src/app/_models/series';
import { FilterEvent, SeriesFilter } from 'src/app/_models/series-filter';
import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service';
import { ImageService } from 'src/app/_services/image.service';
import { ReaderService } from 'src/app/_services/reader.service';
@ -29,6 +33,13 @@ export class BookmarksComponent implements OnInit, OnDestroy {
clearingSeries: {[id: number]: boolean} = {};
actions: ActionItem<Series>[] = [];
pagination!: Pagination;
filter: SeriesFilter | undefined = undefined;
filterSettings: FilterSettings = new FilterSettings();
filterOpen: EventEmitter<boolean> = new EventEmitter();
filterActive: boolean = false;
filterActiveCheck!: SeriesFilter;
trackByIdentity = (index: number, item: Series) => `${item.name}_${item.localizedName}_${item.pagesRead}`;
refresh: EventEmitter<void> = new EventEmitter();
@ -38,12 +49,25 @@ export class BookmarksComponent implements OnInit, OnDestroy {
private downloadService: DownloadService, private toastr: ToastrService,
private confirmService: ConfirmService, public bulkSelectionService: BulkSelectionService,
public imageService: ImageService, private actionFactoryService: ActionFactoryService,
private router: Router, private readonly cdRef: ChangeDetectorRef) { }
private router: Router, private readonly cdRef: ChangeDetectorRef,
private filterUtilityService: FilterUtilitiesService, private route: ActivatedRoute) {
this.filterSettings.ageRatingDisabled = true;
this.filterSettings.collectionDisabled = true;
this.filterSettings.formatDisabled = true;
this.filterSettings.genresDisabled = true;
this.filterSettings.languageDisabled = true;
this.filterSettings.libraryDisabled = true;
this.filterSettings.peopleDisabled = true;
this.filterSettings.publicationStatusDisabled = true;
this.filterSettings.ratingDisabled = true;
this.filterSettings.readProgressDisabled = true;
this.filterSettings.tagsDisabled = true;
this.filterSettings.sortDisabled = true;
}
ngOnInit(): void {
this.loadBookmarks();
this.actions = this.actionFactoryService.getBookmarkActions(this.handleAction.bind(this));
this.pagination = this.filterUtilityService.pagination(this.route.snapshot);
}
ngOnDestroy() {
@ -111,9 +135,15 @@ export class BookmarksComponent implements OnInit, OnDestroy {
}
loadBookmarks() {
// The filter is out of sync with the presets from typeaheads on first load but syncs afterwards
if (this.filter == undefined) {
this.filter = this.filterUtilityService.createSeriesFilter();
this.cdRef.markForCheck();
}
this.loadingBookmarks = true;
this.cdRef.markForCheck();
this.readerService.getAllBookmarks().pipe(take(1)).subscribe(bookmarks => {
this.readerService.getAllBookmarks(this.filter).pipe(take(1)).subscribe(bookmarks => {
this.bookmarks = bookmarks;
this.seriesIds = {};
this.bookmarks.forEach(bmk => {
@ -174,4 +204,11 @@ export class BookmarksComponent implements OnInit, OnDestroy {
});
}
updateFilter(data: FilterEvent) {
this.filter = data.filter;
if (!data.isFirst) this.filterUtilityService.updateUrlFromFilter(this.pagination, this.filter);
this.loadBookmarks();
}
}

View file

@ -1,5 +1,5 @@
import { ChangeDetectorRef, Injectable } from '@angular/core';
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
import { Injectable } from '@angular/core';
import { NavigationStart, Router } from '@angular/router';
import { ReplaySubject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Action, ActionFactoryService, ActionItem } from '../_services/action-factory.service';
@ -23,7 +23,6 @@ export class BulkSelectionService {
private selectedCards: { [key: string]: {[key: number]: boolean} } = {};
private dataSourceMax: { [key: string]: number} = {};
public isShiftDown: boolean = false;
private activeRoute: string = '';
private actionsSource = new ReplaySubject<ActionItem<any>[]>(1);
public actions$ = this.actionsSource.asObservable();
@ -34,14 +33,13 @@ export class BulkSelectionService {
*/
public selections$ = this.selectionsSource.asObservable();
constructor(private router: Router, private actionFactory: ActionFactoryService, private route: ActivatedRoute) {
constructor(router: Router, private actionFactory: ActionFactoryService) {
router.events
.pipe(filter(event => event instanceof NavigationStart))
.subscribe((event) => {
.subscribe(() => {
this.deselectAll();
this.dataSourceMax = {};
this.prevIndex = 0;
this.activeRoute = this.router.url;
});
}
@ -53,7 +51,7 @@ export class BulkSelectionService {
this.debugLog('Selecting ' + dataSource + ' cards from ' + this.prevIndex + ' to ' + index);
this.selectCards(dataSource, this.prevIndex, index, !wasSelected);
} else {
const isForwardSelection = index < this.prevIndex;
const isForwardSelection = index > this.prevIndex;
if (isForwardSelection) {
this.debugLog('Selecting ' + this.prevDataSource + ' cards from ' + this.prevIndex + ' to ' + this.dataSourceMax[this.prevDataSource]);

View file

@ -5,6 +5,7 @@ import { Router } from '@angular/router';
import { VirtualScrollerComponent } from '@iharbeck/ngx-virtual-scroller';
import { Subject } from 'rxjs';
import { FilterSettings } from 'src/app/metadata-filter/filter-settings';
import { FilterUtilitiesService } from 'src/app/shared/_services/filter-utilities.service';
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
import { JumpKey } from 'src/app/_models/jumpbar/jump-key';
import { Library } from 'src/app/_models/library';
@ -71,10 +72,10 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges {
return Breakpoint;
}
constructor(private seriesService: SeriesService, public utilityService: UtilityService,
constructor(private filterUtilitySerivce: FilterUtilitiesService, public utilityService: UtilityService,
@Inject(DOCUMENT) private document: Document, private changeDetectionRef: ChangeDetectorRef,
private jumpbarService: JumpbarService, private router: Router) {
this.filter = this.seriesService.createSeriesFilter();
this.filter = this.filterUtilitySerivce.createSeriesFilter();
this.changeDetectionRef.markForCheck();
}

View file

@ -29,7 +29,6 @@ import { DownloadIndicatorComponent } from './download-indicator/download-indica
@NgModule({
declarations: [
CardItemComponent,
@ -68,7 +67,6 @@ import { DownloadIndicatorComponent } from './download-indicator/download-indica
VirtualScrollerModule,
NgbOffcanvasModule, // Series Detail, action of cards
NgbNavModule, //Series Detail
NgbPaginationModule, // EditCollectionTagsComponent

View file

@ -31,7 +31,7 @@
<ng-container>
<div class="d-none d-md-block col-lg-1 col-md-4 col-sm-4 col-4 mb-2">
<ng-container *ngIf="seriesMetadata.publicationStatus | publicationStatus as pubStatus">
<app-icon-and-title label="Publication" [clickable]="true" fontClasses="fa-solid fa-hourglass-{{pubStatus === 'Ongoing' ? 'empty' : 'end'}}" (click)="handleGoTo(FilterQueryParam.PublicationStatus, seriesMetadata.publicationStatus)" title="Publication Status ({{seriesMetadata.maxCount}} / {{seriesMetadata.totalCount}})">
<app-icon-and-title label="Publication" [clickable]="true" fontClasses="fa-solid fa-hourglass-{{pubStatus === 'Ongoing' ? 'empty' : 'end'}}" (click)="handleGoTo(FilterQueryParam.PublicationStatus, seriesMetadata.publicationStatus)" ngbTooltip="Publication Status ({{seriesMetadata.maxCount}} / {{seriesMetadata.totalCount}})">
{{pubStatus}}
</app-icon-and-title>
</ng-container>

View file

@ -140,7 +140,7 @@ export class CollectionDetailComponent implements OnInit, OnDestroy, AfterConten
this.seriesPagination = this.filterUtilityService.pagination(this.route.snapshot);
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
this.filterSettings.presets.collectionTags = [tagId];
this.filterActiveCheck = this.seriesService.createSeriesFilter();
this.filterActiveCheck = this.filterUtilityService.createSeriesFilter();
this.filterActiveCheck.collectionTags = [tagId];
this.cdRef.markForCheck();

View file

@ -134,7 +134,7 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
if (this.filterSettings.presets) this.filterSettings.presets.libraries = [this.libraryId];
// Setup filterActiveCheck to check filter against
this.filterActiveCheck = this.seriesService.createSeriesFilter();
this.filterActiveCheck = this.filterUtilityService.createSeriesFilter();
this.filterActiveCheck.libraries = [this.libraryId];
this.filterSettings.libraryDisabled = true;
@ -230,7 +230,7 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
loadPage() {
// The filter is out of sync with the presets from typeaheads on first load but syncs afterwards
if (this.filter == undefined) {
this.filter = this.seriesService.createSeriesFilter();
this.filter = this.filterUtilityService.createSeriesFilter();
this.filter.libraries.push(this.libraryId);
this.cdRef.markForCheck();
}

View file

@ -468,7 +468,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
this.user = user;
this.hasBookmarkRights = this.accountService.hasBookmarkRole(user);
this.hasBookmarkRights = this.accountService.hasBookmarkRole(user) || this.accountService.hasAdminRole(user);
this.readingDirection = this.user.preferences.readingDirection;
this.scalingOption = this.user.preferences.scalingOption;
this.pageSplitOption = this.user.preferences.pageSplitOption;

View file

@ -99,7 +99,7 @@
<div class="mb-3">
<label for="cover-artist" class="form-label">Cover Artists</label>
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.CoverArtist)" [settings]="getPersonsSettings(PersonRole.CoverArtist)"
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.CoverArtist)">
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.CoverArtist) || filterSettings.peopleDisabled">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -114,7 +114,7 @@
<div class="mb-3">
<label for="writers" class="form-label">Writers</label>
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Writer)" [settings]="getPersonsSettings(PersonRole.Writer)"
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Writer)">
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Writer) || filterSettings.peopleDisabled">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -129,7 +129,7 @@
<div class="mb-3">
<label for="publisher" class="form-label">Publisher</label>
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Publisher)" [settings]="getPersonsSettings(PersonRole.Publisher)"
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Publisher)">
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Publisher) || filterSettings.peopleDisabled">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -144,7 +144,7 @@
<div class="mb-3">
<label for="penciller" class="form-label">Penciller</label>
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Penciller)" [settings]="getPersonsSettings(PersonRole.Penciller)"
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Penciller)">
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Penciller) || filterSettings.peopleDisabled">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -159,7 +159,7 @@
<div class="mb-3">
<label for="letterer" class="form-label">Letterer</label>
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Letterer)" [settings]="getPersonsSettings(PersonRole.Letterer)"
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Letterer)">
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Letterer) || filterSettings.peopleDisabled">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -174,7 +174,7 @@
<div class="mb-3">
<label for="inker" class="form-label">Inker</label>
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Inker)" [settings]="getPersonsSettings(PersonRole.Inker)"
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Inker)">
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Inker) || filterSettings.peopleDisabled">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -189,7 +189,7 @@
<div class="mb-3">
<label for="editor" class="form-label">Editor</label>
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Editor)" [settings]="getPersonsSettings(PersonRole.Editor)"
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Editor)">
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Editor) || filterSettings.peopleDisabled">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -204,7 +204,7 @@
<div class="mb-3">
<label for="colorist" class="form-label">Colorist</label>
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Colorist)" [settings]="getPersonsSettings(PersonRole.Colorist)"
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Colorist)">
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Colorist) || filterSettings.peopleDisabled">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -219,7 +219,7 @@
<div class="mb-3">
<label for="character" class="form-label">Character</label>
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Character)" [settings]="getPersonsSettings(PersonRole.Character)"
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Character)">
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Character) || filterSettings.peopleDisabled">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -234,7 +234,7 @@
<div class="mb-3">
<label for="translators" class="form-label">Translators</label>
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Translator)" [settings]="getPersonsSettings(PersonRole.Translator)"
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Translator)">
[reset]="resetTypeaheads" [disabled]="!peopleSettings.hasOwnProperty(PersonRole.Translator) || filterSettings.peopleDisabled">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>

View file

@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Ev
import { FormControl, FormGroup } from '@angular/forms';
import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap';
import { distinctUntilChanged, forkJoin, map, Observable, of, ReplaySubject, Subject, takeUntil } from 'rxjs';
import { FilterUtilitiesService } from '../shared/_services/filter-utilities.service';
import { UtilityService } from '../shared/_services/utility.service';
import { TypeaheadSettings } from '../typeahead/typeahead-settings';
import { CollectionTag } from '../_models/collection-tag';
@ -17,7 +18,6 @@ import { Tag } from '../_models/tag';
import { CollectionTagService } from '../_services/collection-tag.service';
import { LibraryService } from '../_services/library.service';
import { MetadataService } from '../_services/metadata.service';
import { SeriesService } from '../_services/series.service';
import { ToggleService } from '../_services/toggle.service';
import { FilterSettings } from './filter-settings';
@ -86,9 +86,9 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
return SortField;
}
constructor(private libraryService: LibraryService, private metadataService: MetadataService, private seriesService: SeriesService,
private utilityService: UtilityService, private collectionTagService: CollectionTagService, public toggleService: ToggleService,
private readonly cdRef: ChangeDetectorRef) {
constructor(private libraryService: LibraryService, private metadataService: MetadataService, private utilityService: UtilityService,
private collectionTagService: CollectionTagService, public toggleService: ToggleService,
private readonly cdRef: ChangeDetectorRef, private filterUtilitySerivce: FilterUtilitiesService) {
}
ngOnInit(): void {
@ -105,7 +105,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
});
}
this.filter = this.seriesService.createSeriesFilter();
this.filter = this.filterUtilitySerivce.createSeriesFilter();
this.readProgressGroup = new FormGroup({
read: new FormControl({value: this.filter.readStatus.read, disabled: this.filterSettings.readProgressDisabled}, []),
notRead: new FormControl({value: this.filter.readStatus.notRead, disabled: this.filterSettings.readProgressDisabled}, []),
@ -601,7 +601,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy {
}
clear() {
this.filter = this.seriesService.createSeriesFilter();
this.filter = this.filterUtilitySerivce.createSeriesFilter();
this.readProgressGroup.get('read')?.setValue(true);
this.readProgressGroup.get('notRead')?.setValue(true);
this.readProgressGroup.get('inProgress')?.setValue(true);

View file

@ -18,7 +18,7 @@ import { TypeaheadModule } from '../typeahead/typeahead.module';
NgbRatingModule,
NgbCollapseModule,
SharedModule,
TypeaheadModule
TypeaheadModule,
],
exports: [
MetadataFilterComponent

View file

@ -66,7 +66,7 @@
<li class="list-group-item dark-menu-item">
<div class="d-inline-flex">
<span class="download">
<app-circular-loader [currentValue]="25" [maxValue]="100" fontSize="16px" [showIcon]="true" width="25px" height="unset" [center]="false"></app-circular-loader>
<app-circular-loader [currentValue]="25" fontSize="16px" [showIcon]="true" width="25px" height="unset" [center]="false"></app-circular-loader>
<span class="visually-hidden" role="status">
10% downloaded
</span>

View file

@ -97,12 +97,16 @@
</button>
</div>
<div class="col-auto ms-2">
<ngb-rating class="rating-star" [(rate)]="series.userRating" (rateChange)="updateRating($event)" (click)="promptToReview()"></ngb-rating>
<button *ngIf="series.userRating || series.userRating" class="btn btn-sm btn-icon" (click)="openReviewModal(true)" placement="bottom" ngbTooltip="Edit Review" aria-label="Edit Review"><i class="fa fa-pen" aria-hidden="true"></i></button>
<ngb-rating class="rating-star" [(rate)]="series.userRating" (rateChange)="updateRating($event)" (click)="promptToReview()" [resettable]="false">
<ng-template let-fill="fill" let-index="index">
<span class="star" [class.filled]="(index < series.userRating) && series.userRating > 0">&#9733;</span>
</ng-template>
</ngb-rating>
<button *ngIf="series.userRating || series.userReview" class="btn btn-sm btn-icon" (click)="openReviewModal(true)" placement="bottom" ngbTooltip="Edit Review" aria-label="Edit Review"><i class="fa fa-pen" aria-hidden="true"></i></button>
</div>
</div>
<div class="row g-0">
<!-- TODO: This will be the first of reviews section. Reviews will show your plus other peoples reviews in media cards like Plex does and this will be below metadata -->
<!-- TODO: This will be the first of reviews section. Reviews will show your plus other peoples reviews in media cards like Plex does and this will be below metadata. NOTE: We need to clean the reviews in case any html is in there.-->
<app-read-more class="user-review {{userReview ? 'mt-1' : ''}}" [text]="series.userReview || ''" [maxLength]="250"></app-read-more>
</div>
<div *ngIf="seriesMetadata" class="mt-2">

View file

@ -3,7 +3,7 @@ import { Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { NgbModal, NgbNavChangeEvent, NgbOffcanvas } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { forkJoin, Subject, tap } from 'rxjs';
import { forkJoin, Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
import { BulkSelectionService } from '../cards/bulk-selection.service';
import { EditSeriesModalComponent } from '../cards/_modals/edit-series-modal/edit-series-modal.component';

View file

@ -12,7 +12,6 @@ import { ReactiveFormsModule } from '@angular/forms';
import { SharedSideNavCardsModule } from '../shared-side-nav-cards/shared-side-nav-cards.module';
@NgModule({
declarations: [
SeriesDetailComponent,

View file

@ -42,7 +42,7 @@ export enum FilterQueryParam {
})
export class FilterUtilitiesService {
constructor(private route: ActivatedRoute, private seriesService: SeriesService) { }
constructor() { }
/**
* Updates the window location with a custom url based on filter and pagination objects
@ -145,7 +145,7 @@ export class FilterUtilitiesService {
* @returns The Preset filter and if something was set within
*/
filterPresetsFromUrl(snapshot: ActivatedRouteSnapshot): [SeriesFilter, boolean] {
const filter = this.seriesService.createSeriesFilter();
const filter = this.createSeriesFilter();
let anyChanged = false;
const format = snapshot.queryParamMap.get(FilterQueryParam.Format);
@ -305,4 +305,39 @@ export class FilterUtilitiesService {
return [filter, false]; // anyChanged. Testing out if having a filter active but keep drawer closed by default works better
}
createSeriesFilter(filter?: SeriesFilter) {
if (filter !== undefined) return filter;
const data: SeriesFilter = {
formats: [],
libraries: [],
genres: [],
writers: [],
artists: [],
penciller: [],
inker: [],
colorist: [],
letterer: [],
coverArtist: [],
editor: [],
publisher: [],
character: [],
translators: [],
collectionTags: [],
rating: 0,
readStatus: {
read: true,
inProgress: true,
notRead: true
},
sortOptions: null,
ageRating: [],
tags: [],
languages: [],
publicationStatus: [],
seriesNameQuery: '',
};
return data;
}
}

View file

@ -11,7 +11,7 @@
[space] = "0"
[backgroundPadding]="0"
outerStrokeLinecap="butt"
[outerStrokeColor]="'#4ac694'"
[outerStrokeColor]="outerStrokeColor"
[innerStrokeColor]="innerStrokeColor"
titleFontSize= "24"
unitsFontSize= "24"
@ -21,7 +21,7 @@
[startFromZero]="false"
[responsive]="true"
[backgroundOpacity]="0.5"
[backgroundColor]="'#000'"
[backgroundColor]="backgroundColor"
></circle-progress>
</div>
</ng-container>

View file

@ -1,13 +1,14 @@
.number {
position: absolute;
top:50%;
left:50%;
font-size:18px;
top: 50%;
left: 50%;
font-size: 18px;
}
.indicator {
font-weight:500;
z-index:10;
font-weight: 500;
margin-left: 2px;
z-index: 10;
color: var(--primary-color);
animation: MoveUpDown 1s linear infinite;
}

View file

@ -9,10 +9,23 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
export class CircularLoaderComponent {
@Input() currentValue: number = 0;
@Input() maxValue: number = 0;
/**
* If an animation should be used
*/
@Input() animation: boolean = true;
/**
* Color of an inner bar
*/
@Input() innerStrokeColor: string = 'transparent';
/**
* Color of the Downloader bar
*/
@Input() outerStrokeColor: string = '#4ac694';
@Input() backgroundColor: string = '#000';
@Input() fontSize: string = '36px';
/**
* Show the icon inside the downloader
*/
@Input() showIcon: boolean = true;
/**
* The width in pixels of the loader

View file

@ -85,7 +85,7 @@ export class WantToReadComponent implements OnInit, OnDestroy {
this.seriesPagination = this.filterUtilityService.pagination(this.route.snapshot);
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
this.filterActiveCheck = this.seriesService.createSeriesFilter();
this.filterActiveCheck = this.filterUtilityService.createSeriesFilter();
this.cdRef.markForCheck();
this.hubService.messages$.pipe(takeUntil(this.onDestroy)).subscribe((event) => {

View file

@ -14,6 +14,10 @@
<meta name="msapplication-config" content="assets/icons/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
</head>
<body class="mat-typography" theme="dark">
<app-root></app-root>

View file

@ -1,5 +1,6 @@
@use '../node_modules/swiper/swiper.scss' as swiper;
// Import themes which define the css variables we use to customize the app
@import './theme/themes/dark';

View file

@ -240,4 +240,6 @@
/* List Card Item */
--card-list-item-bg-color: linear-gradient(180deg, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0.15) 1%, rgba(0,0,0,0) 100%);
}