mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-09-29 15:30:50 -04:00
Epub and OPDS Fixes (#4038)
Co-authored-by: Amelia <77553571+Fesaa@users.noreply.github.com>
This commit is contained in:
parent
82d85363b0
commit
21448a86ba
@ -417,7 +417,8 @@ public class OpdsController : BaseApiController
|
||||
|
||||
// Ensure libraries follow SideNav order
|
||||
var userSideNavStreams = await _unitOfWork.UserRepository.GetSideNavStreams(userId);
|
||||
foreach (var library in userSideNavStreams.Where(s => s.StreamType == SideNavStreamType.Library).Select(sideNavStream => sideNavStream.Library))
|
||||
foreach (var library in userSideNavStreams.Where(s => s.StreamType == SideNavStreamType.Library)
|
||||
.Select(sideNavStream => sideNavStream.Library))
|
||||
{
|
||||
feed.Entries.Add(new FeedEntry()
|
||||
{
|
||||
@ -593,6 +594,8 @@ public class OpdsController : BaseApiController
|
||||
|
||||
|
||||
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId, GetUserParams(pageNumber))).ToList();
|
||||
var totalItems = (await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId)).Count();
|
||||
|
||||
|
||||
// Check if there is reading progress or not, if so, inject a "continue-reading" item
|
||||
var firstReadReadingListItem = items.FirstOrDefault(i => i.PagesRead > 0);
|
||||
@ -618,8 +621,9 @@ public class OpdsController : BaseApiController
|
||||
CreateChapter(apiKey, $"{item.Order} - {item.SeriesName}: {item.Title}",
|
||||
item.Summary ?? string.Empty, item.ChapterId, item.VolumeId, item.SeriesId, prefix, baseUrl));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AddPagination(feed, pageNumber, totalItems, UserParams.Default.PageSize, $"{prefix}{apiKey}/reading-list/{readingListId}/");
|
||||
return CreateXmlResult(SerializeXml(feed));
|
||||
}
|
||||
|
||||
@ -868,7 +872,6 @@ public class OpdsController : BaseApiController
|
||||
}
|
||||
|
||||
feed.Total = feed.Entries.Count;
|
||||
|
||||
return CreateXmlResult(SerializeXml(feed));
|
||||
}
|
||||
|
||||
@ -1127,6 +1130,45 @@ public class OpdsController : BaseApiController
|
||||
feed.StartIndex = (Math.Max(list.CurrentPage - 1, 0) * list.PageSize) + 1;
|
||||
}
|
||||
|
||||
private static void AddPagination(Feed feed, int currentPage, int totalItems, int pageSize, string href)
|
||||
{
|
||||
var url = href;
|
||||
if (href.Contains('?'))
|
||||
{
|
||||
url += "&";
|
||||
}
|
||||
else
|
||||
{
|
||||
url += "?";
|
||||
}
|
||||
|
||||
var pageNumber = Math.Max(currentPage, 1);
|
||||
var totalPages = totalItems / pageSize;
|
||||
|
||||
if (pageNumber > 1)
|
||||
{
|
||||
feed.Links.Add(CreateLink(FeedLinkRelation.Prev, FeedLinkType.AtomNavigation, url + "pageNumber=" + (pageNumber - 1)));
|
||||
}
|
||||
|
||||
if (pageNumber + 1 <= totalPages)
|
||||
{
|
||||
feed.Links.Add(CreateLink(FeedLinkRelation.Next, FeedLinkType.AtomNavigation, url + "pageNumber=" + (pageNumber + 1)));
|
||||
}
|
||||
|
||||
// Update self to point to current page
|
||||
var selfLink = feed.Links.SingleOrDefault(l => l.Rel == FeedLinkRelation.Self);
|
||||
if (selfLink != null)
|
||||
{
|
||||
selfLink.Href = url + "pageNumber=" + pageNumber;
|
||||
}
|
||||
|
||||
|
||||
feed.Total = totalItems;
|
||||
feed.ItemsPerPage = pageSize;
|
||||
feed.StartIndex = (Math.Max(currentPage - 1, 0) * pageSize) + 1;
|
||||
}
|
||||
|
||||
|
||||
private static FeedEntry CreateSeries(SeriesDto seriesDto, SeriesMetadataDto metadata, string apiKey, string prefix, string baseUrl)
|
||||
{
|
||||
return new FeedEntry()
|
||||
|
@ -111,6 +111,10 @@ public sealed record UserReadingProfileDto
|
||||
[Required]
|
||||
public bool BookReaderImmersiveMode { get; set; } = false;
|
||||
|
||||
/// <inheritdoc cref="AppUserReadingProfile.BookReaderEpubPageCalculationMethod"/>
|
||||
[Required]
|
||||
public EpubPageCalculationMethod BookReaderEpubPageCalculationMethod { get; set; } = EpubPageCalculationMethod.Default;
|
||||
|
||||
#endregion
|
||||
|
||||
#region PdfReader
|
||||
|
3892
API/Data/Migrations/20250921211542_EpubPageCalcMethod.Designer.cs
generated
Normal file
3892
API/Data/Migrations/20250921211542_EpubPageCalcMethod.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
API/Data/Migrations/20250921211542_EpubPageCalcMethod.cs
Normal file
29
API/Data/Migrations/20250921211542_EpubPageCalcMethod.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class EpubPageCalcMethod : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "BookReaderEpubPageCalculationMethod",
|
||||
table: "AppUserReadingProfiles",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BookReaderEpubPageCalculationMethod",
|
||||
table: "AppUserReadingProfiles");
|
||||
}
|
||||
}
|
||||
}
|
@ -732,6 +732,9 @@ namespace API.Data.Migrations
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValue("#000000");
|
||||
|
||||
b.Property<int>("BookReaderEpubPageCalculationMethod")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BookReaderFontFamily")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
@ -139,6 +139,10 @@ public class AppUserReadingProfile
|
||||
/// </summary>
|
||||
/// <remarks>Defaults to false</remarks>
|
||||
public bool BookReaderImmersiveMode { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Book Reader Option: Different calculation modes for the page due to a bleed bug that devs cannot reproduce reliably or fix
|
||||
/// </summary>
|
||||
public EpubPageCalculationMethod BookReaderEpubPageCalculationMethod { get; set; } = EpubPageCalculationMethod.Default;
|
||||
#endregion
|
||||
|
||||
#region PdfReader
|
||||
|
14
API/Entities/Enums/EpubPageCalculationMethod.cs
Normal file
14
API/Entities/Enums/EpubPageCalculationMethod.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace API.Entities.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Due to a bleeding text bug in the Epub reader with 1/2 column layout, multiple calculation modes are present
|
||||
/// </summary>
|
||||
public enum EpubPageCalculationMethod
|
||||
{
|
||||
[Description("Default")]
|
||||
Default = 0,
|
||||
[Description("Calculation 1")]
|
||||
Calculation1 = 1,
|
||||
}
|
@ -445,6 +445,7 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
||||
existingProfile.BookThemeName = dto.BookReaderThemeName;
|
||||
existingProfile.BookReaderLayoutMode = dto.BookReaderLayoutMode;
|
||||
existingProfile.BookReaderImmersiveMode = dto.BookReaderImmersiveMode;
|
||||
existingProfile.BookReaderEpubPageCalculationMethod = dto.BookReaderEpubPageCalculationMethod;
|
||||
|
||||
// PDF Reading
|
||||
existingProfile.PdfTheme = dto.PdfTheme;
|
||||
|
@ -10,9 +10,8 @@ import {PdfTheme} from "./pdf-theme";
|
||||
import {PdfScrollMode} from "./pdf-scroll-mode";
|
||||
import {PdfLayoutMode} from "./pdf-layout-mode";
|
||||
import {PdfSpreadMode} from "./pdf-spread-mode";
|
||||
import {Series} from "../series";
|
||||
import {Library} from "../library/library";
|
||||
import {UserBreakpoint} from "../../shared/_services/utility.service";
|
||||
import {EpubPageCalculationMethod} from "../readers/epub-page-calculation-method";
|
||||
|
||||
export enum ReadingProfileKind {
|
||||
Default = 0,
|
||||
@ -53,6 +52,7 @@ export interface ReadingProfile {
|
||||
bookReaderThemeName: string;
|
||||
bookReaderLayoutMode: BookPageLayoutMode;
|
||||
bookReaderImmersiveMode: boolean;
|
||||
bookReaderEpubPageCalculationMethod: EpubPageCalculationMethod;
|
||||
|
||||
// PDF Reader
|
||||
pdfTheme: PdfTheme;
|
||||
|
@ -0,0 +1,6 @@
|
||||
export enum EpubPageCalculationMethod {
|
||||
Default = 0,
|
||||
Calculation1 = 1
|
||||
}
|
||||
|
||||
export const allCalcMethods = [EpubPageCalculationMethod.Default, EpubPageCalculationMethod.Calculation1];
|
20
UI/Web/src/app/_pipes/epub-page-calc-method.pipe.ts
Normal file
20
UI/Web/src/app/_pipes/epub-page-calc-method.pipe.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {EpubPageCalculationMethod} from "../_models/readers/epub-page-calculation-method";
|
||||
import {translate} from "@jsverse/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'epubPageCalcMethod'
|
||||
})
|
||||
export class EpubPageCalcMethodPipe implements PipeTransform {
|
||||
|
||||
transform(value: EpubPageCalculationMethod) {
|
||||
switch (value) {
|
||||
case EpubPageCalculationMethod.Default:
|
||||
return translate('epub-page-calc-method-pipe.default');
|
||||
case EpubPageCalculationMethod.Calculation1:
|
||||
return translate('epub-page-calc-method-pipe.calc1');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -15,13 +15,13 @@ import {translate} from "@jsverse/transloco";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {UserBreakpoint, UtilityService} from "../shared/_services/utility.service";
|
||||
import {LayoutMeasurementService} from "./layout-measurement.service";
|
||||
import {environment} from "../../environments/environment";
|
||||
import {EpubFont} from "../_models/preferences/epub-font";
|
||||
import {FontService} from "./font.service";
|
||||
import {EpubPageCalculationMethod} from "../_models/readers/epub-page-calculation-method";
|
||||
|
||||
export interface ReaderSettingUpdate {
|
||||
setting: 'pageStyle' | 'clickToPaginate' | 'fullscreen' | 'writingStyle' | 'layoutMode' | 'readingDirection' | 'immersiveMode' | 'theme';
|
||||
setting: 'pageStyle' | 'clickToPaginate' | 'fullscreen' | 'writingStyle' | 'layoutMode' | 'readingDirection' | 'immersiveMode' | 'theme' | 'pageCalcMethod';
|
||||
object: any;
|
||||
}
|
||||
|
||||
@ -35,12 +35,10 @@ export type BookReadingProfileFormGroup = FormGroup<{
|
||||
bookReaderWritingStyle: FormControl<WritingStyle>;
|
||||
bookReaderThemeName: FormControl<string>;
|
||||
bookReaderLayoutMode: FormControl<BookPageLayoutMode>;
|
||||
bookReaderImmersiveMode:FormControl <boolean>;
|
||||
bookReaderImmersiveMode: FormControl<boolean>;
|
||||
bookReaderEpubPageCalculationMethod: FormControl<EpubPageCalculationMethod>;
|
||||
}>
|
||||
|
||||
const COLUMN_GAP = 20; //px gap between columns
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class EpubReaderSettingsService {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
@ -51,7 +49,6 @@ export class EpubReaderSettingsService {
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly document = inject(DOCUMENT);
|
||||
private readonly fb = inject(NonNullableFormBuilder);
|
||||
private readonly layoutMeasurements = inject(LayoutMeasurementService);
|
||||
|
||||
// Core signals - these will be the single source of truth
|
||||
private readonly _currentReadingProfile = signal<ReadingProfile | null>(null);
|
||||
@ -67,6 +64,7 @@ export class EpubReaderSettingsService {
|
||||
private readonly _activeTheme = signal<BookTheme | undefined>(undefined);
|
||||
private readonly _clickToPaginate = signal<boolean>(false);
|
||||
private readonly _layoutMode = signal<BookPageLayoutMode>(BookPageLayoutMode.Default);
|
||||
private readonly _pageCalcMode = signal<EpubPageCalculationMethod>(EpubPageCalculationMethod.Default);
|
||||
private readonly _immersiveMode = signal<boolean>(false);
|
||||
private readonly _isFullscreen = signal<boolean>(false);
|
||||
|
||||
@ -91,6 +89,7 @@ export class EpubReaderSettingsService {
|
||||
public readonly immersiveMode = this._immersiveMode.asReadonly();
|
||||
public readonly isFullscreen = this._isFullscreen.asReadonly();
|
||||
public readonly epubFonts = this._epubFonts.asReadonly();
|
||||
public readonly pageCalcMode = this._pageCalcMode.asReadonly();
|
||||
|
||||
// Computed signals for derived state
|
||||
public readonly layoutMode = computed(() => {
|
||||
@ -210,6 +209,18 @@ export class EpubReaderSettingsService {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
const pageCalcMethod = this._pageCalcMode();
|
||||
if (!this.isInitialized) return;
|
||||
|
||||
if (pageCalcMethod) {
|
||||
this.settingUpdateSubject.next({
|
||||
setting: 'pageCalcMethod',
|
||||
object: pageCalcMethod
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -252,7 +263,7 @@ export class EpubReaderSettingsService {
|
||||
*/
|
||||
private setupDefaultsFromProfile(profile: ReadingProfile): void {
|
||||
// Set defaults if undefined
|
||||
if (profile.bookReaderFontFamily === undefined) {
|
||||
if (profile.bookReaderFontFamily === undefined || profile.bookReaderFontFamily === 'default') {
|
||||
profile.bookReaderFontFamily = FontService.DefaultEpubFont;
|
||||
}
|
||||
if (profile.bookReaderFontSize === undefined || profile.bookReaderFontSize < 50) {
|
||||
@ -273,6 +284,9 @@ export class EpubReaderSettingsService {
|
||||
if (profile.bookReaderLayoutMode === undefined) {
|
||||
profile.bookReaderLayoutMode = BookPageLayoutMode.Default;
|
||||
}
|
||||
if (profile.bookReaderEpubPageCalculationMethod === undefined) {
|
||||
profile.bookReaderEpubPageCalculationMethod = EpubPageCalculationMethod.Default;
|
||||
}
|
||||
|
||||
// Update signals from profile
|
||||
this._readingDirection.set(profile.bookReaderReadingDirection);
|
||||
@ -280,6 +294,7 @@ export class EpubReaderSettingsService {
|
||||
this._clickToPaginate.set(profile.bookReaderTapToPaginate);
|
||||
this._layoutMode.set(profile.bookReaderLayoutMode);
|
||||
this._immersiveMode.set(profile.bookReaderImmersiveMode);
|
||||
this._pageCalcMode.set(profile.bookReaderEpubPageCalculationMethod);
|
||||
|
||||
// Set up page styles
|
||||
this.setPageStyles(
|
||||
@ -378,6 +393,11 @@ export class EpubReaderSettingsService {
|
||||
this.settingsForm.get('bookReaderWritingStyle')?.setValue(value);
|
||||
}
|
||||
|
||||
updatePageCalcMethod(value: EpubPageCalculationMethod) {
|
||||
this._pageCalcMode.set(value);
|
||||
this.settingsForm.get('bookReaderEpubPageCalculationMethod')?.setValue(value);
|
||||
}
|
||||
|
||||
updateFullscreen(value: boolean) {
|
||||
this._isFullscreen.set(value);
|
||||
if (!this._isInitialized()) return;
|
||||
@ -472,6 +492,7 @@ export class EpubReaderSettingsService {
|
||||
bookReaderThemeName: this.fb.control(profile.bookReaderThemeName),
|
||||
bookReaderLayoutMode: this.fb.control(this._layoutMode()),
|
||||
bookReaderImmersiveMode: this.fb.control(this._immersiveMode()),
|
||||
bookReaderEpubPageCalculationMethod: this.fb.control(this._pageCalcMode())
|
||||
});
|
||||
|
||||
// Set up value change subscriptions
|
||||
@ -586,6 +607,15 @@ export class EpubReaderSettingsService {
|
||||
this.isUpdatingFromForm = false;
|
||||
});
|
||||
|
||||
// Page Calc Method
|
||||
this.settingsForm.get('bookReaderEpubPageCalculationMethod')?.valueChanges.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(value => {
|
||||
this.isUpdatingFromForm = true;
|
||||
this._pageCalcMode.set(value as EpubPageCalculationMethod);
|
||||
this.isUpdatingFromForm = false;
|
||||
});
|
||||
|
||||
// Update implicit profile on form changes (debounced) - ONLY source of profile updates
|
||||
this.settingsForm.valueChanges.pipe(
|
||||
debounceTime(500),
|
||||
@ -648,6 +678,7 @@ export class EpubReaderSettingsService {
|
||||
data.bookReaderImmersiveMode = this._immersiveMode();
|
||||
data.bookReaderReadingDirection = this._readingDirection();
|
||||
data.bookReaderWritingStyle = this._writingStyle();
|
||||
data.bookReaderEpubPageCalculationMethod = this._pageCalcMode();
|
||||
|
||||
const activeTheme = this._activeTheme();
|
||||
if (activeTheme) {
|
||||
|
@ -56,7 +56,7 @@
|
||||
<ng-template #topActionBar>
|
||||
@if (shouldShowMenu()) {
|
||||
<div class="reader-header">
|
||||
<div class="action-bar px-2">
|
||||
<div class="action-bar top-action-bar px-2">
|
||||
<div>
|
||||
<button class="btn btn-secondary me-2" (click)="toggleDrawer()">
|
||||
<i class="fa fa-bars" aria-hidden="true"></i>
|
||||
@ -146,10 +146,10 @@
|
||||
</span>
|
||||
|
||||
<div class="d-none d-sm-block">
|
||||
<span class="me-1">•</span>
|
||||
<span class="mx-1">•</span>
|
||||
{{t('completion-label', {percent: (virtualizedPageNum() / virtualizedMaxPages()) | percent})}}
|
||||
@if (readingTimeLeftResource.value(); as timeLeft) {
|
||||
<span class="me-1">•</span>
|
||||
<span class="mx-1">•</span>
|
||||
<span class="time-left">
|
||||
<i class="fa-solid fa-clock" aria-hidden="true"></i>
|
||||
{{timeLeft! | readTimeLeft:true }}
|
||||
|
@ -79,7 +79,7 @@ $action-bar-height: 38px;
|
||||
}
|
||||
|
||||
@media (min-width: 876px) {
|
||||
.action-bar {
|
||||
.top-action-bar {
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ $action-bar-height: 38px;
|
||||
|
||||
/* Mobile - 2 columns */
|
||||
@media (max-width: 875px) {
|
||||
.action-bar {
|
||||
.top-action-bar {
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,7 @@ import {environment} from "../../../../environments/environment";
|
||||
import {LoadPageEvent} from "../_drawers/view-bookmarks-drawer/view-bookmark-drawer.component";
|
||||
import {FontService} from "../../../_services/font.service";
|
||||
import afterFrame from "afterframe";
|
||||
import {EpubPageCalculationMethod} from "../../../_models/readers/epub-page-calculation-method";
|
||||
|
||||
|
||||
interface HistoryPoint {
|
||||
@ -159,11 +160,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private readonly colorscapeService = inject(ColorscapeService);
|
||||
private readonly fontService = inject(FontService);
|
||||
|
||||
protected readonly BookPageLayoutMode = BookPageLayoutMode;
|
||||
protected readonly WritingStyle = WritingStyle;
|
||||
protected readonly ReadingDirection = ReadingDirection;
|
||||
protected readonly PAGING_DIRECTION = PAGING_DIRECTION;
|
||||
|
||||
libraryId!: number;
|
||||
seriesId!: number;
|
||||
volumeId!: number;
|
||||
@ -385,6 +381,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
protected readonly readingDirection = this.readerSettingsService.readingDirection;
|
||||
protected readonly writingStyle = this.readerSettingsService.writingStyle;
|
||||
protected readonly clickToPaginate = this.readerSettingsService.clickToPaginate;
|
||||
protected readonly pageCalcMode = this.readerSettingsService.pageCalcMode;
|
||||
|
||||
protected columnWidth!: Signal<string>;
|
||||
protected columnHeight!: Signal<string>;
|
||||
@ -962,6 +959,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
// Attempt to restore the reading position
|
||||
this.snapScrollOnResize();
|
||||
afterFrame(() => {
|
||||
this.injectImageBookmarkIndicators(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1171,6 +1171,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
const promptConfig = {...this.confirmService.defaultPrompt};
|
||||
promptConfig.header = translate('book-reader.go-to-page');
|
||||
promptConfig.content = translate('book-reader.go-to-page-prompt', {totalPages: this.maxPages()});
|
||||
promptConfig.bookReader = true;
|
||||
|
||||
const goToPageNum = await this.confirmService.prompt(undefined, promptConfig);
|
||||
|
||||
@ -1307,6 +1308,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
border-radius: 2px;
|
||||
background: ${backgroundColor} !important;
|
||||
color: ${textColor} !important;
|
||||
font-family: var(--_fa-family) !important;
|
||||
`;
|
||||
|
||||
|
||||
@ -1681,9 +1683,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
*/
|
||||
pageWidth = computed(() => {
|
||||
this.windowWidth(); // Ensure re-compute when windows size changes (element clientWidth isn't a signal)
|
||||
this.pageCalcMode();
|
||||
|
||||
console.log('page width recalulated')
|
||||
const calculationMethod = this.pageCalcMode();
|
||||
const marginLeft = this.pageStyles()['margin-left'];
|
||||
const columnGapModifier = this.layoutMode() === BookPageLayoutMode.Default ? 0 : 1;
|
||||
const columnGapModifier = this.columnGapModifier();
|
||||
if (this.readingSectionElemRef == null) return 0;
|
||||
|
||||
const margin = (this.convertVwToPx(parseInt(marginLeft, 10)) * 2);
|
||||
@ -1692,7 +1697,25 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
// console.log('page size calc, margin: ', margin)
|
||||
// console.log('page size calc, col gap: ', ((COLUMN_GAP / 2) * columnGapModifier));
|
||||
// console.log("clientWidth", this.readingSectionElemRef.nativeElement.clientWidth, "window", window.innerWidth, "margin", margin, "left", marginLeft)
|
||||
return this.readingSectionElemRef.nativeElement.clientWidth - margin + ((COLUMN_GAP) * columnGapModifier);
|
||||
// console.log('clientWidth: ', this.readingSectionElemRef.nativeElement.clientWidth, 'offsetWidth:', this.readingSectionElemRef.nativeElement.offsetWidth, 'bbox:', this.readingSectionElemRef.nativeElement.getBoundingClientRect().width);
|
||||
|
||||
if (calculationMethod === EpubPageCalculationMethod.Default) {
|
||||
return this.readingSectionElemRef.nativeElement.clientWidth - margin + (((COLUMN_GAP) * columnGapModifier));
|
||||
} else {
|
||||
return this.readingSectionElemRef.nativeElement.clientWidth - margin + (((COLUMN_GAP) * columnGapModifier) + 10);
|
||||
}
|
||||
});
|
||||
|
||||
columnGapModifier = computed(() => {
|
||||
const calculationMethod = this.pageCalcMode();
|
||||
switch(this.layoutMode()) {
|
||||
case BookPageLayoutMode.Default:
|
||||
return 0;
|
||||
case BookPageLayoutMode.Column1:
|
||||
return 1;
|
||||
case BookPageLayoutMode.Column2:
|
||||
return calculationMethod === EpubPageCalculationMethod.Default ? 1 : 1.25;
|
||||
}
|
||||
});
|
||||
|
||||
pageHeight = computed(() => {
|
||||
@ -2436,4 +2459,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
protected readonly Breakpoint = Breakpoint;
|
||||
protected readonly environment = environment;
|
||||
protected readonly BookPageLayoutMode = BookPageLayoutMode;
|
||||
protected readonly WritingStyle = WritingStyle;
|
||||
protected readonly ReadingDirection = ReadingDirection;
|
||||
protected readonly PAGING_DIRECTION = PAGING_DIRECTION;
|
||||
}
|
||||
|
@ -119,7 +119,7 @@
|
||||
<ng-template #fullscreenTooltip>{{t('fullscreen-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="fullscreen-help">
|
||||
<ng-container [ngTemplateOutlet]="fullscreenTooltip" />
|
||||
</span>
|
||||
</span>
|
||||
<button (click)="toggleFullscreen()" class="btn btn-icon" aria-labelledby="fullscreen">
|
||||
<i class="fa {{isFullscreen() ? 'fa-compress-alt' : 'fa-expand-alt'}} {{isFullscreen() ? 'icon-primary-color' : ''}}" aria-hidden="true"></i>
|
||||
@if (activeTheme()?.isDarkTheme) {
|
||||
@ -133,7 +133,7 @@
|
||||
<ng-template #layoutTooltip><span [innerHTML]="t('layout-mode-tooltip')"></span></ng-template>
|
||||
<span class="visually-hidden" id="layout-help">
|
||||
<ng-container [ngTemplateOutlet]="layoutTooltip" />
|
||||
</span>
|
||||
</span>
|
||||
<br>
|
||||
<div class="btn-group d-flex justify-content-center" role="group" [attr.aria-label]="t('layout-mode-label')">
|
||||
<input type="radio" formControlName="bookReaderLayoutMode" [value]="BookPageLayoutMode.Default" class="btn-check" id="layout-mode-default" autocomplete="off">
|
||||
@ -149,6 +149,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls mt-2" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<label id="page-calc-method" class="form-label">{{t('page-calc-method-label')}}
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top"
|
||||
[ngbTooltip]="pageCalcMethodTooltip" role="button" tabindex="1" aria-describedby="fullscreen-help"></i>
|
||||
</label>
|
||||
<ng-template #pageCalcMethodTooltip>{{t('page-calc-method-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="page-calc-method-help">
|
||||
<ng-container [ngTemplateOutlet]="pageCalcMethodTooltip" />
|
||||
</span>
|
||||
<br>
|
||||
<div>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderEpubPageCalculationMethod">
|
||||
@for (opt of calcMethods; track opt) {
|
||||
<option [ngValue]="opt">{{opt | epubPageCalcMethod}}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-template>
|
||||
</div>
|
||||
|
@ -22,9 +22,9 @@ import {
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {ReadingProfile, ReadingProfileKind} from "../../../_models/preferences/reading-profiles";
|
||||
import {BookReadingProfileFormGroup, EpubReaderSettingsService} from "../../../_services/epub-reader-settings.service";
|
||||
import {LayoutMode} from "../../../manga-reader/_models/layout-mode";
|
||||
import {FontService} from "../../../_services/font.service";
|
||||
import {EpubFont} from "../../../_models/preferences/epub-font";
|
||||
import {EpubPageCalcMethodPipe} from "../../../_pipes/epub-page-calc-method.pipe";
|
||||
import {allCalcMethods, EpubPageCalculationMethod} from "../../../_models/readers/epub-page-calculation-method";
|
||||
|
||||
/**
|
||||
* Used for book reader. Do not use for other components
|
||||
@ -85,9 +85,9 @@ export const bookColorThemes = [
|
||||
templateUrl: './reader-settings.component.html',
|
||||
styleUrls: ['./reader-settings.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [ReactiveFormsModule, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionButton,
|
||||
NgbAccordionCollapse, NgbAccordionBody, NgbTooltip, NgTemplateOutlet, NgClass, NgStyle,
|
||||
TitleCasePipe, TranslocoDirective]
|
||||
imports: [ReactiveFormsModule, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionButton,
|
||||
NgbAccordionCollapse, NgbAccordionBody, NgbTooltip, NgTemplateOutlet, NgClass, NgStyle,
|
||||
TitleCasePipe, TranslocoDirective, EpubPageCalcMethodPipe]
|
||||
})
|
||||
export class ReaderSettingsComponent implements OnInit {
|
||||
|
||||
@ -116,9 +116,7 @@ export class ReaderSettingsComponent implements OnInit {
|
||||
protected parentReadingProfile!: Signal<ReadingProfile | null>;
|
||||
protected currentReadingProfile!: Signal<ReadingProfile | null>;
|
||||
protected epubFonts!: Signal<EpubFont[]>;
|
||||
|
||||
|
||||
protected isVerticalLayout!: Signal<boolean>;
|
||||
protected pageCalcMode!: Signal<EpubPageCalculationMethod>;
|
||||
|
||||
|
||||
async ngOnInit() {
|
||||
@ -135,6 +133,7 @@ export class ReaderSettingsComponent implements OnInit {
|
||||
this.parentReadingProfile = this.readerSettingsService.parentReadingProfile;
|
||||
this.currentReadingProfile = this.readerSettingsService.currentReadingProfile;
|
||||
this.epubFonts = this.readerSettingsService.epubFonts;
|
||||
this.pageCalcMode = this.readerSettingsService.pageCalcMode;
|
||||
|
||||
|
||||
this.themes = this.readerSettingsService.getThemes();
|
||||
@ -166,7 +165,6 @@ export class ReaderSettingsComponent implements OnInit {
|
||||
|
||||
toggleFullscreen() {
|
||||
this.readerSettingsService.toggleFullscreen();
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
// menu only code
|
||||
@ -183,4 +181,5 @@ export class ReaderSettingsComponent implements OnInit {
|
||||
protected readonly WritingStyle = WritingStyle;
|
||||
protected readonly ReadingDirection = ReadingDirection;
|
||||
protected readonly BookPageLayoutMode = BookPageLayoutMode;
|
||||
protected readonly calcMethods = allCalcMethods;
|
||||
}
|
||||
|
@ -9,4 +9,8 @@ export class ConfirmConfig {
|
||||
* If the close button shouldn't be rendered
|
||||
*/
|
||||
disableEscape: boolean = false;
|
||||
/**
|
||||
* Enables book theme css classes to style the popup properly
|
||||
*/
|
||||
bookReader?: boolean = false;
|
||||
}
|
||||
|
@ -1,23 +1,25 @@
|
||||
<ng-container *transloco="let t">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header" [class.book-reader]="config.bookReader">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{config.header | confirmTranslate}}</h4>
|
||||
@if (!config.disableEscape) {
|
||||
<button type="button" class="btn-close" [attr.aria-label]="t('common.close')" (click)="close()"></button>
|
||||
<button type="button" class="btn-unstyled ms-auto" [attr.aria-label]="t('close')" (click)="close()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (config._type === 'prompt') {
|
||||
<div class="modal-body" style="overflow-x: auto">
|
||||
<div class="modal-body" style="overflow-x: auto" [class.book-reader]="config.bookReader">
|
||||
<form [formGroup]="formGroup">
|
||||
<div [innerHtml]="(config.content | confirmTranslate)! | safeHtml"></div>
|
||||
<input type="text" class="form-control" aria-labelledby="modal-basic-title" formControlName="prompt" />
|
||||
</form>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="modal-body" style="overflow-x: auto" [innerHtml]="(config.content | confirmTranslate)! | safeHtml"></div>
|
||||
<div class="modal-body" [class.book-reader]="config.bookReader" style="overflow-x: auto" [innerHtml]="(config.content | confirmTranslate)! | safeHtml"></div>
|
||||
}
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="modal-footer" [class.book-reader]="config.bookReader">
|
||||
@for(btn of config.buttons; track btn) {
|
||||
<div>
|
||||
<button type="button" class="btn btn-{{btn.type}}" (click)="clickButton(btn)">{{btn.text | confirmTranslate}}</button>
|
||||
|
@ -0,0 +1,5 @@
|
||||
|
||||
.book-reader {
|
||||
color: var(--drawer-text-color);
|
||||
background-color: var(--drawer-bg-color);
|
||||
}
|
@ -308,6 +308,22 @@
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('page-calc-method-label')" [subtitle]="t('page-calc-method-tooltip')">
|
||||
<ng-template #view>
|
||||
{{readingProfileForm.get('bookReaderEpubPageCalculationMethod')!.value | epubPageCalcMethod}}
|
||||
</ng-template>
|
||||
<ng-template #edit>
|
||||
<select class="form-select" aria-describedby="book-reader-heading"
|
||||
formControlName="bookReaderEpubPageCalculationMethod">
|
||||
@for (opt of calcMethods; track opt) {
|
||||
<option [ngValue]="opt">{{opt | epubPageCalcMethod}}</option>
|
||||
}
|
||||
</select>
|
||||
</ng-template>
|
||||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-item [title]="t('reading-direction-label')" [subtitle]="t('reading-direction-tooltip')">
|
||||
<ng-template #view>
|
||||
|
@ -32,7 +32,6 @@ import {AccountService} from "../../_services/account.service";
|
||||
import {debounceTime, distinctUntilChanged, tap} from "rxjs/operators";
|
||||
import {SentenceCasePipe} from "../../_pipes/sentence-case.pipe";
|
||||
import {FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators} from "@angular/forms";
|
||||
import {BookService} from "../../book-reader/_services/book.service";
|
||||
import {BookPageLayoutMode} from "../../_models/readers/book-page-layout-mode";
|
||||
import {PdfTheme} from "../../_models/preferences/pdf-theme";
|
||||
import {PdfScrollMode} from "../../_models/preferences/pdf-scroll-mode";
|
||||
@ -65,6 +64,8 @@ import {ColorscapeService} from "../../_services/colorscape.service";
|
||||
import {Color} from "@iplab/ngx-color-picker";
|
||||
import {FontService} from "../../_services/font.service";
|
||||
import {EpubFont} from "../../_models/preferences/epub-font";
|
||||
import {EpubPageCalcMethodPipe} from "../../_pipes/epub-page-calc-method.pipe";
|
||||
import {allCalcMethods} from "../../_models/readers/epub-page-calculation-method";
|
||||
|
||||
enum TabId {
|
||||
ImageReader = "image-reader",
|
||||
@ -104,6 +105,7 @@ enum TabId {
|
||||
NgbTooltip,
|
||||
BreakpointPipe,
|
||||
SettingColorPickerComponent,
|
||||
EpubPageCalcMethodPipe,
|
||||
],
|
||||
templateUrl: './manage-reading-profiles.component.html',
|
||||
styleUrl: './manage-reading-profiles.component.scss',
|
||||
@ -115,7 +117,6 @@ export class ManageReadingProfilesComponent implements OnInit {
|
||||
protected readonly colorscapeService = inject(ColorscapeService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly accountService = inject(AccountService);
|
||||
private readonly bookService = inject(BookService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly confirmService = inject(ConfirmService);
|
||||
@ -231,6 +232,7 @@ export class ManageReadingProfilesComponent implements OnInit {
|
||||
this.readingProfileForm.addControl('bookReaderLayoutMode', new FormControl(this.selectedProfile.bookReaderLayoutMode || BookPageLayoutMode.Default, []));
|
||||
this.readingProfileForm.addControl('bookReaderThemeName', new FormControl(this.selectedProfile.bookReaderThemeName || bookColorThemes[0].name, []));
|
||||
this.readingProfileForm.addControl('bookReaderImmersiveMode', new FormControl(this.selectedProfile.bookReaderImmersiveMode, []));
|
||||
this.readingProfileForm.addControl('bookReaderEpubPageCalculationMethod', new FormControl(this.selectedProfile.bookReaderEpubPageCalculationMethod, []));
|
||||
|
||||
// Pdf reader
|
||||
this.readingProfileForm.addControl('pdfTheme', new FormControl(this.selectedProfile.pdfTheme || PdfTheme.Dark, []));
|
||||
@ -340,6 +342,7 @@ export class ManageReadingProfilesComponent implements OnInit {
|
||||
}
|
||||
|
||||
protected readonly readingDirections = readingDirections;
|
||||
protected readonly calcMethods = allCalcMethods;
|
||||
protected readonly pdfSpreadModes = pdfSpreadModes;
|
||||
protected readonly pageSplitOptions = pageSplitOptions;
|
||||
protected readonly bookLayoutModes = bookLayoutModes;
|
||||
|
@ -1383,6 +1383,8 @@
|
||||
"layout-mode-option-1col": "1 Column",
|
||||
"layout-mode-option-2col": "2 Column",
|
||||
"color-theme-title": "Color Theme",
|
||||
"page-calc-method-label": "Page Calculation",
|
||||
"page-calc-method-tooltip": "If you are experiencing text bleeding on column mode, try an alternative method of calculation",
|
||||
|
||||
"line-spacing-min-label": "1x",
|
||||
"line-spacing-max-label": "2.5x",
|
||||
@ -3023,6 +3025,11 @@
|
||||
"hours-left": "Hours left"
|
||||
},
|
||||
|
||||
"epub-page-calc-method-pipe": {
|
||||
"default": "Default",
|
||||
"calc1": "Calc 1"
|
||||
},
|
||||
|
||||
"metadata-setting-field-pipe": {
|
||||
"covers": "Covers",
|
||||
"age-rating": "{{metadata-fields.age-rating-title}}",
|
||||
@ -3234,6 +3241,9 @@
|
||||
"pdf-theme-label": "Theme",
|
||||
"pdf-theme-tooltip": "Color theme of the reader",
|
||||
|
||||
"page-calc-method-label": "{{reader-settings.page-calc-method-label}}",
|
||||
"page-calc-method-tooltip": "{{reader-settings.page-calc-method-tooltip}}",
|
||||
|
||||
"reading-profile-series-settings-title": "Series",
|
||||
"reading-profile-library-settings-title": "Library",
|
||||
"delete": "{{common.delete}}"
|
||||
|
Loading…
x
Reference in New Issue
Block a user