og-image/src/browser.ts

405 lines
14 KiB
TypeScript
Raw Normal View History

2019-01-25 13:19:44 -08:00
const { H, R, copee } = (window as any);
2019-01-31 07:35:16 -08:00
let timeout = -1;
2019-01-24 09:50:34 -08:00
2019-01-25 13:19:44 -08:00
interface ImagePreviewProps {
src: string;
onclick: () => void;
onload: () => void;
onerror: () => void;
loading: boolean;
}
const ImagePreview = ({ src, onclick, onload, onerror, loading }: ImagePreviewProps) => {
2019-01-24 09:18:22 -08:00
const style = {
filter: loading ? 'blur(5px)' : '',
opacity: loading ? 0.1 : 1,
};
2019-01-23 12:00:45 -08:00
return H('a',
2019-01-23 12:44:49 -08:00
{ className: 'image-wrapper', href: src, onclick },
2019-01-23 12:00:45 -08:00
H('img',
2019-01-24 12:34:45 -08:00
{ src, onload, onerror, style }
2019-01-23 12:00:45 -08:00
)
);
2019-01-23 07:16:53 -08:00
}
2019-01-25 13:19:44 -08:00
interface DropdownOption {
text: string;
value: string;
}
interface DropdownProps {
options: DropdownOption[];
value: string;
onchange: (val: string) => void;
2019-03-01 14:54:33 -08:00
small: boolean;
2019-01-25 13:19:44 -08:00
}
2019-03-01 14:54:33 -08:00
const Dropdown = ({ options, value, onchange, small }: DropdownProps) => {
const wrapper = small ? 'select-wrapper small' : 'select-wrapper';
const arrow = small ? 'select-arrow small' : 'select-arrow';
2019-01-23 10:59:32 -08:00
return H('div',
2019-03-01 14:54:33 -08:00
{ className: wrapper },
2019-01-23 10:59:32 -08:00
H('select',
{ onchange: (e: any) => onchange(e.target.value) },
2019-01-23 10:59:32 -08:00
options.map(o =>
H('option',
{ value: o.value, selected: value === o.value },
o.text
)
2019-01-23 07:16:53 -08:00
)
2019-01-23 10:59:32 -08:00
),
H('div',
2019-03-01 14:54:33 -08:00
{ className: arrow },
2019-01-23 10:59:32 -08:00
'▼'
2019-01-23 07:16:53 -08:00
)
);
}
2019-01-25 13:19:44 -08:00
interface TextInputProps {
value: string;
oninput: (val: string) => void;
}
const TextInput = ({ value, oninput }: TextInputProps) => {
2019-01-23 10:59:32 -08:00
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) }
2019-01-23 10:59:32 -08:00
)
)
2019-01-23 08:09:35 -08:00
);
}
2019-01-25 13:19:44 -08:00
interface ButtonProps {
label: string;
onclick: () => void;
}
const Button = ({ label, onclick }: ButtonProps) => {
return H('button', { onclick }, label);
2019-01-23 07:16:53 -08:00
}
2019-01-25 13:19:44 -08:00
interface FieldProps {
label: string;
input: any;
}
const Field = ({ label, input }: FieldProps) => {
2019-01-23 07:16:53 -08:00
return H('div',
2019-01-23 10:59:32 -08:00
{ className: 'field' },
H('label',
H('div', {className: 'field-label'}, label),
H('div', { className: 'field-value' }, input),
),
2019-01-23 07:16:53 -08:00
);
}
2019-01-25 13:19:44 -08:00
interface ToastProps {
show: boolean;
message: string;
}
const Toast = ({ show, message }: ToastProps) => {
2019-01-23 12:00:45 -08:00
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
)
)
),
);
}
2019-01-23 07:16:53 -08:00
2019-01-25 13:19:44 -08:00
const themeOptions: DropdownOption[] = [
2019-01-24 14:40:38 -08:00
{ text: 'Light', value: 'light' },
{ text: 'Dark', value: 'dark' },
];
2019-01-25 13:19:44 -08:00
const fileTypeOptions: DropdownOption[] = [
2019-01-23 07:16:53 -08:00
{ text: 'PNG', value: 'png' },
{ text: 'JPEG', value: 'jpeg' },
];
2019-01-25 13:19:44 -08:00
const fontSizeOptions: DropdownOption[] = Array
2019-01-23 07:16:53 -08:00
.from({ length: 10 })
.map((_, i) => i * 25)
.filter(n => n > 0)
.map(n => ({ text: n + 'px', value: n + 'px' }));
2019-01-25 13:19:44 -08:00
const markdownOptions: DropdownOption[] = [
2019-01-23 07:16:53 -08:00
{ text: 'Plain Text', value: '0' },
{ text: 'Markdown', value: '1' },
];
2019-01-25 14:53:07 -08:00
const imageLightOptions: DropdownOption[] = [
{ text: 'Now', value: 'https://assets.zeit.co/image/upload/front/assets/design/now-black.svg' },
{ text: 'ZEIT', value: 'https://assets.zeit.co/image/upload/front/assets/design/zeit-black-triangle.svg' },
{ text: 'Next.js', value: 'https://assets.zeit.co/image/upload/front/assets/design/nextjs-black-logo.svg' },
{ text: 'Hyper', value: 'https://assets.zeit.co/image/upload/front/assets/design/hyper-color-logo.svg' },
];
const imageDarkOptions: DropdownOption[] = [
{ text: 'Now', value: 'https://assets.zeit.co/image/upload/front/assets/design/now-white.svg' },
{ text: 'ZEIT', value: 'https://assets.zeit.co/image/upload/front/assets/design/zeit-white-triangle.svg' },
{ text: 'Next.js', value: 'https://assets.zeit.co/image/upload/front/assets/design/nextjs-white-logo.svg' },
{ text: 'Hyper', value: 'https://assets.zeit.co/image/upload/front/assets/design/hyper-bw-logo.svg' },
];
2019-03-01 14:54:33 -08:00
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' },
];
2019-01-25 13:19:44 -08:00
interface AppState extends ParsedRequest {
loading: boolean;
showToast: boolean;
messageToast: string;
2019-01-25 14:53:07 -08:00
selectedImageIndex: number;
2019-03-01 14:54:33 -08:00
widths: string[];
heights: string[];
2019-01-31 07:35:16 -08:00
overrideUrl: URL | null;
2019-01-25 13:19:44 -08:00
}
type SetState = (state: Partial<AppState>) => void;
const App = (_: any, state: AppState, setState: SetState) => {
const setLoadingState = (newState: Partial<AppState>) => {
2019-01-31 07:35:16 -08:00
window.clearTimeout(timeout);
if (state.overrideUrl && state.overrideUrl !== newState.overrideUrl) {
newState.overrideUrl = state.overrideUrl;
}
if (newState.overrideUrl) {
timeout = window.setTimeout(() => setState({ overrideUrl: null }), 200);
}
2019-01-24 14:40:38 -08:00
setState({ ...newState, loading: true });
};
2019-01-24 12:34:45 -08:00
const {
fileType = 'png',
2019-01-31 07:35:16 -08:00
fontSize = '100px',
2019-01-24 14:40:38 -08:00
theme = 'light',
2019-01-25 13:19:44 -08:00
md = true,
2019-01-24 12:34:45 -08:00
text = '**Hello** World',
2019-01-28 07:21:58 -08:00
images=[imageLightOptions[0].value],
2019-03-01 14:54:33 -08:00
widths=[],
heights=[],
2019-01-24 12:34:45 -08:00
showToast = false,
messageToast = '',
2019-01-25 14:53:07 -08:00
loading = true,
selectedImageIndex = 0,
2019-01-31 07:35:16 -08:00
overrideUrl = null,
2019-01-24 12:34:45 -08:00
} = state;
2019-01-25 14:53:07 -08:00
const mdValue = md ? '1' : '0';
const imageOptions = theme === 'light' ? imageLightOptions : imageDarkOptions;
const url = new URL(window.location.origin);
2019-01-24 13:22:17 -08:00
url.pathname = `${encodeURIComponent(text)}.${fileType}`;
2019-01-24 14:40:38 -08:00
url.searchParams.append('theme', theme);
2019-01-25 14:53:07 -08:00
url.searchParams.append('md', mdValue);
2019-01-23 08:09:35 -08:00
url.searchParams.append('fontSize', fontSize);
2019-01-23 13:08:01 -08:00
for (let image of images) {
url.searchParams.append('images', image);
2019-01-23 08:09:35 -08:00
}
2019-03-01 14:54:33 -08:00
for (let width of widths) {
url.searchParams.append('widths', width);
}
for (let height of heights) {
url.searchParams.append('heights', height);
}
2019-01-31 07:35:16 -08:00
2019-01-23 07:16:53 -08:00
return H('div',
2019-01-23 12:22:51 -08:00
{ className: 'split' },
H('div',
2019-01-24 08:09:26 -08:00
{ className: 'pull-left' },
H('div',
2019-01-24 14:40:38 -08:00
H(Field, {
label: 'Theme',
input: H(Dropdown, {
options: themeOptions,
value: theme,
2019-01-25 13:19:44 -08:00
onchange: (val: Theme) => {
2019-01-28 07:21:58 -08:00
const options = val === 'light' ? imageLightOptions : imageDarkOptions
let clone = [...images];
clone[0] = options[selectedImageIndex].value;
setLoadingState({ theme: val, images: clone });
2019-01-24 14:40:38 -08:00
}
})
}),
2019-01-24 08:09:26 -08:00
H(Field, {
label: 'File Type',
2019-01-25 14:53:07 -08:00
input: H(Dropdown, {
options: fileTypeOptions,
value: fileType,
onchange: (val: FileType) => setLoadingState({ fileType: val })
})
2019-01-24 08:09:26 -08:00
}),
H(Field, {
label: 'Font Size',
2019-01-25 14:53:07 -08:00
input: H(Dropdown, {
options: fontSizeOptions,
value: fontSize,
onchange: (val: string) => setLoadingState({ fontSize: val })
})
2019-01-24 08:09:26 -08:00
}),
H(Field, {
label: 'Text Type',
2019-01-25 14:53:07 -08:00
input: H(Dropdown, {
options: markdownOptions,
value: mdValue,
onchange: (val: string) => setLoadingState({ md: val === '1' })
})
2019-01-24 08:09:26 -08:00
}),
H(Field, {
label: 'Text Input',
2019-01-24 09:50:34 -08:00
input: H(TextInput, {
value: text,
2019-01-31 07:35:16 -08:00
oninput: (val: string) => {
console.log('oninput ' + val);
setLoadingState({ text: val, overrideUrl: url });
}
2019-01-24 09:50:34 -08:00
})
2019-01-23 12:22:51 -08:00
}),
2019-01-25 14:53:07 -08:00
H(Field, {
label: 'Image 1',
2019-03-01 14:54:33 -08:00
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 });
}
})
)
),
2019-01-25 14:53:07 -08:00
}),
...images.slice(1).map((image, i) => H(Field, {
label: `Image ${i + 2}`,
2019-03-01 14:54:33 -08:00
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 });
}
})
)
)
2019-01-24 08:09:26 -08:00
})),
H(Field, {
label: `Image ${images.length + 1}`,
input: H(Button, {
label: `Add Image ${images.length + 1}`,
2019-01-24 13:38:10 -08:00
onclick: () => {
const nextImage = images.length === 1
? 'https://cdn.jsdelivr.net/gh/remojansen/logo.ts@master/ts.svg'
: '';
setLoadingState({ images: [...images, nextImage] })
}
2019-01-24 08:09:26 -08:00
}),
}),
)
2019-01-23 12:22:51 -08:00
),
H('div',
2019-04-21 07:01:07 -07:00
{ className: 'pull-right' },
2019-01-23 12:22:51 -08:00
H(ImagePreview, {
2019-01-31 07:35:16 -08:00
src: overrideUrl ? overrideUrl.href : url.href,
2019-01-24 09:18:22 -08:00
loading: loading,
2019-01-25 13:19:44 -08:00
onload: () => setState({ loading: false }),
onerror: () => {
2019-01-24 12:34:45 -08:00
setState({ showToast: true, messageToast: 'Oops, an error occurred' });
setTimeout(() => setState({ showToast: false }), 2000);
},
2019-01-25 13:19:44 -08:00
onclick: (e: Event) => {
2019-01-23 12:22:51 -08:00
e.preventDefault();
2019-01-25 13:19:44 -08:00
const success = copee.toClipboard(url.href);
2019-01-23 12:22:51 -08:00
if (success) {
2019-01-24 12:34:45 -08:00
setState({ showToast: true, messageToast: 'Copied image URL to clipboard' });
2019-01-23 12:22:51 -08:00
setTimeout(() => setState({ showToast: false }), 3000);
} else {
window.open(url.href, '_blank');
}
return false;
2019-01-23 12:00:45 -08:00
}
2019-01-23 12:22:51 -08:00
})
2019-01-24 08:09:26 -08:00
),
H(Toast, {
2019-01-24 12:34:45 -08:00
message: messageToast,
2019-01-24 08:09:26 -08:00
show: showToast,
})
2019-01-23 12:22:51 -08:00
);
2019-01-23 07:16:53 -08:00
};
2019-01-23 08:09:35 -08:00
2019-01-30 14:48:00 -08:00
R(H(App), document.getElementById('app'));