feat: add loading skeletons for Search screens

This commit is contained in:
Phan An 2022-08-01 09:55:23 +02:00
parent 61b6ffeeb1
commit 18fe84e1d0
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
10 changed files with 90 additions and 54 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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 () {