Esplora le funzionalità del browser nuove e in arrivo per la tua PWA: da Fugu con amore

1. Prima di iniziare

Le Progressive Web App (PWA) sono un tipo di software applicativo distribuito tramite il web, creato utilizzando tecnologie web comuni, tra cui HTML, CSS e JavaScript. Sono progettati per funzionare su qualsiasi piattaforma che utilizzi un browser conforme agli standard.

In questo codelab, inizierai con una PWA di base e poi esplorerai nuove funzionalità del browser che alla fine daranno alla tua PWA dei superpoteri 🦸.

Molte di queste nuove funzionalità del browser sono in fase di implementazione e standardizzazione, quindi a volte dovrai impostare i flag del browser per utilizzarle.

Prerequisiti

Per questo codelab, devi avere familiarità con JavaScript moderno, in particolare con le promesse e async/await. Poiché non tutti i passaggi del codelab sono supportati su tutte le piattaforme, è utile per i test avere a portata di mano dispositivi aggiuntivi, ad esempio uno smartphone Android o un laptop che utilizza un sistema operativo diverso da quello del dispositivo su cui modifichi il codice. In alternativa ai dispositivi reali, puoi provare a utilizzare simulatori come il simulatore Android o servizi online come BrowserStack, che ti consentono di eseguire test dal tuo dispositivo attuale. Altrimenti, puoi anche saltare qualsiasi passaggio, non dipendono l'uno dall'altro.

Cosa creerai

Creerai un'app web per biglietti di auguri e scoprirai come le funzionalità nuove e future del browser possono migliorare la tua app in modo che offra un'esperienza avanzata su determinati browser (ma rimanga utile su tutti i browser moderni).

Scoprirai come aggiungere funzionalità di supporto, come l'accesso al file system, l'accesso agli appunti di sistema, il recupero dei contatti, la sincronizzazione periodica in background, il blocco della riattivazione dello schermo, le funzionalità di condivisione e altro ancora.

Dopo aver completato il codelab, avrai una solida comprensione di come migliorare progressivamente le tue app web con le nuove funzionalità del browser, senza imporre un carico di download al sottoinsieme di utenti che utilizzano browser incompatibili e, soprattutto, senza escluderli dalla tua app.

Che cosa ti serve

Al momento, i browser completamente supportati sono:

Ti consigliamo di utilizzare il canale di sviluppo specifico.

2. Project Fugu

Le app web progressive (PWA) sono create e migliorate con API moderne per offrire funzionalità, affidabilità e installabilità avanzate, raggiungendo chiunque sul web, in qualsiasi parte del mondo e utilizzando qualsiasi tipo di dispositivo.

Alcune di queste API sono molto potenti e, se gestite in modo errato, possono causare problemi. Proprio come il pesce palla 🐡: se lo tagli nel modo giusto, è una prelibatezza, ma se lo tagli nel modo sbagliato, può essere letale (ma non preoccuparti, in questo codelab non si rompe nulla).

Per questo motivo, il nome in codice interno del progetto Web Capabilities (in cui le aziende coinvolte stanno sviluppando queste nuove API) è Project Fugu.

Le funzionalità web, già oggi, consentono alle aziende grandi e piccole di basarsi su soluzioni pure basate su browser, spesso consentendo un deployment più rapido con costi di sviluppo inferiori rispetto all'utilizzo di un percorso specifico per la piattaforma.

3. Inizia

Scarica uno dei due browser, quindi imposta il seguente flag di runtime 🚩 andando su about://flags, che funziona sia in Chrome che in Edge:

  • #enable-experimental-web-platform-features

Dopo averlo attivato, riavvia il browser.

Utilizzerai la piattaforma Glitch, perché ti consente di ospitare la tua PWA e perché ha un editor decente. Glitch supporta anche l'importazione e l'esportazione su GitHub, quindi non c'è alcun vincolo al fornitore. Vai a fugu-paint.glitch.me per provare l'applicazione. È un'app di disegno di base 🎨 che migliorerai durante il codelab.

PWA di base Fugu Greetings con una tela di grandi dimensioni su cui è dipinta la parola "Google".

Dopo aver provato l'applicazione, remixa l'app per creare una tua copia che puoi modificare. L'URL del tuo remix avrà un aspetto simile a glitch.com/edit/#!/bouncy-candytuft ("bouncy-candytuft" sarà diverso per te). Questo remix è accessibile direttamente in tutto il mondo. Accedi al tuo account esistente o creane uno nuovo su Glitch per salvare il tuo lavoro. Puoi visualizzare la tua app facendo clic sul pulsante "🕶 Mostra" e l'URL dell'app ospitata sarà simile a bouncy-candytuft.glitch.me (nota .me anziché .com come dominio di primo livello).

Ora puoi modificare e migliorare la tua app. Ogni volta che apporti modifiche, l'app viene ricaricata e le modifiche sono visibili direttamente.

Glitch IDE che mostra la modifica di un documento HTML.

Le seguenti attività devono essere completate in ordine, ma come indicato in precedenza, puoi sempre saltare un passaggio se non hai accesso a un dispositivo compatibile. Ricorda che ogni attività è contrassegnata con 🐟, un pesce d'acqua dolce innocuo, o 🐡, un pesce palla da maneggiare con cura, per indicare il livello di sperimentazione di una funzionalità.

