From ef60ac52055adda5a490c2ef1009bc43c35943bd Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Tue, 5 Jan 2021 00:18:23 +0100 Subject: [PATCH] Minor cleanup --- src/api/ApiHooks.ts | 98 ++++++++++++++++++++++++++++++++ src/api/hooks.ts | 30 ---------- src/api/queries.ts | 53 ----------------- src/routes/ContentRoute.tsx | 4 +- src/routes/main/MainPage.tsx | 18 +++--- src/routes/main/MediaEntry.tsx | 13 +++-- src/routes/main/MediaEpisode.tsx | 2 +- src/routes/player/Player.tsx | 4 +- src/util/fetchJson.ts | 18 ++++++ 9 files changed, 137 insertions(+), 103 deletions(-) create mode 100644 src/api/ApiHooks.ts delete mode 100644 src/api/hooks.ts delete mode 100644 src/api/queries.ts create mode 100644 src/util/fetchJson.ts diff --git a/src/api/ApiHooks.ts b/src/api/ApiHooks.ts new file mode 100644 index 0000000..b05dc36 --- /dev/null +++ b/src/api/ApiHooks.ts @@ -0,0 +1,98 @@ +import {Content} from "./models/Content"; +import {useQuery} from "react-query"; +import {fetchJson} from "../util/fetchJson"; +import {Instalment} from "./models/Instalment"; +import {useApiEndpoint} from "./ApiEndpointContext"; +import {ContentMeta} from "./models/dto/ContentMeta"; +import {Genre} from "./models/Genre"; + +export function useShowEpisodes( + item: Content | undefined +): [Instalment[], boolean, unknown] { + const apiEndpoint = useApiEndpoint(); + + const {data, isLoading, error} = useQuery( + ["episodes", item?.ids?.uuid], + () => item?.kind === "show" + ? fetchJson<Instalment[]>( + apiEndpoint, + `api/v1/content/${item.ids.uuid}/episodes`, + { + method: "GET" + } + ) + : [] + ) + + return [data || [], isLoading, error]; +} + +export function useSingleContent( + id: string +): [ContentMeta | null, boolean, unknown] { + const apiEndpoint = useApiEndpoint(); + + const {data, isLoading, error} = useQuery( + ["media", id], + () => fetchJson<ContentMeta>( + apiEndpoint, + `api/v1/content/${id}`, + { + method: "GET" + } + ) + ) + + return [data || null, isLoading, error]; +} + +export function useAllContent(): [Content[], boolean, unknown] { + const apiEndpoint = useApiEndpoint(); + + const {data, isLoading, error} = useQuery( + "media", + () => fetchJson<Content[]>( + apiEndpoint, + `api/v1/content`, + { + method: "GET" + } + ) + ); + + return [data || [], isLoading, error]; +} + +export function useGenres(): [Genre[], boolean, unknown] { + const apiEndpoint = useApiEndpoint(); + + const {data, isLoading, error} = useQuery( + "media", + () => fetchJson<Genre[]>( + apiEndpoint, + `api/v1/genres`, + { + method: "GET" + } + ) + ); + + return [data || [], isLoading, error]; +} + +export function useGenreContent(genreId: string): [Content[], boolean, unknown] { + const apiEndpoint = useApiEndpoint(); + + const {data, isLoading, error} = useQuery( + "media", + () => fetchJson<Content[]>( + apiEndpoint, + `api/v1/genres/${genreId}`, + { + method: "GET" + } + ) + ); + + return [data || [], isLoading, error]; +} diff --git a/src/api/hooks.ts b/src/api/hooks.ts deleted file mode 100644 index 074f081..0000000 --- a/src/api/hooks.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {Content} from "./models/Content"; -import {useQuery} from "react-query"; -import {getContent, listEpisodes} from "./queries"; -import {Instalment} from "./models/Instalment"; -import {useApiEndpoint} from "./ApiEndpointContext"; -import {ContentMeta} from "./models/dto/ContentMeta"; - -export function useEpisodes(item: Content | undefined): [Instalment[], boolean, unknown] { - const apiEndpoint = useApiEndpoint(); - - const {data, isLoading, error} = useQuery( - ["episodes", item?.ids?.uuid], - () => item?.kind === "show" - ? listEpisodes(apiEndpoint, item.ids.uuid) - : [] - ) - - return [data || [], isLoading, error]; -} - -export function useContent(id: string): [ContentMeta | null, boolean, unknown] { - const apiEndpoint = useApiEndpoint(); - - const {data, isLoading, error} = useQuery( - ["media", id], - () => getContent(apiEndpoint, id) - ) - - return [data || null, isLoading, error]; -} diff --git a/src/api/queries.ts b/src/api/queries.ts deleted file mode 100644 index d7ff6f0..0000000 --- a/src/api/queries.ts +++ /dev/null @@ -1,53 +0,0 @@ -import {Genre} from "./models/Genre"; -import {GenreWithContent} from "./models/dto/GenreWithContent"; -import {Content} from "./models/Content"; -import {ContentMeta} from "./models/dto/ContentMeta"; -import {Instalment} from "./models/Instalment"; -import {RequestErrorKind} from "../util/request/RequestErrorKind"; -import {RequestError} from "../util/request/RequestError"; - -async function request<T>( - baseUrl: URL, - path: string, - headers?: RequestInit -): Promise<T> { - const url = new URL(path, baseUrl).toString(); - const response = await fetch(url, headers); - if (response.ok) { - return await response.json(); - } else if (response.status in RequestErrorKind) { - throw new RequestError(response.statusText, response.status) - } else { - throw new RequestError(response.statusText, RequestErrorKind.UnknownError) - } -} - -export async function listGenres(baseUrl: URL): Promise<Genre[]> { - return await request(baseUrl, `api/v1/genres`, { - method: "GET" - }); -} - -export async function getGenre(baseUrl: URL, genreId: string): Promise<GenreWithContent> { - return await request(baseUrl, `api/v1/genres/${genreId}`, { - method: "GET" - }); -} - -export async function listContent(baseUrl: URL): Promise<Content[]> { - return await request(baseUrl, `api/v1/content`, { - method: "GET" - }); -} - -export async function getContent(baseUrl: URL, contentId: string): Promise<ContentMeta> { - return await request(baseUrl, `api/v1/content/${contentId}`, { - method: "GET" - }); -} - -export async function listEpisodes(baseUrl: URL, contentId: string): Promise<Instalment[]> { - return await request(baseUrl, `api/v1/content/${contentId}/episodes`, { - method: "GET" - }); -} diff --git a/src/routes/ContentRoute.tsx b/src/routes/ContentRoute.tsx index 5a9ec8b..23d9665 100644 --- a/src/routes/ContentRoute.tsx +++ b/src/routes/ContentRoute.tsx @@ -1,12 +1,12 @@ import {PropsWithChildren} from "react"; import {useParams} from "react-router"; import {CurrentContentProvider} from "../util/CurrentContentContext"; -import {useContent} from "../api/hooks"; +import {useSingleContent} from "../api/ApiHooks"; export function ContentRoute(props: PropsWithChildren<{}>) { const {children} = props; const {contentId} = useParams<{ contentId: string }>(); - const [meta/*, isLoading, error*/] = useContent(contentId); + const [meta/*, isLoading, error*/] = useSingleContent(contentId); return ( <CurrentContentProvider value={meta}> diff --git a/src/routes/main/MainPage.tsx b/src/routes/main/MainPage.tsx index 752224f..ef2804e 100644 --- a/src/routes/main/MainPage.tsx +++ b/src/routes/main/MainPage.tsx @@ -1,25 +1,23 @@ -import {useApiEndpoint} from "../../api/ApiEndpointContext"; import {Content, getLocalizedName} from "../../api/models/Content"; import {sortLexicallyAsc} from "../../util/sort/sortLexically"; -import {useQuery} from "react-query"; -import {listContent} from "../../api/queries"; import {MediaEntry} from "./MediaEntry"; import {useLocale} from "../../util/locale/LocalizedContext"; +import {useAllContent} from "../../api/ApiHooks"; export function MainPage() { - const apiEndpoint = useApiEndpoint(); const locale = useLocale(); - const {data/*, isLoading, isError*/} = useQuery( - "media", - () => listContent(apiEndpoint) - ); - const media = data || []; + + const [media/*, isLoading, error*/] = useAllContent(); return ( <div> {media .sort(sortLexicallyAsc(item => getLocalizedName(item, locale)?.name || "")) - .map((item: Content) => (<MediaEntry item={item}/>))} + .map((item: Content) => ( + <MediaEntry + key={item.ids.uuid} + item={item}/> + ))} </div> ); } diff --git a/src/routes/main/MediaEntry.tsx b/src/routes/main/MediaEntry.tsx index 042417c..125472b 100644 --- a/src/routes/main/MediaEntry.tsx +++ b/src/routes/main/MediaEntry.tsx @@ -3,7 +3,7 @@ import {Fragment} from "react"; import {Link} from "react-router-dom"; import {createUseStyles} from "react-jss"; import {useLocale} from "../../util/locale/LocalizedContext"; -import {useEpisodes} from "../../api/hooks"; +import {useShowEpisodes} from "../../api/ApiHooks"; import {MediaEpisode} from "./MediaEpisode"; export interface Props { @@ -16,7 +16,7 @@ export function MediaEntry( const locale = useLocale(); const classes = useStyles(); - const [episodes/*, episodesLoading*/, episodesError] = useEpisodes(item); + const [episodes/*, episodesLoading*/, episodesError] = useShowEpisodes(item); const title = getLocalizedName(item, locale); const description = getLocalizedDescription(item, locale); @@ -25,7 +25,7 @@ export function MediaEntry( const backdrop = item.images.find(it => it.kind === "backdrop"); return ( - <div key={item.ids.uuid} className={classes.movie}> + <div className={classes.movie}> <h1>{title?.name}</h1> <p><strong>{rating?.certification}</strong></p> <p><strong>{description?.tagline}</strong></p> @@ -38,11 +38,14 @@ export function MediaEntry( {item.kind === "show" && ( <Fragment> {episodesError ? ( - <p>{""+episodesError}</p> + <p>{"" + episodesError}</p> ) : ( <ul> {episodes.map(episode => - <MediaEpisode item={episode} />)} + <MediaEpisode + key={episode.content.ids.uuid} + item={episode} + />)} </ul> )} </Fragment> diff --git a/src/routes/main/MediaEpisode.tsx b/src/routes/main/MediaEpisode.tsx index 498b80a..ec815e1 100644 --- a/src/routes/main/MediaEpisode.tsx +++ b/src/routes/main/MediaEpisode.tsx @@ -14,7 +14,7 @@ export function MediaEpisode({item, disabled}: Props) { const episodeDescription = getLocalizedDescription(item.content, locale); return ( - <li key={item.content.ids.uuid}> + <li> <p> <strong>S{item.season}E{item.episode}</strong> – {item.airDate} </p> diff --git a/src/routes/player/Player.tsx b/src/routes/player/Player.tsx index b529f79..aac33f4 100644 --- a/src/routes/player/Player.tsx +++ b/src/routes/player/Player.tsx @@ -17,7 +17,7 @@ import {Subtitle} from "../../api/models/Subtitle"; import {MousePosition} from "../../util/mouse/MousePosition"; import {VideoProvider} from "./video/VideoContext"; import {SubtitleRenderer} from "./subtitles/SubtitleRenderer"; -import {useEpisodes} from "../../api/hooks"; +import {useShowEpisodes} from "../../api/ApiHooks"; import {MediaEpisode} from "../main/MediaEpisode"; import {usePaused} from "../../util/media/usePaused"; @@ -33,7 +33,7 @@ export function Player( const classes = useStyles(); const locale = useLocale(); - const [relatedEpisodes/*, relatedEpisodesLoading*/, relatedEpisodesError] = useEpisodes(instalment?.content); + const [relatedEpisodes/*, relatedEpisodesLoading*/, relatedEpisodesError] = useShowEpisodes(instalment?.content); const name = getLocalizedName(content, locale); const description = getLocalizedDescription(content, locale); diff --git a/src/util/fetchJson.ts b/src/util/fetchJson.ts new file mode 100644 index 0000000..f42e348 --- /dev/null +++ b/src/util/fetchJson.ts @@ -0,0 +1,18 @@ +import {RequestErrorKind} from "./request/RequestErrorKind"; +import {RequestError} from "./request/RequestError"; + +export async function fetchJson<T>( + baseUrl: URL, + path: string, + options?: RequestInit +): Promise<T> { + const url = new URL(path, baseUrl).toString(); + const response = await fetch(url, options); + if (response.ok) { + return await response.json(); + } else if (response.status in RequestErrorKind) { + throw new RequestError(response.statusText, response.status) + } else { + throw new RequestError(response.statusText, RequestErrorKind.UnknownError) + } +} -- GitLab