Add:Saving progress for PDF ebooks #1791

This commit is contained in:
advplyr 2023-05-24 16:41:16 -05:00
parent b4aa8f0c9a
commit 36477a832c
5 changed files with 125 additions and 82 deletions

View File

@ -76,6 +76,10 @@
<div ref="moreIcon" v-show="!isSelectionMode && moreMenuItems.length" class="hidden md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickShowMore"> <div ref="moreIcon" v-show="!isSelectionMode && moreMenuItems.length" class="hidden md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickShowMore">
<span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span> <span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span>
</div> </div>
<div v-if="ebookFormat" class="absolute" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }">
<span class="text-white/80" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ ebookFormat }}</span>
</div>
</div> </div>
<!-- Processing/loading spinner overlay --> <!-- Processing/loading spinner overlay -->
@ -221,7 +225,7 @@ export default {
libraryId() { libraryId() {
return this._libraryItem.libraryId return this._libraryItem.libraryId
}, },
hasEbook() { ebookFormat() {
return this.media.ebookFormat return this.media.ebookFormat
}, },
numTracks() { numTracks() {
@ -360,13 +364,13 @@ export default {
return this.store.getters['getIsStreamingFromDifferentLibrary'] return this.store.getters['getIsStreamingFromDifferentLibrary']
}, },
showReadButton() { showReadButton() {
return !this.isSelectionMode && !this.showPlayButton && this.hasEbook && (this.showExperimentalFeatures || this.enableEReader) return !this.isSelectionMode && !this.showPlayButton && this.ebookFormat && (this.showExperimentalFeatures || this.enableEReader)
}, },
showPlayButton() { showPlayButton() {
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode || this.isMusic) return !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode || this.isMusic)
}, },
showSmallEBookIcon() { showSmallEBookIcon() {
return !this.isSelectionMode && this.hasEbook && (this.showExperimentalFeatures || this.enableEReader) return !this.isSelectionMode && this.ebookFormat && (this.showExperimentalFeatures || this.enableEReader)
}, },
isMissing() { isMissing() {
return this._libraryItem.isMissing return this._libraryItem.isMissing
@ -865,7 +869,8 @@ export default {
this.createMoreMenu() this.createMoreMenu()
}, },
async clickReadEBook() { async clickReadEBook() {
var libraryItem = await this.$axios.$get(`/api/items/${this.libraryItemId}?expanded=1`).catch((error) => { const axios = this.$axios || this.$nuxt.$axios
var libraryItem = await axios.$get(`/api/items/${this.libraryItemId}?expanded=1`).catch((error) => {
console.error('Failed to get lirbary item', this.libraryItemId) console.error('Failed to get lirbary item', this.libraryItemId)
return null return null
}) })

View File

@ -19,7 +19,7 @@
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<div :style="{ width: pdfWidth + 'px', height: pdfHeight + 'px' }" class="w-full h-full overflow-auto"> <div :style="{ width: pdfWidth + 'px', height: pdfHeight + 'px' }" class="w-full h-full overflow-auto">
<div v-if="loadedRatio > 0 && loadedRatio < 1" style="background-color: green; color: white; text-align: center" :style="{ width: loadedRatio * 100 + '%' }">{{ Math.floor(loadedRatio * 100) }}%</div> <div v-if="loadedRatio > 0 && loadedRatio < 1" style="background-color: green; color: white; text-align: center" :style="{ width: loadedRatio * 100 + '%' }">{{ Math.floor(loadedRatio * 100) }}%</div>
<pdf ref="pdf" class="m-auto z-10 border border-black border-opacity-20 shadow-md" :src="url" :page="page" :rotate="rotate" @progress="loadedRatio = $event" @error="error" @num-pages="numPagesLoaded" @link-clicked="page = $event"></pdf> <pdf ref="pdf" class="m-auto z-10 border border-black border-opacity-20 shadow-md" :src="url" :page="page" :rotate="rotate" @progress="progressEvt" @error="error" @num-pages="numPagesLoaded" @link-clicked="page = $event" @loaded="loadedEvt"></pdf>
</div> </div>
</div> </div>
</div> </div>
@ -30,14 +30,18 @@
</template> </template>
<script> <script>
import pdf from 'vue-pdf' import pdf from '@teckel/vue-pdf'
export default { export default {
components: { components: {
pdf pdf
}, },
props: { props: {
url: String url: String,
libraryItem: {
type: Object,
default: () => {}
}
}, },
data() { data() {
return { return {
@ -48,6 +52,9 @@ export default {
} }
}, },
computed: { computed: {
libraryItemId() {
return this.libraryItem?.id
},
pdfWidth() { pdfWidth() {
return this.pdfHeight * 0.6667 return this.pdfHeight * 0.6667
}, },
@ -59,19 +66,50 @@ export default {
}, },
canGoPrev() { canGoPrev() {
return this.page > 1 return this.page > 1
},
userMediaProgress() {
if (!this.libraryItemId) return
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
},
savedPage() {
return Number(this.userMediaProgress?.ebookLocation || 0)
} }
}, },
methods: { methods: {
updateProgress() {
if (!this.numPages) {
console.error('Num pages not loaded')
return
}
const payload = {
ebookLocation: this.page,
ebookProgress: Math.max(0, Math.min(1, (Number(this.page) - 1) / Number(this.numPages)))
}
this.$axios.$patch(`/api/me/progress/${this.libraryItemId}`, payload).catch((error) => {
console.error('EpubReader.updateProgress failed:', error)
})
},
loadedEvt() {
if (this.savedPage && this.savedPage > 0 && this.savedPage <= this.numPages) {
this.page = this.savedPage
}
},
progressEvt(progress) {
this.loadedRatio = progress
},
numPagesLoaded(e) { numPagesLoaded(e) {
this.numPages = e this.numPages = e
}, },
prev() { prev() {
if (this.page <= 1) return if (this.page <= 1) return
this.page-- this.page--
this.updateProgress()
}, },
next() { next() {
if (this.page >= this.numPages) return if (this.page >= this.numPages) return
this.page++ this.page++
this.updateProgress()
}, },
error(err) { error(err) {
console.error(err) console.error(err)

144
client/package-lock.json generated
View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@nuxtjs/axios": "^5.13.6", "@nuxtjs/axios": "^5.13.6",
"@nuxtjs/proxy": "^2.1.0", "@nuxtjs/proxy": "^2.1.0",
"@teckel/vue-pdf": "^4.3.5",
"core-js": "^3.16.0", "core-js": "^3.16.0",
"cron-parser": "^4.7.1", "cron-parser": "^4.7.1",
"date-fns": "^2.25.0", "date-fns": "^2.25.0",
@ -21,7 +22,6 @@
"nuxt-socket-io": "^1.1.18", "nuxt-socket-io": "^1.1.18",
"trix": "^1.3.1", "trix": "^1.3.1",
"v-click-outside": "^3.1.2", "v-click-outside": "^3.1.2",
"vue-pdf": "^4.2.0",
"vue-toastification": "^1.7.11", "vue-toastification": "^1.7.11",
"vuedraggable": "^2.24.3" "vuedraggable": "^2.24.3"
}, },
@ -2983,6 +2983,43 @@
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
}, },
"node_modules/@teckel/vue-pdf": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/@teckel/vue-pdf/-/vue-pdf-4.3.5.tgz",
"integrity": "sha512-g2DAbZMPbPc7NPFImOsU/e7rt7wfdmBkmFa2kPsB4x+k+Bs8yC5Icmq/VnTSEq/Y8bNvEY7i6+JoicGnlfQL7Q==",
"dependencies": {
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"loader-utils": "^1.4.0",
"pdfjs-dist": "^2.5.207 <2.8.0",
"raw-loader": "^4.0.1",
"vue-resize-sensor": "^2.0.0",
"worker-loader": "^2.0.0"
}
},
"node_modules/@teckel/vue-pdf/node_modules/json5": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dependencies": {
"minimist": "^1.2.0"
},
"bin": {
"json5": "lib/cli.js"
}
},
"node_modules/@teckel/vue-pdf/node_modules/loader-utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
"integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/@types/anymatch": { "node_modules/@types/anymatch": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-3.0.0.tgz",
@ -15921,43 +15958,6 @@
"resolved": "https://registry.npmjs.org/vue-no-ssr/-/vue-no-ssr-1.1.1.tgz", "resolved": "https://registry.npmjs.org/vue-no-ssr/-/vue-no-ssr-1.1.1.tgz",
"integrity": "sha512-ZMjqRpWabMPqPc7gIrG0Nw6vRf1+itwf0Itft7LbMXs2g3Zs/NFmevjZGN1x7K3Q95GmIjWbQZTVerxiBxI+0g==" "integrity": "sha512-ZMjqRpWabMPqPc7gIrG0Nw6vRf1+itwf0Itft7LbMXs2g3Zs/NFmevjZGN1x7K3Q95GmIjWbQZTVerxiBxI+0g=="
}, },
"node_modules/vue-pdf": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/vue-pdf/-/vue-pdf-4.3.0.tgz",
"integrity": "sha512-zd3lJj6CbtrawgaaDDciTDjkJMUKiLWtbEmBg5CvFn9Noe9oAO/GNy/fc5c59qGuFCJ14ibIV1baw4S07e5bSQ==",
"dependencies": {
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"loader-utils": "^1.4.0",
"pdfjs-dist": "2.6.347",
"raw-loader": "^4.0.2",
"vue-resize-sensor": "^2.0.0",
"worker-loader": "^2.0.0"
}
},
"node_modules/vue-pdf/node_modules/json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dependencies": {
"minimist": "^1.2.0"
},
"bin": {
"json5": "lib/cli.js"
}
},
"node_modules/vue-pdf/node_modules/loader-utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
"integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/vue-resize-sensor": { "node_modules/vue-resize-sensor": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/vue-resize-sensor/-/vue-resize-sensor-2.0.0.tgz", "resolved": "https://registry.npmjs.org/vue-resize-sensor/-/vue-resize-sensor-2.0.0.tgz",
@ -19591,6 +19591,39 @@
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
}, },
"@teckel/vue-pdf": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/@teckel/vue-pdf/-/vue-pdf-4.3.5.tgz",
"integrity": "sha512-g2DAbZMPbPc7NPFImOsU/e7rt7wfdmBkmFa2kPsB4x+k+Bs8yC5Icmq/VnTSEq/Y8bNvEY7i6+JoicGnlfQL7Q==",
"requires": {
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"loader-utils": "^1.4.0",
"pdfjs-dist": "^2.5.207 <2.8.0",
"raw-loader": "^4.0.1",
"vue-resize-sensor": "^2.0.0",
"worker-loader": "^2.0.0"
},
"dependencies": {
"json5": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
"integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
}
}
}
},
"@types/anymatch": { "@types/anymatch": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-3.0.0.tgz",
@ -29618,39 +29651,6 @@
"resolved": "https://registry.npmjs.org/vue-no-ssr/-/vue-no-ssr-1.1.1.tgz", "resolved": "https://registry.npmjs.org/vue-no-ssr/-/vue-no-ssr-1.1.1.tgz",
"integrity": "sha512-ZMjqRpWabMPqPc7gIrG0Nw6vRf1+itwf0Itft7LbMXs2g3Zs/NFmevjZGN1x7K3Q95GmIjWbQZTVerxiBxI+0g==" "integrity": "sha512-ZMjqRpWabMPqPc7gIrG0Nw6vRf1+itwf0Itft7LbMXs2g3Zs/NFmevjZGN1x7K3Q95GmIjWbQZTVerxiBxI+0g=="
}, },
"vue-pdf": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/vue-pdf/-/vue-pdf-4.3.0.tgz",
"integrity": "sha512-zd3lJj6CbtrawgaaDDciTDjkJMUKiLWtbEmBg5CvFn9Noe9oAO/GNy/fc5c59qGuFCJ14ibIV1baw4S07e5bSQ==",
"requires": {
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"loader-utils": "^1.4.0",
"pdfjs-dist": "2.6.347",
"raw-loader": "^4.0.2",
"vue-resize-sensor": "^2.0.0",
"worker-loader": "^2.0.0"
},
"dependencies": {
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
"integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
}
}
}
},
"vue-resize-sensor": { "vue-resize-sensor": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/vue-resize-sensor/-/vue-resize-sensor-2.0.0.tgz", "resolved": "https://registry.npmjs.org/vue-resize-sensor/-/vue-resize-sensor-2.0.0.tgz",
@ -30753,4 +30753,4 @@
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
} }
} }
} }

View File

@ -15,6 +15,7 @@
"dependencies": { "dependencies": {
"@nuxtjs/axios": "^5.13.6", "@nuxtjs/axios": "^5.13.6",
"@nuxtjs/proxy": "^2.1.0", "@nuxtjs/proxy": "^2.1.0",
"@teckel/vue-pdf": "^4.3.5",
"core-js": "^3.16.0", "core-js": "^3.16.0",
"cron-parser": "^4.7.1", "cron-parser": "^4.7.1",
"date-fns": "^2.25.0", "date-fns": "^2.25.0",
@ -25,7 +26,6 @@
"nuxt-socket-io": "^1.1.18", "nuxt-socket-io": "^1.1.18",
"trix": "^1.3.1", "trix": "^1.3.1",
"v-click-outside": "^3.1.2", "v-click-outside": "^3.1.2",
"vue-pdf": "^4.2.0",
"vue-toastification": "^1.7.11", "vue-toastification": "^1.7.11",
"vuedraggable": "^2.24.3" "vuedraggable": "^2.24.3"
}, },
@ -37,4 +37,4 @@
"postcss": "^8.3.6", "postcss": "^8.3.6",
"tailwindcss": "^3.1.4" "tailwindcss": "^3.1.4"
} }
} }

View File

@ -10,7 +10,7 @@ class MediaProgress {
this.isFinished = false this.isFinished = false
this.hideFromContinueListening = false this.hideFromContinueListening = false
this.ebookLocation = null // current cfi tag this.ebookLocation = null // cfi tag for epub, page number for pdf
this.ebookProgress = null // 0 to 1 this.ebookProgress = null // 0 to 1
this.lastUpdate = null this.lastUpdate = null