diff --git a/components/IngredientsList.tsx b/components/IngredientsList.tsx index c435b46..3d669a8 100644 --- a/components/IngredientsList.tsx +++ b/components/IngredientsList.tsx @@ -1,33 +1,71 @@ -import type { Ingredient, Ingredients } from "../lib/recipes.ts"; +import type { + Ingredient, + IngredientGroup, + Ingredients, +} from "../lib/recipes.ts"; +import { FunctionalComponent } from "preact"; type IngredientsProps = { ingredients: Ingredients; }; -function formatIngredient(ingredient: Ingredient) { - return `${ - ingredient.amount && ingredient.unit && - ` - ${ingredient.amount} ${ingredient.unit}` - } ${ingredient.type}`; -} - -export const IngredientsList = ({ ingredients }: IngredientsProps) => { +const IngredientList = ({ ingredients }: { ingredients: Ingredient[] }) => { return ( -
- {ingredients.map((item, index) => ( -
- {"type" in item && formatIngredient(item)} - {"ingredients" in item && Array.isArray(item.ingredients) && ( - - )} -
- ))} -
+ <> + {ingredients.map((item, index) => { + // Render Ingredient + const { type, amount, unit } = item as Ingredient; + return ( + + + {amount + (typeof unit !== "undefined" ? unit : "")} + + {type} + + ); + })} + ); }; + +const IngredientTable: FunctionalComponent<{ ingredients: Ingredients }> = ( + { ingredients }, +) => { + return ( + + + {ingredients.map((item, index) => { + if ("name" in item) { + // Render IngredientGroup + const { name, ingredients: groupIngredients } = + item as IngredientGroup; + + return ( + <> + + + + + + ); + } else { + // Render Ingredient + const { type, amount, unit } = item as Ingredient; + return ( + + + + + ); + } + })} + +
{name}
+ {(amount ? amount : "") + + (unit ? (" " + unit) : "")} + {type}
+ ); +}; +export const IngredientsList = ({ ingredients }: IngredientsProps) => { + return ; +}; diff --git a/components/RecipeCard.tsx b/components/RecipeCard.tsx index be056ad..a67349f 100644 --- a/components/RecipeCard.tsx +++ b/components/RecipeCard.tsx @@ -1,4 +1,3 @@ -import { Document } from "../lib/documents.ts"; import { Recipe } from "../lib/recipes.ts"; export function RecipeCard({ recipe }: { recipe: Recipe }) { @@ -6,20 +5,25 @@ export function RecipeCard({ recipe }: { recipe: Recipe }) { -
-
-
- {recipe.name} +
+
+ {/* Recipe Card content */} +
+
+ {recipe.name} +
+
); } diff --git a/components/RecipeHero.tsx b/components/RecipeHero.tsx index f7c36ed..244bca0 100644 --- a/components/RecipeHero.tsx +++ b/components/RecipeHero.tsx @@ -4,14 +4,14 @@ export function RecipeHero({ recipe }: { recipe: Recipe }) { return (
Recipe Banner -
+

{recipe.name} diff --git a/lib/recipes.ts b/lib/recipes.ts index ff4f45d..2fa7f6d 100644 --- a/lib/recipes.ts +++ b/lib/recipes.ts @@ -47,12 +47,12 @@ function parseIngredientItem(listItem: DocumentChild): Ingredient | undefined { tableSpoon: { short: "EL", plural: "Table Spoons", - alternates: ["el", "EL"], + alternates: ["el", "EL", "Tbsp", "tbsp"], }, teaSpoon: { short: "TL", plural: "Tea Spoon", - alternates: ["tl", "TL"], + alternates: ["tl", "TL", "Tsp", "tsp"], }, litre: { short: "L", diff --git a/routes/api/recipes/images/[image].ts b/routes/api/recipes/images/[image].ts index fff6fb2..ccfdb1f 100644 --- a/routes/api/recipes/images/[image].ts +++ b/routes/api/recipes/images/[image].ts @@ -1,26 +1,128 @@ import { HandlerContext } from "$fresh/server.ts"; +import { + ImageMagick, + initializeImageMagick, + MagickGeometry, +} from "https://deno.land/x/imagemagick_deno@0.0.14/mod.ts"; +import { parseMediaType } from "https://deno.land/std@0.175.0/media_types/parse_media_type.ts"; -function copyHeader(headerName: string, to: Headers, from: Headers) { - const hdrVal = from.get(headerName); - if (hdrVal) { - to.set(headerName, hdrVal); +await initializeImageMagick(); + +const cache = new Map>(); + +async function getRemoteImage(image: string) { + const sourceRes = await fetch(image); + if (!sourceRes.ok) { + return "Error retrieving image from URL."; } + const mediaType = parseMediaType(sourceRes.headers.get("Content-Type")!)[0]; + if (mediaType.split("/")[0] !== "image") { + return "URL is not image type."; + } + return { + buffer: new Uint8Array(await sourceRes.arrayBuffer()), + mediaType, + }; +} + +function getWidthHeight( + current: { width: number; height: number }, + final: { width: number; height: number }, +) { + const ratio = (current.width / final.width) > (current.height / final.height) + ? (current.height / final.height) + : (current.width / final.width); + + return new MagickGeometry( + current.width / ratio, + current.height / ratio, + ); +} + +function modifyImage( + imageBuffer: Uint8Array, + params: { width: number; height: number; mode: "resize" | "crop" }, +) { + return new Promise((resolve) => { + ImageMagick.read(imageBuffer, (image) => { + const sizingData = getWidthHeight(image, params); + if (params.mode === "resize") { + image.resize(sizingData); + } else { + image.crop(sizingData); + } + image.write((data) => resolve(data)); + }); + }); +} + +function parseParams(reqUrl: URL) { + const height = Number(reqUrl.searchParams.get("height")) || 0; + const width = Number(reqUrl.searchParams.get("width")) || 0; + if (height === 0 && width === 0) { + //return "Missing non-zero 'height' or 'width' query parameter."; + } + if (height < 0 || width < 0) { + return "Negative height or width is not supported."; + } + const maxDimension = 2048; + if (height > maxDimension || width > maxDimension) { + return `Width and height cannot exceed ${maxDimension}.`; + } + return { + height, + width, + }; +} + +async function getImageResponse( + imageUrl: string, + remoteImage: { buffer: Uint8Array; mediaType: string }, + params: { width: number; height: number }, +): Promise { + const modifiedImage = await modifyImage(remoteImage.buffer, { + ...params, + mode: "resize", + }); + + const response = new Response(modifiedImage, { + headers: { + "Content-Type": remoteImage.mediaType, + }, + }); + + return response; } export const handler = async ( _req: Request, _ctx: HandlerContext, ): Promise => { - const proxyRes = await fetch( - "http://192.168.178.56:3007/Recipes/images/" + _ctx.params.image, - ); - console.log({ params: _ctx.params }); - const headers = new Headers(); - copyHeader("content-length", headers, proxyRes.headers); - copyHeader("content-type", headers, proxyRes.headers); - copyHeader("content-disposition", headers, proxyRes.headers); - return new Response(proxyRes.body, { - status: proxyRes.status, - headers, - }); + const imageUrl = "http://192.168.178.56:3007/Recipes/images/" + + _ctx.params.image; + + const url = new URL(_req.url); + + const params = parseParams(url); + + if (typeof params === "string") { + return new Response(params, { status: 400 }); + } + + const remoteImage = await getRemoteImage(imageUrl); + if (typeof remoteImage === "string") { + return new Response(remoteImage, { status: 400 }); + } + + const imageId = `${imageUrl}.${params.width}.${params.height}`; + + if (cache.has(imageId)) { + return (await cache.get(imageId)!).clone(); + } + + const response = getImageResponse(imageUrl, remoteImage, params); + + cache.set(imageId, response); + + return response; }; diff --git a/routes/recipes/[name].tsx b/routes/recipes/[name].tsx index a903bce..1fd851c 100644 --- a/routes/recipes/[name].tsx +++ b/routes/recipes/[name].tsx @@ -16,10 +16,10 @@ export default function Greet(props: PageProps) { return ( -
-

Ingredients

+
+

Ingredients

-

Preperation

+

Preparation

);