De Fugu con amor: Explora las nuevas capacidades del navegador para tu AWP

1. Antes de comenzar

Las aplicaciones web progresivas (AWP) son un tipo de software de aplicación que se entrega a través de la Web y se desarrolló con tecnologías web comunes, como HTML, CSS y JavaScript. Están diseñados para funcionar en cualquier plataforma que use un navegador que cumpla con los estándares.

En este codelab, comenzarás con una AWP de referencia y, luego, explorarás nuevas funciones del navegador que, con el tiempo, te permitirán potenciar a tu AWP 🦸.

Muchas de estas funciones nuevas del navegador están en tránsito y todavía se estandarizan, por lo que a veces necesitarás establecer marcas de navegador para utilizarlas.

Prerequisites

Para este codelab, debes estar familiarizado con el JavaScript moderno, específicamente, con promesas y async/await. Dado que no todos los pasos del codelab son compatibles con todas las plataformas, es útil probar si tienes dispositivos adicionales a mano, por ejemplo, un teléfono Android o una laptop que use un sistema operativo diferente del dispositivo en el que editas el código. Como alternativa a los dispositivos reales, puedes intentar usar simuladores como el de Android o servicios en línea como BrowserStack que te permiten probar desde tu dispositivo actual. De lo contrario, también puedes omitir cualquier paso, ya que no dependen unos de otros.

Qué crearás

Compilarás una app web de tarjetas de saludo y descubrirás cómo las capacidades nuevas y futuras de los navegadores pueden mejorar tu app y ofrecer una experiencia avanzada en ciertos navegadores (pero sigue siendo útil en todos los navegadores modernos).

Aprenderás a agregar capacidades de compatibilidad, como acceso al sistema de archivos, acceso al portapapeles del sistema, recuperación de contactos, sincronización periódica en segundo plano, bloqueo de activación de pantalla, funciones de uso compartido y mucho más.

Después de completar el codelab, tendrás una comprensión sólida de cómo mejorar tus apps web de forma progresiva con nuevas funciones del navegador, todo sin sobrecargar la descarga del subconjunto de usuarios que, en primer lugar, se encuentra en navegadores incompatibles y, lo que es más importante, sin excluirlos de tu app en primer lugar.

Requisitos

Los navegadores totalmente compatibles en este momento son los siguientes:

Se recomienda usar el canal para desarrolladores en particular.

2. Proyecto Fugu

Las aplicaciones web progresivas (AWP) se crean y mejoran con API modernas para ofrecer capacidades mejoradas, confiabilidad y facilidad de instalación, a la vez que permiten que cualquier persona en la Web, en cualquier lugar del mundo, use cualquier tipo de dispositivo.

Algunas de estas API son muy potentes y, si se manejan de forma incorrecta, las cosas pueden salir mal. Al igual que el pez fugu 🐡: Cuando lo cortas, es una exquisitez, pero cuando lo cortas, puede ser letal (pero no te preocupes, nada puede romperse en este codelab).

Por eso, el nombre interno del proyecto Capacidades web (en el que las empresas involucradas están desarrollando estas nuevas API) es Proyecto Fugu.

Las funciones web, que ya están disponibles, les permiten a las grandes y pequeñas empresas crear soluciones basadas exclusivamente en el navegador, las cuales a menudo permiten una implementación más rápida con costos de desarrollo más bajos en comparación con una ruta específica de la plataforma.

3. Comienza ahora

Descarga uno de los navegadores y, luego, configura la siguiente marca en tiempo de ejecución 🚩 dirigiéndote a about://flags, que funciona tanto en Chrome como en Edge:

  • #enable-experimental-web-platform-features

Una vez habilitado, reinicia el navegador.

Usarás la plataforma Glitch, ya que te permite alojar la AWP y, además, tiene un editor aceptable. Glitch también admite la importación y exportación a GitHub, por lo que no hay que depender de proveedores. Ve a fugu-paint.glitch.me para probar la aplicación. Es una app de dibujo básica 🎨 que mejorarás durante el codelab.

AWP de Fugu Greetings con un gran lienzo y la palabra "Google" en blanco.

Después de jugar con la aplicación, remezcla la app para crear tu propia copia que puedas editar. La URL de tu remix tendrá un aspecto similar a glitch.com/editredirect/bouncy-candytuft ("bouncy-candytuft" será algo diferente para ti). Este remix es directamente accesible en todo el mundo. Accede a tu cuenta existente o crea una nueva en Glitch para guardar tu trabajo. Para ver tu app, haz clic en el botón "Show Show"; la URL de la app alojada será similar a bouncy-candytuft.glitch.me (ten en cuenta que .me en lugar de .com como dominio de nivel superior).

Ahora estás listo para editar tu app y mejorarla. Cada vez que realices cambios, se volverá a cargar la app y podrás ver los cambios directamente.

IDE de Glitch que muestra la edición de un documento HTML.

