Skip to content

Commit

Permalink
Merge pull request #289 from openzim/implement-infinite-scroll
Browse files Browse the repository at this point in the history
Implement infinite scroll for video/playlist lists
  • Loading branch information
benoit74 authored Aug 9, 2024
2 parents 05d2b5d + d1930a0 commit a679fe3
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 54 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Disable preloading of subtitles in video.js in `zimui` (#38)
- Fix scraper to exit properly when `Too much videos failed to download` exceptions are raised (#285)
- Clean up temporary files properly in case of exceptions during scraper run (#288)
- Implement infinite scroll for video/playlist lists and add loading spinners in `zimui` (#284)

## [3.0.0] - 2024-07-29

Expand Down
23 changes: 15 additions & 8 deletions zimui/src/components/channel/tabs/PlaylistsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import PlaylistGrid from '@/components/playlist/PlaylistGrid.vue'
import TabInfo from '@/components/common/ViewInfo.vue'
const main = useMainStore()
const playlists = ref<PlaylistPreview[]>()
const playlists = ref<PlaylistPreview[]>([])
const isLoading = ref(true)
// Watch for changes in the main playlist
watch(
Expand All @@ -25,6 +26,7 @@ const fetchData = async function () {
const resp = await main.fetchPlaylists()
if (resp) {
playlists.value = resp.playlists
isLoading.value = false
}
} catch (error) {
main.setErrorMessage('An unexpected error occured when fetching playlists.')
Expand All @@ -39,11 +41,16 @@ onMounted(() => {
</script>

<template>
<tab-info
:title="'Playlists from ' + main.channel?.title || ''"
:count="playlists?.length || 0"
:count-text="playlists?.length === 1 ? 'playlist' : 'playlists'"
icon="mdi-playlist-play"
/>
<playlist-grid v-if="playlists" :playlists="playlists" />
<div v-if="isLoading" class="container mt-8 d-flex justify-center">
<v-progress-circular class="d-inline" indeterminate></v-progress-circular>
</div>
<div v-else>
<tab-info
:title="'Playlists from ' + main.channel?.title || ''"
:count="playlists?.length || 0"
:count-text="playlists?.length === 1 ? 'playlist' : 'playlists'"
icon="mdi-playlist-play"
/>
<playlist-grid v-if="playlists" :playlists="playlists" />
</div>
</template>
21 changes: 14 additions & 7 deletions zimui/src/components/channel/tabs/VideosTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { Playlist } from '@/types/Playlists'
const main = useMainStore()
const videos = ref<VideoPreview[]>([])
const playlist = ref<Playlist>()
const isLoading = ref(true)
// Watch for changes in the main playlist
watch(
Expand All @@ -28,6 +29,7 @@ const fetchData = async function () {
if (resp) {
playlist.value = resp
videos.value = resp.videos
isLoading.value = false
}
} catch (error) {
main.setErrorMessage('An unexpected error occured when fetching videos.')
Expand All @@ -42,11 +44,16 @@ onMounted(() => {
</script>

<template>
<tab-info
:title="playlist?.title || 'Main Playlist'"
:count="playlist?.videosCount || 0"
:count-text="playlist?.videos.length === 1 ? 'video' : 'videos'"
icon="mdi-video-outline"
/>
<video-grid v-if="videos" :videos="videos" :playlist-slug="main.channel?.mainPlaylist" />
<div v-if="isLoading" class="container mt-8 d-flex justify-center">
<v-progress-circular class="d-inline" indeterminate></v-progress-circular>
</div>
<div v-else>
<tab-info
:title="playlist?.title || 'Main Playlist'"
:count="playlist?.videosCount || 0"
:count-text="playlist?.videos.length === 1 ? 'video' : 'videos'"
icon="mdi-video-outline"
/>
<video-grid v-if="videos" :videos="videos" :playlist-slug="main.channel?.mainPlaylist" />
</div>
</template>
51 changes: 37 additions & 14 deletions zimui/src/components/playlist/PlaylistGrid.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useDisplay } from 'vuetify'
import type { PlaylistPreview } from '@/types/Playlists'
import PlaylistCard from './PlaylistCard.vue'
Expand All @@ -8,23 +9,45 @@ const { mdAndDown } = useDisplay()
const props = defineProps<{
playlists: PlaylistPreview[]
}>()
const items = computed(() => props.playlists.slice(0, 48))
const loadMoreItems = async () => {
return new Promise<PlaylistPreview[]>((resolve) => {
setTimeout(() => {
resolve(props.playlists.slice(items.value.length, items.value.length + 12))
}, 100)
})
}
const load = async ({ done }: { done: (status: 'ok' | 'empty') => void }) => {
const moreItems = await loadMoreItems()
items.value.push(...moreItems)
if (items.value.length === props.playlists.length) {
done('empty')
return
}
done('ok')
}
</script>

<template>
<v-container class="px-1" :fluid="mdAndDown">
<v-row dense>
<v-col
v-for="playlist in props.playlists"
:key="playlist.id"
cols="12"
md="4"
lg="3"
xl="2"
xxl="1"
class="mb-2 mb-md-6"
>
<playlist-card :playlist="playlist" />
</v-col>
</v-row>
<v-infinite-scroll class="h-full overflow-hidden" :items="items" empty-text="" @load="load">
<v-row dense>
<v-col
v-for="playlist in items"
:key="playlist.id"
cols="12"
md="4"
lg="3"
xl="2"
xxl="1"
class="mb-2 mb-md-6"
>
<playlist-card :playlist="playlist" />
</v-col>
</v-row>
</v-infinite-scroll>
</v-container>
</template>
48 changes: 38 additions & 10 deletions zimui/src/components/playlist/panel/PlaylistPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Playlist } from '@/types/Playlists'
import { LoopOptions } from '@/types/Playlists'
import PlaylistPanelItem from './PlaylistPanelItem.vue'
import type { VideoPreview } from '@/types/Videos'
const { smAndDown } = useDisplay()
Expand All @@ -19,6 +20,8 @@ const props = defineProps<{
shuffle: boolean
}>()
const isLoading = computed(() => props.playlist.videos === undefined)
const windowHeight = ref(
window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
)
Expand Down Expand Up @@ -51,10 +54,33 @@ const scrollToCurrentVideo = () => {
}
const emit = defineEmits(['shuffle', 'loop', 'hide-panel'])
const items = computed(() => props.playlist.videos.slice(0, 48))
const loadMoreItems = async () => {
return new Promise<VideoPreview[]>((resolve) => {
setTimeout(() => {
resolve(props.playlist.videos.slice(items.value.length, items.value.length + 12))
}, 100)
})
}
const load = async ({ done }: { done: (status: 'ok' | 'empty') => void }) => {
const moreItems = await loadMoreItems()
items.value.push(...moreItems)
if (items.value.length === props.playlist.videos.length) {
done('empty')
return
}
done('ok')
}
</script>

<template>
<v-card class="border-thin rounded-lg" flat>
<div v-if="isLoading" class="container mt-8 d-flex justify-center">
<v-progress-circular class="d-inline" indeterminate></v-progress-circular>
</div>
<v-card v-else class="border-thin rounded-lg" flat>
<v-card-item class="border-b-thin px-2">
<v-row class="px-2">
<v-col :cols="showToggle ? 9 : 12">
Expand Down Expand Up @@ -111,15 +137,17 @@ const emit = defineEmits(['shuffle', 'loop', 'hide-panel'])

<v-card-item class="pa-0">
<div id="panel-items-container" :style="{ height: panelContainerHeight }">
<playlist-panel-item
v-for="(item, index) in props.playlist.videos"
:id="`video-item-${index}`"
:key="item.slug"
:video="item"
:order="index + 1"
:selected="item.slug === props.videoSlug"
:playlist-slug="props.playlistSlug"
></playlist-panel-item>
<v-infinite-scroll class="h-full overflow-hidden" :items="items" empty-text="" @load="load">
<playlist-panel-item
v-for="(item, index) in items"
:id="`video-item-${index}`"
:key="item.slug"
:video="item"
:order="index + 1"
:selected="item.slug === props.videoSlug"
:playlist-slug="props.playlistSlug"
></playlist-panel-item>
</v-infinite-scroll>
</div>
</v-card-item>
</v-card>
Expand Down
51 changes: 37 additions & 14 deletions zimui/src/components/video/VideoGrid.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useDisplay } from 'vuetify'
import type { VideoPreview } from '@/types/Videos'
import VideoCard from './VideoCard.vue'
Expand All @@ -9,23 +10,45 @@ const props = defineProps<{
videos: VideoPreview[]
playlistSlug?: string
}>()
const items = computed(() => props.videos.slice(0, 48))
const loadMoreItems = async () => {
return new Promise<VideoPreview[]>((resolve) => {
setTimeout(() => {
resolve(props.videos.slice(items.value.length, items.value.length + 12))
}, 100)
})
}
const load = async ({ done }: { done: (status: 'ok' | 'empty') => void }) => {
const moreItems = await loadMoreItems()
items.value.push(...moreItems)
if (items.value.length === props.videos.length) {
done('empty')
return
}
done('ok')
}
</script>

<template>
<v-container class="px-1" :fluid="mdAndDown">
<v-row dense>
<v-col
v-for="video in props.videos"
:key="video.id"
cols="12"
md="4"
lg="3"
xl="2"
xxl="1"
class="mb-2 mb-md-6"
>
<video-card :video="video" :playlist-slug="playlistSlug" />
</v-col>
</v-row>
<v-infinite-scroll class="h-full overflow-hidden" :items="items" empty-text="" @load="load">
<v-row dense>
<v-col
v-for="video in items"
:key="video.id"
cols="12"
md="4"
lg="3"
xl="2"
xxl="1"
class="mb-2 mb-md-6"
>
<video-card :video="video" :playlist-slug="playlistSlug" />
</v-col>
</v-row>
</v-infinite-scroll>
</v-container>
</template>
7 changes: 6 additions & 1 deletion zimui/src/views/PlaylistView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const slug: string = route.params.slug as string
const playlist: Ref<Playlist> = ref<Playlist>() as Ref<Playlist>
const thumbnailSrc = ref('')
const isLoading = ref(true)
// Fetch playlist data
const fetchPlaylistData = async function () {
Expand All @@ -26,6 +27,7 @@ const fetchPlaylistData = async function () {
const resp = await main.fetchPlaylist(slug)
if (resp) {
playlist.value = resp
isLoading.value = false
}
} catch (error) {
main.setErrorMessage('An unexpected error occured when fetching playlist data.')
Expand Down Expand Up @@ -70,7 +72,10 @@ const { mdAndDown } = useDisplay()
</script>

<template>
<v-container v-if="playlist" :fluid="mdAndDown">
<div v-if="isLoading" class="container h-screen d-flex justify-center align-center">
<v-progress-circular class="d-inline" indeterminate></v-progress-circular>
</div>
<v-container v-else :fluid="mdAndDown">
<v-row>
<v-spacer />
<v-col cols="12" md="5" lg="4" xl="3" xxl="2">
Expand Down

0 comments on commit a679fe3

Please sign in to comment.