Skip to content

Commit

Permalink
Merge pull request #392 from dmlb/youtube-page-fixes
Browse files Browse the repository at this point in the history
Youtube page fixes
  • Loading branch information
VeckoTheGecko committed Aug 22, 2024
2 parents 1d1f63c + 12cfb6f commit 8eee42e
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 91 deletions.
10 changes: 10 additions & 0 deletions scripts/youtube.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class YoutubeChannel:
channelCustomName: str
channelName: str
channelPic: str
channelPicH: int
channelPicW: int
channelSubCount: int


Expand All @@ -43,6 +45,9 @@ class YoutubeVideo:
publishedAt: str
videoId: str
title: str
videoPic: str
videoPicH: int
videoPicW: int

@property
def published_dt(self):
Expand Down Expand Up @@ -81,6 +86,9 @@ async def get_videos_from_channels(channel_ids: List[str], youtube: build):
videoId=item["id"]["videoId"],
channelId=item["snippet"]["channelId"],
title=item["snippet"]["title"],
videoPic=item["snippet"]["thumbnails"]["medium"]["url"],
videoPicW=item["snippet"]["thumbnails"]["medium"]["width"],
videoPicH=item["snippet"]["thumbnails"]["medium"]["height"],
)
channel_videos.append(video)

Expand Down Expand Up @@ -135,6 +143,8 @@ def save_channel_data(channel_ids: List[str], youtube: build):
channelPic=item["snippet"]["thumbnails"]["default"]["url"].split("=")[
0
], # Can set ?s= on the end to get a custom resolution
channelPicH=item["snippet"]["thumbnails"]["default"]["height"],
channelPicW=item["snippet"]["thumbnails"]["default"]["width"],
channelSubCount=int(item["statistics"]["subscriberCount"]),
)
channels.append(youtube_channel)
Expand Down
23 changes: 13 additions & 10 deletions src/lib/components/FilterForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
$: ({ filterOptions } = filterData)
export let showFilterLogic: boolean = true
export let showReset: boolean = true
// Whether all the selected tags must match the resource (vs any of the selected tags)
let filterLogicAndCtrl: boolean = filterData?.filterLogicAnd ?? true
let filterLogic: FilterLogic
Expand Down Expand Up @@ -128,16 +129,18 @@
>
</label>
{/if}
<ButtonLinks
type="reset"
on:click={resetFilters}
disabled={!isFilterDirty}
version="filled"
color="green"
>
<Trash3 slot="icon" class="w-6 h-6 inline" />
<span slot="label">Clear All</span>
</ButtonLinks>
{#if showReset}
<ButtonLinks
type="reset"
on:click={resetFilters}
disabled={!isFilterDirty}
version="filled"
color="green"
>
<Trash3 slot="icon" class="w-6 h-6 inline" />
<span slot="label">Clear All</span>
</ButtonLinks>
{/if}

<ButtonLinks type="submit" version="filled" color="green">
<Funnel slot="icon" class="w-6 h-6 inline" />
Expand Down
5 changes: 5 additions & 0 deletions src/lib/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface YoutubeChannel {
channelCustomName: string // e.g. "@notjustbikes"
channelName: string // e.g. "Not Just Bikes"
channelPic: string // e.g. "https://yt3.ggpht.com/5DBP22k02WIMvHgeoUj_Tt14Kh8u-oaAhYHQu1gXCoHuisGXnavb5k-ivpyffqIARNDzgpBbUw"
channelPicH: number // e.g. 180 (for 180px)
channelPicW: number // e.g. 320 (for 320px)
channelSubCount: number
}

Expand All @@ -26,6 +28,9 @@ export interface YoutubeVideo {
publishedAt: string
videoId: string
title: string
videoPic: string
videoPicH: number // e.g. 180 (for 180px)
videoPicW: number // e.g. 320 (for 320px)
}

export interface FilterOption extends Tag {
Expand Down
18 changes: 18 additions & 0 deletions src/lib/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,35 @@ describe("YouTube Utilities", () => {
channelCustomName: "climate town",
channelName: "climate town",
channelPic: "",
channelPicH: 180,
channelPicW: 320,
channelSubCount: 2,
},
{
channelId: "UCuVLG9pThvG74cYCm7pkNkA",
channelCustomName: "FAKE1",
channelName: "FAKE1",
channelPic: "",
channelPicH: 180,
channelPicW: 320,
channelSubCount: 100,
},
{
channelId: "UCuVLJKpThvBABcYCm7pkNkA",
channelCustomName: "FAKE2",
channelName: "FAKE2",
channelPic: "",
channelPicH: 180,
channelPicW: 320,
channelSubCount: 270,
},
{
channelId: "UCuVLG9pThvG74cYCm7pLOkA",
channelCustomName: "FAKE3",
channelName: "FAKE3",
channelPic: "",
channelPicH: 180,
channelPicW: 320,
channelSubCount: 5,
},
]
Expand All @@ -85,13 +93,17 @@ describe("getChannelData", () => {
channelCustomName: "climate town",
channelName: "climate town",
channelPic: "",
channelPicH: 180,
channelPicW: 320,
channelSubCount: 2,
},
{
channelId: "UCuVLG9pThvG74cYCm7pkNkA",
channelCustomName: "FAKE1",
channelName: "FAKE1",
channelPic: "",
channelPicH: 180,
channelPicW: 320,
channelSubCount: 100,
},
]
Expand All @@ -101,6 +113,12 @@ describe("getChannelData", () => {

expect(res?.channelName).toBe("climate town")
})

it("should throw error for no channel found", () => {
expect(() => getChannelData(ytList, "fake")).toThrowError(
`Channel ID with name 'fake' could not be found`
)
})
})

