Manga Reader Double Layout bugfixes (#1341)

* Enforce some max heights on series detail page

* Added some icon to layout mode to help the user understand how they work (Robbie needs to css it)

* Adding split-double icon

* Fixing reverse

* Added lots of debug code, refactored documentation, and added some history for wide images

* More prefetching code for wide images

* Fixed the issue where sometimes paging backwards would skip an image

* Fixed up a bug where occasionally on double (manga) paging backwards could skip a page.

Fixed a bug on double where last page could get duplicated.

* Don't update pageDimensionHistory since we don't need it

* Forgot some changes

Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
Joseph Milazzo 2022-06-29 19:48:29 -05:00 committed by GitHub
parent f72459a728
commit dacf15f024
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 188 additions and 37 deletions

View File

@ -1,6 +1,6 @@
<div class="list-item-container d-flex flex-row g-0 mb-2 p-2">
<div class="pe-2">
<app-image [imageUrl]="imageUrl" [height]="imageHeight" [width]="imageWidth"></app-image>
<app-image [imageUrl]="imageUrl" [height]="imageHeight" maxHeight="200px" [width]="imageWidth"></app-image>
<div class="not-read-badge" *ngIf="pagesRead === 0 && totalPages > 0"></div>
<span class="download" *ngIf="download$ | async as download">
<app-circular-loader [currentValue]="download.progress"></app-circular-loader>

View File

@ -13,6 +13,9 @@
</div>
</div>
{{readerService.imageUrlToPageNum(this.canvasImage.src)}}
{{readerService.imageUrlToPageNum(this.canvasImage2.src)}}
<div style="margin-left: auto; padding-right: 3%;">
<button class="btn btn-icon btn-small" title="Shortcuts" (click)="openShortcutModal()">
<i class="fa-regular fa-rectangle-list" aria-hidden="true"></i>
@ -63,13 +66,12 @@
<div class="image-container {{getFittingOptionClass()}}" [ngClass]="{'d-none': renderWithCanvas, 'center-double': ShouldRenderDoublePage,
'fit-to-width-double-offset' : FittingOption === FITTING_OPTION.WIDTH && ShouldRenderDoublePage,
'fit-to-height-double-offset': FittingOption === FITTING_OPTION.HEIGHT && ShouldRenderDoublePage,
'original-double-offset' : FittingOption === FITTING_OPTION.ORIGINAL && ShouldRenderDoublePage,
'reverse': ShouldRenderReverseDouble}">
'original-double-offset' : FittingOption === FITTING_OPTION.ORIGINAL && ShouldRenderDoublePage}">
<img #image [src]="canvasImage.src" id="image-1"
class="{{getFittingOptionClass()}} {{readerMode === ReaderMode.LeftRight || readerMode === ReaderMode.UpDown ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}}">
<ng-container *ngIf="(ShouldRenderDoublePage || ShouldRenderReverseDouble) && (this.pageNum <= maxPages - 1 && this.pageNum > 0)">
<img [src]="canvasImage2.src" id="image-2" class="image-2 {{getFittingOptionClass()}} {{readerMode === ReaderMode.LeftRight || readerMode === ReaderMode.UpDown ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}} {{ShouldRenderReverseDouble ? 'reverse' : ''}}">
<ng-container *ngIf="(this.canvasImage2.src !== '') && (readerService.imageUrlToPageNum(canvasImage2.src) <= maxPages - 1 && !isCoverImage())">
<img [src]="canvasImage2.src" id="image-2" class="image-2 {{getFittingOptionClass()}} {{readerMode === ReaderMode.LeftRight || readerMode === ReaderMode.UpDown ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}}"> <!-- {{ShouldRenderReverseDouble ? 'reverse' : ''}} -->
</ng-container>
</div>
@ -162,7 +164,42 @@
<div class="row mb-2">
<div class="col-md-6 col-sm-12">
<label for="layout-mode" class="form-label">Layout Mode</label>
<label for="layout-mode" class="form-label">Layout Mode</label>&nbsp;
<ng-container [ngSwitch]="layoutMode">
<ng-container *ngSwitchCase="LayoutMode.Single">
<div class="split-double">
<span class="fa-stack fa-1x">
<i class="fa-regular fa-square-full fa-stack-2x"></i>
<i class="fa fa-image fa-stack-1x"></i>
</span>
</div>
</ng-container>
<ng-container *ngSwitchCase="LayoutMode.Double">
<div class="split-double">
<span class="fa-stack fa-1x">
<i class="fa-regular fa-square-full fa-stack-2x"></i>
<i class="fab fa-1 fa-stack-1x"></i>
</span>
<span class="fa-stack fa right">
<i class="fa-regular fa-square-full fa-stack-2x"></i>
<i class="fab fa-2 fa-stack-1x"></i>
</span>
</div>
</ng-container>
<ng-container *ngSwitchCase="LayoutMode.DoubleReversed">
<div class="split-double">
<span class="fa-stack fa-1x">
<i class="fa-regular fa-square-full fa-stack-2x"></i>
<i class="fab fa-2 fa-stack-1x"></i>
</span>
<span class="fa-stack fa right">
<i class="fa-regular fa-square-full fa-stack-2x"></i>
<i class="fab fa-1 fa-stack-1x"></i>
</span>
</div>
</ng-container>
<!-- <div class="{{LayoutModeIconClass}}"></div> -->
</ng-container>
<select class="form-control" id="page-fitting" formControlName="layoutMode">
<option [value]="opt.value" *ngFor="let opt of layoutModes">{{opt.text}}</option>
</select>

