1. need to get a mechanism setup to sync pageNumber, chapterId.
Refactored core logic for reader settings into a service. Still can use some more refactoring to signals once the code is more stable.
This commit is contained in:
parent
28755bdb34
commit
9474e8264a
16 changed files with 976 additions and 441 deletions
|
@ -1,4 +1,4 @@
|
|||
import {inject, Injectable} from '@angular/core';
|
||||
import {inject, Injectable, signal} from '@angular/core';
|
||||
import {CreateAnnotationRequest} from "../book-reader/_models/create-annotation-request";
|
||||
import {NgbOffcanvas} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {
|
||||
|
@ -10,12 +10,16 @@ import {
|
|||
import {
|
||||
ViewBookmarkDrawerComponent
|
||||
} from "../book-reader/_components/_drawers/view-bookmarks-drawer/view-bookmark-drawer.component";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {
|
||||
LoadPageEvent,
|
||||
ViewTocDrawerComponent
|
||||
} from "../book-reader/_components/_drawers/view-toc-drawer/view-toc-drawer.component";
|
||||
import {UserBreakpoint, UtilityService} from "../shared/_services/utility.service";
|
||||
import {
|
||||
EpubSettingDrawerComponent,
|
||||
} from "../book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component";
|
||||
import {ReadingProfile} from "../_models/preferences/reading-profiles";
|
||||
import {ReaderSettingUpdate} from "./epub-reader-settings.service";
|
||||
|
||||
/**
|
||||
* Responsible for opening the different readers and providing any context needed. Handles closing or keeping a stack of menus open.
|
||||
|
@ -27,12 +31,19 @@ export class EpubReaderMenuService {
|
|||
|
||||
private readonly offcanvasService = inject(NgbOffcanvas);
|
||||
private readonly utilityService = inject(UtilityService);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
|
||||
/**
|
||||
* The currently active breakpoint, is {@link UserBreakpoint.Never} until the app has loaded
|
||||
*/
|
||||
public readonly isDrawerOpen = signal<boolean>(false);
|
||||
|
||||
openCreateAnnotationDrawer(annotation: CreateAnnotationRequest) {
|
||||
const ref = this.offcanvasService.open(CreateAnnotationDrawerComponent, {position: 'bottom', panelClass: ''});
|
||||
ref.componentInstance.createAnnotation.set(annotation)
|
||||
ref.closed.subscribe(() => this.setDrawerClosed());
|
||||
ref.dismissed.subscribe(() => this.setDrawerClosed());
|
||||
ref.componentInstance.createAnnotation.set(annotation);
|
||||
|
||||
this.isDrawerOpen.set(true);
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,6 +52,10 @@ export class EpubReaderMenuService {
|
|||
this.offcanvasService.dismiss();
|
||||
}
|
||||
const ref = this.offcanvasService.open(ViewAnnotationDrawerComponent, {position: 'end', panelClass: ''});
|
||||
ref.closed.subscribe(() => this.setDrawerClosed());
|
||||
ref.dismissed.subscribe(() => this.setDrawerClosed());
|
||||
|
||||
this.isDrawerOpen.set(true);
|
||||
}
|
||||
|
||||
openViewTocDrawer(chapterId: number, callbackFn: (evt: LoadPageEvent | null) => void) {
|
||||
|
@ -56,6 +71,10 @@ export class EpubReaderMenuService {
|
|||
}
|
||||
callbackFn(res);
|
||||
});
|
||||
ref.closed.subscribe(() => this.setDrawerClosed());
|
||||
ref.dismissed.subscribe(() => this.setDrawerClosed());
|
||||
|
||||
this.isDrawerOpen.set(true);
|
||||
}
|
||||
|
||||
openViewBookmarksDrawer(chapterId: number) {
|
||||
|
@ -64,13 +83,46 @@ export class EpubReaderMenuService {
|
|||
}
|
||||
const ref = this.offcanvasService.open(ViewBookmarkDrawerComponent, {position: 'end', panelClass: ''});
|
||||
ref.componentInstance.chapterId.set(chapterId);
|
||||
ref.closed.subscribe(() => this.setDrawerClosed());
|
||||
ref.dismissed.subscribe(() => this.setDrawerClosed());
|
||||
|
||||
this.isDrawerOpen.set(true);
|
||||
|
||||
}
|
||||
|
||||
|
||||
openSettingsDrawer(chapterId: number, seriesId: number, readingProfile: ReadingProfile, callbackFn: (evt: ReaderSettingUpdate) => void) {
|
||||
if (this.offcanvasService.hasOpenOffcanvas()) {
|
||||
this.offcanvasService.dismiss();
|
||||
}
|
||||
const ref = this.offcanvasService.open(EpubSettingDrawerComponent, {position: 'start', panelClass: ''});
|
||||
ref.componentInstance.chapterId.set(chapterId);
|
||||
ref.componentInstance.seriesId.set(seriesId);
|
||||
ref.componentInstance.readingProfile.set(readingProfile);
|
||||
|
||||
ref.componentInstance.updated.subscribe((res: ReaderSettingUpdate) => {
|
||||
// Check if we are on mobile to collapse the menu
|
||||
if (this.utilityService.activeUserBreakpoint() <= UserBreakpoint.Mobile) {
|
||||
this.closeAll();
|
||||
}
|
||||
callbackFn(res);
|
||||
});
|
||||
ref.closed.subscribe(() => this.setDrawerClosed());
|
||||
ref.dismissed.subscribe(() => this.setDrawerClosed());
|
||||
|
||||
this.isDrawerOpen.set(true);
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
if (this.offcanvasService.hasOpenOffcanvas()) {
|
||||
this.offcanvasService.dismiss();
|
||||
}
|
||||
this.setDrawerClosed();
|
||||
}
|
||||
|
||||
setDrawerClosed() {
|
||||
console.log('Drawer closed');
|
||||
this.isDrawerOpen.set(false);
|
||||
}
|
||||
|
||||
|
||||
|
|
458
UI/Web/src/app/_services/epub-reader-settings.service.ts
Normal file
458
UI/Web/src/app/_services/epub-reader-settings.service.ts
Normal file
|
@ -0,0 +1,458 @@
|
|||
import {DestroyRef, inject, Injectable} from '@angular/core';
|
||||
import {BehaviorSubject, distinctUntilChanged, Observable, Subject} from 'rxjs';
|
||||
import {bookColorThemes, PageStyle} from "../book-reader/_components/reader-settings/reader-settings.component";
|
||||
import {ReadingDirection} from '../_models/preferences/reading-direction';
|
||||
import {WritingStyle} from '../_models/preferences/writing-style';
|
||||
import {BookPageLayoutMode} from "../_models/readers/book-page-layout-mode";
|
||||
import {FormControl, FormGroup} from "@angular/forms";
|
||||
import {ReadingProfile, ReadingProfileKind} from "../_models/preferences/reading-profiles";
|
||||
import {BookService, FontFamily} from "../book-reader/_services/book.service";
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {ThemeService} from './theme.service';
|
||||
import {ReadingProfileService} from "./reading-profile.service";
|
||||
import {debounceTime, skip, tap} from "rxjs/operators";
|
||||
import {BookTheme} from "../_models/preferences/book-theme";
|
||||
import {DOCUMENT} from "@angular/common";
|
||||
|
||||
export interface ReaderSettingUpdate {
|
||||
setting: 'pageStyle' | 'clickToPaginate' | 'fullscreen' | 'writingStyle' | 'layoutMode' | 'readingDirection' | 'immersiveMode' | 'theme';
|
||||
object: any;
|
||||
}
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EpubReaderSettingsService {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly bookService = inject(BookService);
|
||||
private readonly themeService = inject(ThemeService);
|
||||
private readonly readingProfileService = inject(ReadingProfileService);
|
||||
private readonly document = inject(DOCUMENT);
|
||||
|
||||
private pageStylesSubject = new BehaviorSubject<PageStyle>(this.getDefaultPageStyles());
|
||||
private readingDirectionSubject = new BehaviorSubject<ReadingDirection>(ReadingDirection.LeftToRight);
|
||||
private writingStyleSubject = new BehaviorSubject<WritingStyle>(WritingStyle.Horizontal);
|
||||
// @ts-ignore
|
||||
private activeThemeSubject = new BehaviorSubject<BookTheme | undefined>(undefined);
|
||||
private clickToPaginateSubject = new BehaviorSubject<boolean>(false);
|
||||
private layoutModeSubject = new BehaviorSubject<BookPageLayoutMode>(BookPageLayoutMode.Default);
|
||||
private immersiveModeSubject = new BehaviorSubject<boolean>(false);
|
||||
private readingProfileSubject = new BehaviorSubject<ReadingProfile | null>(null);
|
||||
|
||||
// Event subjects for component communication
|
||||
private settingUpdateSubject = new Subject<ReaderSettingUpdate>();
|
||||
|
||||
// Form and data
|
||||
private settingsForm: FormGroup = new FormGroup({});
|
||||
private currentReadingProfile: ReadingProfile | null = null;
|
||||
private currentSeriesId: number | null = null;
|
||||
private fontFamilies: FontFamily[] = this.bookService.getFontFamilies();
|
||||
|
||||
// Public observables
|
||||
public readonly pageStyles$ = this.pageStylesSubject.asObservable();
|
||||
public readonly readingDirection$ = this.readingDirectionSubject.asObservable();
|
||||
public readonly writingStyle$ = this.writingStyleSubject.asObservable();
|
||||
public readonly activeTheme$ = this.activeThemeSubject.asObservable();
|
||||
public readonly clickToPaginate$ = this.clickToPaginateSubject.asObservable();
|
||||
public readonly layoutMode$ = this.layoutModeSubject.asObservable();
|
||||
public readonly immersiveMode$ = this.immersiveModeSubject.asObservable();
|
||||
public readonly readingProfile$ = this.readingProfileSubject.asObservable();
|
||||
public readonly settingUpdates$ = this.settingUpdateSubject.asObservable();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the service with a reading profile and series ID
|
||||
* This should be called when the reader starts up
|
||||
*/
|
||||
async initialize(seriesId: number, readingProfile: ReadingProfile): Promise<void> {
|
||||
this.currentSeriesId = seriesId;
|
||||
this.currentReadingProfile = readingProfile;
|
||||
this.readingProfileSubject.next(readingProfile);
|
||||
|
||||
// Load parent profile if needed
|
||||
if (readingProfile.kind === ReadingProfileKind.Implicit) {
|
||||
try {
|
||||
// const parent = await this.readingProfileService.getForSeries(seriesId, true).toPromise();
|
||||
// Keep the implicit profile but use parent as reference
|
||||
} catch (error) {
|
||||
console.error('Failed to load parent reading profile:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup defaults and form
|
||||
this.setupDefaultSettings();
|
||||
this.setupSettingsForm();
|
||||
|
||||
// Set initial theme
|
||||
const themeName = readingProfile.bookReaderThemeName || this.themeService.defaultBookTheme;
|
||||
this.setTheme(themeName, false);
|
||||
|
||||
// Emit initial values
|
||||
this.emitInitialSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current settings form (for components that need direct form access)
|
||||
*/
|
||||
getSettingsForm(): FormGroup {
|
||||
return this.settingsForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current reading profile
|
||||
*/
|
||||
getCurrentReadingProfile(): ReadingProfile | null {
|
||||
return this.currentReadingProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get font families for UI
|
||||
*/
|
||||
getFontFamilies(): FontFamily[] {
|
||||
return this.fontFamilies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available themes
|
||||
*/
|
||||
getThemes(): BookTheme[] {
|
||||
return bookColorThemes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle reading direction
|
||||
*/
|
||||
toggleReadingDirection(): void {
|
||||
const current = this.readingDirectionSubject.value;
|
||||
const newDirection = current === ReadingDirection.LeftToRight
|
||||
? ReadingDirection.RightToLeft
|
||||
: ReadingDirection.LeftToRight;
|
||||
|
||||
this.readingDirectionSubject.next(newDirection);
|
||||
this.settingUpdateSubject.next({ setting: 'readingDirection', object: newDirection });
|
||||
this.updateImplicitProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle writing style
|
||||
*/
|
||||
toggleWritingStyle(): void {
|
||||
const current = this.writingStyleSubject.value;
|
||||
const newStyle = current === WritingStyle.Horizontal
|
||||
? WritingStyle.Vertical
|
||||
: WritingStyle.Horizontal;
|
||||
|
||||
this.writingStyleSubject.next(newStyle);
|
||||
this.settingUpdateSubject.next({ setting: 'writingStyle', object: newStyle });
|
||||
this.updateImplicitProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set theme
|
||||
*/
|
||||
setTheme(themeName: string, update: boolean = true): void {
|
||||
const theme = bookColorThemes.find(t => t.name === themeName);
|
||||
if (theme) {
|
||||
this.activeThemeSubject.next(theme);
|
||||
this.settingUpdateSubject.next({ setting: 'theme', object: theme });
|
||||
|
||||
if (update) {
|
||||
this.updateImplicitProfile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit fullscreen toggle event
|
||||
*/
|
||||
toggleFullscreen(): void {
|
||||
this.settingUpdateSubject.next({ setting: 'fullscreen', object: null });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update parent reading profile preferences
|
||||
*/
|
||||
updateParentProfile(): void {
|
||||
if (!this.currentReadingProfile || this.currentReadingProfile.kind !== ReadingProfileKind.Implicit || !this.currentSeriesId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.readingProfileService.updateParentProfile(this.currentSeriesId, this.packReadingProfile())
|
||||
.subscribe(newProfile => {
|
||||
this.currentReadingProfile = newProfile;
|
||||
this.readingProfileSubject.next(newProfile);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Promote implicit profile to named profile
|
||||
*/
|
||||
promoteProfile(): Observable<ReadingProfile> {
|
||||
if (!this.currentReadingProfile || this.currentReadingProfile.kind !== ReadingProfileKind.Implicit) {
|
||||
throw new Error('Can only promote implicit profiles');
|
||||
}
|
||||
|
||||
return this.readingProfileService.promoteProfile(this.currentReadingProfile.id).pipe(
|
||||
tap(newProfile => {
|
||||
this.currentReadingProfile = newProfile;
|
||||
this.readingProfileSubject.next(newProfile);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private setupDefaultSettings(): void {
|
||||
if (!this.currentReadingProfile) return;
|
||||
|
||||
// Set up defaults
|
||||
const profile = this.currentReadingProfile;
|
||||
if (profile.bookReaderFontFamily === undefined) {
|
||||
profile.bookReaderFontFamily = 'default';
|
||||
}
|
||||
if (profile.bookReaderFontSize === undefined || profile.bookReaderFontSize < 50) {
|
||||
profile.bookReaderFontSize = 100;
|
||||
}
|
||||
if (profile.bookReaderLineSpacing === undefined || profile.bookReaderLineSpacing < 100) {
|
||||
profile.bookReaderLineSpacing = 100;
|
||||
}
|
||||
if (profile.bookReaderMargin === undefined) {
|
||||
profile.bookReaderMargin = 0;
|
||||
}
|
||||
if (profile.bookReaderReadingDirection === undefined) {
|
||||
profile.bookReaderReadingDirection = ReadingDirection.LeftToRight;
|
||||
}
|
||||
if (profile.bookReaderWritingStyle === undefined) {
|
||||
profile.bookReaderWritingStyle = WritingStyle.Horizontal;
|
||||
}
|
||||
|
||||
// Update internal state
|
||||
this.readingDirectionSubject.next(profile.bookReaderReadingDirection);
|
||||
this.writingStyleSubject.next(profile.bookReaderWritingStyle);
|
||||
this.clickToPaginateSubject.next(profile.bookReaderTapToPaginate);
|
||||
this.layoutModeSubject.next(profile.bookReaderLayoutMode);
|
||||
this.immersiveModeSubject.next(profile.bookReaderImmersiveMode);
|
||||
|
||||
// Set up page styles
|
||||
this.setPageStyles(
|
||||
profile.bookReaderFontFamily,
|
||||
profile.bookReaderFontSize + '%',
|
||||
profile.bookReaderMargin + 'vw',
|
||||
profile.bookReaderLineSpacing + '%'
|
||||
);
|
||||
}
|
||||
|
||||
private setupSettingsForm(): void {
|
||||
if (!this.currentReadingProfile) return;
|
||||
|
||||
const profile = this.currentReadingProfile;
|
||||
|
||||
// Clear existing form
|
||||
this.settingsForm = new FormGroup({});
|
||||
|
||||
// Add controls
|
||||
this.settingsForm.addControl('bookReaderFontFamily', new FormControl(profile.bookReaderFontFamily));
|
||||
this.settingsForm.addControl('bookReaderFontSize', new FormControl(profile.bookReaderFontSize));
|
||||
this.settingsForm.addControl('bookReaderTapToPaginate', new FormControl(profile.bookReaderTapToPaginate));
|
||||
this.settingsForm.addControl('bookReaderLineSpacing', new FormControl(profile.bookReaderLineSpacing));
|
||||
this.settingsForm.addControl('bookReaderMargin', new FormControl(profile.bookReaderMargin));
|
||||
this.settingsForm.addControl('layoutMode', new FormControl(profile.bookReaderLayoutMode));
|
||||
this.settingsForm.addControl('bookReaderImmersiveMode', new FormControl(profile.bookReaderImmersiveMode));
|
||||
|
||||
// Set up value change subscriptions
|
||||
this.setupFormSubscriptions();
|
||||
}
|
||||
|
||||
private setupFormSubscriptions(): void {
|
||||
// Font family changes
|
||||
this.settingsForm.get('bookReaderFontFamily')?.valueChanges.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(fontName => {
|
||||
const familyName = this.fontFamilies.find(f => f.title === fontName)?.family || 'default';
|
||||
const currentStyles = this.pageStylesSubject.value;
|
||||
|
||||
if (familyName === 'default') {
|
||||
currentStyles['font-family'] = 'inherit';
|
||||
} else {
|
||||
currentStyles['font-family'] = `'${familyName}'`;
|
||||
}
|
||||
|
||||
this.pageStylesSubject.next({ ...currentStyles });
|
||||
this.settingUpdateSubject.next({ setting: 'pageStyle', object: currentStyles });
|
||||
});
|
||||
|
||||
// Font size changes
|
||||
this.settingsForm.get('bookReaderFontSize')?.valueChanges.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(value => {
|
||||
const currentStyles = this.pageStylesSubject.value;
|
||||
currentStyles['font-size'] = value + '%';
|
||||
this.pageStylesSubject.next({ ...currentStyles });
|
||||
this.settingUpdateSubject.next({ setting: 'pageStyle', object: currentStyles });
|
||||
});
|
||||
|
||||
// Tap to paginate changes
|
||||
this.settingsForm.get('bookReaderTapToPaginate')?.valueChanges.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(value => {
|
||||
this.clickToPaginateSubject.next(value);
|
||||
this.settingUpdateSubject.next({ setting: 'clickToPaginate', object: value });
|
||||
});
|
||||
|
||||
// Line spacing changes
|
||||
this.settingsForm.get('bookReaderLineSpacing')?.valueChanges.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(value => {
|
||||
const currentStyles = this.pageStylesSubject.value;
|
||||
currentStyles['line-height'] = value + '%';
|
||||
this.pageStylesSubject.next({ ...currentStyles });
|
||||
this.settingUpdateSubject.next({ setting: 'pageStyle', object: currentStyles });
|
||||
});
|
||||
|
||||
// Margin changes
|
||||
this.settingsForm.get('bookReaderMargin')?.valueChanges.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(value => {
|
||||
const currentStyles = this.pageStylesSubject.value;
|
||||
currentStyles['margin-left'] = value + 'vw';
|
||||
currentStyles['margin-right'] = value + 'vw';
|
||||
this.pageStylesSubject.next({ ...currentStyles });
|
||||
this.settingUpdateSubject.next({ setting: 'pageStyle', object: currentStyles });
|
||||
});
|
||||
|
||||
// Layout mode changes
|
||||
this.settingsForm.get('layoutMode')?.valueChanges.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe((layoutMode: BookPageLayoutMode) => {
|
||||
this.layoutModeSubject.next(layoutMode);
|
||||
this.settingUpdateSubject.next({ setting: 'layoutMode', object: layoutMode });
|
||||
});
|
||||
|
||||
// Immersive mode changes
|
||||
this.settingsForm.get('bookReaderImmersiveMode')?.valueChanges.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe((immersiveMode: boolean) => {
|
||||
if (immersiveMode) {
|
||||
this.settingsForm.get('bookReaderTapToPaginate')?.setValue(true);
|
||||
}
|
||||
this.immersiveModeSubject.next(immersiveMode);
|
||||
this.settingUpdateSubject.next({ setting: 'immersiveMode', object: immersiveMode });
|
||||
});
|
||||
|
||||
// Update implicit profile on form changes
|
||||
this.settingsForm.valueChanges.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
skip(1), // Skip initial form creation
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(() => {
|
||||
this.updateImplicitProfile();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets a selection of settings to their default (Page Styles)
|
||||
*/
|
||||
resetSettings() {
|
||||
const defaultStyles = this.getDefaultPageStyles();
|
||||
this.setPageStyles(
|
||||
defaultStyles["font-family"],
|
||||
defaultStyles["font-size"],
|
||||
defaultStyles['margin-left'] ,
|
||||
defaultStyles['line-height'],
|
||||
);
|
||||
}
|
||||
|
||||
private emitInitialSettings(): void {
|
||||
// Emit all current settings so the reader can initialize properly
|
||||
this.settingUpdateSubject.next({ setting: 'pageStyle', object: this.pageStylesSubject.value });
|
||||
this.settingUpdateSubject.next({ setting: 'clickToPaginate', object: this.clickToPaginateSubject.value });
|
||||
this.settingUpdateSubject.next({ setting: 'layoutMode', object: this.layoutModeSubject.value });
|
||||
this.settingUpdateSubject.next({ setting: 'readingDirection', object: this.readingDirectionSubject.value });
|
||||
this.settingUpdateSubject.next({ setting: 'writingStyle', object: this.writingStyleSubject.value });
|
||||
this.settingUpdateSubject.next({ setting: 'immersiveMode', object: this.immersiveModeSubject.value });
|
||||
|
||||
const activeTheme = this.activeThemeSubject.value;
|
||||
if (activeTheme) {
|
||||
this.settingUpdateSubject.next({ setting: 'theme', object: activeTheme });
|
||||
}
|
||||
}
|
||||
|
||||
private updateImplicitProfile(): void {
|
||||
if (!this.currentReadingProfile || !this.currentSeriesId) return;
|
||||
|
||||
this.readingProfileService.updateImplicit(this.packReadingProfile(), this.currentSeriesId)
|
||||
.subscribe({
|
||||
next: newProfile => {
|
||||
this.currentReadingProfile = newProfile;
|
||||
this.readingProfileSubject.next(newProfile);
|
||||
},
|
||||
error: err => {
|
||||
console.error('Failed to update implicit profile:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private packReadingProfile(): ReadingProfile {
|
||||
if (!this.currentReadingProfile) {
|
||||
throw new Error('No current reading profile');
|
||||
}
|
||||
|
||||
const modelSettings = this.settingsForm.getRawValue();
|
||||
const data = { ...this.currentReadingProfile };
|
||||
|
||||
data.bookReaderFontFamily = modelSettings.bookReaderFontFamily;
|
||||
data.bookReaderFontSize = modelSettings.bookReaderFontSize;
|
||||
data.bookReaderLineSpacing = modelSettings.bookReaderLineSpacing;
|
||||
data.bookReaderMargin = modelSettings.bookReaderMargin;
|
||||
data.bookReaderTapToPaginate = modelSettings.bookReaderTapToPaginate;
|
||||
data.bookReaderLayoutMode = modelSettings.layoutMode;
|
||||
data.bookReaderImmersiveMode = modelSettings.bookReaderImmersiveMode;
|
||||
|
||||
data.bookReaderReadingDirection = this.readingDirectionSubject.value;
|
||||
data.bookReaderWritingStyle = this.writingStyleSubject.value;
|
||||
|
||||
const activeTheme = this.activeThemeSubject.value;
|
||||
if (activeTheme) {
|
||||
data.bookReaderThemeName = activeTheme.name;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private setPageStyles(fontFamily?: string, fontSize?: string, margin?: string, lineHeight?: string): void {
|
||||
const windowWidth = window.innerWidth || this.document.documentElement.clientWidth || this.document.body.clientWidth;
|
||||
const mobileBreakpointMarginOverride = 700;
|
||||
|
||||
let defaultMargin = '15vw';
|
||||
if (windowWidth <= mobileBreakpointMarginOverride) {
|
||||
defaultMargin = '5vw';
|
||||
}
|
||||
|
||||
const currentStyles = this.pageStylesSubject.value;
|
||||
const newStyles: PageStyle = {
|
||||
'font-family': fontFamily || currentStyles['font-family'] || 'default',
|
||||
'font-size': fontSize || currentStyles['font-size'] || '100%',
|
||||
'margin-left': margin || currentStyles['margin-left'] || defaultMargin,
|
||||
'margin-right': margin || currentStyles['margin-right'] || defaultMargin,
|
||||
'line-height': lineHeight || currentStyles['line-height'] || '100%'
|
||||
};
|
||||
|
||||
this.pageStylesSubject.next(newStyles);
|
||||
this.updateImplicitProfile();
|
||||
this.settingUpdateSubject.next({ setting: 'pageStyle', object: newStyles });
|
||||
}
|
||||
|
||||
public getDefaultPageStyles(): PageStyle {
|
||||
return {
|
||||
'font-family': 'default',
|
||||
'font-size': '100%',
|
||||
'margin-left': '15vw',
|
||||
'margin-right': '15vw',
|
||||
'line-height': '100%'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// You must add this on a component based drawer
|
||||
:host {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<ng-container *transloco="let t; prefix: 'epub-setting-drawer'">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title">
|
||||
{{t('title')}}
|
||||
</h5>
|
||||
<button type="button" class="btn-close text-reset" [attr.aria-label]="t('close')" (click)="close()"></button>
|
||||
</div>
|
||||
|
||||
<div class="offcanvas-body">
|
||||
@let sId = seriesId();
|
||||
@let rp = readingProfile();
|
||||
@if (sId && rp) {
|
||||
<app-reader-settings
|
||||
[seriesId]="sId"
|
||||
[readingProfile]="rp"
|
||||
(colorThemeUpdate)="updateColorTheme($event)"
|
||||
(styleUpdate)="updateReaderStyles($event)"
|
||||
(clickToPaginateChanged)="showPaginationOverlay($event)"
|
||||
(fullscreen)="toggleFullscreen()"
|
||||
(bookReaderWritingStyle)="updateWritingStyle($event)"
|
||||
(layoutModeUpdate)="updateLayoutMode($event)"
|
||||
(readingDirection)="updateReadingDirection($event)"
|
||||
(immersiveMode)="updateImmersiveMode($event)"
|
||||
></app-reader-settings>
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
|
@ -0,0 +1,6 @@
|
|||
// You must add this on a component based drawer
|
||||
:host {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
effect,
|
||||
EventEmitter,
|
||||
inject,
|
||||
model
|
||||
} from '@angular/core';
|
||||
import {NgbActiveOffcanvas} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {PageStyle, ReaderSettingsComponent} from "../../reader-settings/reader-settings.component";
|
||||
import {ReadingProfile} from "../../../../_models/preferences/reading-profiles";
|
||||
import {BookTheme} from "../../../../_models/preferences/book-theme";
|
||||
import {WritingStyle} from "../../../../_models/preferences/writing-style";
|
||||
import {BookPageLayoutMode} from "../../../../_models/readers/book-page-layout-mode";
|
||||
import {ReadingDirection} from "../../../../_models/preferences/reading-direction";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {ReaderSettingUpdate} from "../../../../_services/epub-reader-settings.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-epub-setting-drawer',
|
||||
imports: [
|
||||
ReaderSettingsComponent,
|
||||
TranslocoDirective
|
||||
],
|
||||
templateUrl: './epub-setting-drawer.component.html',
|
||||
styleUrl: './epub-setting-drawer.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EpubSettingDrawerComponent {
|
||||
private readonly activeOffcanvas = inject(NgbActiveOffcanvas);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
|
||||
chapterId = model<number>();
|
||||
seriesId = model<number>();
|
||||
readingProfile = model<ReadingProfile>();
|
||||
|
||||
updated = new EventEmitter<ReaderSettingUpdate>();
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
effect(() => {
|
||||
const id = this.chapterId();
|
||||
if (!id) {
|
||||
console.error('You must pass chapterId');
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
updateColorTheme(theme: BookTheme) {
|
||||
const evt = {setting: 'theme', object: theme} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
updateReaderStyles(pageStyles: PageStyle) {
|
||||
const evt = {setting: 'pageStyle', object: pageStyles} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
showPaginationOverlay(clickToPaginate: boolean) {
|
||||
const evt = {setting: 'clickToPaginate', object: clickToPaginate} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
toggleFullscreen() {
|
||||
const evt = {setting: 'fullscreen', object: null} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
updateWritingStyle(writingStyle: WritingStyle) {
|
||||
const evt = {setting: 'writingStyle', object: writingStyle} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
updateLayoutMode(mode: BookPageLayoutMode) {
|
||||
const evt = {setting: 'layoutMode', object: mode} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
updateReadingDirection(readingDirection: ReadingDirection) {
|
||||
const evt = {setting: 'readingDirection', object: readingDirection} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
updateImmersiveMode(immersiveMode: boolean) {
|
||||
const evt = {setting: 'immersiveMode', object: immersiveMode} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.activeOffcanvas.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// You must add this on a component based drawer
|
||||
:host {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// You must add this on a component based drawer
|
||||
:host {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// You must add this on a component based drawer
|
||||
:host {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
|
@ -1,80 +1,26 @@
|
|||
<div class="container-flex {{darkMode ? 'dark-mode' : ''}} reader-container {{ColumnLayout}} {{WritingStyleClass}}"
|
||||
<div class="container-flex {{darkMode ? 'dark-mode' : ''}} reader-container {{layoutMode | columnLayoutClass}} {{writingStyle | writingStyleClass}}"
|
||||
tabindex="0" #reader>
|
||||
<ng-container *transloco="let t; read: 'book-reader'">
|
||||
<ng-container *transloco="let t; prefix: 'book-reader'">
|
||||
<div class="fixed-top" #stickyTop>
|
||||
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">{{t('skip-header')}}</a>
|
||||
<ng-container [ngTemplateOutlet]="topActionBar" [ngTemplateOutletContext]="{isTop: true}"></ng-container>
|
||||
<app-book-line-overlay [parent]="bookContainerElemRef" *ngIf="page !== undefined"
|
||||
[libraryId]="libraryId"
|
||||
[volumeId]="volumeId"
|
||||
[chapterId]="chapterId"
|
||||
[seriesId]="seriesId"
|
||||
[pageNumber]="pageNum"
|
||||
(isOpen)="updateLineOverlayOpen($event)"
|
||||
(refreshToC)="refreshPersonalToC()">
|
||||
</app-book-line-overlay>
|
||||
<app-drawer #commentDrawer="drawer" [(isOpen)]="drawerOpen" [options]="{topOffset: topOffset}">
|
||||
<div header>
|
||||
<h5 class="mb-0">{{t('title')}}</h5>
|
||||
<span style="font-size: 14px; color: var(--primary-color)" tabindex="0" role="button" (click)="closeReader()">{{t('close-reader')}}</span>
|
||||
</div>
|
||||
<div subheader>
|
||||
<div class="pagination-cont">
|
||||
<!-- Column mode needs virtual pages -->
|
||||
@if (layoutMode !== BookPageLayoutMode.Default) {
|
||||
@let vp = getVirtualPage();
|
||||
<div class="virt-pagination-cont">
|
||||
<div class="g-0 text-center">
|
||||
{{t('page-label')}}
|
||||
</div>
|
||||
<div class="d-flex align-items-center justify-content-between text-center row g-0">
|
||||
<button class="btn btn-small btn-icon col-1" (click)="prevPage()" [title]="t('prev-page')" [disabled]="vp[0] === 1">
|
||||
<i class="fa-solid fa-caret-left" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="col-1">{{vp[0]}}</div>
|
||||
<div class="col-8">
|
||||
<ngb-progressbar type="primary" height="5px" [value]="vp[0]" [max]="vp[1]"></ngb-progressbar>
|
||||
</div>
|
||||
<div class="col-1 btn-icon">{{vp[1]}}</div>
|
||||
<button class="btn btn-small btn-icon col-1" (click)="nextPage()" [title]="t('next-page')"><i class="fa-solid fa-caret-right" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="g-0 text-center">
|
||||
{{t('pagination-header')}}
|
||||
</div>
|
||||
<div class="d-flex align-items-center justify-content-between text-center row g-0">
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter()" [title]="t('prev-chapter')"><i class="fa fa-fast-backward" aria-hidden="true"></i></button>
|
||||
<div class="col-1" (click)="goToPage(0)">{{pageNum}}</div>
|
||||
<div class="col-8">
|
||||
<ngb-progressbar class="clickable" [title]="t('go-to-page')" (click)="goToPage()" type="primary" height="5px" [value]="pageNum" [max]="maxPages - 1"></ngb-progressbar>
|
||||
</div>
|
||||
<div class="col-1 btn-icon" (click)="goToPage(maxPages - 1)" [title]="t('go-to-last-page')">{{maxPages - 1}}</div>
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="nextChapterDisabled" (click)="loadNextChapter()" [title]="t('next-chapter')"><i class="fa fa-fast-forward" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div body class="drawer-body">
|
||||
<app-reader-settings
|
||||
[seriesId]="seriesId"
|
||||
[readingProfile]="readingProfile"
|
||||
(colorThemeUpdate)="updateColorTheme($event)"
|
||||
(styleUpdate)="updateReaderStyles($event)"
|
||||
(clickToPaginateChanged)="showPaginationOverlay($event)"
|
||||
(fullscreen)="toggleFullscreen()"
|
||||
(bookReaderWritingStyle)="updateWritingStyle($event)"
|
||||
(layoutModeUpdate)="updateLayoutMode($event)"
|
||||
(readingDirection)="updateReadingDirection($event)"
|
||||
(immersiveMode)="updateImmersiveMode($event)"
|
||||
></app-reader-settings>
|
||||
</div>
|
||||
</app-drawer>
|
||||
@if (page !== undefined) {
|
||||
<app-book-line-overlay [parent]="bookContainerElemRef"
|
||||
[libraryId]="libraryId"
|
||||
[volumeId]="volumeId"
|
||||
[chapterId]="chapterId"
|
||||
[seriesId]="seriesId"
|
||||
[pageNumber]="pageNum"
|
||||
(isOpen)="updateLineOverlayOpen($event)"
|
||||
(refreshToC)="refreshPersonalToC()">
|
||||
</app-book-line-overlay>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div #readingSection class="reading-section {{ColumnLayout}} {{WritingStyleClass}}" [ngStyle]="{'width': PageWidthForPagination}"
|
||||
<div #readingSection class="reading-section {{layoutMode | columnLayoutClass}} {{writingStyle | writingStyleClass}}" [ngStyle]="{'width': PageWidthForPagination}"
|
||||
[ngClass]="{'immersive' : immersiveMode || !actionBarVisible}" [@isLoading]="isLoading" (click)="handleReaderClick($event)">
|
||||
|
||||
<ng-container *ngIf="clickToPaginate && !hidePagination">
|
||||
@if (clickToPaginate && !hidePagination) {
|
||||
<div class="left {{clickOverlayClass('left')}} no-observe"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
[ngClass]="{'immersive' : immersiveMode}"
|
||||
|
@ -85,26 +31,31 @@
|
|||
[ngClass]="{'immersive' : immersiveMode}"
|
||||
tabindex="-1"
|
||||
[ngStyle]="{height: PageHeightForPagination}"></div>
|
||||
</ng-container>
|
||||
<div #bookContainer class="book-container {{WritingStyleClass}}"
|
||||
[ngClass]="{'immersive' : immersiveMode}"
|
||||
(mousedown)="mouseDown($event)" >
|
||||
}
|
||||
|
||||
<div #readingHtml class="book-content {{ColumnLayout}} {{WritingStyleClass}}"
|
||||
[ngStyle]="{'max-height': ColumnHeight, 'max-width': VerticalBookContentWidth, 'width': VerticalBookContentWidth, 'column-width': ColumnWidth}"
|
||||
[ngClass]="{'immersive': immersiveMode && actionBarVisible}"
|
||||
[innerHtml]="page" *ngIf="page !== undefined" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)" (wheel)="onWheel($event)"></div>
|
||||
<div *ngIf="page !== undefined && (scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode === BookPageLayoutMode.Default)"
|
||||
(click)="$event.stopPropagation();"
|
||||
[ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}">
|
||||
<ng-container [ngTemplateOutlet]="actionBar" [ngTemplateOutletContext]="{isTop: false}"></ng-container>
|
||||
</div>
|
||||
<div #bookContainer class="book-container {{writingStyle | writingStyleClass}}"
|
||||
[ngClass]="{'immersive' : immersiveMode}"
|
||||
(mousedown)="mouseDown($event)" >
|
||||
|
||||
@if (page !== undefined) {
|
||||
<div #readingHtml class="book-content {{layoutMode | columnLayoutClass}} {{writingStyle | writingStyleClass}}"
|
||||
[ngStyle]="{'max-height': ColumnHeight, 'max-width': VerticalBookContentWidth, 'width': VerticalBookContentWidth, 'column-width': ColumnWidth}"
|
||||
[ngClass]="{'immersive': immersiveMode && actionBarVisible}"
|
||||
[innerHtml]="page" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)" (wheel)="onWheel($event)"></div>
|
||||
|
||||
@if ((scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode === BookPageLayoutMode.Default)) {
|
||||
<div (click)="$event.stopPropagation();"
|
||||
[ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}">
|
||||
<ng-container [ngTemplateOutlet]="actionBar" [ngTemplateOutletContext]="{isTop: false}"></ng-container>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-template #topActionBar let-isTop>
|
||||
@if (!immersiveMode || drawerOpen || actionBarVisible) {
|
||||
<ng-template #topActionBar>
|
||||
@if (!immersiveMode || epubMenuService.isDrawerOpen() || actionBarVisible) {
|
||||
<div class="action-bar d-flex align-items-center px-2">
|
||||
<!-- Left: Drawer toggle -->
|
||||
<button class="btn btn-secondary me-2" (click)="toggleDrawer()">
|
||||
|
@ -118,9 +69,11 @@
|
|||
<span class="visually-hidden">{{ t('loading-book') }}</span>
|
||||
</div>
|
||||
} @else {
|
||||
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-mode-alt')">
|
||||
(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{ t('incognito-mode-label') }}</span>)
|
||||
</span>
|
||||
@if (incognitoMode) {
|
||||
<span (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-mode-alt')">
|
||||
(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{ t('incognito-mode-label') }}</span>)
|
||||
</span>
|
||||
}
|
||||
<span class="ms-1 book-title-text" [ngbTooltip]="bookTitle">{{ bookTitle }}</span>
|
||||
}
|
||||
</div>
|
||||
|
@ -152,37 +105,40 @@
|
|||
|
||||
|
||||
<ng-template #actionBar let-isTop>
|
||||
<div class="action-bar row g-0 justify-content-between" *ngIf="!immersiveMode || drawerOpen || actionBarVisible">
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
|
||||
(click)="!isTop && movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsPrevDisabled : IsNextDisabled"
|
||||
title="{{readingDirection === ReadingDirection.LeftToRight ? t('previous') : t('next')}} Page">
|
||||
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsPrevChapter : IsNextChapter) ? 'fa-angle-double-left' : 'fa-angle-left'}} {{readingDirection === ReadingDirection.RightToLeft ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button *ngIf="!this.adhocPageHistory.isEmpty()" class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="goBack()" [title]="t('go-back')">
|
||||
<i class="fa fa-reply" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary col-2 col-xs-1" (click)="toggleDrawer()">
|
||||
<i class="fa fa-bars" aria-hidden="true"></i></button>
|
||||
<div class="book-title col-2 d-none d-sm-block">
|
||||
@if(isLoading) {
|
||||
<div class="spinner-border spinner-border-sm text-primary" style="border-radius: 50%;" role="status">
|
||||
<span class="visually-hidden">{{t('loading-book')}}</span>
|
||||
</div>
|
||||
} @else {
|
||||
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-mode-alt')">
|
||||
(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{t('incognito-mode-label')}}</span>)</span>
|
||||
<span class="book-title-text ms-1" [ngbTooltip]="bookTitle">{{bookTitle}}</span>
|
||||
@if (!immersiveMode || epubMenuService.isDrawerOpen() || actionBarVisible) {
|
||||
<div class="action-bar row g-0 justify-content-between">
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
|
||||
(click)="!isTop && movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsPrevDisabled : IsNextDisabled"
|
||||
title="{{readingDirection === ReadingDirection.LeftToRight ? t('previous') : t('next')}} Page">
|
||||
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsPrevChapter : IsNextChapter) ? 'fa-angle-double-left' : 'fa-angle-left'}} {{readingDirection === ReadingDirection.RightToLeft ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
@if (!this.adhocPageHistory.isEmpty()) {
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="goBack()" [title]="t('go-back')">
|
||||
<i class="fa fa-reply" aria-hidden="true"></i>
|
||||
</button>
|
||||
}
|
||||
|
||||
|
||||
<div class="book-title col-2 d-none d-sm-block">
|
||||
@if(isLoading) {
|
||||
<div class="spinner-border spinner-border-sm text-primary" style="border-radius: 50%;" role="status">
|
||||
<span class="visually-hidden">{{t('loading-book')}}</span>
|
||||
</div>
|
||||
} @else {
|
||||
20% complete, 3 hrs to go
|
||||
}
|
||||
</div>
|
||||
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
|
||||
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsNextDisabled : IsPrevDisabled"
|
||||
(click)="!isTop && movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
|
||||
title="{{readingDirection === ReadingDirection.LeftToRight ? t('next') : t('previous')}} Page">
|
||||
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsNextChapter : IsPrevChapter) ? 'fa-angle-double-right' : 'fa-angle-right'}} {{readingDirection === ReadingDirection.LeftToRight ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn-secondary col-2 col-xs-1" (click)="closeReader()"><i class="fa fa-times-circle" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
|
||||
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsNextDisabled : IsPrevDisabled"
|
||||
(click)="!isTop && movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
|
||||
title="{{readingDirection === ReadingDirection.LeftToRight ? t('next') : t('previous')}} Page">
|
||||
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsNextChapter : IsPrevChapter) ? 'fa-angle-double-right' : 'fa-angle-right'}} {{readingDirection === ReadingDirection.LeftToRight ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
HostListener,
|
||||
inject,
|
||||
Inject,
|
||||
model,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Renderer2,
|
||||
|
@ -16,7 +17,7 @@ import {
|
|||
ViewChild,
|
||||
ViewContainerRef
|
||||
} from '@angular/core';
|
||||
import {DOCUMENT, NgClass, NgIf, NgStyle, NgTemplateOutlet} from '@angular/common';
|
||||
import {DOCUMENT, NgClass, NgStyle, NgTemplateOutlet} from '@angular/common';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {ToastrService} from 'ngx-toastr';
|
||||
import {forkJoin, fromEvent, merge, of} from 'rxjs';
|
||||
|
@ -39,15 +40,13 @@ import {LibraryService} from 'src/app/_services/library.service';
|
|||
import {LibraryType} from 'src/app/_models/library/library';
|
||||
import {BookTheme} from 'src/app/_models/preferences/book-theme';
|
||||
import {BookPageLayoutMode} from 'src/app/_models/readers/book-page-layout-mode';
|
||||
import {PageStyle, ReaderSettingsComponent} from '../reader-settings/reader-settings.component';
|
||||
import {PageStyle} from '../reader-settings/reader-settings.component';
|
||||
import {ThemeService} from 'src/app/_services/theme.service';
|
||||
import {ScrollService} from 'src/app/_services/scroll.service';
|
||||
import {PAGING_DIRECTION} from 'src/app/manga-reader/_models/reader-enums';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {NgbProgressbar, NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {DrawerComponent} from '../../../shared/drawer/drawer.component';
|
||||
import {NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {BookLineOverlayComponent} from "../book-line-overlay/book-line-overlay.component";
|
||||
import {PersonalToCEvent} from "../personal-table-of-contents/personal-table-of-contents.component";
|
||||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
||||
import {ReadingProfile} from "../../../_models/preferences/reading-profiles";
|
||||
import {ConfirmService} from "../../../shared/confirm.service";
|
||||
|
@ -55,6 +54,9 @@ import {EpubHighlightComponent} from "../_annotations/epub-highlight/epub-highli
|
|||
import {Annotation} from "../../_models/annotation";
|
||||
import {EpubReaderMenuService} from "../../../_services/epub-reader-menu.service";
|
||||
import {LoadPageEvent} from "../_drawers/view-toc-drawer/view-toc-drawer.component";
|
||||
import {EpubReaderSettingsService, ReaderSettingUpdate} from "../../../_services/epub-reader-settings.service";
|
||||
import {ColumnLayoutClassPipe} from "../../_pipes/column-layout-class.pipe";
|
||||
import {WritingStyleClassPipe} from "../../_pipes/writing-style-class.pipe";
|
||||
|
||||
|
||||
interface HistoryPoint {
|
||||
|
@ -97,9 +99,8 @@ const elementLevelStyles = ['line-height', 'font-family'];
|
|||
transition('false <=> true', animate('4000ms'))
|
||||
])
|
||||
],
|
||||
imports: [NgTemplateOutlet, DrawerComponent, NgIf, NgbProgressbar,
|
||||
ReaderSettingsComponent, NgStyle, NgClass, NgbTooltip,
|
||||
BookLineOverlayComponent, TranslocoDirective]
|
||||
imports: [NgTemplateOutlet, NgStyle, NgClass, NgbTooltip,
|
||||
BookLineOverlayComponent, TranslocoDirective, ColumnLayoutClassPipe, WritingStyleClassPipe]
|
||||
})
|
||||
export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
|
@ -120,6 +121,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
private readonly confirmService = inject(ConfirmService);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
protected readonly epubMenuService = inject(EpubReaderMenuService);
|
||||
protected readonly readerSettingsService = inject(EpubReaderSettingsService);
|
||||
|
||||
protected readonly BookPageLayoutMode = BookPageLayoutMode;
|
||||
protected readonly WritingStyle = WritingStyle;
|
||||
|
@ -177,7 +179,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
/**
|
||||
* Belongs to drawer component
|
||||
*/
|
||||
drawerOpen = false;
|
||||
//drawerOpen = false;
|
||||
/**
|
||||
* If the word/line overlay is open
|
||||
*/
|
||||
|
@ -245,7 +247,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
/**
|
||||
* Internal property used to capture all the different css properties to render on all elements. This is a cached version that is updated from reader-settings component
|
||||
*/
|
||||
pageStyles!: PageStyle;
|
||||
pageStyles = model<PageStyle>(this.readerSettingsService.getDefaultPageStyles());
|
||||
//pageStyles!: PageStyle;
|
||||
|
||||
/**
|
||||
* Offset for drawer and rendering canvas. Fixed to 62px.
|
||||
|
@ -264,7 +267,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
*/
|
||||
darkMode = true;
|
||||
/**
|
||||
* A anchors that map to the page number. When you click on one of these, we will load a given page up for the user.
|
||||
* Anchors that map to the page number. When you click on one of these, we will load a given page up for the user.
|
||||
*/
|
||||
pageAnchors: {[n: string]: number } = {};
|
||||
currentPageAnchor: string = '';
|
||||
|
@ -430,25 +433,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
return '';
|
||||
}
|
||||
|
||||
get ColumnLayout() {
|
||||
switch (this.layoutMode) {
|
||||
case BookPageLayoutMode.Default:
|
||||
return '';
|
||||
case BookPageLayoutMode.Column1:
|
||||
return 'column-layout-1';
|
||||
case BookPageLayoutMode.Column2:
|
||||
return 'column-layout-2';
|
||||
}
|
||||
}
|
||||
|
||||
get WritingStyleClass() {
|
||||
switch (this.writingStyle) {
|
||||
case WritingStyle.Horizontal:
|
||||
return '';
|
||||
case WritingStyle.Vertical:
|
||||
return 'writing-style-vertical';
|
||||
}
|
||||
}
|
||||
|
||||
get PageWidthForPagination() {
|
||||
if (this.layoutMode === BookPageLayoutMode.Default && this.writingStyle === WritingStyle.Vertical && this.horizontalScrollbarNeeded) {
|
||||
|
@ -577,7 +561,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.navService.showSideNav();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
async ngOnInit() {
|
||||
const libraryId = this.route.snapshot.paramMap.get('libraryId');
|
||||
const seriesId = this.route.snapshot.paramMap.get('seriesId');
|
||||
const chapterId = this.route.snapshot.paramMap.get('chapterId');
|
||||
|
@ -599,7 +583,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(data => {
|
||||
this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(async (data) => {
|
||||
this.readingProfile = data['readingProfile'];
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
|
@ -610,16 +594,16 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
this.memberService.hasReadingProgress(this.libraryId).pipe(take(1)).subscribe(hasProgress => {
|
||||
if (!hasProgress) {
|
||||
this.toggleDrawer();
|
||||
this.toggleDrawer(); // TODO: Remove toggling drawer on first load
|
||||
this.toastr.info(translate('toasts.book-settings-info'));
|
||||
}
|
||||
});
|
||||
|
||||
this.init();
|
||||
await this.init();
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
async init() {
|
||||
this.nextChapterId = CHAPTER_ID_NOT_FETCHED;
|
||||
this.prevChapterId = CHAPTER_ID_NOT_FETCHED;
|
||||
this.nextChapterDisabled = false;
|
||||
|
@ -628,17 +612,42 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.cdRef.markForCheck();
|
||||
|
||||
|
||||
this.bookService.getBookInfo(this.chapterId).subscribe(info => {
|
||||
this.bookService.getBookInfo(this.chapterId).subscribe(async (info) => {
|
||||
if (this.readingListMode && info.seriesFormat !== MangaFormat.EPUB) {
|
||||
// Redirect to the manga reader.
|
||||
const params = this.readerService.getQueryParamsObject(this.incognitoMode, this.readingListMode, this.readingListId);
|
||||
this.router.navigate(this.readerService.getNavigationArray(info.libraryId, info.seriesId, this.chapterId, info.seriesFormat), {queryParams: params});
|
||||
await this.router.navigate(this.readerService.getNavigationArray(info.libraryId, info.seriesId, this.chapterId, info.seriesFormat), {queryParams: params});
|
||||
return;
|
||||
}
|
||||
|
||||
this.bookTitle = info.bookTitle;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
await this.readerSettingsService.initialize(this.seriesId, this.readingProfile);
|
||||
|
||||
|
||||
|
||||
this.readerSettingsService.readingProfile$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(profile => {
|
||||
if (profile) {
|
||||
this.readingProfile = profile;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
this.readerSettingsService.settingUpdates$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe((update: ReaderSettingUpdate) => {
|
||||
this.handleReaderSettingsUpdate(update);
|
||||
});
|
||||
|
||||
this.readerSettingsService.activeTheme$.pipe(take(1)).subscribe(res => {
|
||||
if (!res) return;
|
||||
this.updateColorTheme(res);
|
||||
});
|
||||
// TODO: I may need to do this for everything
|
||||
|
||||
forkJoin({
|
||||
chapter: this.seriesService.getChapter(this.chapterId),
|
||||
progress: this.readerService.getProgress(this.chapterId),
|
||||
|
@ -837,19 +846,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
loadChapterPage(event: {pageNum: number, part: string}) {
|
||||
this.setPageNum(event.pageNum);
|
||||
this.loadPage('id("' + event.part + '")');
|
||||
}
|
||||
|
||||
/**
|
||||
* From personal table of contents/bookmark
|
||||
* @param event
|
||||
*/
|
||||
loadChapterPart(event: PersonalToCEvent) {
|
||||
this.setPageNum(event.pageNum);
|
||||
this.loadPage(event.scrollPart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a click handler for any anchors that have 'kavita-page'. If 'kavita-page' present, changes page to kavita-page and optionally passes a part value
|
||||
|
@ -940,7 +936,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
setTimeout(() => {
|
||||
this.addLinkClickHandlers();
|
||||
this.updateReaderStyles(this.pageStyles);
|
||||
this.readerSettingsService.pageStyles$.pipe(take(1)).subscribe(res => {
|
||||
this.updateReaderStyles(res);
|
||||
});
|
||||
|
||||
|
||||
const imgs = this.readingSectionElemRef.nativeElement.querySelectorAll('img');
|
||||
if (imgs === null || imgs.length === 0) {
|
||||
|
@ -1155,6 +1154,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
if (!this.nextChapterPrefetched && this.nextChapterId !== CHAPTER_ID_DOESNT_EXIST) {
|
||||
this.readerService.getChapterInfo(this.nextChapterId).pipe(take(1), catchError(err => {
|
||||
this.nextChapterDisabled = true;
|
||||
console.error(err);
|
||||
this.cdRef.markForCheck();
|
||||
return of(null);
|
||||
})).subscribe(res => {
|
||||
|
@ -1165,6 +1165,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
if (!this.prevChapterPrefetched && this.prevChapterId !== CHAPTER_ID_DOESNT_EXIST) {
|
||||
this.readerService.getChapterInfo(this.prevChapterId).pipe(take(1), catchError(err => {
|
||||
this.prevChapterDisabled = true;
|
||||
console.error(err);
|
||||
this.cdRef.markForCheck();
|
||||
return of(null);
|
||||
})).subscribe(res => {
|
||||
|
@ -1264,7 +1265,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
*/
|
||||
getPageWidth() {
|
||||
if (this.readingSectionElemRef == null) return 0;
|
||||
const margin = (this.convertVwToPx(parseInt(this.pageStyles['margin-left'], 10)) * 2);
|
||||
const margin = (this.convertVwToPx(parseInt(this.pageStyles()['margin-left'], 10)) * 2);
|
||||
return this.readingSectionElemRef.nativeElement.clientWidth - margin + COLUMN_GAP;
|
||||
}
|
||||
|
||||
|
@ -1276,7 +1277,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
getVerticalPageWidth() {
|
||||
const margin = (window.innerWidth * (parseInt(this.pageStyles['margin-left'], 10) / 100)) * 2;
|
||||
if (!(this.pageStyles() || {}).hasOwnProperty('margin-left')) return 0; // TODO: Test this, added for safety during refactor
|
||||
|
||||
const margin = (window.innerWidth * (parseInt(this.pageStyles()['margin-left'], 10) / 100)) * 2;
|
||||
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
return windowWidth - margin;
|
||||
}
|
||||
|
@ -1358,7 +1361,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
* Applies styles onto the html of the book page
|
||||
*/
|
||||
updateReaderStyles(pageStyles: PageStyle) {
|
||||
this.pageStyles = pageStyles;
|
||||
console.log('Got styles from settings: ', pageStyles);
|
||||
|
||||
this.pageStyles.set(pageStyles);
|
||||
if (this.bookContentElemRef === undefined || !this.bookContentElemRef.nativeElement) return;
|
||||
|
||||
// Before we apply styles, let's get an element on the screen so we can scroll to it after any shifts
|
||||
|
@ -1370,7 +1375,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
// Line Height must be placed on each element in the page
|
||||
|
||||
// Apply page level overrides
|
||||
Object.entries(this.pageStyles).forEach(item => {
|
||||
Object.entries(this.pageStyles()).forEach(item => {
|
||||
if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') {
|
||||
// Remove the style or skip
|
||||
this.renderer.removeStyle(this.bookContentElemRef.nativeElement, item[0]);
|
||||
|
@ -1432,10 +1437,47 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
toggleDrawer() {
|
||||
this.drawerOpen = !this.drawerOpen;
|
||||
handleReaderSettingsUpdate(res: ReaderSettingUpdate) {
|
||||
switch (res.setting) {
|
||||
case "pageStyle":
|
||||
this.updateReaderStyles(res.object as PageStyle);
|
||||
break;
|
||||
case "clickToPaginate":
|
||||
this.showPaginationOverlay(res.object as boolean);
|
||||
break;
|
||||
case "fullscreen":
|
||||
this.toggleFullscreen();
|
||||
break;
|
||||
case "writingStyle":
|
||||
this.updateWritingStyle(res.object as WritingStyle);
|
||||
break;
|
||||
case "layoutMode":
|
||||
this.updateLayoutMode(res.object as BookPageLayoutMode);
|
||||
break;
|
||||
case "readingDirection":
|
||||
this.updateReadingDirection(res.object as ReadingDirection);
|
||||
break;
|
||||
case "immersiveMode":
|
||||
this.updateImmersiveMode(res.object as boolean);
|
||||
break;
|
||||
case 'theme':
|
||||
this.updateColorTheme(res.object as BookTheme);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.immersiveMode) {
|
||||
toggleDrawer() {
|
||||
const drawerIsOpen = this.epubMenuService.isDrawerOpen();
|
||||
if (drawerIsOpen) {
|
||||
this.epubMenuService.closeAll();
|
||||
} else {
|
||||
// TODO: BUG: Migrating the reader settings over into the drawer means default settings aren't being set on load
|
||||
this.epubMenuService.openSettingsDrawer(this.chapterId, this.seriesId, this.readingProfile, (res => {
|
||||
this.handleReaderSettingsUpdate(res);
|
||||
}));
|
||||
}
|
||||
|
||||
if (this.immersiveMode) { // NOTE: Shouldn't this check if drawer is open?
|
||||
this.actionBarVisible = false;
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
|
@ -1458,12 +1500,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
if(this.layoutMode === BookPageLayoutMode.Default && this.writingStyle === WritingStyle.Vertical ) {
|
||||
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
const scrollLeft = element.getBoundingClientRect().left + window.pageXOffset - (windowWidth - element.getBoundingClientRect().width);
|
||||
const scrollLeft = element.getBoundingClientRect().left + window.scrollX - (windowWidth - element.getBoundingClientRect().width);
|
||||
setTimeout(() => this.scrollService.scrollToX(scrollLeft, this.reader.nativeElement, 'smooth'), 10);
|
||||
}
|
||||
else if ((this.layoutMode === BookPageLayoutMode.Default) && (this.writingStyle === WritingStyle.Horizontal)) {
|
||||
const fromTopOffset = element.getBoundingClientRect().top + window.pageYOffset + TOP_OFFSET;
|
||||
// We need to use a delay as webkit browsers (aka apple devices) don't always have the document rendered by this point
|
||||
const fromTopOffset = element.getBoundingClientRect().top + window.scrollY + TOP_OFFSET;
|
||||
// We need to use a delay as webkit browsers (aka Apple devices) don't always have the document rendered by this point
|
||||
setTimeout(() => this.scrollService.scrollTo(fromTopOffset, this.reader.nativeElement), 10);
|
||||
} else {
|
||||
setTimeout(() => (element as Element).scrollIntoView({'block': 'start', 'inline': 'start'}));
|
||||
|
@ -1534,6 +1576,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.layoutMode = mode;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
console.log('layout mode changed to: ', this.layoutMode);
|
||||
|
||||
this.clearTimeout(this.updateImageSizeTimeout);
|
||||
this.updateImageSizeTimeout = setTimeout( () => {
|
||||
this.updateImageSizes()
|
||||
|
@ -1566,10 +1610,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
updateImmersiveMode(immersiveMode: boolean) {
|
||||
this.immersiveMode = immersiveMode;
|
||||
if (this.immersiveMode && !this.drawerOpen) {
|
||||
if (this.immersiveMode && !this.epubMenuService.isDrawerOpen()) {
|
||||
this.actionBarVisible = false;
|
||||
this.updateReadingSectionHeight();
|
||||
}
|
||||
this.updateReadingSectionHeight();
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
@ -1580,7 +1625,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
if (renderer === undefined || elem === undefined) return;
|
||||
if (this.immersiveMode) {
|
||||
} else {
|
||||
renderer.setStyle(elem, 'height', 'calc(var(--vh, 1vh) * 100 - ' + this.topOffset + 'px)', RendererStyleFlags2.Important);
|
||||
renderer.setStyle(elem.nativeElement, 'height', 'calc(var(--vh, 1vh) * 100 - ' + this.topOffset + 'px)', RendererStyleFlags2.Important);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
@if (readingProfile !== null) {
|
||||
<ng-container *transloco="let t; read: 'reader-settings'">
|
||||
<!-- IDEA: Move the whole reader drawer into this component and have it self contained -->
|
||||
<form [formGroup]="settingsForm">
|
||||
<div ngbAccordion [closeOthers]="false" #acc="ngbAccordion">
|
||||
<div ngbAccordionItem id="general-panel" title="General Settings" [collapsed]="false">
|
||||
|
@ -17,7 +16,9 @@
|
|||
<div class="mb-3">
|
||||
<label for="library-type" class="form-label">{{t('font-family-label')}}</label>
|
||||
<select class="form-select" id="library-type" formControlName="bookReaderFontFamily">
|
||||
<option [value]="opt" *ngFor="let opt of fontOptions; let i = index">{{opt | titlecase}}</option>
|
||||
@for(opt of fontOptions; track opt) {
|
||||
<option [value]="opt">{{opt | titlecase}}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -117,7 +118,9 @@
|
|||
</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>
|
||||
<span *ngIf="activeTheme?.isDarkTheme"> {{isFullscreen ? t('exit') : t('enter')}}</span>
|
||||
@if (activeTheme?.isDarkTheme) {
|
||||
<span class="ms-1">{{isFullscreen ? t('exit') : t('enter')}}</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -156,12 +159,12 @@
|
|||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="controls">
|
||||
<ng-container *ngFor="let theme of themes">
|
||||
@for(theme of themes; track theme.name) {
|
||||
<button class="btn btn-icon color" (click)="setTheme(theme.name)" [ngClass]="{'active': activeTheme?.name === theme.name}">
|
||||
<div class="dot" [ngStyle]="{'background-color': theme.colorHash}"></div>
|
||||
{{t(theme.translationKey)}}
|
||||
</button>
|
||||
</ng-container>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {DOCUMENT, NgClass, NgFor, NgIf, NgStyle, NgTemplateOutlet, TitleCasePipe} from '@angular/common';
|
||||
import {NgClass, NgStyle, NgTemplateOutlet, TitleCasePipe} from '@angular/common';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
|
@ -6,22 +6,17 @@ import {
|
|||
DestroyRef,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Inject,
|
||||
Input,
|
||||
OnInit,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
import {skip, take} from 'rxjs';
|
||||
import {FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
import {BookPageLayoutMode} from 'src/app/_models/readers/book-page-layout-mode';
|
||||
import {BookTheme} from 'src/app/_models/preferences/book-theme';
|
||||
import {ReadingDirection} from 'src/app/_models/preferences/reading-direction';
|
||||
import {WritingStyle} from 'src/app/_models/preferences/writing-style';
|
||||
import {ThemeProvider} from 'src/app/_models/preferences/site-theme';
|
||||
import {User} from 'src/app/_models/user';
|
||||
import {AccountService} from 'src/app/_services/account.service';
|
||||
import {ThemeService} from 'src/app/_services/theme.service';
|
||||
import {BookService, FontFamily} from '../../_services/book.service';
|
||||
import {FontFamily} from '../../_services/book.service';
|
||||
import {BookBlackTheme} from '../../_models/book-black-theme';
|
||||
import {BookDarkTheme} from '../../_models/book-dark-theme';
|
||||
import {BookWhiteTheme} from '../../_models/book-white-theme';
|
||||
|
@ -39,8 +34,9 @@ import {
|
|||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
||||
import {ReadingProfileService} from "../../../_services/reading-profile.service";
|
||||
import {ReadingProfile, ReadingProfileKind} from "../../../_models/preferences/reading-profiles";
|
||||
import {debounceTime, distinctUntilChanged, tap} from "rxjs/operators";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {EpubReaderSettingsService} from "../../../_services/epub-reader-settings.service";
|
||||
import {tap} from "rxjs/operators";
|
||||
|
||||
/**
|
||||
* Used for book reader. Do not use for other components
|
||||
|
@ -96,18 +92,23 @@ export const bookColorThemes = [
|
|||
},
|
||||
];
|
||||
|
||||
const mobileBreakpointMarginOverride = 700;
|
||||
|
||||
@Component({
|
||||
selector: 'app-reader-settings',
|
||||
templateUrl: './reader-settings.component.html',
|
||||
styleUrls: ['./reader-settings.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [ReactiveFormsModule, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionButton,
|
||||
NgbAccordionCollapse, NgbAccordionBody, NgFor, NgbTooltip, NgTemplateOutlet, NgIf, NgClass, NgStyle,
|
||||
NgbAccordionCollapse, NgbAccordionBody, NgbTooltip, NgTemplateOutlet, NgClass, NgStyle,
|
||||
TitleCasePipe, TranslocoDirective]
|
||||
})
|
||||
export class ReaderSettingsComponent implements OnInit {
|
||||
|
||||
private readonly readerSettingsService = inject(EpubReaderSettingsService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly readingProfileService = inject(ReadingProfileService);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
|
||||
@Input({required:true}) seriesId!: number;
|
||||
@Input({required:true}) readingProfile!: ReadingProfile;
|
||||
/**
|
||||
|
@ -143,7 +144,6 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
*/
|
||||
@Output() immersiveMode: EventEmitter<boolean> = new EventEmitter();
|
||||
|
||||
user!: User;
|
||||
/**
|
||||
* List of all font families user can select from
|
||||
*/
|
||||
|
@ -152,7 +152,7 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
/**
|
||||
* Internal property used to capture all the different css properties to render on all elements
|
||||
*/
|
||||
pageStyles!: PageStyle;
|
||||
pageStyles: PageStyle = this.readerSettingsService.getDefaultPageStyles();
|
||||
|
||||
readingDirectionModel: ReadingDirection = ReadingDirection.LeftToRight;
|
||||
|
||||
|
@ -174,258 +174,100 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
* System provided themes
|
||||
*/
|
||||
themes: Array<BookTheme> = bookColorThemes;
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
|
||||
get BookPageLayoutMode(): typeof BookPageLayoutMode {
|
||||
return BookPageLayoutMode;
|
||||
}
|
||||
|
||||
get ReadingDirection() {
|
||||
return ReadingDirection;
|
||||
}
|
||||
async ngOnInit() {
|
||||
|
||||
get WritingStyle() {
|
||||
return WritingStyle;
|
||||
}
|
||||
// Initialize the service if not already done
|
||||
if (!this.readerSettingsService.getCurrentReadingProfile()) {
|
||||
await this.readerSettingsService.initialize(this.seriesId, this.readingProfile);
|
||||
}
|
||||
|
||||
constructor(private bookService: BookService, private accountService: AccountService,
|
||||
@Inject(DOCUMENT) private document: Document, private themeService: ThemeService,
|
||||
private readonly cdRef: ChangeDetectorRef, private readingProfileService: ReadingProfileService,
|
||||
private toastr: ToastrService) {}
|
||||
this.readerSettingsService.readingProfile$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
tap((profile) => {
|
||||
if (profile) {
|
||||
this.readingProfile = profile;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.settingsForm = this.readerSettingsService.getSettingsForm();
|
||||
this.fontFamilies = this.readerSettingsService.getFontFamilies();
|
||||
this.fontOptions = this.fontFamilies.map(f => f.title);
|
||||
this.themes = this.readerSettingsService.getThemes();
|
||||
|
||||
|
||||
// Subscribe to service state
|
||||
this.readerSettingsService.pageStyles$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(styles => {
|
||||
this.pageStyles = styles;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.readerSettingsService.readingDirection$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(direction => {
|
||||
this.readingDirectionModel = direction;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.readerSettingsService.writingStyle$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(style => {
|
||||
this.writingStyleModel = style;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.readerSettingsService.activeTheme$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(theme => {
|
||||
this.activeTheme = theme;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
// Handle parent reading profile
|
||||
if (this.readingProfile.kind === ReadingProfileKind.Implicit) {
|
||||
this.readingProfileService.getForSeries(this.seriesId, true).subscribe(parent => {
|
||||
this.parentReadingProfile = parent;
|
||||
this.cdRef.markForCheck();
|
||||
})
|
||||
});
|
||||
} else {
|
||||
this.parentReadingProfile = this.readingProfile;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
this.fontFamilies = this.bookService.getFontFamilies();
|
||||
this.fontOptions = this.fontFamilies.map(f => f.title);
|
||||
|
||||
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.setupSettings();
|
||||
|
||||
this.setTheme(this.readingProfile.bookReaderThemeName || this.themeService.defaultBookTheme, false);
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
// Emit first time so book reader gets the setting
|
||||
this.readingDirection.emit(this.readingDirectionModel);
|
||||
this.bookReaderWritingStyle.emit(this.writingStyleModel);
|
||||
this.clickToPaginateChanged.emit(this.readingProfile.bookReaderTapToPaginate);
|
||||
this.layoutModeUpdate.emit(this.readingProfile.bookReaderLayoutMode);
|
||||
this.immersiveMode.emit(this.readingProfile.bookReaderImmersiveMode);
|
||||
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
if (user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
// User needs to be loaded before we call this
|
||||
this.resetSettings();
|
||||
});
|
||||
}
|
||||
|
||||
setupSettings() {
|
||||
if (!this.readingProfile) return;
|
||||
|
||||
if (this.readingProfile.bookReaderFontFamily === undefined) {
|
||||
this.readingProfile.bookReaderFontFamily = 'default';
|
||||
}
|
||||
if (this.readingProfile.bookReaderFontSize === undefined || this.readingProfile.bookReaderFontSize < 50) {
|
||||
this.readingProfile.bookReaderFontSize = 100;
|
||||
}
|
||||
if (this.readingProfile.bookReaderLineSpacing === undefined || this.readingProfile.bookReaderLineSpacing < 100) {
|
||||
this.readingProfile.bookReaderLineSpacing = 100;
|
||||
}
|
||||
if (this.readingProfile.bookReaderMargin === undefined) {
|
||||
this.readingProfile.bookReaderMargin = 0;
|
||||
}
|
||||
if (this.readingProfile.bookReaderReadingDirection === undefined) {
|
||||
this.readingProfile.bookReaderReadingDirection = ReadingDirection.LeftToRight;
|
||||
}
|
||||
if (this.readingProfile.bookReaderWritingStyle === undefined) {
|
||||
this.readingProfile.bookReaderWritingStyle = WritingStyle.Horizontal;
|
||||
}
|
||||
this.readingDirectionModel = this.readingProfile.bookReaderReadingDirection;
|
||||
this.writingStyleModel = this.readingProfile.bookReaderWritingStyle;
|
||||
|
||||
this.settingsForm.addControl('bookReaderFontFamily', new FormControl(this.readingProfile.bookReaderFontFamily, []));
|
||||
this.settingsForm.get('bookReaderFontFamily')!.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(fontName => {
|
||||
const familyName = this.fontFamilies.filter(f => f.title === fontName)[0].family;
|
||||
if (familyName === 'default') {
|
||||
this.pageStyles['font-family'] = 'inherit';
|
||||
} else {
|
||||
this.pageStyles['font-family'] = "'" + familyName + "'";
|
||||
}
|
||||
|
||||
this.styleUpdate.emit(this.pageStyles);
|
||||
});
|
||||
|
||||
this.settingsForm.addControl('bookReaderFontSize', new FormControl(this.readingProfile.bookReaderFontSize, []));
|
||||
this.settingsForm.get('bookReaderFontSize')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => {
|
||||
this.pageStyles['font-size'] = value + '%';
|
||||
this.styleUpdate.emit(this.pageStyles);
|
||||
});
|
||||
|
||||
this.settingsForm.addControl('bookReaderTapToPaginate', new FormControl(this.readingProfile.bookReaderTapToPaginate, []));
|
||||
this.settingsForm.get('bookReaderTapToPaginate')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => {
|
||||
this.clickToPaginateChanged.emit(value);
|
||||
});
|
||||
|
||||
this.settingsForm.addControl('bookReaderLineSpacing', new FormControl(this.readingProfile.bookReaderLineSpacing, []));
|
||||
this.settingsForm.get('bookReaderLineSpacing')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => {
|
||||
this.pageStyles['line-height'] = value + '%';
|
||||
this.styleUpdate.emit(this.pageStyles);
|
||||
});
|
||||
|
||||
this.settingsForm.addControl('bookReaderMargin', new FormControl(this.readingProfile.bookReaderMargin, []));
|
||||
this.settingsForm.get('bookReaderMargin')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => {
|
||||
this.pageStyles['margin-left'] = value + 'vw';
|
||||
this.pageStyles['margin-right'] = value + 'vw';
|
||||
this.styleUpdate.emit(this.pageStyles);
|
||||
});
|
||||
|
||||
this.settingsForm.addControl('layoutMode', new FormControl(this.readingProfile.bookReaderLayoutMode, []));
|
||||
this.settingsForm.get('layoutMode')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((layoutMode: BookPageLayoutMode) => {
|
||||
this.layoutModeUpdate.emit(layoutMode);
|
||||
});
|
||||
|
||||
this.settingsForm.addControl('bookReaderImmersiveMode', new FormControl(this.readingProfile.bookReaderImmersiveMode, []));
|
||||
this.settingsForm.get('bookReaderImmersiveMode')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((immersiveMode: boolean) => {
|
||||
if (immersiveMode) {
|
||||
this.settingsForm.get('bookReaderTapToPaginate')?.setValue(true);
|
||||
}
|
||||
this.immersiveMode.emit(immersiveMode);
|
||||
});
|
||||
|
||||
// Update implicit reading profile while changing settings
|
||||
this.settingsForm.valueChanges.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
skip(1), // Skip the initial creation of the form, we do not want an implicit profile of this snapshot
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
tap(_ => this.updateImplicit())
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
resetSettings() {
|
||||
if (!this.readingProfile) return;
|
||||
|
||||
if (this.user) {
|
||||
this.setPageStyles(this.readingProfile.bookReaderFontFamily, this.readingProfile.bookReaderFontSize + '%', this.readingProfile.bookReaderMargin + 'vw', this.readingProfile.bookReaderLineSpacing + '%');
|
||||
} else {
|
||||
this.setPageStyles();
|
||||
}
|
||||
|
||||
this.settingsForm.get('bookReaderFontFamily')?.setValue(this.readingProfile.bookReaderFontFamily);
|
||||
this.settingsForm.get('bookReaderFontSize')?.setValue(this.readingProfile.bookReaderFontSize);
|
||||
this.settingsForm.get('bookReaderLineSpacing')?.setValue(this.readingProfile.bookReaderLineSpacing);
|
||||
this.settingsForm.get('bookReaderMargin')?.setValue(this.readingProfile.bookReaderMargin);
|
||||
this.settingsForm.get('bookReaderReadingDirection')?.setValue(this.readingProfile.bookReaderReadingDirection);
|
||||
this.settingsForm.get('bookReaderTapToPaginate')?.setValue(this.readingProfile.bookReaderTapToPaginate);
|
||||
this.settingsForm.get('bookReaderLayoutMode')?.setValue(this.readingProfile.bookReaderLayoutMode);
|
||||
this.settingsForm.get('bookReaderImmersiveMode')?.setValue(this.readingProfile.bookReaderImmersiveMode);
|
||||
this.settingsForm.get('bookReaderWritingStyle')?.setValue(this.readingProfile.bookReaderWritingStyle);
|
||||
|
||||
this.cdRef.detectChanges();
|
||||
this.styleUpdate.emit(this.pageStyles);
|
||||
}
|
||||
|
||||
updateImplicit() {
|
||||
this.readingProfileService.updateImplicit(this.packReadingProfile(), this.seriesId).subscribe({
|
||||
next: newProfile => {
|
||||
this.readingProfile = newProfile;
|
||||
this.cdRef.markForCheck();
|
||||
},
|
||||
error: err => {
|
||||
console.error(err);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to be used by resetSettings. Pass items in with quantifiers
|
||||
*/
|
||||
setPageStyles(fontFamily?: string, fontSize?: string, margin?: string, lineHeight?: string, colorTheme?: string) {
|
||||
const windowWidth = window.innerWidth
|
||||
|| this.document.documentElement.clientWidth
|
||||
|| this.document.body.clientWidth;
|
||||
|
||||
|
||||
let defaultMargin = '15vw';
|
||||
if (windowWidth <= mobileBreakpointMarginOverride) {
|
||||
defaultMargin = '5vw';
|
||||
}
|
||||
this.pageStyles = {
|
||||
'font-family': fontFamily || this.pageStyles['font-family'] || 'default',
|
||||
'font-size': fontSize || this.pageStyles['font-size'] || '100%',
|
||||
'margin-left': margin || this.pageStyles['margin-left'] || defaultMargin,
|
||||
'margin-right': margin || this.pageStyles['margin-right'] || defaultMargin,
|
||||
'line-height': lineHeight || this.pageStyles['line-height'] || '100%'
|
||||
};
|
||||
this.readerSettingsService.resetSettings();
|
||||
}
|
||||
|
||||
setTheme(themeName: string, update: boolean = true) {
|
||||
const theme = this.themes.find(t => t.name === themeName);
|
||||
this.activeTheme = theme;
|
||||
this.cdRef.markForCheck();
|
||||
this.colorThemeUpdate.emit(theme);
|
||||
|
||||
if (update) {
|
||||
this.updateImplicit();
|
||||
}
|
||||
this.readerSettingsService.setTheme(themeName, update);
|
||||
}
|
||||
|
||||
toggleReadingDirection() {
|
||||
if (this.readingDirectionModel === ReadingDirection.LeftToRight) {
|
||||
this.readingDirectionModel = ReadingDirection.RightToLeft;
|
||||
} else {
|
||||
this.readingDirectionModel = ReadingDirection.LeftToRight;
|
||||
}
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
this.readingDirection.emit(this.readingDirectionModel);
|
||||
this.updateImplicit();
|
||||
this.readerSettingsService.toggleReadingDirection();
|
||||
}
|
||||
|
||||
toggleWritingStyle() {
|
||||
if (this.writingStyleModel === WritingStyle.Horizontal) {
|
||||
this.writingStyleModel = WritingStyle.Vertical
|
||||
} else {
|
||||
this.writingStyleModel = WritingStyle.Horizontal
|
||||
}
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
this.bookReaderWritingStyle.emit(this.writingStyleModel);
|
||||
this.updateImplicit();
|
||||
this.readerSettingsService.toggleWritingStyle();
|
||||
}
|
||||
|
||||
toggleFullscreen() {
|
||||
this.isFullscreen = !this.isFullscreen;
|
||||
this.readerSettingsService.toggleFullscreen();
|
||||
this.cdRef.markForCheck();
|
||||
this.fullscreen.emit();
|
||||
}
|
||||
|
||||
// menu only code
|
||||
updateParentPref() {
|
||||
if (this.readingProfile.kind !== ReadingProfileKind.Implicit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.readingProfileService.updateParentProfile(this.seriesId, this.packReadingProfile()).subscribe(newProfile => {
|
||||
this.readingProfile = newProfile;
|
||||
this.toastr.success(translate('manga-reader.reading-profile-updated'));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
this.readerSettingsService.updateParentProfile();
|
||||
this.toastr.success(translate('manga-reader.reading-profile-updated'));
|
||||
}
|
||||
|
||||
createNewProfileFromImplicit() {
|
||||
|
@ -433,34 +275,17 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
this.readingProfileService.promoteProfile(this.readingProfile.id).subscribe(newProfile => {
|
||||
this.readerSettingsService.promoteProfile().subscribe(newProfile => {
|
||||
this.readingProfile = newProfile;
|
||||
this.parentReadingProfile = newProfile; // profile is no longer implicit
|
||||
this.parentReadingProfile = newProfile;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.toastr.success(translate("manga-reader.reading-profile-promoted"));
|
||||
});
|
||||
}
|
||||
|
||||
private packReadingProfile(): ReadingProfile {
|
||||
const modelSettings = this.settingsForm.getRawValue();
|
||||
const data = {...this.readingProfile!};
|
||||
data.bookReaderFontFamily = modelSettings.bookReaderFontFamily;
|
||||
data.bookReaderFontSize = modelSettings.bookReaderFontSize
|
||||
data.bookReaderLineSpacing = modelSettings.bookReaderLineSpacing;
|
||||
data.bookReaderMargin = modelSettings.bookReaderMargin;
|
||||
data.bookReaderTapToPaginate = modelSettings.bookReaderTapToPaginate;
|
||||
data.bookReaderLayoutMode = modelSettings.layoutMode;
|
||||
data.bookReaderImmersiveMode = modelSettings.bookReaderImmersiveMode;
|
||||
|
||||
data.bookReaderReadingDirection = this.readingDirectionModel;
|
||||
data.bookReaderWritingStyle = this.writingStyleModel;
|
||||
if (this.activeTheme) {
|
||||
data.bookReaderThemeName = this.activeTheme.name;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
protected readonly ReadingProfileKind = ReadingProfileKind;
|
||||
protected readonly WritingStyle = WritingStyle;
|
||||
protected readonly ReadingDirection = ReadingDirection;
|
||||
protected readonly BookPageLayoutMode = BookPageLayoutMode;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {BookPageLayoutMode} from "../../_models/readers/book-page-layout-mode";
|
||||
|
||||
@Pipe({
|
||||
name: 'columnLayoutClass'
|
||||
})
|
||||
export class ColumnLayoutClassPipe implements PipeTransform {
|
||||
|
||||
transform(value: BookPageLayoutMode): string {
|
||||
switch (value) {
|
||||
case BookPageLayoutMode.Default:
|
||||
return '';
|
||||
case BookPageLayoutMode.Column1:
|
||||
return 'column-layout-1';
|
||||
case BookPageLayoutMode.Column2:
|
||||
return 'column-layout-2';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {WritingStyle} from "../../_models/preferences/writing-style";
|
||||
|
||||
@Pipe({
|
||||
name: 'writingStyleClass'
|
||||
})
|
||||
export class WritingStyleClassPipe implements PipeTransform {
|
||||
|
||||
transform(value: WritingStyle): string {
|
||||
switch (value) {
|
||||
case WritingStyle.Horizontal:
|
||||
return '';
|
||||
case WritingStyle.Vertical:
|
||||
return 'writing-style-vertical';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -824,6 +824,11 @@
|
|||
"personal-header": "Personal"
|
||||
},
|
||||
|
||||
"epub-setting-drawer": {
|
||||
"title": "Book Settings",
|
||||
"close": "{{common.close}}"
|
||||
},
|
||||
|
||||
"create-annotation-drawer": {
|
||||
"title": "Create/Edit an Annotation",
|
||||
"close": "{{common.close}}"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue