mirror of
https://github.com/koel/koel
synced 2024-11-10 14:44:13 +00:00
feat: add loading skeletons for Search screens
This commit is contained in:
parent
61b6ffeeb1
commit
18fe84e1d0
10 changed files with 90 additions and 54 deletions
|
@ -15,17 +15,17 @@
|
|||
|
||||
<template v-else>
|
||||
<div class="two-cols">
|
||||
<MostPlayedSongs data-testid="most-played-songs"/>
|
||||
<RecentlyPlayedSongs data-testid="recently-played-songs"/>
|
||||
<MostPlayedSongs data-testid="most-played-songs" :loading="loading"/>
|
||||
<RecentlyPlayedSongs data-testid="recently-played-songs" :loading="loading"/>
|
||||
</div>
|
||||
|
||||
<div class="two-cols">
|
||||
<RecentlyAddedAlbums data-testid="recently-added-albums"/>
|
||||
<RecentlyAddedSongs data-testid="recently-added-songs"/>
|
||||
<RecentlyAddedAlbums data-testid="recently-added-albums" :loading="loading"/>
|
||||
<RecentlyAddedSongs data-testid="recently-added-songs" :loading="loading"/>
|
||||
</div>
|
||||
|
||||
<MostPlayedArtists data-testid="most-played-artists"/>
|
||||
<MostPlayedAlbums data-testid="most-played-albums"/>
|
||||
<MostPlayedArtists data-testid="most-played-artists" :loading="loading"/>
|
||||
<MostPlayedAlbums data-testid="most-played-albums" :loading="loading"/>
|
||||
|
||||
<ToTopButton/>
|
||||
</template>
|
||||
|
@ -36,7 +36,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { faVolumeOff } from '@fortawesome/free-solid-svg-icons'
|
||||
import { sample } from 'lodash'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { eventBus, noop } from '@/utils'
|
||||
import { commonStore, overviewStore, userStore } from '@/stores'
|
||||
import { useAuthorization, useInfiniteScroll } from '@/composables'
|
||||
|
@ -69,16 +69,15 @@ const greetings = [
|
|||
const greeting = computed(() => sample(greetings)!.replace('%s', userStore.current?.name))
|
||||
const libraryEmpty = computed(() => commonStore.state.song_length === 0)
|
||||
|
||||
const loading = ref(false)
|
||||
let initialized = false
|
||||
|
||||
eventBus.on('LOAD_MAIN_CONTENT', async (view: MainViewName) => {
|
||||
if (view === 'Home' && !initialized) {
|
||||
try {
|
||||
await overviewStore.init()
|
||||
initialized = true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
loading.value = true
|
||||
await overviewStore.init()
|
||||
initialized = true
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -19,12 +19,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRef } from 'vue'
|
||||
import { toRef, toRefs } from 'vue'
|
||||
import { overviewStore } from '@/stores'
|
||||
|
||||
import AlbumCard from '@/components/album/AlbumCard.vue'
|
||||
import AlbumCardSkeleton from '@/components/ui/skeletons/ArtistAlbumCardSkeleton.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{ loading: boolean }>(), { loading: false })
|
||||
const { loading } = toRefs(props)
|
||||
|
||||
const albums = toRef(overviewStore.state, 'mostPlayedAlbums')
|
||||
const loading = toRef(overviewStore.state, 'loading')
|
||||
</script>
|
||||
|
|
|
@ -19,12 +19,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRef } from 'vue'
|
||||
import { toRef, toRefs } from 'vue'
|
||||
import { overviewStore } from '@/stores'
|
||||
|
||||
import ArtistCard from '@/components/artist/ArtistCard.vue'
|
||||
import ArtistCardSkeleton from '@/components/ui/skeletons/ArtistAlbumCardSkeleton.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{ loading: boolean }>(), { loading: false })
|
||||
const { loading } = toRefs(props)
|
||||
|
||||
const artists = toRef(overviewStore.state, 'mostPlayedArtists')
|
||||
const loading = toRef(overviewStore.state, 'loading')
|
||||
</script>
|
||||
|
|
|
@ -18,12 +18,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRef } from 'vue'
|
||||
import { toRef, toRefs } from 'vue'
|
||||
import { overviewStore } from '@/stores'
|
||||
|
||||
import SongCard from '@/components/song/SongCard.vue'
|
||||
import SongCardSkeleton from '@/components/ui/skeletons/SongCardSkeleton.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{ loading: boolean }>(), { loading: false })
|
||||
const { loading } = toRefs(props)
|
||||
|
||||
const songs = toRef(overviewStore.state, 'mostPlayedSongs')
|
||||
const loading = toRef(overviewStore.state, 'loading')
|
||||
</script>
|
||||
|
|
|
@ -19,12 +19,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRef } from 'vue'
|
||||
import { toRef, toRefs } from 'vue'
|
||||
import { overviewStore } from '@/stores'
|
||||
|
||||
import AlbumCard from '@/components/album/AlbumCard.vue'
|
||||
import AlbumCardSkeleton from '@/components/ui/skeletons/ArtistAlbumCardSkeleton.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{ loading: boolean }>(), { loading: false })
|
||||
const { loading } = toRefs(props)
|
||||
|
||||
const albums = toRef(overviewStore.state, 'recentlyAddedAlbums')
|
||||
const loading = toRef(overviewStore.state, 'loading')
|
||||
</script>
|
||||
|
|
|
@ -18,12 +18,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRef } from 'vue'
|
||||
import { toRef, toRefs } from 'vue'
|
||||
import { overviewStore } from '@/stores'
|
||||
|
||||
import SongCard from '@/components/song/SongCard.vue'
|
||||
import SongCardSkeleton from '@/components/ui/skeletons/SongCardSkeleton.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{ loading: boolean }>(), { loading: false })
|
||||
const { loading } = toRefs(props)
|
||||
|
||||
const songs = toRef(overviewStore.state, 'recentlyAddedSongs')
|
||||
const loading = toRef(overviewStore.state, 'loading')
|
||||
</script>
|
||||
|
|
|
@ -31,16 +31,18 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRef } from 'vue'
|
||||
import { toRef, toRefs } from 'vue'
|
||||
import router from '@/router'
|
||||
import { overviewStore, recentlyPlayedStore } from '@/stores'
|
||||
import { recentlyPlayedStore } from '@/stores'
|
||||
|
||||
import Btn from '@/components/ui/Btn.vue'
|
||||
import SongCard from '@/components/song/SongCard.vue'
|
||||
import SongCardSkeleton from '@/components/ui/skeletons/SongCardSkeleton.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{ loading: boolean }>(), { loading: false })
|
||||
const { loading } = toRefs(props)
|
||||
|
||||
const songs = toRef(recentlyPlayedStore.excerptState, 'songs')
|
||||
const loading = toRef(overviewStore.state, 'loading')
|
||||
|
||||
const goToRecentlyPlayedScreen = () => router.go('recently-played')
|
||||
</script>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<h1>
|
||||
Songs
|
||||
<Btn
|
||||
v-if="excerpt.songs.length"
|
||||
v-if="excerpt.songs.length && !searching"
|
||||
data-testid="view-all-songs-btn"
|
||||
orange
|
||||
rounded
|
||||
|
@ -21,32 +21,53 @@
|
|||
View All
|
||||
</Btn>
|
||||
</h1>
|
||||
<ul v-if="excerpt.songs.length">
|
||||
<li v-for="song in excerpt.songs" :key="song.id">
|
||||
<SongCard :song="song"/>
|
||||
<ul v-if="searching">
|
||||
<li v-for="i in 6" :key="i">
|
||||
<SongCardSkeleton/>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else>None found.</p>
|
||||
<template v-else>
|
||||
<ul v-if="excerpt.songs.length">
|
||||
<li v-for="song in excerpt.songs" :key="song.id">
|
||||
<SongCard :song="song"/>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else>None found.</p>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="artists" data-testid="artist-excerpts">
|
||||
<h1>Artists</h1>
|
||||
<ul v-if="excerpt.artists.length">
|
||||
<li v-for="artist in excerpt.artists" :key="artist.id">
|
||||
<ArtistCard :artist="artist" layout="compact"/>
|
||||
<ul v-if="searching">
|
||||
<li v-for="i in 6" :key="i">
|
||||
<ArtistAlbumCardSkeleton layout="compact"/>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else>None found.</p>
|
||||
<template v-else>
|
||||
<ul v-if="excerpt.artists.length">
|
||||
<li v-for="artist in excerpt.artists" :key="artist.id">
|
||||
<ArtistCard :artist="artist" layout="compact"/>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else>None found.</p>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="albums" data-testid="album-excerpts">
|
||||
<h1>Albums</h1>
|
||||
<ul v-if="excerpt.albums.length">
|
||||
<li v-for="album in excerpt.albums" :key="album.id">
|
||||
<AlbumCard :album="album" layout="compact"/>
|
||||
<ul v-if="searching">
|
||||
<li v-for="i in 6" :key="i">
|
||||
<ArtistAlbumCardSkeleton layout="compact"/>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else>None found.</p>
|
||||
<template v-else>
|
||||
<ul v-if="excerpt.albums.length">
|
||||
<li v-for="album in excerpt.albums" :key="album.id">
|
||||
<AlbumCard :album="album" layout="compact"/>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else>None found.</p>
|
||||
</template>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
@ -74,15 +95,20 @@ import ArtistCard from '@/components/artist/ArtistCard.vue'
|
|||
import AlbumCard from '@/components/album/AlbumCard.vue'
|
||||
import Btn from '@/components/ui/Btn.vue'
|
||||
import SongCard from '@/components/song/SongCard.vue'
|
||||
import SongCardSkeleton from '@/components/ui/skeletons/SongCardSkeleton.vue'
|
||||
import ArtistAlbumCardSkeleton from '@/components/ui/skeletons/ArtistAlbumCardSkeleton.vue'
|
||||
|
||||
const excerpt = toRef(searchStore.state, 'excerpt')
|
||||
const q = ref('')
|
||||
const searching = ref(false)
|
||||
|
||||
const goToSongResults = () => router.go(`search/songs/${q.value}`)
|
||||
|
||||
eventBus.on('SEARCH_KEYWORDS_CHANGED', (_q: string) => {
|
||||
eventBus.on('SEARCH_KEYWORDS_CHANGED', async (_q: string) => {
|
||||
q.value = _q
|
||||
searchStore.excerptSearch(q.value)
|
||||
searching.value = true
|
||||
await searchStore.excerptSearch(q.value)
|
||||
searching.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -22,17 +22,19 @@
|
|||
</template>
|
||||
</ScreenHeader>
|
||||
|
||||
<SongList ref="songList" @sort="sort" @press:enter="onPressEnter" @scroll-breakpoint="onScrollBreakpoint"/>
|
||||
<SongListSkeleton v-if="loading"/>
|
||||
<SongList v-else ref="songList" @sort="sort" @press:enter="onPressEnter" @scroll-breakpoint="onScrollBreakpoint"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, toRef, toRefs } from 'vue'
|
||||
import { computed, onMounted, ref, toRef, toRefs } from 'vue'
|
||||
import { searchStore } from '@/stores'
|
||||
import { useSongList } from '@/composables'
|
||||
import { pluralize } from '@/utils'
|
||||
|
||||
import ScreenHeader from '@/components/ui/ScreenHeader.vue'
|
||||
import SongListSkeleton from '@/components/ui/skeletons/SongListSkeleton.vue'
|
||||
|
||||
const props = defineProps<{ q: string }>()
|
||||
const { q } = toRefs(props)
|
||||
|
@ -58,7 +60,13 @@ const {
|
|||
} = useSongList(toRef(searchStore.state, 'songs'), 'search-results')
|
||||
|
||||
const decodedQ = computed(() => decodeURIComponent(q.value))
|
||||
const loading = ref(false)
|
||||
|
||||
searchStore.resetSongResultState()
|
||||
searchStore.songSearch(decodedQ.value)
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
await searchStore.songSearch(q.value)
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -12,13 +12,10 @@ export const overviewStore = {
|
|||
recentlyAddedAlbums: [],
|
||||
mostPlayedSongs: [],
|
||||
mostPlayedAlbums: [],
|
||||
mostPlayedArtists: [],
|
||||
loading: false
|
||||
mostPlayedArtists: []
|
||||
}),
|
||||
|
||||
async init () {
|
||||
this.state.loading = true
|
||||
|
||||
const resource = await httpService.get<{
|
||||
most_played_songs: Song[],
|
||||
most_played_albums: Album[],
|
||||
|
@ -35,7 +32,6 @@ export const overviewStore = {
|
|||
recentlyPlayedStore.excerptState.songs = songStore.syncWithVault(resource.recently_played_songs)
|
||||
|
||||
this.refresh()
|
||||
this.state.loading = false
|
||||
},
|
||||
|
||||
refresh () {
|
||||
|
|
Loading…
Reference in a new issue