diff --git a/src/shared/initial-app/Intro/index.tsx b/src/shared/initial-app/Intro/index.tsx index 10b01cbb..cc6b3362 100644 --- a/src/shared/initial-app/Intro/index.tsx +++ b/src/shared/initial-app/Intro/index.tsx @@ -2,7 +2,6 @@ import { h, Component } from 'preact'; import { linkRef } from 'shared/initial-app/util'; import '../custom-els/loading-spinner'; - import logo from 'url:./imgs/logo.svg'; import largePhoto from 'url:./imgs/demos/demo-large-photo.jpg'; import artwork from 'url:./imgs/demos/demo-artwork.jpg'; @@ -43,6 +42,17 @@ const demos = [ ]; const installButtonSource = 'introInstallButton-Purple'; +const supportsClipboardAPI = + !__PRERENDER__ && navigator.clipboard && navigator.clipboard.read; + +async function getImageClipboardItem( + items: ClipboardItem[], +): Promise { + for (const item of items) { + const type = item.types.find((type) => type.startsWith('image/')); + if (type) return item.getType(type); + } +} interface Props { onFile?: (file: File) => void; @@ -58,10 +68,7 @@ export default class Intro extends Component { private fileInput?: HTMLInputElement; private installingViaButton = false; - constructor() { - super(); - - if (__PRERENDER__) return; + componentDidMount() { // Listen for beforeinstallprompt events, indicating Squoosh is installable. window.addEventListener( 'beforeinstallprompt', @@ -72,15 +79,19 @@ export default class Intro extends Component { window.addEventListener('appinstalled', this.onAppInstalled); } - private resetFileInput = () => { - this.fileInput!.value = ''; - }; + componentWillUnmount() { + window.removeEventListener( + 'beforeinstallprompt', + this.onBeforeInstallPromptEvent, + ); + window.removeEventListener('appinstalled', this.onAppInstalled); + } private onFileChange = (event: Event): void => { const fileInput = event.target as HTMLInputElement; const file = fileInput.files && fileInput.files[0]; if (!file) return; - this.resetFileInput(); + this.fileInput!.value = ''; this.props.onFile!(file); }; @@ -94,10 +105,8 @@ export default class Intro extends Component { const demo = demos[index]; const blob = await fetch(demo.url).then((r) => r.blob()); - // Firefox doesn't like content types like 'image/png; charset=UTF-8', which Webpack's dev - // server returns. https://bugzilla.mozilla.org/show_bug.cgi?id=1497925. - const type = /[^;]*/.exec(blob.type)![0]; - const file = new File([blob], demo.filename, { type }); + // TODO: test this is ok on both netlify and dev server in Firefox + const file = new File([blob], demo.filename, { type: blob.type }); this.props.onFile!(file); } catch (err) { this.setState({ fetchingDemoIndex: undefined }); @@ -154,9 +163,7 @@ export default class Intro extends Component { this.setState({ beforeInstallEvent: undefined }); // Don't log analytics if page is not visible - if (document.hidden) { - return; - } + if (document.hidden) return; // Try to get the install, if it's not set, use 'browser' const source = this.installingViaButton ? installButtonSource : 'browser'; @@ -166,89 +173,65 @@ export default class Intro extends Component { this.installingViaButton = false; }; + private onPasteClick = async () => { + let clipboardItems: ClipboardItem[]; + + try { + clipboardItems = await navigator.clipboard.read(); + } catch (err) { + this.props.showSnack!(`Cannot access clipboard`); + return; + } + + const blob = await getImageClipboardItem(clipboardItems); + + if (!blob) { + this.props.showSnack!(`No image found`); + return; + } + + console.log(blob); + + this.props.onFile!(new File([blob], 'image.unknown')); + }; + render({}: Props, { fetchingDemoIndex, beforeInstallEvent }: State) { return (
-
-
-
- Squoosh + + {beforeInstallEvent && ( + + )} +
+
Logo Placeholder
+
+
+ +
+ Drop OR{' '} + {supportsClipboardAPI ? ( + + ) : ( + 'Paste' + )} +
-

- Drag & drop or{' '} - - -

-

Or try one of these:

-
    - {demos.map((demo, i) => ( -
  • - -
  • - ))} -
- {beforeInstallEvent && ( - - )} -
); } diff --git a/src/shared/initial-app/Intro/missing-types.d.ts b/src/shared/initial-app/Intro/missing-types.d.ts index a35205c5..6ef3d345 100644 --- a/src/shared/initial-app/Intro/missing-types.d.ts +++ b/src/shared/initial-app/Intro/missing-types.d.ts @@ -29,3 +29,12 @@ interface BeforeInstallPromptEvent extends Event { interface WindowEventMap { beforeinstallprompt: BeforeInstallPromptEvent; } + +interface ClipboardItem { + types: string[]; + getType(type: string): Promise; +} + +interface Clipboard { + read(): Promise; +} diff --git a/src/shared/initial-app/Intro/style.css b/src/shared/initial-app/Intro/style.css index 6e936e5c..e7b5c948 100644 --- a/src/shared/initial-app/Intro/style.css +++ b/src/shared/initial-app/Intro/style.css @@ -1,228 +1,62 @@ -@font-face { - font-family: 'intro-text'; - font-style: normal; - font-weight: 300; - font-display: block; - /* This only contains the chars for "Drag & drop or" */ - src: url('data:font/woff2;base64,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') - format('woff2'); -} - -@font-face { - font-family: 'intro-text'; - font-style: normal; - font-weight: 500; - font-display: block; - /* Only contains the chars for "select an image" */ - src: url('data:font/woff2;base64,d09GMgABAAAAAAXMAA4AAAAACwQAAAV5AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbg1IcMAZgAGwRCAqJUId/CxoAATYCJAMwBCAFgnAHIBskCcjEh6dNff8Ou/9Tj9VZGnUhJeqFzWGiVVOkxkTthr9f6kWkRdsBbkIj3YuLaloFZWr7aBg22z7IOoWqBWCW5cZU3GBrh0n+dAcBYAUlzYHAzWTYchqKXEyAT0zOLIS1qqm+B8q2ur4OhEMy0PHHUH8KklaSr8T0mp/EU7kRvXlI1E09HXA1qLN8Djxa0AsSDOg3cJARb9mtQJoSK3gvEn372/gcAigg/gOnbsT/MYv491GTReW4rJC5LA+h5FFclF6QQgoZ5Kx7GbsuGeytUgFClkOomY2Gdake3m9HegkHieAx/a0hBTALsy4jvpxBcnFXUnjC+2ZS5zHnDeEaJVwi+ZWqzOm4Uvgy4k6kGv4kFDVkfjk1gkVRRkk2zlo42PBbRJmG30cClJQjak7BnfQqHza4ITKftQZ/ZMUaEiyy1+mYCh4clKhDA5rQglZ0oG9jw+qiNvT+SfxIXCeuFdeIq8VV4gpxyRaGOl0JiChiCocfc7e93DwZIPvWgPiZJLcJugxyjW7UQyl4TJk6dWqYU7Cn0WQiWnNJCdGeprqjW63fpVS3mKy4YGZ6I3ya4nbIVgM1mwkpNEBzixlNxfPmH7owvdE4973OM9quvk11dwvnzDUy/Zn5S5Ywpn/PeqXBQI2m4lna05CRtsI6+GIENjS9K4jWRHUGYA2ozdZm2Smmf0DI3aqpeNbsJfxe7YdFmcZAn5gXLCFa2/Umqiu017APFhMZ0rfQp4sJX0ZrJ+n9UtAljr5VYWb6oj1MrpvX3qe6u8WRJg0bj7aPkDOa7m+E0Oa9Y8eY/gbRbr+efH7hcO49bMd28fbDVHcUmm3XkozQGKjeBHSJ4TQnI879LIFmF2v/BJuEQlffJPfE9oKayS/PsPE44fvM4MsBESxbuEEV39d5pw6oW4vD6S1WQC3UpSbHNbK0Jikl0bphSs+0CGW8Ew4Kzw7zarmcVz873JHTFhKYay18R8vY0ozPiHPAGyROAqlW5fLj5+HbWBn9TpgekKsOy8N+4dlFfL9i/Nk3+gY1bwzZUAvLVNiFpvqHRenetSoVrgn2obGtPsltEVxEeHJAQFhyBIcHT9rDvTJm06e0TLgase2gd2RffGJNg0o1zrdRyi9s1bYE5bi85cK+o/nUwvBR5+jweEBaSMoCub29fEFISmib9Dn5yl5kVFpoGrPQSmZhafQ7WimttNCH7Cktohb6kFpoEfIsdDF7SgvZTbaY3mKFyLXQh+wpzWE3mUNQQkWnR+lgiW9Afkunej49Nz3sYlI8XFTRdkNhUR5d6h4oOpJc8OjcItMXVqoHW2fSW6ycWuiM8NDYoICouLAZ9BYrwwmhycvKlt4Q8hUlCV5nZ7vOm2ut2rizcFpuWpSrT3K1Z3xinbuHnXBTyGAljV/XzHZaNGu6y7vLDziMpIyUdOBBRXXlxznUQiOoheZsZk9njG8er1mNmz2eOCQ3x9BbLP+Zxt+VrbEz9aWxmimRvyl4/sumyoM/nw+LNzV4/uP0/9T/P5f08cvhl38USAS/xTYs2fL/VNFF0vd+SVsRB/khPwW4SCi5SHhx8fDgVPAiAqIJRQL/EuK5bsRzNnAiezD1i7u2VyHHAKRU+E2YUaA5DUsE2ZfbApAmsJcxjBwmQ2Xk4Y2BqZJ+oxSzsNEogz1O3/xkBMKEBHSiC8PoQStaoEIflPCHL/wQBCUKoUITlMhHP+olt5ojykUPOrEQTWgY+f449KMPKnSiB72jGrLQhEZO24925LtbkG5DndTkD2/4um+NQBEyUIJsRBxX24tcDGmbWtGJjq05jhzTaMgCcR+6EA4f+KAXDay5u6RsL7xJsm3w3mzvFvggB8nI/AYBdVnxNvx/2wA=') - format('woff2'); -} - -@keyframes fade-in { - from { - opacity: 0; - } -} - .intro { - display: grid; - grid-template-rows: 1fr min-content; - align-items: center; - background: rgba(255, 255, 255, 0.25); - text-align: center; - font-size: 2rem; + composes: abs-fill from '../util.css'; -webkit-overflow-scrolling: touch; overflow: auto; - padding: 20px 0 0; - height: 100%; - box-sizing: border-box; overscroll-behavior: contain; - position: relative; -} - -.logo-container { - position: relative; - padding-top: 100%; -} - -.logo-sizer { - width: 90%; - max-width: 52vh; - margin: 0 auto; -} - -.logo { - composes: abs-fill from '../util.css'; - pointer-events: none; -} - -.open-image-guide { - font: 300 11vw intro-text, sans-serif; - margin-bottom: 0; - - @media (min-width: 460px) { - font-size: 50.6px; - padding: 0 40px; - } -} - -.select-button { - composes: unbutton from '../util.css'; - font-weight: 500; - color: #5d509e; - - &:hover, - &:focus { - text-decoration: underline; - } } .hide { display: none; } -.demos { - display: block; - padding: 0; - border-top: 1px solid #e8e8e8; - margin: 0 auto; - - @media (min-width: 400px) { - display: grid; - grid-template-columns: 1fr 1fr; - } - - @media (min-width: 580px) { - border-top: none; - width: 523px; - } - - @media (min-width: 900px) { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr; - width: 773px; - } +.main { + min-height: 50vh; + display: grid; + grid-template-rows: max-content max-content; + justify-items: center; + align-content: center; } -.demo-item { - background: #fff; - display: flex; - border-bottom: 1px solid #e8e8e8; - - @media (min-width: 580px) { - border: 1px solid #e8e8e8; - border-radius: 4px; - margin: 3px; - } +.logo { + font-weight: bold; + font-size: 3rem; + margin: 4rem 0; } -.demo-button { +.load-img { + background: var(--pink); + color: var(--white); + font-style: italic; + font-size: 1.2rem; + --size: 23rem; + border-radius: var(--size); +} + +.load-img-content { + width: var(--size); + height: var(--size); + display: grid; + grid-template-rows: max-content max-content; + justify-items: center; + align-content: center; + gap: 0.7rem; +} + +.load-btn { composes: unbutton from '../util.css'; - flex: 1; - - &:hover, - &:focus { - background: #f5f5f5; - } } -.demo { - display: flex; - align-items: center; - padding: 7px; - font-size: 1.3rem; +.load-icon { + --size: 5rem; + width: var(--size); + height: var(--size); + fill: var(--white); + transform: translate(4.3%, -1%); } -.demo-img-container { - overflow: hidden; - display: block; - width: 47px; - background: #ccc; - border-radius: 3px; - flex: 0 0 auto; -} - -.demo-img-aspect { - position: relative; - padding-top: 100%; -} - -.demo-icon { - composes: abs-fill from '../util.css'; - pointer-events: none; -} - -.demo-description { - display: flex; - align-items: center; - justify-content: flex-start; - text-align: left; - flex: 1; - padding: 0 10px; -} - -.demo-loading { - composes: abs-fill from '../util.css'; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: center; - justify-content: center; - animation: fade-in 300ms ease-in-out; -} - -.demo-loading-spinner { - --color: #fff; -} - -.install-button { +.paste-btn { composes: unbutton from '../util.css'; - - &:hover, - &:focus { - background: #504488; - } - - background: #5d509e; - border: 1px solid #e8e8e8; - color: #fff; - padding: 14px; - font-size: 1.3rem; - - position: absolute; - top: 1rem; - right: 1rem; - - animation: fade-in 0.3s ease-in-out; -} - -@keyframes fade-in { - from { - opacity: 0; - } -} - -.related-links { - display: flex; - padding: 0; - justify-content: center; - font-size: 1.3rem; - - & li { - display: block; - border-left: 1px solid #000; - padding: 0 0.6em; - - &:first-child { - border-left: none; - } - } - - & a:link { - color: #5d509e; - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } + text-decoration: underline; + font: inherit; + color: inherit; } diff --git a/src/shared/initial-app/colors.css b/src/shared/initial-app/colors.css new file mode 100644 index 00000000..bb444290 --- /dev/null +++ b/src/shared/initial-app/colors.css @@ -0,0 +1,4 @@ +html { + --pink: #ff3385; + --white: #fff; +}