mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-08 21:08:43 +02:00
Add download UI indicator, download progress, update bookshelf item to show local items and items matches with local item, remove item before downloading if already exists in file system
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div ref="progressbar" class="progressbar">
|
||||
<svg class="progressbar__svg">
|
||||
<circle cx="20" cy="20" r="17.5" ref="circle" class="progressbar__svg-circle circle-anim"></circle>
|
||||
<circle cx="20" cy="20" r="17.5" class="progressbar__svg-circlebg"></circle>
|
||||
</svg>
|
||||
<p class="progressbar__text text-sm text-warning">{{ count }}</p>
|
||||
<!-- <span class="material-icons progressbar__text text-xl">arrow_downward</span> -->
|
||||
<!-- <div class="w-4 h-4 rounded-full bg-red-600 absolute bottom-1 right-1 flex items-center justify-center transform rotate-90">
|
||||
<p class="text-xs text-white">4</p>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: Number,
|
||||
count: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lastProgress: 0,
|
||||
updateTimeout: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(newVal, oldVal) {
|
||||
this.updateProgress()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
updateProgress() {
|
||||
var progbar = this.$refs.progressbar
|
||||
var circle = this.$refs.circle
|
||||
if (!progbar || !circle) return
|
||||
|
||||
clearTimeout(this.updateTimeout)
|
||||
var progress = Math.min(this.value || 0, 1)
|
||||
|
||||
progbar.style.setProperty('--progress-percent-before', this.lastProgress)
|
||||
progbar.style.setProperty('--progress-percent', progress)
|
||||
|
||||
this.lastProgress = progress
|
||||
circle.classList.remove('circle-static')
|
||||
circle.classList.add('circle-anim')
|
||||
this.updateTimeout = setTimeout(() => {
|
||||
circle.classList.remove('circle-anim')
|
||||
circle.classList.add('circle-static')
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* https://codepen.io/alvarotrigo/pen/VwMvydQ */
|
||||
.progressbar {
|
||||
position: relative;
|
||||
width: 42.5px;
|
||||
height: 42.5px;
|
||||
margin: 0.25em;
|
||||
transform: rotate(-90deg);
|
||||
box-sizing: border-box;
|
||||
--progress-percent-before: 0;
|
||||
--progress-percent: 0;
|
||||
}
|
||||
|
||||
.progressbar__svg {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.progressbar__svg-circlebg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: none;
|
||||
stroke-width: 4;
|
||||
/* stroke-dasharray: 110;
|
||||
stroke-dashoffset: 110; */
|
||||
stroke: #fb8c0022;
|
||||
stroke-linecap: round;
|
||||
transform: translate(2px, 2px);
|
||||
}
|
||||
|
||||
.progressbar__svg-circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: none;
|
||||
stroke-width: 4;
|
||||
stroke-dasharray: 110;
|
||||
stroke-dashoffset: 110;
|
||||
/* stroke: hsl(0, 0%, 100%); */
|
||||
stroke: #fb8c00;
|
||||
stroke-linecap: round;
|
||||
transform: translate(2px, 2px);
|
||||
}
|
||||
|
||||
.circle-anim {
|
||||
animation: anim_circle 0.5s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.circle-static {
|
||||
stroke-dashoffset: calc(110px - (110px * var(--progress-percent)));
|
||||
}
|
||||
|
||||
@keyframes anim_circle {
|
||||
from {
|
||||
stroke-dashoffset: calc(110px - (110px * var(--progress-percent-before)));
|
||||
}
|
||||
to {
|
||||
stroke-dashoffset: calc(110px - (110px * var(--progress-percent)));
|
||||
}
|
||||
}
|
||||
|
||||
.progressbar__text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: 1px;
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
animation: bounce 0.75s infinite;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(-35%, -50%) rotate(90deg);
|
||||
-webkit-animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
-webkit-animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div v-if="numPartsRemaining > 0">
|
||||
<widgets-circle-progress :value="progress" :count="numPartsRemaining" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { AbsDownloader } from '@/plugins/capacitor'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
updateListener: null,
|
||||
completeListener: null,
|
||||
itemDownloadingMap: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
numItemPartsComplete() {
|
||||
var total = 0
|
||||
Object.values(this.itemDownloadingMap).map((item) => (total += item.partsCompleted))
|
||||
return total
|
||||
},
|
||||
numPartsRemaining() {
|
||||
return this.numTotalParts - this.numItemPartsComplete
|
||||
},
|
||||
numTotalParts() {
|
||||
var total = 0
|
||||
Object.values(this.itemDownloadingMap).map((item) => (total += item.totalParts))
|
||||
return total
|
||||
},
|
||||
progress() {
|
||||
var numItems = Object.keys(this.itemDownloadingMap).length
|
||||
if (!numItems) return 0
|
||||
var totalProg = 0
|
||||
Object.values(this.itemDownloadingMap).map((item) => (totalProg += item.itemProgress))
|
||||
return totalProg / numItems
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onItemDownloadUpdate(data) {
|
||||
console.log('DownloadProgressIndicator onItemDownloadUpdate', JSON.stringify(data))
|
||||
if (!data || !data.downloadItemParts) {
|
||||
console.error('Invalid item update payload')
|
||||
return
|
||||
}
|
||||
var downloadItemParts = data.downloadItemParts
|
||||
var partsCompleted = 0
|
||||
var totalPartsProgress = 0
|
||||
var partsRemaining = 0
|
||||
downloadItemParts.forEach((dip) => {
|
||||
if (dip.completed) {
|
||||
totalPartsProgress += 1
|
||||
partsCompleted++
|
||||
} else {
|
||||
var progPercent = dip.progress / 100
|
||||
totalPartsProgress += progPercent
|
||||
partsRemaining++
|
||||
}
|
||||
})
|
||||
var itemProgress = totalPartsProgress / downloadItemParts.length
|
||||
|
||||
var update = {
|
||||
id: data.id,
|
||||
partsRemaining,
|
||||
partsCompleted,
|
||||
totalParts: downloadItemParts.length,
|
||||
itemProgress
|
||||
}
|
||||
data.itemProgress = itemProgress
|
||||
|
||||
console.log('Saving item update download payload', JSON.stringify(update))
|
||||
this.$set(this.itemDownloadingMap, update.id, update)
|
||||
|
||||
this.$store.commit('globals/addUpdateItemDownload', data)
|
||||
},
|
||||
onItemDownloadComplete(data) {
|
||||
console.log('DownloadProgressIndicator onItemDownloadComplete', JSON.stringify(data))
|
||||
if (!data || !data.libraryItemId) {
|
||||
console.error('Invalid item downlaod complete payload')
|
||||
return
|
||||
}
|
||||
|
||||
if (this.itemDownloadingMap[data.libraryItemId]) {
|
||||
delete this.itemDownloadingMap[data.libraryItemId]
|
||||
} else {
|
||||
console.warn('Item download complete but not found in item downloading map', data.libraryItemId)
|
||||
}
|
||||
if (!data.localLibraryItem) {
|
||||
this.$toast.error('Item download complete but failed to create library item')
|
||||
} else {
|
||||
this.$toast.success(`Item "${data.localLibraryItem.media.metadata.title}" download finished`)
|
||||
this.$eventBus.$emit('new-local-library-item', data.localLibraryItem)
|
||||
}
|
||||
|
||||
this.$store.commit('globals/removeItemDownload', data.libraryItemId)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateListener = AbsDownloader.addListener('onItemDownloadUpdate', (data) => this.onItemDownloadUpdate(data))
|
||||
this.completeListener = AbsDownloader.addListener('onItemDownloadComplete', (data) => this.onItemDownloadComplete(data))
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.updateListener) this.updateListener.remove()
|
||||
if (this.completeListener) this.completeListener.remove()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user