mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-30 23:54:30 +02:00
Add:Progress for comics #738
This commit is contained in:
parent
c8b5cefeb5
commit
543ac209e4
7 changed files with 236 additions and 51 deletions
|
@ -1,11 +1,6 @@
|
|||
<template>
|
||||
<div id="comic-reader" class="w-full h-full relative">
|
||||
<div v-show="showPageMenu" v-click-outside="clickOutsideObj" class="pagemenu absolute top-12 right-16 rounded-md overflow-y-auto bg-bg shadow-lg z-20 border border-gray-400 w-52">
|
||||
<div v-for="(file, index) in pages" :key="file" class="w-full cursor-pointer hover:bg-black-200 px-2 py-1" :class="page === index ? 'bg-black-200' : ''" @click.stop="setPage(index)">
|
||||
<p class="text-sm truncate">{{ file }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="showInfoMenu" v-click-outside="clickedOutsideInfoMenu" class="pagemenu absolute top-12 right-0 rounded-md overflow-y-auto bg-bg shadow-lg z-20 border border-gray-400 w-full" @click.stop.prevent>
|
||||
<div v-show="showInfoMenu" v-click-outside="clickedOutside" class="pagemenu absolute top-8 left-0 rounded-md overflow-y-auto bg-bg shadow-lg z-20 border border-gray-400 w-full" @click.stop.prevent>
|
||||
<div v-for="key in comicMetadataKeys" :key="key" class="w-full px-2 py-1">
|
||||
<p class="text-xs">
|
||||
<strong>{{ key }}</strong>
|
||||
|
@ -14,14 +9,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="comicMetadata" class="absolute top-0 right-3 bg-bg text-gray-100 border-b border-l border-r border-gray-400 hover:bg-black-200 cursor-pointer rounded-b-md w-10 h-9 flex items-center justify-center text-center z-20" @mousedown.prevent @click.stop.prevent="clickShowInfoMenu">
|
||||
<span class="material-icons text-lg">more</span>
|
||||
<div v-if="comicMetadata" id="btn-metadata" class="absolute top-0 left-14 bg-bg text-gray-100 border-b border-l border-r border-gray-400 hover:bg-black-200 cursor-pointer rounded-b-md w-8 h-7 flex items-center justify-center text-center z-20" @mousedown.prevent @click.stop.prevent="clickShowInfoMenu">
|
||||
<span class="material-icons text-base pointer-events-none">more</span>
|
||||
</div>
|
||||
<div class="absolute top-0 right-16 bg-bg text-gray-100 border-b border-l border-r border-gray-400 hover:bg-black-200 cursor-pointer rounded-b-md w-10 h-9 flex items-center justify-center text-center z-20" @mousedown.prevent @mouseup.prevent @click.stop.prevent="clickShowPageMenu">
|
||||
<span class="material-icons text-lg">menu</span>
|
||||
<div v-if="numPages" id="btn-pages" class="absolute top-0 left-3 bg-bg text-gray-100 border-b border-l border-r border-gray-400 hover:bg-black-200 cursor-pointer rounded-b-md w-8 h-7 flex items-center justify-center text-center z-20" @mousedown.prevent @mouseup.prevent @click.stop.prevent="clickShowPageMenu">
|
||||
<span class="material-icons text-base pointer-events-none">menu</span>
|
||||
</div>
|
||||
<div class="absolute top-0 left-3 bg-bg text-gray-100 border-b border-l border-r border-gray-400 rounded-b-md px-2 h-9 flex items-center text-center z-20" @click.stop>
|
||||
<p class="font-mono">{{ page + 1 }} / {{ numPages }}</p>
|
||||
<div v-if="numPages" class="absolute top-0 right-3 bg-bg text-gray-100 border-b border-l border-r border-gray-400 rounded-b-md px-2 h-7 flex items-center text-center z-20" @click.stop>
|
||||
<p class="text-sm">{{ page }} / {{ numPages }}</p>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden m-auto comicwrapper relative">
|
||||
|
@ -33,6 +28,8 @@
|
|||
<ui-loading-indicator />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<modals-dialog v-model="showPageMenu" :items="pageItems" :selected="page" :width="360" item-padding-y="8px" @action="setPage" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -51,7 +48,9 @@ export default {
|
|||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
isLocal: Boolean,
|
||||
keepProgress: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -66,11 +65,7 @@ export default {
|
|||
loadTimeout: null,
|
||||
loadedFirstPage: false,
|
||||
comicMetadata: null,
|
||||
clickOutsideObj: {
|
||||
handler: this.clickedOutside,
|
||||
events: ['mousedown'],
|
||||
isActive: true
|
||||
}
|
||||
pageMenuWidth: 256
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -85,17 +80,112 @@ export default {
|
|||
userToken() {
|
||||
return this.$store.getters['user/getToken']
|
||||
},
|
||||
libraryItemId() {
|
||||
return this.libraryItem?.id
|
||||
},
|
||||
localLibraryItem() {
|
||||
if (this.isLocal) return this.libraryItem
|
||||
return this.libraryItem.localLibraryItem || null
|
||||
},
|
||||
localLibraryItemId() {
|
||||
return this.localLibraryItem?.id
|
||||
},
|
||||
serverLibraryItemId() {
|
||||
if (!this.isLocal) return this.libraryItem.id
|
||||
// Check if local library item is connected to the current server
|
||||
if (!this.libraryItem.serverAddress || !this.libraryItem.libraryItemId) return null
|
||||
if (this.$store.getters['user/getServerAddress'] === this.libraryItem.serverAddress) {
|
||||
return this.libraryItem.libraryItemId
|
||||
}
|
||||
return null
|
||||
},
|
||||
comicMetadataKeys() {
|
||||
return this.comicMetadata ? Object.keys(this.comicMetadata) : []
|
||||
},
|
||||
canGoNext() {
|
||||
return this.page < this.numPages - 1
|
||||
return this.page < this.numPages
|
||||
},
|
||||
canGoPrev() {
|
||||
return this.page > 0
|
||||
return this.page > 1
|
||||
},
|
||||
userItemProgress() {
|
||||
if (this.isLocal) return this.localItemProgress
|
||||
return this.serverItemProgress
|
||||
},
|
||||
localItemProgress() {
|
||||
return this.$store.getters['globals/getLocalMediaProgressById'](this.localLibraryItemId)
|
||||
},
|
||||
serverItemProgress() {
|
||||
return this.$store.getters['user/getUserMediaProgress'](this.serverLibraryItemId)
|
||||
},
|
||||
savedPage() {
|
||||
if (!this.keepProgress) return 0
|
||||
|
||||
// Validate ebookLocation is a number
|
||||
if (!this.userItemProgress?.ebookLocation || isNaN(this.userItemProgress.ebookLocation)) return 0
|
||||
return Number(this.userItemProgress.ebookLocation)
|
||||
},
|
||||
selectedCleanedPage() {
|
||||
return this.cleanedPageNames[this.page - 1]
|
||||
},
|
||||
cleanedPageNames() {
|
||||
return (
|
||||
this.pages?.map((p) => {
|
||||
if (p.length > 40) {
|
||||
let firstHalf = p.slice(0, 18)
|
||||
let lastHalf = p.slice(p.length - 17)
|
||||
return `${firstHalf} ... ${lastHalf}`
|
||||
}
|
||||
return p
|
||||
}) || []
|
||||
)
|
||||
},
|
||||
pageItems() {
|
||||
let index = 1
|
||||
return this.cleanedPageNames.map((p) => {
|
||||
return {
|
||||
text: p,
|
||||
value: index++
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async updateProgress() {
|
||||
if (!this.keepProgress) return
|
||||
|
||||
if (!this.numPages) {
|
||||
console.error('Num pages not loaded')
|
||||
return
|
||||
}
|
||||
if (this.savedPage === this.page) {
|
||||
return
|
||||
}
|
||||
|
||||
const payload = {
|
||||
ebookLocation: String(this.page),
|
||||
ebookProgress: Math.max(0, Math.min(1, (Number(this.page) - 1) / Number(this.numPages)))
|
||||
}
|
||||
|
||||
// Update local item
|
||||
if (this.localLibraryItemId) {
|
||||
const localPayload = {
|
||||
localLibraryItemId: this.localLibraryItemId,
|
||||
...payload
|
||||
}
|
||||
const localResponse = await this.$db.updateLocalEbookProgress(localPayload)
|
||||
if (localResponse.localMediaProgress) {
|
||||
this.$store.commit('globals/updateLocalMediaProgress', localResponse.localMediaProgress)
|
||||
}
|
||||
}
|
||||
|
||||
// Update server item
|
||||
if (this.serverLibraryItemId) {
|
||||
this.$axios.$patch(`/api/me/progress/${this.serverLibraryItemId}`, payload).catch((error) => {
|
||||
console.error('ComicReader.updateProgress failed:', error)
|
||||
})
|
||||
}
|
||||
},
|
||||
clickShowInfoMenu() {
|
||||
this.showInfoMenu = !this.showInfoMenu
|
||||
this.showPageMenu = false
|
||||
|
@ -104,11 +194,10 @@ export default {
|
|||
this.showPageMenu = !this.showPageMenu
|
||||
this.showInfoMenu = false
|
||||
},
|
||||
clickedOutside() {
|
||||
this.showPageMenu = false
|
||||
},
|
||||
clickedOutsideInfoMenu() {
|
||||
this.showInfoMenu = false
|
||||
clickedOutside(e) {
|
||||
if (e.target?.id == 'btn-metadata') return
|
||||
|
||||
if (this.showInfoMenu) this.showInfoMenu = false
|
||||
},
|
||||
next() {
|
||||
if (!this.canGoNext) return
|
||||
|
@ -118,13 +207,15 @@ export default {
|
|||
if (!this.canGoPrev) return
|
||||
this.setPage(this.page - 1)
|
||||
},
|
||||
setPage(index) {
|
||||
if (index < 0 || index > this.numPages - 1) {
|
||||
setPage(page) {
|
||||
if (page <= 0 || page > this.numPages) {
|
||||
return
|
||||
}
|
||||
this.showPageMenu = false
|
||||
const filename = this.pages[index]
|
||||
this.page = index
|
||||
const filename = this.pages[page - 1]
|
||||
this.page = page
|
||||
|
||||
this.updateProgress()
|
||||
return this.extractFile(filename)
|
||||
},
|
||||
setLoadTimeout() {
|
||||
|
@ -154,7 +245,6 @@ export default {
|
|||
},
|
||||
async extract() {
|
||||
this.loading = true
|
||||
console.log('Extracting', this.url)
|
||||
|
||||
var buff = await this.$axios.$get(this.url, {
|
||||
responseType: 'blob',
|
||||
|
@ -176,9 +266,28 @@ export default {
|
|||
|
||||
this.numPages = this.pages.length
|
||||
|
||||
// Calculate page menu size
|
||||
const largestFilename = this.cleanedPageNames
|
||||
.map((p) => p)
|
||||
.sort((a, b) => a.length - b.length)
|
||||
.pop()
|
||||
const pEl = document.createElement('p')
|
||||
pEl.innerText = largestFilename
|
||||
pEl.style.fontSize = '0.875rem'
|
||||
pEl.style.opacity = 0
|
||||
pEl.style.position = 'absolute'
|
||||
document.body.appendChild(pEl)
|
||||
const textWidth = pEl.getBoundingClientRect()?.width
|
||||
if (textWidth) {
|
||||
this.pageMenuWidth = textWidth + (16 + 5 + 2 + 5)
|
||||
}
|
||||
pEl.remove()
|
||||
|
||||
if (this.pages.length) {
|
||||
this.loading = false
|
||||
await this.setPage(0)
|
||||
|
||||
const startPage = this.savedPage > 0 && this.savedPage <= this.numPages ? this.savedPage : 1
|
||||
await this.setPage(startPage)
|
||||
this.loadedFirstPage = true
|
||||
} else {
|
||||
this.$toast.error('Unable to extract pages')
|
||||
|
@ -206,7 +315,6 @@ export default {
|
|||
return flattenObject(filesObject)
|
||||
},
|
||||
async extractXmlFile(filename) {
|
||||
console.log('extracting xml filename', filename)
|
||||
try {
|
||||
var file = await this.filesObject[filename].extract()
|
||||
var reader = new FileReader()
|
||||
|
@ -272,7 +380,7 @@ export default {
|
|||
}
|
||||
|
||||
.pagemenu {
|
||||
max-height: calc(100% - 80px);
|
||||
max-height: calc(100% - 20px);
|
||||
}
|
||||
.comicimg {
|
||||
height: 100%;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue