Merge branch 'master' into ios-downloads

This commit is contained in:
ronaldheft
2022-08-16 16:56:47 -04:00
50 changed files with 840 additions and 361 deletions
+10 -2
View File
@@ -56,6 +56,9 @@ export default {
},
altViewEnabled() {
return this.$store.getters['getAltViewEnabled']
},
localMediaProgress() {
return this.$store.state.globals.localMediaProgress
}
},
methods: {
@@ -69,6 +72,7 @@ export default {
var podcasts = []
localMedia.forEach((item) => {
if (item.mediaType == 'book') {
item.progress = this.localMediaProgress.find((lmp) => lmp.id === item.id)
books.push(item)
} else if (item.mediaType == 'podcast') {
podcasts.push(item)
@@ -80,7 +84,11 @@ export default {
id: 'local-books',
label: 'Local Books',
type: 'book',
entities: books.slice(0, 10)
entities: books.sort((a, b) => {
if (a.progress && a.progress.isFinished) return 1
else if (b.progress && b.progress.isFinished) return -1
return 0
})
})
}
if (podcasts.length) {
@@ -88,7 +96,7 @@ export default {
id: 'local-podcasts',
label: 'Local Podcasts',
type: 'podcast',
entities: podcasts.slice(0, 10)
entities: podcasts
})
}
+61 -13
View File
@@ -9,20 +9,21 @@
</div>
<div class="title-container flex-grow pl-2">
<div class="flex relative pr-6">
<h1 class="text-base">{{ title }}</h1>
<h1 class="text-base font-semibold">{{ title }}</h1>
<button class="absolute top-0 right-0 h-full px-1 outline-none" @click="moreButtonPress">
<span class="material-icons text-xl">more_vert</span>
</button>
</div>
<p v-if="subtitle" class="text-gray-100 text-sm py-0.5">{{ subtitle }}</p>
<p v-if="seriesList && seriesList.length" class="text-sm text-gray-300 py-0.5">
<template v-for="(series, index) in seriesList"
><nuxt-link :key="series.id" :to="`/bookshelf/series/${series.id}`">{{ series.text }}</nuxt-link
><span :key="`${series.id}-comma`" v-if="index < seriesList.length - 1">,&nbsp;</span></template
>
</p>
<p v-if="podcastAuthor" class="text-sm text-gray-400 py-0.5">By {{ podcastAuthor }}</p>
<p v-else-if="bookAuthors && bookAuthors.length" class="text-sm text-gray-400 py-0.5">
<p v-if="podcastAuthor" class="text-sm text-gray-300 py-0.5">By {{ podcastAuthor }}</p>
<p v-else-if="bookAuthors && bookAuthors.length" class="text-sm text-gray-300 py-0.5">
By
<template v-for="(author, index) in bookAuthors"
><nuxt-link :key="author.id" :to="`/bookshelf/library?filter=authors.${$encode(author.id)}`">{{ author.name }}</nuxt-link
@@ -84,7 +85,7 @@
<div v-else-if="(user && (showPlay || showRead)) || hasLocal" class="flex mt-4">
<ui-btn v-if="showPlay" color="success" :disabled="isPlaying" class="flex items-center justify-center flex-grow mr-2" :padding-x="4" @click="playClick">
<span v-show="!isPlaying" class="material-icons">play_arrow</span>
<span class="px-1 text-sm">{{ isPlaying ? (isStreaming ? 'Streaming' : 'Playing') : hasLocal ? 'Play' : 'Stream' }}</span>
<span class="px-1 text-sm">{{ isPlaying ? (isStreaming ? 'Streaming' : 'Playing') : isPodcast ? 'Next Episode' : hasLocal ? 'Play' : 'Stream' }}</span>
</ui-btn>
<ui-btn v-if="showRead && user" color="info" class="flex items-center justify-center mr-2" :class="showPlay ? '' : 'flex-grow'" :padding-x="2" @click="readBook">
<span class="material-icons">auto_stories</span>
@@ -218,6 +219,9 @@ export default {
title() {
return this.mediaMetadata.title
},
subtitle() {
return this.mediaMetadata.subtitle
},
podcastAuthor() {
if (!this.isPodcast) return null
return this.mediaMetadata.author || ''
@@ -301,13 +305,13 @@ export default {
return this.libraryItem.isIncomplete
},
showPlay() {
return !this.isMissing && !this.isIncomplete && this.numTracks
return !this.isMissing && !this.isIncomplete && (this.numTracks || this.episodes.length)
},
showRead() {
return this.ebookFile && this.ebookFormat !== 'pdf'
},
showDownload() {
// if (this.isIos) return false
if (this.isPodcast) return false
return this.user && this.userCanDownload && this.showPlay && !this.hasLocal
},
ebookFile() {
@@ -364,14 +368,58 @@ export default {
this.$store.commit('openReader', this.libraryItem)
},
playClick() {
// Todo: Allow playing local or streaming
if (this.hasLocal && this.serverLibraryItemId && this.isCasting) {
// If casting and connected to server for local library item then send server library item id
this.$eventBus.$emit('play-item', { libraryItemId: this.serverLibraryItemId })
return
var episodeId = null
if (this.isPodcast) {
this.episodes.sort((a, b) => {
return String(b.publishedAt).localeCompare(String(a.publishedAt), undefined, { numeric: true, sensitivity: 'base' })
})
var episode = this.episodes.find((ep) => {
var podcastProgress = null
if (!this.isLocal) {
podcastProgress = this.$store.getters['user/getUserMediaProgress'](this.libraryItemId, ep.id)
} else {
podcastProgress = this.$store.getters['globals/getLocalMediaProgressById'](this.libraryItemId, ep.id)
}
return !podcastProgress || !podcastProgress.isFinished
})
if (!episode) episode = this.episodes[0]
episodeId = episode.id
var localEpisode = null
if (this.hasLocal && !this.isLocal) {
localEpisode = this.localLibraryItem.media.episodes.find((ep) => ep.serverEpisodeId == episodeId)
} else if (this.isLocal) {
localEpisode = episode
}
var serverEpisodeId = !this.isLocal ? episodeId : localEpisode ? localEpisode.serverEpisodeId : null
if (serverEpisodeId && this.serverLibraryItemId && this.isCasting) {
// If casting and connected to server for local library item then send server library item id
this.$eventBus.$emit('play-item', { libraryItemId: this.serverLibraryItemId, episodeId: serverEpisodeId })
return
}
if (localEpisode) {
this.$eventBus.$emit('play-item', { libraryItemId: this.localLibraryItem.id, episodeId: localEpisode.id, serverLibraryItemId: this.serverLibraryItemId, serverEpisodeId })
return
}
} else {
// Audiobook
if (this.hasLocal && this.serverLibraryItemId && this.isCasting) {
// If casting and connected to server for local library item then send server library item id
this.$eventBus.$emit('play-item', { libraryItemId: this.serverLibraryItemId })
return
}
if (this.hasLocal) {
this.$eventBus.$emit('play-item', { libraryItemId: this.localLibraryItem.id, serverLibraryItemId: this.serverLibraryItemId })
return
}
}
if (this.hasLocal) return this.$eventBus.$emit('play-item', { libraryItemId: this.localLibraryItem.id, serverLibraryItemId: this.serverLibraryItemId })
this.$eventBus.$emit('play-item', { libraryItemId: this.libraryItemId })
this.$eventBus.$emit('play-item', { libraryItemId: this.libraryItemId, episodeId })
},
async clearProgressClick() {
const { value } = await Dialog.confirm({
+41 -4
View File
@@ -1,16 +1,19 @@
<template>
<div class="w-full h-full p-8">
<p class="uppercase text-xs font-semibold text-gray-300 mb-2">Display Settings</p>
<div class="flex items-center py-3" @click="toggleEnableAltView">
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.enableAltView" @input="saveSettings" />
</div>
<p class="pl-4">Alternative Bookshelf View</p>
<p class="pl-4">Alternative bookshelf view</p>
</div>
<div v-if="$platform !== 'ios'" class="flex items-center py-3" @click="toggleDisableAutoRewind">
<p class="uppercase text-xs font-semibold text-gray-300 mb-2 mt-6">Playback Settings</p>
<div v-if="!isiOS" class="flex items-center py-3" @click="toggleDisableAutoRewind">
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.disableAutoRewind" @input="saveSettings" />
</div>
<p class="pl-4">Disable Auto Rewind</p>
<p class="pl-4">Disable auto rewind</p>
</div>
<div class="flex items-center py-3" @click="toggleJumpBackwards">
<div class="w-10 flex justify-center">
@@ -24,10 +27,21 @@
</div>
<p class="pl-4">Jump forwards time</p>
</div>
<p v-if="!isiOS" class="uppercase text-xs font-semibold text-gray-300 mb-2 mt-6">Sleep Timer Settings</p>
<div v-if="!isiOS" class="flex items-center py-3" @click="toggleDisableShakeToResetSleepTimer">
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.disableShakeToResetSleepTimer" @input="saveSettings" />
</div>
<p class="pl-4">Disable shake to reset</p>
<span class="material-icons-outlined ml-2" @click.stop="showInfo('disableShakeToResetSleepTimer')">info</span>
</div>
</div>
</template>
<script>
import { Dialog } from '@capacitor/dialog'
export default {
data() {
return {
@@ -36,11 +50,21 @@ export default {
disableAutoRewind: false,
enableAltView: false,
jumpForwardTime: 10,
jumpBackwardsTime: 10
jumpBackwardsTime: 10,
disableShakeToResetSleepTimer: false
},
settingInfo: {
disableShakeToResetSleepTimer: {
name: 'Disable shake to reset sleep timer',
message: 'The sleep timer will start fading out when 30s is remaining. Shaking your device will reset the timer if it is within 30s OR has finished less than 2 mintues ago. Enable this setting to disable that feature.'
}
}
}
},
computed: {
isiOS() {
return this.$platform === 'ios'
},
jumpForwardItems() {
return this.$store.state.globals.jumpForwardItems || []
},
@@ -63,6 +87,18 @@ export default {
}
},
methods: {
showInfo(setting) {
if (this.settingInfo[setting]) {
Dialog.alert({
title: this.settingInfo[setting].name,
message: this.settingInfo[setting].message
})
}
},
toggleDisableShakeToResetSleepTimer() {
this.settings.disableShakeToResetSleepTimer = !this.settings.disableShakeToResetSleepTimer
this.saveSettings()
},
toggleDisableAutoRewind() {
this.settings.disableAutoRewind = !this.settings.disableAutoRewind
this.saveSettings()
@@ -99,6 +135,7 @@ export default {
this.settings.enableAltView = !!deviceSettings.enableAltView
this.settings.jumpForwardTime = deviceSettings.jumpForwardTime || 10
this.settings.jumpBackwardsTime = deviceSettings.jumpBackwardsTime || 10
this.settings.disableShakeToResetSleepTimer = !!deviceSettings.disableShakeToResetSleepTimer
}
},
mounted() {
+114
View File
@@ -0,0 +1,114 @@
<template>
<div class="w-full h-full px-0 py-4 overflow-y-auto">
<h1 class="text-xl px-4">
Stats for <b>{{ username }}</b>
</h1>
<div class="flex text-center justify-center">
<div class="flex p-2">
<div class="px-3">
<p class="text-4xl md:text-5xl font-bold">{{ userItemsFinished.length }}</p>
<p class="font-book text-xs md:text-sm text-white text-opacity-80">Items Finished</p>
</div>
</div>
<div class="flex p-2">
<div class="px-1">
<p class="text-4xl md:text-5xl font-bold">{{ totalDaysListened }}</p>
<p class="font-book text-xs md:text-sm text-white text-opacity-80">Days Listened</p>
</div>
</div>
<div class="flex p-2">
<div class="px-1">
<p class="text-4xl md:text-5xl font-bold">{{ totalMinutesListening }}</p>
<p class="font-book text-xs md:text-sm text-white text-opacity-80">Minutes Listening</p>
</div>
</div>
</div>
<div class="flex flex-col md:flex-row overflow-hidden max-w-full">
<stats-daily-listening-chart :listening-stats="listeningStats" class="lg:scale-100 transform scale-90 px-0" />
<div class="w-80 my-6 mx-auto">
<div class="flex mb-4 items-center">
<h1 class="text-2xl font-book">Recent Sessions</h1>
<div class="flex-grow" />
</div>
<p v-if="!mostRecentListeningSessions.length">No Listening Sessions</p>
<template v-for="(item, index) in mostRecentListeningSessions">
<div :key="item.id" class="w-full py-0.5">
<div class="flex items-center mb-1">
<p class="text-sm font-book text-white text-opacity-70 w-8">{{ index + 1 }}.&nbsp;</p>
<div class="w-56">
<p class="text-sm font-book text-white text-opacity-80 truncate">{{ item.mediaMetadata ? item.mediaMetadata.title : '' }}</p>
<p class="text-xs text-white text-opacity-50">{{ $dateDistanceFromNow(item.updatedAt) }}</p>
</div>
<div class="flex-grow" />
<div class="w-18 text-right">
<p class="text-sm font-bold">{{ $elapsedPretty(item.timeListening) }}</p>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
listeningStats: null,
windowWidth: 0
}
},
watch: {
currentLibraryId(newVal) {
if (newVal) {
this.init()
}
}
},
computed: {
user() {
return this.$store.state.user.user
},
username() {
return this.user ? this.user.username : ''
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
},
userMediaProgress() {
return this.user ? this.user.mediaProgress : []
},
userItemsFinished() {
return this.userMediaProgress.filter((lip) => !!lip.isFinished)
},
mostRecentListeningSessions() {
if (!this.listeningStats) return []
return this.listeningStats.recentSessions || []
},
totalMinutesListening() {
if (!this.listeningStats) return 0
return Math.round(this.listeningStats.totalTime / 60)
},
totalDaysListened() {
if (!this.listeningStats) return 0
return Object.values(this.listeningStats.days).length
}
},
methods: {
async init() {
this.listeningStats = await this.$axios.$get(`/api/me/listening-stats`).catch((err) => {
console.error('Failed to load listening sesions', err)
return []
})
console.log('Loaded user listening data', this.listeningStats)
}
},
mounted() {
this.init()
}
}
</script>