Conheça os novos e futuros recursos de navegador para seu PWA: From Fugu With Love

1. Antes de começar

Os Progressive Web Apps (PWAs) são um tipo de software de aplicativo oferecido pela Web, criado usando tecnologias comuns da Web, incluindo HTML, CSS e JavaScript. Eles são destinados a qualquer plataforma que use um navegador compatível com os padrões.

Neste codelab, você começará com um PWA de referência e, em seguida, conhecerá novos recursos do navegador que vão oferecer superpoderes ao PWA 🦸.

Muitas dessas novas funcionalidades do navegador ainda estão em desenvolvimento e ainda são padronizadas. Por isso, às vezes você precisará definir sinalizações do navegador para poder usá-las.

Prerequisites

Neste codelab, você já sabe usar JavaScript moderno, especificamente Promises e async/await. Como nem todas as etapas do codelab são compatíveis com todas as plataformas, ele ajuda a testar se você tem outros dispositivos em mãos, por exemplo, um smartphone Android ou um laptop usando um sistema operacional diferente do dispositivo em que você edita o código. Em vez de dispositivos reais, você pode tentar usar simuladores como o Android Simulator ou serviços on-line, como o BrowserStack, que permitem testar o dispositivo atual. Ou você pode pular qualquer etapa, que não dependem umas das outras.

O que você vai criar

Você criará um app da Web de cartões de visita e saberá como os novos e futuros recursos de navegador podem melhorar seu aplicativo para oferecer uma experiência avançada em alguns navegadores. No entanto, ele continua sendo útil em todos os navegadores mais modernos.

Você aprenderá a adicionar recursos de suporte, como acesso ao sistema de arquivos, acesso à área de transferência do sistema, recuperação de contatos, sincronização periódica em segundo plano, wake lock de tela, recursos de compartilhamento e muito mais.

Após concluir o codelab, você terá uma compreensão sólida de como aprimorar progressivamente seus apps da Web com novos recursos do navegador, sem sobrecarregar o subconjunto de usuários que estão em navegadores incompatíveis e, o mais importante, não os excluir do app.

Pré-requisitos

Estes são os navegadores totalmente compatíveis:

Recomendamos usar o Canal de Desenvolvedor específico.

2. Projeto fugu

Os Progressive Web Apps (PWAs) são criados e aprimorados com APIs modernas para fornecer recursos, confiabilidade e instalação aprimorados, além de alcançar qualquer pessoa na Web, em qualquer lugar do mundo, usando qualquer tipo de dispositivo.

Algumas APIs são muito eficientes e, se processadas incorretamente, podem dar errado. Assim como o peixe fugu 🐡: quando você corta corretamente, é uma delícia, mas quando você corta errado, pode ser letal. Mas não se preocupe, nada pode realmente quebrar neste codelab.

É por isso que o nome do código interno do projeto Web Capabilities (no qual as empresas envolvidas estão desenvolvendo essas novas APIs) é o Project Fugu.

Os recursos da Web (já existentes) permitem que grandes e pequenas empresas criem soluções puras baseadas no navegador, muitas vezes permitindo uma implantação mais rápida com custos de desenvolvimento mais baixos em comparação com o caminho específico da plataforma.

3. Introdução

Faça o download do navegador e defina a seguinte sinalização de tempo de execução 🚩 acessando about://flags, que funciona no Chrome e no Edge:

  • #enable-experimental-web-platform-features

Depois da ativação, reinicie o navegador.

Você usará a plataforma Glitch, que permite hospedar o PWA e porque tem um editor decente. O Glitch também é compatível com a importação e exportação para o GitHub, para que não haja dependência de um único fornecedor. Acesse fugu-paint.glitch.me para testar o aplicativo. Ele é um app de desenho básico Emoji que você vai melhorar durante o codelab.

PWA do Fum Greetings com uma tela grande com as palavras “Google” pintadas.

Depois de brincar com o aplicativo, remixe o app para criar uma cópia que você possa editar. O URL do seu remix será semelhante a glitch.com/edit/#!/bouncy-candytuft. Esse remix pode ser acessado em todo o mundo. Faça login na sua conta atual ou crie uma nova no Glitch para salvar o trabalho. Para ver o app, clique no botão "Mostrar" , e o URL do aplicativo hospedado será algo como bouncy-candytuft.glitch.me. Observe .me em vez de .com como o domínio de nível mais alto.

