diff --git a/components/PageHero.tsx b/components/PageHero.tsx new file mode 100644 index 0000000..ab0cb74 --- /dev/null +++ b/components/PageHero.tsx @@ -0,0 +1,154 @@ +import { withSubComponents } from "@components/helpers/withSubComponents.ts"; +import Image from "@components/Image.tsx"; +import { IconExternalLink } from "@components/icons.tsx"; +import { type ComponentChildren, createContext } from "preact"; +import { IconArrowNarrowLeft } from "@components/icons.tsx"; +import { IconEdit } from "@components/icons.tsx"; +import { useContext } from "preact/hooks"; +import { GenericResource } from "@lib/types.ts"; + +const HeroContext = createContext<{ image?: string; thumbnail?: string }>({ + image: undefined, + thumbnail: undefined, +}); + +function Wrapper( + { children, image, thumbnail }: { + children: ComponentChildren; + image?: string; + thumbnail?: string; + }, +) { + return ( +
+ + {image && + ( + Recipe Banner + )} + {children} + +
+ ); +} + +function Title( + { children, link }: { children: ComponentChildren; link?: string }, +) { + const ctx = useContext(HeroContext); + return ( +
+

+ {children} + {link && + ( + + + + )} +

+
+ ); +} + +function BackLink({ href }: { href: string }) { + return ( + + Back + + ); +} + +function EditLink({ href }: { href: string }) { + const ctx = useContext(HeroContext); + return ( + + + + ); +} + +function Header({ children }: { children: ComponentChildren }) { + return ( +
+ {children} +
+ ); +} + +function Subline( + { entries, children }: { + children?: ComponentChildren; + entries: (string | { href: string; title: string })[]; + }, +) { + const ctx = useContext(HeroContext); + return ( +
+ {children} + {entries.filter((s) => + s && (typeof s === "string" ? s?.length > 1 : true) + ).map((s) => { + if (typeof s === "string") { + return {s}; + } else { + return {s.title}; + } + })} +
+ ); +} + +function Footer({ children }: { children: ComponentChildren }) { + const ctx = useContext(HeroContext); + + return ( +
+ {children} +
+ ); +} + +export default withSubComponents(Wrapper, { + EditLink, + BackLink, + Footer, + Subline, + Header, + Title, +}); diff --git a/components/RecipeHero.tsx b/components/RecipeHero.tsx deleted file mode 100644 index a2a9a70..0000000 --- a/components/RecipeHero.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { Star } from "@components/Stars.tsx"; -import { - IconArrowNarrowLeft, - IconEdit, - IconExternalLink, -} from "@components/icons.tsx"; -import Image from "@components/Image.tsx"; -import { GenericResource } from "@lib/types.ts"; - -export function RecipeHero( - { data, subline, backlink, editLink }: { - backlink: string; - subline?: (string | { title: string; href: string })[]; - editLink?: string; - data: GenericResource; - }, -) { - const { meta: { image } = {} } = data; - - return ( -
- {image && - ( - Recipe Banner - )} - -
- - Back - - {editLink && - ( - - - - )} -
- -
-
-

- {data.name} - {data.meta?.link && - ( - - - - )} -

- - {data.meta?.rating && } -
- {subline?.length && - ( -
- {subline.filter((s) => - s && (typeof s === "string" ? s?.length > 1 : true) - ).map((s) => { - if (typeof s === "string") { - return {s}; - } else { - return {s.title}; - } - })} -
- )} -
-
- ); -} diff --git a/components/helpers/withSubComponents.ts b/components/helpers/withSubComponents.ts new file mode 100644 index 0000000..ea735e1 --- /dev/null +++ b/components/helpers/withSubComponents.ts @@ -0,0 +1,11 @@ +// deno-lint-ignore no-explicit-any +export function withSubComponents>( + component: A, + subcomponents: B, +): A & B { + Object.keys(subcomponents).forEach((key) => { + // deno-lint-ignore no-explicit-any + (component as any)[key] = (subcomponents as any)[key]; + }); + return component as A & B; +} diff --git a/components/icons.tsx b/components/icons.tsx index cd7b5de..abaa363 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -16,3 +16,4 @@ export { default as IconLogout } from "https://deno.land/x/tabler_icons_tsx@0.0. export { default as IconSearch } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/search.tsx"; export { default as IconGhost } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/ghost.tsx"; export { default as IconBrandYoutube } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/brand-youtube.tsx"; +export { default as IconWand } from "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/wand.tsx"; diff --git a/islands/Recommendations.tsx b/islands/Recommendations.tsx index e26dbd3..7cac972 100644 --- a/islands/Recommendations.tsx +++ b/islands/Recommendations.tsx @@ -1,9 +1,16 @@ import { useCallback, useState } from "preact/hooks"; +import { IconWand } from "@components/icons.tsx"; -export function Recommendations({ id, type }: { id: string; type: string }) { - const [state, setState] = useState<"disabled" | "loading">( - "disabled", - ); +type RecommendationState = "disabled" | "loading"; + +export function Recommendations( + { id, type, load = false }: { + id: string; + type: string; + load?: boolean; + }, +) { + const [state, setState] = useState("disabled"); const [results, setResults] = useState(); const startFetch = useCallback( @@ -17,27 +24,54 @@ export function Recommendations({ id, type }: { id: string; type: string }) { [id, type], ); - if (results) { - return ( - - ); + if (load) { + startFetch(); } - if (state === "loading") { - return

