Poznaj nowe i najbliższe możliwości przeglądarki PWA: Fugu With Love

1. Zanim zaczniesz

Progresywne aplikacje internetowe (PWA) to typ oprogramowania instalowanego w internecie przy użyciu popularnych technologii internetowych, takich jak HTML, CSS i JavaScript. Są one przeznaczone do każdej platformy, która używa standardowej przeglądarki.

W tym ćwiczeniu zaczniesz od bazowej aplikacji PWA, a potem poznasz nowe możliwości przeglądarki, które z pewnością udoskonalą proces PWA 🦸.

Wiele z nich jest wciąż rozwijanych i nadal jest ustandaryzowanych, więc czasami korzystanie z nich wymaga ustawienia flag przeglądarki.

Wymagania wstępne

Uczestnicy tego ćwiczenia z programowania powinni znać nowoczesny JavaScript, który jest szczególnie obiecujący i asynchroniczny/oczekujący. Nie wszystkie kroki ćwiczeń z programowania są obsługiwane na wszystkich platformach, więc warto sprawdzić, czy masz pod ręką dodatkowe urządzenia, na przykład telefon z Androidem czy laptop z innym systemem operacyjnym niż urządzenie, na którym edytujesz kod. Zamiast prawdziwych urządzeń możesz wykorzystać symulatory, takie jak symulator Androida czy usługi online, takie jak BrowserStack, które umożliwiają testowanie z dotychczasowego urządzenia. Możesz też pominąć dowolny krok – nie są one od siebie zależne.

Co stworzysz

Będziesz stworzyć aplikację internetową z pocztówką i dowiesz się, w jaki sposób nowe i nadchodzące funkcje przeglądarki mogą ulepszyć aplikację w taki sposób, aby działała w niektórych przeglądarkach (ale będzie przydatna we wszystkich nowoczesnych przeglądarkach).

Dowiesz się, jak dodać funkcje pomocy, takie jak dostęp do systemu plików, dostęp do schowka systemowego, pobieranie kontaktów, okresowa synchronizacja w tle, blokada uśpienia ekranu, funkcje udostępniania i inne.

Po ukończeniu ćwiczeń będziesz wiedzieć, jak stopniowo rozbudowywać aplikacje internetowe za pomocą nowych funkcji w przeglądarce, nie ograniczając obciążenia pobierania przez podzbiór użytkowników korzystających z niezgodnych przeglądarek i, co najważniejsze, bez wykluczania ich z aplikacji.

Czego potrzebujesz

Obecnie w pełni obsługiwane przeglądarki:

Zalecamy korzystanie z konkretnego kanału deweloperskiego.

2. Projekt Fugu

Progresywne aplikacje internetowe (PWA) zostały opracowane i udoskonalone za pomocą nowoczesnych interfejsów API, aby zwiększać możliwości, niezawodność i możliwość instalacji aplikacji, a także docierać do użytkowników internetu w dowolnym miejscu na świecie przy użyciu dowolnego typu urządzeń.

Niektóre z nich zawierają bardzo przydatne funkcje, a w przypadku ich nieprawidłowej obsługi wszystko może pójść nie tak. Tak jak ryby fugu 🐡: Jeśli się przejrzysz, może to być specjał, ale pamiętaj, że może to być straszne (ale nie martw się, w tych ćwiczeniach z programowania nic się nie zepsuje).

Z tego powodu wewnętrzna nazwa kodowa projektu Web Capities (w których firmy biorące udział w programowaniu opracowują nowe interfejsy API) to „Project Fugu”.

Funkcje internetowe – już dziś – umożliwiają dużym i małym przedsiębiorstwom skupienie się na rozwiązaniach opartych tylko na przeglądarkach, które często umożliwiają szybsze wdrażanie przy niższych kosztach związanych z programowaniem w porównaniu z rozwojem konkretnej platformy.

3. Rozpocznij

Pobierz jedną z przeglądarek, a następnie ustaw następujący znacznik czasu działania 🚩, przechodząc do about://flags, który działa zarówno w Chrome, jak i w Edge.

  • #enable-experimental-web-platform-features

Po włączeniu tej funkcji uruchom ponownie przeglądarkę.

Użyjesz platformy Glitch, ponieważ jest to miejsce, w którym możesz hostować swoją aplikację PWA oraz że ma ona odpowiedni edytor. Glitch obsługuje też importowanie i eksportowanie danych do GitHuba, więc nie trzeba stosować blokady dostawcy. Otwórz fugu-paint.glitch.me, aby wypróbować aplikację. To podstawowa aplikacja do rysowania 🎨, którą możesz poprawić podczas ćwiczeń z programowania.

Fugu Greetings bazowa aplikacja PWA z wielkim odbitką na płótnie namalowaną słowem “Google” namalowaną na tym tle.

Po zakończeniu odtwarzania aplikacji remiksuj ją, aby utworzyć jej kopię, którą możesz edytować. Adres URL Twojego remiksu będzie wyglądać na przykład tak: glitch.com/editmyaccount/bouncy-candytuft ("bouncy-candytuft" będzie inny). Ten remiks jest dostępny bezpośrednio na całym świecie. Aby zapisać swoją pracę, zaloguj się na istniejące konto lub utwórz nowe w Glitch. Aby zobaczyć swoją aplikację, kliknij przycisk „🕶 Show"”, a adres URL hostowanej aplikacji będzie wyglądać mniej więcej tak: unckcy-candytuft.glitch.me (zwróć uwagę, że .me zamiast .com to domena najwyższego poziomu).

Teraz możesz edytować aplikację i udoskonalić ją. Za każdym razem, gdy wprowadzisz zmiany, aplikacja załaduje się ponownie, a zmiany będą widoczne bezpośrednio.

IDE Glitch przedstawiający edytowanie dokumentu HTML.

Najlepiej wykonać poniższe zadania w odpowiedniej kolejności, ale jak wspomnieliśmy powyżej, zawsze możesz pominąć ten krok, jeśli nie masz dostępu do zgodnego urządzenia. Pamiętaj, że każde zadanie jest oznaczone jako 🐟, niegroźną rybą słodkowodną lub 🐡, ostrożnym &rybą fugu, co wskazuje na eksperymentalną lub niepraktyczną funkcję.

Sprawdź w konsoli programisty, czy interfejs API jest obsługiwany przez bieżące urządzenie. Używamy też Glitch, aby łatwo sprawdzić tę samą aplikację na różnych urządzeniach, na przykład na telefonie komórkowym i komputerze.

Zgodność interfejsu API zapisana w konsoli deweloperskiej.

4. 🐟 Dodaj obsługę interfejsu Web Share API

Tworzenie niesamowitych rysunków jest nudne, jeśli nikt ich nie doceni. Dodaj funkcję, która umożliwia użytkownikom udostępnianie ich rysunków całemu światu w postaci kartek z życzeniami.

Web Share API obsługuje udostępnianie plików. Jak być może wiesz, File jest szczególnym rodzajem plików Blob. Dlatego w pliku o nazwie share.mjs zaimportuj przycisk udostępniania i funkcję ułatwień dostępu toBlob(), która konwertuje zawartość obszaru roboczego na obiekt blob, a potem dodaj funkcję udostępniania zgodnie z poniższym kodem.

Jeśli przycisk został zaimplementowany, ale nie widzisz tego przycisku, oznacza to, że Twoja przeglądarka nie wdroży interfejsu Web Share API.

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. 🐟 Dodaj obsługę interfejsu Web Share Target API

Teraz użytkownicy mogą udostępniać karty powitalne utworzone w aplikacji, ale możesz też zezwolić im na udostępnianie obrazów w aplikacji i przekształcanie ich w karty powitalne. Możesz do tego użyć interfejsu Web Share Target API.

W pliku manifestu aplikacji internetowej musisz poinformować aplikację, jakie rodzaje plików możesz akceptować i jaki ma być adres URL otwierany w przypadku udostępnienia jednego lub kilku plików. Widać to w poniższym fragmencie pliku manifest.webmanifest.

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

Skrypt service worker obsługuje wtedy otrzymane pliki. Adres URL ./share-target/ nie istnieje. Aplikacja działa po prostu w obrębie modułu obsługi fetch i przekierowuje żądanie do głównego adresu URL, dodając parametr zapytania ?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 */

  /* ... */
});

Po załadowaniu aplikacji sprawdza, czy jest ustawiony ten parametr zapytania, a jeśli tak, pobiera udostępniony obraz na płótnie i usuwa go z pamięci podręcznej. Wszystko, co dzieje się w 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');
  }
};

Ta funkcja jest następnie używana podczas inicjowania aplikacji.

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

6. 🐟 Dodaj obsługę importowania obrazów

Rysowanie wszystkiego od zera jest trudne. Dodaj funkcję, która umożliwia użytkownikom przesyłanie lokalnych obrazów z urządzeń do aplikacji.

Najpierw przeczytaj o funkcji Canvas drawImage(). Zapoznaj się też z elementem <​input
type=file>
.

Dzięki tej wiedzy możesz edytować plik o nazwie import_image_legacy.mjs i dodać następujący fragment. W górnej części pliku możesz importować przycisk importowania i korzystać z funkcji ułatwień dostępu drawBlob(), która umożliwia rysowanie obiektu blob w obszarze roboczym.

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. 🐟 Dodaj obsługę eksportu obrazów

W jaki sposób użytkownik zapisze na urządzeniu plik utworzony w aplikacji? Tradycyjnie było to możliwe za pomocą elementu <​a
download>
.

W pliku export_image_legacy.mjs dodaj zawartość podaną poniżej. Zaimportuj przycisk eksportu i funkcję toBlob(), która konwertuje zawartość obszaru roboczego na obiekt 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. 🐟 Dodaj obsługę interfejsu File System Access API

Udostępnianie nie jest dobrym pomysłem, ale użytkownicy prawdopodobnie będą chcieli zapisywać najlepsze wyniki na swoich urządzeniach. Dodawanie funkcji, która umożliwia użytkownikom zapisywanie (i ponowne otwieranie) rysunków.

Wcześniej przy importowaniu plików stosowana była starsza metoda importowania plików (<​input type=file>) i starsza metoda (<​a download>). Teraz interfejs File System Access API będzie działać lepiej.

Ten interfejs API umożliwia otwieranie i zapisywanie plików w systemie plików systemu operacyjnego. Zmodyfikuj oba te pliki (import_image.mjs i export_image.mjs), dodając treść poniżej. Aby wczytać te pliki, usuń emotikony 🐡 z pliku script.mjs.

Zastąp ten wiersz:

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

...w tym wierszu:

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

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

Za 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. 🐟 Dodaj obsługę interfejsów API selektora kontaktów

Użytkownicy mogą chcieć dodać wiadomość do swojej kartki pocztowej i skontaktować się z kimś osobiście. Dodaj funkcję, która umożliwia użytkownikom wybranie 1 lub wielu kontaktów lokalnych i dodanie im nazw do wiadomości udostępnionej.

Na urządzeniu z Androidem lub iOS interfejs Contact Picker API pozwala wybrać kontakty z aplikacji menedżera kontaktów na urządzeniu i zwrócić je do aplikacji. Edytuj plik contacts.mjs i dodaj poniższy kod.

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. 🐟 Dodaj obsługę interfejsu API asynchronicznego schowka

Użytkownicy mogą chcieć wkleić zdjęcie z innej aplikacji do swojej aplikacji lub skopiować rysunek z innej aplikacji. Dodaj funkcję, która umożliwi użytkownikom kopiowanie i wklejanie obrazów do aplikacji i z niej. Asynchroniczny interfejs API schowka obsługuje obrazy w formacie PNG, więc możesz teraz odczytywać i zapisywać dane obrazów w schowku.

Znajdź plik clipboard.mjs i dodaj:

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. 🐟 Dodaj obsługę interfejsu API Badging

Gdy użytkownicy zainstalują aplikację, na ekranie głównym pojawi się ikona. Możesz jej użyć do przedstawienia zabawnych informacji, takich jak liczba pędzli wykonanych podczas danego rysunku.

Dodaj funkcję, która liczy plakietkę za każdym razem, gdy użytkownik wykona nowy gest pędzla. Za pomocą interfejsu Badging API możesz ustawić ikonę liczbową na ikonie aplikacji. Możesz ją aktualizować za każdym razem, gdy wystąpi zdarzenie pointerdown (czyli gdy wystąpią pędzlem), a także wyczyścić logo po wyczyszczeniu obszaru roboczego.

Umieść ten kod w pliku 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. 🐟 Dodaj obsługę interfejsu Wake Lock API

Czasami użytkownicy potrzebują chwili na patrzenie na rysunek przez wystarczająco długi czas, by znaleźć inspirację. Dodaj funkcję, która uniemożliwia wybudzanie ekranu i zapobiega uruchamianiu wygaszacza ekranu. Interfejs Screen Wake Lock API zapobiega zasypianiu użytkownika. Blokada uśpienia jest zwalniana automatycznie w momencie wystąpienia zmiany widoczności określonej przez funkcję Widoczność strony. Z tego powodu blokada uśpienia musi zostać ponownie ustawiona, gdy strona znowu stanie się widoczna.

Znajdź plik wake_lock.mjs i dodaj zawartość widoczną poniżej. Aby sprawdzić, czy to działa, skonfiguruj wygaszacz ekranu po upływie minuty.

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. 🐟 Dodaj okresową obsługę interfejsu API synchronizacji w tle

Zaczynanie od pustej przestrzeni może być nudne. Inicjuj obraz z użyciem okresowego interfejsu API synchronizacji w tle, aby inicjować użytkowników każdego dnia z nowym obrazem, na przykład Codziennym zdjęciem fugu (Rozmycie).

Wymaga to 2 plików: pliku periodic_background_sync.mjs, który rejestruje okresową synchronizację w tle, oraz drugiego pliku image_of_the_day.mjs, który służy do pobierania obrazu dnia.

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

Za 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. 🐟 Dodaj obsługę interfejsów API wykrywania kształtu

Czasami użytkownicy mogą korzystać z rysunków lub używanych obrazów tła, takich jak kody kreskowe. Wyodrębnianie tych informacji umożliwia interfejs wykrywania kształtu, a w szczególności interfejs Barcode Detection API. Dodaj funkcję, która próbuje wykrywać kody kreskowe Twoich użytkowników. Znajdź plik barcode.mjs i dodaj zawartość widoczną poniżej. Aby przetestować tę funkcję, wczytaj obraz z kodem kreskowym lub wklej go w obszarze roboczym. Przykładowy kod kreskowy możesz skopiować z wyszukiwarki kodów 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. 🐡 Dodaj obsługę interfejsu API wykrywania bezczynności

Jeśli wyobraź sobie, że Twoja aplikacja działa w trybie przypominającym kiosk, przydatną funkcją jest zresetowanie obszaru roboczego po określonym czasie nieaktywności. Interfejs Idle Detection API umożliwia wykrywanie, gdy użytkownik nie korzysta już z urządzenia.

Znajdź plik idle_detection.mjs i wklej zawartość widoczną poniżej.

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. 🐡 Dodaj obsługę interfejsu API obsługi plików

Co się stanie, jeśli użytkownicy będą mogli jedynie powielić pliki graficzne, a Twoja aplikacja się pojawi? Możesz to zrobić za pomocą interfejsu File Handling API.

Musisz zarejestrować PWA jako moduł obsługi plików. Dzieje się tak w manifeście aplikacji internetowej, co pokazuje to w poniższym fragmencie pliku manifest.webmanifest. (Jest to już część manifestu, więc nie musisz dodawać go samodzielnie).

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

Aby obsługiwać otwarte pliki, dodaj poniższy kod do pliku 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. Gratulacje

🎉 Brawo, udało Ci się!

W projekcie Project Fugu opracowuje się mnóstwo ekscytujących interfejsów API dla przeglądarek 🐡, więc to ćwiczenie z programowania ledwo odbije się na powierzchni.

Więcej informacji znajdziesz na naszej stronie web.dev.

Strona docelowa sekcji &ldquo;Capability&rdquo; witryny web.dev.

To jeszcze nie wszystko. Aby uzyskać aktualne informacje, które nie zostały jeszcze opublikowane, skorzystaj z naszego modułu do śledzenia interfejsu API Facebooka. Znajdziesz w nim linki do wszystkich wysłanych ofert pakietowych, wersji próbnej lub wersji deweloperskiej, wszystkich ofert pakietowych, w ramach których rozpoczęła się praca, i wszystkich elementów rozpatrywanych, które nie zostały jeszcze rozpoczęte.

Strona monitorowania interfejsu Fugu API

Ćwiczenia z programowania zostały napisane przez Thomasa Steinera (@tomayac). Chętnie odpowiem na Twoje pytania. Czekam na Twoją opinię. Specjalne podziękowania należą się do Hemantha H.M (@GNUmanth), Christian Liebel (@christianliebel), Sven May (@Svenmay), Lars Knudsen (@larsgk) i Jackie Han (@hanguokai), którzy pomogli w tworzeniu tego ćwiczenia z programowania.