feat: share latest activity

This commit is contained in:
benonymity 2022-07-06 16:55:58 -04:00
parent 06ec2159f5
commit 1022033e0f
6 changed files with 199 additions and 1 deletions

View file

@ -63,6 +63,14 @@
<div v-show="isPodcastSearchPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="socialTrue" :to="`/social`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-opacity-40 cursor-pointer relative" :class="isSocialPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons-outlined">connect_without_contact</span>
<p class="font-book pt-1.5" style="font-size: 0.9rem">Social</p>
<div v-show="paramId === 'collections'" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="numIssues" :to="`/library/${currentLibraryId}/bookshelf?filter=issues`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-opacity-40 cursor-pointer relative" :class="showingIssues ? 'bg-error bg-opacity-40' : ' bg-error bg-opacity-20'">
<span class="material-icons text-2xl">warning</span>
@ -128,6 +136,9 @@ export default {
isAuthorsPage() {
return this.$route.name === 'library-library-authors'
},
isSocialPage() {
return this.$route.name === 'social'
},
libraryBookshelfPage() {
return this.$route.name === 'library-library-bookshelf-id'
},
@ -137,6 +148,15 @@ export default {
filterBy() {
return this.$store.getters['user/getUserSetting']('filterBy')
},
async socialTrue() {
let listeningStats = await this.$axios.$get(`/api/social`).catch((err) => {
console.error('Failed to load shared user listening sesions', err)
return false
})
if (listeningStats != null) {
return true
} else {return false}
},
showingIssues() {
if (!this.$route.query) return false
return this.libraryBookshelfPage && this.$route.query.filter === 'issues'

View file

@ -15,6 +15,18 @@
<div class="w-full h-px bg-primary my-4" />
<div class="flex items-center py-1">
<ui-toggle-switch v-model="isSharingActivity" @input="changeSharingSetting" />
<ui-tooltip text="Share your latest listening activity with other users in the social tab">
<p class="pl-4">
Share Listening Activity
<span class="material-icons icon-text text-sm">info_outlined</span>
</p>
</ui-tooltip>
</div>
<div class="w-full h-px bg-primary my-4" />
<p v-if="!isGuest" class="mb-4 text-lg">Change Password</p>
<form v-if="!isGuest" @submit.prevent="submitChangePassword">
<ui-text-input-with-label v-model="password" :disabled="changingPassword" type="password" label="Password" class="my-2" />
@ -63,6 +75,9 @@ export default {
},
isGuest() {
return this.usertype === 'guest'
},
isSharingActivity() {
return this.$store.getters['user/getUserSetting']('shareListeningActivity')
}
},
methods: {
@ -111,6 +126,11 @@ export default {
this.$toast.error('Api call failed')
this.changingPassword = false
})
},
changeSharingSetting() {
console.log('test')
console.log(String(!this.isSharingActivity))
this.$store.dispatch('user/updateUserSettings', { shareListeningActivity: !this.isSharingActivity })
}
},
mounted() {}

134
client/pages/social.vue Normal file
View file