Loading...

; - } + return ( + + {results && ( +
+

+ Similar Movies +

+
    + {results.map((res) => { + return ( +
    + +

    {res.title}

    +
    + ); + })} +
+
+ )} - return ; + {state === "loading" && !results && ( +
+

Loading...

+
+ )} + + {!results && state === "disabled" && + ( + + )} +
+ ); } diff --git a/routes/_layout.tsx b/routes/_layout.tsx index 409d49a..89aa89c 100644 --- a/routes/_layout.tsx +++ b/routes/_layout.tsx @@ -4,7 +4,7 @@ import { CSS, KATEX_CSS } from "https://deno.land/x/gfm@0.2.5/mod.ts"; import { Head, Partial } from "$fresh/runtime.ts"; import { Emoji } from "@components/Emoji.tsx"; -export default function MyLayout({ Component, url }: LayoutProps) { +export default function MyLayout({ Component }: LayoutProps) { return (
{} {m.name} diff --git a/routes/articles/[name].tsx b/routes/articles/[name].tsx index 0fcf91a..63eb9a5 100644 --- a/routes/articles/[name].tsx +++ b/routes/articles/[name].tsx @@ -1,15 +1,16 @@ import { Handlers, PageProps } from "$fresh/server.ts"; import { MainLayout } from "@components/layouts/main.tsx"; import { Article, getArticle } from "@lib/resource/articles.ts"; -import { RecipeHero } from "@components/RecipeHero.tsx"; import { KMenu } from "@islands/KMenu.tsx"; import { YoutubePlayer } from "@components/Youtube.tsx"; import { HashTags } from "@components/HashTags.tsx"; import { isYoutubeLink } from "@lib/string.ts"; import { renderMarkdown } from "@lib/documents.ts"; import { RedirectSearchHandler } from "@islands/Search.tsx"; +import PageHero from "@components/PageHero.tsx"; +import { Star } from "@components/Stars.tsx"; -export const handler: Handlers
= { +export const handler: Handlers<{ article: Article; session: unknown }> = { async GET(_, ctx) { const article = await getArticle(ctx.params.name); return ctx.render({ article, session: ctx.state.session }); @@ -33,20 +34,33 @@ export default function Greet( > - + + + + + {session && ( + + )} + + + + {article.name} + + + {article.meta.rating && } + + + {article.tags.length > 0 && ( <>
diff --git a/routes/movies/[name].tsx b/routes/movies/[name].tsx index 7820637..aa1dc47 100644 --- a/routes/movies/[name].tsx +++ b/routes/movies/[name].tsx @@ -1,12 +1,13 @@ -import { Handlers, PageProps, RouteContext } from "$fresh/server.ts"; +import { PageProps, RouteContext } from "$fresh/server.ts"; import { MainLayout } from "@components/layouts/main.tsx"; import { getMovie, Movie } from "@lib/resource/movies.ts"; -import { RecipeHero } from "@components/RecipeHero.tsx"; import { HashTags } from "@components/HashTags.tsx"; import { renderMarkdown } from "@lib/documents.ts"; import { KMenu } from "@islands/KMenu.tsx"; import { RedirectSearchHandler } from "@islands/Search.tsx"; import { Recommendations } from "@islands/Recommendations.tsx"; +import PageHero from "@components/PageHero.tsx"; +import { Star } from "@components/Stars.tsx"; export default async function Greet( props: PageProps<{ movie: Movie; session: Record }>, @@ -23,19 +24,33 @@ export default async function Greet( ${movie.name}`} context={movie}> - + + + {session && ( + + )} + + + {movie.name} + + {movie.meta.rating && } + + + + {movie.tags.length > 0 && ( <> @@ -53,8 +68,6 @@ export default async function Greet( > {content} - -
); diff --git a/routes/recipes/[name].tsx b/routes/recipes/[name].tsx index eca5cf3..acd9668 100644 --- a/routes/recipes/[name].tsx +++ b/routes/recipes/[name].tsx @@ -1,14 +1,15 @@ import { Handlers, PageProps } from "$fresh/server.ts"; import { IngredientsList } from "@islands/IngredientsList.tsx"; -import { RecipeHero } from "@components/RecipeHero.tsx"; import { MainLayout } from "@components/layouts/main.tsx"; import Counter from "@islands/Counter.tsx"; import { useSignal } from "@preact/signals"; import { getRecipe, Recipe } from "@lib/resource/recipes.ts"; import { RedirectSearchHandler } from "@islands/Search.tsx"; import { KMenu } from "@islands/KMenu.tsx"; +import PageHero from "@components/PageHero.tsx"; +import { Star } from "@components/Stars.tsx"; -export const handler: Handlers = { +export const handler: Handlers<{ recipe: Recipe; session: unknown } | null> = { async GET(_, ctx) { const recipe = await getRecipe(ctx.params.name); return ctx.render({ recipe, session: ctx.state.session }); @@ -25,7 +26,7 @@ export default function Greet( const subline = [ recipe?.meta?.time && `Duration ${recipe.meta.time}`, - ].filter(Boolean); + ].filter(Boolean) as string[]; return ( - + + + + + {session && ( + + )} + + + {recipe.name} + + {recipe.meta?.rating && } + + +

Ingredients

diff --git a/routes/series/[name].tsx b/routes/series/[name].tsx index 79a7442..122494d 100644 --- a/routes/series/[name].tsx +++ b/routes/series/[name].tsx @@ -1,11 +1,12 @@ import { Handlers, PageProps } from "$fresh/server.ts"; import { MainLayout } from "@components/layouts/main.tsx"; -import { RecipeHero } from "@components/RecipeHero.tsx"; import { HashTags } from "@components/HashTags.tsx"; import { renderMarkdown } from "@lib/documents.ts"; import { getSeries, Series } from "@lib/resource/series.ts"; import { RedirectSearchHandler } from "@islands/Search.tsx"; import { KMenu } from "@islands/KMenu.tsx"; +import PageHero from "@components/PageHero.tsx"; +import { Star } from "@components/Stars.tsx"; export const handler: Handlers<{ serie: Series; session: unknown }> = { async GET(_, ctx) { @@ -27,20 +28,31 @@ export default function Greet( ${serie.name}`} context={serie}> - + + + + + {session && ( + + )} + + + {serie.name} + + {serie.meta.rating && } + + + {serie.tags.length > 0 && ( <>