Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,12 @@ export default defineConfig({
}),
},
},

vite: {
plugins: [qrcode()],
optimizeDeps: {
exclude: ["@takumi-rs/image-response", "@takumi-rs/core"],
},
},

integrations: [
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@astrojs/sitemap": "3.7.1",
"@astrojs/tailwind": "6.0.2",
"@astrojs/vercel": "10.0.4",
"@bearstudio/astro-assets-generation": "0.1.0",
"@bearstudio/astro-typed-routes": "0.1.6",
"@bearstudio/lunalink": "0.3.1",
"@fontsource-variable/inter": "5.2.8",
Expand Down
1,661 changes: 503 additions & 1,158 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/@types/takumi.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import "react";

declare module "react" {
interface HTMLAttributes<T> {
tw?: string | undefined;
}
interface SVGAttributes<T> {
tw?: string | undefined;
}
}
72 changes: 0 additions & 72 deletions src/generated-assets/api.ts

This file was deleted.

17 changes: 2 additions & 15 deletions src/generated-assets/components/CfpCoverNoFlag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,12 @@ import { LogoIcon } from "@/components/LogoIcon";
import {
getAstroImageBase64,
type AssetImageConfig,
} from "@/generated-assets/image";
} from "@bearstudio/astro-assets-generation";
import { COLORS } from "@/generated-assets/theme";
import worldImage from "@/assets/images/world.png";
import { getImage } from "astro:assets";

export const CfpCoverNoFlag = async (props: { config: AssetImageConfig }) => {
const worldImageResult = await getImage({
format: worldImage.format,
src: worldImage,
});

const defaultBackgroundImage = {
src: worldImageResult.src,
width: worldImage.width,
height: worldImage.height,
format: worldImage.format,
};

const noFlagImage = await getAstroImageBase64(defaultBackgroundImage);
const noFlagImage = await getAstroImageBase64(worldImage);

return (
<div
Expand Down
26 changes: 16 additions & 10 deletions src/generated-assets/components/EventBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,24 @@ export const EventBanner = ({
left: notchLeft,
width: notchW,
height: NOTCH_HEIGHT,
paddingBottom: 16,
color: color,
fontFamily: "Tomorrow",
fontSize: FONT_SIZE,
fontWeight: 500,
textTransform: "uppercase",
lineHeight: 1,
letterSpacing: LETTER_SPACING,
whiteSpace: "nowrap",
paddingBottom: 12,
}}
>
{cityName.toUpperCase()}'{year}
<div
style={{
color: color,
fontFamily: "Tomorrow",
fontSize: FONT_SIZE,
fontWeight: 500,
textTransform: "uppercase",
lineHeight: 1,
letterSpacing: LETTER_SPACING,
whiteSpace: "nowrap",
textAlign: "center",
}}
>
{cityName.toUpperCase()}'{year}
</div>
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/generated-assets/components/LogoWithFriends.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const LogoWithFriends = (props: { logos?: string[] | undefined }) => {
style={{
width: 169 * 3,
height: 18 * 3,
color: "white",
}}
/>
)}
Expand Down
188 changes: 0 additions & 188 deletions src/generated-assets/image.ts
Original file line number Diff line number Diff line change
@@ -1,191 +1,3 @@
import fs from "fs/promises";
import satori from "satori";
import sharp from "sharp";
import path from "node:path";
import type { ImageMetadata } from "astro";
import { match } from "ts-pattern";
import { renderToStaticMarkup } from "react-dom/server";

import { COLORS, FONTS } from "./theme";

export type AssetImageConfig = {
width: number;
height: number;
debugScale?: number | undefined;
resizeConfig?: {
width: number;
height: number;
};
};

export async function SVG(
component: JSX.Element,
params: { width: number; height: number },
) {
const fonts = await Promise.all(
FONTS.map(async ({ url, ...font }) => ({
...font,
data: await match(import.meta.env.DEV)
.with(true, async () => await fs.readFile(`./public/${url}`))
.with(false, async () => {
const res = await fetch(new URL(url, import.meta.env.SITE));

if (!res.ok) {
throw new Error(`Failed to fetch font: ${url}`);
}
return Buffer.from(await res.arrayBuffer());
})
.run(),
})),
);

return await satori(component, {
width: params.width,
height: params.height,
fonts,
});
}

export async function JPG(component: JSX.Element, params: AssetImageConfig) {
const imgSharp = sharp(Buffer.from(await SVG(component, params)));
if (!params.resizeConfig) {
return await imgSharp.jpeg().toBuffer();
}
return await imgSharp
.resize(params.resizeConfig.width, params.resizeConfig.height)
.jpeg()
.toBuffer();
}

export async function DEBUG_HTML(
component: JSX.Element,
params: AssetImageConfig,
) {
const html = renderToStaticMarkup(component);
return `<!DOCTYPE html>
<html>
<head>
<title>Debug</title>
<style>
${FONTS.map(
(font) => `
@font-face {
font-family: ${font.name};
font-style: ${font.style};
font-weight: ${font.weight};
src: url("${font.url}") format("truetype");
}
`,
).join(" ")}
:root {
--width: ${params.width}px;
--height: ${params.height}px;
--scale: ${params.debugScale ?? 0.4};
}
body {
background: ${COLORS.background} url('/debug.png') repeat;
margin: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
min-width: calc(var(--width)*var(--scale));
min-height: calc(var(--height)*var(--scale));
}
#screen {
width: calc(var(--width)*var(--scale));
height: calc(var(--height)*var(--scale));
overflow: hidden;
}
#render {
width: var(--width);
height: var(--height);
flex: none;
transform: scale(var(--scale));
transform-origin: top left;
background: black;
}

</style>
</head>
<body>
<div id="screen">
<div id="render">
${html}
</div>
</div>
</body>
</html>`;
}

export async function generateImageResponseSVG(svg: string) {
return new Response(svg, {
headers: {
"Content-Type": "image/svg+xml",
},
});
}

export async function generateImageResponseJPG(jpg: Buffer) {
return new Response(new Uint8Array(jpg), {
headers: {
"Content-Type": "image/jpeg",
},
});
}

export async function generateImageResponseHTML(html: string) {
return new Response(html, {
headers: {
"Content-Type": "text/html; charset=utf-8",
},
});
}

function getAstroImagePath(image: ImageMetadata) {
return import.meta.env.DEV
? path.resolve(image.src.replace(/\?.*/, "").replace("/@fs", ""))
: image.src;
}

async function getAstroImageBuffer(image: ImageMetadata) {
const fileExtension = RegExp(/.(jpg|jpeg|png)$/)
.exec(image.src)?.[0]
.slice(1);
const fileToRead = getAstroImagePath(image);

return {
buffer: await match(import.meta.env.DEV || !import.meta.env.SSR)
.with(true, async () => await fs.readFile(fileToRead))
.with(false, async () => {
const res = await fetch(new URL(fileToRead, import.meta.env.SITE));

if (!res.ok) {
throw new Error(`Failed to fetch image: ${fileToRead}`);
}

return Buffer.from(await res.arrayBuffer());
})
.run(),
fileType: match(fileExtension)
.with("jpg", "jpeg", () => "jpeg")
.with("png", () => "png")
.otherwise(() => {
throw new Error(`Must be a jpg, jpeg or png`);
}),
};
}

export async function getAstroImageBase64(image: ImageMetadata) {
const { buffer, fileType } = await getAstroImageBuffer(image);
return imageBufferToBase64(buffer, fileType);
}

export function imageBufferToBase64(buffer: Buffer, fileType: string) {
return `data:image/${fileType};base64, ${buffer.toString("base64")}`;
}

export function getImageNameFromTsxPath(path: string) {
return path
.split("/")
Expand Down
28 changes: 28 additions & 0 deletions src/generated-assets/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { configure } from "@bearstudio/astro-assets-generation";

export const loadConfig = () =>
configure({
debugBackground: "#0a0a0a",
siteUrl: import.meta.env.SITE ?? "http://localhost:4321",
isDev: import.meta.env.DEV,
customFonts: [
{
name: "Tomorrow",
url: "/fonts/tomorrow/Tomorrow-Regular.ttf",
style: "normal",
weight: 400,
},
{
name: "Tomorrow",
url: "/fonts/tomorrow/Tomorrow-Medium.ttf",
style: "normal",
weight: 500,
},
{
name: "Tomorrow",
url: "/fonts/tomorrow/Tomorrow-Bold.ttf",
style: "normal",
weight: 700,
},
],
});
Loading