@ -0,0 +1,134 @@
<template>
<div class="w-full h-full overflow-y-auto px-2 py-18 md:p-8">
<div class="flex flex-col md:flex-row">
<div class="w-200 my-6 mx-auto">
<div class="grid grid-cols-2 content-between">
<h1 class="text-2xl mb-4 font-book">Social</h1>
<div class="flex py-1 justify-end">
<ui-tooltip text="Share your latest listening activity with other users in the social tab">
<p class="pl-4">
Share Listening Activity
<span class="material-icons icon-text text-sm">info_outlined</span>
</p>
</ui-tooltip>
<ui-toggle-switch v-model="isSharingActivity" @input="changeSharingSetting" />
</div>
</div>
<p v-if="!users.length">No Other User Sessions</p>
<template v-for="(user, num) in users">
<div :key="user.username" class="w-full py-0.5">
<div class="flex items-center mb-1">
<nuxt-link :to="`/item/${user.latest.libraryItemId}`">
<ui-tooltip direction="left" :text="Math.round(user.latest.progress * 100) + '%'">
<covers-book-cover class="absolute left-0 right-0" :library-item="user.libraryItem" :width="bookCoverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</ui-tooltip>
</nuxt-link>
<p class="text-lg px-2 font-book text-white">{{ user.username }}&nbsp;</p>
<div class="">
<p class="text-xs text-white text-opacity-50">{{user.latest.lastUpdate != null ? "Last listened " + $dateDistanceFromNow(user.latest.lastUpdate) : "Never listened" }}</p>
</div>
<div class="flex-grow" />
<div class="w-30 text-right">
<p class="text-sm font-bold">{{ user.minutesListened > 0 ? "Has listened for " + $elapsedPrettyExtended(user.minutesListened) : "No listening time" }}</p>
</div>
<widgets-online-indicator class="mx-2" :value="!!usersOnline[user.id]" />
</div>
<div v-if="num+1 < users.length" class="flex border-t border-white border-opacity-10 my-3"/>
</div>
</template>
</div>
</div>
<div class="h-16" />
</div>
</template>
<script>
import BookCover from '../components/covers/BookCover.vue'
import Cover from '../components/modals/item/tabs/Cover.vue'
export default {
components: { Cover },
data() {
return {
listeningStats: null
}
},
computed: {
coverAspectRatio() {
return this.$store.getters['getServerSetting']('coverAspectRatio')
},
bookCoverAspectRatio() {
return this.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE ? 1 : 1.6
},
bookCoverWidth() {
return 70
},
users() {
if (!this.listeningStats) return []
return this.listeningStats.filter(c => c.latest)
},
userMediaProgress() {
return this.user.mediaProgress || []
},
userItemsFinished() {
if (!this.listeningStats) return []
return this.listeningStats.itemsRead
},
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
},
usersOnline() {
var usermap = {}
this.$store.state.users.users.forEach((u) => (usermap[u.id] = { online: true, session: u.session }))
return usermap
},
isSharingActivity() {
return this.$store.getters['user/getUserSetting']('shareListeningActivity')
}
},
methods: {
async init() {
let listeningStats = await this.$axios.$get(`/api/social`).catch((err) => {
console.error('Failed to load shared user listening sesions', err)
return []
})
for (let i = 0; i < listeningStats.length; i++) {
if (listeningStats[i].latest != null) {
listeningStats[i].libraryItem = await this.$axios
.$get(`/api/items/${listeningStats[i].latest.libraryItemId}`)
.catch((error) => {
console.error('Failed', error)
})
}
}
console.log('Loaded users shared listening data', listeningStats)
this.listeningStats = listeningStats
},
async getLibraryItem(user) {
user.libraryItem = await this.$axios
.$get(`/api/items/${id}`)
.catch((error) => {
console.error('Failed', error)
return false
})
},
changeSharingSetting() {
console.log('test')
console.log(String(!this.isSharingActivity))
this.$store.dispatch('user/updateUserSettings', { shareListeningActivity: !this.isSharingActivity })
this.init()
}
},
mounted() {
this.init()
}
}
</script>

View file

@ -263,5 +263,27 @@ class MiscController {
})
res.json(tags)
}
// GET: api/social
async getSocialStats(req, res) {
var filteredUsers = this.db.users.filter(c => c.settings.shareListeningActivity == true)
var userData = []
for (let i = 0; i < filteredUsers.length; i++) {
var user = filteredUsers[i]
var listeningStats = await this.getUserListeningStatsHelpers(user.id)
var latestItem = user.mediaProgress.sort(p => p.lastUpdate)[0]
var totalItems = Object.keys(listeningStats.items).length
var totalDays = Object.keys(listeningStats.days).length
var session = {
username: user.username,
lastSeen: user.lastSeen,
latest: latestItem || null,
itemsRead: totalItems || 0,
minutesListened: listeningStats.totalTime || 0,
daysListened: totalDays| 0,
}
userData.push(session)
}
res.json(userData)
}
}
module.exports = new MiscController()

View file

@ -68,7 +68,8 @@ class User {
filterBy: 'all',
playbackRate: 1,
bookshelfCoverSize: 120,
collapseSeries: false
collapseSeries: false,
shareListeningActivity: false
}
}

View file

@ -209,6 +209,7 @@ class ApiRouter {
this.router.get('/search/podcast', MiscController.findPodcasts.bind(this))
this.router.get('/search/authors', MiscController.findAuthor.bind(this))
this.router.get('/search/chapters', MiscController.findChapters.bind(this))
this.router.get('/social', MiscController.getSocialStats.bind(this))
this.router.get('/tags', MiscController.getAllTags.bind(this))
}