Controlla la console in DevTools per vedere se un'API è supportata sul dispositivo attuale. Utilizziamo anche Glitch per consentirti di controllare facilmente la stessa app su dispositivi diversi, ad esempio sul tuo cellulare e sul tuo computer.

Compatibilità dell'API registrata nella console di DevTools.

4. 🐟 Aggiunta del supporto dell'API Web Share

Creare i disegni più belli è noioso se non c'è nessuno che li apprezzi. Aggiungi una funzionalità che consenta agli utenti di condividere i propri disegni con il mondo sotto forma di biglietti di auguri.

L'API Web Share supporta la condivisione di file e, come forse ricorderai, un File è solo un tipo specifico di Blob. Pertanto, nel file denominato share.mjs, importa il pulsante di condivisione e una funzione di convenienza toBlob() che converte i contenuti di un canvas in un blob e aggiungi la funzionalità di condivisione come da codice riportato di seguito.

Se hai implementato questa funzionalità, ma non vedi il pulsante, è perché il tuo browser non implementa l'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. 🐟 Aggiunta del supporto dell'API Web Share Target

Ora gli utenti possono condividere biglietti di auguri creati utilizzando l'app, ma puoi anche consentire loro di condividere immagini nella tua app e trasformarle in biglietti di auguri. A questo scopo, puoi utilizzare l'API Web Share Target.

Nel file manifest dell'app web, devi indicare all'app quali tipi di file puoi accettare e quale URL deve chiamare il browser quando vengono condivisi uno o più file. Lo dimostra l'estratto del file manifest.webmanifest riportato di seguito.

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

Il service worker gestisce quindi i file ricevuti. L'URL ./share-target/ non esiste, l'app agisce su di esso nel gestore fetch e reindirizza la richiesta all'URL principale aggiungendo un parametro di query ?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 */

  /* ... */
});

Quando l'app viene caricata, controlla se questo parametro di query è impostato e, in caso affermativo, disegna l'immagine condivisa sul canvas ed elimina l'immagine dalla cache. Tutto questo avviene in 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');
  }
};

Questa funzione viene poi utilizzata quando l'app viene inizializzata.

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

6. 🐟 Aggiunta del supporto per l'importazione di immagini

Disegnare tutto da zero è difficile. Aggiungi una funzionalità che consenta agli utenti di caricare un'immagine locale dal proprio dispositivo nell'app.

Innanzitutto, leggi la funzione drawImage() del canvas. Poi, acquisisci familiarità con l'elemento <​input
type=file>
.

Con queste informazioni, puoi modificare il file denominato import_image_legacy.mjs e aggiungere il seguente snippet. Nella parte superiore del file che importi, trovi il pulsante di importazione e una funzione di utilità drawBlob() che ti consente di disegnare una macchia sulla 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. 🐟 Aggiungi il supporto per l'esportazione di immagini

In che modo l'utente salverà sul dispositivo un file creato nell'app? Tradizionalmente, questo risultato è stato ottenuto con un elemento <​a
download>
.

Nel file export_image_legacy.mjs aggiungi i contenuti come segue. Importa il pulsante di esportazione e una funzione di convenienza toBlob() che converte i contenuti del canvas in 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. 🐟 Aggiungi il supporto dell'API File System Access

La condivisione è importante, ma gli utenti probabilmente vorranno salvare i loro lavori migliori sui propri dispositivi. Aggiungi una funzionalità che consenta agli utenti di salvare (e riaprire) i propri disegni.

In precedenza, utilizzavi un approccio legacy <​input type=file> per l'importazione dei file e un approccio legacy <​a download> per l'esportazione dei file. Ora utilizzerai l'API File System Access per migliorare l'esperienza.

Questa API consente l'apertura e il salvataggio di file dal file system del sistema operativo. Modifica i due file, import_image.mjs e export_image.mjs, aggiungendo i contenuti riportati di seguito. Per caricare questi file, rimuovi le emoji 🐡 da script.mjs.

Sostituisci questa riga:

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

...con questa riga:

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

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

In 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. 🐟 Aggiunta del supporto dell'API Contact Picker

I tuoi utenti potrebbero voler aggiungere un messaggio al biglietto di auguri e rivolgersi a qualcuno personalmente. Aggiungi una funzionalità che consenta agli utenti di selezionare uno o più contatti locali e aggiungere i loro nomi al messaggio di condivisione.

Su un dispositivo Android o iOS, l'API Contact Picker ti consente di scegliere i contatti dall'app di gestione dei contatti del dispositivo e restituirli all'applicazione. Modifica il file contacts.mjs e aggiungi il codice riportato di seguito.

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. 🐟 Aggiunta del supporto dell'API Async Clipboard

I tuoi utenti potrebbero voler incollare un'immagine da un'altra app nella tua app o copiare un disegno dalla tua app in un'altra app. Aggiungi una funzionalità che consenta agli utenti di copiare e incollare immagini nella tua app e da questa. L'API Async Clipboard supporta le immagini PNG, quindi ora puoi leggere e scrivere dati delle immagini negli appunti.

