Migrate home component
Adds some code that is a starter to #1316 and #1534 but depends on #1827
This commit is contained in:
parent
7408fe17ec
commit
e03e2ec901
|
@ -1,3 +1,78 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Track, Listening } from '~/types'
|
||||||
|
import useWebSocketHandler from '~/composables/useWebSocketHandler'
|
||||||
|
|
||||||
|
// TODO (wvffle): Fix websocket update (#1534)
|
||||||
|
import { clone } from 'lodash-es'
|
||||||
|
import axios from 'axios'
|
||||||
|
import PlayButton from '~/components/audio/PlayButton.vue'
|
||||||
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
filters: Record<string, string>
|
||||||
|
url: string
|
||||||
|
isActivity?: boolean
|
||||||
|
showCount?: boolean
|
||||||
|
limit?: number
|
||||||
|
itemClasses?: string
|
||||||
|
websocketHandlers?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
isActivity: true,
|
||||||
|
showCount: false,
|
||||||
|
limit: 5,
|
||||||
|
itemClasses: '',
|
||||||
|
websocketHandlers: () => []
|
||||||
|
})
|
||||||
|
|
||||||
|
const objects = reactive([] as Listening[])
|
||||||
|
const count = ref(0)
|
||||||
|
const nextPage = ref<string | null>(null)
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const fetchData = async (url = props.url) => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
...clone(props.filters),
|
||||||
|
page_size: props.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, { params })
|
||||||
|
nextPage.value = response.data.next
|
||||||
|
count.value = response.data.count
|
||||||
|
|
||||||
|
const newObjects = !props.isActivity
|
||||||
|
? response.data.results.map((track: Track) => { track })
|
||||||
|
: response.data.results
|
||||||
|
|
||||||
|
objects.push(...newObjects)
|
||||||
|
} catch (error) {
|
||||||
|
// TODO (wvffle): Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
|
||||||
|
const emit = defineEmits(['count'])
|
||||||
|
watch(count, (to) => emit('count', to))
|
||||||
|
|
||||||
|
if (props.websocketHandlers.includes('Listen')) {
|
||||||
|
useWebSocketHandler('Listen', (event) => {
|
||||||
|
// TODO (wvffle): Add reactivity to recently listened / favorited / added (#1316, #1534)
|
||||||
|
// count.value += 1
|
||||||
|
|
||||||
|
// objects.unshift(event as Listening)
|
||||||
|
// objects.pop()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="component-track-widget">
|
<div class="component-track-widget">
|
||||||
<h3 v-if="!!$slots.title">
|
<h3 v-if="!!$slots.title">
|
||||||
|
@ -28,7 +103,7 @@
|
||||||
alt=""
|
alt=""
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-else-if="object.track.artist.cover"
|
v-else-if="object.track.artist?.cover"
|
||||||
v-lazy="$store.getters['instance/absoluteUrl'](object.track.artist.cover.urls.medium_square_crop)"
|
v-lazy="$store.getters['instance/absoluteUrl'](object.track.artist.cover.urls.medium_square_crop)"
|
||||||
alt=""
|
alt=""
|
||||||
>
|
>
|
||||||
|
@ -52,7 +127,7 @@
|
||||||
{{ object.track.title }}
|
{{ object.track.title }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta ellipsis">
|
<div v-if="object.track.artist" class="meta ellipsis">
|
||||||
<span>
|
<span>
|
||||||
<router-link
|
<router-link
|
||||||
class="discrete link"
|
class="discrete link"
|
||||||
|
@ -122,9 +197,8 @@
|
||||||
<template v-if="nextPage">
|
<template v-if="nextPage">
|
||||||
<div class="ui hidden divider" />
|
<div class="ui hidden divider" />
|
||||||
<button
|
<button
|
||||||
v-if="nextPage"
|
|
||||||
:class="['ui', 'basic', 'button']"
|
:class="['ui', 'basic', 'button']"
|
||||||
@click="fetchData(nextPage)"
|
@click="fetchData(nextPage as string)"
|
||||||
>
|
>
|
||||||
<translate translate-context="*/*/Button,Label">
|
<translate translate-context="*/*/Button,Label">
|
||||||
Show more
|
Show more
|
||||||
|
@ -133,87 +207,3 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import { clone } from 'lodash-es'
|
|
||||||
import axios from 'axios'
|
|
||||||
import PlayButton from '~/components/audio/PlayButton.vue'
|
|
||||||
import TagsList from '~/components/tags/List.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
PlayButton,
|
|
||||||
TagsList
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
filters: { type: Object, required: true },
|
|
||||||
url: { type: String, required: true },
|
|
||||||
isActivity: { type: Boolean, default: true },
|
|
||||||
showCount: { type: Boolean, default: false },
|
|
||||||
limit: { type: Number, default: 5 },
|
|
||||||
itemClasses: { type: String, default: '' }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
objects: [],
|
|
||||||
count: 0,
|
|
||||||
isLoading: false,
|
|
||||||
errors: null,
|
|
||||||
previousPage: null,
|
|
||||||
nextPage: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
offset () {
|
|
||||||
this.fetchData()
|
|
||||||
},
|
|
||||||
'$store.state.moderation.lastUpdate': function () {
|
|
||||||
this.fetchData(this.url)
|
|
||||||
},
|
|
||||||
count (v) {
|
|
||||||
this.$emit('count', v)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchData(this.url)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData (url) {
|
|
||||||
if (!url) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.isLoading = true
|
|
||||||
const self = this
|
|
||||||
const params = clone(this.filters)
|
|
||||||
params.page_size = this.limit
|
|
||||||
params.offset = this.offset
|
|
||||||
axios.get(url, { params: params }).then((response) => {
|
|
||||||
self.previousPage = response.data.previous
|
|
||||||
self.nextPage = response.data.next
|
|
||||||
self.isLoading = false
|
|
||||||
self.count = response.data.count
|
|
||||||
let newObjects
|
|
||||||
if (self.isActivity) {
|
|
||||||
// we have listening/favorites objects, not directly tracks
|
|
||||||
newObjects = response.data.results
|
|
||||||
} else {
|
|
||||||
newObjects = response.data.results.map((r) => {
|
|
||||||
return { track: r }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
self.objects = [...self.objects, ...newObjects]
|
|
||||||
}, error => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateOffset (increment) {
|
|
||||||
if (increment) {
|
|
||||||
this.offset += this.limit
|
|
||||||
} else {
|
|
||||||
this.offset = Math.max(this.offset - this.limit, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,6 +1,57 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import axios from 'axios'
|
||||||
|
import ChannelsWidget from '~/components/audio/ChannelsWidget.vue'
|
||||||
|
import TrackWidget from '~/components/audio/track/Widget.vue'
|
||||||
|
import AlbumWidget from '~/components/audio/album/Widget.vue'
|
||||||
|
import PlaylistWidget from '~/components/playlists/Widget.vue'
|
||||||
|
import useLogger from '~/composables/useLogger'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
scope?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
scope: 'all'
|
||||||
|
})
|
||||||
|
|
||||||
|
const artists = ref([])
|
||||||
|
|
||||||
|
const logger = useLogger()
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const labels = computed(() => ({
|
||||||
|
title: $pgettext('Head/Home/Title', 'Library')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
logger.time('Loading latest artists')
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
ordering: '-creation_date',
|
||||||
|
playable: true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get('artists/', { params: params })
|
||||||
|
artists.value = response.data.results
|
||||||
|
} catch (error) {
|
||||||
|
// TODO (wvffle): Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
logger.timeEnd('Loading latest artists')
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main
|
<main
|
||||||
:key="$route.name"
|
:key="$route?.name ?? undefined"
|
||||||
v-title="labels.title"
|
v-title="labels.title"
|
||||||
>
|
>
|
||||||
<section class="ui vertical stripe segment">
|
<section class="ui vertical stripe segment">
|
||||||
|
@ -8,7 +59,8 @@
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<track-widget
|
<track-widget
|
||||||
:url="'history/listenings/'"
|
:url="'history/listenings/'"
|
||||||
:filters="{scope: scope, ordering: '-creation_date'}"
|
:filters="{ scope, ordering: '-creation_date' }"
|
||||||
|
:websocket-handlers="['Listen']"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<translate translate-context="Content/Home/Title">
|
<translate translate-context="Content/Home/Title">
|
||||||
|
@ -69,62 +121,3 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
import ChannelsWidget from '~/components/audio/ChannelsWidget.vue'
|
|
||||||
import TrackWidget from '~/components/audio/track/Widget.vue'
|
|
||||||
import AlbumWidget from '~/components/audio/album/Widget.vue'
|
|
||||||
import PlaylistWidget from '~/components/playlists/Widget.vue'
|
|
||||||
import useLogger from '~/composables/useLogger'
|
|
||||||
|
|
||||||
const logger = useLogger()
|
|
||||||
|
|
||||||
const ARTISTS_URL = 'artists/'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Library',
|
|
||||||
components: {
|
|
||||||
TrackWidget,
|
|
||||||
AlbumWidget,
|
|
||||||
PlaylistWidget,
|
|
||||||
ChannelsWidget
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
scope: { type: String, default: 'all' }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
artists: [],
|
|
||||||
isLoadingArtists: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
title: this.$pgettext('Head/Home/Title', 'Library')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.fetchArtists()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchArtists () {
|
|
||||||
const self = this
|
|
||||||
this.isLoadingArtists = true
|
|
||||||
const params = {
|
|
||||||
ordering: '-creation_date',
|
|
||||||
playable: true
|
|
||||||
}
|
|
||||||
const url = ARTISTS_URL
|
|
||||||
logger.time('Loading latest artists')
|
|
||||||
axios.get(url, { params: params }).then(response => {
|
|
||||||
self.artists = response.data.results
|
|
||||||
logger.timeEnd('Loading latest artists')
|
|
||||||
self.isLoadingArtists = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -148,8 +148,9 @@ const store: Module<State, RootState> = {
|
||||||
if (!rootState.auth.authenticated) {
|
if (!rootState.auth.authenticated) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return axios.post('history/listenings/', { track: track.id }).then(() => {}, () => {
|
|
||||||
logger.error('Could not record track in history')
|
return axios.post('history/listenings/', { track: track.id }).catch((error) => {
|
||||||
|
logger.error('Could not record track in history', error)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
trackEnded ({ commit, dispatch, rootState }) {
|
trackEnded ({ commit, dispatch, rootState }) {
|
||||||
|
|
|
@ -186,6 +186,14 @@ export interface Radio {
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Listening {
|
||||||
|
id: string
|
||||||
|
track: Track
|
||||||
|
user: User
|
||||||
|
actor: Actor
|
||||||
|
creation_date: string
|
||||||
|
}
|
||||||
|
|
||||||
// API stuff
|
// API stuff
|
||||||
export interface APIErrorResponse extends Record<string, APIErrorResponse | string[] | { code: string }[]> {}
|
export interface APIErrorResponse extends Record<string, APIErrorResponse | string[] | { code: string }[]> {}
|
||||||
|
|
||||||
|
@ -226,6 +234,11 @@ export interface ListenWSEvent {
|
||||||
object: ListenWsEventObject
|
object: ListenWsEventObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO (wvffle): Add reactivity to recently listened / favorited / added (#1316, #1534)
|
||||||
|
// export interface ListenWSEvent extends Listening {
|
||||||
|
// type: 'Listen'
|
||||||
|
// }
|
||||||
|
|
||||||
export type WebSocketEvent = PendingReviewEditsWSEvent | PendingReviewReportsWSEvent | PendingReviewRequestsWSEvent | ListenWSEvent
|
export type WebSocketEvent = PendingReviewEditsWSEvent | PendingReviewReportsWSEvent | PendingReviewRequestsWSEvent | ListenWSEvent
|
||||||
|
|
||||||
// FS Browser
|
// FS Browser
|
||||||
|
|
Loading…
Reference in New Issue