More Bugfixes (#2874)

This commit is contained in:
Joe Milazzo 2024-04-14 17:37:22 -05:00 committed by GitHub
parent f02e1f7d1f
commit 6d9a5d8f65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 303 additions and 108 deletions

View File

@ -176,6 +176,7 @@ public class SeriesController : BaseApiController
return Ok(await _unitOfWork.ChapterRepository.AddChapterModifiers(User.GetUserId(), chapter)); return Ok(await _unitOfWork.ChapterRepository.AddChapterModifiers(User.GetUserId(), chapter));
} }
[Obsolete("All chapter entities will load this data by default. Will not be maintained as of v0.8.1")]
[HttpGet("chapter-metadata")] [HttpGet("chapter-metadata")]
public async Task<ActionResult<ChapterMetadataDto>> GetChapterMetadata(int chapterId) public async Task<ActionResult<ChapterMetadataDto>> GetChapterMetadata(int chapterId)
{ {

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using API.DTOs.Metadata;
using API.Entities.Enums; using API.Entities.Enums;
using API.Entities.Interfaces; using API.Entities.Interfaces;
@ -120,4 +121,42 @@ public class ChapterDto : IHasReadTimeEstimate
/// </summary> /// </summary>
/// <remarks>This is guaranteed to be Valid</remarks> /// <remarks>This is guaranteed to be Valid</remarks>
public string ISBN { get; set; } public string ISBN { get; set; }
#region Metadata
public ICollection<PersonDto> Writers { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> CoverArtists { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Publishers { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Characters { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Pencillers { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Inkers { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Imprints { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Colorists { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Letterers { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Editors { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Translators { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Teams { get; set; } = new List<PersonDto>();
public ICollection<PersonDto> Locations { get; set; } = new List<PersonDto>();
public ICollection<GenreTagDto> Genres { get; set; } = new List<GenreTagDto>();
/// <summary>
/// Collection of all Tags from underlying chapters for a Series
/// </summary>
public ICollection<TagDto> Tags { get; set; } = new List<TagDto>();
public PublicationStatus PublicationStatus { get; set; }
/// <summary>
/// Language for the Chapter/Issue
/// </summary>
public string? Language { get; set; }
/// <summary>
/// Number in the TotalCount of issues
/// </summary>
public int Count { get; set; }
/// <summary>
/// Total number of issues for the series
/// </summary>
public int TotalCount { get; set; }
#endregion
} }

View File

@ -51,7 +51,6 @@ public class AutoMapperProfiles : Profile
CreateMap<Volume, VolumeDto>() CreateMap<Volume, VolumeDto>()
.ForMember(dest => dest.Number, opt => opt.MapFrom(src => (int) src.MinNumber)); .ForMember(dest => dest.Number, opt => opt.MapFrom(src => (int) src.MinNumber));
CreateMap<MangaFile, MangaFileDto>(); CreateMap<MangaFile, MangaFileDto>();
CreateMap<Chapter, ChapterDto>();
CreateMap<Series, SeriesDto>(); CreateMap<Series, SeriesDto>();
CreateMap<CollectionTag, CollectionTagDto>(); CreateMap<CollectionTag, CollectionTagDto>();
CreateMap<AppUserCollection, AppUserCollectionDto>() CreateMap<AppUserCollection, AppUserCollectionDto>()
@ -191,6 +190,48 @@ public class AutoMapperProfiles : Profile
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Location).OrderBy(p => p.NormalizedName))) opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Location).OrderBy(p => p.NormalizedName)))
; ;
CreateMap<Chapter, ChapterDto>()
.ForMember(dest => dest.Writers,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Writer).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.CoverArtists,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.CoverArtist).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Colorists,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Colorist).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Inkers,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Inker).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Imprints,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Imprint).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Letterers,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Letterer).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Pencillers,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Penciller).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Publishers,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Publisher).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Translators,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Translator).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Characters,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Character).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Editors,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Editor).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Teams,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Team).OrderBy(p => p.NormalizedName)))
.ForMember(dest => dest.Locations,
opt =>
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Location).OrderBy(p => p.NormalizedName)))
;
CreateMap<AppUser, UserDto>() CreateMap<AppUser, UserDto>()
.ForMember(dest => dest.AgeRestriction, .ForMember(dest => dest.AgeRestriction,
opt => opt =>

View File

@ -191,7 +191,14 @@ public class CacheService : ICacheService
if (files.Count > 0 && files[0].Format == MangaFormat.Image) if (files.Count > 0 && files[0].Format == MangaFormat.Image)
{ {
_readingItemService.Extract(files[0].FilePath, extractPath, MangaFormat.Image, files.Count); foreach (var file in files)
{
if (fileCount > 1)
{
extraPath = file.Id + string.Empty;
}
_readingItemService.Extract(file.FilePath, Path.Join(extractPath, extraPath), MangaFormat.Image, files.Count);
}
_directoryService.Flatten(extractDi.FullName); _directoryService.Flatten(extractDi.FullName);
} }

View File

@ -1,5 +1,9 @@
import { MangaFile } from './manga-file'; import { MangaFile } from './manga-file';
import { AgeRating } from './metadata/age-rating'; import { AgeRating } from './metadata/age-rating';
import {PublicationStatus} from "./metadata/publication-status";
import {Genre} from "./metadata/genre";
import {Tag} from "./tag";
import {Person} from "./metadata/person";
export const LooseLeafOrDefaultNumber = -100000; export const LooseLeafOrDefaultNumber = -100000;
export const SpecialVolumeNumber = 100000; export const SpecialVolumeNumber = 100000;
@ -51,4 +55,28 @@ export interface Chapter {
isbn: string; isbn: string;
lastReadingProgress: string; lastReadingProgress: string;
sortOrder: number; sortOrder: number;
// originally in ChapterMetadata but now inlined with Chapter data
year: string;
language: string;
publicationStatus: PublicationStatus;
count: number;
totalCount: number;
genres: Array<Genre>;
tags: Array<Tag>;
writers: Array<Person>;
coverArtists: Array<Person>;
publishers: Array<Person>;
characters: Array<Person>;
pencillers: Array<Person>;
inkers: Array<Person>;
imprints: Array<Person>;
colorists: Array<Person>;
letterers: Array<Person>;
editors: Array<Person>;
translators: Array<Person>;
teams: Array<Person>;
locations: Array<Person>;
} }

View File

@ -1,42 +0,0 @@
import { Genre } from "./genre";
import { AgeRating } from "./age-rating";
import { PublicationStatus } from "./publication-status";
import { Person } from "./person";
import { Tag } from "../tag";
export interface ChapterMetadata {
id: number;
chapterId: number;
title: string;
year: string;
ageRating: AgeRating;
releaseDate: string;
language: string;
publicationStatus: PublicationStatus;
summary: string;
count: number;
totalCount: number;
wordCount: number;
genres: Array<Genre>;
tags: Array<Tag>;
writers: Array<Person>;
coverArtists: Array<Person>;
publishers: Array<Person>;
characters: Array<Person>;
pencillers: Array<Person>;
inkers: Array<Person>;
imprints: Array<Person>;
colorists: Array<Person>;
letterers: Array<Person>;
editors: Array<Person>;
translators: Array<Person>;
teams: Array<Person>;
locations: Array<Person>;
}

View File

@ -5,8 +5,6 @@ import { map } from 'rxjs/operators';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { UtilityService } from '../shared/_services/utility.service'; import { UtilityService } from '../shared/_services/utility.service';
import { Chapter } from '../_models/chapter'; import { Chapter } from '../_models/chapter';
import { ChapterMetadata } from '../_models/metadata/chapter-metadata';
import { UserCollection } from '../_models/collection-tag';
import { PaginatedResult } from '../_models/pagination'; import { PaginatedResult } from '../_models/pagination';
import { Series } from '../_models/series'; import { Series } from '../_models/series';
import { RelatedSeries } from '../_models/series-detail/related-series'; import { RelatedSeries } from '../_models/series-detail/related-series';
@ -75,10 +73,6 @@ export class SeriesService {
return this.httpClient.get<Chapter>(this.baseUrl + 'series/chapter?chapterId=' + chapterId); return this.httpClient.get<Chapter>(this.baseUrl + 'series/chapter?chapterId=' + chapterId);
} }
getChapterMetadata(chapterId: number) {
return this.httpClient.get<ChapterMetadata>(this.baseUrl + 'series/chapter-metadata?chapterId=' + chapterId);
}
delete(seriesId: number) { delete(seriesId: number) {
return this.httpClient.delete<string>(this.baseUrl + 'series/' + seriesId, TextResonse).pipe(map(s => s === "true")); return this.httpClient.delete<string>(this.baseUrl + 'series/' + seriesId, TextResonse).pipe(map(s => s === "true"));
} }

