diff --git a/package.json b/package.json index 53a2e39af..b63397788 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "dev": "next dev", "build": "next build", "start": "next start --port ${PORT-3000}", - "clean": "rm -rf .next" + "clean": "rm -rf .next yarn-error.log" }, "dependencies": { "chrome-aws-lambda": "9.1.0", @@ -17,6 +17,7 @@ "react-dom": "^17.0.2", "react-feather": "^2.0.9", "styled-components": "^5.3.0", + "theme-custom-properties": "^1.0.0", "twemoji": "13.0.2", "use-local-storage-state": "^9.0.2", "zustand": "^3.5.1" diff --git a/src/colours.ts b/src/colours.ts new file mode 100644 index 000000000..1b3dfbfaf --- /dev/null +++ b/src/colours.ts @@ -0,0 +1,14 @@ +import { Colours, Theme } from "./types"; + +export const colourThemes: Record = { + light: { + fg: "black", + bg: "white", + gray: "dimgray", + }, + dark: { + fg: "white", + bg: "black", + gray: "dimgray", + }, +}; diff --git a/src/hooks/useConfig.ts b/src/hooks/useConfig.ts index b8df8b02a..1cae01f3f 100644 --- a/src/hooks/useConfig.ts +++ b/src/hooks/useConfig.ts @@ -2,8 +2,13 @@ import { createLocalStorageStateHook } from "use-local-storage-state"; import { simpleLayout } from "../layouts"; import { IConfig } from "../types"; -export const useConfig = createLocalStorageStateHook("config", { +export const defaultConfig: IConfig = { theme: "light", fileType: "png", layoutName: simpleLayout.name, -}); +}; + +export const useConfig = createLocalStorageStateHook( + "config", + defaultConfig, +); diff --git a/src/layouts.ts b/src/layouts.ts index 7f8850438..30d07c474 100644 --- a/src/layouts.ts +++ b/src/layouts.ts @@ -1,16 +1,23 @@ import { ILayout, ILayoutConfig } from "./types"; +import twemoji from "twemoji"; + +const twOptions = { folder: "svg", ext: ".svg" }; +const emojify = (text: string) => twemoji.parse(text, twOptions); + +const gString = (layoutConfig: ILayoutConfig, name: string): string => { + const value = layoutConfig[name]; + return Array.isArray(value) ? value.join(", ") : value; +}; export const simpleLayout: ILayout = { name: "Simple", - properties: [ - { name: "Test", type: "text" }, - { - name: "Boop", - description: "This is a test", - type: "select", - options: ["one", "two", "three"], - }, - ], + properties: [{ name: "Test", type: "text" }], + getCSS: () => ` + h1 { font-size: 100px; } + `, + getBody: c => ` +

${emojify("✨")} ${gString(c, "Test")} ${emojify("✨")}

+ `, }; export const starterLayout: ILayout = { @@ -44,3 +51,23 @@ export const getDefaultLayout = (layout: ILayout): ILayoutConfig => { return config; }; + +export const getLayoutConfigFromQuery = ( + layoutName: string, + query: Record, +): ILayoutConfig => { + const layout = layouts.find(l => l.name === layoutName); + + if (layout == null) { + return {}; + } + + const config: ILayoutConfig = getDefaultLayout(layout); + for (const p of layout.properties) { + if (query[p.name] != null) { + config[p.name] = query[p.name]; + } + } + + return config; +}; diff --git a/src/pages/api/[...og].ts b/src/pages/api/[...og].ts index 44518e839..b46e1acd7 100644 --- a/src/pages/api/[...og].ts +++ b/src/pages/api/[...og].ts @@ -3,19 +3,23 @@ import { getScreenshot } from "./_lib/chromium"; import { parseRequest } from "./_lib/parser"; import { getHtml } from "./_lib/template"; -const isDev = !process.env.AWS_REGION; -const isHtmlDebug = process.env.OG_HTML_DEBUG === "1"; +const isDev = !process.env.RAILWAY_STATIC_URL; +const isHtmlDebug = process.env.OG_HTML_DEBUG === "true"; const handler: NextApiHandler = async (req, res) => { try { - const parsedReq = parseRequest(req); - const html = getHtml(parsedReq); + const { config, layoutConfig } = parseRequest(req); + console.log("\n\n---"); + console.log("CONFIG", config); + console.log("LAYOUT CONFIG", layoutConfig); + + const html = getHtml(config, layoutConfig); if (isHtmlDebug) { res.setHeader("Content-Type", "text/html"); res.end(html); return; } - const { fileType } = parsedReq; + const { fileType } = config; const file = await getScreenshot(html, fileType, isDev); res.statusCode = 200; res.setHeader("Content-Type", `image/${fileType}`); diff --git a/src/pages/api/_lib/chromium.ts b/src/pages/api/_lib/chromium.ts index 847d2517b..a19cc9a82 100644 --- a/src/pages/api/_lib/chromium.ts +++ b/src/pages/api/_lib/chromium.ts @@ -1,6 +1,6 @@ import core from "puppeteer-core"; +import { FileType } from "../../../types"; import { getOptions } from "./options"; -import { FileType } from "./types"; let _page: core.Page | null; async function getPage(isDev: boolean) { diff --git a/src/pages/api/_lib/parser.ts b/src/pages/api/_lib/parser.ts index eca3a612c..5dbd77905 100644 --- a/src/pages/api/_lib/parser.ts +++ b/src/pages/api/_lib/parser.ts @@ -1,47 +1,59 @@ -import { IncomingMessage } from "http"; -import { parse } from "url"; -import { ParsedRequest, Theme } from "./types"; +import { NextApiRequest } from "next"; +import { defaultConfig } from "../../../hooks/useConfig"; +import { getLayoutConfigFromQuery } from "../../../layouts"; +import { Theme, IConfig, ILayoutConfig, FileType } from "../../../types"; -export function parseRequest(req: IncomingMessage) { - console.log("HTTP " + req.url); - const { pathname, query } = parse(req.url || "/", true); - const { fontSize, images, widths, heights, theme, md } = query || {}; +export const parseRequest = ( + req: NextApiRequest, +): { + config: IConfig; + layoutConfig: ILayoutConfig; +} => { + // const arr = (pathname || "/").slice(1).split("."); + // let extension = ""; + // let text = ""; + // if (arr.length === 0) { + // text = ""; + // } else if (arr.length === 1) { + // text = arr[0]; + // } else { + // extension = arr.pop() as string; + // text = arr.join("."); + // } - if (Array.isArray(fontSize)) { - throw new Error("Expected a single fontSize"); - } - if (Array.isArray(theme)) { + const config: IConfig = { + ...defaultConfig, + ...req.query, + // fileType: extension as FileType, + }; + + const layoutConfig = getLayoutConfigFromQuery(config.layoutName, req.query); + + if (Array.isArray(config.theme)) { throw new Error("Expected a single theme"); } - const arr = (pathname || "/").slice(1).split("."); - let extension = ""; - let text = ""; - if (arr.length === 0) { - text = ""; - } else if (arr.length === 1) { - text = arr[0]; - } else { - extension = arr.pop() as string; - text = arr.join("."); - } + // const parsedRequest: ParsedRequest = { + // fileType: extension === "jpeg" ? extension : "png", + // text: decodeURIComponent(text), + // theme: theme === "dark" ? "dark" : "light", + // md: md === "1" || md === "true", + // fontSize: fontSize || "96px", + // images: getArray(images), + // widths: getArray(widths), + // heights: getArray(heights), + // }; + // parsedRequest.images = getDefaultImages( + // parsedRequest.images, + // parsedRequest.theme, + // ); + // return parsedRequest; - const parsedRequest: ParsedRequest = { - fileType: extension === "jpeg" ? extension : "png", - text: decodeURIComponent(text), - theme: theme === "dark" ? "dark" : "light", - md: md === "1" || md === "true", - fontSize: fontSize || "96px", - images: getArray(images), - widths: getArray(widths), - heights: getArray(heights), + return { + config, + layoutConfig, }; - parsedRequest.images = getDefaultImages( - parsedRequest.images, - parsedRequest.theme, - ); - return parsedRequest; -} +}; function getArray(stringOrArray: string[] | string | undefined): string[] { if (typeof stringOrArray === "undefined") { diff --git a/src/pages/api/_lib/template.ts b/src/pages/api/_lib/template.ts index bf7e4a180..7ffabb236 100644 --- a/src/pages/api/_lib/template.ts +++ b/src/pages/api/_lib/template.ts @@ -1,32 +1,28 @@ import marked from "marked"; +import { colourThemes } from "../../../colours"; +import { layouts } from "../../../layouts"; +import { IConfig, ILayoutConfig, Theme } from "../../../types"; import { sanitizeHtml } from "./sanitizer"; -import { ParsedRequest } from "./types"; -const twemoji = require("twemoji"); -const twOptions = { folder: "svg", ext: ".svg" }; -const emojify = (text: string) => twemoji.parse(text, twOptions); - -function getCss(theme: string, fontSize: string) { - let background = "white"; - let foreground = "black"; - let radial = "lightgray"; - - if (theme === "dark") { - background = "black"; - foreground = "white"; - radial = "dimgray"; - } + +const getCommonCSS = (theme: Theme) => { + const colours = colourThemes[theme ?? "light"]; + + console.log(colours); + return ` @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap'); body { - background: ${background}; - background-image: radial-gradient(circle at 25px 25px, ${radial} 2%, transparent 0%), radial-gradient(circle at 75px 75px, ${radial} 2%, transparent 0%); + background: ${colours.bg}; + color: ${colours.fg}; background-size: 100px 100px; height: 100vh; display: flex; text-align: center; align-items: center; justify-content: center; + font-family: 'Inter', sans-serif; + font-weight: 400; } code { @@ -40,6 +36,10 @@ function getCss(theme: string, fontSize: string) { content: '\`'; } + h1 { + font-weight: 800; + } + .logo-wrapper { display: flex; align-items: center; @@ -71,15 +71,36 @@ function getCss(theme: string, fontSize: string) { .heading { font-family: 'Inter', sans-serif; - font-size: ${sanitizeHtml(fontSize)}; + font-size: 100px; font-style: normal; - color: ${foreground}; + color: ${colours.fg}; line-height: 1.8; }`; -} +}; + +export function getHtml(config: IConfig, layoutConfig: ILayoutConfig) { + // const { text, theme, md, fontSize, images, widths, heights } = parsedReq; + const layout = layouts.find(l => l.name === config.layoutName); + + return ` + + + Generated Image + + + + ${ + layout?.getBody != null + ? layout.getBody(layoutConfig) + : `

Layout body not implemented

` + } + +`; -export function getHtml(parsedReq: ParsedRequest) { - const { text, theme, md, fontSize, images, widths, heights } = parsedReq; + /* return ` @@ -107,6 +128,7 @@ export function getHtml(parsedReq: ParsedRequest) { `; +*/ } function getImage(src: string, width = "auto", height = "225") { diff --git a/src/pages/api/_lib/types.ts b/src/pages/api/_lib/types.ts deleted file mode 100644 index 1335b84fa..000000000 --- a/src/pages/api/_lib/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type FileType = "png" | "jpeg"; -export type Theme = "light" | "dark"; - -export interface ParsedRequest { - fileType: FileType; - text: string; - theme: Theme; - md: boolean; - fontSize: string; - images: string[]; - widths: string[]; - heights: string[]; -} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index da41b6455..eb29bdb1f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -6,6 +6,7 @@ import { Layout } from "../components/Layout"; import { Select } from "../components/Select"; import { useConfig } from "../hooks/useConfig"; import { useIsMounted } from "../hooks/useIsMounted"; +import { useLayoutConfig } from "../hooks/useLayoutConfig"; import { layouts } from "../layouts"; import { FileType, Theme } from "../types"; @@ -40,7 +41,7 @@ export const Config: React.FC = () => { ); return ( -
+