mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-06 20:08:48 +02:00
Support for android 8+, selectable download location, permissions check, recursive delete
This commit is contained in:
@@ -3,12 +3,7 @@
|
||||
<template v-for="(shelf, index) in groupedBooks">
|
||||
<div :key="index" class="border-b border-opacity-10 w-full bookshelfRow py-4 flex justify-around relative">
|
||||
<template v-for="audiobook in shelf">
|
||||
<!-- <div :key="audiobook.id" class="relative px-4"> -->
|
||||
<cards-book-card :key="audiobook.id" :audiobook="audiobook" :width="cardWidth" :user-progress="userAudiobooks[audiobook.id]" :local-user-progress="localUserAudiobooks[audiobook.id]" />
|
||||
<!-- <nuxt-link :to="`/audiobook/${audiobook.id}`">
|
||||
<cards-book-cover :audiobook="audiobook" :width="cardWidth" class="mx-auto -mb-px" style="box-shadow: 4px 1px 8px #11111166, -4px 1px 8px #11111166, 1px -4px 8px #11111166" />
|
||||
</nuxt-link> -->
|
||||
<!-- </div> -->
|
||||
</template>
|
||||
<div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
|
||||
</div>
|
||||
@@ -93,16 +88,22 @@ export default {
|
||||
} else {
|
||||
console.log('Disconnected - Reset to local storage')
|
||||
this.$store.commit('audiobooks/reset')
|
||||
this.$store.dispatch('downloads/loadFromStorage')
|
||||
this.$store.dispatch('audiobooks/useDownloaded')
|
||||
// this.calcShelves()
|
||||
// this.$store.dispatch('downloads/loadFromStorage')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated })
|
||||
this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
|
||||
|
||||
window.addEventListener('resize', this.resize)
|
||||
|
||||
if (!this.$server) {
|
||||
console.error('Bookshelf mounted no server')
|
||||
return
|
||||
}
|
||||
|
||||
this.$server.on('connected', this.socketConnected)
|
||||
if (this.$server.connected) {
|
||||
this.$store.dispatch('audiobooks/load')
|
||||
@@ -115,6 +116,12 @@ export default {
|
||||
this.$store.commit('audiobooks/removeListener', 'bookshelf')
|
||||
this.$store.commit('user/removeSettingsListener', 'bookshelf')
|
||||
window.removeEventListener('resize', this.resize)
|
||||
|
||||
if (!this.$server) {
|
||||
console.error('Bookshelf beforeDestroy no server')
|
||||
return
|
||||
}
|
||||
this.$server.off('connected', this.socketConnected)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -128,6 +128,7 @@ export default {
|
||||
if (this.$refs.audioPlayerMini) {
|
||||
this.$refs.audioPlayerMini.terminateStream()
|
||||
}
|
||||
this.download = null
|
||||
this.$store.commit('setPlayingDownload', null)
|
||||
|
||||
this.$localStore.setCurrent(null)
|
||||
@@ -167,12 +168,6 @@ export default {
|
||||
}
|
||||
|
||||
if (this.$server.connected) {
|
||||
// var updateObj = {
|
||||
// audiobookId: this.download.id,
|
||||
// totalDuration: this.download.audiobook.duration,
|
||||
// clientCurrentTime: currentTime,
|
||||
// clientProgress: Number((currentTime / this.download.audiobook.duration).toFixed(3))
|
||||
// }
|
||||
this.$server.socket.emit('progress_update', progressUpdate)
|
||||
}
|
||||
this.$localStore.updateUserAudiobookProgress(progressUpdate).then(() => {
|
||||
@@ -184,7 +179,6 @@ export default {
|
||||
closeStream() {},
|
||||
streamClosed(audiobookId) {
|
||||
console.log('Stream Closed')
|
||||
|
||||
if (this.stream.audiobook.id === audiobookId || audiobookId === 'n/a') {
|
||||
this.$store.commit('setStreamAudiobook', null)
|
||||
}
|
||||
@@ -305,23 +299,15 @@ export default {
|
||||
|
||||
if (this.playingDownload) {
|
||||
console.log('[StreamContainer] Play download on audio mount')
|
||||
if (!this.download) {
|
||||
this.download = { ...this.playingDownload }
|
||||
}
|
||||
this.playDownload()
|
||||
} else if (this.$server.stream) {
|
||||
console.log('[StreamContainer] Open stream on audio mount')
|
||||
this.streamOpen(this.$server.stream)
|
||||
}
|
||||
},
|
||||
setListeners() {
|
||||
if (!this.$server.socket) {
|
||||
console.error('Invalid server socket not set')
|
||||
return
|
||||
}
|
||||
this.$server.socket.on('stream_open', this.streamOpen)
|
||||
this.$server.socket.on('stream_closed', this.streamClosed)
|
||||
this.$server.socket.on('stream_progress', this.streamProgress)
|
||||
this.$server.socket.on('stream_ready', this.streamReady)
|
||||
this.$server.socket.on('stream_reset', this.streamReset)
|
||||
},
|
||||
changePlaybackSpeed(speed) {
|
||||
this.$store.dispatch('user/updateUserSettings', { playbackRate: speed })
|
||||
},
|
||||
@@ -342,6 +328,17 @@ export default {
|
||||
this.cancelStream()
|
||||
}
|
||||
}
|
||||
},
|
||||
setListeners() {
|
||||
if (!this.$server.socket) {
|
||||
console.error('Invalid server socket not set')
|
||||
return
|
||||
}
|
||||
this.$server.socket.on('stream_open', this.streamOpen)
|
||||
this.$server.socket.on('stream_closed', this.streamClosed)
|
||||
this.$server.socket.on('stream_progress', this.streamProgress)
|
||||
this.$server.socket.on('stream_ready', this.streamReady)
|
||||
this.$server.socket.on('stream_reset', this.streamReset)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -110,6 +110,7 @@ export default {
|
||||
return this.download ? this.download.cover : null
|
||||
},
|
||||
download() {
|
||||
return null
|
||||
return this.$store.getters['downloads/getDownloadIfReady'](this.audiobookId)
|
||||
},
|
||||
errorText() {
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
<template>
|
||||
<div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }">
|
||||
<div class="w-full h-full relative">
|
||||
<div v-if="showCoverBg" class="bg-primary absolute top-0 left-0 w-full h-full">
|
||||
<div class="w-full h-full z-0" ref="coverBg" />
|
||||
<div class="bg-primary absolute top-0 left-0 w-full h-full">
|
||||
<!-- Blurred background for covers that dont fill -->
|
||||
<div v-if="showCoverBg" class="w-full h-full z-0" ref="coverBg" />
|
||||
|
||||
<!-- Image Loading indicator -->
|
||||
<div v-if="!isImageLoaded" class="w-full h-full flex items-center justify-center text-white">
|
||||
<svg class="animate-spin w-12 h-12" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<img ref="cover" :src="fullCoverUrl" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
|
||||
</div>
|
||||
@@ -42,7 +50,8 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
imageFailed: false,
|
||||
showCoverBg: false
|
||||
showCoverBg: false,
|
||||
isImageLoaded: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -142,10 +151,11 @@ export default {
|
||||
this.showCoverBg = false
|
||||
}
|
||||
}
|
||||
this.isImageLoaded = true
|
||||
},
|
||||
imageError(err) {
|
||||
console.error('ImgError', err)
|
||||
this.imageFailed = true
|
||||
console.error('ImgError', err, `SET IMAGE FAILED ${this.imageFailed}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,17 @@
|
||||
<div class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center" @click="show = false">
|
||||
<p class="absolute top-6 left-2 text-2xl">Downloads</p>
|
||||
|
||||
<div class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-white border-opacity-20" style="max-height: 75%" @click.stop>
|
||||
<div class="absolute top-16 left-0 right-0 w-full flex items-center px-2 py-1" :class="hasStoragePermission ? '' : 'text-error'">
|
||||
<span class="material-icons" @click="changeDownloadFolderClick">{{ hasStoragePermission ? 'folder' : 'error' }}</span>
|
||||
<p v-if="hasStoragePermission" class="text-sm px-4" @click="changeDownloadFolderClick">{{ downloadFolderSimplePath || 'No Download Folder Selected' }}</p>
|
||||
<p v-else class="text-sm px-4" @click="changeDownloadFolderClick">No Storage Permissions. Click here</p>
|
||||
</div>
|
||||
|
||||
<div v-if="totalSize" class="absolute bottom-0 left-0 right-0 w-full py-3 text-center">
|
||||
<p class="text-sm text-center text-gray-300">Total: {{ $bytesPretty(totalSize) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-white border-opacity-20 mt-10" style="max-height: 75%" @click.stop>
|
||||
<div v-if="!totalDownloads" class="flex items-center justify-center h-40">
|
||||
<p>No Downloads</p>
|
||||
</div>
|
||||
@@ -28,9 +38,10 @@
|
||||
<div class="flex items-center justify-center">
|
||||
<img v-if="download.cover" :src="download.cover" class="w-10 h-16 object-contain" />
|
||||
<img v-else src="/book_placeholder.jpg" class="w-10 h-16 object-contain" />
|
||||
<div class="pl-2 w-1/2">
|
||||
<div class="pl-2 w-2/3">
|
||||
<p class="font-normal truncate text-sm">{{ download.audiobook.book.title }}</p>
|
||||
<p class="font-normal truncate text-xs text-gray-400">{{ download.audiobook.book.author }}</p>
|
||||
<p class="font-normal truncate text-xs text-gray-400">{{ $bytesPretty(download.size) }}</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div v-if="download.isIncomplete" class="shadow-sm text-warning flex items-center justify-center rounded-full mr-4">
|
||||
@@ -71,11 +82,22 @@
|
||||
|
||||
<script>
|
||||
import { Dialog } from '@capacitor/dialog'
|
||||
import AudioDownloader from '@/plugins/audio-downloader'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
downloadingProgress: {}
|
||||
downloadFolder: null,
|
||||
downloadingProgress: {},
|
||||
totalSize: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async show(newValue) {
|
||||
if (newValue) {
|
||||
this.downloadFolder = await this.$localStore.getDownloadFolder()
|
||||
this.setTotalSize()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -87,6 +109,12 @@ export default {
|
||||
this.$store.commit('downloads/setShowModal', val)
|
||||
}
|
||||
},
|
||||
hasStoragePermission() {
|
||||
return this.$store.state.hasStoragePermission
|
||||
},
|
||||
downloadFolderSimplePath() {
|
||||
return this.downloadFolder ? this.downloadFolder.simplePath : null
|
||||
},
|
||||
totalDownloads() {
|
||||
return this.downloadsReady.length + this.orphanDownloads.length + this.downloadsDownloading.length
|
||||
},
|
||||
@@ -132,6 +160,25 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setTotalSize() {
|
||||
var totalSize = 0
|
||||
this.downloadsReady.forEach((dl) => {
|
||||
totalSize += dl.size && !isNaN(dl.size) ? Number(dl.size) : 0
|
||||
})
|
||||
this.totalSize = totalSize
|
||||
},
|
||||
async changeDownloadFolderClick() {
|
||||
if (!this.hasStoragePermission) {
|
||||
console.log('Requesting Storage Permission')
|
||||
AudioDownloader.requestStoragePermission()
|
||||
} else {
|
||||
var folderObj = await AudioDownloader.selectFolder()
|
||||
if (folderObj.error) {
|
||||
return this.$toast.error(`Error: ${folderObj.error || 'Unknown Error'}`)
|
||||
}
|
||||
await this.$localStore.setDownloadFolder(folderObj)
|
||||
}
|
||||
},
|
||||
updateDownloadProgress({ audiobookId, progress }) {
|
||||
this.$set(this.downloadingProgress, audiobookId, progress)
|
||||
},
|
||||
|
||||
@@ -43,6 +43,10 @@ export default {
|
||||
text: 'Added At',
|
||||
value: 'addedAt'
|
||||
},
|
||||
{
|
||||
text: 'Volume #',
|
||||
value: 'book.volumeNumber'
|
||||
},
|
||||
{
|
||||
text: 'Duration',
|
||||
value: 'duration'
|
||||
|
||||
Reference in New Issue
Block a user