Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Youtube page fixes #392

Merged
merged 15 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)}
VeckoTheGecko marked this conversation as resolved.
Show resolved Hide resolved
/>
</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>
Loading