feat(webreader): better display of landscape images

in double page mode, landscape images will be displayed as a single page
media analysis must be rerun for this to work

closes #123
This commit is contained in:
Gauthier Roebroeck 2020-07-31 09:23:05 +08:00
parent f9d55ecfd0
commit 09984a4284
3 changed files with 100 additions and 65 deletions

View file

@ -0,0 +1,3 @@
export function isPageLandscape (p: PageDto): boolean {
return (p?.width ?? 0) > (p?.height ?? 0)
}

View file

@ -23,7 +23,9 @@ interface MediaDto {
interface PageDto { interface PageDto {
number: number, number: number,
fileName: string, fileName: string,
mediaType: string mediaType: string,
width?: number,
height?: number,
} }
interface BookMetadataDto { interface BookMetadataDto {

View file

@ -53,30 +53,30 @@
<v-row justify="center"> <v-row justify="center">
<!-- Menu: page slider --> <!-- Menu: page slider -->
<v-col class="px-0"> <v-col class="px-0">
<v-slider <v-slider
hide-details hide-details
thumb-label thumb-label
@change="goTo" @change="goTo"
v-model="goToPage" v-model="goToPage"
class="align-center" class="align-center"
min="1" min="1"
:max="pagesCount" :max="pagesCount"
> >
<template v-slot:prepend> <template v-slot:prepend>
<v-icon @click="previousBook" class="">mdi-undo</v-icon> <v-icon @click="previousBook" class="">mdi-undo</v-icon>
<v-icon @click="goToFirst" class="mx-2">mdi-skip-previous</v-icon> <v-icon @click="goToFirst" class="mx-2">mdi-skip-previous</v-icon>
<v-label> <v-label>
{{ currentPage }} {{ currentPage }}
</v-label> </v-label>
</template> </template>
<template v-slot:append> <template v-slot:append>
<v-label> <v-label>
{{ pagesCount }} {{ pagesCount }}
</v-label> </v-label>
<v-icon @click="goToLast" class="mx-1">mdi-skip-next</v-icon> <v-icon @click="goToLast" class="mx-1">mdi-skip-next</v-icon>
<v-icon @click="nextBook" class="">mdi-redo</v-icon> <v-icon @click="nextBook" class="">mdi-redo</v-icon>
</template> </template>
</v-slider> </v-slider>
</v-col> </v-col>
</v-row> </v-row>
@ -114,24 +114,20 @@
height="100%" height="100%"
> >
<!-- Carousel: pages --> <!-- Carousel: pages -->
<v-carousel-item v-for="p in slidesRange" <v-carousel-item v-for="(spread, i) in spreads"
:key="doublePages ? `db${p}` : `sp${p}`" :key="`spread${i}`"
:eager="eagerLoad(p)" :eager="eagerLoad(i)"
class="full-height" class="full-height"
:transition="animations ? undefined : false" :transition="animations ? undefined : false"
:reverse-transition="animations ? undefined : false" :reverse-transition="animations ? undefined : false"
> >
<div class="full-height d-flex flex-column justify-center"> <div class="full-height d-flex flex-column justify-center">
<div :class="`d-flex flex-row${flipDirection ? '-reverse' : ''} justify-center px-0 mx-0` "> <div :class="`d-flex flex-row${flipDirection ? '-reverse' : ''} justify-center px-0 mx-0` ">
<img :src="getPageUrl(p)" <img v-for="(page, j) in spread"
:key="`spread${i}-${j}`"
:src="getPageUrl(page.number)"
:height="maxHeight" :height="maxHeight"
:width="maxWidth(p)" :width="maxWidth(i)"
:style="imgStyle"
/>
<img v-if="doublePages && p !== 1 && p !== pagesCount && p+1 !== pagesCount"
:src="getPageUrl(p+1)"
:height="maxHeight"
:width="maxWidth(p+1)"
:style="imgStyle" :style="imgStyle"
/> />
</div> </div>
@ -240,7 +236,8 @@
top top
> >
<div>This book has specific reading direction set.</div> <div>This book has specific reading direction set.</div>
<div>Reading direction has been set to <span class="font-weight-bold">{{ readingDirs.find(x => x.value === readingDirection).text }}</span>. <div>Reading direction has been set to <span
class="font-weight-bold">{{ readingDirectionText(readingDirection as ReadingDirection) }}</span>.
</div> </div>
<div>This only applies to this book and will not overwrite your settings.</div> <div>This only applies to this book and will not overwrite your settings.</div>
<v-btn <v-btn
@ -278,6 +275,7 @@ import { bookPageUrl } from '@/functions/urls'
import { ReadingDirection } from '@/types/enum-books' import { ReadingDirection } from '@/types/enum-books'
import { executeShortcut } from '@/functions/shortcuts' import { executeShortcut } from '@/functions/shortcuts'
import Vue from 'vue' import Vue from 'vue'
import { isPageLandscape } from '@/functions/page'
const cookieFit = 'webreader.fit' const cookieFit = 'webreader.fit'
const cookieReadingDirection = 'webreader.readingDirection' const cookieReadingDirection = 'webreader.readingDirection'
@ -415,7 +413,10 @@ export default Vue.extend({
return this.carouselPage + 1 return this.carouselPage + 1
}, },
currentPage (): number { currentPage (): number {
return this.doublePages ? this.toSinglePages(this.currentSlide) : this.currentSlide if (this.carouselPage >= 0 && this.carouselPage < this.spreads.length && this.spreads.length > 0) {
return this.spreads[this.carouselPage][0].number
}
return 1
}, },
canPrev (): boolean { canPrev (): boolean {
return this.currentSlide > 1 return this.currentSlide > 1
@ -432,18 +433,8 @@ export default Vue.extend({
imgStyle (): string { imgStyle (): string {
return this.imageFit === ImageFit.WIDTH ? 'height:intrinsic' : '' return this.imageFit === ImageFit.WIDTH ? 'height:intrinsic' : ''
}, },
slidesRange (): number[] {
if (!this.doublePages) {
return this.$_.range(1, this.pagesCount + 1)
}
// for double pages the first and last pages are shown as single, while others are doubled
const ret = this.$_.range(2, this.pagesCount, 2)
ret.unshift(1)
ret.push(this.pagesCount)
return ret
},
slidesCount (): number { slidesCount (): number {
return this.slidesRange.length return this.spreads.length
}, },
pagesCount (): number { pagesCount (): number {
return this.pages.length return this.pages.length
@ -514,6 +505,34 @@ export default Vue.extend({
this.$cookies.set(cookieSwipe, swipe, Infinity) this.$cookies.set(cookieSwipe, swipe, Infinity)
}, },
}, },
spreads (): PageDto[][] {
if (this.pages.length === 0) return []
if (this.doublePages) {
const spreads = []
spreads.push([this.pages[0]])
const pages = this.$_.drop(this.$_.dropRight(this.pages)) as PageDto[]
while (pages.length > 0) {
const p = pages.shift() as PageDto
if (isPageLandscape(p)) {
spreads.push([p])
} else {
if (pages.length > 0) {
const p2 = pages.shift() as PageDto
if (isPageLandscape(p2)) {
spreads.push([p])
spreads.push([p2])
} else {
spreads.push([p, p2])
}
}
}
}
spreads.push([this.pages[this.pages.length - 1]])
return spreads
} else {
return this.pages.map(p => [p])
}
},
}, },
methods: { methods: {
keyPressed (e: KeyboardEvent) { keyPressed (e: KeyboardEvent) {
@ -554,6 +573,9 @@ export default Vue.extend({
this.siblingPrevious = {} as BookDto this.siblingPrevious = {} as BookDto
} }
}, },
readingDirectionText (readingDirection: ReadingDirection): string {
return readingDirs.find(x => x.value === readingDirection).text
},
getPageUrl (page: number): string { getPageUrl (page: number): string {
if (!this.supportedMediaTypes.includes(this.pages[page - 1].mediaType)) { if (!this.supportedMediaTypes.includes(this.pages[page - 1].mediaType)) {
return bookPageUrl(this.bookId, page, this.convertTo) return bookPageUrl(this.bookId, page, this.convertTo)
@ -614,7 +636,7 @@ export default Vue.extend({
} }
}, },
goTo (page: number) { goTo (page: number) {
this.carouselPage = this.doublePages ? this.toDoublePages(page) - 1 : page - 1 this.carouselPage = this.toSpreadIndex(page)
}, },
goToFirst () { goToFirst () {
this.goTo(1) this.goTo(1)
@ -639,35 +661,43 @@ export default Vue.extend({
if (i === this.slidesCount) return this.pagesCount if (i === this.slidesCount) return this.pagesCount
return (i - 1) * 2 return (i - 1) * 2
}, },
toDoublePages (i: number): number { toSpreadIndex (i: number): number {
let ret = Math.floor(i / 2) + 1 if (this.spreads.length > 0) {
if (i === this.pagesCount && this.pagesCount % 2 === 1) { if (this.doublePages) {
ret++ for (let j = 0; j < this.spreads.length; j++) {
for (let k = 0; k < this.spreads[j].length; k++) {
if (this.spreads[j][k].number === i) {
return j
}
}
}
} else {
return i - 1
}
} }
return ret return i - 1
}, },
eagerLoad (p: number): boolean { eagerLoad (spreadIndex: number): boolean {
return Math.abs(this.currentPage - p) <= 2 return Math.abs(this.carouselPage - spreadIndex) <= 2
}, },
maxWidth (p: number): number | null { maxWidth (spreadIndex: number): number | null {
if (this.imageFit !== ImageFit.WIDTH) { if (this.imageFit !== ImageFit.WIDTH) {
return null return null
} }
if (this.doublePages && p !== 1 && p !== this.pagesCount) { if (this.doublePages && this.spreads[spreadIndex].length === 2) {
return this.$vuetify.breakpoint.width / 2 return this.$vuetify.breakpoint.width / 2
} }
return this.$vuetify.breakpoint.width return this.$vuetify.breakpoint.width
}, },
loadFromCookie (cookieKey: string, setter: (value: any) => void): void { loadFromCookie (cookieKey: string, setter: (value: any) => void): void {
if (this.$cookies.isKey(cookieKey)) { if (this.$cookies.isKey(cookieKey)) {
let value = this.$cookies.get(cookieKey) setter(this.$cookies.get(cookieKey))
setter(value)
} }
}, },
changeReadingDir (dir: ReadingDirection) { changeReadingDir (dir: ReadingDirection) {
this.readingDirection = dir this.readingDirection = dir
let i = this.settings.readingDirs.indexOf(this.readingDirection) const i = this.settings.readingDirs.indexOf(this.readingDirection)
let text = this.readingDirs[i].text const text = this.readingDirs[i].text
this.sendNotification(`Changing Reading Direction to: ${text}`) this.sendNotification(`Changing Reading Direction to: ${text}`)
}, },
cycleScale () { cycleScale () {
@ -682,13 +712,13 @@ export default Vue.extend({
this.doublePages = !this.doublePages this.doublePages = !this.doublePages
this.sendNotification(`${this.doublePages ? 'Enabled' : 'Disabled'} Double Pages`) this.sendNotification(`${this.doublePages ? 'Enabled' : 'Disabled'} Double Pages`)
}, },
sendNotification (message:string, timeout: number = 4000) { sendNotification (message: string, timeout: number = 4000) {
this.notification.timeout = 4000 this.notification.timeout = 4000
this.notification.message = message this.notification.message = message
this.notification.enabled = true this.notification.enabled = true
}, },
async markProgress (page: number) { async markProgress (page: number) {
this.$komgaBooks.updateReadProgress(this.bookId, { page: page }) await this.$komgaBooks.updateReadProgress(this.bookId, { page: page })
}, },
}, },
}) })