feat(webui): redesign reader to follow material design

closes #74
This commit is contained in:
primetoxinz 2020-03-02 22:27:08 -05:00 committed by GitHub
parent 917012b7c5
commit 7f0ab5fde3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 566 additions and 326 deletions

View file

@ -0,0 +1,75 @@
<template>
<v-row justify-md="center" justify-sm="start">
<v-col cols="5" md="4" class="text-left" align-self="center">
<v-label class=""> {{ label }} </v-label>
</v-col>
<v-col cols="7" md="4" >
<v-select
filled
dense
solo
:items="items"
v-model="input"
hide-selected
@input="updateInput"
@change="updateInput"
hide-details="true"
>
<template v-slot:item="data">
<slot name="item" v-bind="data"></slot>
</template>
<template v-slot:selection="data">
<slot name="selection" v-bind="data"></slot>
</template>
</v-select>
</v-col>
</v-row>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
name: 'SettingsSelect',
props: {
items: null as any,
label: {
type: String
},
value: {
type: String
},
display: {
type: String
}
},
data () {
return {
input: ''
}
},
watch: {
value: {
handler (after) {
this.input = after
},
immediate: true
}
},
methods: {
updateInput () {
this.$emit('input', this.input)
}
}
})
</script>
<style>
.v-text-field__details, div.v-input__control {
min-height: 0 !important;
}
.v-text-field__details {
display: none !important;
}
</style>

View file

@ -0,0 +1,55 @@
<template>
<v-row justify-md="center" justify-sm="start">
<v-col cols="5" md="3" align-self="center">
<v-label> {{ label }} </v-label>
</v-col>
<v-col cols="4" md="3" align-self="center" class="text-right">
<v-label> {{ status }} </v-label>
</v-col>
<v-col cols="3" md="2" align-self="center">
<v-switch v-model="input"
@input="updateInput"
@change="updateInput"
class="float-right"
>
</v-switch>
</v-col>
</v-row>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
name: 'SettingsSwitch',
props: {
label: {
type: String
},
value: {
type: Boolean
},
status: {
type: String
}
},
data () {
return {
input: ''
}
},
watch: {
value: {
handler (after) {
this.input = after
},
immediate: true
}
},
methods: {
updateInput () {
this.$emit('input', this.input)
}
}
})
</script>

View file

@ -0,0 +1,100 @@
<template>
<v-dialog v-model="input" scrollable>
<v-card :max-height="$vuetify.breakpoint.height * .9" dark>
<v-card-title>
<v-pagination
v-model="page"
:total-visible="perPage"
:length="Math.ceil(thumbnails.length/perPage)"
></v-pagination>
</v-card-title>
<v-card-text>
<v-container fluid>
<v-row class="mb-2 align-center justify-space-around">
<div v-for="(url, i) in visibleThumbnails"
:key="url"
style="min-height: 220px; max-width: 140px"
class="d-flex flex-column justify-center"
>
<v-img
:src="url"
lazy-src="../assets/cover.svg"
aspect-ratio="0.7071"
:contain="true"
max-height="200"
max-width="140"
class="ma-2"
@click="input = false; goTo(((page - 1 ) * perPage + i + 1))"
style="cursor: pointer"
/>
<div class="white--text text-center font-weight-bold">{{ (page - 1 ) * perPage + i + 1 }}</div>
</div>
</v-row>
</v-container>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import Vue from 'vue'
import { bookPageThumbnailUrl } from '@/functions/urls'
export default Vue.extend({
name: 'ThumbnailExplorerDialog',
props: {
pagesCount: {
type: Number
},
value: {
type: Boolean
},
bookId: {
type: Number
}
},
data: () => {
return {
input: '',
page: 1,
perPage: 8
}
},
watch: {
value (val) {
this.input = val
},
input (val) {
!val && this.$emit('input', false)
}
},
computed: {
thumbnails (): string[] {
let thumbnails = []
for (let p = 1; p <= this.pagesCount; p++) {
thumbnails.push(this.getThumbnailUrl(p))
}
return thumbnails
},
visibleThumbnails (): String[] {
let a : number = (this.page - 1) * this.perPage
let b : number = this.page * this.perPage
return this.thumbnails.slice(a, b)
}
},
methods: {
updateInput () {
this.$emit('input', this.input)
},
goTo (page: number) {
this.$emit('goToPage', page)
},
getThumbnailUrl (page: number): string {
return bookPageThumbnailUrl(this.bookId, page)
}
}
})
</script>

