import { h, Component } from 'preact'; import { linkRef } from 'shared/prerendered-app/util'; import '../../custom-els/loading-spinner'; import logo from 'url:./imgs/logo.svg'; import githubLogo from 'url:./imgs/github-logo.svg'; import largePhoto from 'url:./imgs/demos/demo-large-photo.jpg'; import artwork from 'url:./imgs/demos/demo-artwork.jpg'; import deviceScreen from 'url:./imgs/demos/demo-device-screen.png'; import largePhotoIcon from 'url:./imgs/demos/icon-demo-large-photo.jpg'; import artworkIcon from 'url:./imgs/demos/icon-demo-artwork.jpg'; import deviceScreenIcon from 'url:./imgs/demos/icon-demo-device-screen.jpg'; import logoIcon from 'url:./imgs/demos/icon-demo-logo.png'; import logoWithText from 'url:./imgs/logo-with-text.svg'; import * as style from './style.css'; import type SnackBarElement from 'shared/custom-els/snack-bar'; import 'shared/custom-els/snack-bar'; import { startBlobs } from './blob-anim/meta'; const demos = [ { description: 'Large photo', size: '2.8mb', filename: 'photo.jpg', url: largePhoto, iconUrl: largePhotoIcon, }, { description: 'Artwork', size: '2.9mb', filename: 'art.jpg', url: artwork, iconUrl: artworkIcon, }, { description: 'Device screen', size: '1.6mb', filename: 'pixel3.png', url: deviceScreen, iconUrl: deviceScreenIcon, }, { description: 'SVG icon', size: '13k', filename: 'squoosh.svg', url: logo, iconUrl: logoIcon, }, ] as const; const blobAnimImport = !__PRERENDER__ && matchMedia('(prefers-reduced-motion: reduce)').matches ? undefined : import('./blob-anim'); 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; showSnack?: SnackBarElement['showSnackbar']; } interface State { fetchingDemoIndex?: number; beforeInstallEvent?: BeforeInstallPromptEvent; showBlobSVG: boolean; } export default class Intro extends Component { state: State = { showBlobSVG: true, }; private fileInput?: HTMLInputElement; private blobCanvas?: HTMLCanvasElement; private installingViaButton = false; componentDidMount() { // Listen for beforeinstallprompt events, indicating Squoosh is installable. window.addEventListener( 'beforeinstallprompt', this.onBeforeInstallPromptEvent, ); // Listen for the appinstalled event, indicating Squoosh has been installed. window.addEventListener('appinstalled', this.onAppInstalled); if (blobAnimImport) { blobAnimImport.then((module) => { this.setState( { showBlobSVG: false, }, () => module.startBlobAnim(this.blobCanvas!), ); }); } } 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.fileInput!.value = ''; this.props.onFile!(file); }; private onOpenClick = () => { this.fileInput!.click(); }; private onDemoClick = async (index: number, event: Event) => { try { this.setState({ fetchingDemoIndex: index }); const demo = demos[index]; const blob = await fetch(demo.url).then((r) => r.blob()); const file = new File([blob], demo.filename, { type: blob.type }); this.props.onFile!(file); } catch (err) { this.setState({ fetchingDemoIndex: undefined }); this.props.showSnack!("Couldn't fetch demo image"); } }; private onBeforeInstallPromptEvent = (event: BeforeInstallPromptEvent) => { // Don't show the mini-infobar on mobile event.preventDefault(); // Save the beforeinstallprompt event so it can be called later. this.setState({ beforeInstallEvent: event }); // Log the event. const gaEventInfo = { eventCategory: 'pwa-install', eventAction: 'promo-shown', nonInteraction: true, }; ga('send', 'event', gaEventInfo); }; private onInstallClick = async (event: Event) => { // Get the deferred beforeinstallprompt event const beforeInstallEvent = this.state.beforeInstallEvent; // If there's no deferred prompt, bail. if (!beforeInstallEvent) return; this.installingViaButton = true; // Show the browser install prompt beforeInstallEvent.prompt(); // Wait for the user to accept or dismiss the install prompt const { outcome } = await beforeInstallEvent.userChoice; // Send the analytics data const gaEventInfo = { eventCategory: 'pwa-install', eventAction: 'promo-clicked', eventLabel: installButtonSource, eventValue: outcome === 'accepted' ? 1 : 0, }; ga('send', 'event', gaEventInfo); // If the prompt was dismissed, we aren't going to install via the button. if (outcome === 'dismissed') { this.installingViaButton = false; } }; private onAppInstalled = () => { // We don't need the install button, if it's shown this.setState({ beforeInstallEvent: undefined }); // Don't log analytics if page is not visible if (document.hidden) return; // Try to get the install, if it's not set, use 'browser' const source = this.installingViaButton ? installButtonSource : 'browser'; ga('send', 'event', 'pwa-install', 'installed', source); // Clear the install method property this.installingViaButton = false; }; private onPasteClick = async () => { let clipboardItems: ClipboardItem[]; try { clipboardItems = await navigator.clipboard.read(); } catch (err) { this.props.showSnack!(`No permission to access clipboard`); return; } const blob = await getImageClipboardItem(clipboardItems); if (!blob) { this.props.showSnack!(`No image found in the clipboard`); return; } this.props.onFile!(new File([blob], 'image.unknown')); }; render( {}: Props, { fetchingDemoIndex, beforeInstallEvent, showBlobSVG }: State, ) { return (
{!__PRERENDER__ && ( )}

Squoosh

{showBlobSVG && ( {startBlobs.map((points) => ( { const nextI = i === points.length - 1 ? 0 : i + 1; let d = ''; if (i === 0) { d += `M${point[2]} ${point[3]}`; } return ( d + `C${point[4]} ${point[5]} ${points[nextI][0]} ${points[nextI][1]} ${points[nextI][2]} ${points[nextI][3]}` ); }) .join('')} /> ))} )}
Drop OR{' '} {supportsClipboardAPI ? ( ) : ( 'Paste' )}

Or try one of these:

    {demos.map((demo, i) => (
  • ))}
{beforeInstallEvent && ( )}
); } }