1const { H, R, copee } = window; 2let timeout = -1; 3const ImagePreview = ({ src, onclick, onload, onerror, loading }) => { 4 const style = { 5 filter: loading ? 'blur(5px)' : '', 6 opacity: loading ? 0.1 : 1, 7 }; 8 const title = 'Click to copy image URL to clipboard'; 9 return H('a', { className: 'image-wrapper', href: src, onclick }, H('img', { src, onload, onerror, style, title })); 10}; 11const Dropdown = ({ options, value, onchange, small }) => { 12 const wrapper = small ? 'select-wrapper small' : 'select-wrapper'; 13 const arrow = small ? 'select-arrow small' : 'select-arrow'; 14 return H('div', { className: wrapper }, H('select', { onchange: (e) => onchange(e.target.value) }, options.map(o => H('option', { value: o.value, selected: value === o.value }, o.text))), H('div', { className: arrow }, 'â¼')); 15}; 16const TextInput = ({ value, oninput, small, type = 'text', placeholder = '' }) => { 17 return H('div', { className: 'input-outer-wrapper' + (small ? ' small' : '') }, H('div', { className: 'input-inner-wrapper' }, H('input', { type, value, placeholder, oninput: (e) => oninput(e.target.value) }))); 18}; 19const Button = ({ label, onclick }) => { 20 return H('button', { onclick }, label); 21}; 22const Field = ({ label, input }) => { 23 return H('div', { className: 'field' }, H('label', H('div', { className: 'field-label' }, label), H('div', { className: 'field-value' }, input))); 24}; 25const Toast = ({ show, message }) => { 26 const style = { transform: show ? 'translate3d(0,-0px,-0px) scale(1)' : '' }; 27 return H('div', { className: 'toast-area' }, H('div', { className: 'toast-outer', style }, H('div', { className: 'toast-inner' }, H('div', { className: 'toast-message' }, message)))); 28}; 29const themeOptions = [ 30 { text: 'Light', value: 'light' }, 31 { text: 'Dark', value: 'dark' }, 32]; 33const fileTypeOptions = [ 34 { text: 'PNG', value: 'png' }, 35 { text: 'JPEG', value: 'jpeg' }, 36]; 37const fontSizeOptions = Array 38 .from({ length: 10 }) 39 .map((_, i) => i * 25) 40 .filter(n => n > 0) 41 .map(n => ({ text: n + 'px', value: n + 'px' })); 42const markdownOptions = [ 43 { text: 'Plain Text', value: '0' }, 44 { text: 'Markdown', value: '1' }, 45]; 46const imageLightOptions = [ 47 { text: 'Vercel', value: 'https://assets.vercel.com/image/upload/front/assets/design/vercel-triangle-black.svg' }, 48 { text: 'Next.js', value: 'https://assets.vercel.com/image/upload/front/assets/design/nextjs-black-logo.svg' }, 49 { text: 'Hyper', value: 'https://assets.vercel.com/image/upload/front/assets/design/hyper-color-logo.svg' }, 50]; 51const imageDarkOptions = [ 52 { text: 'Vercel', value: 'https://assets.vercel.com/image/upload/front/assets/design/vercel-triangle-white.svg' }, 53 { text: 'Next.js', value: 'https://assets.vercel.com/image/upload/front/assets/design/nextjs-white-logo.svg' }, 54 { text: 'Hyper', value: 'https://assets.vercel.com/image/upload/front/assets/design/hyper-bw-logo.svg' }, 55]; 56const App = (_, state, setState) => { 57 const setLoadingState = (newState) => { 58 window.clearTimeout(timeout); 59 if (state.overrideUrl && state.overrideUrl !== newState.overrideUrl) { 60 newState.overrideUrl = state.overrideUrl; 61 } 62 if (newState.overrideUrl) { 63 timeout = window.setTimeout(() => setState({ overrideUrl: null }), 200); 64 } 65 setState({ ...newState, loading: true }); 66 }; 67 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; 68 const mdValue = md ? '1' : '0'; 69 const imageOptions = theme === 'light' ? imageLightOptions : imageDarkOptions; 70 const url = new URL(window.location.origin); 71 url.pathname = `${encodeURIComponent(text)}.${fileType}`; 72 url.searchParams.append('theme', theme); 73 url.searchParams.append('md', mdValue); 74 url.searchParams.append('fontSize', fontSize); 75 for (let image of images) { 76 url.searchParams.append('images', image); 77 } 78 for (let width of widths) { 79 url.searchParams.append('widths', width); 80 } 81 for (let height of heights) { 82 url.searchParams.append('heights', height); 83 } 84 return H('div', { className: 'split' }, H('div', { className: 'pull-left' }, H('div', H(Field, { 85 label: 'Theme', 86 input: H(Dropdown, { 87 options: themeOptions, 88 value: theme, 89 onchange: (val) => { 90 const options = val === 'light' ? imageLightOptions : imageDarkOptions; 91 let clone = [...images]; 92 clone[0] = options[selectedImageIndex].value;
93 setLoadingState({ theme: val, images: clone }); 94 } 95 }) 96 }), H(Field, { 97 label: 'File Type', 98 input: H(Dropdown, { 99 options: fileTypeOptions, 100 value: fileType, 101 onchange: (val) => setLoadingState({ fileType: val }) 102 }) 103 }), H(Field, { 104 label: 'Font Size', 105 input: H(Dropdown, { 106 options: fontSizeOptions, 107 value: fontSize, 108 onchange: (val) => setLoadingState({ fontSize: val }) 109 }) 110 }), H(Field, { 111 label: 'Text Type', 112 input: H(Dropdown, { 113 options: markdownOptions, 114 value: mdValue, 115 onchange: (val) => setLoadingState({ md: val === '1' }) 116 }) 117 }), H(Field, { 118 label: 'Text Input', 119 input: H(TextInput, { 120 value: text, 121 oninput: (val) => { 122 console.log('oninput ' + val); 123 setLoadingState({ text: val, overrideUrl: url }); 124 } 125 }) 126 }), H(Field, { 127 label: 'Image 1', 128 input: H('div', H(Dropdown, { 129 options: imageOptions, 130 value: imageOptions[selectedImageIndex].value, 131 onchange: (val) => { 132 let clone = [...images]; 133 clone[0] = val; 134 const selected = imageOptions.map(o => o.value).indexOf(val); 135 setLoadingState({ images: clone, selectedImageIndex: selected }); 136 } 137 }), H('div', { className: 'field-flex' }, H(TextInput, { 138 value: widths[0], 139 type: 'number', 140 placeholder: 'width', 141 small: true, 142 oninput: (val) => { 143 let clone = [...widths]; 144 clone[0] = val; 145 setLoadingState({ widths: clone }); 146 } 147 }), H(TextInput, { 148 value: heights[0], 149 type: 'number', 150 placeholder: 'height', 151 small: true, 152 oninput: (val) => { 153 let clone = [...heights]; 154 clone[0] = val; 155 setLoadingState({ heights: clone }); 156 } 157 }))), 158 }), ...images.slice(1).map((image, i) => H(Field, { 159 label: `Image ${i + 2}`, 160 input: H('div', H(TextInput, { 161 value: image, 162 oninput: (val) => { 163 let clone = [...images]; 164 clone[i + 1] = val; 165 setLoadingState({ images: clone, overrideUrl: url }); 166 } 167 }), H('div', { className: 'field-flex' }, H(TextInput, { 168 value: widths[i + 1], 169 type: 'number', 170 placeholder: 'width', 171 small: true, 172 oninput: (val) => { 173 let clone = [...widths]; 174 clone[i + 1] = val; 175 setLoadingState({ widths: clone }); 176 } 177 }), H(TextInput, { 178 value: heights[i + 1], 179 type: 'number', 180 placeholder: 'height', 181 small: true, 182 oninput: (val) => { 183 let clone = [...heights]; 184 clone[i + 1] = val; 185 setLoadingState({ heights: clone }); 186 } 187 })), H('div', { className: 'field-flex' }, H(Button, { 188 label: `Remove Image ${i + 2}`, 189 onclick: (e) => { 190 e.preventDefault(); 191 const filter = (arr) => [...arr].filter((_, n) => n !== i + 1); 192 const imagesClone = filter(images); 193 const widthsClone = filter(widths); 194 const heightsClone = filter(heights); 195 setLoadingState({ images: imagesClone, widths: widthsClone, heights: heightsClone }); 196 } 197 }))) 198 })), H(Field, { 199 label: `Image ${images.length + 1}`, 200 input: H(Button, { 201 label: `Add Image ${images.length + 1}`, 202 onclick: () => { 203 const nextImage = images.length === 1 204 ? 'https://cdn.jsdelivr.net/gh/remojansen/logo.ts@master/ts.svg' 205 : '';
206 setLoadingState({ images: [...images, nextImage] }); 207 } 208 }), 209 }))), H('div', { className: 'pull-right' }, H(ImagePreview, { 210 src: overrideUrl ? overrideUrl.href : url.href, 211 loading: loading, 212 onload: () => setState({ loading: false }), 213 onerror: () => { 214 setState({ showToast: true, messageToast: 'Oops, an error occurred' }); 215 setTimeout(() => setState({ showToast: false }), 2000); 216 }, 217 onclick: (e) => { 218 e.preventDefault(); 219 const success = copee.toClipboard(url.href); 220 if (success) { 221 setState({ showToast: true, messageToast: 'Copied image URL to clipboard' }); 222 setTimeout(() => setState({ showToast: false }), 3000); 223 } 224 else { 225 window.open(url.href, '_blank'); 226 } 227 return false; 228 } 229 })), H(Toast, { 230 message: messageToast, 231 show: showToast, 232 })); 233}; 234R(H(App), document.getElementById('app')); 235export {}; 236//# sourceMappingURL=index.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.