Skip to content
Open
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
1 change: 1 addition & 0 deletions apps/jam-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"dependencies": {
"@jam/frames": "*",
"@jam/image-generator": "*",
"@mantine/core": "^7.10.1",
"@mantine/form": "^7.10.1",
"@mantine/hooks": "^7.10.1",
Expand Down
Binary file modified apps/jam-ui/src/app/favicon.ico
Binary file not shown.
61 changes: 61 additions & 0 deletions apps/jam-ui/src/app/imaginarium/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { createImageFromContent } from "@jam/image-generator";
import { Stack } from "@mantine/core";
import { Metadata } from "next";
import Image from "next/image";
import { downy, haiti } from "../../providers/colors";

export const metadata: Metadata = {
title: "Cometing - Imaginarium",
};

function addParagraphs(list: { text: string; address: string }[]) {
let text = "";
list.forEach((item) => {
text += `<p>${item.text}</p>`;
});
return text;
}

async function createImgFromTextOfComet(cometId: number) {
// dummy structure to resemble the comet-info returned.
const data = {
entries: [
{
text: "Hello world from genesis content!!!",
address: "0x000000000",
},
{ text: "Hello my second message here", address: "0x000000000" },
{ text: "Hello my third message here", address: "0x000000000" },
{ text: "Hello my fourth message here", address: "0x000000000" },
{ text: "Hello message here", address: "0x000000000" },
{ text: "Hello message here", address: "0x000000000" },
{ text: "Hello message here", address: "0x000000000" },
// add more text to check the truncation effect
],
};
const mappedContent = data.entries.map((value) => value.text);

return await createImageFromContent(mappedContent, {
backgroundColor: haiti[9] as `#${string}`,
textColor: downy[2] as `#${string}`,
height: "400px",
width: "400px",
});
}

export default async function JamsPage() {
// Used for example of backend generated base64 image.
// the whole page will be deleted before merging the branch.
const base64Img = await createImgFromTextOfComet(1);
return (
<Stack>
<h1>Generated images goes here!</h1>
<Image
src={`data:image/jpeg;base64,${base64Img}`}
alt="Some auto generated image in the backend"
width={400}
height={400}
/>
</Stack>
);
}
14 changes: 12 additions & 2 deletions apps/jam-ui/src/components/layout/shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useDisclosure } from "@mantine/hooks";
import Link from "next/link";
import { FC, ReactNode } from "react";
import { FaHome, FaPencilAlt, FaTags } from "react-icons/fa";
import { FaImages } from "react-icons/fa6";
import { TbMoonStars, TbSun } from "react-icons/tb";
import { ActionMenu } from "../ActionMenu";
import CometLogo from "../CometLogo";
Expand Down Expand Up @@ -118,7 +119,7 @@ const Shell: FC<{ children: ReactNode }> = ({ children }) => {
onClick={closeMobileMenu}
href="/jams"
leftSection={<FaPencilAlt />}
data-testid="jams-link"
data-testid="comets-link"
/>

<NavLink
Expand All @@ -127,7 +128,16 @@ const Shell: FC<{ children: ReactNode }> = ({ children }) => {
onClick={closeMobileMenu}
href="/collections"
leftSection={<FaTags />}
data-testid="jams-link"
data-testid="collections-link"
/>

<NavLink
component={Link}
label="Imaginarium"
onClick={closeMobileMenu}
href="/imaginarium"
leftSection={<FaImages />}
data-testid="imaginarium-link"
/>
</Stack>
</AppShell.Navbar>
Expand Down
34 changes: 34 additions & 0 deletions packages/imageGenerator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@jam/image-generator",
"version": "0.0.0",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
"files": [
"dist"
],
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"sideEffects": false,
"license": "Apache-2.0",
"scripts": {
"build": "tsup",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
"dev": "tsup --watch"
},
"devDependencies": {
"@jam/tsconfig": "*",
"@types/node": "^20.10.0",
"node-html-to-image": "^4.0.0",
"tsup": "^7",
"typescript": "^5"
},
"publishConfig": {
"access": "public"
}
}
96 changes: 96 additions & 0 deletions packages/imageGenerator/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import nodeHtmlToImage from "node-html-to-image";

type RGB = `rgb(${number}, ${number}, ${number})`;
type RGBA = `rgba(${number}, ${number}, ${number}, ${number})`;
type HEX = `#${string}`;

type Color = RGB | RGBA | HEX;

type LibOpts = Omit<Parameters<typeof nodeHtmlToImage>[0], "html">;

type Options = {
// image background color
backgroundColor: Color;
// image text color
textColor: Color;
width: string;
height: string;
// node-html-to-image options excluding the html content that is generated.
converterOptions?: LibOpts;
};

type ContentReturned = ReturnType<typeof nodeHtmlToImage>;

type CreateImageFromContent = (
contentList: string[],
opt: Options,
) => ContentReturned;

function addParagraphs(list: string[]) {
let paragraphs = "";
list.forEach((text) => {
paragraphs += `<p>${text}</p>`;
});
return paragraphs;
}

/**
* Return the list of text content passed as an PNG base64 encoded.
* Attention: If the height/width is not enough for the content it will
* be "truncated" like a overflow hidden equivalent effect.
*
* @param contentList
* @param options
* @returns
*/
export const createImageFromContent: CreateImageFromContent = async (
contentList,
options,
) => {
const height = options.height ?? "400px";
const width = options.width ?? "400px";
const bg = options.backgroundColor ?? "#352787";
const txtColor = options.textColor ?? "#d5f5f2";
const converterOptions = options.converterOptions ?? {};
const libOpts: LibOpts = {
encoding: "base64",
puppeteerArgs: {
headless: true,
args: [
"--no-sandbox",
"--remote-debugging-address=0.0.0.0",
"--remote-debugging-port=9222",
],
},
...converterOptions,
};

const content = `
<html>
<head>
<style>
html {
background-color: ${bg}
}
body {
width: ${width};
height: ${height};
padding-top: 8px;
}
p {
color: ${txtColor};
text-align:center;
}
</style>
</head>
<body>
${addParagraphs(contentList)}
</body>
</html>
`;

return await nodeHtmlToImage({
html: content,
...libOpts,
});
};
17 changes: 17 additions & 0 deletions packages/imageGenerator/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es2020",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"resolveJsonModule": true,
"isolatedModules": true
},
"include": ["**/*.ts"],
"exclude": ["node_modules"]
}
8 changes: 8 additions & 0 deletions packages/imageGenerator/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/index.ts"],
format: ["esm", "cjs"],
dts: true,
sourcemap: true,
});
8 changes: 8 additions & 0 deletions packages/imageGenerator/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": ["//"],
"pipeline": {
"build": {
"outputs": ["dist/**", "src/index.tsx"]
}
}
}
Loading