View File

@ -57,9 +57,14 @@ img {
}
&.reverse {
flex-direction: row-reverse;
overflow: unset;
justify-content: flex-end;
display: flex;
align-content: center;
justify-content: center;
img {
margin: unset;
}
}
#image-2 {
@ -172,6 +177,7 @@ img {
overflow: hidden;
border: 2px solid #ccc;
vertical-align: sub;
display: inline-block;
&::before {
margin-left: 30%;
@ -197,6 +203,19 @@ img {
.none {
background-color: rgba(255, 255, 255, 0.5);
}
// For layout only
}
.split-double {
height: 20px;
display: inline-block;
font-size: .7em;
.right {
left: -7px;
}
}
::ng-deep {

View File

@ -38,6 +38,7 @@ const OVERLAY_AUTO_CLOSE_TIME = 3000;
const CLICK_OVERLAY_TIMEOUT = 3000;
@Component({
selector: 'app-manga-reader',
templateUrl: './manga-reader.component.html',
@ -66,6 +67,14 @@ const CLICK_OVERLAY_TIMEOUT = 3000;
]
})
export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('reader') reader!: ElementRef;
@ViewChild('readingArea') readingArea!: ElementRef;
@ViewChild('content') canvas: ElementRef | undefined;
@ViewChild('image') image!: ElementRef;
libraryId!: number;
seriesId!: number;
volumeId!: number;
@ -123,33 +132,41 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
*/
isLoose = false;
@ViewChild('reader') reader!: ElementRef;
@ViewChild('readingArea') readingArea!: ElementRef;
@ViewChild('content') canvas: ElementRef | undefined;
@ViewChild('image') image!: ElementRef;
private ctx!: CanvasRenderingContext2D;
/**
* Used to render a page on the canvas or in the image tag. This Image element is prefetched by the cachedImages buffer
* Used to render a page on the canvas or in the image tag. This Image element is prefetched by the cachedImages buffer.
* @remarks Used for rendering to screen.
*/
canvasImage = new Image();
/**
* Used solely for LayoutMode.Double rendering. Will always hold the next image in buffer.
* Used solely for LayoutMode.Double rendering.
* @remarks Used for rendering to screen.
*/
canvasImage2 = new Image();
/**
* Used solely for LayoutMode.Double rendering. Will always hold the previous image in buffer.
* Used solely for LayoutMode.Double rendering. Will always hold the previous image to canvasImage
* @see canvasImage
*/
canvasImagePrev = new Image();
/**
* Used solely for LayoutMode.Double rendering. Will always hold the next image in buffer.
* Used solely for LayoutMode.Double rendering. Will always hold the next image to canvasImage
* @see canvasImage
*/
canvasImageNext = new Image();
/**
* Used solely for LayoutMode.DoubleReverse rendering. Will always hold the image after next in buffer.
* Responsible to hold current page + 2. Used to know if we should render
* @remarks Used solely for LayoutMode.DoubleReverse rendering.
*/
canvasImageNextDouble = new Image();
canvasImageAheadBy2 = new Image();
/**
* Dictates if we use render with canvas or with image. This is only for Splitting.
* Responsible to hold current page -2 2. Used to know if we should render
* @remarks Used solely for LayoutMode.DoubleReverse rendering.
*/
canvasImageBehindBy2 = new Image();
/**
* Dictates if we use render with canvas or with image.
* @remarks This is only for Splitting.
*/
renderWithCanvas: boolean = false;
@ -296,25 +313,32 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return Math.max(Math.min(this.pageNum, this.maxPages - 1), 0);
}
/**
* Determines if we should render a double page.
* The general gist is if we are on double layout mode, the current page (first page) is not a cover image or a wide image
* and the next page is not a wide image (as only non-wides should be shown next to each other).
* @remarks This will always fail if the window's width is greater than the height
*/
get ShouldRenderDoublePage() {
// window.innerWidth > window.innerHeight // Don't render double if orientation is portrait, mostly mobile
return (
this.layoutMode === LayoutMode.Double &&
!this.isCoverImage() &&
!this.isWideImage(this.canvasImage) &&
!this.isWideImage(this.canvasImageNext) &&
window.innerWidth > window.innerHeight // Don't render double if orientation is portrait, mostly mobile
!this.isWideImage(this.canvasImageNext)
);
}
get ShouldRenderReverseDouble() {
// NOTE: I'm not going to care about window.innerWidth > window.innerHeight since a higher level handler will manage
// window.innerWidth > window.innerHeight // Don't render double reversed if orientation is portrait, mostly mobile
return (
this.layoutMode === LayoutMode.DoubleReversed &&
!this.isCoverImage() &&
!this.isCoverImage(this.pageNum - 1) &&
!this.isWideImage(this.canvasImage) &&
!this.isWideImage(this.canvasImageNext) &&
!this.isLoose &&
window.innerWidth > window.innerHeight // Don't render double reversed if orientation is portrait, mostly mobile
!this.isLoose
);
}
@ -372,6 +396,17 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return 'right-side';
}
get LayoutModeIconClass() {
switch (this.layoutMode) {
case LayoutMode.Single:
return 'none';
case LayoutMode.Double:
return 'double';
case LayoutMode.DoubleReversed:
return 'double-reversed';
}
}
get ReaderModeIcon() {
switch(this.readerMode) {
case ReaderMode.LeftRight:
@ -960,6 +995,12 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
const notInSplit = this.currentImageSplitPart !== (this.isSplitLeftToRight() ? SPLIT_PAGE_PART.LEFT_PART : SPLIT_PAGE_PART.RIGHT_PART);
// console.log('Current, Next: ', this.readerService.imageUrlToPageNum(this.canvasImage.src), ',', this.readerService.imageUrlToPageNum(this.canvasImageNext.src));
// console.log('Is canvasImage wide: ', this.isWideImage(this.canvasImage));
// console.log('Is canvasImage next wide: ', this.isWideImage(this.canvasImageNext));
// console.log('PRev: ', this.readerService.imageUrlToPageNum(this.canvasImagePrev.src));
let pageAmount = 1;
if (this.layoutMode === LayoutMode.Double) {
pageAmount = (
@ -969,12 +1010,11 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
!this.isSecondLastImage() &&
!this.isLastImage()
? 2 : 1);
}
if (this.layoutMode === LayoutMode.DoubleReversed) {
} else if (this.layoutMode === LayoutMode.DoubleReversed) {
pageAmount = (
!this.isCoverImage(this.pageNum - 1) &&
!this.isWideImage(this.canvasImagePrev) &&
!this.isWideImage(this.canvasImageNextDouble) &&
//!this.isCoverImage(this.pageNum - 1) && //
!this.isWideImage(this.canvasImageNext) &&
!this.isWideImage(this.canvasImageAheadBy2) && // Remember we are doing this logic before we've hit the next page, so we need this
!this.isSecondLastImage() &&
!this.isLastImage()
? 2 : 1);
@ -1014,16 +1054,27 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
let pageAmount = 1;
if (this.layoutMode === LayoutMode.Double) {
// Current is current and next is current + 1 (current is the page we are paging from)
// console.log('Current, Next: ', this.readerService.imageUrlToPageNum(this.canvasImage.src), ',', this.readerService.imageUrlToPageNum(this.canvasImageNext.src));
// console.log('Is canvasImage wide: ', this.isWideImage(this.canvasImage));
// console.log('Is canvasImage next wide: ', this.isWideImage(this.canvasImageNext));
// console.log('PRev: ', this.readerService.imageUrlToPageNum(this.canvasImagePrev.src)); // Prev is actually currentPage - 1 on double
pageAmount = (
!this.isCoverImage() &&
!this.isWideImage(this.canvasImagePrev)
? 2 : 1);
}
if (this.layoutMode === LayoutMode.DoubleReversed) {
// Current is current - 1 and next is current -2 (current is the page we are paging from)
// console.log('Current, Next: ', this.readerService.imageUrlToPageNum(this.canvasImage.src), ',', this.readerService.imageUrlToPageNum(this.canvasImageNext.src));
// console.log('Is canvasImage wide: ', this.isWideImage(this.canvasImage));
// console.log('Is canvasImage next wide: ', this.isWideImage(this.canvasImageNext));
// console.log('PRev: ', this.readerService.imageUrlToPageNum(this.canvasImagePrev.src)); // Prev is actually currentPage + 1 on double reversed
pageAmount = (
!this.isCoverImage() &&
!this.isCoverImage(this.pageNum - 1) &&
!this.isWideImage(this.canvasImage) &&
!this.isWideImage(this.canvasImage) && // JOE: At this point, these aren't yet set to the new values
!this.isWideImage(this.canvasImageNext)
? 2 : 1);
}
@ -1202,24 +1253,41 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.generalSettingsForm.get('fittingOption')?.setValue(newScale, {emitEvent: false});
}
/**
* If pagenumber is 0 aka first page, which on double page rendering should always render as a single.
*
* @param pageNumber Defaults to current page number
* @returns
*/
isCoverImage(pageNumber = this.pageNum) {
return pageNumber === 0;
}
/**
* If the image's width is greater than it's height
* @param elem Optional Image
*/
isWideImage(elem?: HTMLImageElement) {
if (elem) {
elem.onload = () => {
return elem.width > elem.height;
}
if (elem.src === '') return false;
}
const element = elem || this.canvasImage;
return element.width > element.height;
}
/**
* If the current page is second to last image
*/
isSecondLastImage() {
return this.maxPages - 1 - this.pageNum === 1;
}
/**
* If the current image is last image
*/
isLastImage() {
return this.maxPages - 1 === this.pageNum;
}
@ -1230,7 +1298,10 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return true;
}
/**
* Maintains a circular array of images (that are requested from backend) around the user's current page. This allows for quick loading (seemless to user)
* and also maintains page info (wide image, etc) due to onload event.
*/
prefetch() {
let index = 1;
@ -1251,19 +1322,43 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
//console.log('cachedImages: ', this.cachedImages.arr.map(img => this.readerService.imageUrlToPageNum(img.src) + ': ' + img.complete));
}
loadPage() {
this.isLoading = true;
this.canvasImage2.src = '';
this.canvasImageAheadBy2.src = '';
this.isLoose = (this.pageAmount === 1 ? true : false);
this.canvasImage.src = this.getPageUrl(this.pageNum);
if (this.layoutMode !== LayoutMode.Single) {
this.canvasImagePrev.src = this.getPageUrl(this.pageNum + (this.layoutMode !== LayoutMode.DoubleReversed ? - 1 : + 1));
this.canvasImageNext.src = this.getPageUrl(this.pageNum + (this.layoutMode !== LayoutMode.DoubleReversed ? + 1 : - 1));
this.canvasImageNextDouble.src = this.getPageUrl(this.pageNum + 2);
if (this.ShouldRenderDoublePage || this.ShouldRenderReverseDouble) {
this.canvasImage2.src = this.canvasImageNext.src;
this.canvasImageNext.src = this.getPageUrl(this.pageNum + 1); // This needs to be capped at maxPages !this.isLastImage()
this.canvasImagePrev.src = this.getPageUrl(this.pageNum - 1);
if (this.pageNum + 2 < this.maxPages - 1) {
this.canvasImageAheadBy2.src = this.getPageUrl(this.pageNum + 2);
}
if (this.pageNum - 2 >= 0) {
this.canvasImageBehindBy2.src = this.getPageUrl(this.pageNum - 2 || 0);
}
if (this.ShouldRenderDoublePage || this.ShouldRenderReverseDouble) {
if (this.layoutMode === LayoutMode.Double) {
this.canvasImage2.src = this.canvasImageNext.src;
} else {
this.canvasImage2.src = this.canvasImagePrev.src;
}
}
// console.log('======================================================');
// console.log('Page History: ', this.pageDimensionHistory);
// console.log('Current Page: ', this.pageNum);
// console.log('CanvasImage page: ', this.readerService.imageUrlToPageNum(this.canvasImage.src));
// console.log('CanvasImage2 page: ', this.readerService.imageUrlToPageNum(this.canvasImage2.src));
// console.log('Canvas Image Next:', this.readerService.imageUrlToPageNum(this.canvasImageNext.src));
// console.log('Canvas Image Next Ahead by 2:', this.readerService.imageUrlToPageNum(this.canvasImageAheadBy2.src));
// console.log('Canvas Image Prev:', this.readerService.imageUrlToPageNum(this.canvasImagePrev.src));
// console.log('======================================================');
}
this.renderPage();
this.prefetch();

View File

@ -57,7 +57,7 @@
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" *ngIf="series !== undefined" #scrollingBlock>
<div class="row mb-3 info-container">
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
<app-image maxWidth="300px" [imageUrl]="seriesImage"></app-image>
<app-image maxWidth="300px" maxHeight="400px" [imageUrl]="seriesImage"></app-image>
<!-- NOTE: We can put continue point here as Vol X Ch Y or just Ch Y or Book Z ?-->
</div>
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">