Agora está tudo pronto para editar e melhorar seu app. Sempre que fizer uma alteração, o aplicativo será atualizado e suas alterações ficarão visíveis diretamente.

Glitch IDE mostrando a edição de um documento HTML.

As tarefas a seguir devem ser concluídas na ordem certa, mas, como indicado acima, você pode pular uma etapa se não tiver acesso a um dispositivo compatível. Lembre-se de que cada tarefa é marcada com websearch, um peixe de água doce inofensivo, ou 🐡, um peixe & uma colher de cuidados, alertando você sobre a experiência ou não da característica.

Verifique o Console do DevTools para ver se uma API é compatível com o dispositivo atual. Além disso, usamos o Glitch para verificar o mesmo aplicativo em dispositivos diferentes com facilidade, como em seu telefone celular e computador.

Compatibilidade de API registrada no Console no DevTools.

4. 🇪Adicionar suporte à API Web Share

Criar os desenhos mais incríveis é entediante se não houver ninguém para aprová-los. Adicione um recurso que permita que os usuários compartilhem desenhos com o mundo na forma de cartões de visita.

A API Web Share é compatível com o compartilhamento de arquivos e, como você deve se lembrar, uma File é apenas um tipo específico de Blob. Portanto, no arquivo chamado share.mjs, importe o botão de compartilhamento e uma função de conveniência toBlob() que converte o conteúdo de uma tela para um blob e adicione a funcionalidade de compartilhamento de acordo com o código abaixo.

Se você tiver implementado esse botão, mas não o vir, isso significa que o navegador não implementa a API Web Share.

import { shareButton, toBlob } from './script.mjs';