Idealmente, las siguientes tareas se deben completar en orden, pero como se mencionó anteriormente, puedes omitir un paso si no tienes acceso a un dispositivo compatible. Recuerda que cada tarea está marcada con Memcache, un pez de agua dulce inofensiva o 🐡, un pez fugu que se administra con cuidado y que te indica qué tan experimental es una función.

Consulta la consola en las Herramientas para desarrolladores para ver si se admite una API en el dispositivo actual. También usamos Glitch, para que puedas verificar fácilmente la misma app en diferentes dispositivos, por ejemplo, en tu teléfono celular y computadora de escritorio.

Compatibilidad de la API registrada en Console con las Herramientas para desarrolladores.

4. Memcache Agrega compatibilidad con la API de Web Share

Crear los dibujos más increíbles es aburrido si no hay nadie que los agradezca. Agrega una función que permita a tus usuarios compartir sus dibujos con todo el mundo en forma de tarjetas festivas.

La API de Web Share admite el uso compartido de archivos, y es posible que recuerde que un File es solo un tipo específico de Blob. Por lo tanto, en el archivo llamado share.mjs, importa el botón para compartir y una función conveniente toBlob() que convierte el contenido de un lienzo en un BLOB y agrega la funcionalidad de compartir según el siguiente código.

Si implementaste esta opción, pero no ves el botón, significa que el navegador no implementa la API de 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. Memcache Agrega compatibilidad con la API de Target Share

Ahora tus usuarios pueden compartir tarjetas de saludo creadas con la app, pero también puedes permitir que los usuarios compartan imágenes y las conviertan en tarjetas de saludo. Para ello, puedes usar la API de Web Share Target.

En el manifiesto de la aplicación web, debes indicar a la aplicación qué tipo de archivos puedes aceptar y a qué URL debe llamar el navegador cuando se comparten uno o varios archivos. El extracto que aparece a continuación del archivo manifest.webmanifest lo muestra.

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

Luego, el service worker se encarga de los archivos recibidos. La URL ./share-target/ no existe; la app solo actúa sobre ella en el controlador fetch y redirecciona la solicitud a la URL raíz agregando un parámetro de búsqueda ?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 */

  /* ... */
});

Cuando se carga la app, verifica si este parámetro de búsqueda está configurado y, si es así, dibuja la imagen compartida en el recuadro y la borra de la caché. Todo esto sucede en 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');
  }
};

Luego, se usa la función cuando se inicializa la app.

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

6. Memcache Agrega compatibilidad con importación de imágenes

Dibujar todo desde cero es difícil. Agrega una función que les permita a los usuarios subir una imagen local desde su dispositivo a la app.

Primero, lee sobre el lienzo drawImage(). Luego, familiarízate con el elemento <​input
type=file>
.

Con estos conocimientos, puedes editar el archivo llamado import_image_legacy.mjs y agregar el siguiente fragmento. En la parte superior del archivo, importarás el botón de importación y una función de conveniencia drawBlob() que te permite dibujar un BLOB en el lienzo.

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. Memcache Agrega compatibilidad con la exportación de imágenes

¿Cómo guardará el usuario un archivo creado en la app en su dispositivo? Tradicionalmente, esto se logra con un elemento <​a
download>
.

En el archivo export_image_legacy.mjs, agrega el contenido como se muestra a continuación. Importa el botón de exportación y una función de conveniencia toBlob() que convierta el contenido del lienzo en un 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. Memcache Agrega compatibilidad con la API de Acceso al sistema de archivos

Compartir es beneficioso, pero es probable que los usuarios quieran guardar su mejor trabajo en sus propios dispositivos. Agrega una función que les permita a los usuarios guardar (y volver a abrir) sus dibujos.

Anteriormente, usaste el enfoque heredado <​input type=file> para importar archivos y el método heredado <​a download> para exportarlos. Ahora, usarás la API de Acceso al sistema de archivos para mejorar la experiencia.

Esta API permite abrir y guardar archivos desde el sistema operativo. Para editar los dos archivos, import_image.mjs y export_image.mjs, respectivamente, agrega el contenido a continuación. Para que se carguen estos archivos, quita los emojis 🐡 de script.mjs.

Reemplaza esta línea:

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

Con esta línea:

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

En 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);
  }
});

En 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. Memcache Agrega la compatibilidad con la API del selector de contactos

Es posible que los usuarios quieran agregar un mensaje a su tarjeta festiva y dirigirse a alguien personalmente. Agrega una función que permita a tus usuarios elegir uno (o varios) de sus contactos locales y agregar sus nombres al mensaje para compartir.

En un dispositivo Android o iOS, la API del selector de contactos te permite seleccionar contactos de la aplicación del administrador de contactos del dispositivo y devolverlos a la aplicación. Edita el archivo contacts.mjs y agrega el siguiente código.

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. Memcache Agrega compatibilidad con la API de Async Clipboard

