Skip to content

Commit

Permalink
implement UI
Browse files Browse the repository at this point in the history
  • Loading branch information
RyanL123 committed Sep 30, 2024
1 parent f537764 commit ac88ce9
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 16 deletions.
10 changes: 7 additions & 3 deletions functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ export const getVideosFromPlaylist = onCall(async (request) => {
});

export const getVideosFromChannel = onCall(async (request) => {
const channelId = request.data.id;
const channelHandle = request.data.id;
const channels = await youtubeClient.channels.list({
forHandle: channelHandle,
part: ["snippet"],
});
const channelVideos = await youtubeClient.search.list({
channelId: channelId,
part: ["contentDetails", "snippet"],
channelId: channels.data?.items?.[0]?.id ?? "",
part: ["snippet"],
maxResults: 50,
});

Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const theme = createTheme({
const root = createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<CssBaseline enableColorScheme />
<ThemeProvider theme={theme}>
<CssBaseline enableColorScheme />
<App />
</ThemeProvider>
</React.StrictMode>,
Expand Down
76 changes: 65 additions & 11 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { InfoOutlined, Search as SearchIcon } from "@mui/icons-material";
import { LoadingButton } from "@mui/lab";
import { Box, Button, MenuItem, TextField } from "@mui/material";
import {
Box,
Button,
FormControl,
FormControlLabel,
FormLabel,
MenuItem,
Radio,
RadioGroup,
TextField,
} from "@mui/material";
import { parse, toSeconds } from "iso8601-duration";
import React, { useState } from "react";
import ReactGA from "react-ga4";
import { Link } from "react-router-dom";
import Video from "../components/Video";
import { SortOptions, VideoMetadata } from "../types";
import { findPlaylistById } from "../util/playlistUtil";
import { IdType, SortOptions, VideoMetadata } from "../types";
import {
findVideosByChannelId,
findVideosByPlaylistId,
} from "../util/playlistUtil";

ReactGA.initialize("G-LRVNS567ZT");
ReactGA.send(window.location.pathname + window.location.search);
Expand Down Expand Up @@ -60,6 +73,11 @@ function sortPlaylist(videos: VideoMetadata[], order: SortOptions) {
return ret.sort(compFunction);
}

const placeholderTextByIdType = {
[IdType.PLAYLIST]: "Playlist ID or Link",
[IdType.CHANNEL]: "Channel Handle (e.g. @Apple)",
};

const SearchPanel = ({
setPlaylist,
playlist,
Expand All @@ -70,6 +88,7 @@ const SearchPanel = ({
const [playlistID, setPlaylistID] = useState("");
const [loading, setLoading] = useState(false);
const [order, setOrder] = useState(SortOptions.VIEWS_DESC);
const [idType, setIdType] = useState(IdType.PLAYLIST);

const updateSortOrder = (event) => {
const value = event.target.value;
Expand All @@ -96,14 +115,28 @@ const SearchPanel = ({

setLoading(true);

// extracts playlist ID from a complete link
// if ?list= doesn't exist, assume user pasted in only the ID
const sanitizedPlaylistID =
playlistID.indexOf("list=") === -1
? playlistID
: playlistID.slice(playlistID.indexOf("list=") + "list=".length);
let videoData;

switch (idType) {
case IdType.CHANNEL:
const sanitizedChannelName = playlistID.replace(
new RegExp("@", "g"), // remove all instances of "@"
"",
);
videoData = findVideosByChannelId(sanitizedChannelName);
break;
case IdType.PLAYLIST:
// extracts playlist ID from a complete link
// if ?list= doesn't exist, assume user pasted in only the ID
const sanitizedPlaylistID =
playlistID.indexOf("list=") === -1
? playlistID
: playlistID.slice(playlistID.indexOf("list=") + "list=".length);
videoData = findVideosByPlaylistId(sanitizedPlaylistID);
break;
}

findPlaylistById(sanitizedPlaylistID)
videoData
.then((data) => {
setPlaylist(sortPlaylist(data, order));
})
Expand All @@ -130,7 +163,7 @@ const SearchPanel = ({
<TextField
fullWidth
variant="outlined"
label="Playlist ID or Link"
label={placeholderTextByIdType[idType]}
name="playlistID"
value={playlistID}
onChange={(event) => setPlaylistID(event.target.value)}
Expand All @@ -150,6 +183,27 @@ const SearchPanel = ({
</MenuItem>
))}
</TextField>
<Box width="100%">
<FormControl>
<FormLabel>Type</FormLabel>
<RadioGroup
row
value={idType}
onChange={(e) => setIdType(e.target.value as IdType)}
>
<FormControlLabel
value={IdType.PLAYLIST}
control={<Radio />}
label={IdType.PLAYLIST.toString()}
/>
<FormControlLabel
value={IdType.CHANNEL}
control={<Radio />}
label={IdType.CHANNEL.toString()}
/>
</RadioGroup>
</FormControl>
</Box>
<Box
mt="20px"
display="flex"
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ export enum SortOptions {
LONGEST = "Longest",
SHORTEST = "Shortest",
}

export enum IdType {
PLAYLIST = "Playlist",
CHANNEL = "Channel",
}
12 changes: 11 additions & 1 deletion src/util/playlistUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ if (
connectFunctionsEmulator(functions, "localhost", 5001);
}

export async function findPlaylistById(
export async function findVideosByPlaylistId(
playlistId: string,
): Promise<VideoMetadata[]> {
const getVideosFromPlaylist = httpsCallable(
Expand All @@ -30,3 +30,13 @@ export async function findPlaylistById(
};
return data.videos;
}

export async function findVideosByChannelId(
channelId: string,
): Promise<VideoMetadata[]> {
const getVideosFromChannel = httpsCallable(functions, "getVideosFromChannel");
const data = (await getVideosFromChannel({ id: channelId })).data as {
videos: VideoMetadata[];
};
return data.videos;
}

0 comments on commit ac88ce9

Please sign in to comment.