describe("Emoji Utilities", () => {
Expand Down
9 changes: 7 additions & 2 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,13 @@ export const sortChannelBySubCount = (
export const getChannelData = (
channelData: YoutubeChannel[],
channelId: string
): YoutubeChannel | undefined => {
return channelData.find((channel) => channel.channelId === channelId)
): YoutubeChannel => {
const channel = channelData.find((channel) => channel.channelId === channelId)
if (!channel) {
throw new Error(`Channel ID with name '${channelId}' could not be found.`)
}

return channel
}

/**
Expand Down
51 changes: 35 additions & 16 deletions src/routes/youtube/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<script lang="ts">
import { onMount } from "svelte"
import { page } from "$app/stores"
import type { PageData } from "./$types"
import type { YoutubeVideo, FilterOption, FilterLogic } from "$lib/interfaces"
import { DEFAULT_DISPLAY_LIMIT } from "$lib/constants"
import {
sortChannelBySubCount,
semanticNumber,
getChannelData,
tagQParamSetActive,
activeTagsSet,
} from "$lib/utils"
import YoutubeThumbnail from "./YoutubeThumbnail.svelte"
import FilterForm from "$lib/components/FilterForm.svelte"
Expand All @@ -19,7 +23,7 @@
let displayedVideoLimit: number = DEFAULT_DISPLAY_LIMIT
$: displayedVideoLimit
let displayedVideos = videoData
let displayedVideos: YoutubeVideo[] = [...videoData]
let rerender: boolean = false
// Creating form filter options, default view
Expand All @@ -34,21 +38,26 @@
filterObject.push(channelOption)
}
function filterResources(
event: CustomEvent<{
filterOptions: FilterOption[]
filterLogic: FilterLogic
}>
) {
const { filterOptions } = event.detail
displayedVideos = []
const getChannelIdFromName = (channelName: string) => {
return channelData.find((channel) => channel.channelName === channelName)
}
const filterVideos = (
event: CustomEvent<{ filterTags: Set<string>; filterLogic: FilterLogic }>
) => {
const { filterTags } = event.detail
applyVideoFilter(filterTags)
}
const applyVideoFilter = (filterTags: Set<string>) => {
const selectedChannels = Array.from(filterTags).map(
(option) => getChannelIdFromName(option)?.channelId
)
const filteredActiveChannelIds: string[] = filterOptions
.filter((channel: FilterOption) => channel.active === true)
.map((channel: FilterOption) => (channel.id ? channel.id : channel.name))
displayedVideos = []
const filteredVideos: YoutubeVideo[] = videoData.filter((video) =>
filteredActiveChannelIds.includes(video.channelId)
selectedChannels.includes(video.channelId)
)
// Force svelte re-render
Expand All @@ -60,6 +69,15 @@
const { displayLimit } = event.detail
displayedVideoLimit = displayLimit
}
onMount(() => {
const params = Object.fromEntries($page.url.searchParams)
if (params.tags) {
filterObject = tagQParamSetActive(params.tags, filterObject)
}
applyVideoFilter(activeTagsSet(filterObject))
})
</script>

<h1>Climate YouTube</h1>
Expand All @@ -69,9 +87,10 @@
the latest long-form videos from each YouTuber.
</div>
<FilterForm
filterOptions={filterObject}
filterData={{ filterOptions: filterObject, filterLogicAnd: false }}
showFilterLogic={false}
on:filter={filterResources}
showReset={false}
on:filter={filterVideos}
/>

{#key rerender}
Expand All @@ -81,7 +100,7 @@
{#each displayedVideos.slice(0, displayedVideoLimit) as video (video)}
<li>
<YoutubeThumbnail
{...video}
{video}
channelInfo={getChannelData(channelData, video.channelId)}
/>
</li>
Expand Down
105 changes: 42 additions & 63 deletions src/routes/youtube/YoutubeThumbnail.svelte
Original file line number Diff line number Diff line change
@@ -1,70 +1,49 @@
<script lang="ts">
export let title: string
export let videoId: string
export let channelInfo: any // Contains channel data for the video uploader
// export let realeasedDatetime: string;
import type { YoutubeChannel, YoutubeVideo } from "$lib/interfaces"
const thumbnail = `https://i.ytimg.com/vi/${videoId}/default.jpg`
const { channelCustomName, channelName } = channelInfo
export let video: YoutubeVideo
export let channelInfo: YoutubeChannel // Contains channel data for the video uploader
function trimString(str: string, maxLength: number): string {
/**
* Trims a string to a max length, finding a space character in order to end the string.
*/
// Check if the length of the input string is already less than or equal to maxLength
if (str.length <= maxLength) {
return str
}
// Trim the string to maxLength characters
let trimmedString = str.substring(0, maxLength)
// Find the last occurrence of a space character in the trimmed string
let lastIndex = trimmedString.lastIndexOf(" ")
// If a space character is found, trim the string again to remove everything after the space character
if (lastIndex !== -1) {
trimmedString = trimmedString.substring(0, lastIndex)
}
// Add an ellipsis to indicate that the string has been trimmed
return trimmedString + "..."
}
const { title, videoId, videoPic, videoPicH, videoPicW } = video
const { channelCustomName, channelName, channelPic } = channelInfo
</script>

<a
href="https://youtu.be/{videoId}"
class="lg:hover:scale-105"
target="_blank"
rel="noreferrer"
<div
class="flex flex-col w-full flex-row gap-2 bg-white dark:bg-zinc-800 md:flex md:w-auto md:h-auto shadow-md dark:shadow-zinc-900 rounded-lg lg:hover:scale-105"
>
<div
class="flex w-full flex-row bg-white dark:bg-zinc-800 md:block md:w-auto md:h-auto shadow-md dark:shadow-zinc-900 rounded-lg"
>
<img
loading="lazy"
class="rounded-l-lg md:rounded-bl-none md:rounded-t-lg md:w-full"
height="90"
width="120"
src={thumbnail}
alt="{title} thumbnail"
/>
<div class="mx-2">
<!-- Don't trim title on mobile, trim on desktop (probably a better way to do this with media queries) -->
<div
class="md:block text-sm font-semibold text-zinc-900 dark:text-zinc-100"
>
{@html title}
</div>
<a
class="text-sm text-zinc-700 dark:text-zinc-400"
href="https://www.youtube.com/{channelCustomName}"
target="_blank"
rel="noreferrer"
>
{@html channelName}</a
>
<a href="https://youtu.be/{videoId}" target="_blank" rel="noreferrer">
{#if videoPic}
<img
loading="lazy"
class="rounded-l-lg md:rounded-bl-none md:rounded-t-lg md:w-full"
height={videoPicH}
width={videoPicW}
src={videoPic}
alt=""
/>
{/if}
<div
class="md:block text-sm font-semibold text-zinc-900 dark:text-zinc-100"
>
{@html title}
</div>
</div>
</a>
</a>
<a
class="text-sm text-zinc-700 dark:text-zinc-400 inline-flex gap-1 items-center"
href="https://www.youtube.com/{channelCustomName}"
target="_blank"
rel="noreferrer"
aria-label="{channelName} youtube channel (in a new tab)"
>
{#if channelPic}
<img
class="rounded-full"
src={channelPic}
height="18"
width="18"
alt=""
/>
{/if}
{channelName}
</a>
</div>

0 comments on commit 8eee42e

Please sign in to comment.