mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Reworks how the image sizes in the book reader gets handled (#1872)
* Fixes bus in vertical writing mode. * Reworks how the images gets resized. They should be more adaptive and pages with single images will get centered. * Adds back an unintended change * Revert "Fixes bus in vertical writing mode." This reverts commit a1cf9675def45fd626a149d90f573104b5d280cd. * Added debounce and timeout to the updateImageSize to prevent it from getting run before the page loads * Fixes semicolons missing semicolons * Improves the detection in checkSingleImagePage.
This commit is contained in:
parent
e5e2d02e04
commit
1f8b591ed5
@ -212,7 +212,6 @@ $action-bar-height: 38px;
|
||||
height: calc((var(--vh) * 100) - calc($action-bar-height)); // * 2
|
||||
|
||||
&.writing-style-vertical {
|
||||
height: auto;
|
||||
padding: 0 10px 0 0;
|
||||
margin: 20px 0;
|
||||
}
|
||||
@ -222,12 +221,15 @@ $action-bar-height: 38px;
|
||||
height: calc((var(--vh) * 100) - calc($action-bar-height)); // * 2
|
||||
|
||||
&.writing-style-vertical {
|
||||
height: auto;
|
||||
padding: 0 10px 0 0;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.writing-style-vertical {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
// &.immersive {
|
||||
// // Note: I removed this for bug: https://github.com/Kareadita/Kavita/issues/1726
|
||||
// //height: calc((var(--vh, 1vh) * 100) - $action-bar-height);
|
||||
@ -301,11 +303,18 @@ $action-bar-height: 38px;
|
||||
::ng-deep .kavita-scale-width-container {
|
||||
width: auto;
|
||||
max-height: calc(var(--book-reader-content-max-height) - ($action-bar-height)) !important;
|
||||
max-width: calc(var(--book-reader-content-max-width)) !important;
|
||||
position: var(--book-reader-content-position) !important;
|
||||
top: var(--book-reader-content-top) !important;
|
||||
left: var(--book-reader-content-left) !important;
|
||||
transform: var(--book-reader-content-transform) !important;
|
||||
|
||||
}
|
||||
|
||||
// This is applied to images in the backend
|
||||
::ng-deep .kavita-scale-width {
|
||||
max-width: 100%;
|
||||
max-height: calc(var(--book-reader-content-max-height) - ($action-bar-height)) !important;
|
||||
max-width: calc(var(--book-reader-content-max-width)) !important;
|
||||
object-fit: contain;
|
||||
object-position: top center;
|
||||
break-inside: avoid;
|
||||
|
@ -122,7 +122,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
* TODO: See if continuousChaptersStack can be moved into reader service so we can reduce code duplication between readers (and also use ChapterInfo with it instead)
|
||||
*/
|
||||
continuousChaptersStack: Stack<number> = new Stack();
|
||||
|
||||
/*
|
||||
* The current page only contains an image. This is used to determine if we should show the image in the center of the screen.
|
||||
*/
|
||||
isSingleImagePage = false;
|
||||
/**
|
||||
* Belongs to the drawer component
|
||||
*/
|
||||
@ -153,6 +156,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
clickToPaginateVisualOverlay = false;
|
||||
clickToPaginateVisualOverlayTimeout: any = undefined; // For animation
|
||||
clickToPaginateVisualOverlayTimeout2: any = undefined; // For kicking off animation, giving enough time to render html
|
||||
updateImageSizeTimeout: any = undefined;
|
||||
/**
|
||||
* This is the html we get from the server
|
||||
*/
|
||||
@ -372,17 +376,14 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
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 '';
|
||||
@ -592,8 +593,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.libraryType = type;
|
||||
});
|
||||
|
||||
this.updateImagesWithHeight();
|
||||
|
||||
this.updateImageSizes();
|
||||
|
||||
if (this.pageNum >= this.maxPages) {
|
||||
this.pageNum = this.maxPages - 1;
|
||||
@ -637,6 +637,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
onResize(){
|
||||
// Update the window Height
|
||||
this.updateWidthAndHeightCalcs();
|
||||
this.updateImageSizes();
|
||||
const resumeElement = this.getFirstVisibleElementXPath();
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default && resumeElement !== null && resumeElement !== undefined) {
|
||||
this.scrollTo(resumeElement); // This works pretty well, but not perfect
|
||||
@ -836,7 +837,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.bookService.getBookPage(this.chapterId, this.pageNum).pipe(take(1)).subscribe(content => {
|
||||
this.isSingleImagePage = this.checkSingleImagePage(content) // This needs be performed before we set this.page to avoid image jumping
|
||||
this.updateSingleImagePageStyles()
|
||||
this.page = this.domSanitizer.bypassSecurityTrustHtml(content); // PERF: Potential optimization to prefetch next/prev page and store in localStorage
|
||||
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
setTimeout(() => {
|
||||
@ -854,40 +859,76 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
.map(img => new Promise(resolve => { img.onload = img.onerror = resolve; })))
|
||||
.then(() => {
|
||||
this.setupPage(part, scrollTop);
|
||||
this.updateImagesWithHeight();
|
||||
this.updateImageSizes();
|
||||
});
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a max-height inline css property on each image in the page if the layout mode is column-based, else it removes the property
|
||||
* Updates the image properties to fit the current layout mode and screen size
|
||||
*/
|
||||
updateImagesWithHeight() {
|
||||
const images = this.readingSectionElemRef?.nativeElement.querySelectorAll('img') || [];
|
||||
let maxHeight: number | undefined;
|
||||
updateImageSizes() {
|
||||
const isVerticalWritingStyle = this.writingStyle === WritingStyle.Vertical;
|
||||
const height = this.windowHeight - (this.topOffset * 2);
|
||||
let maxHeight = 'unset';
|
||||
let maxWidth = '';
|
||||
switch (this.layoutMode) {
|
||||
case BookPageLayoutMode.Default:
|
||||
if (isVerticalWritingStyle) {
|
||||
maxHeight = `${height}px`;
|
||||
} else {
|
||||
maxWidth = `${this.getVerticalPageWidth()}px`;
|
||||
}
|
||||
break
|
||||
|
||||
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;
|
||||
case BookPageLayoutMode.Column1:
|
||||
maxHeight = `${height}px`;
|
||||
maxWidth = `${this.getVerticalPageWidth()}px`;
|
||||
break
|
||||
|
||||
case BookPageLayoutMode.Column2:
|
||||
maxWidth = `${this.getVerticalPageWidth()}px`;
|
||||
if (isVerticalWritingStyle && !this.isSingleImagePage) {
|
||||
maxHeight = `${height / 2}px`;
|
||||
} else {
|
||||
maxHeight = undefined;
|
||||
maxHeight = `${height}px`;
|
||||
}
|
||||
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`);
|
||||
break
|
||||
}
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-max-height', maxHeight);
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-max-width', maxWidth);
|
||||
|
||||
}
|
||||
|
||||
updateSingleImagePageStyles() {
|
||||
if (this.isSingleImagePage && this.layoutMode !== BookPageLayoutMode.Default) {
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-position', 'absolute');
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-top', '50%');
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-left', '50%');
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-transform', 'translate(-50%, -50%)');
|
||||
} 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`);
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-position', '');
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-top', '');
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-left', '');
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-transform', '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkSingleImagePage(content: string) {
|
||||
// Exclude the style element from the HTML content as it messes up innerText
|
||||
const htmlContent = content.replace(/<style>.*<\/style>/s, '');
|
||||
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlContent, 'text/html');
|
||||
const html = doc.querySelector('html');
|
||||
|
||||
if (html?.innerText.trim() !== '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const images = doc.querySelectorAll('img, svg');
|
||||
return images.length === 1;
|
||||
|
||||
}
|
||||
|
||||
@ -1166,9 +1207,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
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();
|
||||
}
|
||||
this.updateImageSizes();
|
||||
|
||||
// Line Height must be placed on each element in the page
|
||||
|
||||
// Apply page level overrides
|
||||
@ -1335,11 +1375,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
updateWritingStyle(writingStyle: WritingStyle) {
|
||||
this.writingStyle = writingStyle;
|
||||
setTimeout(() => this.updateImageSizes());
|
||||
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();
|
||||
@ -1356,8 +1396,13 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
const layoutModeChanged = mode !== this.layoutMode;
|
||||
this.layoutMode = mode;
|
||||
this.cdRef.markForCheck();
|
||||
// Remove any max-heights from column layout
|
||||
this.updateImagesWithHeight();
|
||||
|
||||
this.clearTimeout(this.updateImageSizeTimeout);
|
||||
this.updateImageSizeTimeout = setTimeout( () => {
|
||||
this.updateImageSizes()
|
||||
}, 200);
|
||||
|
||||
this.updateSingleImagePageStyles()
|
||||
|
||||
// Calulate if bottom actionbar is needed. On a timeout to get accurate heights
|
||||
if (this.bookContentElemRef == null) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user