Merge pull request #66 from styfle/image-size
Add support for modifying image width & height
This commit is contained in:
commit
9762ae2fdc
|
@ -152,6 +152,12 @@ button:hover {
|
|||
border-color: #ddd;
|
||||
}
|
||||
|
||||
.select-wrapper.small {
|
||||
height: 24px;
|
||||
min-width: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.select-arrow {
|
||||
border-left: 1px solid #eaeaea;
|
||||
background: #fff;
|
||||
|
@ -165,6 +171,10 @@ button:hover {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.select-arrow.small {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
select {
|
||||
height: 100%;
|
||||
border: none;
|
||||
|
@ -180,8 +190,13 @@ select {
|
|||
text-transform: none;
|
||||
}
|
||||
|
||||
.field {
|
||||
margin: 20px 80px;
|
||||
.field-flex {
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
margin: 10px 80px;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
|
@ -196,6 +211,11 @@ select {
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toast-area {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
|
|
127
src/browser.ts
127
src/browser.ts
|
@ -31,11 +31,14 @@ interface DropdownProps {
|
|||
options: DropdownOption[];
|
||||
value: string;
|
||||
onchange: (val: string) => void;
|
||||
small: boolean;
|
||||
}
|
||||
|
||||
const Dropdown = ({ options, value, onchange }: DropdownProps) => {
|
||||
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: 'select-wrapper'},
|
||||
{ className: wrapper },
|
||||
H('select',
|
||||
{ onchange: (e: any) => onchange(e.target.value) },
|
||||
options.map(o =>
|
||||
|
@ -46,7 +49,7 @@ const Dropdown = ({ options, value, onchange }: DropdownProps) => {
|
|||
)
|
||||
),
|
||||
H('div',
|
||||
{ className: 'select-arrow' },
|
||||
{ className: arrow },
|
||||
'▼'
|
||||
)
|
||||
);
|
||||
|
@ -150,11 +153,35 @@ const imageDarkOptions: DropdownOption[] = [
|
|||
{ text: 'Hyper', value: 'https://assets.zeit.co/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' },
|
||||
];
|
||||
|
||||
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' },
|
||||
];
|
||||
|
||||
interface AppState extends ParsedRequest {
|
||||
loading: boolean;
|
||||
showToast: boolean;
|
||||
messageToast: string;
|
||||
selectedImageIndex: number;
|
||||
widths: string[];
|
||||
heights: string[];
|
||||
overrideUrl: URL | null;
|
||||
}
|
||||
|
||||
|
@ -179,6 +206,8 @@ const App = (_: any, state: AppState, setState: SetState) => {
|
|||
md = true,
|
||||
text = '**Hello** World',
|
||||
images=[imageLightOptions[0].value],
|
||||
widths=[],
|
||||
heights=[],
|
||||
showToast = false,
|
||||
messageToast = '',
|
||||
loading = true,
|
||||
|
@ -195,6 +224,12 @@ const App = (_: any, state: AppState, setState: SetState) => {
|
|||
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' },
|
||||
|
@ -250,27 +285,77 @@ const App = (_: any, state: AppState, setState: SetState) => {
|
|||
}),
|
||||
H(Field, {
|
||||
label: 'Image 1',
|
||||
input: 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 });
|
||||
}
|
||||
})
|
||||
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(TextInput, {
|
||||
value: image,
|
||||
oninput: (val: string) => {
|
||||
let clone = [...images];
|
||||
clone[i + 1] = val;
|
||||
setLoadingState({ images: clone, overrideUrl: url });
|
||||
}
|
||||
})
|
||||
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(Field, {
|
||||
label: `Image ${images.length + 1}`,
|
||||
|
|
|
@ -5,11 +5,17 @@ import { getHtml } from './template';
|
|||
import { writeTempFile, pathToFileURL } from './file';
|
||||
|
||||
const isDev = !process.env.NOW_REGION;
|
||||
const isHtmlDebug = process.env.OG_HTML_DEBUG === '1';
|
||||
|
||||
export default async function handler(req: IncomingMessage, res: ServerResponse) {
|
||||
try {
|
||||
const parsedReq = parseRequest(req);
|
||||
const html = getHtml(parsedReq);
|
||||
if (isHtmlDebug) {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end(html);
|
||||
return;
|
||||
}
|
||||
const { text, fileType } = parsedReq;
|
||||
const filePath = await writeTempFile(text, html);
|
||||
const fileUrl = pathToFileURL(filePath);
|
||||
|
|
|
@ -4,14 +4,14 @@ import { parse } from 'url';
|
|||
export function parseRequest(req: IncomingMessage) {
|
||||
console.log('HTTP ' + req.url);
|
||||
const { pathname = '/', query = {} } = parse(req.url || '', true);
|
||||
const { fontSize, images, theme, md } = query;
|
||||
const { fontSize, images, 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 = '';
|
||||
|
@ -31,13 +31,19 @@ export function parseRequest(req: IncomingMessage) {
|
|||
theme: theme === 'dark' ? 'dark' : 'light',
|
||||
md: md === '1' || md === 'true',
|
||||
fontSize: fontSize || '96px',
|
||||
images: Array.isArray(images) ? images : [images],
|
||||
images: getArray(images),
|
||||
widths: getArray(widths),
|
||||
heights: getArray(heights),
|
||||
};
|
||||
parsedRequest.images = getDefaultImages(parsedRequest.images, parsedRequest.theme);
|
||||
return parsedRequest;
|
||||
}
|
||||
|
||||
function getDefaultImages(images: string[], theme: Theme) {
|
||||
function getArray(stringOrArray: string[] | string): string[] {
|
||||
return Array.isArray(stringOrArray) ? stringOrArray : [stringOrArray];
|
||||
}
|
||||
|
||||
function getDefaultImages(images: string[], theme: Theme): string[] {
|
||||
if (images.length > 0 && images[0] && images[0].startsWith('https://assets.zeit.co/image/upload/front/assets/design/')) {
|
||||
return images;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ function getCss(theme: string, fontSize: string) {
|
|||
content: '\`';
|
||||
}
|
||||
|
||||
.img-wrapper {
|
||||
.logo-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
|
@ -73,8 +73,6 @@ function getCss(theme: string, fontSize: string) {
|
|||
}
|
||||
|
||||
.logo {
|
||||
width: auto;
|
||||
height: 225px;
|
||||
margin: 0 75px;
|
||||
}
|
||||
|
||||
|
@ -88,7 +86,7 @@ function getCss(theme: string, fontSize: string) {
|
|||
margin: 150px;
|
||||
}
|
||||
|
||||
img.emoji {
|
||||
.emoji {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
margin: 0 .05em 0 .1em;
|
||||
|
@ -105,8 +103,7 @@ function getCss(theme: string, fontSize: string) {
|
|||
}
|
||||
|
||||
export function getHtml(parsedReq: ParsedRequest) {
|
||||
const { text, theme, md, fontSize, images } = parsedReq;
|
||||
const [ firstImage, ...otherImages ] = images;
|
||||
const { text, theme, md, fontSize, images, widths, heights } = parsedReq;
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<meta charset="utf-8">
|
||||
|
@ -118,11 +115,10 @@ export function getHtml(parsedReq: ParsedRequest) {
|
|||
<body>
|
||||
<div>
|
||||
<div class="spacer">
|
||||
<div class="img-wrapper">
|
||||
<img class="logo" src="${sanitizeHtml(firstImage)}" />
|
||||
${otherImages.map(img =>
|
||||
`<div class="plus">+</div><img class="logo" src="${sanitizeHtml(img)}" />`
|
||||
)}
|
||||
<div class="logo-wrapper">
|
||||
${images.map((img, i) =>
|
||||
getPlusSign(i) + getImage(img, widths[i], heights[i])
|
||||
).join('')}
|
||||
</div>
|
||||
<div class="spacer">
|
||||
<div class="heading">${emojify(
|
||||
|
@ -133,3 +129,16 @@ export function getHtml(parsedReq: ParsedRequest) {
|
|||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
function getImage(src: string, width ='auto', height = '225') {
|
||||
return `<img
|
||||
class="logo"
|
||||
src="${sanitizeHtml(src)}"
|
||||
width="${sanitizeHtml(width)}"
|
||||
height="${sanitizeHtml(height)}"
|
||||
/>`
|
||||
}
|
||||
|
||||
function getPlusSign(i: number) {
|
||||
return i === 0 ? '' : '<div class="plus">+</div>';
|
||||
}
|
||||
|
|
|
@ -8,4 +8,6 @@ interface ParsedRequest {
|
|||
md: boolean;
|
||||
fontSize: string;
|
||||
images: string[];
|
||||
widths: string[];
|
||||
heights: string[];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue