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 smallSectionAsset from 'url:./imgs/info-content/small.svg'; import simpleSectionAsset from 'url:./imgs/info-content/simple.svg'; import secureSectionAsset from 'url:./imgs/info-content/secure.svg'; import logoIcon from 'url:./imgs/demos/icon-demo-logo.png'; import logoWithText from 'data-url-text:./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'; import SlideOnScroll from './SlideOnScroll'; 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) => (
  • ))}

Small

Smaller images mean faster load times. Squoosh can reduce file size and maintain high quality.

silhouette of a large 1.4 megabyte image shrunk into a smaller 80 kilobyte image

Simple

Open your image, inspect the differences, then save instantly. Feeling adventurous? Adjust the settings for even smaller files.

grid of multiple shrunk images displaying various options

Secure

Worried about privacy? Images never leave your device since Squoosh does all the work locally.

silhouette of a cloud with a 'no' symbol on top
{beforeInstallEvent && ( )}
); } }