Offline support, downloading, syncing progress

This commit is contained in:
Mark Cooper
2021-09-12 18:37:08 -05:00
parent e33a054288
commit 7fe37ff983
37 changed files with 2836 additions and 201 deletions
+22 -2
View File
@@ -202,7 +202,17 @@ export default {
this.isResetting = false
this.initObject = { ...audiobookStreamData }
this.currentPlaybackRate = this.initObject.playbackSpeed
MyNativeAudio.initPlayer(this.initObject)
MyNativeAudio.initPlayer(this.initObject).then((res) => {
if (res && res.success) {
console.log('Success init audio player')
} else {
console.error('Failed to init audio player')
}
})
if (audiobookStreamData.isLocal) {
this.setStreamReady()
}
},
setFromObj() {
if (!this.initObject) {
@@ -210,7 +220,17 @@ export default {
return
}
this.isResetting = false
MyNativeAudio.initPlayer(this.initObject)
MyNativeAudio.initPlayer(this.initObject).then((res) => {
if (res && res.success) {
console.log('Success init audio player')
} else {
console.error('Failed to init audio player')
}
})
if (audiobookStreamData.isLocal) {
this.setStreamReady()
}
},
play() {
MyNativeAudio.playPlayer()
+49 -2
View File
@@ -13,14 +13,18 @@
<div class="flex-grow" />
<!-- <ui-menu :label="username" :items="menuItems" @action="menuAction" class="ml-5" /> -->
<nuxt-link to="/account" class="relative w-28 bg-fg border border-gray-500 rounded shadow-sm ml-5 pl-3 pr-10 py-2 text-left focus:outline-none sm:text-sm cursor-pointer hover:bg-bg hover:bg-opacity-40" aria-haspopup="listbox" aria-expanded="true">
<span class="material-icons cursor-pointer mx-4" @click="$store.commit('downloads/setShowModal', true)">source</span>
<widgets-connection-icon />
<!-- <nuxt-link to="/account" class="relative w-28 bg-fg border border-gray-500 rounded shadow-sm ml-5 pl-3 pr-10 py-2 text-left focus:outline-none sm:text-sm cursor-pointer hover:bg-bg hover:bg-opacity-40" aria-haspopup="listbox" aria-expanded="true">
<span class="flex items-center">
<span class="block truncate">{{ username }}</span>
</span>
<span class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<span class="material-icons text-gray-100">person</span>
</span>
</nuxt-link>
</nuxt-link> -->
</div>
</div>
</template>
@@ -89,4 +93,47 @@ export default {
#appbar {
box-shadow: 0px 5px 5px #11111155;
}
.loader-dots div {
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
.loader-dots div:nth-child(1) {
left: 0px;
animation: loader-dots1 0.6s infinite;
}
.loader-dots div:nth-child(2) {
left: 0px;
animation: loader-dots2 0.6s infinite;
}
.loader-dots div:nth-child(3) {
left: 10px;
animation: loader-dots2 0.6s infinite;
}
.loader-dots div:nth-child(4) {
left: 20px;
animation: loader-dots3 0.6s infinite;
}
@keyframes loader-dots1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes loader-dots3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes loader-dots2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(10px, 0);
}
}
</style>
+21 -7
View File
@@ -4,7 +4,7 @@
<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]" />
<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> -->
@@ -44,6 +44,9 @@ export default {
},
userAudiobooks() {
return this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {}
},
localUserAudiobooks() {
return this.$store.state.user.localUserAudiobooks || {}
}
},
methods: {
@@ -52,11 +55,6 @@ export default {
filterBy: 'all'
})
},
playAudiobook(audiobook) {
console.log('Play Audiobook', audiobook)
this.$store.commit('setStreamAudiobook', audiobook)
this.$server.socket.emit('open_stream', audiobook.id)
},
calcShelves() {
var booksPerShelf = Math.floor(this.pageWidth / (this.cardWidth + 32))
var groupedBooks = []
@@ -87,6 +85,16 @@ export default {
if (this.currFilterOrderKey !== this.filterOrderKey) {
this.calcShelves()
}
},
socketConnected(isConnected) {
if (isConnected) {
console.log('Connected - Load from server')
this.$store.dispatch('audiobooks/load')
} else {
console.log('Disconnected - Reset to local storage')
this.$store.commit('audiobooks/reset')
this.$store.dispatch('downloads/loadFromStorage')
}
}
},
mounted() {
@@ -94,7 +102,13 @@ export default {
this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
window.addEventListener('resize', this.resize)
this.$store.dispatch('audiobooks/load')
this.$server.on('connected', this.socketConnected)
if (this.$server.connected) {
this.$store.dispatch('audiobooks/load')
} else {
console.log('Bookshelf - Server not connected using downloaded')
}
this.init()
},
beforeDestroy() {
+197 -35
View File
@@ -1,21 +1,21 @@
<template>
<div class="w-full p-4 pointer-events-none fixed bottom-0 left-0 right-0 z-20">
<div v-if="streamAudiobook" class="w-full bg-primary absolute bottom-0 left-0 right-0 z-50 p-2 pointer-events-auto" @click.stop @mousedown.stop @mouseup.stop>
<div v-if="audiobook" class="w-full bg-primary absolute bottom-0 left-0 right-0 z-50 p-2 pointer-events-auto" @click.stop @mousedown.stop @mouseup.stop>
<div class="pl-16 pr-2 flex items-center pb-2">
<div>
<p class="px-2">{{ title }}</p>
<p class="px-2 text-xs text-gray-400">by {{ author }}</p>
</div>
<div class="flex-grow" />
<div class="cursor-pointer flex items-center justify-center mr-6 w-6 text-center" :class="chapters.length ? 'text-gray-300' : 'text-gray-400'" @mousedown.prevent @mouseup.prevent @click="clickChapterBtn">
<div v-if="chapters.length" class="cursor-pointer flex items-center justify-center mr-6 w-6 text-center" :class="chapters.length ? 'text-gray-300' : 'text-gray-400'" @mousedown.prevent @mouseup.prevent @click="clickChapterBtn">
<span class="material-icons text-2xl">format_list_bulleted</span>
</div>
<span class="material-icons" @click="cancelStream">close</span>
</div>
<div class="absolute left-2 -top-10">
<cards-book-cover :audiobook="streamAudiobook" :width="64" />
<cards-book-cover :audiobook="audiobook" :width="64" />
</div>
<audio-player-mini ref="audioPlayerMini" :loading="!stream || currStreamAudiobookId !== streamAudiobookId" @updateTime="updateTime" @selectPlaybackSpeed="showPlaybackSpeedModal = true" @hook:mounted="audioPlayerMounted" />
<audio-player-mini ref="audioPlayerMini" :loading="isLoading" @updateTime="updateTime" @selectPlaybackSpeed="showPlaybackSpeedModal = true" @hook:mounted="audioPlayerMounted" />
</div>
<modals-playback-speed-modal v-model="showPlaybackSpeedModal" :playback-speed.sync="playbackSpeed" @change="changePlaybackSpeed" />
<modals-chapters-modal v-model="showChapterModal" :chapters="chapters" @select="selectChapter" />
@@ -30,24 +30,62 @@ export default {
return {
audioPlayerReady: false,
stream: null,
lastServerUpdateSentSeconds: 0,
download: null,
lastProgressTimeUpdate: 0,
showPlaybackSpeedModal: false,
playbackSpeed: 1,
showChapterModal: false
}
},
watch: {
// playingDownload: {
// handler(newVal, oldVal) {
// console.log('[StreamContainer] Download AUDIOBOOK Changed ' + newVal + '|' + oldVal)
// if (newVal) {
// if (this.audioPlayerReady) {
// this.playDownload()
// }
// } else if (this.download) {
// this.download = null
// }
// }
// },
// streamAudiobook(newVal, oldval) {
// console.log('[StreamContainer] Stream AUDIOBOOK Changed ' + newVal + '|' + oldVal)
// }
},
watch: {
socketConnected(newVal) {
if (newVal) {
console.log('Socket Connected set listeners')
this.setListeners()
}
}
},
computed: {
socketConnected() {
return this.$store.state.socketConnected
},
isLoading() {
if (this.playingDownload) return false
if (!this.streamAudiobook) return false
return !this.stream || this.streamAudiobook.id !== this.stream.audiobook.id
},
playingDownload() {
return this.$store.state.playingDownload
},
audiobook() {
if (this.playingDownload) return this.playingDownload.audiobook
return this.streamAudiobook
},
audiobookId() {
return this.audiobook ? this.audiobook.id : null
},
streamAudiobook() {
return this.$store.state.streamAudiobook
},
streamAudiobookId() {
return this.streamAudiobook ? this.streamAudiobook.id : null
},
currStreamAudiobookId() {
return this.stream ? this.stream.audiobook.id : null
},
book() {
return this.streamAudiobook ? this.streamAudiobook.book || {} : {}
return this.audiobook ? this.audiobook.book || {} : {}
},
title() {
return this.book ? this.book.title : ''
@@ -62,7 +100,7 @@ export default {
return this.book ? this.book.series : ''
},
chapters() {
return this.streamAudiobook ? this.streamAudiobook.chapters || [] : []
return this.audiobook ? this.audiobook.chapters || [] : []
},
volumeNumber() {
return this.book ? this.book.volumeNumber : ''
@@ -73,7 +111,7 @@ export default {
return `${this.series} #${this.volumeNumber}`
},
duration() {
return this.streamAudiobook ? this.streamAudiobook.duration || 0 : 0
return this.audiobook ? this.audiobook.duration || 0 : 0
},
coverForNative() {
if (!this.cover) {
@@ -100,27 +138,61 @@ export default {
this.showChapterModal = false
},
async cancelStream() {
const { value } = await Dialog.confirm({
title: 'Confirm',
message: 'Cancel this stream?'
})
if (value) {
this.$server.socket.emit('close_stream')
this.$store.commit('setStreamAudiobook', null)
if (this.download) {
if (this.$refs.audioPlayerMini) {
this.$refs.audioPlayerMini.terminateStream()
}
this.$store.commit('setPlayingDownload', null)
this.$localStore.setCurrent(null)
} else {
const { value } = await Dialog.confirm({
title: 'Confirm',
message: 'Cancel this stream?'
})
if (value) {
this.$server.socket.emit('close_stream')
this.$store.commit('setStreamAudiobook', null)
if (this.$refs.audioPlayerMini) {
this.$refs.audioPlayerMini.terminateStream()
}
}
}
},
updateTime(currentTime) {
var diff = currentTime - this.lastServerUpdateSentSeconds
var diff = currentTime - this.lastProgressTimeUpdate
if (diff > 4 || diff < 0) {
this.lastServerUpdateSentSeconds = currentTime
var updatePayload = {
currentTime,
streamId: this.stream.id
this.lastProgressTimeUpdate = currentTime
if (this.stream) {
var updatePayload = {
currentTime,
streamId: this.stream.id
}
this.$server.socket.emit('stream_update', updatePayload)
} else if (this.download) {
var progressUpdate = {
audiobookId: this.download.id,
currentTime: currentTime,
totalDuration: this.download.audiobook.duration,
progress: Number((currentTime / this.download.audiobook.duration).toFixed(3)),
lastUpdate: Date.now(),
isRead: false
}
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(() => {
console.log('Updated user audiobook progress', currentTime)
})
}
this.$server.socket.emit('stream_update', updatePayload)
}
},
closeStream() {},
@@ -151,8 +223,67 @@ export default {
}
}
},
async getDownloadStartTime() {
var userAudiobook = await this.$localStore.getMostRecentUserAudiobook(this.audiobookId)
if (!userAudiobook) {
console.log('[StreamContainer] getDownloadStartTime no user audiobook record found')
return 0
}
return userAudiobook.currentTime
},
async playDownload() {
if (this.stream) {
if (this.$refs.audioPlayerMini) {
this.$refs.audioPlayerMini.terminateStream()
}
this.stream = null
}
this.lastProgressTimeUpdate = 0
console.log('[StreamContainer] Playing local', this.playingDownload)
if (!this.$refs.audioPlayerMini) {
console.error('No Audio Player Mini')
return
}
var playOnLoad = this.$store.state.playOnLoad
if (playOnLoad) this.$store.commit('setPlayOnLoad', false)
var currentTime = await this.getDownloadStartTime()
if (isNaN(currentTime) || currentTime === null) currentTime = 0
// Update local current time
this.$localStore.setCurrent({
audiobookId: this.download.id,
lastUpdate: Date.now()
})
var audiobookStreamData = {
title: this.title,
author: this.author,
playWhenReady: !!playOnLoad,
startTime: String(Math.floor(currentTime * 1000)),
playbackSpeed: this.playbackSpeed || 1,
cover: this.download.coverUrl || null,
duration: String(Math.floor(this.duration * 1000)),
series: this.seriesTxt,
token: this.$store.getters['user/getToken'],
contentUrl: this.playingDownload.contentUrl,
isLocal: true
}
this.$refs.audioPlayerMini.set(audiobookStreamData)
},
streamOpen(stream) {
console.log('[StreamContainer] Stream Open', stream)
if (this.download) {
if (this.$refs.audioPlayerMini) {
this.$refs.audioPlayerMini.terminateStream()
}
}
this.lastProgressTimeUpdate = 0
console.log('[StreamContainer] Stream Open: ' + this.title)
if (!this.$refs.audioPlayerMini) {
console.error('No Audio Player Mini')
return
@@ -160,6 +291,9 @@ export default {
this.stream = stream
// Update local remove current
this.$localStore.setCurrent(null)
var playlistUrl = stream.clientPlaylistUri
var currentTime = stream.clientCurrentTime || 0
var playOnLoad = this.$store.state.playOnLoad
@@ -177,13 +311,17 @@ export default {
playlistUrl: this.$server.url + playlistUrl,
token: this.$store.getters['user/getToken']
}
this.$refs.audioPlayerMini.set(audiobookStreamData)
},
audioPlayerMounted() {
console.log('Audio Player Mounted', this.$server.stream)
this.audioPlayerReady = true
if (this.$server.stream) {
if (this.playingDownload) {
console.log('[StreamContainer] Play download on audio mount')
this.playDownload()
} else if (this.$server.stream) {
console.log('[StreamContainer] Open stream on audio mount')
this.streamOpen(this.$server.stream)
}
},
@@ -206,6 +344,26 @@ export default {
this.playbackSpeed = settings.playbackRate
this.$refs.audioPlayerMini.updatePlaybackRate()
}
},
streamUpdated(type, data) {
if (type === 'download') {
if (data) {
console.log('START DOWNLOAD PLAY')
this.download = { ...data }
if (this.audioPlayerReady) {
this.playDownload()
}
} else if (this.download) {
console.log('STOP DOWNLOAD PLAY')
this.cancelStream()
}
} else {
if (data) {
console.log('STARTING STREAM')
} else {
console.log('STOPPING STREAM')
}
}
}
},
mounted() {
@@ -213,15 +371,19 @@ export default {
this.setListeners()
this.$store.commit('user/addSettingsListener', { id: 'streamContainer', meth: this.settingsUpdated })
this.$store.commit('setStreamListener', this.streamUpdated)
},
beforeDestroy() {
this.$server.socket.off('stream_open', this.streamOpen)
this.$server.socket.off('stream_closed', this.streamClosed)
this.$server.socket.off('stream_progress', this.streamProgress)
this.$server.socket.off('stream_ready', this.streamReady)
this.$server.socket.off('stream_reset', this.streamReset)
if (this.$server.socket) {
this.$server.socket.off('stream_open', this.streamOpen)
this.$server.socket.off('stream_closed', this.streamClosed)
this.$server.socket.off('stream_progress', this.streamProgress)
this.$server.socket.off('stream_ready', this.streamReady)
this.$server.socket.off('stream_reset', this.streamReset)
}
this.$store.commit('user/removeSettingsListener', 'streamContainer')
this.$store.commit('removeStreamListener')
}
}
</script>
+22 -8
View File
@@ -11,7 +11,11 @@
<div class="rounded-sm h-full overflow-hidden relative box-shadow-book">
<nuxt-link :to="`/audiobook/${audiobookId}`" class="cursor-pointer">
<div class="w-full relative" :style="{ height: height + 'px' }">
<cards-book-cover :audiobook="audiobook" :author-override="authorFormat" :width="width" />
<cards-book-cover :audiobook="audiobook" :download-cover="downloadCover" :author-override="authorFormat" :width="width" />
<div v-if="download" class="absolute" :style="{ top: 0.5 * sizeMultiplier + 'rem', right: 0.5 * sizeMultiplier + 'rem' }">
<span class="material-icons text-success" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">download_done</span>
</div>
<div class="absolute bottom-0 left-0 h-1.5 bg-yellow-400 shadow-sm" :style="{ width: width * userProgressPercent + 'px' }"></div>
@@ -35,6 +39,10 @@ export default {
type: Object,
default: () => null
},
localUserProgress: {
type: Object,
default: () => null
},
width: {
type: Number,
default: 140
@@ -81,8 +89,13 @@ export default {
orderBy() {
return this.$store.getters['user/getUserSetting']('orderBy')
},
mostRecentUserProgress() {
if (!this.localUserProgress) return this.userProgress
if (!this.userProgress) return this.localUserProgress
return this.localUserProgress.lastUpdate > this.userProgress.lastUpdate ? this.localUserProgress : this.userProgress
},
userProgressPercent() {
return this.userProgress ? this.userProgress.progress || 0 : 0
return this.mostRecentUserProgress ? this.mostRecentUserProgress.progress || 0 : 0
},
showError() {
return this.hasMissingParts || this.hasInvalidParts
@@ -93,6 +106,12 @@ export default {
hasInvalidParts() {
return this.audiobook.hasInvalidParts
},
downloadCover() {
return this.download ? this.download.cover : null
},
download() {
return this.$store.getters['downloads/getDownloadIfReady'](this.audiobookId)
},
errorText() {
var txt = ''
if (this.hasMissingParts) {
@@ -105,12 +124,7 @@ export default {
return txt || 'Unknown Error'
}
},
methods: {
play() {
this.$store.commit('setStreamAudiobook', this.audiobook)
this.$root.socket.emit('open_stream', this.audiobookId)
}
},
methods: {},
mounted() {}
}
</script>
+8
View File
@@ -32,6 +32,7 @@ export default {
type: Object,
default: () => {}
},
downloadCover: String,
authorOverride: String,
width: {
type: Number,
@@ -78,7 +79,13 @@ export default {
serverUrl() {
return this.$store.state.serverUrl
},
networkConnected() {
return this.$store.state.networkConnected
},
fullCoverUrl() {
if (this.downloadCover) return this.downloadCover
else if (!this.networkConnected) return this.placeholderUrl
if (this.cover.startsWith('http')) return this.cover
var _clean = this.cover.replace(/\\/g, '/')
if (_clean.startsWith('/local')) {
@@ -91,6 +98,7 @@ export default {
return this.book.cover || this.placeholderUrl
},
hasCover() {
if (!this.networkConnected && !this.downloadCover) return false
return !!this.book.cover
},
sizeMultiplier() {
+147
View File
@@ -0,0 +1,147 @@
<template>
<modals-modal v-model="show" width="100%" height="100%">
<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 v-if="!totalDownloads" class="flex items-center justify-center h-40">
<p>No Downloads</p>
</div>
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
<template v-for="download in downloadsDownloading">
<li :key="download.id" class="text-gray-400 select-none relative px-4 py-5 border-b border-white border-opacity-10 bg-black bg-opacity-10">
<div class="flex items-center justify-center">
<div>
<span class="text-xs">({{ downloadingProgress[download.id] || 0 }}%) {{ download.isPreparing ? 'Preparing' : 'Downloading' }}...</span>
<p class="font-normal truncate text-sm">{{ download.audiobook.book.title }}</p>
</div>
<div class="flex-grow" />
<div class="shadow-sm text-white flex items-center justify-center rounded-full animate-spin">
<span class="material-icons">refresh</span>
</div>
</div>
</li>
</template>
<template v-for="download in downloadsReady">
<li :key="download.id" class="text-gray-50 select-none relative pr-4 pl-2 py-5 border-b border-white border-opacity-10" @click="jumpToAudiobook(download)">
<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">
<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>
</div>
<div class="flex-grow" />
<div v-if="download.isIncomplete" class="shadow-sm text-warning flex items-center justify-center rounded-full mr-4">
<span class="material-icons">error_outline</span>
</div>
<button class="shadow-sm text-accent flex items-center justify-center rounded-full" @click.stop="clickedOption(download)">
<span class="material-icons" style="font-size: 2rem">play_arrow</span>
</button>
<div class="shadow-sm text-error flex items-center justify-center rounded-ful ml-4" @click.stop="clickDelete(download)">
<span class="material-icons" style="font-size: 1.2rem">delete</span>
</div>
</div>
</li>
</template>
<template v-for="download in orphanDownloads">
<li :key="download.id" class="text-gray-50 select-none relative cursor-pointer px-4 py-5 border-b border-white border-opacity-10">
<div class="flex items-center justify-center">
<span class="font-normal block truncate text-sm">{{ download.filename }}</span>
<div class="flex-grow" />
<div class="shadow-sm text-warning flex items-center justify-center rounded-full">
<span class="material-icons">error_outline</span>
</div>
<div class="shadow-sm text-error flex items-center justify-center rounded-ful ml-4" @click="clickDelete(download)">
<span class="material-icons" style="font-size: 1.2rem">delete</span>
</div>
</div>
</li>
</template>
</ul>
</div>
</div>
</modals-modal>
</template>
<script>
import { Dialog } from '@capacitor/dialog'
export default {
data() {
return {
downloadingProgress: {}
}
},
computed: {
show: {
get() {
return this.$store.state.downloads.showModal
},
set(val) {
this.$store.commit('downloads/setShowModal', val)
}
},
totalDownloads() {
return this.downloadsReady.length + this.orphanDownloads.length + this.downloadsDownloading.length
},
downloadsDownloading() {
return this.downloads.filter((d) => d.isDownloading || d.isPreparing)
},
downloadsReady() {
return this.downloads.filter((d) => !d.isDownloading && !d.isPreparing)
},
orphanDownloads() {
return this.$store.state.downloads.orphanDownloads
},
downloads() {
return this.$store.state.downloads.downloads
// return [
// {
// id: 'asdf1',
// title: 'Test Title 1',
// author: 'Test Author 1',
// isDownloading: true
// },
// {
// id: 'asdf2',
// title: 'Test Title 2',
// author: 'Author 2',
// isReady: true
// },
// {
// id: 'asdf3',
// name: 'asdf.mp3',
// isReady: false,
// isDownloading: false
// }
// ]
}
},
methods: {
updateDownloadProgress({ audiobookId, progress }) {
this.$set(this.downloadingProgress, audiobookId, progress)
},
jumpToAudiobook(download) {
this.show = false
this.$router.push(`/audiobook/${download.id}`)
},
async clickDelete(download) {
const { value } = await Dialog.confirm({
title: 'Confirm',
message: 'Delete this download?'
})
if (value) {
this.$emit('deleteDownload', download)
}
},
clickedOption(download) {
console.log('Clicked download', download)
this.$emit('selectDownload', download)
this.show = false
}
},
mounted() {}
}
</script>
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<div ref="wrapper" class="modal modal-bg w-full h-full max-h-screen fixed top-0 left-0 bg-primary bg-opacity-75 flex items-center justify-center z-30 opacity-0">
<div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
<div class="absolute top-0 left-0 w-full h-36 bg-gradient-to-b from-black to-transparent opacity-70 pointer-events-none" />
<div class="absolute z-40 top-4 right-4 h-12 w-12 flex items-center justify-center cursor-pointer text-white hover:text-gray-300" @click="show = false">
<span class="material-icons text-4xl">close</span>
+103
View File
@@ -0,0 +1,103 @@
<template>
<div>
<nuxt-link v-if="isConnected" to="/account" class="p-2 bg-white bg-opacity-10 border border-white border-opacity-40 rounded-full h-11 w-11 flex items-center justify-center">
<span class="material-icons">person</span>
</nuxt-link>
<div v-else-if="processing" class="relative p-2 bg-warning bg-opacity-10 border border-warning border-opacity-40 rounded-full h-11 w-11 flex items-center justify-center">
<div class="loader-dots block relative w-10 h-2.5">
<div class="absolute top-0 mt-0.5 w-1.5 h-1.5 rounded-full bg-warning"></div>
<div class="absolute top-0 mt-0.5 w-1.5 h-1.5 rounded-full bg-warning"></div>
<div class="absolute top-0 mt-0.5 w-1.5 h-1.5 rounded-full bg-warning"></div>
<div class="absolute top-0 mt-0.5 w-1.5 h-1.5 rounded-full bg-warning"></div>
</div>
</div>
<nuxt-link v-else to="/connect" class="relative p-2 bg-warning bg-opacity-10 border border-warning border-opacity-40 rounded-full h-11 w-11 flex items-center justify-center">
<span class="material-icons">{{ networkIcon }}</span>
<!-- <div class="absolute top-0 left-0"> -->
<!-- <div class="absolute -top-5 -right-5 overflow-hidden">
<svg class="w-20 h-20 animate-spin" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M15.165 8.53a.5.5 0 01-.404.58A7 7 0 1023 16a.5.5 0 011 0 8 8 0 11-9.416-7.874.5.5 0 01.58.404z" fill="currentColor" fill-rule="evenodd" />
</svg>
</div> -->
</nuxt-link>
</div>
</template>
<script>
export default {
props: {},
data() {
return {
processing: false,
serverUrl: null,
isConnected: false
}
},
watch: {
networkConnected(newVal) {
if (newVal) {
this.init()
}
}
},
computed: {
user() {
return this.$store.state.user.user
},
networkIcon() {
if (!this.networkConnected) return 'signal_wifi_connected_no_internet_4'
return 'cloud_off'
},
networkConnected() {
return this.$store.state.networkConnected
}
},
methods: {
socketConnected(val) {
this.processing = false
this.isConnected = val
},
async init() {
if (this.isConnected) {
return
}
if (!this.$server) {
console.error('Invalid server not initialized')
return
}
if (!this.networkConnected) return
this.$server.on('connected', this.socketConnected)
var localServerUrl = await this.$localStore.getServerUrl()
var localUserToken = await this.$localStore.getToken()
if (localServerUrl) {
this.serverUrl = localServerUrl
// Server and Token are stored
if (localUserToken) {
this.processing = true
var success = await this.$server.connect(localServerUrl, localUserToken)
if (!success && !this.$server.url) {
this.processing = false
this.serverUrl = null
} else if (!success) {
this.processing = false
}
} else {
// Server only is stored
var success = await this.$server.check(this.serverUrl)
if (!success) {
console.error('Invalid server')
this.$server.setServerUrl(null)
}
}
}
}
},
mounted() {
this.init()
}
}
</script>