View file

@ -3,12 +3,19 @@ import 'typeface-roboto/index.css'
import Vue from 'vue' import Vue from 'vue'
import Vuetify from 'vuetify/lib' import Vuetify from 'vuetify/lib'
Vue.use(Vuetify) import { Touch } from 'vuetify/lib/directives'
Vue.use(Vuetify, {
directives: {
Touch
}
})
export default new Vuetify({ export default new Vuetify({
icons: { icons: {
iconfont: 'mdi' iconfont: 'mdi'
}, },
theme: { theme: {
options: { options: {
customProperties: true customProperties: true

View file

@ -10,6 +10,11 @@ export enum ImageFit {
ORIGINAL = 'original' ORIGINAL = 'original'
} }
export enum ReadingDirection {
LeftToRight = 'ltl',
RightToLeft = 'rtl'
}
export enum MediaStatus { export enum MediaStatus {
READY = 'READY', READY = 'READY',
UNKNOWN = 'UNKNOWN', UNKNOWN = 'UNKNOWN',

View file

@ -1,284 +1,223 @@
<template> <template>
<div v-if="pages.length > 0" style="background: black; width: 100%; height: 100%"> <v-container class="ma-0 pa-0 full-height" fluid v-if="pages.length > 0" style="width: 100%;"
<!-- clickable zone: left --> v-touch="{
<div @click="rtl ? next() : prev()" left: () => turnLeft(),
class="left-quarter full-height" right: () => turnRight(),
style="z-index: 1; position: absolute" }"
/> >
<div>
<!-- clickable zone: menu --> <v-slide-y-transition>
<div @click="showMenu = true" <v-toolbar
class="center-half full-height" dense elevation="1"
style="z-index: 1; position: absolute" v-if="toolbar"
/> class="settings full-width"
style="position: fixed; top: 0"
<!-- clickable zone: right --> >
<div @click="rtl ? prev() : next()" <v-btn
class="right-quarter full-height" icon
style="z-index: 1; position: absolute" @click="closeBook"
/>
<!-- Carousel -->
<v-carousel v-model="carouselPage"
:show-arrows="false"
hide-delimiters
:continuous="false"
height="auto"
touchless
:reverse="rtl"
>
<!-- Carousel: pages -->
<v-carousel-item v-for="p in slidesRange"
:key="doublePages ? `db${p}` : `sp${p}`"
:eager="eagerLoad(p)"
>
<div :class="`d-flex flex-row${rtl ? '-reverse' : ''} justify-center`">
<img :src="getPageUrl(p)"
:height="maxHeight"
:width="maxWidth(p)"
/>
<img v-if="doublePages && p !== 1 && p !== pagesCount && p+1 !== pagesCount"
:src="getPageUrl(p+1)"
:height="maxHeight"
:width="maxWidth(p+1)"
/>
</div>
</v-carousel-item>
</v-carousel>
<!-- Menu -->
<v-overlay :value="showMenu"
opacity=".8"
>
<!-- Menu: left zone with arrow -->
<div class="fixed-position full-height left-quarter"
style="display: flex; align-items: center; justify-content: center"
>
<v-icon size="8em">
mdi-chevron-left
</v-icon>
</div>
<!-- Menu: central zone -->
<div class="dashed-x fixed-position center-half full-height"
@click.self="showMenu = false"
>
<div style="position: absolute; top: 1em; left: 1em">
<v-btn @click="closeBook"
color="primary"
> >
Close book <v-icon>mdi-arrow-left</v-icon>
</v-btn> </v-btn>
<v-toolbar-title> {{ this.bookTitle }}</v-toolbar-title>
<v-btn @click="showMenu = false; showThumbnailsExplorer = true" <v-spacer></v-spacer>
color="primary" <v-btn
class="ml-2" icon
@click="showThumbnailsExplorer = !showThumbnailsExplorer"
> >
<v-icon>mdi-view-grid</v-icon> <v-icon>mdi-view-grid</v-icon>
</v-btn> </v-btn>
</div> <v-btn
icon
<v-btn icon @click="menu = !menu"
@click="showMenu = false" >
absolute <v-icon>mdi-settings</v-icon>
top </v-btn>
right </v-toolbar>
</v-slide-y-transition>
<v-slide-y-reverse-transition>
<v-toolbar
dense
elevation="1"
class="settings full-width"
style="position: fixed; bottom: 0"
horizontal
v-if="toolbar"
> >
<v-icon>mdi-close</v-icon> <v-row justify="center">
</v-btn> <!-- Menu: page slider -->
<v-col>
<v-container fluid
class="pa-6 pt-12"
style="border-bottom: 4px dashed"
>
<!-- Menu: book title -->
<v-row>
<v-col class="text-center title">
{{ bookTitle }}
</v-col>
</v-row>
<!-- Menu: number of pages -->
<v-row>
<v-col class="text-center title">
Page {{ currentPage }} of {{ pagesCount }}
</v-col>
</v-row>
<!-- Menu: progress bar -->
<v-row>
<v-col cols="12">
<v-progress-linear :value="progress"
height="20"
background-color="white"
color="secondary"
rounded
/>
</v-col>
</v-row>
<!-- Menu: go to page -->
<v-row align="baseline" justify="center">
<v-col cols="auto">
Go to page
</v-col>
<v-col cols="auto">
<v-text-field
v-model="goToPage"
hide-details
single-line
type="number"
@change="goTo"
style="width: 4em"
/>
</v-col>
</v-row>
<!-- Menu: page slider -->
<v-row align="baseline">
<v-col cols="12">
<v-slider <v-slider
hide-details
thumb-label
@change="goTo"
v-model="goToPage" v-model="goToPage"
class="align-center" class="align-center"
:max="pagesCount"
min="1" min="1"
hide-details :max="pagesCount"
@change="goTo"
> >
<template v-slot:prepend> <template v-slot:prepend>
<v-btn icon @click="goToFirst"> <v-icon @click="goToFirst" class="mx-2">mdi-arrow-collapse-left</v-icon>
<v-icon>mdi-arrow-collapse-left</v-icon> <v-label>
</v-btn> {{ currentPage }}
</v-label>
</template> </template>
<template v-slot:append> <template v-slot:append>
<v-btn icon @click="goToLast"> <v-label>
<v-icon>mdi-arrow-collapse-right</v-icon> {{ pagesCount }}
</v-btn> </v-label>
<v-icon @click="goToLast" class="mx-2">mdi-arrow-collapse-right</v-icon>
</template> </template>
</v-slider> </v-slider>
<!-- <v-dialog v-model="dialogGoto" persistent max-width="290">-->
<!-- <template v-slot:activator="{ on }">-->
<!-- <v-icon large class="ma-auto" v-on="on">mdi-arrow-right-bold-circle</v-icon>-->
<!-- </template>-->
<!-- <v-card >-->
<!-- <v-card-text class="d-flex flex-row">-->
<!-- <v-text-field-->
<!-- v-model="goToPage"-->
<!-- hide-details-->
<!-- single-line-->
<!-- type="number"-->
<!-- autofocus-->
<!-- />-->
<!-- </v-card-text>-->
<!-- <v-card-actions>-->
<!-- <v-spacer></v-spacer>-->
<!-- <v-btn color="darken-1" text @click="dialogGoto = false">Close</v-btn>-->
<!-- <v-btn color="green darken-1" text @click="goTo(goToPage)">Go To</v-btn>-->
<!-- </v-card-actions>-->
<!-- </v-card>-->
<!-- </v-dialog>-->
</v-col> </v-col>
</v-row> </v-row>
<!-- Menu: fit buttons --> </v-toolbar>
<v-row justify="center"> </v-slide-y-reverse-transition>
<v-col cols="auto"> </div>
<v-btn-toggle v-model="fitButtons" dense mandatory active-class="primary" class="flex-column flex-md-row">
<v-btn @click="setFit(ImageFit.WIDTH)">
Fit to width
</v-btn>
<v-btn @click="setFit(ImageFit.HEIGHT)"> <!-- clickable zone: left -->
Fit to height <div @click="turnLeft()"
</v-btn> class="left-quarter full-height top"
style="z-index: 1;"
/>
<v-btn @click="setFit(ImageFit.ORIGINAL)"> <!-- clickable zone: menu -->
Original <div @click="toolbar = !toolbar"
</v-btn> class="center-half full-height top"
</v-btn-toggle> style="z-index: 1;"
</v-col> />
</v-row>
<!-- Menu: RTL buttons --> <!-- clickable zone: right -->
<v-row justify="center"> <div @click="turnRight()"
<v-col cols="auto"> class="right-quarter full-height top"
<v-btn-toggle v-model="rtlButtons" dense mandatory active-class="primary" class="flex-column flex-md-row"> style="z-index: 1;"
<v-btn @click="setRtl(false)"> />
Left to right
</v-btn>
<v-btn @click="setRtl(true)"> <div class="full-height">
Right to left <!-- Carousel -->
</v-btn> <v-carousel v-model="carouselPage"
</v-btn-toggle> :show-arrows="false"
</v-col> :continuous="false"
</v-row> :reverse="flipDirection"
hide-delimiters
<!-- Menu: double pages buttons --> touchless
<v-row justify="center"> height="100%"
<v-col cols="auto">
<v-btn-toggle v-model="doublePagesButtons" dense mandatory active-class="primary" class="flex-column flex-md-row">
<v-btn @click="setDoublePages(false)">
Single page
</v-btn>
<v-btn @click="setDoublePages(true)">
Double pages
</v-btn>
</v-btn-toggle>
</v-col>
</v-row>
<!-- Menu: keyboard shortcuts -->
<v-row>
<v-col cols="auto">
<div><kbd></kbd> / <kbd></kbd></div>
<div><kbd></kbd> / <kbd></kbd></div>
<div><kbd>home</kbd></div>
<div><kbd>end</kbd></div>
<div><kbd>space</kbd></div>
<div><kbd>m</kbd></div>
<div><kbd>t</kbd></div>
<div><kbd>esc</kbd></div>
</v-col>
<v-col>
<div v-if="!rtl">Previous page</div>
<div v-else>Next page</div>
<div v-if="!rtl">Next page</div>
<div v-else>Previous page</div>
<div>First page</div>
<div>Last page</div>
<div>Scroll down</div>
<div>Show / hide menu</div>
<div>Show / hide thumbnails</div>
<div>Close book</div>
</v-col>
</v-row>
</v-container>
</div>
<!-- Menu: right zone with arrow -->
<div class="fixed-position full-height right-quarter"
style="display: flex; align-items: center; justify-content: center"
> >
<v-icon size="8em"> <!-- Carousel: pages -->
mdi-chevron-right <v-carousel-item v-for="p in slidesRange"
</v-icon> :key="doublePages ? `db${p}` : `sp${p}`"
</div> :eager="eagerLoad(p)"
class="full-height"
:transition="animations ? undefined : false"
:reverse-transition="animations ? undefined : false"
>
<div class="full-height d-flex flex-column justify-center reader-background">
<div :class="`d-flex flex-row${flipDirection ? '-reverse' : ''} justify-center px-0 mx-0` " >
<img :src="getPageUrl(p)"
:height="maxHeight"
:width="maxWidth(p)"
/>
<img v-if="doublePages && p !== 1 && p !== pagesCount && p+1 !== pagesCount"
:src="getPageUrl(p+1)"
:height="maxHeight"
:width="maxWidth(p+1)"
/>
</div>
</div>
</v-carousel-item>
</v-carousel>
</div>
</v-overlay> <thumbnail-explorer-dialog
v-model="showThumbnailsExplorer"
:bookId="bookId"
@goToPage="goTo"
:pagesCount="pagesCount"
></thumbnail-explorer-dialog>
<v-dialog v-model="showThumbnailsExplorer" scrollable> <v-dialog
<v-card :max-height="$vuetify.breakpoint.height * .9" v-model="menu"
dark :close-on-content-click="false"
> transition="dialog-bottom-transition"
<v-card-text> :width="$vuetify.breakpoint.width * ($vuetify.breakpoint.smAndUp ? 0.5 : 1)"
<v-container fluid> >
<v-row> <v-container fluid class="pa-0">
<div v-for="p in pagesCount" <v-toolbar dark color="primary">
:key="p" <v-btn icon dark @click="menu = false">
style="min-height: 220px; max-width: 140px" <v-icon>mdi-close</v-icon>
class="mb-2" </v-btn>
> <v-toolbar-title>Reader Settings</v-toolbar-title>
<v-img </v-toolbar>
:src="getThumbnailUrl(p)"
lazy-src="../assets/cover.svg" <v-list class="full-height full-width">
aspect-ratio="0.7071" <v-list-item>
:contain="true" <settings-switch v-model="doublePages" label="Page Layout" :status="`${ doublePages ? 'Double Pages' : 'Single Page'}`"></settings-switch>
max-height="200" </v-list-item>
max-width="140" <v-list-item>
class="ma-2" <settings-switch v-model="animations" label="Page Transitions"></settings-switch>
@click="showThumbnailsExplorer = false; goTo(p)" </v-list-item>
style="cursor: pointer" <v-list-item>
/> <settings-select
<div class="white--text text-center font-weight-bold">{{p}}</div> :items="settings.readingDirections"
</div> v-model="readingDirection"
</v-row> label="Reading Direction"
</v-container> >
</v-card-text> <template v-slot:item="data" >
</v-card> <div class="text-capitalize">
{{ readingDirectionDisplay(data.item) }}
</div>
</template>
<template v-slot:selection="data">
<div class="text-capitalize">
{{ readingDirectionDisplay(data.item) }}
</div>
</template>
</settings-select>
</v-list-item>
<v-list-item>
<settings-select
:items="settings.imageFits"
v-model="imageFit"
label="Scaling"
>
<template v-slot:item="data">
<div class="text-capitalize">
{{ imageFitDisplay(data.item) }}
</div>
</template>
<template v-slot:selection="data">
<div class="text-capitalize">
{{ imageFitDisplay(data.item) }}
</div>
</template>
</settings-select>
</v-list-item>
</v-list>
</v-container>
</v-dialog> </v-dialog>
<v-snackbar <v-snackbar
v-model="jumpToPreviousBook" v-model="jumpToPreviousBook"
:timeout="jumpConfirmationDelay" :timeout="jumpConfirmationDelay"
@ -309,23 +248,38 @@
<p v-else>Click or press next again<br/>to exit the reader.</p> <p v-else>Click or press next again<br/>to exit the reader.</p>
</div> </div>
</v-snackbar> </v-snackbar>
</v-container>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import SettingsSwitch from '@/components/SettingsSwitch.vue'
import SettingsSelect from '@/components/SettingsSelect.vue'
import ThumbnailExplorerDialog from '@/components/ThumbnailExplorerDialog.vue'
import { checkWebpFeature } from '@/functions/check-webp' import { checkWebpFeature } from '@/functions/check-webp'
import { bookPageThumbnailUrl, bookPageUrl } from '@/functions/urls' import { bookPageUrl } from '@/functions/urls'
import { ImageFit } from '@/types/common' import { ImageFit, ReadingDirection } from '@/types/common'
import Vue from 'vue' import Vue from 'vue'
import { getBookTitleCompact } from '@/functions/book-title' import { getBookTitleCompact } from '@/functions/book-title'
const cookieFit = 'webreader.fit' const cookieFit = 'webreader.fit'
const cookieRtl = 'webreader.rtl' const cookieReadingDirection = 'webreader.readingDirection'
const cookieDoublePages = 'webreader.doublePages' const cookieDoublePages = 'webreader.doublePages'
const cookieAnimations = 'webreader.animations'
const fitDisplay = {
[ImageFit.HEIGHT]: 'fit to height',
[ImageFit.WIDTH]: 'fit to width',
[ImageFit.ORIGINAL]: 'original'
}
const dirDisplay = {
[ReadingDirection.RightToLeft]: 'right to left',
[ReadingDirection.LeftToRight]: 'left to right'
}
export default Vue.extend({ export default Vue.extend({
name: 'BookReader', name: 'BookReader',
components: { SettingsSwitch, SettingsSelect, ThumbnailExplorerDialog },
data: () => { data: () => {
return { return {
ImageFit, ImageFit,
@ -340,15 +294,19 @@ export default Vue.extend({
supportedMediaTypes: ['image/jpeg', 'image/png', 'image/gif'], supportedMediaTypes: ['image/jpeg', 'image/png', 'image/gif'],
convertTo: 'jpeg', convertTo: 'jpeg',
carouselPage: 0, carouselPage: 0,
showThumbnailsExplorer: false,
toolbar: false,
menu: false,
dialogGoto: false,
goToPage: 1, goToPage: 1,
showMenu: false, settings: {
fitButtons: 1, doublePages: false,
fit: ImageFit.HEIGHT, imageFits: Object.values(ImageFit),
rtlButtons: 0, fit: ImageFit.HEIGHT,
rtl: false, readingDirections: Object.values(ReadingDirection),
doublePages: false, readingDirection: ReadingDirection.RightToLeft,
doublePagesButtons: 0, animations: true
showThumbnailsExplorer: false }
} }
}, },
created () { created () {
@ -362,20 +320,10 @@ export default Vue.extend({
window.addEventListener('keydown', this.keyPressed) window.addEventListener('keydown', this.keyPressed)
this.setup(this.bookId, Number(this.$route.query.page)) this.setup(this.bookId, Number(this.$route.query.page))
// restore options for RTL, fit, and double pages this.loadFromCookie(cookieReadingDirection, (v) => { this.readingDirection = v })
if (this.$cookies.isKey(cookieRtl)) { this.loadFromCookie(cookieAnimations, (v) => { this.animations = (v === 'true') })
if (this.$cookies.get(cookieRtl) === 'true') { this.loadFromCookie(cookieDoublePages, (v) => { this.doublePages = (v === 'true') })
this.setRtl(true) this.loadFromCookie(cookieFit, (v) => { if (v) { this.imageFit = v } })
}
}
if (this.$cookies.isKey(cookieFit)) {
this.setFit(this.$cookies.get(cookieFit))
}
if (this.$cookies.isKey(cookieDoublePages)) {
if (this.$cookies.get(cookieDoublePages) === 'true') {
this.setDoublePages(true)
}
}
}, },
destroyed () { destroyed () {
window.removeEventListener('keydown', this.keyPressed) window.removeEventListener('keydown', this.keyPressed)
@ -421,8 +369,8 @@ export default Vue.extend({
progress (): number { progress (): number {
return this.currentPage / this.pagesCount * 100 return this.currentPage / this.pagesCount * 100
}, },
maxHeight (): number | string { maxHeight (): number | null {
return this.fit === ImageFit.HEIGHT ? this.$vuetify.breakpoint.height : 'auto' return this.imageFit === ImageFit.HEIGHT ? this.$vuetify.breakpoint.height : null
}, },
slidesRange (): number[] { slidesRange (): number[] {
if (!this.doublePages) { if (!this.doublePages) {
@ -442,18 +390,69 @@ export default Vue.extend({
}, },
bookTitle (): string { bookTitle (): string {
return getBookTitleCompact(this.book.name, this.series.name) return getBookTitleCompact(this.book.name, this.series.name)
},
animations: {
get: function (): boolean {
return this.settings.animations
},
set: function (animations: boolean): void {
this.settings.animations = animations
this.$cookies.set(cookieAnimations, animations, Infinity)
}
},
readingDirection: {
get: function (): ReadingDirection {
return this.settings.readingDirection
},
set: function (readingDirection: ReadingDirection): void {
this.settings.readingDirection = readingDirection
this.$cookies.set(cookieReadingDirection, readingDirection, Infinity)
}
},
flipDirection (): boolean {
switch (this.readingDirection) {
case ReadingDirection.LeftToRight:
return false
case ReadingDirection.RightToLeft:
default:
return true
}
},
imageFit: {
get: function (): ImageFit {
return this.settings.fit
},
set: function (fit: ImageFit): void {
this.settings.fit = fit
this.$cookies.set(cookieFit, fit, Infinity)
}
},
doublePages: {
get: function (): boolean {
return this.settings.doublePages
},
set: function (doublePages: boolean): void {
const current = this.currentPage
this.settings.doublePages = doublePages
this.goTo(current)
this.$cookies.set(cookieDoublePages, doublePages, Infinity)
}
} }
}, },
methods: { methods: {
swipe (direction: string) {
alert(direction)
},
keyPressed (e: KeyboardEvent) { keyPressed (e: KeyboardEvent) {
switch (e.key) { switch (e.key) {
case 'PageUp': case 'PageUp':
case 'ArrowRight': case 'ArrowRight':
this.rtl ? this.prev() : this.next() this.flipDirection ? this.prev() : this.next()
break break
case 'PageDown': case 'PageDown':
case 'ArrowLeft': case 'ArrowLeft':
this.rtl ? this.next() : this.prev() this.flipDirection ? this.next() : this.prev()
break break
case 'Home': case 'Home':
this.goToFirst() this.goToFirst()
@ -462,7 +461,7 @@ export default Vue.extend({
this.goToLast() this.goToLast()
break break
case 'm': case 'm':
this.showMenu = !this.showMenu this.toolbar = !this.toolbar
break break
case 't': case 't':
this.showThumbnailsExplorer = !this.showThumbnailsExplorer this.showThumbnailsExplorer = !this.showThumbnailsExplorer
@ -499,8 +498,11 @@ export default Vue.extend({
return bookPageUrl(this.bookId, page) return bookPageUrl(this.bookId, page)
} }
}, },
getThumbnailUrl (page: number): string { turnRight () {
return bookPageThumbnailUrl(this.bookId, page) return this.flipDirection ? this.prev() : this.next()
},
turnLeft () {
return this.flipDirection ? this.next() : this.prev()
}, },
prev () { prev () {
if (this.canPrev) { if (this.canPrev) {
@ -555,33 +557,6 @@ export default Vue.extend({
closeBook () { closeBook () {
this.$router.push({ name: 'browse-book', params: { bookId: this.bookId.toString() } }) this.$router.push({ name: 'browse-book', params: { bookId: this.bookId.toString() } })
}, },
setRtl (rtl: boolean) {
this.rtl = rtl
this.rtlButtons = rtl ? 1 : 0
this.$cookies.set(cookieRtl, rtl, Infinity)
},
setFit (fit: ImageFit) {
this.fit = fit
switch (fit) {
case ImageFit.WIDTH:
this.fitButtons = 0
break
case ImageFit.HEIGHT:
this.fitButtons = 1
break
case ImageFit.ORIGINAL:
this.fitButtons = 2
break
}
this.$cookies.set(cookieFit, fit, Infinity)
},
setDoublePages (doublePages: boolean) {
const current = this.currentPage
this.doublePages = doublePages
this.goTo(current)
this.doublePagesButtons = doublePages ? 1 : 0
this.$cookies.set(cookieDoublePages, doublePages, Infinity)
},
toSinglePages (i: number): number { toSinglePages (i: number): number {
if (i === 1) return 1 if (i === 1) return 1
if (i === this.slidesCount) return this.pagesCount if (i === this.slidesCount) return this.pagesCount
@ -597,46 +572,69 @@ export default Vue.extend({
eagerLoad (p: number): boolean { eagerLoad (p: number): boolean {
return Math.abs(this.currentPage - p) <= 2 return Math.abs(this.currentPage - p) <= 2
}, },
maxWidth (p: number): number | string { maxWidth (p: number): number | null {
if (this.fit !== ImageFit.WIDTH) { if (this.imageFit !== ImageFit.WIDTH) {
return 'auto' return null
} }
if (this.doublePages && p !== 1 && p !== this.pagesCount) { if (this.doublePages && p !== 1 && p !== this.pagesCount) {
return this.$vuetify.breakpoint.width / 2 return this.$vuetify.breakpoint.width / 2
} }
return this.$vuetify.breakpoint.width return this.$vuetify.breakpoint.width
},
imageFitDisplay (fit: ImageFit): string {
return fitDisplay[fit]
},
readingDirectionDisplay (dir: ReadingDirection): string {
return dirDisplay[dir]
},
loadFromCookie (cookieKey: string, setter: (value: any) => void): void {
if (this.$cookies.isKey(cookieKey)) {
let value = this.$cookies.get(cookieKey)
setter(value)
}
} }
} }
}) })
</script> </script>
<style scoped> <style scoped>
.fixed-position {
position: fixed; .reader-background {
background-color: white; /* TODO add a setting for this, some books might not be white */
}
.settings {
/*position: absolute;*/
z-index: 2;
}
.top {
top: 0;
} }
.full-height { .full-height {
top: 0;
height: 100%; height: 100%;
} }
.full-width {
width: 100%;
}
.left-quarter { .left-quarter {
left: 0; left: 0;
width: 20%; width: 20%;
position: absolute;
} }
.right-quarter { .right-quarter {
right: 0; right: 0;
width: 20%; width: 20%;
position: absolute;
} }
.center-half { .center-half {
left: 20%; left: 20%;
width: 60%; width: 60%;
} position: absolute;
.dashed-x {
border-left: 4px dashed;
border-right: 4px dashed;
} }
</style> </style>