Skip to content

Commit

Permalink
Merge pull request #349 from openzim/new-landing-page-ui
Browse files Browse the repository at this point in the history
Update layout of `Videos` tab in `zimui` to display videos from all playlists in the ZIM
  • Loading branch information
benoit74 authored Oct 8, 2024
2 parents cde86e3 + c7639c4 commit e1a0f2b
Show file tree
Hide file tree
Showing 19 changed files with 582 additions and 70 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Merge behaviors of user/channel types and add support for `forHandle` (#339, fix for #338)
- Update layout of `Videos` tab in `zimui` to display videos from all playlists in the ZIM (#337)

## [3.1.0] - 2024-09-05

Expand Down
7 changes: 7 additions & 0 deletions scraper/src/youtube2zim/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class Playlist(CamelModel):
"""Class to serialize data about a YouTube playlist."""

id: str
slug: str
author: Author
title: str
description: str
Expand All @@ -87,6 +88,12 @@ class Playlists(CamelModel):
playlists: list[PlaylistPreview]


class HomePlaylists(CamelModel):
"""Class to serialize data about a list of YouTube playlists."""

playlists: list[Playlist]


class Channel(CamelModel):
"""Class to serialize data about a YouTube channel."""

Expand Down
35 changes: 28 additions & 7 deletions scraper/src/youtube2zim/scraper.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
Author,
Channel,
Config,
HomePlaylists,
Playlist,
PlaylistPreview,
Playlists,
Expand Down Expand Up @@ -1122,6 +1123,7 @@ def generate_playlist_object(playlist) -> Playlist:

return Playlist(
id=playlist.playlist_id,
slug=get_playlist_slug(playlist),
title=playlist.title,
description=playlist.description,
videos=playlist_videos,
Expand Down Expand Up @@ -1177,6 +1179,7 @@ def get_playlist_slug(playlist) -> str:

# write playlists JSON files
playlist_list = []
home_playlist_list = []

main_playlist_slug = None
if len(self.playlists) > 0:
Expand All @@ -1188,13 +1191,6 @@ def get_playlist_slug(playlist) -> str:
playlist_slug = get_playlist_slug(playlist)
playlist_path = f"playlists/{playlist_slug}.json"

if playlist.playlist_id != self.uploads_playlist_id:
playlist_list.append(generate_playlist_preview_object(playlist))
else:
main_playlist_slug = (
playlist_slug # set uploads playlist as main playlist
)

playlist_obj = generate_playlist_object(playlist)
self.zim_file.add_item_for(
path=playlist_path,
Expand All @@ -1212,6 +1208,20 @@ def get_playlist_slug(playlist) -> str:
f"playlist/{playlist_slug}",
)

# modify playlist object for preview on homepage
playlist_obj.videos = playlist_obj.videos[:12]

if playlist.playlist_id == self.uploads_playlist_id:
main_playlist_slug = (
playlist_slug # set uploads playlist as main playlist
)
# insert uploads playlist at the beginning of the list
playlist_list.insert(0, generate_playlist_preview_object(playlist))
home_playlist_list.insert(0, playlist_obj)
else:
playlist_list.append(generate_playlist_preview_object(playlist))
home_playlist_list.append(playlist_obj)

# write playlists.json file
self.zim_file.add_item_for(
path="playlists.json",
Expand All @@ -1223,6 +1233,17 @@ def get_playlist_slug(playlist) -> str:
is_front=False,
)

# write home_playlists.json file
self.zim_file.add_item_for(
path="home_playlists.json",
title="Home Playlists",
content=HomePlaylists(playlists=home_playlist_list).model_dump_json(
by_alias=True, indent=2
),
mimetype="application/json",
is_front=False,
)

# write channel.json file
channel_data = get_channel_json(self.main_channel_id)
self.zim_file.add_item_for(
Expand Down
15 changes: 8 additions & 7 deletions zimui/cypress/e2e/channel_home_page.cy.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
describe('home page for a channel', () => {
beforeEach(() => {
cy.intercept('GET', '/channel.json', { fixture: 'channel/channel.json' }).as('getChannel')
cy.intercept('GET', '/playlists/uploads_from_openzim_testing-917Q.json', {
fixture: 'channel/playlists/uploads_from_openzim_testing-917Q.json'
}).as('getUploads')
cy.intercept('GET', '/home_playlists.json', {
fixture: 'channel/home_playlists.json'
}).as('getHomePlaylists')
cy.visit('/')
cy.wait('@getChannel')
cy.wait('@getUploads')
cy.wait('@getHomePlaylists')
})

it('loads the videos tab', () => {
cy.contains('2 videos').should('be.visible')
cy.contains('Coffee Machine').should('be.visible')
cy.contains('Timelapse').should('be.visible')
cy.contains('Uploads from openZIM_testing').should('be.visible')
cy.contains('Trailers').should('be.visible')
cy.contains('Timelapses').should('be.visible')
cy.contains('Coffee').should('be.visible')
})

it('loads the playlist tab', () => {
Expand Down
10 changes: 7 additions & 3 deletions zimui/cypress/e2e/video_player_page.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@ describe('video player page', () => {
cy.intercept('GET', '/channel.json', { fixture: 'channel/channel.json' }).as('getChannel')
cy.intercept('GET', '/playlists/uploads_from_openzim_testing-917Q.json', {
fixture: 'channel/playlists/uploads_from_openzim_testing-917Q.json'
}).as('getUploads')
}).as('getPlaylist')
cy.intercept('GET', '/home_playlists.json', {
fixture: 'channel/home_playlists.json'
}).as('getHomePlaylists')
cy.intercept('GET', '/videos/sample/video.webm', {
fixture: 'channel/videos/sample/video.webm,null'
}).as('getVideoFile')
cy.visit('/')
cy.wait('@getChannel')
cy.wait('@getUploads')
cy.wait('@getHomePlaylists')
})

it('loads the video and related information', () => {
cy.intercept('GET', '/videos/timelapse-9Tgo.json', {
fixture: 'channel//videos/timelapse-9Tgo.json'
fixture: 'channel/videos/timelapse-9Tgo.json'
}).as('getVideo')
cy.contains('.v-card-title ', 'Timelapse').click()
cy.wait('@getVideo')
cy.wait('@getPlaylist')
cy.wait('@getVideoFile')

cy.url().should('include', '/watch')
Expand Down
122 changes: 122 additions & 0 deletions zimui/cypress/fixtures/channel/home_playlists.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"playlists": [
{
"id": "UU8elThf5TGMpQfQc_VE917Q",
"slug": "uploads_from_openzim_testing-917Q",
"author": {
"channelId": "UC8elThf5TGMpQfQc_VE917Q",
"channelTitle": "openZIM_testing",
"channelDescription": "",
"channelJoinedDate": "2024-06-04T13:30:16.232286Z",
"profilePath": "channels/UC8elThf5TGMpQfQc_VE917Q/profile.jpg",
"bannerPath": "channels/UC8elThf5TGMpQfQc_VE917Q/banner.jpg"
},
"title": "Uploads from openZIM_testing",
"description": "",
"publicationDate": "2024-06-04T14:57:45Z",
"thumbnailPath": "videos/DYvYGQHYScc/video.webp",
"videos": [
{
"slug": "coffee_machine-DYvY",
"id": "DYvYGQHYScc",
"title": "Coffee Machine",
"thumbnailPath": "videos/DYvYGQHYScc/video.webp",
"duration": "PT9S"
},
{
"slug": "timelapse-9Tgo",
"id": "9TgosbGRsTk",
"title": "Timelapse",
"thumbnailPath": "videos/9TgosbGRsTk/video.webp",
"duration": "PT11S"
}
],
"videosCount": 2
},
{
"id": "PLMK6hZr9PcshJpNSRVaKReGlwhVlh5Gph",
"slug": "trailers-5Gph",
"author": {
"channelId": "UC8elThf5TGMpQfQc_VE917Q",
"channelTitle": "openZIM_testing",
"channelDescription": "",
"channelJoinedDate": "2024-06-04T13:30:16.232286Z",
"profilePath": "channels/UC8elThf5TGMpQfQc_VE917Q/profile.jpg",
"bannerPath": "channels/UC8elThf5TGMpQfQc_VE917Q/banner.jpg"
},
"title": "Trailers",
"description": "",
"publicationDate": "2024-06-04T15:15:48Z",
"thumbnailPath": "videos/TcMBFSGVi1c/video.webp",
"videos": [
{
"slug": "marvel_studios_avengers_endgame_official_trailer-TcMB",
"id": "TcMBFSGVi1c",
"title": "Marvel Studios' Avengers: Endgame - Official Trailer",
"thumbnailPath": "videos/TcMBFSGVi1c/video.webp",
"duration": "PT2M27S"
}
],
"videosCount": 1
},
{
"id": "PLMK6hZr9PcsjcF5mnaQk8Fi-xnb0AQgGI",
"slug": "timelapses-QgGI",
"author": {
"channelId": "UC8elThf5TGMpQfQc_VE917Q",
"channelTitle": "openZIM_testing",
"channelDescription": "",
"channelJoinedDate": "2024-06-04T13:30:16.232286Z",
"profilePath": "channels/UC8elThf5TGMpQfQc_VE917Q/profile.jpg",
"bannerPath": "channels/UC8elThf5TGMpQfQc_VE917Q/banner.jpg"
},
"title": "Timelapses",
"description": "",
"publicationDate": "2024-06-04T15:04:46Z",
"thumbnailPath": "videos/9TgosbGRsTk/video.webp",
"videos": [
{
"slug": "timelapse-9Tgo",
"id": "9TgosbGRsTk",
"title": "Timelapse",
"thumbnailPath": "videos/9TgosbGRsTk/video.webp",
"duration": "PT11S"
},
{
"slug": "cloudy_sky_time_lapse_4k_free_footage_video_gopro_11-k02q",
"id": "k02qXOcCrbo",
"title": "Cloudy Sky ☀️ Time Lapse 4K Free Footage Video | GoPro 11",
"thumbnailPath": "videos/k02qXOcCrbo/video.webp",
"duration": "PT36S"
}
],
"videosCount": 2
},
{
"id": "PLMK6hZr9PcsjTJ5u2z-khzvDNXlqdO2wS",
"slug": "coffee-O2wS",
"author": {
"channelId": "UC8elThf5TGMpQfQc_VE917Q",
"channelTitle": "openZIM_testing",
"channelDescription": "",
"channelJoinedDate": "2024-06-04T13:30:16.232286Z",
"profilePath": "channels/UC8elThf5TGMpQfQc_VE917Q/profile.jpg",
"bannerPath": "channels/UC8elThf5TGMpQfQc_VE917Q/banner.jpg"
},
"title": "Coffee",
"description": "",
"publicationDate": "2024-06-04T15:04:25Z",
"thumbnailPath": "videos/DYvYGQHYScc/video.webp",
"videos": [
{
"slug": "coffee_machine-DYvY",
"id": "DYvYGQHYScc",
"title": "Coffee Machine",
"thumbnailPath": "videos/DYvYGQHYScc/video.webp",
"duration": "PT9S"
}
],
"videosCount": 1
}
]
}
1 change: 1 addition & 0 deletions zimui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"vite-plugin-vuetify": "^2.0.4",
"vue": "^3.5.4",
"vue-router": "^4.4.4",
"vue3-carousel": "^0.3.4",
"vuetify": "^3.7.1",
"webp-hero": "^0.0.2"
},
Expand Down
32 changes: 32 additions & 0 deletions zimui/src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ html {
font-family: 'Roboto', sans-serif;
}

body {
background: rgba(var(--v-theme-background)) !important;
}

a {
text-decoration: none;
}
Expand Down Expand Up @@ -124,3 +128,31 @@ a {
.v-btn__content > .v-icon--start {
margin-right: 0.5rem;
}

.carousel__icon {
fill: black !important;
}

.carousel__next,
.carousel__prev {
background: rgba(var(--v-theme-background)) !important;
border-radius: 100% !important;
box-shadow:
0 4px 4px rgba(0, 0, 0, 0.3),
0 0 4px rgba(0, 0, 0, 0.2);
width: 40px !important;
height: 40px !important;
}

.carousel__track {
align-items: start !important;
}

.carousel__prev--disabled,
.carousel__next--disabled {
display: none !important;
}

.video-list .v-infinite-scroll__side {
padding: 0 !important;
}
59 changes: 59 additions & 0 deletions zimui/src/components/channel/tabs/VideosGridTab.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { useMainStore } from '@/stores/main'
import type { VideoPreview } from '@/types/Videos'
import VideoGrid from '@/components/video/VideoGrid.vue'
import TabInfo from '@/components/common/ViewInfo.vue'
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(
() => main.channel?.mainPlaylist,
() => {
fetchData()
}
)
// Fetch the videos for the main playlist
const fetchData = async function () {
if (main.channel?.mainPlaylist) {
try {
const resp = await main.fetchPlaylist(main.channel?.mainPlaylist)
if (resp) {
playlist.value = resp
videos.value = resp.videos
isLoading.value = false
}
} catch (error) {
main.setErrorMessage('An unexpected error occured when fetching videos.')
}
}
}
// Fetch the data on component mount
onMounted(() => {
fetchData()
})
</script>

<template>
<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>
Loading

0 comments on commit e1a0f2b

Please sign in to comment.