From 3556a1fc60cc92e49d7831dae269aba1aba4f6bd Mon Sep 17 00:00:00 2001 From: saml33 Date: Fri, 22 Oct 2021 12:49:46 +1100 Subject: [PATCH] test-template --- api/_lib/parser.ts | 133 ++++---- api/_lib/template.ts | 196 +++++++----- api/_lib/types.ts | 24 +- package.json | 2 +- web/index.ts | 711 +++++++++++++++++++++---------------------- 5 files changed, 566 insertions(+), 500 deletions(-) diff --git a/api/_lib/parser.ts b/api/_lib/parser.ts index 88ab0ff..78a73a4 100644 --- a/api/_lib/parser.ts +++ b/api/_lib/parser.ts @@ -1,65 +1,92 @@ -import { IncomingMessage } from 'http'; -import { parse } from 'url'; -import { ParsedRequest, Theme } from './types'; +import { IncomingMessage } from "http"; +import { parse } from "url"; +import { ParsedRequest, Theme } 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 || {}); + console.log("HTTP " + req.url); + const { pathname, query } = parse(req.url || "/", true); + const { + fontSize, + images, + market, + pnl, + avgEntry, + markPrice, + widths, + heights, + theme, + md, + } = query || {}; - if (Array.isArray(fontSize)) { - throw new Error('Expected a single fontSize'); - } - if (Array.isArray(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('.'); - } + if (Array.isArray(fontSize)) { + throw new Error("Expected a single fontSize"); + } + if (Array.isArray(theme)) { + throw new Error("Expected a single theme"); + } - 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 arr = (pathname || "/").slice(1).split("."); + let extension = ""; + let side = ""; + if (arr.length === 0) { + side = ""; + } else if (arr.length === 1) { + side = arr[0]; + } else { + extension = arr.pop() as string; + side = arr.join("."); + } + + const parsedMarket = Array.isArray(market) ? market[0] : market; + const parsedPnl = Array.isArray(pnl) ? pnl[0] : pnl; + const parsedAvgEntry = Array.isArray(avgEntry) ? avgEntry[0] : avgEntry; + const parsedMarkPrice = Array.isArray(markPrice) ? markPrice[0] : markPrice; + + const parsedRequest: ParsedRequest = { + fileType: extension === "jpeg" ? extension : "png", + market: decodeURIComponent(parsedMarket!), + pnl: decodeURIComponent(parsedPnl!), + avgEntry: decodeURIComponent(parsedAvgEntry!), + markPrice: decodeURIComponent(parsedMarkPrice!), + side: decodeURIComponent(side), + 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; } function getArray(stringOrArray: string[] | string | undefined): string[] { - if (typeof stringOrArray === 'undefined') { - return []; - } else if (Array.isArray(stringOrArray)) { - return stringOrArray; - } else { - return [stringOrArray]; - } + if (typeof stringOrArray === "undefined") { + return []; + } else if (Array.isArray(stringOrArray)) { + return stringOrArray; + } else { + return [stringOrArray]; + } } function getDefaultImages(images: string[], theme: Theme): string[] { - const defaultImage = theme === 'light' - ? 'https://assets.vercel.com/image/upload/front/assets/design/vercel-triangle-black.svg' - : 'https://assets.vercel.com/image/upload/front/assets/design/vercel-triangle-white.svg'; + const defaultImage = + theme === "light" + ? "https://trade.mango.markets/assets/icons/logo.svg" + : "https://trade.mango.markets/assets/icons/logo.svg"; - if (!images || !images[0]) { - return [defaultImage]; - } - if (!images[0].startsWith('https://assets.vercel.com/') && !images[0].startsWith('https://assets.zeit.co/')) { - images[0] = defaultImage; - } - return images; + if (!images || !images[0]) { + return [defaultImage]; + } + if ( + !images[0].startsWith("https://assets.vercel.com/") && + !images[0].startsWith("https://assets.zeit.co/") + ) { + images[0] = defaultImage; + } + return images; } diff --git a/api/_lib/template.ts b/api/_lib/template.ts index 93d3264..d72bcd3 100644 --- a/api/_lib/template.ts +++ b/api/_lib/template.ts @@ -1,62 +1,22 @@ +import marked from "marked"; +import { sanitizeHtml } from "./sanitizer"; +import { ParsedRequest } from "./types"; -import { readFileSync } from 'fs'; -import marked from 'marked'; -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); - -const rglr = readFileSync(`${__dirname}/../_fonts/Inter-Regular.woff2`).toString('base64'); -const bold = readFileSync(`${__dirname}/../_fonts/Inter-Bold.woff2`).toString('base64'); -const mono = readFileSync(`${__dirname}/../_fonts/Vera-Mono.woff2`).toString('base64'); - -function getCss(theme: string, fontSize: string) { - let background = 'white'; - let foreground = 'black'; - let radial = 'lightgray'; - - if (theme === 'dark') { - background = 'black'; - foreground = 'white'; - radial = 'dimgray'; - } - return ` - @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: normal; - src: url(data:font/woff2;charset=utf-8;base64,${rglr}) format('woff2'); - } - - @font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: bold; - src: url(data:font/woff2;charset=utf-8;base64,${bold}) format('woff2'); - } - - @font-face { - font-family: 'Vera'; - font-style: normal; - font-weight: normal; - src: url(data:font/woff2;charset=utf-8;base64,${mono}) format("woff2"); - } - +function getCss(side: string, pnl: string) { + const pnlColor = parseFloat(pnl) >= 0 ? "#AFD803" : "#E54033"; + const sideColor = side.toLowerCase() === "long" ? "#AFD803" : "#E54033"; + return ` 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: #141026; background-size: 100px 100px; + color: white; + font-family: 'Lato', sans-serif; height: 100vh; - display: flex; - text-align: center; - align-items: center; - justify-content: center; + padding: 120px 256px; } code { color: #D400FF; - font-family: 'Vera'; white-space: pre-wrap; letter-spacing: -5px; } @@ -87,60 +47,142 @@ function getCss(theme: string, fontSize: string) { margin: 150px; } - .emoji { - height: 1em; - width: 1em; - margin: 0 .05em 0 .1em; - vertical-align: -0.1em; + .side-market { + display: flex; + font-size: 40px; + align-items: center; + justify-content: center; + padding: 40px 40px 24px; + } + + .side { + color: ${sideColor}; + padding: 0px 24px; + } + + .divider { + color: rgba(255,255,255,0.5); + margin-bottom: 8px; + } + + .market { + padding: 0px 24px; + } + + .pnl { + border: 4px ${pnlColor} solid; + border-radius: 24px; + color: ${pnlColor}; + display: flex; + font-size: 140px; + font-weight: bold; + height: 240px; + align-items: center; + justify-content: center; } - .heading { - font-family: 'Inter', sans-serif; - font-size: ${sanitizeHtml(fontSize)}; - font-style: normal; - color: ${foreground}; - line-height: 1.8; + .trade-details-wrapper { + display: flex; + justify-content: space-between; + padding: 0px 200px; + } + + .trade-details { + font-size: 56px; + font-weight: bold; + line-height: 0.5; + padding: 100px; + } + + .label { + color: rgba(255,255,255,0.5); + font-size: 40px; + font-weight: normal; }`; } export function getHtml(parsedReq: ParsedRequest) { - const { text, theme, md, fontSize, images, widths, heights } = parsedReq; - return ` + const { + market, + side, + pnl, + avgEntry, + markPrice, + md, + images, + widths, + heights, + } = parsedReq; + return ` Generated Image + + +
-
- ${images.map((img, i) => - getPlusSign(i) + getImage(img, widths[i], heights[i]) - ).join('')} + ${images + .map( + (img, i) => + getPlusSign(i) + getImage(img, widths[i], heights[i]) + ) + .join("")}
-
-
${emojify( - md ? marked(text) : sanitizeHtml(text) - )} +
${ + md ? marked(side.toUpperCase()) : sanitizeHtml(side.toUpperCase()) + } + | + ${ + md ? marked(market) : sanitizeHtml(market) + } +
+
${md ? marked(`${pnl}%`) : sanitizeHtml(`${pnl}%`)} +
+
+
+
Avg Entry Price
+
${ + md + ? marked(`$${parseFloat(avgEntry).toLocaleString()}`) + : sanitizeHtml( + `$${parseFloat(avgEntry).toLocaleString()}` + ) + } +
+
+
+
Mark Price
+
${ + md + ? marked(`$${parseFloat(markPrice).toLocaleString()}`) + : sanitizeHtml( + `$${parseFloat(markPrice).toLocaleString()}` + ) + } +
+
`; } -function getImage(src: string, width ='auto', height = '225') { - return `` + />`; } function getPlusSign(i: number) { - return i === 0 ? '' : '
+
'; + return i === 0 ? "" : '
+
'; } diff --git a/api/_lib/types.ts b/api/_lib/types.ts index e4aafde..c639187 100644 --- a/api/_lib/types.ts +++ b/api/_lib/types.ts @@ -1,13 +1,17 @@ -export type FileType = 'png' | 'jpeg'; -export type Theme = 'light' | 'dark'; +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[]; + fileType: FileType; + side: string; + market: string; + pnl: string; + avgEntry: string; + markPrice: string; + theme: Theme; + md: boolean; + fontSize: string; + images: string[]; + widths: string[]; + heights: string[]; } diff --git a/package.json b/package.json index 483c2ae..294c846 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "engines": { - "node": "14.x" + "node": "16.x" }, "scripts": { "build": "tsc -p api/tsconfig.json && tsc -p web/tsconfig.json" diff --git a/web/index.ts b/web/index.ts index 180471c..c2a7d91 100644 --- a/web/index.ts +++ b/web/index.ts @@ -1,419 +1,412 @@ -import { ParsedRequest, Theme, FileType } from '../api/_lib/types'; -const { H, R, copee } = (window as any); +import { ParsedRequest, FileType } from "../api/_lib/types"; +const { H, R, copee } = window as any; let timeout = -1; interface ImagePreviewProps { - src: string; - onclick: () => void; - onload: () => void; - onerror: () => void; - loading: boolean; + src: string; + onclick: () => void; + onload: () => void; + onerror: () => void; + loading: boolean; } -const ImagePreview = ({ src, onclick, onload, onerror, loading }: ImagePreviewProps) => { - const style = { - filter: loading ? 'blur(5px)' : '', - opacity: loading ? 0.1 : 1, - }; - const title = 'Click to copy image URL to clipboard'; - return H('a', - { className: 'image-wrapper', href: src, onclick }, - H('img', - { src, onload, onerror, style, title } - ) - ); -} +const ImagePreview = ({ + src, + onclick, + onload, + onerror, + loading, +}: ImagePreviewProps) => { + const style = { + filter: loading ? "blur(5px)" : "", + opacity: loading ? 0.1 : 1, + }; + const title = "Click to copy image URL to clipboard"; + return H( + "a", + { className: "image-wrapper", href: src, onclick }, + H("img", { src, onload, onerror, style, title }) + ); +}; interface DropdownOption { - text: string; - value: string; + text: string; + value: string; } interface DropdownProps { - options: DropdownOption[]; - value: string; - onchange: (val: string) => void; - small: boolean; + options: DropdownOption[]; + value: string; + onchange: (val: string) => void; + small: boolean; } const Dropdown = ({ options, value, onchange, small }: DropdownProps) => { - const wrapper = small ? 'select-wrapper small' : 'select-wrapper'; - const arrow = small ? 'select-arrow small' : 'select-arrow'; - return H('div', - { className: wrapper }, - H('select', - { onchange: (e: any) => onchange(e.target.value) }, - options.map(o => - H('option', - { value: o.value, selected: value === o.value }, - o.text - ) - ) - ), - H('div', - { className: arrow }, - '▼' - ) - ); -} + const wrapper = small ? "select-wrapper small" : "select-wrapper"; + const arrow = small ? "select-arrow small" : "select-arrow"; + return H( + "div", + { className: wrapper }, + H( + "select", + { onchange: (e: any) => onchange(e.target.value) }, + options.map((o) => + H("option", { value: o.value, selected: value === o.value }, o.text) + ) + ), + H("div", { className: arrow }, "▼") + ); +}; interface TextInputProps { - value: string; - oninput: (val: string) => void; + value: string; + oninput: (val: string) => void; } const TextInput = ({ value, oninput }: TextInputProps) => { - return H('div', - { className: 'input-outer-wrapper' }, - H('div', - { className: 'input-inner-wrapper' }, - H('input', - { type: 'text', value, oninput: (e: any) => oninput(e.target.value) } - ) - ) - ); -} - -interface ButtonProps { - label: string; - onclick: () => void; -} - -const Button = ({ label, onclick }: ButtonProps) => { - return H('button', { onclick }, label); -} + return H( + "div", + { className: "input-outer-wrapper" }, + H( + "div", + { className: "input-inner-wrapper" }, + H("input", { + type: "text", + value, + oninput: (e: any) => oninput(e.target.value), + }) + ) + ); +}; interface FieldProps { - label: string; - input: any; + label: string; + input: any; } const Field = ({ label, input }: FieldProps) => { - return H('div', - { className: 'field' }, - H('label', - H('div', {className: 'field-label'}, label), - H('div', { className: 'field-value' }, input), - ), - ); -} + return H( + "div", + { className: "field" }, + H( + "label", + H("div", { className: "field-label" }, label), + H("div", { className: "field-value" }, input) + ) + ); +}; interface ToastProps { - show: boolean; - message: string; + show: boolean; + message: string; } const Toast = ({ show, message }: ToastProps) => { - const style = { transform: show ? 'translate3d(0,-0px,-0px) scale(1)' : '' }; - return H('div', - { className: 'toast-area' }, - H('div', - { className: 'toast-outer', style }, - H('div', - { className: 'toast-inner' }, - H('div', - { className: 'toast-message'}, - message - ) - ) - ), - ); -} - -const themeOptions: DropdownOption[] = [ - { text: 'Light', value: 'light' }, - { text: 'Dark', value: 'dark' }, -]; + const style = { transform: show ? "translate3d(0,-0px,-0px) scale(1)" : "" }; + return H( + "div", + { className: "toast-area" }, + H( + "div", + { className: "toast-outer", style }, + H( + "div", + { className: "toast-inner" }, + H("div", { className: "toast-message" }, message) + ) + ) + ); +}; const fileTypeOptions: DropdownOption[] = [ - { text: 'PNG', value: 'png' }, - { text: 'JPEG', value: 'jpeg' }, + { text: "PNG", value: "png" }, + { text: "JPEG", value: "jpeg" }, ]; -const fontSizeOptions: DropdownOption[] = Array - .from({ length: 10 }) - .map((_, i) => i * 25) - .filter(n => n > 0) - .map(n => ({ text: n + 'px', value: n + 'px' })); - const markdownOptions: DropdownOption[] = [ - { text: 'Plain Text', value: '0' }, - { text: 'Markdown', value: '1' }, + { text: "Plain Text", value: "0" }, + { text: "Markdown", value: "1" }, ]; const imageLightOptions: DropdownOption[] = [ - { text: 'Vercel', value: 'https://assets.vercel.com/image/upload/front/assets/design/vercel-triangle-black.svg' }, - { text: 'Next.js', value: 'https://assets.vercel.com/image/upload/front/assets/design/nextjs-black-logo.svg' }, - { text: 'Hyper', value: 'https://assets.vercel.com/image/upload/front/assets/design/hyper-color-logo.svg' }, + { + text: "Mango", + value: "https://trade.mango.markets/assets/icons/logo.svg", + }, + { + text: "Next.js", + value: + "https://assets.vercel.com/image/upload/front/assets/design/nextjs-black-logo.svg", + }, + { + text: "Hyper", + value: + "https://assets.vercel.com/image/upload/front/assets/design/hyper-color-logo.svg", + }, ]; const imageDarkOptions: DropdownOption[] = [ - - { text: 'Vercel', value: 'https://assets.vercel.com/image/upload/front/assets/design/vercel-triangle-white.svg' }, - { text: 'Next.js', value: 'https://assets.vercel.com/image/upload/front/assets/design/nextjs-white-logo.svg' }, - { text: 'Hyper', value: 'https://assets.vercel.com/image/upload/front/assets/design/hyper-bw-logo.svg' }, + { + text: "Mango", + value: "https://trade.mango.markets/assets/icons/logo.svg", + }, + { + text: "Next.js", + value: + "https://assets.vercel.com/image/upload/front/assets/design/nextjs-white-logo.svg", + }, + { + text: "Hyper", + value: + "https://assets.vercel.com/image/upload/front/assets/design/hyper-bw-logo.svg", + }, ]; const widthOptions = [ - { text: 'width', value: 'auto' }, - { text: '50', value: '50' }, - { text: '100', value: '100' }, - { text: '150', value: '150' }, - { text: '200', value: '200' }, - { text: '250', value: '250' }, - { text: '300', value: '300' }, - { text: '350', value: '350' }, + { text: "width", value: "auto" }, + { text: "50", value: "50" }, + { text: "100", value: "100" }, + { text: "150", value: "150" }, + { text: "200", value: "200" }, + { text: "250", value: "250" }, + { text: "300", value: "300" }, + { text: "350", value: "350" }, ]; const heightOptions = [ - { text: 'height', value: 'auto' }, - { text: '50', value: '50' }, - { text: '100', value: '100' }, - { text: '150', value: '150' }, - { text: '200', value: '200' }, - { text: '250', value: '250' }, - { text: '300', value: '300' }, - { text: '350', value: '350' }, + { text: "height", value: "auto" }, + { text: "50", value: "50" }, + { text: "100", value: "100" }, + { text: "150", value: "150" }, + { text: "200", value: "200" }, + { text: "250", value: "250" }, + { text: "300", value: "300" }, + { text: "350", value: "350" }, ]; interface AppState extends ParsedRequest { - loading: boolean; - showToast: boolean; - messageToast: string; - selectedImageIndex: number; - widths: string[]; - heights: string[]; - overrideUrl: URL | null; + loading: boolean; + showToast: boolean; + messageToast: string; + selectedImageIndex: number; + widths: string[]; + heights: string[]; + overrideUrl: URL | null; + side: string; + market: string; + pnl: string; + avgEntry: string; + markPrice: string; } type SetState = (state: Partial) => void; const App = (_: any, state: AppState, setState: SetState) => { - const setLoadingState = (newState: Partial) => { - window.clearTimeout(timeout); - if (state.overrideUrl && state.overrideUrl !== newState.overrideUrl) { - newState.overrideUrl = state.overrideUrl; - } - if (newState.overrideUrl) { - timeout = window.setTimeout(() => setState({ overrideUrl: null }), 200); - } - - setState({ ...newState, loading: true }); - }; - const { - fileType = 'png', - fontSize = '100px', - theme = 'light', - md = true, - text = '**Hello** World', - images=[imageLightOptions[0].value], - widths=[], - heights=[], - showToast = false, - messageToast = '', - loading = true, - selectedImageIndex = 0, - overrideUrl = null, - } = state; - const mdValue = md ? '1' : '0'; - const imageOptions = theme === 'light' ? imageLightOptions : imageDarkOptions; - const url = new URL(window.location.origin); - url.pathname = `${encodeURIComponent(text)}.${fileType}`; - url.searchParams.append('theme', theme); - url.searchParams.append('md', mdValue); - url.searchParams.append('fontSize', fontSize); - for (let image of images) { - url.searchParams.append('images', image); + const setLoadingState = (newState: Partial) => { + window.clearTimeout(timeout); + if (state.overrideUrl && state.overrideUrl !== newState.overrideUrl) { + newState.overrideUrl = state.overrideUrl; } - for (let width of widths) { - url.searchParams.append('widths', width); - } - for (let height of heights) { - url.searchParams.append('heights', height); + if (newState.overrideUrl) { + timeout = window.setTimeout(() => setState({ overrideUrl: null }), 200); } - return H('div', - { className: 'split' }, - H('div', - { className: 'pull-left' }, - H('div', - H(Field, { - label: 'Theme', - input: H(Dropdown, { - options: themeOptions, - value: theme, - onchange: (val: Theme) => { - const options = val === 'light' ? imageLightOptions : imageDarkOptions - let clone = [...images]; - clone[0] = options[selectedImageIndex].value; - setLoadingState({ theme: val, images: clone }); - } - }) - }), - H(Field, { - label: 'File Type', - input: H(Dropdown, { - options: fileTypeOptions, - value: fileType, - onchange: (val: FileType) => setLoadingState({ fileType: val }) - }) - }), - H(Field, { - label: 'Font Size', - input: H(Dropdown, { - options: fontSizeOptions, - value: fontSize, - onchange: (val: string) => setLoadingState({ fontSize: val }) - }) - }), - H(Field, { - label: 'Text Type', - input: H(Dropdown, { - options: markdownOptions, - value: mdValue, - onchange: (val: string) => setLoadingState({ md: val === '1' }) - }) - }), - H(Field, { - label: 'Text Input', - input: H(TextInput, { - value: text, - oninput: (val: string) => { - console.log('oninput ' + val); - setLoadingState({ text: val, overrideUrl: url }); - } - }) - }), - H(Field, { - label: 'Image 1', - input: H('div', - H(Dropdown, { - options: imageOptions, - value: imageOptions[selectedImageIndex].value, - onchange: (val: string) => { - let clone = [...images]; - clone[0] = val; - const selected = imageOptions.map(o => o.value).indexOf(val); - setLoadingState({ images: clone, selectedImageIndex: selected }); - } - }), - H('div', - { className: 'field-flex' }, - H(Dropdown, { - options: widthOptions, - value: widths[0], - small: true, - onchange: (val: string) => { - let clone = [...widths]; - clone[0] = val; - setLoadingState({ widths: clone }); - } - }), - H(Dropdown, { - options: heightOptions, - value: heights[0], - small: true, - onchange: (val: string) => { - let clone = [...heights]; - clone[0] = val; - setLoadingState({ heights: clone }); - } - }) - ) - ), - }), - ...images.slice(1).map((image, i) => H(Field, { - label: `Image ${i + 2}`, - input: H('div', - H(TextInput, { - value: image, - oninput: (val: string) => { - let clone = [...images]; - clone[i + 1] = val; - setLoadingState({ images: clone, overrideUrl: url }); - } - }), - H('div', - { className: 'field-flex' }, - H(Dropdown, { - options: widthOptions, - value: widths[i + 1], - small: true, - onchange: (val: string) => { - let clone = [...widths]; - clone[i + 1] = val; - setLoadingState({ widths: clone }); - } - }), - H(Dropdown, { - options: heightOptions, - value: heights[i + 1], - small: true, - onchange: (val: string) => { - let clone = [...heights]; - clone[i + 1] = val; - setLoadingState({ heights: clone }); - } - }) - ), - H('div', - { className: 'field-flex' }, - H(Button, { - label: `Remove Image ${i + 2}`, - onclick: (e: MouseEvent) => { - e.preventDefault(); - const filter = (arr: any[]) => [...arr].filter((_, n) => n !== i + 1); - const imagesClone = filter(images); - const widthsClone = filter(widths); - const heightsClone = filter(heights); - setLoadingState({ images: imagesClone, widths: widthsClone, heights: heightsClone }); - } - }) - ) - ) - })), - H(Field, { - label: `Image ${images.length + 1}`, - input: H(Button, { - label: `Add Image ${images.length + 1}`, - onclick: () => { - const nextImage = images.length === 1 - ? 'https://cdn.jsdelivr.net/gh/remojansen/logo.ts@master/ts.svg' - : ''; - setLoadingState({ images: [...images, nextImage] }) - } - }), - }), - ) - ), - H('div', - { className: 'pull-right' }, - H(ImagePreview, { - src: overrideUrl ? overrideUrl.href : url.href, - loading: loading, - onload: () => setState({ loading: false }), - onerror: () => { - setState({ showToast: true, messageToast: 'Oops, an error occurred' }); - setTimeout(() => setState({ showToast: false }), 2000); + setState({ ...newState, loading: true }); + }; + const { + fileType = "png", + fontSize = "100px", + theme = "light", + md = true, + side = "Long", + market = "BTC-PERP", + pnl = "0.00", + avgEntry = "0.00", + markPrice = "0.00", + images = [imageLightOptions[0].value], + widths = [], + heights = [], + showToast = false, + messageToast = "", + loading = true, + selectedImageIndex = 0, + overrideUrl = null, + } = state; + const mdValue = md ? "1" : "0"; + const imageOptions = theme === "light" ? imageLightOptions : imageDarkOptions; + const url = new URL(window.location.origin); + url.pathname = `${encodeURIComponent(side)}.${fileType}`; + url.searchParams.append("theme", theme); + url.searchParams.append("market", market); + url.searchParams.append("pnl", pnl); + url.searchParams.append("avgEntry", avgEntry); + url.searchParams.append("markPrice", markPrice); + url.searchParams.append("md", mdValue); + url.searchParams.append("fontSize", fontSize); + for (let image of images) { + url.searchParams.append("images", image); + } + for (let width of widths) { + url.searchParams.append("widths", width); + } + for (let height of heights) { + url.searchParams.append("heights", height); + } + + return H( + "div", + { className: "split" }, + H( + "div", + { className: "pull-left" }, + H( + "div", + H(Field, { + label: "File Type", + input: H(Dropdown, { + options: fileTypeOptions, + value: fileType, + onchange: (val: FileType) => setLoadingState({ fileType: val }), + }), + }), + H(Field, { + label: "Text Type", + input: H(Dropdown, { + options: markdownOptions, + value: mdValue, + onchange: (val: string) => setLoadingState({ md: val === "1" }), + }), + }), + H(Field, { + label: "Side", + input: H(TextInput, { + value: side, + oninput: (val: string) => { + console.log("oninput " + val); + setLoadingState({ side: val, overrideUrl: url }); + }, + }), + }), + H(Field, { + label: "Market", + input: H(TextInput, { + value: market, + oninput: (val: string) => { + console.log("oninput " + val); + setLoadingState({ market: val }); + }, + }), + }), + H(Field, { + label: "PnL", + input: H(TextInput, { + value: pnl, + oninput: (val: string) => { + console.log("oninput " + val); + setLoadingState({ pnl: val }); + }, + }), + }), + H(Field, { + label: "Avg Entry Price", + input: H(TextInput, { + value: avgEntry, + oninput: (val: string) => { + console.log("oninput " + val); + setLoadingState({ avgEntry: val }); + }, + }), + }), + H(Field, { + label: "Mark Price", + input: H(TextInput, { + value: markPrice, + oninput: (val: string) => { + console.log("oninput " + val); + setLoadingState({ markPrice: val }); + }, + }), + }), + H(Field, { + label: "Image", + input: H( + "div", + H(Dropdown, { + options: imageOptions, + value: imageOptions[selectedImageIndex].value, + onchange: (val: string) => { + let clone = [...images]; + clone[0] = val; + const selected = imageOptions.map((o) => o.value).indexOf(val); + setLoadingState({ + images: clone, + selectedImageIndex: selected, + }); + }, + }), + H( + "div", + { className: "field-flex" }, + H(Dropdown, { + options: widthOptions, + value: widths[0], + small: true, + onchange: (val: string) => { + let clone = [...widths]; + clone[0] = val; + setLoadingState({ widths: clone }); }, - onclick: (e: Event) => { - e.preventDefault(); - const success = copee.toClipboard(url.href); - if (success) { - setState({ showToast: true, messageToast: 'Copied image URL to clipboard' }); - setTimeout(() => setState({ showToast: false }), 3000); - } else { - window.open(url.href, '_blank'); - } - return false; - } - }) - ), - H(Toast, { - message: messageToast, - show: showToast, + }), + H(Dropdown, { + options: heightOptions, + value: heights[0], + small: true, + onchange: (val: string) => { + let clone = [...heights]; + clone[0] = val; + setLoadingState({ heights: clone }); + }, + }) + ) + ), }) - ); + ) + ), + H( + "div", + { className: "pull-right" }, + H(ImagePreview, { + src: overrideUrl ? overrideUrl.href : url.href, + loading: loading, + onload: () => setState({ loading: false }), + onerror: () => { + setState({ + showToast: true, + messageToast: "Oops, an error occurred", + }); + setTimeout(() => setState({ showToast: false }), 2000); + }, + onclick: (e: Event) => { + e.preventDefault(); + const success = copee.toClipboard(url.href); + if (success) { + setState({ + showToast: true, + messageToast: "Copied image URL to clipboard", + }); + setTimeout(() => setState({ showToast: false }), 3000); + } else { + window.open(url.href, "_blank"); + } + return false; + }, + }) + ), + H(Toast, { + message: messageToast, + show: showToast, + }) + ); }; -R(H(App), document.getElementById('app')); +R(H(App), document.getElementById("app"));