mirror of
https://github.com/gotson/komga.git
synced 2026-05-06 03:27:08 +02:00
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:
parent
f9d55ecfd0
commit
09984a4284
3 changed files with 100 additions and 65 deletions
3
komga-webui/src/functions/page.ts
Normal file
3
komga-webui/src/functions/page.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function isPageLandscape (p: PageDto): boolean {
|
||||||
|
return (p?.width ?? 0) > (p?.height ?? 0)
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue