Migrate usages to ReadingProfiles

TODO:
- Settings
- An exception in the book reader (probably to do with the settings being loaded later)
This commit is contained in:
Amelia 2025-05-17 18:23:14 +02:00
parent 616916548a
commit 5741a92bb2
13 changed files with 559 additions and 426 deletions

View file

@ -1,48 +1,7 @@
import {LayoutMode} from 'src/app/manga-reader/_models/layout-mode';
import {BookPageLayoutMode} from '../readers/book-page-layout-mode';
import {PageLayoutMode} from '../page-layout-mode';
import {PageSplitOption} from './page-split-option';
import {ReaderMode} from './reader-mode';
import {ReadingDirection} from './reading-direction';
import {ScalingOption} from './scaling-option';
import {SiteTheme} from './site-theme';
import {WritingStyle} from "./writing-style";
import {PdfTheme} from "./pdf-theme";
import {PdfScrollMode} from "./pdf-scroll-mode";
import {PdfLayoutMode} from "./pdf-layout-mode";
import {PdfSpreadMode} from "./pdf-spread-mode";
export interface Preferences {
// Manga Reader
readingDirection: ReadingDirection;
scalingOption: ScalingOption;
pageSplitOption: PageSplitOption;
readerMode: ReaderMode;
autoCloseMenu: boolean;
layoutMode: LayoutMode;
backgroundColor: string;
showScreenHints: boolean;
emulateBook: boolean;
swipeToPaginate: boolean;
allowAutomaticWebtoonReaderDetection: boolean;
// Book Reader
bookReaderMargin: number;
bookReaderLineSpacing: number;
bookReaderFontSize: number;
bookReaderFontFamily: string;
bookReaderTapToPaginate: boolean;
bookReaderReadingDirection: ReadingDirection;
bookReaderWritingStyle: WritingStyle;
bookReaderThemeName: string;
bookReaderLayoutMode: BookPageLayoutMode;
bookReaderImmersiveMode: boolean;
// PDF Reader
pdfTheme: PdfTheme;
pdfScrollMode: PdfScrollMode;
pdfSpreadMode: PdfSpreadMode;
// Global
theme: SiteTheme;
globalPageLayoutMode: PageLayoutMode;
@ -58,15 +17,3 @@ export interface Preferences {
wantToReadSync: boolean;
}
export const readingDirections = [{text: 'left-to-right', value: ReadingDirection.LeftToRight}, {text: 'right-to-left', value: ReadingDirection.RightToLeft}];
export const bookWritingStyles = [{text: 'horizontal', value: WritingStyle.Horizontal}, {text: 'vertical', value: WritingStyle.Vertical}];
export const scalingOptions = [{text: 'automatic', value: ScalingOption.Automatic}, {text: 'fit-to-height', value: ScalingOption.FitToHeight}, {text: 'fit-to-width', value: ScalingOption.FitToWidth}, {text: 'original', value: ScalingOption.Original}];
export const pageSplitOptions = [{text: 'fit-to-screen', value: PageSplitOption.FitSplit}, {text: 'right-to-left', value: PageSplitOption.SplitRightToLeft}, {text: 'left-to-right', value: PageSplitOption.SplitLeftToRight}, {text: 'no-split', value: PageSplitOption.NoSplit}];
export const readingModes = [{text: 'left-to-right', value: ReaderMode.LeftRight}, {text: 'up-to-down', value: ReaderMode.UpDown}, {text: 'webtoon', value: ReaderMode.Webtoon}];
export const layoutModes = [{text: 'single', value: LayoutMode.Single}, {text: 'double', value: LayoutMode.Double}, {text: 'double-manga', value: LayoutMode.DoubleReversed}]; // TODO: Build this, {text: 'Double (No Cover)', value: LayoutMode.DoubleNoCover}
export const bookLayoutModes = [{text: 'scroll', value: BookPageLayoutMode.Default}, {text: '1-column', value: BookPageLayoutMode.Column1}, {text: '2-column', value: BookPageLayoutMode.Column2}];
export const pageLayoutModes = [{text: 'cards', value: PageLayoutMode.Cards}, {text: 'list', value: PageLayoutMode.List}];
export const pdfLayoutModes = [{text: 'pdf-multiple', value: PdfLayoutMode.Multiple}, {text: 'pdf-book', value: PdfLayoutMode.Book}];
export const pdfScrollModes = [{text: 'pdf-vertical', value: PdfScrollMode.Vertical}, {text: 'pdf-horizontal', value: PdfScrollMode.Horizontal}, {text: 'pdf-page', value: PdfScrollMode.Page}];
export const pdfSpreadModes = [{text: 'pdf-none', value: PdfSpreadMode.None}, {text: 'pdf-odd', value: PdfSpreadMode.Odd}, {text: 'pdf-even', value: PdfSpreadMode.Even}];
export const pdfThemes = [{text: 'pdf-light', value: PdfTheme.Light}, {text: 'pdf-dark', value: PdfTheme.Dark}];

View file

@ -0,0 +1,64 @@
import {LayoutMode} from 'src/app/manga-reader/_models/layout-mode';
import {BookPageLayoutMode} from '../readers/book-page-layout-mode';
import {PageLayoutMode} from '../page-layout-mode';
import {PageSplitOption} from './page-split-option';
import {ReaderMode} from './reader-mode';
import {ReadingDirection} from './reading-direction';
import {ScalingOption} from './scaling-option';
import {WritingStyle} from "./writing-style";
import {PdfTheme} from "./pdf-theme";
import {PdfScrollMode} from "./pdf-scroll-mode";
import {PdfLayoutMode} from "./pdf-layout-mode";
import {PdfSpreadMode} from "./pdf-spread-mode";
export interface ReadingProfile {
id: number;
name: string;
normalizedName: string;
// Manga Reader
readingDirection: ReadingDirection;
scalingOption: ScalingOption;
pageSplitOption: PageSplitOption;
readerMode: ReaderMode;
autoCloseMenu: boolean;
layoutMode: LayoutMode;
backgroundColor: string;
showScreenHints: boolean;
emulateBook: boolean;
swipeToPaginate: boolean;
allowAutomaticWebtoonReaderDetection: boolean;
widthOverride?: number;
// Book Reader
bookReaderMargin: number;
bookReaderLineSpacing: number;
bookReaderFontSize: number;
bookReaderFontFamily: string;
bookReaderTapToPaginate: boolean;
bookReaderReadingDirection: ReadingDirection;
bookReaderWritingStyle: WritingStyle;
bookReaderThemeName: string;
bookReaderLayoutMode: BookPageLayoutMode;
bookReaderImmersiveMode: boolean;
// PDF Reader
pdfTheme: PdfTheme;
pdfScrollMode: PdfScrollMode;
pdfSpreadMode: PdfSpreadMode;
}
export const readingDirections = [{text: 'left-to-right', value: ReadingDirection.LeftToRight}, {text: 'right-to-left', value: ReadingDirection.RightToLeft}];
export const bookWritingStyles = [{text: 'horizontal', value: WritingStyle.Horizontal}, {text: 'vertical', value: WritingStyle.Vertical}];
export const scalingOptions = [{text: 'automatic', value: ScalingOption.Automatic}, {text: 'fit-to-height', value: ScalingOption.FitToHeight}, {text: 'fit-to-width', value: ScalingOption.FitToWidth}, {text: 'original', value: ScalingOption.Original}];
export const pageSplitOptions = [{text: 'fit-to-screen', value: PageSplitOption.FitSplit}, {text: 'right-to-left', value: PageSplitOption.SplitRightToLeft}, {text: 'left-to-right', value: PageSplitOption.SplitLeftToRight}, {text: 'no-split', value: PageSplitOption.NoSplit}];
export const readingModes = [{text: 'left-to-right', value: ReaderMode.LeftRight}, {text: 'up-to-down', value: ReaderMode.UpDown}, {text: 'webtoon', value: ReaderMode.Webtoon}];
export const layoutModes = [{text: 'single', value: LayoutMode.Single}, {text: 'double', value: LayoutMode.Double}, {text: 'double-manga', value: LayoutMode.DoubleReversed}]; // TODO: Build this, {text: 'Double (No Cover)', value: LayoutMode.DoubleNoCover}
export const bookLayoutModes = [{text: 'scroll', value: BookPageLayoutMode.Default}, {text: '1-column', value: BookPageLayoutMode.Column1}, {text: '2-column', value: BookPageLayoutMode.Column2}];
export const pageLayoutModes = [{text: 'cards', value: PageLayoutMode.Cards}, {text: 'list', value: PageLayoutMode.List}];
export const pdfLayoutModes = [{text: 'pdf-multiple', value: PdfLayoutMode.Multiple}, {text: 'pdf-book', value: PdfLayoutMode.Book}];
export const pdfScrollModes = [{text: 'pdf-vertical', value: PdfScrollMode.Vertical}, {text: 'pdf-horizontal', value: PdfScrollMode.Horizontal}, {text: 'pdf-page', value: PdfScrollMode.Page}];
export const pdfSpreadModes = [{text: 'pdf-none', value: PdfSpreadMode.None}, {text: 'pdf-odd', value: PdfSpreadMode.Odd}, {text: 'pdf-even', value: PdfSpreadMode.Even}];
export const pdfThemes = [{text: 'pdf-light', value: PdfTheme.Light}, {text: 'pdf-dark', value: PdfTheme.Dark}];

View file

@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {environment} from "../../environments/environment";
import {ReadingProfile} from "../_models/preferences/reading-profiles";
@Injectable({
providedIn: 'root'
})
export class ReadingProfileService {
baseUrl = environment.apiUrl;
constructor(private httpClient: HttpClient) { }
getForSeries(seriesId: number) {
return this.httpClient.get<ReadingProfile>(this.baseUrl + "ReadingProfile/"+seriesId);
}
updateProfile(profile: ReadingProfile, seriesId?: number) {
if (seriesId) {
return this.httpClient.post(this.baseUrl + "ReadingProfile?seriesCtx="+seriesId, profile);
}
return this.httpClient.post(this.baseUrl + "ReadingProfile", profile);
}
updateImplicit(profile: ReadingProfile, seriesId: number) {
return this.httpClient.post(this.baseUrl + "ReadingProfile/series?seriesId="+seriesId, profile);
}
}

View file

@ -9,12 +9,12 @@ import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {allEncodeFormats} from '../_models/encode-format';
import {translate, TranslocoDirective, TranslocoService} from "@jsverse/transloco";
import {allCoverImageSizes, CoverImageSize} from '../_models/cover-image-size';
import {pageLayoutModes} from "../../_models/preferences/preferences";
import {SettingItemComponent} from "../../settings/_components/setting-item/setting-item.component";
import {EncodeFormatPipe} from "../../_pipes/encode-format.pipe";
import {CoverImageSizePipe} from "../../_pipes/cover-image-size.pipe";
import {ConfirmService} from "../../shared/confirm.service";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {pageLayoutModes} from "../../_models/preferences/reading-profiles";
@Component({
selector: 'app-manage-media-settings',

View file

@ -60,6 +60,7 @@
<a ngbNavLink>{{t('settings-header')}}</a>
<ng-template ngbNavContent>
<app-reader-settings
[seriesId]="seriesId"
(colorThemeUpdate)="updateColorTheme($event)"
(styleUpdate)="updateReaderStyles($event)"
(clickToPaginateChanged)="showPaginationOverlay($event)"

View file

@ -1,172 +1,179 @@
<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">
<h2 class="accordion-header" ngbAccordionHeader>
<button ngbAccordionButton class="accordion-button" type="button" [attr.aria-expanded]="acc.isExpanded('general-panel')" aria-controls="collapseOne">
{{t('general-settings-title')}}
</button>
</h2>
<div ngbAccordionCollapse>
<div ngbAccordionBody>
<ng-template>
<div class="control-container" >
<div class="controls">
<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>
</select>
@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">
<h2 class="accordion-header" ngbAccordionHeader>
<button ngbAccordionButton class="accordion-button" type="button" [attr.aria-expanded]="acc.isExpanded('general-panel')" aria-controls="collapseOne">
{{t('general-settings-title')}}
</button>
</h2>
<div ngbAccordionCollapse>
<div ngbAccordionBody>
<ng-template>
<div class="control-container" >
<div class="controls">
<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>
</select>
</div>
</div>
</div>
<div class="row g-0 controls">
<label for="fontsize" class="form-label col-6">{{t('font-size-label')}}</label>
<span class="col-6 float-end" style="display: inline-flex;">
<div class="row g-0 controls">
<label for="fontsize" class="form-label col-6">{{t('font-size-label')}}</label>
<span class="col-6 float-end" style="display: inline-flex;">
<i class="fa-solid fa-font" style="font-size: 12px;"></i>
<input type="range" class="form-range ms-2 me-2" id="fontsize" min="50" max="300" step="10" formControlName="bookReaderFontSize" [ngbTooltip]="settingsForm.get('bookReaderFontSize')?.value + '%'">
<i class="fa-solid fa-font" style="font-size: 24px;"></i>
</span>
</div>
</div>
<div class="row g-0 controls">
<label for="linespacing" class="form-label col-6">{{t('line-spacing-label')}}</label>
<span class="col-6 float-end" style="display: inline-flex;">
<div class="row g-0 controls">
<label for="linespacing" class="form-label col-6">{{t('line-spacing-label')}}</label>
<span class="col-6 float-end" style="display: inline-flex;">
1x
<input type="range" class="form-range ms-2 me-2" id="linespacing" min="100" max="200" step="10" formControlName="bookReaderLineSpacing" [ngbTooltip]="settingsForm.get('bookReaderLineSpacing')?.value + '%'">
2.5x
</span>
</div>
</div>
<div class="row g-0 controls">
<label for="margin" class="form-label col-6">{{t('margin-label')}}</label>
<span class="col-6 float-end" style="display: inline-flex;">
<div class="row g-0 controls">
<label for="margin" class="form-label col-6">{{t('margin-label')}}</label>
<span class="col-6 float-end" style="display: inline-flex;">
<i class="fa-solid fa-outdent"></i>
<input type="range" class="form-range ms-2 me-2" id="margin" min="0" max="30" step="5" formControlName="bookReaderMargin" [ngbTooltip]="settingsForm.get('bookReaderMargin')?.value + '%'">
<i class="fa-solid fa-indent"></i>
</span>
</div>
</div>
<div class="row g-0 justify-content-between mt-2">
<button (click)="resetSettings()" class="btn btn-primary col">{{t('reset-to-defaults')}}</button>
<div class="row g-0 justify-content-between mt-2">
<button (click)="resetSettings()" class="btn btn-primary col">{{t('reset-to-defaults')}}</button>
</div>
</div>
</div>
</ng-template>
</ng-template>
</div>
</div>
</div>
</div>
<div ngbAccordionItem id="reader-panel" title="Reader Settings" [collapsed]="false">
<h2 class="accordion-header" ngbAccordionHeader>
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded('reader-panel')" aria-controls="collapseOne">
{{t('reader-settings-title')}}
</button>
</h2>
<div ngbAccordionCollapse>
<div ngbAccordionBody>
<ng-template>
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
<label id="readingdirection" class="form-label">{{t('reading-direction-label')}}</label>
<button (click)="toggleReadingDirection()" class="btn btn-icon" aria-labelledby="readingdirection" title="{{readingDirectionModel === ReadingDirection.LeftToRight ? t('left-to-right') : t('right-to-left')}}">
<i class="fa {{readingDirectionModel === ReadingDirection.LeftToRight ? 'fa-arrow-right' : 'fa-arrow-left'}} " aria-hidden="true"></i>
<span class="phone-hidden">&nbsp;{{readingDirectionModel === ReadingDirection.LeftToRight ? t('left-to-right') : t('right-to-left')}}</span>
</button>
</div>
<div class="controls" style="display: flex; justify-content: space-between; align-items: center; ">
<label for="writing-style" class="form-label">{{t('writing-style-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="writingStyleTooltip" role="button" tabindex="0" aria-describedby="writingStyle-help"></i></label>
<ng-template #writingStyleTooltip>{{t('writing-style-tooltip')}}</ng-template>
<span class="visually-hidden" id="writingStyle-help"><ng-container [ngTemplateOutlet]="writingStyleTooltip"></ng-container></span>
<button (click)="toggleWritingStyle()" id="writing-style" class="btn btn-icon" aria-labelledby="writingStyle-help" title="{{writingStyleModel === WritingStyle.Horizontal ? t('horizontal') : t('vertical')}}">
<i class="fa {{writingStyleModel === WritingStyle.Horizontal ? 'fa-arrows-left-right' : 'fa-arrows-up-down' }}" aria-hidden="true"></i>
<span class="phone-hidden"> {{writingStyleModel === WritingStyle.Horizontal ? t('horizontal') : t('vertical') }}</span>
</button>
<div ngbAccordionItem id="reader-panel" title="Reader Settings" [collapsed]="false">
<h2 class="accordion-header" ngbAccordionHeader>
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded('reader-panel')" aria-controls="collapseOne">
{{t('reader-settings-title')}}
</button>
</h2>
<div ngbAccordionCollapse>
<div ngbAccordionBody>
<ng-template>
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
<label id="readingdirection" class="form-label">{{t('reading-direction-label')}}</label>
<button (click)="toggleReadingDirection()" class="btn btn-icon" aria-labelledby="readingdirection" title="{{readingDirectionModel === ReadingDirection.LeftToRight ? t('left-to-right') : t('right-to-left')}}">
<i class="fa {{readingDirectionModel === ReadingDirection.LeftToRight ? 'fa-arrow-right' : 'fa-arrow-left'}} " aria-hidden="true"></i>
<span class="phone-hidden">&nbsp;{{readingDirectionModel === ReadingDirection.LeftToRight ? t('left-to-right') : t('right-to-left')}}</span>
</button>
</div>
<div class="controls" style="display: flex; justify-content: space-between; align-items: center; ">
<label for="writing-style" class="form-label">{{t('writing-style-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="writingStyleTooltip" role="button" tabindex="0" aria-describedby="writingStyle-help"></i></label>
<ng-template #writingStyleTooltip>{{t('writing-style-tooltip')}}</ng-template>
<span class="visually-hidden" id="writingStyle-help"><ng-container [ngTemplateOutlet]="writingStyleTooltip"></ng-container></span>
<button (click)="toggleWritingStyle()" id="writing-style" class="btn btn-icon" aria-labelledby="writingStyle-help" title="{{writingStyleModel === WritingStyle.Horizontal ? t('horizontal') : t('vertical')}}">
<i class="fa {{writingStyleModel === WritingStyle.Horizontal ? 'fa-arrows-left-right' : 'fa-arrows-up-down' }}" aria-hidden="true"></i>
<span class="phone-hidden"> {{writingStyleModel === WritingStyle.Horizontal ? t('horizontal') : t('vertical') }}</span>
</button>
</div>
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
<label for="tap-pagination" class="form-label">{{t('tap-to-paginate-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="tapPaginationTooltip" role="button" tabindex="0" aria-describedby="tapPagination-help"></i></label>
<ng-template #tapPaginationTooltip>{{t('tap-to-paginate-tooltip')}}</ng-template>
<span class="visually-hidden" id="tapPagination-help">
</div>
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
<label for="tap-pagination" class="form-label">{{t('tap-to-paginate-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="tapPaginationTooltip" role="button" tabindex="0" aria-describedby="tapPagination-help"></i></label>
<ng-template #tapPaginationTooltip>{{t('tap-to-paginate-tooltip')}}</ng-template>
<span class="visually-hidden" id="tapPagination-help">
<ng-container [ngTemplateOutlet]="tapPaginationTooltip"></ng-container>
</span>
<div class="form-check form-switch">
<input type="checkbox" id="tap-pagination" formControlName="bookReaderTapToPaginate" class="form-check-input" aria-labelledby="tapPagination-help">
<label>{{settingsForm.get('bookReaderTapToPaginate')?.value ? t('on') : t('off')}} </label>
<div class="form-check form-switch">
<input type="checkbox" id="tap-pagination" formControlName="bookReaderTapToPaginate" class="form-check-input" aria-labelledby="tapPagination-help">
<label>{{settingsForm.get('bookReaderTapToPaginate')?.value ? t('on') : t('off')}} </label>
</div>
</div>
</div>
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
<label for="immersive-mode" class="form-label">{{t('immersive-mode-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="immersiveModeTooltip" role="button" tabindex="0" aria-describedby="immersiveMode-help"></i></label>
<ng-template #immersiveModeTooltip>{{t('immersive-mode-tooltip')}}</ng-template>
<span class="visually-hidden" id="immersiveMode-help">
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
<label for="immersive-mode" class="form-label">{{t('immersive-mode-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="immersiveModeTooltip" role="button" tabindex="0" aria-describedby="immersiveMode-help"></i></label>
<ng-template #immersiveModeTooltip>{{t('immersive-mode-tooltip')}}</ng-template>
<span class="visually-hidden" id="immersiveMode-help">
<ng-container [ngTemplateOutlet]="immersiveModeTooltip"></ng-container>
</span>
<div class="form-check form-switch">
<input type="checkbox" id="immersive-mode" formControlName="bookReaderImmersiveMode" class="form-check-input" aria-labelledby="immersiveMode-help">
<label>{{settingsForm.get('bookReaderImmersiveMode')?.value ? t('on') : t('off')}} </label>
<div class="form-check form-switch">
<input type="checkbox" id="immersive-mode" formControlName="bookReaderImmersiveMode" class="form-check-input" aria-labelledby="immersiveMode-help">
<label>{{settingsForm.get('bookReaderImmersiveMode')?.value ? t('on') : t('off')}} </label>
</div>
</div>
</div>
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
<label id="fullscreen" class="form-label">{{t('fullscreen-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top"
[ngbTooltip]="fullscreenTooltip" role="button" tabindex="1" aria-describedby="fullscreen-help"></i></label>
<ng-template #fullscreenTooltip>{{t('fullscreen-tooltip')}}</ng-template>
<span class="visually-hidden" id="fullscreen-help">
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
<label id="fullscreen" class="form-label">{{t('fullscreen-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top"
[ngbTooltip]="fullscreenTooltip" role="button" tabindex="1" aria-describedby="fullscreen-help"></i></label>
<ng-template #fullscreenTooltip>{{t('fullscreen-tooltip')}}</ng-template>
<span class="visually-hidden" id="fullscreen-help">
<ng-container [ngTemplateOutlet]="fullscreenTooltip"></ng-container>
</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">&nbsp;{{isFullscreen ? t('exit') : t('enter')}}</span>
</button>
</div>
<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">&nbsp;{{isFullscreen ? t('exit') : t('enter')}}</span>
</button>
</div>
<div class="controls">
<label id="layout-mode" class="form-label" style="margin-bottom:0.5rem">{{t('layout-mode-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="layoutTooltip" role="button" tabindex="1" aria-describedby="layout-help"></i></label>
<ng-template #layoutTooltip><span [innerHTML]="t('layout-mode-tooltip')"></span></ng-template>
<span class="visually-hidden" id="layout-help">
<div class="controls">
<label id="layout-mode" class="form-label" style="margin-bottom:0.5rem">{{t('layout-mode-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="layoutTooltip" role="button" tabindex="1" aria-describedby="layout-help"></i></label>
<ng-template #layoutTooltip><span [innerHTML]="t('layout-mode-tooltip')"></span></ng-template>
<span class="visually-hidden" id="layout-help">
<ng-container [ngTemplateOutlet]="layoutTooltip"></ng-container>
</span>
<br>
<div class="btn-group d-flex justify-content-center" role="group" [attr.aria-label]="t('layout-mode-label')">
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Default" class="btn-check" id="layout-mode-default" autocomplete="off">
<label class="btn btn-outline-primary" for="layout-mode-default">{{t('layout-mode-option-scroll')}}</label>
<br>
<div class="btn-group d-flex justify-content-center" role="group" [attr.aria-label]="t('layout-mode-label')">
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Default" class="btn-check" id="layout-mode-default" autocomplete="off">
<label class="btn btn-outline-primary" for="layout-mode-default">{{t('layout-mode-option-scroll')}}</label>
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Column1" class="btn-check" id="layout-mode-col1" autocomplete="off">
<label class="btn btn-outline-primary" for="layout-mode-col1">{{t('layout-mode-option-1col')}}</label>
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Column1" class="btn-check" id="layout-mode-col1" autocomplete="off">
<label class="btn btn-outline-primary" for="layout-mode-col1">{{t('layout-mode-option-1col')}}</label>
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Column2" class="btn-check" id="layout-mode-col2" autocomplete="off">
<label class="btn btn-outline-primary" for="layout-mode-col2">{{t('layout-mode-option-2col')}}</label>
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Column2" class="btn-check" id="layout-mode-col2" autocomplete="off">
<label class="btn btn-outline-primary" for="layout-mode-col2">{{t('layout-mode-option-2col')}}</label>
</div>
</div>
</div>
</ng-template>
</ng-template>
</div>
</div>
</div>
</div>
<div ngbAccordionItem id="color-panel" [title]="t('color-theme-title')" [collapsed]="false">
<h2 class="accordion-header" ngbAccordionHeader>
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded('color-panel')" aria-controls="collapseOne">
{{t('color-theme-title')}}
</button>
</h2>
<div ngbAccordionCollapse>
<div ngbAccordionBody>
<ng-template>
<div class="controls">
<ng-container *ngFor="let theme of themes">
<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 ngbAccordionItem id="color-panel" [title]="t('color-theme-title')" [collapsed]="false">
<h2 class="accordion-header" ngbAccordionHeader>
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded('color-panel')" aria-controls="collapseOne">
{{t('color-theme-title')}}
</button>
</h2>
<div ngbAccordionCollapse>
<div ngbAccordionBody>
<ng-template>
<div class="controls">
<ng-container *ngFor="let theme of themes">
<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>
</div>
</div>
</div>
</div>
</form>
</ng-container>
<div class="row g-0 justify-content-between mt-2">
<button (click)="savePref()" class="btn btn-primary col">{{t('save-global')}}</button>
</div>
</div>
</form>
</ng-container>
}

View file

@ -5,7 +5,7 @@ import {
Component, DestroyRef,
EventEmitter,
inject,
Inject,
Inject, Input,
OnInit,
Output
} from '@angular/core';
@ -27,6 +27,9 @@ import { BookPaperTheme } from '../../_models/book-paper-theme';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import { NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionToggle, NgbAccordionButton, NgbCollapse, NgbAccordionCollapse, NgbAccordionBody, NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
import {TranslocoDirective} from "@jsverse/transloco";
import {ReadingProfileService} from "../../../_services/reading-profile.service";
import {ReadingProfile} from "../../../_models/preferences/reading-profiles";
import {debounceTime, distinctUntilChanged, tap} from "rxjs/operators";
/**
* Used for book reader. Do not use for other components
@ -89,9 +92,10 @@ const mobileBreakpointMarginOverride = 700;
templateUrl: './reader-settings.component.html',
styleUrls: ['./reader-settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionToggle, NgbAccordionButton, NgbCollapse, NgbAccordionCollapse, NgbAccordionBody, NgFor, NgbTooltip, NgTemplateOutlet, NgIf, NgClass, NgStyle, TitleCasePipe, TranslocoDirective]
imports: [ReactiveFormsModule, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionButton, NgbAccordionCollapse, NgbAccordionBody, NgFor, NgbTooltip, NgTemplateOutlet, NgIf, NgClass, NgStyle, TitleCasePipe, TranslocoDirective]
})
export class ReaderSettingsComponent implements OnInit {
@Input({required:true}) seriesId!: number;
/**
* Outputs when clickToPaginate is changed
*/
@ -125,6 +129,8 @@ export class ReaderSettingsComponent implements OnInit {
*/
@Output() immersiveMode: EventEmitter<boolean> = new EventEmitter();
readingProfile: ReadingProfile | null = null;
user!: User;
/**
* List of all font families user can select from
@ -170,7 +176,7 @@ export class ReaderSettingsComponent implements OnInit {
constructor(private bookService: BookService, private accountService: AccountService,
@Inject(DOCUMENT) private document: Document, private themeService: ThemeService,
private readonly cdRef: ChangeDetectorRef) {}
private readonly cdRef: ChangeDetectorRef, private readingProfileService: ReadingProfileService) {}
ngOnInit(): void {
@ -178,124 +184,146 @@ export class ReaderSettingsComponent implements OnInit {
this.fontOptions = this.fontFamilies.map(f => f.title);
this.cdRef.markForCheck();
this.readingProfileService.getForSeries(this.seriesId).subscribe(profile => {
this.readingProfile = profile;
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.setupSettings();
this.setTheme(this.readingProfile.bookReaderThemeName || this.themeService.defaultBookTheme);
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.resetSettings();
})
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) {
this.user = user;
if (this.user.preferences.bookReaderFontFamily === undefined) {
this.user.preferences.bookReaderFontFamily = 'default';
}
if (this.user.preferences.bookReaderFontSize === undefined || this.user.preferences.bookReaderFontSize < 50) {
this.user.preferences.bookReaderFontSize = 100;
}
if (this.user.preferences.bookReaderLineSpacing === undefined || this.user.preferences.bookReaderLineSpacing < 100) {
this.user.preferences.bookReaderLineSpacing = 100;
}
if (this.user.preferences.bookReaderMargin === undefined) {
this.user.preferences.bookReaderMargin = 0;
}
if (this.user.preferences.bookReaderReadingDirection === undefined) {
this.user.preferences.bookReaderReadingDirection = ReadingDirection.LeftToRight;
}
if (this.user.preferences.bookReaderWritingStyle === undefined) {
this.user.preferences.bookReaderWritingStyle = WritingStyle.Horizontal;
}
this.readingDirectionModel = this.user.preferences.bookReaderReadingDirection;
this.writingStyleModel = this.user.preferences.bookReaderWritingStyle;
this.settingsForm.addControl('bookReaderFontFamily', new FormControl(this.user.preferences.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.user.preferences.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.user.preferences.bookReaderTapToPaginate, []));
this.settingsForm.get('bookReaderTapToPaginate')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => {
this.clickToPaginateChanged.emit(value);
});
this.settingsForm.addControl('bookReaderLineSpacing', new FormControl(this.user.preferences.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.user.preferences.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.user.preferences.bookReaderLayoutMode || BookPageLayoutMode.Default, []));
this.settingsForm.get('layoutMode')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((layoutMode: BookPageLayoutMode) => {
this.layoutModeUpdate.emit(layoutMode);
});
this.settingsForm.addControl('bookReaderImmersiveMode', new FormControl(this.user.preferences.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);
});
this.setTheme(this.user.preferences.bookReaderThemeName || this.themeService.defaultBookTheme);
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.user.preferences.bookReaderTapToPaginate);
this.layoutModeUpdate.emit(this.user.preferences.bookReaderLayoutMode);
this.immersiveMode.emit(this.user.preferences.bookReaderImmersiveMode);
this.resetSettings();
} else {
this.resetSettings();
}
});
}
setupSettings() {
if (!this.readingProfile) return;
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 || BookPageLayoutMode.Default, []));
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(),
takeUntilDestroyed(this.destroyRef),
tap(_ => this.updateImplicit())
).subscribe();
}
resetSettings() {
if (!this.readingProfile) return;
if (this.user) {
this.setPageStyles(this.user.preferences.bookReaderFontFamily, this.user.preferences.bookReaderFontSize + '%', this.user.preferences.bookReaderMargin + 'vw', this.user.preferences.bookReaderLineSpacing + '%');
this.setPageStyles(this.readingProfile.bookReaderFontFamily, this.readingProfile.bookReaderFontSize + '%', this.readingProfile.bookReaderMargin + 'vw', this.readingProfile.bookReaderLineSpacing + '%');
} else {
this.setPageStyles();
}
this.settingsForm.get('bookReaderFontFamily')?.setValue(this.user.preferences.bookReaderFontFamily);
this.settingsForm.get('bookReaderFontSize')?.setValue(this.user.preferences.bookReaderFontSize);
this.settingsForm.get('bookReaderLineSpacing')?.setValue(this.user.preferences.bookReaderLineSpacing);
this.settingsForm.get('bookReaderMargin')?.setValue(this.user.preferences.bookReaderMargin);
this.settingsForm.get('bookReaderReadingDirection')?.setValue(this.user.preferences.bookReaderReadingDirection);
this.settingsForm.get('bookReaderTapToPaginate')?.setValue(this.user.preferences.bookReaderTapToPaginate);
this.settingsForm.get('bookReaderLayoutMode')?.setValue(this.user.preferences.bookReaderLayoutMode);
this.settingsForm.get('bookReaderImmersiveMode')?.setValue(this.user.preferences.bookReaderImmersiveMode);
this.settingsForm.get('bookReaderWritingStyle')?.setValue(this.user.preferences.bookReaderWritingStyle);
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({
error: err => {
console.error(err);
}
})
}
/**
* Internal method to be used by resetSettings. Pass items in with quantifiers
*/
@ -323,6 +351,7 @@ export class ReaderSettingsComponent implements OnInit {
this.activeTheme = theme;
this.cdRef.markForCheck();
this.colorThemeUpdate.emit(theme);
this.updateImplicit();
}
toggleReadingDirection() {
@ -334,6 +363,7 @@ export class ReaderSettingsComponent implements OnInit {
this.cdRef.markForCheck();
this.readingDirection.emit(this.readingDirectionModel);
this.updateImplicit();
}
toggleWritingStyle() {
@ -345,6 +375,7 @@ export class ReaderSettingsComponent implements OnInit {
this.cdRef.markForCheck();
this.bookReaderWritingStyle.emit(this.writingStyleModel);
this.updateImplicit();
}
toggleFullscreen() {
@ -352,4 +383,29 @@ export class ReaderSettingsComponent implements OnInit {
this.cdRef.markForCheck();
this.fullscreen.emit();
}
savePref() {
this.readingProfileService.updateProfile(this.packReadingProfile(), this.seriesId).subscribe()
}
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;
}
}

View file

@ -53,92 +53,93 @@
<div class="reading-area"
ngSwipe (swipeEnd)="onSwipeEnd($event)" (swipeMove)="onSwipeMove($event)"
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : '100dvh'}" #readingArea>
@if (readerMode !== ReaderMode.Webtoon) {
<div appDblClick (dblclick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
<app-canvas-renderer
[readerSettings$]="readerSettings$"
[image$]="currentImage$"
[bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$">
</app-canvas-renderer>
</div>
<!-- Pagination controls and screen hints-->
<div class="pagination-area">
<div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, KeyDirection.Left)"
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'), 'max-height': MaxHeight}">
@if (showClickOverlay) {
<div>
<i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}"
[title]="t('prev-page-tooltip')" aria-hidden="true"></i>
</div>
}
@if (readingProfile !== null) {
@if (readerMode !== ReaderMode.Webtoon) {
<div appDblClick (dblclick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
<app-canvas-renderer
[readerSettings$]="readerSettings$"
[image$]="currentImage$"
[bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$">
</app-canvas-renderer>
</div>
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, KeyDirection.Right)"
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'),
<!-- Pagination controls and screen hints-->
<div class="pagination-area">
<div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, KeyDirection.Left)"
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'), 'max-height': MaxHeight}">
@if (showClickOverlay) {
<div>
<i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}"
[title]="t('prev-page-tooltip')" aria-hidden="true"></i>
</div>
}
</div>
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, KeyDirection.Right)"
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'),
'left': 'inherit',
'right': RightPaginationOffset + 'px',
'max-height': MaxHeight}">
@if (showClickOverlay) {
<div>
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
[title]="t('next-page-tooltip')" aria-hidden="true"></i>
</div>
}
@if (showClickOverlay) {
<div>
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
[title]="t('next-page-tooltip')" aria-hidden="true"></i>
</div>
}
</div>
</div>
</div>
<div appDblClick (doubleClick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
<app-single-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$"
[pageNum$]="pageNum$"
[showClickOverlay$]="showClickOverlay$">
</app-single-renderer>
<div appDblClick (doubleClick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
<app-single-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$"
[pageNum$]="pageNum$"
[showClickOverlay$]="showClickOverlay$">
</app-single-renderer>
<app-double-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$"
[pageNum$]="pageNum$"
[getPage]="getPageFn">
</app-double-renderer>
<app-double-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$"
[pageNum$]="pageNum$"
[getPage]="getPageFn">
</app-double-renderer>
<app-double-reverse-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$"
[pageNum$]="pageNum$"
[getPage]="getPageFn">
</app-double-reverse-renderer>
<app-double-reverse-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$"
[pageNum$]="pageNum$"
[getPage]="getPageFn">
</app-double-reverse-renderer>
<app-double-no-cover-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$"
[pageNum$]="pageNum$"
[getPage]="getPageFn">
</app-double-no-cover-renderer>
</div>
} @else {
@if (!isLoading && !inSetup) {
<div class="webtoon-images" appDblClick (doubleClick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
<app-infinite-scroller [pageNum]="pageNum"
[bufferPages]="5"
[goToPage]="goToPageEvent"
(pageNumberChange)="handleWebtoonPageChange($event)"
[totalPages]="maxPages"
[urlProvider]="getPageUrl"
(loadNextChapter)="loadNextChapter()"
(loadPrevChapter)="loadPrevChapter()"
[bookmarkPage]="showBookmarkEffectEvent"
[fullscreenToggled]="fullscreenEvent"
[readerSettings$]="readerSettings$">
</app-infinite-scroller>
<app-double-no-cover-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$"
[pageNum$]="pageNum$"
[getPage]="getPageFn">
</app-double-no-cover-renderer>
</div>
} @else {
@if (!isLoading && !inSetup) {
<div class="webtoon-images" appDblClick (doubleClick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
<app-infinite-scroller [pageNum]="pageNum"
[bufferPages]="5"
[goToPage]="goToPageEvent"
(pageNumberChange)="handleWebtoonPageChange($event)"
[totalPages]="maxPages"
[urlProvider]="getPageUrl"
(loadNextChapter)="loadNextChapter()"
(loadPrevChapter)="loadPrevChapter()"
[bookmarkPage]="showBookmarkEffectEvent"
[fullscreenToggled]="fullscreenEvent"
[readerSettings$]="readerSettings$">
</app-infinite-scroller>
</div>
}
}
}
}
</div>
@if (menuOpen) {

View file

@ -40,7 +40,6 @@ import {Breakpoint, KEY_CODES, UtilityService} from 'src/app/shared/_services/ut
import {LibraryType} from 'src/app/_models/library/library';
import {MangaFormat} from 'src/app/_models/manga-format';
import {PageSplitOption} from 'src/app/_models/preferences/page-split-option';
import {layoutModes, pageSplitOptions} from 'src/app/_models/preferences/preferences';
import {ReaderMode} from 'src/app/_models/preferences/reader-mode';
import {ReadingDirection} from 'src/app/_models/preferences/reading-direction';
import {ScalingOption} from 'src/app/_models/preferences/scaling-option';
@ -70,6 +69,8 @@ import {LoadingComponent} from '../../../shared/loading/loading.component';
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {shareReplay} from "rxjs/operators";
import {DblClickDirective} from "../../../_directives/dbl-click.directive";
import {layoutModes, pageSplitOptions, ReadingProfile} from "../../../_models/preferences/reading-profiles";
import {ReadingProfileService} from "../../../_services/reading-profile.service";
const PREFETCH_PAGES = 10;
@ -150,6 +151,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
private readonly modalService = inject(NgbModal);
private readonly cdRef = inject(ChangeDetectorRef);
private readonly toastr = inject(ToastrService);
private readonly readingProfileService = inject(ReadingProfileService);
public readonly readerService = inject(ReaderService);
public readonly utilityService = inject(UtilityService);
public readonly mangaReaderService = inject(MangaReaderService);
@ -194,6 +196,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
totalSeriesPages = 0;
totalSeriesPagesRead = 0;
user!: User;
readingProfile: ReadingProfile | null = null;
generalSettingsForm!: FormGroup;
readingDirection = ReadingDirection.LeftToRight;
@ -496,34 +499,54 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.continuousChaptersStack.push(this.chapterId);
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
forkJoin([
this.accountService.currentUser$.pipe(take(1)),
this.readingProfileService.getForSeries(this.seriesId)])
.subscribe(([user, profile]) => {
if (!user) {
this.router.navigateByUrl('/login');
return;
}
this.readingProfile = profile;
this.user = user;
this.hasBookmarkRights = this.accountService.hasBookmarkRole(user) || this.accountService.hasAdminRole(user);
this.readingDirection = this.user.preferences.readingDirection;
this.scalingOption = this.user.preferences.scalingOption;
this.pageSplitOption = this.user.preferences.pageSplitOption;
this.autoCloseMenu = this.user.preferences.autoCloseMenu;
this.readerMode = this.user.preferences.readerMode;
this.layoutMode = this.user.preferences.layoutMode || LayoutMode.Single;
this.backgroundColor = this.user.preferences.backgroundColor || '#000000';
this.readingDirection = this.readingProfile.readingDirection;
this.scalingOption = this.readingProfile.scalingOption;
this.pageSplitOption = this.readingProfile.pageSplitOption;
this.autoCloseMenu = this.readingProfile.autoCloseMenu;
this.readerMode = this.readingProfile.readerMode;
this.layoutMode = this.readingProfile.layoutMode || LayoutMode.Single;
this.backgroundColor = this.readingProfile.backgroundColor || '#000000';
this.readerService.setOverrideStyles(this.backgroundColor);
this.generalSettingsForm = this.formBuilder.nonNullable.group({
autoCloseMenu: new FormControl(this.autoCloseMenu),
pageSplitOption: new FormControl(this.pageSplitOption),
fittingOption: new FormControl(this.mangaReaderService.translateScalingOption(this.scalingOption)),
widthSlider: new FormControl('none'),
widthSlider: new FormControl(this.readingProfile.widthOverride ?? 'none'),
layoutMode: new FormControl(this.layoutMode),
darkness: new FormControl(100),
emulateBook: new FormControl(this.user.preferences.emulateBook),
swipeToPaginate: new FormControl(this.user.preferences.swipeToPaginate)
emulateBook: new FormControl(this.readingProfile.emulateBook),
swipeToPaginate: new FormControl(this.readingProfile.swipeToPaginate)
});
// Update implicit reading profile while changing settings
this.generalSettingsForm.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
takeUntilDestroyed(this.destroyRef),
tap(_ => {
this.readingProfileService.updateImplicit(this.packReadingProfile(), this.seriesId).subscribe({
error: err => {
console.error(err);
}
})
})
).subscribe();
this.readerModeSubject.next(this.readerMode);
this.pagingDirectionSubject.next(this.pagingDirection);
@ -562,7 +585,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.layoutMode = parseInt(val, 10);
if (this.layoutMode === LayoutMode.Single) {
this.generalSettingsForm.get('pageSplitOption')?.setValue(this.user.preferences.pageSplitOption);
this.generalSettingsForm.get('pageSplitOption')?.setValue(this.readingProfile!.pageSplitOption);
this.generalSettingsForm.get('pageSplitOption')?.enable();
this.generalSettingsForm.get('widthSlider')?.enable();
this.generalSettingsForm.get('fittingOption')?.enable();
@ -604,9 +627,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.toastr.info(translate('manga-reader.first-time-reading-manga'));
}
});
});
this.init();
this.init();
});
}
ngAfterViewInit() {
@ -780,7 +803,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
switchToWebtoonReaderIfPagesLikelyWebtoon() {
if (this.readerMode === ReaderMode.Webtoon) return;
if (!this.user.preferences.allowAutomaticWebtoonReaderDetection) return;
if (!this.readingProfile!.allowAutomaticWebtoonReaderDetection) return;
if (this.mangaReaderService.shouldBeWebtoonMode()) {
this.readerMode = ReaderMode.Webtoon;
@ -1460,7 +1483,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.readingDirection = ReadingDirection.LeftToRight;
}
if (this.menuOpen && this.user.preferences.showScreenHints) {
if (this.menuOpen && this.readingProfile!.showScreenHints) {
this.showClickOverlay = true;
this.showClickOverlaySubject.next(true);
setTimeout(() => {
@ -1731,28 +1754,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// menu only code
savePref() {
const modelSettings = this.generalSettingsForm.getRawValue();
// Get latest preferences from user, overwrite with what we manage in this UI, then save
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (!user) return;
const data = {...user.preferences};
data.layoutMode = parseInt(modelSettings.layoutMode, 10);
data.readerMode = this.readerMode;
data.autoCloseMenu = this.autoCloseMenu;
data.readingDirection = this.readingDirection;
data.emulateBook = modelSettings.emulateBook;
data.swipeToPaginate = modelSettings.swipeToPaginate;
data.pageSplitOption = parseInt(modelSettings.pageSplitOption, 10);
data.locale = data.locale || 'en';
this.accountService.updatePreferences(data).subscribe(updatedPrefs => {
this.toastr.success(translate('manga-reader.user-preferences-updated'));
if (this.user) {
this.user.preferences = updatedPrefs;
this.cdRef.markForCheck();
}
})
});
this.readingProfileService.updateProfile(this.packReadingProfile(), this.seriesId).subscribe(_ => {
this.toastr.success(translate('manga-reader.user-preferences-updated'));
})
}
translatePrefOptions(o: {text: string, value: any}) {
@ -1761,4 +1765,19 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return d;
}
private packReadingProfile(): ReadingProfile {
const modelSettings = this.generalSettingsForm.getRawValue();
const data = {...this.readingProfile!};
data.layoutMode = parseInt(modelSettings.layoutMode, 10);
data.readerMode = this.readerMode;
data.autoCloseMenu = this.autoCloseMenu;
data.readingDirection = this.readingDirection;
data.emulateBook = modelSettings.emulateBook;
data.swipeToPaginate = modelSettings.swipeToPaginate;
data.pageSplitOption = parseInt(modelSettings.pageSplitOption, 10);
// TODO: Check if this saves correctly!
data.widthOverride = modelSettings.widthSlider === 'none' ? null : modelSettings.widthOverride;
return data;
}
}

View file

@ -14,7 +14,7 @@ import {
import {ActivatedRoute, Router} from '@angular/router';
import {NgxExtendedPdfViewerModule, PageViewModeType, ProgressBarEvent, ScrollModeType} from 'ngx-extended-pdf-viewer';
import {ToastrService} from 'ngx-toastr';
import {take} from 'rxjs';
import {forkJoin, take} from 'rxjs';
import {BookService} from 'src/app/book-reader/_services/book.service';
import {Breakpoint, KEY_CODES, UtilityService} from 'src/app/shared/_services/utility.service';
import {Chapter} from 'src/app/_models/chapter';
@ -34,6 +34,8 @@ import {PdfSpreadMode} from "../../../_models/preferences/pdf-spread-mode";
import {SpreadType} from "ngx-extended-pdf-viewer/lib/options/spread-type";
import {PdfScrollModeTypePipe} from "../../_pipe/pdf-scroll-mode.pipe";
import {PdfSpreadTypePipe} from "../../_pipe/pdf-spread-mode.pipe";
import {ReadingProfileService} from "../../../_services/reading-profile.service";
import {ReadingProfile} from "../../../_models/preferences/reading-profiles";
@Component({
selector: 'app-pdf-reader',
@ -54,6 +56,7 @@ export class PdfReaderComponent implements OnInit, OnDestroy {
private readonly themeService = inject(ThemeService);
private readonly cdRef = inject(ChangeDetectorRef);
public readonly accountService = inject(AccountService);
private readonly readingProfileService = inject(ReadingProfileService);
public readonly readerService = inject(ReaderService);
public readonly utilityService = inject(UtilityService);
public readonly destroyRef = inject(DestroyRef);
@ -69,6 +72,7 @@ export class PdfReaderComponent implements OnInit, OnDestroy {
chapterId!: number;
chapter!: Chapter;
user!: User;
readingProfile!: ReadingProfile;
/**
* Reading List id. Defaults to -1.
@ -171,9 +175,13 @@ export class PdfReaderComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck();
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
forkJoin([
this.accountService.currentUser$.pipe(take(1)),
this.readingProfileService.getForSeries(this.seriesId)
]).subscribe(([user, profile]) => {
if (user) {
this.user = user;
this.readingProfile = profile;
this.init();
}
});
@ -237,9 +245,9 @@ export class PdfReaderComponent implements OnInit, OnDestroy {
init() {
this.pageLayoutMode = this.convertPdfLayoutMode(PdfLayoutMode.Multiple);
this.scrollMode = this.convertPdfScrollMode(this.user.preferences.pdfScrollMode || PdfScrollMode.Vertical);
this.spreadMode = this.convertPdfSpreadMode(this.user.preferences.pdfSpreadMode || PdfSpreadMode.None);
this.theme = this.convertPdfTheme(this.user.preferences.pdfTheme || PdfTheme.Dark);
this.scrollMode = this.convertPdfScrollMode(this.readingProfile.pdfScrollMode || PdfScrollMode.Vertical);
this.spreadMode = this.convertPdfSpreadMode(this.readingProfile.pdfSpreadMode || PdfSpreadMode.None);
this.theme = this.convertPdfTheme(this.readingProfile.pdfTheme || PdfTheme.Dark);
this.backgroundColor = this.themeMap[this.theme].background;
this.fontColor = this.themeMap[this.theme].font; // TODO: Move this to an observable or something

View file

@ -201,6 +201,7 @@
</app-setting-item>
</div>
<!--
<div class="row g-0 mt-4 mb-4">
<app-setting-item [title]="t('background-color-label')" [subtitle]="t('background-color-tooltip')">
<ng-template #view>
@ -217,6 +218,7 @@
</ng-template>
</app-setting-item>
</div>
-->
<div class="row g-0 mt-4 mb-4">
<app-setting-switch [title]="t('auto-close-menu-label')" [subtitle]="t('auto-close-menu-tooltip')">

View file

@ -1,17 +1,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core';
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {
bookLayoutModes,
bookWritingStyles,
layoutModes,
pageSplitOptions,
pdfScrollModes,
pdfSpreadModes,
pdfThemes,
Preferences,
readingDirections,
readingModes,
scalingOptions
Preferences
} from "../../_models/preferences/preferences";
import {AccountService} from "../../_services/account.service";
import {BookService} from "../../book-reader/_services/book.service";
@ -44,6 +34,13 @@ import {PdfThemePipe} from "../../_pipes/pdf-theme.pipe";
import {PdfScrollModePipe} from "../../_pipes/pdf-scroll-mode.pipe";
import {LicenseService} from "../../_services/license.service";
import {ColorPickerDirective} from "ngx-color-picker";
import {
bookLayoutModes, bookWritingStyles,
layoutModes, pageSplitOptions,
pdfScrollModes,
pdfSpreadModes,
pdfThemes, readingDirections, readingModes, scalingOptions
} from "../../_models/preferences/reading-profiles";
@Component({
selector: 'app-manga-user-preferences',
@ -140,7 +137,7 @@ export class ManageUserPreferencesComponent implements OnInit {
this.user = results.user;
this.user.preferences = results.pref;
if (this.fontFamilies.indexOf(this.user.preferences.bookReaderFontFamily) < 0) {
/*if (this.fontFamilies.indexOf(this.user.preferences.bookReaderFontFamily) < 0) {
this.user.preferences.bookReaderFontFamily = 'default';
}
@ -169,7 +166,7 @@ export class ManageUserPreferencesComponent implements OnInit {
this.settingsForm.addControl('pdfTheme', new FormControl(this.user?.preferences.pdfTheme || PdfTheme.Dark, []));
this.settingsForm.addControl('pdfScrollMode', new FormControl(this.user?.preferences.pdfScrollMode || PdfScrollMode.Vertical, []));
this.settingsForm.addControl('pdfSpreadMode', new FormControl(this.user?.preferences.pdfSpreadMode || PdfSpreadMode.None, []));
this.settingsForm.addControl('pdfSpreadMode', new FormControl(this.user?.preferences.pdfSpreadMode || PdfSpreadMode.None, []));*/
this.settingsForm.addControl('theme', new FormControl(this.user.preferences.theme, []));
this.settingsForm.addControl('globalPageLayoutMode', new FormControl(this.user.preferences.globalPageLayoutMode, []));
@ -217,7 +214,7 @@ export class ManageUserPreferencesComponent implements OnInit {
reset() {
if (!this.user) return;
this.settingsForm.get('readingDirection')?.setValue(this.user.preferences.readingDirection, {onlySelf: true, emitEvent: false});
/*this.settingsForm.get('readingDirection')?.setValue(this.user.preferences.readingDirection, {onlySelf: true, emitEvent: false});
this.settingsForm.get('scalingOption')?.setValue(this.user.preferences.scalingOption, {onlySelf: true, emitEvent: false});
this.settingsForm.get('pageSplitOption')?.setValue(this.user.preferences.pageSplitOption, {onlySelf: true, emitEvent: false});
this.settingsForm.get('autoCloseMenu')?.setValue(this.user.preferences.autoCloseMenu, {onlySelf: true, emitEvent: false});
@ -242,7 +239,7 @@ export class ManageUserPreferencesComponent implements OnInit {
this.settingsForm.get('pdfTheme')?.setValue(this.user?.preferences.pdfTheme || PdfTheme.Dark, {onlySelf: true, emitEvent: false});
this.settingsForm.get('pdfScrollMode')?.setValue(this.user?.preferences.pdfScrollMode || PdfScrollMode.Vertical, {onlySelf: true, emitEvent: false});
this.settingsForm.get('pdfSpreadMode')?.setValue(this.user?.preferences.pdfSpreadMode || PdfSpreadMode.None, {onlySelf: true, emitEvent: false});
this.settingsForm.get('pdfSpreadMode')?.setValue(this.user?.preferences.pdfSpreadMode || PdfSpreadMode.None, {onlySelf: true, emitEvent: false});*/
this.settingsForm.get('theme')?.setValue(this.user.preferences.theme, {onlySelf: true, emitEvent: false});
this.settingsForm.get('globalPageLayoutMode')?.setValue(this.user.preferences.globalPageLayoutMode, {onlySelf: true, emitEvent: false});
@ -260,7 +257,7 @@ export class ManageUserPreferencesComponent implements OnInit {
packSettings(): Preferences {
const modelSettings = this.settingsForm.value;
return {
readingDirection: parseInt(modelSettings.readingDirection, 10),
/*readingDirection: parseInt(modelSettings.readingDirection, 10),
scalingOption: parseInt(modelSettings.scalingOption, 10),
pageSplitOption: parseInt(modelSettings.pageSplitOption, 10),
autoCloseMenu: modelSettings.autoCloseMenu,
@ -277,21 +274,21 @@ export class ManageUserPreferencesComponent implements OnInit {
bookReaderReadingDirection: parseInt(modelSettings.bookReaderReadingDirection, 10),
bookReaderWritingStyle: parseInt(modelSettings.bookReaderWritingStyle, 10),
bookReaderLayoutMode: parseInt(modelSettings.bookReaderLayoutMode, 10),
bookReaderThemeName: modelSettings.bookReaderThemeName,
bookReaderThemeName: modelSettings.bookReaderThemeName,*/
theme: modelSettings.theme,
bookReaderImmersiveMode: modelSettings.bookReaderImmersiveMode,
//bookReaderImmersiveMode: modelSettings.bookReaderImmersiveMode,
globalPageLayoutMode: parseInt(modelSettings.globalPageLayoutMode, 10),
blurUnreadSummaries: modelSettings.blurUnreadSummaries,
promptForDownloadSize: modelSettings.promptForDownloadSize,
noTransitions: modelSettings.noTransitions,
emulateBook: modelSettings.emulateBook,
swipeToPaginate: modelSettings.swipeToPaginate,
//emulateBook: modelSettings.emulateBook,
//swipeToPaginate: modelSettings.swipeToPaginate,
collapseSeriesRelationships: modelSettings.collapseSeriesRelationships,
shareReviews: modelSettings.shareReviews,
locale: modelSettings.locale || 'en',
pdfTheme: parseInt(modelSettings.pdfTheme, 10),
pdfScrollMode: parseInt(modelSettings.pdfScrollMode, 10),
pdfSpreadMode: parseInt(modelSettings.pdfSpreadMode, 10),
//pdfTheme: parseInt(modelSettings.pdfTheme, 10),
//pdfScrollMode: parseInt(modelSettings.pdfScrollMode, 10),
//pdfSpreadMode: parseInt(modelSettings.pdfSpreadMode, 10),
aniListScrobblingEnabled: modelSettings.aniListScrobblingEnabled,
wantToReadSync: modelSettings.wantToReadSync
};
@ -301,7 +298,7 @@ export class ManageUserPreferencesComponent implements OnInit {
this.settingsForm.markAsDirty();
this.settingsForm.markAsTouched();
if (this.user?.preferences) {
this.user.preferences.backgroundColor = color;
//this.user.preferences.backgroundColor = color;
}
this.settingsForm.get('backgroundColor')?.setValue(color);

View file

@ -1181,6 +1181,7 @@
"line-spacing-label": "{{user-preferences.line-height-book-label}}",
"margin-label": "{{user-preferences.margin-book-label}}",
"reset-to-defaults": "Reset to Defaults",
"save-global": "Save to your reading profile",
"reader-settings-title": "Reader Settings",
"reading-direction-label": "{{user-preferences.reading-direction-book-label}}",
"right-to-left": "Right to Left",