Trova il file clipboard.mjs e aggiungi quanto segue:

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. 🐟 Aggiunta del supporto dell'API Badging

Quando gli utenti installano la tua app, nella schermata Home viene visualizzata un'icona. Puoi utilizzare questa icona per trasmettere informazioni divertenti, ad esempio il numero di pennellate necessarie per un determinato disegno.

Aggiungi una funzionalità che incrementa il badge ogni volta che l'utente esegue un nuovo tratto di pennello. L'API Badging consente di impostare un badge numerico sull'icona dell'app. Puoi aggiornare il badge ogni volta che si verifica un evento pointerdown (ovvero quando si verifica un tratto di pennello) e reimpostarlo quando la tela viene cancellata.

Inserisci il codice riportato di seguito nel file 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. 🐟 Aggiunta del supporto dell'API Screen Wake Lock

A volte, gli utenti potrebbero aver bisogno di qualche istante per fissare un disegno, il tempo necessario per trovare l'ispirazione. Aggiungi una funzionalità che mantiene lo schermo attivo e impedisce l'attivazione del salvaschermo. L'API Screen Wake Lock impedisce che lo schermo dell'utente si spenga. Il blocco di riattivazione viene rilasciato automaticamente quando si verifica un evento di modifica della visibilità definito da Visibilità pagina. Pertanto, il blocco di riattivazione deve essere riacquisito quando la pagina torna visibile.

Individua il file wake_lock.mjs e aggiungi i contenuti riportati di seguito. Per verificare se funziona, configura il salvaschermo in modo che venga visualizzato dopo 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. 🐟 Aggiunta del supporto dell'API Periodic Background Sync

Iniziare con una tela vuota può essere noioso. Puoi utilizzare l'API Periodic Background Sync per inizializzare il canvas dei tuoi utenti con una nuova immagine ogni giorno, ad esempio la foto giornaliera del pesce palla di Unsplash.

Sono necessari due file: un file periodic_background_sync.mjs che registra la sincronizzazione periodica in background e un altro file image_of_the_day.mjs che gestisce il download dell'immagine del giorno.

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

In 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. 🐟 Aggiunta del supporto dell'API Shape Detection

A volte i disegni degli utenti o le immagini di sfondo utilizzate possono contenere informazioni utili come, ad esempio, codici a barre. L'API Shape Detection e, in particolare, l'API Barcode Detection, ti consentono di estrarre queste informazioni. Aggiungi una funzionalità che tenta di rilevare i codici a barre dai disegni degli utenti. Individua il file barcode.mjs e aggiungi i contenuti riportati di seguito. Per testare questa funzionalità, carica o incolla un'immagine con un codice a barre nel canvas. Puoi copiare un codice a barre di esempio da una ricerca di immagini di codici 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. 🐡 Aggiunta del supporto dell'API Idle Detection

Se immagini che la tua app venga eseguita in una configurazione simile a un chiosco, una funzionalità utile sarebbe quella di reimpostare il canvas dopo un determinato periodo di inattività. L'API Idle Detection consente di rilevare quando un utente non interagisce più con il proprio dispositivo.

Trova il file idle_detection.mjs e incolla i contenuti riportati di seguito.

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. 🐡 Aggiungi il supporto dell'API File Handling

Cosa succederebbe se i tuoi utenti potessero fare doppio clic su un file immagine e la tua app si aprisse? L'API File Handling ti consente di farlo.

Dovrai registrare la PWA come gestore di file per le immagini. Ciò avviene nel file manifest dell'applicazione web, come mostrato nell'estratto riportato di seguito del file manifest.webmanifest. (Questo fa già parte del manifest, non è necessario aggiungerlo manualmente.)

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

Per gestire effettivamente i file aperti, aggiungi il codice seguente al file 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. Complimenti

🎉 Evviva, ce l'hai fatta!

Nel contesto del progetto Fugu 🐡 vengono sviluppate così tante entusiasmanti API del browser che questo codelab è riuscito a grattare appena la superficie.

Per approfondire o semplicemente per saperne di più, segui le nostre pubblicazioni sul nostro sito web.dev.

Pagina di destinazione della sezione &quot;Funzionalità&quot; del sito web.dev.

Ma non finisce qui. Per gli aggiornamenti non ancora pubblicati, puoi accedere al nostro tracker API Fugu con link a tutte le proposte che sono state implementate, che sono in fase di prova di origine o di prova per sviluppatori, a tutte le proposte per cui è iniziato il lavoro e a tutto ciò che è in fase di valutazione, ma non ancora iniziato.

Sito web di monitoraggio dell&#39;API Fugu

Questo codelab è stato scritto da Thomas Steiner (@tomayac). Sarò felice di rispondere alle tue domande e non vedo l'ora di leggere il tuo feedback. Un ringraziamento speciale a Hemanth H.M (@GNUmanth), Christian Liebel (@christianliebel), Sven May (@Svenmay), Lars Knudsen (@larsgk) e Jackie Han (@hanguokai) che hanno contribuito a dare forma a questo codelab.