220 lines
5.5 KiB
Vue
220 lines
5.5 KiB
Vue
<template>
|
|
<div class="reader" v-shortkey="{left: ['arrowleft'], right: ['arrowright']}" @shortkey="navigation">
|
|
<div class="page-container">
|
|
<img ref="currentImage" :style="getRealPageStyle()" :src="info.pages[page]" @load="setScroll(); setOrientation()">
|
|
<!-- prefetching -->
|
|
<img style="display: none;" :src="info.pages[page-1]">
|
|
<img style="display: none;" :src="info.pages[page+1]">
|
|
</div>
|
|
<div class="sidebar" :style="{ display: showSidebar ? '' : 'none' }">
|
|
<h3>{{ info.title }}</h3>
|
|
<router-link :to="'/series/' + info.series">All Volumes</router-link>
|
|
<hr/>
|
|
<div class="sidebar-item">{{ page + 1 }}/{{ info.pages.length }}</div>
|
|
<div class="page-display-settings-wrapper">
|
|
<div class="page-display-settings-item">
|
|
<label for="page-width">Width</label>
|
|
<input id="page-width" v-model.number="pageDisplay.width" type="number" min="5" max="100" step="5">
|
|
</div>
|
|
<div class="page-display-settings-item">
|
|
<label for="page-height">Height</label>
|
|
<input id="page-height" v-model.number="pageDisplay.height" type="number" min="5" max="100" step="5">
|
|
</div>
|
|
<div>
|
|
<button class="sidebar-item" id="display-mode" v-on:click="cyclePageDisplayMode()">Fit: {{ pageDisplay.mode }}</button>
|
|
</div>
|
|
</div>
|
|
<button class="sidebar-item" v-on:click="changeDirection">{{ direction.toUpperCase() }}</button>
|
|
<button class="hide" v-on:click="showSidebar = false">Hide</button>
|
|
</div>
|
|
<div class="sidebar-enabler" :style="{ display: showSidebar ? 'none' : '' }" v-on:click="showSidebar = true"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import API from '@/api-client.js'
|
|
|
|
export default {
|
|
name: 'Reader',
|
|
data () {
|
|
return {
|
|
info: {
|
|
pages: []
|
|
},
|
|
scrollPositions: [],
|
|
pageDisplay: {
|
|
width: 60,
|
|
height: 100,
|
|
mode: 'width'
|
|
},
|
|
showSidebar: true,
|
|
direction: 'rtl',
|
|
orientation: 'vertical'
|
|
}
|
|
},
|
|
computed: {
|
|
page: {
|
|
get () {
|
|
if (this.$route.hash) {
|
|
return this.$route.hash.substr(1) - 1
|
|
} else {
|
|
return 0
|
|
}
|
|
},
|
|
set (value) {
|
|
this.$router.push({ hash: '#' + (value + 1) })
|
|
}
|
|
}
|
|
},
|
|
mounted () {
|
|
API.getVolumeInfo(this.$route.params.id, info => (this.info = info))
|
|
|
|
if (localStorage.pageDisplay) {
|
|
this.pageDisplay = JSON.parse(localStorage.pageDisplay)
|
|
}
|
|
},
|
|
watch: {
|
|
pageDisplay: {
|
|
handler (newPageDisplay) {
|
|
localStorage.pageDisplay = JSON.stringify(newPageDisplay)
|
|
},
|
|
deep: true
|
|
}
|
|
},
|
|
methods: {
|
|
setPage (page) {
|
|
// save scroll position
|
|
this.scrollPositions[this.page] = window.pageYOffset
|
|
|
|
// set page
|
|
if (page >= 0 && page < this.info.pages.length) {
|
|
this.page = page
|
|
}
|
|
},
|
|
|
|
setScroll () {
|
|
// load saved scroll posotion, if available, reset to 0 otherwise
|
|
if (this.scrollPositions[this.page]) {
|
|
window.scrollTo(0, this.scrollPositions[this.page])
|
|
} else {
|
|
window.scrollTo(0, 0)
|
|
}
|
|
},
|
|
|
|
navigation (event) {
|
|
if ((event.srcKey === 'right' && this.direction === 'ltr') || (event.srcKey === 'left' && this.direction === 'rtl')) {
|
|
this.setPage(this.page + 1)
|
|
} else if ((event.srcKey === 'left' && this.direction === 'ltr') || (event.srcKey === 'right' && this.direction === 'rtl')) {
|
|
this.setPage(this.page - 1)
|
|
} else {
|
|
throw new Error('cannot nagivate: invalid parameters')
|
|
}
|
|
},
|
|
|
|
changeDirection () {
|
|
if (this.direction === 'rtl') {
|
|
this.direction = 'ltr'
|
|
} else if (this.direction === 'ltr') {
|
|
this.direction = 'rtl'
|
|
} else {
|
|
throw new Error('cannot change reading direction: invalid direction currently set')
|
|
}
|
|
},
|
|
|
|
setOrientation () {
|
|
const image = this.$refs.currentImage
|
|
|
|
if (image.width < image.height) {
|
|
this.orientation = 'vertical'
|
|
} else if (image.width > image.height) {
|
|
this.orientation = 'horizontal'
|
|
} else if (image.width === image.height) {
|
|
this.orientation = 'square'
|
|
}
|
|
},
|
|
|
|
getRealPageStyle () {
|
|
let width = 'auto'
|
|
let height = 'auto'
|
|
|
|
if (this.pageDisplay.mode === 'width') {
|
|
if (this.orientation === 'horizontal') {
|
|
width = '100%'
|
|
} else {
|
|
width = this.pageDisplay.width + '%'
|
|
}
|
|
} else if (this.pageDisplay.mode === 'height') {
|
|
height = this.pageDisplay.height + 'vh'
|
|
}
|
|
|
|
return {
|
|
width: width,
|
|
height: height
|
|
}
|
|
},
|
|
|
|
cyclePageDisplayMode () {
|
|
const pageDisplayModes = ['width', 'height', 'auto']
|
|
|
|
this.pageDisplay.mode = pageDisplayModes[(pageDisplayModes.indexOf(this.pageDisplay.mode) + 1) % pageDisplayModes.length]
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.reader {
|
|
display: flex;
|
|
position: relative;
|
|
}
|
|
|
|
.page-container {
|
|
flex-grow: 1;
|
|
}
|
|
|
|
img {
|
|
vertical-align: top;
|
|
}
|
|
|
|
.sidebar-enabler {
|
|
position: absolute;
|
|
top: 0px;
|
|
right: 0px;
|
|
height: 100%;
|
|
width: 250px;
|
|
}
|
|
|
|
.sidebar {
|
|
flex: 0 0 250px;
|
|
background-color: #eee;
|
|
height: 100vh;
|
|
position: sticky;
|
|
top: 0;
|
|
}
|
|
|
|
.sidebar-item {
|
|
margin: 0.5em 0;
|
|
}
|
|
|
|
button.hide {
|
|
bottom: 1em;
|
|
right: 1em;
|
|
position: absolute;
|
|
}
|
|
|
|
.page-display-settings-wrapper {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
|
|
.page-display-settings-item {
|
|
margin: 10px;
|
|
width: 33%;
|
|
|
|
input {
|
|
width: 100%;
|
|
}
|
|
}
|
|
}
|
|
</style>
|