diff --git a/apps/web/app/api/assets/[assetId]/route.ts b/apps/web/app/api/assets/[assetId]/route.ts index 3bff79ba..edc9a3e7 100644 --- a/apps/web/app/api/assets/[assetId]/route.ts +++ b/apps/web/app/api/assets/[assetId]/route.ts @@ -1,5 +1,6 @@ import { createContextFromRequest } from "@/server/api/client"; import { and, eq } from "drizzle-orm"; +import sharp from "sharp"; import { assets } from "@hoarder/db/schema"; import { readAsset } from "@hoarder/shared/assetdb"; @@ -45,12 +46,34 @@ export async function GET( }, }); } else { - return new Response(asset, { + let finalAsset = asset; + + const { searchParams } = new URL(request.url); + if (searchParams.has("preview")) { + finalAsset = await cropImage(asset); + } + + return new Response(finalAsset, { status: 200, headers: { - "Content-Length": asset.length.toString(), "Content-type": metadata.contentType, }, }); } } + +async function cropImage(buffer: Buffer): Promise { + try { + const metadata = await sharp(buffer).metadata(); + if (!metadata.height || metadata.height < 1000) { + return buffer; + } + return await sharp(buffer) + // cropping to 1000 pixels which is just a conservative guess and not something that is actually calculated + .extract({ left: 0, top: 0, width: metadata.width!, height: 1000 }) + .toBuffer(); + } catch (error) { + console.error("Error cropping image:", error); + throw error; + } +} diff --git a/apps/web/components/dashboard/bookmarks/LinkCard.tsx b/apps/web/components/dashboard/bookmarks/LinkCard.tsx index 86eed9e7..ffc9d771 100644 --- a/apps/web/components/dashboard/bookmarks/LinkCard.tsx +++ b/apps/web/components/dashboard/bookmarks/LinkCard.tsx @@ -37,12 +37,14 @@ function LinkImage({ unoptimized={unoptimized} className={className} alt="card banner" - fill={true} + fill + // below 640px only 1 bookmark, until 768px 1 bookmark + sidebar, until 1024px 2 bookmarks + sidebar, after that 3 bookmarks + sidebar + sizes="(max-width: 640px) 100vw, (max-width: 768px) 60vw, (max-width: 1024px) 40vw, 25vw" src={url} /> ); - const imageDetails = getBookmarkLinkImageUrl(link); + const imageDetails = getBookmarkLinkImageUrl(link, true); let img: React.ReactNode; if (isBookmarkStillCrawling(bookmark)) { diff --git a/packages/shared-react/utils/assetUtils.ts b/packages/shared-react/utils/assetUtils.ts index 119451d9..9b05d00d 100644 --- a/packages/shared-react/utils/assetUtils.ts +++ b/packages/shared-react/utils/assetUtils.ts @@ -1,3 +1,3 @@ -export function getAssetUrl(assetId: string) { - return `/api/assets/${assetId}`; +export function getAssetUrl(assetId: string, preview?: boolean) { + return `/api/assets/${assetId}${preview ? "?preview" : ""}`; } diff --git a/packages/shared-react/utils/bookmarkUtils.ts b/packages/shared-react/utils/bookmarkUtils.ts index 0a089f64..4a728afd 100644 --- a/packages/shared-react/utils/bookmarkUtils.ts +++ b/packages/shared-react/utils/bookmarkUtils.ts @@ -8,12 +8,21 @@ import { getAssetUrl } from "./assetUtils"; const MAX_LOADING_MSEC = 30 * 1000; -export function getBookmarkLinkImageUrl(bookmark: ZBookmarkedLink) { +export function getBookmarkLinkImageUrl( + bookmark: ZBookmarkedLink, + preview?: boolean, +) { if (bookmark.imageAssetId) { - return { url: getAssetUrl(bookmark.imageAssetId), localAsset: true }; + return { + url: getAssetUrl(bookmark.imageAssetId, preview), + localAsset: true, + }; } if (bookmark.screenshotAssetId) { - return { url: getAssetUrl(bookmark.screenshotAssetId), localAsset: true }; + return { + url: getAssetUrl(bookmark.screenshotAssetId, preview), + localAsset: true, + }; } return bookmark.imageUrl ? { url: bookmark.imageUrl, localAsset: false }