View File

@ -39,7 +39,7 @@
<li [ngbNavItem]="tabs[TabID.Metadata]"> <li [ngbNavItem]="tabs[TabID.Metadata]">
<a ngbNavLink>{{t(tabs[TabID.Metadata].title)}}</a> <a ngbNavLink>{{t(tabs[TabID.Metadata].title)}}</a>
<ng-template ngbNavContent> <ng-template ngbNavContent>
<app-chapter-metadata-detail [chapter]="chapterMetadata"></app-chapter-metadata-detail> <app-chapter-metadata-detail [chapter]="chapter"></app-chapter-metadata-detail>
</ng-template> </ng-template>
</li> </li>

View File

@ -21,7 +21,6 @@ import { Observable, of, map, shareReplay } from 'rxjs';
import { DownloadService } from 'src/app/shared/_services/download.service'; import { DownloadService } from 'src/app/shared/_services/download.service';
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service'; import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
import {Chapter, LooseLeafOrDefaultNumber} from 'src/app/_models/chapter'; import {Chapter, LooseLeafOrDefaultNumber} from 'src/app/_models/chapter';
import { ChapterMetadata } from 'src/app/_models/metadata/chapter-metadata';
import { Device } from 'src/app/_models/device/device'; import { Device } from 'src/app/_models/device/device';
import { LibraryType } from 'src/app/_models/library/library'; import { LibraryType } from 'src/app/_models/library/library';
import { MangaFile } from 'src/app/_models/manga-file'; import { MangaFile } from 'src/app/_models/manga-file';
@ -48,7 +47,7 @@ import {BytesPipe} from "../../_pipes/bytes.pipe";
import {BadgeExpanderComponent} from "../../shared/badge-expander/badge-expander.component"; import {BadgeExpanderComponent} from "../../shared/badge-expander/badge-expander.component";
import {TagBadgeComponent} from "../../shared/tag-badge/tag-badge.component"; import {TagBadgeComponent} from "../../shared/tag-badge/tag-badge.component";
import {PersonBadgeComponent} from "../../shared/person-badge/person-badge.component"; import {PersonBadgeComponent} from "../../shared/person-badge/person-badge.component";
import {translate, TranslocoDirective, TranslocoService} from "@ngneat/transloco"; import {translate, TranslocoDirective} from "@ngneat/transloco";
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component"; import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
import {EditChapterProgressComponent} from "../edit-chapter-progress/edit-chapter-progress.component"; import {EditChapterProgressComponent} from "../edit-chapter-progress/edit-chapter-progress.component";
@ -113,9 +112,7 @@ export class CardDetailDrawerComponent implements OnInit {
]; ];
active = this.tabs[0]; active = this.tabs[0];
chapterMetadata: ChapterMetadata | undefined;
summary: string = ''; summary: string = '';
downloadInProgress: boolean = false; downloadInProgress: boolean = false;
@ -139,10 +136,6 @@ export class CardDetailDrawerComponent implements OnInit {
this.isChapter = this.utilityService.isChapter(this.data); this.isChapter = this.utilityService.isChapter(this.data);
this.chapter = this.utilityService.isChapter(this.data) ? (this.data as Chapter) : (this.data as Volume).chapters[0]; this.chapter = this.utilityService.isChapter(this.data) ? (this.data as Chapter) : (this.data as Volume).chapters[0];
this.seriesService.getChapterMetadata(this.chapter.id).subscribe(metadata => {
this.chapterMetadata = metadata;
this.cdRef.markForCheck();
});
if (this.isChapter) { if (this.isChapter) {
this.coverImageUrl = this.imageService.getChapterCoverImage(this.data.id); this.coverImageUrl = this.imageService.getChapterCoverImage(this.data.id);

View File

@ -171,8 +171,10 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
hasCustomSort() { hasCustomSort() {
if (this.filteringDisabled) return false; if (this.filteringDisabled) return false;
return this.filter?.sortOptions?.sortField != SortField.SortName || !this.filter?.sortOptions.isAscending const hasCustomSort = this.filter?.sortOptions?.sortField != SortField.SortName || !this.filter?.sortOptions.isAscending;
|| this.filterSettings?.presetsV2?.sortOptions?.sortField != SortField.SortName || !this.filterSettings?.presetsV2?.sortOptions?.isAscending; const hasNonDefaultSortField = this.filterSettings?.presetsV2?.sortOptions?.sortField != SortField.SortName;
return hasCustomSort;
} }
performAction(action: ActionItem<any>) { performAction(action: ActionItem<any>) {

View File

@ -1,9 +1,9 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChapterMetadata } from 'src/app/_models/metadata/chapter-metadata';
import {CommonModule} from "@angular/common"; import {CommonModule} from "@angular/common";
import {BadgeExpanderComponent} from "../../shared/badge-expander/badge-expander.component"; import {BadgeExpanderComponent} from "../../shared/badge-expander/badge-expander.component";
import {PersonBadgeComponent} from "../../shared/person-badge/person-badge.component"; import {PersonBadgeComponent} from "../../shared/person-badge/person-badge.component";
import {TranslocoDirective} from "@ngneat/transloco"; import {TranslocoDirective} from "@ngneat/transloco";
import {Chapter} from "../../_models/chapter";
@Component({ @Component({
selector: 'app-chapter-metadata-detail', selector: 'app-chapter-metadata-detail',
@ -14,5 +14,5 @@ import {TranslocoDirective} from "@ngneat/transloco";
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ChapterMetadataDetailComponent { export class ChapterMetadataDetailComponent {
@Input() chapter: ChapterMetadata | undefined; @Input() chapter: Chapter | undefined;
} }

View File

@ -1,13 +1,13 @@
<ng-container *transloco="let t; read: 'entity-info-cards'"> <ng-container *transloco="let t; read: 'entity-info-cards'">
<div class="mt-3 mb-3"> <div class="mt-3 mb-3">
<div class="row g-0" *ngIf="chapterMetadata "> <div class="row g-0" *ngIf="chapter ">
<!-- Tags and Characters are used a lot of Hentai and Doujinshi type content, so showing in list item has value add on first glance --> <!-- Tags and Characters are used a lot of Hentai and Doujinshi type content, so showing in list item has value add on first glance -->
<app-metadata-detail [tags]="chapterMetadata.tags" [libraryId]="libraryId" [queryParam]="FilterField.Tags" heading="Tags"> <app-metadata-detail [tags]="chapter.tags" [libraryId]="libraryId" [queryParam]="FilterField.Tags" heading="Tags">
<ng-template #titleTemplate let-item>{{item.title}}</ng-template> <ng-template #titleTemplate let-item>{{item.title}}</ng-template>
</app-metadata-detail> </app-metadata-detail>
<app-metadata-detail [tags]="chapterMetadata.characters" [libraryId]="libraryId" [queryParam]="FilterField.Characters" heading="Characters"> <app-metadata-detail [tags]="chapter.characters" [libraryId]="libraryId" [queryParam]="FilterField.Characters" heading="Characters">
<ng-template #titleTemplate let-item>{{item.name}}</ng-template> <ng-template #titleTemplate let-item>{{item.name}}</ng-template>
</app-metadata-detail> </app-metadata-detail>
</div> </div>

View File

@ -8,7 +8,6 @@ import {
} from '@angular/core'; } from '@angular/core';
import { UtilityService } from 'src/app/shared/_services/utility.service'; import { UtilityService } from 'src/app/shared/_services/utility.service';
import { Chapter } from 'src/app/_models/chapter'; import { Chapter } from 'src/app/_models/chapter';
import { ChapterMetadata } from 'src/app/_models/metadata/chapter-metadata';
import { HourEstimateRange } from 'src/app/_models/series-detail/hour-estimate-range'; import { HourEstimateRange } from 'src/app/_models/series-detail/hour-estimate-range';
import { MangaFormat } from 'src/app/_models/manga-format'; import { MangaFormat } from 'src/app/_models/manga-format';
import { AgeRating } from 'src/app/_models/metadata/age-rating'; import { AgeRating } from 'src/app/_models/metadata/age-rating';
@ -51,10 +50,6 @@ export class EntityInfoCardsComponent implements OnInit {
@Input({required: true}) entity!: Volume | Chapter; @Input({required: true}) entity!: Volume | Chapter;
@Input({required: true}) libraryId!: number; @Input({required: true}) libraryId!: number;
/**
* This will pull extra information
*/
@Input() includeMetadata: boolean = false;
/** /**
* Hide more system based fields, like id or Date Added * Hide more system based fields, like id or Date Added
@ -64,7 +59,6 @@ export class EntityInfoCardsComponent implements OnInit {
isChapter = false; isChapter = false;
chapter!: Chapter; chapter!: Chapter;
chapterMetadata!: ChapterMetadata;
ageRating!: string; ageRating!: string;
totalPages: number = 0; totalPages: number = 0;
totalWordCount: number = 0; totalWordCount: number = 0;
@ -94,12 +88,6 @@ export class EntityInfoCardsComponent implements OnInit {
}, 0); }, 0);
} }
if (this.includeMetadata) {
this.seriesService.getChapterMetadata(this.chapter.id).subscribe(metadata => {
this.chapterMetadata = metadata;
this.cdRef.markForCheck();
});
}
this.totalPages = this.chapter.pages; this.totalPages = this.chapter.pages;
if (!this.isChapter) { if (!this.isChapter) {

View File

@ -31,7 +31,7 @@
</div> </div>
</ng-container> </ng-container>
<div class="ps-2 d-none d-md-inline-block" style="width: 100%"> <div class="ps-2 d-none d-md-inline-block" style="width: 100%">
<app-entity-info-cards [entity]="entity" [libraryId]="libraryId" [includeMetadata]="ShowExtended" [showExtendedProperties]="ShowExtended"></app-entity-info-cards> <app-entity-info-cards [entity]="entity" [libraryId]="libraryId" [showExtendedProperties]="ShowExtended"></app-entity-info-cards>
</div> </div>
</div> </div>
</div> </div>

View File

@ -30,6 +30,7 @@
<div infinite-scroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="50"> <div infinite-scroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="50">
<ng-container *ngFor="let item of webtoonImages | async; let index = index;"> <ng-container *ngFor="let item of webtoonImages | async; let index = index;">
<img src="{{item.src}}" style="display: block" <img src="{{item.src}}" style="display: block"
[style.filter]="(darkness$ | async) ?? '' | safeStyle"
class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}}" class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}}"
rel="nofollow" alt="image" (load)="onImageLoad($event)" id="page-{{item.page}}" [attr.page]="item.page" ondragstart="return false;" onselectstart="return false;"> rel="nofollow" alt="image" (load)="onImageLoad($event)" id="page-{{item.page}}" [attr.page]="item.page" ondragstart="return false;" onselectstart="return false;">
</ng-container> </ng-container>

View File

@ -16,7 +16,7 @@ import {
Renderer2, Renderer2,
SimpleChanges, ViewChild SimpleChanges, ViewChild
} from '@angular/core'; } from '@angular/core';
import { BehaviorSubject, fromEvent, ReplaySubject } from 'rxjs'; import {BehaviorSubject, filter, fromEvent, map, Observable, of, ReplaySubject} from 'rxjs';
import { debounceTime } from 'rxjs/operators'; import { debounceTime } from 'rxjs/operators';
import { ScrollService } from 'src/app/_services/scroll.service'; import { ScrollService } from 'src/app/_services/scroll.service';
import { ReaderService } from '../../../_services/reader.service'; import { ReaderService } from '../../../_services/reader.service';
@ -27,6 +27,8 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {TranslocoDirective} from "@ngneat/transloco"; import {TranslocoDirective} from "@ngneat/transloco";
import {MangaReaderComponent} from "../manga-reader/manga-reader.component"; import {MangaReaderComponent} from "../manga-reader/manga-reader.component";
import {InfiniteScrollModule} from "ngx-infinite-scroll"; import {InfiniteScrollModule} from "ngx-infinite-scroll";
import {ReaderSetting} from "../../_models/reader-setting";
import {SafeStylePipe} from "../../../_pipes/safe-style.pipe";
/** /**
* How much additional space should pass, past the original bottom of the document height before we trigger the next chapter load * How much additional space should pass, past the original bottom of the document height before we trigger the next chapter load
@ -61,7 +63,7 @@ const enum DEBUG_MODES {
styleUrls: ['./infinite-scroller.component.scss'], styleUrls: ['./infinite-scroller.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [NgIf, NgFor, AsyncPipe, TranslocoDirective, InfiniteScrollModule] imports: [NgIf, NgFor, AsyncPipe, TranslocoDirective, InfiniteScrollModule, SafeStylePipe]
}) })
export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit { export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {
@ -70,6 +72,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy,
private readonly renderer = inject(Renderer2); private readonly renderer = inject(Renderer2);
private readonly scrollService = inject(ScrollService); private readonly scrollService = inject(ScrollService);
private readonly cdRef = inject(ChangeDetectorRef); private readonly cdRef = inject(ChangeDetectorRef);
private readonly destroyRef = inject(DestroyRef);
/** /**
* Current page number aka what's recorded on screen * Current page number aka what's recorded on screen
@ -87,6 +90,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy,
* Method to generate the src for Image loading * Method to generate the src for Image loading
*/ */
@Input({required: true}) urlProvider!: (page: number) => string; @Input({required: true}) urlProvider!: (page: number) => string;
@Input({required: true}) readerSettings$!: Observable<ReaderSetting>;
@Output() pageNumberChange: EventEmitter<number> = new EventEmitter<number>(); @Output() pageNumberChange: EventEmitter<number> = new EventEmitter<number>();
@Output() loadNextChapter: EventEmitter<void> = new EventEmitter<void>(); @Output() loadNextChapter: EventEmitter<void> = new EventEmitter<void>();
@Output() loadPrevChapter: EventEmitter<void> = new EventEmitter<void>(); @Output() loadPrevChapter: EventEmitter<void> = new EventEmitter<void>();
@ -99,7 +103,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy,
bottomSpacerIntersectionObserver: IntersectionObserver = new IntersectionObserver((entries) => this.handleBottomIntersection(entries), bottomSpacerIntersectionObserver: IntersectionObserver = new IntersectionObserver((entries) => this.handleBottomIntersection(entries),
{ threshold: 1.0 }); { threshold: 1.0 });
private readonly destroyRef = inject(DestroyRef); darkness$: Observable<string> = of('brightness(100%)');
readerElemRef!: ElementRef<HTMLDivElement>; readerElemRef!: ElementRef<HTMLDivElement>;
@ -223,6 +227,11 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy,
this.recalculateImageWidth(); this.recalculateImageWidth();
this.darkness$ = this.readerSettings$.pipe(
map(values => 'brightness(' + values.darkness + '%)'),
takeUntilDestroyed(this.destroyRef)
);
if (this.goToPage) { if (this.goToPage) {
this.goToPage.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(page => { this.goToPage.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(page => {
const isSamePage = this.pageNum === page; const isSamePage = this.pageNum === page;

View File

@ -28,11 +28,13 @@
<i class="fa-regular fa-rectangle-list" aria-hidden="true"></i> <i class="fa-regular fa-rectangle-list" aria-hidden="true"></i>
<span class="visually-hidden">{{t('shortcuts-menu-alt')}}</span> <span class="visually-hidden">{{t('shortcuts-menu-alt')}}</span>
</button> </button>
<button *ngIf="!bookmarkMode && hasBookmarkRights" class="btn btn-icon" role="checkbox" [attr.aria-checked]="CurrentPageBookmarked" @if (!bookmarkMode && hasBookmarkRights) {
title="{{t(CurrentPageBookmarked ? 'unbookmark-page-tooltip' : 'bookmark-page-tooltip')}}" (click)="bookmarkPage()"> <button class="btn btn-icon" role="checkbox" [attr.aria-checked]="CurrentPageBookmarked"
<i class="{{CurrentPageBookmarked ? 'fa' : 'far'}} fa-bookmark" aria-hidden="true"></i> title="{{t(CurrentPageBookmarked ? 'unbookmark-page-tooltip' : 'bookmark-page-tooltip')}}" (click)="bookmarkPage()">
<span class="visually-hidden">{{t(CurrentPageBookmarked ? 'unbookmark-page-tooltip' : 'bookmark-page-tooltip')}}</span> <i class="{{CurrentPageBookmarked ? 'fa' : 'far'}} fa-bookmark" aria-hidden="true"></i>
</button> <span class="visually-hidden">{{t(CurrentPageBookmarked ? 'unbookmark-page-tooltip' : 'bookmark-page-tooltip')}}</span>
</button>
}
</div> </div>
</div> </div>
</div> </div>
@ -118,7 +120,8 @@
(loadNextChapter)="loadNextChapter()" (loadNextChapter)="loadNextChapter()"
(loadPrevChapter)="loadPrevChapter()" (loadPrevChapter)="loadPrevChapter()"
[bookmarkPage]="showBookmarkEffectEvent" [bookmarkPage]="showBookmarkEffectEvent"
[fullscreenToggled]="fullscreenEvent"> [fullscreenToggled]="fullscreenEvent"
[readerSettings$]="readerSettings$">
</app-infinite-scroller> </app-infinite-scroller>
</div> </div>
</ng-template> </ng-template>

View File

@ -2,12 +2,14 @@
<div class="d-flex flex-row g-0 mb-2 reading-list-item"> <div class="d-flex flex-row g-0 mb-2 reading-list-item">
<div class="pe-2"> <div class="pe-2">
<app-image width="106px" maxHeight="125px" class="img-top me-3" [imageUrl]="imageService.getChapterCoverImage(item.chapterId)"></app-image> <app-image width="106px" maxHeight="125px" class="img-top me-3" [imageUrl]="imageService.getChapterCoverImage(item.chapterId)"></app-image>
<ng-container *ngIf="item.pagesRead === 0 && item.pagesTotal > 0"> @if (item.pagesRead === 0 && item.pagesTotal > 0) {
<div class="not-read-badge" ></div> <div class="not-read-badge" ></div>
</ng-container> }
<div class="progress-banner" *ngIf="item.pagesRead < item.pagesTotal && item.pagesTotal > 0 && item.pagesRead !== item.pagesTotal"> @if (item.pagesRead < item.pagesTotal && item.pagesTotal > 0 && item.pagesRead !== item.pagesTotal) {
<p><ngb-progressbar type="primary" height="5px" [value]="item.pagesRead" [max]="item.pagesTotal"></ngb-progressbar></p> <div class="progress-banner">
</div> <p><ngb-progressbar type="primary" height="5px" [value]="item.pagesRead" [max]="item.pagesTotal"></ngb-progressbar></p>
</div>
}
</div> </div>
<div class="flex-grow-1"> <div class="flex-grow-1">
@ -37,9 +39,11 @@
<!-- TODO: Let's add summary here--> <!-- TODO: Let's add summary here-->
<div class="ps-1 mt-2" *ngIf="item.releaseDate !== '0001-01-01T00:00:00'"> @if (item.releaseDate !== '0001-01-01T00:00:00') {
Released: {{item.releaseDate | date:'longDate'}} <div class="ps-1 mt-2">
</div> Released: {{item.releaseDate | date:'longDate'}}
</div>
}
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import {ChangeDetectionStrategy, Component, EventEmitter, inject, Input, Output} from '@angular/core';
import { LibraryType } from 'src/app/_models/library/library'; import { LibraryType } from 'src/app/_models/library/library';
import { MangaFormat } from 'src/app/_models/manga-format'; import { MangaFormat } from 'src/app/_models/manga-format';
import { ReadingListItem } from 'src/app/_models/reading-list'; import { ReadingListItem } from 'src/app/_models/reading-list';
@ -6,7 +6,7 @@ import { ImageService } from 'src/app/_services/image.service';
import { MangaFormatIconPipe } from '../../../_pipes/manga-format-icon.pipe'; import { MangaFormatIconPipe } from '../../../_pipes/manga-format-icon.pipe';
import { MangaFormatPipe } from '../../../_pipes/manga-format.pipe'; import { MangaFormatPipe } from '../../../_pipes/manga-format.pipe';
import { NgbProgressbar } from '@ng-bootstrap/ng-bootstrap'; import { NgbProgressbar } from '@ng-bootstrap/ng-bootstrap';
import { NgIf, DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { ImageComponent } from '../../../shared/image/image.component'; import { ImageComponent } from '../../../shared/image/image.component';
import {TranslocoDirective} from "@ngneat/transloco"; import {TranslocoDirective} from "@ngneat/transloco";
import {SeriesFormatComponent} from "../../../shared/series-format/series-format.component"; import {SeriesFormatComponent} from "../../../shared/series-format/series-format.component";
@ -17,10 +17,13 @@ import {SeriesFormatComponent} from "../../../shared/series-format/series-format
styleUrls: ['./reading-list-item.component.scss'], styleUrls: ['./reading-list-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [ImageComponent, NgIf, NgbProgressbar, DatePipe, MangaFormatPipe, MangaFormatIconPipe, TranslocoDirective, SeriesFormatComponent] imports: [ImageComponent, NgbProgressbar, DatePipe, MangaFormatPipe, MangaFormatIconPipe, TranslocoDirective, SeriesFormatComponent]
}) })
export class ReadingListItemComponent { export class ReadingListItemComponent {
protected readonly imageService = inject(ImageService);
protected readonly MangaFormat = MangaFormat;
@Input({required: true}) item!: ReadingListItem; @Input({required: true}) item!: ReadingListItem;
@Input() position: number = 0; @Input() position: number = 0;
@Input() libraryTypes: {[key: number]: LibraryType} = {}; @Input() libraryTypes: {[key: number]: LibraryType} = {};
@ -32,15 +35,7 @@ export class ReadingListItemComponent {
@Output() read: EventEmitter<ReadingListItem> = new EventEmitter(); @Output() read: EventEmitter<ReadingListItem> = new EventEmitter();
@Output() remove: EventEmitter<ReadingListItem> = new EventEmitter(); @Output() remove: EventEmitter<ReadingListItem> = new EventEmitter();
get MangaFormat(): typeof MangaFormat {
return MangaFormat;
}
constructor(public imageService: ImageService) { }
readChapter(item: ReadingListItem) { readChapter(item: ReadingListItem) {
this.read.emit(item); this.read.emit(item);
} }
} }

View File

@ -7,7 +7,7 @@
"name": "GPL-3.0", "name": "GPL-3.0",
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
}, },
"version": "0.8.0.3" "version": "0.8.0.4"
}, },
"servers": [ "servers": [
{ {
@ -14716,6 +14716,138 @@
"type": "string", "type": "string",
"description": "ISBN-13 (usually) of the Chapter", "description": "ISBN-13 (usually) of the Chapter",
"nullable": true "nullable": true
},
"writers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"coverArtists": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"publishers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"characters": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"pencillers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"inkers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"imprints": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"colorists": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"letterers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"editors": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"translators": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"teams": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"locations": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PersonDto"
},
"nullable": true
},
"genres": {
"type": "array",
"items": {
"$ref": "#/components/schemas/GenreTagDto"
},
"nullable": true
},
"tags": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TagDto"
},
"description": "Collection of all Tags from underlying chapters for a Series",
"nullable": true
},
"publicationStatus": {
"enum": [
0,
1,
2,
3,
4
],
"type": "integer",
"format": "int32"
},
"language": {
"type": "string",
"description": "Language for the Chapter/Issue",
"nullable": true
},
"count": {
"type": "integer",
"description": "Number in the TotalCount of issues",
"format": "int32"
},
"totalCount": {
"type": "integer",
"description": "Total number of issues for the series",
"format": "int32"
} }
}, },
"additionalProperties": false, "additionalProperties": false,