Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion komga-webui/src/components/readers/PagedReader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@
>
<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="[spineClass(spread)]"
z-index=0>
</div>
<img v-for="(page, j) in spread"
:alt="`Page ${page.number}`"
:key="`spread${i}-${j}`"
:src="page.url"
:class="imgClass(spread)"
:class="[imgClass(spread), offsetClass(spread, i)]"
class="img-fit-all"
/>
</div>
Expand Down Expand Up @@ -108,6 +111,10 @@ export default Vue.extend({
type: Boolean,
required: true,
},
simulateSpine: {
type: Boolean,
required: true,
},
swipe: {
type: Boolean,
required: true,
Expand Down Expand Up @@ -196,11 +203,19 @@ export default Vue.extend({
isDoublePages(): boolean {
return this.pageLayout === PagedReaderLayout.DOUBLE_PAGES || this.pageLayout === PagedReaderLayout.DOUBLE_NO_COVER
},
isSpineRequired(): boolean {
return this.simulateSpine
&& (this.pageLayout === PagedReaderLayout.DOUBLE_NO_COVER
|| (this.pageLayout === PagedReaderLayout.DOUBLE_PAGES && this.canPrev && this.canNext))
},
},
methods: {
keyPressed(e: KeyboardEvent) {
this.shortcuts[e.key]?.execute(this)
},
spineClass(spread: PageDtoWithUrl[]): string {
return spread.length === 1 || !this.isSpineRequired ? 'spine-none' : 'spine'
},
imgClass(spread: PageDtoWithUrl[]): string {
const double = spread.length > 1
switch (this.scale) {
Expand All @@ -216,6 +231,12 @@ export default Vue.extend({
return 'img-fit-original'
}
},
offsetClass(spread: PageDtoWithUrl[], index: number): string{
if (spread.length === 1 || !this.isSpineRequired) {
return 'offset-none'
}
return index % 2 === 1 ? 'offset-to-left' : 'offset-to-right'
},
eagerLoad(spreadIndex: number): boolean {
return Math.abs(this.carouselPage - spreadIndex) <= 2
},
Expand Down Expand Up @@ -322,6 +343,20 @@ export default Vue.extend({
position: absolute;
}

.spine {
width: 5px;
height: 100vh;
position: absolute;
background: linear-gradient(to right, transparent, black 50%, transparent);
}

.spine-none {
width: 5px;
height: 100vh;
position: absolute;
background: transparent;
}

.img-fit-all {
object-fit: contain;
object-position: center;
Expand Down Expand Up @@ -368,4 +403,16 @@ export default Vue.extend({
max-width: 50vw;
height: 100vh;
}

.offset-to-left {
margin-right: 1px;
}

.offset-to-right {
margin-left: 1px;
}

.offset-none {
margin: 0;
}
</style>
76 changes: 34 additions & 42 deletions komga-webui/src/functions/book-spreads.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,46 @@
import {PageDtoWithUrl} from '@/types/komga-books'
import {PagedReaderLayout} from '@/types/enum-reader'
import {isPageLandscape} from '@/functions/page'
import {cloneDeep} from 'lodash'


export function buildSpreads(pages: PageDtoWithUrl[], pageLayout: PagedReaderLayout): PageDtoWithUrl[][] {
if (pages.length === 0) return []
if (pageLayout !== PagedReaderLayout.SINGLE_PAGE) {
const spreads = [] as PageDtoWithUrl[][]
const pagesClone = cloneDeep(pages)
let lastPages = undefined
if (pageLayout === PagedReaderLayout.DOUBLE_PAGES) {
const firstPage = pagesClone.shift() as PageDtoWithUrl
if (isPageLandscape(firstPage))
spreads.push([firstPage] as PageDtoWithUrl[])
else
spreads.push([createEmptyPage(firstPage), firstPage] as PageDtoWithUrl[])
if (pagesClone.length > 0) {
const lastPage = pagesClone.pop() as PageDtoWithUrl
if(isPageLandscape(lastPage))
lastPages = [lastPage] as PageDtoWithUrl[]
else
lastPages = [lastPage, createEmptyPage(lastPage)] as PageDtoWithUrl[]
}
}
while (pagesClone.length > 0) {
const p = pagesClone.shift() as PageDtoWithUrl
if (isPageLandscape(p)) {
spreads.push([p])
} else {
if (pagesClone.length > 0) {
const p2 = pagesClone.shift() as PageDtoWithUrl
if (isPageLandscape(p2)) {
spreads.push([p, createEmptyPage(p)])
spreads.push([p2])
} else {
spreads.push([p, p2])
}
} else {
spreads.push([p, createEmptyPage(p)])
}
}
}
if (lastPages) spreads.push(lastPages)
return spreads
} else {
return pages.map(p => [p])
switch (pageLayout) {
case PagedReaderLayout.DOUBLE_PAGES: return createDoublePages(pages)
case PagedReaderLayout.DOUBLE_NO_COVER: return createDoubleNoCover(pages)
default: return createSinglePage(pages)
}
}

function createSinglePage(pages: PageDtoWithUrl[]): PageDtoWithUrl[][] {
return pages.length !== 0 ? pages.map(p => [p]) : []
}

function createDoublePages(pages: PageDtoWithUrl[]): PageDtoWithUrl[][] {
const spreads = createDoubleNoCover(pages.slice(1, -1))
const n = pages.length
if (n > 0) {
spreads.unshift(isPageLandscape(pages[0]) ? [pages[0]] : [createEmptyPage(pages[0]), pages[0]])
}
if (n > 1) {
spreads.push(isPageLandscape(pages[n - 1]) ? [pages[n-1]] : [createEmptyPage(pages[n-1]), pages[n-1]])
}
return spreads
}

function createDoubleNoCover(pages: PageDtoWithUrl[]): PageDtoWithUrl[][] {
const n = pages.length
if (n === 0) return []
const spreads = [] as PageDtoWithUrl[][]
for (let i = 0; i < n; ++i) {
const p1 = pages[i] as PageDtoWithUrl
const p2 = i !== n - 1 ? pages[i + 1] as PageDtoWithUrl : createEmptyPage(p1)
isPageLandscape(p1) ? spreads.push([p1]) :
isPageLandscape(p2) ? spreads.push([p1, createEmptyPage(p1)]) :
(spreads.push([p1, p2]), ++i)
}
return spreads
}

function createEmptyPage(page: PageDtoWithUrl): PageDtoWithUrl {
return {
url: createTransparentDataUrl(page?.width || 20, page?.height || 30),
Expand Down
1 change: 1 addition & 0 deletions komga-webui/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"scale_type": "Scale type",
"side_padding": "Side padding",
"side_padding_none": "None",
"simulate_spine": "Simulate spine",
"webtoon": "Webtoon Reader Options"
},
"shortcuts": {
Expand Down
1 change: 1 addition & 0 deletions komga-webui/src/locales/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"scale_type": "缩放模式",
"side_padding": "边距",
"side_padding_none": "无",
"simulate_spine": "模拟书脊",
"webtoon": "Webtoon 模式"
},
"shortcuts": {
Expand Down
1 change: 1 addition & 0 deletions komga-webui/src/locales/zh_Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
"scale_type": "縮放模式",
"side_padding": "側邊邊距",
"side_padding_none": "無",
"simulate_spine": "模擬書脊",
"webtoon": "Webtoon模式"
},
"shortcuts": {
Expand Down
4 changes: 4 additions & 0 deletions komga-webui/src/plugins/persisted-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const persistedModule: Module<any, any> = {
paged: {
scale: '',
pageLayout: '',
simulateSpine: true,
},
continuous: {
scale: '',
Expand Down Expand Up @@ -74,6 +75,9 @@ export const persistedModule: Module<any, any> = {
setWebreaderReadingDirection(state, val) {
state.webreader.readingDirection = val
},
setWebreaderPagedSimulateSpine(state, val) {
state.webreader.paged.simulateSpine = val
},
setWebreaderSwipe(state, val) {
state.webreader.swipe = val
},
Expand Down
23 changes: 23 additions & 0 deletions komga-webui/src/views/BookReader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
:page-layout="pageLayout"
:scale="scale"
:animations="animations"
:simulateSpine="simulateSpine"
:swipe="swipe"
@menu="toggleToolbars()"
@jump-previous="jumpToPrevious()"
Expand Down Expand Up @@ -187,6 +188,13 @@
:label="$t('bookreader.settings.animate_page_transitions')"/>
</v-list-item>

<template v-if="doublePageLayout">
<v-list-item>
<settings-switch v-model="simulateSpine"
:label="$t('bookreader.settings.simulate_spine')"/>
</v-list-item>
</template>

<v-list-item>
<settings-switch v-model="swipe" :label="$t('bookreader.settings.gestures')"/>
</v-list-item>
Expand Down Expand Up @@ -388,6 +396,7 @@ export default Vue.extend({
sidePadding: 0,
readingDirection: ReadingDirection.LEFT_TO_RIGHT,
backgroundColor: 'black',
simulateSpine: true,
},
shortcuts: {} as any,
notification: {
Expand Down Expand Up @@ -448,6 +457,7 @@ export default Vue.extend({
this.continuousScale = this.$store.state.persistedState.webreader.continuous.scale
this.sidePadding = this.$store.state.persistedState.webreader.continuous.padding
this.backgroundColor = this.$store.state.persistedState.webreader.background
this.simulateSpine = this.$store.state.persistedState.webreader.paged.simulateSpine

this.setup(this.bookId, Number(this.$route.query.page))
},
Expand Down Expand Up @@ -493,6 +503,10 @@ export default Vue.extend({
continuousReader(): boolean {
return this.readingDirection === ReadingDirection.WEBTOON
},
doublePageLayout(): boolean {
return this.readingDirection !== ReadingDirection.WEBTOON
&& this.settings.pageLayout !== PagedReaderLayout.SINGLE_PAGE
},
progress(): number {
return this.page / this.pagesCount * 100
},
Expand Down Expand Up @@ -548,6 +562,15 @@ export default Vue.extend({
this.$store.commit('setWebreaderAnimations', animations)
},
},
simulateSpine: {
get: function (): boolean {
return this.settings.simulateSpine
},
set: function (simulate_spine: boolean): void {
this.settings.simulateSpine = simulate_spine
this.$store.commit('setWebreaderPagedSimulateSpine', simulate_spine)
},
},
scale: {
get: function (): ScaleType {
return this.settings.scale
Expand Down