Added vertical reading mode to the book reader (#1787)

* Add vertical reading mode support and update API for reading mode preference

* Removed dead code, added a fix for scroll end margins in chrome when in vertical mode(book reader). Added back some comments

* Added Description()] annotation for the ReadingMode enum, like other enums, and added summary documentation

* Added the ability to scroll in vertical writing style without holding down shift. Also renamed the book reader's readingMode to writing style.

* Renamed the BookReadingMode to BookWritingStyle. And changed the migrations accordingly.

* Fixed some minor bugs, regarding scrolling and vertical writing style when the book settings is open.

* Fixed a minor bug where the graphics regarding the current page would require the mouse to be moved before it got updated when switching between writing styles.

* Fixed some bugs regarding furigana getting a bit cropped same for images

* Add vertical reading mode support and update API for reading mode preference

* Removed dead code, added a fix for scroll end margins in chrome when in vertical mode(book reader). Added back some comments

* Added Description()] annotation for the ReadingMode enum, like other enums, and added summary documentation

* Added the ability to scroll in vertical writing style without holding down shift. Also renamed the book reader's readingMode to writing style.

* Renamed the BookReadingMode to BookWritingStyle. And changed the migrations accordingly.

* Fixed some minor bugs, regarding scrolling and vertical writing style when the book settings is open.

* Fixed a minor bug where the graphics regarding the current page would require the mouse to be moved before it got updated when switching between writing styles.

* Fixed some bugs regarding furigana getting a bit cropped same for images

* Added reset support for writing style, after rebase.

* Changes pagination for vertical scrolling such as the user will need to scroll to end before being able to paginate. Previously it felt unnatural and the user could accidentally paginate while scrolling on mobile.

* Pagination would not stick to the left if the content was smaller than the reader in vertical writing style.

* Fixed summary text

* Added missing line, fixes build error

* Addresses the comments given in code-review.

* Moved columnGap outside the class, and changed it to a const
This commit is contained in:
CKolle 2023-03-06 21:02:29 +01:00 committed by GitHub
parent 5fb942bfe1
commit 3484211132
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2268 additions and 104 deletions

View File

@ -99,6 +99,7 @@ public class UsersController : BaseApiController
existingPreferences.BookReaderFontSize = preferencesDto.BookReaderFontSize;
existingPreferences.BookReaderTapToPaginate = preferencesDto.BookReaderTapToPaginate;
existingPreferences.BookReaderReadingDirection = preferencesDto.BookReaderReadingDirection;
existingPreferences.BookReaderWritingStyle = preferencesDto.BookReaderWritingStyle;
existingPreferences.BookThemeName = preferencesDto.BookReaderThemeName;
existingPreferences.BookReaderLayoutMode = preferencesDto.BookReaderLayoutMode;
existingPreferences.BookReaderImmersiveMode = preferencesDto.BookReaderImmersiveMode;

View File

@ -93,6 +93,12 @@ public class UserPreferencesDto
[Required]
public ReadingDirection BookReaderReadingDirection { get; set; }
/// <summary>
/// Book Reader Option: What writing style should be used, horizontal or vertical.
/// </summary>
[Required]
public WritingStyle BookReaderWritingStyle { get; set; }
/// <summary>
/// UI Site Global Setting: The UI theme the user should use.
/// </summary>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Data.Migrations
{
public partial class BookWritingStylePref : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "BookReaderWritingStyle",
table: "AppUserPreferences",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "BookReaderWritingStyle",
table: "AppUserPreferences");
}
}
}

View File

@ -221,6 +221,9 @@ namespace API.Data.Migrations
b.Property<int>("BookReaderReadingDirection")
.HasColumnType("INTEGER");
b.Property<int>("BookReaderWritingStyle")
.HasColumnType("INTEGER");
b.Property<bool>("BookReaderTapToPaginate")
.HasColumnType("INTEGER");

View File

@ -76,6 +76,10 @@ public class AppUserPreferences
/// </summary>
public ReadingDirection BookReaderReadingDirection { get; set; } = ReadingDirection.LeftToRight;
/// <summary>
/// Book Reader Option: Defines the writing styles vertical/horizontal
/// </summary>
public WritingStyle BookReaderWritingStyle { get; set; } = WritingStyle.Horizontal;
/// <summary>
/// UI Site Global Setting: The UI theme the user should use.
/// </summary>

View File

@ -0,0 +1,20 @@
using System.ComponentModel;
namespace API.Entities.Enums;
/// <summary>
/// Represents the writing styles for the book-reader
/// </summary>
public enum WritingStyle
{
/// <summary>
/// Vertical writing style for the book-reader
/// </summary>
[Description ("Vertical")]
Vertical = 0,
/// <summary>
/// Horizontal writing style for the book-reader
/// </summary>
[Description ("Horizontal")]
Horizontal = 1
}

View File

@ -7,6 +7,7 @@ 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";
export interface Preferences {
// Manga Reader
@ -28,6 +29,7 @@ export interface Preferences {
bookReaderFontFamily: string;
bookReaderTapToPaginate: boolean;
bookReaderReadingDirection: ReadingDirection;
bookReaderWritingStyle: WritingStyle;
bookReaderThemeName: string;
bookReaderLayoutMode: BookPageLayoutMode;
bookReaderImmersiveMode: boolean;
@ -41,6 +43,7 @@ export interface Preferences {
}
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}];

View File

@ -0,0 +1,7 @@
/*
* Mode the user is reading the book in. Not applicable with ReaderMode.Webtoon
*/
export enum WritingStyle{
Vertical = 0,
Horizontal = 1
}

View File

@ -24,22 +24,31 @@ export class ScrollService {
}
get scrollPosition() {
return (window.pageYOffset
|| document.documentElement.scrollTop
return (window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop || 0);
}
scrollTo(top: number, el: Element | Window = window) {
/*
* When in the scroll vertical position the scroll in the horizontal position is needed
*/
get scrollPositionX() {
return (window.pageXOffset
|| document.documentElement.scrollLeft
|| document.body.scrollLeft || 0);
}
scrollTo(top: number, el: Element | Window = window, behavior: 'auto' | 'smooth' = 'smooth') {
el.scroll({
top: top,
behavior: 'smooth'
behavior: behavior
});
}
scrollToX(left: number, el: Element | Window = window) {
scrollToX(left: number, el: Element | Window = window, behavior: 'auto' | 'smooth' = 'auto') {
el.scroll({
left: left,
behavior: 'auto'
behavior: behavior
});
}

View File

@ -1,4 +1,4 @@
<div class="container-flex {{darkMode ? 'dark-mode' : ''}} reader-container {{ColumnLayout}}" tabindex="0" #reader>
<div class="container-flex {{darkMode ? 'dark-mode' : ''}} reader-container {{ColumnLayout}} {{WritingStyle}}" tabindex="0" #reader>
<div class="fixed-top" #stickyTop>
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
@ -52,6 +52,7 @@
(styleUpdate)="updateReaderStyles($event)"
(clickToPaginateChanged)="showPaginationOverlay($event)"
(fullscreen)="toggleFullscreen()"
(bookReaderWritingStyle)="updateWritingStyle($event)"
(layoutModeUpdate)="updateLayoutMode($event)"
(readingDirection)="updateReadingDirection($event)"
(immersiveMode)="updateImmersiveMode($event)"
@ -72,26 +73,25 @@
</app-drawer>
</div>
<div #readingSection class="reading-section {{ColumnLayout}}" [ngClass]="{'immersive' : immersiveMode || !actionBarVisible}" [@isLoading]="isLoading ? true : false">
<div #readingSection class="reading-section {{ColumnLayout}} {{WritingStyle}}" [ngStyle]="{'width': PageWidthForPagination}" [ngClass]="{'immersive' : immersiveMode || !actionBarVisible}" [@isLoading]="isLoading ? true : false">
<ng-container *ngIf="clickToPaginate">
<div class="left {{clickOverlayClass('left')}} no-observe" [ngClass]="{'immersive' : immersiveMode}"
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div>
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe"
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe"
[ngClass]="{'immersive' : immersiveMode}"
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div>
</ng-container>
<div #bookContainer class="book-container {{WritingStyle}}" [ngClass]="{'immersive' : immersiveMode}">
<div #bookContainer class="book-container" [ngClass]="{'immersive' : immersiveMode}">
<div #readingHtml class="book-content {{ColumnLayout}}" [ngStyle]="{'max-height': ColumnHeight, 'column-width': ColumnWidth}"
[ngClass]="{'immersive': immersiveMode && actionBarVisible}"
[innerHtml]="page" *ngIf="page !== undefined" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)"></div>
<div #readingHtml class="book-content {{ColumnLayout}} {{WritingStyle}}" [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)" (click)="$event.stopPropagation();"
<div *ngIf="page !== undefined && (scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default)" (click)="$event.stopPropagation();"
[ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}">
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
</div>

View File

@ -87,6 +87,7 @@ $action-bar-height: 38px;
.fixed-top {
z-index: 1022;
direction: ltr;
}
.dark-mode .overlay {
@ -95,7 +96,7 @@ $action-bar-height: 38px;
.action-bar {
background-color: var(--br-actionbar-bg-color);
background-color: var(--br-actionbar-bg-color);
overflow: hidden;
box-shadow: 0 0 6px 0 rgb(0 0 0 / 70%);
max-height: $action-bar-height;
@ -109,13 +110,13 @@ $action-bar-height: 38px;
-webkit-box-orient: vertical;
overflow: hidden;
}
@media(max-width: 875px) {
.book-title {
display: none;
}
}
}
.book-title {
margin-top: 10px;
text-align: center;
@ -142,6 +143,10 @@ $action-bar-height: 38px;
&.column-layout-2 {
height: calc(var(--vh) * 100);
}
&.writing-style-vertical {
direction: rtl;
}
}
.reading-section {
@ -151,6 +156,7 @@ $action-bar-height: 38px;
padding-top: $action-bar-height;
padding-bottom: $action-bar-height;
position: relative;
direction: ltr;
//background-color: green !important;
@ -167,12 +173,18 @@ $action-bar-height: 38px;
//padding-top: 0px;
//padding-bottom: 0px;
}
&.writing-style-vertical {
writing-mode: vertical-rl;
height: 100%;
}
}
.book-container {
position: relative;
height: 100%;
//background-color: purple !important;
&.column-layout-1 {
@ -182,6 +194,12 @@ $action-bar-height: 38px;
&.column-layout-2 {
height: calc((var(--vh, 1vh) * 100) - $action-bar-height);
}
&.writing-style-vertical {
// Fixes an issue where chrome will cut of margins, doesn't seem to affect other browsers
overflow: auto;
}
}
.book-content {
@ -192,10 +210,22 @@ $action-bar-height: 38px;
&.column-layout-1 {
height: calc((var(--vh) * 100) - calc($action-bar-height)); // * 2
&.writing-style-vertical {
height: auto;
padding: 0 10px 0 0;
margin: 20px 0;
}
}
&.column-layout-2 {
height: calc((var(--vh) * 100) - calc($action-bar-height)); // * 2
&.writing-style-vertical {
height: auto;
padding: 0 10px 0 0;
margin: 20px 0;
}
}
// &.immersive {
@ -228,6 +258,8 @@ $action-bar-height: 38px;
position: fixed;
width: 100%;
bottom: 0px;
left: 0px;
writing-mode: horizontal-tb;
}
@ -242,7 +274,7 @@ $action-bar-height: 38px;
overflow-wrap: break-word;
}
}
.column-layout-2 {
@ -254,7 +286,7 @@ $action-bar-height: 38px;
overflow-wrap: break-word;
}
}
@ -293,7 +325,7 @@ $action-bar-height: 38px;
position: absolute;
right: 0px;
top: $action-bar-height;
width: 20%;
width: 20vw;
z-index: 3;
cursor: pointer;
background: transparent;
@ -330,7 +362,7 @@ $action-bar-height: 38px;
position: absolute;
left: 0px;
top: $action-bar-height;
width: 20%;
width: 20vw;
background: transparent;
border-color: transparent;
border: none !important;
@ -342,6 +374,7 @@ $action-bar-height: 38px;
&.immersive {
top: 0px;
}
}
@ -360,7 +393,7 @@ $action-bar-height: 38px;
.btn {
&.btn-secondary {
color: var(--br-actionbar-button-text-color);
color: var(--br-actionbar-button-text-color);
border-color: transparent;
background-color: unset;
@ -368,7 +401,7 @@ $action-bar-height: 38px;
border-color: var(--br-actionbar-button-hover-border-color);
}
}
&.btn-outline-secondary {
border-color: transparent;
background-color: unset;
@ -380,7 +413,7 @@ $action-bar-height: 38px;
span {
background-color: unset;
color: var(--br-actionbar-button-text-color);
color: var(--br-actionbar-button-text-color);
}
i {

View File

@ -17,6 +17,7 @@ import { animate, state, style, transition, trigger } from '@angular/animations'
import { Stack } from 'src/app/shared/data-structures/stack';
import { MemberService } from 'src/app/_services/member.service';
import { ReadingDirection } from 'src/app/_models/preferences/reading-direction';
import {WritingStyle} from "../../../_models/preferences/writing-style";
import { MangaFormat } from 'src/app/_models/manga-format';
import { LibraryService } from 'src/app/_services/library.service';
import { LibraryType } from 'src/app/_models/library';
@ -42,11 +43,12 @@ interface HistoryPoint {
/**
* XPath to scroll to
*/
scrollPart: string;
scrollPart: string;
}
const TOP_OFFSET = -50 * 1.5; // px the sticky header takes up // TODO: Do I need this or can I change it with new fixed top height
const COLUMN_GAP = 20; // px
/**
* Styles that should be applied on the top level book-content tag
*/
@ -201,6 +203,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* Used for showing/hiding bottom action bar. Calculates if there is enough scroll to show it.
* Will hide if all content in book is absolute positioned
*/
horizontalScrollbarNeeded = false;
scrollbarNeeded = false;
readingDirection: ReadingDirection = ReadingDirection.LeftToRight;
clickToPaginate = false;
@ -250,6 +254,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
*/
pagingDirection: PAGING_DIRECTION = PAGING_DIRECTION.FORWARD;
writingStyle: WritingStyle = WritingStyle.Horizontal;
private readonly onDestroy = new Subject<void>();
@ViewChild('bookContainer', {static: false}) bookContainerElemRef!: ElementRef<HTMLDivElement>;
@ -346,26 +352,39 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
get ColumnWidth() {
const base = this.writingStyle === WritingStyle.Vertical ? this.windowHeight : this.windowWidth;
switch (this.layoutMode) {
case BookPageLayoutMode.Default:
return 'unset';
case BookPageLayoutMode.Column1:
return (this.windowWidth /2) + 'px';
return (base / 2) + 'px';
case BookPageLayoutMode.Column2:
return ((this.windowWidth / 4)) + 'px';
return (base / 4) + 'px';
default:
return 'unset';
}
}
get ColumnHeight() {
if (this.layoutMode !== BookPageLayoutMode.Default) {
if (this.layoutMode !== BookPageLayoutMode.Default || this.writingStyle === WritingStyle.Vertical) {
// Take the height after page loads, subtract the top/bottom bar
const height = this.windowHeight - (this.topOffset * 2);
this.document.documentElement.style.setProperty('--book-reader-content-max-height', `${height}px`);
return height + 'px';
}
return 'unset';
}
get VerticalBookContentWidth() {
if (this.layoutMode !== BookPageLayoutMode.Default && this.writingStyle !== WritingStyle.Horizontal ) {
const width = this.getVerticalPageWidth()
this.document.documentElement.style.setProperty('--book-reader-content-max-width', `${width}px`);
return width + 'px';
}
return '';
}
get ColumnLayout() {
switch (this.layoutMode) {
case BookPageLayoutMode.Default:
@ -377,6 +396,22 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
}
get WritingStyle() {
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) {
return 'unset';
}
return '100%'
}
get PageHeightForPagination() {
if (this.layoutMode === BookPageLayoutMode.Default) {
// if the book content is less than the height of the container, override and return height of container for pagination area
@ -531,7 +566,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.router.navigate(this.readerService.getNavigationArray(info.libraryId, info.seriesId, this.chapterId, info.seriesFormat), {queryParams: params});
return;
}
this.bookTitle = info.bookTitle;
this.cdRef.markForCheck();
@ -599,7 +634,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
onResize(){
// Update the window Height
this.updateWidthAndHeightCalcs();
const resumeElement = this.getFirstVisibleElementXPath();
if (this.layoutMode !== BookPageLayoutMode.Default && resumeElement !== null && resumeElement !== undefined) {
this.scrollTo(resumeElement); // This works pretty well, but not perfect
@ -625,6 +659,17 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
}
onWheel(event: WheelEvent) {
// This allows the user to scroll the page horizontally without holding shift
if (this.layoutMode !== BookPageLayoutMode.Default || this.writingStyle !== WritingStyle.Vertical) {
return;
}
if (event.deltaY !== 0) {
event.preventDefault()
this.scrollService.scrollToX( event.deltaY + this.reader.nativeElement.scrollLeft, this.reader.nativeElement);
}
}
closeReader() {
this.readerService.closeReader(this.readingListMode, this.readingListId);
}
@ -674,7 +719,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.isLoading = false;
this.cdRef.markForCheck();
return;
}
}
if (this.prevChapterId === CHAPTER_ID_NOT_FETCHED || this.prevChapterId === this.chapterId && !this.prevChapterPrefetched) {
this.readerService.getPrevChapter(this.seriesId, this.volumeId, this.chapterId, this.readingListId).pipe(take(1)).subscribe(chapterId => {
@ -790,7 +835,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.bookService.getBookPage(this.chapterId, this.pageNum).pipe(take(1)).subscribe(content => {
this.page = this.domSanitizer.bypassSecurityTrustHtml(content); // PERF: Potential optimization to prefetch next/prev page and store in localStorage
this.cdRef.markForCheck();
setTimeout(() => {
this.addLinkClickHandlers();
this.updateReaderStyles(this.pageStyles);
@ -817,19 +862,33 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
*/
updateImagesWithHeight() {
const images = this.readingSectionElemRef?.nativeElement.querySelectorAll('img') || [];
let maxHeight: number | undefined;
if (this.layoutMode !== BookPageLayoutMode.Default) {
const height = (parseInt(this.ColumnHeight.replace('px', ''), 10) - (this.topOffset * 2)) + 'px';
Array.from(images).forEach(img => {
this.renderer.setStyle(img, 'max-height', height);
});
if (this.layoutMode !== BookPageLayoutMode.Default && this.writingStyle !== WritingStyle.Vertical) {
maxHeight = (parseInt(this.ColumnHeight.replace('px', ''), 10) - (this.topOffset * 2));
} else if (this.layoutMode !== BookPageLayoutMode.Column2 && this.writingStyle === WritingStyle.Vertical) {
maxHeight = this.getPageHeight() - COLUMN_GAP;
} else if (this.layoutMode === BookPageLayoutMode.Column2 && this.writingStyle === WritingStyle.Vertical) {
maxHeight = this.getPageHeight() / 2 - COLUMN_GAP;
} else {
Array.from(images).forEach(img => {
this.renderer.removeStyle(img, 'max-height');
});
maxHeight = undefined;
}
Array.from(images).forEach(img => {
if (maxHeight === undefined) {
this.renderer.removeStyle(img, 'max-height');
} else if (this.writingStyle === WritingStyle.Horizontal) {
this.renderer.setStyle(img, 'max-height', `${maxHeight}px`);
} else {
const aspectRatio = img.width / img.height;
const pageWidth = this.getVerticalPageWidth()
const maxImgHeight = Math.min(maxHeight, pageWidth / aspectRatio);
this.renderer.setStyle(img, 'max-height', `${maxImgHeight}px`);
}
});
}
setupPage(part?: string | undefined, scrollTop?: number | undefined) {
this.isLoading = false;
this.cdRef.markForCheck();
@ -846,11 +905,20 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.scrollTo(part);
} else if (scrollTop !== undefined && scrollTop !== 0) {
this.scrollService.scrollTo(scrollTop, this.reader.nativeElement);
} else if ((this.writingStyle === WritingStyle.Vertical) && (this.layoutMode === BookPageLayoutMode.Default)) {
setTimeout(()=> this.scrollService.scrollToX(this.bookContentElemRef.nativeElement.clientWidth, this.reader.nativeElement));
} else {
if (this.layoutMode === BookPageLayoutMode.Default) {
this.scrollService.scrollTo(0, this.reader.nativeElement);
} else {
} else if (this.writingStyle === WritingStyle.Vertical) {
if (this.pagingDirection === PAGING_DIRECTION.BACKWARDS) {
setTimeout(() => this.scrollService.scrollTo(this.bookContentElemRef.nativeElement.scrollHeight, this.bookContentElemRef.nativeElement));
} else {
setTimeout(() => this.scrollService.scrollTo(0, this.bookContentElemRef.nativeElement));
}
}
else {
this.reader.nativeElement.children
// We need to check if we are paging back, because we need to adjust the scroll
if (this.pagingDirection === PAGING_DIRECTION.BACKWARDS) {
@ -858,7 +926,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} else {
setTimeout(() => this.scrollService.scrollToX(0, this.bookContentElemRef.nativeElement));
}
}
}
}
// we need to click the document before arrow keys will scroll down.
@ -909,7 +977,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
/**
* Given a direction, calls the next or prev page method
* @param direction Direction to move
* @param direction Direction to move
*/
movePage(direction: PAGING_DIRECTION) {
if (direction === PAGING_DIRECTION.BACKWARDS) {
@ -931,7 +999,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (currentVirtualPage > 1) {
// -2 apparently goes back 1 virtual page...
this.scrollService.scrollToX((currentVirtualPage - 2) * pageWidth, this.bookContentElemRef.nativeElement);
if (this.writingStyle === WritingStyle.Vertical) {
this.scrollService.scrollTo((currentVirtualPage - 2) * pageWidth, this.bookContentElemRef.nativeElement, "auto");
} else {
this.scrollService.scrollToX((currentVirtualPage - 2) * pageWidth, this.bookContentElemRef.nativeElement);
}
this.handleScrollEvent();
return;
}
@ -956,14 +1028,17 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
this.pagingDirection = PAGING_DIRECTION.FORWARD;
// We need to handle virtual paging before we increment the actual page
if (this.layoutMode !== BookPageLayoutMode.Default) {
const [currentVirtualPage, totalVirtualPages, pageWidth] = this.getVirtualPage();
if (currentVirtualPage < totalVirtualPages) {
// +0 apparently goes forward 1 virtual page...
this.scrollService.scrollToX((currentVirtualPage) * pageWidth, this.bookContentElemRef.nativeElement);
if (this.writingStyle === WritingStyle.Vertical) {
this.scrollService.scrollTo( (currentVirtualPage) * pageWidth, this.bookContentElemRef.nativeElement, 'auto');
} else {
this.scrollService.scrollToX((currentVirtualPage) * pageWidth, this.bookContentElemRef.nativeElement);
}
this.handleScrollEvent();
return;
}
@ -985,47 +1060,75 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
/**
*
*
* @returns Total Page width (excluding margin)
*/
getPageWidth() {
if (this.readingSectionElemRef == null) return 0;
const margin = (this.readingSectionElemRef.nativeElement.clientWidth * (parseInt(this.pageStyles['margin-left'], 10) / 100)) * 2;
const columnGap = 20;
return this.readingSectionElemRef.nativeElement.clientWidth - margin + columnGap;
return this.readingSectionElemRef.nativeElement.clientWidth - margin + COLUMN_GAP;
}
getPageHeight() {
if (this.readingSectionElemRef == null) return 0;
const height = (parseInt(this.ColumnHeight.replace('px', ''), 10));
return height - COLUMN_GAP;
}
getVerticalPageWidth() {
const margin = (window.innerWidth * (parseInt(this.pageStyles['margin-left'], 10) / 100)) * 2;
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
return windowWidth - margin;
}
/**
* currentVirtualPage starts at 1
* @returns
* @returns
*/
getVirtualPage() {
if (this.bookContentElemRef === undefined || this.readingSectionElemRef === undefined) return [1, 1, 0];
if (!this.bookContentElemRef || !this.readingSectionElemRef) return [1, 1, 0];
const scrollOffset = this.bookContentElemRef.nativeElement.scrollLeft;
const totalScroll = this.bookContentElemRef.nativeElement.scrollWidth;
const pageWidth = this.getPageWidth();
const delta = totalScroll - scrollOffset;
const totalVirtualPages = Math.max(1, Math.round((totalScroll) / pageWidth));
const [scrollOffset, totalScroll] = this.getScrollOffsetAndTotalScroll();
const pageSize = this.getPageSize();
const totalVirtualPages = Math.max(1, Math.round(totalScroll / pageSize));
const delta = scrollOffset - totalScroll;
let currentVirtualPage = 1;
// If first virtual page, i.e. totalScroll and delta are the same value
if (totalScroll - delta === 0) {
//If first virtual page, i.e. totalScroll and delta are the same value
if (totalScroll === delta) {
currentVirtualPage = 1;
// If second virtual page
} else if (totalScroll - delta === pageWidth) {
// If second virtual page
} else if (totalScroll - delta === pageSize) {
currentVirtualPage = 2;
// Otherwise do math to get correct page. i.e. scrollOffset + pageWidth (this accounts for first page offset)
// Otherwise do math to get correct page. i.e. scroll + pageHeight/pageWidth (this accounts for first page offset)
} else {
currentVirtualPage = Math.min(Math.max(1, Math.round((scrollOffset + pageWidth) / pageWidth)), totalVirtualPages);
}
currentVirtualPage = Math.min(Math.max(1, Math.round((scrollOffset + pageSize) / pageSize)), totalVirtualPages);
}
return [currentVirtualPage, totalVirtualPages, pageSize];
return [currentVirtualPage, totalVirtualPages, pageWidth];
}
private getScrollOffsetAndTotalScroll() {
const { nativeElement: bookContent } = this.bookContentElemRef;
const scrollOffset = this.writingStyle === WritingStyle.Vertical
? bookContent.scrollTop
: bookContent.scrollLeft;
const totalScroll = this.writingStyle === WritingStyle.Vertical
? bookContent.scrollHeight
: bookContent.scrollWidth;
return [scrollOffset, totalScroll];
}
private getPageSize() {
return this.writingStyle === WritingStyle.Vertical
? this.getPageHeight()
: this.getPageWidth();
}
getFirstVisibleElementXPath() {
let resumeElement: string | null = null;
if (this.bookContentElemRef === null) return null;
@ -1059,7 +1162,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// Before we apply styles, let's get an element on the screen so we can scroll to it after any shifts
const resumeElement: string | null | undefined = this.getFirstVisibleElementXPath();
// Needs to update the image size when reading mode is vertically
if (this.writingStyle === WritingStyle.Vertical) {
this.updateImagesWithHeight();
}
// Line Height must be placed on each element in the page
// Apply page level overrides
@ -1097,7 +1203,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
/**
* Applies styles and classes that control theme
* @param theme
* @param theme
*/
updateColorTheme(theme: BookTheme) {
// Remove all themes
@ -1121,6 +1227,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// Recalculate if bottom action bar is needed
this.scrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientHeight > this.reader?.nativeElement?.clientHeight;
this.horizontalScrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientWidth > this.reader?.nativeElement?.clientWidth;
this.cdRef.markForCheck();
}
@ -1148,7 +1255,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (element === null) return;
if (this.layoutMode === BookPageLayoutMode.Default) {
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);
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
setTimeout(() => this.scrollService.scrollTo(fromTopOffset, this.reader.nativeElement), 10);
@ -1210,7 +1322,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.isFullscreen = true;
this.cdRef.markForCheck();
// HACK: This is a bug with how browsers change the background color for fullscreen mode
this.renderer.setStyle(this.reader.nativeElement, 'background', this.themeService.getCssVariable('--bs-body-color'));
this.renderer.setStyle(this.reader.nativeElement, 'background', this.themeService.getCssVariable('--bs-body-color'));
if (!this.darkMode) {
this.renderer.setStyle(this.reader.nativeElement, 'background', 'white');
}
@ -1218,11 +1330,29 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
}
updateWritingStyle(writingStyle: WritingStyle) {
this.writingStyle = writingStyle;
if (this.layoutMode !== BookPageLayoutMode.Default) {
const lastSelector = this.lastSeenScrollPartPath;
setTimeout(() => {
this.scrollTo(lastSelector);
this.updateLayoutMode(this.layoutMode);
});
} else if (this.bookContentElemRef !== undefined) {
const resumeElement = this.getFirstVisibleElementXPath();
if (resumeElement) {
setTimeout(() => {
this.scrollTo(resumeElement);
});
}
}
this.cdRef.markForCheck();
}
updateLayoutMode(mode: BookPageLayoutMode) {
const layoutModeChanged = mode !== this.layoutMode;
this.layoutMode = mode;
this.cdRef.markForCheck();
// Remove any max-heights from column layout
this.updateImagesWithHeight();
@ -1233,10 +1363,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
setTimeout(() => {
this.scrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientHeight > this.reader?.nativeElement?.clientHeight;
this.horizontalScrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientWidth > this.reader?.nativeElement?.clientWidth;
this.cdRef.markForCheck();
});
// When I switch layout, I might need to resume the progress point.
// When I switch layout, I might need to resume the progress point.
if (mode === BookPageLayoutMode.Default && layoutModeChanged) {
const lastSelector = this.lastSeenScrollPartPath;
setTimeout(() => this.scrollTo(lastSelector));
@ -1263,7 +1394,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
setTimeout(() => {
if (renderer === undefined || elem === undefined) return;
if (this.immersiveMode) {
renderer.setStyle(elem, 'height', 'calc(var(--vh, 1vh) * 100)', RendererStyleFlags2.Important);
} else {
renderer.setStyle(elem, 'height', 'calc(var(--vh, 1vh) * 100 - ' + this.topOffset + 'px)', RendererStyleFlags2.Important);
}

View File

@ -70,6 +70,16 @@
<span class="phone-hidden">&nbsp;{{readingDirectionModel === ReadingDirection.LeftToRight ? 'Left to Right' : '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">Writing Style <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="writingStyleTooltip" role="button" tabindex="0" aria-describedby="writingStyle-help"></i></label>
<ng-template #writingStyleTooltip>Changes the direction of the text. Horizontal is left to right, vertical is top to bottom.</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 ? 'Horizontal' : '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 ? 'Horizontal' : 'Vertical' }}</span>
</button>
</div>
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
<label for="tap-pagination" class="form-label">Tap Pagination&nbsp;<i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="tapPaginationTooltip" role="button" tabindex="0" aria-describedby="tapPagination-help"></i></label>
<ng-template #tapPaginationTooltip>Click the edges of the screen to paginate</ng-template>
@ -116,10 +126,10 @@
<div class="btn-group d-flex justify-content-center" role="group" aria-label="Layout Mode">
<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">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">1 Column</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">2 Column</label>
</div>
@ -150,4 +160,4 @@
</ngb-panel>
</ngb-accordion>
</form>
</form>

View File

@ -5,6 +5,7 @@ import { Subject, take, takeUntil } from 'rxjs';
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';
@ -88,6 +89,10 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
* Outputs when reading direction is changed
*/
@Output() readingDirection: EventEmitter<ReadingDirection> = new EventEmitter();
/**
* Outputs when reading mode is changed
*/
@Output() bookReaderWritingStyle: EventEmitter<WritingStyle> = new EventEmitter();
/**
* Outputs when immersive mode is changed
*/
@ -106,6 +111,9 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
readingDirectionModel: ReadingDirection = ReadingDirection.LeftToRight;
writingStyleModel: WritingStyle = WritingStyle.Horizontal;
activeTheme: BookTheme | undefined;
isFullscreen: boolean = false;
@ -129,6 +137,10 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
return ReadingDirection;
}
get WritingStyle() {
return WritingStyle;
}
constructor(private bookService: BookService, private accountService: AccountService,
@ -160,7 +172,12 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
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, []));
@ -194,11 +211,13 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
this.settingsForm.addControl('bookReaderMargin', new FormControl(this.user.preferences.bookReaderMargin, []));
this.settingsForm.get('bookReaderMargin')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(value => {
this.pageStyles['margin-left'] = value + '%';
this.pageStyles['margin-right'] = 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(takeUntil(this.onDestroy)).subscribe((layoutMode: BookPageLayoutMode) => {
this.layoutModeUpdate.emit(layoutMode);
@ -218,6 +237,7 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
// 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);
@ -239,7 +259,7 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
resetSettings() {
if (this.user) {
this.setPageStyles(this.user.preferences.bookReaderFontFamily, this.user.preferences.bookReaderFontSize + '%', this.user.preferences.bookReaderMargin + '%', this.user.preferences.bookReaderLineSpacing + '%');
this.setPageStyles(this.user.preferences.bookReaderFontFamily, this.user.preferences.bookReaderFontSize + '%', this.user.preferences.bookReaderMargin + 'vw', this.user.preferences.bookReaderLineSpacing + '%');
} else {
this.setPageStyles();
}
@ -252,6 +272,7 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
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.cdRef.detectChanges();
this.styleUpdate.emit(this.pageStyles);
}
@ -265,9 +286,9 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
|| this.document.body.clientWidth;
let defaultMargin = '15%';
let defaultMargin = '15vw';
if (windowWidth <= mobileBreakpointMarginOverride) {
defaultMargin = '5%';
defaultMargin = '5vw';
}
this.pageStyles = {
'font-family': fontFamily || this.pageStyles['font-family'] || 'default',
@ -296,6 +317,17 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
this.readingDirection.emit(this.readingDirectionModel);
}
toggleWritingStyle() {
if (this.writingStyleModel === WritingStyle.Horizontal) {
this.writingStyleModel = WritingStyle.Vertical
} else {
this.writingStyleModel = WritingStyle.Horizontal
}
this.cdRef.markForCheck();
this.bookReaderWritingStyle.emit(this.writingStyleModel);
}
toggleFullscreen() {
this.isFullscreen = !this.isFullscreen;
this.cdRef.markForCheck();

View File

@ -47,7 +47,7 @@
<input type="checkbox" id="auto-close" role="switch" formControlName="blurUnreadSummaries" class="form-check-input" aria-describedby="settings-global-blurUnreadSummaries-help" [value]="true" aria-labelledby="auto-close-label">
<label class="form-check-label" for="auto-close">Blur Unread Summaries</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="blurUnreadSummariesTooltip" role="button" tabindex="0"></i>
</div>
<ng-template #blurUnreadSummariesTooltip>Blurs summary text on volumes or chapters that have no read progress (to avoid spoilers)</ng-template>
<span class="visually-hidden" id="settings-global-blurUnreadSummaries-help">Blurs summary text on volumes or chapters that have no read progress (to avoid spoilers)</span>
</div>
@ -59,7 +59,7 @@
<input type="checkbox" id="prompt-download" role="switch" formControlName="promptForDownloadSize" class="form-check-input" aria-describedby="settings-global-promptForDownloadSize-help" [value]="true" aria-labelledby="auto-close-label">
<label class="form-check-label" for="prompt-download">Prompt on Download</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="promptForDownloadSizeTooltip" role="button" tabindex="0"></i>
</div>
<ng-template #promptForDownloadSizeTooltip>Prompt when a download exceedes 100MB in size</ng-template>
<span class="visually-hidden" id="settings-global-promptForDownloadSize-help">Prompt when a download exceedes 100MB in size</span>
</div>
@ -142,11 +142,12 @@
<div class="col-md-6 col-sm-12 pe-2 mb-2">
<label for="settings-backgroundcolor-option" class="form-label">Background Color</label>
<input [value]="user.preferences.backgroundColor"
class="form-control"
(colorPickerChange)="handleBackgroundColorChange()"
[style.background]="user.preferences.backgroundColor"
[cpAlphaChannel]="'disabled'"
[(colorPicker)]="user.preferences.backgroundColor"/>
class="form-control"
id="settings-backgroundcolor-option"
(colorPickerChange)="handleBackgroundColorChange()"
[style.background]="user.preferences.backgroundColor"
[cpAlphaChannel]="'disabled'"
[(colorPicker)]="user.preferences.backgroundColor"/>
</div>
</div>
@ -251,6 +252,15 @@
</div>
<div class="row g-0">
<div class="col-md-6 col-sm-12 pe-2 mb-3">
<label for="settings-book-writing-style" class="form-label me-1">Writing Style</label><i class="fa fa-info-circle" aria-hidden="true" aria-describedby="settings-book-writing-style-help" placement="right" [ngbTooltip]="bookWritingStyleToolTip" role="button" tabindex="0"></i>
<ng-template #bookWritingStyleToolTip>Changes the direction of the text. Horizontal is left to right, vertical is top to bottom.</ng-template>
<span class="visually-hidden" id="settings-book-writing-style-help"><ng-container [ngTemplateOutlet]="bookWritingStyleToolTip"></ng-container></span>
<select class="form-select" aria-describedby="settings-book-writing-style-help" formControlName="bookReaderWritingStyle" id="settings-book-writing-style" >
<option *ngFor="let opt of bookWritingStyles" [value]="opt.value">{{opt.text | titlecase}}</option>
</select>
</div>
<div class="col-md-6 col-sm-12 pe-2 mb-3">
<label for="settings-book-layout-mode" class="form-label">Layout Mode</label>&nbsp;<i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="bookLayoutModeTooltip" role="button" tabindex="0"></i>
<ng-template #bookLayoutModeTooltip>How content should be laid out. Scroll is as the book packs it. 1 or 2 Column fits to the height of the device and fits 1 or 2 columns of text per page</ng-template>
@ -259,26 +269,28 @@
<option *ngFor="let opt of bookLayoutModes" [value]="opt.value">{{opt.text | titlecase}}</option>
</select>
</div>
</div>
<div class="row g-0">
<div class="col-md-6 col-sm-12 pe-2 mb-3">
<label for="settings-color-theme-option" class="form-label">Color Theme</label>&nbsp;<i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="bookColorThemeTooltip" role="button" tabindex="0"></i>
<ng-template #bookColorThemeTooltip>What color theme to apply to the book reader content and menuing</ng-template>
<span class="visually-hidden" id="settings-color-theme-option-help"><ng-container [ngTemplateOutlet]="bookColorThemeTooltip"></ng-container></span>
<select class="form-select" aria-describedby="settings-color-theme-option-help" formControlName="bookReaderThemeName" id="settings-color-theme-option">
<option *ngFor="let opt of bookColorThemes" [value]="opt.name">{{opt.name | titlecase}}</option>
</select>
</div>
<label for="settings-color-theme-option" class="form-label">Color Theme</label>&nbsp;<i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="bookColorThemeTooltip" role="button" tabindex="0"></i>
<ng-template #bookColorThemeTooltip>What color theme to apply to the book reader content and menuing</ng-template>
<span class="visually-hidden" id="settings-color-theme-option-help"><ng-container [ngTemplateOutlet]="bookColorThemeTooltip"></ng-container></span>
<select class="form-select" aria-describedby="settings-color-theme-option-help" formControlName="bookReaderThemeName" id="settings-color-theme-option">
<option *ngFor="let opt of bookColorThemes" [value]="opt.name">{{opt.name | titlecase}}</option>
</select>
</div>
</div>
<div class="row g-0">
<div class="col-md-4 col-sm-12 pe-2 mb-3">
<label for="fontsize" class="form-label range-label">Font Size</label>
<input type="range" class="form-range" id="fontsize"
<input type="range" class="form-range" id="fontsize"
min="50" max="300" step="10" formControlName="bookReaderFontSize">
<span class="range-text">{{settingsForm.get('bookReaderFontSize')?.value + '%'}}</span>
</div>
<div class="col-md-4 col-sm-12 pe-2 mb-3">
<div class="range-label">
@ -286,7 +298,7 @@
<ng-template #bookLineHeightOptionTooltip>How much spacing between the lines of the book</ng-template>
<span class="visually-hidden" id="settings-booklineheight-option-help">How much spacing between the lines of the book</span>
</div>
<input type="range" class="form-range" id="linespacing" min="100" max="200" step="10"
<input type="range" class="form-range" id="linespacing" min="100" max="200" step="10"
formControlName="bookReaderLineSpacing" aria-describedby="settings-booklineheight-option-help">
<span class="range-text">{{settingsForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
</div>

View File

@ -3,7 +3,17 @@ import { FormControl, FormGroup } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { take, takeUntil } from 'rxjs/operators';
import { Title } from '@angular/platform-browser';
import { readingDirections, scalingOptions, pageSplitOptions, readingModes, Preferences, bookLayoutModes, layoutModes, pageLayoutModes } from 'src/app/_models/preferences/preferences';
import {
readingDirections,
scalingOptions,
pageSplitOptions,
readingModes,
Preferences,
bookLayoutModes,
layoutModes,
pageLayoutModes,
bookWritingStyles
} from 'src/app/_models/preferences/preferences';
import { User } from 'src/app/_models/user';
import { AccountService } from 'src/app/_services/account.service';
import { ActivatedRoute, Router } from '@angular/router';
@ -45,6 +55,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
bookLayoutModes = bookLayoutModes;
bookColorThemes = bookColorThemes;
pageLayoutModes = pageLayoutModes;
bookWritingStyles = bookWritingStyles;
settingsForm: FormGroup = new FormGroup({});
user: User | undefined = undefined;
@ -134,6 +145,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
this.settingsForm.addControl('bookReaderLineSpacing', new FormControl(this.user.preferences.bookReaderLineSpacing, []));
this.settingsForm.addControl('bookReaderMargin', new FormControl(this.user.preferences.bookReaderMargin, []));
this.settingsForm.addControl('bookReaderReadingDirection', new FormControl(this.user.preferences.bookReaderReadingDirection, []));
this.settingsForm.addControl('bookReaderWritingStyle', new FormControl(this.user.preferences.bookReaderWritingStyle, []))
this.settingsForm.addControl('bookReaderTapToPaginate', new FormControl(!!this.user.preferences.bookReaderTapToPaginate, []));
this.settingsForm.addControl('bookReaderLayoutMode', new FormControl(this.user.preferences.bookReaderLayoutMode || BookPageLayoutMode.Default, []));
this.settingsForm.addControl('bookReaderThemeName', new FormControl(this.user?.preferences.bookReaderThemeName || bookColorThemes[0].name, []));
@ -179,6 +191,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
this.settingsForm.get('bookReaderMargin')?.setValue(this.user.preferences.bookReaderMargin);
this.settingsForm.get('bookReaderTapToPaginate')?.setValue(this.user.preferences.bookReaderTapToPaginate);
this.settingsForm.get('bookReaderReadingDirection')?.setValue(this.user.preferences.bookReaderReadingDirection);
this.settingsForm.get('bookReaderWritingStyle')?.setValue(this.user.preferences.bookReaderWritingStyle);
this.settingsForm.get('bookReaderLayoutMode')?.setValue(this.user.preferences.bookReaderLayoutMode);
this.settingsForm.get('bookReaderThemeName')?.setValue(this.user.preferences.bookReaderThemeName);
this.settingsForm.get('theme')?.setValue(this.user.preferences.theme);
@ -211,6 +224,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
bookReaderMargin: modelSettings.bookReaderMargin,
bookReaderTapToPaginate: modelSettings.bookReaderTapToPaginate,
bookReaderReadingDirection: parseInt(modelSettings.bookReaderReadingDirection, 10),
bookReaderWritingStyle: parseInt(modelSettings.bookReaderWritingStyle, 10),
bookReaderLayoutMode: parseInt(modelSettings.bookReaderLayoutMode, 10),
bookReaderThemeName: modelSettings.bookReaderThemeName,
theme: modelSettings.theme,