const share = async (title, text, blob) => {
  const data = {
    files: [
      new File([blob], 'fugu-greeting.png', {
        type: blob.type,
      }),
    ],
    title: title,
    text: text,
  };
  try {
    if (!navigator.canShare(data)) {
      throw new Error("Can't share data.", data);
    }
    await navigator.share(data);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

shareButton.style.display = 'block';
shareButton.addEventListener('click', async () => {
  return share('Fugu Greetings', 'From Fugu With Love', await toBlob());
});

5. ∙ Adicionar compatibilidade com a API Web Share Target

Agora os usuários podem compartilhar cartões de visita criados com o app. Você também pode permitir que os usuários compartilhem imagens e transformá-los em cartões de visita. Para isso, você pode usar a API Web Share Target.

No manifesto do aplicativo da Web, você precisa informar ao app que tipos de arquivos podem ser aceitos e qual URL o navegador chama quando um ou vários arquivos são compartilhados. O trecho abaixo do arquivo manifest.webmanifest mostra essa informação.

{
  "share_target": {
    "action": "./share-target/",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "files": [
        {
          "name": "image",
          "accept": ["image/jpeg", "image/png", "image/webp", "image/gif"]
        }
      ]
    }
  }
}

Em seguida, o service worker lida com os arquivos recebidos. O URL ./share-target/ não existe. O app apenas realiza ações nele no gerenciador fetch e redireciona a solicitação para o URL raiz adicionando um parâmetro de consulta ?share-target:

self.addEventListener('fetch', (fetchEvent) => {
  /* 🐡 Start Web Share Target */
  if (
    fetchEvent.request.url.endsWith('/share-target/') &&
    fetchEvent.request.method === 'POST'
  ) {
    return fetchEvent.respondWith(
      (async () => {
        const formData = await fetchEvent.request.formData();
        const image = formData.get('image');
        const keys = await caches.keys();
        const mediaCache = await caches.open(
          keys.filter((key) => key.startsWith('media'))[0],
        );
        await mediaCache.put('shared-image', new Response(image));
        return Response.redirect('./?share-target', 303);
      })(),
    );
  }
  /* 🐡 End Web Share Target */

  /* ... */
});

Durante o carregamento do app, ele verifica se esse parâmetro de consulta está definido e, em caso afirmativo, extrai a imagem compartilhada para a tela e a exclui do cache. Tudo isso acontece em script.mjs:

const restoreImageFromShare = async () => {
  const mediaCache = await getMediaCache();
  const image = await mediaCache.match('shared-image');
  if (image) {
    const blob = await image.blob();
    await drawBlob(blob);
    await mediaCache.delete('shared-image');
  }
};

Essa função é usada quando o app é inicializado.

if (location.search.includes('share-target')) {
  restoreImageFromShare();
} else {
  drawDefaultImage();
}

6. websearch Adicione suporte para importação de imagem

Desenhar tudo do zero é difícil. Adicione um recurso para que os usuários façam upload de uma imagem local do dispositivo no app.

Primeiro, leia sobre a função Canvas' drawImage(). Em seguida, conheça o elemento <​input
type=file>
.

Com esses conhecimentos, você pode editar o arquivo import_image_legacy.mjs e adicionar o snippet a seguir. Na parte superior do arquivo, você importa o botão de importação e uma função de conveniência drawBlob() que permite desenhar um blob na tela.

import { importButton, drawBlob } from './script.mjs';

const importImage = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/png, image/jpeg, image/*';
    input.addEventListener('change', () => {
      const file = input.files[0];
      input.remove();
      return resolve(file);
    });
    input.click();
  });
};

importButton.style.display = 'block';
importButton.addEventListener('click', async () => {
  const file = await importImage();
  if (file) {
    await drawBlob(file);
  }
});

7. websearch e suporte à exportação de imagens

Como o usuário salva no dispositivo um arquivo criado no app? Tradicionalmente, isso é possível com um elemento <​a
download>
.

No arquivo export_image_legacy.mjs, adicione o conteúdo da seguinte maneira: Importe o botão de exportação e uma função de conveniência toBlob() que converte o conteúdo da tela para um blob.

import { exportButton, toBlob } from './script.mjs';

export const exportImage = async (blob) => {
  const a = document.createElement('a');
  a.download = 'fugu-greeting.png';
  a.href = URL.createObjectURL(blob);
  a.addEventListener('click', (e) => {
    a.remove();
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
  });
  setTimeout(() => a.click(), 0);
};

exportButton.style.display = 'block';
exportButton.addEventListener('click', async () => {
  exportImage(await toBlob());
});

8. websearch Adicionar suporte à API File System Access

Compartilhar é cuidar, mas seus usuários provavelmente vão querer salvar o melhor trabalho possível em seus próprios dispositivos. Adicione um recurso que permita aos usuários salvar (e reabrir) desenhos.

Antes, você usava uma abordagem legada do <​input type=file> para importar arquivos e uma abordagem legada do <​a download> para exportar arquivos. Agora, você usará a API File System Access para melhorar a experiência.

Essa API permite abrir e salvar arquivos no sistema de arquivos do sistema operacional. Edite os dois arquivos, import_image.mjs e export_image.mjs, respectivamente, adicionando o conteúdo abaixo. Para que esses arquivos sejam carregados, remova os emojis 🐡 de script.mjs.

Substitua esta linha:

// Remove all the emojis for this feature test to succeed.
if ('show🐡Open🐡File🐡Picker' in window) {
  /* ... */
}

...com esta linha:

if ('showOpenFilePicker' in window) {
  /* ... */
}

Em import_image.mjs:

import { importButton, drawBlob } from './script.mjs';