Es posible que los usuarios quieran pegar una imagen de otra app o copiar un dibujo de tu app a otra. Agrega una función que permita a tus usuarios copiar y pegar imágenes en tu app. La API de portapapeles asíncrono admite imágenes PNG para que puedas leer y escribir datos en el portapapeles.

Busca el archivo clipboard.mjs y agrega lo siguiente:

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. Memcache Agrega compatibilidad con la API de insignias

Cuando los usuarios instalen tu app, aparecerá un ícono en su pantalla principal. Puedes usar este ícono para transmitir información divertida, como la cantidad de pinceladas que tomó un dibujo determinado.

Agrega una función que cuente la insignia cada vez que tu usuario realice una nueva pincelada. La API de insignias permite configurar una insignia numérica en el ícono de la app. Puedes actualizar la insignia cada vez que se produzca un evento de pointerdown (es decir, cuando se produce una pincelada) y restablecer la insignia cuando se borra el recuadro.

Ingresa el siguiente código en el archivo 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. Memcache Agrega compatibilidad con la API de bloqueo de activación de pantalla

A veces, es posible que los usuarios necesiten unos minutos para mirar un dibujo, durante el tiempo suficiente para que se inspiren. Agrega una función que mantenga activa la pantalla y evite que el protector de pantalla se active. La API de bloqueo de activación de pantalla impide que la pantalla del usuario se suspenda. El bloqueo de activación se libera automáticamente cuando se produce un evento de cambio de visibilidad, como se define en Visibilidad de la página. Por lo tanto, se debe volver a activar el bloqueo de activación cuando la página vuelva a estar a la vista.

Busca el archivo wake_lock.mjs y agrega el contenido a continuación. Para probar si funciona, configura tu protector de pantalla a fin de que se muestre después de un 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. Memcache Agrega compatibilidad con la API de Periodic Background Sync

Comenzar con un lienzo en blanco puede ser aburrido. Puedes usar la API de Periodic Background Sync para inicializar tus usuarios y una página nueva con cada imagen nueva, por ejemplo, la fotografía fugu diaria de Unsplash.

Esto requiere dos archivos: un archivo periodic_background_sync.mjs que registra la sincronización en segundo plano periódica y otro archivo image_of_the_day.mjs que se encarga de descargar la imagen del día.

En 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());
});

En 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. Memcache Agrega compatibilidad con la API de Shape Detection

En ocasiones, los dibujos o las imágenes de fondo que usan los usuarios pueden contener información útil, como códigos de barras. La API de detección de formas y, en particular, la API de detección de códigos de barras te permite extraer esta información. Agrega una función que intente detectar los códigos de barras de los dibujos de tus usuarios. Ubica el archivo barcode.mjs y agrega el contenido a continuación. Para probar esta función, solo carga o pega una imagen con un código de barras en el lienzo. Puedes copiar un código de barras de ejemplo de una búsqueda de imágenes 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. 🐡 Agrega compatibilidad con la API de Idle Detection

Si te imaginas que la app se estaba ejecutando en un entorno similar al de un kiosco, una función útil sería restablecer el lienzo después de una cierta inactividad. La API de detección de inactividad te permite detectar cuando un usuario ya no interactúa con su dispositivo.

Busca el archivo idle_detection.mjs y pega el contenido que aparece a continuación.

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. 🐡 Agrega compatibilidad con la API de File Handling

¿Qué pasaría si sus usuarios pudieran hacer doble clic en un archivo de imagen y apareciera su aplicación? La API de File Handling le permite hacer justamente eso.

Deberás registrar la AWP como un controlador de archivos para las imágenes. Esto sucede en el manifiesto de la aplicación web; en el extracto que aparece a continuación, en el archivo manifest.webmanifest se muestra lo siguiente. (Ya forma parte del manifiesto, por lo que no es necesario que lo agregues).

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

Para controlar los archivos abiertos, agrega el siguiente código al archivo 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. Felicitaciones

🎉 ¡Felicitaciones!

Hay tantas API de navegador emocionantes en desarrollo en el contexto de Project Fúgu 🐡 que este codelab apenas podría cubrir la superficie.

Para obtener más información, o simplemente para obtener más información, sigue nuestras publicaciones en nuestro sitio, web.dev.

Página de destino de la sección &ldquo;Capabilities&rdquo del sitio web.dev.

Pero esto no es todo. En el caso de las actualizaciones que aún no se publicaron, puede acceder a nuestro rastreador de la API de Fugu con vínculos a todas las propuestas que se enviaron, están en prueba de origen o de desarrollo, todas las propuestas en las que se inició el trabajo y todo lo que se está considerando, pero que aún no se ha iniciado.

Sitio web de seguimiento de la API de Fugu

Este codelab fue escrito por Thomas Steiner (@tomayac), será un placer responder tus preguntas y esperamos leer tus comentarios. Agradecimientos especiales a Hemanth H.M (@GNUmanth), Christian Liebel (@christianliebel), Sven May (@Svenmay), Lars Knudsen (@larsgk) y Jackie Han (@hanguokai) por su ayuda.