const importImage = async () => {
  try {
    const [handle] = await window.showOpenFilePicker({
      types: [
        {
          description: 'Image files',
          accept: {
            'image/*': ['.png', '.jpg', '.jpeg', '.avif', '.webp', '.svg'],
          },
        },
      ],
    });
    return await handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

importButton.style.display = 'block';
importButton.addEventListener('click', async () => {
  const file = await importImage();
  if (file) {
    await drawBlob(file);
  }
});

Em export_image.mjs:

import { exportButton, toBlob } from './script.mjs';

const exportImage = async () => {
  try {
    const handle = await window.showSaveFilePicker({
      suggestedName: 'fugu-greetings.png',
      types: [
        {
          description: 'Image file',
          accept: {
            'image/png': ['.png'],
          },
        },
      ],
    });
    const blob = await toBlob();
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

exportButton.style.display = 'block';
exportButton.addEventListener('click', async () => {
  await exportImage();
});

9. 🇪Adicionar suporte à API Contacts Picker

Talvez os usuários queiram adicionar uma mensagem ao cartão comemorativo e falar com alguém pessoalmente. Adicione um recurso que permita que os usuários selecionem um ou vários contatos locais e adicionem os nomes deles à mensagem de compartilhamento.

Em um dispositivo Android ou iOS, a API Contact Picker permite escolher os contatos do app gerenciador de contatos do dispositivo e retorná-los ao app. Edite o arquivo contacts.mjs e adicione o código abaixo.

import { contactsButton, ctx, canvas } from './script.mjs';

const getContacts = async () => {
  const properties = ['name'];
  const options = { multiple: true };
  try {
    return await navigator.contacts.select(properties, options);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

contactsButton.style.display = 'block';
contactsButton.addEventListener('click', async () => {
  const contacts = await getContacts();
  if (contacts) {
    ctx.font = '1em Comic Sans MS';
    contacts.forEach((contact, index) => {
      ctx.fillText(contact.name.join(), 20, 16 * ++index, canvas.width);
    });
  }
});

10. 🇪Adicionar suporte a API Async Clipboard

Talvez os usuários queiram colar uma imagem de outro app no app ou copiar um desenho do app para outro. Adicione um recurso que permita aos usuários copiar e colar imagens dentro e fora do app. A API Async Clipboard é compatível com imagens PNG. Agora você pode ler e gravar dados de imagem na área de transferência.

Localize o arquivo clipboard.mjs e adicione o seguinte:

import { copyButton, pasteButton, toBlob, drawImage } from './script.mjs';

const copy = async (blob) => {
  try {
    await navigator.clipboard.write([
      /* global ClipboardItem */
      new ClipboardItem({
        [blob.type]: blob,
      }),
    ]);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

const paste = async () => {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      try {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          return blob;
        }
      } catch (err) {
        console.error(err.name, err.message);
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
};

copyButton.style.display = 'block';
copyButton.addEventListener('click', async () => {
  await copy(await toBlob());
});

pasteButton.style.display = 'block';
pasteButton.addEventListener('click', async () => {
  const image = new Image();
  image.addEventListener('load', () => {
    drawImage(image);
  });
  image.src = URL.createObjectURL(await paste());
});

11. 🇪Adicionar suporte à API Badging

Quando os usuários instalarem seu app, um ícone aparecerá na tela inicial. Você pode usar esse ícone para transmitir informações divertidas, como o número de pinceladas de um determinado desenho.

Adicione um recurso que faça a contagem do selo sempre que o usuário fizer uma nova pincelada. O Badging API permite que um selo numérico seja definido no ícone do aplicativo. Você pode atualizar o selo sempre que um evento de pointerdown ocorrer, ou seja, quando ocorrer um pincel, e redefini-lo quando a tela for limpa.

Coloque o código abaixo no arquivo badge.mjs:

import { canvas, clearButton } from './script.mjs';

let strokes = 0;

canvas.addEventListener('pointerdown', () => {
  navigator.setAppBadge(++strokes);
});

clearButton.addEventListener('click', () => {
  strokes = 0;
  navigator.setAppBadge(strokes);
});

12. websearch Adicionar suporte à API de wake lock de tela

Às vezes, seus usuários talvez precisem de um momento para olhar para um desenho, por tempo suficiente para que a inspiração apareça. Adicione um recurso para manter a tela ligada e impedir que o protetor de tela inicie. A API Screen Wake Lock impede que a tela do usuário adormeça. O wake lock é liberado automaticamente quando ocorre um evento de mudança de visibilidade, conforme definido pela Visibilidade de página. Portanto, o wake lock precisa ser adquirido novamente quando a página for visualizada novamente.

Localize o arquivo wake_lock.mjs e adicione o conteúdo abaixo. Para testar se isso funciona, configure o protetor de tela para ser exibido após um minuto.

import { wakeLockInput, wakeLockLabel } from './script.mjs';

let wakeLock = null;

const requestWakeLock = async () => {
  try {
    wakeLock = await navigator.wakeLock.request('screen');
    wakeLock.addEventListener('release', () => {
      console.log('Wake Lock was released');
    });
    console.log('Wake Lock is active');
  } catch (err) {
    console.error(err.name, err.message);
  }
};

const handleVisibilityChange = () => {
  if (wakeLock !== null && document.visibilityState === 'visible') {
    requestWakeLock();
  }
};

document.addEventListener('visibilitychange', handleVisibilityChange);

wakeLockInput.style.display = 'block';
wakeLockLabel.style.display = 'block';
wakeLockInput.addEventListener('change', async () => {
  if (wakeLockInput.checked) {
    await requestWakeLock();
  } else {
    wakeLock.release();
  }
});

13. 🇪Adicionar suporte à API Periodic Background Sync

Começar com uma tela em branco pode ser chato. Você pode usar a API periodicamente Background Sync para inicializar a tela do usuário com uma nova imagem a cada dia, por exemplo, a foto diária de fugu do Unsplash.

Isso requer dois arquivos: um arquivo periodic_background_sync.mjs, que registra a sincronização em segundo plano periódica, e outro, image_of_the_day.mjs, que faz o download da imagem do dia.

Em periodic_background_sync.mjs:

import { periodicBackgroundSyncButton, drawBlob } from './script.mjs';

const getPermission = async () => {
  const status = await navigator.permissions.query({
    name: 'periodic-background-sync',
  });
  return status.state === 'granted';
};

const registerPeriodicBackgroundSync = async () => {
  const registration = await navigator.serviceWorker.ready;
  try {
    registration.periodicSync.register('image-of-the-day-sync', {
      // An interval of one day.
      minInterval: 24 * 60 * 60 * 1000,
    });
  } catch (err) {
    console.error(err.name, err.message);
  }
};

navigator.serviceWorker.addEventListener('message', async (event) => {
  const fakeURL = event.data.image;
  const mediaCache = await getMediaCache();
  const response = await mediaCache.match(fakeURL);
  drawBlob(await response.blob());
});

const getMediaCache = async () => {
  const keys = await caches.keys();
  return await caches.open(keys.filter((key) => key.startsWith('media'))[0]);
};

periodicBackgroundSyncButton.style.display = 'block';
periodicBackgroundSyncButton.addEventListener('click', async () => {
  if (await getPermission()) {
    await registerPeriodicBackgroundSync();
  }
  const mediaCache = await getMediaCache();
  let blob = await mediaCache.match('./assets/background.jpg');
  if (!blob) {
    blob = await mediaCache.match('./assets/fugu_greeting_card.jpg');
  }
  drawBlob(await blob.blob());
});

Em image_of_the_day.mjs:

const getImageOfTheDay = async () => {
  try {
    const fishes = ['blowfish', 'pufferfish', 'fugu'];
    const fish = fishes[Math.floor(fishes.length * Math.random())];
    const response = await fetch(`https://source.unsplash.com/daily?${fish}`);
    if (!response.ok) {
      throw new Error('Response was', response.status, response.statusText);
    }
    return await response.blob();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

const getMediaCache = async () => {
  const keys = await caches.keys();
  return await caches.open(keys.filter((key) => key.startsWith('media'))[0]);
};

self.addEventListener('periodicsync', (syncEvent) => {
  if (syncEvent.tag === 'image-of-the-day-sync') {
    syncEvent.waitUntil(
      (async () => {
        try {
          const blob = await getImageOfTheDay();
          const mediaCache = await getMediaCache();
          const fakeURL = './assets/background.jpg';
          await mediaCache.put(fakeURL, new Response(blob));
          const clients = await self.clients.matchAll();
          clients.forEach((client) => {
            client.postMessage({
              image: fakeURL,
            });
          });
        } catch (err) {
          console.error(err.name, err.message);
        }
      })(),
    );
  }
});

14. 🇪Adicionar suporte à API Shape Detection

Algumas vezes, os desenhos dos seus usuários ou as imagens de plano de fundo usadas podem conter informações úteis, como códigos de barras. A API Detection API e, especificamente, a API Barcode Detection, permitem extrair essas informações. Adicione um recurso que tenta detectar códigos de barras dos seus desenhos. Localize o arquivo barcode.mjs e adicione o conteúdo abaixo. Para testar esse recurso, basta carregar ou colar uma imagem com um código de barras na tela. Você pode copiar um exemplo de código de barras de uma pesquisa de imagens para códigos QR.

/* global BarcodeDetector */
import {
  scanButton,
  clearButton,
  canvas,
  ctx,
  CANVAS_BACKGROUND,
  CANVAS_COLOR,
  floor,
} from './script.mjs';

const barcodeDetector = new BarcodeDetector();

const detectBarcodes = async (canvas) => {
  return await barcodeDetector.detect(canvas);
};

scanButton.style.display = 'block';
let seenBarcodes = [];
clearButton.addEventListener('click', () => {
  seenBarcodes = [];
});
scanButton.addEventListener('click', async () => {
  const barcodes = await detectBarcodes(canvas);
  if (barcodes.length) {
    barcodes.forEach((barcode) => {
      const rawValue = barcode.rawValue;
      if (seenBarcodes.includes(rawValue)) {
        return;
      }
      seenBarcodes.push(rawValue);
      ctx.font = '1em Comic Sans MS';
      ctx.textAlign = 'center';
      ctx.fillStyle = CANVAS_BACKGROUND;
      const boundingBox = barcode.boundingBox;
      const left = boundingBox.left;
      const top = boundingBox.top;
      const height = boundingBox.height;
      const oneThirdHeight = floor(height / 3);
      const width = boundingBox.width;
      ctx.fillRect(left, top + oneThirdHeight, width, oneThirdHeight);
      ctx.fillStyle = CANVAS_COLOR;
      ctx.fillText(
        rawValue,
        left + floor(width / 2),
        top + floor(height / 2),
        width,
      );
    });
  }
});

15. 🐡 Adicionar suporte à API Idle Detection

Imagine que o aplicativo esteja sendo executado em uma configuração semelhante a um quiosque. Um recurso útil é redefinir a tela depois de um período de inatividade. A API ID Detection permite detectar quando um usuário não interage mais com o dispositivo.

Localize o arquivo idle_detection.mjs e cole o conteúdo abaixo.

import { ephemeralInput, ephemeralLabel, clearCanvas } from './script.mjs';

let controller;

ephemeralInput.style.display = 'block';
ephemeralLabel.style.display = 'block';

ephemeralInput.addEventListener('change', async () => {
  if (ephemeralInput.checked) {
    const state = await IdleDetector.requestPermission();
    if (state !== 'granted') {
      ephemeralInput.checked = false;
      return alert('Idle detection permission must be granted!');
    }
    try {
      controller = new AbortController();
      const idleDetector = new IdleDetector();
      idleDetector.addEventListener('change', (e) => {
        const { userState, screenState } = e.target;
        console.log(`idle change: ${userState}, ${screenState}`);
        if (userState === 'idle') {
          clearCanvas();
        }
      });
      idleDetector.start({
        threshold: 60000,
        signal: controller.signal,
      });
    } catch (err) {
      console.error(err.name, err.message);
    }
  } else {
    console.log('Idle detection stopped.');
    controller.abort();
  }
});

16. 🐡 Adicionar suporte à API File Handling

E se os usuários pudessem apenas clicar em um arquivo de imagem e o app fosse exibido? A API File Handling permite fazer isso.

Você precisará registrar o PWA como um gerenciador de arquivos para imagens. Isso acontece no manifesto do aplicativo da Web e mostra o trecho abaixo do arquivo manifest.webmanifest. Isso já faz parte do manifesto, você não precisa adicioná-lo por conta própria.

{
  "file_handlers": [
    {
      "action": "./",
      "accept": {
        "image/*": [".jpg", ".jpeg", ".png", ".webp", ".svg"]
      }
    }
  ]
}

Para processar arquivos abertos, adicione o código abaixo ao arquivo file-handling.mjs:

import { drawBlob } from './script.mjs';

const handleLaunchFiles = () => {
  window.launchQueue.setConsumer((launchParams) => {
    if (!launchParams.files.length) {
      return;
    }
    launchParams.files.forEach(async (handle) => {
      const file = await handle.getFile();
      drawBlob(file);
    });
  });
};

handleLaunchFiles();

17. Parabéns

🎉 Uau, você conseguiu!

Existem tantas APIs de navegador incríveis sendo desenvolvidas no contexto do Project Fugu 🐡. Este codelab mal pode ser usado.

Para saber mais ou saber mais, siga nossas publicações no nosso site web.dev (link em inglês).

Página de destino da seção &ldquo;Capabilities&rdquo; do site web.dev.

Mas não para por aqui. Para atualizações ainda não divulgadas, você pode acessar nosso rastreador da API Fugu com links para todas as propostas enviadas, em teste de origem ou de desenvolvimento, todas as propostas em que o trabalho começou e tudo que está sendo considerado, mas ainda não foi iniciada.

Site do rastreador de API Fugu

Este codelab foi escrito por Thomas Steiner (@tomayac). Fico feliz em responder às suas perguntas e estou ansioso para ler seu feedback. Agradecimentos especiais a Hemanth H.M (@GNUmanth), Christian Liebel (@christianliebel), Sven May (@Svenmay), Lars Knudsen (@larsgk) e Jackie Han (@hanguokai) que ajudaram a moldar este codelab.